feat: Implement browser-compatible processing pipeline for student requests

- Added SimpleBrowserProcessor class to handle student requests in a queue.
- Introduced methods for adding requests, notifying subscribers, and processing the queue.
- Mock AI matching logic implemented to simulate resource matching based on request categories and urgency.
- Added generateInsights method to analyze request trends and urgencies.

feat: Create authentication context for user management

- Developed AuthContext to manage user authentication state.
- Implemented login and logout functionalities with mock user data.
- Added loading state management during authentication processes.

feat: Add notification context for managing user notifications

- Created NotificationContext to handle notifications throughout the application.
- Implemented methods for adding, marking as read, and removing notifications.

feat: Introduce common hooks for utility functions

- Developed useHashRoute for hash-based routing.
- Created useLocalStorage for managing local storage with TypeScript support.
- Implemented useMediaQuery for responsive design handling.
- Added useDebounce for debouncing values in state.

feat: Implement donation impact calculation hook

- Created useDonationImpact hook to calculate the impact of donations.
- Added useFormValidation hook for form state management and validation.

feat: Design main layout and page structure components

- Developed MainLayout and PageShell components for consistent layout structure.
- Created SectionHeader and Card components for reusable UI elements.

feat: Build HomePage with impact statistics and service offerings

- Implemented HomePage component with hero section, impact stats, and service cards.
- Integrated tracking for donation and volunteer actions.

feat: Add analytics utilities for tracking events

- Created analytics utility for tracking page views and custom events.
- Implemented donation and volunteer tracking functionalities.

feat: Enhance helper functions for various utilities

- Developed utility functions for impact calculation, formatting, validation, and debouncing.
This commit is contained in:
defiQUG
2025-10-05 08:59:50 -07:00
parent e6aec40163
commit ad8e763750
20 changed files with 3398 additions and 1689 deletions

3011
package-lock.json generated

File diff suppressed because it is too large Load Diff

View File

@@ -32,23 +32,18 @@
"homepage": "https://miraclesinmotion.org",
"dependencies": {
"@tensorflow/tfjs": "^4.22.0",
"bull": "^4.16.5",
"compromise": "^14.14.4",
"date-fns": "^4.1.0",
"framer-motion": "^10.16.16",
"ioredis": "^5.8.0",
"lucide-react": "^0.290.0",
"ml-matrix": "^6.12.1",
"natural": "^8.1.0",
"react": "^18.2.0",
"react-dom": "^18.2.0",
"redis": "^5.8.3",
"socket.io-client": "^4.8.1",
"uuid": "^13.0.0",
"ws": "^8.18.3"
"react-helmet-async": "^1.3.0"
},
"devDependencies": {
"@tailwindcss/typography": "^0.5.10",
"@testing-library/jest-dom": "^6.9.1",
"@testing-library/react": "^16.3.0",
"@types/jest": "^30.0.0",
"@types/react": "^18.2.37",
"@types/react-dom": "^18.2.15",
"@typescript-eslint/eslint-plugin": "^6.10.0",
@@ -60,8 +55,11 @@
"eslint-plugin-react-refresh": "^0.4.4",
"gh-pages": "^6.0.0",
"postcss": "^8.4.31",
"react-helmet-async": "^2.0.5",
"tailwindcss": "^3.3.5",
"terser": "^5.44.0",
"typescript": "^5.2.2",
"vite": "^4.5.0"
"vite": "^7.1.9",
"vite-bundle-analyzer": "^1.2.3"
}
}

267
src/AppNew.tsx Normal file
View File

@@ -0,0 +1,267 @@
import React, { useState, useEffect } from 'react'
import { AnimatePresence } from 'framer-motion'
import { Sun, Moon, Menu, X } from 'lucide-react'
// Contexts
import { AuthProvider } from './contexts/AuthContext'
import { NotificationProvider } from './contexts/NotificationContext'
// Pages
import { HomePage } from './pages/HomePage'
import { DonatePage } from './pages/DonatePage'
// Components
import AIAssistancePortal from './components/AIAssistancePortal'
import AdvancedAnalyticsDashboard from './components/AdvancedAnalyticsDashboard'
import MobileVolunteerApp from './components/MobileVolunteerApp'
import StaffTrainingDashboard from './components/StaffTrainingDashboard'
// Hooks
import { useHashRoute } from './hooks/useCommon'
// Analytics
import { analytics } from './utils/analytics'
// Main App Component
const App: React.FC = () => {
const [darkMode, setDarkMode] = useState<boolean>(false)
const [mobileMenuOpen, setMobileMenuOpen] = useState<boolean>(false)
const { route } = useHashRoute()
// Initialize analytics
useEffect(() => {
analytics.init()
}, [])
// Dark mode toggle
const toggleDarkMode = (): void => {
setDarkMode(prev => {
const newMode = !prev
document.documentElement.classList.toggle('dark', newMode)
localStorage.setItem('darkMode', newMode.toString())
return newMode
})
}
// Initialize dark mode from localStorage
useEffect(() => {
const savedMode = localStorage.getItem('darkMode') === 'true'
setDarkMode(savedMode)
document.documentElement.classList.toggle('dark', savedMode)
}, [])
// Close mobile menu on route change
useEffect(() => {
setMobileMenuOpen(false)
}, [route])
const renderPage = (): React.ReactNode => {
switch (route) {
case '/':
return <HomePage />
case '/donate':
return <DonatePage />
case '/ai-assistance':
return <AIAssistancePortal userRole="admin" />
case '/analytics':
return <AdvancedAnalyticsDashboard />
case '/mobile-volunteer':
return <MobileVolunteerApp />
case '/staff-training':
return <StaffTrainingDashboard />
default:
return <HomePage />
}
}
return (
<AuthProvider>
<NotificationProvider>
<div className="min-h-screen bg-gradient-to-br from-purple-50 via-white to-pink-50 dark:from-gray-900 dark:via-gray-800 dark:to-purple-900">
{/* Navigation */}
<Navigation
darkMode={darkMode}
toggleDarkMode={toggleDarkMode}
mobileMenuOpen={mobileMenuOpen}
setMobileMenuOpen={setMobileMenuOpen}
/>
{/* Main Content */}
<main>
<AnimatePresence mode="wait">
{renderPage()}
</AnimatePresence>
</main>
{/* Footer */}
<Footer />
</div>
</NotificationProvider>
</AuthProvider>
)
}
// Navigation Component
interface NavigationProps {
darkMode: boolean
toggleDarkMode: () => void
mobileMenuOpen: boolean
setMobileMenuOpen: (open: boolean) => void
}
const Navigation: React.FC<NavigationProps> = ({
darkMode,
toggleDarkMode,
mobileMenuOpen,
setMobileMenuOpen
}) => {
const navItems = [
{ label: 'Home', href: '/' },
{ label: 'Donate', href: '/donate' },
{ label: 'Volunteer', href: '/volunteer' },
{ label: 'About', href: '/about' },
{ label: 'Contact', href: '/contact' }
]
return (
<nav className="sticky top-0 z-50 bg-white/80 dark:bg-gray-900/80 backdrop-blur-md border-b border-white/20 dark:border-gray-700/50">
<div className="mx-auto max-w-7xl px-4 sm:px-6 lg:px-8">
<div className="flex justify-between items-center h-16">
{/* Logo */}
<div className="flex items-center">
<a href="#/" className="text-2xl font-bold text-primary-600 hover:text-primary-700 transition-colors">
Miracles in Motion
</a>
</div>
{/* Desktop Navigation */}
<div className="hidden md:flex items-center space-x-8">
{navItems.map((item) => (
<a
key={item.href}
href={`#${item.href}`}
className="text-gray-600 dark:text-gray-300 hover:text-primary-600 dark:hover:text-primary-400 transition-colors font-medium"
>
{item.label}
</a>
))}
<button
onClick={toggleDarkMode}
className="p-2 rounded-lg bg-gray-100 dark:bg-gray-800 hover:bg-gray-200 dark:hover:bg-gray-700 transition-colors"
aria-label="Toggle dark mode"
>
{darkMode ? (
<Sun className="w-5 h-5 text-yellow-500" />
) : (
<Moon className="w-5 h-5 text-gray-600" />
)}
</button>
</div>
{/* Mobile Menu Button */}
<div className="md:hidden flex items-center space-x-2">
<button
onClick={toggleDarkMode}
className="p-2 rounded-lg bg-gray-100 dark:bg-gray-800 hover:bg-gray-200 dark:hover:bg-gray-700 transition-colors"
aria-label="Toggle dark mode"
>
{darkMode ? (
<Sun className="w-5 h-5 text-yellow-500" />
) : (
<Moon className="w-5 h-5 text-gray-600" />
)}
</button>
<button
onClick={() => setMobileMenuOpen(!mobileMenuOpen)}
className="p-2 rounded-lg bg-gray-100 dark:bg-gray-800 hover:bg-gray-200 dark:hover:bg-gray-700 transition-colors"
aria-label="Toggle menu"
>
{mobileMenuOpen ? (
<X className="w-5 h-5 text-gray-600 dark:text-gray-300" />
) : (
<Menu className="w-5 h-5 text-gray-600 dark:text-gray-300" />
)}
</button>
</div>
</div>
{/* Mobile Menu */}
{mobileMenuOpen && (
<div className="md:hidden py-4 border-t border-gray-200 dark:border-gray-700">
<div className="space-y-2">
{navItems.map((item) => (
<a
key={item.href}
href={`#${item.href}`}
className="block px-4 py-2 text-gray-600 dark:text-gray-300 hover:text-primary-600 dark:hover:text-primary-400 hover:bg-gray-50 dark:hover:bg-gray-800 rounded-lg transition-colors font-medium"
>
{item.label}
</a>
))}
</div>
</div>
)}
</div>
</nav>
)
}
// Footer Component
const Footer: React.FC = () => {
return (
<footer className="bg-gray-900 text-white py-12">
<div className="mx-auto max-w-7xl px-4 sm:px-6 lg:px-8">
<div className="grid grid-cols-1 md:grid-cols-4 gap-8">
{/* Organization Info */}
<div className="col-span-1 md:col-span-2">
<h3 className="text-2xl font-bold mb-4">Miracles in Motion</h3>
<p className="text-gray-300 mb-4 max-w-md">
A 501(c)3 non-profit organization dedicated to providing essential support
to students and families in need. Every contribution makes a lasting impact.
</p>
<p className="text-sm text-gray-400">
EIN: 12-3456789 All donations are tax-deductible
</p>
</div>
{/* Quick Links */}
<div>
<h4 className="font-semibold mb-4">Quick Links</h4>
<div className="space-y-2">
<a href="#/donate" className="block text-gray-300 hover:text-white transition-colors">
Donate Now
</a>
<a href="#/volunteer" className="block text-gray-300 hover:text-white transition-colors">
Volunteer
</a>
<a href="#/about" className="block text-gray-300 hover:text-white transition-colors">
About Us
</a>
<a href="#/impact" className="block text-gray-300 hover:text-white transition-colors">
Our Impact
</a>
</div>
</div>
{/* Contact */}
<div>
<h4 className="font-semibold mb-4">Contact</h4>
<div className="space-y-2 text-gray-300">
<p>contact@miraclesinmotion.org</p>
<p>(555) 123-4567</p>
<p>123 Community Way<br />Hometown, ST 12345</p>
</div>
</div>
</div>
<div className="border-t border-gray-700 mt-8 pt-8 text-center text-gray-400">
<p>&copy; 2025 Miracles in Motion. All rights reserved.</p>
</div>
</div>
</footer>
)
}
export default App

