feat: enhance authentication flow and user redirection (#183)

This commit is contained in:
Daniel Luiz Alves
2025-07-30 02:03:19 -03:00
committed by GitHub
23 changed files with 219 additions and 123 deletions

View File

@@ -113,14 +113,21 @@ export class AuthController {
async getCurrentUser(request: FastifyRequest, reply: FastifyReply) { async getCurrentUser(request: FastifyRequest, reply: FastifyReply) {
try { try {
const userId = (request as any).user?.userId; let userId: string | null = null;
try {
await request.jwtVerify();
userId = (request as any).user?.userId;
} catch (err) {
return reply.send({ user: null });
}
if (!userId) { if (!userId) {
return reply.status(401).send({ error: "Unauthorized: a valid token is required to access this resource." }); return reply.send({ user: null });
} }
const user = await this.authService.getUserById(userId); const user = await this.authService.getUserById(userId);
if (!user) { if (!user) {
return reply.status(404).send({ error: "User not found" }); return reply.send({ user: null });
} }
return reply.send({ user }); return reply.send({ user });

View File

@@ -153,33 +153,29 @@ export async function authRoutes(app: FastifyInstance) {
tags: ["Authentication"], tags: ["Authentication"],
operationId: "getCurrentUser", operationId: "getCurrentUser",
summary: "Get Current User", summary: "Get Current User",
description: "Returns the current authenticated user's information", description: "Returns the current authenticated user's information or null if not authenticated",
response: { response: {
200: z.object({ 200: z.union([
user: z.object({ z.object({
id: z.string().describe("User ID"), user: z.object({
firstName: z.string().describe("User first name"), id: z.string().describe("User ID"),
lastName: z.string().describe("User last name"), firstName: z.string().describe("User first name"),
username: z.string().describe("User username"), lastName: z.string().describe("User last name"),
email: z.string().email().describe("User email"), username: z.string().describe("User username"),
image: z.string().nullable().describe("User profile image URL"), email: z.string().email().describe("User email"),
isAdmin: z.boolean().describe("User is admin"), image: z.string().nullable().describe("User profile image URL"),
isActive: z.boolean().describe("User is active"), isAdmin: z.boolean().describe("User is admin"),
createdAt: z.date().describe("User creation date"), isActive: z.boolean().describe("User is active"),
updatedAt: z.date().describe("User last update date"), createdAt: z.date().describe("User creation date"),
updatedAt: z.date().describe("User last update date"),
}),
}), }),
}), z.object({
401: z.object({ error: z.string().describe("Error message") }), user: z.null().describe("No user when not authenticated"),
}),
]),
}, },
}, },
preValidation: async (request: FastifyRequest, reply: FastifyReply) => {
try {
await request.jwtVerify();
} catch (err) {
console.error(err);
reply.status(401).send({ error: "Unauthorized: a valid token is required to access this resource." });
}
},
}, },
authController.getCurrentUser.bind(authController) authController.getCurrentUser.bind(authController)
); );

View File

@@ -455,6 +455,11 @@
}, },
"pageTitle": "الملف الشخصي" "pageTitle": "الملف الشخصي"
}, },
"qrCodeModal": {
"title": "مشاركة رمز QR",
"description": "امسح رمز QR هذا للوصول إلى الرابط.",
"download": "تحميل رمز QR"
},
"quickAccess": { "quickAccess": {
"files": { "files": {
"title": "ملفاتي", "title": "ملفاتي",
@@ -1746,10 +1751,5 @@
"passwordRequired": "كلمة المرور مطلوبة", "passwordRequired": "كلمة المرور مطلوبة",
"nameRequired": "الاسم مطلوب", "nameRequired": "الاسم مطلوب",
"required": "هذا الحقل مطلوب" "required": "هذا الحقل مطلوب"
},
"qrCodeModal": {
"title": "مشاركة رمز QR",
"description": "امسح رمز QR هذا للوصول إلى الرابط.",
"download": "تحميل رمز QR"
} }
} }

View File

