# Setup guide (developers) ## 1. Requirements * Node.js 20+ and npm * PHP 8.1+ with `curl`, `pdo_sqlite`, `mbstring`, `json` (macOS: `brew install php`) * `zip` (for `npm run package`) ## 2. Run locally ```bash npm install cp .env.example .env cp .env.example private/.env ``` Edit `.env` (frontend build settings): ``` NEXT_PUBLIC_API_BASE=http://127.0.0.1:8080/api ``` Edit `private/.env` (PHP API; stays in mock mode by default): ``` INTEGRATION_MODE=mock ALLOWED_ORIGINS=http://localhost:3000,http://127.0.0.1:3000 DEBUG_GEO=true # lets you fake a location: /api/locate.php?debug_lat=42.19&debug_lng=-71.19 ``` Then in two terminals: ```bash npm run dev # http://localhost:3000 npm run dev:api # http://127.0.0.1:8080/api/health.php ``` Open http://localhost:3000. Submit the form: in mock mode the API stores the lead in `private/data/leads.sqlite` and logs *what it would have sent* to `private/logs/app.log` without contacting Mailchimp or Sinch. ### Preview the exact production build ```bash npm run build php -S 127.0.0.1:8080 -t out ``` This serves the static export and the PHP API from one origin, like cPanel will. ## 3. Test without sending messages * `INTEGRATION_MODE=mock` (default) never calls Mailchimp or Sinch. Responses carry `"status": "mock"` so the confirmation screen still renders correctly. * `npm test` runs the TypeScript suite (jsdom). * `npm run test:php` spins up fake Mailchimp/Sinch/geocode servers on `127.0.0.1:8099` and the real API on `127.0.0.1:8098`, then checks validation, consent enforcement, idempotency, partial failure + cron retry, and every HTTP endpoint end to end. * To inspect stored leads: `php public/api/cron/export-leads.php > leads.csv` (run with `VISIT_PRIVATE_DIR=private` locally). ## 4. Enter campus details Everything campus-related is in **`config/campuses.json`**. Each entry: | Field | Notes | | --- | --- | | `id`, `name`, `label` | `id` is stable and appears in analytics + Mailchimp tags | | `status` | `open` (selectable), `launching` (shown as "coming soon"), `hidden` | | `address`, `coordinates` | Coordinates drive the "closest campus" math. Verify on Google Maps (right-click → copy coordinates). | | `serviceTimes[]` | `{ day, times[], note? }` — times are display strings like `"9:30 AM"` | | `phone`, `smsNumber` | E.164. `smsNumber` must be SMS-enabled on the Sinch Engage account | | `sinchAccountId` | Sinch Engage sub-account used as the sender (`Account` header) | | `directionsUrl`, `campusUrl` | Google Maps directions link; campus page on waterschurch.org | | `arrivalNotes`, `parkingNotes`, `kidsNotes` | Optional short text shown on the campus card | | `image` | Photo key; put the file at `assets/source/photos/campus-.jpg`, run `npm run images` | | `verify[]` | Reminder list of fields Waters Church still needs to confirm (documentation only) | After editing: `npm run build` and upload `out/` again (the API reads `out/api/config/campuses.json`). Copy and FAQ text live in `src/content/` (`copy.ts`, `faqs.ts`, `kids.ts`, `consent.ts`, `testimonials.ts`). Site-wide settings (phone, email, privacy URL, central SMS number) are in `src/config/site.ts` and can be overridden by `NEXT_PUBLIC_*` variables. ## 5. Connect Mailchimp 1. In Mailchimp: **Profile → Extras → API keys → Create a key**. The key ends in `-usXX`; that is your `MAILCHIMP_SERVER_PREFIX`. 2. **Audience → Settings → Audience name and defaults** → copy the **Audience ID**. 3. Create these **merge fields** in the audience (Settings → Audience fields): `CAMPUS`, `VISITDATE`, `SERVICE`, `ADULTS`, `KIDS`, `KIDAGES`, `QUESTIONS`, `UTMSOURCE`, `UTMCAMP`, `LPVARIANT`, `SUBID`, `PHONE` (text). Unknown fields are ignored (`skip_merge_validation=true`), but they only store data if they exist. 4. Tags applied automatically (prefix configurable via `MAILCHIMP_TAG_PREFIX`, default `Visit:`), matching the church's existing `Visit:` convention: `Visit:` (e.g. `Visit:Norwood`), `Visit:First-Time Visitor`, `Visit:Source visit.waterschurch.org`, `Visit:Platform `, `Visit:Campaign `, `Visit:Visit ` or `Visit:Service