Refactor code structure and remove redundant changes

This commit is contained in:
pacnpal
2025-11-09 16:31:34 -05:00
parent 2884bc23ce
commit eb68cf40c6
1080 changed files with 27361 additions and 56687 deletions

View File

@@ -0,0 +1,110 @@
'use client';
import { useState } from 'react';
import {
Dialog,
DialogContent,
DialogHeader,
DialogTitle,
DialogDescription,
} from '@/components/ui/dialog';
import { LoginForm } from './LoginForm';
import { RegisterForm } from './RegisterForm';
import { PasswordResetForm } from './PasswordResetForm';
import { OAuthButtons } from './OAuthButtons';
type AuthView = 'login' | 'register' | 'reset';
interface AuthModalProps {
open: boolean;
onOpenChange: (open: boolean) => void;
defaultView?: AuthView;
onSuccess?: () => void;
showOAuth?: boolean;
}
export function AuthModal({
open,
onOpenChange,
defaultView = 'login',
onSuccess,
showOAuth = true,
}: AuthModalProps) {
const [view, setView] = useState<AuthView>(defaultView);
const handleSuccess = () => {
onSuccess?.();
onOpenChange(false);
};
const getTitle = () => {
switch (view) {
case 'login':
return 'Welcome Back';
case 'register':
return 'Create Account';
case 'reset':
return 'Reset Password';
}
};
const getDescription = () => {
switch (view) {
case 'login':
return 'Sign in to your account to continue';
case 'register':
return 'Create a new account to get started';
case 'reset':
return 'Reset your password';
}
};
return (
<Dialog open={open} onOpenChange={onOpenChange}>
<DialogContent className="sm:max-w-[425px]">
<DialogHeader>
<DialogTitle>{getTitle()}</DialogTitle>
<DialogDescription>{getDescription()}</DialogDescription>
</DialogHeader>
<div className="mt-4">
{view === 'login' && (
<>
<LoginForm
onSuccess={handleSuccess}
onSwitchToRegister={() => setView('register')}
onSwitchToReset={() => setView('reset')}
/>
{showOAuth && (
<div className="mt-6">
<OAuthButtons />
</div>
)}
</>
)}
{view === 'register' && (
<>
<RegisterForm
onSuccess={handleSuccess}
onSwitchToLogin={() => setView('login')}
/>
{showOAuth && (
<div className="mt-6">
<OAuthButtons />
</div>
)}
</>
)}
{view === 'reset' && (
<PasswordResetForm
onSuccess={handleSuccess}
onBack={() => setView('login')}
/>
)}
</div>
</DialogContent>
</Dialog>
);
}

View File

