BusyBee3333 4e6467ffb0 Add CRESync CRM application with Setup page
- Build complete Next.js CRM for commercial real estate
- Add authentication with JWT sessions and role-based access
- Add GoHighLevel API integration for contacts, conversations, opportunities
- Add AI-powered Control Center with tool calling
- Add Setup page with onboarding checklist (/setup)
- Add sidebar navigation with Setup menu item
- Fix type errors in onboarding API, GHL services, and control center tools
- Add Prisma schema with SQLite for local development
- Add UI components with clay morphism design system

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
2026-01-14 17:30:55 -05:00

267 lines
9.6 KiB
TypeScript

'use client';
import { useState } from 'react';
import { useRouter } from 'next/navigation';
import Link from 'next/link';
import { useAuth } from '@/lib/hooks/useAuth';
import { Building2, Mail, Lock, User, Briefcase, ArrowRight, Loader2 } from 'lucide-react';
export default function SignupPage() {
const [formData, setFormData] = useState({
email: '',
password: '',
confirmPassword: '',
firstName: '',
lastName: '',
brokerage: '',
});
const [error, setError] = useState('');
const [loading, setLoading] = useState(false);
const [agreed, setAgreed] = useState(false);
const router = useRouter();
const { signup } = useAuth();
const handleSubmit = async (e: React.FormEvent) => {
e.preventDefault();
setError('');
if (!agreed) {
setError('Please agree to the Terms of Service and Privacy Policy');
return;
}
if (formData.password !== formData.confirmPassword) {
setError('Passwords do not match');
return;
}
if (formData.password.length < 8) {
setError('Password must be at least 8 characters');
return;
}
setLoading(true);
try {
await signup({
email: formData.email,
password: formData.password,
firstName: formData.firstName,
lastName: formData.lastName,
});
router.push('/onboarding');
} catch (err: any) {
setError(err.message || 'Signup failed');
} finally {
setLoading(false);
}
};
return (
<div className="min-h-screen flex items-center justify-center p-4 py-8">
<div className="w-full max-w-md">
{/* Logo */}
<div className="text-center mb-8 animate-fade-in-up">
<Link href="/" className="inline-flex items-center gap-3 group">
<div className="w-14 h-14 clay-avatar">
<Building2 className="w-7 h-7" />
</div>
<div className="text-left">
<span className="text-2xl font-bold text-foreground">CRESync</span>
<p className="text-xs text-muted-foreground">Commercial Real Estate CRM</p>
</div>
</Link>
</div>
{/* Signup Card */}
<div className="clay-card p-8 animate-fade-in-scale" style={{ animationDelay: '0.1s' }}>
<div className="text-center mb-6">
<h1 className="text-2xl font-bold text-foreground mb-2">Create Account</h1>
<p className="text-muted-foreground">Start managing your CRE business</p>
</div>
<form onSubmit={handleSubmit} className="space-y-4">
{error && (
<div className="p-4 rounded-xl bg-error-soft text-error text-sm animate-fade-in-up">
{error}
</div>
)}
{/* Name Fields */}
<div className="grid grid-cols-2 gap-4">
<div className="space-y-2">
<label htmlFor="firstName" className="block text-sm font-medium text-foreground">
First Name
</label>
<div className="relative">
<User className="absolute left-4 top-1/2 -translate-y-1/2 w-5 h-5 text-muted-foreground" />
<input
id="firstName"
type="text"
placeholder="John"
value={formData.firstName}
onChange={(e) => setFormData({ ...formData, firstName: e.target.value })}
className="w-full clay-input clay-input-icon"
required
/>
</div>
</div>
<div className="space-y-2">
<label htmlFor="lastName" className="block text-sm font-medium text-foreground">
Last Name
</label>
<input
id="lastName"
type="text"
placeholder="Doe"
value={formData.lastName}
onChange={(e) => setFormData({ ...formData, lastName: e.target.value })}
className="w-full clay-input"
required
/>
</div>
</div>
{/* Email */}
<div className="space-y-2">
<label htmlFor="email" className="block text-sm font-medium text-foreground">
Email Address
</label>
<div className="relative">
<Mail className="absolute left-4 top-1/2 -translate-y-1/2 w-5 h-5 text-muted-foreground" />
<input
id="email"
type="email"
placeholder="name@company.com"
value={formData.email}
onChange={(e) => setFormData({ ...formData, email: e.target.value })}
className="w-full clay-input clay-input-icon"
required
/>
</div>
</div>
{/* Brokerage */}
<div className="space-y-2">
<label htmlFor="brokerage" className="block text-sm font-medium text-foreground">
Brokerage <span className="text-muted-foreground font-normal">(Optional)</span>
</label>
<div className="relative">
<Briefcase className="absolute left-4 top-1/2 -translate-y-1/2 w-5 h-5 text-muted-foreground" />
<input
id="brokerage"
type="text"
placeholder="Your Company"
value={formData.brokerage}
onChange={(e) => setFormData({ ...formData, brokerage: e.target.value })}
className="w-full clay-input clay-input-icon"
/>
</div>
</div>
{/* Password */}
<div className="space-y-2">
<label htmlFor="password" className="block text-sm font-medium text-foreground">
Password
</label>
<div className="relative">
<Lock className="absolute left-4 top-1/2 -translate-y-1/2 w-5 h-5 text-muted-foreground" />
<input
id="password"
type="password"
placeholder="Min 8 characters"
value={formData.password}
onChange={(e) => setFormData({ ...formData, password: e.target.value })}
className="w-full clay-input clay-input-icon"
required
minLength={8}
/>
</div>
</div>
{/* Confirm Password */}
<div className="space-y-2">
<label htmlFor="confirmPassword" className="block text-sm font-medium text-foreground">
Confirm Password
</label>
<div className="relative">
<Lock className="absolute left-4 top-1/2 -translate-y-1/2 w-5 h-5 text-muted-foreground" />
<input
id="confirmPassword"
type="password"
placeholder="Confirm your password"
value={formData.confirmPassword}
onChange={(e) => setFormData({ ...formData, confirmPassword: e.target.value })}
className="w-full clay-input clay-input-icon"
required
/>
</div>
</div>
{/* Terms Agreement */}
<div className="flex items-start gap-3 py-2">
<button
type="button"
onClick={() => setAgreed(!agreed)}
className={`w-5 h-5 rounded-md transition-all flex-shrink-0 mt-0.5 ${
agreed ? 'bg-primary' : 'clay-card-pressed'
}`}
>
{agreed && (
<svg className="w-5 h-5 text-white" fill="none" viewBox="0 0 24 24" stroke="currentColor">
<path strokeLinecap="round" strokeLinejoin="round" strokeWidth={2} d="M5 13l4 4L19 7" />
</svg>
)}
</button>
<label
onClick={() => setAgreed(!agreed)}
className="text-sm text-muted-foreground cursor-pointer leading-relaxed"
>
I agree to the{' '}
<Link href="/terms" className="text-primary hover:underline">
Terms of Service
</Link>{' '}
and{' '}
<Link href="/privacy" className="text-primary hover:underline">
Privacy Policy
</Link>
</label>
</div>
<button
type="submit"
disabled={loading}
className="w-full py-3.5 clay-btn-primary flex items-center justify-center gap-2 disabled:opacity-50"
>
{loading ? (
<>
<Loader2 className="w-5 h-5 animate-spin" />
Creating account...
</>
) : (
<>
Create Account
<ArrowRight className="w-5 h-5" />
</>
)}
</button>
</form>
<div className="clay-divider my-6" />
<p className="text-center text-sm text-muted-foreground">
Already have an account?{' '}
<Link href="/login" className="text-primary font-medium hover:underline">
Sign in
</Link>
</p>
</div>
{/* Footer */}
<p className="text-center text-xs text-muted-foreground mt-6 animate-fade-in-up" style={{ animationDelay: '0.3s' }}>
Protected by industry-standard encryption
</p>
</div>
</div>
);
}