feat: password reset UI (forgot password, reset page, admin reset button)

This commit is contained in:
2026-01-28 21:44:22 +00:00
parent 6e451e0795
commit 696e2187f4
6 changed files with 420 additions and 10 deletions

View File

@@ -255,6 +255,10 @@ class ApiClient {
await this.fetch(`/admin/invites/${id}`, { method: 'DELETE' });
}
async createPasswordReset(userId: string): Promise<{ resetUrl: string; email: string }> {
return this.fetch(`/admin/users/${userId}/reset-password`, { method: 'POST' });
}
// Invite acceptance (public - no auth)
async validateInvite(token: string): Promise<{ id: string; email: string; name: string; role: string; expiresAt: string }> {
const response = await fetch(`${AUTH_BASE}/auth/invite/${token}`);
@@ -265,6 +269,41 @@ class ApiClient {
return response.json();
}
async requestPasswordReset(email: string): Promise<{ success: boolean; message: string; resetUrl?: string }> {
const response = await fetch(`${AUTH_BASE}/auth/reset-password/request`, {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({ email }),
});
if (!response.ok) {
const error = await response.json().catch(() => ({ error: 'Request failed' }));
throw new Error(error.error || 'Request failed');
}
return response.json();
}
async validateResetToken(token: string): Promise<{ valid: boolean; email?: string }> {
const response = await fetch(`${AUTH_BASE}/auth/reset-password/${token}`);
if (!response.ok) {
const error = await response.json().catch(() => ({ error: 'Invalid token' }));
throw new Error(error.error || 'Invalid or expired reset link');
}
return response.json();
}
async resetPassword(token: string, password: string): Promise<{ success: boolean }> {
const response = await fetch(`${AUTH_BASE}/auth/reset-password/${token}`, {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({ password }),
});
if (!response.ok) {
const error = await response.json().catch(() => ({ error: 'Reset failed' }));
throw new Error(error.error || 'Failed to reset password');
}
return response.json();
}
async acceptInvite(token: string, password: string, name?: string): Promise<{ success: boolean; token?: string }> {
const response = await fetch(`${AUTH_BASE}/auth/invite/${token}/accept`, {
method: 'POST',