@@ -0,0 +1,202 @@
'use client';
import { useState } from 'react';
import { useForm } from 'react-hook-form';
import { zodResolver } from '@hookform/resolvers/zod';
import { z } from 'zod';
import { useAuth } from '@/lib/contexts/AuthContext';
import { mfaService } from '@/lib/services/auth';
import { Button } from '@/components/ui/button';
import { Input } from '@/components/ui/input';
import { Label } from '@/components/ui/label';
import { Alert, AlertDescription } from '@/components/ui/alert';
import { AlertCircle, Loader2 } from 'lucide-react';
const loginSchema = z.object({
email: z.string().email('Invalid email address'),
password: z.string().min(1, 'Password is required'),
});
type LoginFormData = z.infer<typeof loginSchema>;
interface LoginFormProps {
onSuccess?: () => void;
onSwitchToRegister?: () => void;
onSwitchToReset?: () => void;
}
export function LoginForm({ onSuccess, onSwitchToRegister, onSwitchToReset }: LoginFormProps) {
const { login } = useAuth();
const [error, setError] = useState<string>('');
const [isLoading, setIsLoading] = useState(false);
const [showMFAChallenge, setShowMFAChallenge] = useState(false);
const [mfaCode, setMfaCode] = useState('');
const {
register,
handleSubmit,
formState: { errors },
} = useForm<LoginFormData>({
resolver: zodResolver(loginSchema),
});
const onSubmit = async (data: LoginFormData) => {
setError('');
setIsLoading(true);
try {
await login(data);
onSuccess?.();
} catch (err: any) {
const errorMessage = err.response?.data?.detail || err.message || 'Login failed';
// Check if MFA is required
if (errorMessage.toLowerCase().includes('mfa') || errorMessage.toLowerCase().includes('two-factor')) {
setShowMFAChallenge(true);
} else {
setError(errorMessage);
}
} finally {
setIsLoading(false);
}
};
const handleMFASubmit = async (e: React.FormEvent) => {
e.preventDefault();
setError('');
setIsLoading(true);
try {
await mfaService.challengeMFA({ code: mfaCode });
// Tokens are stored automatically by the service
onSuccess?.();
} catch (err: any) {
setError(err.response?.data?.detail || 'Invalid MFA code');
} finally {
setIsLoading(false);
}
};
if (showMFAChallenge) {
return (
<form onSubmit={handleMFASubmit} className="space-y-4">
<div className="text-center mb-4">
<h3 className="text-lg font-semibold">Two-Factor Authentication</h3>
<p className="text-sm text-muted-foreground mt-1">
Enter the 6-digit code from your authenticator app
</p>
</div>
{error && (
<Alert variant="destructive">
<AlertCircle className="h-4 w-4" />
<AlertDescription>{error}</AlertDescription>
</Alert>
)}
<div className="space-y-2">
<Label htmlFor="mfa-code">Verification Code</Label>
<Input
id="mfa-code"
type="text"
inputMode="numeric"
pattern="[0-9]*"
maxLength={6}
placeholder="000000"
value={mfaCode}
onChange={(e) => setMfaCode(e.target.value.replace(/\D/g, ''))}
disabled={isLoading}
autoFocus
/>
</div>
<div className="flex gap-2">
<Button
type="button"
variant="outline"
onClick={() => {
setShowMFAChallenge(false);
setMfaCode('');
setError('');
}}
disabled={isLoading}
className="flex-1"
>
Back
</Button>
<Button type="submit" disabled={isLoading || mfaCode.length !== 6} className="flex-1">
{isLoading && <Loader2 className="mr-2 h-4 w-4 animate-spin" />}
Verify
</Button>
</div>
</form>
);
}
return (
<form onSubmit={handleSubmit(onSubmit)} className="space-y-4">
{error && (
<Alert variant="destructive">
<AlertCircle className="h-4 w-4" />
<AlertDescription>{error}</AlertDescription>
</Alert>
)}
<div className="space-y-2">
<Label htmlFor="email">Email</Label>
<Input
id="email"
type="email"
placeholder="you@example.com"
{...register('email')}
disabled={isLoading}
/>
{errors.email && (
<p className="text-sm text-destructive">{errors.email.message}</p>
)}
</div>
<div className="space-y-2">
<Label htmlFor="password">Password</Label>
<Input
id="password"
type="password"
placeholder="••••••••"
{...register('password')}
disabled={isLoading}
/>
{errors.password && (
<p className="text-sm text-destructive">{errors.password.message}</p>
)}
</div>
<Button type="submit" className="w-full" disabled={isLoading}>
{isLoading && <Loader2 className="mr-2 h-4 w-4 animate-spin" />}
Sign In
</Button>
<div className="flex flex-col gap-2 text-sm text-center">
{onSwitchToReset && (
<button
type="button"
onClick={onSwitchToReset}
className="text-primary hover:underline"
disabled={isLoading}
>
Forgot password?
</button>
)}
{onSwitchToRegister && (
<button
type="button"
onClick={onSwitchToRegister}
className="text-muted-foreground hover:text-foreground"
disabled={isLoading}
>
Don't have an account? <span className="text-primary">Sign up</span>
</button>
)}
</div>
</form>
);
}

