1180 lines
26 KiB
Markdown
1180 lines
26 KiB
Markdown
# LOAF Membership Platform - Frontend
|
||
|
||
React 19-based frontend application for the LOAF (LGBT Organization and Friends) membership management platform.
|
||
|
||
## Table of Contents
|
||
|
||
- [Setup & Installation](#setup--installation)
|
||
- [Architecture & Code Structure](#architecture--code-structure)
|
||
- [Design System](#design-system)
|
||
- [Deployment Guide](#deployment-guide)
|
||
- [Troubleshooting](#troubleshooting)
|
||
|
||
---
|
||
|
||
## 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
|
||
|
||
```bash
|
||
cd frontend
|
||
|
||
# Using Yarn (recommended)
|
||
yarn install
|
||
|
||
# Or using npm
|
||
npm install
|
||
```
|
||
|
||
**Key Dependencies:**
|
||
|
||
- `react@19.0.0` - UI library
|
||
- `react-router-dom@7.5.1` - Routing
|
||
- `axios@1.8.4` - HTTP client
|
||
- `react-hook-form@7.56.2` - Form handling
|
||
- `zod@3.24.4` - Schema validation
|
||
- `@radix-ui/*` - UI components (45+ components)
|
||
- `tailwindcss@3.4.17` - CSS framework
|
||
- `lucide-react@0.507.0` - Icons
|
||
- `sonner@1.7.4` - Toast notifications
|
||
|
||
### 2. Environment Configuration
|
||
|
||
Create `.env` file in the frontend directory:
|
||
|
||
```bash
|
||
# 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
|
||
|
||
```bash
|
||
# 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
|
||
|
||
```bash
|
||
# Create production build
|
||
yarn build
|
||
|
||
# Or with npm
|
||
npm build
|
||
```
|
||
|
||
Build output will be in `/build` directory.
|
||
|
||
### 5. Run Tests
|
||
|
||
```bash
|
||
# 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:**
|
||
|
||
```jsx
|
||
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:**
|
||
|
||
```jsx
|
||
<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:**
|
||
|
||
```jsx
|
||
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:**
|
||
|
||
```jsx
|
||
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:**
|
||
|
||
```jsx
|
||
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:
|
||
1. Basic Info (email, password, name)
|
||
2. Personal Details (phone, address, DOB)
|
||
3. Partner Information (optional)
|
||
4. 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:**
|
||
|
||
```jsx
|
||
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-var(--purple-lavender) 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:**
|
||
|
||
```css
|
||
--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:**
|
||
|
||
```jsx
|
||
<div className="bg-var(--purple-ink) text-white">
|
||
<h1 className="text-var(--orange-light)">Accent Text</h1>
|
||
<p className="text-var(--purple-lavender)">Secondary Text</p>
|
||
</div>
|
||
```
|
||
|
||
### 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)
|
||
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:**
|
||
|
||
```jsx
|
||
<h1 className="text-3xl font-semibold text-var(--purple-ink)" style={{ fontFamily: "'Inter', sans-serif" }}>
|
||
Page Title
|
||
</h1>
|
||
<p className="text-base text-var(--purple-lavender)" style={{ fontFamily: "'Nunito Sans', sans-serif" }}>
|
||
Body text
|
||
</p>
|
||
```
|
||
|
||
### Spacing System
|
||
|
||
**Tailwind Spacing Scale:**
|
||
|
||
```css
|
||
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:**
|
||
|
||
```jsx
|
||
<Card className="p-6 bg-background rounded-2xl border-2 border-var(--neutral-800)">
|
||
{/* Content */}
|
||
</Card>
|
||
```
|
||
|
||
**Buttons:**
|
||
|
||
```jsx
|
||
// Primary
|
||
<Button className="bg-var(--purple-lavender) text-white hover:bg-var(--purple-ink) rounded-full px-6 py-3">
|
||
Primary Action
|
||
</Button>
|
||
|
||
// Secondary
|
||
<Button variant="outline" className="border-2 border-var(--neutral-800) text-var(--purple-lavender) hover:bg-var(--lavender-300) rounded-full">
|
||
Secondary Action
|
||
</Button>
|
||
|
||
// Destructive
|
||
<Button className="bg-red-600 text-white hover:bg-red-700 rounded-full">
|
||
Delete
|
||
</Button>
|
||
```
|
||
|
||
**Form Inputs:**
|
||
|
||
```jsx
|
||
<Input className="rounded-xl border-2 border-var(--neutral-800) focus:border-var(--purple-lavender)" />
|
||
<Textarea className="rounded-xl border-2 border-var(--neutral-800) min-h-[120px]" />
|
||
<Select>
|
||
<SelectTrigger className="rounded-xl border-2 border-var(--neutral-800)">
|
||
<SelectValue placeholder="Select..." />
|
||
</SelectTrigger>
|
||
</Select>
|
||
```
|
||
|
||
**Badges:**
|
||
|
||
```jsx
|
||
// 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:**
|
||
|
||
```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 className="h-5 w-5 text-var(--purple-lavender)" />;
|
||
```
|
||
|
||
### Responsive Design
|
||
|
||
**Breakpoints:**
|
||
|
||
```css
|
||
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:**
|
||
|
||
```jsx
|
||
<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:**
|
||
|
||
```jsx
|
||
<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`:
|
||
|
||
```bash
|
||
REACT_APP_BACKEND_URL=https://api.loaf.org
|
||
REACT_APP_SENTRY_DSN=your-production-sentry-dsn
|
||
```
|
||
|
||
#### 2. Build Application
|
||
|
||
```bash
|
||
# 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:**
|
||
|
||
```bash
|
||
# Install Netlify CLI
|
||
npm install -g netlify-cli
|
||
|
||
# Login
|
||
netlify login
|
||
|
||
# Deploy
|
||
netlify deploy --prod --dir=build
|
||
```
|
||
|
||
**Via Git Integration:**
|
||
|
||
1. Push code to GitHub/GitLab
|
||
2. Connect repository in Netlify dashboard
|
||
3. Configure:
|
||
- Build command: `yarn build`
|
||
- Publish directory: `build`
|
||
- Environment variables (from .env.production)
|
||
4. Deploy automatically on push
|
||
|
||
**Configure Redirects** (`public/_redirects`):
|
||
|
||
```
|
||
/* /index.html 200
|
||
```
|
||
|
||
This enables client-side routing.
|
||
|
||
### Option B: Vercel
|
||
|
||
```bash
|
||
# Install Vercel CLI
|
||
npm install -g vercel
|
||
|
||
# Deploy
|
||
vercel --prod
|
||
```
|
||
|
||
**Configure** (`vercel.json`):
|
||
|
||
```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:**
|
||
|
||
```bash
|
||
scp -r build/* user@server:/var/www/loaf-frontend/
|
||
```
|
||
|
||
**2. Configure Nginx** (`/etc/nginx/sites-available/loaf-frontend`):
|
||
|
||
```nginx
|
||
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:**
|
||
|
||
```bash
|
||
sudo ln -s /etc/nginx/sites-available/loaf-frontend /etc/nginx/sites-enabled/
|
||
sudo nginx -t
|
||
sudo systemctl restart nginx
|
||
```
|
||
|
||
**4. SSL Certificate:**
|
||
|
||
```bash
|
||
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
|
||
```
|
||
|
||
**3. Create CloudFront distribution** pointing to S3 bucket with custom error pages (all errors → /index.html).
|
||
|
||
### Performance Optimization
|
||
|
||
**1. Code Splitting:**
|
||
|
||
```jsx
|
||
// 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:**
|
||
|
||
```bash
|
||
# 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`):
|
||
|
||
```yaml
|
||
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:**
|
||
|
||
```bash
|
||
# 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:**
|
||
|
||
```bash
|
||
# 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 `_redirects` file
|
||
- 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
|
||
|
||
```jsx
|
||
// 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.md` and `PRD.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
|
||
|
||
**Backend API**
|
||
|
||
**Auth**
|
||
- POST `/api/auth/register`
|
||
- GET `/api/auth/verify-email`
|
||
- POST `/api/auth/resend-verification-email`
|
||
- POST `/api/auth/login`
|
||
- POST `/api/auth/forgot-password`
|
||
- POST `/api/auth/reset-password`
|
||
- GET `/api/auth/me`
|
||
- GET `/api/auth/permissions`
|
||
|
||
**Users**
|
||
- PUT `/api/users/change-password`
|
||
- GET `/api/users/profile`
|
||
- PUT `/api/users/profile`
|
||
|
||
**Members**
|
||
- GET `/api/members/directory` (defined twice in code)
|
||
- GET `/api/members/directory/{user_id}`
|
||
- GET `/api/members/profile`
|
||
- PUT `/api/members/profile`
|
||
- POST `/api/members/profile/upload-photo`
|
||
- DELETE `/api/members/profile/delete-photo`
|
||
- GET `/api/members/calendar/events`
|
||
- GET `/api/members/gallery`
|
||
- GET `/api/members/event-activity`
|
||
|
||
**Events (public/member)**
|
||
- GET `/api/events`
|
||
- GET `/api/events/{event_id}`
|
||
- GET `/api/events/{event_id}/gallery`
|
||
- POST `/api/events/{event_id}/rsvp`
|
||
- GET `/api/events/{event_id}/download.ics`
|
||
|
||
**Calendars**
|
||
- GET `/api/calendars/subscribe.ics`
|
||
- GET `/api/calendars/all-events.ics`
|
||
|
||
**Newsletters (public)**
|
||
- GET `/api/newsletters`
|
||
- GET `/api/newsletters/years`
|
||
|
||
**Financials (public)**
|
||
- GET `/api/financials`
|
||
|
||
**Bylaws (public)**
|
||
- GET `/api/bylaws/current`
|
||
- GET `/api/bylaws/history`
|
||
|
||
**Config/Diagnostics**
|
||
- GET `/api/config`
|
||
- GET `/api/config/limits`
|
||
- GET `/api/diagnostics/cors`
|
||
|
||
**Invitations**
|
||
- GET `/api/invitations/verify/{token}`
|
||
- POST `/api/invitations/accept`
|
||
|
||
**Subscriptions**
|
||
- GET `/api/subscriptions/plans`
|
||
- POST `/api/subscriptions/checkout`
|
||
|
||
**Donations**
|
||
- POST `/api/donations/checkout`
|
||
|
||
**Contact**
|
||
- POST `/api/contact`
|
||
|
||
**Admin – Calendar**
|
||
- POST `/api/admin/calendar/sync/{event_id}`
|
||
- DELETE `/api/admin/calendar/unsync/{event_id}`
|
||
|
||
**Admin – Event Gallery**
|
||
- POST `/api/admin/events/{event_id}/gallery`
|
||
- DELETE `/api/admin/event-gallery/{image_id}`
|
||
- PUT `/api/admin/event-gallery/{image_id}`
|
||
|
||
**Admin – Events**
|
||
- POST `/api/admin/events`
|
||
- PUT `/api/admin/events/{event_id}`
|
||
- GET `/api/admin/events/{event_id}`
|
||
- GET `/api/admin/events/{event_id}/rsvps`
|
||
- PUT `/api/admin/events/{event_id}/attendance`
|
||
- GET `/api/admin/events`
|
||
- DELETE `/api/admin/events/{event_id}`
|
||
|
||
**Admin – Storage**
|
||
- GET `/api/admin/storage/usage`
|
||
- GET `/api/admin/storage/breakdown`
|
||
|
||
**Admin – Users & Invitations**
|
||
- GET `/api/admin/users`
|
||
- GET `/api/admin/users/invitations`
|
||
- GET `/api/admin/users/export`
|
||
- GET `/api/admin/users/{user_id}`
|
||
- PUT `/api/admin/users/{user_id}`
|
||
- PUT `/api/admin/users/{user_id}/validate`
|
||
- PUT `/api/admin/users/{user_id}/status`
|
||
- POST `/api/admin/users/{user_id}/reject`
|
||
- POST `/api/admin/users/{user_id}/activate-payment`
|
||
- PUT `/api/admin/users/{user_id}/reset-password`
|
||
- PUT `/api/admin/users/{user_id}/role`
|
||
- POST `/api/admin/users/{user_id}/resend-verification`
|
||
- POST `/api/admin/users/{user_id}/upload-photo`
|
||
- DELETE `/api/admin/users/{user_id}/delete-photo`
|
||
- POST `/api/admin/users/create`
|
||
- POST `/api/admin/users/invite`
|
||
- POST `/api/admin/users/invitations/{invitation_id}/resend`
|
||
- DELETE `/api/admin/users/invitations/{invitation_id}`
|
||
- POST `/api/admin/users/import`
|
||
- GET `/api/admin/users/import-jobs`
|
||
- GET `/api/admin/users/import-jobs/{job_id}`
|
||
|
||
**Admin – Imports**
|
||
- POST `/api/admin/import/upload-csv`
|
||
- GET `/api/admin/import/{job_id}/preview`
|
||
- POST `/api/admin/import/{job_id}/execute`
|
||
- POST `/api/admin/import/{job_id}/rollback`
|
||
- GET `/api/admin/import/{job_id}/status`
|
||
- GET `/api/admin/import/{job_id}/errors/download`
|
||
|
||
**Admin – Subscriptions**
|
||
- GET `/api/admin/subscriptions/plans`
|
||
- GET `/api/admin/subscriptions/plans/{plan_id}`
|
||
- POST `/api/admin/subscriptions/plans`
|
||
- PUT `/api/admin/subscriptions/plans/{plan_id}`
|
||
- DELETE `/api/admin/subscriptions/plans/{plan_id}`
|
||
- GET `/api/admin/subscriptions`
|
||
- GET `/api/admin/subscriptions/stats`
|
||
- PUT `/api/admin/subscriptions/{subscription_id}`
|
||
- POST `/api/admin/subscriptions/{subscription_id}/cancel`
|
||
- GET `/api/admin/subscriptions/export`
|
||
|
||
**Admin – Donations**
|
||
- GET `/api/admin/donations`
|
||
- GET `/api/admin/donations/stats`
|
||
- GET `/api/admin/donations/export`
|
||
|
||
**Admin – Newsletters**
|
||
- POST `/api/admin/newsletters`
|
||
- PUT `/api/admin/newsletters/{newsletter_id}`
|
||
- DELETE `/api/admin/newsletters/{newsletter_id}`
|
||
|
||
**Admin – Financials**
|
||
- POST `/api/admin/financials`
|
||
- PUT `/api/admin/financials/{report_id}`
|
||
- DELETE `/api/admin/financials/{report_id}`
|
||
|
||
**Admin – Bylaws**
|
||
- POST `/api/admin/bylaws`
|
||
- PUT `/api/admin/bylaws/{bylaws_id}`
|
||
- DELETE `/api/admin/bylaws/{bylaws_id}`
|
||
|
||
**Admin – Roles**
|
||
- GET `/api/admin/roles`
|
||
- GET `/api/admin/roles/assignable`
|
||
- POST `/api/admin/roles`
|
||
- GET `/api/admin/roles/{role_id}`
|
||
- PUT `/api/admin/roles/{role_id}`
|
||
- DELETE `/api/admin/roles/{role_id}`
|
||
- GET `/api/admin/roles/{role_id}/permissions`
|
||
- PUT `/api/admin/roles/{role_id}/permissions`
|
||
|
||
**Admin – Permissions**
|
||
- GET `/api/admin/permissions`
|
||
- GET `/api/admin/permissions/modules`
|
||
- GET `/api/admin/permissions/roles/{role}`
|
||
- PUT `/api/admin/permissions/roles/{role}`
|
||
- POST `/api/admin/permissions/seed`
|
||
|
||
**Admin – Stripe Settings**
|
||
- GET `/api/admin/settings/stripe/status`
|
||
- POST `/api/admin/settings/stripe/test-connection`
|
||
- PUT `/api/admin/settings/stripe`
|
||
|
||
**Webhooks**
|
||
- POST `/api/webhooks/stripe` |