Merge branch 'master' into feature/cases
This commit is contained in:
@@ -1,6 +1,6 @@
|
||||
// src/lib/api/auth.ts
|
||||
import { apiClient } from './client';
|
||||
import type { AuthResponse, TrialRegistrationData, LoginData } from '@/types/auth';
|
||||
import type { AuthResponse, TrialRegistrationData, LoginData, ForgotPasswordVerifyResponse } from '@/types/auth';
|
||||
import type { SubscriptionAlertData } from '@/types/subscription';
|
||||
|
||||
export const authApi = {
|
||||
@@ -65,4 +65,25 @@ export const authApi = {
|
||||
const response = await apiClient.post('/auth/refresh', { refreshToken });
|
||||
return response.data;
|
||||
},
|
||||
|
||||
sendForgotPasswordCode: async (mobile: string): Promise<{ success: boolean; message: string }> => {
|
||||
const response = await apiClient.post('/auth/forgot-password/send-code', { mobile });
|
||||
return response.data;
|
||||
},
|
||||
|
||||
verifyForgotPasswordCode: async (
|
||||
mobile: string,
|
||||
code: string,
|
||||
): Promise<ForgotPasswordVerifyResponse> => {
|
||||
const response = await apiClient.post('/auth/forgot-password/verify', { mobile, code });
|
||||
return response.data;
|
||||
},
|
||||
|
||||
changePassword: async (data: {
|
||||
currentPassword?: string;
|
||||
newPassword: string;
|
||||
}): Promise<{ success: boolean; message: string }> => {
|
||||
const response = await apiClient.patch('/auth/profile/password', data);
|
||||
return response.data;
|
||||
},
|
||||
};
|
||||
@@ -34,7 +34,9 @@ function shouldSkipRefreshRetry(url: string | undefined): boolean {
|
||||
url.includes('/auth/refresh') ||
|
||||
url.includes('/auth/login') ||
|
||||
url.includes('/auth/register') ||
|
||||
url.includes('/auth/logout')
|
||||
url.includes('/auth/logout') ||
|
||||
url.includes('/auth/forgot-password') ||
|
||||
url.includes('/auth/profile/password')
|
||||
);
|
||||
}
|
||||
|
||||
|
||||
14
frontend/src/lib/auth/rememberMe.ts
Normal file
14
frontend/src/lib/auth/rememberMe.ts
Normal file
@@ -0,0 +1,14 @@
|
||||
const REMEMBERED_EMAIL_KEY = 'rememberedEmail';
|
||||
|
||||
export function getRememberedEmail(): string {
|
||||
if (typeof window === 'undefined') return '';
|
||||
return localStorage.getItem(REMEMBERED_EMAIL_KEY) ?? '';
|
||||
}
|
||||
|
||||
export function setRememberedEmail(email: string): void {
|
||||
localStorage.setItem(REMEMBERED_EMAIL_KEY, email);
|
||||
}
|
||||
|
||||
export function clearRememberedEmail(): void {
|
||||
localStorage.removeItem(REMEMBERED_EMAIL_KEY);
|
||||
}
|
||||
@@ -4,6 +4,11 @@ import React, { createContext, useCallback, useContext, useEffect, useMemo, useS
|
||||
import { useTranslations } from 'next-intl';
|
||||
import { useRouter } from '@/i18n/navigation';
|
||||
import { authApi } from '@/lib/api/auth';
|
||||
import {
|
||||
clearRememberedEmail,
|
||||
getRememberedEmail,
|
||||
setRememberedEmail,
|
||||
} from '@/lib/auth/rememberMe';
|
||||
import { User, Organization } from '@/types/organization';
|
||||
import { isAppLocale, getLocaleFromPathname } from '@/i18n/routing';
|
||||
|
||||
@@ -18,11 +23,12 @@ interface AuthContextType {
|
||||
email: string,
|
||||
password: string,
|
||||
name: string,
|
||||
mobile: string,
|
||||
organizationName: string,
|
||||
organizationEmail: string,
|
||||
organizationType: 'CLINIC' | 'LAB'
|
||||
) => Promise<void>;
|
||||
login: (email: string, password: string) => Promise<void>;
|
||||
login: (email: string, password: string, rememberMe?: boolean) => Promise<void>;
|
||||
logout: () => Promise<void>;
|
||||
selectOrganization: (orgId: string) => Promise<void>;
|
||||
createOrganization: (
|
||||
@@ -32,6 +38,7 @@ interface AuthContextType {
|
||||
planName?: string,
|
||||
) => Promise<string>;
|
||||
setUserLanguage: (language: string) => void;
|
||||
refreshSession: () => Promise<void>;
|
||||
clearError: () => void;
|
||||
}
|
||||
|
||||
@@ -153,6 +160,7 @@ export function AuthProvider({ children }: { children: React.ReactNode }) {
|
||||
email: string,
|
||||
password: string,
|
||||
name: string,
|
||||
mobile: string,
|
||||
organizationName: string,
|
||||
organizationEmail: string,
|
||||
organizationType: 'CLINIC' | 'LAB'
|
||||
@@ -163,6 +171,7 @@ export function AuthProvider({ children }: { children: React.ReactNode }) {
|
||||
|
||||
const response = await authApi.registerTrial({
|
||||
email,
|
||||
mobile,
|
||||
password,
|
||||
name,
|
||||
organizationName,
|
||||
@@ -196,12 +205,22 @@ export function AuthProvider({ children }: { children: React.ReactNode }) {
|
||||
}, [applyUrlLocaleToUser, router, t]);
|
||||
|
||||
// ✅ LOGIN
|
||||
const login = useCallback(async (email: string, password: string) => {
|
||||
const login = useCallback(async (
|
||||
email: string,
|
||||
password: string,
|
||||
rememberMe = false,
|
||||
) => {
|
||||
try {
|
||||
setIsLoading(true);
|
||||
setError(null);
|
||||
|
||||
const response = await authApi.login({ email, password });
|
||||
const response = await authApi.login({ email, password, rememberMe });
|
||||
|
||||
if (rememberMe) {
|
||||
setRememberedEmail(email);
|
||||
} else {
|
||||
clearRememberedEmail();
|
||||
}
|
||||
|
||||
const userData = await applyUrlLocaleToUser(response.data.user);
|
||||
setUser(userData);
|
||||
@@ -235,7 +254,11 @@ export function AuthProvider({ children }: { children: React.ReactNode }) {
|
||||
} catch (err) {
|
||||
console.error('Logout API failed:', err);
|
||||
} finally {
|
||||
const rememberedEmail = getRememberedEmail();
|
||||
localStorage.clear();
|
||||
if (rememberedEmail) {
|
||||
setRememberedEmail(rememberedEmail);
|
||||
}
|
||||
setUser(null);
|
||||
setOrganizations([]);
|
||||
setCurrentOrganization(null);
|
||||
@@ -265,7 +288,16 @@ export function AuthProvider({ children }: { children: React.ReactNode }) {
|
||||
plan: (organization as { plan?: Organization['plan'] }).plan,
|
||||
});
|
||||
|
||||
router.push('/today');
|
||||
const redirectPath =
|
||||
typeof window !== 'undefined'
|
||||
? sessionStorage.getItem('authRedirect')
|
||||
: null;
|
||||
if (redirectPath) {
|
||||
sessionStorage.removeItem('authRedirect');
|
||||
router.push(redirectPath);
|
||||
} else {
|
||||
router.push('/today');
|
||||
}
|
||||
|
||||
} catch (err: any) {
|
||||
setError(err.message);
|
||||
@@ -308,6 +340,10 @@ export function AuthProvider({ children }: { children: React.ReactNode }) {
|
||||
}
|
||||
}, [normalizeProfilePayload, t]);
|
||||
|
||||
const refreshSession = useCallback(async () => {
|
||||
await checkAuth();
|
||||
}, [checkAuth]);
|
||||
|
||||
const clearError = useCallback(() => setError(null), []);
|
||||
|
||||
const setUserLanguage = useCallback((language: string) => {
|
||||
@@ -329,6 +365,7 @@ export function AuthProvider({ children }: { children: React.ReactNode }) {
|
||||
selectOrganization,
|
||||
createOrganization,
|
||||
setUserLanguage,
|
||||
refreshSession,
|
||||
clearError,
|
||||
}),
|
||||
[
|
||||
@@ -344,6 +381,7 @@ export function AuthProvider({ children }: { children: React.ReactNode }) {
|
||||
selectOrganization,
|
||||
createOrganization,
|
||||
setUserLanguage,
|
||||
refreshSession,
|
||||
clearError,
|
||||
],
|
||||
);
|
||||
|
||||
Reference in New Issue
Block a user