Initial Commit

This commit is contained in:
Koncept Kit
2025-12-05 16:40:33 +07:00
parent 0834f12410
commit 94c7d5aec0
91 changed files with 20446 additions and 0 deletions

388
src/pages/Register.js Normal file
View File

@@ -0,0 +1,388 @@
import React, { useState } from 'react';
import { useNavigate, Link } from 'react-router-dom';
import { useAuth } from '../context/AuthContext';
import { Button } from '../components/ui/button';
import { Input } from '../components/ui/input';
import { Label } from '../components/ui/label';
import { Card } from '../components/ui/card';
import { Checkbox } from '../components/ui/checkbox';
import { toast } from 'sonner';
import Navbar from '../components/Navbar';
import { ArrowRight, ArrowLeft } from 'lucide-react';
const Register = () => {
const navigate = useNavigate();
const { register } = useAuth();
const [loading, setLoading] = useState(false);
const [formData, setFormData] = useState({
email: '',
password: '',
first_name: '',
last_name: '',
phone: '',
address: '',
city: '',
state: '',
zipcode: '',
date_of_birth: '',
lead_sources: [],
partner_first_name: '',
partner_last_name: '',
partner_is_member: false,
partner_plan_to_become_member: false,
referred_by_member_name: ''
});
const leadSourceOptions = [
'Current member',
'Friend',
'OutSmart Magazine',
'Search engine (Google etc.)',
"I've known about LOAF for a long time",
'Other'
];
const handleInputChange = (e) => {
const { name, value, type, checked } = e.target;
setFormData(prev => ({
...prev,
[name]: type === 'checkbox' ? checked : value
}));
};
const handleLeadSourceChange = (source) => {
setFormData(prev => {
const sources = prev.lead_sources.includes(source)
? prev.lead_sources.filter(s => s !== source)
: [...prev.lead_sources, source];
return { ...prev, lead_sources: sources };
});
};
const handleSubmit = async (e) => {
e.preventDefault();
setLoading(true);
try {
// Convert date to ISO format
const dataToSubmit = {
...formData,
date_of_birth: new Date(formData.date_of_birth).toISOString()
};
await register(dataToSubmit);
toast.success('Registration successful! Please check your email to verify your account.');
navigate('/login');
} catch (error) {
toast.error(error.response?.data?.detail || 'Registration failed. Please try again.');
} finally {
setLoading(false);
}
};
return (
<div className="min-h-screen bg-[#FDFCF8]">
<Navbar />
<div className="max-w-4xl mx-auto px-6 py-12">
<div className="mb-8">
<Link to="/" className="inline-flex items-center text-[#6B708D] hover:text-[#E07A5F] transition-colors">
<ArrowLeft className="h-4 w-4 mr-2" />
Back to Home
</Link>
</div>
<Card className="p-8 md:p-12 bg-white rounded-2xl border border-[#EAE0D5] shadow-lg">
<div className="mb-8">
<h1 className="text-4xl md:text-5xl font-semibold fraunces text-[#3D405B] mb-4">
Join Our Community
</h1>
<p className="text-lg text-[#6B708D]">
Fill out the form below to start your membership journey.
</p>
</div>
<form onSubmit={handleSubmit} className="space-y-8" data-testid="register-form">
{/* Account Information */}
<div className="space-y-4">
<h2 className="text-2xl font-semibold fraunces text-[#3D405B]">
Account Information
</h2>
<div className="grid md:grid-cols-2 gap-4">
<div>
<Label htmlFor="email">Email *</Label>
<Input
id="email"
name="email"
type="email"
required
value={formData.email}
onChange={handleInputChange}
className="h-14 rounded-xl border-2 border-[#EAE0D5] focus:border-[#E07A5F]"
data-testid="email-input"
/>
</div>
<div>
<Label htmlFor="password">Password *</Label>
<Input
id="password"
name="password"
type="password"
required
minLength={6}
value={formData.password}
onChange={handleInputChange}
className="h-14 rounded-xl border-2 border-[#EAE0D5] focus:border-[#E07A5F]"
data-testid="password-input"
/>
</div>
</div>
</div>
{/* Personal Information */}
<div className="space-y-4">
<h2 className="text-2xl font-semibold fraunces text-[#3D405B]">
Personal Information
</h2>
<div className="grid md:grid-cols-2 gap-4">
<div>
<Label htmlFor="first_name">First Name *</Label>
<Input
id="first_name"
name="first_name"
required
value={formData.first_name}
onChange={handleInputChange}
className="h-14 rounded-xl border-2 border-[#EAE0D5] focus:border-[#E07A5F]"
data-testid="first-name-input"
/>
</div>
<div>
<Label htmlFor="last_name">Last Name *</Label>
<Input
id="last_name"
name="last_name"
required
value={formData.last_name}
onChange={handleInputChange}
className="h-14 rounded-xl border-2 border-[#EAE0D5] focus:border-[#E07A5F]"
data-testid="last-name-input"
/>
</div>
</div>
<div className="grid md:grid-cols-2 gap-4">
<div>
<Label htmlFor="phone">Phone *</Label>
<Input
id="phone"
name="phone"
type="tel"
required
value={formData.phone}
onChange={handleInputChange}
className="h-14 rounded-xl border-2 border-[#EAE0D5] focus:border-[#E07A5F]"
data-testid="phone-input"
/>
</div>
<div>
<Label htmlFor="date_of_birth">Date of Birth *</Label>
<Input
id="date_of_birth"
name="date_of_birth"
type="date"
required
value={formData.date_of_birth}
onChange={handleInputChange}
className="h-14 rounded-xl border-2 border-[#EAE0D5] focus:border-[#E07A5F]"
data-testid="dob-input"
/>
</div>
</div>
<div>
<Label htmlFor="address">Address *</Label>
<Input
id="address"
name="address"
required
value={formData.address}
onChange={handleInputChange}
className="h-14 rounded-xl border-2 border-[#EAE0D5] focus:border-[#E07A5F]"
data-testid="address-input"
/>
</div>
<div className="grid md:grid-cols-3 gap-4">
<div>
<Label htmlFor="city">City *</Label>
<Input
id="city"
name="city"
required
value={formData.city}
onChange={handleInputChange}
className="h-14 rounded-xl border-2 border-[#EAE0D5] focus:border-[#E07A5F]"
data-testid="city-input"
/>
</div>
<div>
<Label htmlFor="state">State *</Label>
<Input
id="state"
name="state"
required
value={formData.state}
onChange={handleInputChange}
className="h-14 rounded-xl border-2 border-[#EAE0D5] focus:border-[#E07A5F]"
data-testid="state-input"
/>
</div>
<div>
<Label htmlFor="zipcode">Zipcode *</Label>
<Input
id="zipcode"
name="zipcode"
required
value={formData.zipcode}
onChange={handleInputChange}
className="h-14 rounded-xl border-2 border-[#EAE0D5] focus:border-[#E07A5F]"
data-testid="zipcode-input"
/>
</div>
</div>
</div>
{/* How Did You Hear About Us */}
<div className="space-y-4">
<h2 className="text-2xl font-semibold fraunces text-[#3D405B]">
How Did You Hear About Us? *
</h2>
<div className="space-y-3">
{leadSourceOptions.map((source) => (
<div key={source} className="flex items-center space-x-2">
<Checkbox
id={source}
checked={formData.lead_sources.includes(source)}
onCheckedChange={() => handleLeadSourceChange(source)}
data-testid={`lead-source-${source.toLowerCase().replace(/\s+/g, '-')}`}
/>
<Label htmlFor={source} className="text-base cursor-pointer">
{source}
</Label>
</div>
))}
</div>
</div>
{/* Partner Information */}
<div className="space-y-4">
<h2 className="text-2xl font-semibold fraunces text-[#3D405B]">
Partner Information (Optional)
</h2>
<div className="grid md:grid-cols-2 gap-4">
<div>
<Label htmlFor="partner_first_name">Partner First Name</Label>
<Input
id="partner_first_name"
name="partner_first_name"
value={formData.partner_first_name}
onChange={handleInputChange}
className="h-14 rounded-xl border-2 border-[#EAE0D5] focus:border-[#E07A5F]"
data-testid="partner-first-name-input"
/>
</div>
<div>
<Label htmlFor="partner_last_name">Partner Last Name</Label>
<Input
id="partner_last_name"
name="partner_last_name"
value={formData.partner_last_name}
onChange={handleInputChange}
className="h-14 rounded-xl border-2 border-[#EAE0D5] focus:border-[#E07A5F]"
data-testid="partner-last-name-input"
/>
</div>
</div>
<div className="space-y-3">
<div className="flex items-center space-x-2">
<Checkbox
id="partner_is_member"
name="partner_is_member"
checked={formData.partner_is_member}
onCheckedChange={(checked) =>
setFormData(prev => ({ ...prev, partner_is_member: checked }))
}
data-testid="partner-is-member-checkbox"
/>
<Label htmlFor="partner_is_member" className="text-base cursor-pointer">
Is your partner already a member?
</Label>
</div>
<div className="flex items-center space-x-2">
<Checkbox
id="partner_plan_to_become_member"
name="partner_plan_to_become_member"
checked={formData.partner_plan_to_become_member}
onCheckedChange={(checked) =>
setFormData(prev => ({ ...prev, partner_plan_to_become_member: checked }))
}
data-testid="partner-plan-member-checkbox"
/>
<Label htmlFor="partner_plan_to_become_member" className="text-base cursor-pointer">
Does your partner plan to become a member?
</Label>
</div>
</div>
</div>
{/* Referral */}
<div className="space-y-4">
<h2 className="text-2xl font-semibold fraunces text-[#3D405B]">
Referral (Optional)
</h2>
<div>
<Label htmlFor="referred_by_member_name">Referred by Member (Name or Email)</Label>
<Input
id="referred_by_member_name"
name="referred_by_member_name"
value={formData.referred_by_member_name}
onChange={handleInputChange}
placeholder="If a current member referred you, enter their name or email"
className="h-14 rounded-xl border-2 border-[#EAE0D5] focus:border-[#E07A5F]"
data-testid="referral-input"
/>
<p className="text-sm text-[#6B708D] mt-2">
If referred by a current member, you may skip the event attendance requirement.
</p>
</div>
</div>
{/* Submit Button */}
<div className="pt-6">
<Button
type="submit"
disabled={loading || formData.lead_sources.length === 0}
className="w-full bg-[#E07A5F] text-white hover:bg-[#D0694E] rounded-full py-6 text-lg font-medium shadow-lg hover:scale-105 transition-transform disabled:opacity-50 disabled:cursor-not-allowed"
data-testid="submit-register-button"
>
{loading ? 'Creating Account...' : 'Create Account'}
<ArrowRight className="ml-2 h-5 w-5" />
</Button>
<p className="text-center text-[#6B708D] mt-4">
Already have an account?{' '}
<Link to="/login" className="text-[#E07A5F] hover:underline font-medium">
Login here
</Link>
</p>
</div>
</form>
</Card>
</div>
</div>
);
};
export default Register;