Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
The table of contents is too big for display.
Diff view
Diff view
  •  
  •  
  •  
9 changes: 8 additions & 1 deletion .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -49,4 +49,11 @@ prompt
server.log

# kilocode mcp config (contains sensitive data)
.kilocode/mcp.json
.kilocode/mcp.json

# generated UGC media — regenerable outputs, served from gw.724care.com (not source)
/public/videos/
/public/audio/

# model weights (downloaded, not source)
*.pt
133 changes: 133 additions & 0 deletions CLAUDE.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,133 @@
# CLAUDE.md

This file provides guidance to Claude Code (claude.ai/code) when working with code in this repository.

## Project Overview

Mexico Paradise Vacations — a travel certificate sales platform with payment plans, client portal, and admin dashboard. Built with Next.js 15 (App Router) + TypeScript + Tailwind CSS 4 + shadcn/ui.

## Safety & Authorization

- **Confirm before irreversible or live-serving steps.** Before installing software (docker, miniconda, model downloads, etc.) or running deploy/destructive commands (`rsync`, `scp` to a live host, symlink-to-live, `rm`, `pkill`, killing screens/PM2 apps), show a numbered plan of the exact commands and which host they target, and wait for an explicit go-ahead. A generic "go ahead" does **not** authorize promoting to production — confirm that step specifically.
- **Do not over-reach beyond the literal request.** Don't enumerate subdomains/SSH keys, provision new boxes, or patch safety/validation code that wasn't asked about.
- **Discover before you guess.** Before using a model name, endpoint, file path, asset ID, keyframe, or remote directory, query/list the real value from the source first and confirm it exists — never guess from memory (e.g. real model names like `Wan2_1-InfiniteTalk_Single_Q8.gguf`, not a remembered approximation).
- **Verify the right variant before publishing.** When deploying an installer/script/render config, confirm it's the correct variant and read its contents before pushing it live.
- **Never echo secret values** (FISH_API_KEY, CLOUDFLARE_API_TOKEN, HF_TOKEN, VAST_API_KEY, GEMINI_API_KEY, passwords). Save to file without disclosing.

## Environment Constraints

- Shell state (working directory, env vars) does **not** persist between Bash calls; use absolute paths. The harness cannot permanently `cd` or switch CLI sessions/projects mid-session — when an action requires that, give the user the exact CLI command to run themselves rather than attempting it.

## Deploy Targets

