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:
@@ -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
|
||||
});
|
||||
}
|
||||
|
||||
@@ -22,3 +22,34 @@ export function createSystemPlaylistsStatusQuery() {
|
||||
staleTime: 10_000
|
||||
});
|
||||
}
|
||||
|
||||
// Self-service profile / password / API-token endpoints (U3) ---------------
|
||||
|
||||
export type MyProfile = {
|
||||
id: string;
|
||||
username: string;
|
||||
display_name: string | null;
|
||||
email: string | null;
|
||||
is_admin: boolean;
|
||||
};
|
||||
|
||||
export type APITokenResponse = { api_token: string };
|
||||
|
||||
export async function changePassword(currentPassword: string, newPassword: string): Promise<void> {
|
||||
await api.put('/api/me/password', {
|
||||
current_password: currentPassword,
|
||||
new_password: newPassword,
|
||||
});
|
||||
}
|
||||
|
||||
export async function updateProfile(input: { display_name?: string; email?: string }): Promise<MyProfile> {
|
||||
return api.put<MyProfile>('/api/me/profile', input);
|
||||
}
|
||||
|
||||
export async function getAPIToken(): Promise<APITokenResponse> {
|
||||
return api.get<APITokenResponse>('/api/me/api-token');
|
||||
}
|
||||
|
||||
export async function regenerateAPIToken(): Promise<APITokenResponse> {
|
||||
return api.post<APITokenResponse>('/api/me/api-token', {});
|
||||
}
|
||||
|
||||
@@ -40,6 +40,7 @@ export const qk = {
|
||||
coverProviders: () => ['coverProviders'] as const,
|
||||
adminUsers: () => ['adminUsers'] as const,
|
||||
adminInvites: () => ['adminInvites'] as const,
|
||||
smtpConfig: () => ['smtpConfig'] as const,
|
||||
suggestions: (limit?: number) =>
|
||||
['suggestions', { limit: limit ?? 12 }] as const,
|
||||
home: () => ['home'] as const,
|
||||
|
||||
Reference in New Issue
Block a user