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)
- {
-
+ }
+ else
+ {
+
+
Your Skills
+
Select your experience level and the technologies you work with.
+
+
+
+
+
+
+
+
+
+
+ }
@@ -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 {