- **gw.724care.com** internal artifacts: the live Apache vhost DocumentRoot is **`/var/www/html`** (verified 2026-06-05 via `apache2ctl -S` + the `DocumentRoot` directive in `000-default.conf` / `000-default-le-ssl.conf`; real batch HTML and video URLs return 200). Deploy batch HTML and media to `root@gw.724care.com:/var/www/html/...`; the super-index lands at `/var/www/html/batches.html`. (A `/workspace` dir exists but is NOT what gw serves — don't deploy there.)
- **hi2b.com** production: `root@api3.amdy.io`, app at `/opt/hi2b` (PM2 app `hi2b`), deploy via scp + `npm run build` + `pm2 reload`.

## Internal HTML on gw.724care.com

Any internal artifact that's a standalone HTML page (UGC batch index, voice audition, script doc, comparison sheet, research summary) **must be**:

1. Saved locally at `public/research/<name>.html`.
2. Deployed with `scp` to `root@gw.724care.com:/var/www/html/<name>.html` (the live docroot — see Deploy Targets) so it's reachable at `https://gw.724care.com/<name>.html`.
3. Linked from the super-index at `https://gw.724care.com/batches.html`. Add a card to the appropriate section of `public/research/batches-index.html`, then re-deploy it as `batches.html` on gw.

This does **not** apply to hi2b.com production routes (those live in `src/app/` and deploy via the prod server). It applies only to internal-tooling HTML.

## Commands

- **Dev server**: `npm run dev` (uses nodemon + tsx to run custom `server.ts`; HMR is disabled in favor of nodemon-based full reload)
- **Build**: `npm run build`
- **Production**: `npm run start`
- **Lint**: `npm run lint`
- **DB push schema**: `npm run db:push`
- **DB generate client**: `npm run db:generate`
- **DB migrate**: `npm run db:migrate`

## Architecture

### Custom Server (`server.ts`)
The app uses a custom Node HTTP server (not the default `next dev`/`next start`). It creates a Next.js app and attaches a Socket.IO server on `/api/socketio`. Dev mode runs via `nodemon --exec "npx tsx server.ts"`.

### Dual Database Setup
- **Supabase** (`src/lib/supabase.ts`): Primary data store for the business domain — users, signups, payments, certificates. The Supabase client is initialized with hardcoded project URL/anon key. Database types are defined inline in this file.
- **Prisma + SQLite** (`src/lib/db.ts`, `prisma/schema.prisma`): Secondary database with a basic User/Post schema. Uses the singleton pattern to avoid multiple PrismaClient instances in dev.

### Authentication
Simple demo auth in `src/lib/auth.ts` — password is hardcoded as `demo123`, user session stored in localStorage. Role-based: `admin` or `client`.

### Payment Integration
`src/lib/maverick.ts` — `MaverickPaymentAPI` class wrapping the Maverick Payments REST API. Configured via `MAVERICK_API_URL` and `MAVERICK_API_KEY` env vars.

### API Routes
All under `src/app/api/`:
- `POST /api/signup` — new vacation package signup
- `POST /api/payment/create` — create payment
- `POST /api/payment/confirm` — confirm payment status
- `POST /api/admin/refund` — process refund (admin only)
- `GET /api/health` — health check

### Pages
- `/` — landing page
- `/client-portal` — client dashboard (auth-guarded)
- `/admin-portal` — admin dashboard (auth-guarded)
- `/payment/success`, `/payment/cancel` — payment result pages

### UI Components
shadcn/ui (new-york style) in `src/components/ui/`. Custom components: `auth-guard.tsx`, `login-modal.tsx`.

## Path Aliases

`@/*` maps to `./src/*` (configured in tsconfig.json).

## Environment Variables

- `DATABASE_URL` — Prisma/SQLite connection string
- `NEXT_PUBLIC_APP_URL` — application URL
- `MAVERICK_API_URL` — Maverick Payments API base URL
- `MAVERICK_API_KEY` — Maverick Payments API key

## Build Notes

- TypeScript build errors are ignored (`typescript.ignoreBuildErrors: true` in next.config.ts)
- ESLint errors are ignored during builds (`eslint.ignoreDuringBuilds: true`)
- ESLint config is very permissive — most strict rules are turned off

## Best Practices

### Next.js App Router
- Use Server Components by default; only add `"use client"` when you need browser APIs, event handlers, or React hooks (useState, useEffect, etc.)
- Place data fetching in Server Components or API routes — never fetch in `useEffect` when a server-side approach works
- Use `route.ts` files for API routes; export named functions matching HTTP methods (`GET`, `POST`, `PUT`, `DELETE`)
- Return `NextResponse.json()` from API routes with appropriate status codes
- Use `loading.tsx`, `error.tsx`, and `not-found.tsx` for route-level UI states
- Use `layout.tsx` for shared UI that persists across navigations; avoid re-fetching data that a parent layout already provides
- Prefer Next.js `<Image>` over `<img>` and `<Link>` over `<a>` for optimized loading and client-side navigation
- Use `metadata` exports or `generateMetadata()` for SEO — not manual `<head>` tags

### Node.js / Server-Side
- Never block the event loop — use async/await for I/O, avoid synchronous file or network calls in request handlers
- Keep secrets in environment variables, never hardcode them (note: this project currently has hardcoded Supabase keys that should be moved to env vars)
- Use the Prisma singleton pattern from `src/lib/db.ts` to prevent connection exhaustion in dev
- Validate and sanitize all user input at API boundaries using Zod (already a dependency)
- Handle errors explicitly in API routes — return structured error responses, don't let unhandled exceptions leak stack traces
- Use `try/catch` around external API calls (Maverick, Supabase) and return meaningful error messages

### React / Frontend
- Co-locate component state as close to where it's used as possible; lift state only when siblings need to share it
- Use Zustand (already installed) for global client state; avoid prop drilling more than 2 levels deep
- Use React Query (`@tanstack/react-query`, already installed) for server state — caching, refetching, and optimistic updates
- Prefer controlled form inputs with `react-hook-form` + Zod validation (both already installed)
- Use shadcn/ui components from `src/components/ui/` — don't rebuild existing primitives
- Add new shadcn components via `npx shadcn@latest add <component-name>`

### TypeScript
- Use explicit return types on exported functions and API route handlers
- Define shared types/interfaces in dedicated files or alongside their domain (e.g., Supabase types in `src/lib/supabase.ts`)
- Prefer `interface` for object shapes that may be extended; use `type` for unions, intersections, and computed types
- Use Zod schemas as the single source of truth for validation and infer TypeScript types from them with `z.infer<>`

### Styling
- Use Tailwind CSS utility classes; avoid custom CSS unless Tailwind cannot express the style
- Follow the shadcn/ui `new-york` style variant (configured in `components.json`)
- Use CSS variables defined in `src/app/globals.css` for theme colors
- Use `cn()` from `src/lib/utils.ts` to merge conditional Tailwind classes (combines `clsx` + `tailwind-merge`)
89 changes: 89 additions & 0 deletions HANDOVER.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,89 @@
# HANDOVER — hi2b.com UGC Operation & Funnel

_Last updated: 2026-06-21_

Mexico Paradise Vacations / **hi2b.com** — travel-certificate offer ($249 one-time / $29/mo,
5 days / 4 nights all-inclusive, kids free, 4 Mexico destinations). Brand pillar:
**"Hour to Paradise"** — honest framing of the one resort presentation as "the only catch."

---

## 🚨 TOP PRIORITY — the funnel is structurally broken (fix before spending more)

Observed: **~2,000 landing-page visits → 0 sales → 0 PDF downloads.** That is not a weak-hook
signature; it means the message isn't being delivered. Root cause found:

**`src/app/page.tsx:10-13` redirects every visitor to a RANDOM landing page, client-side**
(`router.replace('/lp/' + randomLP)`). Confirmed live: fetching `hi2b.com` returns only the
`"Loading your paradise..."` spinner. Consequences:

1. **No message-match** — a "$249 honest hour" ad lands on a random page out of 43.
2. **TikTok in-app browser bounces** on client-side redirects; pixel PageView may be lost.
3. **Price/tone whiplash** — ads promise *$249 one-time, honest*; LPs lead with *$29/mo* +
fake urgency ("2,847 claimed," "expires at midnight"). This re-triggers the "scam / too
good to be true" fear the UGC works to defuse.
4. **0 downloads from ~1,000 ebook-page visitors** → verify the ebook form actually fires.

### Recommended fixes (in order)
1. Replace the random redirect with **one server-rendered, message-matched page** mirroring the
ad: *$249, 5 days all-inclusive, kids free, one ~60-min presentation, polite "no" is fine.*
2. Answer both core objections above the fold: **why it's cheap** (resort subsidizes for the
presentation slot) and **exactly what the presentation is** (~60 min, no obligation).
3. Make the **free guide the first ask** (email capture) → nurture → sale. Don't ask cold
TikTok traffic for $249 on first touch.
4. **Verify GA4 + TikTok pixel + the download form** actually register events before scaling.

