feat: add forgot password UI flow

- ForgotPasswordPage: email input, sends reset request, shows success
- ResetPasswordPage: reads token from ?token= query param, password
  + confirm inputs, validates min length, shows success with login link
- LoginForm: added [FORGOT PASSWORD?] link below the form
- App.tsx: /forgot-password and /reset-password routes (public, no auth)
- auth store: requestPasswordReset + resetPassword actions
- Backend routes already existed at POST /auth/request-password-reset
  and POST /auth/reset-password
This commit is contained in:
2026-06-30 15:49:23 -04:00
parent f4437c5e1d
commit 82ddf914e4
6 changed files with 276 additions and 1 deletions
+36
View File
@@ -62,6 +62,8 @@ interface AuthState {
currentPassword: string,
newPassword: string,
) => Promise<void>;
requestPasswordReset: (email: string) => Promise<void>;
resetPassword: (token: string, newPassword: string) => Promise<void>;
getPublicProfile: (userId: string) => Promise<PublicProfile>;
listBlocks: () => Promise<BlockedUser[]>;
blockUser: (userId: string) => Promise<void>;
@@ -178,6 +180,40 @@ export const useAuthStore = create<AuthState>((set) => ({
}
},
requestPasswordReset: async (email) => {
set({ isLoading: true, error: null });
try {
await api.post("/auth/request-password-reset", { email });
set({ isLoading: false });
} catch (error) {
set({
isLoading: false,
error:
error instanceof Error
? error.message
: "Failed to request password reset",
});
throw error;
}
},
resetPassword: async (token, newPassword) => {
set({ isLoading: true, error: null });
try {
await api.post("/auth/reset-password", { token, new_password: newPassword });
set({ isLoading: false });
} catch (error) {
set({
isLoading: false,
error:
error instanceof Error
? error.message
: "Failed to reset password",
});
throw error;
}
},
clearError: () => set({ error: null }),
getPublicProfile: async (userId) => api.get<PublicProfile>(`/users/${userId}/profile`),