open-navigator / frontend /src /components /RegistrationModal.tsx
jcbowyer's picture
Clean HuggingFace deployment without binary files
61d29fc
import { Fragment, useState } from 'react'
import { Dialog, Transition } from '@headlessui/react'
import { XMarkIcon, MapPinIcon } from '@heroicons/react/24/outline'
interface RegistrationModalProps {
isOpen: boolean
onClose: () => void
onComplete: (data: LocationData) => void
initialData?: {
state?: string
county?: string
city?: string
school_board?: string
}
}
interface LocationData {
state: string
county: string
city: string
school_board: string
}
export default function RegistrationModal({ isOpen, onClose, onComplete, initialData }: RegistrationModalProps) {
const [formData, setFormData] = useState<LocationData>({
state: initialData?.state || '',
county: initialData?.county || '',
city: initialData?.city || '',
school_board: initialData?.school_board || '',
})
const [errors, setErrors] = useState<Record<string, string>>({})
const handleSubmit = (e: React.FormEvent) => {
e.preventDefault()
// Validate required fields
const newErrors: Record<string, string> = {}
if (!formData.state.trim()) newErrors.state = 'State is required'
if (!formData.county.trim()) newErrors.county = 'County is required'
if (!formData.city.trim()) newErrors.city = 'City is required'
if (Object.keys(newErrors).length > 0) {
setErrors(newErrors)
return
}
onComplete(formData)
}
const handleChange = (field: keyof LocationData, value: string) => {
setFormData(prev => ({ ...prev, [field]: value }))
// Clear error for this field
if (errors[field]) {
setErrors(prev => {
const newErrors = { ...prev }
delete newErrors[field]
return newErrors
})
}
}
return (
<Transition appear show={isOpen} as={Fragment}>
<Dialog as="div" className="relative z-50" onClose={onClose}>
<Transition.Child
as={Fragment}
enter="ease-out duration-300"
enterFrom="opacity-0"
enterTo="opacity-100"
leave="ease-in duration-200"
leaveFrom="opacity-100"
leaveTo="opacity-0"
>
<div className="fixed inset-0 bg-black bg-opacity-25" />
</Transition.Child>
<div className="fixed inset-0 overflow-y-auto">
<div className="flex min-h-full items-center justify-center p-4 text-center">
<Transition.Child
as={Fragment}
enter="ease-out duration-300"
enterFrom="opacity-0 scale-95"
enterTo="opacity-100 scale-100"
leave="ease-in duration-200"
leaveFrom="opacity-100 scale-100"
leaveTo="opacity-0 scale-95"
>
<Dialog.Panel className="w-full max-w-md transform overflow-hidden rounded-2xl bg-white p-6 text-left align-middle shadow-xl transition-all">
<div className="flex items-center justify-between mb-4">
<div className="flex items-center gap-2">
<MapPinIcon className="h-6 w-6 text-primary-600" />
<Dialog.Title
as="h3"
className="text-lg font-medium leading-6 text-gray-900"
>
Complete Your Profile
</Dialog.Title>
</div>
<button
onClick={onClose}
className="text-gray-400 hover:text-gray-500 transition-colors"
>
<XMarkIcon className="h-6 w-6" />
</button>
</div>
<p className="text-sm text-gray-500 mb-6">
Help us personalize your experience by telling us where you're located.
You can update this information anytime in Settings.
</p>
<form onSubmit={handleSubmit} className="space-y-4">
{/* State */}
<div>
<label htmlFor="state" className="block text-sm font-medium text-gray-700 mb-1">
State <span className="text-red-500">*</span>
</label>
<input
type="text"
id="state"
value={formData.state}
onChange={(e) => handleChange('state', e.target.value)}
placeholder="e.g., California, Texas, New York"
className={`w-full px-4 py-2 border rounded-lg focus:outline-none focus:ring-2 focus:ring-primary-500 ${
errors.state ? 'border-red-500' : 'border-gray-300'
}`}
/>
{errors.state && (
<p className="mt-1 text-sm text-red-600">{errors.state}</p>
)}
</div>
{/* County */}
<div>
<label htmlFor="county" className="block text-sm font-medium text-gray-700 mb-1">
County <span className="text-red-500">*</span>
</label>
<input
type="text"
id="county"
value={formData.county}
onChange={(e) => handleChange('county', e.target.value)}
placeholder="e.g., Los Angeles County, Harris County"
className={`w-full px-4 py-2 border rounded-lg focus:outline-none focus:ring-2 focus:ring-primary-500 ${
errors.county ? 'border-red-500' : 'border-gray-300'
}`}
/>
{errors.county && (
<p className="mt-1 text-sm text-red-600">{errors.county}</p>
)}
</div>
{/* City */}
<div>
<label htmlFor="city" className="block text-sm font-medium text-gray-700 mb-1">
City <span className="text-red-500">*</span>
</label>
<input
type="text"
id="city"
value={formData.city}
onChange={(e) => handleChange('city', e.target.value)}
placeholder="e.g., Los Angeles, Houston, New York"
className={`w-full px-4 py-2 border rounded-lg focus:outline-none focus:ring-2 focus:ring-primary-500 ${
errors.city ? 'border-red-500' : 'border-gray-300'
}`}
/>
{errors.city && (
<p className="mt-1 text-sm text-red-600">{errors.city}</p>
)}
</div>
{/* School Board */}
<div>
<label htmlFor="school_board" className="block text-sm font-medium text-gray-700 mb-1">
School Board / District <span className="text-gray-400 text-xs">(Optional)</span>
</label>
<input
type="text"
id="school_board"
value={formData.school_board}
onChange={(e) => handleChange('school_board', e.target.value)}
placeholder="e.g., LAUSD, Houston ISD"
className="w-full px-4 py-2 border border-gray-300 rounded-lg focus:outline-none focus:ring-2 focus:ring-primary-500"
/>
</div>
{/* Submit Button */}
<div className="flex gap-3 pt-4">
<button
type="button"
onClick={onClose}
className="flex-1 px-4 py-2 border border-gray-300 rounded-lg text-gray-700 hover:bg-gray-50 transition-colors font-medium"
>
Skip for Now
</button>
<button
type="submit"
className="flex-1 px-4 py-2 text-white rounded-lg transition-colors font-medium"
style={{ backgroundColor: '#354F52' }}
onMouseEnter={(e) => e.currentTarget.style.backgroundColor = '#2e4346'}
onMouseLeave={(e) => e.currentTarget.style.backgroundColor = '#354F52'}
>
Complete Profile
</button>
</div>
</form>
</Dialog.Panel>
</Transition.Child>
</div>
</div>
</Dialog>
</Transition>
)
}