Landing-page system: `src/app/lp/[slug]/`, config in `src/app/lp/_config/pages.ts`
(43 pages; ctaFocus split = 20 `ebook`, 23 `pay-now`). Analytics: GA4 `G-Q8TPF405Z1`
(`src/app/layout.tsx`) + TikTok pixel (`src/components/TikTokPixel.tsx`,
`NEXT_PUBLIC_TIKTOK_PIXEL_ID`).

---

## Render engine (talking-head UGC) — healthy and self-sustaining

- **Orchestrator:** `scripts/orchestrator.sh` (runs via `nohup`). Queue `scripts/render-queue.txt`,
done `scripts/render-done.txt`, log `scripts/orchestrator.log`. Renders queued batches, then
`finalize-batchNN.sh` deploys each to gw.
- **GPU (Inst 2):** `root@51.83.197.242 -p 43312`, ComfyUI on `localhost:18188` (per-batch SSH
tunnel). Single-speaker model `Wan2_1-InfiniteTalk_Single_Q8.gguf` (Wan2.1), v13 keyframes.
- **TTS:** Fish Audio `s2-pro`, Sarah `voice_ref 933563129e564b19a115bedd57b7406a`, via
`scripts/batch10-tts.ts <scripts.json>` (skips existing mp3s; safe to re-run to fill gaps).
- **Deploy target (gw):** live docroot `/var/www/html` (NOT `/workspace`). Batches →
`root@gw.724care.com:/var/www/html/batchNN/`. Super-index `public/research/batches-index.html`
→ deployed as `/var/www/html/batches.html`.
- **State:** ~82 batches live (≈820 videos). 83–85 authored/queued. Each batch = 10 single-speaker
scripts.

### Authoring a new batch (proven template)
1. Clone latest `batchNN-render.ts` via `sed` (batchOLD→batchNEW, bOLD→bNEW, tunnelPort, title);
fix `ALL_BNN` + `KEYFRAME_MAP` ids to match the new `scripts.json`. Verify with `diff` of ids.
2. Clone `finalize-batchNN.sh` and `public/research/batchNN.html`.
3. `npx tsx scripts/batch10-tts.ts scripts/batchNN-scripts.json`.
4. Add index card to `batches-index.html`, redeploy as `batches.html`, append `NN` to
`render-queue.txt`. Keep a 3-deep buffer.

