Portals link in nav/footer/mobile; foundation-aligned request assistance, donate, legal, and SEO; expanded sitemap; families-focused impact labels; fix document.title hook placement. Co-authored-by: Cursor <[email protected]>
108 lines
3.0 KiB
TypeScript
108 lines
3.0 KiB
TypeScript
import { useState, useEffect } from 'react'
|
|
|
|
// Types
|
|
export interface ImpactCalculation {
|
|
students: number
|
|
families: number
|
|
backpacks: number
|
|
clothing: number
|
|
emergency: number
|
|
annual: {
|
|
students: number
|
|
families: number
|
|
totalImpact: string
|
|
}
|
|
}
|
|
|
|
// Custom hook for donation impact calculations
|
|
export const useDonationImpact = (amount: number): ImpactCalculation => {
|
|
const [impact, setImpact] = useState<ImpactCalculation>({
|
|
students: 0,
|
|
families: 0,
|
|
backpacks: 0,
|
|
clothing: 0,
|
|
emergency: 0,
|
|
annual: {
|
|
students: 0,
|
|
families: 0,
|
|
totalImpact: '0 families supported annually'
|
|
}
|
|
})
|
|
|
|
useEffect(() => {
|
|
const calculateImpact = (donationAmount: number): ImpactCalculation => {
|
|
const families = Math.floor(donationAmount / 50) // $50 per family for comprehensive support
|
|
const students = families // legacy field — displayed as families in UI
|
|
const backpacks = Math.floor(donationAmount / 30) // outreach kits
|
|
const clothing = Math.floor(donationAmount / 45) // clothing & essentials
|
|
const emergency = Math.floor(donationAmount / 75) // emergency assistance
|
|
|
|
return {
|
|
students,
|
|
families,
|
|
backpacks,
|
|
clothing,
|
|
emergency,
|
|
annual: {
|
|
students: Math.floor((donationAmount * 12) / 50),
|
|
families: Math.floor((donationAmount * 12) / 50),
|
|
totalImpact: `${Math.floor((donationAmount * 12) / 50)} families supported annually`
|
|
}
|
|
}
|
|
}
|
|
|
|
setImpact(calculateImpact(amount))
|
|
}, [amount])
|
|
|
|
return impact
|
|
}
|
|
|
|
// Hook for form validation
|
|
export const useFormValidation = <T extends Record<string, any>>(
|
|
initialValues: T,
|
|
validationRules: Record<keyof T, (value: any) => string | null>
|
|
) => {
|
|
const [values, setValues] = useState<T>(initialValues)
|
|
const [errors, setErrors] = useState<Partial<Record<keyof T, string>>>({})
|
|
const [touched, setTouched] = useState<Partial<Record<keyof T, boolean>>>({})
|
|
|
|
const validate = (fieldName?: keyof T): boolean => {
|
|
const fieldsToValidate = fieldName ? [fieldName] : Object.keys(validationRules) as (keyof T)[]
|
|
const newErrors: Partial<Record<keyof T, string>> = { ...errors }
|
|
let isValid = true
|
|
|
|
fieldsToValidate.forEach((field) => {
|
|
const error = validationRules[field](values[field])
|
|
if (error) {
|
|
newErrors[field] = error
|
|
isValid = false
|
|
} else {
|
|
delete newErrors[field]
|
|
}
|
|
})
|
|
|
|
setErrors(newErrors)
|
|
return isValid
|
|
}
|
|
|
|
const setValue = (fieldName: keyof T, value: any): void => {
|
|
setValues(prev => ({ ...prev, [fieldName]: value }))
|
|
setTouched(prev => ({ ...prev, [fieldName]: true }))
|
|
}
|
|
|
|
const resetForm = (): void => {
|
|
setValues(initialValues)
|
|
setErrors({})
|
|
setTouched({})
|
|
}
|
|
|
|
return {
|
|
values,
|
|
errors,
|
|
touched,
|
|
setValue,
|
|
validate,
|
|
resetForm,
|
|
isValid: Object.keys(errors).length === 0
|
|
}
|
|
} |