diff --git a/frontend/package.json b/frontend/package.json index ac87fd2..f40abbb 100644 --- a/frontend/package.json +++ b/frontend/package.json @@ -6,7 +6,7 @@ "dev": "next dev --turbopack", "build": "next build --turbopack", "start": "next start", - "lint": "eslint" + "lint": "next lint" }, "dependencies": { "@hookform/resolvers": "^5.2.2", diff --git a/frontend/src/app/(app)/layout.tsx b/frontend/src/app/(app)/layout.tsx index b2afcc9..319c8ad 100644 --- a/frontend/src/app/(app)/layout.tsx +++ b/frontend/src/app/(app)/layout.tsx @@ -1,8 +1,30 @@ 'use client'; import type { ReactElement, ReactNode } from 'react'; +import { useEffect } from 'react'; import { AppShell } from '@/components/layout/app-shell'; +import { useAuth } from '@/providers/auth-context'; +import { useRouter } from 'next/navigation'; +import { Loader2 } from 'lucide-react'; export default function ProtectedLayout({ children }: { children: ReactNode }): ReactElement { + const { isAuthenticated } = useAuth(); + const router = useRouter(); + + useEffect(() => { + if (!isAuthenticated) { + router.push('/'); + } + }, [isAuthenticated, router]); + + if (!isAuthenticated) { + return ( +
+

Loading

+ +
+ ); + } + return {children}; } diff --git a/frontend/src/components/layout/app-header.tsx b/frontend/src/components/layout/app-header.tsx index 3a4ae49..edfa98c 100644 --- a/frontend/src/components/layout/app-header.tsx +++ b/frontend/src/components/layout/app-header.tsx @@ -141,7 +141,7 @@ export function AppHeader({ onMobileMenuToggle }: AppHeaderProps = {}): ReactEle variant="ghost" size="md" onClick={() => { - void signOut(); + signOut(); }} leftIcon={} className={cn('border border-transparent text-slate-300 hover:border-rose-500/50 hover:bg-rose-500/10 hover:text-rose-200', isLoading && 'pointer-events-none opacity-60')} diff --git a/frontend/src/providers/auth-context.tsx b/frontend/src/providers/auth-context.tsx index 3b2bdf4..d58b6b1 100644 --- a/frontend/src/providers/auth-context.tsx +++ b/frontend/src/providers/auth-context.tsx @@ -5,6 +5,7 @@ import type { BackendUser } from '@/types/api'; import { useCallback, useMemo } from 'react'; import { useActiveAccount, useActiveWallet } from 'thirdweb/react'; import { client } from '@/lib/thirdweb'; +import { useRouter } from 'next/navigation'; export type AuthContextValue = { user: BackendUser | null; @@ -28,6 +29,7 @@ export function useAuth(): AuthContextValue { // Always call hooks first, then handle the conditional logic const account = useActiveAccount(); const wallet = useActiveWallet(); + const router = useRouter(); const address = account?.address ?? null; @@ -46,11 +48,15 @@ export function useAuth(): AuthContextValue { const signOut = useCallback(async () => { try { - await wallet?.disconnect?.(); - } catch { - // ignore + if (wallet) { + await wallet.disconnect?.(); + } + await new Promise(resolve => setTimeout(resolve, 100)); + router.push('/'); + } catch (error) { + router.push('/'); } - }, [wallet]); + }, [wallet, router]); const refreshProfile = useCallback(async (): Promise => { return user; @@ -58,13 +64,11 @@ export function useAuth(): AuthContextValue { const signInWithEmail = useCallback(async (_email: string): Promise => { // TODO: Implement email authentication when backend is ready - console.log('Email sign-in attempted:', _email); return false; }, []); const resendVerification = useCallback(async (_email: string): Promise => { // TODO: Implement email verification when backend is ready - console.log('Email verification requested:', _email); return false; }, []); diff --git a/frontend/tsconfig.json b/frontend/tsconfig.json index c133409..ee38357 100644 --- a/frontend/tsconfig.json +++ b/frontend/tsconfig.json @@ -5,7 +5,7 @@ "allowJs": true, "skipLibCheck": true, "strict": true, - "noEmit": true, + "noEmit": false, "esModuleInterop": true, "module": "esnext", "moduleResolution": "bundler", diff --git a/frontend/vercel.json b/frontend/vercel.json new file mode 100644 index 0000000..63c6810 --- /dev/null +++ b/frontend/vercel.json @@ -0,0 +1,9 @@ +{ + "buildCommand": "pnpm build", + "outputDirectory": ".next", + "framework": "nextjs", + "installCommand": "pnpm install", + "env": { + "NODE_ENV": "production" + } +}