### Stall detection
GPU 0% is normal in the inter-video model-reload gap. REAL stall = GPU 0% + mem ~3GB + ComfyUI
`/queue` empty + log still "running". Re-sample 3–4×. Fix: `pkill -f "batchNN-render"`; the
orchestrator retries and resumes (skips existing mp4s). `finalize` scp can silently fail — always
verify gw file count (`ls /var/www/html/batchNN/*.mp4 | wc -l`) and re-deploy if < 10.

---

## Quote-card pipeline (separate; CPU/ffmpeg + Gemini/Veo, no GPU)

- `scripts/build-quote-card-vo.ts` — windswept Veo bg + Sarah VO + slow, halved, WHITE captions,
`-movflags +faststart`. Driver `build-all-quote-cards-vo.ts` (`--force`). Wave bgs via
`gen-wind-clips.ts` (Veo text-to-video, 9:16). `travel-quotes.json`.
- **55 voiced cards live** at `https://gw.724care.com/quote-cards.html`
(deployed to `/var/www/html/quotecards-vo/`). faststart fixed (moov before mdat).

---

## Conventions & guardrails
- Confirm before irreversible/live-serving steps; a generic "go ahead" does NOT authorize prod.
- gw live docroot = `/var/www/html`. hi2b prod = `root@api3.amdy.io:/opt/hi2b` (PM2 `hi2b`),
deploy via scp + `npm run build` + `pm2 reload`.
- Never echo secrets (FISH_API_KEY, GEMINI_API_KEY, etc.).
- Internal standalone HTML → `public/research/<name>.html` → scp to gw → link from `batches.html`.
61 changes: 61 additions & 0 deletions docs/DEPLOYMENT.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,61 @@
# Deployment

## Production

**hi2b.com** runs on `api3.amdy.io` (65.21.161.173), fronted by Cloudflare.

| | |
|---|---|
| **SSH** | `ssh root@api3.amdy.io` |
| **App directory** | `/opt/hi2b` |
| **Process manager** | PM2, app name `hi2b` (id 5) |
| **Origin port** | `127.0.0.1:3015` (Cloudflare proxies to it) |

> ⚠️ `/opt/hi2b` is **not a git checkout**. There is no `.git` directory there, so `git pull` will not work. Deploys are done by copying files over with `scp` from this repo at `/home/na/ai-management-dashboard`.

## Standard deploy

From the dev tree, for any changed files:

```bash
# 1. copy the changed files to the production app dir
scp <changed-files> root@api3.amdy.io:/opt/hi2b/<same-relative-path>

# 2. build and reload
ssh root@api3.amdy.io 'cd /opt/hi2b && npm run build && pm2 reload hi2b'
```

PM2 `reload` is a zero-downtime restart. Use `pm2 restart hi2b` for a full restart if `reload` isn't enough.

## Other PM2 apps on this box (do not touch)

`api3.amdy.io` also runs unrelated services. **Leave them alone**:

- `58dakota`, `58dakota-portal`
- `724care-next`
- `amdy-portal`
- `amdy-recording-manager`
- `amdy-worker-emails`
- `did-optimizer`

## Useful one-liners

```bash
# tail the prod app log
ssh root@api3.amdy.io 'pm2 logs hi2b --lines 50 --nostream'

# pm2 status (just hi2b)
ssh root@api3.amdy.io 'pm2 show hi2b'

# quickly verify a route after deploy
curl -sI https://hi2b.com/<path>
```

## Static SEO / crawler assets (live)

| File | Purpose |
|---|---|
| `/robots.txt` | Explicit Allow for every major search + AI/LLM crawler |
| `/llms.txt` | [llmstxt.org](https://llmstxt.org) site summary for AI assistants |
| `/sitemap.xml` | Auto-generated by `src/app/sitemap.ts` — covers all 43 LPs + static pages |
| `src/app/not-found.tsx` | Catches any 404 and `redirect()`s to `/lp/golden-hour` so no ad traffic is lost |
6 changes: 5 additions & 1 deletion next.config.ts
Original file line number Diff line number Diff line change
@@ -1,7 +1,11 @@
import type { NextConfig } from "next";

const nextConfig: NextConfig = {
/* config options here */
images: {
remotePatterns: [
{ protocol: 'https', hostname: 'images.unsplash.com' },
],
},
typescript: {
ignoreBuildErrors: true,
},
Expand Down
Loading