diff --git a/.env.public.example b/.env.public.example new file mode 100644 index 0000000..3ce6a65 --- /dev/null +++ b/.env.public.example @@ -0,0 +1,28 @@ +# Public Environment Variables Only +# This file can be committed to version control + +# Application Info +VITE_APP_NAME="Miracles In Motion" +VITE_APP_VERSION="3.0.0" +VITE_APP_ENV="production" + +# Public URLs +VITE_API_BASE_URL="https://api.miraclesinmotion.org" +VITE_CDN_URL="https://cdn.miraclesinmotion.org" + +# Feature Flags +VITE_ENABLE_AI_ASSISTANCE="true" +VITE_ENABLE_ANALYTICS="true" +VITE_ENABLE_MOBILE_APP="true" +VITE_ENABLE_TRAINING="true" + +# Performance Settings +VITE_AI_BATCH_SIZE="5" +VITE_CACHE_TIMEOUT="300000" +VITE_REQUEST_TIMEOUT="30000" + +# Note: Sensitive data should be managed through: +# - Server-side environment variables +# - Secure credential management systems +# - Runtime configuration APIs +# Never commit API keys, passwords, or tokens to version control \ No newline at end of file diff --git a/.eslintrc.recommended.json b/.eslintrc.recommended.json new file mode 100644 index 0000000..6a239b4 --- /dev/null +++ b/.eslintrc.recommended.json @@ -0,0 +1,32 @@ +{ + "root": true, + "env": { "browser": true, "es2020": true }, + "extends": [ + "eslint:recommended", + "@typescript-eslint/recommended", + "plugin:react-hooks/recommended", + "plugin:react/recommended", + "plugin:jsx-a11y/recommended" + ], + "ignorePatterns": ["dist", ".eslintrc.cjs"], + "parser": "@typescript-eslint/parser", + "plugins": ["react-refresh", "jsx-a11y"], + "rules": { + "react-refresh/only-export-components": [ + "warn", + { "allowConstantExport": true } + ], + "react/react-in-jsx-scope": "off", + "react/prop-types": "off", + "@typescript-eslint/no-unused-vars": ["error", { "argsIgnorePattern": "^_" }], + "@typescript-eslint/explicit-function-return-type": "warn", + "jsx-a11y/anchor-is-valid": "error", + "jsx-a11y/alt-text": "error", + "no-console": "warn" + }, + "settings": { + "react": { + "version": "detect" + } + } +} \ No newline at end of file diff --git a/.github/workflows/deploy.yml b/.github/workflows/deploy.yml new file mode 100644 index 0000000..8ebabb7 --- /dev/null +++ b/.github/workflows/deploy.yml @@ -0,0 +1,87 @@ +name: Build and Deploy + +on: + push: + branches: [ main ] + pull_request: + branches: [ main ] + +jobs: + test: + runs-on: ubuntu-latest + + steps: + - uses: actions/checkout@v3 + + - name: Setup Node.js + uses: actions/setup-node@v3 + with: + node-version: '18' + cache: 'npm' + + - name: Install dependencies + run: npm ci + + - name: Run type checking + run: npm run type-check + + - name: Run linting + run: npm run lint + + - name: Run tests + run: npm run test:ci + + - name: Security audit + run: npm audit --audit-level moderate + + build: + needs: test + runs-on: ubuntu-latest + + steps: + - uses: actions/checkout@v3 + + - name: Setup Node.js + uses: actions/setup-node@v3 + with: + node-version: '18' + cache: 'npm' + + - name: Install dependencies + run: npm ci + + - name: Build application + run: npm run build + env: + VITE_APP_VERSION: ${{ github.sha }} + VITE_BUILD_TIME: ${{ github.event.head_commit.timestamp }} + + - name: Analyze bundle size + run: npx bundlesize + + - name: Upload build artifacts + uses: actions/upload-artifact@v3 + with: + name: dist + path: dist/ + + deploy: + needs: build + runs-on: ubuntu-latest + if: github.ref == 'refs/heads/main' + + steps: + - uses: actions/checkout@v3 + + - name: Download build artifacts + uses: actions/download-artifact@v3 + with: + name: dist + path: dist/ + + - name: Deploy to GitHub Pages + uses: peaceiris/actions-gh-pages@v3 + with: + github_token: ${{ secrets.GITHUB_TOKEN }} + publish_dir: ./dist + cname: miraclesinmotion.org \ No newline at end of file diff --git a/docs/API.md b/docs/API.md new file mode 100644 index 0000000..a4fc690 --- /dev/null +++ b/docs/API.md @@ -0,0 +1,76 @@ +# API Documentation + +## Student Assistance AI API + +### Core Endpoints + +#### `POST /api/student-requests` +Process new student assistance requests through AI matching engine. + +**Request Body:** +```typescript +{ + studentId: string + description: string + category: 'clothing' | 'supplies' | 'food' | 'transportation' | 'emergency' + urgency: 'low' | 'medium' | 'high' | 'critical' + constraints: { + maxBudget?: number + timeframe: string + geographic?: { + maxDistance: number + preferredAreas?: string[] + } + } +} +``` + +**Response:** +```typescript +{ + requestId: string + status: 'pending' | 'processing' | 'matched' | 'completed' + matches: MatchResult[] + estimatedCompletion: string + aiConfidence: number +} +``` + +#### `GET /api/requests/{requestId}/status` +Get real-time status of a student request. + +#### `POST /api/ai/feedback` +Submit feedback for AI model improvement. + +**Request Body:** +```typescript +{ + requestId: string + matchId: string + outcome: 'successful' | 'partial' | 'failed' + feedback: { + satisfactionScore: number (1-5) + issues?: string[] + improvements?: string[] + } +} +``` + +### Error Handling + +All API endpoints return errors in the following format: +```typescript +{ + error: { + code: string + message: string + details?: any + } +} +``` + +Common error codes: +- `INVALID_REQUEST`: Request format is incorrect +- `AI_MODEL_UNAVAILABLE`: AI service is temporarily unavailable +- `INSUFFICIENT_RESOURCES`: No matching resources found +- `RATE_LIMIT_EXCEEDED`: Too many requests from client \ No newline at end of file diff --git a/jest.config.js b/jest.config.js new file mode 100644 index 0000000..b893594 --- /dev/null +++ b/jest.config.js @@ -0,0 +1,28 @@ +// Jest Configuration for Testing +module.exports = { + preset: 'ts-jest', + testEnvironment: 'jsdom', + setupFilesAfterEnv: ['/src/test/setup.ts'], + moduleNameMapping: { + '^@/(.*)$': '/src/$1', + '\\.(css|less|scss)$': 'identity-obj-proxy' + }, + collectCoverageFrom: [ + 'src/**/*.{ts,tsx}', + '!src/**/*.d.ts', + '!src/test/**/*', + '!src/main.tsx' + ], + coverageThreshold: { + global: { + branches: 70, + functions: 70, + lines: 70, + statements: 70 + } + }, + testMatch: [ + '/src/**/__tests__/**/*.{ts,tsx}', + '/src/**/*.{test,spec}.{ts,tsx}' + ] +} \ No newline at end of file diff --git a/package.recommended.json b/package.recommended.json new file mode 100644 index 0000000..111fb86 --- /dev/null +++ b/package.recommended.json @@ -0,0 +1,91 @@ +{ + "name": "miracles-in-motion-web", + "private": true, + "version": "1.0.0", + "type": "module", + "description": "Public website for Miracles In Motion 501(c)3 non-profit organization", + "scripts": { + "dev": "vite", + "build": "tsc && vite build", + "build:analyze": "npm run build && npx vite-bundle-analyzer dist", + "lint": "eslint . --ext ts,tsx --report-unused-disable-directives --max-warnings 0", + "lint:fix": "eslint . --ext ts,tsx --fix", + "type-check": "tsc --noEmit", + "preview": "vite preview", + "test": "jest", + "test:watch": "jest --watch", + "test:ci": "jest --ci --coverage --watchAll=false", + "deploy": "npm run build && gh-pages -d dist", + "audit:security": "npm audit --audit-level moderate", + "audit:bundle": "npx bundlesize" + }, + "bundlesize": [ + { + "path": "./dist/assets/*.js", + "maxSize": "500kb" + } + ], + "keywords": [ + "non-profit", + "charity", + "501c3", + "miracles-in-motion", + "community", + "donations", + "volunteers", + "react", + "vite", + "tailwind" + ], + "author": "Miracles In Motion", + "license": "MIT", + "repository": { + "type": "git", + "url": "https://github.com/Miracles-In-Motion/public-web.git" + }, + "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", + "@types/react": "^18.2.37", + "@types/react-dom": "^18.2.15", + "@types/jest": "^29.5.7", + "@testing-library/react": "^13.4.0", + "@testing-library/jest-dom": "^6.1.4", + "@typescript-eslint/eslint-plugin": "^6.10.0", + "@typescript-eslint/parser": "^6.10.0", + "@vitejs/plugin-react": "^4.1.0", + "autoprefixer": "^10.4.16", + "bundlesize": "^0.18.1", + "eslint": "^8.53.0", + "eslint-plugin-react-hooks": "^4.6.0", + "eslint-plugin-react-refresh": "^0.4.4", + "eslint-plugin-jsx-a11y": "^6.8.0", + "gh-pages": "^6.0.0", + "jest": "^29.7.0", + "jest-environment-jsdom": "^29.7.0", + "postcss": "^8.4.31", + "tailwindcss": "^3.3.5", + "ts-jest": "^29.1.1", + "typescript": "^5.2.2", + "vite": "^4.5.0", + "vite-bundle-analyzer": "^0.7.0" + } +} \ No newline at end of file diff --git a/src/ai/OptimizedStudentAssistanceAI.ts b/src/ai/OptimizedStudentAssistanceAI.ts new file mode 100644 index 0000000..90c1368 --- /dev/null +++ b/src/ai/OptimizedStudentAssistanceAI.ts @@ -0,0 +1,69 @@ +// AI Model Lazy Loading Implementation +export class OptimizedStudentAssistanceAI { + private static models: Map = new Map() + private static modelUrls = { + 'text-vectorization': '/models/text-vectorizer.json', + 'matching-engine': '/models/matcher.json', + 'priority-classifier': '/models/priority.json' + } + + // Lazy load models on demand + private static async loadModel(modelType: string) { + if (this.models.has(modelType)) { + return this.models.get(modelType) + } + + try { + console.log(`🤖 Loading AI model: ${modelType}`) + const model = await tf.loadLayersModel(this.modelUrls[modelType]) + this.models.set(modelType, model) + console.log(`✅ Model ${modelType} loaded successfully`) + return model + } catch (error) { + console.error(`❌ Failed to load model ${modelType}:`, error) + // Fallback to rule-based system + return null + } + } + + // Preload critical models in background + static async preloadCriticalModels() { + try { + // Load text vectorization model first (most commonly used) + await this.loadModel('text-vectorization') + + // Load others in background + setTimeout(() => this.loadModel('matching-engine'), 2000) + setTimeout(() => this.loadModel('priority-classifier'), 4000) + } catch (error) { + console.warn('Background model preloading failed:', error) + } + } + + async processRequest(request: StudentRequest): Promise { + // Load models as needed + const textModel = await OptimizedStudentAssistanceAI.loadModel('text-vectorization') + const matchingModel = await OptimizedStudentAssistanceAI.loadModel('matching-engine') + + // Process with loaded models or fallback to rule-based + if (textModel && matchingModel) { + return this.aiBasedMatching(request, textModel, matchingModel) + } else { + return this.ruleBasedMatching(request) + } + } + + private async aiBasedMatching(request: StudentRequest, textModel: any, matchingModel: any): Promise { + // AI-powered matching logic + console.log('🤖 Using AI-powered matching') + // ... implementation + return [] + } + + private async ruleBasedMatching(request: StudentRequest): Promise { + // Fallback rule-based matching + console.log('📏 Using rule-based matching (fallback)') + // ... implementation + return [] + } +} \ No newline at end of file diff --git a/src/components/SEO/index.tsx b/src/components/SEO/index.tsx new file mode 100644 index 0000000..03e63ed --- /dev/null +++ b/src/components/SEO/index.tsx @@ -0,0 +1,69 @@ +import React from 'react' +import { Helmet } from 'react-helmet-async' + +interface SEOProps { + title?: string + description?: string + keywords?: string[] + image?: string + url?: string + type?: 'website' | 'article' | 'organization' +} + +export const SEO: React.FC = ({ + title = 'Miracles In Motion - Supporting Students in Need', + description = 'A 501(c)3 non-profit providing school supplies, clothing, and emergency assistance to students and families in need.', + keywords = ['nonprofit', 'charity', '501c3', 'student assistance', 'school supplies', 'donations'], + image = 'https://miraclesinmotion.org/og-image.png', + url = 'https://miraclesinmotion.org', + type = 'website' +}) => { + const structuredData = { + "@context": "https://schema.org", + "@type": "Organization", + "name": "Miracles In Motion", + "description": description, + "url": url, + "logo": "https://miraclesinmotion.org/logo.png", + "contactPoint": { + "@type": "ContactPoint", + "telephone": "+1-555-123-4567", + "contactType": "Customer Service" + }, + "sameAs": [ + "https://facebook.com/miraclesinmotion", + "https://instagram.com/miraclesinmotion" + ] + } + + return ( + + {title} + + + + {/* Open Graph */} + + + + + + + {/* Twitter Card */} + + + + + + {/* Structured Data */} + + + {/* Additional Meta Tags */} + + + + + ) +} \ No newline at end of file diff --git a/src/pages/DonatePage/index.tsx b/src/pages/DonatePage/index.tsx new file mode 100644 index 0000000..f40a9fa --- /dev/null +++ b/src/pages/DonatePage/index.tsx @@ -0,0 +1,42 @@ +// 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' + +interface DonatePageProps { + className?: string +} + +export const DonatePage: React.FC = ({ className }) => { + return ( + <> + + + +
+
+ + +
+ +
+ + {/* Add trust badges, testimonials, etc. */} +
+
+
+ + ) +} \ No newline at end of file diff --git a/vite.config.optimized.ts b/vite.config.optimized.ts new file mode 100644 index 0000000..17ec7a7 --- /dev/null +++ b/vite.config.optimized.ts @@ -0,0 +1,61 @@ +import { defineConfig } from 'vite' +import react from '@vitejs/plugin-react' +import { resolve } from 'path' + +// Optimized Vite Configuration +export default defineConfig({ + plugins: [react()], + resolve: { + alias: { + '@': resolve(__dirname, './src'), + }, + }, + server: { + port: 3000, + open: true, + }, + build: { + outDir: 'dist', + sourcemap: true, + rollupOptions: { + output: { + manualChunks: { + // Core React libraries + vendor: ['react', 'react-dom'], + + // UI and animations + ui: ['framer-motion', 'lucide-react'], + + // AI and ML libraries (largest chunk) + ai: ['@tensorflow/tfjs', 'natural', 'ml-matrix', 'compromise'], + + // Real-time and networking + realtime: ['socket.io-client', 'ws', 'ioredis', 'redis'], + + // Queue management + queue: ['bull', 'uuid'], + + // Date utilities + utils: ['date-fns'] + }, + // Optimize chunk sizes + chunkFileNames: (chunkInfo) => { + const facadeModuleId = chunkInfo.facadeModuleId + ? chunkInfo.facadeModuleId.split('/').pop() + : 'chunk' + return `js/${facadeModuleId}-[hash].js` + } + }, + }, + // Enable compression + minify: 'terser', + terserOptions: { + compress: { + drop_console: true, + drop_debugger: true, + }, + }, + // Optimize chunks + chunkSizeWarningLimit: 500, + }, +}) \ No newline at end of file