@@ -455,6 +455,11 @@
}, },
"pageTitle": "Profil" "pageTitle": "Profil"
}, },
"qrCodeModal": {
"title": "QR-Code teilen",
"description": "Scannen Sie diesen QR-Code, um auf den Link zuzugreifen.",
"download": "QR-Code herunterladen"
},
"quickAccess": { "quickAccess": {
"files": { "files": {
"title": "Meine Dateien", "title": "Meine Dateien",
@@ -1744,10 +1749,5 @@
"passwordRequired": "Passwort ist erforderlich", "passwordRequired": "Passwort ist erforderlich",
"nameRequired": "Name ist erforderlich", "nameRequired": "Name ist erforderlich",
"required": "Dieses Feld ist erforderlich" "required": "Dieses Feld ist erforderlich"
},
"qrCodeModal": {
"title": "QR-Code teilen",
"description": "Scannen Sie diesen QR-Code, um auf den Link zuzugreifen.",
"download": "QR-Code herunterladen"
} }
} }

View File

@@ -455,6 +455,11 @@
}, },
"pageTitle": "Perfil" "pageTitle": "Perfil"
}, },
"qrCodeModal": {
"title": "Compartir Código QR",
"description": "Escanea este código QR para acceder al enlace.",
"download": "Descargar Código QR"
},
"quickAccess": { "quickAccess": {
"files": { "files": {
"title": "Mis archivos", "title": "Mis archivos",
@@ -1744,10 +1749,5 @@
"passwordRequired": "Se requiere la contraseña", "passwordRequired": "Se requiere la contraseña",
"nameRequired": "El nombre es obligatorio", "nameRequired": "El nombre es obligatorio",
"required": "Este campo es obligatorio" "required": "Este campo es obligatorio"
},
"qrCodeModal": {
"title": "Compartir Código QR",
"description": "Escanea este código QR para acceder al enlace.",
"download": "Descargar Código QR"
} }
} }

View File

@@ -455,6 +455,11 @@
}, },
"pageTitle": "Profil" "pageTitle": "Profil"
}, },
"qrCodeModal": {
"title": "Code QR de Partage",
"description": "Scannez ce code QR pour accéder au lien.",
"download": "Télécharger le Code QR"
},
"quickAccess": { "quickAccess": {
"files": { "files": {
"title": "Mes Fichiers", "title": "Mes Fichiers",
@@ -1744,10 +1749,5 @@
"passwordRequired": "Le mot de passe est requis", "passwordRequired": "Le mot de passe est requis",
"nameRequired": "Nome é obrigatório", "nameRequired": "Nome é obrigatório",
"required": "Este campo é obrigatório" "required": "Este campo é obrigatório"
},
"qrCodeModal": {
"title": "Code QR de Partage",
"description": "Scannez ce code QR pour accéder au lien.",
"download": "Télécharger le Code QR"
} }
} }

View File

@@ -455,6 +455,11 @@
}, },
"pageTitle": "प्रोफ़ाइल" "pageTitle": "प्रोफ़ाइल"
}, },
"qrCodeModal": {
"title": "QR कोड साझा करें",
"description": "इस QR कोड को स्कैन करके लिंक तक पहुंच सकते हैं।",
"download": "QR कोड डाउनलोड करें"
},
"quickAccess": { "quickAccess": {
"files": { "files": {
"title": "मेरी फाइलें", "title": "मेरी फाइलें",
@@ -1744,10 +1749,5 @@
"passwordRequired": "पासवर्ड आवश्यक है", "passwordRequired": "पासवर्ड आवश्यक है",
"nameRequired": "नाम आवश्यक है", "nameRequired": "नाम आवश्यक है",
"required": "यह फ़ील्ड आवश्यक है" "required": "यह फ़ील्ड आवश्यक है"
},
"qrCodeModal": {
"title": "QR कोड साझा करें",
"description": "इस QR कोड को स्कैन करके लिंक तक पहुंच सकते हैं।",
"download": "QR कोड डाउनलोड करें"
} }
} }

View File

@@ -455,6 +455,11 @@
}, },
"pageTitle": "Profilo" "pageTitle": "Profilo"
}, },
"qrCodeModal": {
"title": "Condividi QR Code",
"description": "Scansiona questo codice QR per accedere al link.",
"download": "Scarica QR Code"
},
"quickAccess": { "quickAccess": {
"files": { "files": {
"title": "I Miei File", "title": "I Miei File",
@@ -1744,10 +1749,5 @@
"passwordMinLength": "La password deve contenere almeno 6 caratteri", "passwordMinLength": "La password deve contenere almeno 6 caratteri",
"nameRequired": "Il nome è obbligatorio", "nameRequired": "Il nome è obbligatorio",
"required": "Questo campo è obbligatorio" "required": "Questo campo è obbligatorio"
},
"qrCodeModal": {
"title": "Condividi QR Code",
"description": "Scansiona questo codice QR per accedere al link.",
"download": "Scarica QR Code"
} }
} }

