Skip to content

mkbrown261/Park-Peer

Repository files navigation

ParkPeer β€” P2P Parking Marketplace

"Your Space. Their Spot." β€” The Airbnb for parking driveways, garages, and lots.

🌐 Live URL

App: https://parkpeer.pages.dev Admin Panel: https://parkpeer.pages.dev/admin/login

🎯 Project Overview

  • Goal: Peer-to-peer parking marketplace where users list private parking and drivers book affordable spots
  • Target Markets: Urban downtown areas, airports, stadiums, universities
  • MVP City: Chicago, IL

🎨 Branding

Token Value
Primary Electric Indigo #5B2EFF
Accent Neon Lime #C6FF00
Dark Background Charcoal Black #121212
Font Inter

πŸ“„ Pages & Routes

Route Description
/ Landing page β€” hero, how-it-works, calculator, testimonials
/search Search page with split map/listings panel
/listing/:id Listing detail with photos, reviews, booking widget
/booking/:id Checkout flow with Stripe-ready payment UI
/dashboard Driver dashboard β€” active bookings, history, favorites
/host Host dashboard β€” listings, booking requests, earnings
/auth/login Login page with social auth options
/auth/signup Signup with role selection (Driver / Host)
/admin Admin panel β€” users, listings, bookings, disputes

πŸ”Œ API Endpoints

Method Route Description
GET /api/health System health check
GET /api/listings Search listings (q, type, min/max price)
GET /api/listings/:id Get single listing detail
POST /api/bookings Create a booking
GET /api/bookings Get user bookings
GET /api/reviews/listing/:id Get listing reviews
GET /api/estimate-earnings Calculate host earnings
GET /api/listings/:id/availability Check availability
GET /api/admin/stats Admin platform statistics

πŸ›‘οΈ Admin User Management System

Admin Control Panel (/admin/user-control)

Full user lifecycle management with compliance, refunds, and auditing.

Features Implemented

Feature Status
Paginated user list with search/filter βœ…
Suspend / Unsuspend accounts βœ…
Delete account with PII scrub (GDPR) βœ…
Blocker enforcement (disputes, active bookings) βœ…
Force-override with admin password re-entry βœ…
Auto-calculate refundable balance βœ…
Stripe refund for driver credits βœ…
Manual refund flag for host earnings βœ…
AdminAuditLog (immutable) βœ…
AdminRefundLog (all money movements) βœ…
User deletions GDPR compliance record βœ…
Sidebar user detail panel βœ…
Real-time toast notifications βœ…
Audit Log viewer (/admin/audit-log) βœ…
Refund Log viewer βœ…

Admin API Endpoints

Method Route Description
GET /api/admin/stats Dashboard stats
GET /api/admin/users Paginated user list
GET /api/admin/users/:id User detail + balance + blockers
POST /api/admin/users/:id/delete Full deletion pipeline
POST /api/admin/users/:id/suspend Suspend or unsuspend
POST /api/admin/users/:id/refund Standalone manual refund
POST /api/admin/verify-password Admin password re-confirm
GET /api/admin/audit-log Paginated audit log
GET /api/admin/refund-log Paginated refund log

Deletion Pipeline Steps

  1. Verify admin identity via session cookie
  2. Check blockers (open disputes, active bookings) β€” block or force-override
  3. Cancel all future bookings (cancel_reason = 'Account deleted by admin')
  4. Deactivate all listings (β†’ archived)
  5. Calculate refundable balance (driver future credits + host unpaid earnings)
  6. Issue Stripe refunds via Payment Intent API (auto-fallback to manual_required)
  7. Write to admin_audit_log (immutable, with full details JSON)
  8. Write to admin_refund_log (per-refund record)
  9. Write to user_deletions (GDPR compliance, email SHA-256 hashed)
  10. Soft-delete: scrub PII (email β†’ deleted_{id}@deleted.parkpeer, name β†’ [Deleted User])

Database Tables

Table Purpose
admin_audit_log Immutable log of every admin action
admin_refund_log Every refund/money-movement record
user_deletions GDPR compliance records (email hashed)

🧠 Data Models

Users

id, first_name, last_name, email, phone, role (HOST/DRIVER/ADMIN), profile_photo, id_verified, rating_avg

Listings

id, host_id, title, description, address, lat/lon, hourly_rate, daily_rate, monthly_rate, type, max_vehicle_size, security_features, instant_book, status

Bookings

id, listing_id, driver_id, start_datetime, end_datetime, total_price, service_fee, payment_status, booking_status, qr_checkin_code

Payments

id, booking_id, stripe_payment_intent_id, amount, platform_fee (15-20%), host_payout_amount, payout_status

Reviews

