diff --git a/frontend/.dockerignore b/frontend/.dockerignore deleted file mode 100644 index 1ffb8558713c063271998e708a6b46b1cfed2e02..0000000000000000000000000000000000000000 --- a/frontend/.dockerignore +++ /dev/null @@ -1,75 +0,0 @@ -# Docker ignore file for LocalLend Frontend -# This prevents unnecessary files from being copied to Docker build context - -# Dependencies -node_modules/ -npm-debug.log* -yarn-debug.log* -yarn-error.log* - -# Development files -.env.local -.env.development.local -.env.test.local -.env.production.local - -# Build outputs (these will be generated inside the container) -dist/ -build/ - -# IDE and editor files -.vscode/ -.idea/ -*.swp -*.swo -*~ - -# OS generated files -.DS_Store -.DS_Store? -._* -.Spotlight-V100 -.Trashes -ehthumbs.db -Thumbs.db - -# Git -.git/ -.gitignore - -# Documentation (optional - comment out if you want docs in container) -README.md -README-LOCALLEND.md -SETUP.md -*.md - -# Development configuration -.eslintrc* -.prettierrc* - -# Testing -coverage/ -.nyc_output/ - -# Docker files themselves (to avoid recursion) -Dockerfile -docker-compose*.yml -.dockerignore - -# Logs -logs/ -*.log - -# Runtime data -pids/ -*.pid -*.seed -*.pid.lock - -# Coverage directory used by tools like istanbul -coverage/ -.nyc_output/ - -# Dependencies lock files (package-lock.json will be copied separately) -package-lock.json -yarn.lock \ No newline at end of file diff --git a/frontend/.env b/frontend/.env deleted file mode 100644 index 5b0cb82ddb500a29f5389f865efa5dcf5b7be778..0000000000000000000000000000000000000000 --- a/frontend/.env +++ /dev/null @@ -1,15 +0,0 @@ -# Environment Variables for LocalLend Frontend - -# API Configuration -VITE_API_BASE_URL=http://localhost:8080 - -# Image/Upload Configuration - DEPRECATED -# Images are now stored in Cloudinary (cloud storage) and return full URLs -# This local upload endpoint is no longer used -# VITE_IMAGE_BASE_URL=http://localhost:8080/uploads - -# App Configuration -VITE_APP_TITLE=LocalLend - -# Development Configuration -VITE_NODE_ENV=development \ No newline at end of file diff --git a/frontend/.env.example b/frontend/.env.example deleted file mode 100644 index cfb1291930b300f85ceb2046dab460530db5712c..0000000000000000000000000000000000000000 --- a/frontend/.env.example +++ /dev/null @@ -1,18 +0,0 @@ -# Environment Variables for LocalLend Frontend - -# API Configuration -VITE_API_BASE_URL=http://localhost:8080 - -# Image/Upload Configuration - DEPRECATED -# Images are now stored in Cloudinary (cloud storage) and return full URLs -# This local upload endpoint is no longer used -# VITE_IMAGE_BASE_URL=http://localhost:8080/uploads - -# App Configuration -VITE_APP_TITLE=LocalLend - -# Development Settings -VITE_DEV_MODE=true - -# Note: All environment variables in Vite must be prefixed with VITE_ -# These variables will be available in the application via import.meta.env \ No newline at end of file diff --git a/frontend/.env.production b/frontend/.env.production deleted file mode 100644 index 1520434af2c8af0e4534d92ee0a71ff351fb85fc..0000000000000000000000000000000000000000 --- a/frontend/.env.production +++ /dev/null @@ -1,18 +0,0 @@ -# Production Environment Variables for Docker Container - -# API Configuration - Update this to point to your backend service -VITE_API_BASE_URL=http://localhost:8080 - -# Image/Upload Configuration - DEPRECATED -# Images are now stored in Cloudinary (cloud storage) and return full URLs -# This local upload endpoint is no longer used -# VITE_IMAGE_BASE_URL=http://localhost:8080/uploads - -# App Configuration -VITE_APP_TITLE=LocalLend - -# Production Settings -NODE_ENV=production - -# Note: In Docker Compose, you might want to use service names: -# VITE_API_BASE_URL=http://locallend-backend:8080 \ No newline at end of file diff --git a/frontend/.gitignore b/frontend/.gitignore deleted file mode 100644 index eef46fea3a7bfbce429c732c9d1bee72a5145cd7..0000000000000000000000000000000000000000 --- a/frontend/.gitignore +++ /dev/null @@ -1,30 +0,0 @@ -# Logs -logs -*.log -npm-debug.log* -yarn-debug.log* -yarn-error.log* -pnpm-debug.log* -lerna-debug.log* - -node_modules -dist -dist-ssr -*.local - -# Environment files -.env -.env.local -.env.*.local - - -# Editor directories and files -.vscode/* -!.vscode/extensions.json -.idea -.DS_Store -*.suo -*.ntvs* -*.njsproj -*.sln -*.sw? diff --git a/frontend/DOCKER-SUMMARY.md b/frontend/DOCKER-SUMMARY.md deleted file mode 100644 index 675637477eaad812aa006935cc029e85d904dfe5..0000000000000000000000000000000000000000 --- a/frontend/DOCKER-SUMMARY.md +++ /dev/null @@ -1,119 +0,0 @@ -# ๐Ÿณ LocalLend Frontend - Docker Setup Complete! - -## ๐Ÿ“‹ What's Been Created - -Your LocalLend frontend is now fully containerized with a complete Docker setup: - -### ๐Ÿ”ง Docker Configuration Files - -- **`Dockerfile`** - Production build with Nginx serving -- **`Dockerfile.dev`** - Development build with hot reload -- **`docker-compose.yml`** - Production orchestration -- **`docker-compose.dev.yml`** - Development orchestration -- **`nginx.conf`** - Nginx configuration with React Router support -- **`.dockerignore`** - Optimized build context -- **`.env.production`** - Production environment variables -- **`healthcheck.sh`** - Container health monitoring - -### ๐Ÿš€ Quick Commands - -```bash -# Development (with hot reload) -npm run docker:dev - -# Production build and run -npm run docker:compose:build - -# View development logs -npm run docker:dev:logs - -# Stop all containers -npm run docker:stop -``` - -## ๐Ÿ— Architecture - -### Production Container -- **Multi-stage build**: Node.js build โ†’ Nginx serve -- **Size optimized**: ~50MB final image -- **Performance**: Gzip compression, asset caching -- **Security**: Security headers, non-root user -- **Health checks**: Automatic container monitoring - -### Development Container -- **Hot reload**: Live code changes -- **Volume mounting**: Source code sync -- **Port mapping**: 5173 (Vite dev server) -- **Fast rebuilds**: Cached node_modules - -## ๐ŸŒ Usage Options - -### Option 1: Development with Docker -```bash -# Start development container with hot reload -npm run docker:dev - -# Access at: http://localhost:5173 -# Code changes will auto-reload -``` - -### Option 2: Production with Docker -```bash -# Build and run production container -npm run docker:compose:build - -# Access at: http://localhost:3000 -# Served by Nginx with optimizations -``` - -### Option 3: Backend Integration -Update `.env.production` for your backend: -```env -# Local backend -VITE_API_BASE_URL=http://localhost:8080 - -# Docker backend (same network) -VITE_API_BASE_URL=http://locallend-backend:8080 -``` - -## ๐Ÿ”— Full Stack Docker (Optional) - -To run backend + frontend + database together, uncomment the backend services in `docker-compose.yml`: - -```bash -# Edit docker-compose.yml and uncomment backend/mongodb -# Then run: -docker-compose up --build -d - -# Services available: -# Frontend: http://localhost:3000 -# Backend: http://localhost:8080 -# MongoDB: localhost:27017 -``` - -## โœ… Benefits of This Docker Setup - -1. **Consistent Environment**: Same runtime everywhere -2. **Easy Deployment**: Single container deployment -3. **Development Parity**: Dev/prod environment matching -4. **Scalability**: Ready for orchestration (Kubernetes, etc.) -5. **Isolation**: No dependency conflicts -6. **Performance**: Optimized for production serving -7. **Monitoring**: Built-in health checks - -## ๐Ÿ“š Documentation - -- **`DOCKER.md`** - Complete Docker setup guide -- **`README-LOCALLEND.md`** - Full project documentation -- **`SETUP.md`** - Installation troubleshooting - ---- - -## ๐Ÿš€ Next Steps - -1. **Start Development**: `npm run docker:dev` -2. **Configure Backend**: Update API URLs in environment files -3. **Test Integration**: Verify frontend โ†’ backend communication -4. **Deploy**: Use production Docker setup for deployment - -Your LocalLend frontend is now production-ready with Docker! ๐ŸŽ‰ \ No newline at end of file diff --git a/frontend/DOCKER.md b/frontend/DOCKER.md deleted file mode 100644 index f981ffd293d19bc3c329a9392d98cc9a8c69d26f..0000000000000000000000000000000000000000 --- a/frontend/DOCKER.md +++ /dev/null @@ -1,219 +0,0 @@ -# LocalLend Frontend - Docker Setup Guide - -## ๐Ÿณ Docker Containerization - -This guide explains how to run the LocalLend frontend application using Docker. - -## ๐Ÿ“‹ Prerequisites - -- **Docker Desktop** installed and running -- **Docker Compose** (included with Docker Desktop) -- **Backend API** running (either locally or in Docker) - -## ๐Ÿš€ Quick Start with Docker - -### Option 1: Using Docker Compose (Recommended) - -```bash -# Build and start the container -npm run docker:compose:build - -# Or use docker-compose directly -docker-compose up --build -d -``` - -The frontend will be available at: `http://localhost:3000` - -### Option 2: Using Docker directly - -```bash -# Build the Docker image -npm run docker:build - -# Run the container -npm run docker:run - -# Or use docker commands directly -docker build -t locallend-frontend . -docker run -p 3000:80 locallend-frontend -``` - -## ๐Ÿ”ง Docker Configuration Files - -### Dockerfile (Multi-stage build) -- **Stage 1 (Builder):** Installs dependencies and builds the React app -- **Stage 2 (Production):** Serves the built app using Nginx - -### docker-compose.yml -- Orchestrates the frontend container -- Includes optional backend and MongoDB services (commented out) -- Sets up networking between services - -### nginx.conf -- Handles client-side routing for React Router -- Serves static assets with proper caching -- Includes security headers and gzip compression - -## ๐ŸŒ Environment Configuration - -### Production Environment (.env.production) -```env -VITE_API_BASE_URL=http://localhost:8080 -VITE_IMAGE_BASE_URL=http://localhost:8080/uploads -VITE_APP_TITLE=LocalLend -``` - -### Docker Compose Environment -If using Docker Compose with backend services, update the API URL: -```env -VITE_API_BASE_URL=http://locallend-backend:8080 -``` - -## ๐Ÿ“ฆ Container Details - -- **Base Image:** node:18-alpine (for building), nginx:alpine (for serving) -- **Port:** 80 inside container, mapped to 3000 on host -- **Size:** Optimized multi-stage build (~50MB final image) -- **Restart Policy:** unless-stopped - -## ๐Ÿ›  Docker Commands - -```bash -# Build and run with compose -docker-compose up --build -d - -# View logs -docker-compose logs -f locallend-frontend - -# Stop containers -docker-compose down - -# Rebuild only frontend -docker-compose build locallend-frontend - -# Access container shell (for debugging) -docker exec -it locallend-frontend_locallend-frontend_1 sh - -# View Nginx config inside container -docker exec locallend-frontend_locallend-frontend_1 cat /etc/nginx/conf.d/default.conf -``` - -## ๐Ÿ”— Backend Integration - -### Option 1: Backend running locally -```env -# In .env.production -VITE_API_BASE_URL=http://localhost:8080 -``` - -### Option 2: Backend in Docker (same network) -```env -# In .env.production -VITE_API_BASE_URL=http://locallend-backend:8080 -``` - -### Option 3: Nginx proxy (recommended for production) -Uncomment the API proxy section in `nginx.conf`: -```nginx -location /api/ { - proxy_pass http://backend:8080; - proxy_set_header Host $host; - proxy_set_header X-Real-IP $remote_addr; - proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for; - proxy_set_header X-Forwarded-Proto $scheme; -} -``` - -## ๐Ÿ— Full Stack Docker Setup - -To run the entire LocalLend application with Docker: - -1. **Uncomment backend services** in `docker-compose.yml` -2. **Build/obtain backend Docker image** -3. **Update environment variables** for service communication -4. **Run the full stack:** - ```bash - docker-compose up --build -d - ``` - -Services will be available at: -- Frontend: `http://localhost:3000` -- Backend API: `http://localhost:8080` -- MongoDB: `localhost:27017` - -## ๐Ÿ” Troubleshooting - -### Build Issues -```bash -# Clear Docker cache -docker system prune -a - -# Build without cache -docker-compose build --no-cache - -# Check build logs -docker-compose logs locallend-frontend -``` - -### Network Issues -```bash -# Check container networks -docker network ls - -# Inspect network -docker network inspect locallend-frontend_locallend-network -``` - -### Container Access -```bash -# Check running containers -docker ps - -# Access container logs -docker logs - -# Shell into container -docker exec -it sh -``` - -## ๐Ÿ“Š Performance Optimization - -The Docker setup includes: -- **Multi-stage builds** to minimize image size -- **Nginx gzip compression** for faster loading -- **Static asset caching** with proper headers -- **Production build optimization** via Vite - -## ๐Ÿ”’ Security Features - -- Non-root user in container -- Security headers (X-Frame-Options, X-Content-Type-Options, etc.) -- Minimal attack surface with Alpine Linux -- No sensitive data in container layers - ---- - -## ๐Ÿš€ Production Deployment - -For production deployment: - -1. **Update environment variables** for production URLs -2. **Configure HTTPS** (add SSL certificates to nginx.conf) -3. **Set up proper logging** and monitoring -4. **Use Docker secrets** for sensitive data -5. **Implement health checks** in docker-compose.yml - -Example production docker-compose: -```yaml -services: - locallend-frontend: - image: locallend-frontend:latest - restart: always - environment: - - VITE_API_BASE_URL=https://api.yourdomain.com - healthcheck: - test: ["CMD", "wget", "--quiet", "--tries=1", "--spider", "http://localhost:80/"] - interval: 30s - timeout: 10s - retries: 3 -``` \ No newline at end of file diff --git a/frontend/Dockerfile b/frontend/Dockerfile deleted file mode 100644 index c6d92b1922d0f67608ffbdeb628139f32c05bdab..0000000000000000000000000000000000000000 --- a/frontend/Dockerfile +++ /dev/null @@ -1,44 +0,0 @@ -# Use the official Node.js runtime as the base image -FROM node:18-alpine AS builder - -# Set the working directory in the container -WORKDIR /app - -# Copy package.json and package-lock.json (if available) -COPY package*.json ./ - -# Install dependencies (using npm install since package-lock.json may not exist) -RUN npm install --legacy-peer-deps - -# Copy the rest of the application code -COPY . . - -# Accept build argument for API URL -ARG VITE_API_BASE_URL=http://localhost:8080 -ENV VITE_API_BASE_URL=$VITE_API_BASE_URL - -# Build the application for production -RUN npm run build - -# Production stage - Use Nginx to serve the built app -FROM nginx:alpine AS production - -# Copy the built app from the builder stage -COPY --from=builder /app/dist /usr/share/nginx/html - -# Copy custom nginx configuration -COPY nginx.conf /etc/nginx/conf.d/default.conf - -# Copy health check script -COPY healthcheck.sh /usr/local/bin/healthcheck.sh -RUN chmod +x /usr/local/bin/healthcheck.sh - -# Add health check -HEALTHCHECK --interval=30s --timeout=10s --start-period=5s --retries=3 \ - CMD /usr/local/bin/healthcheck.sh - -# Expose port 80 -EXPOSE 80 - -# Start Nginx -CMD ["nginx", "-g", "daemon off;"] \ No newline at end of file diff --git a/frontend/Dockerfile.dev b/frontend/Dockerfile.dev deleted file mode 100644 index 7ad7ba5a9bc8b3936183141515f37906f5db6d0e..0000000000000000000000000000000000000000 --- a/frontend/Dockerfile.dev +++ /dev/null @@ -1,21 +0,0 @@ -# Development Dockerfile - Hot reload enabled -FROM node:20-alpine - -# Set working directory -WORKDIR /app - -# Install patch-package globally to fix the missing dependency -RUN npm install -g patch-package - -# Install dependencies -COPY package*.json ./ -RUN npm install --legacy-peer-deps - -# Copy source code -COPY . . - -# Expose development port -EXPOSE 5173 - -# Start development server -CMD ["npm", "run", "dev", "--", "--host"] \ No newline at end of file diff --git a/frontend/ENDPOINT_FIX_SUMMARY.md b/frontend/ENDPOINT_FIX_SUMMARY.md deleted file mode 100644 index 25808ab2c33d26349d023cec546655868515b4b0..0000000000000000000000000000000000000000 --- a/frontend/ENDPOINT_FIX_SUMMARY.md +++ /dev/null @@ -1,184 +0,0 @@ -# FIXED: Item Status Toggle - Wrong Endpoint Issue - -## ๐ŸŽฏ **Root Cause Identified** - -**The frontend was calling the wrong API endpoint!** - -### **Frontend was calling:** -``` -PATCH /api/items/{id}/status -``` - -### **Backend actually has:** -``` -PATCH /api/items/{id}/toggle-availability -PATCH /api/items/{id}/availability -``` - -This mismatch caused the "No static resource" error because the backend doesn't have a `/status` endpoint. - ---- - -## ๐Ÿ”ง **Frontend Fixes Applied** - -### **1. Updated DashboardPage.tsx** -```typescript -// OLD (wrong endpoint) -await fetch(`/api/items/${itemId}/status`, { - method: 'PATCH', - body: JSON.stringify({ status: newStatus }) -}); - -// NEW (correct endpoints with fallback) -// Try toggle endpoint first -let response = await fetch(`/api/items/${itemId}/toggle-availability`, { - method: 'PATCH' // No body needed for toggle -}); - -// If 404, try availability endpoint -if (!response.ok && response.status === 404) { - response = await fetch(`/api/items/${itemId}/availability`, { - method: 'PATCH', - body: JSON.stringify({ status: newStatus }) - }); -} -``` - -### **2. Updated itemService.ts** -```typescript -// OLD (wrong endpoint) -updateItemStatus: async (itemId: string, status: ItemStatus, userId: string) => { - return await api.patch(`/api/items/${itemId}/status`, { status }, { - headers: addUserIdHeader(userId) - }); -} - -// NEW (correct endpoints with fallback) -updateItemStatus: async (itemId: string, status: ItemStatus, userId: string) => { - try { - // Try toggle endpoint first (simpler) - return await api.patch(`/api/items/${itemId}/toggle-availability`, null, { - headers: addUserIdHeader(userId) - }); - } catch (error) { - // Fallback to availability endpoint - if (error?.response?.status === 404) { - return await api.patch(`/api/items/${itemId}/availability`, { status }, { - headers: addUserIdHeader(userId) - }); - } - throw error; - } -} -``` - ---- - -## ๐Ÿงช **Backend Testing** - -### **Test Toggle Endpoint:** -```bash -curl -X PATCH http://localhost:8080/api/items/test123/toggle-availability \ - -H "Content-Type: application/json" \ - -H "Authorization: Bearer {TOKEN}" \ - -H "X-User-Id: {USER_ID}" -``` - -### **Test Availability Endpoint:** -```bash -curl -X PATCH http://localhost:8080/api/items/test123/availability \ - -H "Content-Type: application/json" \ - -H "Authorization: Bearer {TOKEN}" \ - -H "X-User-Id: {USER_ID}" \ - -d '{"status":"UNAVAILABLE"}' -``` - -### **Expected Responses:** -- โœ… **200 OK** with updated item JSON = Working correctly -- โœ… **401 Unauthorized** = Auth required (expected without valid token) -- โœ… **403 Forbidden** = Permission issue (expected if not owner) -- โŒ **404 Not Found** = Endpoint doesn't exist -- โŒ **"No static resource"** = API server not running - ---- - -## ๐ŸŽฏ **What Should Happen Now** - -### **With Toggle Endpoint (`/toggle-availability`):** -1. User clicks "Make Unavailable" -2. Frontend calls PATCH `/api/items/{id}/toggle-availability` -3. Backend toggles status (AVAILABLE โ†’ UNAVAILABLE) -4. Returns updated item with new status -5. Frontend updates UI to show new status - -### **With Availability Endpoint (`/availability`):** -1. User clicks "Make Unavailable" -2. Frontend calls PATCH `/api/items/{id}/availability` with `{"status":"UNAVAILABLE"}` -3. Backend sets specific status -4. Returns updated item with new status -5. Frontend updates UI to show new status - ---- - -## ๐Ÿ” **Debugging Steps** - -### **1. Test which endpoint your backend has:** -```bash -# Test toggle -curl -X PATCH http://localhost:8080/api/items/test/toggle-availability - -# Test availability -curl -X PATCH http://localhost:8080/api/items/test/availability -``` - -### **2. Check frontend logs:** -- Look for "๐Ÿ“ก Used endpoint:" in console -- Should show either `/toggle-availability` or `/availability` -- Should NOT show `/status` anymore - -### **3. Verify success:** -- Click "Make Unavailable" button -- Should see "โœ… Item status updated successfully" -- Button should change to "Make Available" -- Status badge should update color - ---- - -## ๐Ÿ’ก **Backend Colleague Notes** - -### **If using toggle endpoint:** -```java -@PatchMapping("/api/items/{itemId}/toggle-availability") -public ResponseEntity toggleAvailability(@PathVariable String itemId, @RequestHeader("X-User-Id") String userId) { - // Toggle between AVAILABLE โ†” UNAVAILABLE - // Don't change if status is BORROWED -} -``` - -### **If using availability endpoint:** -```java -@PatchMapping("/api/items/{itemId}/availability") -public ResponseEntity setAvailability(@PathVariable String itemId, @RequestBody Map body, @RequestHeader("X-User-Id") String userId) { - String status = body.get("status"); - // Set to specific status (AVAILABLE/UNAVAILABLE) -} -``` - -### **Both should:** -- โœ… Validate user owns the item -- โœ… Return updated item JSON -- โœ… Handle authentication/authorization -- โœ… Prevent changing BORROWED items - ---- - -## ๐ŸŽ‰ **Expected Result** - -The "Make Unavailable" button should now work correctly because: - -1. โœ… **Correct API endpoints** - Using actual backend endpoints -2. โœ… **Fallback logic** - Tries both endpoints to find which works -3. โœ… **Proper error handling** - Shows specific error messages -4. โœ… **Enhanced debugging** - Logs which endpoint was used - -**The static resource error should be gone!** The frontend now calls the endpoints that actually exist on your backend. \ No newline at end of file diff --git a/frontend/FIELD_NAME_FIX.md b/frontend/FIELD_NAME_FIX.md deleted file mode 100644 index bc84ab2ec88d341bc0761bbbaf94869f731e47c5..0000000000000000000000000000000000000000 --- a/frontend/FIELD_NAME_FIX.md +++ /dev/null @@ -1,58 +0,0 @@ -# FIXED: Field Name Error - "isAvailable" Required - -## ๐ŸŽฏ **Backend Error Message** -``` -"isAvailable field is required" -``` - -**Issue:** Backend expects field name `isAvailable` (not `available`) - ---- - -## ๐Ÿ”ง **Quick Fix Applied** - -### **BEFORE (Wrong field name):** -```json -{ - "available": true // โŒ Wrong field name -} -``` - -### **AFTER (Correct field name):** -```json -{ - "isAvailable": true // โœ… Correct field name -} -``` - ---- - -## ๐Ÿ“ **Files Updated:** - -### **1. itemService.ts** -```typescript -// Changed from: -{ available: isAvailable } - -// To: -{ isAvailable } // ES6 shorthand for { isAvailable: isAvailable } -``` - -### **2. DashboardPage.tsx** -```typescript -// Changed from: -body: JSON.stringify({ available: isAvailable }) - -// To: -body: JSON.stringify({ isAvailable }) -``` - ---- - -## โœ… **Should Work Now:** - -- **"Make Unavailable"** โ†’ Sends `{"isAvailable": false}` -- **"Make Available"** โ†’ Sends `{"isAvailable": true}` -- **No more field validation errors** from backend - -The backend should now accept the requests with the correct `isAvailable` field name! \ No newline at end of file diff --git a/frontend/FRONTEND_ENV_FIXED.md b/frontend/FRONTEND_ENV_FIXED.md deleted file mode 100644 index a0303b69d768b4c6d9a1850af2a6c642c21de2be..0000000000000000000000000000000000000000 --- a/frontend/FRONTEND_ENV_FIXED.md +++ /dev/null @@ -1,260 +0,0 @@ -# Frontend Environment Variables - Fixed! ๐Ÿ”ง - -## ๐Ÿšจ Issue Identified - -The frontend `.env` file had a **conflict** with the new Cloudinary implementation: - -1. โŒ `.env` was **NOT gitignored** (could leak configuration) -2. โŒ `VITE_IMAGE_BASE_URL=http://localhost:8080/uploads` pointed to non-existent local upload endpoint -3. โŒ Suggested local file storage (incompatible with Cloudinary cloud storage) - -## โœ… Solution Applied - -### 1. Added `.env` to `.gitignore` - -**File: `locallend-frontend/.gitignore`** - -Added: -```gitignore -# Environment files -.env -.env.local -.env.*.local -``` - -Now the `.env` file won't be accidentally committed! - -### 2. Deprecated `VITE_IMAGE_BASE_URL` - -**Files Updated:** -- `locallend-frontend/.env` -- `locallend-frontend/.env.example` -- `locallend-frontend/.env.production` - -**Before:** -```bash -VITE_IMAGE_BASE_URL=http://localhost:8080/uploads -``` - -**After:** -```bash -# Image/Upload Configuration - DEPRECATED -# Images are now stored in Cloudinary (cloud storage) and return full URLs -# This local upload endpoint is no longer used -# VITE_IMAGE_BASE_URL=http://localhost:8080/uploads -``` - -### 3. Updated TypeScript Definitions - -**File: `src/vite-env.d.ts`** - -Made `VITE_IMAGE_BASE_URL` optional: -```typescript -interface ImportMetaEnv { - readonly VITE_API_BASE_URL: string; - readonly VITE_IMAGE_BASE_URL?: string; // DEPRECATED: Images use Cloudinary full URLs - readonly VITE_APP_TITLE: string; -} -``` - -### 4. Enhanced `getImageUrl` Helper - -**File: `src/utils/helpers.ts`** - -Updated to properly handle Cloudinary URLs: - -```typescript -export const getImageUrl = (imagePath: string): string => { - if (!imagePath) return '/placeholder-image.jpg'; - - // If already a full URL (Cloudinary or other), return as-is - if (imagePath.startsWith('http://') || imagePath.startsWith('https://')) { - return imagePath; - } - - // Legacy support for relative paths (backward compatibility) - if (import.meta.env.VITE_IMAGE_BASE_URL) { - return `${import.meta.env.VITE_IMAGE_BASE_URL}${imagePath}`; - } - - return imagePath; -}; -``` - -**Benefits:** -- โœ… Works with Cloudinary full URLs (primary use case) -- โœ… Backward compatible with relative paths -- โœ… Proper fallback to placeholder -- โœ… Well-documented with JSDoc - -## ๐Ÿ“Š Comparison: Local vs Cloudinary Storage - -### Local File Storage (Old Approach - Deprecated) - -``` -Frontend Upload Flow: -1. User selects image -2. Upload to /api/uploads endpoint -3. Server saves to disk: /uploads/image123.jpg -4. Server returns: "/image123.jpg" -5. Frontend combines: VITE_IMAGE_BASE_URL + "/image123.jpg" -6. Result: "http://localhost:8080/uploads/image123.jpg" - -Problems: -โŒ Files stored on server disk -โŒ Not Docker-friendly -โŒ No CDN/optimization -โŒ Manual cleanup needed -โŒ Doesn't scale -``` - -### Cloudinary Storage (New Approach - Implemented) - -``` -Frontend Upload Flow: -1. User selects image -2. Upload to /api/images/upload endpoint -3. Server uploads to Cloudinary cloud -4. Cloudinary returns full URL -5. Server returns: "https://res.cloudinary.com/demo/image/upload/v1234/locallend/items/abc123.jpg" -6. Frontend uses URL directly (no base URL needed) - -Benefits: -โœ… Cloud storage (infinite capacity) -โœ… Works everywhere (Docker, cloud, local) -โœ… CDN delivery (fast worldwide) -โœ… Automatic optimization -โœ… Image transformations -โœ… Scales automatically -โœ… No base URL concatenation needed -``` - -## ๐ŸŽฏ How Images Work Now - -### Backend (Spring Boot) - -1. **Upload Endpoint:** `POST /api/images/upload` - - Accepts `multipart/form-data` with file - - Uploads to Cloudinary - - Returns **full Cloudinary URL** - -2. **Item Creation:** `POST /api/items` - - Accepts array of image URLs - - Stores full URLs in MongoDB - -### Frontend (React) - -1. **Upload (AddItemPage):** - ```typescript - // User selects files - const formData = new FormData(); - formData.append('file', file); - - // Upload to backend - const response = await fetch('/api/images/upload', { - method: 'POST', - body: formData - }); - - const result = await response.json(); - // result.url = "https://res.cloudinary.com/.../image.jpg" - ``` - -2. **Display (DashboardPage, SearchPage, etc):** - ```typescript - // Item has full Cloudinary URLs - {item.name} - // Renders: - ``` - -3. **Using Helper (Optional):** - ```typescript - import { getImageUrl } from '@/utils/helpers'; - - {item.name} - // Helper passes through full URLs unchanged - ``` - -## ๐Ÿ” No More Conflicts! - -### Before (Conflicting Approaches) - -``` -.env says: Use /uploads endpoint -Backend implements: Cloudinary cloud storage -Frontend expects: Relative paths + base URL -Result: CONFLICT! ๐Ÿ˜ฑ -``` - -### After (Unified Approach) - -``` -.env says: Use Cloudinary (deprecated old config) -Backend implements: Cloudinary cloud storage โœ… -Frontend uses: Full Cloudinary URLs โœ… -Result: WORKS PERFECTLY! ๐ŸŽ‰ -``` - -## ๐Ÿ“ What to Do Next - -### For Development: - -1. **Pull latest changes:** - ```bash - git pull origin prishiv_dev - ``` - -2. **The `.env` file is now gitignored**, so you need to: - ```bash - cd locallend-frontend - cp .env.example .env - ``` - -3. **No configuration needed!** - - `VITE_API_BASE_URL` still points to `http://localhost:8080` - - `VITE_IMAGE_BASE_URL` is deprecated (not needed) - - Images work automatically with Cloudinary - -4. **Start frontend:** - ```bash - npm run dev - ``` - -### For Production: - -Update `VITE_API_BASE_URL` to point to your production backend: - -```bash -# .env.production -VITE_API_BASE_URL=https://your-production-api.com -``` - -Images will work automatically since Cloudinary provides full URLs. - -## โœ… Verification Checklist - -- [x] `.env` added to `.gitignore` -- [x] `VITE_IMAGE_BASE_URL` deprecated (commented out) -- [x] TypeScript types updated (optional property) -- [x] `getImageUrl` helper enhanced for Cloudinary -- [x] Backward compatibility maintained -- [x] Documentation updated -- [x] No breaking changes for existing code - -## ๐ŸŽ“ Key Takeaways - -1. **Cloudinary URLs are complete** - No base URL needed -2. **`.env` files should be gitignored** - Prevents config leaks -3. **Helper functions still work** - Pass-through for full URLs -4. **Backward compatible** - Old code won't break -5. **Future-proof** - Can switch storage providers if needed - -## ๐Ÿ“š Related Documentation - -- **Backend:** `ENV_SETUP.md` - Environment variable setup -- **Images:** `IMAGE_UPLOAD_SETUP.md` - Complete image upload guide -- **Quick Start:** `IMAGE_UPLOAD_QUICKSTART.md` - 5-minute setup - ---- - -**No more conflicts!** The frontend and backend now work together seamlessly with Cloudinary. ๐Ÿš€ diff --git a/frontend/FRONTEND_INTEGRATION_GUIDE.txt b/frontend/FRONTEND_INTEGRATION_GUIDE.txt deleted file mode 100644 index 48aa5e498f32a78ccca55213fe03936053386f9a..0000000000000000000000000000000000000000 --- a/frontend/FRONTEND_INTEGRATION_GUIDE.txt +++ /dev/null @@ -1,1510 +0,0 @@ -================================================================================ - LOCALLEND BACKEND API INTEGRATION GUIDE - Frontend Development Reference -================================================================================ - -TABLE OF CONTENTS ------------------ -1. System Overview -2. Authentication & Authorization -3. API Endpoints Reference -4. Data Models & DTOs -5. Frontend Pages/Views Required -6. Common Workflows -7. Error Handling -8. Best Practices - - -================================================================================ -1. SYSTEM OVERVIEW -================================================================================ - -LocalLend is a peer-to-peer item sharing platform where users can: -- List items they own for others to borrow -- Browse and search items available in their community -- Request to borrow items from other users -- Manage bookings (approve, track, complete) -- Rate users and items after completed transactions -- Build trust scores through positive interactions - -TECHNOLOGY STACK: -- Backend: Spring Boot 3.5.6 with MongoDB -- Authentication: JWT (JSON Web Tokens) -- API Style: RESTful JSON APIs -- Security: Spring Security with role-based access - - -================================================================================ -2. AUTHENTICATION & AUTHORIZATION -================================================================================ - -2.1 AUTHENTICATION FLOW ------------------------ -1. User registers via POST /api/auth/register -2. User logs in via POST /api/auth/login โ†’ receives JWT token -3. Include token in all subsequent requests: - Header: Authorization: Bearer - -2.2 REQUIRED HEADERS --------------------- -For ALL authenticated requests: -- Authorization: Bearer - -For item/booking operations (owner/borrower specific): -- Authorization: Bearer -- X-User-Id: - -2.3 PUBLIC ENDPOINTS (No Authentication Required) -------------------------------------------------- -- POST /api/auth/register -- POST /api/auth/login -- GET /api/categories/** -- GET /api/items/** -- GET /api/users/{userId}/public -- GET /api/users/search - -2.4 USER ROLES --------------- -- USER: Standard user (default role) -- ADMIN: Administrative access (future feature) - -All users have the same basic permissions but operations are restricted based on -ownership (e.g., only item owner can delete their items). - - -================================================================================ -3. API ENDPOINTS REFERENCE -================================================================================ - -BASE URL: http://localhost:8080 - ------------------------------------------------------------------------------- -3.1 AUTHENTICATION ENDPOINTS ------------------------------------------------------------------------------- - -POST /api/auth/register ------------------------- -Register a new user account - -Request Body: -{ - "username": "string (3-50 chars, required)", - "name": "string (2-100 chars, required)", - "email": "string (valid email, required)", - "password": "string (min 8 chars, required)", - "phoneNumber": "string (optional)" -} - -Response (200): -{ - "id": "string", - "username": "string", - "name": "string", - "email": "string", - "phoneNumber": "string", - "profileImageUrl": "string", - "role": "USER", - "isActive": true, - "createdDate": "timestamp", - "trustScore": 5.0, - "itemCount": 0, - "memberSince": "string", - "totalItemsShared": 0, - "totalBookings": 0 -} - - -POST /api/auth/login --------------------- -Login and receive JWT token - -Request Body: -{ - "usernameOrEmail": "string (required)", - "password": "string (required)" -} - -Response (200): -{ - "token": "eyJhbGc...", - "type": "Bearer", - "user": { - "id": "string", - "username": "string", - "name": "string", - "email": "string", - "role": "USER", - "trustScore": 5.0 - } -} - - ------------------------------------------------------------------------------- -3.2 USER ENDPOINTS ------------------------------------------------------------------------------- - -GET /api/users/{userId}/public -------------------------------- -Get public user profile (no authentication required) - -Response (200): -{ - "id": "string", - "username": "string", - "name": "string", - "profileImageUrl": "string", - "trustScore": 5.0, - "itemCount": 3, - "memberSince": "Member since November 2025" -} - - -GET /api/users/search?query={searchTerm} ------------------------------------------ -Search users by username or name - -Query Parameters: -- query: string (search term) - -Response (200): -{ - "data": [ - { - "id": "string", - "username": "string", - "name": "string", - "profileImageUrl": "string", - "trustScore": 5.0, - "itemCount": 3 - } - ], - "count": 1 -} - - ------------------------------------------------------------------------------- -3.3 CATEGORY ENDPOINTS ------------------------------------------------------------------------------- - -GET /api/categories -------------------- -Get all categories (public endpoint) - -Query Parameters: -- sort: string (optional, default: "name") - -Response (200): -{ - "data": [ - { - "id": "string", - "name": "string", - "description": "string", - "parent_category_id": "string", - "parent_category_name": "string", - "is_active": true, - "item_count": 5, - "has_subcategories": false, - "created_at": "timestamp", - "updated_at": "timestamp" - } - ], - "success": true, - "count": 2, - "message": "Categories retrieved successfully" -} - - -POST /api/categories --------------------- -Create a new category (authenticated) - -Headers: -- Authorization: Bearer - -Request Body: -{ - "name": "string (required)", - "description": "string (optional)", - "parentCategoryId": "string (optional)" -} - -Response (201): Returns created category object - - -GET /api/categories/{categoryId} ---------------------------------- -Get single category details - -Response (200): Returns category object with subcategories - - -GET /api/categories/search?term={searchTerm} --------------------------------------------- -Search categories by name or description - -Response (200): Returns matching categories - - ------------------------------------------------------------------------------- -3.4 ITEM ENDPOINTS ------------------------------------------------------------------------------- - -GET /api/items --------------- -Get all items with pagination (public endpoint) - -Query Parameters: -- page: integer (default: 0) -- size: integer (default: 10) -- sort: string (default: "createdAt,desc") -- categoryId: string (optional, filter by category) -- ownerId: string (optional, filter by owner) -- condition: string (optional, NEW|EXCELLENT|GOOD|FAIR|POOR) -- status: string (optional, AVAILABLE|UNAVAILABLE|BORROWED) - -Response (200): -{ - "content": [ - { - "id": "string", - "name": "string", - "description": "string", - "condition": "EXCELLENT", - "status": "AVAILABLE", - "deposit": 50.0, - "images": ["url1", "url2"], - "averageRating": 4.5, - "ownerId": "string", - "ownerName": "string", - "categoryId": "string", - "categoryName": "string", - "canBeBorrowed": true, - "createdAt": "timestamp", - "updatedAt": "timestamp" - } - ], - "pageable": {...}, - "totalElements": 6, - "totalPages": 1, - "first": true, - "last": true -} - - -POST /api/items ---------------- -Create a new item listing (authenticated) - -Headers: -- Authorization: Bearer -- X-User-Id: - -Request Body: -{ - "name": "string (required)", - "description": "string (required)", - "categoryId": "string (required)", - "deposit": 0.0, - "images": ["url1", "url2"], - "condition": "EXCELLENT" // NEW|EXCELLENT|GOOD|FAIR|POOR -} - -Response (201): Returns created item object - - -GET /api/items/{itemId} ------------------------ -Get single item details - -Response (200): Returns detailed item object - - -PUT /api/items/{itemId} ------------------------ -Update item details (owner only) - -Headers: -- Authorization: Bearer -- X-User-Id: - -Request Body: -{ - "name": "string", - "description": "string", - "categoryId": "string", - "deposit": 50.0, - "condition": "GOOD" -} - -Response (200): Returns updated item object - - -DELETE /api/items/{itemId} --------------------------- -Delete item (owner only, must have no active bookings) - -Headers: -- Authorization: Bearer -- X-User-Id: - -Response (204): No content on success - - -PATCH /api/items/{itemId}/status ---------------------------------- -Update item availability status (owner only) - -Headers: -- Authorization: Bearer -- X-User-Id: - -Request Body: -{ - "status": "AVAILABLE" // AVAILABLE|UNAVAILABLE|BORROWED -} - -Response (200): Returns updated item object - - -GET /api/items/search?query={searchTerm} ----------------------------------------- -Search items by name or description - -Query Parameters: -- query: string (required) -- categoryId: string (optional) -- condition: string (optional) - -Response (200): Returns matching items - - ------------------------------------------------------------------------------- -3.5 BOOKING ENDPOINTS ------------------------------------------------------------------------------- - -POST /api/bookings ------------------- -Create a new booking request - -Headers: -- Authorization: Bearer -- X-User-Id: - -Request Body: -{ - "itemId": "string (required)", - "startDate": "2025-11-10T10:00:00 (required, future date)", - "endDate": "2025-11-13T10:00:00 (required, after start date)", - "bookingNotes": "string (max 500 chars, optional)", - "depositAmount": 100.0, - "requestedDurationDays": 3, - "acceptTerms": true (required, must be true) -} - -Response (201): -{ - "id": "string", - "itemId": "string", - "itemName": "string", - "borrowerId": "string", - "borrowerName": "string", - "ownerId": "string", - "ownerName": "string", - "status": "PENDING", - "startDate": "timestamp", - "endDate": "timestamp", - "bookingNotes": "string", - "depositAmount": 100.0, - "createdDate": "timestamp", - "isRated": false, - "durationDays": 3, - "statusDescription": "string", - "timeAgo": "string", - "canBeCancelled": true, - "canBeConfirmed": false -} - - -GET /api/bookings/my-bookings ------------------------------ -Get bookings where current user is borrower - -Headers: -- Authorization: Bearer -- X-User-Id: - -Response (200): Array of booking objects - - -GET /api/bookings/my-owned-bookings ------------------------------------ -Get bookings for items owned by current user - -Headers: -- Authorization: Bearer -- X-User-Id: - -Response (200): Array of booking objects - - -GET /api/bookings/pending-approvals ------------------------------------- -Get bookings awaiting owner approval - -Headers: -- Authorization: Bearer -- X-User-Id: - -Response (200): Array of pending booking objects - - -GET /api/bookings/{bookingId} ------------------------------- -Get single booking details - -Headers: -- Authorization: Bearer -- X-User-Id: - -Response (200): Returns booking object - - -PATCH /api/bookings/{bookingId}/approve ----------------------------------------- -Approve a booking request (owner only) - -Headers: -- Authorization: Bearer -- X-User-Id: - -Response (200): Returns updated booking with status CONFIRMED - - -PATCH /api/bookings/{bookingId}/reject ---------------------------------------- -Reject a booking request (owner only) - -Headers: -- Authorization: Bearer -- X-User-Id: - -Request Body (optional): -{ - "rejectionReason": "string (max 500 chars)" -} - -Response (200): Returns updated booking with status REJECTED - - -PATCH /api/bookings/{bookingId}/start --------------------------------------- -Start a confirmed booking (borrower picks up item) - -Headers: -- Authorization: Bearer -- X-User-Id: - -Response (200): Returns updated booking with status ACTIVE - - -PATCH /api/bookings/{bookingId}/complete ------------------------------------------ -Complete an active booking (borrower returns item) - -Headers: -- Authorization: Bearer -- X-User-Id: - -Response (200): Returns updated booking with status COMPLETED - - -PATCH /api/bookings/{bookingId}/cancel ---------------------------------------- -Cancel a booking (borrower only, before it starts) - -Headers: -- Authorization: Bearer -- X-User-Id: - -Request Body (optional): -{ - "cancellationReason": "string (max 500 chars)" -} - -Response (200): Returns updated booking with status CANCELLED - - -BOOKING STATUS FLOW: -PENDING โ†’ CONFIRMED โ†’ ACTIVE โ†’ COMPLETED - โ†“ โ†“ โ†“ -REJECTED CANCELLED CANCELLED - - ------------------------------------------------------------------------------- -3.6 RATING ENDPOINTS ------------------------------------------------------------------------------- - -POST /api/ratings ------------------ -Create a new rating (requires completed booking for user ratings) - -Headers: -- Authorization: Bearer - -Request Body: -{ - "rateeId": "string (user being rated, required)", - "ratingType": "USER_TO_USER|USER_TO_ITEM|BORROWER_TO_OWNER|OWNER_TO_BORROWER", - "ratingValue": 5, // 1-5 (required) - "comment": "string (max 1000 chars, optional)", - "bookingId": "string (required for user ratings)", - "itemId": "string (required for USER_TO_ITEM)", - "isAnonymous": false -} - -Response (201): -{ - "success": true, - "message": "Rating created successfully", - "data": { - "id": "string", - "rater_id": "string", - "rater_name": "string", - "ratee_id": "string", - "ratee_name": "string", - "rating_type": "BORROWER_TO_OWNER", - "rating_value": 5, - "comment": "string", - "booking_id": "string", - "created_at": "timestamp", - "is_anonymous": false - } -} - -RATING VALIDATION (Issue #26): -- Booking must exist and be COMPLETED -- Only borrower can rate the booking -- Each booking can only be rated once -- Trust scores automatically updated after rating - - -GET /api/ratings/can-rate/{bookingId} --------------------------------------- -Check if current user can rate a booking (Issue #26 feature) - -Headers: -- Authorization: Bearer - -Response (200): -{ - "success": true, - "can_rate": true, - "booking_id": "string" -} - -Returns false if: -- Booking doesn't exist -- Booking status is not COMPLETED -- User is not the borrower -- Booking has already been rated - - -GET /api/ratings/user/{userId}/received ----------------------------------------- -Get ratings received by a user - -Headers: -- Authorization: Bearer - -Query Parameters: -- page: integer (default: 0) -- size: integer (default: 10) -- ratingType: string (optional filter) - -Response (200): -{ - "data": [ - { - "id": "string", - "rater_name": "string", - "rating_value": 5, - "comment": "string", - "rating_type": "BORROWER_TO_OWNER", - "created_at": "timestamp" - } - ], - "pageable": {...}, - "totalElements": 10 -} - - -GET /api/ratings/user/{userId}/given -------------------------------------- -Get ratings given by a user - -Headers: -- Authorization: Bearer - -Response (200): Similar structure to received ratings - - -GET /api/ratings/item/{itemId} -------------------------------- -Get all ratings for an item - -Headers: -- Authorization: Bearer - -Response (200): Array of rating objects - - -GET /api/ratings/user/{userId}/statistics ------------------------------------------- -Get rating statistics for a user - -Headers: -- Authorization: Bearer - -Response (200): -{ - "data": { - "user_id": "string", - "total_ratings_received": 5, - "average_rating": 4.6, - "trust_score": 4.8, - "ratings_by_type": { - "BORROWER_TO_OWNER": { - "count": 3, - "average": 4.7 - }, - "OWNER_TO_BORROWER": { - "count": 2, - "average": 4.5 - } - }, - "rating_distribution": { - "5": 3, - "4": 1, - "3": 1 - }, - "recent_ratings": [...] - } -} - - -GET /api/ratings/{ratingId} ----------------------------- -Get single rating details - -Headers: -- Authorization: Bearer - -Response (200): Returns rating object - - -================================================================================ -4. DATA MODELS & DTOs -================================================================================ - -4.1 USER MODEL --------------- -{ - "id": "string", - "username": "string (unique)", - "name": "string", - "email": "string (unique)", - "phoneNumber": "string", - "profileImageUrl": "string", - "role": "USER|ADMIN", - "isActive": boolean, - "trustScore": number (0-5), - "location": { - "type": "Point", - "coordinates": [longitude, latitude] - }, - "address": { - "street": "string", - "city": "string", - "state": "string", - "zipCode": "string", - "country": "string" - }, - "createdDate": "timestamp", - "lastLogin": "timestamp", - "totalRatingsReceived": number, - "ratingCountAsBorrower": number, - "ratingCountAsLender": number, - "averageRatingAsBorrower": number, - "averageRatingAsLender": number -} - - -4.2 ITEM MODEL --------------- -{ - "id": "string", - "name": "string", - "description": "string", - "condition": "NEW|EXCELLENT|GOOD|FAIR|POOR", - "status": "AVAILABLE|UNAVAILABLE|BORROWED", - "deposit": number, - "images": ["url1", "url2"], - "averageRating": number, - "ownerId": "string", - "ownerName": "string", - "categoryId": "string", - "categoryName": "string", - "canBeBorrowed": boolean, - "createdAt": "timestamp", - "updatedAt": "timestamp" -} - - -4.3 BOOKING MODEL ------------------ -{ - "id": "string", - "itemId": "string", - "itemName": "string", - "itemImageUrl": "string", - "borrowerId": "string", - "borrowerName": "string", - "ownerId": "string", - "ownerName": "string", - "status": "PENDING|CONFIRMED|ACTIVE|COMPLETED|REJECTED|CANCELLED", - "startDate": "timestamp", - "endDate": "timestamp", - "actualStartDate": "timestamp", - "actualEndDate": "timestamp", - "bookingNotes": "string", - "ownerNotes": "string", - "depositAmount": number, - "depositPaid": boolean, - "createdDate": "timestamp", - "updatedDate": "timestamp", - "confirmedDate": "timestamp", - "cancelledDate": "timestamp", - "cancellationReason": "string", - "isRated": boolean, - "durationDays": number, - "statusDescription": "string", - "timeAgo": "string", - "daysUntilStart": number, - "daysUntilEnd": number, - "canBeCancelled": boolean, - "canBeConfirmed": boolean, - "canBeActivated": boolean, - "canBeCompleted": boolean, - "isOverdue": boolean, - "requiresDeposit": boolean -} - - -4.4 RATING MODEL ----------------- -{ - "id": "string", - "raterId": "string", - "raterName": "string", - "rateeId": "string", - "rateeName": "string", - "ratingType": "USER_TO_USER|USER_TO_ITEM|BORROWER_TO_OWNER|OWNER_TO_BORROWER", - "ratingValue": number (1-5), - "comment": "string", - "bookingId": "string", - "itemId": "string", - "isAnonymous": boolean, - "createdAt": "timestamp" -} - - -4.5 CATEGORY MODEL ------------------- -{ - "id": "string", - "name": "string", - "description": "string", - "parentCategoryId": "string", - "parentCategoryName": "string", - "isActive": boolean, - "itemCount": number, - "hasSubcategories": boolean, - "createdAt": "timestamp", - "updatedAt": "timestamp" -} - - -================================================================================ -5. FRONTEND PAGES/VIEWS REQUIRED -================================================================================ - -5.1 AUTHENTICATION PAGES -------------------------- -- /register - User Registration Page - โ€ข Form: username, name, email, password, phone number - โ€ข Validation: Real-time field validation - โ€ข Auto-login after successful registration - -- /login - User Login Page - โ€ข Form: username/email, password - โ€ข Store JWT token in localStorage/sessionStorage - โ€ข Redirect to dashboard after login - - -5.2 PUBLIC PAGES ----------------- -- / (Home) - Landing Page - โ€ข Hero section with platform overview - โ€ข Featured/recent items - โ€ข Category browse - โ€ข Search bar - -- /items - Browse All Items - โ€ข Grid/list view of all items - โ€ข Filters: category, condition, availability - โ€ข Pagination - โ€ข Search functionality - -- /items/{itemId} - Item Detail Page - โ€ข Full item details with image gallery - โ€ข Owner information - โ€ข Average rating and reviews - โ€ข "Request to Borrow" button (if authenticated) - โ€ข Calendar showing availability - -- /categories - Browse Categories - โ€ข Category grid with item counts - โ€ข Click to filter items by category - -- /users/{userId} - Public User Profile - โ€ข User name, trust score, member since - โ€ข Items shared by user - โ€ข Public ratings/reviews - - -5.3 AUTHENTICATED USER PAGES ------------------------------ -- /dashboard - User Dashboard - โ€ข Overview of user's items - โ€ข Active bookings (as borrower and owner) - โ€ข Pending approvals - โ€ข Recent activity - โ€ข Trust score display - -- /my-items - Manage My Items - โ€ข List of all user's items - โ€ข Add new item button - โ€ข Edit/delete item actions - โ€ข Item status toggle (available/unavailable) - -- /my-items/new - Add New Item - โ€ข Form: name, description, category, condition, deposit, images - โ€ข Image upload functionality - โ€ข Category selector - -- /my-items/{itemId}/edit - Edit Item - โ€ข Pre-filled form with existing item data - โ€ข Update item details - -- /my-bookings - My Bookings (as Borrower) - โ€ข List of all bookings where user is borrower - โ€ข Filter by status (pending, active, completed) - โ€ข Actions: cancel, start, complete - โ€ข Rate after completion - -- /bookings-received - Bookings Received (as Owner) - โ€ข List of bookings for user's items - โ€ข Pending approvals highlighted - โ€ข Actions: approve, reject - -- /bookings/{bookingId} - Booking Detail Page - โ€ข Full booking information - โ€ข Timeline/status tracker - โ€ข Chat/messaging (future feature) - โ€ข Actions based on role and status - -- /profile - User Profile Settings - โ€ข Edit profile information - โ€ข Change password - โ€ข Upload profile picture - โ€ข Location/address settings - -- /profile/ratings - My Ratings - โ€ข Tabs: Received, Given - โ€ข Rating statistics - โ€ข Filter by rating type - - -5.4 BOOKING FLOW PAGES ------------------------ -- /items/{itemId}/book - Request Booking - โ€ข Date picker for start/end dates - โ€ข Duration calculator - โ€ข Deposit amount display - โ€ข Booking notes textarea - โ€ข Terms and conditions checkbox - โ€ข "Submit Booking Request" button - -- /bookings/{bookingId}/rate - Rate User/Item - โ€ข Star rating selector (1-5) - โ€ข Comment textarea - โ€ข Anonymous option checkbox - โ€ข Pre-check if booking can be rated (use can-rate endpoint) - - -5.5 ADDITIONAL UI COMPONENTS ------------------------------ -- Navigation Bar - โ€ข Logo/Home link - โ€ข Search bar - โ€ข Categories dropdown - โ€ข User menu (if authenticated) - - Dashboard - - My Items - - My Bookings - - Profile - - Logout - โ€ข Login/Register buttons (if not authenticated) - -- Footer - โ€ข About, Terms, Privacy links - โ€ข Social media links - โ€ข Contact information - -- Search Component - โ€ข Auto-complete suggestions - โ€ข Filter options - โ€ข Recent searches - -- Notification System - โ€ข Toast/snackbar for success/error messages - โ€ข Notification bell for: - - New booking requests - - Booking approvals/rejections - - Upcoming booking dates - - Rating reminders - -- Trust Score Badge - โ€ข Visual representation (stars/number) - โ€ข Tooltip with breakdown - -- Status Badges - โ€ข Color-coded status indicators - โ€ข PENDING: Yellow - โ€ข CONFIRMED: Blue - โ€ข ACTIVE: Green - โ€ข COMPLETED: Gray - โ€ข REJECTED/CANCELLED: Red - - -================================================================================ -6. COMMON WORKFLOWS -================================================================================ - -6.1 USER REGISTRATION & LOGIN ------------------------------- -1. User fills registration form -2. POST /api/auth/register with user details -3. Store returned user data and auto-login -4. Redirect to dashboard - -For login: -1. User enters credentials -2. POST /api/auth/login -3. Store JWT token in localStorage -4. Store user data in app state -5. Redirect to dashboard - - -6.2 BROWSING & SEARCHING ITEMS -------------------------------- -1. GET /api/items with pagination/filters -2. Display items in grid/list view -3. For search: GET /api/items/search?query={term} -4. Click item โ†’ Navigate to /items/{itemId} -5. GET /api/items/{itemId} for full details - - -6.3 CREATING AN ITEM LISTING ------------------------------ -1. Navigate to /my-items/new -2. Fill item details form -3. Upload images (store URLs) -4. POST /api/items with Authorization and X-User-Id headers -5. On success, redirect to /my-items -6. Show success notification - - -6.4 BOOKING REQUEST FLOW (Complete Lifecycle) ----------------------------------------------- -BORROWER SIDE: -1. Browse items, click "Request to Borrow" -2. Fill booking form (dates, notes) -3. POST /api/bookings โ†’ Status: PENDING -4. Wait for owner approval -5. When approved (CONFIRMED), receive notification -6. When ready to pick up, click "Start Booking" -7. PATCH /api/bookings/{id}/start โ†’ Status: ACTIVE -8. Use item during booking period -9. When returning, click "Complete Booking" -10. PATCH /api/bookings/{id}/complete โ†’ Status: COMPLETED -11. System prompts to rate owner -12. Check if can rate: GET /api/ratings/can-rate/{bookingId} -13. If can_rate: true, show rating form -14. POST /api/ratings with booking details -15. Booking marked as rated (isRated: true) - -OWNER SIDE: -1. Receive notification of new booking request -2. View pending bookings in dashboard -3. GET /api/bookings/pending-approvals -4. Click booking to view details -5. Review borrower profile, trust score -6. Decide to approve or reject -7. PATCH /api/bookings/{id}/approve โ†’ Status: CONFIRMED - OR - PATCH /api/bookings/{id}/reject โ†’ Status: REJECTED -8. Track booking status through lifecycle -9. After completion, optionally rate borrower - - -6.5 RATING AFTER BOOKING -------------------------- -1. Booking completed -2. Call GET /api/ratings/can-rate/{bookingId} -3. If response.can_rate === true: - - Show rating button/form - - User selects rating (1-5 stars) - - User writes optional comment - - POST /api/ratings with: - โ€ข rateeId: owner's user ID - โ€ข ratingType: BORROWER_TO_OWNER - โ€ข ratingValue: 1-5 - โ€ข comment: optional text - โ€ข bookingId: booking ID -4. On success: - - Show success message - - Update booking UI (show "Rated" badge) - - Refresh user's trust score -5. If response.can_rate === false: - - Hide rating button - - Show "Already Rated" badge - - -6.6 UPDATING ITEM STATUS -------------------------- -1. Navigate to /my-items -2. Toggle item availability switch -3. PATCH /api/items/{itemId}/status -4. Headers: Authorization + X-User-Id -5. Body: { "status": "AVAILABLE" | "UNAVAILABLE" } -6. Update UI to reflect new status - - -================================================================================ -7. ERROR HANDLING -================================================================================ - -7.1 HTTP STATUS CODES ----------------------- -- 200 OK: Successful GET/PUT/PATCH -- 201 Created: Successful POST -- 204 No Content: Successful DELETE -- 400 Bad Request: Validation errors, invalid data -- 401 Unauthorized: Missing or invalid JWT token -- 403 Forbidden: Insufficient permissions -- 404 Not Found: Resource doesn't exist -- 409 Conflict: Duplicate resource (username/email exists) -- 500 Internal Server Error: Server-side error - - -7.2 ERROR RESPONSE FORMAT --------------------------- -{ - "status": 400, - "message": "Validation failed", - "timestamp": "2025-11-08T10:00:00", - "details": "Username already exists", - "error_code": "VALIDATION_ERROR" -} - - -7.3 COMMON ERROR SCENARIOS & HANDLING --------------------------------------- -401 Unauthorized: -- JWT token expired or invalid -- Action: Redirect to login, clear stored token -- Show message: "Session expired, please login again" - -403 Forbidden: -- User trying to modify someone else's resource -- Action: Show error message, don't allow action -- Example: Trying to delete another user's item - -400 Bad Request (Booking): -- "Cannot transition from CONFIRMED to COMPLETED" -- Action: Show current status, available actions -- Guide user through correct status flow - -400 Bad Request (Rating): -- "Required request header 'X-User-Id' is not present" -- Action: Ensure X-User-Id header is included in request - -409 Conflict: -- "Username already exists" -- Action: Highlight field, suggest alternatives -- Real-time validation during registration - -Issue #26 Specific Errors: -- "Booking must be COMPLETED to rate" - โ†’ Show booking status, explain rating requirements -- "Only borrower can rate this booking" - โ†’ Hide rating button if user is not borrower -- "Booking has already been rated" - โ†’ Show "Already Rated" badge, hide rating form - - -7.4 FRONTEND ERROR HANDLING BEST PRACTICES -------------------------------------------- -1. Always wrap API calls in try-catch blocks -2. Check response status before parsing JSON -3. Display user-friendly error messages -4. Log errors for debugging (console.error) -5. Provide actionable guidance (e.g., "Check your internet connection") -6. Use loading states during API calls -7. Implement retry logic for transient failures -8. Validate forms client-side before API calls - - -================================================================================ -8. BEST PRACTICES -================================================================================ - -8.1 AUTHENTICATION & SECURITY ------------------------------- -โœ“ Store JWT token securely (httpOnly cookies preferred, or localStorage) -โœ“ Include Authorization header in ALL authenticated requests -โœ“ Clear token on logout -โœ“ Redirect to login on 401 Unauthorized -โœ“ Don't store sensitive data (passwords) in frontend -โœ“ Implement token refresh mechanism if token has expiration -โœ“ Validate user input before sending to backend - - -8.2 API CALLS -------------- -โœ“ Use environment variables for API base URL -โœ“ Create reusable API service/utility functions -โœ“ Implement request interceptors for adding auth headers -โœ“ Implement response interceptors for error handling -โœ“ Show loading indicators during API calls -โœ“ Debounce search inputs (300-500ms delay) -โœ“ Cache frequently accessed data (categories, user profile) -โœ“ Use pagination for large data sets - - -8.3 STATE MANAGEMENT --------------------- -โœ“ Store authenticated user data in global state -โœ“ Update local state optimistically for better UX -โœ“ Sync state after API responses -โœ“ Invalidate cached data after mutations -โœ“ Use React Query, SWR, or similar for data fetching - - -8.4 UI/UX CONSIDERATIONS ------------------------- -โœ“ Show clear status indicators for bookings -โœ“ Use color coding for different statuses -โœ“ Provide real-time feedback (success/error messages) -โœ“ Implement skeleton loaders for better perceived performance -โœ“ Make trust scores prominent to build confidence -โœ“ Show confirmation dialogs for destructive actions -โœ“ Use form validation with clear error messages -โœ“ Implement responsive design (mobile-first) -โœ“ Add empty states ("No items yet" with CTA) -โœ“ Show item availability calendars - - -8.5 RATING SYSTEM (Issue #26) ------------------------------- -โœ“ Always call can-rate endpoint before showing rating form -โœ“ Disable rating button if can_rate is false -โœ“ Show clear messages: - - "Complete the booking to rate" - - "You've already rated this booking" - - "Only borrowers can rate" -โœ“ Display "Rated" badge on completed rated bookings -โœ“ Prevent duplicate rating attempts on frontend -โœ“ Show trust score updates immediately after rating -โœ“ Allow users to see their rating history - - -8.6 IMAGE HANDLING ------------------- -โœ“ Implement image upload functionality -โœ“ Compress images before upload -โœ“ Support multiple image formats (JPG, PNG, WebP) -โœ“ Show image previews before upload -โœ“ Implement image gallery/carousel for item details -โœ“ Use placeholder images for items without photos -โœ“ Lazy load images for better performance - - -8.7 SEARCH & FILTERING ------------------------ -โœ“ Implement auto-complete for search -โœ“ Show recent searches -โœ“ Persist search query in URL for shareability -โœ“ Allow multiple filter combinations -โœ“ Show filter count ("Showing 12 of 45 items") -โœ“ Clear filters button -โœ“ Sort options (newest, popular, nearest) - - -8.8 NOTIFICATIONS ------------------ -โœ“ Show success messages after actions -โœ“ Display error messages clearly -โœ“ Implement in-app notification center -โœ“ Badge count for unread notifications -โœ“ Types of notifications: - - New booking request received - - Booking approved/rejected - - Booking starting soon - - Booking completed - reminder to rate - - New rating received -โœ“ Push notifications (future enhancement) - - -8.9 PERFORMANCE OPTIMIZATION ------------------------------ -โœ“ Lazy load routes/pages -โœ“ Implement infinite scroll for item lists -โœ“ Cache API responses -โœ“ Minimize bundle size -โœ“ Use CDN for static assets -โœ“ Implement service workers for offline support -โœ“ Optimize images (WebP, responsive sizes) - - -8.10 TESTING ------------- -โœ“ Test authentication flow end-to-end -โœ“ Test booking lifecycle thoroughly -โœ“ Test rating system with various scenarios -โœ“ Test error handling and edge cases -โœ“ Test on different devices and browsers -โœ“ Test with slow network (throttling) -โœ“ Verify security (XSS, CSRF protection) - - -================================================================================ -APPENDIX A: EXAMPLE API CALL SNIPPETS -================================================================================ - -A.1 JAVASCRIPT FETCH EXAMPLES ------------------------------- - -// Login -async function login(usernameOrEmail, password) { - const response = await fetch('http://localhost:8080/api/auth/login', { - method: 'POST', - headers: { 'Content-Type': 'application/json' }, - body: JSON.stringify({ usernameOrEmail, password }) - }); - const data = await response.json(); - localStorage.setItem('token', data.token); - return data; -} - -// Get items with auth -async function getItems(page = 0, size = 10) { - const token = localStorage.getItem('token'); - const response = await fetch( - `http://localhost:8080/api/items?page=${page}&size=${size}`, - { - headers: { 'Authorization': `Bearer ${token}` } - } - ); - return response.json(); -} - -// Create item -async function createItem(itemData, userId) { - const token = localStorage.getItem('token'); - const response = await fetch('http://localhost:8080/api/items', { - method: 'POST', - headers: { - 'Content-Type': 'application/json', - 'Authorization': `Bearer ${token}`, - 'X-User-Id': userId - }, - body: JSON.stringify(itemData) - }); - return response.json(); -} - -// Check if can rate booking (Issue #26) -async function canRateBooking(bookingId) { - const token = localStorage.getItem('token'); - const response = await fetch( - `http://localhost:8080/api/ratings/can-rate/${bookingId}`, - { - headers: { 'Authorization': `Bearer ${token}` } - } - ); - const data = await response.json(); - return data.can_rate; -} - -// Create rating -async function createRating(ratingData) { - const token = localStorage.getItem('token'); - const response = await fetch('http://localhost:8080/api/ratings', { - method: 'POST', - headers: { - 'Content-Type': 'application/json', - 'Authorization': `Bearer ${token}` - }, - body: JSON.stringify(ratingData) - }); - return response.json(); -} - - -A.2 AXIOS EXAMPLES ------------------- - -import axios from 'axios'; - -// Configure axios instance -const api = axios.create({ - baseURL: 'http://localhost:8080', - headers: { 'Content-Type': 'application/json' } -}); - -// Request interceptor to add auth token -api.interceptors.request.use(config => { - const token = localStorage.getItem('token'); - if (token) { - config.headers.Authorization = `Bearer ${token}`; - } - return config; -}); - -// Response interceptor for error handling -api.interceptors.response.use( - response => response, - error => { - if (error.response?.status === 401) { - localStorage.removeItem('token'); - window.location.href = '/login'; - } - return Promise.reject(error); - } -); - -// Usage examples -const login = (usernameOrEmail, password) => - api.post('/api/auth/login', { usernameOrEmail, password }); - -const getItems = (params) => - api.get('/api/items', { params }); - -const createItem = (itemData, userId) => - api.post('/api/items', itemData, { - headers: { 'X-User-Id': userId } - }); - -const canRateBooking = (bookingId) => - api.get(`/api/ratings/can-rate/${bookingId}`); - -const createRating = (ratingData) => - api.post('/api/ratings', ratingData); - -const approveBooking = (bookingId, ownerId) => - api.patch(`/api/bookings/${bookingId}/approve`, null, { - headers: { 'X-User-Id': ownerId } - }); - - -================================================================================ -APPENDIX B: SAMPLE FORM VALIDATION RULES -================================================================================ - -Registration Form: -- username: 3-50 chars, alphanumeric, unique -- name: 2-100 chars, required -- email: valid email format, unique -- password: min 8 chars, required -- phoneNumber: optional, valid phone format - -Item Form: -- name: required, max 200 chars -- description: required, max 2000 chars -- categoryId: required, must exist -- deposit: number, min 0, max 10000 -- condition: one of [NEW, EXCELLENT, GOOD, FAIR, POOR] -- images: array of valid URLs - -Booking Form: -- itemId: required, must exist and be available -- startDate: required, must be future date -- endDate: required, must be after startDate -- bookingNotes: max 500 chars -- depositAmount: number, min 0 -- acceptTerms: must be true - -Rating Form: -- rateeId: required, valid user ID -- ratingType: required, valid enum -- ratingValue: required, integer 1-5 -- comment: max 1000 chars -- bookingId: required for user ratings -- itemId: required for item ratings - - -================================================================================ -APPENDIX C: WEBSOCKET/REAL-TIME FEATURES (Future Enhancement) -================================================================================ - -For future implementation, consider adding WebSocket support for: -- Real-time notifications -- Chat between borrowers and owners -- Live booking status updates -- Real-time availability updates - -Endpoint: ws://localhost:8080/ws -Authentication: Send JWT token in connection handshake - - -================================================================================ -APPENDIX D: MOBILE APP CONSIDERATIONS -================================================================================ - -This backend is fully compatible with mobile app development (React Native, -Flutter, etc.). Same API endpoints, authentication flow, and data models apply. - -Additional considerations for mobile: -- Implement token refresh for longer sessions -- Use secure storage for JWT tokens (KeyChain, KeyStore) -- Handle offline mode gracefully -- Optimize image uploads for mobile networks -- Implement push notifications -- Add location-based features (nearby items) -- Use biometric authentication - - -================================================================================ -END OF GUIDE -================================================================================ - -For questions or issues, refer to: -- Backend source code: /src/main/java/com/locallend/locallend/ -- API documentation: Swagger UI at http://localhost:8080/swagger-ui.html (if enabled) -- Backend logs: docker logs locallend-backend - -Last Updated: November 8, 2025 -Version: 1.0 diff --git a/frontend/FRONTEND_INTEGRATION_GUIDE_MARK2.md b/frontend/FRONTEND_INTEGRATION_GUIDE_MARK2.md deleted file mode 100644 index 6bc7040592f678ae55765b591066fc000d7bcb3e..0000000000000000000000000000000000000000 --- a/frontend/FRONTEND_INTEGRATION_GUIDE_MARK2.md +++ /dev/null @@ -1,945 +0,0 @@ -# ๐Ÿš€ LocalLend Frontend Integration Guide - Mark 2 -## Comprehensive Backend API Documentation & Integration Manual - -**Version**: 2.0 -**Date**: November 12, 2025 -**Backend Base URL**: `http://localhost:8080` -**Frontend Base URL**: `http://localhost:5173` - ---- - -## ๐Ÿ“‹ Table of Contents - -1. [๐Ÿ” Authentication System](#authentication-system) -2. [๐Ÿ—๏ธ API Architecture](#api-architecture) -3. [๐Ÿ“Š Data Models & Enums](#data-models--enums) -4. [๐Ÿ› ๏ธ API Endpoints](#api-endpoints) -5. [๐Ÿ”„ Authentication Workflows](#authentication-workflows) -6. [โš ๏ธ Error Handling](#error-handling) -7. [๐Ÿงช Testing Guide](#testing-guide) -8. [๐Ÿ› Common Issues & Solutions](#common-issues--solutions) - ---- - -## ๐Ÿ” Authentication System - -### **JWT Token Configuration** -```properties -# Backend Configuration (application.properties) -app.jwt.secret=change-this-secret-in-production-with-a-very-long-secure-random-string-at-least-256-bits -app.jwt.expiration-ms=86400000 # 24 hours -``` - -### **Authentication Headers Required** -```http -# For authenticated endpoints -Authorization: Bearer {jwt_token} -X-User-Id: {user_id} -Content-Type: application/json -``` - -### **Public Endpoints (No Authentication Required)** -- `POST /api/auth/register` -- `POST /api/auth/login` -- `GET /api/categories/**` -- `GET /api/items/**` (browsing only) -- `GET /api/users/*/public` -- `GET /api/users/search` - -### **Protected Endpoints (Authentication Required)** -- All other endpoints require valid JWT token -- X-User-Id header must match the token's user - ---- - -## ๐Ÿ—๏ธ API Architecture - -### **Base URL Structure** -``` -Backend API: http://localhost:8080/api -โ”œโ”€โ”€ /auth # Authentication -โ”œโ”€โ”€ /users # User management -โ”œโ”€โ”€ /items # Item management -โ”œโ”€โ”€ /categories # Category management -โ”œโ”€โ”€ /bookings # Booking system -โ””โ”€โ”€ /ratings # Rating system -``` - -### **Security Configuration** -- **CORS**: Enabled for `http://localhost:*` -- **Methods**: GET, POST, PUT, PATCH, DELETE -- **Headers**: All headers allowed including Authorization and X-User-Id -- **Session**: Stateless (JWT-based) -- **CSRF**: Disabled for API - ---- - -## ๐Ÿ“Š Data Models & Enums - -### **ItemStatus Enum** -```java -public enum ItemStatus { - AVAILABLE, // Item is available for booking - BOOKED, // Item has been booked - BORROWED, // Item is currently borrowed - MAINTENANCE, // Item is under maintenance - UNAVAILABLE // Owner has marked it unavailable -} -``` - -### **ItemCondition Enum** -```java -public enum ItemCondition { - NEW, - EXCELLENT, - GOOD, - FAIR, - POOR -} -``` - -### **BookingStatus Enum** -```java -public enum BookingStatus { - PENDING, // Waiting for owner approval - CONFIRMED, // Owner has approved - ACTIVE, // Booking is currently active - COMPLETED, // Booking completed successfully - CANCELLED, // Cancelled by borrower - REJECTED // Rejected by owner -} -``` - -### **RatingType Enum** -```java -public enum RatingType { - USER_TO_ITEM, // Rating an item - USER_TO_USER, // General user rating - OWNER_TO_BORROWER, // Owner rating borrower - BORROWER_TO_OWNER // Borrower rating owner -} -``` - ---- - -## ๐Ÿ› ๏ธ API Endpoints - -### **๐Ÿ” Authentication Endpoints** - -#### **Register User** -```http -POST /api/auth/register -Content-Type: application/json - -{ - "username": "johndoe", # Required, 3-50 chars - "name": "John Doe", # Required, 2-100 chars - "email": "john@example.com", # Required, valid email - "password": "password123", # Required, min 8 chars - "phoneNumber": "+1234567890" # Optional -} -``` - -**Response (201 Created):** -```json -{ - "id": "user_id", - "username": "johndoe", - "name": "John Doe", - "email": "john@example.com", - "phoneNumber": "+1234567890", - "profileImageUrl": null, - "isActive": true, - "createdAt": "2025-11-12T10:00:00Z" -} -``` - -#### **Login User** -```http -POST /api/auth/login -Content-Type: application/json - -{ - "usernameOrEmail": "johndoe", # Username or email - "password": "password123" -} -``` - -**Response (200 OK):** -```json -{ - "token": "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9...", - "tokenType": "Bearer", - "user": { - "id": "user_id", - "username": "johndoe", - "name": "John Doe", - "email": "john@example.com", - "phoneNumber": "+1234567890", - "profileImageUrl": null, - "isActive": true, - "createdAt": "2025-11-12T10:00:00Z" - } -} -``` - ---- - -### **๐Ÿ‘ค User Management Endpoints** - -#### **Get Current User Profile** -```http -GET /api/users/profile -Authorization: Bearer {jwt_token} -``` - -#### **Update Current User Profile** -```http -PUT /api/users/profile -Authorization: Bearer {jwt_token} -Content-Type: application/json - -{ - "name": "John Smith", # Optional - "phoneNumber": "+1987654321", # Optional - "profileImageUrl": "https://..." # Optional -} -``` - -#### **Get User Public Info** -```http -GET /api/users/{userId}/public -# No authentication required -``` - -#### **Search Users** -```http -GET /api/users/search?term=john&page=0&size=10 -# No authentication required -``` - ---- - -### **๐Ÿ“ฆ Item Management Endpoints** - -#### **Create Item** -```http -POST /api/items -Authorization: Bearer {jwt_token} -X-User-Id: {user_id} -Content-Type: application/json - -{ - "name": "Laptop", # Required, 3-100 chars - "description": "Gaming laptop", # Optional, max 500 chars - "categoryId": "category_id", # Required - "deposit": 100.0, # Optional, default 0.0 - "images": ["url1", "url2"], # Optional - "condition": "EXCELLENT" # Optional, default GOOD -} -``` - -#### **Get All Available Items** -```http -GET /api/items?page=0&size=10&sortBy=name&sortDir=asc -# No authentication required -``` - -#### **Get Item by ID** -```http -GET /api/items/{itemId} -# No authentication required -``` - -#### **Search Items** -```http -GET /api/items/search?q=laptop&page=0&size=10 -# No authentication required -``` - -#### **Get Items by Category** -```http -GET /api/items/category/{categoryId}?page=0&size=10 -# No authentication required -``` - -#### **Get Items by Owner** -```http -GET /api/items/owner/{ownerId}?page=0&size=10 -# No authentication required -``` - -#### **Get Current User's Items** -```http -GET /api/items/my-items?page=0&size=10 -Authorization: Bearer {jwt_token} -X-User-Id: {user_id} -``` - -#### **Update Item** -```http -PUT /api/items/{itemId} -Authorization: Bearer {jwt_token} -X-User-Id: {user_id} -Content-Type: application/json - -{ - "name": "Updated Laptop", # Optional - "description": "New description", # Optional - "condition": "GOOD", # Optional - "deposit": 150.0, # Optional - "images": ["new_url"], # Optional - "isAvailable": true # Optional -} -``` - -#### **โš ๏ธ MISSING ENDPOINT - Item Status Update** -**โŒ Frontend expects but backend doesn't have:** -```http -PATCH /api/items/{itemId}/status -Authorization: Bearer {jwt_token} -X-User-Id: {user_id} -Content-Type: application/json - -{ - "status": "UNAVAILABLE" # AVAILABLE, UNAVAILABLE, MAINTENANCE -} -``` - -**โœ… Available alternatives:** -```http -# Toggle availability (switches between AVAILABLE/UNAVAILABLE) -PATCH /api/items/{itemId}/toggle-availability -Authorization: Bearer {jwt_token} -X-User-Id: {user_id} - -# Set specific availability -PATCH /api/items/{itemId}/availability -Authorization: Bearer {jwt_token} -X-User-Id: {user_id} -Content-Type: application/json - -{ - "isAvailable": false # true = AVAILABLE, false = UNAVAILABLE -} -``` - -#### **Delete Item** -```http -DELETE /api/items/{itemId} -Authorization: Bearer {jwt_token} -X-User-Id: {user_id} -``` - ---- - -### **๐Ÿ“‚ Category Management Endpoints** - -#### **Create Category** -```http -POST /api/categories -Authorization: Bearer {jwt_token} -Content-Type: application/json - -{ - "name": "Electronics", # Required - "description": "Electronic items", # Optional - "parentId": "parent_category_id" # Optional (for subcategories) -} -``` - -#### **Get All Categories** -```http -GET /api/categories?sort=name -# No authentication required -``` - -#### **Get Category by ID** -```http -GET /api/categories/{categoryId} -# No authentication required -``` - -#### **Get Root Categories** -```http -GET /api/categories/root?sort=name -# No authentication required -``` - ---- - -### **๐Ÿ“… Booking Management Endpoints** - -#### **Create Booking** -```http -POST /api/bookings -Authorization: Bearer {jwt_token} -X-User-Id: {borrower_id} -Content-Type: application/json - -{ - "itemId": "item_id", - "startDate": "2025-11-15", - "endDate": "2025-11-20", - "notes": "I need this for a project" # Optional -} -``` - -#### **Get Booking by ID** -```http -GET /api/bookings/{bookingId} -Authorization: Bearer {jwt_token} -``` - -#### **Get User's Bookings (as Borrower)** -```http -GET /api/bookings/my-bookings -Authorization: Bearer {jwt_token} -X-User-Id: {user_id} - -# Alternative endpoint: -GET /api/bookings/my -Authorization: Bearer {jwt_token} -X-User-Id: {user_id} -``` - -#### **Get Pending Approvals (as Owner)** -```http -GET /api/bookings/pending-approvals -Authorization: Bearer {jwt_token} -X-User-Id: {owner_id} -``` - -#### **Get Owner's Bookings** -```http -GET /api/bookings/my-owned -Authorization: Bearer {jwt_token} -X-User-Id: {owner_id} -``` - -#### **Approve/Confirm Booking** -```http -# New naming (Issue #15) -PATCH /api/bookings/{bookingId}/approve -Authorization: Bearer {jwt_token} -X-User-Id: {owner_id} -Content-Type: application/json - -{ - "ownerNotes": "Please take care of it" # Optional -} - -# Original endpoint (still works) -PATCH /api/bookings/{bookingId}/confirm -Authorization: Bearer {jwt_token} -X-User-Id: {owner_id} -Content-Type: application/json - -{ - "ownerNotes": "Please take care of it" # Optional -} -``` - -#### **Start/Activate Booking** -```http -# New naming (Issue #15) -PATCH /api/bookings/{bookingId}/start -Authorization: Bearer {jwt_token} -X-User-Id: {borrower_id} - -# Original endpoint (still works) -PATCH /api/bookings/{bookingId}/activate -Authorization: Bearer {jwt_token} -X-User-Id: {borrower_id} -``` - -#### **Complete Booking** -```http -PATCH /api/bookings/{bookingId}/complete -Authorization: Bearer {jwt_token} -X-User-Id: {borrower_id} -``` - -#### **Cancel Booking** -```http -PATCH /api/bookings/{bookingId}/cancel -Authorization: Bearer {jwt_token} -X-User-Id: {borrower_id} -Content-Type: application/json - -{ - "reason": "Plans changed" # Optional -} -``` - -#### **Reject Booking** -```http -PATCH /api/bookings/{bookingId}/reject -Authorization: Bearer {jwt_token} -X-User-Id: {owner_id} -Content-Type: application/json - -{ - "reason": "Item not available" # Optional -} -``` - ---- - -### **โญ Rating System Endpoints** - -#### **Create Rating** -```http -POST /api/ratings -Authorization: Bearer {jwt_token} -Content-Type: application/json - -# Rating an item -{ - "ratingType": "USER_TO_ITEM", - "itemId": "item_id", - "ratingValue": 5, # 1-5 - "comment": "Great item!", # Optional - "isAnonymous": false # Optional, default false -} - -# Rating a user -{ - "ratingType": "BORROWER_TO_OWNER", # or OWNER_TO_BORROWER, USER_TO_USER - "rateeId": "user_id", - "bookingId": "booking_id", # Optional - "ratingValue": 4, # 1-5 - "comment": "Great communication!" # Optional -} -``` - -#### **Get User Ratings** -```http -GET /api/ratings/user/{userId} -# No authentication required -``` - -#### **Get Item Ratings** -```http -GET /api/ratings/item/{itemId} -# No authentication required -``` - -#### **Get Rating Statistics** -```http -GET /api/ratings/user/{userId}/stats -# No authentication required - -GET /api/ratings/item/{itemId}/stats -# No authentication required -``` - ---- - -## ๐Ÿ”„ Authentication Workflows - -### **Registration Flow** -```mermaid -sequenceDiagram - participant F as Frontend - participant B as Backend - participant DB as Database - - F->>B: POST /api/auth/register - B->>B: Validate input - B->>B: Hash password - B->>DB: Save user - B->>F: Return user data (201) - Note over F: Redirect to login -``` - -### **Login Flow** -```mermaid -sequenceDiagram - participant F as Frontend - participant B as Backend - participant DB as Database - - F->>B: POST /api/auth/login - B->>DB: Verify credentials - B->>B: Generate JWT token - B->>F: Return token + user data - F->>F: Store token in localStorage - F->>F: Set Authorization header -``` - -### **Authenticated Request Flow** -```mermaid -sequenceDiagram - participant F as Frontend - participant B as Backend - participant DB as Database - - F->>B: Request with Authorization header - B->>B: Validate JWT token - B->>B: Extract user from token - B->>B: Check X-User-Id matches token - B->>DB: Process request - B->>F: Return response -``` - -### **Token Storage & Management** -```javascript -// Frontend token management -class AuthService { - login(credentials) { - return api.post('/api/auth/login', credentials) - .then(response => { - const { token, user } = response.data; - localStorage.setItem('token', token); - localStorage.setItem('user', JSON.stringify(user)); - this.setAuthHeader(token); - return { token, user }; - }); - } - - setAuthHeader(token) { - api.defaults.headers.common['Authorization'] = `Bearer ${token}`; - } - - logout() { - localStorage.removeItem('token'); - localStorage.removeItem('user'); - delete api.defaults.headers.common['Authorization']; - } - - getCurrentUser() { - return JSON.parse(localStorage.getItem('user')); - } - - getToken() { - return localStorage.getItem('token'); - } - - isAuthenticated() { - const token = this.getToken(); - if (!token) return false; - - // Check if token is expired - try { - const payload = JSON.parse(atob(token.split('.')[1])); - return payload.exp * 1000 > Date.now(); - } catch { - return false; - } - } -} -``` - ---- - -## โš ๏ธ Error Handling - -### **Standard Error Response Format** -```json -{ - "success": false, - "message": "Error description", - "error_code": "ERROR_TYPE", - "timestamp": "2025-11-12T10:00:00Z" -} -``` - -### **HTTP Status Codes** -- **200** - Success -- **201** - Created -- **400** - Bad Request (validation errors) -- **401** - Unauthorized (invalid/missing token) -- **403** - Forbidden (no permission) -- **404** - Not Found -- **409** - Conflict (duplicate data) -- **500** - Internal Server Error - -### **Common Error Scenarios** - -#### **Authentication Errors** -```json -// 401 - Invalid token -{ - "success": false, - "message": "Invalid or expired token", - "error_code": "INVALID_TOKEN" -} - -// 403 - Insufficient permissions -{ - "success": false, - "message": "You don't own this item", - "error_code": "ACCESS_DENIED" -} -``` - -#### **Validation Errors** -```json -// 400 - Missing required fields -{ - "success": false, - "message": "Name is required", - "error_code": "VALIDATION_ERROR" -} -``` - -#### **Business Logic Errors** -```json -// 409 - Booking conflict -{ - "success": false, - "message": "Item is already booked for this period", - "error_code": "BOOKING_CONFLICT" -} -``` - ---- - -## ๐Ÿงช Testing Guide - -### **Basic API Health Check** -```bash -# Test if backend is running -curl -I http://localhost:8080/api/categories -# Expected: HTTP/1.1 200 OK - -# Test authentication endpoint -curl -X POST http://localhost:8080/api/auth/login \ - -H "Content-Type: application/json" \ - -d '{"usernameOrEmail":"test","password":"test"}' -# Expected: 401 or valid response (not 404/500) -``` - -### **Authentication Testing** -```bash -# 1. Register a test user -curl -X POST http://localhost:8080/api/auth/register \ - -H "Content-Type: application/json" \ - -d '{ - "username": "testuser", - "name": "Test User", - "email": "test@example.com", - "password": "password123" - }' - -# 2. Login to get token -curl -X POST http://localhost:8080/api/auth/login \ - -H "Content-Type: application/json" \ - -d '{ - "usernameOrEmail": "testuser", - "password": "password123" - }' - -# 3. Test authenticated endpoint -curl -X GET http://localhost:8080/api/users/profile \ - -H "Authorization: Bearer YOUR_TOKEN_HERE" -``` - -### **Item Management Testing** -```bash -# Test item availability endpoints (the missing one) -curl -X PATCH http://localhost:8080/api/items/ITEM_ID/status \ - -H "Content-Type: application/json" \ - -H "Authorization: Bearer TOKEN" \ - -H "X-User-Id: USER_ID" \ - -d '{"status":"UNAVAILABLE"}' -# Expected: 404 (endpoint doesn't exist) - -# Test working availability endpoint -curl -X PATCH http://localhost:8080/api/items/ITEM_ID/availability \ - -H "Content-Type: application/json" \ - -H "Authorization: Bearer TOKEN" \ - -H "X-User-Id: USER_ID" \ - -d '{"isAvailable":false}' -# Expected: 200 with updated item -``` - ---- - -## ๐Ÿ› Common Issues & Solutions - -### **Issue 1: "Make Unavailable" Button Not Working** - -**Problem**: Frontend calls `PATCH /api/items/{id}/status` but backend doesn't have this endpoint. - -**Root Cause**: Mismatch between frontend expectations and backend implementation. - -**Solutions**: - -**Option A: Update Frontend (Recommended)** -```javascript -// Change in itemService.ts -updateItemStatus: async (itemId: string, status: ItemStatus, userId: string): Promise => { - // Instead of: - // const response = await api.patch(`/api/items/${itemId}/status`, { status }, { - - // Use: - const isAvailable = status === 'AVAILABLE'; - const response = await api.patch(`/api/items/${itemId}/availability`, { isAvailable }, { - headers: addUserIdHeader(userId) - }); - return response; -} -``` - -**Option B: Add Missing Backend Endpoint** -```java -// Add to ItemController.java -@PatchMapping("/{id}/status") -public ResponseEntity updateItemStatus( - @PathVariable String id, - @RequestBody Map request, - @RequestHeader(value = "X-User-Id", required = true) String userId) { - - try { - String status = request.get("status"); - if (status == null) { - return ResponseEntity.badRequest() - .body(Map.of("error", "Status is required")); - } - - // Convert status to availability - boolean isAvailable = "AVAILABLE".equals(status.toUpperCase()); - ItemDTO updatedItem = itemService.setAvailability(id, isAvailable, userId); - return ResponseEntity.ok(updatedItem); - } catch (Exception e) { - return ResponseEntity.status(HttpStatus.BAD_REQUEST) - .body(Map.of("error", e.getMessage())); - } -} -``` - -### **Issue 2: CORS Errors** - -**Problem**: Frontend can't reach backend due to CORS policy. - -**Solution**: Backend already has CORS configured correctly: -```java -@CrossOrigin(origins = "*", maxAge = 3600) // On controllers -``` - -### **Issue 3: Authentication Failures** - -**Common Problems**: -1. **Missing X-User-Id header** -2. **Token format issues** (missing "Bearer " prefix) -3. **Expired tokens** (24-hour expiration) - -**Frontend Fix**: -```javascript -// Ensure proper headers -const addUserIdHeader = (userId) => ({ - 'X-User-Id': userId, - 'Authorization': `Bearer ${localStorage.getItem('token')}` -}); -``` - -### **Issue 4: Static Resource Errors** - -**Problem**: Getting "No static resource" instead of API responses. - -**Causes**: -1. Backend not running on port 8080 -2. Wrong URL (missing `/api` prefix) -3. Spring Boot serving static files instead of API - -**Solutions**: -1. Verify backend is running: `curl http://localhost:8080/api/categories` -2. Check application.properties for conflicting static mappings -3. Ensure controllers are properly registered - ---- - -## ๐Ÿ“ Frontend Implementation Checklist - -### **Authentication Integration** -- [ ] Implement login/register forms -- [ ] Store JWT token in localStorage -- [ ] Set Authorization header on all authenticated requests -- [ ] Include X-User-Id header where required -- [ ] Handle token expiration (401 responses) -- [ ] Implement logout functionality - -### **Item Management Integration** -- [ ] โš ๏ธ **Fix item status update** (use `/availability` endpoint) -- [ ] Implement item creation form -- [ ] Add item browsing/search functionality -- [ ] Handle image uploads properly -- [ ] Implement owner-only actions (edit/delete) - -### **Booking System Integration** -- [ ] Create booking request form -- [ ] Implement booking approval workflow -- [ ] Add booking status tracking -- [ ] Handle booking state transitions -- [ ] Display pending approvals for owners - -### **Rating System Integration** -- [ ] Add rating creation forms -- [ ] Display ratings on items/users -- [ ] Implement rating statistics -- [ ] Handle different rating types - -### **Error Handling** -- [ ] Implement global error interceptor -- [ ] Handle authentication errors -- [ ] Display user-friendly error messages -- [ ] Add loading states - ---- - -## ๐ŸŽฏ Quick Start Checklist - -1. **Start Backend**: `mvn spring-boot:run` (port 8080) -2. **Start Frontend**: `npm run dev` (port 5173) -3. **Test Registration**: Create a test user -4. **Test Login**: Get JWT token -5. **Test Authenticated Endpoint**: Use token to access protected routes -6. **Fix Item Status Issue**: Update frontend to use `/availability` endpoint - ---- - -## ๐Ÿ“ž Support & Troubleshooting - -### **Backend Logs** -```bash -# Check Spring Boot logs for errors -mvn spring-boot:run - -# Look for: -โœ… "Started Application in X seconds" -โœ… "Mapping servlet: 'dispatcherServlet' to [/]" -โŒ Authentication/authorization errors -โŒ Database connection issues -``` - -### **Frontend Debugging** -```javascript -// Add to browser console for debugging -console.log('Token:', localStorage.getItem('token')); -console.log('User:', localStorage.getItem('user')); - -// Check API calls in Network tab -// Verify Authorization headers are set -// Check response status codes -``` - -### **Common Commands** -```bash -# Backend health check -curl http://localhost:8080/api/categories - -# Test authentication -curl -X POST http://localhost:8080/api/auth/login \ - -H "Content-Type: application/json" \ - -d '{"usernameOrEmail":"test","password":"test"}' - -# Test protected endpoint -curl -X GET http://localhost:8080/api/users/profile \ - -H "Authorization: Bearer YOUR_TOKEN" -``` - ---- - -**This guide should provide everything needed for successful frontend-backend integration. The key issue identified is the missing `/status` endpoint - fix this first for the "Make Unavailable" button to work!** \ No newline at end of file diff --git a/frontend/JSON_FIX_SUMMARY.md b/frontend/JSON_FIX_SUMMARY.md deleted file mode 100644 index 4ebcb6a1099347a2d6f55474c730b4c5002ea577..0000000000000000000000000000000000000000 --- a/frontend/JSON_FIX_SUMMARY.md +++ /dev/null @@ -1,79 +0,0 @@ -# FIXED: JSON Parse Error - Boolean vs String Issue - -## ๐ŸŽฏ **Root Cause** - -**Backend Error:** `Cannot deserialize value of type java.lang.Boolean from String "UNAVAILABLE"` - -**Issue:** The `/api/items/{id}/availability` endpoint expects a **boolean** field, not a string status. - ---- - -## ๐Ÿ”ง **Fix Applied** - -### **BEFORE (Wrong JSON):** -```json -{ - "status": "UNAVAILABLE" // โŒ String value -} -``` - -### **AFTER (Correct JSON):** -```json -{ - "available": false // โœ… Boolean value -} -``` - -### **Mapping Logic:** -- **"AVAILABLE" status** โ†’ `{"available": true}` -- **"UNAVAILABLE" status** โ†’ `{"available": false}` - ---- - -## ๐Ÿ“ **Files Updated:** - -### **1. itemService.ts** -```typescript -// OLD (wrong format) -{ status } // Sends: {"status": "UNAVAILABLE"} - -// NEW (correct format) -const isAvailable = status === 'AVAILABLE'; -{ available: isAvailable } // Sends: {"available": false} -``` - -### **2. DashboardPage.tsx** -```typescript -// OLD (wrong format) -body: JSON.stringify({ status: newStatus }) - -// NEW (correct format) -const isAvailable = newStatus === 'AVAILABLE'; -body: JSON.stringify({ available: isAvailable }) -``` - ---- - -## ๐ŸŽฏ **What Happens Now:** - -### **When clicking "Make Unavailable":** -1. Frontend calculates: `isAvailable = false` (since newStatus !== 'AVAILABLE') -2. Sends: `{"available": false}` to `/api/items/{id}/availability` -3. Backend receives boolean and processes correctly -4. Item becomes unavailable โœ… - -### **When clicking "Make Available":** -1. Frontend calculates: `isAvailable = true` (since newStatus === 'AVAILABLE') -2. Sends: `{"available": true}` to `/api/items/{id}/availability` -3. Backend receives boolean and processes correctly -4. Item becomes available โœ… - ---- - -## โœ… **Expected Result:** - -- **No more JSON parse errors** - Backend gets boolean values it expects -- **Status toggle works correctly** - Available/Unavailable as intended -- **Proper button behavior** - "Make Available" vs "Make Unavailable" - -The malformed JSON error should be gone and the status toggle should work properly! \ No newline at end of file diff --git a/frontend/README-LOCALLEND.md b/frontend/README-LOCALLEND.md deleted file mode 100644 index 0e260baa18493db0be6ccc4941a96a6fc49620d5..0000000000000000000000000000000000000000 --- a/frontend/README-LOCALLEND.md +++ /dev/null @@ -1,323 +0,0 @@ -# LocalLend Frontend - -A React + TypeScript + Vite application for the LocalLend peer-to-peer item sharing platform. - -## ๐Ÿ“‹ Project Overview - -LocalLend is a community platform where users can: -- List items they own for others to borrow -- Browse and search items available in their community -- Request to borrow items from other users -- Manage bookings (approve, track, complete) -- Rate users and items after completed transactions -- Build trust scores through positive interactions - -## ๐Ÿ›  Technology Stack - -- **Frontend Framework:** React 19+ with TypeScript -- **Build Tool:** Vite 7+ -- **UI Framework:** Material-UI (MUI) v6 -- **Routing:** React Router DOM v6 -- **HTTP Client:** Axios (currently using fetch as fallback) -- **Form Handling:** React Hook Form with Yup validation -- **Date Handling:** Day.js -- **State Management:** React Context API -- **Authentication:** JWT tokens with localStorage - -## ๐Ÿ“ Project Structure - -``` -src/ -โ”œโ”€โ”€ components/ # Reusable UI components -โ”œโ”€โ”€ pages/ # Page-level components -โ”œโ”€โ”€ services/ # API service layer -โ”œโ”€โ”€ context/ # React context providers -โ”œโ”€โ”€ hooks/ # Custom React hooks -โ”œโ”€โ”€ utils/ # Helper functions and constants -โ”œโ”€โ”€ types/ # TypeScript type definitions -โ””โ”€โ”€ assets/ # Static assets -``` - -## ๐Ÿš€ Getting Started - -### Prerequisites - -- Node.js 18+ and npm/yarn -- Backend API running on http://localhost:8080 - -### Installation - -1. **Install Dependencies** (if npm install fails due to esbuild issues): - ```bash - # Remove problematic node_modules if they exist - rm -rf node_modules package-lock.json - - # Install dependencies - npm install - - # If still failing, try with force flag - npm install --force - ``` - -2. **Environment Setup** - ```bash - # Create .env file in root directory - echo "VITE_API_BASE_URL=http://localhost:8080" > .env - ``` - -3. **Start Development Server** - ```bash - npm run dev - ``` - -## ๐Ÿ“ฆ Dependencies Status - -The project dependencies are configured in package.json but may need to be installed: - -### Core Dependencies (Added to package.json) -- `react` & `react-dom` - React framework -- `react-router-dom` - Client-side routing -- `axios` - HTTP client for API calls -- `@mui/material` - Material-UI component library -- `@emotion/react` & `@emotion/styled` - CSS-in-JS for MUI -- `@mui/icons-material` - Material Design icons - -### Form & Validation -- `react-hook-form` - Form state management -- `@hookform/resolvers` - Form validation resolvers -- `yup` - Schema validation - -### Date Handling -- `dayjs` - Date manipulation library -- `@mui/x-date-pickers` - MUI date picker components - -### Notifications -- `react-hot-toast` - Toast notifications - -## ๐Ÿ— API Integration - -The frontend integrates with the LocalLend backend API following these patterns: - -### Authentication -- JWT tokens stored in localStorage -- Automatic token refresh on API calls -- Redirect to login on 401 responses - -### API Services (Implemented) -- `authService` - User authentication -- `itemService` - Item CRUD operations -- `bookingService` - Booking lifecycle management -- `categoryService` - Category management -- `ratingService` - User and item ratings -- `userService` - User profile operations - -### Required Headers -```javascript -// For authenticated requests -Authorization: Bearer - -// For owner/borrower specific operations -X-User-Id: -``` - -## ๐Ÿ“ฑ Key Features (Designed) - -### Authentication Flow -1. User registration/login -2. JWT token storage -3. Protected route navigation -4. Automatic session management - -### Item Management -1. Browse items with pagination/filters -2. Create/edit item listings -3. Upload item images -4. Category-based organization - -### Booking System -1. Request to borrow items -2. Owner approval workflow -3. Booking status tracking -4. Start/complete booking actions - -### Rating System -1. Rate users after completed bookings -2. Rate items based on experience -3. Trust score calculation -4. Community feedback display - -## ๐ŸŽจ UI Components Structure (Planned) - -### Pages (Major Views) -- `HomePage` - Item browsing and search -- `LoginPage` - User authentication โœ… (Structure created) -- `RegisterPage` - Account creation โœ… (Structure created) -- `DashboardPage` - User dashboard -- `ItemDetailPage` - Individual item view -- `BookingPage` - Booking management -- `ProfilePage` - User profile settings - -### Components (Reusable) -- `Navbar` - Navigation with search -- `ItemCard` - Item display component -- `BookingCard` - Booking status display -- `RatingForm` - Rating submission -- `LoadingSpinner` - Loading states -- `ErrorBoundary` - Error handling - -## ๐Ÿ”ง Development Commands - -```bash -# Development server -npm run dev - -# Production build -npm run build - -# Preview production build -npm run preview - -# Lint code -npm run lint -``` - -## ๐ŸŒ API Endpoints Integration - -The frontend connects to these key backend endpoints: - -### Authentication -- `POST /api/auth/register` - User registration -- `POST /api/auth/login` - User login - -### Items -- `GET /api/items` - Browse items (public) -- `POST /api/items` - Create item (authenticated) -- `GET /api/items/{id}` - Item details -- `PUT /api/items/{id}` - Update item -- `PATCH /api/items/{id}/status` - Update status - -### Bookings -- `POST /api/bookings` - Create booking request -- `GET /api/bookings` - My bookings -- `PATCH /api/bookings/{id}/approve` - Approve booking -- `PATCH /api/bookings/{id}/start` - Start booking -- `PATCH /api/bookings/{id}/complete` - Complete booking - -### Ratings -- `GET /api/ratings/can-rate/{bookingId}` - Check if can rate -- `POST /api/ratings` - Submit rating - -## ๐Ÿšจ Current Status - -โœ… **Completed:** -- Project scaffolding and structure -- TypeScript type definitions (complete API coverage) -- API service layer with fetch-based client -- Authentication context setup -- Basic component structure (placeholder) -- Form validation utilities -- Constants and helper functions -- Project documentation - -โณ **Pending (requires dependency installation):** -- Material-UI component integration -- React Router implementation -- Form handling with React Hook Form -- Complete page implementations -- Image upload functionality -- Real-time notifications -- Error boundary implementation - -## ๐Ÿ›  Implementation Roadmap - -### Phase 1: Dependencies & Core Setup -1. Resolve npm installation issues -2. Install all required dependencies -3. Set up Material-UI theme provider -4. Implement React Router with protected routes - -### Phase 2: Authentication & Navigation -1. Complete Login/Register pages with MUI components -2. Implement navigation bar with search -3. Set up authentication guards -4. Add toast notification system - -### Phase 3: Core Features -1. Home page with item listings -2. Item detail and creation pages -3. User dashboard and profile pages -4. Booking request and management system - -### Phase 4: Advanced Features -1. Rating and review system -2. Real-time notifications -3. Image upload handling -4. Search and filter functionality - -### Phase 5: Polish & Optimization -1. Loading states and error handling -2. Responsive design improvements -3. Performance optimizations -4. Testing implementation - -## ๐Ÿ”— Backend Integration - -This frontend is designed to work with the LocalLend backend API. Ensure the backend is running on `http://localhost:8080` before starting the frontend development server. - -### Backend Requirements -- Spring Boot 3.5.6 application -- MongoDB database -- JWT authentication enabled -- CORS configured for `http://localhost:5173` (Vite dev server) - -## ๐Ÿ“š Documentation Reference - -- [Frontend Integration Guide](../FRONTEND_INTEGRATION_GUIDE.txt) - Complete API reference -- [React Documentation](https://react.dev/) -- [Material-UI Documentation](https://mui.com/) -- [Vite Documentation](https://vitejs.dev/) - -## ๐Ÿค Contributing - -1. Follow the existing code structure and patterns -2. Implement proper TypeScript typing -3. Add proper error handling for all API calls -4. Follow the authentication patterns established -5. Test with the backend API endpoints - -## โš ๏ธ Known Issues - -1. **NPM Installation**: There are currently issues with installing dependencies due to esbuild conflicts -2. **React Types**: JSX types are not available until React dependencies are properly installed -3. **Import Meta**: Environment variables access needs proper Vite typing - -## ๐Ÿ”ง Troubleshooting - -### NPM Installation Issues -If you encounter esbuild-related errors during `npm install`: - -```bash -# Method 1: Clean install -rm -rf node_modules package-lock.json -npm cache clean --force -npm install - -# Method 2: Force install -npm install --force - -# Method 3: Use different package manager -yarn install -# or -pnpm install -``` - -### Development Server Issues -If the dev server fails to start: - -1. Ensure you're in the correct directory (`locallend-frontend/`) -2. Verify package.json exists in the current directory -3. Try running with verbose output: `npm run dev --verbose` - ---- - -**Note:** This project is currently in development phase with complete architectural planning done. All core functionality is designed and ready for implementation once dependency installation is resolved. \ No newline at end of file diff --git a/frontend/README.md b/frontend/README.md deleted file mode 100644 index d2e77611fd3d959fee0487c41bd27b318be32b04..0000000000000000000000000000000000000000 --- a/frontend/README.md +++ /dev/null @@ -1,73 +0,0 @@ -# React + TypeScript + Vite - -This template provides a minimal setup to get React working in Vite with HMR and some ESLint rules. - -Currently, two official plugins are available: - -- [@vitejs/plugin-react](https://github.com/vitejs/vite-plugin-react/blob/main/packages/plugin-react) uses [Babel](https://babeljs.io/) (or [oxc](https://oxc.rs) when used in [rolldown-vite](https://vite.dev/guide/rolldown)) for Fast Refresh -- [@vitejs/plugin-react-swc](https://github.com/vitejs/vite-plugin-react/blob/main/packages/plugin-react-swc) uses [SWC](https://swc.rs/) for Fast Refresh - -## React Compiler - -The React Compiler is not enabled on this template because of its impact on dev & build performances. To add it, see [this documentation](https://react.dev/learn/react-compiler/installation). - -## Expanding the ESLint configuration - -If you are developing a production application, we recommend updating the configuration to enable type-aware lint rules: - -```js -export default defineConfig([ - globalIgnores(['dist']), - { - files: ['**/*.{ts,tsx}'], - extends: [ - // Other configs... - - // Remove tseslint.configs.recommended and replace with this - tseslint.configs.recommendedTypeChecked, - // Alternatively, use this for stricter rules - tseslint.configs.strictTypeChecked, - // Optionally, add this for stylistic rules - tseslint.configs.stylisticTypeChecked, - - // Other configs... - ], - languageOptions: { - parserOptions: { - project: ['./tsconfig.node.json', './tsconfig.app.json'], - tsconfigRootDir: import.meta.dirname, - }, - // other options... - }, - }, -]) -``` - -You can also install [eslint-plugin-react-x](https://github.com/Rel1cx/eslint-react/tree/main/packages/plugins/eslint-plugin-react-x) and [eslint-plugin-react-dom](https://github.com/Rel1cx/eslint-react/tree/main/packages/plugins/eslint-plugin-react-dom) for React-specific lint rules: - -```js -// eslint.config.js -import reactX from 'eslint-plugin-react-x' -import reactDom from 'eslint-plugin-react-dom' - -export default defineConfig([ - globalIgnores(['dist']), - { - files: ['**/*.{ts,tsx}'], - extends: [ - // Other configs... - // Enable lint rules for React - reactX.configs['recommended-typescript'], - // Enable lint rules for React DOM - reactDom.configs.recommended, - ], - languageOptions: { - parserOptions: { - project: ['./tsconfig.node.json', './tsconfig.app.json'], - tsconfigRootDir: import.meta.dirname, - }, - // other options... - }, - }, -]) -``` diff --git a/frontend/SETUP.md b/frontend/SETUP.md deleted file mode 100644 index 85b5f4fd592d34bc55e4f32700d93ea82c413a60..0000000000000000000000000000000000000000 --- a/frontend/SETUP.md +++ /dev/null @@ -1,166 +0,0 @@ -# LocalLend Frontend - Installation & Setup Guide - -## ๐Ÿš€ Quick Start - -This guide will help you set up the LocalLend frontend application. - -### Prerequisites - -1. **Node.js 18+** - Download from [nodejs.org](https://nodejs.org/) -2. **Backend API** - Ensure the LocalLend backend is running on `http://localhost:8080` -3. **Git** (optional) - For version control - -### Step 1: Clone or Navigate to Project - -```bash -# If you're working with the existing project -cd "d:\Bits\OOAD\Project OOAD\locallend_front\locallend-frontend" -``` - -### Step 2: Install Dependencies - -The project may have npm installation issues due to esbuild conflicts. Try these approaches: - -#### Method 1: Clean Install -```bash -# Remove existing node_modules if present -rm -rf node_modules package-lock.json - -# Clear npm cache -npm cache clean --force - -# Install dependencies -npm install -``` - -#### Method 2: Force Install (if Method 1 fails) -```bash -npm install --force -``` - -#### Method 3: Alternative Package Manager -```bash -# Using Yarn (install yarn first: npm install -g yarn) -yarn install - -# Or using pnpm (install pnpm first: npm install -g pnpm) -pnpm install -``` - -### Step 3: Environment Configuration - -```bash -# Copy environment template -cp .env.example .env - -# Edit .env file with your settings (optional, defaults should work) -# VITE_API_BASE_URL=http://localhost:8080 -``` - -### Step 4: Start Development Server - -```bash -npm run dev -``` - -The application should open at `http://localhost:5173` - -## ๐Ÿ›  Troubleshooting - -### Common Issues - -#### 1. NPM Install Fails with esbuild Error -``` -Error: EBUSY: resource busy or locked, rmdir 'node_modules\esbuild' -``` - -**Solution:** -- Close VS Code and any other processes that might be using the files -- Delete `node_modules` folder manually -- Try `npm install --force` - -#### 2. Development Server Won't Start -``` -npm error code ENOENT -npm error syscall open -npm error path package.json -``` - -**Solution:** -- Ensure you're in the correct directory (`locallend-frontend/`) -- Verify `package.json` exists -- Use `pwd` (PowerShell) to check current directory - -#### 3. TypeScript/JSX Errors -``` -JSX element implicitly has type 'any' -Cannot find module 'react' -``` - -**Solution:** -- These errors are expected until React dependencies are properly installed -- Complete the installation process first, then restart the dev server - -#### 4. API Connection Issues -``` -Network Error / CORS Error -``` - -**Solution:** -- Ensure backend API is running on `http://localhost:8080` -- Check backend CORS configuration allows `http://localhost:5173` -- Verify API endpoints are accessible via browser - -### Development Notes - -1. **Current Status:** The project structure is complete with all TypeScript types, services, and component structures ready -2. **Dependencies:** Some dependencies may need to be installed/resolved -3. **Components:** Most components are created as placeholders and will render properly once React is loaded -4. **API Integration:** All API services are implemented and ready to connect to the backend - -## ๐Ÿ“ฆ Project Structure Overview - -``` -locallend-frontend/ -โ”œโ”€โ”€ src/ -โ”‚ โ”œโ”€โ”€ components/ # UI components (Navbar, etc.) -โ”‚ โ”œโ”€โ”€ pages/ # Page components (Home, Login, etc.) -โ”‚ โ”œโ”€โ”€ services/ # API service layer -โ”‚ โ”œโ”€โ”€ context/ # React contexts (Auth) -โ”‚ โ”œโ”€โ”€ utils/ # Helper functions -โ”‚ โ”œโ”€โ”€ types/ # TypeScript definitions -โ”‚ โ””โ”€โ”€ assets/ # Static files -โ”œโ”€โ”€ public/ # Public assets -โ”œโ”€โ”€ package.json # Dependencies and scripts -โ”œโ”€โ”€ vite.config.ts # Vite configuration -โ”œโ”€โ”€ tsconfig.json # TypeScript configuration -โ””โ”€โ”€ README-LOCALLEND.md # Detailed documentation -``` - -## ๐Ÿ”— Next Steps After Installation - -1. **Verify Backend Connection:** Open browser dev tools and check for successful API calls -2. **Test Authentication:** Try to register/login a user -3. **Browse Items:** Navigate through the item listing functionality -4. **Create Test Data:** Use the backend to create some test categories and items -5. **UI Development:** Continue implementing the Material-UI components - -## ๐Ÿ“š Documentation - -- **Complete API Guide:** `../FRONTEND_INTEGRATION_GUIDE.txt` -- **Project Documentation:** `README-LOCALLEND.md` -- **Backend Setup:** Refer to backend documentation for API setup - -## ๐Ÿ†˜ Support - -If you encounter issues: - -1. Check this troubleshooting guide first -2. Review the error messages carefully -3. Ensure backend API is running and accessible -4. Check network/CORS configuration -5. Try alternative installation methods above - ---- - -**Note:** This project follows the exact specifications from the Frontend Integration Guide to ensure seamless backend compatibility. \ No newline at end of file diff --git a/frontend/STATUS_TOGGLE_DEBUG.md b/frontend/STATUS_TOGGLE_DEBUG.md deleted file mode 100644 index 3e30ffe79060aa9edb30ed88d4e7d2e0212be413..0000000000000000000000000000000000000000 --- a/frontend/STATUS_TOGGLE_DEBUG.md +++ /dev/null @@ -1,238 +0,0 @@ -# Item Status Toggle - Debugging Guide - -## ๐Ÿ” Issue Analysis - -The "Make Unavailable" button is not working. Based on the Integration Guide, here are potential causes and debugging steps: - -## ๐Ÿ“‹ Integration Guide Requirements - -### **PATCH /api/items/{itemId}/status** -``` -Headers: -- Authorization: Bearer -- X-User-Id: -- Content-Type: application/json - -Request Body: -{ - "status": "AVAILABLE" // AVAILABLE|UNAVAILABLE|BORROWED -} - -Response (200): Returns updated item object -``` - -## ๐Ÿ› Potential Issues & Debugging - -### 1. **Backend Connectivity** -**Added**: Health check to verify backend is reachable -```javascript -const healthCheck = await fetch('http://localhost:8080/api/items'); -``` - -### 2. **Authentication Issues** -**Added**: Token validation and preview -```javascript -console.log('๐Ÿ” Token exists:', !!token); -console.log('๐Ÿ” Token preview:', token.substring(0, 20) + '...'); -``` - -### 3. **Request Format Issues** -**Added**: Detailed request logging -```javascript -console.log('๐Ÿ“ Request body:', JSON.stringify({ status: newStatus })); -console.log('๐Ÿ“ Request headers:', headers); -``` - -### 4. **Status Validation Issues** -**Added**: Client-side validation -```javascript -// Validate current status -if (!['AVAILABLE', 'UNAVAILABLE', 'BORROWED'].includes(currentStatus)) { - alert('Invalid current item status'); - return; -} - -// Prevent changing borrowed items -if (currentStatus === 'BORROWED') { - alert('Cannot change status of borrowed items'); - return; -} -``` - -### 5. **Backend Response Issues** -**Added**: Detailed error parsing -```javascript -// Parse different error types -if (directResponse.status === 400) { - errorMessage = 'Bad Request: Invalid status value or request format'; -} else if (directResponse.status === 403) { - errorMessage = 'Forbidden: You are not the owner of this item'; -} else if (directResponse.status === 404) { - errorMessage = 'Not Found: Item not found or endpoint not available'; -} -``` - -## ๐Ÿงช Enhanced Debugging Steps - -### **Step 1: Check Browser Console** -1. Open Developer Tools โ†’ Console -2. Click "Make Unavailable" button -3. Look for these logs: - - `๐Ÿฅ Backend health check for items endpoint: 200` - - `๐Ÿ” Token exists: true` - - `๐Ÿ“ Status change: AVAILABLE โ†’ UNAVAILABLE` - - `๐Ÿ“ก Direct response status: 200` - -### **Step 2: Check Network Tab** -1. Open Developer Tools โ†’ Network tab -2. Filter by "Fetch/XHR" -3. Click the status toggle button -4. Look for PATCH request to `/api/items/{itemId}/status` -5. Check: - - Request headers (Authorization, X-User-Id) - - Request body: `{"status":"UNAVAILABLE"}` - - Response status and body - -### **Step 3: Test Backend Directly** -Test the backend endpoint directly: -```bash -# Replace with actual values -curl -X PATCH http://localhost:8080/api/items/{ITEM_ID}/status \ - -H "Content-Type: application/json" \ - -H "Authorization: Bearer {JWT_TOKEN}" \ - -H "X-User-Id: {USER_ID}" \ - -d '{"status":"UNAVAILABLE"}' -``` - -## ๐Ÿ”ง Common Error Scenarios - -### **400 Bad Request** -**Possible Causes:** -- Invalid status value (not AVAILABLE/UNAVAILABLE/BORROWED) -- Missing Content-Type header -- Malformed JSON body -- Invalid item ID format - -**Debug**: Check request body format and headers - -### **401 Unauthorized** -**Possible Causes:** -- Missing Authorization header -- Invalid/expired JWT token -- Token format incorrect - -**Debug**: Check token existence and format - -### **403 Forbidden** -**Possible Causes:** -- User is not the owner of the item -- Missing X-User-Id header -- X-User-Id doesn't match token user - -**Debug**: Verify user ownership and header presence - -### **404 Not Found** -**Possible Causes:** -- Item doesn't exist -- Invalid item ID -- Backend endpoint not available -- Wrong API URL - -**Debug**: Verify item exists and API URL is correct - -## ๐ŸŽฏ Alternative Implementation - -If the direct approach fails, try using the service layer properly: - -```typescript -// Alternative: Use service layer with better error handling -const handleToggleStatusAlternative = async (itemId: string, currentStatus: string) => { - try { - const newStatus = currentStatus === 'AVAILABLE' ? 'UNAVAILABLE' : 'AVAILABLE'; - - // Use the item service - const updatedItem = await itemService.updateItemStatus(itemId, newStatus, user.id); - - // Update local state - setMyItems(prevItems => - prevItems.map(item => - item.id === itemId ? { ...item, status: updatedItem.status } : item - ) - ); - - alert(`โœ… Item status changed to ${updatedItem.status}`); - } catch (error: any) { - console.error('Service layer error:', error); - - // Extract detailed error information - let errorMessage = 'Failed to update item status'; - if (error?.response?.status === 400) { - errorMessage = 'Invalid request: Check item status and permissions'; - } else if (error?.response?.status === 403) { - errorMessage = 'You are not authorized to modify this item'; - } else if (error?.response?.data?.message) { - errorMessage = error.response.data.message; - } - - alert(`โŒ ${errorMessage}`); - } -}; -``` - -## ๐Ÿ” Backend Validation Checklist - -### **Item Requirements:** -- โœ… Item must exist -- โœ… Item must be owned by current user -- โœ… Item must not be currently borrowed (status !== 'BORROWED') - -### **Request Requirements:** -- โœ… Valid JWT token in Authorization header -- โœ… Correct X-User-Id header -- โœ… Valid status value (AVAILABLE/UNAVAILABLE) -- โœ… Proper JSON format - -### **Permission Requirements:** -- โœ… User must be authenticated -- โœ… User must own the item -- โœ… No active bookings preventing status change - -## ๐Ÿ“ฑ Testing Instructions - -### **Test Valid Status Change:** -1. Find an item with status "AVAILABLE" -2. Click "โธ๏ธ Make Unavailable" -3. Should see success message -4. Item status should change to "UNAVAILABLE" -5. Button should change to "โ–ถ๏ธ Make Available" - -### **Test Error Scenarios:** -1. Try changing status of borrowed item -2. Try with network disconnected -3. Try with invalid authentication - -### **Verify UI Updates:** -1. Status badge color should change -2. Button text should update -3. Local state should reflect change immediately - -## ๐ŸŽฏ Expected Behavior - -### **Success Flow:** -1. User clicks status toggle button -2. Confirmation (optional) -3. API call with proper headers -4. Backend validates ownership and status -5. Backend updates item status -6. Frontend receives updated item -7. UI updates immediately -8. Success message shown - -### **Error Flow:** -1. User clicks status toggle button -2. API call fails with specific error -3. Detailed error message shown -4. UI remains in original state -5. User can retry or fix issue - -The enhanced debugging should now pinpoint exactly where the status toggle is failing! \ No newline at end of file diff --git a/frontend/TOGGLE_FIX_SUMMARY.md b/frontend/TOGGLE_FIX_SUMMARY.md deleted file mode 100644 index 787670e41dca8f403f825007b5e4ea9404513762..0000000000000000000000000000000000000000 --- a/frontend/TOGGLE_FIX_SUMMARY.md +++ /dev/null @@ -1,96 +0,0 @@ -# TOGGLE FIX: Prioritize Specific Status Setting - -## ๐ŸŽฏ **Issue Fixed** - -**Problem:** Clicking "Make Unavailable" was using the toggle endpoint first, which just flips the status regardless of intent. - -**Result:** Item that was already UNAVAILABLE would become AVAILABLE when clicking "Make Unavailable" (opposite of expected behavior). - ---- - -## ๐Ÿ”ง **Solution Applied** - -### **Changed Endpoint Priority Order:** - -**BEFORE (Wrong):** -1. Try `/api/items/{id}/toggle-availability` (just flips status) -2. Fallback to `/api/items/{id}/availability` (sets specific status) - -**AFTER (Fixed):** -1. Try `/api/items/{id}/availability` (sets specific status) โœ… **PREFERRED** -2. Fallback to `/api/items/{id}/toggle-availability` (just flips status) - -### **Why This Fixes It:** - -- **`/availability` endpoint:** Lets us set EXACTLY the status we want (AVAILABLE or UNAVAILABLE) -- **`/toggle-availability` endpoint:** Just flips whatever the current status is - -By using `/availability` first, we ensure: -- โœ… "Make Unavailable" โ†’ Always sets to UNAVAILABLE -- โœ… "Make Available" โ†’ Always sets to AVAILABLE -- โœ… No unwanted toggling behavior - ---- - -## ๐Ÿ“ **Files Updated:** - -### **1. DashboardPage.tsx** -```typescript -// Now tries /availability first (with specific status) -endpointUsed = `/api/items/${itemId}/availability`; -directResponse = await fetch(url, { - method: 'PATCH', - body: JSON.stringify({ status: newStatus }) // Specific status! -}); - -// Only uses toggle as fallback if availability doesn't exist -if (directResponse.status === 404) { - endpointUsed = `/api/items/${itemId}/toggle-availability`; - // ... -} -``` - -### **2. itemService.ts** -```typescript -updateItemStatus: async (itemId: string, status: ItemStatus, userId: string) => { - // Try availability endpoint first (specific status setting) - try { - return await api.patch(`/api/items/${itemId}/availability`, { status }); - } catch (error) { - // Fallback to toggle only if availability doesn't exist - if (error?.response?.status === 404) { - return await api.patch(`/api/items/${itemId}/toggle-availability`); - } - throw error; - } -} -``` - ---- - -## ๐ŸŽฏ **Expected Behavior Now:** - -### **When clicking "Make Unavailable":** -1. Frontend sends: `PATCH /api/items/{id}/availability` with `{"status":"UNAVAILABLE"}` -2. Backend sets item status to UNAVAILABLE (regardless of current status) -3. Button changes to "Make Available" -4. Status badge shows red/unavailable - -### **When clicking "Make Available":** -1. Frontend sends: `PATCH /api/items/{id}/availability` with `{"status":"AVAILABLE"}` -2. Backend sets item status to AVAILABLE (regardless of current status) -3. Button changes to "Make Unavailable" -4. Status badge shows green/available - ---- - -## ๐Ÿงช **Test It:** - -1. **Find an item that shows "AVAILABLE"** -2. **Click "Make Unavailable"** - - Should change to UNAVAILABLE and button becomes "Make Available" -3. **Click "Make Available"** - - Should change to AVAILABLE and button becomes "Make Unavailable" -4. **Repeat several times** - should work predictably every time - -**No more unwanted toggling!** The button will now do exactly what it says. \ No newline at end of file diff --git a/frontend/TOGGLE_SIMPLIFIED.md b/frontend/TOGGLE_SIMPLIFIED.md deleted file mode 100644 index f123117bc60393973a576a847b0368b4569651eb..0000000000000000000000000000000000000000 --- a/frontend/TOGGLE_SIMPLIFIED.md +++ /dev/null @@ -1,95 +0,0 @@ -# SIMPLIFIED: Using Toggle Endpoint Only - -## ๐ŸŽฏ **Simplification Applied** - -**Removed:** Complex endpoint fallback logic with `/availability` endpoint -**Using:** Only the `/api/items/{id}/toggle-availability` endpoint - ---- - -## ๐Ÿ”ง **What Changed** - -### **BEFORE (Complex with fallbacks):** -```typescript -// Try /availability endpoint first with { isAvailable: boolean } -// Fallback to /toggle-availability if 404 -// Handle different request body formats -// Complex error handling -``` - -### **AFTER (Simple toggle only):** -```typescript -// Just call /toggle-availability -// No request body needed -// Let backend handle the toggle logic -// Much simpler! -``` - ---- - -## ๐Ÿ“ **Files Updated:** - -### **1. itemService.ts** -```typescript -// BEFORE: Complex dual-endpoint logic -updateItemStatus: async (itemId: string, status: ItemStatus, userId: string) => { - // Try availability endpoint... - // Fallback to toggle... -} - -// AFTER: Simple toggle only -updateItemStatus: async (itemId: string, _status: ItemStatus, userId: string) => { - return await api.patch(`/api/items/${itemId}/toggle-availability`, null, { - headers: addUserIdHeader(userId) - }); -} -``` - -### **2. DashboardPage.tsx** -```typescript -// BEFORE: Try availability, then toggle -endpointUsed = `/api/items/${itemId}/availability`; -// ... complex fallback logic - -// AFTER: Just toggle -endpointUsed = `/api/items/${itemId}/toggle-availability`; -// No body needed, just PATCH request -``` - ---- - -## ๐ŸŽฏ **How It Works Now:** - -### **When clicking "Make Unavailable":** -1. Frontend calls: `PATCH /api/items/{id}/toggle-availability` -2. Backend looks at current status and flips it -3. If currently AVAILABLE โ†’ becomes UNAVAILABLE -4. Returns updated item with new status -5. Frontend updates UI - -### **When clicking "Make Available":** -1. Frontend calls: `PATCH /api/items/{id}/toggle-availability` -2. Backend looks at current status and flips it -3. If currently UNAVAILABLE โ†’ becomes AVAILABLE -4. Returns updated item with new status -5. Frontend updates UI - ---- - -## โœ… **Benefits:** - -- **โœ… Simpler code** - No complex endpoint fallback logic -- **โœ… Less error prone** - One endpoint, one request format -- **โœ… Backend handles logic** - Let the backend decide what "toggle" means -- **โœ… No field name issues** - No request body means no JSON parsing errors - ---- - -## ๐Ÿงช **Test It:** - -1. Go to Dashboard โ†’ My Items -2. Click any status toggle button -3. Should work smoothly with just the toggle endpoint -4. Button text and status should update correctly - -**Much simpler and should work reliably now!** \ No newline at end of file diff --git a/frontend/VALIDATION_FIXES.md b/frontend/VALIDATION_FIXES.md deleted file mode 100644 index 54f550ba86a53cfd491940e30b7e7931cfcfa6ac..0000000000000000000000000000000000000000 --- a/frontend/VALIDATION_FIXES.md +++ /dev/null @@ -1,112 +0,0 @@ -# Registration Validation Fixes - -## Issues Found and Fixed - -### 1. Frontend Validation Mismatches -**FIXED** - The following validation rules were corrected to match backend requirements: - -#### Username Field โœ… -- **Requirement**: 3-50 characters, alphanumeric -- **Status**: Already correct in frontend - -#### Full Name Field โŒโžก๏ธโœ… -- **Backend Requirement**: 2-100 characters, required -- **Frontend Before**: No length validation -- **Frontend After**: Added `minLength={2}` and `maxLength={100}` - -#### Email Field โœ… -- **Requirement**: Valid email format, required -- **Status**: Already correct with `type="email"` - -#### Password Field โŒโžก๏ธโœ… -- **Backend Requirement**: Minimum 8 characters -- **Frontend Before**: `minLength={6}` -- **Frontend After**: Changed to `minLength={8}` + updated label - -#### Phone Number Field โŒโžก๏ธโœ… -- **Backend Requirement**: Optional field -- **Frontend Before**: `required` attribute set -- **Frontend After**: Removed `required`, updated label to "(Optional)" - -#### Confirm Password Field โŒโžก๏ธโœ… -- **Frontend Before**: `minLength={6}` -- **Frontend After**: Changed to `minLength={8}` to match password field - -### 2. Client-Side Validation Enhancement -**ADDED** - Comprehensive validation in `handleSubmit` function: - -```typescript -// Client-side validation to match backend requirements -const validationErrors: string[] = []; - -if (formData.username.length < 3 || formData.username.length > 50) { - validationErrors.push('Username must be between 3 and 50 characters'); -} - -if (formData.name.length < 2 || formData.name.length > 100) { - validationErrors.push('Full name must be between 2 and 100 characters'); -} - -if (formData.password.length < 8) { - validationErrors.push('Password must be at least 8 characters long'); -} - -if (formData.password !== formData.confirmPassword) { - validationErrors.push('Passwords do not match'); -} - -const emailRegex = /^[^\s@]+@[^\s@]+\.[^\s@]+$/; -if (!emailRegex.test(formData.email)) { - validationErrors.push('Please enter a valid email address'); -} -``` - -### 3. Backend Connectivity Issue -**IDENTIFIED** - Backend is not running: -- The `docker-compose.yml` has the backend service commented out -- This means when you try to register, the API call fails with network errors - -## Testing the Registration - -### Method 1: Test with Name "user" (Should Work Now) -1. Username: `testuser` (3+ characters) -2. Full Name: `user` (2+ characters - now valid!) -3. Email: `test@example.com` -4. Password: `password123` (8+ characters) -5. Phone: Leave empty (now optional) - -### Method 2: Start Backend First -To actually register and connect to backend: - -1. **Option A: Start Backend via Docker** - ```bash - # If you have backend docker image - docker run -p 8080:8080 locallend/backend:latest - ``` - -2. **Option B: Enable Backend in docker-compose** - - Uncomment the backend service in `docker-compose.yml` - - Run: `docker-compose up` - -3. **Option C: Check if Backend is Running** - - Use the "Test Backend Connection" button in the registration page - - Try alternative ports (8081, 8082, etc.) using the port test buttons - -## Validation Rules Summary (Now Aligned) - -| Field | Frontend | Backend | Status | -|-------|----------|---------|---------| -| Username | 3-50 chars, required | 3-50 chars, alphanumeric, unique | โœ… Aligned | -| Name | 2-100 chars, required | 2-100 chars, required | โœ… Fixed | -| Email | Valid email, required | Valid email, unique | โœ… Aligned | -| Password | 8+ chars, required | Min 8 chars, required | โœ… Fixed | -| Phone | Optional | Optional | โœ… Fixed | - -## Next Steps - -1. **Test the registration form** with name "user" - it should now work on the frontend -2. **Start the backend** to test full registration flow -3. **Check browser console** for detailed API call logs -4. **Use the connection test buttons** to verify backend connectivity - -The frontend validation now matches the backend requirements from the integration guide! \ No newline at end of file diff --git a/frontend/eslint.config.js b/frontend/eslint.config.js deleted file mode 100644 index b19330b103a5dff2c35a37637addb0cc87e9ac9f..0000000000000000000000000000000000000000 --- a/frontend/eslint.config.js +++ /dev/null @@ -1,23 +0,0 @@ -import js from '@eslint/js' -import globals from 'globals' -import reactHooks from 'eslint-plugin-react-hooks' -import reactRefresh from 'eslint-plugin-react-refresh' -import tseslint from 'typescript-eslint' -import { defineConfig, globalIgnores } from 'eslint/config' - -export default defineConfig([ - globalIgnores(['dist']), - { - files: ['**/*.{ts,tsx}'], - extends: [ - js.configs.recommended, - tseslint.configs.recommended, - reactHooks.configs['recommended-latest'], - reactRefresh.configs.vite, - ], - languageOptions: { - ecmaVersion: 2020, - globals: globals.browser, - }, - }, -]) diff --git a/frontend/healthcheck.sh b/frontend/healthcheck.sh deleted file mode 100644 index 74b2ae24bc9531003f2dafe00b98c681b1aabac0..0000000000000000000000000000000000000000 --- a/frontend/healthcheck.sh +++ /dev/null @@ -1,11 +0,0 @@ -#!/bin/sh -# Health check script for LocalLend Frontend container - -# Check if Nginx is running and serving content -if wget --quiet --tries=1 --spider http://localhost:80/; then - echo "Health check passed: Frontend is responding" - exit 0 -else - echo "Health check failed: Frontend is not responding" - exit 1 -fi \ No newline at end of file diff --git a/frontend/index.html b/frontend/index.html deleted file mode 100644 index 25e1ef4240808a346c71f66107ed74840275fa8e..0000000000000000000000000000000000000000 --- a/frontend/index.html +++ /dev/null @@ -1,13 +0,0 @@ - - - - - - - locallend-frontend - - -
- - - diff --git a/frontend/nginx.conf b/frontend/nginx.conf deleted file mode 100644 index 84f2b85d113382776fe5113facd184fbe1573f0a..0000000000000000000000000000000000000000 --- a/frontend/nginx.conf +++ /dev/null @@ -1,52 +0,0 @@ -server { - listen 0.0.0.0:7860; - server_name _; - root /usr/share/nginx/html; - index index.html; - - # Handle client-side routing (for React Router) - location / { - try_files $uri $uri/ /index.html; - } - - # Proxy API requests to Spring Boot backend running on port 8080 - location /api/ { - proxy_pass http://localhost:8080/; - proxy_set_header Host $host; - proxy_set_header X-Real-IP $remote_addr; - proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for; - proxy_set_header X-Forwarded-Proto $scheme; - - # WebSocket support (if needed) - proxy_http_version 1.1; - proxy_set_header Upgrade $http_upgrade; - proxy_set_header Connection "upgrade"; - - # Timeout settings - proxy_connect_timeout 60s; - proxy_send_timeout 60s; - proxy_read_timeout 60s; - } - - # Health check endpoint for HF Spaces - location /health { - access_log off; - return 200 "healthy\n"; - add_header Content-Type text/plain; - } - - # Cache static assets - location ~* \.(js|css|png|jpg|jpeg|gif|ico|svg)$ { - expires 1y; - add_header Cache-Control "public, immutable"; - } - - # Security headers - add_header X-Frame-Options "SAMEORIGIN" always; - add_header X-Content-Type-Options "nosniff" always; - add_header X-XSS-Protection "1; mode=block" always; - - # Gzip compression - gzip on; - gzip_types text/plain text/css application/json application/javascript text/xml application/xml application/xml+rss text/javascript; -} diff --git a/frontend/package-lock.json b/frontend/package-lock.json deleted file mode 100644 index 498038cdcc3b2a6ecf243c9b547cd0a5c93fb516..0000000000000000000000000000000000000000 --- a/frontend/package-lock.json +++ /dev/null @@ -1,3289 +0,0 @@ -{ - "name": "locallend-frontend", - "version": "0.0.0", - "lockfileVersion": 3, - "requires": true, - "packages": { - "": { - "name": "locallend-frontend", - "version": "0.0.0", - "dependencies": { - "@emotion/react": "^11.13.3", - "@emotion/styled": "^11.13.0", - "@hookform/resolvers": "^3.9.1", - "@mui/icons-material": "^6.1.7", - "@mui/material": "^6.1.7", - "@mui/x-date-pickers": "^7.20.0", - "axios": "^1.7.7", - "dayjs": "^1.11.13", - "react": "^19.1.1", - "react-dom": "^19.1.1", - "react-hook-form": "^7.53.2", - "react-hot-toast": "^2.4.1", - "react-router-dom": "^6.27.0", - "yup": "^1.4.0" - }, - "devDependencies": { - "@eslint/js": "^9.36.0", - "@types/node": "^24.6.0", - "@types/react": "^19.1.16", - "@types/react-dom": "^19.1.9", - "@vitejs/plugin-react": "^5.0.4", - "eslint": "^9.36.0", - "eslint-plugin-react-hooks": "^5.2.0", - "eslint-plugin-react-refresh": "^0.4.22", - "globals": "^16.4.0", - "typescript": "~5.9.3", - "typescript-eslint": "^8.45.0", - "vite": "^7.1.7" - } - }, - "node_modules/@babel/code-frame": { - "version": "7.27.1", - "license": "MIT", - "dependencies": { - "@babel/helper-validator-identifier": "^7.27.1", - "js-tokens": "^4.0.0", - "picocolors": "^1.1.1" - }, - "engines": { - "node": ">=6.9.0" - } - }, - "node_modules/@babel/compat-data": { - "version": "7.28.5", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=6.9.0" - } - }, - "node_modules/@babel/core": { - "version": "7.28.5", - "dev": true, - "license": "MIT", - "dependencies": { - "@babel/code-frame": "^7.27.1", - "@babel/generator": "^7.28.5", - "@babel/helper-compilation-targets": "^7.27.2", - "@babel/helper-module-transforms": "^7.28.3", - "@babel/helpers": "^7.28.4", - "@babel/parser": "^7.28.5", - "@babel/template": "^7.27.2", - "@babel/traverse": "^7.28.5", - "@babel/types": "^7.28.5", - "@jridgewell/remapping": "^2.3.5", - "convert-source-map": "^2.0.0", - "debug": "^4.1.0", - "gensync": "^1.0.0-beta.2", - "json5": "^2.2.3", - "semver": "^6.3.1" - }, - "engines": { - "node": ">=6.9.0" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/babel" - } - }, - "node_modules/@babel/core/node_modules/convert-source-map": { - "version": "2.0.0", - "dev": true, - "license": "MIT" - }, - "node_modules/@babel/generator": { - "version": "7.28.5", - "license": "MIT", - "dependencies": { - "@babel/parser": "^7.28.5", - "@babel/types": "^7.28.5", - "@jridgewell/gen-mapping": "^0.3.12", - "@jridgewell/trace-mapping": "^0.3.28", - "jsesc": "^3.0.2" - }, - "engines": { - "node": ">=6.9.0" - } - }, - "node_modules/@babel/helper-compilation-targets": { - "version": "7.27.2", - "dev": true, - "license": "MIT", - "dependencies": { - "@babel/compat-data": "^7.27.2", - "@babel/helper-validator-option": "^7.27.1", - "browserslist": "^4.24.0", - "lru-cache": "^5.1.1", - "semver": "^6.3.1" - }, - "engines": { - "node": ">=6.9.0" - } - }, - "node_modules/@babel/helper-globals": { - "version": "7.28.0", - "license": "MIT", - "engines": { - "node": ">=6.9.0" - } - }, - "node_modules/@babel/helper-module-imports": { - "version": "7.27.1", - "license": "MIT", - "dependencies": { - "@babel/traverse": "^7.27.1", - "@babel/types": "^7.27.1" - }, - "engines": { - "node": ">=6.9.0" - } - }, - "node_modules/@babel/helper-module-transforms": { - "version": "7.28.3", - "dev": true, - "license": "MIT", - "dependencies": { - "@babel/helper-module-imports": "^7.27.1", - "@babel/helper-validator-identifier": "^7.27.1", - "@babel/traverse": "^7.28.3" - }, - "engines": { - "node": ">=6.9.0" - }, - "peerDependencies": { - "@babel/core": "^7.0.0" - } - }, - "node_modules/@babel/helper-plugin-utils": { - "version": "7.27.1", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=6.9.0" - } - }, - "node_modules/@babel/helper-string-parser": { - "version": "7.27.1", - "license": "MIT", - "engines": { - "node": ">=6.9.0" - } - }, - "node_modules/@babel/helper-validator-identifier": { - "version": "7.28.5", - "license": "MIT", - "engines": { - "node": ">=6.9.0" - } - }, - "node_modules/@babel/helper-validator-option": { - "version": "7.27.1", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=6.9.0" - } - }, - "node_modules/@babel/helpers": { - "version": "7.28.4", - "dev": true, - "license": "MIT", - "dependencies": { - "@babel/template": "^7.27.2", - "@babel/types": "^7.28.4" - }, - "engines": { - "node": ">=6.9.0" - } - }, - "node_modules/@babel/parser": { - "version": "7.28.5", - "license": "MIT", - "dependencies": { - "@babel/types": "^7.28.5" - }, - "bin": { - "parser": "bin/babel-parser.js" - }, - "engines": { - "node": ">=6.0.0" - } - }, - "node_modules/@babel/plugin-transform-react-jsx-self": { - "version": "7.27.1", - "dev": true, - "license": "MIT", - "dependencies": { - "@babel/helper-plugin-utils": "^7.27.1" - }, - "engines": { - "node": ">=6.9.0" - }, - "peerDependencies": { - "@babel/core": "^7.0.0-0" - } - }, - "node_modules/@babel/plugin-transform-react-jsx-source": { - "version": "7.27.1", - "dev": true, - "license": "MIT", - "dependencies": { - "@babel/helper-plugin-utils": "^7.27.1" - }, - "engines": { - "node": ">=6.9.0" - }, - "peerDependencies": { - "@babel/core": "^7.0.0-0" - } - }, - "node_modules/@babel/runtime": { - "version": "7.28.4", - "license": "MIT", - "engines": { - "node": ">=6.9.0" - } - }, - "node_modules/@babel/template": { - "version": "7.27.2", - "license": "MIT", - "dependencies": { - "@babel/code-frame": "^7.27.1", - "@babel/parser": "^7.27.2", - "@babel/types": "^7.27.1" - }, - "engines": { - "node": ">=6.9.0" - } - }, - "node_modules/@babel/traverse": { - "version": "7.28.5", - "license": "MIT", - "dependencies": { - "@babel/code-frame": "^7.27.1", - "@babel/generator": "^7.28.5", - "@babel/helper-globals": "^7.28.0", - "@babel/parser": "^7.28.5", - "@babel/template": "^7.27.2", - "@babel/types": "^7.28.5", - "debug": "^4.3.1" - }, - "engines": { - "node": ">=6.9.0" - } - }, - "node_modules/@babel/types": { - "version": "7.28.5", - "license": "MIT", - "dependencies": { - "@babel/helper-string-parser": "^7.27.1", - "@babel/helper-validator-identifier": "^7.28.5" - }, - "engines": { - "node": ">=6.9.0" - } - }, - "node_modules/@emotion/babel-plugin": { - "version": "11.13.5", - "license": "MIT", - "dependencies": { - "@babel/helper-module-imports": "^7.16.7", - "@babel/runtime": "^7.18.3", - "@emotion/hash": "^0.9.2", - "@emotion/memoize": "^0.9.0", - "@emotion/serialize": "^1.3.3", - "babel-plugin-macros": "^3.1.0", - "convert-source-map": "^1.5.0", - "escape-string-regexp": "^4.0.0", - "find-root": "^1.1.0", - "source-map": "^0.5.7", - "stylis": "4.2.0" - } - }, - "node_modules/@emotion/cache": { - "version": "11.14.0", - "license": "MIT", - "dependencies": { - "@emotion/memoize": "^0.9.0", - "@emotion/sheet": "^1.4.0", - "@emotion/utils": "^1.4.2", - "@emotion/weak-memoize": "^0.4.0", - "stylis": "4.2.0" - } - }, - "node_modules/@emotion/hash": { - "version": "0.9.2", - "license": "MIT" - }, - "node_modules/@emotion/is-prop-valid": { - "version": "1.4.0", - "license": "MIT", - "dependencies": { - "@emotion/memoize": "^0.9.0" - } - }, - "node_modules/@emotion/memoize": { - "version": "0.9.0", - "license": "MIT" - }, - "node_modules/@emotion/react": { - "version": "11.14.0", - "license": "MIT", - "dependencies": { - "@babel/runtime": "^7.18.3", - "@emotion/babel-plugin": "^11.13.5", - "@emotion/cache": "^11.14.0", - "@emotion/serialize": "^1.3.3", - "@emotion/use-insertion-effect-with-fallbacks": "^1.2.0", - "@emotion/utils": "^1.4.2", - "@emotion/weak-memoize": "^0.4.0", - "hoist-non-react-statics": "^3.3.1" - }, - "peerDependencies": { - "react": ">=16.8.0" - }, - "peerDependenciesMeta": { - "@types/react": { - "optional": true - } - } - }, - "node_modules/@emotion/serialize": { - "version": "1.3.3", - "license": "MIT", - "dependencies": { - "@emotion/hash": "^0.9.2", - "@emotion/memoize": "^0.9.0", - "@emotion/unitless": "^0.10.0", - "@emotion/utils": "^1.4.2", - "csstype": "^3.0.2" - } - }, - "node_modules/@emotion/sheet": { - "version": "1.4.0", - "license": "MIT" - }, - "node_modules/@emotion/styled": { - "version": "11.14.1", - "license": "MIT", - "dependencies": { - "@babel/runtime": "^7.18.3", - "@emotion/babel-plugin": "^11.13.5", - "@emotion/is-prop-valid": "^1.3.0", - "@emotion/serialize": "^1.3.3", - "@emotion/use-insertion-effect-with-fallbacks": "^1.2.0", - "@emotion/utils": "^1.4.2" - }, - "peerDependencies": { - "@emotion/react": "^11.0.0-rc.0", - "react": ">=16.8.0" - }, - "peerDependenciesMeta": { - "@types/react": { - "optional": true - } - } - }, - "node_modules/@emotion/unitless": { - "version": "0.10.0", - "license": "MIT" - }, - "node_modules/@emotion/use-insertion-effect-with-fallbacks": { - "version": "1.2.0", - "license": "MIT", - "peerDependencies": { - "react": ">=16.8.0" - } - }, - "node_modules/@emotion/utils": { - "version": "1.4.2", - "license": "MIT" - }, - "node_modules/@emotion/weak-memoize": { - "version": "0.4.0", - "license": "MIT" - }, - "node_modules/@esbuild/linux-x64": { - "version": "0.25.12", - "cpu": [ - "x64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "linux" - ], - "engines": { - "node": ">=18" - } - }, - "node_modules/@eslint-community/eslint-utils": { - "version": "4.9.0", - "dev": true, - "license": "MIT", - "dependencies": { - "eslint-visitor-keys": "^3.4.3" - }, - "engines": { - "node": "^12.22.0 || ^14.17.0 || >=16.0.0" - }, - "funding": { - "url": "https://opencollective.com/eslint" - }, - "peerDependencies": { - "eslint": "^6.0.0 || ^7.0.0 || >=8.0.0" - } - }, - "node_modules/@eslint-community/eslint-utils/node_modules/eslint-visitor-keys": { - "version": "3.4.3", - "dev": true, - "license": "Apache-2.0", - "engines": { - "node": "^12.22.0 || ^14.17.0 || >=16.0.0" - }, - "funding": { - "url": "https://opencollective.com/eslint" - } - }, - "node_modules/@eslint-community/regexpp": { - "version": "4.12.2", - "dev": true, - "license": "MIT", - "engines": { - "node": "^12.0.0 || ^14.0.0 || >=16.0.0" - } - }, - "node_modules/@eslint/config-array": { - "version": "0.21.1", - "dev": true, - "license": "Apache-2.0", - "dependencies": { - "@eslint/object-schema": "^2.1.7", - "debug": "^4.3.1", - "minimatch": "^3.1.2" - }, - "engines": { - "node": "^18.18.0 || ^20.9.0 || >=21.1.0" - } - }, - "node_modules/@eslint/config-helpers": { - "version": "0.4.2", - "dev": true, - "license": "Apache-2.0", - "dependencies": { - "@eslint/core": "^0.17.0" - }, - "engines": { - "node": "^18.18.0 || ^20.9.0 || >=21.1.0" - } - }, - "node_modules/@eslint/core": { - "version": "0.17.0", - "dev": true, - "license": "Apache-2.0", - "dependencies": { - "@types/json-schema": "^7.0.15" - }, - "engines": { - "node": "^18.18.0 || ^20.9.0 || >=21.1.0" - } - }, - "node_modules/@eslint/eslintrc": { - "version": "3.3.1", - "dev": true, - "license": "MIT", - "dependencies": { - "ajv": "^6.12.4", - "debug": "^4.3.2", - "espree": "^10.0.1", - "globals": "^14.0.0", - "ignore": "^5.2.0", - "import-fresh": "^3.2.1", - "js-yaml": "^4.1.0", - "minimatch": "^3.1.2", - "strip-json-comments": "^3.1.1" - }, - "engines": { - "node": "^18.18.0 || ^20.9.0 || >=21.1.0" - }, - "funding": { - "url": "https://opencollective.com/eslint" - } - }, - "node_modules/@eslint/eslintrc/node_modules/globals": { - "version": "14.0.0", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=18" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/@eslint/js": { - "version": "9.39.1", - "dev": true, - "license": "MIT", - "engines": { - "node": "^18.18.0 || ^20.9.0 || >=21.1.0" - }, - "funding": { - "url": "https://eslint.org/donate" - } - }, - "node_modules/@eslint/object-schema": { - "version": "2.1.7", - "dev": true, - "license": "Apache-2.0", - "engines": { - "node": "^18.18.0 || ^20.9.0 || >=21.1.0" - } - }, - "node_modules/@eslint/plugin-kit": { - "version": "0.4.1", - "dev": true, - "license": "Apache-2.0", - "dependencies": { - "@eslint/core": "^0.17.0", - "levn": "^0.4.1" - }, - "engines": { - "node": "^18.18.0 || ^20.9.0 || >=21.1.0" - } - }, - "node_modules/@hookform/resolvers": { - "version": "3.10.0", - "license": "MIT", - "peerDependencies": { - "react-hook-form": "^7.0.0" - } - }, - "node_modules/@humanfs/core": { - "version": "0.19.1", - "dev": true, - "license": "Apache-2.0", - "engines": { - "node": ">=18.18.0" - } - }, - "node_modules/@humanfs/node": { - "version": "0.16.7", - "dev": true, - "license": "Apache-2.0", - "dependencies": { - "@humanfs/core": "^0.19.1", - "@humanwhocodes/retry": "^0.4.0" - }, - "engines": { - "node": ">=18.18.0" - } - }, - "node_modules/@humanwhocodes/module-importer": { - "version": "1.0.1", - "dev": true, - "license": "Apache-2.0", - "engines": { - "node": ">=12.22" - }, - "funding": { - "type": "github", - "url": "https://github.com/sponsors/nzakas" - } - }, - "node_modules/@humanwhocodes/retry": { - "version": "0.4.3", - "dev": true, - "license": "Apache-2.0", - "engines": { - "node": ">=18.18" - }, - "funding": { - "type": "github", - "url": "https://github.com/sponsors/nzakas" - } - }, - "node_modules/@jridgewell/gen-mapping": { - "version": "0.3.13", - "license": "MIT", - "dependencies": { - "@jridgewell/sourcemap-codec": "^1.5.0", - "@jridgewell/trace-mapping": "^0.3.24" - } - }, - "node_modules/@jridgewell/remapping": { - "version": "2.3.5", - "dev": true, - "license": "MIT", - "dependencies": { - "@jridgewell/gen-mapping": "^0.3.5", - "@jridgewell/trace-mapping": "^0.3.24" - } - }, - "node_modules/@jridgewell/resolve-uri": { - "version": "3.1.2", - "license": "MIT", - "engines": { - "node": ">=6.0.0" - } - }, - "node_modules/@jridgewell/sourcemap-codec": { - "version": "1.5.5", - "license": "MIT" - }, - "node_modules/@jridgewell/trace-mapping": { - "version": "0.3.31", - "license": "MIT", - "dependencies": { - "@jridgewell/resolve-uri": "^3.1.0", - "@jridgewell/sourcemap-codec": "^1.4.14" - } - }, - "node_modules/@mui/core-downloads-tracker": { - "version": "6.5.0", - "license": "MIT", - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/mui-org" - } - }, - "node_modules/@mui/icons-material": { - "version": "6.5.0", - "license": "MIT", - "dependencies": { - "@babel/runtime": "^7.26.0" - }, - "engines": { - "node": ">=14.0.0" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/mui-org" - }, - "peerDependencies": { - "@mui/material": "^6.5.0", - "@types/react": "^17.0.0 || ^18.0.0 || ^19.0.0", - "react": "^17.0.0 || ^18.0.0 || ^19.0.0" - }, - "peerDependenciesMeta": { - "@types/react": { - "optional": true - } - } - }, - "node_modules/@mui/material": { - "version": "6.5.0", - "license": "MIT", - "dependencies": { - "@babel/runtime": "^7.26.0", - "@mui/core-downloads-tracker": "^6.5.0", - "@mui/system": "^6.5.0", - "@mui/types": "~7.2.24", - "@mui/utils": "^6.4.9", - "@popperjs/core": "^2.11.8", - "@types/react-transition-group": "^4.4.12", - "clsx": "^2.1.1", - "csstype": "^3.1.3", - "prop-types": "^15.8.1", - "react-is": "^19.0.0", - "react-transition-group": "^4.4.5" - }, - "engines": { - "node": ">=14.0.0" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/mui-org" - }, - "peerDependencies": { - "@emotion/react": "^11.5.0", - "@emotion/styled": "^11.3.0", - "@mui/material-pigment-css": "^6.5.0", - "@types/react": "^17.0.0 || ^18.0.0 || ^19.0.0", - "react": "^17.0.0 || ^18.0.0 || ^19.0.0", - "react-dom": "^17.0.0 || ^18.0.0 || ^19.0.0" - }, - "peerDependenciesMeta": { - "@emotion/react": { - "optional": true - }, - "@emotion/styled": { - "optional": true - }, - "@mui/material-pigment-css": { - "optional": true - }, - "@types/react": { - "optional": true - } - } - }, - "node_modules/@mui/private-theming": { - "version": "6.4.9", - "license": "MIT", - "dependencies": { - "@babel/runtime": "^7.26.0", - "@mui/utils": "^6.4.9", - "prop-types": "^15.8.1" - }, - "engines": { - "node": ">=14.0.0" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/mui-org" - }, - "peerDependencies": { - "@types/react": "^17.0.0 || ^18.0.0 || ^19.0.0", - "react": "^17.0.0 || ^18.0.0 || ^19.0.0" - }, - "peerDependenciesMeta": { - "@types/react": { - "optional": true - } - } - }, - "node_modules/@mui/styled-engine": { - "version": "6.5.0", - "license": "MIT", - "dependencies": { - "@babel/runtime": "^7.26.0", - "@emotion/cache": "^11.13.5", - "@emotion/serialize": "^1.3.3", - "@emotion/sheet": "^1.4.0", - "csstype": "^3.1.3", - "prop-types": "^15.8.1" - }, - "engines": { - "node": ">=14.0.0" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/mui-org" - }, - "peerDependencies": { - "@emotion/react": "^11.4.1", - "@emotion/styled": "^11.3.0", - "react": "^17.0.0 || ^18.0.0 || ^19.0.0" - }, - "peerDependenciesMeta": { - "@emotion/react": { - "optional": true - }, - "@emotion/styled": { - "optional": true - } - } - }, - "node_modules/@mui/system": { - "version": "6.5.0", - "license": "MIT", - "dependencies": { - "@babel/runtime": "^7.26.0", - "@mui/private-theming": "^6.4.9", - "@mui/styled-engine": "^6.5.0", - "@mui/types": "~7.2.24", - "@mui/utils": "^6.4.9", - "clsx": "^2.1.1", - "csstype": "^3.1.3", - "prop-types": "^15.8.1" - }, - "engines": { - "node": ">=14.0.0" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/mui-org" - }, - "peerDependencies": { - "@emotion/react": "^11.5.0", - "@emotion/styled": "^11.3.0", - "@types/react": "^17.0.0 || ^18.0.0 || ^19.0.0", - "react": "^17.0.0 || ^18.0.0 || ^19.0.0" - }, - "peerDependenciesMeta": { - "@emotion/react": { - "optional": true - }, - "@emotion/styled": { - "optional": true - }, - "@types/react": { - "optional": true - } - } - }, - "node_modules/@mui/types": { - "version": "7.2.24", - "license": "MIT", - "peerDependencies": { - "@types/react": "^17.0.0 || ^18.0.0 || ^19.0.0" - }, - "peerDependenciesMeta": { - "@types/react": { - "optional": true - } - } - }, - "node_modules/@mui/utils": { - "version": "6.4.9", - "license": "MIT", - "dependencies": { - "@babel/runtime": "^7.26.0", - "@mui/types": "~7.2.24", - "@types/prop-types": "^15.7.14", - "clsx": "^2.1.1", - "prop-types": "^15.8.1", - "react-is": "^19.0.0" - }, - "engines": { - "node": ">=14.0.0" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/mui-org" - }, - "peerDependencies": { - "@types/react": "^17.0.0 || ^18.0.0 || ^19.0.0", - "react": "^17.0.0 || ^18.0.0 || ^19.0.0" - }, - "peerDependenciesMeta": { - "@types/react": { - "optional": true - } - } - }, - "node_modules/@mui/x-date-pickers": { - "version": "7.29.4", - "license": "MIT", - "dependencies": { - "@babel/runtime": "^7.25.7", - "@mui/utils": "^5.16.6 || ^6.0.0 || ^7.0.0", - "@mui/x-internals": "7.29.0", - "@types/react-transition-group": "^4.4.11", - "clsx": "^2.1.1", - "prop-types": "^15.8.1", - "react-transition-group": "^4.4.5" - }, - "engines": { - "node": ">=14.0.0" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/mui-org" - }, - "peerDependencies": { - "@emotion/react": "^11.9.0", - "@emotion/styled": "^11.8.1", - "@mui/material": "^5.15.14 || ^6.0.0 || ^7.0.0", - "@mui/system": "^5.15.14 || ^6.0.0 || ^7.0.0", - "date-fns": "^2.25.0 || ^3.2.0 || ^4.0.0", - "date-fns-jalali": "^2.13.0-0 || ^3.2.0-0 || ^4.0.0-0", - "dayjs": "^1.10.7", - "luxon": "^3.0.2", - "moment": "^2.29.4", - "moment-hijri": "^2.1.2 || ^3.0.0", - "moment-jalaali": "^0.7.4 || ^0.8.0 || ^0.9.0 || ^0.10.0", - "react": "^17.0.0 || ^18.0.0 || ^19.0.0", - "react-dom": "^17.0.0 || ^18.0.0 || ^19.0.0" - }, - "peerDependenciesMeta": { - "@emotion/react": { - "optional": true - }, - "@emotion/styled": { - "optional": true - }, - "date-fns": { - "optional": true - }, - "date-fns-jalali": { - "optional": true - }, - "dayjs": { - "optional": true - }, - "luxon": { - "optional": true - }, - "moment": { - "optional": true - }, - "moment-hijri": { - "optional": true - }, - "moment-jalaali": { - "optional": true - } - } - }, - "node_modules/@mui/x-internals": { - "version": "7.29.0", - "license": "MIT", - "dependencies": { - "@babel/runtime": "^7.25.7", - "@mui/utils": "^5.16.6 || ^6.0.0 || ^7.0.0" - }, - "engines": { - "node": ">=14.0.0" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/mui-org" - }, - "peerDependencies": { - "react": "^17.0.0 || ^18.0.0 || ^19.0.0" - } - }, - "node_modules/@nodelib/fs.scandir": { - "version": "2.1.5", - "dev": true, - "license": "MIT", - "dependencies": { - "@nodelib/fs.stat": "2.0.5", - "run-parallel": "^1.1.9" - }, - "engines": { - "node": ">= 8" - } - }, - "node_modules/@nodelib/fs.stat": { - "version": "2.0.5", - "dev": true, - "license": "MIT", - "engines": { - "node": ">= 8" - } - }, - "node_modules/@nodelib/fs.walk": { - "version": "1.2.8", - "dev": true, - "license": "MIT", - "dependencies": { - "@nodelib/fs.scandir": "2.1.5", - "fastq": "^1.6.0" - }, - "engines": { - "node": ">= 8" - } - }, - "node_modules/@popperjs/core": { - "version": "2.11.8", - "license": "MIT", - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/popperjs" - } - }, - "node_modules/@remix-run/router": { - "version": "1.23.1", - "license": "MIT", - "engines": { - "node": ">=14.0.0" - } - }, - "node_modules/@rolldown/pluginutils": { - "version": "1.0.0-beta.47", - "dev": true, - "license": "MIT" - }, - "node_modules/@rollup/rollup-linux-x64-gnu": { - "version": "4.53.2", - "cpu": [ - "x64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "linux" - ] - }, - "node_modules/@types/babel__core": { - "version": "7.20.5", - "dev": true, - "license": "MIT", - "dependencies": { - "@babel/parser": "^7.20.7", - "@babel/types": "^7.20.7", - "@types/babel__generator": "*", - "@types/babel__template": "*", - "@types/babel__traverse": "*" - } - }, - "node_modules/@types/babel__generator": { - "version": "7.27.0", - "dev": true, - "license": "MIT", - "dependencies": { - "@babel/types": "^7.0.0" - } - }, - "node_modules/@types/babel__template": { - "version": "7.4.4", - "dev": true, - "license": "MIT", - "dependencies": { - "@babel/parser": "^7.1.0", - "@babel/types": "^7.0.0" - } - }, - "node_modules/@types/babel__traverse": { - "version": "7.28.0", - "dev": true, - "license": "MIT", - "dependencies": { - "@babel/types": "^7.28.2" - } - }, - "node_modules/@types/estree": { - "version": "1.0.8", - "dev": true, - "license": "MIT" - }, - "node_modules/@types/json-schema": { - "version": "7.0.15", - "dev": true, - "license": "MIT" - }, - "node_modules/@types/node": { - "version": "24.10.1", - "dev": true, - "license": "MIT", - "dependencies": { - "undici-types": "~7.16.0" - } - }, - "node_modules/@types/parse-json": { - "version": "4.0.2", - "license": "MIT" - }, - "node_modules/@types/prop-types": { - "version": "15.7.15", - "license": "MIT" - }, - "node_modules/@types/react": { - "version": "19.2.4", - "dev": true, - "license": "MIT", - "dependencies": { - "csstype": "^3.0.2" - } - }, - "node_modules/@types/react-dom": { - "version": "19.2.3", - "dev": true, - "license": "MIT", - "peerDependencies": { - "@types/react": "^19.2.0" - } - }, - "node_modules/@types/react-transition-group": { - "version": "4.4.12", - "license": "MIT", - "peerDependencies": { - "@types/react": "*" - } - }, - "node_modules/@typescript-eslint/eslint-plugin": { - "version": "8.46.4", - "dev": true, - "license": "MIT", - "dependencies": { - "@eslint-community/regexpp": "^4.10.0", - "@typescript-eslint/scope-manager": "8.46.4", - "@typescript-eslint/type-utils": "8.46.4", - "@typescript-eslint/utils": "8.46.4", - "@typescript-eslint/visitor-keys": "8.46.4", - "graphemer": "^1.4.0", - "ignore": "^7.0.0", - "natural-compare": "^1.4.0", - "ts-api-utils": "^2.1.0" - }, - "engines": { - "node": "^18.18.0 || ^20.9.0 || >=21.1.0" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/typescript-eslint" - }, - "peerDependencies": { - "@typescript-eslint/parser": "^8.46.4", - "eslint": "^8.57.0 || ^9.0.0", - "typescript": ">=4.8.4 <6.0.0" - } - }, - "node_modules/@typescript-eslint/eslint-plugin/node_modules/ignore": { - "version": "7.0.5", - "dev": true, - "license": "MIT", - "engines": { - "node": ">= 4" - } - }, - "node_modules/@typescript-eslint/parser": { - "version": "8.46.4", - "dev": true, - "license": "MIT", - "dependencies": { - "@typescript-eslint/scope-manager": "8.46.4", - "@typescript-eslint/types": "8.46.4", - "@typescript-eslint/typescript-estree": "8.46.4", - "@typescript-eslint/visitor-keys": "8.46.4", - "debug": "^4.3.4" - }, - "engines": { - "node": "^18.18.0 || ^20.9.0 || >=21.1.0" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/typescript-eslint" - }, - "peerDependencies": { - "eslint": "^8.57.0 || ^9.0.0", - "typescript": ">=4.8.4 <6.0.0" - } - }, - "node_modules/@typescript-eslint/project-service": { - "version": "8.46.4", - "dev": true, - "license": "MIT", - "dependencies": { - "@typescript-eslint/tsconfig-utils": "^8.46.4", - "@typescript-eslint/types": "^8.46.4", - "debug": "^4.3.4" - }, - "engines": { - "node": "^18.18.0 || ^20.9.0 || >=21.1.0" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/typescript-eslint" - }, - "peerDependencies": { - "typescript": ">=4.8.4 <6.0.0" - } - }, - "node_modules/@typescript-eslint/scope-manager": { - "version": "8.46.4", - "dev": true, - "license": "MIT", - "dependencies": { - "@typescript-eslint/types": "8.46.4", - "@typescript-eslint/visitor-keys": "8.46.4" - }, - "engines": { - "node": "^18.18.0 || ^20.9.0 || >=21.1.0" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/typescript-eslint" - } - }, - "node_modules/@typescript-eslint/tsconfig-utils": { - "version": "8.46.4", - "dev": true, - "license": "MIT", - "engines": { - "node": "^18.18.0 || ^20.9.0 || >=21.1.0" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/typescript-eslint" - }, - "peerDependencies": { - "typescript": ">=4.8.4 <6.0.0" - } - }, - "node_modules/@typescript-eslint/type-utils": { - "version": "8.46.4", - "dev": true, - "license": "MIT", - "dependencies": { - "@typescript-eslint/types": "8.46.4", - "@typescript-eslint/typescript-estree": "8.46.4", - "@typescript-eslint/utils": "8.46.4", - "debug": "^4.3.4", - "ts-api-utils": "^2.1.0" - }, - "engines": { - "node": "^18.18.0 || ^20.9.0 || >=21.1.0" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/typescript-eslint" - }, - "peerDependencies": { - "eslint": "^8.57.0 || ^9.0.0", - "typescript": ">=4.8.4 <6.0.0" - } - }, - "node_modules/@typescript-eslint/types": { - "version": "8.46.4", - "dev": true, - "license": "MIT", - "engines": { - "node": "^18.18.0 || ^20.9.0 || >=21.1.0" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/typescript-eslint" - } - }, - "node_modules/@typescript-eslint/typescript-estree": { - "version": "8.46.4", - "dev": true, - "license": "MIT", - "dependencies": { - "@typescript-eslint/project-service": "8.46.4", - "@typescript-eslint/tsconfig-utils": "8.46.4", - "@typescript-eslint/types": "8.46.4", - "@typescript-eslint/visitor-keys": "8.46.4", - "debug": "^4.3.4", - "fast-glob": "^3.3.2", - "is-glob": "^4.0.3", - "minimatch": "^9.0.4", - "semver": "^7.6.0", - "ts-api-utils": "^2.1.0" - }, - "engines": { - "node": "^18.18.0 || ^20.9.0 || >=21.1.0" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/typescript-eslint" - }, - "peerDependencies": { - "typescript": ">=4.8.4 <6.0.0" - } - }, - "node_modules/@typescript-eslint/typescript-estree/node_modules/brace-expansion": { - "version": "2.0.2", - "dev": true, - "license": "MIT", - "dependencies": { - "balanced-match": "^1.0.0" - } - }, - "node_modules/@typescript-eslint/typescript-estree/node_modules/minimatch": { - "version": "9.0.5", - "dev": true, - "license": "ISC", - "dependencies": { - "brace-expansion": "^2.0.1" - }, - "engines": { - "node": ">=16 || 14 >=14.17" - }, - "funding": { - "url": "https://github.com/sponsors/isaacs" - } - }, - "node_modules/@typescript-eslint/typescript-estree/node_modules/semver": { - "version": "7.7.3", - "dev": true, - "license": "ISC", - "bin": { - "semver": "bin/semver.js" - }, - "engines": { - "node": ">=10" - } - }, - "node_modules/@typescript-eslint/utils": { - "version": "8.46.4", - "dev": true, - "license": "MIT", - "dependencies": { - "@eslint-community/eslint-utils": "^4.7.0", - "@typescript-eslint/scope-manager": "8.46.4", - "@typescript-eslint/types": "8.46.4", - "@typescript-eslint/typescript-estree": "8.46.4" - }, - "engines": { - "node": "^18.18.0 || ^20.9.0 || >=21.1.0" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/typescript-eslint" - }, - "peerDependencies": { - "eslint": "^8.57.0 || ^9.0.0", - "typescript": ">=4.8.4 <6.0.0" - } - }, - "node_modules/@typescript-eslint/visitor-keys": { - "version": "8.46.4", - "dev": true, - "license": "MIT", - "dependencies": { - "@typescript-eslint/types": "8.46.4", - "eslint-visitor-keys": "^4.2.1" - }, - "engines": { - "node": "^18.18.0 || ^20.9.0 || >=21.1.0" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/typescript-eslint" - } - }, - "node_modules/@vitejs/plugin-react": { - "version": "5.1.1", - "dev": true, - "license": "MIT", - "dependencies": { - "@babel/core": "^7.28.5", - "@babel/plugin-transform-react-jsx-self": "^7.27.1", - "@babel/plugin-transform-react-jsx-source": "^7.27.1", - "@rolldown/pluginutils": "1.0.0-beta.47", - "@types/babel__core": "^7.20.5", - "react-refresh": "^0.18.0" - }, - "engines": { - "node": "^20.19.0 || >=22.12.0" - }, - "peerDependencies": { - "vite": "^4.2.0 || ^5.0.0 || ^6.0.0 || ^7.0.0" - } - }, - "node_modules/acorn": { - "version": "8.15.0", - "dev": true, - "license": "MIT", - "bin": { - "acorn": "bin/acorn" - }, - "engines": { - "node": ">=0.4.0" - } - }, - "node_modules/acorn-jsx": { - "version": "5.3.2", - "dev": true, - "license": "MIT", - "peerDependencies": { - "acorn": "^6.0.0 || ^7.0.0 || ^8.0.0" - } - }, - "node_modules/ajv": { - "version": "6.12.6", - "dev": true, - "license": "MIT", - "dependencies": { - "fast-deep-equal": "^3.1.1", - "fast-json-stable-stringify": "^2.0.0", - "json-schema-traverse": "^0.4.1", - "uri-js": "^4.2.2" - }, - "funding": { - "type": "github", - "url": "https://github.com/sponsors/epoberezkin" - } - }, - "node_modules/ansi-styles": { - "version": "4.3.0", - "dev": true, - "license": "MIT", - "dependencies": { - "color-convert": "^2.0.1" - }, - "engines": { - "node": ">=8" - }, - "funding": { - "url": "https://github.com/chalk/ansi-styles?sponsor=1" - } - }, - "node_modules/argparse": { - "version": "2.0.1", - "dev": true, - "license": "Python-2.0" - }, - "node_modules/asynckit": { - "version": "0.4.0", - "license": "MIT" - }, - "node_modules/axios": { - "version": "1.13.2", - "license": "MIT", - "dependencies": { - "follow-redirects": "^1.15.6", - "form-data": "^4.0.4", - "proxy-from-env": "^1.1.0" - } - }, - "node_modules/babel-plugin-macros": { - "version": "3.1.0", - "license": "MIT", - "dependencies": { - "@babel/runtime": "^7.12.5", - "cosmiconfig": "^7.0.0", - "resolve": "^1.19.0" - }, - "engines": { - "node": ">=10", - "npm": ">=6" - } - }, - "node_modules/balanced-match": { - "version": "1.0.2", - "dev": true, - "license": "MIT" - }, - "node_modules/baseline-browser-mapping": { - "version": "2.8.28", - "dev": true, - "license": "Apache-2.0", - "bin": { - "baseline-browser-mapping": "dist/cli.js" - } - }, - "node_modules/brace-expansion": { - "version": "1.1.12", - "dev": true, - "license": "MIT", - "dependencies": { - "balanced-match": "^1.0.0", - "concat-map": "0.0.1" - } - }, - "node_modules/braces": { - "version": "3.0.3", - "dev": true, - "license": "MIT", - "dependencies": { - "fill-range": "^7.1.1" - }, - "engines": { - "node": ">=8" - } - }, - "node_modules/browserslist": { - "version": "4.28.0", - "dev": true, - "funding": [ - { - "type": "opencollective", - "url": "https://opencollective.com/browserslist" - }, - { - "type": "tidelift", - "url": "https://tidelift.com/funding/github/npm/browserslist" - }, - { - "type": "github", - "url": "https://github.com/sponsors/ai" - } - ], - "license": "MIT", - "dependencies": { - "baseline-browser-mapping": "^2.8.25", - "caniuse-lite": "^1.0.30001754", - "electron-to-chromium": "^1.5.249", - "node-releases": "^2.0.27", - "update-browserslist-db": "^1.1.4" - }, - "bin": { - "browserslist": "cli.js" - }, - "engines": { - "node": "^6 || ^7 || ^8 || ^9 || ^10 || ^11 || ^12 || >=13.7" - } - }, - "node_modules/call-bind-apply-helpers": { - "version": "1.0.2", - "license": "MIT", - "dependencies": { - "es-errors": "^1.3.0", - "function-bind": "^1.1.2" - }, - "engines": { - "node": ">= 0.4" - } - }, - "node_modules/callsites": { - "version": "3.1.0", - "license": "MIT", - "engines": { - "node": ">=6" - } - }, - "node_modules/caniuse-lite": { - "version": "1.0.30001754", - "dev": true, - "funding": [ - { - "type": "opencollective", - "url": "https://opencollective.com/browserslist" - }, - { - "type": "tidelift", - "url": "https://tidelift.com/funding/github/npm/caniuse-lite" - }, - { - "type": "github", - "url": "https://github.com/sponsors/ai" - } - ], - "license": "CC-BY-4.0" - }, - "node_modules/chalk": { - "version": "4.1.2", - "dev": true, - "license": "MIT", - "dependencies": { - "ansi-styles": "^4.1.0", - "supports-color": "^7.1.0" - }, - "engines": { - "node": ">=10" - }, - "funding": { - "url": "https://github.com/chalk/chalk?sponsor=1" - } - }, - "node_modules/clsx": { - "version": "2.1.1", - "license": "MIT", - "engines": { - "node": ">=6" - } - }, - "node_modules/color-convert": { - "version": "2.0.1", - "dev": true, - "license": "MIT", - "dependencies": { - "color-name": "~1.1.4" - }, - "engines": { - "node": ">=7.0.0" - } - }, - "node_modules/color-name": { - "version": "1.1.4", - "dev": true, - "license": "MIT" - }, - "node_modules/combined-stream": { - "version": "1.0.8", - "license": "MIT", - "dependencies": { - "delayed-stream": "~1.0.0" - }, - "engines": { - "node": ">= 0.8" - } - }, - "node_modules/concat-map": { - "version": "0.0.1", - "dev": true, - "license": "MIT" - }, - "node_modules/convert-source-map": { - "version": "1.9.0", - "license": "MIT" - }, - "node_modules/cosmiconfig": { - "version": "7.1.0", - "license": "MIT", - "dependencies": { - "@types/parse-json": "^4.0.0", - "import-fresh": "^3.2.1", - "parse-json": "^5.0.0", - "path-type": "^4.0.0", - "yaml": "^1.10.0" - }, - "engines": { - "node": ">=10" - } - }, - "node_modules/cross-spawn": { - "version": "7.0.6", - "dev": true, - "license": "MIT", - "dependencies": { - "path-key": "^3.1.0", - "shebang-command": "^2.0.0", - "which": "^2.0.1" - }, - "engines": { - "node": ">= 8" - } - }, - "node_modules/csstype": { - "version": "3.1.3", - "license": "MIT" - }, - "node_modules/dayjs": { - "version": "1.11.19", - "license": "MIT" - }, - "node_modules/debug": { - "version": "4.4.3", - "license": "MIT", - "dependencies": { - "ms": "^2.1.3" - }, - "engines": { - "node": ">=6.0" - }, - "peerDependenciesMeta": { - "supports-color": { - "optional": true - } - } - }, - "node_modules/deep-is": { - "version": "0.1.4", - "dev": true, - "license": "MIT" - }, - "node_modules/delayed-stream": { - "version": "1.0.0", - "license": "MIT", - "engines": { - "node": ">=0.4.0" - } - }, - "node_modules/dom-helpers": { - "version": "5.2.1", - "license": "MIT", - "dependencies": { - "@babel/runtime": "^7.8.7", - "csstype": "^3.0.2" - } - }, - "node_modules/dunder-proto": { - "version": "1.0.1", - "license": "MIT", - "dependencies": { - "call-bind-apply-helpers": "^1.0.1", - "es-errors": "^1.3.0", - "gopd": "^1.2.0" - }, - "engines": { - "node": ">= 0.4" - } - }, - "node_modules/electron-to-chromium": { - "version": "1.5.250", - "dev": true, - "license": "ISC" - }, - "node_modules/error-ex": { - "version": "1.3.4", - "license": "MIT", - "dependencies": { - "is-arrayish": "^0.2.1" - } - }, - "node_modules/es-define-property": { - "version": "1.0.1", - "license": "MIT", - "engines": { - "node": ">= 0.4" - } - }, - "node_modules/es-errors": { - "version": "1.3.0", - "license": "MIT", - "engines": { - "node": ">= 0.4" - } - }, - "node_modules/es-object-atoms": { - "version": "1.1.1", - "license": "MIT", - "dependencies": { - "es-errors": "^1.3.0" - }, - "engines": { - "node": ">= 0.4" - } - }, - "node_modules/es-set-tostringtag": { - "version": "2.1.0", - "license": "MIT", - "dependencies": { - "es-errors": "^1.3.0", - "get-intrinsic": "^1.2.6", - "has-tostringtag": "^1.0.2", - "hasown": "^2.0.2" - }, - "engines": { - "node": ">= 0.4" - } - }, - "node_modules/esbuild": { - "version": "0.25.12", - "dev": true, - "hasInstallScript": true, - "license": "MIT", - "bin": { - "esbuild": "bin/esbuild" - }, - "engines": { - "node": ">=18" - }, - "optionalDependencies": { - "@esbuild/aix-ppc64": "0.25.12", - "@esbuild/android-arm": "0.25.12", - "@esbuild/android-arm64": "0.25.12", - "@esbuild/android-x64": "0.25.12", - "@esbuild/darwin-arm64": "0.25.12", - "@esbuild/darwin-x64": "0.25.12", - "@esbuild/freebsd-arm64": "0.25.12", - "@esbuild/freebsd-x64": "0.25.12", - "@esbuild/linux-arm": "0.25.12", - "@esbuild/linux-arm64": "0.25.12", - "@esbuild/linux-ia32": "0.25.12", - "@esbuild/linux-loong64": "0.25.12", - "@esbuild/linux-mips64el": "0.25.12", - "@esbuild/linux-ppc64": "0.25.12", - "@esbuild/linux-riscv64": "0.25.12", - "@esbuild/linux-s390x": "0.25.12", - "@esbuild/linux-x64": "0.25.12", - "@esbuild/netbsd-arm64": "0.25.12", - "@esbuild/netbsd-x64": "0.25.12", - "@esbuild/openbsd-arm64": "0.25.12", - "@esbuild/openbsd-x64": "0.25.12", - "@esbuild/openharmony-arm64": "0.25.12", - "@esbuild/sunos-x64": "0.25.12", - "@esbuild/win32-arm64": "0.25.12", - "@esbuild/win32-ia32": "0.25.12", - "@esbuild/win32-x64": "0.25.12" - } - }, - "node_modules/escalade": { - "version": "3.2.0", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=6" - } - }, - "node_modules/escape-string-regexp": { - "version": "4.0.0", - "license": "MIT", - "engines": { - "node": ">=10" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/eslint": { - "version": "9.39.1", - "dev": true, - "license": "MIT", - "dependencies": { - "@eslint-community/eslint-utils": "^4.8.0", - "@eslint-community/regexpp": "^4.12.1", - "@eslint/config-array": "^0.21.1", - "@eslint/config-helpers": "^0.4.2", - "@eslint/core": "^0.17.0", - "@eslint/eslintrc": "^3.3.1", - "@eslint/js": "9.39.1", - "@eslint/plugin-kit": "^0.4.1", - "@humanfs/node": "^0.16.6", - "@humanwhocodes/module-importer": "^1.0.1", - "@humanwhocodes/retry": "^0.4.2", - "@types/estree": "^1.0.6", - "ajv": "^6.12.4", - "chalk": "^4.0.0", - "cross-spawn": "^7.0.6", - "debug": "^4.3.2", - "escape-string-regexp": "^4.0.0", - "eslint-scope": "^8.4.0", - "eslint-visitor-keys": "^4.2.1", - "espree": "^10.4.0", - "esquery": "^1.5.0", - "esutils": "^2.0.2", - "fast-deep-equal": "^3.1.3", - "file-entry-cache": "^8.0.0", - "find-up": "^5.0.0", - "glob-parent": "^6.0.2", - "ignore": "^5.2.0", - "imurmurhash": "^0.1.4", - "is-glob": "^4.0.0", - "json-stable-stringify-without-jsonify": "^1.0.1", - "lodash.merge": "^4.6.2", - "minimatch": "^3.1.2", - "natural-compare": "^1.4.0", - "optionator": "^0.9.3" - }, - "bin": { - "eslint": "bin/eslint.js" - }, - "engines": { - "node": "^18.18.0 || ^20.9.0 || >=21.1.0" - }, - "funding": { - "url": "https://eslint.org/donate" - }, - "peerDependencies": { - "jiti": "*" - }, - "peerDependenciesMeta": { - "jiti": { - "optional": true - } - } - }, - "node_modules/eslint-plugin-react-hooks": { - "version": "5.2.0", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=10" - }, - "peerDependencies": { - "eslint": "^3.0.0 || ^4.0.0 || ^5.0.0 || ^6.0.0 || ^7.0.0 || ^8.0.0-0 || ^9.0.0" - } - }, - "node_modules/eslint-plugin-react-refresh": { - "version": "0.4.24", - "dev": true, - "license": "MIT", - "peerDependencies": { - "eslint": ">=8.40" - } - }, - "node_modules/eslint-scope": { - "version": "8.4.0", - "dev": true, - "license": "BSD-2-Clause", - "dependencies": { - "esrecurse": "^4.3.0", - "estraverse": "^5.2.0" - }, - "engines": { - "node": "^18.18.0 || ^20.9.0 || >=21.1.0" - }, - "funding": { - "url": "https://opencollective.com/eslint" - } - }, - "node_modules/eslint-visitor-keys": { - "version": "4.2.1", - "dev": true, - "license": "Apache-2.0", - "engines": { - "node": "^18.18.0 || ^20.9.0 || >=21.1.0" - }, - "funding": { - "url": "https://opencollective.com/eslint" - } - }, - "node_modules/espree": { - "version": "10.4.0", - "dev": true, - "license": "BSD-2-Clause", - "dependencies": { - "acorn": "^8.15.0", - "acorn-jsx": "^5.3.2", - "eslint-visitor-keys": "^4.2.1" - }, - "engines": { - "node": "^18.18.0 || ^20.9.0 || >=21.1.0" - }, - "funding": { - "url": "https://opencollective.com/eslint" - } - }, - "node_modules/esquery": { - "version": "1.6.0", - "dev": true, - "license": "BSD-3-Clause", - "dependencies": { - "estraverse": "^5.1.0" - }, - "engines": { - "node": ">=0.10" - } - }, - "node_modules/esrecurse": { - "version": "4.3.0", - "dev": true, - "license": "BSD-2-Clause", - "dependencies": { - "estraverse": "^5.2.0" - }, - "engines": { - "node": ">=4.0" - } - }, - "node_modules/estraverse": { - "version": "5.3.0", - "dev": true, - "license": "BSD-2-Clause", - "engines": { - "node": ">=4.0" - } - }, - "node_modules/esutils": { - "version": "2.0.3", - "dev": true, - "license": "BSD-2-Clause", - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/fast-deep-equal": { - "version": "3.1.3", - "dev": true, - "license": "MIT" - }, - "node_modules/fast-glob": { - "version": "3.3.3", - "dev": true, - "license": "MIT", - "dependencies": { - "@nodelib/fs.stat": "^2.0.2", - "@nodelib/fs.walk": "^1.2.3", - "glob-parent": "^5.1.2", - "merge2": "^1.3.0", - "micromatch": "^4.0.8" - }, - "engines": { - "node": ">=8.6.0" - } - }, - "node_modules/fast-glob/node_modules/glob-parent": { - "version": "5.1.2", - "dev": true, - "license": "ISC", - "dependencies": { - "is-glob": "^4.0.1" - }, - "engines": { - "node": ">= 6" - } - }, - "node_modules/fast-json-stable-stringify": { - "version": "2.1.0", - "dev": true, - "license": "MIT" - }, - "node_modules/fast-levenshtein": { - "version": "2.0.6", - "dev": true, - "license": "MIT" - }, - "node_modules/fastq": { - "version": "1.19.1", - "dev": true, - "license": "ISC", - "dependencies": { - "reusify": "^1.0.4" - } - }, - "node_modules/fdir": { - "version": "6.5.0", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=12.0.0" - }, - "peerDependencies": { - "picomatch": "^3 || ^4" - }, - "peerDependenciesMeta": { - "picomatch": { - "optional": true - } - } - }, - "node_modules/file-entry-cache": { - "version": "8.0.0", - "dev": true, - "license": "MIT", - "dependencies": { - "flat-cache": "^4.0.0" - }, - "engines": { - "node": ">=16.0.0" - } - }, - "node_modules/fill-range": { - "version": "7.1.1", - "dev": true, - "license": "MIT", - "dependencies": { - "to-regex-range": "^5.0.1" - }, - "engines": { - "node": ">=8" - } - }, - "node_modules/find-root": { - "version": "1.1.0", - "license": "MIT" - }, - "node_modules/find-up": { - "version": "5.0.0", - "dev": true, - "license": "MIT", - "dependencies": { - "locate-path": "^6.0.0", - "path-exists": "^4.0.0" - }, - "engines": { - "node": ">=10" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/flat-cache": { - "version": "4.0.1", - "dev": true, - "license": "MIT", - "dependencies": { - "flatted": "^3.2.9", - "keyv": "^4.5.4" - }, - "engines": { - "node": ">=16" - } - }, - "node_modules/flatted": { - "version": "3.3.3", - "dev": true, - "license": "ISC" - }, - "node_modules/follow-redirects": { - "version": "1.15.11", - "funding": [ - { - "type": "individual", - "url": "https://github.com/sponsors/RubenVerborgh" - } - ], - "license": "MIT", - "engines": { - "node": ">=4.0" - }, - "peerDependenciesMeta": { - "debug": { - "optional": true - } - } - }, - "node_modules/form-data": { - "version": "4.0.4", - "license": "MIT", - "dependencies": { - "asynckit": "^0.4.0", - "combined-stream": "^1.0.8", - "es-set-tostringtag": "^2.1.0", - "hasown": "^2.0.2", - "mime-types": "^2.1.12" - }, - "engines": { - "node": ">= 6" - } - }, - "node_modules/function-bind": { - "version": "1.1.2", - "license": "MIT", - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, - "node_modules/gensync": { - "version": "1.0.0-beta.2", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=6.9.0" - } - }, - "node_modules/get-intrinsic": { - "version": "1.3.0", - "license": "MIT", - "dependencies": { - "call-bind-apply-helpers": "^1.0.2", - "es-define-property": "^1.0.1", - "es-errors": "^1.3.0", - "es-object-atoms": "^1.1.1", - "function-bind": "^1.1.2", - "get-proto": "^1.0.1", - "gopd": "^1.2.0", - "has-symbols": "^1.1.0", - "hasown": "^2.0.2", - "math-intrinsics": "^1.1.0" - }, - "engines": { - "node": ">= 0.4" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, - "node_modules/get-proto": { - "version": "1.0.1", - "license": "MIT", - "dependencies": { - "dunder-proto": "^1.0.1", - "es-object-atoms": "^1.0.0" - }, - "engines": { - "node": ">= 0.4" - } - }, - "node_modules/glob-parent": { - "version": "6.0.2", - "dev": true, - "license": "ISC", - "dependencies": { - "is-glob": "^4.0.3" - }, - "engines": { - "node": ">=10.13.0" - } - }, - "node_modules/globals": { - "version": "16.5.0", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=18" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/goober": { - "version": "2.1.18", - "license": "MIT", - "peerDependencies": { - "csstype": "^3.0.10" - } - }, - "node_modules/gopd": { - "version": "1.2.0", - "license": "MIT", - "engines": { - "node": ">= 0.4" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, - "node_modules/graphemer": { - "version": "1.4.0", - "dev": true, - "license": "MIT" - }, - "node_modules/has-flag": { - "version": "4.0.0", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=8" - } - }, - "node_modules/has-symbols": { - "version": "1.1.0", - "license": "MIT", - "engines": { - "node": ">= 0.4" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, - "node_modules/has-tostringtag": { - "version": "1.0.2", - "license": "MIT", - "dependencies": { - "has-symbols": "^1.0.3" - }, - "engines": { - "node": ">= 0.4" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, - "node_modules/hasown": { - "version": "2.0.2", - "license": "MIT", - "dependencies": { - "function-bind": "^1.1.2" - }, - "engines": { - "node": ">= 0.4" - } - }, - "node_modules/hoist-non-react-statics": { - "version": "3.3.2", - "license": "BSD-3-Clause", - "dependencies": { - "react-is": "^16.7.0" - } - }, - "node_modules/hoist-non-react-statics/node_modules/react-is": { - "version": "16.13.1", - "license": "MIT" - }, - "node_modules/ignore": { - "version": "5.3.2", - "dev": true, - "license": "MIT", - "engines": { - "node": ">= 4" - } - }, - "node_modules/import-fresh": { - "version": "3.3.1", - "license": "MIT", - "dependencies": { - "parent-module": "^1.0.0", - "resolve-from": "^4.0.0" - }, - "engines": { - "node": ">=6" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/imurmurhash": { - "version": "0.1.4", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=0.8.19" - } - }, - "node_modules/is-arrayish": { - "version": "0.2.1", - "license": "MIT" - }, - "node_modules/is-core-module": { - "version": "2.16.1", - "license": "MIT", - "dependencies": { - "hasown": "^2.0.2" - }, - "engines": { - "node": ">= 0.4" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, - "node_modules/is-extglob": { - "version": "2.1.1", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/is-glob": { - "version": "4.0.3", - "dev": true, - "license": "MIT", - "dependencies": { - "is-extglob": "^2.1.1" - }, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/is-number": { - "version": "7.0.0", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=0.12.0" - } - }, - "node_modules/isexe": { - "version": "2.0.0", - "dev": true, - "license": "ISC" - }, - "node_modules/js-tokens": { - "version": "4.0.0", - "license": "MIT" - }, - "node_modules/js-yaml": { - "version": "4.1.1", - "dev": true, - "license": "MIT", - "dependencies": { - "argparse": "^2.0.1" - }, - "bin": { - "js-yaml": "bin/js-yaml.js" - } - }, - "node_modules/jsesc": { - "version": "3.1.0", - "license": "MIT", - "bin": { - "jsesc": "bin/jsesc" - }, - "engines": { - "node": ">=6" - } - }, - "node_modules/json-buffer": { - "version": "3.0.1", - "dev": true, - "license": "MIT" - }, - "node_modules/json-parse-even-better-errors": { - "version": "2.3.1", - "license": "MIT" - }, - "node_modules/json-schema-traverse": { - "version": "0.4.1", - "dev": true, - "license": "MIT" - }, - "node_modules/json-stable-stringify-without-jsonify": { - "version": "1.0.1", - "dev": true, - "license": "MIT" - }, - "node_modules/json5": { - "version": "2.2.3", - "dev": true, - "license": "MIT", - "bin": { - "json5": "lib/cli.js" - }, - "engines": { - "node": ">=6" - } - }, - "node_modules/keyv": { - "version": "4.5.4", - "dev": true, - "license": "MIT", - "dependencies": { - "json-buffer": "3.0.1" - } - }, - "node_modules/levn": { - "version": "0.4.1", - "dev": true, - "license": "MIT", - "dependencies": { - "prelude-ls": "^1.2.1", - "type-check": "~0.4.0" - }, - "engines": { - "node": ">= 0.8.0" - } - }, - "node_modules/lines-and-columns": { - "version": "1.2.4", - "license": "MIT" - }, - "node_modules/locate-path": { - "version": "6.0.0", - "dev": true, - "license": "MIT", - "dependencies": { - "p-locate": "^5.0.0" - }, - "engines": { - "node": ">=10" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/lodash.merge": { - "version": "4.6.2", - "dev": true, - "license": "MIT" - }, - "node_modules/loose-envify": { - "version": "1.4.0", - "license": "MIT", - "dependencies": { - "js-tokens": "^3.0.0 || ^4.0.0" - }, - "bin": { - "loose-envify": "cli.js" - } - }, - "node_modules/lru-cache": { - "version": "5.1.1", - "dev": true, - "license": "ISC", - "dependencies": { - "yallist": "^3.0.2" - } - }, - "node_modules/math-intrinsics": { - "version": "1.1.0", - "license": "MIT", - "engines": { - "node": ">= 0.4" - } - }, - "node_modules/merge2": { - "version": "1.4.1", - "dev": true, - "license": "MIT", - "engines": { - "node": ">= 8" - } - }, - "node_modules/micromatch": { - "version": "4.0.8", - "dev": true, - "license": "MIT", - "dependencies": { - "braces": "^3.0.3", - "picomatch": "^2.3.1" - }, - "engines": { - "node": ">=8.6" - } - }, - "node_modules/mime-db": { - "version": "1.52.0", - "license": "MIT", - "engines": { - "node": ">= 0.6" - } - }, - "node_modules/mime-types": { - "version": "2.1.35", - "license": "MIT", - "dependencies": { - "mime-db": "1.52.0" - }, - "engines": { - "node": ">= 0.6" - } - }, - "node_modules/minimatch": { - "version": "3.1.2", - "dev": true, - "license": "ISC", - "dependencies": { - "brace-expansion": "^1.1.7" - }, - "engines": { - "node": "*" - } - }, - "node_modules/ms": { - "version": "2.1.3", - "license": "MIT" - }, - "node_modules/nanoid": { - "version": "3.3.11", - "dev": true, - "funding": [ - { - "type": "github", - "url": "https://github.com/sponsors/ai" - } - ], - "license": "MIT", - "bin": { - "nanoid": "bin/nanoid.cjs" - }, - "engines": { - "node": "^10 || ^12 || ^13.7 || ^14 || >=15.0.1" - } - }, - "node_modules/natural-compare": { - "version": "1.4.0", - "dev": true, - "license": "MIT" - }, - "node_modules/node-releases": { - "version": "2.0.27", - "dev": true, - "license": "MIT" - }, - "node_modules/object-assign": { - "version": "4.1.1", - "license": "MIT", - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/optionator": { - "version": "0.9.4", - "dev": true, - "license": "MIT", - "dependencies": { - "deep-is": "^0.1.3", - "fast-levenshtein": "^2.0.6", - "levn": "^0.4.1", - "prelude-ls": "^1.2.1", - "type-check": "^0.4.0", - "word-wrap": "^1.2.5" - }, - "engines": { - "node": ">= 0.8.0" - } - }, - "node_modules/p-limit": { - "version": "3.1.0", - "dev": true, - "license": "MIT", - "dependencies": { - "yocto-queue": "^0.1.0" - }, - "engines": { - "node": ">=10" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/p-locate": { - "version": "5.0.0", - "dev": true, - "license": "MIT", - "dependencies": { - "p-limit": "^3.0.2" - }, - "engines": { - "node": ">=10" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/parent-module": { - "version": "1.0.1", - "license": "MIT", - "dependencies": { - "callsites": "^3.0.0" - }, - "engines": { - "node": ">=6" - } - }, - "node_modules/parse-json": { - "version": "5.2.0", - "license": "MIT", - "dependencies": { - "@babel/code-frame": "^7.0.0", - "error-ex": "^1.3.1", - "json-parse-even-better-errors": "^2.3.0", - "lines-and-columns": "^1.1.6" - }, - "engines": { - "node": ">=8" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/path-exists": { - "version": "4.0.0", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=8" - } - }, - "node_modules/path-key": { - "version": "3.1.1", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=8" - } - }, - "node_modules/path-parse": { - "version": "1.0.7", - "license": "MIT" - }, - "node_modules/path-type": { - "version": "4.0.0", - "license": "MIT", - "engines": { - "node": ">=8" - } - }, - "node_modules/picocolors": { - "version": "1.1.1", - "license": "ISC" - }, - "node_modules/picomatch": { - "version": "2.3.1", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=8.6" - }, - "funding": { - "url": "https://github.com/sponsors/jonschlinkert" - } - }, - "node_modules/postcss": { - "version": "8.5.6", - "dev": true, - "funding": [ - { - "type": "opencollective", - "url": "https://opencollective.com/postcss/" - }, - { - "type": "tidelift", - "url": "https://tidelift.com/funding/github/npm/postcss" - }, - { - "type": "github", - "url": "https://github.com/sponsors/ai" - } - ], - "license": "MIT", - "dependencies": { - "nanoid": "^3.3.11", - "picocolors": "^1.1.1", - "source-map-js": "^1.2.1" - }, - "engines": { - "node": "^10 || ^12 || >=14" - } - }, - "node_modules/prelude-ls": { - "version": "1.2.1", - "dev": true, - "license": "MIT", - "engines": { - "node": ">= 0.8.0" - } - }, - "node_modules/prop-types": { - "version": "15.8.1", - "license": "MIT", - "dependencies": { - "loose-envify": "^1.4.0", - "object-assign": "^4.1.1", - "react-is": "^16.13.1" - } - }, - "node_modules/prop-types/node_modules/react-is": { - "version": "16.13.1", - "license": "MIT" - }, - "node_modules/property-expr": { - "version": "2.0.6", - "license": "MIT" - }, - "node_modules/proxy-from-env": { - "version": "1.1.0", - "license": "MIT" - }, - "node_modules/punycode": { - "version": "2.3.1", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=6" - } - }, - "node_modules/queue-microtask": { - "version": "1.2.3", - "dev": true, - "funding": [ - { - "type": "github", - "url": "https://github.com/sponsors/feross" - }, - { - "type": "patreon", - "url": "https://www.patreon.com/feross" - }, - { - "type": "consulting", - "url": "https://feross.org/support" - } - ], - "license": "MIT" - }, - "node_modules/react": { - "version": "19.2.0", - "license": "MIT", - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/react-dom": { - "version": "19.2.0", - "license": "MIT", - "dependencies": { - "scheduler": "^0.27.0" - }, - "peerDependencies": { - "react": "^19.2.0" - } - }, - "node_modules/react-hook-form": { - "version": "7.66.0", - "license": "MIT", - "engines": { - "node": ">=18.0.0" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/react-hook-form" - }, - "peerDependencies": { - "react": "^16.8.0 || ^17 || ^18 || ^19" - } - }, - "node_modules/react-hot-toast": { - "version": "2.6.0", - "license": "MIT", - "dependencies": { - "csstype": "^3.1.3", - "goober": "^2.1.16" - }, - "engines": { - "node": ">=10" - }, - "peerDependencies": { - "react": ">=16", - "react-dom": ">=16" - } - }, - "node_modules/react-is": { - "version": "19.2.0", - "license": "MIT" - }, - "node_modules/react-refresh": { - "version": "0.18.0", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/react-router": { - "version": "6.30.2", - "license": "MIT", - "dependencies": { - "@remix-run/router": "1.23.1" - }, - "engines": { - "node": ">=14.0.0" - }, - "peerDependencies": { - "react": ">=16.8" - } - }, - "node_modules/react-router-dom": { - "version": "6.30.2", - "license": "MIT", - "dependencies": { - "@remix-run/router": "1.23.1", - "react-router": "6.30.2" - }, - "engines": { - "node": ">=14.0.0" - }, - "peerDependencies": { - "react": ">=16.8", - "react-dom": ">=16.8" - } - }, - "node_modules/react-transition-group": { - "version": "4.4.5", - "license": "BSD-3-Clause", - "dependencies": { - "@babel/runtime": "^7.5.5", - "dom-helpers": "^5.0.1", - "loose-envify": "^1.4.0", - "prop-types": "^15.6.2" - }, - "peerDependencies": { - "react": ">=16.6.0", - "react-dom": ">=16.6.0" - } - }, - "node_modules/resolve": { - "version": "1.22.11", - "license": "MIT", - "dependencies": { - "is-core-module": "^2.16.1", - "path-parse": "^1.0.7", - "supports-preserve-symlinks-flag": "^1.0.0" - }, - "bin": { - "resolve": "bin/resolve" - }, - "engines": { - "node": ">= 0.4" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, - "node_modules/resolve-from": { - "version": "4.0.0", - "license": "MIT", - "engines": { - "node": ">=4" - } - }, - "node_modules/reusify": { - "version": "1.1.0", - "dev": true, - "license": "MIT", - "engines": { - "iojs": ">=1.0.0", - "node": ">=0.10.0" - } - }, - "node_modules/rollup": { - "version": "4.53.2", - "dev": true, - "license": "MIT", - "dependencies": { - "@types/estree": "1.0.8" - }, - "bin": { - "rollup": "dist/bin/rollup" - }, - "engines": { - "node": ">=18.0.0", - "npm": ">=8.0.0" - }, - "optionalDependencies": { - "@rollup/rollup-android-arm-eabi": "4.53.2", - "@rollup/rollup-android-arm64": "4.53.2", - "@rollup/rollup-darwin-arm64": "4.53.2", - "@rollup/rollup-darwin-x64": "4.53.2", - "@rollup/rollup-freebsd-arm64": "4.53.2", - "@rollup/rollup-freebsd-x64": "4.53.2", - "@rollup/rollup-linux-arm-gnueabihf": "4.53.2", - "@rollup/rollup-linux-arm-musleabihf": "4.53.2", - "@rollup/rollup-linux-arm64-gnu": "4.53.2", - "@rollup/rollup-linux-arm64-musl": "4.53.2", - "@rollup/rollup-linux-loong64-gnu": "4.53.2", - "@rollup/rollup-linux-ppc64-gnu": "4.53.2", - "@rollup/rollup-linux-riscv64-gnu": "4.53.2", - "@rollup/rollup-linux-riscv64-musl": "4.53.2", - "@rollup/rollup-linux-s390x-gnu": "4.53.2", - "@rollup/rollup-linux-x64-gnu": "4.53.2", - "@rollup/rollup-linux-x64-musl": "4.53.2", - "@rollup/rollup-openharmony-arm64": "4.53.2", - "@rollup/rollup-win32-arm64-msvc": "4.53.2", - "@rollup/rollup-win32-ia32-msvc": "4.53.2", - "@rollup/rollup-win32-x64-gnu": "4.53.2", - "@rollup/rollup-win32-x64-msvc": "4.53.2", - "fsevents": "~2.3.2" - } - }, - "node_modules/run-parallel": { - "version": "1.2.0", - "dev": true, - "funding": [ - { - "type": "github", - "url": "https://github.com/sponsors/feross" - }, - { - "type": "patreon", - "url": "https://www.patreon.com/feross" - }, - { - "type": "consulting", - "url": "https://feross.org/support" - } - ], - "license": "MIT", - "dependencies": { - "queue-microtask": "^1.2.2" - } - }, - "node_modules/scheduler": { - "version": "0.27.0", - "license": "MIT" - }, - "node_modules/semver": { - "version": "6.3.1", - "dev": true, - "license": "ISC", - "bin": { - "semver": "bin/semver.js" - } - }, - "node_modules/shebang-command": { - "version": "2.0.0", - "dev": true, - "license": "MIT", - "dependencies": { - "shebang-regex": "^3.0.0" - }, - "engines": { - "node": ">=8" - } - }, - "node_modules/shebang-regex": { - "version": "3.0.0", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=8" - } - }, - "node_modules/source-map": { - "version": "0.5.7", - "license": "BSD-3-Clause", - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/source-map-js": { - "version": "1.2.1", - "dev": true, - "license": "BSD-3-Clause", - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/strip-json-comments": { - "version": "3.1.1", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=8" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/stylis": { - "version": "4.2.0", - "license": "MIT" - }, - "node_modules/supports-color": { - "version": "7.2.0", - "dev": true, - "license": "MIT", - "dependencies": { - "has-flag": "^4.0.0" - }, - "engines": { - "node": ">=8" - } - }, - "node_modules/supports-preserve-symlinks-flag": { - "version": "1.0.0", - "license": "MIT", - "engines": { - "node": ">= 0.4" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, - "node_modules/tiny-case": { - "version": "1.0.3", - "license": "MIT" - }, - "node_modules/tinyglobby": { - "version": "0.2.15", - "dev": true, - "license": "MIT", - "dependencies": { - "fdir": "^6.5.0", - "picomatch": "^4.0.3" - }, - "engines": { - "node": ">=12.0.0" - }, - "funding": { - "url": "https://github.com/sponsors/SuperchupuDev" - } - }, - "node_modules/tinyglobby/node_modules/picomatch": { - "version": "4.0.3", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=12" - }, - "funding": { - "url": "https://github.com/sponsors/jonschlinkert" - } - }, - "node_modules/to-regex-range": { - "version": "5.0.1", - "dev": true, - "license": "MIT", - "dependencies": { - "is-number": "^7.0.0" - }, - "engines": { - "node": ">=8.0" - } - }, - "node_modules/toposort": { - "version": "2.0.2", - "license": "MIT" - }, - "node_modules/ts-api-utils": { - "version": "2.1.0", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=18.12" - }, - "peerDependencies": { - "typescript": ">=4.8.4" - } - }, - "node_modules/type-check": { - "version": "0.4.0", - "dev": true, - "license": "MIT", - "dependencies": { - "prelude-ls": "^1.2.1" - }, - "engines": { - "node": ">= 0.8.0" - } - }, - "node_modules/type-fest": { - "version": "2.19.0", - "license": "(MIT OR CC0-1.0)", - "engines": { - "node": ">=12.20" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/typescript": { - "version": "5.9.3", - "dev": true, - "license": "Apache-2.0", - "bin": { - "tsc": "bin/tsc", - "tsserver": "bin/tsserver" - }, - "engines": { - "node": ">=14.17" - } - }, - "node_modules/typescript-eslint": { - "version": "8.46.4", - "dev": true, - "license": "MIT", - "dependencies": { - "@typescript-eslint/eslint-plugin": "8.46.4", - "@typescript-eslint/parser": "8.46.4", - "@typescript-eslint/typescript-estree": "8.46.4", - "@typescript-eslint/utils": "8.46.4" - }, - "engines": { - "node": "^18.18.0 || ^20.9.0 || >=21.1.0" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/typescript-eslint" - }, - "peerDependencies": { - "eslint": "^8.57.0 || ^9.0.0", - "typescript": ">=4.8.4 <6.0.0" - } - }, - "node_modules/undici-types": { - "version": "7.16.0", - "dev": true, - "license": "MIT" - }, - "node_modules/update-browserslist-db": { - "version": "1.1.4", - "dev": true, - "funding": [ - { - "type": "opencollective", - "url": "https://opencollective.com/browserslist" - }, - { - "type": "tidelift", - "url": "https://tidelift.com/funding/github/npm/browserslist" - }, - { - "type": "github", - "url": "https://github.com/sponsors/ai" - } - ], - "license": "MIT", - "dependencies": { - "escalade": "^3.2.0", - "picocolors": "^1.1.1" - }, - "bin": { - "update-browserslist-db": "cli.js" - }, - "peerDependencies": { - "browserslist": ">= 4.21.0" - } - }, - "node_modules/uri-js": { - "version": "4.4.1", - "dev": true, - "license": "BSD-2-Clause", - "dependencies": { - "punycode": "^2.1.0" - } - }, - "node_modules/vite": { - "version": "7.2.2", - "dev": true, - "license": "MIT", - "dependencies": { - "esbuild": "^0.25.0", - "fdir": "^6.5.0", - "picomatch": "^4.0.3", - "postcss": "^8.5.6", - "rollup": "^4.43.0", - "tinyglobby": "^0.2.15" - }, - "bin": { - "vite": "bin/vite.js" - }, - "engines": { - "node": "^20.19.0 || >=22.12.0" - }, - "funding": { - "url": "https://github.com/vitejs/vite?sponsor=1" - }, - "optionalDependencies": { - "fsevents": "~2.3.3" - }, - "peerDependencies": { - "@types/node": "^20.19.0 || >=22.12.0", - "jiti": ">=1.21.0", - "less": "^4.0.0", - "lightningcss": "^1.21.0", - "sass": "^1.70.0", - "sass-embedded": "^1.70.0", - "stylus": ">=0.54.8", - "sugarss": "^5.0.0", - "terser": "^5.16.0", - "tsx": "^4.8.1", - "yaml": "^2.4.2" - }, - "peerDependenciesMeta": { - "@types/node": { - "optional": true - }, - "jiti": { - "optional": true - }, - "less": { - "optional": true - }, - "lightningcss": { - "optional": true - }, - "sass": { - "optional": true - }, - "sass-embedded": { - "optional": true - }, - "stylus": { - "optional": true - }, - "sugarss": { - "optional": true - }, - "terser": { - "optional": true - }, - "tsx": { - "optional": true - }, - "yaml": { - "optional": true - } - } - }, - "node_modules/vite/node_modules/picomatch": { - "version": "4.0.3", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=12" - }, - "funding": { - "url": "https://github.com/sponsors/jonschlinkert" - } - }, - "node_modules/which": { - "version": "2.0.2", - "dev": true, - "license": "ISC", - "dependencies": { - "isexe": "^2.0.0" - }, - "bin": { - "node-which": "bin/node-which" - }, - "engines": { - "node": ">= 8" - } - }, - "node_modules/word-wrap": { - "version": "1.2.5", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/yallist": { - "version": "3.1.1", - "dev": true, - "license": "ISC" - }, - "node_modules/yaml": { - "version": "1.10.2", - "license": "ISC", - "engines": { - "node": ">= 6" - } - }, - "node_modules/yocto-queue": { - "version": "0.1.0", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=10" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/yup": { - "version": "1.7.1", - "license": "MIT", - "dependencies": { - "property-expr": "^2.0.5", - "tiny-case": "^1.0.3", - "toposort": "^2.0.2", - "type-fest": "^2.19.0" - } - } - } -} diff --git a/frontend/package.json b/frontend/package.json deleted file mode 100644 index 81801e49489e9bfdd49b166b2f675591a4f46e00..0000000000000000000000000000000000000000 --- a/frontend/package.json +++ /dev/null @@ -1,50 +0,0 @@ -{ - "name": "locallend-frontend", - "private": true, - "version": "0.0.0", - "type": "module", - "scripts": { - "dev": "vite", - "build": "tsc -b && vite build", - "lint": "eslint .", - "preview": "vite preview", - "docker:build": "docker build -t locallend-frontend .", - "docker:run": "docker run -p 3000:80 locallend-frontend", - "docker:compose": "docker-compose up -d", - "docker:compose:build": "docker-compose up --build -d", - "docker:stop": "docker-compose down", - "docker:dev": "docker-compose -f docker-compose.dev.yml up --build -d", - "docker:dev:stop": "docker-compose -f docker-compose.dev.yml down", - "docker:dev:logs": "docker-compose -f docker-compose.dev.yml logs -f" - }, - "dependencies": { - "react": "^19.1.1", - "react-dom": "^19.1.1", - "react-router-dom": "^6.27.0", - "axios": "^1.7.7", - "@mui/material": "^6.1.7", - "@emotion/react": "^11.13.3", - "@emotion/styled": "^11.13.0", - "@mui/icons-material": "^6.1.7", - "@mui/x-date-pickers": "^7.20.0", - "dayjs": "^1.11.13", - "react-hook-form": "^7.53.2", - "@hookform/resolvers": "^3.9.1", - "yup": "^1.4.0", - "react-hot-toast": "^2.4.1" - }, - "devDependencies": { - "@eslint/js": "^9.36.0", - "@types/node": "^24.6.0", - "@types/react": "^19.1.16", - "@types/react-dom": "^19.1.9", - "@vitejs/plugin-react": "^5.0.4", - "eslint": "^9.36.0", - "eslint-plugin-react-hooks": "^5.2.0", - "eslint-plugin-react-refresh": "^0.4.22", - "globals": "^16.4.0", - "typescript": "~5.9.3", - "typescript-eslint": "^8.45.0", - "vite": "^7.1.7" - } -} diff --git a/frontend/public/vite.svg b/frontend/public/vite.svg deleted file mode 100644 index e7b8dfb1b2a60bd50538bec9f876511b9cac21e3..0000000000000000000000000000000000000000 --- a/frontend/public/vite.svg +++ /dev/null @@ -1 +0,0 @@ - \ No newline at end of file diff --git a/frontend/src/App.css b/frontend/src/App.css deleted file mode 100644 index b9d355df2a5956b526c004531b7b0ffe412461e0..0000000000000000000000000000000000000000 --- a/frontend/src/App.css +++ /dev/null @@ -1,42 +0,0 @@ -#root { - max-width: 1280px; - margin: 0 auto; - padding: 2rem; - text-align: center; -} - -.logo { - height: 6em; - padding: 1.5em; - will-change: filter; - transition: filter 300ms; -} -.logo:hover { - filter: drop-shadow(0 0 2em #646cffaa); -} -.logo.react:hover { - filter: drop-shadow(0 0 2em #61dafbaa); -} - -@keyframes logo-spin { - from { - transform: rotate(0deg); - } - to { - transform: rotate(360deg); - } -} - -@media (prefers-reduced-motion: no-preference) { - a:nth-of-type(2) .logo { - animation: logo-spin infinite 20s linear; - } -} - -.card { - padding: 2em; -} - -.read-the-docs { - color: #888; -} diff --git a/frontend/src/App.tsx b/frontend/src/App.tsx deleted file mode 100644 index 7ccbd4b16beb56275d30df34e80132de4a01f30f..0000000000000000000000000000000000000000 --- a/frontend/src/App.tsx +++ /dev/null @@ -1,232 +0,0 @@ -// App.tsx - Main application component for LocalLend - -import React, { useState, useEffect } from 'react'; -import { useAuth } from './context/AuthContext'; -import { LoginPage } from './pages/LoginPage'; -import { RegisterPage } from './pages/RegisterPage'; -import { BrowsePage } from './pages/BrowsePage'; -import { DashboardPage } from './pages/DashboardPage'; -import { MyItemsPage } from './pages/MyItemsPage'; -import { MyBookingsPage } from './pages/MyBookingsPage'; -import { AddItemPage } from './pages/AddItemPage'; -import { SearchResultsPage } from './pages/SearchResultsPage'; -import { BookItemPage } from './pages/BookItemPage'; -import { categoryService } from './services/categoryService'; -import type { Category } from './types'; - -type Page = 'home' | 'login' | 'register' | 'browse' | 'search' | 'dashboard' | 'my-items' | 'my-bookings' | 'add-item' | 'book-item'; - -const App = () => { - const { user, isAuthenticated, logout } = useAuth(); - const [searchQuery, setSearchQuery] = useState(''); - const [currentPage, setCurrentPage] = useState('home'); - const [categories, setCategories] = useState([]); - - // Restore last page from localStorage (simple persistence) - useEffect(() => { - const saved = localStorage.getItem('currentPage') as Page | null; - if (saved) { - // If page requires auth and user not logged in, fallback to home - const authPages: Page[] = ['dashboard', 'my-items', 'my-bookings', 'add-item', 'book-item']; - if (authPages.includes(saved) && !user) { - setCurrentPage('home'); - } else { - setCurrentPage(saved); - // Restore selected item for book-item page - if (saved === 'book-item') { - const savedItem = localStorage.getItem('selectedItem'); - if (savedItem) { - try { - setSelectedItem(JSON.parse(savedItem)); - } catch {} - } - } - } - } - }, [user]); - - // Persist page on change - useEffect(() => { - localStorage.setItem('currentPage', currentPage); - if (currentPage !== 'book-item') { - localStorage.removeItem('selectedItem'); - } - }, [currentPage]); - - // Fetch categories for dropdown - useEffect(() => { - const fetchCategories = async () => { - try { - const response = await categoryService.getCategories(); - if (response && response.data) { - setCategories(response.data); - } - } catch (error) { - console.error('Failed to fetch categories:', error); - } - }; - fetchCategories(); - }, []); - - const [selectedItem, setSelectedItem] = useState(null); - - // Auto-route to dashboard after successful login - useEffect(() => { - if (isAuthenticated && user && currentPage === 'login') { - setCurrentPage('dashboard'); - } - }, [isAuthenticated, user, currentPage]); - - const handleLogin = () => setCurrentPage('login'); - - const handleRegister = () => setCurrentPage('register'); - - const handleBrowse = () => setCurrentPage('browse'); - - const handleDashboard = () => setCurrentPage('dashboard'); - - const _handleMyItems = () => setCurrentPage('my-items'); - - const _handleMyBookings = () => setCurrentPage('my-bookings'); - - const handleAddItem = () => setCurrentPage('add-item'); - - const handleBookItem = (item: any) => { - if (!user) { - alert('Please log in to book items'); - return; - } - // Prevent booking own item - if (item?.ownerId === user.id || item?.owner?.id === user.id) { - alert("You can't book your own item."); - return; - } - - setSelectedItem(item); - try { localStorage.setItem('selectedItem', JSON.stringify(item)); } catch {} - setCurrentPage('book-item'); - }; - - const handleLogout = () => { - logout(); - setCurrentPage('home'); - }; - - const handleSearch = (e: React.FormEvent) => { - e.preventDefault(); - if (searchQuery.trim()) { - setCurrentPage('search'); - } - }; - - const navigateToHome = () => { - setCurrentPage('home'); - }; - - // Render different pages based on current page state - if (currentPage === 'login') { - return ; - } - - if (currentPage === 'register') { - return ; - } - - if (currentPage === 'browse') { - return ; - } - - if (currentPage === 'search') { - return ; - } - - if (currentPage === 'dashboard') { - return ; - } - - if (currentPage === 'my-items') { - return ; - } - - if (currentPage === 'my-bookings') { - return ; - } - - if (currentPage === 'add-item') { - return ; - } - - if (currentPage === 'book-item') { - return setCurrentPage('search')} item={selectedItem} />; - } - - return ( -
- - -
-
-

LocalLend - Peer-to-Peer Item Sharing

-

A community platform for borrowing and lending items

-
- -
-
-

Welcome to LocalLend

-

LocalLend is a peer-to-peer item sharing platform where users can:

-
    -
  • List items they own for others to borrow
  • -
  • Browse and search items available in their community
  • -
  • Request to borrow items from other users
  • -
  • Manage bookings (approve, track, complete)
  • -
  • Rate users and items after completed transactions
  • -
  • Build trust scores through positive interactions
  • -
-
- -
- -
-

LocalLend Frontend - Built with React + TypeScript + Vite

-

Backend API: http://localhost:8080

-

Frontend: http://localhost:5173

-
-
-
- ); -}; - -export default App; diff --git a/frontend/src/App.tsx.backup b/frontend/src/App.tsx.backup deleted file mode 100644 index 65cc603ff5ce3f6b251ad136e69541720577cd2d..0000000000000000000000000000000000000000 --- a/frontend/src/App.tsx.backup +++ /dev/null @@ -1,2048 +0,0 @@ -// App.tsx - Main application component for LocalLend -import React, { useState } from 'react'; -import { useAuth } from './context/AuthContext'; -import { LoginPage } from './pages/LoginPage'; -import { RegisterPage } from './pages/RegisterPage'; -import { BrowsePage } from './pages/BrowsePage'; -import { DashboardPage } from './pages/DashboardPage'; -import { MyItemsPage } from './pages/MyItemsPage'; -import { MyBookingsPage } from './pages/MyBookingsPage'; -import { AddItemPage } from './pages/AddItemPage'; -import { SearchResultsPage } from './pages/SearchResultsPage'; -import { BookItemPage } from './pages/BookItemPage'; - -type Page = 'home' | 'login' | 'register' | 'browse' | 'search' | 'dashboard' | 'my-items' | 'my-bookings' | 'add-item' | 'book-item'; - -const App = () => { - const { user } = useAuth(); - const [myItems, setMyItems] = React.useState([]); - const [myBookings, setMyBookings] = React.useState([]); - const [receivedBookings, setReceivedBookings] = React.useState([]); - const [isLoading, setIsLoading] = React.useState(true); - - React.useEffect(() => { - const loadDashboardData = async () => { - try { - setIsLoading(true); - console.log('=== DASHBOARD DATA LOADING START ==='); - console.log('Current user:', user); - console.log('Current user ID:', user?.id, 'Type:', typeof user?.id); - console.log('JWT Token exists:', !!localStorage.getItem('token')); - - // Load items I'm lending (owned by me) - const itemsResponse = await fetch(`${import.meta.env.VITE_API_BASE_URL || 'http://localhost:8080'}/api/items?ownerId=${user?.id}`, { - headers: { - 'Content-Type': 'application/json', - 'Authorization': `Bearer ${localStorage.getItem('token')}`, - 'X-User-Id': user?.id?.toString() || '' - } - }); - if (itemsResponse.ok) { - const itemsData = await itemsResponse.json(); - console.log('Items API response:', itemsData); - // Backend returns paginated data with 'content' array, not 'data' array - const allItems = itemsData.content || itemsData.data || []; - console.log('All items from API:', allItems); - console.log('Query URL was:', `${import.meta.env.VITE_API_BASE_URL || 'http://localhost:8080'}/api/items?ownerId=${user?.id}`); - - // Filter items to only show items owned by current user (frontend filtering as backup) - const myOwnedItems = allItems.filter((item: any) => { - console.log(`Comparing item.ownerId (${item.ownerId}) === user.id (${user?.id}):`, item.ownerId === user?.id); - return item.ownerId === user?.id || item.owner_id === user?.id; - }); - console.log('Filtered myItems to:', myOwnedItems); - setMyItems(myOwnedItems); - } else { - console.log('Items API failed:', itemsResponse.status); - } - - // Try to load my bookings (items I've borrowed) - gracefully handle server errors - try { - console.log('Fetching my bookings for user:', user?.id); - - // Try multiple possible endpoints for my bookings - let bookingsResponse; - let endpointUsed = ''; - - // Try endpoint 1: /api/bookings/my-bookings (confirmed working by backend) - try { - endpointUsed = '/api/bookings/my-bookings'; - bookingsResponse = await fetch(`${import.meta.env.VITE_API_BASE_URL || 'http://localhost:8080'}/api/bookings/my-bookings`, { - headers: { - 'Content-Type': 'application/json', - 'X-User-Id': user?.id?.toString() || '' - } - }); - - if (!bookingsResponse.ok && bookingsResponse.status === 404) { - // Try endpoint 2: /api/bookings/user/{userId} (alternative pattern) - endpointUsed = `/api/bookings/user/${user?.id}`; - bookingsResponse = await fetch(`${import.meta.env.VITE_API_BASE_URL || 'http://localhost:8080'}/api/bookings/user/${user?.id}`, { - headers: { - 'Content-Type': 'application/json', - 'Authorization': `Bearer ${localStorage.getItem('token')}`, - 'X-User-Id': user?.id?.toString() || '' - } - }); - } - - if (!bookingsResponse.ok && bookingsResponse.status === 404) { - // Try endpoint 3: /api/bookings (with query param) - endpointUsed = `/api/bookings?borrowerId=${user?.id}`; - bookingsResponse = await fetch(`${import.meta.env.VITE_API_BASE_URL || 'http://localhost:8080'}/api/bookings?borrowerId=${user?.id}`, { - headers: { - 'Content-Type': 'application/json', - 'Authorization': `Bearer ${localStorage.getItem('token')}`, - 'X-User-Id': user?.id?.toString() || '' - } - }); - } - } catch (fetchError) { - console.log('Error trying booking endpoints:', fetchError); - setMyBookings([]); - return; - } - - console.log('Bookings response status:', bookingsResponse.status, 'from endpoint:', endpointUsed); - - if (bookingsResponse.ok) { - const bookingsData = await bookingsResponse.json(); - console.log('โœ… BOOKINGS SUCCESS - API response:', bookingsData); - console.log('Bookings response type:', typeof bookingsData, 'is array:', Array.isArray(bookingsData)); - // Handle both paginated (content) and direct array responses - const bookings = bookingsData.content || bookingsData.data || bookingsData || []; - console.log('Final bookings array:', bookings, 'length:', bookings.length); - - // Let's also try to fetch ALL bookings to see what exists in the system - try { - const allBookingsResponse = await fetch(`${import.meta.env.VITE_API_BASE_URL || 'http://localhost:8080'}/api/bookings`, { - headers: { - 'Content-Type': 'application/json', - 'Authorization': `Bearer ${localStorage.getItem('token')}`, - 'X-User-Id': user?.id?.toString() || '' - } - }); - if (allBookingsResponse.ok) { - const allBookingsData = await allBookingsResponse.json(); - console.log('๐Ÿ” ALL BOOKINGS in system:', allBookingsData); - } - } catch (e) { - console.log('Could not fetch all bookings for debugging'); - } - - setMyBookings(bookings); - } else { - const errorText = await bookingsResponse.text(); - console.log('All booking endpoints failed. Last error:', bookingsResponse.status, 'response:', errorText); - setMyBookings([]); - } - } catch (error) { - console.log('Bookings endpoint failed:', error); - setMyBookings([]); - } - - // Try to load bookings received (people wanting to borrow my items) - gracefully handle server errors - try { - console.log('Fetching received bookings for owner:', user?.id); - - // Try multiple possible endpoints for received bookings - let receivedResponse; - let ownerEndpointUsed = ''; - - // Try endpoint 1: /api/bookings/my-owned (confirmed working by backend) - try { - ownerEndpointUsed = '/api/bookings/my-owned'; - receivedResponse = await fetch(`${import.meta.env.VITE_API_BASE_URL || 'http://localhost:8080'}/api/bookings/my-owned`, { - headers: { - 'Content-Type': 'application/json', - 'X-User-Id': user?.id?.toString() || '' - } - }); - - if (!receivedResponse.ok && receivedResponse.status === 404) { - // Try endpoint 2: /api/bookings/my-owned (alternative from earlier conversation) - ownerEndpointUsed = '/api/bookings/my-owned'; - receivedResponse = await fetch(`${import.meta.env.VITE_API_BASE_URL || 'http://localhost:8080'}/api/bookings/my-owned`, { - headers: { - 'Content-Type': 'application/json', - 'Authorization': `Bearer ${localStorage.getItem('token')}`, - 'X-User-Id': user?.id?.toString() || '' - } - }); - } - - if (!receivedResponse.ok && receivedResponse.status === 404) { - // Try endpoint 3: /api/bookings/owner/{userId} - ownerEndpointUsed = `/api/bookings/owner/${user?.id}`; - receivedResponse = await fetch(`${import.meta.env.VITE_API_BASE_URL || 'http://localhost:8080'}/api/bookings/owner/${user?.id}`, { - headers: { - 'Content-Type': 'application/json', - 'Authorization': `Bearer ${localStorage.getItem('token')}`, - 'X-User-Id': user?.id?.toString() || '' - } - }); - } - - if (!receivedResponse.ok && receivedResponse.status === 404) { - // Try endpoint 4: /api/bookings (with query param) - ownerEndpointUsed = `/api/bookings?ownerId=${user?.id}`; - receivedResponse = await fetch(`${import.meta.env.VITE_API_BASE_URL || 'http://localhost:8080'}/api/bookings?ownerId=${user?.id}`, { - headers: { - 'Content-Type': 'application/json', - 'Authorization': `Bearer ${localStorage.getItem('token')}`, - 'X-User-Id': user?.id?.toString() || '' - } - }); - } - } catch (fetchError) { - console.log('Error trying owner booking endpoints:', fetchError); - setReceivedBookings([]); - return; - } - - console.log('Received bookings response status:', receivedResponse.status, 'from endpoint:', ownerEndpointUsed); - - if (receivedResponse.ok) { - const receivedData = await receivedResponse.json(); - console.log('Received bookings API response:', receivedData); - console.log('Received bookings response type:', typeof receivedData, 'is array:', Array.isArray(receivedData)); - // Handle both paginated (content) and direct array responses - const bookings = receivedData.content || receivedData.data || receivedData || []; - console.log('Setting receivedBookings to:', bookings, 'length:', bookings.length); - setReceivedBookings(bookings); - } else { - const errorText = await receivedResponse.text(); - console.log('All owner booking endpoints failed. Last error:', receivedResponse.status, 'response:', errorText); - setReceivedBookings([]); - } - } catch (error) { - console.log('Received bookings endpoint failed:', error); - setReceivedBookings([]); - } - } catch (error) { - console.error('Error loading dashboard data:', error); - // Set empty arrays if there's a general error - setMyItems([]); - setMyBookings([]); - setReceivedBookings([]); - } finally { - setIsLoading(false); - } - }; - - if (user) { - loadDashboardData(); - } - }, [user]); - - // Debug: Log current state values - console.log('Dashboard render - myItems:', myItems, 'myBookings:', myBookings, 'receivedBookings:', receivedBookings); - - return ( -
- {/* Navigation Bar */} - - - {/* Dashboard Content */} -
-