View File

@@ -455,6 +455,11 @@
}, },
"pageTitle": "プロフィール" "pageTitle": "プロフィール"
}, },
"qrCodeModal": {
"title": "QRコードを共有",
"description": "このQRコードをスキャンしてリンクにアクセスしてください。",
"download": "QRコードをダウンロード"
},
"quickAccess": { "quickAccess": {
"files": { "files": {
"title": "マイファイル", "title": "マイファイル",
@@ -1744,10 +1749,5 @@
"passwordRequired": "パスワードは必須です", "passwordRequired": "パスワードは必須です",
"nameRequired": "名前は必須です", "nameRequired": "名前は必須です",
"required": "このフィールドは必須です" "required": "このフィールドは必須です"
},
"qrCodeModal": {
"title": "QRコードを共有",
"description": "このQRコードをスキャンしてリンクにアクセスしてください。",
"download": "QRコードをダウンロード"
} }
} }

View File

@@ -455,6 +455,11 @@
}, },
"pageTitle": "프로필" "pageTitle": "프로필"
}, },
"qrCodeModal": {
"title": "QR 코드 공유",
"description": "이 QR 코드를 스캔하여 링크에 접근할 수 있습니다.",
"download": "QR 코드 다운로드"
},
"quickAccess": { "quickAccess": {
"files": { "files": {
"title": "내 파일", "title": "내 파일",
@@ -1744,10 +1749,5 @@
"passwordRequired": "비밀번호는 필수입니다", "passwordRequired": "비밀번호는 필수입니다",
"nameRequired": "이름은 필수입니다", "nameRequired": "이름은 필수입니다",
"required": "이 필드는 필수입니다" "required": "이 필드는 필수입니다"
},
"qrCodeModal": {
"title": "QR 코드 공유",
"description": "이 QR 코드를 스캔하여 링크에 접근할 수 있습니다.",
"download": "QR 코드 다운로드"
} }
} }

View File

@@ -455,6 +455,11 @@
}, },
"pageTitle": "Profiel" "pageTitle": "Profiel"
}, },
"qrCodeModal": {
"title": "QR Code Delen",
"description": "Scan deze QR-code om toegang te krijgen tot de link.",
"download": "QR Code Downloaden"
},
"quickAccess": { "quickAccess": {
"files": { "files": {
"title": "Mijn Bestanden", "title": "Mijn Bestanden",
@@ -1744,10 +1749,5 @@
"passwordMinLength": "Wachtwoord moet minimaal 6 tekens bevatten", "passwordMinLength": "Wachtwoord moet minimaal 6 tekens bevatten",
"nameRequired": "Naam is verplicht", "nameRequired": "Naam is verplicht",
"required": "Dit veld is verplicht" "required": "Dit veld is verplicht"
},
"qrCodeModal": {
"title": "QR Code Delen",
"description": "Scan deze QR-code om toegang te krijgen tot de link.",
"download": "QR Code Downloaden"
} }
} }

View File

@@ -455,6 +455,11 @@
}, },
"pageTitle": "Profil" "pageTitle": "Profil"
}, },
"qrCodeModal": {
"title": "Udostępnij kod QR",
"description": "Skanuj ten kod QR, aby uzyskać dostęp do linku.",
"download": "Pobierz kod QR"
},
"quickAccess": { "quickAccess": {
"files": { "files": {
"title": "Moje pliki", "title": "Moje pliki",
@@ -1744,10 +1749,5 @@
"passwordMinLength": "Hasło musi mieć co najmniej 6 znaków", "passwordMinLength": "Hasło musi mieć co najmniej 6 znaków",
"nameRequired": "Nazwa jest wymagana", "nameRequired": "Nazwa jest wymagana",
"required": "To pole jest wymagane" "required": "To pole jest wymagane"
},
"qrCodeModal": {
"title": "Udostępnij kod QR",
"description": "Skanuj ten kod QR, aby uzyskać dostęp do linku.",
"download": "Pobierz kod QR"
} }
} }

View File