id, booking_id, reviewer_id, reviewee_id, rating (1-5), comment

πŸ’° Monetization

  • 15% service fee on every booking
  • Featured listing upgrades
  • Subscription commuter plans (Phase 2)
  • Surge pricing for events (Phase 2)

πŸ— Tech Stack

Layer Technology
Framework Hono 4.x (TypeScript)
Runtime Cloudflare Workers / Pages
Build Vite + @hono/vite-build
Process Manager PM2
Styling Tailwind CSS (CDN)
Icons Font Awesome 6
Payments Stripe (βœ… Production-wired β€” live keys, Stripe.js v3 Payment Element, idempotency)
Database Cloudflare D1 (βœ… Production β€” 33 tables, 14 migrations applied, host_availability_schedule added)

πŸš€ Local Development

npm install
npm run build
pm2 start ecosystem.config.cjs

🌍 Deployment to Cloudflare Pages

npm run build
npx wrangler pages deploy dist --project-name parkpeer

βœ… Features Implemented (MVP)

  • Landing page with floating map pins, earnings calculator, city coverage
  • Full-featured search with filter sidebar + visual map
  • Listing detail page with gallery, reviews, availability calendar, booking widget
  • Stripe payment integration β€” live keys, Stripe.js v3 Payment Element, idempotency keys, ghost-booking prevention
  • Cloudflare D1 persistence β€” 33+ tables, 14 migrations, full booking/payment lifecycle in D1
  • Production-grade booking pipeline β€” holds-first flow, atomic D1 batch, recovery logging, integrity audit
  • Full time-based reservations β€” 15-min time grid, 14-day date strip, multi-day bookings, in-range highlighting, quick duration chips
  • Host availability schedule β€” Mon–Sun open/close hours, closed days; server-enforced on validate-slot + holds
  • 15-min increment pricing β€” aligned across validate-slot, /api/holds, create-intent; minimum 15-min booking
  • Mobile-first booking UI β€” scroll-snap date strip, large touch targets, auto-scroll to first available slot
  • Smart error messages β€” SLOT_BOOKED / SLOT_HELD / HOST_CLOSED_DAY / OUTSIDE_HOST_HOURS / LISTING_UNAVAILABLE / TOO_SHORT
  • Hold countdown timer β€” live 10-min countdown in payment panel; auto-invalidates Stripe on expiry
  • Driver dashboard with live countdown timer, booking history, saved spots
  • Host dashboard with listing management, booking approvals, revenue chart, calendar
  • Sign Up / Login with role selection, password strength meter, social OAuth UI
  • Admin panel with fraud alerts, listing moderation, user management, system health
  • Admin integrity endpoint β€” ghost booking detection, orphan payment audit, stale hold cleanup
  • RESTful API with full CRUD + Stripe + notifications
  • Mobile-responsive across all pages
  • Dark mode design system with Electric Indigo + Neon Lime palette

πŸ”œ Phase 2 Roadmap

  • Google Maps / Mapbox real map (Mapbox already integrated, real listings needed)
  • SMS/Email notifications (Twilio + Resend secrets already set in Cloudflare)
  • Surge pricing engine (events)
  • QR code generation for check-in
  • Superhost achievement system
  • AI pricing suggestions

Built with Hono + Cloudflare Pages β€” Deploy to the edge globally in seconds.

⏱️ Recent Fixes (2026-03-06) β€” Full Time-Based Reservation System v2

Booking UI Overhaul

Feature Detail
14-day scrollable date strip Scroll-snap, auto-selects first open day, host-closed days greyed out
15-minute time grid 96 slots/day (00:00–23:45), colour-coded: available / booked / held / closed / past
In-range highlighting All 15-min slots between start and end glow indigo when selecting end time
Quick duration chips 30min / 1h / 2h / 3h / 4h / 8h β€” only shown if end slot is actually available
Multi-day support If end time ≀ start time, end-date strip auto-appears; user picks next-day (or +N days)
Mobile UX Min 36–42px touch targets, scroll-snap date strip, auto-scroll to first available slot on open
Hold countdown Live M:SS pill in payment panel; turns red and invalidates Stripe on expiry
Success modal Shows PP-YYYY-XXXX booking reference, full date range, start–end times

Backend / Pricing Fixes

Fix Detail
15-min increment pricing Math.ceil(rawMins / 15) * 15 β€” all 3 endpoints aligned (validate-slot, /holds, create-intent)
2h example 2h β†’ $24.00 subtotal + $3.60 fee = $27.60 total (verified in tests)
15-min minimum TOO_SHORT code returned if < 15 min; minimum bookable unit = 0.25h = $3.00
Host schedule enforcement OUTSIDE_HOST_HOURS / HOST_CLOSED_DAY returned by validate-slot AND /holds
Overlap detection start1 < end2 && start2 < end1 β€” checked against confirmed/active bookings AND active holds

