feat(developers): add developer program signup with SilverDESK integration

Add developer application page with form submission that creates tickets
in SilverDESK. Includes provisioning service scaffolding for Mattermost,
Mailcow, and Gitea account creation. Fixes API key header casing
(X-API-Key) and ticket payload to match SilverDESK's CreateTicketDto
contract.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
2026-02-21 20:51:36 +00:00
parent d2780e9295
commit 21c07adf54
11 changed files with 1056 additions and 3 deletions

View File

@@ -7,6 +7,7 @@
<base href="/" />
<link rel="stylesheet" href="@Assets["styles.css"]" />
<link rel="stylesheet" href="@Assets["sdk-styles.css"]" />
<link rel="stylesheet" href="@Assets["developers-styles.css"]" />
<ImportMap />
<link rel="icon" type="image/png" href="favicon.png" />
<HeadOutlet />

View File

@@ -0,0 +1,244 @@
@page "/developers"
@using SilverLabs.Website.Models
@using SilverLabs.Website.Services
@inject DeveloperApplicationService ApplicationService
@rendermode InteractiveServer
<PageTitle>Join the Team - SilverLabs</PageTitle>
<div class="main-content visible">
<header class="header">
<img src="logo.png" alt="SilverLabs Logo" class="logo">
</header>
<div class="dev-container">
<div class="dev-header">
<h1>Join the SilverLabs Team</h1>
<p class="dev-subtitle">Help us build privacy-first infrastructure. Whether you test our products or write code, there's a place for you.</p>
</div>
@if (_submitted)
{
<div class="dev-success-panel">
<div class="success-icon">
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round">
<path d="M22 11.08V12a10 10 0 1 1-5.93-9.14"></path>
<polyline points="22 4 12 14.01 9 11.01"></polyline>
</svg>
</div>
<h2>Application Submitted</h2>
<p>@_resultMessage</p>
<a href="/" class="dev-btn dev-btn-secondary">Back to Home</a>
</div>
}
else
{
<EditForm Model="_application" OnValidSubmit="HandleSubmit" FormName="developer-application">
<DataAnnotationsValidator />
<!-- Role Selector -->
<div class="dev-section">
<h2 class="dev-section-title">Choose Your Role</h2>
<div class="role-selector">
<div class="role-card @(_application.Role == ApplicationRole.Tester ? "role-active" : "")"
@onclick="() => SelectRole(ApplicationRole.Tester)">
<div class="role-icon">
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round">
<path d="M14 2H6a2 2 0 0 0-2 2v16a2 2 0 0 0 2 2h12a2 2 0 0 0 2-2V8z"></path>
<polyline points="14 2 14 8 20 8"></polyline>
<line x1="16" y1="13" x2="8" y2="13"></line>
<line x1="16" y1="17" x2="8" y2="17"></line>
<polyline points="10 9 9 9 8 9"></polyline>
</svg>
</div>
<h3>Product Tester</h3>
<p>Test our apps across devices, find bugs, and provide feedback that shapes our products.</p>
</div>
<div class="role-card @(_application.Role == ApplicationRole.Developer ? "role-active" : "")"
@onclick="() => SelectRole(ApplicationRole.Developer)">
<div class="role-icon">
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round">
<polyline points="16 18 22 12 16 6"></polyline>
<polyline points="8 6 2 12 8 18"></polyline>
</svg>
</div>
<h3>Developer</h3>
<p>Contribute code, build modules, and help architect privacy-first solutions.</p>
</div>
</div>
<ValidationMessage For="() => _application.Role" />
</div>
<!-- Personal Details -->
<div class="dev-section">
<h2 class="dev-section-title">About You</h2>
<div class="form-grid">
<div class="form-group">
<label for="fullName">Full Name</label>
<InputText id="fullName" @bind-Value="_application.FullName" class="form-input" placeholder="Jane Doe" />
<ValidationMessage For="() => _application.FullName" />
</div>
<div class="form-group">
<label for="email">Email Address</label>
<InputText id="email" @bind-Value="_application.Email" class="form-input" placeholder="jane@example.com" />
<ValidationMessage For="() => _application.Email" />
</div>
<div class="form-group">
<label for="username">Desired Username</label>
<InputText id="username" @bind-Value="_application.DesiredUsername" class="form-input" placeholder="janedoe" />
<span class="form-hint">This will be your handle across SilverLabs services</span>
<ValidationMessage For="() => _application.DesiredUsername" />
</div>
<div class="form-group">
<label for="timezone">Location / Timezone</label>
<InputText id="timezone" @bind-Value="_application.Timezone" class="form-input" placeholder="e.g. Europe/London, US/Eastern" />
<ValidationMessage For="() => _application.Timezone" />
</div>
</div>
</div>
<!-- Platforms -->
<div class="dev-section">
<h2 class="dev-section-title">Devices & Platforms</h2>
<p class="dev-section-desc">Which platforms do you use or have access to?</p>
<div class="platform-grid">
@foreach (var platform in _availablePlatforms)
{
var isChecked = _application.Platforms.Contains(platform);
<label class="platform-chip @(isChecked ? "platform-active" : "")">
<input type="checkbox" checked="@isChecked"
@onchange="() => TogglePlatform(platform)" />
<span>@platform</span>
</label>
}
</div>
<ValidationMessage For="() => _application.Platforms" />
</div>
<!-- Developer-only: Skills -->
@if (_application.Role == ApplicationRole.Developer)
{
<div class="dev-section dev-section-fade-in">
<h2 class="dev-section-title">Skills & Experience</h2>
<p class="dev-section-desc">Tell us about your technical background — languages, frameworks, and any open-source contributions.</p>
<div class="form-group">
<InputTextArea id="skills" @bind-Value="_application.Skills" class="form-input form-textarea"
placeholder="e.g. C#/.NET 5 years, Blazor, PostgreSQL, Docker, contributed to..." rows="5" />
</div>
</div>
}
<!-- Motivation -->
<div class="dev-section">
<h2 class="dev-section-title">Why SilverLabs?</h2>
<p class="dev-section-desc">What draws you to privacy-first development? What do you hope to contribute?</p>
<div class="form-group">
<InputTextArea id="motivation" @bind-Value="_application.Motivation" class="form-input form-textarea"
placeholder="Tell us what motivates you..." rows="5" />
<ValidationMessage For="() => _application.Motivation" />
</div>
</div>
<!-- What You Get -->
<div class="dev-section dev-perks">
<h2 class="dev-section-title">What You'll Get</h2>
<div class="perks-grid">
<div class="perk-item">
<strong>@@@("username")@@silverlabs.uk</strong>
<span>Your own SilverLabs email</span>
</div>
<div class="perk-item">
<strong>SilverDESK</strong>
<span>Project management & issue tracking</span>
</div>
<div class="perk-item">
<strong>Mattermost</strong>
<span>Team chat & collaboration</span>
</div>
<div class="perk-item">
<strong>Gitea Access</strong>
<span>Source code repositories</span>
</div>
</div>
</div>
<!-- Submit -->
<div class="dev-submit-area">
@if (!string.IsNullOrEmpty(_errorMessage))
{
<div class="dev-error">@_errorMessage</div>
}
<button type="submit" class="dev-btn dev-btn-primary" disabled="@_submitting">
@if (_submitting)
{
<span class="btn-spinner"></span>
<span>Submitting...</span>
}
else
{
<span>Submit Application</span>
}
</button>
</div>
</EditForm>
}
<a href="/" class="back-link">← Back to SilverLabs Home</a>
</div>
</div>
@code {
private DeveloperApplication _application = new() { Role = ApplicationRole.Tester };
private bool _submitting;
private bool _submitted;
private string? _resultMessage;
private string? _errorMessage;
private readonly string[] _availablePlatforms = { "Windows", "macOS", "Linux", "Android", "iOS", "Other" };
private void SelectRole(ApplicationRole role)
{
_application.Role = role;
}
private void TogglePlatform(string platform)
{
if (_application.Platforms.Contains(platform))
_application.Platforms.Remove(platform);
else
_application.Platforms.Add(platform);
}
private async Task HandleSubmit()
{
_errorMessage = null;
_submitting = true;
try
{
var (success, message) = await ApplicationService.SubmitApplicationAsync(_application);
if (success)
{
_resultMessage = message;
_submitted = true;
}
else
{
_errorMessage = message;
}
}
catch
{
_errorMessage = "An unexpected error occurred. Please try again later.";
}
finally
{
_submitting = false;
}
}
}