@@ -455,6 +455,11 @@
}, },
"pageTitle": "Perfil" "pageTitle": "Perfil"
}, },
"qrCodeModal": {
"title": "Compartilhar QR Code",
"description": "Escaneie este código QR para acessar o link.",
"download": "Baixar QR Code"
},
"quickAccess": { "quickAccess": {
"files": { "files": {
"title": "Meus Arquivos", "title": "Meus Arquivos",
@@ -1744,10 +1749,5 @@
"lastNameRequired": "O sobrenome é necessário", "lastNameRequired": "O sobrenome é necessário",
"usernameLength": "O nome de usuário deve ter pelo menos 3 caracteres", "usernameLength": "O nome de usuário deve ter pelo menos 3 caracteres",
"usernameSpaces": "O nome de usuário não pode conter espaços" "usernameSpaces": "O nome de usuário não pode conter espaços"
},
"qrCodeModal": {
"title": "Compartilhar QR Code",
"description": "Escaneie este código QR para acessar o link.",
"download": "Baixar QR Code"
} }
} }

View File

@@ -455,6 +455,11 @@
}, },
"pageTitle": "Профиль" "pageTitle": "Профиль"
}, },
"qrCodeModal": {
"title": "Поделиться QR-кодом",
"description": "Отсканируйте этот QR-код, чтобы получить доступ к ссылке.",
"download": "Скачать QR-код"
},
"quickAccess": { "quickAccess": {
"files": { "files": {
"title": "Мои файлы", "title": "Мои файлы",
@@ -1744,10 +1749,5 @@
"passwordRequired": "Требуется пароль", "passwordRequired": "Требуется пароль",
"nameRequired": "Требуется имя", "nameRequired": "Требуется имя",
"required": "Это поле обязательно" "required": "Это поле обязательно"
},
"qrCodeModal": {
"title": "Поделиться QR-кодом",
"description": "Отсканируйте этот QR-код, чтобы получить доступ к ссылке.",
"download": "Скачать QR-код"
} }
} }

View File

@@ -455,6 +455,11 @@
}, },
"pageTitle": "Profil" "pageTitle": "Profil"
}, },
"qrCodeModal": {
"title": "QR Kodu Paylaş",
"description": "Bu QR kodu tarayarak bağlantıya erişebilirsiniz.",
"download": "QR Kodu İndir"
},
"quickAccess": { "quickAccess": {
"files": { "files": {
"title": "Benim Dosyalarım", "title": "Benim Dosyalarım",
@@ -1744,10 +1749,5 @@
"passwordRequired": "Şifre gerekli", "passwordRequired": "Şifre gerekli",
"nameRequired": "İsim gereklidir", "nameRequired": "İsim gereklidir",
"required": "Bu alan zorunludur" "required": "Bu alan zorunludur"
},
"qrCodeModal": {
"title": "QR Kodu Paylaş",
"description": "Bu QR kodu tarayarak bağlantıya erişebilirsiniz.",
"download": "QR Kodu İndir"
} }
} }

View File

@@ -455,6 +455,11 @@
}, },
"pageTitle": "个人资料" "pageTitle": "个人资料"
}, },
"qrCodeModal": {
"title": "分享QR Code",
"description": "扫描此QR Code以访问链接。",
"download": "下载QR Code"
},
"quickAccess": { "quickAccess": {
"files": { "files": {
"title": "我的文件", "title": "我的文件",
@@ -1507,11 +1512,7 @@
"copyToClipboard": "复制到剪贴板", "copyToClipboard": "复制到剪贴板",
"savedMessage": "我已保存备用码", "savedMessage": "我已保存备用码",
"available": "可用备用码:{count}个", "available": "可用备用码:{count}个",
"instructions": [ "instructions": ["• 将这些代码保存在安全的位置", "• 每个备用码只能使用一次", "• 您可以随时生成新的备用码"]
"• 将这些代码保存在安全的位置",
"• 每个备用码只能使用一次",
"• 您可以随时生成新的备用码"
]
}, },
"verification": { "verification": {
"title": "双重认证", "title": "双重认证",
@@ -1744,10 +1745,5 @@
"passwordRequired": "密码为必填项", "passwordRequired": "密码为必填项",
"nameRequired": "名称为必填项", "nameRequired": "名称为必填项",
"required": "此字段为必填项" "required": "此字段为必填项"
},
"qrCodeModal": {
"title": "分享QR Code",
"description": "扫描此QR Code以访问链接。",
"download": "下载QR Code"
} }
} }

