feat(web/m7-user-mgmt): /settings expansion + /forgot-password (U3-T5a)

Lands four of the U3 frontend surfaces. Splits T5 because the
dispatch covering everything in one shot crashed the runtime
mid-execution; this is the salvageable half.

- /settings gains three cards alongside Appearance: Profile
  (display name + email), Password (current + new + confirm with
  client-side mismatch check), API Token (display + copy +
  regenerate-with-double-click-confirm). Server error codes map
  to clear toasts.
- /forgot-password — public; takes an email and always shows the
  success message regardless of whether the email is on file
  (mirrors the server's no-enumeration posture).
- Login page gets a "Forgot password?" link below the existing
  register link.
- API client functions for the four /me endpoints (changePassword,
  updateProfile, getAPIToken, regenerateAPIToken), the SMTP admin
  trio (getSMTPConfig, updateSMTPConfig, testSMTPConfig), and the
  forgot/reset auth pair (forgotPassword, resetPassword).

Tests for these surfaces + /reset-password page + admin SMTP card
land in T5b (next follow-up).
This commit is contained in:
2026-05-07 17:33:48 -04:00
parent cbe838cbe3
commit 72f46a885b
7 changed files with 337 additions and 0 deletions
+33
View File
@@ -459,3 +459,36 @@ export function createAdminInvitesQuery() {
queryFn: listInvites
});
}
// SMTP config (U3) ----------------------------------------------------------
export type SMTPConfig = {
enabled: boolean;
host: string;
port: number;
username: string;
password: string; // "***" when set, "" when unset
from_address: string;
from_name: string;
use_tls: boolean;
};
export async function getSMTPConfig(): Promise<SMTPConfig> {
return api.get<SMTPConfig>('/api/admin/smtp-config');
}
export async function updateSMTPConfig(input: SMTPConfig): Promise<void> {
await api.put('/api/admin/smtp-config', input);
}
export async function testSMTPConfig(): Promise<void> {
await api.post('/api/admin/smtp-config/test', {});
}
export function createSMTPConfigQuery() {
return createQuery({
queryKey: qk.smtpConfig(),
queryFn: getSMTPConfig,
staleTime: 60_000
});
}