180 lines
5.7 KiB
HTML
180 lines
5.7 KiB
HTML
<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>
|
||
} |