Files
onebox/ui/src/app/features/login/login.component.ts

164 lines
5.4 KiB
TypeScript
Raw Normal View History

2025-11-24 19:52:35 +00:00
import { Component, inject, signal } from '@angular/core';
2025-11-18 00:03:24 +00:00
import { Router } from '@angular/router';
2025-11-24 19:52:35 +00:00
import { FormsModule } from '@angular/forms';
2025-11-18 00:03:24 +00:00
import { AuthService } from '../../core/services/auth.service';
2025-11-24 19:52:35 +00:00
import { ThemeService } from '../../core/services/theme.service';
import {
CardComponent,
CardHeaderComponent,
CardTitleComponent,
CardDescriptionComponent,
CardContentComponent,
CardFooterComponent,
} from '../../ui/card/card.component';
import { ButtonComponent } from '../../ui/button/button.component';
import { InputComponent } from '../../ui/input/input.component';
import { LabelComponent } from '../../ui/label/label.component';
import { AlertComponent, AlertDescriptionComponent } from '../../ui/alert/alert.component';
2025-11-18 00:03:24 +00:00
@Component({
selector: 'app-login',
standalone: true,
2025-11-24 19:52:35 +00:00
imports: [
FormsModule,
CardComponent,
CardHeaderComponent,
CardTitleComponent,
CardDescriptionComponent,
CardContentComponent,
CardFooterComponent,
ButtonComponent,
InputComponent,
LabelComponent,
AlertComponent,
AlertDescriptionComponent,
],
2025-11-18 00:03:24 +00:00
template: `
2025-11-24 19:52:35 +00:00
<div class="min-h-screen flex items-center justify-center bg-background p-4">
<div class="absolute top-4 right-4">
<button
uiButton
variant="ghost"
size="icon"
(click)="theme.toggle()"
>
@if (theme.resolvedTheme() === 'dark') {
<svg class="h-4 w-4" fill="none" viewBox="0 0 24 24" stroke="currentColor" stroke-width="2">
<path stroke-linecap="round" stroke-linejoin="round" d="M12 3v1m0 16v1m9-9h-1M4 12H3m15.364 6.364l-.707-.707M6.343 6.343l-.707-.707m12.728 0l-.707.707M6.343 17.657l-.707.707M16 12a4 4 0 11-8 0 4 4 0 018 0z" />
</svg>
} @else {
<svg class="h-4 w-4" fill="none" viewBox="0 0 24 24" stroke="currentColor" stroke-width="2">
<path stroke-linecap="round" stroke-linejoin="round" d="M20.354 15.354A9 9 0 018.646 3.646 9.003 9.003 0 0012 21a9.003 9.003 0 008.354-5.646z" />
</svg>
}
</button>
</div>
<ui-card class="w-full max-w-md">
<ui-card-header class="text-center">
<div class="mx-auto mb-4">
<svg class="h-12 w-12 text-primary" viewBox="0 0 24 24" fill="currentColor">
<path d="M21 16V8a2 2 0 0 0-1-1.73l-7-4a2 2 0 0 0-2 0l-7 4A2 2 0 0 0 3 8v8a2 2 0 0 0 1 1.73l7 4a2 2 0 0 0 2 0l7-4A2 2 0 0 0 21 16z"/>
</svg>
</div>
<ui-card-title>Welcome to Onebox</ui-card-title>
<ui-card-description>Enter your credentials to sign in</ui-card-description>
</ui-card-header>
<form (ngSubmit)="onSubmit()">
<ui-card-content class="space-y-4">
@if (error()) {
<ui-alert variant="destructive">
<ui-alert-description>{{ error() }}</ui-alert-description>
</ui-alert>
}
<div class="space-y-2">
<label uiLabel for="username">Username</label>
2025-11-18 00:03:24 +00:00
<input
2025-11-24 19:52:35 +00:00
uiInput
2025-11-18 00:03:24 +00:00
id="username"
type="text"
[(ngModel)]="username"
2025-11-24 19:52:35 +00:00
name="username"
placeholder="Enter username"
autocomplete="username"
2025-11-18 00:03:24 +00:00
required
/>
</div>
2025-11-24 19:52:35 +00:00
<div class="space-y-2">
<label uiLabel for="password">Password</label>
2025-11-18 00:03:24 +00:00
<input
2025-11-24 19:52:35 +00:00
uiInput
2025-11-18 00:03:24 +00:00
id="password"
type="password"
[(ngModel)]="password"
2025-11-24 19:52:35 +00:00
name="password"
placeholder="Enter password"
autocomplete="current-password"
2025-11-18 00:03:24 +00:00
required
/>
</div>
2025-11-24 19:52:35 +00:00
</ui-card-content>
2025-11-18 00:03:24 +00:00
2025-11-24 19:52:35 +00:00
<ui-card-footer>
2025-11-18 00:03:24 +00:00
<button
2025-11-24 19:52:35 +00:00
uiButton
2025-11-18 00:03:24 +00:00
type="submit"
2025-11-24 19:52:35 +00:00
class="w-full"
[disabled]="loading()"
2025-11-18 00:03:24 +00:00
>
2025-11-24 19:52:35 +00:00
@if (loading()) {
<svg class="animate-spin h-4 w-4 mr-2" fill="none" viewBox="0 0 24 24">
<circle class="opacity-25" cx="12" cy="12" r="10" stroke="currentColor" stroke-width="4"></circle>
<path class="opacity-75" fill="currentColor" d="M4 12a8 8 0 018-8V0C5.373 0 0 5.373 0 12h4zm2 5.291A7.962 7.962 0 014 12H0c0 3.042 1.135 5.824 3 7.938l3-2.647z"></path>
</svg>
Signing in...
} @else {
Sign in
}
2025-11-18 00:03:24 +00:00
</button>
2025-11-24 19:52:35 +00:00
</ui-card-footer>
2025-11-18 00:03:24 +00:00
</form>
2025-11-24 19:52:35 +00:00
<div class="px-6 pb-6">
<p class="text-xs text-center text-muted-foreground">
Default credentials: admin / admin
</p>
</div>
</ui-card>
2025-11-18 00:03:24 +00:00
</div>
`,
})
export class LoginComponent {
2025-11-24 19:52:35 +00:00
private auth = inject(AuthService);
2025-11-18 00:03:24 +00:00
private router = inject(Router);
2025-11-24 19:52:35 +00:00
theme = inject(ThemeService);
2025-11-18 00:03:24 +00:00
username = '';
password = '';
2025-11-24 19:52:35 +00:00
loading = signal(false);
error = signal<string | null>(null);
async onSubmit(): Promise<void> {
if (!this.username || !this.password) {
this.error.set('Please enter username and password');
return;
}
this.loading.set(true);
this.error.set(null);
const result = await this.auth.login(this.username, this.password);
2025-11-18 00:03:24 +00:00
2025-11-24 19:52:35 +00:00
this.loading.set(false);
2025-11-18 00:03:24 +00:00
2025-11-24 19:52:35 +00:00
if (result.success) {
this.router.navigate(['/dashboard']);
} else {
this.error.set(result.error || 'Invalid credentials');
}
2025-11-18 00:03:24 +00:00
}
}