AI Implementation feature(823): Landing Page and Login/Register Modal (#11)

This commit was merged in pull request #11.
This commit is contained in:
2026-07-21 18:34:45 +00:00
parent 8476e4a59f
commit 685a8bca84
51 changed files with 2354 additions and 226 deletions
@@ -0,0 +1,180 @@
<section class="landing">
<header class="landing-header">
@if (logo()) {
<img class="landing-logo" [src]="logo()" alt="" data-testid="landing-logo" />
}
<h1 class="landing-title" data-testid="landing-title">{{ pageTitle() }}</h1>
</header>
<article class="landing-welcome" [innerHTML]="welcomeHtml" data-testid="landing-welcome"></article>
<button
type="button"
class="landing-login-btn"
(click)="openLogin()"
data-testid="landing-open-login"
>
Login
</button>
@if (landing.posts().length > 0) {
<section class="landing-blog" data-testid="landing-blog">
@for (post of landing.posts(); track post.id) {
<article class="landing-blog-item">
<h2 class="landing-blog-title">{{ post.title }}</h2>
<time class="landing-blog-date">{{ post.publishedAt | date: 'medium' }}</time>
<div class="landing-blog-body" [innerHTML]="markdown.render(post.bodyMd)"></div>
</article>
}
</section>
} @else if (!landing.loading()) {
<p class="landing-blog-empty">No announcements yet.</p>
}
</section>
@if (mode() !== 'closed') {
<div
class="modal-overlay"
(click)="closeModal()"
data-testid="landing-modal-overlay"
>
<div
class="modal"
role="dialog"
aria-modal="true"
[attr.aria-labelledby]="mode() === 'login' ? 'login-title' : 'register-title'"
(click)="$event.stopPropagation()"
>
<button
type="button"
class="modal-close"
(click)="closeModal()"
aria-label="Close"
data-testid="landing-modal-close"
>×</button>
@if (mode() === 'login') {
<h2 id="login-title">Sign in</h2>
<form [formGroup]="loginForm" (ngSubmit)="submitLogin()" novalidate>
<label class="field">
<span>Username</span>
<input
type="text"
autocomplete="username"
formControlName="username"
data-testid="login-username"
/>
</label>
<label class="field">
<span>Password</span>
<input
type="password"
autocomplete="current-password"
formControlName="password"
data-testid="login-password"
/>
</label>
@if (loginError()) {
<small class="field-error" data-testid="login-field-error">{{ loginError() }}</small>
}
@if (errorText()) {
<div
class="server-error"
role="alert"
[class.server-error-warn]="errorCode() === 'RATE_LIMITED'"
data-testid="login-server-error"
>
<span>{{ errorText() }}</span>
@if (retryAfterSeconds() !== null) {
<span class="server-error-hint">
({{ retryAfterSeconds() }}s remaining)
</span>
}
</div>
}
<button
type="submit"
class="primary"
[disabled]="submitting()"
data-testid="login-submit"
>
{{ submitting() ? 'Signing in...' : 'Login' }}
</button>
@if (registrationsEnabled()) {
<p class="modal-link-row" data-testid="login-switch">
No account?
<button type="button" class="link" (click)="switchToRegister()" data-testid="login-switch-btn">
Register here
</button>
</p>
}
</form>
}
@if (mode() === 'register') {
<h2 id="register-title">Create your account</h2>
<form [formGroup]="registerForm" (ngSubmit)="submitRegister()" novalidate>
<label class="field">
<span>Username</span>
<input
type="text"
autocomplete="username"
formControlName="username"
data-testid="register-username"
/>
</label>
<label class="field">
<span>Password</span>
<input
type="password"
autocomplete="new-password"
formControlName="password"
data-testid="register-password"
/>
</label>
<label class="field">
<span>Confirm password</span>
<input
type="password"
autocomplete="new-password"
formControlName="passwordConfirm"
data-testid="register-confirm"
/>
</label>
@if (registerError()) {
<small class="field-error" data-testid="register-field-error">{{ registerError() }}</small>
}
@if (errorText()) {
<div
class="server-error"
role="alert"
[class.server-error-warn]="errorCode() === 'RATE_LIMITED' || errorCode() === 'REGISTRATIONS_DISABLED'"
data-testid="register-server-error"
>
<span>{{ errorText() }}</span>
@if (retryAfterSeconds() !== null) {
<span class="server-error-hint">
({{ retryAfterSeconds() }}s remaining)
</span>
}
</div>
}
<button
type="submit"
class="primary"
[disabled]="submitting()"
data-testid="register-submit"
>
{{ submitting() ? 'Registering...' : 'Register' }}
</button>
<p class="modal-link-row" data-testid="register-switch">
Already have an account?
<button type="button" class="link" (click)="switchToLogin()" data-testid="register-switch-btn">
Login here
</button>
</p>
</form>
}
</div>
</div>
}