View File

@@ -4,34 +4,51 @@ import { useEffect } from "react";
import { useRouter } from "next/navigation"; import { useRouter } from "next/navigation";
import { create } from "zustand"; import { create } from "zustand";
import { useAuth } from "@/contexts/auth-context";
import { useSecureConfigValue } from "@/hooks/use-secure-configs"; import { useSecureConfigValue } from "@/hooks/use-secure-configs";
interface HomeStore { interface HomeStore {
isLoading: boolean; isLoading: boolean;
shouldShowHomePage: boolean;
setIsLoading: (loading: boolean) => void; setIsLoading: (loading: boolean) => void;
setShouldShowHomePage: (show: boolean) => void;
} }
const useHomeStore = create<HomeStore>((set) => ({ const useHomeStore = create<HomeStore>((set) => ({
isLoading: true, isLoading: true,
shouldShowHomePage: false,
setIsLoading: (loading: boolean) => set({ isLoading: loading }), setIsLoading: (loading: boolean) => set({ isLoading: loading }),
setShouldShowHomePage: (show: boolean) => set({ shouldShowHomePage: show }),
})); }));
export function useHome() { export function useHome() {
const router = useRouter(); const router = useRouter();
const { isLoading, setIsLoading } = useHomeStore(); const { isLoading, shouldShowHomePage, setIsLoading, setShouldShowHomePage } = useHomeStore();
const { isAuthenticated } = useAuth();
const { value: showHomePage, isLoading: configLoading } = useSecureConfigValue("showHomePage"); const { value: showHomePage, isLoading: configLoading } = useSecureConfigValue("showHomePage");
useEffect(() => { useEffect(() => {
if (!configLoading) { if (isAuthenticated === true) {
router.replace("/dashboard");
return;
}
}, [isAuthenticated, router]);
useEffect(() => {
if (!configLoading && isAuthenticated !== null) {
setIsLoading(false); setIsLoading(false);
if (showHomePage !== "true") { if (showHomePage !== "true") {
router.push("/login"); router.push("/login");
setShouldShowHomePage(false);
} else if (isAuthenticated === false) {
setShouldShowHomePage(true);
} }
} }
}, [router, showHomePage, configLoading, setIsLoading]); }, [router, showHomePage, configLoading, isAuthenticated, setIsLoading, setShouldShowHomePage]);
return { return {
isLoading, isLoading,
shouldShowHomePage,
}; };
} }

View File

