import { useEffect, useState } from 'react'; import { useMutation } from '@tanstack/react-query'; import { Box, Button, FormControl, FormLabel, FormErrorMessage, Heading, Input, Text, Textarea, VStack, useToast, useColorModeValue, Container, Tabs, TabList, TabPanels, Tab, TabPanel, SimpleGrid, HStack, Avatar, Badge, Icon, Link, Divider } from '@chakra-ui/react'; import { useForm } from 'react-hook-form'; import { sendContact } from '../services/public'; import { useSettings } from '../hooks/useSettings'; import MainLayout from '../components/layout/MainLayout'; import { FiMail, FiPhone, FiMapPin } from 'react-icons/fi'; import { trackContactSubmit, trackFormSubmit } from '../utils/umami'; import ContactMap from '../components/home/ContactMap'; import { getPublicContacts, GroupedContacts } from '../services/contactInfo'; import { facrApi } from '../services/facr/facrApi'; import { getCompetitionAliasesPublic } from '../services/competitionAliases'; import { getImageUrl } from '../utils/imageUtils'; import { useTranslation } from 'react-i18next'; type ContactFormData = { name: string; email: string; subject: string; message: string; source?: string; }; const ContactPage: React.FC = () => { const { t } = useTranslation(); const toast = useToast(); const { settings } = useSettings(); const cardBg = useColorModeValue('white', 'gray.800'); const cardBorder = useColorModeValue('gray.200', 'gray.700'); const bgColor = useColorModeValue('white', 'gray.800'); const borderColor = useColorModeValue('gray.200', 'gray.700'); // Public contacts (grouped by category) const [contactsData, setContactsData] = useState(null); const [contactsLoading, setContactsLoading] = useState(true); // Club competitions (for tabs fallback) and aliases map const [competitions, setCompetitions] = useState>([]); const [aliasesMap, setAliasesMap] = useState>({}); const { register, handleSubmit, reset, formState: { errors, isSubmitting }, } = useForm(); const { mutate, isLoading } = useMutation({ mutationFn: (data: ContactFormData) => sendContact({ ...data, source: 'contact' }), onSuccess: () => { reset(); // Track successful contact form submission trackContactSubmit(true); trackFormSubmit('Contact Form', true); toast({ title: t('contact.message_sent'), description: t('contact.message_sent_desc'), status: 'success', duration: 5000, isClosable: true, }); }, onError: (error: any) => { // Heuristics for Axios errors const code = error?.code || ''; const msgFromServer = error?.response?.data?.error || error?.response?.data?.message; const isTimeout = code === 'ECONNABORTED' || /timeout/i.test(String(error?.message || '')); const isNetwork = !!error?.isAxiosError && !error?.response; const description = msgFromServer || (isTimeout ? t('contact.timeout_error') : isNetwork ? t('contact.network_error') : t('contact.general_error')); // Track failed contact form submission trackContactSubmit(false); trackFormSubmit('Contact Form', false); toast({ title: t('contact.error_title'), description, status: 'error', duration: 6000, isClosable: true, }); }, }); // Load public contacts once useEffect(() => { let mounted = true; (async () => { try { const data = await getPublicContacts(); if (mounted) setContactsData(data); } catch (e) { console.error('Failed to load contacts', e); } finally { if (mounted) setContactsLoading(false); } })(); return () => { mounted = false; }; }, []); // Load club competitions + aliases (used to populate tabs when no contact categories are defined) useEffect(() => { (async () => { try { const clubId = (settings as any)?.club_id || ''; const clubType = ((settings as any)?.club_type || 'football') as 'football' | 'futsal'; let comps: Array<{ code?: string; name: string }> = []; if (clubId) { try { const club = await facrApi.getClub(String(clubId), clubType); const arr = Array.isArray((club as any)?.competitions) ? (club as any).competitions : []; arr.forEach((c: any) => comps.push({ code: c.code, name: c.name || c.code })); } catch {} } let amap: Record = {}; try { const list = await getCompetitionAliasesPublic(); list.forEach((a) => { if (a.code && a.alias) amap[a.code] = a.alias; }); } catch {} const withAliases = comps.map((c) => ({ code: c.code, name: (c.code && amap[c.code]) ? amap[c.code] : c.name })); setAliasesMap(amap); setCompetitions(withAliases); } catch {} })(); }, [settings]); const onSubmit = (data: ContactFormData) => { mutate(data); }; return ( {/* Top: Map + Contact categories (tabs) */} Kontakt {/* contacts are loaded in useEffect */} {(() => { const lat = (settings as any)?.location_latitude; const lng = (settings as any)?.location_longitude; const hasLocation = !!lat && !!lng; const categories = Object.entries(contactsData?.categories || {}); const uncategorized = contactsData?.uncategorized || []; const hasContacts = categories.length > 0 || uncategorized.length > 0; const hasContactInfo = !!( (settings as any)?.contact_address || (settings as any)?.contact_phone || (settings as any)?.contact_email ); if (!hasLocation && !hasContacts && !hasContactInfo) return null; return ( {/* Map on the left */} {hasLocation && ( )} {/* Right column: contact info card (if any) + contacts tabs */} {(hasContactInfo || hasContacts) && ( {hasContactInfo && ( {t('contact.contact_info')} {(settings as any)?.contact_address && ( {t('contact.address')} {(settings as any)?.contact_address} {(settings as any)?.contact_city && ( {(settings as any)?.contact_zip && `${(settings as any)?.contact_zip} `} {(settings as any)?.contact_city} )} {(settings as any)?.contact_country && {(settings as any)?.contact_country}} )} {(settings as any)?.contact_phone && ( {t('contact.phone')} {(settings as any)?.contact_phone} )} {(settings as any)?.contact_email && ( {t('contact.email')} {(settings as any)?.contact_email} )} )} {hasContacts && ( {t('contact.contact_persons')} {(() => { const categoryEntries = Object.entries(contactsData?.categories || {}); const compNames = competitions.map((c) => (c.code && aliasesMap[c.code]) ? aliasesMap[c.code] : c.name).filter(Boolean); const useCategories = categoryEntries.length > 0; const tabs = useCategories ? categoryEntries.map(([n]) => n) : compNames; const hasOthers = uncategorized.length > 0; return ( <> {tabs.map((n) => ( {n} ))} {hasOthers && {t('contact.others')}} {useCategories ? categoryEntries.map(([name, persons]) => ( {persons.map((contact) => ( {contact.image_url && ( )} {contact.name} {contact.position && ( {contact.position} )} {contact.description && ( {contact.description} )} {contact.email && ( {contact.email} )} {contact.phone && ( {contact.phone} )} ))} )) : tabs.map((name) => { const persons = (contactsData?.categories || {})[name] || []; return ( {persons.length > 0 ? ( {persons.map((contact) => ( {contact.image_url && ( )} {contact.name} {contact.position && ( {contact.position} )} {contact.description && ( {contact.description} )} {contact.email && ( {contact.email} )} {contact.phone && ( {contact.phone} )} ))} ) : ( {t('contact.no_contacts')} )} ); })} {hasOthers && ( {uncategorized.map((contact) => ( {contact.image_url && ( )} {contact.name} {contact.position && ( {contact.position} )} {contact.description && ( {contact.description} )} {contact.email && ( {contact.email} )} {contact.phone && ( {contact.phone} )} ))} )} ); })()} )} )} ); })()} {/* Contact form at the end */} {t('contact.contact_us')} {t('contact.contact_description')}
{t('contact.name_label')} {errors.name && errors.name.message} {t('contact.email_label')} {errors.email && errors.email.message} {t('contact.subject_label')} {errors.subject && errors.subject.message} {t('contact.message_label')}