Files
ColaFlow-Web/app/(auth)/login/page.tsx
Yaojia Wang 2b134b0d6f feat(frontend): Implement Phase 2 - Complete Projects UI with CRUD operations
Implemented comprehensive Projects UI with full CRUD functionality following
modern React best practices and using shadcn/ui components.

Changes:
- Created ProjectForm component with react-hook-form + zod validation
  - Auto-uppercase project key input
  - Comprehensive field validation (name, key, description)
  - Support for both create and edit modes
  - Toast notifications for success/error states

- Enhanced Projects List Page (app/(dashboard)/projects/page.tsx)
  - Beautiful card-based grid layout with hover effects
  - Skeleton loading states for better UX
  - Empty state with call-to-action
  - Project metadata display (key badge, created date)
  - Integrated ProjectForm in Dialog for creation

- Enhanced Project Detail Page (app/(dashboard)/projects/[id]/page.tsx)
  - Comprehensive project information display
  - Edit functionality with dialog form
  - Delete functionality with confirmation AlertDialog
  - Epics preview section with stats
  - Quick actions sidebar (Kanban, Epics)
  - Statistics card (Total/Active/Completed epics)
  - Skeleton loading states
  - Error handling with retry capability

- Added toast notifications (Sonner)
  - Installed and configured sonner package
  - Added Toaster component to root layout
  - Success/error notifications for all CRUD operations

- Installed required dependencies
  - date-fns for date formatting
  - sonner for toast notifications
  - shadcn/ui alert-dialog component

Technical highlights:
- TypeScript with strict type checking
- React Query for data fetching and caching
- Optimistic updates with automatic rollback
- Responsive design (mobile-friendly)
- Accessibility-focused components
- Clean error handling throughout

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

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

122 lines
3.7 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 { useLogin } from '@/lib/hooks/useAuth';
import { Button } from '@/components/ui/button';
import { Input } from '@/components/ui/input';
import { Label } from '@/components/ui/label';
import { useSearchParams } from 'next/navigation';
const loginSchema = z.object({
email: z.string().email('Invalid email address'),
password: z.string().min(8, 'Password must be at least 8 characters'),
tenantSlug: z.string().min(1, 'Tenant slug is required'),
});
type LoginForm = z.infer<typeof loginSchema>;
export default function LoginPage() {
const searchParams = useSearchParams();
const registered = searchParams.get('registered');
const { mutate: login, isPending, error } = useLogin();
const {
register,
handleSubmit,
formState: { errors },
} = useForm<LoginForm>({
resolver: zodResolver(loginSchema),
});
const onSubmit = (data: LoginForm) => {
login(data);
};
return (
<div className="flex min-h-screen items-center justify-center bg-gray-50 px-4">
<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">Sign in to your account</p>
</div>
<form
onSubmit={handleSubmit(onSubmit)}
className="mt-8 space-y-6 rounded-lg bg-white p-8 shadow"
>
{registered && (
<div className="rounded bg-green-50 p-3 text-sm text-green-600">
Registration successful! Please sign in.
</div>
)}
{error && (
<div className="rounded bg-red-50 p-3 text-sm text-red-600">
{(error as { response?: { data?: { message?: string } } })
?.response?.data?.message || 'Login failed. Please try again.'}
</div>
)}
<div>
<Label htmlFor="tenantSlug">Tenant Slug</Label>
<Input
id="tenantSlug"
type="text"
{...register('tenantSlug')}
className="mt-1"
placeholder="your-company"
/>
{errors.tenantSlug && (
<p className="mt-1 text-sm text-red-600">{errors.tenantSlug.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>
)}
</div>
<Button type="submit" className="w-full" disabled={isPending}>
{isPending ? 'Signing in...' : 'Sign in'}
</Button>
<div className="text-center text-sm">
<Link href="/register" className="text-blue-600 hover:underline">
Don&apos;t have an account? Sign up
</Link>
</div>
</form>
</div>
</div>
);
}