Files
Website/BlazorApp/Components/Pages/DeploymentConfirm.razor
SysAdmin 44e3ad94e0
All checks were successful
Build and Deploy / deploy (push) Successful in 41s
feat(developers): add service URLs and onboarding guide to provisioning reply and success page
Ticket replies now include full onboarding info (webmail, IMAP/SMTP, Mattermost, Gitea, SilverDESK URLs) instead of raw provisioning status. Confirmation success page uses clickable service links with email client config details.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-02-22 22:57:51 +00:00

358 lines
12 KiB
Plaintext

@page "/developers/confirm/{Token}"
@inject HttpClient Http
@inject NavigationManager Navigation
@inject IConfiguration Configuration
@rendermode InteractiveServer
<PageTitle>Activate Your Accounts - 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>Activate Your Accounts</h1>
<p class="dev-subtitle">Confirm your identity to provision your SilverLabs developer accounts.</p>
</div>
@if (_loading)
{
<div class="dev-section" style="text-align: center; padding: 3rem;">
<div class="btn-spinner" style="width: 32px; height: 32px; margin: 0 auto 1rem;"></div>
<p style="color: rgba(255,255,255,0.6);">Loading deployment details...</p>
</div>
}
else if (_invalidToken)
{
<div class="dev-section" style="text-align: center; padding: 3rem;">
<div class="confirm-icon confirm-icon-error">
<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>
<line x1="15" y1="9" x2="9" y2="15"></line>
<line x1="9" y1="9" x2="15" y2="15"></line>
</svg>
</div>
<h2 style="color: #f87171; margin-bottom: 0.75rem;">Invalid or Expired Link</h2>
<p style="color: rgba(255,255,255,0.6); max-width: 400px; margin: 0 auto;">This confirmation link is no longer valid. It may have expired or already been used. Please contact an administrator if you need a new link.</p>
</div>
}
else if (_provisioned)
{
<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>Accounts Activated</h2>
<p>@_resultMessage</p>
<div class="confirm-services">
<a href="https://mail.silverlined.uk" target="_blank" class="confirm-service-item confirm-service-link">
<strong>Email</strong>
<span>@(_username)@@silverlabs.uk</span>
</a>
<a href="https://ops.silverlined.uk" target="_blank" class="confirm-service-item confirm-service-link">
<strong>Mattermost</strong>
<span>Team chat & collaboration</span>
</a>
<a href="https://git.silverlabs.uk" target="_blank" class="confirm-service-item confirm-service-link">
<strong>Gitea</strong>
<span>Source code repositories</span>
</a>
<a href="https://silverdesk.silverlabs.uk" target="_blank" class="confirm-service-item confirm-service-link">
<strong>SilverDESK</strong>
<span>Support & tickets</span>
</a>
</div>
<div class="confirm-email-config">
<strong>Email Client Setup</strong>
<div class="confirm-email-detail"><span>IMAP:</span> mail.silverlined.uk:993 (SSL)</div>
<div class="confirm-email-detail"><span>SMTP:</span> mail.silverlined.uk:465 (SSL)</div>
</div>
<p class="dev-account-note">All accounts use the same password you just entered.</p>
<div class="dev-success-actions">
<a href="https://silverdesk.silverlabs.uk" target="_blank" class="dev-btn dev-btn-primary">Go to SilverDESK</a>
<a href="/" class="dev-btn dev-btn-secondary">Back to Home</a>
</div>
</div>
}
else
{
<div class="dev-section">
<h2 class="dev-section-title">Confirm Your Identity</h2>
<p class="dev-section-desc">Enter your SilverDESK password to activate your accounts. All services will use this same password.</p>
<div class="confirm-user-info">
<div class="confirm-user-field">
<span class="confirm-label">Username</span>
<span class="confirm-value">@_username</span>
</div>
<div class="confirm-user-field">
<span class="confirm-label">Email</span>
<span class="confirm-value">@_email</span>
</div>
</div>
<div class="form-group" style="margin-top: 1.5rem; max-width: 400px;">
<label for="password">SilverDESK Password</label>
<input id="password" type="password" class="form-input" @bind="_password"
@bind:event="oninput" @onkeydown="HandleKeyDown" placeholder="Enter your password" />
</div>
@if (!string.IsNullOrEmpty(_errorMessage))
{
<div class="dev-error" style="margin-top: 1rem;">@_errorMessage</div>
}
<div style="margin-top: 1.5rem;">
<button class="dev-btn dev-btn-primary" disabled="@_submitting" @onclick="HandleConfirm">
@if (_submitting)
{
<span class="btn-spinner"></span>
<span>Activating accounts...</span>
}
else
{
<span>Activate My Accounts</span>
}
</button>
</div>
</div>
}
<a href="/" class="back-link">← Back to SilverLabs Home</a>
</div>
</div>
<style>
.confirm-icon-error {
width: 72px;
height: 72px;
margin: 0 auto 1.5rem;
background: rgba(248, 113, 113, 0.15);
border-radius: 50%;
display: flex;
align-items: center;
justify-content: center;
}
.confirm-icon-error svg {
width: 36px;
height: 36px;
stroke: #f87171;
}
.confirm-user-info {
display: flex;
gap: 2rem;
margin-top: 1rem;
padding: 1rem;
background: rgba(0, 0, 0, 0.2);
border-radius: 10px;
border: 1px solid rgba(255, 255, 255, 0.08);
}
.confirm-user-field {
display: flex;
flex-direction: column;
gap: 0.25rem;
}
.confirm-label {
font-size: 0.78rem;
color: rgba(255, 255, 255, 0.4);
text-transform: uppercase;
letter-spacing: 0.05em;
}
.confirm-value {
font-size: 1rem;
color: #4DD0E1;
font-weight: 600;
}
.confirm-services {
display: grid;
grid-template-columns: repeat(4, 1fr);
gap: 1rem;
margin: 1.5rem auto;
max-width: 600px;
}
.confirm-service-item {
display: flex;
flex-direction: column;
padding: 0.75rem;
background: rgba(255, 255, 255, 0.04);
border-radius: 10px;
text-align: center;
}
.confirm-service-link {
text-decoration: none;
border: 1px solid rgba(77, 208, 225, 0.15);
transition: background 0.2s, border-color 0.2s;
}
.confirm-service-link:hover {
background: rgba(77, 208, 225, 0.08);
border-color: rgba(77, 208, 225, 0.35);
}
.confirm-service-item strong {
color: #4DD0E1;
font-size: 0.95rem;
margin-bottom: 0.2rem;
}
.confirm-service-item span {
color: rgba(255, 255, 255, 0.55);
font-size: 0.8rem;
}
.confirm-email-config {
margin: 1rem auto;
max-width: 360px;
padding: 0.75rem 1rem;
background: rgba(0, 0, 0, 0.2);
border-radius: 8px;
border: 1px solid rgba(255, 255, 255, 0.06);
font-size: 0.82rem;
}
.confirm-email-config strong {
display: block;
color: rgba(255, 255, 255, 0.5);
font-size: 0.72rem;
text-transform: uppercase;
letter-spacing: 0.05em;
margin-bottom: 0.4rem;
}
.confirm-email-detail {
color: rgba(255, 255, 255, 0.6);
padding: 0.15rem 0;
}
.confirm-email-detail span {
color: rgba(255, 255, 255, 0.4);
font-size: 0.8rem;
}
@@media (max-width: 768px) {
.confirm-user-info {
flex-direction: column;
gap: 0.75rem;
}
.confirm-services {
grid-template-columns: repeat(2, 1fr);
}
}
</style>
@code {
[Parameter] public string Token { get; set; } = "";
private bool _loading = true;
private bool _invalidToken;
private bool _provisioned;
private bool _submitting;
private string? _username;
private string? _email;
private string? _password;
private string? _errorMessage;
private string? _resultMessage;
protected override async Task OnAfterRenderAsync(bool firstRender)
{
if (!firstRender) return;
try
{
var baseUrl = Navigation.BaseUri.TrimEnd('/');
using var client = new HttpClient();
var response = await client.GetAsync($"{baseUrl}/api/developers/deployment-info/{Token}");
if (response.IsSuccessStatusCode)
{
var data = await response.Content.ReadFromJsonAsync<DeploymentInfo>();
_username = data?.Username;
_email = data?.Email;
}
else
{
_invalidToken = true;
}
}
catch
{
_invalidToken = true;
}
_loading = false;
StateHasChanged();
}
private async Task HandleKeyDown(KeyboardEventArgs e)
{
if (e.Key == "Enter" && !_submitting && !string.IsNullOrEmpty(_password))
await HandleConfirm();
}
private async Task HandleConfirm()
{
if (string.IsNullOrEmpty(_password))
{
_errorMessage = "Please enter your password.";
return;
}
_errorMessage = null;
_submitting = true;
StateHasChanged();
try
{
var baseUrl = Navigation.BaseUri.TrimEnd('/');
using var client = new HttpClient();
var payload = new { token = Token, password = _password };
var response = await client.PostAsJsonAsync($"{baseUrl}/api/developers/confirm-deployment", payload);
if (response.IsSuccessStatusCode)
{
var result = await response.Content.ReadFromJsonAsync<ProvisionResult>();
_resultMessage = result?.Message ?? "Accounts activated successfully.";
_provisioned = true;
}
else if ((int)response.StatusCode == 401)
{
_errorMessage = "Incorrect password. Please enter the password you created when you applied.";
}
else if ((int)response.StatusCode == 404)
{
_invalidToken = true;
}
else
{
_errorMessage = "Something went wrong. Please try again or contact an administrator.";
}
}
catch
{
_errorMessage = "Connection error. Please try again.";
}
finally
{
_submitting = false;
StateHasChanged();
}
}
private record DeploymentInfo(string Username, string Email, string FullName, DateTime ExpiresAt);
private record ProvisionResult(bool Success, string Message);
}