Add rental system: listings, bookings, payments, payouts, reviews

Full rental marketplace with 6 categories (apartment, house, car, motorcycle, bicycle, ebike).
Booking workflow: create → confirm → pay → active → complete → payout.
Landlord dashboard, admin moderation, availability calendar, Stripe Connect payouts.
14 QA bugs found and fixed including validator schemas, API response types, HTTP methods.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
delta-lynx-89e8
2026-02-22 15:33:29 -08:00
parent 8961fa701a
commit dbbbbd26f4
90 changed files with 11052 additions and 124 deletions

View File

@@ -1,81 +1,214 @@
# Color Wheel Palette Generator
# Marketplace
An interactive color palette generator built with React, TypeScript, and Tailwind CSS v4. Pick a base color on a color wheel, choose a harmony type, adjust tint/shade, and export palettes in multiple formats.
## Features
- **Interactive Color Wheel** — Canvas-based HSL wheel with drag-to-select hue and saturation. Touch support for mobile.
- **7 Harmony Types** — Complementary, Split-Complementary, Analogous, Triadic, Tetradic, Square, Monochromatic.
- **Tint & Shade Slider** — Adjust lightness in real-time.
- **Color Input** — Enter HEX values directly, view RGB/HSL, adjust via RGB sliders.
- **Click-to-Copy** — Copy any color value (HEX, RGB, HSL) to clipboard.
- **Export** — CSS variables, JSON, Tailwind config snippet, or PNG image.
- **Dark / Light Theme** — Toggle between dark and light modes.
- **Responsive** — Mobile-first layout with horizontal scroll palette on small screens.
## Setup
```bash
npm install
npm run dev
```
Open `http://localhost:5173` in your browser.
## Build
```bash
npm run build
npm run preview
```
## Architecture
```
src/
├── components/ # UI components
│ ├── ColorWheel/ # Canvas-based color wheel with pointer events
│ ├── PaletteDisplay/ # Generated swatch grid
│ ├── HarmonySelector/ # Dropdown for harmony type
│ ├── ColorInput/ # HEX/RGB/HSL inputs and sliders
│ ├── TintShadeSlider/ # Lightness adjustment
│ ├── ExportPanel/ # CSS/JSON/Tailwind/PNG export
│ ├── Header/ # App header with theme toggle
│ └── MobileNav/ # Bottom sheet navigation for mobile
├── hooks/
│ ├── useColorWheel.ts # Wheel interaction (drag, position calculation)
│ └── useColorHarmony.ts # Harmony palette generation
├── utils/
│ ├── colorConversions.ts # HEX <-> RGB <-> HSL converters
│ └── harmonies.ts # Harmony algorithms
├── App.tsx # Main layout
├── main.tsx # Entry point
└── index.css # Tailwind directives + theme variables
```
## Harmony Algorithms
| Type | Colors Generated |
|---|---|
| Complementary | Base + 180° |
| Split-Complementary | Base, +150°, +210° |
| Analogous | -30°, Base, +30° |
| Triadic | Base, +120°, +240° |
| Tetradic | Base, +90°, +180°, +270° |
| Square | Base, +90°, +180°, +270° |
| Monochromatic | Same hue, varied S/L |
## MCP Integration
The project includes MCP server configuration in `.claude/settings.json`:
- **Figma MCP** — Connect to Figma to read design files and create new frames.
- **Chrome DevTools MCP** — Debug the running app, inspect elements, and validate layouts.
A full-stack online marketplace and rental platform where users can buy, sell, and rent items ranging from electronics and furniture to apartments, cars, and bicycles. The platform includes real-time chat, an offer/negotiation system, Stripe-powered payments, and a multi-role admin panel with content moderation.
## Tech Stack
- React 19 + TypeScript
- Vite
- Tailwind CSS v4
- HTML5 Canvas API
- Clipboard API
| Layer | Technology |
|----------------|------------------------------------------------------|
| **Client** | React 19, TypeScript, Tailwind CSS 4, Vite 6 |
| **Routing** | React Router 7 |
| **Server** | Express 4, TypeScript, tsx (dev runner) |
| **Database** | PostgreSQL 17 (Docker), Prisma ORM 6 |
| **Auth** | JWT (access + refresh tokens), bcryptjs |
| **Payments** | Stripe (PaymentIntents, Connect, Webhooks) |
| **Real-time** | Socket.io 4 (notifications, chat) |
| **File Upload**| Multer (local storage) |
| **Validation** | Zod |
| **Charts** | Recharts 3 (admin dashboard) |
| **Icons** | Lucide React |
## Features
- **Listings** -- Create, browse, search, and filter buy/sell listings across categories (Electronics, Furniture, Clothing, Vehicles, etc.)
- **Offers & Negotiation** -- Make offers, counter, accept, or decline with real-time notifications
- **Rentals** -- Full rental system for apartments, houses, cars, motorcycles, bicycles, and e-bikes with daily/monthly pricing, booking management, deposits, and cancellation policies
- **Chat** -- Real-time messaging between buyers/sellers and tenants/landlords via Socket.io
- **Payments** -- Stripe integration for listing fees, commissions, rental bookings, deposits, and landlord payouts via Stripe Connect
- **Subscriptions & Promotions** -- Tiered seller subscriptions (Basic, Pro, Business) and listing promotion/boosting
- **Notifications** -- Real-time in-app notifications for offers, messages, bookings, payouts, and moderation actions
- **Moderation** -- Content reporting, review queue, user banning, and moderation logs
- **Admin Panel** -- Dashboard with analytics, user management, listing management, and moderation tools
- **Auth & Roles** -- JWT authentication with four roles: `USER`, `MODERATOR`, `ADMIN`, `SUPER_ADMIN`
- **Location** -- Google Maps autocomplete for listing and rental locations
## Prerequisites
- **Node.js** >= 18
- **npm** >= 9 (uses npm workspaces)
- **Docker** (for PostgreSQL)
## Getting Started
### 1. Start the Database
If the Docker container already exists:
```bash
docker start marketplace-postgres
```
If you need to create it from scratch:
```bash
docker run -d \
--name marketplace-postgres \
-e POSTGRES_USER=marketplace \
-e POSTGRES_PASSWORD=marketplace_dev \
-e POSTGRES_DB=marketplace \
-p 5432:5432 \
-v marketplace-pgdata:/var/lib/postgresql/data \
--restart unless-stopped \
postgres:17-alpine
```
Verify it is running:
```bash
docker ps -f name=marketplace-postgres
```
### 2. Install Dependencies
From the project root:
```bash
npm install
```
This installs dependencies for both the `client` and `server` workspaces.
### 3. Configure Environment Variables
Create `server/.env`:
```env
DATABASE_URL=postgresql://marketplace:marketplace_dev@localhost:5432/marketplace
JWT_SECRET=your-secret-key
JWT_REFRESH_SECRET=your-refresh-secret-key
STRIPE_SECRET_KEY=sk_test_...
STRIPE_PUBLISHABLE_KEY=pk_test_...
STRIPE_WEBHOOK_SECRET=whsec_...
GOOGLE_MAPS_API_KEY=your-google-maps-key
CLIENT_URL=http://localhost:5173
UPLOAD_DIR=./uploads
PORT=3000
```
For local development, only `DATABASE_URL` is strictly required. Stripe keys are optional (payment features will be disabled). The server provides sensible defaults for all other values.
### 4. Set Up the Database Schema
```bash
cd server
npx prisma db push
```
Or use migrations:
```bash
cd server
npx prisma migrate dev
```
### 5. Seed the Database (Optional)
```bash
cd server
npx tsx prisma/seed.ts
```
### 6. Run the Application
Start both client and server simultaneously:
```bash
npm run dev
```
Or run them separately:
```bash
npm run dev:server # Express API on http://localhost:3000
npm run dev:client # React app on http://localhost:5173
```
## Seed Data
After seeding, the following test users are available (all use password `password123`):
| Email | Name |
|----------------------------|----------------|
| alice.chen@example.com | Alice Chen |
| bob.smith@example.com | Bob Smith |
| carol.jones@example.com | Carol Jones |
| david.wilson@example.com | David Wilson |
| eva.martinez@example.com | Eva Martinez |
## Project Structure
```
marketplace/
├── package.json # Root workspace config
├── CLAUDE.md # Dev notes (DB setup, commands)
├── docs/ # Documentation
│ ├── README.md # This file
│ └── architecture.md # Architecture overview
├── client/ # React frontend
│ ├── package.json
│ └── src/
│ ├── api/ # API client functions
│ ├── components/ # Reusable UI components
│ ├── context/ # React context providers
│ ├── pages/ # Route page components
│ ├── router.tsx # Route definitions
│ ├── types/ # TypeScript type definitions
│ ├── utils/ # Utility functions
│ ├── App.tsx # App root component
│ └── main.tsx # Entry point
└── server/ # Express backend
├── package.json
├── prisma/
│ ├── schema.prisma # Database schema
│ └── seed.ts # Seed script
└── src/
├── config/ # Environment config
├── middleware/ # Auth, upload, validation, error handling
├── routes/ # API route handlers
│ └── admin/ # Admin-specific routes
├── socket/ # Socket.io setup and handlers
├── utils/ # Shared utilities
├── validators/ # Zod validation schemas
└── index.ts # Server entry point
```
## Environment Variables Reference
| Variable | Required | Default | Description |
|---------------------------|----------|----------------------------------|------------------------------------|
| `DATABASE_URL` | Yes | localhost connection string | PostgreSQL connection URL |
| `PORT` | No | `3000` | Server port |
| `JWT_SECRET` | No* | Dev fallback | Access token signing secret |
| `JWT_REFRESH_SECRET` | No* | Dev fallback | Refresh token signing secret |
| `CLIENT_URL` | No | `http://localhost:5173` | CORS allowed origin |
| `UPLOAD_DIR` | No | `./uploads` | File upload directory |
| `STRIPE_SECRET_KEY` | No | Empty (payments disabled) | Stripe secret key |
| `STRIPE_PUBLISHABLE_KEY` | No | Empty (payments disabled) | Stripe publishable key |
| `STRIPE_WEBHOOK_SECRET` | No | Empty | Stripe webhook signing secret |
| `GOOGLE_MAPS_API_KEY` | No | Dev key | Google Maps API key |
*Must be set to secure values in production.
## Useful Commands
| Command | Description |
|---------------------------|------------------------------------------|
| `npm run dev` | Start client + server concurrently |
| `npm run dev:client` | Start React dev server (port 5173) |
| `npm run dev:server` | Start Express dev server (port 3000) |
| `npm run build` | Build client and server for production |
| `npm run lint` | Lint client code |
| `npx prisma studio` | Open Prisma Studio (DB browser) |
| `npx prisma db push` | Push schema changes to database |
| `npx prisma migrate dev` | Create and apply a migration |
| `npx tsx prisma/seed.ts` | Seed the database |