View File

@@ -12,7 +12,7 @@
<p class="subtitle">Your Innovation Gateway</p>
<div class="gateway-grid">
<a href="https://helpdesk.silverlabs.uk" class="gateway-card">
<a href="https://silverdesk.silverlabs.uk" class="gateway-card">
<div class="card-icon">
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round">
<circle cx="12" cy="12" r="10"></circle>
@@ -57,6 +57,19 @@
<h2 class="card-title">SDK</h2>
<p class="card-description">Developer Resources</p>
</a>
<a href="/developers" class="gateway-card">
<div class="card-icon">
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round">
<path d="M17 21v-2a4 4 0 0 0-4-4H5a4 4 0 0 0-4 4v2"></path>
<circle cx="9" cy="7" r="4"></circle>
<path d="M23 21v-2a4 4 0 0 0-3-3.87"></path>
<path d="M16 3.13a4 4 0 0 1 0 7.75"></path>
</svg>
</div>
<h2 class="card-title">Developers</h2>
<p class="card-description">Join the Team</p>
</a>
</div>
</main>

View File

@@ -227,7 +227,7 @@ curl https://library.silverlabs.uk/api/modules/search?q=auth</code></div>
<h2>💬 Support</h2>
<p>Need help? We're here for you:</p>
<ul>
<li><strong>Help Desk:</strong> <a href="https://helpdesk.silverlabs.uk" target="_blank" style="color: #a78bfa;">helpdesk.silverlabs.uk</a></li>
<li><strong>Help Desk:</strong> <a href="https://silverdesk.silverlabs.uk" target="_blank" style="color: #a78bfa;">silverdesk.silverlabs.uk</a></li>
<li><strong>Issues:</strong> <a href="https://gitlab.silverlabs.uk/silverlabs/silvershell/-/issues" target="_blank" style="color: #a78bfa;">GitLab Issues</a></li>
</ul>
</div>