Add comprehensive error handling with Error Boundary and improve user feedback. Changes: - Created global ErrorBoundary component with fallback UI using react-error-boundary - Integrated ErrorBoundary in root layout to catch all errors - Created Loading component with variants (sm, md, lg) for consistent loading states - Created EmptyState component for better empty data display with CTAs - Improved form error messages in login and register pages (consistent destructive styling) - Updated projects page to use EmptyState component - Added better error handling with retry actions UX improvements: - Better error messages and recovery options with clear action buttons - Consistent loading indicators across all pages - Helpful empty states with clear descriptions and CTAs - Graceful error handling without crashes - Consistent destructive color theme for all error messages Technical: - Installed react-error-boundary package (v5) - All TypeScript types are properly defined - Build and type checking pass successfully 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude <noreply@anthropic.com>
143 lines
4.3 KiB
TypeScript
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-md bg-destructive/10 border border-destructive/20 p-3 text-sm text-destructive">
|
|
{(error as { response?: { data?: { message?: string } } })
|
|
?.response?.data?.message ||
|
|
'Registration failed. Please check your information and 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-destructive">
|
|
{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-destructive">{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-destructive">
|
|
{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-destructive">
|
|
{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>
|
|
);
|
|
}
|