diff --git a/CEUS/src/app/contact/ContactClient.tsx b/CEUS/src/app/contact/ContactClient.tsx index 6d9b214..6af7cc0 100644 --- a/CEUS/src/app/contact/ContactClient.tsx +++ b/CEUS/src/app/contact/ContactClient.tsx @@ -1,129 +1,410 @@ 'use client' // src/app/contact/ContactClient.tsx import React, { useState } from 'react'; +import { useForm } from 'react-hook-form'; +import { zodResolver } from '@hookform/resolvers/zod'; +import { z } from 'zod'; import { FaMapMarkerAlt, FaEnvelope } from 'react-icons/fa'; import { submitContactForm } from '../../lib/supabase'; import { cn } from '@/lib/utils'; -const ContactClient: React.FC = () => { - const [formData, setFormData] = useState({ - name: '', - email: '', - subject: '', - message: '', - }); +const FALLBACK_EMAIL = 'ceus@unsw.edu.au'; - const [isSubmitting, setIsSubmitting] = useState(false); - const [submitStatus, setSubmitStatus] = useState<'idle' | 'success' | 'error'>('idle'); +const contactSchema = z.object({ + name: z + .string() + .trim() + .min(2, { message: 'Please enter your full name (at least 2 characters).' }) + .max(80, { message: 'That name is a bit long. Try 80 characters or fewer.' }), + email: z + .string() + .trim() + .min(1, { message: 'We need an email so we can write back.' }) + .email({ message: 'That email looks incomplete. Check for an @ and a domain.' }), + subject: z + .string() + .trim() + .min(3, { message: 'Add a short subject so we know what it is about.' }) + .max(120, { message: 'Try to keep the subject under 120 characters.' }), + message: z + .string() + .trim() + .min(10, { message: 'Add a few more details so we can help properly (at least 10 characters).' }) + .max(2000, { message: 'That is over 2000 characters. Trim it down or email us instead.' }), +}); - const handleChange = (e: React.ChangeEvent) => { - const { name, value } = e.target; - setFormData(prevState => ({ ...prevState, [name]: value })); - }; +type ContactFormValues = z.infer; - const handleSubmit = async (e: React.FormEvent) => { - e.preventDefault(); - setIsSubmitting(true); - setSubmitStatus('idle'); +const inputBase = + 'w-full rounded-md border bg-white px-4 py-3 text-base text-gray-900 placeholder:text-gray-400 ' + + 'focus:outline-none focus:border-blue-600 focus:ring-[3px] focus:ring-blue-600/25 ' + + 'disabled:bg-gray-50 disabled:text-gray-500'; + +const inputDefault = 'border-gray-200'; +const inputError = 'border-red-600'; + +const ContactClient: React.FC = () => { + const [submitState, setSubmitState] = useState<'idle' | 'success' | 'error'>('idle'); + const [serverErrorMessage, setServerErrorMessage] = useState(null); + const { + register, + handleSubmit, + reset, + formState: { errors, isSubmitting, isSubmitted }, + } = useForm({ + resolver: zodResolver(contactSchema), + mode: 'onBlur', + reValidateMode: 'onChange', + defaultValues: { name: '', email: '', subject: '', message: '' }, + }); + + const onSubmit = async (values: ContactFormValues) => { + setServerErrorMessage(null); try { await submitContactForm({ - name: formData.name, - email: formData.email, - subject: formData.subject, - message: formData.message, + name: values.name, + email: values.email, + subject: values.subject, + message: values.message, }); - setSubmitStatus('success'); - setFormData({ name: '', email: '', subject: '', message: '' }); - setTimeout(() => setSubmitStatus('idle'), 5000); + setSubmitState('success'); } catch (error) { console.error('Failed to submit form:', error); - setSubmitStatus('error'); - setTimeout(() => setSubmitStatus('idle'), 5000); - } finally { - setIsSubmitting(false); + setServerErrorMessage( + 'Something went wrong on our end. Please try again in a moment, or email us directly.' + ); + setSubmitState('error'); } }; + const handleSendAnother = () => { + reset(); + setSubmitState('idle'); + setServerErrorMessage(null); + }; + return (
-
-
-

Get in Touch

-

- We'd love to hear from you! Whether you have a question about our events, merch, or anything else, our team is ready to answer all your questions. +

+
+

+ Contact CEUS +

+

+ Questions about events, merch, or a sponsorship enquiry? Send us a note and + the committee will be in touch.

-
+

+ We reply within 3 to 4 days during term. Responses may be slower during exams and breaks. +

+ + +
+ {/* Form column (wider) */} +
+

+ Send us a message +

+

+ Fields marked with + an asterisk are required. +

+ + {submitState === 'success' ? ( +
+

+ Thanks, your message is on its way. +

+

+ A committee member will reply within 3 to 4 days during term. + In the meantime, feel free to follow us on socials for updates. +

+

+ Prefer email? Reach us directly at{' '} + + {FALLBACK_EMAIL} + + . +

+
+ + + Email us instead + +
+
+ ) : ( +
+ {/* Name */} +
+ + + {errors.name && ( +

+ {errors.name.message} +

+ )} +
+ + {/* Email */} +
+ + + {errors.email ? ( +

+ {errors.email.message} +

+ ) : ( +

+ We will only use this to reply to your message. +

+ )} +
-
-
-

Contact Information

+ {/* Subject */} +
+ + + {errors.subject ? ( +

+ {errors.subject.message} +

+ ) : ( +

+ A short line like "Sponsorship enquiry" or "Merch question". +

+ )} +
+ + {/* Message */} +
+ + -
-
- -
- {submitStatus === 'success' && ( -
-

✓ Message sent successfully!

-

Thank you for contacting us. We'll get back to you shortly.

-
- )} - {submitStatus === 'error' && ( -
-

✗ Failed to send message

-

Please try again or email us directly at ceus@unsw.edu.au

-
- )} - -
+
diff --git a/CEUS/src/app/contact/page.tsx b/CEUS/src/app/contact/page.tsx index 5832a0e..b8127fa 100644 --- a/CEUS/src/app/contact/page.tsx +++ b/CEUS/src/app/contact/page.tsx @@ -4,8 +4,9 @@ import ContactClient from './ContactClient'; import { Metadata } from 'next'; export const metadata: Metadata = { - title: 'Contact Us', - description: 'Get in touch with the Chemical Engineering Undergraduate Society at UNSW. We are here to answer your questions.', + title: 'Contact CEUS', + description: + 'Get in touch with the Chemical Engineering Undergraduate Society at UNSW. Send us a message about events, merch, or sponsorship and we will reply within 3 to 4 days during term.', }; export default function ContactPage() {