View File

@@ -0,0 +1,98 @@
'use client';
import { useState } from 'react';
import { oauthService } from '@/lib/services/auth';
import { Button } from '@/components/ui/button';
import { Alert, AlertDescription } from '@/components/ui/alert';
import { AlertCircle, Loader2 } from 'lucide-react';
interface OAuthButtonsProps {
redirectUrl?: string;
}
export function OAuthButtons({ redirectUrl = '/dashboard' }: OAuthButtonsProps) {
const [isLoading, setIsLoading] = useState<string | null>(null);
const [error, setError] = useState<string>('');
const handleOAuth = async (provider: 'google' | 'discord') => {
setError('');
setIsLoading(provider);
try {
await oauthService.initiateOAuth(provider, redirectUrl);
// User will be redirected to OAuth provider
} catch (err: any) {
setError(err.response?.data?.detail || `Failed to initiate ${provider} login`);
setIsLoading(null);
}
};
return (
<div className="space-y-3">
{error && (
<Alert variant="destructive">
<AlertCircle className="h-4 w-4" />
<AlertDescription>{error}</AlertDescription>
</Alert>
)}
<div className="relative">
<div className="absolute inset-0 flex items-center">
<span className="w-full border-t" />
</div>
<div className="relative flex justify-center text-xs uppercase">
<span className="bg-background px-2 text-muted-foreground">Or continue with</span>
</div>
</div>
<div className="grid grid-cols-2 gap-3">
<Button
type="button"
variant="outline"
onClick={() => handleOAuth('google')}
disabled={isLoading !== null}
>
{isLoading === 'google' ? (
<Loader2 className="mr-2 h-4 w-4 animate-spin" />
) : (
<svg className="mr-2 h-4 w-4" viewBox="0 0 24 24">
<path
fill="currentColor"
d="M22.56 12.25c0-.78-.07-1.53-.2-2.25H12v4.26h5.92c-.26 1.37-1.04 2.53-2.21 3.31v2.77h3.57c2.08-1.92 3.28-4.74 3.28-8.09z"
/>
<path
fill="currentColor"
d="M12 23c2.97 0 5.46-.98 7.28-2.66l-3.57-2.77c-.98.66-2.23 1.06-3.71 1.06-2.86 0-5.29-1.93-6.16-4.53H2.18v2.84C3.99 20.53 7.7 23 12 23z"
/>
<path
fill="currentColor"
d="M5.84 14.09c-.22-.66-.35-1.36-.35-2.09s.13-1.43.35-2.09V7.07H2.18C1.43 8.55 1 10.22 1 12s.43 3.45 1.18 4.93l2.85-2.22.81-.62z"
/>
<path
fill="currentColor"
d="M12 5.38c1.62 0 3.06.56 4.21 1.64l3.15-3.15C17.45 2.09 14.97 1 12 1 7.7 1 3.99 3.47 2.18 7.07l3.66 2.84c.87-2.6 3.3-4.53 6.16-4.53z"
/>
</svg>
)}
Google
</Button>
<Button
type="button"
variant="outline"
onClick={() => handleOAuth('discord')}
disabled={isLoading !== null}
>
{isLoading === 'discord' ? (
<Loader2 className="mr-2 h-4 w-4 animate-spin" />
) : (
<svg className="mr-2 h-4 w-4" viewBox="0 0 24 24" fill="currentColor">
<path d="M20.317 4.37a19.791 19.791 0 0 0-4.885-1.515a.074.074 0 0 0-.079.037c-.21.375-.444.864-.608 1.25a18.27 18.27 0 0 0-5.487 0a12.64 12.64 0 0 0-.617-1.25a.077.077 0 0 0-.079-.037A19.736 19.736 0 0 0 3.677 4.37a.07.07 0 0 0-.032.027C.533 9.046-.32 13.58.099 18.057a.082.082 0 0 0 .031.057a19.9 19.9 0 0 0 5.993 3.03a.078.078 0 0 0 .084-.028a14.09 14.09 0 0 0 1.226-1.994a.076.076 0 0 0-.041-.106a13.107 13.107 0 0 1-1.872-.892a.077.077 0 0 1-.008-.128a10.2 10.2 0 0 0 .372-.292a.074.074 0 0 1 .077-.01c3.928 1.793 8.18 1.793 12.062 0a.074.074 0 0 1 .078.01c.12.098.246.198.373.292a.077.077 0 0 1-.006.127a12.299 12.299 0 0 1-1.873.892a.077.077 0 0 0-.041.107c.36.698.772 1.362 1.225 1.993a.076.076 0 0 0 .084.028a19.839 19.839 0 0 0 6.002-3.03a.077.077 0 0 0 .032-.054c.5-5.177-.838-9.674-3.549-13.66a.061.061 0 0 0-.031-.03zM8.02 15.33c-1.183 0-2.157-1.085-2.157-2.419c0-1.333.956-2.419 2.157-2.419c1.21 0 2.176 1.096 2.157 2.42c0 1.333-.956 2.418-2.157 2.418zm7.975 0c-1.183 0-2.157-1.085-2.157-2.419c0-1.333.955-2.419 2.157-2.419c1.21 0 2.176 1.096 2.157 2.42c0 1.333-.946 2.418-2.157 2.418z" />
</svg>
)}
Discord
</Button>
</div>
</div>
);
}

