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';
/** Profile name, password, storage, look. */
@Component({
selector: 'app-settings',
imports: [DatePipe, FormsModule, MatIconModule, MailboxesSection],
changeDetection: ChangeDetectionStrategy.OnPush,
template: `
badge PROFILE
@if (state.profile(); as p) {
- Mailbox
- {{ p.email }}
- Since
- {{ p.createdAt | date: 'd MMMM y' }}
}
storage STORAGE
{{ used() }} of {{ quota() }}{{ percent() }}%
@for (f of state.folders(); track f.id) {
- {{ f.system ? label(f.name) : f.name }}
{{ f.messageCount }} messages
}
palette LOOK
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 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))
});
}
}