import { ChangeDetectionStrategy, Component, computed, effect, inject, signal } from '@angular/core'; import { DatePipe } from '@angular/common'; import { FormsModule } from '@angular/forms'; import { MatIconModule } from '@angular/material/icon'; import { ApiService } from '../../core/api.service'; import { MailStateService } from '../../core/mail-state.service'; import { errorText, formatBytes } from '../../core/models'; import { THEMES, ThemeService } from '../../ui/theme.service'; import { ToastService } from '../../ui/toast.service'; import { MailboxesSection } from './mailboxes.section'; import { ListsSection } from './lists.section'; /** Profile name, password, storage, look. */ @Component({ selector: 'app-settings', imports: [DatePipe, FormsModule, MatIconModule, MailboxesSection, ListsSection], changeDetection: ChangeDetectionStrategy.OnPush, template: `

badge PROFILE

@if (state.profile(); as p) {
Mailbox
{{ p.email }}
Since
{{ p.createdAt | date: 'd MMMM y' }}
}

key PASSWORD

@if (passwordError()) { }

storage STORAGE

{{ used() }} of {{ quota() }}{{ percent() }}%

palette LOOK

Palette
@for (t of themes; track t.value) { }
High contrast
Black background, bright outlines
Grid motion
The moving retro floor in the background
@if (state.profile()?.admin) { }
` }) export class SettingsPage { readonly state = inject(MailStateService); readonly theme = inject(ThemeService); private api = inject(ApiService); private toasts = inject(ToastService); readonly themes = THEMES; displayName = ''; current = ''; next = ''; again = ''; readonly passwordError = signal(''); readonly showPw = signal(false); readonly used = computed(() => formatBytes(this.state.profile()?.usedBytes)); readonly quota = computed(() => formatBytes(this.state.profile()?.quotaBytes)); readonly percent = computed(() => { const p = this.state.profile(); return p && p.quotaBytes ? Math.min(100, Math.round(p.usedBytes / p.quotaBytes * 1000) / 10) : 0; }); constructor() { this.state.refreshProfile(); effect(() => { this.displayName = this.state.profile()?.displayName ?? ''; }); } label(name: string): string { return ({ INBOX: 'Inbox', Drafts: 'Drafts', Sent: 'Sent', Spam: 'Spam', Trash: 'Trash' } as Record)[name] ?? name; } saveName(): void { const name = this.displayName.trim(); if (!name) { this.toasts.error('The name cannot be empty'); return; } this.api.updateMe(name).subscribe({ next: p => { this.state.profile.set(p); this.toasts.ok('Name saved'); }, error: e => this.toasts.error(errorText(e)) }); } changePassword(): void { const problem = !this.current ? 'Enter the current password' : this.next.length < 8 ? 'The new password is shorter than 8 characters' : this.next !== this.again ? 'The new passwords do not match' : ''; this.passwordError.set(problem); if (problem) return; this.api.changePassword(this.current, this.next).subscribe({ next: () => { this.current = this.next = this.again = ''; this.toasts.ok('Password changed'); }, error: e => this.passwordError.set(errorText(e)) }); } }