View File

@@ -0,0 +1,234 @@
'use client';
import { useState } from 'react';
import { useForm } from 'react-hook-form';
import { zodResolver } from '@hookform/resolvers/zod';
import { z } from 'zod';
import { authService } from '@/lib/services/auth';
import { Button } from '@/components/ui/button';
import { Input } from '@/components/ui/input';
import { Label } from '@/components/ui/label';
import { Alert, AlertDescription } from '@/components/ui/alert';
import { AlertCircle, Loader2, CheckCircle2 } from 'lucide-react';
const resetRequestSchema = z.object({
email: z.string().email('Invalid email address'),
});
const resetConfirmSchema = z
.object({
password: z
.string()
.min(8, 'Password must be at least 8 characters')
.regex(/[A-Z]/, 'Password must contain at least one uppercase letter')
.regex(/[a-z]/, 'Password must contain at least one lowercase letter')
.regex(/[0-9]/, 'Password must contain at least one number'),
confirmPassword: z.string(),
})
.refine((data) => data.password === data.confirmPassword, {
message: "Passwords don't match",
path: ['confirmPassword'],
});
type ResetRequestData = z.infer<typeof resetRequestSchema>;
type ResetConfirmData = z.infer<typeof resetConfirmSchema>;
interface PasswordResetFormProps {
token?: string;
uid?: string;
onSuccess?: () => void;
onBack?: () => void;
}
export function PasswordResetForm({ token, uid, onSuccess, onBack }: PasswordResetFormProps) {
const [error, setError] = useState<string>('');
const [isLoading, setIsLoading] = useState(false);
const [success, setSuccess] = useState(false);
const {
register: registerRequest,
handleSubmit: handleSubmitRequest,
formState: { errors: requestErrors },
} = useForm<ResetRequestData>({
resolver: zodResolver(resetRequestSchema),
});
const {
register: registerConfirm,
handleSubmit: handleSubmitConfirm,
formState: { errors: confirmErrors },
} = useForm<ResetConfirmData>({
resolver: zodResolver(resetConfirmSchema),
});
const onSubmitRequest = async (data: ResetRequestData) => {
setError('');
setIsLoading(true);
try {
await authService.requestPasswordReset(data.email);
setSuccess(true);
} catch (err: any) {
setError(err.response?.data?.detail || 'Failed to send reset email');
} finally {
setIsLoading(false);
}
};
const onSubmitConfirm = async (data: ResetConfirmData) => {
if (!token || !uid) {
setError('Invalid reset link');
return;
}
setError('');
setIsLoading(true);
try {
await authService.confirmPasswordReset({
uid,
token,
new_password: data.password,
});
setSuccess(true);
setTimeout(() => {
onSuccess?.();
}, 2000);
} catch (err: any) {
setError(err.response?.data?.detail || 'Failed to reset password');
} finally {
setIsLoading(false);
}
};
if (success) {
return (
<div className="text-center space-y-4">
<div className="mx-auto w-12 h-12 rounded-full bg-green-100 dark:bg-green-900 flex items-center justify-center">
<CheckCircle2 className="h-6 w-6 text-green-600 dark:text-green-400" />
</div>
<div>
<h3 className="text-lg font-semibold">
{token ? 'Password Reset!' : 'Email Sent!'}
</h3>
<p className="text-sm text-muted-foreground mt-2">
{token
? 'Your password has been reset successfully. You can now log in with your new password.'
: 'Check your email for a link to reset your password. If it doesn\'t appear within a few minutes, check your spam folder.'}
</p>
</div>
{onBack && (
<Button onClick={onBack} variant="outline">
Back to Login
</Button>
)}
</div>
);
}
// Reset confirmation form (when token and uid are provided)
if (token && uid) {
return (
<form onSubmit={handleSubmitConfirm(onSubmitConfirm)} className="space-y-4">
<div className="text-center mb-4">
<h3 className="text-lg font-semibold">Set New Password</h3>
<p className="text-sm text-muted-foreground mt-1">
Enter your new password below
</p>
</div>
{error && (
<Alert variant="destructive">
<AlertCircle className="h-4 w-4" />
<AlertDescription>{error}</AlertDescription>
</Alert>
)}
<div className="space-y-2">
<Label htmlFor="password">New Password</Label>
<Input
id="password"
type="password"
placeholder="••••••••"
{...registerConfirm('password')}
disabled={isLoading}
/>
{confirmErrors.password && (
<p className="text-sm text-destructive">{confirmErrors.password.message}</p>
)}
</div>
<div className="space-y-2">
<Label htmlFor="confirmPassword">Confirm Password</Label>
<Input
id="confirmPassword"
type="password"
placeholder="••••••••"
{...registerConfirm('confirmPassword')}
disabled={isLoading}
/>
{confirmErrors.confirmPassword && (
<p className="text-sm text-destructive">{confirmErrors.confirmPassword.message}</p>
)}
</div>
<Button type="submit" className="w-full" disabled={isLoading}>
{isLoading && <Loader2 className="mr-2 h-4 w-4 animate-spin" />}
Reset Password
</Button>
</form>
);
}
// Reset request form (default)
return (
<form onSubmit={handleSubmitRequest(onSubmitRequest)} className="space-y-4">
<div className="text-center mb-4">
<h3 className="text-lg font-semibold">Reset Password</h3>
<p className="text-sm text-muted-foreground mt-1">
Enter your email and we'll send you a link to reset your password
</p>
</div>
{error && (
<Alert variant="destructive">
<AlertCircle className="h-4 w-4" />
<AlertDescription>{error}</AlertDescription>
</Alert>
)}
<div className="space-y-2">
<Label htmlFor="email">Email</Label>
<Input
id="email"
type="email"
placeholder="you@example.com"
{...registerRequest('email')}
disabled={isLoading}
autoFocus
/>
{requestErrors.email && (
<p className="text-sm text-destructive">{requestErrors.email.message}</p>
)}
</div>
<Button type="submit" className="w-full" disabled={isLoading}>
{isLoading && <Loader2 className="mr-2 h-4 w-4 animate-spin" />}
Send Reset Link
</Button>
{onBack && (
<div className="text-sm text-center">
<button
type="button"
onClick={onBack}
className="text-muted-foreground hover:text-foreground"
disabled={isLoading}
>
Back to login
</button>
</div>
)}
</form>
);
}

