diff --git a/src/components/StatusBadge.js b/src/components/StatusBadge.js index 89ce768..f021d8c 100644 --- a/src/components/StatusBadge.js +++ b/src/components/StatusBadge.js @@ -6,7 +6,6 @@ const STATUS_BADGE_CONFIG = { //status-based badges pending_email: { label: 'Pending Email', variant: 'orange2' }, pending_validation: { label: 'Pending Validation', variant: 'gray' }, - pre_validated: { label: 'Pre-Validated', variant: 'green' }, payment_pending: { label: 'Payment Pending', variant: 'orange' }, active: { label: 'Active', variant: 'green' }, inactive: { label: 'Inactive', variant: 'gray2' }, @@ -23,7 +22,12 @@ const STATUS_BADGE_CONFIG = { admin: { label: 'Admin', variant: 'purple' }, moderator: { label: 'Moderator', variant: 'bg-[var(--neutral-800)] text-[var(--purple-ink)]' }, staff: { label: 'Staff', variant: 'gray' }, - media: { label: 'Media', variant: 'gray2' } + media: { label: 'Media', variant: 'gray2' }, + + //donation badges + pending: { label: 'Payment Pending', variant: 'orange' }, + completed: { label: 'Completed', variant: 'green' }, + failed: { label: 'Failed', className: 'bg-red-100 text-red-700' } }; //todo: make shield icon dynamic based on status diff --git a/src/components/ViewRegistrationDialog.js b/src/components/ViewRegistrationDialog.js index 8f02dcf..d667a51 100644 --- a/src/components/ViewRegistrationDialog.js +++ b/src/components/ViewRegistrationDialog.js @@ -1,4 +1,4 @@ -import React from 'react'; +import React, { useEffect, useRef, useState } from 'react'; import { Dialog, DialogContent, @@ -9,12 +9,77 @@ import { } from './ui/dialog'; import { Button } from './ui/button'; import { Card } from './ui/card'; +import { Checkbox } from './ui/checkbox'; +import { Input } from './ui/input'; +import { Label } from './ui/label'; +import { Textarea } from './ui/textarea'; import { User, Mail, Phone, Calendar, UserCheck, Clock, FileText } from 'lucide-react'; import StatusBadge from './StatusBadge'; +import api from '../utils/api'; +import { toast } from 'sonner'; const ViewRegistrationDialog = ({ open, onOpenChange, user }) => { if (!user) return null; + const [formData, setFormData] = useState(null); + const [isSaving, setIsSaving] = useState(false); + const [hasUnsavedChanges, setHasUnsavedChanges] = useState(false); + const autoSaveTimeoutRef = useRef(null); + const pendingSaveRef = useRef(false); + + const leadSourceOptions = [ + 'Current member', + 'Friend', + 'OutSmart Magazine', + 'Search engine (Google etc.)', + "I've known about LOAF for a long time", + 'Other' + ]; + + 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' + ]; + + useEffect(() => { + if (!open || !user) return; + const nextFormData = { + lead_sources: Array.isArray(user.lead_sources) ? user.lead_sources : [], + partner_first_name: user.partner_first_name || '', + partner_last_name: user.partner_last_name || '', + partner_is_member: Boolean(user.partner_is_member), + partner_plan_to_become_member: Boolean(user.partner_plan_to_become_member), + newsletter_publish_name: Boolean(user.newsletter_publish_name), + newsletter_publish_photo: Boolean(user.newsletter_publish_photo), + newsletter_publish_birthday: Boolean(user.newsletter_publish_birthday), + newsletter_publish_none: Boolean(user.newsletter_publish_none), + referred_by_member_name: user.referred_by_member_name || '', + volunteer_interests: Array.isArray(user.volunteer_interests) ? user.volunteer_interests : [], + scholarship_requested: Boolean(user.scholarship_requested), + scholarship_reason: user.scholarship_reason || '' + }; + setFormData(nextFormData); + setHasUnsavedChanges(false); + }, [open, user]); + + useEffect(() => { + return () => { + if (autoSaveTimeoutRef.current) { + clearTimeout(autoSaveTimeoutRef.current); + } + }; + }, []); + const formatDate = (dateString) => { if (!dateString) return '—'; return new Date(dateString).toLocaleDateString('en-US', { @@ -44,6 +109,91 @@ const ViewRegistrationDialog = ({ open, onOpenChange, user }) => { return phone; }; + const saveProfile = async (showToast = true) => { + if (!formData) return; + if (isSaving) { + pendingSaveRef.current = true; + return; + } + + setIsSaving(true); + try { + await api.put('/users/profile', { + lead_sources: formData.lead_sources, + partner_first_name: formData.partner_first_name, + partner_last_name: formData.partner_last_name, + partner_is_member: formData.partner_is_member, + partner_plan_to_become_member: formData.partner_plan_to_become_member, + newsletter_publish_name: formData.newsletter_publish_name, + newsletter_publish_photo: formData.newsletter_publish_photo, + newsletter_publish_birthday: formData.newsletter_publish_birthday, + newsletter_publish_none: formData.newsletter_publish_none, + referred_by_member_name: formData.referred_by_member_name, + volunteer_interests: formData.volunteer_interests, + scholarship_requested: formData.scholarship_requested, + scholarship_reason: formData.scholarship_reason + }); + setHasUnsavedChanges(false); + if (showToast) { + toast.success('Registration details saved'); + } + } catch (error) { + if (showToast) { + toast.error(error.response?.data?.detail || 'Failed to save registration details'); + } + } finally { + setIsSaving(false); + if (pendingSaveRef.current) { + pendingSaveRef.current = false; + saveProfile(showToast); + } + } + }; + + const scheduleAutoSave = () => { + setHasUnsavedChanges(true); + if (autoSaveTimeoutRef.current) { + clearTimeout(autoSaveTimeoutRef.current); + } + autoSaveTimeoutRef.current = setTimeout(() => { + saveProfile(false); + }, 800); + }; + + const handleInputChange = (e) => { + const { name, value } = e.target; + setFormData(prev => { + const next = { ...prev, [name]: value }; + return next; + }); + scheduleAutoSave(); + }; + + const handleCheckboxChange = (name, checked) => { + setFormData(prev => ({ ...prev, [name]: checked })); + scheduleAutoSave(); + }; + + const handleLeadSourceChange = (source) => { + setFormData(prev => { + const sources = prev.lead_sources.includes(source) + ? prev.lead_sources.filter((item) => item !== source) + : [...prev.lead_sources, source]; + return { ...prev, lead_sources: sources }; + }); + scheduleAutoSave(); + }; + + const handleVolunteerChange = (option) => { + setFormData(prev => { + const interests = prev.volunteer_interests.includes(option) + ? prev.volunteer_interests.filter((item) => item !== option) + : [...prev.volunteer_interests, option]; + return { ...prev, volunteer_interests: interests }; + }); + scheduleAutoSave(); + }; + const InfoRow = ({ icon: Icon, label, value }) => (
+ Please check what information may be published in LOAF Newsletter +
++ If referred by a current member, you may skip the event attendance requirement. +
++ I may at some time be interested in volunteering with LOAF in the following ways (training is provided) +
++ Scholarship information is kept confidential +
+ {formData.scholarship_requested && ( +