Files
ColaFlow-Web/app/(auth)/register/page.tsx
Yaojia Wang e60b70de52 feat(frontend): Implement complete authentication system
Implemented comprehensive JWT-based authentication with token refresh mechanism, user state management, and protected routes.

Changes:
- Upgraded API client from fetch to Axios with automatic token refresh interceptors
- Created API configuration with centralized endpoint definitions
- Implemented Zustand auth store for user state management with persistence
- Created React Query hooks for login, register, logout, and current user
- Built login and registration pages with form validation (Zod + React Hook Form)
- Implemented AuthGuard component for route protection
- Enhanced Header with user dropdown menu and logout functionality
- Updated Sidebar with user information display at bottom
- Added Team navigation item to sidebar
- Configured environment variables for API base URL

Technical Details:
- JWT token storage in localStorage with secure key names
- Automatic token refresh on 401 responses
- Request queueing during token refresh to prevent race conditions
- TypeScript strict typing throughout
- ESLint compliant code (fixed type safety issues)
- Proper error handling with user-friendly messages

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude <noreply@anthropic.com>
2025-11-04 09:09:09 +01:00

143 lines
4.3 KiB
TypeScript

'use client';
import { useForm } from 'react-hook-form';
import { zodResolver } from '@hookform/resolvers/zod';
import { z } from 'zod';
import Link from 'next/link';
import { useRegisterTenant } from '@/lib/hooks/useAuth';
import { Button } from '@/components/ui/button';
import { Input } from '@/components/ui/input';
import { Label } from '@/components/ui/label';
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])(?=.*[A-Z])(?=.*\d)/,
'Password must contain uppercase, lowercase, and number'
),
fullName: z.string().min(2, 'Full name must be at least 2 characters'),
tenantName: z
.string()
.min(2, 'Organization name must be at least 2 characters'),
});
type RegisterForm = z.infer<typeof registerSchema>;
export default function RegisterPage() {
const { mutate: registerTenant, isPending, error } = useRegisterTenant();
const {
register,
handleSubmit,
formState: { errors },
} = useForm<RegisterForm>({
resolver: zodResolver(registerSchema),
});
const onSubmit = (data: RegisterForm) => {
registerTenant(data);
};
return (
<div className="flex min-h-screen items-center justify-center bg-gray-50 px-4 py-12">
<div className="w-full max-w-md space-y-8">
<div className="text-center">
<h1 className="text-3xl font-bold">ColaFlow</h1>
<p className="mt-2 text-gray-600">Create your account</p>
</div>
<form
onSubmit={handleSubmit(onSubmit)}
className="mt-8 space-y-6 rounded-lg bg-white p-8 shadow"
>
{error && (
<div className="rounded bg-red-50 p-3 text-sm text-red-600">
{(error as { response?: { data?: { message?: string } } })
?.response?.data?.message ||
'Registration failed. Please try again.'}
</div>
)}
<div>
<Label htmlFor="fullName">Full Name</Label>
<Input
id="fullName"
type="text"
{...register('fullName')}
className="mt-1"
placeholder="John Doe"
/>
{errors.fullName && (
<p className="mt-1 text-sm text-red-600">
{errors.fullName.message}
</p>
)}
</div>
<div>
<Label htmlFor="email">Email</Label>
<Input
id="email"
type="email"
{...register('email')}
className="mt-1"
placeholder="you@example.com"
/>
{errors.email && (
<p className="mt-1 text-sm text-red-600">{errors.email.message}</p>
)}
</div>
<div>
<Label htmlFor="password">Password</Label>
<Input
id="password"
type="password"
{...register('password')}
className="mt-1"
placeholder="••••••••"
/>
{errors.password && (
<p className="mt-1 text-sm text-red-600">
{errors.password.message}
</p>
)}
<p className="mt-1 text-xs text-gray-500">
Must contain uppercase, lowercase, and number
</p>
</div>
<div>
<Label htmlFor="tenantName">Organization Name</Label>
<Input
id="tenantName"
type="text"
{...register('tenantName')}
className="mt-1"
placeholder="Acme Inc."
/>
{errors.tenantName && (
<p className="mt-1 text-sm text-red-600">
{errors.tenantName.message}
</p>
)}
</div>
<Button type="submit" className="w-full" disabled={isPending}>
{isPending ? 'Creating account...' : 'Create account'}
</Button>
<div className="text-center text-sm">
<Link href="/login" className="text-blue-600 hover:underline">
Already have an account? Sign in
</Link>
</div>
</form>
</div>
</div>
);
}