View File

@@ -0,0 +1,197 @@
'use client';
import { useState } from 'react';
import { useForm } from 'react-hook-form';
import { zodResolver } from '@hookform/resolvers/zod';
import { z } from 'zod';
import { useAuth } from '@/lib/contexts/AuthContext';
import { Button } from '@/components/ui/button';
import { Input } from '@/components/ui/input';
import { Label } from '@/components/ui/label';
import { Alert, AlertDescription } from '@/components/ui/alert';
import { AlertCircle, Loader2, CheckCircle2 } from 'lucide-react';
const registerSchema = z
.object({
email: z.string().email('Invalid email address'),
password: z
.string()
.min(8, 'Password must be at least 8 characters')
.regex(/[A-Z]/, 'Password must contain at least one uppercase letter')
.regex(/[a-z]/, 'Password must contain at least one lowercase letter')
.regex(/[0-9]/, 'Password must contain at least one number'),
confirmPassword: z.string(),
username: z
.string()
.min(3, 'Username must be at least 3 characters')
.max(30, 'Username must be at most 30 characters')
.regex(/^[a-zA-Z0-9_-]+$/, 'Username can only contain letters, numbers, hyphens, and underscores'),
})
.refine((data) => data.password === data.confirmPassword, {
message: "Passwords don't match",
path: ['confirmPassword'],
});
type RegisterFormData = z.infer<typeof registerSchema>;
interface RegisterFormProps {
onSuccess?: () => void;
onSwitchToLogin?: () => void;
}
export function RegisterForm({ onSuccess, onSwitchToLogin }: RegisterFormProps) {
const { register: registerUser } = useAuth();
const [error, setError] = useState<string>('');
const [isLoading, setIsLoading] = useState(false);
const [success, setSuccess] = useState(false);
const {
register,
handleSubmit,
formState: { errors },
watch,
} = useForm<RegisterFormData>({
resolver: zodResolver(registerSchema),
});
const password = watch('password', '');
const onSubmit = async (data: RegisterFormData) => {
setError('');
setIsLoading(true);
try {
await registerUser({
email: data.email,
password: data.password,
username: data.username,
});
setSuccess(true);
setTimeout(() => {
onSuccess?.();
}, 2000);
} catch (err: any) {
const errorMessage = err.response?.data?.detail || err.message || 'Registration failed';
setError(errorMessage);
} finally {
setIsLoading(false);
}
};
if (success) {
return (
<div className="text-center space-y-4">
<div className="mx-auto w-12 h-12 rounded-full bg-green-100 dark:bg-green-900 flex items-center justify-center">
<CheckCircle2 className="h-6 w-6 text-green-600 dark:text-green-400" />
</div>
<div>
<h3 className="text-lg font-semibold">Account Created!</h3>
<p className="text-sm text-muted-foreground mt-2">
Your account has been successfully created. Redirecting to dashboard...
</p>
</div>
</div>
);
}
return (
<form onSubmit={handleSubmit(onSubmit)} className="space-y-4">
{error && (
<Alert variant="destructive">
<AlertCircle className="h-4 w-4" />
<AlertDescription>{error}</AlertDescription>
</Alert>
)}
<div className="space-y-2">
<Label htmlFor="username">Username</Label>
<Input
id="username"
type="text"
placeholder="johndoe"
{...register('username')}
disabled={isLoading}
/>
{errors.username && (
<p className="text-sm text-destructive">{errors.username.message}</p>
)}
</div>
<div className="space-y-2">
<Label htmlFor="email">Email</Label>
<Input
id="email"
type="email"
placeholder="you@example.com"
{...register('email')}
disabled={isLoading}
/>
{errors.email && (
<p className="text-sm text-destructive">{errors.email.message}</p>
)}
</div>
<div className="space-y-2">
<Label htmlFor="password">Password</Label>
<Input
id="password"
type="password"
placeholder="••••••••"
{...register('password')}
disabled={isLoading}
/>
{errors.password && (
<p className="text-sm text-destructive">{errors.password.message}</p>
)}
{password && (
<div className="space-y-1 text-xs">
<p className={password.length >= 8 ? 'text-green-600' : 'text-muted-foreground'}>
{password.length >= 8 ? '✓' : '○'} At least 8 characters
</p>
<p className={/[A-Z]/.test(password) ? 'text-green-600' : 'text-muted-foreground'}>
{/[A-Z]/.test(password) ? '✓' : '○'} One uppercase letter
</p>
<p className={/[a-z]/.test(password) ? 'text-green-600' : 'text-muted-foreground'}>
{/[a-z]/.test(password) ? '✓' : '○'} One lowercase letter
</p>
<p className={/[0-9]/.test(password) ? 'text-green-600' : 'text-muted-foreground'}>
{/[0-9]/.test(password) ? '✓' : '○'} One number
</p>
</div>
)}
</div>
<div className="space-y-2">
<Label htmlFor="confirmPassword">Confirm Password</Label>
<Input
id="confirmPassword"
type="password"
placeholder="••••••••"
{...register('confirmPassword')}
disabled={isLoading}
/>
{errors.confirmPassword && (
<p className="text-sm text-destructive">{errors.confirmPassword.message}</p>
)}
</div>
<Button type="submit" className="w-full" disabled={isLoading}>
{isLoading && <Loader2 className="mr-2 h-4 w-4 animate-spin" />}
Create Account
</Button>
{onSwitchToLogin && (
<div className="text-sm text-center">
<button
type="button"
onClick={onSwitchToLogin}
className="text-muted-foreground hover:text-foreground"
disabled={isLoading}
>
Already have an account? <span className="text-primary">Sign in</span>
</button>
</div>
)}
</form>
);
}

