diff --git a/src/components/registration/RegistrationStep1.js b/src/components/registration/RegistrationStep1.js
new file mode 100644
index 0000000..147cc5c
--- /dev/null
+++ b/src/components/registration/RegistrationStep1.js
@@ -0,0 +1,235 @@
+import React from 'react';
+import { Label } from '../ui/label';
+import { Input } from '../ui/input';
+import { Checkbox } from '../ui/checkbox';
+
+const RegistrationStep1 = ({ formData, setFormData, handleInputChange }) => {
+ const leadSourceOptions = [
+ 'Current member',
+ 'Friend',
+ 'OutSmart Magazine',
+ 'Search engine (Google etc.)',
+ "I've known about LOAF for a long time",
+ 'Other'
+ ];
+
+ 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 };
+ });
+ };
+
+ return (
+
+ {/* Personal Information */}
+
+
+ Personal Information
+
+
+ {/* First Name, Last Name */}
+
+
+ {/* Phone, Date of Birth */}
+
+
+ {/* Address */}
+
+ Address *
+
+
+
+ {/* City, State, Zipcode */}
+
+
+
+ {/* How Did You Hear About Us */}
+
+
+ How Did You Hear About Us? *
+
+
+ {leadSourceOptions.map((source) => (
+
+ handleLeadSourceChange(source)}
+ data-testid={`lead-source-${source.toLowerCase().replace(/\s+/g, '-')}`}
+ />
+
+ {source}
+
+
+ ))}
+
+
+
+ {/* Partner Information */}
+
+
+ Partner Information (Optional)
+
+
+
+
+
+
+
+ setFormData(prev => ({ ...prev, partner_is_member: checked }))
+ }
+ data-testid="partner-is-member-checkbox"
+ />
+
+ Is your partner already a member?
+
+
+
+
+
+ setFormData(prev => ({ ...prev, partner_plan_to_become_member: checked }))
+ }
+ data-testid="partner-plan-member-checkbox"
+ />
+
+ Does your partner plan to become a member?
+
+
+
+
+
+ );
+};
+
+export default RegistrationStep1;
diff --git a/src/components/registration/RegistrationStep2.js b/src/components/registration/RegistrationStep2.js
new file mode 100644
index 0000000..0a98d1d
--- /dev/null
+++ b/src/components/registration/RegistrationStep2.js
@@ -0,0 +1,186 @@
+import React from 'react';
+import { Label } from '../ui/label';
+import { Input } from '../ui/input';
+import { Textarea } from '../ui/textarea';
+import { Checkbox } from '../ui/checkbox';
+
+const RegistrationStep2 = ({ formData, setFormData, handleInputChange }) => {
+ const volunteerOptions = [
+ 'Welcoming new members at events',
+ 'Sending out birthday cards',
+ 'Care Team Calls',
+ 'Sharing ideas for events',
+ 'Researching grants',
+ 'Applying for grants',
+ 'Assisting with TeatherLOAFers',
+ 'Assisting with ActiveLOAFers',
+ 'Assisting with weekday Lunch Bunch',
+ 'Uploading Photos to the Website',
+ 'Assisting with eNewsletter',
+ 'Other administrative task'
+ ];
+
+ const handleVolunteerChange = (option) => {
+ setFormData(prev => {
+ const interests = prev.volunteer_interests.includes(option)
+ ? prev.volunteer_interests.filter(i => i !== option)
+ : [...prev.volunteer_interests, option];
+ return { ...prev, volunteer_interests: interests };
+ });
+ };
+
+ return (
+
+ {/* Newsletter Publication Preferences */}
+
+
+ Newsletter Publication Preferences *
+
+
+ Please check what information may be published in LOAF Newsletter
+
+
+
+
+
+ setFormData(prev => ({ ...prev, newsletter_publish_name: checked }))
+ }
+ />
+
+ Name
+
+
+
+
+
+ setFormData(prev => ({ ...prev, newsletter_publish_photo: checked }))
+ }
+ />
+
+ Photo (added later in profile)
+
+
+
+
+
+ setFormData(prev => ({ ...prev, newsletter_publish_birthday: checked }))
+ }
+ />
+
+ Birthday
+
+
+
+
+
+ setFormData(prev => ({ ...prev, newsletter_publish_none: checked }))
+ }
+ />
+
+ Do not publish any of my information
+
+
+
+
+
+ {/* Referral */}
+
+
+ Referral
+
+
+
+ Name of a LOAF Member who already knows you
+
+
+
+ If referred by a current member, you may skip the event attendance requirement.
+
+
+
+
+ {/* Volunteer Interests */}
+
+
+ Volunteer Interests (Optional)
+
+
+ I may at some time be interested in volunteering with LOAF in the following ways (training is provided)
+
+
+
+ {volunteerOptions.map((option) => (
+
+ handleVolunteerChange(option)}
+ />
+
+ {option}
+
+
+ ))}
+
+
+
+ {/* Scholarship Request */}
+
+
+
+ setFormData(prev => ({ ...prev, scholarship_requested: checked }))
+ }
+ />
+
+ I am requesting for scholarship
+
+
+
+ Scholarship information is kept confidential
+
+
+ {formData.scholarship_requested && (
+
+
+ Please explain your situation *
+
+
+
+ )}
+
+
+ );
+};
+
+export default RegistrationStep2;
diff --git a/src/components/registration/RegistrationStep3.js b/src/components/registration/RegistrationStep3.js
new file mode 100644
index 0000000..aa8d8cc
--- /dev/null
+++ b/src/components/registration/RegistrationStep3.js
@@ -0,0 +1,174 @@
+import React, { useEffect } from 'react';
+import { Label } from '../ui/label';
+import { Input } from '../ui/input';
+import { Textarea } from '../ui/textarea';
+
+const RegistrationStep3 = ({ formData, setFormData, handleInputChange }) => {
+ // Pre-fill directory fields when user opts in
+ useEffect(() => {
+ if (formData.show_in_directory && !formData.directory_email) {
+ setFormData(prev => ({
+ ...prev,
+ directory_email: prev.email || '',
+ directory_address: prev.address || '',
+ directory_phone: prev.phone || '',
+ directory_dob: prev.date_of_birth || '',
+ directory_partner_name: prev.partner_first_name && prev.partner_last_name
+ ? `${prev.partner_first_name} ${prev.partner_last_name}`
+ : ''
+ }));
+ }
+ }, [formData.show_in_directory]);
+
+ return (
+
+
+
+ Members Directory
+
+
+
+ Would you like to be displayed on our private members directory? (optional and you can change the answer later)
+
+
+ {/* Directory Opt-in Radio Buttons */}
+
+
setFormData(prev => ({ ...prev, show_in_directory: true }))}
+ >
+
+
+ {formData.show_in_directory && (
+
+ )}
+
+
+ Yes, include me in the Members Directory
+
+
+
+
+
setFormData(prev => ({ ...prev, show_in_directory: false }))}
+ >
+
+
+ {!formData.show_in_directory && (
+
+ )}
+
+
+ No, don't include me in the Members Directory
+
+
+
+
+
+
+ {/* Conditional Directory Fields */}
+ {formData.show_in_directory && (
+
+
+ Below, choose what information you would like include in the Members Only Directory.
+ (If you ever want to update this information, remember the Directory Section and Account Section are separate)
+
+
+
+ Your preferred contact email
+
+
+
+
+ Bio for my Member Profile
+
+
+
+
+ Preferred address
+
+
+
+
+ Preferred contact phone number
+
+
+
+
+ Date of birth
+
+
+
+
+
+ Enter your partner's name as you'd like it to appear in the Members Directory
+
+
+
+
+ )}
+
+ );
+};
+
+export default RegistrationStep3;
diff --git a/src/components/registration/RegistrationStep4.js b/src/components/registration/RegistrationStep4.js
new file mode 100644
index 0000000..d0a5779
--- /dev/null
+++ b/src/components/registration/RegistrationStep4.js
@@ -0,0 +1,78 @@
+import React from 'react';
+import { Label } from '../ui/label';
+import { Input } from '../ui/input';
+
+const RegistrationStep4 = ({ formData, handleInputChange }) => {
+ return (
+
+ {/* Progress Bar */}
+
+ {steps.map((step, index) => (
+
+ {/* Step Circle */}
+
+
step.number
+ ? 'bg-[#81B29A] text-white'
+ : 'bg-[#EAE0D5] text-[#6B708D]'
+ }
+ `}>
+ {currentStep > step.number ? '✓' : step.number}
+
+
+ {step.title}
+
+
+
+ {/* Connecting Line */}
+ {index < steps.length - 1 && (
+
+
step.number ? 'bg-[#81B29A] w-full' : 'bg-transparent w-0'}
+ `}
+ />
+
+ )}
+
+ ))}
+
+
+ {/* Step Counter */}
+
+ Step {currentStep} of {totalSteps}
+
+
+ );
+};
+
+export default RegistrationStepIndicator;
diff --git a/src/pages/Register.js b/src/pages/Register.js
index 703fd73..32a4740 100644
--- a/src/pages/Register.js
+++ b/src/pages/Register.js
@@ -2,45 +2,61 @@ 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';
+import RegistrationStepIndicator from '../components/registration/RegistrationStepIndicator';
+import RegistrationStep1 from '../components/registration/RegistrationStep1';
+import RegistrationStep2 from '../components/registration/RegistrationStep2';
+import RegistrationStep3 from '../components/registration/RegistrationStep3';
+import RegistrationStep4 from '../components/registration/RegistrationStep4';
const Register = () => {
const navigate = useNavigate();
const { register } = useAuth();
const [loading, setLoading] = useState(false);
+ const [currentStep, setCurrentStep] = useState(1);
const [formData, setFormData] = useState({
- email: '',
- password: '',
+ // Step 1: Personal & Partner Information
first_name: '',
last_name: '',
phone: '',
+ date_of_birth: '',
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'
- ];
+ // Step 2: Newsletter, Volunteer & Scholarship
+ referred_by_member_name: '',
+ newsletter_publish_name: false,
+ newsletter_publish_photo: false,
+ newsletter_publish_birthday: false,
+ newsletter_publish_none: false,
+ volunteer_interests: [],
+ scholarship_requested: false,
+ scholarship_reason: '',
+
+ // Step 3: Directory Settings
+ show_in_directory: false,
+ directory_email: '',
+ directory_bio: '',
+ directory_address: '',
+ directory_phone: '',
+ directory_dob: '',
+ directory_partner_name: '',
+
+ // Step 4: Account Credentials
+ email: '',
+ password: '',
+ confirmPassword: ''
+ });
const handleInputChange = (e) => {
const { name, value, type, checked } = e.target;
@@ -50,28 +66,113 @@ const Register = () => {
}));
};
- 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 validateStep1 = () => {
+ const required = ['first_name', 'last_name', 'phone', 'date_of_birth',
+ 'address', 'city', 'state', 'zipcode'];
+ for (const field of required) {
+ if (!formData[field]?.trim()) {
+ toast.error('Please fill in all required fields');
+ return false;
+ }
+ }
+ if (formData.lead_sources.length === 0) {
+ toast.error('Please select at least one option for how you heard about us');
+ return false;
+ }
+ return true;
+ };
+
+ const validateStep2 = () => {
+ const { newsletter_publish_name, newsletter_publish_photo,
+ newsletter_publish_birthday, newsletter_publish_none } = formData;
+
+ if (!newsletter_publish_name && !newsletter_publish_photo &&
+ !newsletter_publish_birthday && !newsletter_publish_none) {
+ toast.error('Please select at least one newsletter publication preference');
+ return false;
+ }
+
+ if (formData.scholarship_requested && !formData.scholarship_reason?.trim()) {
+ toast.error('Please explain your scholarship request');
+ return false;
+ }
+
+ return true;
+ };
+
+ const validateStep3 = () => {
+ return true; // No required fields
+ };
+
+ const validateStep4 = () => {
+ if (!formData.email || !formData.password || !formData.confirmPassword) {
+ toast.error('Please fill in all account fields');
+ return false;
+ }
+
+ const emailRegex = /^[^\s@]+@[^\s@]+\.[^\s@]+$/;
+ if (!emailRegex.test(formData.email)) {
+ toast.error('Please enter a valid email address');
+ return false;
+ }
+
+ if (formData.password.length < 6) {
+ toast.error('Password must be at least 6 characters');
+ return false;
+ }
+
+ if (formData.password !== formData.confirmPassword) {
+ toast.error('Passwords do not match');
+ return false;
+ }
+
+ return true;
+ };
+
+ const handleNext = () => {
+ let isValid = false;
+
+ switch (currentStep) {
+ case 1: isValid = validateStep1(); break;
+ case 2: isValid = validateStep2(); break;
+ case 3: isValid = validateStep3(); break;
+ default: isValid = false;
+ }
+
+ if (isValid) {
+ setCurrentStep(prev => Math.min(prev + 1, 4));
+ window.scrollTo({ top: 0, behavior: 'smooth' });
+ }
+ };
+
+ const handleBack = () => {
+ setCurrentStep(prev => Math.max(prev - 1, 1));
+ window.scrollTo({ top: 0, behavior: 'smooth' });
};
const handleSubmit = async (e) => {
e.preventDefault();
+
+ // Final validation
+ if (!validateStep4()) return;
+
setLoading(true);
try {
- // Convert date to ISO format
- const dataToSubmit = {
- ...formData,
- date_of_birth: new Date(formData.date_of_birth).toISOString()
+ // Remove confirmPassword (client-side only)
+ const { confirmPassword, ...dataToSubmit } = formData;
+
+ // Convert date fields to ISO format
+ const submitData = {
+ ...dataToSubmit,
+ date_of_birth: new Date(dataToSubmit.date_of_birth).toISOString(),
+ directory_dob: dataToSubmit.directory_dob
+ ? new Date(dataToSubmit.directory_dob).toISOString()
+ : null
};
- await register(dataToSubmit);
- toast.success('Registration successful! Please check your email to verify your account.');
+ await register(submitData);
+ toast.success('Please check your email for a confirmation email.');
navigate('/login');
} catch (error) {
toast.error(error.response?.data?.detail || 'Registration failed. Please try again.');
@@ -103,281 +204,83 @@ const Register = () => {