Migration 0014 β€” host_availability_schedule

CREATE TABLE host_availability_schedule (
  id INTEGER PRIMARY KEY AUTOINCREMENT,
  listing_id INTEGER NOT NULL,
  day_of_week INTEGER NOT NULL CHECK(day_of_week BETWEEN 0 AND 6),
  is_available INTEGER NOT NULL DEFAULT 1,
  open_time TEXT NOT NULL DEFAULT '07:00',
  close_time TEXT NOT NULL DEFAULT '22:00',
  created_at DATETIME DEFAULT CURRENT_TIMESTAMP,
  updated_at DATETIME DEFAULT CURRENT_TIMESTAMP,
  UNIQUE(listing_id, day_of_week)
);

Default seeded: all 7 days, 07:00–22:00 open for existing listings.

API Endpoints for Time-Based Reservations

Method Route Description
GET /api/listings/:id/time-slots?date=YYYY-MM-DD 96 Γ— 15-min slots with status (available/booked/held/closed/past)
POST /api/listings/:id/validate-slot Server-side check before hold; returns pricing if valid
GET /api/listings/:id/availability-schedule Host's 7-day weekly schedule
PUT /api/listings/:id/availability-schedule Host updates their schedule (authenticated)
POST /api/holds Acquire 10-min slot hold before Stripe PI
GET /api/holds/:token Poll hold status (valid/expired/converted)

Verification Test Results

βœ“ 96 slots/day (15-min grid, 0:00–23:45)
βœ“ 15-min pricing: 6 duration cases (0.25h–2.5h) all match
βœ“ Host hours: before-open β†’ OUTSIDE_HOST_HOURS, after-close β†’ OUTSIDE_HOST_HOURS  
βœ“ TOO_SHORT: < 15 min rejected with correct code
βœ“ LISTING_UNAVAILABLE: nonexistent listing rejected
βœ“ Rate limiting: 429 fires on burst > 20/min
βœ“ Admin integrity requires auth (HTTP 401)
βœ“ Webhook rejects missing signature
βœ“ 7-day host schedule returns correctly
βœ“ 23/23 UI elements present in booking page

πŸ”§ Recent Fixes (2026-03-06) β€” Stripe + D1 Production Wiring

Stripe Payment Integration (βœ… Complete)

  • Live keys active β€” pk_live_... / sk_live_... / whsec_... all set in Cloudflare Pages secrets
  • Checkout page β€” full Stripe.js v3 Payment Element replacing static card HTML
  • 3-step flow (holds-first):
    1. POST /api/holds β†’ atomic slot lock (10-min TTL, blocks other users)
    2. POST /api/payments/create-intent β†’ creates Stripe PI tied to hold; Stripe Idempotency-Key = checkout_token prevents duplicate PIs on retry
    3. stripe.confirmPayment() in browser β†’ on success β†’ POST /api/payments/confirm
  • /api/payments/confirm β€” fully wired to D1:
    • Looks up booking by stripe_payment_intent_id (or fallback booking_id)
    • UPDATE bookings SET status='confirmed', stamps stripe_charge_id
    • INSERT INTO payments row (idempotent β€” skips if PI already recorded)
    • Sends confirmation email + SMS; fires in-app notification async
  • Stripe webhook (/api/webhooks/stripe) β€” handles payment_intent.succeeded, payment_intent.payment_failed, charge.refunded, charge.dispute.created
  • Webhook registered at https://parkpeer.pages.dev/api/webhooks/stripe

Ghost Booking Prevention (βœ… Complete)

Mechanism Description
reservation_holds 10-min atomic slot lock; overlap query excludes caller's own session
booking_idempotency Maps checkout_token β†’ booking_id; prevents double-INSERTs on retry
Stripe idempotency key Same checkout_token passed as Idempotency-Key β†’ same PI returned
D1 atomic batch Booking + hold-convert + payment INSERT in one db.batch() call
payment_recovery_log PI success + D1 batch failure β†’ logged; client gets recovery_pending status
Admin integrity scan GET /api/admin/integrity detects orphan payments, unconfirmed bookings, stale holds

Cloudflare D1 Persistence (βœ… Complete)

  • 33 tables fully applied in production (database_id: 119f9fd4-...)
  • 13 migrations all confirmed applied remotely
  • 5 new tables (migrations 0012–0013): reservation_holds, payment_recovery_log, booking_idempotency, orphan_payments, integrity_log
  • Booking lifecycle: pending β†’ confirmed β†’ active β†’ completed / cancelled / refunded all tracked in D1

