From 502d48da99d427d16d43189916c99db9bb98edd2 Mon Sep 17 00:00:00 2001 From: SysAdmin Date: Tue, 24 Feb 2026 14:48:02 +0000 Subject: [PATCH] app update --- BlazorApp/Components/Pages/Developers.razor | 121 +++++++++++++++--- BlazorApp/Models/DeveloperApplication.cs | 37 +++++- BlazorApp/Models/SkillCatalog.cs | 37 ++++++ .../Services/DeveloperApplicationService.cs | 86 ++++++++++++- BlazorApp/wwwroot/developers-styles.css | 113 ++++++++++++++++ 5 files changed, 361 insertions(+), 33 deletions(-) create mode 100644 BlazorApp/Models/SkillCatalog.cs diff --git a/BlazorApp/Components/Pages/Developers.razor b/BlazorApp/Components/Pages/Developers.razor index 875e74d..aac98be 100644 --- a/BlazorApp/Components/Pages/Developers.razor +++ b/BlazorApp/Components/Pages/Developers.razor @@ -172,30 +172,101 @@ - -
-

What You Bring

-

Tell us about your skills and what you'd bring to the team.

+ + @if (_application.Role == ApplicationRole.Tester) + { +
+

About Your Experience

+

Help us understand your background — there are no wrong answers.

- @if (_application.Role == ApplicationRole.Developer) - { -
- - - Languages, frameworks, tools, and any open-source contributions +
+ +
+ @for (int i = 1; i <= 5; i++) + { + var rating = i; + @(rating <= (_internetHover > 0 ? _internetHover : (_application.InternetUnderstanding ?? 0)) ? "\u2605" : "\u2606") + } +
+
+ Beginner + Expert +
+
- } -
- - - +
+ +
+ @for (int i = 1; i <= 5; i++) + { + var rating = i; + @(rating <= (_testingHover > 0 ? _testingHover : (_application.EnjoysTesting ?? 0)) ? "\u2605" : "\u2606") + } +
+
+ Not really + Love it +
+ +
+ +
+ + +
-
+ } + else + { +
+

Your Skills

+

Select your experience level and the technologies you work with.

+ +
+ +
+ @foreach (var range in SkillCatalog.ExperienceRanges) + { + + } +
+ +
+ +
+ + @foreach (var category in SkillCatalog.SkillCategories) + { +
@category.Key
+
+ @foreach (var skill in category.Value) + { + + } +
+ } + +
+ +
+ + +
+
+ }
@@ -251,6 +322,8 @@ private bool _submitted; private string? _resultMessage; private string? _errorMessage; + private int _internetHover; + private int _testingHover; private UsernameCheckState _usernameCheckState = UsernameCheckState.None; private string? _usernameFormatError; @@ -329,6 +402,14 @@ _application.Platforms.Add(platform); } + private void ToggleSkill(string skill) + { + if (_application.SelectedSkills.Contains(skill)) + _application.SelectedSkills.Remove(skill); + else + _application.SelectedSkills.Add(skill); + } + private void OnUsernameInput(ChangeEventArgs e) { var username = e.Value?.ToString() ?? ""; diff --git a/BlazorApp/Models/DeveloperApplication.cs b/BlazorApp/Models/DeveloperApplication.cs index db5d99a..bbe9db9 100644 --- a/BlazorApp/Models/DeveloperApplication.cs +++ b/BlazorApp/Models/DeveloperApplication.cs @@ -2,7 +2,7 @@ using System.ComponentModel.DataAnnotations; namespace SilverLabs.Website.Models; -public class DeveloperApplication +public class DeveloperApplication : IValidatableObject { [Required(ErrorMessage = "Full name is required")] [StringLength(100, MinimumLength = 2)] @@ -25,8 +25,6 @@ public class DeveloperApplication [MinLength(1, ErrorMessage = "Please select at least one platform")] public List Platforms { get; set; } = new(); - public string? Skills { get; set; } - [Required(ErrorMessage = "Password is required")] [StringLength(100, MinimumLength = 8, ErrorMessage = "Password must be at least 8 characters")] public string Password { get; set; } = string.Empty; @@ -35,9 +33,36 @@ public class DeveloperApplication [Compare("Password", ErrorMessage = "Passwords do not match")] public string ConfirmPassword { get; set; } = string.Empty; - [Required(ErrorMessage = "Please tell us how you'll contribute")] - [StringLength(2000, MinimumLength = 20, ErrorMessage = "Please write at least 20 characters")] - public string Motivation { get; set; } = string.Empty; + // Tester-specific + public int? InternetUnderstanding { get; set; } + public int? EnjoysTesting { get; set; } + + // Developer-specific + public string? ExperienceRange { get; set; } + public List SelectedSkills { get; set; } = new(); + + // Shared optional + public string? AdditionalNotes { get; set; } + + public IEnumerable Validate(ValidationContext validationContext) + { + if (Role == ApplicationRole.Tester) + { + if (!InternetUnderstanding.HasValue || InternetUnderstanding < 1 || InternetUnderstanding > 5) + yield return new ValidationResult("Please rate your internet understanding", new[] { nameof(InternetUnderstanding) }); + + if (!EnjoysTesting.HasValue || EnjoysTesting < 1 || EnjoysTesting > 5) + yield return new ValidationResult("Please rate your enthusiasm for testing", new[] { nameof(EnjoysTesting) }); + } + else if (Role == ApplicationRole.Developer) + { + if (string.IsNullOrWhiteSpace(ExperienceRange)) + yield return new ValidationResult("Please select your experience level", new[] { nameof(ExperienceRange) }); + + if (SelectedSkills.Count == 0) + yield return new ValidationResult("Please select at least one skill", new[] { nameof(SelectedSkills) }); + } + } } public enum ApplicationRole diff --git a/BlazorApp/Models/SkillCatalog.cs b/BlazorApp/Models/SkillCatalog.cs new file mode 100644 index 0000000..c82356e --- /dev/null +++ b/BlazorApp/Models/SkillCatalog.cs @@ -0,0 +1,37 @@ +namespace SilverLabs.Website.Models; + +public static class SkillCatalog +{ + public static readonly string[] ExperienceRanges = + { + "< 1 year", + "1-3 years", + "3-5 years", + "5-10 years", + "10+ years" + }; + + public static readonly Dictionary SkillCategories = new() + { + ["Languages"] = new[] + { + "C#", "Python", "JavaScript", "TypeScript", "Go", "Rust", + "Java", "C/C++", "PHP", "Ruby", "Swift", "Kotlin" + }, + ["Frameworks"] = new[] + { + ".NET/Blazor", "React", "Angular", "Vue", "Django", + "Node.js", "Next.js", "Svelte", "Spring Boot", "Flask" + }, + ["Infrastructure"] = new[] + { + "Docker", "Kubernetes", "Linux", "Nginx", "Terraform", + "CI/CD", "AWS", "Azure", "Proxmox" + }, + ["Databases"] = new[] + { + "PostgreSQL", "MySQL", "SQLite", "MongoDB", "Redis", + "SQL Server", "Elasticsearch" + } + }; +} diff --git a/BlazorApp/Services/DeveloperApplicationService.cs b/BlazorApp/Services/DeveloperApplicationService.cs index 298b646..660fece 100644 --- a/BlazorApp/Services/DeveloperApplicationService.cs +++ b/BlazorApp/Services/DeveloperApplicationService.cs @@ -121,8 +121,8 @@ public class DeveloperApplicationService timezone = application.Timezone, appliedRole = application.Role.ToString(), platforms = application.Platforms, - skills = application.Skills ?? "", - motivation = application.Motivation, + skills = SerializeAssessment(application), + motivation = GenerateMotivationSummary(application), status = 0, // Pending silverDeskProvisioned = true }; @@ -158,6 +158,64 @@ public class DeveloperApplicationService } } + /// + /// Serializes structured assessment data as JSON for the Skills column. + /// + internal static string SerializeAssessment(DeveloperApplication app) + { + object data; + + if (app.Role == ApplicationRole.Tester) + { + data = new + { + type = "tester", + internetUnderstanding = app.InternetUnderstanding ?? 0, + enjoysTesting = app.EnjoysTesting ?? 0, + additionalNotes = app.AdditionalNotes ?? "" + }; + } + else + { + data = new + { + type = "developer", + experienceRange = app.ExperienceRange ?? "", + selectedSkills = app.SelectedSkills, + additionalNotes = app.AdditionalNotes ?? "" + }; + } + + return JsonSerializer.Serialize(data, new JsonSerializerOptions + { + PropertyNamingPolicy = JsonNamingPolicy.CamelCase + }); + } + + /// + /// Generates a human-readable summary for the Motivation field (backward compatibility). + /// + internal static string GenerateMotivationSummary(DeveloperApplication app) + { + if (app.Role == ApplicationRole.Tester) + { + var summary = $"Internet understanding: {app.InternetUnderstanding}/5, Testing enthusiasm: {app.EnjoysTesting}/5"; + if (!string.IsNullOrWhiteSpace(app.AdditionalNotes)) + summary += $". Notes: {app.AdditionalNotes.Trim()}"; + return summary; + } + else + { + var skills = app.SelectedSkills.Count > 0 + ? string.Join(", ", app.SelectedSkills) + : "None selected"; + var summary = $"{app.ExperienceRange} experience. Skills: {skills}"; + if (!string.IsNullOrWhiteSpace(app.AdditionalNotes)) + summary += $". Notes: {app.AdditionalNotes.Trim()}"; + return summary; + } + } + private static string ParseRegistrationError(string errorBody) { try @@ -198,15 +256,29 @@ public class DeveloperApplicationService sb.AppendLine($"**Platforms:** {string.Join(", ", app.Platforms)}"); sb.AppendLine(); - if (app.Role == ApplicationRole.Developer && !string.IsNullOrWhiteSpace(app.Skills)) + if (app.Role == ApplicationRole.Tester) { - sb.AppendLine("**Skills & Experience:**"); - sb.AppendLine(app.Skills); + sb.AppendLine("### Assessment"); + sb.AppendLine($"- Internet understanding: {"*".PadLeft(app.InternetUnderstanding ?? 0, '*')}{new string('-', 5 - (app.InternetUnderstanding ?? 0))} ({app.InternetUnderstanding}/5)"); + sb.AppendLine($"- Testing enthusiasm: {"*".PadLeft(app.EnjoysTesting ?? 0, '*')}{new string('-', 5 - (app.EnjoysTesting ?? 0))} ({app.EnjoysTesting}/5)"); + } + else + { + sb.AppendLine("### Skills & Experience"); + sb.AppendLine($"**Experience:** {app.ExperienceRange}"); sb.AppendLine(); + if (app.SelectedSkills.Count > 0) + { + sb.AppendLine($"**Technologies:** {string.Join(", ", app.SelectedSkills)}"); + } } - sb.AppendLine("**Motivation:**"); - sb.AppendLine(app.Motivation); + if (!string.IsNullOrWhiteSpace(app.AdditionalNotes)) + { + sb.AppendLine(); + sb.AppendLine("### Additional Notes"); + sb.AppendLine(app.AdditionalNotes.Trim()); + } return sb.ToString(); } diff --git a/BlazorApp/wwwroot/developers-styles.css b/BlazorApp/wwwroot/developers-styles.css index 4772a24..64d1257 100644 --- a/BlazorApp/wwwroot/developers-styles.css +++ b/BlazorApp/wwwroot/developers-styles.css @@ -412,6 +412,119 @@ color: #00B8D4; } +/* Star Rating */ +.star-rating { + display: flex; + gap: 0.35rem; + margin-top: 0.4rem; +} + +.star-rating-star { + font-size: 1.8rem; + cursor: pointer; + color: rgba(255, 255, 255, 0.2); + transition: color 0.15s ease, transform 0.15s ease; + user-select: none; + line-height: 1; +} + +.star-rating-star:hover { + transform: scale(1.15); +} + +.star-rating-star.star-filled { + color: #4DD0E1; + text-shadow: 0 0 8px rgba(77, 208, 225, 0.4); +} + +.rating-labels { + display: flex; + justify-content: space-between; + margin-top: 0.3rem; + font-size: 0.75rem; + color: rgba(255, 255, 255, 0.35); + max-width: 170px; +} + +/* Experience Selector */ +.experience-selector { + display: flex; + flex-wrap: wrap; + gap: 0.5rem; + margin-top: 0.4rem; +} + +.exp-btn { + padding: 0.45rem 1.1rem; + background: rgba(255, 255, 255, 0.05); + border: 1px solid rgba(255, 255, 255, 0.15); + border-radius: 100px; + color: rgba(255, 255, 255, 0.7); + font-size: 0.88rem; + font-family: inherit; + cursor: pointer; + transition: all 0.2s ease; + user-select: none; +} + +.exp-btn:hover { + border-color: rgba(77, 208, 225, 0.4); + background: rgba(77, 208, 225, 0.06); +} + +.exp-btn.exp-active { + background: rgba(77, 208, 225, 0.12); + border-color: #4DD0E1; + color: #4DD0E1; + box-shadow: 0 0 12px rgba(77, 208, 225, 0.15); +} + +/* Skill Bubbles */ +.skill-category-label { + font-size: 0.72rem; + font-weight: 700; + text-transform: uppercase; + letter-spacing: 0.08em; + color: rgba(255, 255, 255, 0.4); + margin-top: 1rem; + margin-bottom: 0.4rem; +} + +.skill-category-label:first-of-type { + margin-top: 0.5rem; +} + +.skill-bubbles { + display: flex; + flex-wrap: wrap; + gap: 0.4rem; +} + +.skill-bubble { + padding: 0.35rem 0.85rem; + background: rgba(255, 255, 255, 0.04); + border: 1px solid rgba(255, 255, 255, 0.12); + border-radius: 100px; + color: rgba(255, 255, 255, 0.6); + font-size: 0.82rem; + font-family: inherit; + cursor: pointer; + transition: all 0.2s ease; + user-select: none; +} + +.skill-bubble:hover { + border-color: rgba(77, 208, 225, 0.35); + background: rgba(77, 208, 225, 0.05); + color: rgba(255, 255, 255, 0.8); +} + +.skill-bubble.skill-active { + background: rgba(77, 208, 225, 0.12); + border-color: #4DD0E1; + color: #4DD0E1; +} + /* Responsive */ @media (max-width: 768px) { .dev-header h1 {