207 lines
9.5 KiB
TypeScript
207 lines
9.5 KiB
TypeScript
// src/app/register/page.tsx\
|
|
'use client';
|
|
import { useState } from 'react';
|
|
import { useForm } from 'react-hook-form';
|
|
import { zodResolver } from '@hookform/resolvers/zod';
|
|
import * as z from 'zod';
|
|
import Link from 'next/link';
|
|
import { Mail, Lock, User } from 'lucide-react';
|
|
import { useAuth } from '@/lib/hooks/useAuth';
|
|
import { OrganizationDetailsFields } from '@/components/ui/auth/OrganizationDetailsFields';
|
|
import { RegistrationProgressSteps } from '@/components/ui/auth/RegistrationProgressSteps';
|
|
import { Button } from '@/components/ui/common/Button';
|
|
import { Input } from '@/components/ui/common/Input';
|
|
const registerSchema = z.object({
|
|
name: z.string().min(2, 'Name must be at least 2 characters'),
|
|
email: z.string().email('Please enter a valid email address'),
|
|
password: z.string()
|
|
.min(8, 'Password must be at least 8 characters')
|
|
.regex(/[A-Z]/, 'Password must contain at least one uppercase letter')
|
|
.regex(/[0-9]/, 'Password must contain at least one number'),
|
|
confirmPassword: z.string(),
|
|
organizationName: z.string().min(2, 'Organization name must be at least 2 characters'),
|
|
organizationEmail: z.string().email('Please enter a valid organization email'),
|
|
organizationType: z.enum(['CLINIC', 'LAB'], {
|
|
message: 'Please select organization type',
|
|
}),
|
|
}).refine((data) => data.password === data.confirmPassword, {
|
|
message: "Passwords don't match",
|
|
path: ['confirmPassword'],
|
|
});
|
|
type RegisterForm = z.infer<typeof registerSchema>;
|
|
|
|
export default function RegisterPage() {
|
|
const { registerTrial, isLoading } = useAuth();
|
|
const [step, setStep] = useState(1);
|
|
const [error, setError] = useState<string | null>(null);
|
|
|
|
const {
|
|
register,
|
|
handleSubmit,
|
|
watch,
|
|
formState: { errors },
|
|
trigger,
|
|
setValue,
|
|
} = useForm<RegisterForm>({
|
|
resolver: zodResolver(registerSchema),
|
|
mode: 'onChange',
|
|
});
|
|
const organizationType = watch('organizationType');
|
|
const handleNext = async () => {
|
|
const fieldsToValidate = step === 1
|
|
? ['name', 'email', 'password', 'confirmPassword']
|
|
: ['organizationName', 'organizationEmail', 'organizationType'];
|
|
|
|
const isValid = await trigger(fieldsToValidate as any);
|
|
if (isValid) {
|
|
setStep(step + 1);
|
|
}
|
|
};
|
|
const onSubmit = async (data: RegisterForm) => {
|
|
try {
|
|
setError(null);
|
|
await registerTrial(
|
|
data.email,
|
|
data.password,
|
|
data.name,
|
|
data.organizationName,
|
|
data.organizationEmail,
|
|
data.organizationType
|
|
);
|
|
// No need to redirect - auth context will handle it
|
|
} catch (err: any) {
|
|
setError(err.message || 'Registration failed. Please try again.');
|
|
}
|
|
};
|
|
return (
|
|
<div className="min-h-screen app-web-bg flex flex-col justify-center py-12 sm:px-6 lg:px-8">
|
|
<div className="sm:mx-auto sm:w-full sm:max-w-md">
|
|
<Link href="/" className="flex justify-center">
|
|
<span className="text-3xl font-semibold text-text-primary">DyoLink</span>
|
|
</Link>
|
|
<h2 className="mt-6 text-center text-3xl font-semibold text-text-primary">
|
|
Start your 30-day free trial
|
|
</h2>
|
|
<p className="mt-2 text-center text-sm text-text-secondary">
|
|
Already have an account?{' '}
|
|
<Link href="/login" className="font-medium text-primary hover:opacity-90">
|
|
Sign in
|
|
</Link>
|
|
</p>
|
|
</div>
|
|
<div className="mt-8 sm:mx-auto sm:w-full sm:max-w-md">
|
|
<div className="surface-card py-8 px-4 sm:px-10">
|
|
<RegistrationProgressSteps step={step} />
|
|
{/* Trial Info Banner */}
|
|
<div className="mb-6 p-4 bg-primary-soft rounded-[var(--radius-md)] border border-primary/35">
|
|
<h3 className="text-sm font-medium text-text-primary mb-2">Your trial
|
|
includes:</h3>
|
|
<ul className="text-sm text-text-secondary space-y-1">
|
|
<li className="flex items-center">
|
|
<span className="mr-2">✓</span> Up to 5 team members
|
|
</li>
|
|
<li className="flex items-center">
|
|
<span className="mr-2">✓</span> Full access to all features
|
|
</li>
|
|
<li className="flex items-center">
|
|
<span className="mr-2">✓</span> 30 days free, no credit card
|
|
required
|
|
</li>
|
|
</ul>
|
|
</div>
|
|
<form onSubmit={handleSubmit(onSubmit)} className="space-y-6">
|
|
{step === 1 && (
|
|
<>
|
|
<Input
|
|
label="Full name"
|
|
{...register('name')}
|
|
placeholder="John Doe"
|
|
error={errors.name?.message}
|
|
icon={<User className="h-5 w-5 icon-flat" />}
|
|
/>
|
|
<Input
|
|
label="Email address"
|
|
{...register('email')}
|
|
type="email"
|
|
placeholder="you@example.com"
|
|
error={errors.email?.message}
|
|
icon={<Mail className="h-5 w-5 icon-flat" />}
|
|
/>
|
|
<Input
|
|
label="Password"
|
|
{...register('password')}
|
|
type="password"
|
|
placeholder="••••••••"
|
|
error={errors.password?.message}
|
|
icon={<Lock className="h-5 w-5 icon-flat" />}
|
|
/>
|
|
<Input
|
|
label="Confirm password"
|
|
{...register('confirmPassword')}
|
|
type="password"
|
|
placeholder="••••••••"
|
|
error={errors.confirmPassword?.message}
|
|
icon={<Lock className="h-5 w-5 icon-flat" />}
|
|
/>
|
|
<Button
|
|
type="button"
|
|
variant="primary"
|
|
onClick={handleNext}
|
|
fullWidth
|
|
>
|
|
Continue
|
|
</Button>
|
|
</>
|
|
)}
|
|
{step === 2 && (
|
|
<>
|
|
<OrganizationDetailsFields
|
|
register={register as never}
|
|
errors={errors as never}
|
|
organizationType={organizationType}
|
|
setValue={setValue as never}
|
|
/>
|
|
{error && (
|
|
<div className="p-3 bg-red-950/30 border border-red-600/40 rounded-[var(--radius-md)]">
|
|
<p className="text-sm text-red-600">{error}</p>
|
|
</div>
|
|
)}
|
|
<div className="flex gap-3">
|
|
<Button
|
|
type="button"
|
|
variant="outline"
|
|
onClick={() => setStep(1)}
|
|
>
|
|
Back
|
|
</Button>
|
|
<Button
|
|
type="submit"
|
|
variant="primary"
|
|
isLoading={isLoading}
|
|
fullWidth
|
|
>
|
|
Start my free trial
|
|
</Button>
|
|
</div>
|
|
</>
|
|
)}
|
|
</form>
|
|
<p className="mt-6 text-xs text-center text-text-muted">
|
|
By signing up, you agree to our{' '}
|
|
<Link href="/terms" className="text-primary hover:opacity-90">
|
|
Terms of Service
|
|
</Link>{' '}
|
|
and{' '}
|
|
<Link href="/privacy" className="text-primary hover:opacity-90">
|
|
Privacy Policy
|
|
</Link>
|
|
</p>
|
|
</div>
|
|
</div>
|
|
|
|
</div>
|
|
);
|
|
}
|
|
|
|
|