diff --git a/src/App.tsx b/src/App.tsx index 2805331..965861a 100644 --- a/src/App.tsx +++ b/src/App.tsx @@ -1,4 +1,4 @@ -import React, { useState, useEffect, useRef } from 'react' +import React, { useState, useEffect, useRef, useContext, createContext } from 'react' import { motion, useMotionValue, useSpring, useTransform, useScroll } from 'framer-motion' import { ArrowRight, @@ -51,11 +51,178 @@ import { * donation processing, volunteer management, and impact tracking. */ +/* ===================== Authentication Context ===================== */ +const AuthContext = createContext(null) + +export function AuthProvider({ children }: { children: React.ReactNode }) { + const [user, setUser] = useState(null) + const [isLoading, setIsLoading] = useState(false) + + const login = async (email: string, password: string): Promise => { + setIsLoading(true) + try { + // Simulate authentication - in production, this would call your API + await new Promise(resolve => setTimeout(resolve, 1000)) + + // Simple demo validation (in production, validate against secure backend) + if (password.length < 3) { + return false + } + + // Mock user data based on email domain + const mockUser: AuthUser = { + id: Math.random().toString(36), + email, + role: email.includes('admin') ? 'admin' : email.includes('volunteer') ? 'volunteer' : 'resource', + name: email.split('@')[0].replace('.', ' ').replace(/\b\w/g, l => l.toUpperCase()), + lastLogin: new Date(), + permissions: email.includes('admin') ? ['read', 'write', 'delete', 'manage'] : ['read', 'write'] + } + + setUser(mockUser) + localStorage.setItem('mim_user', JSON.stringify(mockUser)) + return true + } catch (error) { + console.error('Login failed:', error) + return false + } finally { + setIsLoading(false) + } + } + + const logout = () => { + setUser(null) + localStorage.removeItem('mim_user') + } + + // Restore user session on app load + useEffect(() => { + const savedUser = localStorage.getItem('mim_user') + if (savedUser) { + try { + setUser(JSON.parse(savedUser)) + } catch (error) { + localStorage.removeItem('mim_user') + } + } + }, []) + + return ( + + {children} + + ) +} + +function useAuth() { + const context = useContext(AuthContext) + if (!context) { + throw new Error('useAuth must be used within AuthProvider') + } + return context +} + + + +/* ===================== 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 + + return { + students, + families, + backpacks, + clothing, + emergency, + annual: { + students: Math.floor((amount * 12) / 25), + families: Math.floor((amount * 12) / 50), + totalImpact: `${Math.floor((amount * 12) / 25)} students supported annually` + } + } +} + +/* ===================== Analytics Tracking ===================== */ +function trackEvent(eventName: string, properties: Record = {}) { + // In production, integrate with Google Analytics, Mixpanel, or similar + if (typeof window !== 'undefined' && (window as any).gtag) { + (window as any).gtag('event', eventName, properties) + } + console.log(`Analytics: ${eventName}`, properties) +} + +/* ===================== SEO Meta Tags Component ===================== */ +function SEOHead({ title, description, image }: { title?: string, description?: string, image?: string }) { + useEffect(() => { + // Update document title + if (title) { + document.title = `${title} | Miracles in Motion` + } + + // Update meta description + const metaDescription = document.querySelector('meta[name="description"]') + if (description && metaDescription) { + metaDescription.setAttribute('content', description) + } + + // Update Open Graph tags + const updateOGTag = (property: string, content: string) => { + let tag = document.querySelector(`meta[property="${property}"]`) + if (!tag) { + tag = document.createElement('meta') + tag.setAttribute('property', property) + document.head.appendChild(tag) + } + 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:image', image || '/og-image.jpg') + updateOGTag('og:type', 'website') + }, [title, description, image]) + + return null +} + /* ===================== Types ===================== */ interface IconProps { className?: string } +interface AuthUser { + id: string + email: string + role: 'admin' | 'volunteer' | 'resource' + name: string + lastLogin: Date + permissions: string[] +} + +interface AuthContextType { + user: AuthUser | null + login: (email: string, password: string) => Promise + logout: () => void + isLoading: boolean +} + +interface ImpactCalculation { + students: number + families: number + backpacks: number + clothing: number + emergency: number + annual: { + students: number + families: number + totalImpact: string + } +} + interface TiltCardProps { icon: React.ComponentType title: string @@ -1331,6 +1498,7 @@ function DonatePage() { const [selectedAmount, setSelectedAmount] = useState(50) const [customAmount, setCustomAmount] = useState('') const [isRecurring, setIsRecurring] = useState(false) + const [donorInfo, setDonorInfo] = useState({ email: '', name: '', anonymous: false }) const suggestedAmounts = [ { amount: 25, impact: "School supplies for 1 student", popular: false }, @@ -1339,40 +1507,98 @@ function DonatePage() { { amount: 250, impact: "Emergency fund for 5 families", popular: false } ] - const getImpactText = (amount: number) => { - if (amount >= 250) return `Emergency support for ${Math.floor(amount / 50)} families` - if (amount >= 100) return `School clothing for ${Math.floor(amount / 50)} students` - if (amount >= 50) return `Complete support for ${Math.floor(amount / 50)} student${Math.floor(amount / 50) > 1 ? 's' : ''}` - if (amount >= 25) return `School supplies for ${Math.floor(amount / 25)} student${Math.floor(amount / 25) > 1 ? 's' : ''}` - return "Every dollar helps a student in need" + const finalAmount = customAmount ? parseInt(customAmount) || 0 : selectedAmount + const impactData = calculateDonationImpact(finalAmount) + + // Enhanced impact text with real calculations + const getDetailedImpactText = (amount: number) => { + const impact = calculateDonationImpact(amount) + const impactItems = [] + + if (impact.students > 0) impactItems.push(`${impact.students} student${impact.students > 1 ? 's' : ''} with supplies`) + 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" } - const finalAmount = customAmount ? parseInt(customAmount) || 0 : selectedAmount + // Track donation interactions + useEffect(() => { + trackEvent('donation_page_view', { amount: finalAmount, recurring: isRecurring }) + }, []) + + const handleDonationSubmit = () => { + trackEvent('donation_initiated', { + amount: finalAmount, + recurring: isRecurring, + anonymous: donorInfo.anonymous + }) + // This would integrate with Stripe or another payment processor + alert(`Processing ${isRecurring ? 'monthly ' : ''}donation of $${finalAmount}`) + } return ( - Policies}> + <> + + Policies}>
- {/* Impact Calculator */} + {/* Enhanced Impact Calculator */} -
-
+
+ -
+

Your Impact: ${finalAmount}

-

- {getImpactText(finalAmount)} +

+ {getDetailedImpactText(finalAmount)}

- {isRecurring && ( -

- That's {getImpactText(finalAmount * 12).replace(/\d+/, String(Math.floor((finalAmount * 12) / 25)))} annually! -

+ + {/* Real-time impact breakdown */} + {finalAmount > 0 && ( +
+
+ + Students Supported + + {impactData.students} +
+
+ + Backpack Kits + + {impactData.backpacks} +
+
+ + Clothing Items + + {impactData.clothing} +
+
+ )} + + {isRecurring && finalAmount > 0 && ( +
+

+ 🎉 Annual Impact: {impactData.annual.totalImpact} +

+
)}
@@ -1385,20 +1611,22 @@ function DonatePage() {

Every dollar directly supports students in need

- {/* Suggested Amounts */} -
+ {/* Suggested Amounts - Mobile Optimized */} +
{suggestedAmounts.map((tier) => ( { setSelectedAmount(tier.amount) setCustomAmount('') + trackEvent('donation_amount_selected', { amount: tier.amount, method: 'suggested' }) }} className={`relative p-4 rounded-xl border-2 transition-all focus:outline-none focus:ring-2 focus:ring-primary-500 focus:ring-offset-2 ${ selectedAmount === tier.amount && !customAmount ? 'border-primary-500 bg-primary-50 dark:bg-primary-900/20' : 'border-neutral-200 dark:border-neutral-700 hover:border-primary-300' }`} + style={{ minHeight: '88px', minWidth: '120px' }} // Enhanced touch targets for mobile whileHover={{ scale: 1.02 }} whileTap={{ scale: 0.98 }} aria-label={`Donate $${tier.amount} - ${tier.impact}`} @@ -1443,53 +1671,103 @@ function DonatePage() {
- {/* Recurring Option */} -
-