View File

@@ -0,0 +1,93 @@
'use client';
/**
* User Navigation Component
*
* Displays login/register buttons when logged out
* Displays user menu with logout when logged in
*/
import { useAuth } from '@/lib/contexts/AuthContext';
import { Button } from '@/components/ui/button';
import { useState } from 'react';
import { useRouter } from 'next/navigation';
import { AuthModal } from './AuthModal';
export function UserNav() {
const { user, isAuthenticated, isLoading, logout } = useAuth();
const router = useRouter();
const [showAuthModal, setShowAuthModal] = useState(false);
const [authMode, setAuthMode] = useState<'login' | 'register'>('login');
const handleLogout = async () => {
await logout();
router.push('/');
};
const handleLogin = () => {
setAuthMode('login');
setShowAuthModal(true);
};
const handleRegister = () => {
setAuthMode('register');
setShowAuthModal(true);
};
if (isLoading) {
return (
<div className="flex items-center gap-2">
<div className="h-9 w-20 animate-pulse bg-gray-200 rounded-md"></div>
</div>
);
}
if (isAuthenticated && user) {
return (
<div className="flex items-center gap-4">
<div className="flex items-center gap-2">
<div className="flex flex-col items-end">
<span className="text-sm font-medium">{user.username}</span>
<span className="text-xs text-gray-500">{user.email}</span>
</div>
<div className="h-9 w-9 rounded-full bg-gradient-to-br from-blue-500 to-purple-600 flex items-center justify-center text-white font-semibold">
{user.username.charAt(0).toUpperCase()}
</div>
</div>
<Button
onClick={handleLogout}
variant="outline"
size="sm"
>
Logout
</Button>
</div>
);
}
return (
<>
<div className="flex items-center gap-2">
<Button
onClick={handleLogin}
variant="outline"
size="sm"
>
Login
</Button>
<Button
onClick={handleRegister}
size="sm"
>
Sign Up
</Button>
</div>
<AuthModal
isOpen={showAuthModal}
onClose={() => setShowAuthModal(false)}
defaultMode={authMode}
/>
</>
);
}