diff options
Diffstat (limited to 'frontend/src/app/(auth)')
-rw-r--r-- | frontend/src/app/(auth)/layout.tsx | 36 | ||||
-rw-r--r-- | frontend/src/app/(auth)/login/page.tsx | 88 | ||||
-rw-r--r-- | frontend/src/app/(auth)/signup/page.tsx | 116 |
3 files changed, 240 insertions, 0 deletions
diff --git a/frontend/src/app/(auth)/layout.tsx b/frontend/src/app/(auth)/layout.tsx new file mode 100644 index 0000000..9651b4b --- /dev/null +++ b/frontend/src/app/(auth)/layout.tsx @@ -0,0 +1,36 @@ +'use client'; + +import { useEffect } from 'react'; +import { useRouter } from 'next/navigation'; + +export default function AuthLayout({ + children, +}: Readonly<{ + children: React.ReactNode; +}>) { + const router = useRouter(); + + useEffect(() => { + // If already logged in, redirect to dashboard + const token = localStorage.getItem('token'); + if (token) { + router.push('/dashboard'); + } + }, [router]); + + return ( + <div className="flex min-h-screen flex-col items-center justify-center bg-muted/40"> + <div className="w-full max-w-md rounded-lg border bg-card p-6 shadow-sm"> + <div className="flex flex-col space-y-2 text-center mb-6"> + <h1 className="text-2xl font-semibold tracking-tight"> + Finance Management + </h1> + <p className="text-sm text-muted-foreground"> + Manage your personal finances + </p> + </div> + {children} + </div> + </div> + ); +}
\ No newline at end of file diff --git a/frontend/src/app/(auth)/login/page.tsx b/frontend/src/app/(auth)/login/page.tsx new file mode 100644 index 0000000..7460b8a --- /dev/null +++ b/frontend/src/app/(auth)/login/page.tsx @@ -0,0 +1,88 @@ +'use client'; + +import { useState, useEffect } from 'react'; +import { useRouter, useSearchParams } from 'next/navigation'; +import Link from 'next/link'; +import { Button } from '@/components/ui/button'; +import { Input } from '@/components/ui/input'; +import { Label } from '@/components/ui/label'; +import { authApi } from '@/lib/api'; +import { useNotification } from '@/components/shared/NotificationContext'; + +export default function LoginPage() { + const router = useRouter(); + const searchParams = useSearchParams(); + const { showNotification } = useNotification(); + const [email, setEmail] = useState(''); + const [password, setPassword] = useState(''); + const [error, setError] = useState(''); + const [isLoading, setIsLoading] = useState(false); + + useEffect(() => { + // Check if the user was redirected from signup + if (searchParams.get('signup') === 'success') { + showNotification('success', 'Account created successfully! Please log in.'); + } + }, [searchParams, showNotification]); + + const handleSubmit = async (e: React.FormEvent) => { + e.preventDefault(); + setError(''); + setIsLoading(true); + + try { + await authApi.login(email, password); + showNotification('success', 'Logged in successfully!'); + router.push('/dashboard'); + } catch (err: any) { + setError(err.message || 'Login failed'); + } finally { + setIsLoading(false); + } + }; + + return ( + <div> + <form onSubmit={handleSubmit} className="space-y-4"> + <div className="space-y-2"> + <Label htmlFor="email">Email</Label> + <Input + id="email" + type="email" + placeholder="your@email.com" + value={email} + onChange={(e) => setEmail(e.target.value)} + required + /> + </div> + <div className="space-y-2"> + <Label htmlFor="password">Password</Label> + <Input + id="password" + type="password" + value={password} + onChange={(e) => setPassword(e.target.value)} + required + /> + </div> + + {error && ( + <div className="text-sm text-red-500"> + {error} + </div> + )} + + <Button type="submit" className="w-full" disabled={isLoading}> + {isLoading ? 'Logging in...' : 'Login'} + </Button> + </form> + + <div className="mt-4 text-center text-sm"> + Don't have an account?{' '} + <Link href="/signup" className="text-primary underline underline-offset-2"> + Sign up + </Link> + </div> + </div> + ); +}
\ No newline at end of file diff --git a/frontend/src/app/(auth)/signup/page.tsx b/frontend/src/app/(auth)/signup/page.tsx new file mode 100644 index 0000000..af25031 --- /dev/null +++ b/frontend/src/app/(auth)/signup/page.tsx @@ -0,0 +1,116 @@ +'use client'; + +import { useState } from 'react'; +import { useRouter } from 'next/navigation'; +import Link from 'next/link'; +import { Button } from '@/components/ui/button'; +import { Input } from '@/components/ui/input'; +import { Label } from '@/components/ui/label'; +import { authApi } from '@/lib/api'; +import { useNotification } from '@/components/shared/NotificationContext'; + +export default function SignupPage() { + const router = useRouter(); + const { showNotification } = useNotification(); + const [name, setName] = useState(''); + const [email, setEmail] = useState(''); + const [password, setPassword] = useState(''); + const [confirmPassword, setConfirmPassword] = useState(''); + const [error, setError] = useState(''); + const [isLoading, setIsLoading] = useState(false); + + const handleSubmit = async (e: React.FormEvent) => { + e.preventDefault(); + setError(''); + + // Validate passwords match + if (password !== confirmPassword) { + setError('Passwords do not match'); + return; + } + + setIsLoading(true); + + try { + await authApi.signup(name, email, password); + // Show success notification + showNotification('success', `Your email ${email} has been successfully registered!`); + // Redirect to login after successful signup with a slight delay to see the notification + setTimeout(() => { + router.push('/login?signup=success'); + }, 1500); + } catch (err: any) { + setError(err.message || 'Signup failed'); + } finally { + setIsLoading(false); + } + }; + + return ( + <div> + <form onSubmit={handleSubmit} className="space-y-4"> + <div className="space-y-2"> + <Label htmlFor="name">Name</Label> + <Input + id="name" + placeholder="Your Name" + value={name} + onChange={(e) => setName(e.target.value)} + required + /> + </div> + + <div className="space-y-2"> + <Label htmlFor="email">Email</Label> + <Input + id="email" + type="email" + placeholder="your@email.com" + value={email} + onChange={(e) => setEmail(e.target.value)} + required + /> + </div> + + <div className="space-y-2"> + <Label htmlFor="password">Password</Label> + <Input + id="password" + type="password" + value={password} + onChange={(e) => setPassword(e.target.value)} + required + /> + </div> + + <div className="space-y-2"> + <Label htmlFor="confirmPassword">Confirm Password</Label> + <Input + id="confirmPassword" + type="password" + value={confirmPassword} + onChange={(e) => setConfirmPassword(e.target.value)} + required + /> + </div> + + {error && ( + <div className="text-sm text-red-500"> + {error} + </div> + )} + + <Button type="submit" className="w-full" disabled={isLoading}> + {isLoading ? 'Creating Account...' : 'Create Account'} + </Button> + </form> + + <div className="mt-4 text-center text-sm"> + Already have an account?{' '} + <Link href="/login" className="text-primary underline underline-offset-2"> + Login + </Link> + </div> + </div> + ); +}
\ No newline at end of file |