diff --git a/.crossnote/config.js b/.crossnote/config.js new file mode 100644 index 0000000..80613c4 --- /dev/null +++ b/.crossnote/config.js @@ -0,0 +1,15 @@ +({ + katexConfig: { + "macros": {} +}, + + mathjaxConfig: { + "tex": {}, + "options": {}, + "loader": {} +}, + + mermaidConfig: { + "startOnLoad": false +}, +}) \ No newline at end of file diff --git a/.crossnote/head.html b/.crossnote/head.html new file mode 100644 index 0000000..079058b --- /dev/null +++ b/.crossnote/head.html @@ -0,0 +1,6 @@ + + \ No newline at end of file diff --git a/.crossnote/parser.js b/.crossnote/parser.js new file mode 100644 index 0000000..0f6b5a9 --- /dev/null +++ b/.crossnote/parser.js @@ -0,0 +1,12 @@ +({ + // Please visit the URL below for more information: + // https://shd101wyy.github.io/markdown-preview-enhanced/#/extend-parser + + onWillParseMarkdown: async function(markdown) { + return markdown; + }, + + onDidParseMarkdown: async function(html) { + return html; + }, +}) \ No newline at end of file diff --git a/.crossnote/style.less b/.crossnote/style.less new file mode 100644 index 0000000..cf7e2ab --- /dev/null +++ b/.crossnote/style.less @@ -0,0 +1,8 @@ + +/* Please visit the URL below for more information: */ +/* https://shd101wyy.github.io/markdown-preview-enhanced/#/customize-css */ + +.markdown-preview.markdown-preview { + // modify your style here + // eg: background-color: blue; +} diff --git a/README.md b/README.md index 2589962..c92fe77 100644 --- a/README.md +++ b/README.md @@ -33,6 +33,7 @@ npm install ``` **Key Dependencies:** + - `react@19.0.0` - UI library - `react-router-dom@7.5.1` - Routing - `axios@1.8.4` - HTTP client @@ -57,6 +58,7 @@ REACT_APP_BACKEND_URL=http://localhost:8000 ``` **Important:** + - All environment variables must start with `REACT_APP_` - Restart development server after changing `.env` @@ -71,6 +73,7 @@ npm start ``` **Development server will be available at:** + - Frontend: http://localhost:3000 - Auto-reloads on file changes @@ -154,31 +157,33 @@ frontend/ ### Core Technologies -| Technology | Purpose | Version | -|------------|---------|---------| -| React | UI library | 19.0.0 | -| React Router | Client-side routing | 7.5.1 | -| Axios | HTTP requests | 1.8.4 | -| React Hook Form | Form handling | 7.56.2 | -| Zod | Schema validation | 3.24.4 | -| Tailwind CSS | CSS framework | 3.4.17 | -| Radix UI | Component library | Latest | -| Lucide React | Icons | 0.507.0 | -| Sonner | Toast notifications | 1.7.4 | +| Technology | Purpose | Version | +| --------------- | ------------------- | ------- | +| React | UI library | 19.0.0 | +| React Router | Client-side routing | 7.5.1 | +| Axios | HTTP requests | 1.8.4 | +| React Hook Form | Form handling | 7.56.2 | +| Zod | Schema validation | 3.24.4 | +| Tailwind CSS | CSS framework | 3.4.17 | +| Radix UI | Component library | Latest | +| Lucide React | Icons | 0.507.0 | +| Sonner | Toast notifications | 1.7.4 | ### Key Features #### Authentication System **Global Auth Context** (`src/context/AuthContext.js`): + - JWT token storage in localStorage - Automatic token injection via Axios interceptor - User state management - Protected route wrapper **Usage:** + ```jsx -import { useAuth } from '../context/AuthContext'; +import { useAuth } from "../context/AuthContext"; function MyComponent() { const { user, login, logout, isAuthenticated } = useAuth(); @@ -190,6 +195,7 @@ function MyComponent() { #### Protected Routes **PrivateRoute Wrapper:** + ```jsx @@ -208,61 +214,68 @@ function MyComponent() { #### API Integration **Axios Instance** (`src/utils/api.js`): + - Automatic JWT token injection - Request/response interceptors - Error handling - Base URL configuration **Usage:** + ```jsx -import api from '../utils/api'; +import api from "../utils/api"; // GET request -const response = await api.get('/members/profile'); +const response = await api.get("/members/profile"); // POST request with data -const response = await api.post('/admin/users/123/reject', { - reason: 'Incomplete application' +const response = await api.post("/admin/users/123/reject", { + reason: "Incomplete application", }); // File upload const formData = new FormData(); -formData.append('file', file); -const response = await api.post('/members/profile/upload-photo', formData, { - headers: { 'Content-Type': 'multipart/form-data' } +formData.append("file", file); +const response = await api.post("/members/profile/upload-photo", formData, { + headers: { "Content-Type": "multipart/form-data" }, }); ``` #### Form Handling **React Hook Form + Zod Pattern:** + ```jsx -import { useForm } from 'react-hook-form'; -import { zodResolver } from '@hookform/resolvers/zod'; -import { z } from 'zod'; +import { useForm } from "react-hook-form"; +import { zodResolver } from "@hookform/resolvers/zod"; +import { z } from "zod"; const schema = z.object({ - email: z.string().email('Invalid email address'), - password: z.string().min(8, 'Password must be at least 8 characters') + email: z.string().email("Invalid email address"), + password: z.string().min(8, "Password must be at least 8 characters"), }); function LoginForm() { - const { register, handleSubmit, formState: { errors } } = useForm({ - resolver: zodResolver(schema) + const { + register, + handleSubmit, + formState: { errors }, + } = useForm({ + resolver: zodResolver(schema), }); const onSubmit = async (data) => { try { - await api.post('/auth/login', data); - toast.success('Login successful!'); + await api.post("/auth/login", data); + toast.success("Login successful!"); } catch (error) { - toast.error(error.response?.data?.detail || 'Login failed'); + toast.error(error.response?.data?.detail || "Login failed"); } }; return ( - + {errors.email && {errors.email.message}} {/* ... */} @@ -273,18 +286,19 @@ function LoginForm() { #### Toast Notifications **Using Sonner:** + ```jsx -import { toast } from 'sonner'; +import { toast } from "sonner"; // Success -toast.success('Profile updated successfully!'); +toast.success("Profile updated successfully!"); // Error -toast.error('Failed to upload photo'); +toast.error("Failed to upload photo"); // Custom -toast('Processing...', { - description: 'Please wait while we process your request' +toast("Processing...", { + description: "Please wait while we process your request", }); ``` @@ -293,11 +307,13 @@ toast('Processing...', { #### Public Pages **Landing.js** + - Hero section with LOAF branding - Feature highlights - Call-to-action buttons **Register.js** (388 lines) + - 4-step registration wizard: 1. Basic Info (email, password, name) 2. Personal Details (phone, address, DOB) @@ -308,6 +324,7 @@ toast('Processing...', { - Email verification trigger **Login.js** + - Email/password authentication - JWT token storage - Remember me functionality @@ -316,12 +333,14 @@ toast('Processing...', { #### Member Pages **Dashboard.js** + - Welcome message with user name - Upcoming events preview - Membership status card - Quick action buttons **Profile.js** + - Profile photo upload with Avatar component - File validation (image types, 50MB max) - Personal information editing @@ -329,12 +348,14 @@ toast('Processing...', { - Save changes with API update **Events.js** + - Event listing with filters - Search functionality - Upcoming/past events toggle - Event cards with cover images **EventDetails.js** + - Full event information - RSVP form (Yes/No/Maybe) - Attendance confirmation @@ -343,36 +364,42 @@ toast('Processing...', { #### Admin Pages **AdminDashboard.js** + - Statistics overview - Pending validations count - Recent activity feed - Quick links to management pages **AdminUsers.js** + - User listing with search/filters - Status badges - Bulk operations - CSV export **AdminValidations.js** + - Pending user applications - Approve button - **Reject button with RejectionDialog** - Validation workflow management **AdminSubscriptions.js** + - Subscription listing - Status filters (active, cancelled, expired) - **CSV export dropdown** (Export All / Export Current View) - Edit subscription dialog **AdminDonations.js** (400+ lines) + - 4 stats cards: Total, Member, Public, Total Amount - Filters: type, status, date range, search - Responsive table with mobile cards - **CSV export functionality** **AdminEvents.js** + - Event management interface - Create/edit events - Publish/unpublish toggle @@ -385,40 +412,50 @@ toast('Processing...', { **45+ UI Components** in `src/components/ui/`: **Form Components:** + - Button, Input, Textarea, Checkbox, Radio - Select, Label, Form **Layout Components:** + - Card, Dialog, Sheet (Drawer), Popover - Dropdown Menu, Tabs, Accordion **Feedback Components:** + - Alert, Badge, Toast (Sonner) - Progress, Skeleton, Spinner **Navigation:** + - Navigation Menu, Breadcrumb, Pagination **Usage Example:** -```jsx -import { Button } from './components/ui/button'; -import { Dialog, DialogContent, DialogTitle } from './components/ui/dialog'; -import { Select, SelectTrigger, SelectContent, SelectItem } from './components/ui/select'; - - Click me - +```jsx +import { Button } from "./components/ui/button"; +import { Dialog, DialogContent, DialogTitle } from "./components/ui/dialog"; +import { + Select, + SelectTrigger, + SelectContent, + SelectItem, +} from "./components/ui/select"; + +Click me; ``` ### Admin Sidebar Features **Logo Integration:** + - LOAF logo in header - Shows logo + "Admin" text when expanded - Logo only when collapsed - Smooth transition animations **Navigation Groups:** + - **Dashboard** (standalone) - **MEMBERSHIP** - Staff, Members, Validations - **FINANCIALS** - Plans, Subscriptions, Donations @@ -433,6 +470,7 @@ import { Select, SelectTrigger, SelectContent, SelectItem } from './components/u ### Color Palette **LOAF Brand Colors:** + ```css --primary: #422268 /* Deep Purple - Primary brand */ --secondary: #664fa3 /* Light Purple - Secondary elements */ @@ -443,21 +481,24 @@ import { Select, SelectTrigger, SelectContent, SelectItem } from './components/u ``` **Usage in Tailwind:** + ```jsx - - Accent Text - Secondary Text + + Accent Text + Secondary Text ``` ### Typography **Font Families:** + - **Headings**: 'Inter', sans-serif - **Body**: 'Nunito Sans', sans-serif - **Code**: 'Fira Code', monospace (if needed) **Font Sizes:** + ```css text-xs → 0.75rem (12px) text-sm → 0.875rem (14px) @@ -469,11 +510,12 @@ text-3xl → 1.875rem (30px) ``` **Usage:** + ```jsx - + Page Title - + Body text ``` @@ -481,6 +523,7 @@ text-3xl → 1.875rem (30px) ### Spacing System **Tailwind Spacing Scale:** + ```css p-2 → 0.5rem (8px) p-4 → 1rem (16px) @@ -493,42 +536,46 @@ gap-2, gap-4, gap-6, gap-8 (same scale for flex/grid gaps) ### Component Styling Patterns **Cards:** + ```jsx - + {/* Content */} ``` **Buttons:** + ```jsx // Primary - + Primary Action // Secondary - + Secondary Action // Destructive - + Delete ``` **Form Inputs:** + ```jsx - - + + - + ``` **Badges:** + ```jsx // Status badges Active @@ -539,22 +586,38 @@ gap-2, gap-4, gap-6, gap-8 (same scale for flex/grid gaps) ### Icons (Lucide React) **Common Icons:** + ```jsx import { - User, Mail, Phone, MapPin, // User info - Calendar, Clock, CheckCircle, // Status - Edit, Trash2, X, Check, // Actions - Search, Filter, Download, // Utilities - Heart, DollarSign, CreditCard, // Financial - AlertTriangle, Info, HelpCircle // Alerts -} from 'lucide-react'; + User, + Mail, + Phone, + MapPin, // User info + Calendar, + Clock, + CheckCircle, // Status + Edit, + Trash2, + X, + Check, // Actions + Search, + Filter, + Download, // Utilities + Heart, + DollarSign, + CreditCard, // Financial + AlertTriangle, + Info, + HelpCircle, // Alerts +} from "lucide-react"; - +; ``` ### Responsive Design **Breakpoints:** + ```css sm: 640px → @media (min-width: 640px) md: 768px → @media (min-width: 768px) @@ -564,6 +627,7 @@ xl: 1280px → @media (min-width: 1280px) ``` **Mobile-First Approach:** + ```jsx {/* Stacks vertically on mobile, horizontal on tablet+ */} @@ -581,6 +645,7 @@ xl: 1280px → @media (min-width: 1280px) ### Animations **Tailwind Transitions:** + ```jsx Hover me @@ -600,6 +665,7 @@ xl: 1280px → @media (min-width: 1280px) #### 1. Environment Variables Create `.env.production`: + ```bash REACT_APP_BACKEND_URL=https://api.loaf.org REACT_APP_SENTRY_DSN=your-production-sentry-dsn @@ -622,6 +688,7 @@ Build output will be in `/build` directory. ### Option A: Netlify (Recommended) **Via Netlify CLI:** + ```bash # Install Netlify CLI npm install -g netlify-cli @@ -634,6 +701,7 @@ netlify deploy --prod --dir=build ``` **Via Git Integration:** + 1. Push code to GitHub/GitLab 2. Connect repository in Netlify dashboard 3. Configure: @@ -643,9 +711,11 @@ netlify deploy --prod --dir=build 4. Deploy automatically on push **Configure Redirects** (`public/_redirects`): + ``` /* /index.html 200 ``` + This enables client-side routing. ### Option B: Vercel @@ -659,11 +729,10 @@ vercel --prod ``` **Configure** (`vercel.json`): + ```json { - "rewrites": [ - { "source": "/(.*)", "destination": "/index.html" } - ], + "rewrites": [{ "source": "/(.*)", "destination": "/index.html" }], "env": { "REACT_APP_BACKEND_URL": "https://api.loaf.org" } @@ -673,11 +742,13 @@ vercel --prod ### Option C: Traditional Web Server (Nginx) **1. Copy build files to server:** + ```bash scp -r build/* user@server:/var/www/loaf-frontend/ ``` **2. Configure Nginx** (`/etc/nginx/sites-available/loaf-frontend`): + ```nginx server { listen 80; @@ -702,6 +773,7 @@ server { ``` **3. Enable site and restart:** + ```bash sudo ln -s /etc/nginx/sites-available/loaf-frontend /etc/nginx/sites-enabled/ sudo nginx -t @@ -709,6 +781,7 @@ sudo systemctl restart nginx ``` **4. SSL Certificate:** + ```bash sudo certbot --nginx -d app.loaf.org ``` @@ -716,12 +789,14 @@ sudo certbot --nginx -d app.loaf.org ### Option D: AWS S3 + CloudFront **1. Create S3 bucket:** + ```bash aws s3 mb s3://loaf-frontend aws s3 sync build/ s3://loaf-frontend --delete ``` **2. Configure bucket for static hosting:** + ```bash aws s3 website s3://loaf-frontend --index-document index.html --error-document index.html ``` @@ -731,25 +806,31 @@ aws s3 website s3://loaf-frontend --index-document index.html --error-document i ### Performance Optimization **1. Code Splitting:** + ```jsx // Lazy load routes -import { lazy, Suspense } from 'react'; +import { lazy, Suspense } from "react"; -const AdminDonations = lazy(() => import('./pages/admin/AdminDonations')); +const AdminDonations = lazy(() => import("./pages/admin/AdminDonations")); -Loading...}> - - -} /> +Loading...}> + + + } +/>; ``` **2. Image Optimization:** + - Use WebP format when possible - Compress images before upload - Use lazy loading: `loading="lazy"` **3. Bundle Analysis:** + ```bash # Install analyzer yarn add --dev webpack-bundle-analyzer @@ -762,6 +843,7 @@ npx webpack-bundle-analyzer build/static/js/*.js ### CI/CD Pipeline **GitHub Actions** (`.github/workflows/deploy.yml`): + ```yaml name: Deploy Frontend @@ -776,7 +858,7 @@ jobs: - uses: actions/checkout@v3 - uses: actions/setup-node@v3 with: - node-version: '18' + node-version: "18" - run: yarn install - run: yarn build env: @@ -800,6 +882,7 @@ jobs: **Error:** `Module not found: Can't resolve 'package-name'` **Solution:** + ```bash # Clear node_modules and reinstall rm -rf node_modules yarn.lock @@ -815,6 +898,7 @@ npm install **Error:** `Access to XMLHttpRequest blocked by CORS policy` **Solution:** + - Backend must include frontend URL in CORS_ORIGINS - Check REACT_APP_BACKEND_URL is correct - Backend: `CORS_ORIGINS=http://localhost:3000,https://app.loaf.org` @@ -824,6 +908,7 @@ npm install **Error:** API returns 401 after some time **Solution:** + - JWT token expired (default 30 minutes) - User needs to log in again - Check token is being sent in Authorization header @@ -833,6 +918,7 @@ npm install **Error:** `process.env.REACT_APP_BACKEND_URL is undefined` **Solution:** + - Ensure variable name starts with `REACT_APP_` - Restart development server after changing .env - Don't commit .env to git (use .env.example) @@ -842,6 +928,7 @@ npm install **Error:** `npm run build` fails with memory error **Solution:** + ```bash # Increase Node memory limit NODE_OPTIONS=--max_old_space_size=4096 yarn build @@ -852,6 +939,7 @@ NODE_OPTIONS=--max_old_space_size=4096 yarn build **Error:** Refresh on /dashboard returns 404 **Solution:** + - Configure server to redirect all routes to index.html - Netlify: Add `_redirects` file - Nginx: Use `try_files $uri /index.html` @@ -862,6 +950,7 @@ NODE_OPTIONS=--max_old_space_size=4096 yarn build **Error:** Profile photos return 404 **Solution:** + - Check R2_PUBLIC_URL is correct in backend .env - Verify Cloudflare R2 bucket is public - Check CORS settings in R2 bucket @@ -870,16 +959,16 @@ NODE_OPTIONS=--max_old_space_size=4096 yarn build ```jsx // Add to any component for debugging -console.log('Component rendered', { user, props }); +console.log("Component rendered", { user, props }); // Check API responses api.interceptors.response.use( - response => { - console.log('API Response:', response); + (response) => { + console.log("API Response:", response); return response; }, - error => { - console.error('API Error:', error.response); + (error) => { + console.error("API Error:", error.response); return Promise.reject(error); } ); diff --git a/src/components/AddToCalendarButton.js b/src/components/AddToCalendarButton.js index 8726c4e..cf28aad 100644 --- a/src/components/AddToCalendarButton.js +++ b/src/components/AddToCalendarButton.js @@ -128,7 +128,7 @@ export default function AddToCalendarButton({ {event && ( <> {/* Single Event Export Options */} - + Add This Event @@ -137,7 +137,7 @@ export default function AddToCalendarButton({ className="cursor-pointer" > - + Google Calendar @@ -147,7 +147,7 @@ export default function AddToCalendarButton({ className="cursor-pointer" > - + Outlook Web @@ -157,7 +157,7 @@ export default function AddToCalendarButton({ className="cursor-pointer" > - + Apple Calendar @@ -177,7 +177,7 @@ export default function AddToCalendarButton({ {showSubscribe && ( <> {/* Subscription Options */} - + Calendar Feeds @@ -187,7 +187,7 @@ export default function AddToCalendarButton({ > Subscribe to My Events - + Auto-syncs your RSVP'd events @@ -198,7 +198,7 @@ export default function AddToCalendarButton({ > Download All Events - + One-time import of all upcoming events @@ -206,7 +206,7 @@ export default function AddToCalendarButton({ )} {!event && !showSubscribe && ( - + No event selected )} diff --git a/src/components/AdminSidebar.js b/src/components/AdminSidebar.js index ffc10bf..35ec567 100644 --- a/src/components/AdminSidebar.js +++ b/src/components/AdminSidebar.js @@ -211,10 +211,10 @@ const AdminSidebar = ({ isOpen, onToggle, isMobile }) => { className={` flex items-center gap-3 px-4 py-3 rounded-lg transition-all relative ${item.disabled - ? 'opacity-50 cursor-not-allowed text-[#664fa3]' + ? 'opacity-50 cursor-not-allowed text-muted-foreground' : active - ? 'bg-[#ff9e77]/10 text-[#ff9e77]' - : 'text-[#422268] hover:bg-[#DDD8EB]/20' + ? 'bg-accent/10 text-accent' + : 'text-primary hover:bg-chart-6/20' } `} > @@ -229,7 +229,7 @@ const AdminSidebar = ({ isOpen, onToggle, isMobile }) => { <> {item.name} {item.disabled && ( - + Soon )} @@ -265,7 +265,7 @@ const AdminSidebar = ({ isOpen, onToggle, isMobile }) => { {/* Sidebar */}
Secondary Text
+
Body text