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:
natreex
2026-03-30 21:17:46 -04:00
parent e1d87c6997
commit acacd95f38
2 changed files with 229 additions and 1 deletions

222
hooks/useAuthentication.ts Normal file
View 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,
};
}

View File

@@ -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),