231
src/ai/BrowserProcessor.ts Normal file
View File

@@ -0,0 +1,231 @@
// Simplified Browser-Compatible Processing Pipeline
import type {
StudentRequest,
MatchResult,
AIUpdate,
AIInsight
} from './types'
// Simple browser-compatible processing
export class SimpleBrowserProcessor {
private processingQueue: StudentRequest[] = []
private isProcessing = false
private subscribers: Set<(update: AIUpdate) => void> = new Set()
// Add request to processing queue
async addRequest(request: StudentRequest): Promise<void> {
this.processingQueue.push(request)
console.log(`📋 Added request ${request.id} to processing queue`)
// Start processing if not already running
if (!this.isProcessing) {
this.processQueue()
}
}
// Subscribe to processing updates
subscribe(callback: (update: AIUpdate) => void): () => void {
this.subscribers.add(callback)
return () => this.subscribers.delete(callback)
}
// Notify subscribers of updates
private notify(update: AIUpdate): void {
this.subscribers.forEach(callback => {
try {
callback(update)
} catch (error) {
console.error('Error in processing callback:', error)
}
})
}
// Process the queue
private async processQueue(): Promise<void> {
if (this.isProcessing || this.processingQueue.length === 0) {
return
}
this.isProcessing = true
console.log('🔄 Starting processing queue...')
while (this.processingQueue.length > 0) {
const request = this.processingQueue.shift()!
try {
this.notify({
type: 'request-processed',
requestId: request.id,
message: `Processing request for ${request.studentName}`,
timestamp: new Date()
})
// Mock AI processing with realistic delay
await new Promise(resolve => setTimeout(resolve, 2000))
const matches = await this.mockAIMatching(request)
this.notify({
type: 'request-processed',
requestId: request.id,
message: `Found ${matches.length} potential matches`,
timestamp: new Date()
})
} catch (error) {
console.error('Error processing request:', error)
this.notify({
type: 'alert',
requestId: request.id,
message: 'Processing failed',
timestamp: new Date()
})
}
}
this.isProcessing = false
console.log('✅ Processing queue completed')
}
// Mock AI matching for browser demo
private async mockAIMatching(request: StudentRequest): Promise<MatchResult[]> {
console.log(`🤖 AI processing: ${request.description}`)
// Simulate AI analysis
const categoryMatch = this.getCategoryMatch(request.category)
const urgencyMultiplier = this.getUrgencyMultiplier(request.urgency)
return [{
resourceId: `resource-${Date.now()}`,
resourceName: categoryMatch.name,
resourceType: this.mapCategoryToResourceType(request.category),
confidenceScore: 0.75 + (Math.random() * 0.2), // 75-95%
estimatedImpact: categoryMatch.impact * urgencyMultiplier,
logisticalComplexity: categoryMatch.complexity,
estimatedCost: categoryMatch.cost,
fulfillmentTimeline: categoryMatch.timeline,
reasoningFactors: [
'AI-powered semantic analysis',
`${request.category} category match`,
`${request.urgency} urgency level`,
'Location compatibility verified'
],
riskFactors: categoryMatch.risks
}]
}
private getCategoryMatch(category: string) {
const categoryData: Record<string, any> = {
'clothing': {
name: 'School Clothing Package',
impact: 8.5,
complexity: 2.0,
cost: 60,
timeline: '3-5 days',
risks: ['Size verification needed', 'Seasonal appropriateness']
},
'supplies': {
name: 'Complete Supply Kit',
impact: 9.0,
complexity: 1.5,
cost: 35,
timeline: '1-2 days',
risks: ['Grade-level appropriateness']
},
'food': {
name: 'Emergency Food Support',
impact: 9.5,
complexity: 3.0,
cost: 45,
timeline: '4-6 hours',
risks: ['Dietary restrictions', 'Perishable items']
},
'transportation': {
name: 'Transport Assistance',
impact: 7.5,
complexity: 4.0,
cost: 25,
timeline: '1-3 days',
risks: ['Schedule coordination', 'Safety verification']
},
'emergency': {
name: 'Emergency Response Package',
impact: 10.0,
complexity: 3.5,
cost: 100,
timeline: '2-4 hours',
risks: ['Immediate availability', 'Specialized needs']
}
}
return categoryData[category] || categoryData['supplies']
}
private getUrgencyMultiplier(urgency: string): number {
const multipliers: Record<string, number> = {
'low': 0.8,
'medium': 1.0,
'high': 1.3,
'critical': 1.6
}
return multipliers[urgency] || 1.0
}
private mapCategoryToResourceType(category: string): 'supplies' | 'clothing' | 'food' | 'transport' | 'emergency' | 'other' {
const mapping: Record<string, 'supplies' | 'clothing' | 'food' | 'transport' | 'emergency' | 'other'> = {
'school-supplies': 'supplies',
'clothing': 'clothing',
'food': 'food',
'transportation': 'transport',
'emergency': 'emergency'
}
return mapping[category] || 'other'
}
// Generate insights for dashboard
async generateInsights(requests: StudentRequest[]): Promise<AIInsight[]> {
const insights: AIInsight[] = []
// Category analysis
const categoryCount = requests.reduce((acc, req) => {
acc[req.category] = (acc[req.category] || 0) + 1
return acc
}, {} as Record<string, number>)
const topCategory = Object.entries(categoryCount)
.sort(([,a], [,b]) => b - a)[0]
if (topCategory) {
insights.push({
id: `insight-category-${Date.now()}`,
type: 'trend',
title: `High Demand: ${topCategory[0]}`,
description: `${topCategory[1]} requests for ${topCategory[0]} in recent batch. Consider stocking additional resources.`,
confidence: 0.85,
severity: topCategory[1] > 3 ? 'medium' : 'low',
timestamp: new Date()
})
}
// Urgency analysis
const criticalCount = requests.filter(r => r.urgency.toString() === 'critical').length
if (criticalCount > 0) {
insights.push({
id: `insight-urgency-${Date.now()}`,
type: 'recommendation',
title: 'Critical Requests Detected',
description: `${criticalCount} critical priority requests require immediate attention.`,
confidence: 1.0,
severity: 'high',
timestamp: new Date()
})
}
return insights
}
}
// Export singleton instance
export const browserProcessor = new SimpleBrowserProcessor()

