95eab42f4b
Documentation pass — reviewed codebase in full, identified stale references, and aligned docs with actual code state. Changes: - CLAUDE.md, PRODUCTION_SETUP.md, LAUNCH_CHECKLIST.md, src/auth.config.ts: middleware path src/middleware.ts → src/proxy.ts (Next.js 16+ convention) - CLAUDE.md, ENVIRONMENT.md, README.md: dev server port localhost:3000 → localhost:4000 (per package.json dev script 'next dev --webpack -H 0.0.0.0 -p 4000') - PRODUCTION_DEPLOYMENT_CHECKLIST.md: rewrote migration list — actual db/migrations/ contains 10 files (0000–0091), not the Supabase-era 001–092 series. Updated platform version 1.6 → 2.0 and last-updated stamp. - MEMORY.md: refreshed 'Last updated' to 2026-06-25, added current-state header flagging Auth.js v5 wiring as historical, and documented the SSH/Gitea workflow (id_ed25519_crispygoat + no API token → push-hook URL flow for opening PRs). - LAUNCH_CHECKLIST.md: added 'Last updated: 2026-06-25' header and refresh note in footer. - CLAUDE.md: new 'Gitea authentication (SSH)' subsection under 'Canonical Remote' documenting ~/.ssh/id_ed25519_crispygoat, ssh-agent setup, what works (git push) vs what doesn't (REST API without token), and the actual PR-creation workflow (push + open the URL the Gitea hook prints). Verification: - npx tsc --noEmit: pre-existing Stripe API version + test mock errors only (verified by stashing changes and re-running — same error set). - No new errors introduced.
319 lines
12 KiB
Markdown
319 lines
12 KiB
Markdown
# Route Commerce
|
|
|
|
A multi-tenant B2B e-commerce platform for fresh produce wholesale distribution. Brands manage products, stops, orders, and wholesale customers from a single admin dashboard.
|
|
|
|
## What It Does
|
|
|
|
Route Commerce helps produce brands run their wholesale operations:
|
|
|
|
- **Product catalogs** — Manage products with pricing, variants, and images
|
|
- **Stop/route scheduling** — Schedule pickup stops across regions with customer tracking
|
|
- **Order management** — Handle pickup and shipping orders, track fulfillment
|
|
- **Wholesale portal** — Self-service portal for wholesale customers to place orders
|
|
- **Billing** — Stripe-based subscription billing with plan tiers and add-ons
|
|
- **Communications** — Email/SMS campaigns via Harvest Reach module
|
|
- **Square sync** — Optional inventory sync with Square POS
|
|
|
|
## Tech Stack
|
|
|
|
- **Framework:** Next.js 16 (App Router)
|
|
- **Database:** Postgres (direct via `pg` + Drizzle) with Neon Auth (Better Auth)
|
|
- **Payments:** Stripe + Square
|
|
- **Email/SMS:** Resend
|
|
- **Styling:** Tailwind CSS v4
|
|
|
|
## Getting Started
|
|
|
|
### 1. Clone and install
|
|
|
|
```bash
|
|
git clone <your-repo-url>
|
|
cd route-commerce-platform
|
|
npm install
|
|
```
|
|
|
|
### 2. Set up environment variables
|
|
|
|
Create a `.env.local` file with:
|
|
|
|
```bash
|
|
# Database (Postgres)
|
|
DATABASE_URL=postgresql://user:pass@host:5432/dbname?sslmode=require
|
|
|
|
# Stripe
|
|
STRIPE_SECRET_KEY=sk_test_...
|
|
NEXT_PUBLIC_STRIPE_PUBLISHABLE_KEY=pk_test_...
|
|
STRIPE_WEBHOOK_SECRET=whsec_...
|
|
STRIPE_PRICE_STARTER=price_...
|
|
STRIPE_PRICE_FARM=price_...
|
|
STRIPE_PRICE_ENTERPRISE=price_...
|
|
|
|
# Resend (email)
|
|
RESEND_API_KEY=re_...
|
|
```
|
|
|
|
### 3. Set up the database
|
|
|
|
The migrations live in `db/migrations/` and are applied via the `pg` driver (no Supabase CLI):
|
|
|
|
```bash
|
|
npm run migrate
|
|
```
|
|
|
|
Or push a single migration:
|
|
|
|
```bash
|
|
npm run migrate:one 83
|
|
```
|
|
|
|
### 4. Start the dev server
|
|
|
|
```bash
|
|
npm run dev
|
|
```
|
|
|
|
Open [http://localhost:4000](http://localhost:4000). The dev server auto-runs `fix-agents.js` to patch Next.js App Router agent issues. (The dev script binds to `0.0.0.0:4000`; override with `npm run dev -- -p <port>` if you need a different port.)
|
|
|
|
### 5. Dev auth bypass
|
|
|
|
In development, `/login` sets a `dev_session` cookie for full platform access:
|
|
|
|
- `dev_session=platform_admin` — full access, all brands
|
|
- `dev_session=brand_admin` — brand-scoped access only
|
|
|
|
Visit `/login` as a shortcut to set the platform_admin session.
|
|
|
|
## Key Commands
|
|
|
|
```bash
|
|
npm run dev # Start dev server
|
|
npm run build # Production build
|
|
npm run lint # ESLint
|
|
npm run migrate # Push all pending migrations
|
|
npm run migrate:one 83 # Push migration 083_*.sql only
|
|
npx tsc --noEmit # TypeScript check (no emit)
|
|
npx playwright test # Run E2E tests
|
|
```
|
|
|
|
## Project Structure
|
|
|
|
```
|
|
src/
|
|
├── actions/ # Server actions (database writes)
|
|
├── app/
|
|
│ ├── admin/ # Admin dashboard pages
|
|
│ ├── api/ # API routes (Stripe, Resend, Square webhooks)
|
|
│ └── storefront/ # Public brand storefront pages
|
|
├── components/ # Shared React components
|
|
│ ├── admin/ # Admin-specific components
|
|
│ └── storefront/ # Storefront components
|
|
└── lib/ # Utilities, auth, feature flags, formatting
|
|
db/
|
|
└── migrations/ # SQL migrations (numbered sequentially)
|
|
```
|
|
|
|
## Plan Tiers
|
|
|
|
| Tier | Price | Includes |
|
|
|---|---|---|
|
|
| **Starter** | $49/mo | Products, 10 stops/mo, Orders, Basic Pickup, 1 user, 25 products |
|
|
| **Farm** | $149/mo | Everything in Starter + Wholesale Portal, Harvest Reach, unlimited stops/products, 5 users |
|
|
| **Enterprise** | Custom | Everything in Farm + AI Intelligence Pack, SMS Campaigns, Square Sync, Water Log, unlimited users |
|
|
|
|
**Add-ons (à la carte on any plan):**
|
|
|
|
- Wholesale Portal ($99/mo)
|
|
- Harvest Reach email/SMS ($79/mo)
|
|
- AI Intelligence Pack ($59/mo)
|
|
- Water Log ($39/mo)
|
|
- Square Sync ($39/mo)
|
|
- SMS Campaigns ($29/mo)
|
|
|
|
## Time Tracking
|
|
|
|
### Adding Workers
|
|
1. Go to `/admin/settings` → expand **Workers & PINs**
|
|
2. Click **+ Add Worker** — enter name, role (Worker or Time Admin), language (EN/ES)
|
|
3. PINs are auto-generated. Click **Reset PIN** next to any worker to issue a new one
|
|
|
|
### Adding Tasks
|
|
1. Go to `/admin/settings` → expand **Tasks**
|
|
2. Click **+ Add Task** — enter EN name, optional ES name, unit (hours/pieces/units), sort order
|
|
3. Tasks appear in the time clock app for workers to clock into
|
|
|
|
### Notification Alerts
|
|
1. Go to `/admin/settings` → expand **General Settings**
|
|
2. Add email addresses and/or SMS numbers under **Notification Recipients**
|
|
3. Configure daily/weekly overtime alert thresholds
|
|
|
|
## Admin Settings Page
|
|
|
|
All brand configuration lives at `/admin/settings` with five sections:
|
|
|
|
- **General** — Pay period, overtime thresholds, alert settings, notification recipients
|
|
- **Workers & PINs** — Manage workers, reset PINs, toggle active/inactive
|
|
- **Tasks** — Define tasks workers can clock into
|
|
- **Users & Permissions** — Manage admin users and role flags
|
|
- **Integrations** — Stripe, Resend, Twilio, OpenAI credentials and sync options
|
|
|
|
## Admin Modules
|
|
|
|
The admin dashboard lives at `/admin`:
|
|
|
|
- **Command Center** — Overview dashboard
|
|
- **Orders** — All orders with pickup/ship status
|
|
- **Stops** — Route/stop scheduling and management
|
|
- **Products** — Product catalog management
|
|
- **Customers** — Wholesale customer management
|
|
- **Communications** — Harvest Reach campaign manager
|
|
- **Wholesale** — Wholesale portal settings
|
|
- **Billing** — Plan and subscription management
|
|
- **Water Log** — Irrigation tracking (add-on) — see [docs/water-log.md](docs/water-log.md)
|
|
- **Settings** — Brand settings, payments, apps
|
|
|
|
## Email Automations (Harvest Reach)
|
|
|
|
Automated email sequences for wholesale customer engagement.
|
|
|
|
### Abandoned Cart Recovery
|
|
|
|
3-email sequence (1hr → 24hr → 48hr) triggered when a wholesale customer adds items to cart but doesn't checkout.
|
|
|
|
**Cron schedule:** Every 6 hours via Vercel crons (`vercel.json`)
|
|
|
|
**Dashboard:** `/admin/communications/abandoned-carts`
|
|
|
|
**How it works:**
|
|
1. `detect_abandoned_wholesale_carts` RPC finds pending wholesale orders not yet enrolled
|
|
2. `enroll_abandoned_cart` RPC creates a tracking record with first email scheduled in 1 hour
|
|
3. Cron job calls `get_active_abandoned_carts` to find carts where `next_email_at <= now()`
|
|
4. `sendAbandonedCartEmail` fires the email via Resend, then updates `sequence_step`, `next_email_at`, and `status`
|
|
|
|
**Admin actions:**
|
|
- **View** — see all carts with status (Active/Recovered/Expired/Manually Closed), step, items, total
|
|
- **Close** — marks cart as manually_closed (stops further emails)
|
|
- **Resend** — resets `next_email_at` to now() and fires email immediately
|
|
|
|
### Welcome Email Sequence
|
|
|
|
4-email onboarding series triggered when a wholesale contact subscribes (email_opt_in = TRUE).
|
|
|
|
**Cron schedule:** Every 6 hours (same as abandoned cart)
|
|
|
|
**Dashboard:** `/admin/communications/welcome-sequence`
|
|
|
|
**How it works:**
|
|
1. Contact subscribes via wholesale portal → `enroll_welcome_sequence` RPC is called
|
|
2. Cron job calls `get_active_welcome_sequence` for entries where `next_email_at <= now()`
|
|
3. `sendWelcomeEmail` fires via Resend, updates step, schedules next email 24h later
|
|
4. After step 4, status → `completed`
|
|
|
|
**Admin actions:**
|
|
- **Resend** — resets entry to step 1 and fires immediately
|
|
- Unsubscribed/bounced contacts are skipped automatically
|
|
|
|
### Manual Trigger (curl)
|
|
|
|
```bash
|
|
# Abandoned cart — simulate cron
|
|
curl -X POST https://route-commerce-platform.vercel.app/api/email-automation/abandoned-cart \
|
|
-H "Authorization: Bearer $CRON_SECRET" \
|
|
-H "Content-Type: application/json"
|
|
|
|
# Welcome sequence
|
|
curl -X POST https://route-commerce-platform.vercel.app/api/email-automation/welcome-sequence \
|
|
-H "Authorization: Bearer $CRON_SECRET" \
|
|
-H "Content-Type: application/json"
|
|
|
|
# Check status / health
|
|
curl https://route-commerce-platform.vercel.app/api/email-automation/abandoned-cart
|
|
```
|
|
|
|
### Environment Variables Required
|
|
|
|
```bash
|
|
RESEND_API_KEY=re_... # Resend API key for sending emails
|
|
FROM_EMAIL="Tuxedo Corn <no-reply@routecommerce.com>" # Sender address
|
|
CRON_SECRET=... # Bearer token to secure cron endpoints (generate a long random string)
|
|
```
|
|
|
|
### Troubleshooting
|
|
|
|
| Problem | Likely cause | Fix |
|
|
|---|---|---|
|
|
| Emails not sending | `RESEND_API_KEY` not set | Check env var in Vercel dashboard |
|
|
| 401 Unauthorized on cron | `CRON_SECRET` mismatch | Verify local `.env.local` matches Vercel env |
|
|
| Empty abandoned cart table | No pending wholesale orders | Create test order, add items, abandon checkout |
|
|
| Welcome sequence not enrolling contacts | `enroll_welcome_sequence` not called | Ensure subscription UI calls the RPC on email_opt_in = TRUE |
|
|
|
|
### Testing Locally
|
|
|
|
```bash
|
|
# Run abandoned cart with verbose output
|
|
curl -X POST http://localhost:4000/api/email-automation/abandoned-cart \
|
|
-H "Authorization: Bearer local-dev-secret" \
|
|
-H "Content-Type: application/json"
|
|
|
|
# Response: { ok: true, results: { "Tuxedo Corn": { sent: 0, failed: 0, skipped: 0 }, ... } }
|
|
```
|
|
|
|
Set `CRON_SECRET=local-dev-secret` in `.env.local` for local testing.
|
|
|
|
## Go-Live Checklist
|
|
|
|
Use this checklist before adding real products and doing a soft launch.
|
|
|
|
### Pre-Launch
|
|
|
|
- [ ] Set `CRON_SECRET` in Vercel project environment variables (generate with `openssl rand -hex 32`)
|
|
- [ ] Verify `RESEND_API_KEY` is set in Vercel (for email sending)
|
|
- [ ] Verify `FROM_EMAIL` is set (e.g. `"Tuxedo Corn <no-reply@routecommerce.com>"`)
|
|
- [ ] Run `npm run build` locally — must pass clean with zero TypeScript errors
|
|
- [ ] Deploy latest commit to production: `vercel --prod`
|
|
- [ ] Smoke test: visit `/admin`, `/admin/settings`, `/tuxedo` — no console errors
|
|
|
|
### Adding Your First Brand's Products
|
|
|
|
1. Go to `/admin/products` → **Add Product**
|
|
2. For each product: name, wholesale price, image, shipping type (pickup/ship/all)
|
|
3. Set `is_taxable` if Colorado sales tax applies
|
|
4. Make products **active** — inactive products don't show on storefront
|
|
5. For Tuxedo Corn: use brand slug `tuxedo`, set brand accent `green`
|
|
|
|
### Adding Pickup Stops
|
|
|
|
1. Go to `/admin/stops` → **Create Stop**
|
|
2. Fill city, state, address, date, time, cutoff date
|
|
3. Assign brand — stops show on storefront at `/{brand-slug}#stops`
|
|
4. Set `is_public: true` to display on storefront
|
|
|
|
### Verifying Wholesale Portal
|
|
|
|
1. Go to `/admin/wholesale` — confirm portal is enabled
|
|
2. Visit `/{brand-slug}/wholesale/portal` as a customer
|
|
3. Add items to cart, start checkout — confirm abandoned cart appears in `/admin/communications/abandoned-carts` within minutes
|
|
|
|
### Verifying Email Automations
|
|
|
|
```bash
|
|
# Manually trigger welcome sequence (use a real email you control)
|
|
curl -X POST https://route-commerce-platform.vercel.app/api/email-automation/welcome-sequence \
|
|
-H "Authorization: Bearer $CRON_SECRET"
|
|
|
|
# Check Resend dashboard — should see sent emails
|
|
# Check /admin/communications/welcome-sequence — entry should show "Active"
|
|
```
|
|
|
|
### Monitoring Production
|
|
|
|
- Check Vercel dashboard → **Functions** logs for any 500 errors
|
|
- Check `/admin/communications/abandoned-carts` for recovered orders
|
|
- Vercel crons fire every 6 hours — check function invocations in dashboard
|
|
|
|
## Notes
|
|
|
|
- All database writes go through **server actions** (`src/actions/`) over a shared `pg` `Pool` from `src/lib/db.ts` — no Supabase JS client, no REST gateway
|
|
- Dev mode bypasses Neon Auth via `dev_session` cookie — never use this in production
|
|
- Brand-scoped data is enforced at the application layer via `p_brand_id` parameters in SECURITY DEFINER RPCs
|
|
- Display dates use `formatDate()` (MM/DD/YYYY) — never raw `toLocaleDateString()`
|
|
- Migration files are numbered sequentially — never reuse numbers |