Dashboard

- - {isLoading ? ( -
-

Loading your dashboard...

-
- ) : ( -
- - {/* My Items Section */} -
-

My Items ({myItems.length})

-

Items you're lending to others

- - {myItems.length === 0 ? ( -

No items listed yet. Start lending!

- ) : ( -
- {myItems.map((item) => ( -
-

{item.name || item.title}

-

{item.description}

-
- - {item.status} - - - {item.condition} - -
-
- ))} -
- )} -
- - {/* My Bookings Section */} -
-

My Bookings ({myBookings.length})

-

Items you've borrowed from others

- - {myBookings.length === 0 ? ( -

No active bookings. Browse items to borrow!

- ) : ( -
- {myBookings.map((booking) => ( -
-

{booking.itemTitle}

-

- {booking.startDate} to {booking.endDate} -

- - {booking.status} - -
- ))} -
- )} -
- - {/* Booking Requests Section */} -
-

Booking Requests ({receivedBookings.length})

-

People wanting to borrow your items

- - {receivedBookings.length === 0 ? ( -

No pending booking requests.

- ) : ( -
- {receivedBookings.map((booking) => ( -
-
-

{booking.itemTitle}

-

- Requested by: {booking.borrowerName} -

-

- From: {booking.startDate} -

-
-
- - -
-
- ))} -
- )} -
- -
- )} - - {/* Quick Actions */} -
- - - - - -
-
-
- ); -}; - -const MyItemsPage: React.FC = ({ onBack }) => ( -
-

My Items

-

Here you can manage the items you're lending out to others.

- -
-); - -const MyBookingsPage: React.FC = ({ onBack }) => ( -
-

My Bookings

-

Here you can see all the items you've booked from other users.

- -
-); - -const AddItemPage: React.FC = ({ onBack }) => { - const { user } = useAuth(); - const [isLoading, setIsLoading] = React.useState(false); - const [categories, setCategories] = React.useState([]); - const [formData, setFormData] = React.useState({ - title: '', - description: '', - category: '', - condition: 'GOOD', - pricePerDay: '', - availableFrom: '', - availableTo: '', - pickupLocation: '', - specialInstructions: '' - }); - - // Load categories from backend on component mount - React.useEffect(() => { - const fetchCategories = async () => { - try { - const response = await fetch(`${import.meta.env.VITE_API_BASE_URL || 'http://localhost:8080'}/api/categories`); - if (response.ok) { - const categoryResponse = await response.json(); - console.log('Categories loaded:', categoryResponse); - // Extract the data array from the response - const categoryData = categoryResponse.data || categoryResponse; - setCategories(categoryData); - } else { - console.error('Failed to load categories'); - // Fallback categories if API fails - setCategories([ - { id: '1', name: 'Electronics' }, - { id: '2', name: 'Tools' }, - { id: '3', name: 'Sports' }, - { id: '4', name: 'Books' }, - { id: '5', name: 'Furniture' }, - { id: '6', name: 'Vehicles' }, - { id: '7', name: 'Appliances' }, - { id: '8', name: 'Other' } - ]); - } - } catch (error) { - console.error('Error fetching categories:', error); - // Fallback categories if API fails - setCategories([ - { id: '1', name: 'Electronics' }, - { id: '2', name: 'Tools' }, - { id: '3', name: 'Sports' }, - { id: '4', name: 'Books' }, - { id: '5', name: 'Furniture' }, - { id: '6', name: 'Vehicles' }, - { id: '7', name: 'Appliances' }, - { id: '8', name: 'Other' } - ]); - } - }; - - fetchCategories(); - }, []); - - const conditions = [ - { value: 'EXCELLENT', label: 'Excellent - Like new' }, - { value: 'GOOD', label: 'Good - Minor wear' }, - { value: 'FAIR', label: 'Fair - Noticeable wear' }, - { value: 'POOR', label: 'Poor - Significant wear' } - ]; - - const handleChange = (e: React.ChangeEvent) => { - const { name, value } = e.target; - setFormData(prev => ({ - ...prev, - [name]: value - })); - }; - - const handleSubmit = async (e: React.FormEvent) => { - e.preventDefault(); - - if (!user) { - alert('You must be logged in to add items'); - return; - } - - // Client-side validation - if (formData.title.length < 3 || formData.title.length > 100) { - alert('Item title must be between 3 and 100 characters long'); - return; - } - - if (formData.description.length < 10 || formData.description.length > 500) { - alert('Item description must be between 10 and 500 characters long'); - return; - } - - if (!formData.category) { - alert('Please select a category'); - return; - } - - if (formData.pricePerDay && parseFloat(formData.pricePerDay) < 0) { - alert('Price per day must be a positive number'); - return; - } - - setIsLoading(true); - - try { - // Prepare item data matching the backend CreateItemRequest interface - const itemData = { - name: formData.title, // Backend expects 'name', not 'title' - description: formData.description, - categoryId: formData.category, // This is now the actual category ID from the backend - condition: formData.condition, - deposit: formData.pricePerDay ? parseFloat(formData.pricePerDay) : undefined, - images: [] // No image upload implemented yet - }; - - console.log('Submitting item data:', itemData); - - // Use the itemService to create the item (it handles auth headers properly) - const newItem = await fetch(`${import.meta.env.VITE_API_BASE_URL || 'http://localhost:8080'}/api/items`, { - method: 'POST', - headers: { - 'Content-Type': 'application/json', - 'Authorization': `Bearer ${localStorage.getItem('token')}`, - 'X-User-Id': user.id.toString() - }, - body: JSON.stringify(itemData) - }); - - if (newItem.ok) { - const createdItem = await newItem.json(); - console.log('Item created successfully:', createdItem); - alert(`Item "${createdItem.name || formData.title}" added successfully!`); - - // Reset form - setFormData({ - title: '', - description: '', - category: '', - condition: 'GOOD', - pricePerDay: '', - availableFrom: '', - availableTo: '', - pickupLocation: '', - specialInstructions: '' - }); - - onBack(); // Go back to dashboard/home - } else { - const errorResponse = await newItem.text(); - console.error('Server error response:', errorResponse); - - let errorMessage = 'Failed to add item'; - try { - const errorData = JSON.parse(errorResponse); - errorMessage = errorData.message || errorData.error || errorMessage; - } catch (e) { - errorMessage = errorResponse || errorMessage; - } - - throw new Error(`Server error (${newItem.status}): ${errorMessage}`); - } - } catch (error: any) { - console.error('Error adding item:', error); - - if (error.name === 'TypeError' && error.message.includes('fetch')) { - alert('Network error: Cannot connect to backend server. Please ensure the backend is running on http://localhost:8080'); - } else { - alert(`Error adding item: ${error.message}`); - } - } finally { - setIsLoading(false); - } - }; - - return ( -
- {/* Navigation Bar */} - - - {/* Add Item Form */} -
-

Add New Item

-

- List an item you'd like to lend to others in your community. -

- -
-
- {/* Title */} -
- - - {formData.title.length > 0 && formData.title.length < 3 && ( -
- Title must be at least 3 characters long -
- )} - {formData.title.length > 100 && ( -
- Title must be no more than 100 characters long -
- )} -
- - {/* Description */} -
- -