View File

@@ -1,7 +1,10 @@
// AI Model Lazy Loading Implementation
import * as tf from '@tensorflow/tfjs'
import type { StudentRequest, MatchResult } from './types'
export class OptimizedStudentAssistanceAI {
private static models: Map<string, any> = new Map()
private static modelUrls = {
private static modelUrls: Record<string, string> = {
'text-vectorization': '/models/text-vectorizer.json',
'matching-engine': '/models/matcher.json',
'priority-classifier': '/models/priority.json'
@@ -55,15 +58,37 @@ export class OptimizedStudentAssistanceAI {
private async aiBasedMatching(request: StudentRequest, textModel: any, matchingModel: any): Promise<MatchResult[]> {
// AI-powered matching logic
console.log('🤖 Using AI-powered matching')
// ... implementation
return []
console.log('🤖 Using AI-powered matching', { request: request.id, textModel: !!textModel, matchingModel: !!matchingModel })
// Mock implementation for now
return [{
resourceId: 'resource-1',
resourceName: 'School Supply Kit',
resourceType: 'supplies',
confidenceScore: 0.85,
estimatedImpact: 8.5,
logisticalComplexity: 2.1,
estimatedCost: 50,
fulfillmentTimeline: '2-3 days',
reasoningFactors: ['AI-based match using TensorFlow models', 'High confidence score'],
riskFactors: ['Low risk - standard supplies']
}]
}
private async ruleBasedMatching(request: StudentRequest): Promise<MatchResult[]> {
// Fallback rule-based matching
console.log('📏 Using rule-based matching (fallback)')
// ... implementation
return []
console.log('📏 Using rule-based matching (fallback)', { request: request.id })
// Mock implementation for now
return [{
resourceId: 'resource-1',
resourceName: 'Basic Supply Kit',
resourceType: 'supplies',
confidenceScore: 0.65,
estimatedImpact: 6.5,
logisticalComplexity: 3.2,
estimatedCost: 50,
fulfillmentTimeline: '3-5 days',
reasoningFactors: ['Rule-based fallback matching', 'Basic category match'],
riskFactors: ['Medium risk - manual verification needed']
}]
}
}

View File

@@ -1,23 +1,26 @@
// Phase 3: Real-time Processing Pipeline for AI Assistance Matching
import type {
StudentRequest,
MatchResult,
// Phase 3: Browser-Compatible Processing Pipeline for AI Assistance Matching
import {
StudentRequest,
MatchResult,
AIInsight,
AIUpdate,
AIInsight,
ProcessingPipelineConfig
ProcessingPipelineConfig
} from './types'
import { aiEngine } from './StudentAssistanceAI'
// Mock Queue Implementation (in production, use Redis + Bull)
class MockQueue<T> {
private jobs: Array<{ id: string; data: T; priority: number }> = []
// Re-export from the new browser processor
export { browserProcessor, SimpleBrowserProcessor } from './BrowserProcessor'
// Browser-compatible job queue
class BrowserQueue<T> {
private jobs: Array<{ id: string; data: T; priority: number; timestamp: number }> = []
private processors: Map<string, (job: { id: string; data: T }) => Promise<void>> = new Map()
async add(jobType: string, data: T, options: { priority: number; attempts?: number; backoff?: string } = { priority: 0 }): Promise<{ id: string }> {
const job = {
id: `job-${Date.now()}-${Math.random().toString(36).substr(2, 9)}`,
data,
priority: options.priority
priority: options.priority,
timestamp: Date.now()
}
this.jobs.push(job)
@@ -131,14 +134,14 @@ class ReviewManager {
}
}
// Main Processing Pipeline
// Main Processing Pipeline - Simplified browser-compatible version
export class RealTimeProcessingPipeline {
private queue: MockQueue<StudentRequest>
private queue: BrowserQueue<StudentRequest>
private notificationService: NotificationService
private config: ProcessingPipelineConfig
constructor(config: Partial<ProcessingPipelineConfig> = {}) {
this.queue = new MockQueue<StudentRequest>()
this.queue = new BrowserQueue<StudentRequest>()
this.notificationService = new NotificationService()
this.config = {
autoApprovalThreshold: 0.85,
@@ -169,14 +172,14 @@ export class RealTimeProcessingPipeline {
}
private setupQueueProcessors(): void {
this.queue.process('analyze-request', 5, async (job) => {
this.queue.process('analyze-request', 5, async (job: { id: string; data: StudentRequest }) => {
const request = job.data
try {
console.log(`🔄 Processing request ${request.id} for ${request.studentName}`)
// AI analysis and matching
const matches = await aiEngine.processRequest(request)
// Mock AI processing for browser demo
const matches = await this.mockProcessRequest(request)
// Auto-approval for high-confidence matches
if (matches.length > 0 && matches[0].confidenceScore >= this.config.autoApprovalThreshold) {
@@ -397,8 +400,35 @@ export class RealTimeProcessingPipeline {
return this.notificationService.subscribe(callback)
}
private async mockProcessRequest(request: StudentRequest): Promise<MatchResult[]> {
// Mock implementation for browser demo
console.log('Mock processing request:', request.id)
await new Promise(resolve => setTimeout(resolve, 1000))
return [{
resourceId: 'mock-resource-1',
resourceName: 'Mock School Supplies',
resourceType: 'supplies',
confidenceScore: 0.8,
estimatedImpact: 8.0,
logisticalComplexity: 2.5,
estimatedCost: 50,
fulfillmentTimeline: '2-3 days',
reasoningFactors: ['Mock processing', 'Demo mode'],
riskFactors: ['Demo data only']
}]
}
async generateInsights(requests: StudentRequest[]): Promise<AIInsight[]> {
return await aiEngine.generateInsights(requests)
// Mock insights for browser demo
return requests.map((req, index) => ({
id: `insight-${index}`,
type: 'recommendation' as const,
title: `Insight for ${req.studentName}`,
description: `Mock insight for request ${req.id}`,
confidence: 0.75,
timestamp: new Date()
}))
}
getConfig(): ProcessingPipelineConfig {

View File

@@ -1,4 +1,4 @@
// Phase 3: AI-Powered Student Assistance Matching Engine
// Phase 3: AI-Powered Student Assistance Matching Engine (Browser-Compatible)
import * as tf from '@tensorflow/tfjs'
import type {
StudentRequest,
@@ -10,10 +10,10 @@ import type {
ResourceRequirement
} from './types'
// Text Vectorization for NLP Analysis
class TextVectorizer {
// Simple browser-compatible text analysis
class BrowserTextAnalyzer {
private vocabulary: Map<string, number> = new Map()
private vectorSize: number = 100
private vectorSize: number = 50 // Reduced for browser performance
constructor() {
this.initializeVocabulary()
@@ -110,7 +110,7 @@ class TextVectorizer {
// Core AI Matching Engine
export class StudentAssistanceAI {
private vectorizer: TextVectorizer
private vectorizer: BrowserTextAnalyzer
private matchingModel: tf.LayersModel | null = null
private impactModel: tf.LayersModel | null = null
private isInitialized = false
@@ -132,7 +132,7 @@ export class StudentAssistanceAI {
]
constructor() {
this.vectorizer = new TextVectorizer()
this.vectorizer = new BrowserTextAnalyzer()
this.initializeModels()
}

View File

@@ -0,0 +1,103 @@
import React, { createContext, useContext, useState, ReactNode } from 'react'
// Types
export interface AuthUser {
id: string
email: string
role: 'admin' | 'volunteer' | 'resource'
name: string
lastLogin: Date
permissions: string[]
}
export interface AuthContextType {
user: AuthUser | null
login: (email: string, password: string) => Promise<boolean>
logout: () => void
isLoading: boolean
}
// Create Context
const AuthContext = createContext<AuthContextType | null>(null)
// Auth Provider Component
export const AuthProvider: React.FC<{ children: ReactNode }> = ({ children }) => {
const [user, setUser] = useState<AuthUser | null>(null)
const [isLoading, setIsLoading] = useState(false)
const login = async (email: string, password: string): Promise<boolean> => {
setIsLoading(true)
try {
// Mock authentication - replace with real API call
console.log('🔐 Attempting login for:', email)
// Simulate API call
await new Promise(resolve => setTimeout(resolve, 1000))
// Mock user data based on email
const mockUsers: Record<string, AuthUser> = {
'admin@miraclesinmotion.org': {
id: '1',
email: 'admin@miraclesinmotion.org',
role: 'admin',
name: 'Admin User',
lastLogin: new Date(),
permissions: ['all']
},
'volunteer@miraclesinmotion.org': {
id: '2',
email: 'volunteer@miraclesinmotion.org',
role: 'volunteer',
name: 'Volunteer User',
lastLogin: new Date(),
permissions: ['view_requests', 'update_assignments']
}
}
const authenticatedUser = mockUsers[email]
if (authenticatedUser && password === 'demo123') {
setUser(authenticatedUser)
localStorage.setItem('authToken', `token-${authenticatedUser.id}`)
console.log('✅ Login successful')
return true
} else {
console.log('❌ Login failed')
return false
}
} catch (error) {
console.error('Login error:', error)
return false
} finally {
setIsLoading(false)
}
}
const logout = (): void => {
setUser(null)
localStorage.removeItem('authToken')
console.log('👋 User logged out')
}
const value: AuthContextType = {
user,
login,
logout,
isLoading
}
return (
<AuthContext.Provider value={value}>
{children}
</AuthContext.Provider>
)
}
// Custom hook for using auth context
export const useAuth = (): AuthContextType => {
const context = useContext(AuthContext)
if (!context) {
throw new Error('useAuth must be used within an AuthProvider')
}
return context
}

View File

@@ -0,0 +1,85 @@
import React, { createContext, useContext, useState, ReactNode } from 'react'
// Types
export interface Notification {
id: string
type: 'success' | 'info' | 'warning' | 'error'
title: string
message: string
timestamp: Date
read: boolean
actions?: { label: string; action: () => void }[]
}
export interface NotificationContextType {
notifications: Notification[]
addNotification: (notif: Omit<Notification, 'id' | 'timestamp' | 'read'>) => void
markAsRead: (id: string) => void
removeNotification: (id: string) => void
clearAll: () => void
}
// Create Context
const NotificationContext = createContext<NotificationContextType | null>(null)
// Notification Provider Component
export const NotificationProvider: React.FC<{ children: ReactNode }> = ({ children }) => {
const [notifications, setNotifications] = useState<Notification[]>([])
const addNotification = (notif: Omit<Notification, 'id' | 'timestamp' | 'read'>): void => {
const newNotification: Notification = {
...notif,
id: Math.random().toString(36),
timestamp: new Date(),
read: false
}
setNotifications(prev => [newNotification, ...prev])
// Auto-remove success notifications after 5 seconds
if (notif.type === 'success') {
setTimeout(() => {
setNotifications(prev => prev.filter(n => n.id !== newNotification.id))
}, 5000)
}
}
const markAsRead = (id: string): void => {
setNotifications(prev =>
prev.map(notif =>
notif.id === id ? { ...notif, read: true } : notif
)
)
}
const removeNotification = (id: string): void => {
setNotifications(prev => prev.filter(notif => notif.id !== id))
}
const clearAll = (): void => {
setNotifications([])
}
const value: NotificationContextType = {
notifications,
addNotification,
markAsRead,
removeNotification,
clearAll
}
return (
<NotificationContext.Provider value={value}>
{children}
</NotificationContext.Provider>
)
}
// Custom hook for using notification context
export const useNotifications = (): NotificationContextType => {
const context = useContext(NotificationContext)
if (!context) {
throw new Error('useNotifications must be used within a NotificationProvider')
}
return context
}

View File

@@ -1,6 +1,9 @@
// Phase 3B: Salesforce Nonprofit Cloud CRM Integration
// Phase 3B: Browser-Compatible CRM Integration (Mock Implementation)
import type { StudentRequest, MatchResult } from '../ai/types'
// Note: In a real implementation, this would connect to Salesforce via REST API
// For browser demo, we use mock implementations
export interface SalesforceConfig {
instanceUrl: string
clientId: string

84
src/hooks/useCommon.ts Normal file
View File

@@ -0,0 +1,84 @@
import { useState, useEffect } from 'react'
// Hash-based routing hook
export const useHashRoute = () => {
const parseRoute = (): string => window.location.hash?.slice(1) || "/"
const [route, setRoute] = useState<string>(parseRoute())
useEffect(() => {
const handleHashChange = (): void => setRoute(parseRoute())
window.addEventListener("hashchange", handleHashChange)
return () => window.removeEventListener("hashchange", handleHashChange)
}, [])
const navigate = (newRoute: string): void => {
window.location.hash = newRoute
}
return { route, navigate }
}
// Local storage hook with TypeScript support
export const useLocalStorage = <T>(key: string, initialValue: T) => {
const [storedValue, setStoredValue] = useState<T>(() => {
try {
const item = window.localStorage.getItem(key)
return item ? JSON.parse(item) : initialValue
} catch (error) {
console.warn(`Error reading localStorage key "${key}":`, error)
return initialValue
}
})
const setValue = (value: T | ((val: T) => T)): void => {
try {
const valueToStore = value instanceof Function ? value(storedValue) : value
setStoredValue(valueToStore)
window.localStorage.setItem(key, JSON.stringify(valueToStore))
} catch (error) {
console.warn(`Error setting localStorage key "${key}":`, error)
}
}
return [storedValue, setValue] as const
}
// Media query hook
export const useMediaQuery = (query: string): boolean => {
const [matches, setMatches] = useState<boolean>(false)
useEffect(() => {
const media = window.matchMedia(query)
if (media.matches !== matches) {
setMatches(media.matches)
}
const listener = (event: MediaQueryListEvent): void => {
setMatches(event.matches)
}
media.addEventListener('change', listener)
return () => media.removeEventListener('change', listener)
}, [matches, query])
return matches
}
// Debounce hook
export const useDebounce = <T>(value: T, delay: number): T => {
const [debouncedValue, setDebouncedValue] = useState<T>(value)
useEffect(() => {
const handler = setTimeout(() => {
setDebouncedValue(value)
}, delay)
return () => {
clearTimeout(handler)
}
}, [value, delay])
return debouncedValue
}

View File

@@ -0,0 +1,108 @@
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 students 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
return {
students,
families,
backpacks,
clothing,
emergency,
annual: {
students: Math.floor((donationAmount * 12) / 25),
families: Math.floor((donationAmount * 12) / 50),
totalImpact: `${Math.floor((donationAmount * 12) / 25)} students 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
}
}

View File

@@ -1,5 +1,6 @@
/* @tailwind directives removed because Tailwind CSS is not being processed.
If you are using Tailwind, make sure to process this file with Tailwind CLI or PostCSS. */
@tailwind base;
@tailwind components;
@tailwind utilities;
@layer base {
html {

155
src/layouts/MainLayout.tsx Normal file
View File

@@ -0,0 +1,155 @@
import React, { ReactNode } from 'react'
import { motion } from 'framer-motion'
import { LucideIcon } from 'lucide-react'
interface MainLayoutProps {
children: ReactNode
className?: string
}
interface PageShellProps {
title: string
icon?: LucideIcon
eyebrow?: string
subtitle?: string
cta?: ReactNode
children: ReactNode
className?: string
}
// Main layout wrapper
export const MainLayout: React.FC<MainLayoutProps> = ({ children, className = '' }) => {
return (
<div className={`min-h-screen bg-gradient-to-br from-purple-50 via-white to-pink-50 dark:from-gray-900 dark:via-gray-800 dark:to-purple-900 ${className}`}>
{children}
</div>
)
}
// Page shell component for consistent page structure
export const PageShell: React.FC<PageShellProps> = ({
title,
icon: Icon,
eyebrow,
subtitle,
cta,
children,
className = ''
}) => {
return (
<MainLayout>
<div className={`mx-auto max-w-7xl px-4 sm:px-6 lg:px-8 py-12 ${className}`}>
{/* Page Header */}
<motion.div
initial={{ opacity: 0, y: 20 }}
animate={{ opacity: 1, y: 0 }}
className="text-center mb-12"
>
{eyebrow && (
<div className="flex items-center justify-center gap-2 mb-4">
{Icon && <Icon className="w-5 h-5 text-primary-600" />}
<span className="text-sm font-medium text-primary-600 uppercase tracking-wider">
{eyebrow}
</span>
</div>
)}
<h1 className="text-4xl md:text-6xl font-bold text-gray-900 dark:text-white mb-4">
{title}
</h1>
{subtitle && (
<p className="text-xl text-gray-600 dark:text-gray-300 max-w-3xl mx-auto mb-8">
{subtitle}
</p>
)}
{cta && (
<div className="flex justify-center">
{cta}
</div>
)}
</motion.div>
{/* Page Content */}
<motion.div
initial={{ opacity: 0, y: 20 }}
animate={{ opacity: 1, y: 0 }}
transition={{ delay: 0.2 }}
>
{children}
</motion.div>
</div>
</MainLayout>
)
}
// Section header component
interface SectionHeaderProps {
eyebrow?: string
title: string
subtitle?: string
className?: string
}
export const SectionHeader: React.FC<SectionHeaderProps> = ({
eyebrow,
title,
subtitle,
className = ''
}) => {
return (
<div className={`text-center mb-12 ${className}`}>
{eyebrow && (
<div className="text-sm font-medium text-primary-600 uppercase tracking-wider mb-2">
{eyebrow}
</div>
)}
<h2 className="text-3xl md:text-4xl font-bold text-gray-900 dark:text-white mb-4">
{title}
</h2>
{subtitle && (
<p className="text-lg text-gray-600 dark:text-gray-300 max-w-2xl mx-auto">
{subtitle}
</p>
)}
</div>
)
}
// Card component for consistent styling
interface CardProps {
children: ReactNode
className?: string
hover?: boolean
padding?: 'sm' | 'md' | 'lg'
}
export const Card: React.FC<CardProps> = ({
children,
className = '',
hover = false,
padding = 'md'
}) => {
const paddingClasses = {
sm: 'p-4',
md: 'p-6',
lg: 'p-8'
}
return (
<div
className={`
bg-white/70 dark:bg-white/5
backdrop-blur-sm
border border-white/30 dark:border-white/10
rounded-xl shadow-lg
${hover ? 'hover:shadow-xl transition-shadow duration-300' : ''}
${paddingClasses[padding]}
${className}
`}
>
{children}
</div>
)
}

View File

@@ -1,42 +1,369 @@
// Example: Modular Page Structure
import React from 'react'
import { SEOHead } from '@/components/SEO'
import { PageShell } from '@/components/Layout'
import { DonationForm } from './DonationForm'
import { ImpactCalculator } from './ImpactCalculator'
import { DonationTiers } from './DonationTiers'
import { Heart } from 'lucide-react'
import React, { useState, useEffect } from 'react'
import { motion } from 'framer-motion'
import { Heart, CreditCard, Shield, Lock, CheckCircle2 } from 'lucide-react'
import { PageShell, Card } from '@/layouts/MainLayout'
import { useDonationImpact } from '@/hooks/useDonationImpact'
import { useFormValidation } from '@/hooks/useDonationImpact'
import { formatCurrency } from '@/utils/helpers'
import { trackEvent, trackPageView } from '@/utils/analytics'
interface DonatePageProps {
className?: string
}
export const DonatePage: React.FC<DonatePageProps> = ({ className }) => {
const [selectedAmount, setSelectedAmount] = useState<number>(50)
const [customAmount, setCustomAmount] = useState<string>('')
const [isCustom, setIsCustom] = useState<boolean>(false)
const finalAmount = isCustom && customAmount ? parseInt(customAmount) || 0 : selectedAmount
const impact = useDonationImpact(finalAmount)
useEffect(() => {
trackPageView('/donate', 'Donate - Support Students in Need')
}, [])
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" },
]
const handleAmountSelect = (amount: number): void => {
setSelectedAmount(amount)
setIsCustom(false)
setCustomAmount('')
trackEvent('donation_amount_selected', { amount, type: 'preset' })
}
const handleCustomAmount = (value: string): void => {
setCustomAmount(value)
setIsCustom(true)
if (parseInt(value)) {
trackEvent('donation_amount_selected', { amount: parseInt(value), type: 'custom' })
}
}
return (
<>
<SEOHead
title="Donate - Support Students in Need"
description="Your donation directly supports students with school supplies, clothing, and emergency assistance."
/>
<PageShell
title="Make a Difference Today"
icon={Heart}
eyebrow="Every dollar counts"
subtitle="Your donation directly supports students with school supplies, clothing, and emergency assistance."
className={className}
>
<div className="grid gap-8 lg:grid-cols-3">
<div className="lg:col-span-2 space-y-8">
{/* Donation Tiers */}
<Card>
<h3 className="text-xl font-semibold text-gray-900 dark:text-white mb-6">
Choose Your Impact Level
</h3>
<div className="grid grid-cols-1 sm:grid-cols-2 gap-4 mb-6">
{donationTiers.map((tier) => (
<button
key={tier.amount}
onClick={() => handleAmountSelect(tier.amount)}
className={`p-4 rounded-lg border-2 transition-all duration-200 text-left ${
selectedAmount === tier.amount && !isCustom
? 'border-primary-500 bg-primary-50 dark:bg-primary-900/20'
: 'border-gray-200 dark:border-gray-700 hover:border-primary-300'
}`}
>
<div className="font-semibold text-lg text-gray-900 dark:text-white">
{formatCurrency(tier.amount)}
</div>
<div className="text-sm text-gray-600 dark:text-gray-300">
{tier.label}
</div>
</button>
))}
</div>
<div className="border-t pt-6">
<label className="block text-sm font-medium text-gray-700 dark:text-gray-300 mb-2">
Or enter a custom amount
</label>
<div className="relative">
<span className="absolute left-3 top-1/2 transform -translate-y-1/2 text-gray-500">
$
</span>
<input
type="number"
placeholder="0"
value={customAmount}
onChange={(e) => handleCustomAmount(e.target.value)}
className="w-full pl-8 pr-4 py-3 border border-gray-300 dark:border-gray-600 rounded-lg focus:ring-2 focus:ring-primary-500 focus:border-transparent bg-white dark:bg-gray-800 text-gray-900 dark:text-white"
/>
</div>
</div>
</Card>
{/* Donation Form */}
<DonationForm amount={finalAmount} />
</div>
<div className="space-y-6">
{/* Impact Calculator */}
<ImpactCalculator impact={impact} amount={finalAmount} />
{/* Trust Badges */}
<TrustBadges />
</div>
</div>
</PageShell>
)
}
// Impact Calculator Component
interface ImpactCalculatorProps {
impact: ReturnType<typeof useDonationImpact>
amount: number
}
const ImpactCalculator: React.FC<ImpactCalculatorProps> = ({ impact, amount }) => {
if (amount <= 0) return null
return (
<Card>
<h3 className="text-xl font-semibold text-gray-900 dark:text-white mb-4 flex items-center gap-2">
<Heart className="w-5 h-5 text-primary-600" />
Your Impact
</h3>
<PageShell
title="Make a Difference Today"
icon={Heart}
eyebrow="Every dollar counts"
className={className}
>
<div className="grid gap-8 lg:grid-cols-3">
<div className="lg:col-span-2 space-y-8">
<DonationTiers />
<DonationForm />
<div className="text-2xl font-bold text-primary-600 mb-4">
{formatCurrency(amount)}
</div>
<div className="space-y-3">
{impact.students > 0 && (
<ImpactItem
count={impact.students}
label={`student${impact.students > 1 ? 's' : ''} with supplies`}
/>
)}
{impact.backpacks > 0 && (
<ImpactItem
count={impact.backpacks}
label={`backpack kit${impact.backpacks > 1 ? 's' : ''}`}
/>
)}
{impact.clothing > 0 && (
<ImpactItem
count={impact.clothing}
label={`clothing item${impact.clothing > 1 ? 's' : ''}`}
/>
)}
{impact.emergency > 0 && (
<ImpactItem
count={impact.emergency}
label={`emergency response${impact.emergency > 1 ? 's' : ''}`}
/>
)}
</div>
<div className="mt-6 pt-6 border-t border-gray-200 dark:border-gray-700">
<div className="text-sm text-gray-600 dark:text-gray-400">
<strong>Annual Impact:</strong> {impact.annual.totalImpact}
</div>
</div>
</Card>
)
}
const ImpactItem: React.FC<{ count: number; label: string }> = ({ count, label }) => (
<div className="flex items-center gap-2">
<CheckCircle2 className="w-4 h-4 text-green-500" />
<span className="text-sm">
<strong>{count}</strong> {label}
</span>
</div>
)
// Donation Form Component
interface DonationFormProps {
amount: number
}
const DonationForm: React.FC<DonationFormProps> = ({ amount }) => {
const [paymentMethod, setPaymentMethod] = useState<'card' | 'paypal'>('card')
const formValidation = useFormValidation(
{
email: '',
firstName: '',
lastName: '',
phone: ''
},
{
email: (value) => {
if (!value) return 'Email is required'
if (!/\S+@\S+\.\S+/.test(value)) return 'Invalid email format'
return null
},
firstName: (value) => !value ? 'First name is required' : null,
lastName: (value) => !value ? 'Last name is required' : null,
phone: (value) => {
if (!value) return null // Optional
if (!/^\+?[\d\s\-\(\)]{10,}$/.test(value)) return 'Invalid phone format'
return null
}
}
)
const handleSubmit = (e: React.FormEvent): void => {
e.preventDefault()
if (formValidation.validate()) {
trackEvent('donation_form_submitted', {
amount,
payment_method: paymentMethod,
form_valid: true
})
// Process donation
console.log('Processing donation:', { amount, paymentMethod, ...formValidation.values })
} else {
trackEvent('donation_form_error', {
amount,
errors: Object.keys(formValidation.errors)
})
}
}
return (
<Card>
<h3 className="text-xl font-semibold text-gray-900 dark:text-white mb-6">
Complete Your Donation
</h3>
<form onSubmit={handleSubmit} className="space-y-6">
{/* Contact Information */}
<div className="grid grid-cols-1 sm:grid-cols-2 gap-4">
<div>
<label className="block text-sm font-medium text-gray-700 dark:text-gray-300 mb-1">
First Name *
</label>
<input
type="text"
value={formValidation.values.firstName}
onChange={(e) => formValidation.setValue('firstName', e.target.value)}
className="w-full px-3 py-2 border border-gray-300 dark:border-gray-600 rounded-lg focus:ring-2 focus:ring-primary-500 focus:border-transparent bg-white dark:bg-gray-800"
/>
{formValidation.errors.firstName && (
<p className="text-red-500 text-sm mt-1">{formValidation.errors.firstName}</p>
)}
</div>
<div className="space-y-6">
<ImpactCalculator />
{/* Add trust badges, testimonials, etc. */}
<div>
<label className="block text-sm font-medium text-gray-700 dark:text-gray-300 mb-1">
Last Name *
</label>
<input
type="text"
value={formValidation.values.lastName}
onChange={(e) => formValidation.setValue('lastName', e.target.value)}
className="w-full px-3 py-2 border border-gray-300 dark:border-gray-600 rounded-lg focus:ring-2 focus:ring-primary-500 focus:border-transparent bg-white dark:bg-gray-800"
/>
{formValidation.errors.lastName && (
<p className="text-red-500 text-sm mt-1">{formValidation.errors.lastName}</p>
)}
</div>
</div>
</PageShell>
</>
<div>
<label className="block text-sm font-medium text-gray-700 dark:text-gray-300 mb-1">
Email Address *
</label>
<input
type="email"
value={formValidation.values.email}
onChange={(e) => formValidation.setValue('email', e.target.value)}
className="w-full px-3 py-2 border border-gray-300 dark:border-gray-600 rounded-lg focus:ring-2 focus:ring-primary-500 focus:border-transparent bg-white dark:bg-gray-800"
/>
{formValidation.errors.email && (
<p className="text-red-500 text-sm mt-1">{formValidation.errors.email}</p>
)}
</div>
{/* Payment Method */}
<div>
<label className="block text-sm font-medium text-gray-700 dark:text-gray-300 mb-3">
Payment Method
</label>
<div className="grid grid-cols-2 gap-4">
<button
type="button"
onClick={() => setPaymentMethod('card')}
className={`p-3 rounded-lg border-2 flex items-center justify-center gap-2 transition-colors ${
paymentMethod === 'card'
? 'border-primary-500 bg-primary-50 dark:bg-primary-900/20'
: 'border-gray-200 dark:border-gray-700'
}`}
>
<CreditCard className="w-5 h-5" />
Credit Card
</button>
<button
type="button"
onClick={() => setPaymentMethod('paypal')}
className={`p-3 rounded-lg border-2 flex items-center justify-center gap-2 transition-colors ${
paymentMethod === 'paypal'
? 'border-primary-500 bg-primary-50 dark:bg-primary-900/20'
: 'border-gray-200 dark:border-gray-700'
}`}
>
PayPal
</button>
</div>
</div>
{/* Submit Button */}
<motion.button
type="submit"
disabled={amount <= 0}
whileHover={{ scale: 1.02 }}
whileTap={{ scale: 0.98 }}
className="w-full btn-primary py-4 text-lg disabled:opacity-50 disabled:cursor-not-allowed"
>
Donate {formatCurrency(amount)}
</motion.button>
</form>
</Card>
)
}
}
// Trust Badges Component
const TrustBadges: React.FC = () => (
<Card>
<h3 className="text-lg font-semibold text-gray-900 dark:text-white mb-4">
Secure & Trusted
</h3>
<div className="space-y-4">
<div className="flex items-center gap-3">
<Shield className="w-5 h-5 text-green-500" />
<span className="text-sm">SSL Encrypted Payments</span>
</div>
<div className="flex items-center gap-3">
<Lock className="w-5 h-5 text-green-500" />
<span className="text-sm">501(c)3 Tax Deductible</span>
</div>
<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>
</div>
</div>
<div className="mt-6 pt-4 border-t border-gray-200 dark:border-gray-700">
<p className="text-xs text-gray-500">
EIN: 12-3456789 All donations are tax-deductible to the fullest extent allowed by law.
</p>
</div>
</Card>
)

View File

@@ -0,0 +1,204 @@
import React from 'react'
import { motion } from 'framer-motion'
import { Heart, Globe, ArrowRight, CheckCircle2 } from 'lucide-react'
import { MainLayout, SectionHeader, Card } from '@/layouts/MainLayout'
import { trackEvent, trackPageView } from '@/utils/analytics'
export const HomePage: React.FC = () => {
React.useEffect(() => {
trackPageView('/', 'Home - Miracles In Motion')
}, [])
const handleDonateClick = (): void => {
trackEvent('cta_clicked', { button: 'hero_donate', location: 'homepage' })
window.location.hash = '/donate'
}
const handleVolunteerClick = (): void => {
trackEvent('cta_clicked', { button: 'hero_volunteer', location: 'homepage' })
window.location.hash = '/volunteer'
}
return (
<MainLayout>
{/* Hero Section */}
<section className="relative overflow-hidden">
<div className="mx-auto max-w-7xl px-4 sm:px-6 lg:px-8 py-20">
<div className="text-center">
<motion.div
initial={{ opacity: 0, y: 30 }}
animate={{ opacity: 1, y: 0 }}
transition={{ duration: 0.8 }}
>
<div className="flex items-center justify-center gap-2 mb-6">
<Heart className="w-8 h-8 text-primary-600" />
<span className="text-lg font-medium text-primary-600 uppercase tracking-wider">
501(c)3 Non-Profit Organization
</span>
</div>
<h1 className="text-5xl md:text-7xl font-bold text-gray-900 dark:text-white mb-6">
Miracles in <span className="text-transparent bg-clip-text bg-gradient-to-r from-primary-600 to-pink-600">Motion</span>
</h1>
<p className="text-xl md:text-2xl text-gray-600 dark:text-gray-300 max-w-3xl mx-auto mb-12 leading-relaxed">
Empowering students with essential supplies, clothing, and support to succeed in school and life.
Every child deserves the tools they need to learn and grow.
</p>
<div className="flex flex-col sm:flex-row gap-4 justify-center">
<button
onClick={handleDonateClick}
className="btn-primary text-lg px-8 py-4 group"
>
Donate Now
<Heart className="w-5 h-5 ml-2 group-hover:scale-110 transition-transform" />
</button>
<button
onClick={handleVolunteerClick}
className="btn-secondary text-lg px-8 py-4 group"
>
Volunteer Today
<ArrowRight className="w-5 h-5 ml-2 group-hover:translate-x-1 transition-transform" />
</button>
</div>
</motion.div>
</div>
</div>
{/* Background Elements */}
<div className="absolute inset-0 -z-10">
<div className="absolute top-1/4 left-1/4 w-64 h-64 bg-primary-200/30 rounded-full blur-3xl" />
<div className="absolute bottom-1/4 right-1/4 w-96 h-96 bg-pink-200/30 rounded-full blur-3xl" />
</div>
</section>
{/* Impact Stats */}
<section className="py-16 bg-white/50 dark:bg-gray-800/50 backdrop-blur-sm">
<div className="mx-auto max-w-7xl px-4 sm:px-6 lg:px-8">
<SectionHeader
eyebrow="Our Impact"
title="Making a Real Difference"
subtitle="Transparent, measurable outcomes powered by community partnerships"
/>
<div className="grid grid-cols-2 lg:grid-cols-4 gap-6">
<ImpactStat label="Students Helped" value="4,200+" />
<ImpactStat label="Schools Partnered" value="38" />
<ImpactStat label="Avg Response Time" value="24 hrs" />
<ImpactStat label="Counties Served" value="6" />
</div>
</div>
</section>
{/* What We Do */}
<section className="py-20">
<div className="mx-auto max-w-7xl px-4 sm:px-6 lg:px-8">
<SectionHeader
eyebrow="What We Do"
title="Supporting Student Success"
subtitle="Comprehensive support to remove barriers and create opportunities"
/>
<div className="grid md:grid-cols-2 lg:grid-cols-3 gap-8">
<ServiceCard
icon={<CheckCircle2 className="w-8 h-8 text-primary-600" />}
title="School Supplies"
description="Backpacks, notebooks, pencils, and all the essentials students need to succeed in the classroom."
/>
<ServiceCard
icon={<Heart className="w-8 h-8 text-primary-600" />}
title="Clothing & Shoes"
description="Weather-appropriate clothing and sturdy shoes so students can attend school with confidence."
/>
<ServiceCard
icon={<Globe className="w-8 h-8 text-primary-600" />}
title="Emergency Support"
description="Rapid response assistance for urgent needs including food, transportation, and crisis support."
/>
</div>
</div>
</section>
{/* Call to Action */}
<section className="py-20 bg-gradient-to-r from-primary-600 to-pink-600">
<div className="mx-auto max-w-4xl px-4 sm:px-6 lg:px-8 text-center">
<motion.div
initial={{ opacity: 0, y: 20 }}
whileInView={{ opacity: 1, y: 0 }}
viewport={{ once: true }}
>
<h2 className="text-3xl md:text-4xl font-bold text-white mb-6">
Ready to Make a Difference?
</h2>
<p className="text-xl text-white/90 mb-8">
Join our community of supporters helping students succeed. Every contribution creates ripples of positive change.
</p>
<button
onClick={handleDonateClick}
className="bg-white text-primary-600 hover:bg-gray-50 font-semibold px-8 py-4 rounded-lg transition-colors"
>
Start Supporting Students Today
</button>
</motion.div>
</div>
</section>
</MainLayout>
)
}
// Impact Stat Component
interface ImpactStatProps {
label: string
value: string
}
const ImpactStat: React.FC<ImpactStatProps> = ({ label, value }) => (
<motion.div
initial={{ opacity: 0, scale: 0.8 }}
whileInView={{ opacity: 1, scale: 1 }}
viewport={{ once: true }}
className="text-center"
>
<Card className="h-full">
<div className="text-3xl md:text-4xl font-bold text-primary-600 mb-2">
{value}
</div>
<div className="text-gray-600 dark:text-gray-300 font-medium">
{label}
</div>
</Card>
</motion.div>
)
// Service Card Component
interface ServiceCardProps {
icon: React.ReactNode
title: string
description: string
}
const ServiceCard: React.FC<ServiceCardProps> = ({ icon, title, description }) => (
<motion.div
initial={{ opacity: 0, y: 20 }}
whileInView={{ opacity: 1, y: 0 }}
viewport={{ once: true }}
whileHover={{ y: -5 }}
transition={{ duration: 0.3 }}
>
<Card className="h-full text-center hover:shadow-xl transition-shadow duration-300">
<div className="flex justify-center mb-4">
{icon}
</div>
<h3 className="text-xl font-semibold text-gray-900 dark:text-white mb-3">
{title}
</h3>
<p className="text-gray-600 dark:text-gray-300">
{description}
</p>
</Card>
</motion.div>
)

View File

@@ -1,8 +1,28 @@
// Phase 3B: Real-Time Data Processing and Live Deployment System
import { StudentAssistanceAI } from '../ai/StudentAssistanceAI'
import { SalesforceConnector } from '../crm/SalesforceConnector'
// Phase 3B: Browser-Compatible Real-Time Processing System
import type { StudentRequest, MatchResult, AIUpdate } from '../ai/types'
// Browser-compatible AI processor
class BrowserAIProcessor {
async processRequest(request: StudentRequest): Promise<MatchResult[]> {
// Mock implementation for browser
console.log('Processing request in browser:', request.id)
await new Promise(resolve => setTimeout(resolve, 1000))
return [{
resourceId: 'browser-resource-1',
resourceName: 'Browser Mock Resource',
resourceType: 'supplies',
confidenceScore: 0.75,
estimatedImpact: 7.5,
logisticalComplexity: 2.0,
estimatedCost: 45,
fulfillmentTimeline: '2-4 days',
reasoningFactors: ['Browser-based processing', 'Mock data for demo'],
riskFactors: ['Demo mode - not real matching']
}]
}
}
interface RealTimeConfig {
enableWebSockets: boolean
batchSize: number
@@ -34,8 +54,8 @@ interface DataSyncStatus {
}
class RealTimeProcessor {
private ai: StudentAssistanceAI
private salesforce?: SalesforceConnector
private ai: BrowserAIProcessor
private salesforce?: any // Mock for browser demo
private config: RealTimeConfig
private processingQueue: StudentRequest[] = []
private activeProcessors = new Map<string, Promise<MatchResult[]>>()
@@ -48,10 +68,10 @@ class RealTimeProcessor {
constructor(config: RealTimeConfig, salesforceConfig?: any) {
this.config = config
this.ai = new StudentAssistanceAI()
this.ai = new BrowserAIProcessor()
if (salesforceConfig) {
this.salesforce = new SalesforceConnector(salesforceConfig)
this.salesforce = { mock: true } // Mock for browser demo
}
this.metrics = {

128
src/utils/analytics.ts Normal file
View File

@@ -0,0 +1,128 @@
// Analytics and tracking utilities
interface TrackingEvent {
event: string
properties?: Record<string, any>
userId?: string
}
class Analytics {
private isEnabled: boolean = false
private userId: string | null = null
constructor() {
this.isEnabled = process.env.VITE_ANALYTICS_ENABLED === 'true'
}
// Initialize analytics
init(userId?: string): void {
this.userId = userId || null
if (this.isEnabled) {
console.log('🔍 Analytics initialized', { userId })
}
}
// Track page views
trackPageView(page: string, title?: string): void {
if (!this.isEnabled) return
const event: TrackingEvent = {
event: 'page_view',
properties: {
page,
title: title || document.title,
timestamp: new Date().toISOString(),
url: window.location.href
},
userId: this.userId || undefined
}
this.sendEvent(event)
}
// Track custom events
trackEvent(eventName: string, properties?: Record<string, any>): void {
if (!this.isEnabled) return
const event: TrackingEvent = {
event: eventName,
properties: {
...properties,
timestamp: new Date().toISOString(),
page: window.location.pathname
},
userId: this.userId || undefined
}
this.sendEvent(event)
}
// Track donation events
trackDonation(amount: number, method: string): void {
this.trackEvent('donation_completed', {
amount,
method,
impact: this.calculateImpactString(amount)
})
}
// Track volunteer signups
trackVolunteerSignup(volunteerType: string): void {
this.trackEvent('volunteer_signup', {
type: volunteerType
})
}
// Track form submissions
trackFormSubmission(formName: string, success: boolean): void {
this.trackEvent('form_submission', {
form_name: formName,
success
})
}
// Send event to analytics service
private sendEvent(event: TrackingEvent): void {
if (typeof window !== 'undefined' && window.gtag) {
// Google Analytics 4
window.gtag('event', event.event, {
custom_parameters: event.properties,
user_id: event.userId
})
}
// Console logging for development
if (process.env.NODE_ENV === 'development') {
console.log('📊 Analytics Event:', event)
}
// Could also send to other analytics services here
// this.sendToMixpanel(event)
// this.sendToAmplitude(event)
}
private calculateImpactString(amount: number): string {
const students = Math.floor(amount / 25)
return `${students} students supported`
}
}
// Export singleton instance
export const analytics = new Analytics()
// Track event helper function
export const trackEvent = (eventName: string, properties?: Record<string, any>): void => {
analytics.trackEvent(eventName, properties)
}
// Track page view helper function
export const trackPageView = (page: string, title?: string): void => {
analytics.trackPageView(page, title)
}
// Declare global gtag function for TypeScript
declare global {
interface Window {
gtag: (...args: any[]) => void
}
}

122
src/utils/helpers.ts Normal file
View File

@@ -0,0 +1,122 @@
// Enhanced impact calculation utilities
export interface ImpactCalculation {
students: number
families: number
backpacks: number
clothing: number
emergency: number
annual: {
students: number
families: number
totalImpact: string
}
}
export const 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`
}
}
}
// Format currency
export const formatCurrency = (amount: number): string => {
return new Intl.NumberFormat('en-US', {
style: 'currency',
currency: 'USD'
}).format(amount)
}
// Format numbers with commas
export const formatNumber = (num: number): string => {
return new Intl.NumberFormat('en-US').format(num)
}
// Validate email
export const validateEmail = (email: string): boolean => {
const emailRegex = /^[^\s@]+@[^\s@]+\.[^\s@]+$/
return emailRegex.test(email)
}
// Validate phone number
export const validatePhone = (phone: string): boolean => {
const phoneRegex = /^[\+]?[1-9][\d]{0,15}$/
return phoneRegex.test(phone.replace(/\D/g, ''))
}
// Generate unique ID
export const generateId = (): string => {
return Math.random().toString(36).substr(2, 9)
}
// Debounce function
export const debounce = <T extends (...args: any[]) => any>(
func: T,
delay: number
): ((...args: Parameters<T>) => void) => {
let timeoutId: NodeJS.Timeout
return (...args: Parameters<T>) => {
clearTimeout(timeoutId)
timeoutId = setTimeout(() => func(...args), delay)
}
}
// Throttle function
export const throttle = <T extends (...args: any[]) => any>(
func: T,
limit: number
): ((...args: Parameters<T>) => void) => {
let inThrottle: boolean
return (...args: Parameters<T>) => {
if (!inThrottle) {
func(...args)
inThrottle = true
setTimeout(() => inThrottle = false, limit)
}
}
}
// Safe JSON parse
export const safeJsonParse = <T>(str: string, fallback: T): T => {
try {
return JSON.parse(str)
} catch {
return fallback
}
}
// Format date
export const formatDate = (date: Date): string => {
return new Intl.DateTimeFormat('en-US', {
year: 'numeric',
month: 'long',
day: 'numeric'
}).format(date)
}
// Format relative time
export const formatRelativeTime = (date: Date): string => {
const now = new Date()
const diffInSeconds = Math.floor((now.getTime() - date.getTime()) / 1000)
if (diffInSeconds < 60) return 'just now'
if (diffInSeconds < 3600) return `${Math.floor(diffInSeconds / 60)} minutes ago`
if (diffInSeconds < 86400) return `${Math.floor(diffInSeconds / 3600)} hours ago`
if (diffInSeconds < 604800) return `${Math.floor(diffInSeconds / 86400)} days ago`
return formatDate(date)
}

View File

@@ -2,7 +2,7 @@ import { defineConfig } from 'vite'
import react from '@vitejs/plugin-react'
import { resolve } from 'path'
// https://vitejs.dev/config/
// Optimized Vite Configuration for Production
export default defineConfig({
plugins: [react()],
resolve: {
@@ -20,11 +20,30 @@ export default defineConfig({
rollupOptions: {
output: {
manualChunks: {
// Core React libraries
vendor: ['react', 'react-dom'],
motion: ['framer-motion'],
icons: ['lucide-react'],
// UI and animations
ui: ['framer-motion', 'lucide-react'],
// AI libraries (browser-compatible)
ai: ['@tensorflow/tfjs'],
// Utilities
utils: ['date-fns', 'react-helmet-async']
},
chunkFileNames: 'js/[name]-[hash].js'
}
},
// Enable compression
minify: 'terser',
terserOptions: {
compress: {
drop_console: true,
drop_debugger: true,
},
},
// Optimize chunks
chunkSizeWarningLimit: 500,
},
})