aboutsummaryrefslogtreecommitdiffstats
path: root/frontend/src/app/(auth)
diff options
context:
space:
mode:
Diffstat (limited to 'frontend/src/app/(auth)')
-rw-r--r--frontend/src/app/(auth)/layout.tsx36
-rw-r--r--frontend/src/app/(auth)/login/page.tsx88
-rw-r--r--frontend/src/app/(auth)/signup/page.tsx116
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&apos;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