@@ -7,9 +7,9 @@ import { Navbar } from "./components/navbar";
import { useHome } from "./hooks/use-home"; import { useHome } from "./hooks/use-home";
export default function HomePage() { export default function HomePage() {
const { isLoading } = useHome(); const { isLoading, shouldShowHomePage } = useHome();
if (isLoading) { if (isLoading || !shouldShowHomePage) {
return <LoadingScreen />; return <LoadingScreen />;
} }

View File

@@ -4,6 +4,7 @@ import { getLocale } from "next-intl/server";
import "./globals.css"; import "./globals.css";
import { RedirectHandler } from "@/components/auth/redirect-handler";
import { Favicon } from "@/components/layout/favicon"; import { Favicon } from "@/components/layout/favicon";
import { DynamicToaster } from "@/components/ui/dynamic-toaster"; import { DynamicToaster } from "@/components/ui/dynamic-toaster";
import { useAppInfo } from "@/contexts/app-info-context"; import { useAppInfo } from "@/contexts/app-info-context";
@@ -39,7 +40,9 @@ export default async function RootLayout({
<NextIntlClientProvider> <NextIntlClientProvider>
<ThemeProvider attribute="class" defaultTheme="system" enableSystem disableTransitionOnChange> <ThemeProvider attribute="class" defaultTheme="system" enableSystem disableTransitionOnChange>
<AuthProvider> <AuthProvider>
<ShareProvider>{children}</ShareProvider> <RedirectHandler>
<ShareProvider>{children}</ShareProvider>
</RedirectHandler>
</AuthProvider> </AuthProvider>
<DynamicToaster /> <DynamicToaster />
</ThemeProvider> </ThemeProvider>

View File

@@ -34,6 +34,12 @@ export function useLogin() {
const [passwordAuthEnabled, setPasswordAuthEnabled] = useState(true); const [passwordAuthEnabled, setPasswordAuthEnabled] = useState(true);
const [authConfigLoading, setAuthConfigLoading] = useState(true); const [authConfigLoading, setAuthConfigLoading] = useState(true);
useEffect(() => {
if (isAuthenticated === true) {
router.replace("/dashboard");
}
}, [isAuthenticated, router]);
useEffect(() => { useEffect(() => {
const errorParam = searchParams.get("error"); const errorParam = searchParams.get("error");
const messageParam = searchParams.get("message"); const messageParam = searchParams.get("message");

View File

@@ -17,7 +17,7 @@ export default function LoginPage() {
const login = useLogin(); const login = useLogin();
const { firstAccess } = useAppInfo(); const { firstAccess } = useAppInfo();
if (login.isAuthenticated === null) { if (login.isAuthenticated === null || login.isAuthenticated === true) {
return <LoadingScreen />; return <LoadingScreen />;
} }

View File

@@ -0,0 +1,55 @@
"use client";
import { useEffect } from "react";
import { usePathname, useRouter } from "next/navigation";
import { LoadingScreen } from "@/components/layout/loading-screen";
import { useAuth } from "@/contexts/auth-context";
interface RedirectHandlerProps {
children: React.ReactNode;
}
const publicPaths = ["/login", "/forgot-password", "/reset-password", "/auth/callback", "/auth/oidc/callback"];
const homePaths = ["/"];
export function RedirectHandler({ children }: RedirectHandlerProps) {
const router = useRouter();
const pathname = usePathname();
const { isAuthenticated } = useAuth();
useEffect(() => {
if (isAuthenticated === true) {
if (publicPaths.some((path) => pathname.startsWith(path)) || homePaths.includes(pathname)) {
router.replace("/dashboard");
return;
}
} else if (isAuthenticated === false) {
if (!publicPaths.some((path) => pathname.startsWith(path)) && !homePaths.includes(pathname)) {
router.replace("/login");
return;
}
}
}, [isAuthenticated, pathname, router]);
if (isAuthenticated === null) {
return <LoadingScreen />;
}
if (
isAuthenticated === true &&
(publicPaths.some((path) => pathname.startsWith(path)) || homePaths.includes(pathname))
) {
return <LoadingScreen />;
}
if (
isAuthenticated === false &&
!publicPaths.some((path) => pathname.startsWith(path)) &&
!homePaths.includes(pathname)
) {
return <LoadingScreen />;
}
return <>{children}</>;
}

View File

@@ -39,11 +39,15 @@ export function AuthProvider({ children }: { children: React.ReactNode }) {
}; };
useEffect(() => { useEffect(() => {
let isMounted = true;
const checkAuth = async () => { const checkAuth = async () => {
try { try {
const appInfoResponse = await getAppInfo(); const appInfoResponse = await getAppInfo();
const appInfo = appInfoResponse.data; const appInfo = appInfoResponse.data;
if (!isMounted) return;
if (appInfo.firstUserAccess) { if (appInfo.firstUserAccess) {
setUser(null); setUser(null);
setIsAdmin(false); setIsAdmin(false);
@@ -52,8 +56,14 @@ export function AuthProvider({ children }: { children: React.ReactNode }) {
} }
const response = await getCurrentUser(); const response = await getCurrentUser();
if (!isMounted) return;
if (!response?.data?.user) { if (!response?.data?.user) {
throw new Error("No user data"); setUser(null);
setIsAdmin(false);
setIsAuthenticated(false);
return;
} }
const { isAdmin, ...userData } = response.data.user; const { isAdmin, ...userData } = response.data.user;
@@ -62,6 +72,8 @@ export function AuthProvider({ children }: { children: React.ReactNode }) {
setIsAdmin(isAdmin); setIsAdmin(isAdmin);
setIsAuthenticated(true); setIsAuthenticated(true);
} catch (err) { } catch (err) {
if (!isMounted) return;
console.error(err); console.error(err);
setUser(null); setUser(null);
setIsAdmin(false); setIsAdmin(false);
@@ -70,6 +82,10 @@ export function AuthProvider({ children }: { children: React.ReactNode }) {
}; };
checkAuth(); checkAuth();
return () => {
isMounted = false;
};
}, []); }, []);
return ( return (