Add useAuthentication hook and enhance useRouter for improved navigation and session management
- Introduced `useAuthentication` hook to handle session state, PIN verification, and offline mode logic. - Updated `useRouter` to include type definitions for better navigation instance typing. - Refined authentication flow with error handling, user session initialization, and token validation.
This commit is contained in:
222
hooks/useAuthentication.ts
Normal file
222
hooks/useAuthentication.ts
Normal file
@@ -0,0 +1,222 @@
|
||||
'use client';
|
||||
import {Dispatch, SetStateAction, useContext, useEffect, useState} from 'react';
|
||||
import {AppRouterInstance, useRouter} from '@/lib/navigation';
|
||||
import {useTranslations} from '@/lib/i18n';
|
||||
import {AlertContext, AlertContextProps} from '@/context/AlertContext';
|
||||
import {LangContext, LangContextProps} from '@/context/LangContext';
|
||||
import OfflineContext, {OfflineMode} from '@/context/OfflineContext';
|
||||
import {SessionProps} from '@/lib/types/session';
|
||||
import {UserProps} from '@/lib/types/user';
|
||||
import {apiGet} from '@/lib/api/client';
|
||||
import {isDesktop} from '@/lib/configs';
|
||||
import * as tauri from '@/lib/tauri';
|
||||
import {getCookie} from '@/lib/utils/cookies';
|
||||
|
||||
interface UseAuthenticationReturn {
|
||||
session: SessionProps;
|
||||
setSession: Dispatch<SetStateAction<SessionProps>>;
|
||||
isLoading: boolean;
|
||||
currentCredits: number;
|
||||
setCurrentCredits: Dispatch<SetStateAction<number>>;
|
||||
amountSpent: number;
|
||||
setAmountSpent: Dispatch<SetStateAction<number>>;
|
||||
showPinSetup: boolean;
|
||||
setShowPinSetup: Dispatch<SetStateAction<boolean>>;
|
||||
showPinVerify: boolean;
|
||||
setShowPinVerify: Dispatch<SetStateAction<boolean>>;
|
||||
handlePinVerifySuccess: (userId: string) => Promise<void>;
|
||||
}
|
||||
|
||||
export default function useAuthentication(): UseAuthenticationReturn {
|
||||
const t = useTranslations();
|
||||
const {lang: locale}: LangContextProps = useContext<LangContextProps>(LangContext);
|
||||
const {errorMessage}: AlertContextProps = useContext<AlertContextProps>(AlertContext);
|
||||
const {initializeDatabase, setOfflineMode} = useContext(OfflineContext);
|
||||
const router: AppRouterInstance = useRouter();
|
||||
|
||||
const [session, setSession] = useState<SessionProps>({user: null, accessToken: '', isConnected: false});
|
||||
const [currentCredits, setCurrentCredits] = useState<number>(0);
|
||||
const [amountSpent, setAmountSpent] = useState<number>(session.user?.aiUsage || 0);
|
||||
const [isLoading, setIsLoading] = useState<boolean>(true);
|
||||
const [sessionAttempts, setSessionAttempts] = useState<number>(0);
|
||||
const [showPinSetup, setShowPinSetup] = useState<boolean>(false);
|
||||
const [showPinVerify, setShowPinVerify] = useState<boolean>(false);
|
||||
|
||||
useEffect((): void => {
|
||||
checkAuthentification().then();
|
||||
}, []);
|
||||
|
||||
useEffect((): void => {
|
||||
if (session.isConnected) {
|
||||
setIsLoading(false);
|
||||
} else {
|
||||
if (sessionAttempts > 2) {
|
||||
router.push('/');
|
||||
}
|
||||
}
|
||||
setSessionAttempts(sessionAttempts + 1);
|
||||
}, [session]);
|
||||
|
||||
async function checkAuthentification(): Promise<void> {
|
||||
const token: string | null = isDesktop ? await tauri.getToken() : getCookie('token');
|
||||
|
||||
if (token) {
|
||||
try {
|
||||
const user: UserProps = await apiGet<UserProps>('user/infos', token, locale);
|
||||
if (!user) {
|
||||
errorMessage(t('homePage.errors.userNotFound'));
|
||||
if (isDesktop) {
|
||||
await tauri.removeToken();
|
||||
await tauri.logout();
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
if (isDesktop && user.id) {
|
||||
try {
|
||||
const initResult = await tauri.initUser(user.id);
|
||||
if (!initResult.success) {
|
||||
errorMessage(initResult.error || t('homePage.errors.offlineInitError'));
|
||||
return;
|
||||
}
|
||||
try {
|
||||
const offlineStatus = await tauri.offlineModeGet();
|
||||
if (!offlineStatus.hasPin && user.termsAccepted) {
|
||||
setTimeout((): void => {
|
||||
setShowPinSetup(true);
|
||||
}, 2000);
|
||||
}
|
||||
} catch (error) {
|
||||
errorMessage(t('homePage.errors.offlineModeError'));
|
||||
}
|
||||
} catch (error) {
|
||||
errorMessage(t('homePage.errors.offlineInitError'));
|
||||
}
|
||||
|
||||
try {
|
||||
const dbInitialized: boolean = await initializeDatabase(user.id);
|
||||
if (dbInitialized) {
|
||||
try {
|
||||
await tauri.syncUser({
|
||||
userId: user.id,
|
||||
username: user.username || '',
|
||||
email: user.email || ''
|
||||
});
|
||||
} catch (syncError) {
|
||||
errorMessage(t('homePage.errors.syncError'));
|
||||
}
|
||||
}
|
||||
} catch (error) {
|
||||
errorMessage(t('homePage.errors.syncError'));
|
||||
}
|
||||
}
|
||||
|
||||
setSession({
|
||||
isConnected: true,
|
||||
user: user,
|
||||
accessToken: token,
|
||||
});
|
||||
setCurrentCredits(user.creditsBalance);
|
||||
setAmountSpent(user.aiUsage);
|
||||
} catch (e: unknown) {
|
||||
if (isDesktop) {
|
||||
try {
|
||||
const offlineStatus = await tauri.offlineModeGet();
|
||||
if (offlineStatus.hasPin && offlineStatus.lastUserId) {
|
||||
setOfflineMode((prev: OfflineMode): OfflineMode => ({
|
||||
...prev,
|
||||
isOffline: true,
|
||||
isNetworkOnline: false
|
||||
}));
|
||||
setShowPinVerify(true);
|
||||
setIsLoading(false);
|
||||
return;
|
||||
} else {
|
||||
await tauri.removeToken();
|
||||
await tauri.logout();
|
||||
}
|
||||
} catch (offlineError) {
|
||||
errorMessage(t('homePage.errors.offlineError'));
|
||||
}
|
||||
} else {
|
||||
window.location.href = 'https://eritors.com/login';
|
||||
}
|
||||
|
||||
if (e instanceof Error) {
|
||||
errorMessage(e.message);
|
||||
} else {
|
||||
errorMessage(t('homePage.errors.authenticationError'));
|
||||
}
|
||||
}
|
||||
} else {
|
||||
if (isDesktop) {
|
||||
try {
|
||||
const offlineStatus = await tauri.offlineModeGet();
|
||||
if (offlineStatus.hasPin && offlineStatus.lastUserId) {
|
||||
setOfflineMode((prev: OfflineMode): OfflineMode => ({
|
||||
...prev,
|
||||
isOffline: true,
|
||||
isNetworkOnline: false
|
||||
}));
|
||||
setShowPinVerify(true);
|
||||
setIsLoading(false);
|
||||
return;
|
||||
}
|
||||
} catch (error) {
|
||||
errorMessage(t('homePage.errors.authenticationError'));
|
||||
}
|
||||
await tauri.openLoginWindow();
|
||||
} else {
|
||||
window.location.href = 'https://eritors.com/login';
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
async function handlePinVerifySuccess(userId: string): Promise<void> {
|
||||
try {
|
||||
const storedToken: string | null = await tauri.getToken();
|
||||
const encryptionKey: string | null = await tauri.getUserEncryptionKey(userId);
|
||||
|
||||
if (encryptionKey) {
|
||||
await tauri.dbInitialize(userId, encryptionKey);
|
||||
setOfflineMode((prev: OfflineMode): OfflineMode => ({
|
||||
...prev,
|
||||
isDatabaseInitialized: true
|
||||
}));
|
||||
|
||||
const localUser: UserProps = await tauri.getUserInfo();
|
||||
if (localUser && localUser.id) {
|
||||
setSession({
|
||||
isConnected: true,
|
||||
user: localUser,
|
||||
accessToken: storedToken || '',
|
||||
});
|
||||
setShowPinVerify(false);
|
||||
setCurrentCredits(localUser.creditsBalance || 0);
|
||||
setAmountSpent(localUser.aiUsage || 0);
|
||||
} else {
|
||||
errorMessage(t('homePage.errors.localDataError'));
|
||||
}
|
||||
} else {
|
||||
errorMessage(t('homePage.errors.encryptionKeyError'));
|
||||
}
|
||||
} catch (error) {
|
||||
errorMessage(t('homePage.errors.offlineModeError'));
|
||||
}
|
||||
}
|
||||
|
||||
return {
|
||||
session,
|
||||
setSession,
|
||||
isLoading,
|
||||
currentCredits,
|
||||
setCurrentCredits,
|
||||
amountSpent,
|
||||
setAmountSpent,
|
||||
showPinSetup,
|
||||
setShowPinSetup,
|
||||
showPinVerify,
|
||||
setShowPinVerify,
|
||||
handlePinVerifySuccess,
|
||||
};
|
||||
}
|
||||
@@ -1,6 +1,12 @@
|
||||
import {useNavigate, useParams as useRouterParams, Link, useLocation} from 'react-router-dom';
|
||||
|
||||
function useRouter() {
|
||||
export type AppRouterInstance = {
|
||||
push: (path: string) => void;
|
||||
replace: (path: string) => void;
|
||||
back: () => void;
|
||||
};
|
||||
|
||||
function useRouter(): AppRouterInstance {
|
||||
const navigate = useNavigate();
|
||||
return {
|
||||
push: (path: string) => navigate(path),
|
||||
|
||||
Reference in New Issue
Block a user