LOAF Membership Platform - Frontend
React 19-based frontend application for the LOAF (LGBT Organization and Friends) membership management platform.
Table of Contents
Setup & Installation
Prerequisites
- Node.js: 18.0 or higher
- Yarn: 1.22+ (or npm 8+)
- Backend API: Running on http://localhost:8000
1. Install Dependencies
cd frontend
# Using Yarn (recommended)
yarn install
# Or using npm
npm install
Key Dependencies:
react@19.0.0- UI libraryreact-router-dom@7.5.1- Routingaxios@1.8.4- HTTP clientreact-hook-form@7.56.2- Form handlingzod@3.24.4- Schema validation@radix-ui/*- UI components (45+ components)tailwindcss@3.4.17- CSS frameworklucide-react@0.507.0- Iconssonner@1.7.4- Toast notifications
2. Environment Configuration
Create .env file in the frontend directory:
# Backend API URL
REACT_APP_BACKEND_URL=http://localhost:8000
# Optional: Analytics, Sentry, etc.
# REACT_APP_SENTRY_DSN=your-sentry-dsn
# REACT_APP_GA_TRACKING_ID=UA-XXXXXXXXX-X
Important:
- All environment variables must start with
REACT_APP_ - Restart development server after changing
.env
3. Start Development Server
# Start development server
yarn start
# Or with npm
npm start
Development server will be available at:
- Frontend: http://localhost:3000
- Auto-reloads on file changes
4. Build for Production
# Create production build
yarn build
# Or with npm
npm build
Build output will be in /build directory.
5. Run Tests
# Run tests in watch mode
yarn test
# Run tests with coverage
yarn test --coverage
# Or with npm
npm test
Architecture & Code Structure
Project Structure
frontend/
├── public/ # Static assets
│ ├── loaf-logo.png # LOAF logo for admin sidebar
│ ├── hero-loaf.png # Landing page hero image
│ └── index.html # HTML template
├── src/
│ ├── pages/ # Page components (20+ pages)
│ │ ├── Landing.js # Public landing page
│ │ ├── Register.js # 4-step registration (388 lines)
│ │ ├── Login.js # Authentication
│ │ ├── Dashboard.js # Member dashboard
│ │ ├── Profile.js # User profile with photo upload
│ │ ├── Events.js # Event listing
│ │ ├── EventDetails.js # Event details + RSVP
│ │ └── admin/ # Admin pages
│ │ ├── AdminDashboard.js
│ │ ├── AdminUsers.js
│ │ ├── AdminValidations.js # Approve/reject workflow
│ │ ├── AdminEvents.js
│ │ ├── AdminSubscriptions.js # With CSV export
│ │ └── AdminDonations.js # Donation tracking
│ ├── components/ # Reusable components
│ │ ├── Navbar.js # Main navigation
│ │ ├── AdminSidebar.js # Admin sidebar with logo
│ │ ├── RejectionDialog.js # Rejection workflow dialog
│ │ └── ui/ # 45+ Radix UI components
│ │ ├── button.js
│ │ ├── dialog.js
│ │ ├── input.js
│ │ ├── select.js
│ │ └── ... (40+ more)
│ ├── context/
│ │ └── AuthContext.js # Global auth state
│ ├── hooks/
│ │ └── use-toast.js # Toast notification hook
│ ├── utils/
│ │ └── api.js # Axios instance with JWT interceptor
│ ├── App.js # Main routing setup
│ ├── index.js # React entry point
│ └── index.css # Global styles + Tailwind
├── craco.config.js # Craco configuration
├── tailwind.config.js # Tailwind CSS configuration
├── package.json # Dependencies
└── .env # Environment variables (not in git)
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 |
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:
import { useAuth } from '../context/AuthContext';
function MyComponent() {
const { user, login, logout, isAuthenticated } = useAuth();
// user contains: id, email, first_name, last_name, role, status
}
Protected Routes
PrivateRoute Wrapper:
<Route path="/dashboard" element={
<PrivateRoute>
<Dashboard />
</PrivateRoute>
} />
// Admin-only route
<Route path="/admin" element={
<PrivateRoute adminOnly>
<AdminDashboard />
</PrivateRoute>
} />
API Integration
Axios Instance (src/utils/api.js):
- Automatic JWT token injection
- Request/response interceptors
- Error handling
- Base URL configuration
Usage:
import api from '../utils/api';
// GET request
const response = await api.get('/members/profile');
// POST request with data
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' }
});
Form Handling
React Hook Form + Zod Pattern:
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')
});
function LoginForm() {
const { register, handleSubmit, formState: { errors } } = useForm({
resolver: zodResolver(schema)
});
const onSubmit = async (data) => {
try {
await api.post('/auth/login', data);
toast.success('Login successful!');
} catch (error) {
toast.error(error.response?.data?.detail || 'Login failed');
}
};
return (
<form onSubmit={handleSubmit(onSubmit)}>
<Input {...register('email')} />
{errors.email && <span>{errors.email.message}</span>}
{/* ... */}
</form>
);
}
Toast Notifications
Using Sonner:
import { toast } from 'sonner';
// Success
toast.success('Profile updated successfully!');
// Error
toast.error('Failed to upload photo');
// Custom
toast('Processing...', {
description: 'Please wait while we process your request'
});
Page Components
Public Pages
Landing.js
- Hero section with LOAF branding
- Feature highlights
- Call-to-action buttons
Register.js (388 lines)
- 4-step registration wizard:
- Basic Info (email, password, name)
- Personal Details (phone, address, DOB)
- Partner Information (optional)
- Lead Sources & Referral
- Form validation with Zod
- Progress indicator
- Email verification trigger
Login.js
- Email/password authentication
- JWT token storage
- Remember me functionality
- Redirect to dashboard on success
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
- Partner information
- 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
- Google Maps integration (optional)
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
- Cover image upload
- RSVP tracking
- Attendance marking
Component Library (Radix UI)
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:
import { Button } from './components/ui/button';
import { Dialog, DialogContent, DialogTitle } from './components/ui/dialog';
import { Select, SelectTrigger, SelectContent, SelectItem } from './components/ui/select';
<Button className="bg-[#664fa3] text-white">
Click me
</Button>
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
- EVENTS & MEDIA - Events, Gallery
- DOCUMENTATION - Newsletters, Financial Reports, Bylaws
- Permissions (Superadmin only)
Design System
Color Palette
LOAF Brand Colors:
--primary: #422268 /* Deep Purple - Primary brand */
--secondary: #664fa3 /* Light Purple - Secondary elements */
--accent: #ff9e77 /* Coral - Accents and highlights */
--muted: #ddd8eb /* Light Purple Gray - Borders */
--background: #f9f5ff /* Very Light Purple - Backgrounds */
--foreground: #422268 /* Text color */
Usage in Tailwind:
<div className="bg-[#422268] text-white">
<h1 className="text-[#ff9e77]">Accent Text</h1>
<p className="text-[#664fa3]">Secondary Text</p>
</div>
Typography
Font Families:
- Headings: 'Inter', sans-serif
- Body: 'Nunito Sans', sans-serif
- Code: 'Fira Code', monospace (if needed)
Font Sizes:
text-xs → 0.75rem (12px)
text-sm → 0.875rem (14px)
text-base → 1rem (16px)
text-lg → 1.125rem (18px)
text-xl → 1.25rem (20px)
text-2xl → 1.5rem (24px)
text-3xl → 1.875rem (30px)
Usage:
<h1 className="text-3xl font-semibold text-[#422268]" style={{ fontFamily: "'Inter', sans-serif" }}>
Page Title
</h1>
<p className="text-base text-[#664fa3]" style={{ fontFamily: "'Nunito Sans', sans-serif" }}>
Body text
</p>
Spacing System
Tailwind Spacing Scale:
p-2 → 0.5rem (8px)
p-4 → 1rem (16px)
p-6 → 1.5rem (24px)
p-8 → 2rem (32px)
gap-2, gap-4, gap-6, gap-8 (same scale for flex/grid gaps)
Component Styling Patterns
Cards:
<Card className="p-6 bg-white rounded-2xl border-2 border-[#ddd8eb]">
{/* Content */}
</Card>
Buttons:
// Primary
<Button className="bg-[#664fa3] text-white hover:bg-[#422268] rounded-full px-6 py-3">
Primary Action
</Button>
// Secondary
<Button variant="outline" className="border-2 border-[#ddd8eb] text-[#664fa3] hover:bg-[#f1eef9] rounded-full">
Secondary Action
</Button>
// Destructive
<Button className="bg-red-600 text-white hover:bg-red-700 rounded-full">
Delete
</Button>
Form Inputs:
<Input className="rounded-xl border-2 border-[#ddd8eb] focus:border-[#664fa3]" />
<Textarea className="rounded-xl border-2 border-[#ddd8eb] min-h-[120px]" />
<Select>
<SelectTrigger className="rounded-xl border-2 border-[#ddd8eb]">
<SelectValue placeholder="Select..." />
</SelectTrigger>
</Select>
Badges:
// Status badges
<Badge className="bg-green-100 text-green-800">Active</Badge>
<Badge className="bg-yellow-100 text-yellow-800">Pending</Badge>
<Badge className="bg-red-100 text-red-800">Rejected</Badge>
Icons (Lucide React)
Common Icons:
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 className="h-5 w-5 text-[#664fa3]" />
Responsive Design
Breakpoints:
sm: 640px → @media (min-width: 640px)
md: 768px → @media (min-width: 768px)
lg: 1024px → @media (min-width: 1024px)
xl: 1280px → @media (min-width: 1280px)
2xl: 1536px → @media (min-width: 1536px)
Mobile-First Approach:
<div className="flex flex-col md:flex-row gap-4">
{/* Stacks vertically on mobile, horizontal on tablet+ */}
</div>
<div className="hidden md:block">
{/* Only shows on tablet+ */}
</div>
<div className="md:hidden">
{/* Only shows on mobile */}
</div>
Animations
Tailwind Transitions:
<Button className="transition-all duration-200 hover:scale-105">
Hover me
</Button>
<div className="animate-fade-in">
{/* Fades in on mount */}
</div>
Deployment Guide
Production Build
1. Environment Variables
Create .env.production:
REACT_APP_BACKEND_URL=https://api.loaf.org
REACT_APP_SENTRY_DSN=your-production-sentry-dsn
2. Build Application
# Install dependencies
yarn install
# Create production build
yarn build
Build output will be in /build directory.
3. Deployment Options
Option A: Netlify (Recommended)
Via Netlify CLI:
# Install Netlify CLI
npm install -g netlify-cli
# Login
netlify login
# Deploy
netlify deploy --prod --dir=build
Via Git Integration:
- Push code to GitHub/GitLab
- Connect repository in Netlify dashboard
- Configure:
- Build command:
yarn build - Publish directory:
build - Environment variables (from .env.production)
- Build command:
- Deploy automatically on push
Configure Redirects (public/_redirects):
/* /index.html 200
This enables client-side routing.
Option B: Vercel
# Install Vercel CLI
npm install -g vercel
# Deploy
vercel --prod
Configure (vercel.json):
{
"rewrites": [
{ "source": "/(.*)", "destination": "/index.html" }
],
"env": {
"REACT_APP_BACKEND_URL": "https://api.loaf.org"
}
}
Option C: Traditional Web Server (Nginx)
1. Copy build files to server:
scp -r build/* user@server:/var/www/loaf-frontend/
2. Configure Nginx (/etc/nginx/sites-available/loaf-frontend):
server {
listen 80;
server_name app.loaf.org;
root /var/www/loaf-frontend;
index index.html;
location / {
try_files $uri /index.html;
}
# Cache static assets
location /static/ {
expires 1y;
add_header Cache-Control "public, immutable";
}
# Gzip compression
gzip on;
gzip_types text/plain text/css application/json application/javascript text/xml application/xml text/javascript;
}
3. Enable site and restart:
sudo ln -s /etc/nginx/sites-available/loaf-frontend /etc/nginx/sites-enabled/
sudo nginx -t
sudo systemctl restart nginx
4. SSL Certificate:
sudo certbot --nginx -d app.loaf.org
Option D: AWS S3 + CloudFront
1. Create S3 bucket:
aws s3 mb s3://loaf-frontend
aws s3 sync build/ s3://loaf-frontend --delete
2. Configure bucket for static hosting:
aws s3 website s3://loaf-frontend --index-document index.html --error-document index.html
3. Create CloudFront distribution pointing to S3 bucket with custom error pages (all errors → /index.html).
Performance Optimization
1. Code Splitting:
// Lazy load routes
import { lazy, Suspense } from 'react';
const AdminDonations = lazy(() => import('./pages/admin/AdminDonations'));
<Route path="/admin/donations" element={
<Suspense fallback={<div>Loading...</div>}>
<AdminDonations />
</Suspense>
} />
2. Image Optimization:
- Use WebP format when possible
- Compress images before upload
- Use lazy loading:
loading="lazy"
3. Bundle Analysis:
# Install analyzer
yarn add --dev webpack-bundle-analyzer
# Analyze bundle
yarn build
npx webpack-bundle-analyzer build/static/js/*.js
CI/CD Pipeline
GitHub Actions (.github/workflows/deploy.yml):
name: Deploy Frontend
on:
push:
branches: [main]
jobs:
deploy:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v3
- uses: actions/setup-node@v3
with:
node-version: '18'
- run: yarn install
- run: yarn build
env:
REACT_APP_BACKEND_URL: ${{ secrets.BACKEND_URL }}
- uses: netlify/actions/cli@master
with:
args: deploy --dir=build --prod
env:
NETLIFY_AUTH_TOKEN: ${{ secrets.NETLIFY_TOKEN }}
NETLIFY_SITE_ID: ${{ secrets.NETLIFY_SITE_ID }}
Troubleshooting
Common Issues
1. Module Not Found Errors
Error: Module not found: Can't resolve 'package-name'
Solution:
# Clear node_modules and reinstall
rm -rf node_modules yarn.lock
yarn install
# Or with npm
rm -rf node_modules package-lock.json
npm install
2. CORS Errors
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
3. 401 Unauthorized
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
4. Environment Variables Not Working
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)
5. Build Fails
Error: npm run build fails with memory error
Solution:
# Increase Node memory limit
NODE_OPTIONS=--max_old_space_size=4096 yarn build
6. Routing Not Working in Production
Error: Refresh on /dashboard returns 404
Solution:
- Configure server to redirect all routes to index.html
- Netlify: Add
_redirectsfile - Nginx: Use
try_files $uri /index.html - Vercel: Add vercel.json with rewrites
7. Images Not Loading
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
Debug Mode
// Add to any component for debugging
console.log('Component rendered', { user, props });
// Check API responses
api.interceptors.response.use(
response => {
console.log('API Response:', response);
return response;
},
error => {
console.error('API Error:', error.response);
return Promise.reject(error);
}
);
Getting Help
- Project Context: See
CLAUDE.mdandPRD.md - API Docs: http://localhost:8000/docs (backend Swagger)
- React Docs: https://react.dev/
- Tailwind CSS: https://tailwindcss.com/docs
- Radix UI: https://www.radix-ui.com/primitives
Additional Resources
- React Documentation: https://react.dev/
- React Router: https://reactrouter.com/
- Tailwind CSS: https://tailwindcss.com/
- Radix UI: https://www.radix-ui.com/
- React Hook Form: https://react-hook-form.com/
- Zod: https://zod.dev/
- Lucide Icons: https://lucide.dev/
Last Updated: December 18, 2024 Version: 1.0.0 Maintainer: LOAF Development Team