Align site copy with foundation brief, restore Portals nav, and audit public routes.

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]>
This commit is contained in:
defiQUG
2026-05-26 22:33:06 -07:00
co-authored by Cursor
parent 83727e217a
commit 22a889fb91
10 changed files with 219 additions and 178 deletions
Binary file not shown.
+45 -30
View File
@@ -5,36 +5,6 @@
<changefreq>weekly</changefreq>
<priority>1.0</priority>
</url>
<url>
<loc>https://mim4u.org/#/donate</loc>
<changefreq>monthly</changefreq>
<priority>0.9</priority>
</url>
<url>
<loc>https://mim4u.org/#/request-assistance</loc>
<changefreq>monthly</changefreq>
<priority>0.9</priority>
</url>
<url>
<loc>https://mim4u.org/#/volunteers</loc>
<changefreq>monthly</changefreq>
<priority>0.8</priority>
</url>
<url>
<loc>https://mim4u.org/#/stories</loc>
<changefreq>weekly</changefreq>
<priority>0.8</priority>
</url>
<url>
<loc>https://mim4u.org/#/impact</loc>
<changefreq>monthly</changefreq>
<priority>0.8</priority>
</url>
<url>
<loc>https://mim4u.org/#/legal</loc>
<changefreq>monthly</changefreq>
<priority>0.5</priority>
</url>
<url>
<loc>https://mim4u.org/#/about</loc>
<changefreq>monthly</changefreq>
@@ -50,19 +20,64 @@
<changefreq>monthly</changefreq>
<priority>0.8</priority>
</url>
<url>
<loc>https://mim4u.org/#/stories</loc>
<changefreq>weekly</changefreq>
<priority>0.8</priority>
</url>
<url>
<loc>https://mim4u.org/#/testimonies</loc>
<changefreq>weekly</changefreq>
<priority>0.7</priority>
</url>
<url>
<loc>https://mim4u.org/#/events</loc>
<changefreq>weekly</changefreq>
<priority>0.7</priority>
</url>
<url>
<loc>https://mim4u.org/#/volunteers</loc>
<changefreq>monthly</changefreq>
<priority>0.8</priority>
</url>
<url>
<loc>https://mim4u.org/#/donate</loc>
<changefreq>monthly</changefreq>
<priority>0.9</priority>
</url>
<url>
<loc>https://mim4u.org/#/contact</loc>
<changefreq>monthly</changefreq>
<priority>0.8</priority>
</url>
<url>
<loc>https://mim4u.org/#/request-assistance</loc>
<changefreq>monthly</changefreq>
<priority>0.9</priority>
</url>
<url>
<loc>https://mim4u.org/#/sponsors</loc>
<changefreq>monthly</changefreq>
<priority>0.7</priority>
</url>
<url>
<loc>https://mim4u.org/#/impact</loc>
<changefreq>monthly</changefreq>
<priority>0.8</priority>
</url>
<url>
<loc>https://mim4u.org/#/legal</loc>
<changefreq>monthly</changefreq>
<priority>0.5</priority>
</url>
<url>
<loc>https://mim4u.org/#/brand</loc>
<changefreq>monthly</changefreq>
<priority>0.6</priority>
</url>
<url>
<loc>https://mim4u.org/#/portals</loc>
<changefreq>monthly</changefreq>
<priority>0.6</priority>
</url>
</urlset>
+111 -125
View File
@@ -158,12 +158,12 @@ const translations: Translations = {
'nav.donate': { en: 'Donate', es: 'Donar', fr: 'Faire un don' },
'nav.volunteer': { en: 'Volunteer', es: 'Voluntario', fr: 'Bénévole' },
'hero.title': {
en: 'Equipping kids for success—school supplies, clothing, & more',
en: 'Restoring hope through compassion, community, and faith',
es: 'Equipando a los niños para el éxito: útiles escolares, ropa y más',
fr: 'Équiper les enfants pour réussir — fournitures scolaires, vêtements et plus'
},
'donate.title': { en: 'Donate Now', es: 'Donar Ahora', fr: 'Faire un don maintenant' },
'impact.students': { en: 'Students Supported', es: 'Estudiantes Apoyados', fr: 'Étudiants Soutenus' }
'impact.families': { en: 'Families Supported', es: 'Familias Apoyadas', fr: 'Familles Soutenues' }
}
function LanguageProvider({ children }: { children: React.ReactNode }) {
@@ -276,12 +276,12 @@ function useAuth() {
/* ===================== Enhanced Impact Calculator ===================== */
function calculateDonationImpact(amount: number): ImpactCalculation {
const students = Math.floor(amount / 25) // $25 per student for basic supplies
const families = Math.floor(amount / 50) // $50 per family for comprehensive support
const backpacks = Math.floor(amount / 30) // $30 for complete backpack kit
const clothing = Math.floor(amount / 45) // $45 for clothing items
const emergency = Math.floor(amount / 75) // $75 for emergency assistance
const families = Math.floor(amount / 50)
const students = families // legacy display fields — shown as families served
const backpacks = Math.floor(amount / 40)
const clothing = Math.floor(amount / 45)
const emergency = Math.floor(amount / 75)
return {
students,
families,
@@ -289,10 +289,10 @@ function calculateDonationImpact(amount: number): ImpactCalculation {
clothing,
emergency,
annual: {
students: Math.floor((amount * 12) / 25),
students: Math.floor((amount * 12) / 50),
families: Math.floor((amount * 12) / 50),
totalImpact: `${Math.floor((amount * 12) / 25)} students supported annually`
}
totalImpact: `${Math.floor((amount * 12) / 50)} families supported annually`,
},
}
}
@@ -499,8 +499,8 @@ function SEOHead({ title, description, image }: { title?: string, description?:
tag.setAttribute('content', content)
}
updateOGTag('og:title', title || 'Miracles in Motion - Equipping Students for Success')
updateOGTag('og:description', description || 'Nonprofit providing students with school supplies, clothing, and emergency assistance to thrive in their education.')
updateOGTag('og:title', title || `${SITE.name}${SITE.brandMessage}`)
updateOGTag('og:description', description || 'Faith-centered nonprofit bringing hope, outreach, emergency assistance, and wellness support to families in Los Angeles County.')
updateOGTag('og:image', image || '/og-image.jpg')
updateOGTag('og:type', 'website')
}, [title, description, image])
@@ -1166,19 +1166,12 @@ function FeatureCard({ icon: Icon, title, body }: FeatureCardProps) {
<div className="font-semibold tracking-tight">{title}</div>
</div>
<p className="mt-3 text-sm leading-6 text-neutral-700 dark:text-neutral-300">{body}</p>
<motion.div
className="mt-5 text-sm text-primary-700 transition dark:text-primary-300 flex items-center"
whileHover={{ x: 8 }}
transition={{ duration: 0.2 }}
<a
href="#/what-we-do"
className="mt-5 inline-flex items-center text-sm text-primary-700 transition hover:underline dark:text-primary-300"
>
Explore
<motion.div
whileHover={{ x: 4 }}
transition={{ duration: 0.2 }}
>
<ArrowRight className="ml-1 h-4 w-4" />
</motion.div>
</motion.div>
Learn more <ArrowRight className="ml-1 h-4 w-4" />
</a>
</motion.div>
)
}
@@ -1561,10 +1554,10 @@ function DonatePage() {
const { t } = useLanguage()
const suggestedAmounts = [
{ amount: 25, impact: "School supplies for 1 student", popular: false },
{ amount: 50, impact: "Complete backpack kit for 1 student", popular: true },
{ amount: 100, impact: "School clothing for 2 students", popular: false },
{ amount: 250, impact: "Emergency fund for 5 families", popular: false }
{ amount: 25, impact: "Essentials for one individual", popular: false },
{ amount: 50, impact: "Support for one family in crisis", popular: true },
{ amount: 100, impact: "Outreach supplies for two families", popular: false },
{ amount: 250, impact: "Emergency relief for multiple families", popular: false },
]
const finalAmount = customAmount ? parseInt(customAmount) || 0 : selectedAmount
@@ -1575,12 +1568,12 @@ function DonatePage() {
const impact = calculateDonationImpact(amount)
const impactItems = []
if (impact.students > 0) impactItems.push(`${impact.students} student${impact.students > 1 ? 's' : ''} with supplies`)
if (impact.families > 0) impactItems.push(`${impact.families} famil${impact.families > 1 ? 'ies' : 'y'} with compassionate support`)
if (impact.backpacks > 0) impactItems.push(`${impact.backpacks} backpack kit${impact.backpacks > 1 ? 's' : ''}`)
if (impact.clothing > 0) impactItems.push(`${impact.clothing} clothing item${impact.clothing > 1 ? 's' : ''}`)
if (impact.emergency > 0) impactItems.push(`${impact.emergency} emergency response${impact.emergency > 1 ? 's' : ''}`)
return impactItems.length > 0 ? impactItems.join(', ') : "Every dollar helps a student in need"
return impactItems.length > 0 ? impactItems.join(', ') : "Every dollar helps restore hope for someone in need"
}
// Track donation interactions
@@ -1650,8 +1643,8 @@ function DonatePage() {
return (
<>
<SEOHead
title="Donate Now - Help Students in Need"
description="Make a secure donation to Miracles in Motion. Your gift provides school supplies, clothing, and emergency assistance directly to students who need it most."
title="Donate Now — Miracles in Motion Foundation"
description="Make a secure donation to support outreach, emergency assistance, and resource navigation for families in crisis throughout Los Angeles County."
/>
<PageShell title="Donate" icon={Heart} eyebrow="Give with confidence" cta={<a href="#/legal" className="btn-secondary focus:outline-none focus:ring-2 focus:ring-primary-500 focus:ring-offset-2" aria-label="View donation policies">Policies</a>}>
<div className="grid gap-8 md:grid-cols-3">
@@ -1683,13 +1676,13 @@ function DonatePage() {
<div className="grid gap-2 text-sm">
<div className="flex items-center justify-between p-2 bg-white/50 dark:bg-gray-800/50 rounded">
<span className="flex items-center gap-2">
<Backpack className="h-4 w-4" /> Students Supported
<Users className="h-4 w-4" /> Families supported
</span>
<span className="font-semibold">{impactData.students}</span>
<span className="font-semibold">{impactData.families}</span>
</div>
<div className="flex items-center justify-between p-2 bg-white/50 dark:bg-gray-800/50 rounded">
<span className="flex items-center gap-2">
<Package className="h-4 w-4" /> Backpack Kits
<Package className="h-4 w-4" /> Outreach kits
</span>
<span className="font-semibold">{impactData.backpacks}</span>
</div>
@@ -1717,7 +1710,7 @@ function DonatePage() {
<div className="card">
<div className="mb-6">
<h3 className="font-semibold text-lg mb-2">Choose Your Donation Amount</h3>
<p className="text-sm text-neutral-600 dark:text-neutral-400">Every dollar directly supports students in need</p>
<p className="text-sm text-neutral-600 dark:text-neutral-400">Every dollar directly supports families and individuals in need</p>
</div>
{/* Suggested Amounts - Mobile Optimized */}
@@ -2018,7 +2011,7 @@ function DonatePage() {
<div className="flex items-center gap-3">
<div className="w-2 h-2 bg-primary-500 rounded-full"></div>
<div className="text-sm text-neutral-700 dark:text-neutral-300">
<span className="font-medium">127 students</span> received school supplies this month
<span className="font-medium">Families across LA County</span> received outreach and emergency support this month
</div>
</div>
<div className="flex items-center gap-3">
@@ -2080,7 +2073,7 @@ function VolunteerPage() {
<select aria-label="Interests" className="input">
<option>Assemble kits</option>
<option>Delivery driver</option>
<option>School liaison</option>
<option>Community partner</option>
<option>Admin support</option>
</select>
<select aria-label="Availability" className="input">
@@ -2185,10 +2178,10 @@ function StoriesPage() {
<div className="font-medium">Submit your story</div>
<form className="mt-3 grid gap-3" onSubmit={(e)=>{e.preventDefault(); alert("Thanks for sharing! We'll review and publish with consent.");}}>
<input className="input" placeholder="Your name (or Anonymous)" aria-label="Name" />
<textarea className="input min-h-28 resize-none" placeholder="Your story (no private student info)" aria-label="Story" required />
<textarea className="input min-h-28 resize-none" placeholder="Your story (please omit private identifying details)" aria-label="Story" required />
<label className="flex items-start gap-3 text-xs opacity-80">
<input type="checkbox" className="mt-1 h-4 w-4" required />
I grant permission to publish this story (edited for length/clarity). I have removed personally identifying student information.
I grant permission to publish this story (edited for length/clarity). I have removed personally identifying information.
</label>
<button className="btn-primary w-full justify-center">Submit</button>
</form>
@@ -2199,8 +2192,8 @@ function StoriesPage() {
function TestimoniesPage() {
const items = [
{ who: "Parent", quote: "The uniform voucher saved us right before school started." },
{ who: "Counselor", quote: "The 24-hr turnaround is unmatched for our families." },
{ who: "Parent", quote: "They reminded us we were not alone hope came when we needed it most." },
{ who: "Community partner", quote: "Compassionate, fast, and rooted in faith. Our families felt seen." },
{ who: "Principal", quote: "Attendance improves when kids have what they need." },
]
@@ -2220,7 +2213,7 @@ function TestimoniesPage() {
function ImpactReportPage() {
return (
<PageShell title="Impact Report" icon={BarChart3} eyebrow="Our impact in numbers" cta={<a href="#/donate" className="btn-primary">Donate to support more students</a>}>
<PageShell title="Impact Report" icon={BarChart3} eyebrow="Our impact in the community" cta={<a href="#/donate" className="btn-primary">Donate to support more families</a>}>
<div className="max-w-4xl mx-auto space-y-8">
<p className="text-lg text-neutral-700 dark:text-neutral-300">
Miracles in Motion Foundation serves vulnerable individuals and families across Los Angeles County. For impact updates or data requests, contact <a className="underline" href={SITE.emailHref}>{SITE.email}</a>.
@@ -2228,7 +2221,7 @@ function ImpactReportPage() {
<div className="grid gap-6 sm:grid-cols-2 lg:grid-cols-4">
<div className="card text-center">
<div className="text-3xl font-bold text-primary-600 dark:text-primary-400">85%</div>
<div className="mt-1 text-sm text-neutral-600 dark:text-neutral-400">of donations go directly to student support</div>
<div className="mt-1 text-sm text-neutral-600 dark:text-neutral-400">of donations go directly to programs & outreach</div>
</div>
<div className="card text-center">
<div className="text-3xl font-bold text-primary-600 dark:text-primary-400">10%</div>
@@ -2246,7 +2239,7 @@ function ImpactReportPage() {
<div className="card">
<h3 className="font-semibold text-lg mb-2">How we confirm support</h3>
<p className="text-neutral-700 dark:text-neutral-300 text-sm">
When you submit a Get Help request, we verify with the school or counselor and approve within 24 hours when possible. We confirm back to the referrer that support reached the student. All data is handled in line with our <a className="underline" href="#/legal#privacy">Privacy Policy</a> and is never shared for marketing.
When you submit a request for assistance, we review your situation with care and respond as quickly as possible. All information is handled in line with our <a className="underline" href="#/legal#privacy">Privacy Policy</a> and is never shared for marketing.
</p>
</div>
</div>
@@ -2258,7 +2251,7 @@ function LegalPage() {
return (
<PageShell title="Legal & Policies" icon={FileText} eyebrow="Trust & compliance" cta={<a href="#/donate" className="btn-secondary">Donate</a>}>
<PolicySection id="privacy" title="Privacy Policy">
<p>We collect only the data necessary to process donations, volunteer coordination, and email subscriptions. We do not sell or trade personal data. Assistance requests (Get Help) are used only to verify and deliver support; we confirm to the referrer when support has reached the student and we do not share this data for marketing. You may request access, correction, or deletion at <a className="underline" href="mailto:privacy@mim4u.org">[email protected]</a>.</p>
<p>We collect only the data necessary to process donations, volunteer coordination, and email subscriptions. We do not sell or trade personal data. Assistance requests are used only to verify and deliver support; we do not share this data for marketing. You may request access, correction, or deletion at <a className="underline" href={SITE.emailHref}>{SITE.email}</a>.</p>
</PolicySection>
<PolicySection id="donor-privacy" title="Donor Privacy Policy">
<p>We never sell, share, or trade donor names or personal information with any other entity, nor send donor mailings on behalf of other organizations. This policy applies to all information received online and offline.</p>
@@ -2274,12 +2267,12 @@ function LegalPage() {
<p>We use essential cookies for site functionality and, with your consent, privacy-friendly analytics. You can change your choices via the cookie banner or by clearing site data.</p>
</PolicySection>
<PolicySection id="refunds" title="Refund Policy">
<p>Donations are generally non-refundable. If you believe a donation was made in error, contact <a className="underline" href="mailto:donate@mim4u.org">[email protected]</a> within 15 days for assistance.</p>
<p>Donations are generally non-refundable. If you believe a donation was made in error, contact <a className="underline" href={SITE.emailHref}>{SITE.email}</a> within 15 days for assistance.</p>
</PolicySection>
<PolicySection id="gift-acceptance" title="Gift Acceptance Policy">
<ul className="list-disc pl-5 space-y-2">
<li>We accept monetary gifts, new school supplies/clothing, DAF grants, and publicly traded securities.</li>
<li>In-kind items must be new and appropriate for school settings. We reserve the right to decline gifts not aligned with mission or capacity.</li>
<li>We accept monetary gifts, in-kind donations for outreach, DAF grants, and publicly traded securities.</li>
<li>In-kind items must be new and appropriate for families in need. We reserve the right to decline gifts not aligned with mission or capacity.</li>
</ul>
</PolicySection>
<PolicySection id="volunteer-waiver" title="Volunteer Waiver & Liability Release">
@@ -2293,7 +2286,7 @@ function LegalPage() {
</ul>
</PolicySection>
<PolicySection id="nondiscrimination" title="NonDiscrimination Statement">
<p>We serve students regardless of race, color, religion, national origin, sex, sexual orientation, gender identity, disability, or any other protected status.</p>
<p>We serve individuals and families regardless of race, color, religion, national origin, sex, sexual orientation, gender identity, disability, or any other protected status.</p>
</PolicySection>
<PolicySection id="accessibility" title="Accessibility Statement">
<p>We aim to meet WCAG 2.1 AA standards. If you encounter accessibility barriers, email <a className="underline" href="mailto:access@miraclesinmotion.org">[email protected]</a>.</p>
@@ -2417,21 +2410,21 @@ function AssistanceRequestPage() {
case 'requestType':
return !v.trim() ? 'Please select a request type.' : ''
case 'studentInfo.firstName':
return !v.trim() ? 'Student first name is required.' : ''
return !v.trim() ? 'First name is required.' : ''
case 'studentInfo.lastName':
return !v.trim() ? 'Student last name is required.' : ''
return !v.trim() ? 'Last name is required.' : ''
case 'studentInfo.grade':
return !v.trim() ? 'Please select a grade.' : ''
return ''
case 'studentInfo.school':
return !v.trim() ? 'School name is required.' : ''
return !v.trim() ? 'City or neighborhood in LA County is required.' : ''
case 'contactInfo.parentName':
return !v.trim() ? 'Parent/guardian name is required.' : ''
return !v.trim() ? 'Your name is required.' : ''
case 'contactInfo.phone':
return !v.trim() ? 'Phone number is required.' : /^[\d\s\-\(\)\+\.]{10,}$/.test(v.replace(/\s/g, '')) ? '' : 'Please enter a valid phone number (at least 10 digits).'
case 'contactInfo.email':
return !v.trim() ? 'Email is required.' : /^[^\s@]+@[^\s@]+\.[^\s@]+$/.test(v) ? '' : 'Please enter a valid email address.'
case 'contactInfo.relationship':
return !v.trim() ? 'Please select your relationship to the student.' : ''
return !v.trim() ? 'Please select your relationship.' : ''
case 'details':
return !v.trim() ? 'Please describe the situation and specific needs.' : ''
default:
@@ -2453,7 +2446,6 @@ function AssistanceRequestPage() {
['requestType', formData.requestType],
['studentInfo.firstName', formData.studentInfo.firstName],
['studentInfo.lastName', formData.studentInfo.lastName],
['studentInfo.grade', formData.studentInfo.grade],
['studentInfo.school', formData.studentInfo.school],
['contactInfo.parentName', formData.contactInfo.parentName],
['contactInfo.phone', formData.contactInfo.phone],
@@ -2488,7 +2480,7 @@ function AssistanceRequestPage() {
}
return (
<PageShell title="Request Assistance" icon={ClipboardList} eyebrow="Get help for students in need">
<PageShell title="Request Assistance" icon={ClipboardList} eyebrow="Compassionate support for individuals and families in crisis">
<div className="max-w-4xl mx-auto">
{/* Emergency Notice */}
<motion.div
@@ -2523,10 +2515,10 @@ function AssistanceRequestPage() {
{errors.requestType && <p className="mb-2 text-sm text-red-600 dark:text-red-400" role="alert">{errors.requestType}</p>}
<div className="grid gap-3 sm:grid-cols-2 lg:grid-cols-4">
{[
{ id: 'individual', label: 'Individual Student', icon: Users, desc: 'Help for one student' },
{ id: 'family', label: 'Family (Multiple)', icon: HomeIcon, desc: 'Multiple children' },
{ id: 'classroom', label: 'Classroom Need', icon: School, desc: 'Entire class project' },
{ id: 'emergency', label: 'Emergency', icon: AlertCircle, desc: 'Urgent assistance' }
{ id: 'individual', label: 'Individual', icon: Users, desc: 'Support for one person' },
{ id: 'family', label: 'Family', icon: HomeIcon, desc: 'Support for a household' },
{ id: 'partner', label: 'Agency / partner', icon: Building2, desc: 'Referral from a community partner' },
{ id: 'emergency', label: 'Emergency', icon: AlertCircle, desc: 'Urgent assistance — call if immediate' }
].map(type => (
<motion.label
key={type.id}
@@ -2559,12 +2551,12 @@ function AssistanceRequestPage() {
</div>
</div>
{/* Student Information */}
{/* Person needing assistance */}
<div className="card">
<h3 className="font-semibold text-lg mb-4">Student Information</h3>
<h3 className="font-semibold text-lg mb-4">Who needs assistance?</h3>
<div className="grid gap-4 sm:grid-cols-2">
<div>
<label className="block text-sm font-medium mb-2" htmlFor="student-first-name">Student First Name *</label>
<label className="block text-sm font-medium mb-2" htmlFor="student-first-name">First name *</label>
<input
id="student-first-name"
required
@@ -2582,7 +2574,7 @@ function AssistanceRequestPage() {
{errors['studentInfo.firstName'] && <p id="err-student-first-name" className="mt-1 text-sm text-red-600 dark:text-red-400" role="alert">{errors['studentInfo.firstName']}</p>}
</div>
<div>
<label className="block text-sm font-medium mb-2" htmlFor="student-last-name">Student Last Name *</label>
<label className="block text-sm font-medium mb-2" htmlFor="student-last-name">Last name *</label>
<input
id="student-last-name"
required
@@ -2600,37 +2592,30 @@ function AssistanceRequestPage() {
{errors['studentInfo.lastName'] && <p id="err-student-last-name" className="mt-1 text-sm text-red-600 dark:text-red-400" role="alert">{errors['studentInfo.lastName']}</p>}
</div>
<div>
<label className="block text-sm font-medium mb-2" htmlFor="student-grade">Grade Level *</label>
<label className="block text-sm font-medium mb-2" htmlFor="student-grade">Age range (optional)</label>
<select
id="student-grade"
required
className={`input ${errors['studentInfo.grade'] ? 'border-red-500 dark:border-red-400' : ''}`}
className="input"
value={formData.studentInfo.grade}
onChange={(e) => {
setFormData({ ...formData, studentInfo: { ...formData.studentInfo, grade: e.target.value } })
setFieldError('studentInfo.grade', e.target.value)
}}
onBlur={() => setFieldError('studentInfo.grade', formData.studentInfo.grade)}
aria-invalid={!!errors['studentInfo.grade']}
aria-describedby={errors['studentInfo.grade'] ? 'err-student-grade' : undefined}
>
<option value="">Select grade</option>
<option value="PreK">Pre-K</option>
<option value="K">Kindergarten</option>
{Array.from({length: 12}, (_, i) => (
<option key={i+1} value={`${i+1}`}>Grade {i+1}</option>
))}
<option value="">Prefer not to say</option>
<option value="child">Child (under 12)</option>
<option value="teen">Teen (1217)</option>
<option value="adult">Adult (1864)</option>
<option value="senior">Senior (65+)</option>
</select>
{errors['studentInfo.grade'] && <p id="err-student-grade" className="mt-1 text-sm text-red-600 dark:text-red-400" role="alert">{errors['studentInfo.grade']}</p>}
</div>
<div>
<label className="block text-sm font-medium mb-2" htmlFor="student-school">School Name *</label>
<label className="block text-sm font-medium mb-2" htmlFor="student-school">City / neighborhood in LA County *</label>
<input
id="student-school"
required
type="text"
className={`input ${errors['studentInfo.school'] ? 'border-red-500 dark:border-red-400' : ''}`}
placeholder="Full school name"
placeholder="e.g. San Fernando Valley, South LA"
value={formData.studentInfo.school}
onChange={(e) => {
setFormData({ ...formData, studentInfo: { ...formData.studentInfo, school: e.target.value } })
@@ -2650,7 +2635,7 @@ function AssistanceRequestPage() {
<h3 className="font-semibold text-lg mb-4">Contact Information</h3>
<div className="grid gap-4 sm:grid-cols-2">
<div>
<label className="block text-sm font-medium mb-2" htmlFor="contact-parent">Parent/Guardian Name *</label>
<label className="block text-sm font-medium mb-2" htmlFor="contact-parent">Your name *</label>
<input
id="contact-parent"
required
@@ -2705,7 +2690,7 @@ function AssistanceRequestPage() {
{errors['contactInfo.email'] && <p id="err-contact-email" className="mt-1 text-sm text-red-600 dark:text-red-400" role="alert">{errors['contactInfo.email']}</p>}
</div>
<div>
<label className="block text-sm font-medium mb-2" htmlFor="contact-relationship">Relationship to Student *</label>
<label className="block text-sm font-medium mb-2" htmlFor="contact-relationship">Your relationship *</label>
<select
id="contact-relationship"
required
@@ -2723,9 +2708,10 @@ function AssistanceRequestPage() {
<option value="parent">Parent</option>
<option value="guardian">Legal Guardian</option>
<option value="relative">Relative</option>
<option value="counselor">School Counselor</option>
<option value="social-worker">Social Worker</option>
<option value="teacher">Teacher</option>
<option value="self">Self (requesting for myself)</option>
<option value="social-worker">Social worker / case manager</option>
<option value="partner">Community partner / agency</option>
<option value="faith-leader">Faith leader / pastor</option>
</select>
{errors['contactInfo.relationship'] && <p id="err-contact-relationship" className="mt-1 text-sm text-red-600 dark:text-red-400" role="alert">{errors['contactInfo.relationship']}</p>}
</div>
@@ -2734,15 +2720,15 @@ function AssistanceRequestPage() {
{/* Needs Assessment */}
<div className="card">
<h3 className="font-semibold text-lg mb-4">What does the student need? (Select all that apply)</h3>
<h3 className="font-semibold text-lg mb-4">What kind of support is needed? (Select all that apply)</h3>
<div className="grid gap-4 sm:grid-cols-2 lg:grid-cols-3">
{[
{ key: 'schoolSupplies', label: 'School Supplies', icon: BookOpenText, desc: 'Notebooks, pencils, folders, etc.' },
{ key: 'clothing', label: 'School Clothing', icon: Shirt, desc: 'Uniforms, appropriate attire' },
{ key: 'shoes', label: 'Shoes', icon: Package, desc: 'School-appropriate footwear' },
{ key: 'backpack', label: 'Backpack', icon: Backpack, desc: 'New backpack or bag' },
{ key: 'emergency', label: 'Emergency Fund', icon: CreditCard, desc: 'Fees, glasses, urgent needs' },
{ key: 'other', label: 'Other', icon: Plus, desc: 'Specify in description below' }
{ key: 'schoolSupplies', label: 'Essentials & supplies', icon: Package, desc: 'Food, hygiene, household basics' },
{ key: 'clothing', label: 'Clothing', icon: Shirt, desc: 'Seasonal clothing and dignity items' },
{ key: 'shoes', label: 'Wellness support', icon: Heart, desc: 'Grief, trauma, mental health navigation' },
{ key: 'backpack', label: 'Resource navigation', icon: BookOpenText, desc: 'Help connecting to services' },
{ key: 'emergency', label: 'Emergency assistance', icon: CreditCard, desc: 'Urgent financial or crisis needs' },
{ key: 'other', label: 'Other', icon: Plus, desc: 'Describe below' }
].map(need => (
<label key={need.key} className="flex items-start gap-3 p-3 border border-neutral-200 dark:border-neutral-700 rounded-lg hover:bg-neutral-50 dark:hover:bg-neutral-800/50 cursor-pointer transition-colors">
<input
@@ -2779,7 +2765,7 @@ function AssistanceRequestPage() {
required
rows={4}
className={`input resize-none ${errors.details ? 'border-red-500 dark:border-red-400' : ''}`}
placeholder="Please provide details about the student's situation, specific items needed, sizes if applicable, and any special circumstances..."
placeholder="Please describe the situation, what support is needed, urgency, and any relevant details (omit sensitive identifiers you do not wish to share)..."
value={formData.details}
onChange={(e) => {
setFormData({ ...formData, details: e.target.value })
@@ -2834,7 +2820,7 @@ function AssistanceRequestPage() {
<li>• All information is kept confidential and secure</li>
<li>• We will contact you within 24-48 hours to verify details</li>
<li>• Assistance is provided at no cost to families</li>
<li>• We work directly with schools when possible</li>
<li>• We may coordinate with trusted community partners when appropriate</li>
</ul>
</div>
</div>
@@ -2965,14 +2951,14 @@ function PortalsPage() {
</div>
<h3 className="text-xl font-semibold text-green-900 dark:text-green-100">Resource Center Portal</h3>
<p className="text-sm text-green-700 dark:text-green-300 mt-2">
For schools, social workers, and partner organizations
For community partners, agencies, and referral organizations
</p>
</div>
<div className="space-y-2 text-sm text-green-800 dark:text-green-200 mb-6">
<div className="flex items-center gap-2">
<ClipboardList className="h-4 w-4" />
<span>Submit student assistance requests</span>
<span>Submit assistance requests</span>
</div>
<div className="flex items-center gap-2">
<FileCheck className="h-4 w-4" />
@@ -3184,7 +3170,7 @@ function PortalsPage() {
<h4 className="font-medium mb-3">Login Issues</h4>
<ul className="text-sm text-neutral-600 dark:text-neutral-400 space-y-1">
<li>• Use forgot password link on login page</li>
<li>• Contact IT support: [email protected]</li>
<li>• Contact IT support: <a className="underline" href={SITE.emailHref}>{SITE.email}</a></li>
<li>• Call main office: (818) 491-6884</li>
<li>• Check email for account activation</li>
</ul>
@@ -3682,9 +3668,9 @@ function AdminPortalPage() {
</div>
<div className="space-y-4">
{[
{ student: 'Maria S.', school: 'Porter Elementary', need: 'School supplies', priority: 'High', time: '2 hours ago' },
{ student: 'James R.', school: 'Valley Middle School', need: 'Winter clothing', priority: 'Medium', time: '4 hours ago' },
{ student: 'Ana L.', school: 'Northridge High', need: 'Backpack & supplies', priority: 'Low', time: '1 day ago' }
{ student: 'Maria S.', school: 'San Fernando Valley', need: 'Emergency essentials', priority: 'High', time: '2 hours ago' },
{ student: 'James R.', school: 'South LA', need: 'Clothing & outreach', priority: 'Medium', time: '4 hours ago' },
{ student: 'Ana L.', school: 'East LA', need: 'Resource navigation', priority: 'Low', time: '1 day ago' }
].map((request, i) => (
<div key={i} className="flex items-center justify-between p-4 bg-neutral-50 dark:bg-neutral-800 rounded-lg">
<div className="flex-1">
@@ -3776,9 +3762,9 @@ function VolunteerPortalPage() {
<div className="space-y-4">
{[
{ time: '9:00 AM', task: 'Kit Assembly - Elementary Supplies', location: 'Main Warehouse', students: 12 },
{ time: '1:00 PM', task: 'Delivery Route - Porter Ranch Area', location: 'Various Schools', students: 5 },
{ time: '3:30 PM', task: 'Inventory Count - Winter Clothing', location: 'Storage Room B', students: null }
{ time: '9:00 AM', task: 'Outreach kit assembly', location: 'Main warehouse', students: 12 },
{ time: '1:00 PM', task: 'Delivery route — Valley area', location: 'LA County', students: 5 },
{ time: '3:30 PM', task: 'Inventory — seasonal clothing', location: 'Storage room B', students: null }
].map((task, i) => (
<div key={i} className="flex items-center justify-between p-4 bg-white dark:bg-neutral-900 rounded-lg shadow-sm">
<div className="flex items-center gap-4">
@@ -3788,7 +3774,7 @@ function VolunteerPortalPage() {
<div>
<h4 className="font-medium">{task.task}</h4>
<p className="text-sm text-neutral-600 dark:text-neutral-400">{task.location}</p>
{task.students && <p className="text-xs text-green-600 dark:text-green-400">{task.students} students served</p>}
{task.students && <p className="text-xs text-green-600 dark:text-green-400">{task.students} families served</p>}
</div>
</div>
<button className="btn-secondary text-sm">
@@ -3809,9 +3795,9 @@ function VolunteerPortalPage() {
</div>
<div className="space-y-3">
{[
{ student: 'Sofia M.', items: 'Backpack, Notebooks', school: 'Valley Elementary', deadline: 'Tomorrow' },
{ student: 'Carlos R.', items: 'Winter jacket, Gloves', school: 'Oak Middle School', deadline: 'Friday' },
{ student: 'Emma K.', items: 'Art supplies kit', school: 'Northridge High', deadline: 'Next week' }
{ student: 'Sofia M.', items: 'Essentials kit', school: 'San Fernando Valley', deadline: 'Tomorrow' },
{ student: 'Carlos R.', items: 'Winter clothing', school: 'South LA', deadline: 'Friday' },
{ student: 'Emma K.', items: 'Wellness navigation', school: 'East LA', deadline: 'Next week' }
].map((delivery, i) => (
<div key={i} className="p-3 border border-neutral-200 dark:border-neutral-700 rounded-lg">
<div className="flex items-center justify-between mb-2">
@@ -3832,7 +3818,7 @@ function VolunteerPortalPage() {
<div className="flex items-center justify-between">
<div className="flex items-center gap-2">
<Users className="h-4 w-4 text-primary-600 dark:text-primary-400" />
<span>Students Helped</span>
<span>Families helped</span>
</div>
<span className="font-semibold text-primary-600 dark:text-primary-400">47</span>
</div>
@@ -3876,7 +3862,7 @@ function ResourcePortalPage() {
return (
<PortalWrapper requiredRole="resource">
<SEOHead title="Resource Portal" description="Resource center portal for submitting and tracking student assistance requests." />
<SEOHead title="Resource Portal" description="Partner portal for submitting and tracking assistance requests." />
<PageShell
title="Resource Center Portal"
icon={School}
@@ -3894,7 +3880,7 @@ function ResourcePortalPage() {
<Plus className="h-8 w-8 text-green-600 dark:text-green-400" />
<div>
<h3 className="text-lg font-semibold text-green-900 dark:text-green-100">Quick Request Submission</h3>
<p className="text-green-700 dark:text-green-300">Submit a new student assistance request</p>
<p className="text-green-700 dark:text-green-300">Submit a new assistance request</p>
</div>
</div>
@@ -3920,9 +3906,9 @@ function ResourcePortalPage() {
</div>
<div className="space-y-4">
{[
{ id: 'REQ-2024-0342', student: 'Maria Santos', status: 'In Progress', need: 'School supplies', submitted: '2 days ago', eta: 'Tomorrow' },
{ id: 'REQ-2024-0341', student: 'James Rodriguez', status: 'Approved', need: 'Winter clothing', submitted: '3 days ago', eta: 'Today' },
{ id: 'REQ-2024-0340', student: 'Ana Lopez', status: 'Delivered', need: 'Backpack & supplies', submitted: '1 week ago', eta: 'Completed' }
{ id: 'REQ-2024-0342', student: 'Maria Santos', status: 'In Progress', need: 'Emergency essentials', submitted: '2 days ago', eta: 'Tomorrow' },
{ id: 'REQ-2024-0341', student: 'James Rodriguez', status: 'Approved', need: 'Clothing & outreach', submitted: '3 days ago', eta: 'Today' },
{ id: 'REQ-2024-0340', student: 'Ana Lopez', status: 'Delivered', need: 'Resource navigation', submitted: '1 week ago', eta: 'Completed' }
].map((request, i) => (
<div key={i} className="p-4 border border-neutral-200 dark:border-neutral-700 rounded-lg">
<div className="flex items-center justify-between mb-2">
@@ -4091,7 +4077,7 @@ function AnalyticsDashboard() {
>
<div className="flex items-center justify-between">
<div>
<p className="text-sm text-primary-600 dark:text-primary-400">Students Helped</p>
<p className="text-sm text-primary-600 dark:text-primary-400">Families helped</p>
<p className="text-2xl font-bold text-primary-900 dark:text-primary-100">1,847</p>
</div>
<Target className="h-8 w-8 text-primary-500" />
@@ -4168,7 +4154,7 @@ function AnalyticsDashboard() {
{[
{ action: 'New donation', details: '$125 from Sarah M.', time: '2 minutes ago', icon: Heart },
{ action: 'Volunteer signup', details: 'John D. registered', time: '8 minutes ago', icon: Users },
{ action: 'Assistance request', details: 'Roosevelt Elementary', time: '15 minutes ago', icon: School },
{ action: 'Assistance request', details: 'San Fernando Valley', time: '15 minutes ago', icon: MapPin },
{ action: 'Story shared', details: 'Maria\'s success story', time: '1 hour ago', icon: BookOpenText }
].map((activity, index) => (
<motion.div
@@ -4234,7 +4220,7 @@ function AIPortalPage() {
return (
<PortalWrapper requiredRole="admin">
<SEOHead title="AI Assistance Portal" description="AI-powered student assistance matching and insights for Miracles in Motion." />
<SEOHead title="AI Assistance Portal" description="AI-powered request matching and insights for Miracles in Motion Foundation." />
<PageShell
title="AI Assistance Portal"
icon={Brain}
@@ -4488,14 +4474,14 @@ function AppContent() {
}
}, [isOnline, addNotification])
const renderPage = () => {
// Update document title based on current route
useEffect(() => {
document.title = currentPath === "/"
useEffect(() => {
document.title =
currentPath === '/'
? `${SITE.name} — ${SITE.brandMessage}`
: `Miracles in Motion — ${currentPath.replace("/", "").replace(/-/g, " ").replace(/\b\w/g, (m) => m.toUpperCase())}`
}, [currentPath])
: `Miracles in Motion — ${currentPath.replace('/', '').replace(/-/g, ' ').replace(/\b\w/g, (m) => m.toUpperCase())}`
}, [currentPath])
const renderPage = () => {
switch (currentPath) {
case '/':
return <HomePage />
+15
View File
@@ -98,6 +98,21 @@ export function Footer() {
Legal & Policies
</a>
</li>
<li>
<a href="#/brand" className="navlink">
Brand assets
</a>
</li>
<li>
<a href="#/portals" className="navlink">
Staff & partner portals
</a>
</li>
<li>
<a href="#/sponsors" className="navlink">
Corporate partnerships
</a>
</li>
</ul>
</div>
</div>
+7 -4
View File
@@ -139,12 +139,15 @@ export function Navigation({ darkMode, setDarkMode, mobileMenuOpen, setMobileMen
<a className="block rounded py-2 text-sm font-medium" href="#/donate">
Donate
</a>
<a
className="block rounded py-2 text-sm text-neutral-600 dark:text-neutral-400"
href="#/request-assistance"
>
<a className="block rounded py-2 text-sm font-medium" href="#/request-assistance">
Request assistance
</a>
<a className="block rounded py-2 text-sm font-medium" href="#/portals">
Portals
</a>
<a className="block rounded py-2 text-sm font-medium" href="#/sponsors">
Partner with us
</a>
</div>
</div>
</motion.div>
+2 -2
View File
@@ -32,7 +32,7 @@ export function SEOHead({
{/* Basic Meta Tags */}
<title>{fullTitle}</title>
<meta name="description" content={description} />
<meta name="keywords" content="nonprofit, education, student support, school supplies, clothing assistance, emergency aid, 501c3, charity, community" />
<meta name="keywords" content="nonprofit, faith, community outreach, emergency assistance, wellness, trauma support, Los Angeles County, 501c3, charity, family support" />
<link rel="canonical" href={fullUrl} />
{/* Open Graph Meta Tags */}
@@ -114,7 +114,7 @@ export function SEOHead({
"@type": "DonateAction",
"name": "Donate to Miracles in Motion",
"url": "https://mim4u.org/#/donate",
"description": "Donate to support students with essential supplies, clothing, and support.",
"description": "Donate to support outreach, emergency assistance, and compassionate care for families in Los Angeles County.",
"target": {
"@type": "EntryPoint",
"urlTemplate": "https://mim4u.org/#/donate",
@@ -37,6 +37,7 @@ describe('Navigation Component', () => {
expect(screen.getByLabelText('Community events')).toBeInTheDocument()
expect(screen.getByLabelText('Volunteer opportunities')).toBeInTheDocument()
expect(screen.getByLabelText('Contact us')).toBeInTheDocument()
expect(screen.getByLabelText('Staff and partner portals')).toBeInTheDocument()
})
it('toggles dark mode when button is clicked', () => {
+21
View File
@@ -77,6 +77,27 @@ export const NAV_LINKS = [
{ href: '#/events', label: 'Events', ariaLabel: 'Community events' },
{ href: '#/volunteers', label: 'Volunteer', ariaLabel: 'Volunteer opportunities' },
{ href: '#/contact', label: 'Contact', ariaLabel: 'Contact us' },
{ href: '#/portals', label: 'Portals', ariaLabel: 'Staff and partner portals' },
] as const
/** Public hash routes for audits and sitemap alignment */
export const PUBLIC_ROUTES = [
{ path: '/', title: 'Home' },
{ path: '/about', title: 'About Us' },
{ path: '/mission', title: 'Our Mission' },
{ path: '/what-we-do', title: 'What We Do' },
{ path: '/stories', title: 'Stories of Hope' },
{ path: '/testimonies', title: 'Testimonies' },
{ path: '/events', title: 'Community Events' },
{ path: '/volunteers', title: 'Volunteer' },
{ path: '/donate', title: 'Donate' },
{ path: '/contact', title: 'Contact' },
{ path: '/request-assistance', title: 'Request Assistance' },
{ path: '/sponsors', title: 'Corporate Partnerships' },
{ path: '/legal', title: 'Legal & Policies' },
{ path: '/brand', title: 'Brand Assets' },
{ path: '/impact', title: 'Impact Report' },
{ path: '/portals', title: 'Staff & Partner Portals' },
] as const
export const INTRO = {
+7 -7
View File
@@ -25,17 +25,17 @@ export const useDonationImpact = (amount: number): ImpactCalculation => {
annual: {
students: 0,
families: 0,
totalImpact: '0 students supported annually'
totalImpact: '0 families supported annually'
}
})
useEffect(() => {
const calculateImpact = (donationAmount: number): ImpactCalculation => {
const students = Math.floor(donationAmount / 25) // $25 per student for basic supplies
const families = Math.floor(donationAmount / 50) // $50 per family for comprehensive support
const backpacks = Math.floor(donationAmount / 30) // $30 for complete backpack kit
const clothing = Math.floor(donationAmount / 45) // $45 for clothing items
const emergency = Math.floor(donationAmount / 75) // $75 for emergency assistance
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,
@@ -44,9 +44,9 @@ export const useDonationImpact = (amount: number): ImpactCalculation => {
clothing,
emergency,
annual: {
students: Math.floor((donationAmount * 12) / 25),
students: Math.floor((donationAmount * 12) / 50),
families: Math.floor((donationAmount * 12) / 50),
totalImpact: `${Math.floor((donationAmount * 12) / 25)} students supported annually`
totalImpact: `${Math.floor((donationAmount * 12) / 50)} families supported annually`
}
}
}
+10 -10
View File
@@ -20,14 +20,14 @@ export const DonatePage: React.FC<DonatePageProps> = ({ className }) => {
const impact = useDonationImpact(finalAmount)
useEffect(() => {
trackPageView('/donate', 'Donate - Support Students in Need')
trackPageView('/donate', 'Donate — Miracles in Motion Foundation')
}, [])
const donationTiers = [
{ amount: 25, label: "Supplies for one student" },
{ amount: 50, label: "Fill a backpack with essentials" },
{ amount: 75, label: "Shoes + warm coat" },
{ amount: 100, label: "Complete school outfit & supplies" },
{ amount: 25, label: "Essentials for one individual" },
{ amount: 50, label: "Support for one family in crisis" },
{ amount: 75, label: "Outreach supplies + warm clothing" },
{ amount: 100, label: "Emergency relief for two families" },
]
const handleAmountSelect = (amount: number): void => {
@@ -50,7 +50,7 @@ export const DonatePage: React.FC<DonatePageProps> = ({ className }) => {
title="Make a Difference Today"
icon={Heart}
eyebrow="Every dollar counts"
subtitle="Your donation directly supports students with school supplies, clothing, and emergency assistance."
subtitle="Your donation directly supports outreach, emergency assistance, and compassionate care for families throughout Los Angeles County."
className={className}
>
<div className="grid gap-8 lg:grid-cols-3">
@@ -138,10 +138,10 @@ const ImpactCalculator: React.FC<ImpactCalculatorProps> = ({ impact, amount }) =
</div>
<div className="space-y-3">
{impact.students > 0 && (
{impact.families > 0 && (
<ImpactItem
count={impact.students}
label={`student${impact.students > 1 ? 's' : ''} with supplies`}
count={impact.families}
label={`famil${impact.families > 1 ? 'ies' : 'y'} with compassionate support`}
/>
)}
{impact.backpacks > 0 && (
@@ -356,7 +356,7 @@ const TrustBadges: React.FC = () => (
<div className="flex items-center gap-3">
<CheckCircle2 className="w-5 h-5 text-green-500" />
<span className="text-sm">100% Goes to Students</span>
<span className="text-sm">100% to programs & outreach</span>
</div>
</div>