Security & Rate Limiting (βœ… Complete)

  • /api/holds β€” 20 req/min per IP
  • /api/payments/create-intent β€” 10 req/min per IP
  • /api/admin/integrity β€” requires admin session
  • Structured JSON logging (logEvent) on all hold/payment/booking events for wrangler tail monitoring
  • SQL alias bug fixed in payments idempotency check (payments.booking_id not p.booking_id)

Map / UI

  • Popup no longer blocks the walking route pill β€” popup offset increased to 72px when a walk route is active; the #route-info-pill drops to 1.5rem from the bottom while the popup is open (pill-popup-open class), giving the popup full clearance above the route line
  • Smart auto-pan β€” measures actual pin screen position vs pill top and pans by exactly the gap needed (not a fixed constant), so the popup always stays above the pill with β‰₯16px breathing room
  • Viewport restored on close β€” map.easeTo({offset:[0,0]}) on popup close undoes the temporary pan

Security

  • XSS: loadTopHosts β€” all host name / count / rating / id fields now escaped via escHtml() before innerHTML
  • XSS: Admin audit log table β€” all DB-sourced fields (email, action, reason, IP address) escaped via escapeHtml() before render
  • XSS: Admin refund log table β€” all fields escaped
  • XSS: Admin user detail panel β€” full_name, email, role, status escaped; e.message no longer shown in error HTML
  • XSS: Admin blocker reasons list β€” escaped before innerHTML
  • escapeHtml() fixed β€” was missing &, <, > escaping; now full 5-char escape
  • Error detail leak β€” detail: e.message stripped from ALL 500 responses in api.ts (9 routes) and all admin-api.ts routes
  • PII in logs β€” geocode query strings replaced with query_len; geocode coordinates removed from warning log; listing POST body replaced with title-only log; Twilio SMS Body content redacted

Performance

  • Migration 0011 β€” 6 new compound indexes: (listing_id, status) booking guard, stripe_payment_intent_id on payments/bookings, (status, type) listings, availability blocks, notifications composite; applied to both local and production D1

Code Quality

  • Removed 2x console.debug statements left from walk-route debugging
  • Cleaned debug comment on el._lng coordinate freeze

πŸ“± Contact Verification System (v2.1)

Overview

Full email + phone OTP verification built for the checkout flow. Supports guest checkout (no account login required) using session-scoped verification tokens.

API Endpoints

Method Route Description
POST /api/verify/phone/send Send 6-digit OTP via Twilio SMS
POST /api/verify/phone/confirm Verify phone OTP β€” marks verified_contacts row
POST /api/verify/email/send Send 6-digit OTP via Resend email
POST /api/verify/email/confirm Verify email OTP β€” marks verified_contacts row
GET /api/verify/status Check session verification state

Checkout UI Features

  • Predictive email input β€” real-time format hints, progressive error messages, disposable domain rejection
  • Auto-format phone β€” US 10-digit auto-formatted to (555) 123-4567; international + numbers accepted
  • OTP modal β€” 6 individual digit boxes with keyboard nav, paste support, wrong-answer shake animation
  • Verified badges β€” green checkmarks appear on both fields after successful OTP
  • Resend timer β€” 60-second cooldown before resend is allowed
  • Hold token reuse β€” session_token (= checkoutToken) is the same token used for slot holds, so verification and hold are tied to the same checkout session

Post-Payment Messaging

Trigger Channel Contents
Payment success Email Booking confirmation + QR code image (200Γ—200 PNG) + check-in link
Payment success Email Separate payment receipt with last-4 card digits
Payment success SMS Booking summary + QR check-in URL (/checkin?t=TOKEN&b=BOOKING_ID)
Both β€” Only sent to verified contacts; unverified contacts fall back to raw body values

Security

  • OTPs hashed with PBKDF2-SHA-256 (10,000 iterations + random salt)
  • Constant-time comparison to prevent timing attacks
  • Verified contacts expire 2 hours after verification
  • Single-use: used = 1 after payments/confirm reads them (replay prevention)
  • Rate limits: 3 OTPs per contact per 10 minutes; 5 per IP per minute; 5 confirm attempts per session per 15 minutes

DB Tables Added (migration 0017)

  • email_otp_codes β€” stores hashed email OTP codes
  • otp_codes extended β€” adds session_token, type, ip_address, attempts columns
  • verified_contacts β€” session-scoped verification state (email/phone per session)

Last Deployed

2026-03-06 Β· https://1d9fc520.parkpeer.pages.dev

About

ParkPeerApp

Resources

Stars

Watchers

Forks

Releases

No releases published

Packages

 
 
 

Contributors