diff --git a/libs/website/feature/contactpage/constants/types/contact.constants.ts b/libs/website/feature/contactpage/constants/types/contact.constants.ts new file mode 100644 index 000000000..1945b9187 --- /dev/null +++ b/libs/website/feature/contactpage/constants/types/contact.constants.ts @@ -0,0 +1,8 @@ +import type { FormData } from '../types/contact.types' + +export const DEFAULT_FORM_DATA: FormData = { + name: '', + email: '', + subject: '', + message: '', +} diff --git a/libs/website/feature/contactpage/constants/types/contact.types.ts b/libs/website/feature/contactpage/constants/types/contact.types.ts new file mode 100644 index 000000000..60e8495c5 --- /dev/null +++ b/libs/website/feature/contactpage/constants/types/contact.types.ts @@ -0,0 +1,13 @@ +export interface FormData { + name: string + email: string + subject: string + message: string +} + +export interface FormErrors { + name?: string + email?: string + subject?: string + message?: string +} diff --git a/libs/website/feature/contactpage/contactFormValidation.ts b/libs/website/feature/contactpage/contactFormValidation.ts new file mode 100644 index 000000000..9d2f38af5 --- /dev/null +++ b/libs/website/feature/contactpage/contactFormValidation.ts @@ -0,0 +1,48 @@ +export interface FormData { + name: string + email: string + subject: string + message: string +} + +export interface FormErrors { + name?: string + email?: string + subject?: string + message?: string +} + +export function validateForm(values: FormData): FormErrors { + const errors: FormErrors = {} + + /* Name validation */ + if (!values.name.trim()) { + errors.name = 'Name is required' + } + else if (values.name.length < 2) { + errors.name = 'Name must be at least 2 characters' + } + /* Email validation */ + if (!values.email) { + errors.email = 'Email is required' + } + else if (!/^[\w.%+-]+@[A-Z0-9.-]+\.[A-Z]{2,}$/i.test(values.email)) { + errors.email = 'Invalid email address' + } + /* Subject validation */ + if (!values.subject.trim()) { + errors.subject = 'Subject is required' + } + else if (values.subject.length < 3) { + errors.subject = 'Subject must be at least 3 characters' + } + /* Message validation */ + if (!values.message.trim()) { + errors.message = 'Message is required' + } + else if (values.message.length < 10) { + errors.message = 'Message must be at least 10 characters' + } + + return errors +} diff --git a/libs/website/feature/contactpage/index.tsx b/libs/website/feature/contactpage/index.tsx new file mode 100644 index 000000000..44d1b65a7 --- /dev/null +++ b/libs/website/feature/contactpage/index.tsx @@ -0,0 +1,62 @@ +import cuHackingLogo from '@cuhacking/shared/assets/logos/cuHacking/cuhacking-logo-1.svg' +import { GlassmorphicCard } from '@cuhacking/shared/ui/src/cuHacking/components/glassmorphic-card' +import React, { useEffect, useState } from 'react' +import { ContactForm } from './ui/ContactForm' +import { ContactHero } from './ui/ContactHero' +import { StatusMessage } from './ui/StatusMessage' + +export function ContactPage(): React.JSX.Element { + const [submitStatus, setSubmitStatus] = useState<'success' | 'error' | null>(null) + + // hiding message after 3 seconds + useEffect(() => { + if (submitStatus) { + const timer = setTimeout(() => { + setSubmitStatus(null) + }, 3000) + + return () => clearTimeout(timer) + } + }, [submitStatus]) + + const handleSubmit = (status: 'success' | 'error') => { + setSubmitStatus(null) + setTimeout(() => { + setSubmitStatus(status) + }, 100) + } + + return ( +
+
+ +
+ {/* Hero */} + + + {/* Status Message */} + {submitStatus && ( +
+ +
+ )} + + {/* Form */} + +
+ + {/* Logo */} + + cuHacking Logo + +
+
+
+ ) +} diff --git a/libs/website/feature/contactpage/ui/ContactForm.tsx b/libs/website/feature/contactpage/ui/ContactForm.tsx new file mode 100644 index 000000000..bba1e8c19 --- /dev/null +++ b/libs/website/feature/contactpage/ui/ContactForm.tsx @@ -0,0 +1,128 @@ +import type { FormData, FormErrors } from '../types/contact.types' +import React, { useState } from 'react' +import { validateForm } from '../contactFormValidation' + +interface ContactFormProps { + onSubmit: (status: 'success' | 'error') => void +} + +export function ContactForm({ onSubmit }: ContactFormProps): React.JSX.Element { + const [formData, setFormData] = useState({ + name: '', + email: '', + subject: '', + message: '', + }) + + const [errors, setErrors] = useState({}) + const [isLoading, setIsLoading] = useState(false) + + const handleChange = ( + e: React.ChangeEvent, + ): void => { + const { name, value } = e.target + + setFormData((prev: FormData) => ({ + ...prev, + [name]: value, + })) + + // Cleaning errors as user types + if (errors[name as keyof FormErrors]) { + setErrors((prev: FormErrors) => ({ + ...prev, + [name]: undefined, + })) + } + } + + const handleSubmit = async (e: React.FormEvent): Promise => { + e.preventDefault() + + const validationErrors = validateForm(formData) + if (Object.keys(validationErrors).length > 0) { + setErrors(validationErrors) + return + } + + setIsLoading(true) + + try { + await new Promise((resolve) => { + setTimeout(resolve, 1500) + }) + onSubmit('success') + setFormData({ name: '', email: '', subject: '', message: '' }) + } + catch (error) { + console.error('Submission error:', error) + onSubmit('error') + } + finally { + setIsLoading(false) + } + } + + return ( +
+
+
+ {/* Name */} + + +
+ + {/* Subject */} + + + {/* Message */} +