332
docs/architecture.md Normal file
View File

@@ -0,0 +1,332 @@
# Architecture
## High-Level Overview
```
┌──────────────────────────────────────────────────────────────┐
│ Client (React) │
│ http://localhost:5173 (Vite) │
│ │
│ ┌──────────┐ ┌──────────┐ ┌───────────┐ ┌────────────┐ │
│ │ Pages │ │Components│ │ Context │ │ API Layer │ │
│ └──────────┘ └──────────┘ └───────────┘ └─────┬──────┘ │
│ │ │
└────────────────────────────────────────────────────┼─────────┘
REST API (HTTP) │ WebSocket │
│ (Socket.io) │
┌──────────────────────────────────┼─────────────────┼─────────┐
│ Server (Express) │ │
│ http://localhost:3000 │ │
│ │ │
│ ┌───────────┐ ┌────────────┐ ┌──────────────┐ │ │
│ │ Middleware │ │ Routes │ │ Socket.io │◄─┘ │
│ │ (auth, │ │ (REST API) │ │ (real-time) │ │
│ │ upload, │ └─────┬──────┘ └──────┬───────┘ │
│ │ validate)│ │ │ │
│ └───────────┘ │ │ │
│ ┌────┴────────────────┴──────┐ │
│ │ Prisma ORM │ │
│ └────────────┬───────────────┘ │
│ │ │
└───────────────────────────────┼──────────────────────────────┘
┌───────────────────────────────┼──────────────────────────────┐
│ PostgreSQL 17 (Docker) │
│ marketplace-postgres container │
│ Volume: marketplace-pgdata │
└──────────────────────────────────────────────────────────────┘
External Services:
┌─────────┐ ┌──────────────┐
│ Stripe │ │ Google Maps │
│ (pay) │ │ (location) │
└─────────┘ └──────────────┘
```
## Client-Server Communication
### REST API
The client communicates with the server via a JSON REST API. All endpoints are prefixed with `/api/`.
| Prefix | Purpose |
|-----------------------|----------------------------------------------|
| `/api/auth` | Registration, login, token refresh, logout |
| `/api/users` | User profiles, settings, privacy |
| `/api/listings` | CRUD for buy/sell listings, search, favorites|
| `/api/offers` | Create, counter, accept, decline offers |
| `/api/chat` | Conversations and message history |
| `/api/notifications` | Fetch and mark notifications as read |
| `/api/payments` | Stripe payment intents, webhooks |
| `/api/location` | Google Maps autocomplete proxy |
| `/api/reports` | Content/user reporting |
| `/api/subscriptions` | Seller subscription management |
| `/api/promotions` | Listing promotion/boost |
| `/api/admin` | Admin dashboard, user/listing management |
| `/api/rentals` | Rental listing CRUD and search |
| `/api/bookings` | Booking lifecycle management |
| `/api/rental-payments`| Rental payment processing and webhooks |
| `/api/payouts` | Landlord payout management |
| `/api/rental-reviews` | Tenant/landlord reviews |
| `/api/health` | Health check endpoint |
### Socket.io (WebSocket)
Socket.io provides the real-time communication layer. The server creates a Socket.io instance attached to the same HTTP server as Express.
**Events flow:**
- The client connects with an auth token after login
- The server authenticates the socket connection and associates it with a user
- Real-time events are emitted for: new messages, notifications (offers, bookings, moderation), typing indicators, and online status
## Database Layer
### Prisma ORM
The database schema is defined in `server/prisma/schema.prisma`. Prisma generates a type-safe client used throughout the server routes.
**Key models:**
| Model | Purpose |
|-------------------|--------------------------------------------------|
| `User` | Accounts with roles, privacy settings, landlord status |
| `Session` | JWT refresh token sessions |
| `Listing` | Buy/sell items with category, condition, price |
| `ListingImage` | Image attachments for listings |
| `Offer` | Price negotiation between buyer and seller |
| `Conversation` | Chat threads between two users |
| `Message` | Individual chat messages |
| `Favorite` | User bookmarks on listings |
| `Notification` | In-app notifications (19 distinct types) |
| `Payment` | Payment records (8 payment types) |
| `Report` | Content/user reports for moderation |
| `Subscription` | Seller tier subscriptions |
| `PromotedListing` | Boosted listing placements |
| `ModerationLog` | Audit trail for moderation actions |
| `BlockedUser` | User blocking relationships |
| `RentalListing` | Rental items (apartments, cars, bikes, etc.) |
| `Booking` | Rental booking with status lifecycle |
| `RentalReview` | Reviews for completed rentals |
| `Payout` | Landlord payout tracking |
### Enums
The schema uses PostgreSQL enums extensively for type safety:
- **User roles:** `USER`, `MODERATOR`, `ADMIN`, `SUPER_ADMIN`
- **Listing categories:** `ELECTRONICS`, `FURNITURE`, `CLOTHING`, `HOME_GARDEN`, `SPORTS`, `BOOKS`, `GAMES`, `VEHICLES`, `OTHER`
- **Rental categories:** `APARTMENT`, `HOUSE`, `CAR`, `MOTORCYCLE`, `BICYCLE`, `EBIKE`
- **Booking status:** `PENDING` -> `CONFIRMED` -> `ACTIVE` -> `COMPLETED` (or `CANCELLED_*` / `REJECTED` / `EXPIRED`)
- **Cancellation policy:** `FLEXIBLE`, `MODERATE`, `STRICT`
## Authentication Flow
The application uses JWT-based authentication with access and refresh tokens.
```
┌────────┐ ┌────────┐
│ Client │ │ Server │
└───┬────┘ └───┬────┘
│ POST /api/auth/login │
│ { email, password } │
├──────────────────────────────────────►│
│ │ Verify password (bcryptjs)
│ │ Generate access token (short-lived)
│ │ Generate refresh token (long-lived)
│ │ Store session in DB
│ { accessToken, user } │
│ Set-Cookie: refreshToken (httpOnly) │
│◄──────────────────────────────────────┤
│ │
│ GET /api/listings │
│ Authorization: Bearer <accessToken> │
├──────────────────────────────────────►│
│ │ auth middleware verifies JWT
│ │ checkBanned middleware
│ { listings: [...] } │
│◄──────────────────────────────────────┤
│ │
│ POST /api/auth/refresh │
│ Cookie: refreshToken │
├──────────────────────────────────────►│
│ │ Validate refresh token
│ │ Issue new access token
│ { accessToken } │
│◄──────────────────────────────────────┤
```
### Middleware Chain
Requests pass through several middleware layers:
1. **`helmet`** -- Security headers
2. **`cors`** -- Restricts origins to `CLIENT_URL`
3. **`cookieParser`** -- Parses refresh token cookies
4. **`express-rate-limit`** -- Rate limits auth endpoints (20 requests / 15 min)
5. **`auth`** -- Verifies JWT access token, attaches user to request
6. **`checkBanned`** -- Blocks banned users
7. **`requireRole`** -- Role-based access control (e.g., admin-only routes)
8. **`upload`** -- Multer file upload handling
9. **`validate`** -- Zod schema validation for request bodies
## Payment Flow
### Marketplace Payments (Listings)
Stripe PaymentIntents are used for buy/sell transactions:
```
┌────────┐ ┌────────┐ ┌────────┐
│ Buyer │ │ Server │ │ Stripe │
└───┬────┘ └───┬────┘ └───┬────┘
│ Create PI │ │
├───────────────►│ POST /api/ │
│ │ payments │
│ ├───────────────►│ stripe.paymentIntents.create()
│ │ clientSecret │
│ │◄───────────────┤
│ clientSecret │ │
│◄───────────────┤ │
│ │ │
│ Confirm (Stripe.js) │
├────────────────────────────────►│
│ │ │
│ │ Webhook event │
│ │◄───────────────┤ payment_intent.succeeded
│ │ Update DB │
│ │ Notify seller │
│ │ │
```
### Rental Payments
Rental bookings follow a similar flow with additional handling for:
- **Security deposits** -- Held and refunded after completion
- **Landlord payouts** -- Via Stripe Connect (`stripeAccountId` on User model)
- **Commissions** -- Platform fee deducted from rental payments
- **Separate webhook endpoint** -- `/api/rental-payments/webhook`
### Payment Types
The system tracks eight distinct payment types:
| Type | Description |
|------------------------|-------------------------------------------|
| `LISTING_FEE` | Fee to publish a listing |
| `COMMISSION` | Platform commission on sales |
| `PROMOTION` | Listing boost/promotion fee |
| `SUBSCRIPTION` | Seller subscription payment |
| `RENTAL_BOOKING` | Tenant rental payment |
| `RENTAL_COMMISSION` | Platform fee on rentals |
| `RENTAL_DEPOSIT` | Security deposit hold |
| `RENTAL_DEPOSIT_REFUND`| Deposit refund after completion |
| `RENTAL_PAYOUT` | Payment to landlord via Connect |
## File Uploads
File uploads are handled by **Multer** with local disk storage.
- **Configuration:** `server/src/middleware/upload.ts`
- **Storage directory:** Configurable via `UPLOAD_DIR` env var (default: `./uploads`)
- **Serving:** Static files served at `/uploads/*` via `express.static`
- **Usage:** Listing images, rental listing photos, user avatars
Files are stored on the local filesystem. In production, this should be replaced with cloud storage (e.g., S3, Cloudflare R2).
## Real-Time Communication
Socket.io is configured in `server/src/socket/index.ts` and attached to the HTTP server alongside Express.
### Connection Flow
1. Client establishes WebSocket connection after successful login
2. Server authenticates the connection using the JWT token
3. User is registered as "online" and associated with their socket ID
### Event Types
| Category | Events |
|-----------------|--------------------------------------------------|
| **Chat** | New message, typing indicator, read receipts |
| **Notifications**| New offer, offer response, booking updates, payout status, moderation actions |
| **Presence** | User online/offline status |
### Architecture
```
Client A ──WebSocket──┐
├──► Socket.io Server ──► Emit to Client B
Client B ──WebSocket──┘ │
┌─────┴─────┐
│ Express │
│ routes │
│ can emit │
│ via io │
└───────────┘
```
Express routes access the Socket.io instance via `app.get('io')` to emit events from REST handlers (e.g., sending a notification when an offer is created via the REST API).
## Security
| Concern | Implementation |
|----------------------|---------------------------------------------------|
| HTTP headers | Helmet (XSS, clickjacking, MIME sniffing) |
| CORS | Restricted to `CLIENT_URL` |
| Rate limiting | 20 req/15 min on `/api/auth/login` and `/register`|
| Input validation | Zod schemas on all mutation endpoints |
| Password hashing | bcryptjs |
| Token storage | Refresh token in httpOnly cookie |
| Role-based access | `requireRole` middleware for admin/mod routes |
| User banning | `checkBanned` middleware on protected routes |
| Webhook verification | Stripe webhook signature verification |
## Deployment Considerations
### Database
- Use a managed PostgreSQL service (e.g., AWS RDS, Supabase, Neon) in production
- Run `npx prisma migrate deploy` to apply migrations
- The Docker setup is intended for local development only
### File Storage
- Replace local Multer storage with S3-compatible storage (AWS S3, Cloudflare R2, MinIO)
- Update the upload middleware and static file serving accordingly
### Environment
- Set strong, unique values for `JWT_SECRET` and `JWT_REFRESH_SECRET`
- Use production Stripe keys and configure webhook endpoints
- Set `CLIENT_URL` to the deployed frontend URL
### Scaling
- **Horizontal scaling:** Socket.io requires a Redis adapter (`@socket.io/redis-adapter`) for multi-instance deployments
- **Static assets:** Serve the built React app via a CDN or reverse proxy (Nginx, Cloudflare)
- **API:** The Express server is stateless (JWT-based auth) and can be scaled horizontally behind a load balancer
### Recommended Production Stack
```
┌───────────┐
│ CDN / │
│ Nginx │
└─────┬─────┘
┌───────────┴───────────┐
│ │
┌────────▼─────┐ ┌─────────▼────────┐
│ React Static │ │ Express API x N │
│ (built) │ │ (load balanced) │
└──────────────┘ └────────┬──────────┘
┌───────────────┼───────────────┐
│ │ │
┌──────▼──┐ ┌──────▼──┐ ┌───────▼──┐
│PostgreSQL│ │ Redis │ │ S3 / R2 │
│(managed) │ │(sockets)│ │ (files) │
└──────────┘ └─────────┘ └──────────┘
```