v4.0.5
Component architecture - Reorganise src/lib/components into pages/, sections/, ui/ subdirectories and update all imports across routes and tests. Owner admin dashboard (+2k lines) - Scheduled welcome-pack emails: durable queue with cancel/reschedule and a background sender loop (SCHEDULED_CHECK_INTERVAL_SECONDS, default 60s). - Custom welcome-pack subject line, preview recipients, and client BCC. - Add-client, edit client profile, and reset-onboarding flows. - "View as client" onboarding preview (owner impersonation, dry-run submit). - Tabbed owner welcome route (/owner/welcome/[[tab]]). MYOB integration - New mail_api/myob.py: create new clients as MYOB AccountRight customer contacts. Disabled until all MYOB_* env vars are set (no-op otherwise). Onboarding - New "Does your dog resource guard?" Yes/No question in the Behaviour step. - Persist vetAddress, flea/tick, and pet-insurance fields server-side. Misc - vite config, new hooks.ts, responsive CSS tweaks, deploy/docker config, mail-api README and start-dev.ps1. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
@@ -40,6 +40,17 @@ EMAIL_SEND_TIMEOUT_SECONDS=20
|
|||||||
# Rotate with: node -e "console.log(require('crypto').randomBytes(32).toString('hex'))"
|
# Rotate with: node -e "console.log(require('crypto').randomBytes(32).toString('hex'))"
|
||||||
DEPLOY_SMOKE_SECRET=ed7261d3d7a5ac0a51e0cfb2bf4e2bd4009503605d2963d3ee766b7e885e76eb
|
DEPLOY_SMOKE_SECRET=ed7261d3d7a5ac0a51e0cfb2bf4e2bd4009503605d2963d3ee766b7e885e76eb
|
||||||
|
|
||||||
|
# MYOB integration — optional. When all of MYOB_API_KEY / MYOB_API_SECRET /
|
||||||
|
# MYOB_REFRESH_TOKEN / MYOB_COMPANY_FILE_URI are set, the "Also create this
|
||||||
|
# client in MYOB" checkbox in the control panel's Add-client modal creates the
|
||||||
|
# client as a customer contact in MYOB AccountRight. Leave blank to disable.
|
||||||
|
# MYOB_API_KEY=
|
||||||
|
# MYOB_API_SECRET=
|
||||||
|
# MYOB_REFRESH_TOKEN=
|
||||||
|
# MYOB_COMPANY_FILE_URI=https://api.myob.com/accountright/<company-file-guid>
|
||||||
|
# MYOB_CF_USERNAME=Administrator
|
||||||
|
# MYOB_CF_PASSWORD=
|
||||||
|
|
||||||
# Security hardening — sensible defaults are in mail_api/config.py.
|
# Security hardening — sensible defaults are in mail_api/config.py.
|
||||||
# Override only if the public domains change or you need to allow extra origins.
|
# Override only if the public domains change or you need to allow extra origins.
|
||||||
# CORS_ALLOWED_ORIGINS=https://goodwalk.co.nz,https://www.goodwalk.co.nz,https://clients.goodwalk.co.nz,https://cp.goodwalk.co.nz
|
# CORS_ALLOWED_ORIGINS=https://goodwalk.co.nz,https://www.goodwalk.co.nz,https://clients.goodwalk.co.nz,https://cp.goodwalk.co.nz
|
||||||
|
|||||||
@@ -399,6 +399,7 @@ function Invoke-AllFormSmokeTests {
|
|||||||
dogName = 'Smoke'
|
dogName = 'Smoke'
|
||||||
dogBreed = 'Test'
|
dogBreed = 'Test'
|
||||||
vetName = 'Dr Smoke'
|
vetName = 'Dr Smoke'
|
||||||
|
vetAddress = '2 Vet Rd, Auckland'
|
||||||
vetPhone = '+64-00-000-0000'
|
vetPhone = '+64-00-000-0000'
|
||||||
emergencyContactName = 'Smoke Contact'
|
emergencyContactName = 'Smoke Contact'
|
||||||
emergencyContactPhone = '+64-00-000-0000'
|
emergencyContactPhone = '+64-00-000-0000'
|
||||||
|
|||||||
@@ -59,6 +59,13 @@ services:
|
|||||||
TRUSTED_HOSTS: ${TRUSTED_HOSTS:-}
|
TRUSTED_HOSTS: ${TRUSTED_HOSTS:-}
|
||||||
MAX_REQUEST_BODY_BYTES: ${MAX_REQUEST_BODY_BYTES:-}
|
MAX_REQUEST_BODY_BYTES: ${MAX_REQUEST_BODY_BYTES:-}
|
||||||
DEPLOY_SMOKE_SECRET: ${DEPLOY_SMOKE_SECRET:-}
|
DEPLOY_SMOKE_SECRET: ${DEPLOY_SMOKE_SECRET:-}
|
||||||
|
# MYOB integration (optional — see deploy.env.template).
|
||||||
|
MYOB_API_KEY: ${MYOB_API_KEY:-}
|
||||||
|
MYOB_API_SECRET: ${MYOB_API_SECRET:-}
|
||||||
|
MYOB_REFRESH_TOKEN: ${MYOB_REFRESH_TOKEN:-}
|
||||||
|
MYOB_COMPANY_FILE_URI: ${MYOB_COMPANY_FILE_URI:-}
|
||||||
|
MYOB_CF_USERNAME: ${MYOB_CF_USERNAME:-Administrator}
|
||||||
|
MYOB_CF_PASSWORD: ${MYOB_CF_PASSWORD:-}
|
||||||
PYTHONUNBUFFERED: '1'
|
PYTHONUNBUFFERED: '1'
|
||||||
TZ: ${TZ:-Pacific/Auckland}
|
TZ: ${TZ:-Pacific/Auckland}
|
||||||
expose:
|
expose:
|
||||||
|
|||||||
@@ -32,6 +32,13 @@ services:
|
|||||||
REPLY_TO: ${REPLY_TO:-info@goodwalk.co.nz}
|
REPLY_TO: ${REPLY_TO:-info@goodwalk.co.nz}
|
||||||
ENABLE_GENERAL_ENQUIRIES: ${ENABLE_GENERAL_ENQUIRIES:-false}
|
ENABLE_GENERAL_ENQUIRIES: ${ENABLE_GENERAL_ENQUIRIES:-false}
|
||||||
DATA_DIR: ${MAIL_API_DATA_DIR:-/app/data}
|
DATA_DIR: ${MAIL_API_DATA_DIR:-/app/data}
|
||||||
|
# MYOB integration (optional — see deploy.env.template).
|
||||||
|
MYOB_API_KEY: ${MYOB_API_KEY:-}
|
||||||
|
MYOB_API_SECRET: ${MYOB_API_SECRET:-}
|
||||||
|
MYOB_REFRESH_TOKEN: ${MYOB_REFRESH_TOKEN:-}
|
||||||
|
MYOB_COMPANY_FILE_URI: ${MYOB_COMPANY_FILE_URI:-}
|
||||||
|
MYOB_CF_USERNAME: ${MYOB_CF_USERNAME:-Administrator}
|
||||||
|
MYOB_CF_PASSWORD: ${MYOB_CF_PASSWORD:-}
|
||||||
TZ: ${TZ:-Pacific/Auckland}
|
TZ: ${TZ:-Pacific/Auckland}
|
||||||
volumes:
|
volumes:
|
||||||
- mail_api_data:${MAIL_API_DATA_DIR:-/app/data}
|
- mail_api_data:${MAIL_API_DATA_DIR:-/app/data}
|
||||||
|
|||||||
@@ -0,0 +1,22 @@
|
|||||||
|
# Mail API local dev
|
||||||
|
|
||||||
|
Use the project virtualenv, not the global Python install. The local environment already resolves compatible `pydantic` and `pydantic-core` versions together.
|
||||||
|
|
||||||
|
## Start on Windows PowerShell
|
||||||
|
|
||||||
|
```powershell
|
||||||
|
cd C:\Users\mattc\gw-svelte\gw-svelte\mail-api
|
||||||
|
.\start-dev.ps1
|
||||||
|
```
|
||||||
|
|
||||||
|
## First-time setup
|
||||||
|
|
||||||
|
```powershell
|
||||||
|
cd C:\Users\mattc\gw-svelte\gw-svelte\mail-api
|
||||||
|
py -3.14 -m venv .venv
|
||||||
|
.\.venv\Scripts\python.exe -m pip install -r requirements.txt
|
||||||
|
```
|
||||||
|
|
||||||
|
## If you see a `pydantic-core` mismatch
|
||||||
|
|
||||||
|
You are almost certainly running against a global interpreter or a stale environment. Reinstall into the project virtualenv and start the app with `.\start-dev.ps1`.
|
||||||
Binary file not shown.
Binary file not shown.
@@ -207,12 +207,12 @@
|
|||||||
"lifecycle": {
|
"lifecycle": {
|
||||||
"status": "archived",
|
"status": "archived",
|
||||||
"reason": "Imported from legacy Gravity Forms data",
|
"reason": "Imported from legacy Gravity Forms data",
|
||||||
"changedAt": "2026-05-24T05:45:33.160Z",
|
"changedAt": "2026-06-15T21:24:42.302Z",
|
||||||
"changedBy": "legacy-import"
|
"changedBy": "legacy-import"
|
||||||
},
|
},
|
||||||
"legacy": {
|
"legacy": {
|
||||||
"source": "gravity-forms-csv",
|
"source": "gravity-forms-csv",
|
||||||
"importedAt": "2026-05-24T05:45:33.160Z",
|
"importedAt": "2026-06-15T21:24:42.302Z",
|
||||||
"onboardingEntryIds": [
|
"onboardingEntryIds": [
|
||||||
"27"
|
"27"
|
||||||
],
|
],
|
||||||
@@ -432,12 +432,12 @@
|
|||||||
"lifecycle": {
|
"lifecycle": {
|
||||||
"status": "archived",
|
"status": "archived",
|
||||||
"reason": "Imported from legacy Gravity Forms data",
|
"reason": "Imported from legacy Gravity Forms data",
|
||||||
"changedAt": "2026-05-24T05:45:33.160Z",
|
"changedAt": "2026-06-15T21:24:42.302Z",
|
||||||
"changedBy": "legacy-import"
|
"changedBy": "legacy-import"
|
||||||
},
|
},
|
||||||
"legacy": {
|
"legacy": {
|
||||||
"source": "gravity-forms-csv",
|
"source": "gravity-forms-csv",
|
||||||
"importedAt": "2026-05-24T05:45:33.160Z",
|
"importedAt": "2026-06-15T21:24:42.302Z",
|
||||||
"onboardingEntryIds": [
|
"onboardingEntryIds": [
|
||||||
"45"
|
"45"
|
||||||
],
|
],
|
||||||
@@ -657,12 +657,12 @@
|
|||||||
"lifecycle": {
|
"lifecycle": {
|
||||||
"status": "archived",
|
"status": "archived",
|
||||||
"reason": "Imported from legacy Gravity Forms data",
|
"reason": "Imported from legacy Gravity Forms data",
|
||||||
"changedAt": "2026-05-24T05:45:33.160Z",
|
"changedAt": "2026-06-15T21:24:42.302Z",
|
||||||
"changedBy": "legacy-import"
|
"changedBy": "legacy-import"
|
||||||
},
|
},
|
||||||
"legacy": {
|
"legacy": {
|
||||||
"source": "gravity-forms-csv",
|
"source": "gravity-forms-csv",
|
||||||
"importedAt": "2026-05-24T05:45:33.160Z",
|
"importedAt": "2026-06-15T21:24:42.302Z",
|
||||||
"onboardingEntryIds": [
|
"onboardingEntryIds": [
|
||||||
"95"
|
"95"
|
||||||
],
|
],
|
||||||
@@ -882,12 +882,12 @@
|
|||||||
"lifecycle": {
|
"lifecycle": {
|
||||||
"status": "archived",
|
"status": "archived",
|
||||||
"reason": "Imported from legacy Gravity Forms data",
|
"reason": "Imported from legacy Gravity Forms data",
|
||||||
"changedAt": "2026-05-24T05:45:33.160Z",
|
"changedAt": "2026-06-15T21:24:42.302Z",
|
||||||
"changedBy": "legacy-import"
|
"changedBy": "legacy-import"
|
||||||
},
|
},
|
||||||
"legacy": {
|
"legacy": {
|
||||||
"source": "gravity-forms-csv",
|
"source": "gravity-forms-csv",
|
||||||
"importedAt": "2026-05-24T05:45:33.160Z",
|
"importedAt": "2026-06-15T21:24:42.302Z",
|
||||||
"onboardingEntryIds": [
|
"onboardingEntryIds": [
|
||||||
"103"
|
"103"
|
||||||
],
|
],
|
||||||
@@ -1107,12 +1107,12 @@
|
|||||||
"lifecycle": {
|
"lifecycle": {
|
||||||
"status": "archived",
|
"status": "archived",
|
||||||
"reason": "Imported from legacy Gravity Forms data",
|
"reason": "Imported from legacy Gravity Forms data",
|
||||||
"changedAt": "2026-05-24T05:45:33.160Z",
|
"changedAt": "2026-06-15T21:24:42.302Z",
|
||||||
"changedBy": "legacy-import"
|
"changedBy": "legacy-import"
|
||||||
},
|
},
|
||||||
"legacy": {
|
"legacy": {
|
||||||
"source": "gravity-forms-csv",
|
"source": "gravity-forms-csv",
|
||||||
"importedAt": "2026-05-24T05:45:33.160Z",
|
"importedAt": "2026-06-15T21:24:42.302Z",
|
||||||
"onboardingEntryIds": [
|
"onboardingEntryIds": [
|
||||||
"39"
|
"39"
|
||||||
],
|
],
|
||||||
@@ -1332,12 +1332,12 @@
|
|||||||
"lifecycle": {
|
"lifecycle": {
|
||||||
"status": "archived",
|
"status": "archived",
|
||||||
"reason": "Imported from legacy Gravity Forms data",
|
"reason": "Imported from legacy Gravity Forms data",
|
||||||
"changedAt": "2026-05-24T05:45:33.160Z",
|
"changedAt": "2026-06-15T21:24:42.302Z",
|
||||||
"changedBy": "legacy-import"
|
"changedBy": "legacy-import"
|
||||||
},
|
},
|
||||||
"legacy": {
|
"legacy": {
|
||||||
"source": "gravity-forms-csv",
|
"source": "gravity-forms-csv",
|
||||||
"importedAt": "2026-05-24T05:45:33.160Z",
|
"importedAt": "2026-06-15T21:24:42.302Z",
|
||||||
"onboardingEntryIds": [
|
"onboardingEntryIds": [
|
||||||
"41"
|
"41"
|
||||||
],
|
],
|
||||||
@@ -1557,12 +1557,12 @@
|
|||||||
"lifecycle": {
|
"lifecycle": {
|
||||||
"status": "archived",
|
"status": "archived",
|
||||||
"reason": "Imported from legacy Gravity Forms data",
|
"reason": "Imported from legacy Gravity Forms data",
|
||||||
"changedAt": "2026-05-24T05:45:33.160Z",
|
"changedAt": "2026-06-15T21:24:42.302Z",
|
||||||
"changedBy": "legacy-import"
|
"changedBy": "legacy-import"
|
||||||
},
|
},
|
||||||
"legacy": {
|
"legacy": {
|
||||||
"source": "gravity-forms-csv",
|
"source": "gravity-forms-csv",
|
||||||
"importedAt": "2026-05-24T05:45:33.160Z",
|
"importedAt": "2026-06-15T21:24:42.302Z",
|
||||||
"onboardingEntryIds": [
|
"onboardingEntryIds": [
|
||||||
"36"
|
"36"
|
||||||
],
|
],
|
||||||
@@ -1782,12 +1782,12 @@
|
|||||||
"lifecycle": {
|
"lifecycle": {
|
||||||
"status": "archived",
|
"status": "archived",
|
||||||
"reason": "Imported from legacy Gravity Forms data",
|
"reason": "Imported from legacy Gravity Forms data",
|
||||||
"changedAt": "2026-05-24T05:45:33.160Z",
|
"changedAt": "2026-06-15T21:24:42.302Z",
|
||||||
"changedBy": "legacy-import"
|
"changedBy": "legacy-import"
|
||||||
},
|
},
|
||||||
"legacy": {
|
"legacy": {
|
||||||
"source": "gravity-forms-csv",
|
"source": "gravity-forms-csv",
|
||||||
"importedAt": "2026-05-24T05:45:33.160Z",
|
"importedAt": "2026-06-15T21:24:42.302Z",
|
||||||
"onboardingEntryIds": [
|
"onboardingEntryIds": [
|
||||||
"83"
|
"83"
|
||||||
],
|
],
|
||||||
@@ -2007,12 +2007,12 @@
|
|||||||
"lifecycle": {
|
"lifecycle": {
|
||||||
"status": "archived",
|
"status": "archived",
|
||||||
"reason": "Imported from legacy Gravity Forms data",
|
"reason": "Imported from legacy Gravity Forms data",
|
||||||
"changedAt": "2026-05-24T05:45:33.160Z",
|
"changedAt": "2026-06-15T21:24:42.302Z",
|
||||||
"changedBy": "legacy-import"
|
"changedBy": "legacy-import"
|
||||||
},
|
},
|
||||||
"legacy": {
|
"legacy": {
|
||||||
"source": "gravity-forms-csv",
|
"source": "gravity-forms-csv",
|
||||||
"importedAt": "2026-05-24T05:45:33.160Z",
|
"importedAt": "2026-06-15T21:24:42.302Z",
|
||||||
"onboardingEntryIds": [
|
"onboardingEntryIds": [
|
||||||
"76",
|
"76",
|
||||||
"49"
|
"49"
|
||||||
@@ -2233,12 +2233,12 @@
|
|||||||
"lifecycle": {
|
"lifecycle": {
|
||||||
"status": "archived",
|
"status": "archived",
|
||||||
"reason": "Imported from legacy Gravity Forms data",
|
"reason": "Imported from legacy Gravity Forms data",
|
||||||
"changedAt": "2026-05-24T05:45:33.160Z",
|
"changedAt": "2026-06-15T21:24:42.302Z",
|
||||||
"changedBy": "legacy-import"
|
"changedBy": "legacy-import"
|
||||||
},
|
},
|
||||||
"legacy": {
|
"legacy": {
|
||||||
"source": "gravity-forms-csv",
|
"source": "gravity-forms-csv",
|
||||||
"importedAt": "2026-05-24T05:45:33.160Z",
|
"importedAt": "2026-06-15T21:24:42.302Z",
|
||||||
"onboardingEntryIds": [
|
"onboardingEntryIds": [
|
||||||
"99"
|
"99"
|
||||||
],
|
],
|
||||||
@@ -2458,12 +2458,12 @@
|
|||||||
"lifecycle": {
|
"lifecycle": {
|
||||||
"status": "archived",
|
"status": "archived",
|
||||||
"reason": "Imported from legacy Gravity Forms data",
|
"reason": "Imported from legacy Gravity Forms data",
|
||||||
"changedAt": "2026-05-24T05:45:33.160Z",
|
"changedAt": "2026-06-15T21:24:42.302Z",
|
||||||
"changedBy": "legacy-import"
|
"changedBy": "legacy-import"
|
||||||
},
|
},
|
||||||
"legacy": {
|
"legacy": {
|
||||||
"source": "gravity-forms-csv",
|
"source": "gravity-forms-csv",
|
||||||
"importedAt": "2026-05-24T05:45:33.160Z",
|
"importedAt": "2026-06-15T21:24:42.302Z",
|
||||||
"onboardingEntryIds": [
|
"onboardingEntryIds": [
|
||||||
"84"
|
"84"
|
||||||
],
|
],
|
||||||
@@ -2683,12 +2683,12 @@
|
|||||||
"lifecycle": {
|
"lifecycle": {
|
||||||
"status": "archived",
|
"status": "archived",
|
||||||
"reason": "Imported from legacy Gravity Forms data",
|
"reason": "Imported from legacy Gravity Forms data",
|
||||||
"changedAt": "2026-05-24T05:45:33.160Z",
|
"changedAt": "2026-06-15T21:24:42.302Z",
|
||||||
"changedBy": "legacy-import"
|
"changedBy": "legacy-import"
|
||||||
},
|
},
|
||||||
"legacy": {
|
"legacy": {
|
||||||
"source": "gravity-forms-csv",
|
"source": "gravity-forms-csv",
|
||||||
"importedAt": "2026-05-24T05:45:33.160Z",
|
"importedAt": "2026-06-15T21:24:42.302Z",
|
||||||
"onboardingEntryIds": [
|
"onboardingEntryIds": [
|
||||||
"51"
|
"51"
|
||||||
],
|
],
|
||||||
@@ -2908,12 +2908,12 @@
|
|||||||
"lifecycle": {
|
"lifecycle": {
|
||||||
"status": "archived",
|
"status": "archived",
|
||||||
"reason": "Imported from legacy Gravity Forms data",
|
"reason": "Imported from legacy Gravity Forms data",
|
||||||
"changedAt": "2026-05-24T05:45:33.160Z",
|
"changedAt": "2026-06-15T21:24:42.302Z",
|
||||||
"changedBy": "legacy-import"
|
"changedBy": "legacy-import"
|
||||||
},
|
},
|
||||||
"legacy": {
|
"legacy": {
|
||||||
"source": "gravity-forms-csv",
|
"source": "gravity-forms-csv",
|
||||||
"importedAt": "2026-05-24T05:45:33.160Z",
|
"importedAt": "2026-06-15T21:24:42.302Z",
|
||||||
"onboardingEntryIds": [
|
"onboardingEntryIds": [
|
||||||
"87"
|
"87"
|
||||||
],
|
],
|
||||||
@@ -3134,12 +3134,12 @@
|
|||||||
"lifecycle": {
|
"lifecycle": {
|
||||||
"status": "archived",
|
"status": "archived",
|
||||||
"reason": "Imported from legacy Gravity Forms data",
|
"reason": "Imported from legacy Gravity Forms data",
|
||||||
"changedAt": "2026-05-24T05:45:33.160Z",
|
"changedAt": "2026-06-15T21:24:42.302Z",
|
||||||
"changedBy": "legacy-import"
|
"changedBy": "legacy-import"
|
||||||
},
|
},
|
||||||
"legacy": {
|
"legacy": {
|
||||||
"source": "gravity-forms-csv",
|
"source": "gravity-forms-csv",
|
||||||
"importedAt": "2026-05-24T05:45:33.160Z",
|
"importedAt": "2026-06-15T21:24:42.302Z",
|
||||||
"onboardingEntryIds": [
|
"onboardingEntryIds": [
|
||||||
"88"
|
"88"
|
||||||
],
|
],
|
||||||
@@ -3359,12 +3359,12 @@
|
|||||||
"lifecycle": {
|
"lifecycle": {
|
||||||
"status": "archived",
|
"status": "archived",
|
||||||
"reason": "Imported from legacy Gravity Forms data",
|
"reason": "Imported from legacy Gravity Forms data",
|
||||||
"changedAt": "2026-05-24T05:45:33.160Z",
|
"changedAt": "2026-06-15T21:24:42.302Z",
|
||||||
"changedBy": "legacy-import"
|
"changedBy": "legacy-import"
|
||||||
},
|
},
|
||||||
"legacy": {
|
"legacy": {
|
||||||
"source": "gravity-forms-csv",
|
"source": "gravity-forms-csv",
|
||||||
"importedAt": "2026-05-24T05:45:33.160Z",
|
"importedAt": "2026-06-15T21:24:42.302Z",
|
||||||
"onboardingEntryIds": [
|
"onboardingEntryIds": [
|
||||||
"34"
|
"34"
|
||||||
],
|
],
|
||||||
@@ -3584,12 +3584,12 @@
|
|||||||
"lifecycle": {
|
"lifecycle": {
|
||||||
"status": "archived",
|
"status": "archived",
|
||||||
"reason": "Imported from legacy Gravity Forms data",
|
"reason": "Imported from legacy Gravity Forms data",
|
||||||
"changedAt": "2026-05-24T05:45:33.160Z",
|
"changedAt": "2026-06-15T21:24:42.302Z",
|
||||||
"changedBy": "legacy-import"
|
"changedBy": "legacy-import"
|
||||||
},
|
},
|
||||||
"legacy": {
|
"legacy": {
|
||||||
"source": "gravity-forms-csv",
|
"source": "gravity-forms-csv",
|
||||||
"importedAt": "2026-05-24T05:45:33.160Z",
|
"importedAt": "2026-06-15T21:24:42.302Z",
|
||||||
"onboardingEntryIds": [
|
"onboardingEntryIds": [
|
||||||
"61",
|
"61",
|
||||||
"47"
|
"47"
|
||||||
@@ -3811,12 +3811,12 @@
|
|||||||
"lifecycle": {
|
"lifecycle": {
|
||||||
"status": "archived",
|
"status": "archived",
|
||||||
"reason": "Imported from legacy Gravity Forms data",
|
"reason": "Imported from legacy Gravity Forms data",
|
||||||
"changedAt": "2026-05-24T05:45:33.160Z",
|
"changedAt": "2026-06-15T21:24:42.302Z",
|
||||||
"changedBy": "legacy-import"
|
"changedBy": "legacy-import"
|
||||||
},
|
},
|
||||||
"legacy": {
|
"legacy": {
|
||||||
"source": "gravity-forms-csv",
|
"source": "gravity-forms-csv",
|
||||||
"importedAt": "2026-05-24T05:45:33.160Z",
|
"importedAt": "2026-06-15T21:24:42.302Z",
|
||||||
"onboardingEntryIds": [
|
"onboardingEntryIds": [
|
||||||
"57"
|
"57"
|
||||||
],
|
],
|
||||||
@@ -4036,12 +4036,12 @@
|
|||||||
"lifecycle": {
|
"lifecycle": {
|
||||||
"status": "archived",
|
"status": "archived",
|
||||||
"reason": "Imported from legacy Gravity Forms data",
|
"reason": "Imported from legacy Gravity Forms data",
|
||||||
"changedAt": "2026-05-24T05:45:33.160Z",
|
"changedAt": "2026-06-15T21:24:42.302Z",
|
||||||
"changedBy": "legacy-import"
|
"changedBy": "legacy-import"
|
||||||
},
|
},
|
||||||
"legacy": {
|
"legacy": {
|
||||||
"source": "gravity-forms-csv",
|
"source": "gravity-forms-csv",
|
||||||
"importedAt": "2026-05-24T05:45:33.160Z",
|
"importedAt": "2026-06-15T21:24:42.302Z",
|
||||||
"onboardingEntryIds": [
|
"onboardingEntryIds": [
|
||||||
"32"
|
"32"
|
||||||
],
|
],
|
||||||
@@ -4261,12 +4261,12 @@
|
|||||||
"lifecycle": {
|
"lifecycle": {
|
||||||
"status": "archived",
|
"status": "archived",
|
||||||
"reason": "Imported from legacy Gravity Forms data",
|
"reason": "Imported from legacy Gravity Forms data",
|
||||||
"changedAt": "2026-05-24T05:45:33.160Z",
|
"changedAt": "2026-06-15T21:24:42.302Z",
|
||||||
"changedBy": "legacy-import"
|
"changedBy": "legacy-import"
|
||||||
},
|
},
|
||||||
"legacy": {
|
"legacy": {
|
||||||
"source": "gravity-forms-csv",
|
"source": "gravity-forms-csv",
|
||||||
"importedAt": "2026-05-24T05:45:33.160Z",
|
"importedAt": "2026-06-15T21:24:42.302Z",
|
||||||
"onboardingEntryIds": [
|
"onboardingEntryIds": [
|
||||||
"22"
|
"22"
|
||||||
],
|
],
|
||||||
@@ -4486,12 +4486,12 @@
|
|||||||
"lifecycle": {
|
"lifecycle": {
|
||||||
"status": "archived",
|
"status": "archived",
|
||||||
"reason": "Imported from legacy Gravity Forms data",
|
"reason": "Imported from legacy Gravity Forms data",
|
||||||
"changedAt": "2026-05-24T05:45:33.160Z",
|
"changedAt": "2026-06-15T21:24:42.302Z",
|
||||||
"changedBy": "legacy-import"
|
"changedBy": "legacy-import"
|
||||||
},
|
},
|
||||||
"legacy": {
|
"legacy": {
|
||||||
"source": "gravity-forms-csv",
|
"source": "gravity-forms-csv",
|
||||||
"importedAt": "2026-05-24T05:45:33.160Z",
|
"importedAt": "2026-06-15T21:24:42.302Z",
|
||||||
"onboardingEntryIds": [
|
"onboardingEntryIds": [
|
||||||
"64"
|
"64"
|
||||||
],
|
],
|
||||||
@@ -4711,12 +4711,12 @@
|
|||||||
"lifecycle": {
|
"lifecycle": {
|
||||||
"status": "archived",
|
"status": "archived",
|
||||||
"reason": "Imported from legacy Gravity Forms data",
|
"reason": "Imported from legacy Gravity Forms data",
|
||||||
"changedAt": "2026-05-24T05:45:33.160Z",
|
"changedAt": "2026-06-15T21:24:42.302Z",
|
||||||
"changedBy": "legacy-import"
|
"changedBy": "legacy-import"
|
||||||
},
|
},
|
||||||
"legacy": {
|
"legacy": {
|
||||||
"source": "gravity-forms-csv",
|
"source": "gravity-forms-csv",
|
||||||
"importedAt": "2026-05-24T05:45:33.160Z",
|
"importedAt": "2026-06-15T21:24:42.302Z",
|
||||||
"onboardingEntryIds": [
|
"onboardingEntryIds": [
|
||||||
"74"
|
"74"
|
||||||
],
|
],
|
||||||
@@ -4936,12 +4936,12 @@
|
|||||||
"lifecycle": {
|
"lifecycle": {
|
||||||
"status": "archived",
|
"status": "archived",
|
||||||
"reason": "Imported from legacy Gravity Forms data",
|
"reason": "Imported from legacy Gravity Forms data",
|
||||||
"changedAt": "2026-05-24T05:45:33.160Z",
|
"changedAt": "2026-06-15T21:24:42.302Z",
|
||||||
"changedBy": "legacy-import"
|
"changedBy": "legacy-import"
|
||||||
},
|
},
|
||||||
"legacy": {
|
"legacy": {
|
||||||
"source": "gravity-forms-csv",
|
"source": "gravity-forms-csv",
|
||||||
"importedAt": "2026-05-24T05:45:33.160Z",
|
"importedAt": "2026-06-15T21:24:42.302Z",
|
||||||
"onboardingEntryIds": [
|
"onboardingEntryIds": [
|
||||||
"56"
|
"56"
|
||||||
],
|
],
|
||||||
@@ -5161,12 +5161,12 @@
|
|||||||
"lifecycle": {
|
"lifecycle": {
|
||||||
"status": "archived",
|
"status": "archived",
|
||||||
"reason": "Imported from legacy Gravity Forms data",
|
"reason": "Imported from legacy Gravity Forms data",
|
||||||
"changedAt": "2026-05-24T05:45:33.160Z",
|
"changedAt": "2026-06-15T21:24:42.302Z",
|
||||||
"changedBy": "legacy-import"
|
"changedBy": "legacy-import"
|
||||||
},
|
},
|
||||||
"legacy": {
|
"legacy": {
|
||||||
"source": "gravity-forms-csv",
|
"source": "gravity-forms-csv",
|
||||||
"importedAt": "2026-05-24T05:45:33.160Z",
|
"importedAt": "2026-06-15T21:24:42.302Z",
|
||||||
"onboardingEntryIds": [
|
"onboardingEntryIds": [
|
||||||
"116",
|
"116",
|
||||||
"93"
|
"93"
|
||||||
@@ -5387,12 +5387,12 @@
|
|||||||
"lifecycle": {
|
"lifecycle": {
|
||||||
"status": "archived",
|
"status": "archived",
|
||||||
"reason": "Imported from legacy Gravity Forms data",
|
"reason": "Imported from legacy Gravity Forms data",
|
||||||
"changedAt": "2026-05-24T05:45:33.160Z",
|
"changedAt": "2026-06-15T21:24:42.302Z",
|
||||||
"changedBy": "legacy-import"
|
"changedBy": "legacy-import"
|
||||||
},
|
},
|
||||||
"legacy": {
|
"legacy": {
|
||||||
"source": "gravity-forms-csv",
|
"source": "gravity-forms-csv",
|
||||||
"importedAt": "2026-05-24T05:45:33.160Z",
|
"importedAt": "2026-06-15T21:24:42.302Z",
|
||||||
"onboardingEntryIds": [
|
"onboardingEntryIds": [
|
||||||
"59"
|
"59"
|
||||||
],
|
],
|
||||||
@@ -5612,12 +5612,12 @@
|
|||||||
"lifecycle": {
|
"lifecycle": {
|
||||||
"status": "archived",
|
"status": "archived",
|
||||||
"reason": "Imported from legacy Gravity Forms data",
|
"reason": "Imported from legacy Gravity Forms data",
|
||||||
"changedAt": "2026-05-24T05:45:33.160Z",
|
"changedAt": "2026-06-15T21:24:42.302Z",
|
||||||
"changedBy": "legacy-import"
|
"changedBy": "legacy-import"
|
||||||
},
|
},
|
||||||
"legacy": {
|
"legacy": {
|
||||||
"source": "gravity-forms-csv",
|
"source": "gravity-forms-csv",
|
||||||
"importedAt": "2026-05-24T05:45:33.160Z",
|
"importedAt": "2026-06-15T21:24:42.302Z",
|
||||||
"onboardingEntryIds": [
|
"onboardingEntryIds": [
|
||||||
"30"
|
"30"
|
||||||
],
|
],
|
||||||
@@ -5837,12 +5837,12 @@
|
|||||||
"lifecycle": {
|
"lifecycle": {
|
||||||
"status": "archived",
|
"status": "archived",
|
||||||
"reason": "Imported from legacy Gravity Forms data",
|
"reason": "Imported from legacy Gravity Forms data",
|
||||||
"changedAt": "2026-05-24T05:45:33.160Z",
|
"changedAt": "2026-06-15T21:24:42.302Z",
|
||||||
"changedBy": "legacy-import"
|
"changedBy": "legacy-import"
|
||||||
},
|
},
|
||||||
"legacy": {
|
"legacy": {
|
||||||
"source": "gravity-forms-csv",
|
"source": "gravity-forms-csv",
|
||||||
"importedAt": "2026-05-24T05:45:33.160Z",
|
"importedAt": "2026-06-15T21:24:42.302Z",
|
||||||
"onboardingEntryIds": [
|
"onboardingEntryIds": [
|
||||||
"53"
|
"53"
|
||||||
],
|
],
|
||||||
@@ -6062,12 +6062,12 @@
|
|||||||
"lifecycle": {
|
"lifecycle": {
|
||||||
"status": "archived",
|
"status": "archived",
|
||||||
"reason": "Imported from legacy Gravity Forms data",
|
"reason": "Imported from legacy Gravity Forms data",
|
||||||
"changedAt": "2026-05-24T05:45:33.160Z",
|
"changedAt": "2026-06-15T21:24:42.302Z",
|
||||||
"changedBy": "legacy-import"
|
"changedBy": "legacy-import"
|
||||||
},
|
},
|
||||||
"legacy": {
|
"legacy": {
|
||||||
"source": "gravity-forms-csv",
|
"source": "gravity-forms-csv",
|
||||||
"importedAt": "2026-05-24T05:45:33.160Z",
|
"importedAt": "2026-06-15T21:24:42.302Z",
|
||||||
"onboardingEntryIds": [
|
"onboardingEntryIds": [
|
||||||
"107"
|
"107"
|
||||||
],
|
],
|
||||||
@@ -6287,12 +6287,12 @@
|
|||||||
"lifecycle": {
|
"lifecycle": {
|
||||||
"status": "archived",
|
"status": "archived",
|
||||||
"reason": "Imported from legacy Gravity Forms data",
|
"reason": "Imported from legacy Gravity Forms data",
|
||||||
"changedAt": "2026-05-24T05:45:33.160Z",
|
"changedAt": "2026-06-15T21:24:42.302Z",
|
||||||
"changedBy": "legacy-import"
|
"changedBy": "legacy-import"
|
||||||
},
|
},
|
||||||
"legacy": {
|
"legacy": {
|
||||||
"source": "gravity-forms-csv",
|
"source": "gravity-forms-csv",
|
||||||
"importedAt": "2026-05-24T05:45:33.160Z",
|
"importedAt": "2026-06-15T21:24:42.302Z",
|
||||||
"onboardingEntryIds": [
|
"onboardingEntryIds": [
|
||||||
"24"
|
"24"
|
||||||
],
|
],
|
||||||
@@ -6512,12 +6512,12 @@
|
|||||||
"lifecycle": {
|
"lifecycle": {
|
||||||
"status": "archived",
|
"status": "archived",
|
||||||
"reason": "Imported from legacy Gravity Forms data",
|
"reason": "Imported from legacy Gravity Forms data",
|
||||||
"changedAt": "2026-05-24T05:45:33.160Z",
|
"changedAt": "2026-06-15T21:24:42.302Z",
|
||||||
"changedBy": "legacy-import"
|
"changedBy": "legacy-import"
|
||||||
},
|
},
|
||||||
"legacy": {
|
"legacy": {
|
||||||
"source": "gravity-forms-csv",
|
"source": "gravity-forms-csv",
|
||||||
"importedAt": "2026-05-24T05:45:33.160Z",
|
"importedAt": "2026-06-15T21:24:42.302Z",
|
||||||
"onboardingEntryIds": [
|
"onboardingEntryIds": [
|
||||||
"43"
|
"43"
|
||||||
],
|
],
|
||||||
@@ -6737,12 +6737,12 @@
|
|||||||
"lifecycle": {
|
"lifecycle": {
|
||||||
"status": "archived",
|
"status": "archived",
|
||||||
"reason": "Imported from legacy Gravity Forms data",
|
"reason": "Imported from legacy Gravity Forms data",
|
||||||
"changedAt": "2026-05-24T05:45:33.160Z",
|
"changedAt": "2026-06-15T21:24:42.302Z",
|
||||||
"changedBy": "legacy-import"
|
"changedBy": "legacy-import"
|
||||||
},
|
},
|
||||||
"legacy": {
|
"legacy": {
|
||||||
"source": "gravity-forms-csv",
|
"source": "gravity-forms-csv",
|
||||||
"importedAt": "2026-05-24T05:45:33.160Z",
|
"importedAt": "2026-06-15T21:24:42.302Z",
|
||||||
"onboardingEntryIds": [
|
"onboardingEntryIds": [
|
||||||
"97"
|
"97"
|
||||||
],
|
],
|
||||||
@@ -6962,12 +6962,12 @@
|
|||||||
"lifecycle": {
|
"lifecycle": {
|
||||||
"status": "archived",
|
"status": "archived",
|
||||||
"reason": "Imported from legacy Gravity Forms data",
|
"reason": "Imported from legacy Gravity Forms data",
|
||||||
"changedAt": "2026-05-24T05:45:33.160Z",
|
"changedAt": "2026-06-15T21:24:42.302Z",
|
||||||
"changedBy": "legacy-import"
|
"changedBy": "legacy-import"
|
||||||
},
|
},
|
||||||
"legacy": {
|
"legacy": {
|
||||||
"source": "gravity-forms-csv",
|
"source": "gravity-forms-csv",
|
||||||
"importedAt": "2026-05-24T05:45:33.160Z",
|
"importedAt": "2026-06-15T21:24:42.302Z",
|
||||||
"onboardingEntryIds": [
|
"onboardingEntryIds": [
|
||||||
"90"
|
"90"
|
||||||
],
|
],
|
||||||
@@ -7188,12 +7188,12 @@
|
|||||||
"lifecycle": {
|
"lifecycle": {
|
||||||
"status": "archived",
|
"status": "archived",
|
||||||
"reason": "Imported from legacy Gravity Forms data",
|
"reason": "Imported from legacy Gravity Forms data",
|
||||||
"changedAt": "2026-05-24T05:45:33.160Z",
|
"changedAt": "2026-06-15T21:24:42.302Z",
|
||||||
"changedBy": "legacy-import"
|
"changedBy": "legacy-import"
|
||||||
},
|
},
|
||||||
"legacy": {
|
"legacy": {
|
||||||
"source": "gravity-forms-csv",
|
"source": "gravity-forms-csv",
|
||||||
"importedAt": "2026-05-24T05:45:33.160Z",
|
"importedAt": "2026-06-15T21:24:42.302Z",
|
||||||
"onboardingEntryIds": [
|
"onboardingEntryIds": [
|
||||||
"105"
|
"105"
|
||||||
],
|
],
|
||||||
@@ -7413,12 +7413,12 @@
|
|||||||
"lifecycle": {
|
"lifecycle": {
|
||||||
"status": "archived",
|
"status": "archived",
|
||||||
"reason": "Imported from legacy Gravity Forms data",
|
"reason": "Imported from legacy Gravity Forms data",
|
||||||
"changedAt": "2026-05-24T05:45:33.160Z",
|
"changedAt": "2026-06-15T21:24:42.302Z",
|
||||||
"changedBy": "legacy-import"
|
"changedBy": "legacy-import"
|
||||||
},
|
},
|
||||||
"legacy": {
|
"legacy": {
|
||||||
"source": "gravity-forms-csv",
|
"source": "gravity-forms-csv",
|
||||||
"importedAt": "2026-05-24T05:45:33.160Z",
|
"importedAt": "2026-06-15T21:24:42.302Z",
|
||||||
"onboardingEntryIds": [
|
"onboardingEntryIds": [
|
||||||
"101"
|
"101"
|
||||||
],
|
],
|
||||||
@@ -7638,12 +7638,12 @@
|
|||||||
"lifecycle": {
|
"lifecycle": {
|
||||||
"status": "archived",
|
"status": "archived",
|
||||||
"reason": "Imported from legacy Gravity Forms data",
|
"reason": "Imported from legacy Gravity Forms data",
|
||||||
"changedAt": "2026-05-24T05:45:33.160Z",
|
"changedAt": "2026-06-15T21:24:42.302Z",
|
||||||
"changedBy": "legacy-import"
|
"changedBy": "legacy-import"
|
||||||
},
|
},
|
||||||
"legacy": {
|
"legacy": {
|
||||||
"source": "gravity-forms-csv",
|
"source": "gravity-forms-csv",
|
||||||
"importedAt": "2026-05-24T05:45:33.160Z",
|
"importedAt": "2026-06-15T21:24:42.302Z",
|
||||||
"onboardingEntryIds": [
|
"onboardingEntryIds": [
|
||||||
"78"
|
"78"
|
||||||
],
|
],
|
||||||
@@ -7664,12 +7664,12 @@
|
|||||||
"lifecycle": {
|
"lifecycle": {
|
||||||
"status": "archived",
|
"status": "archived",
|
||||||
"reason": "Imported from legacy Gravity Forms data",
|
"reason": "Imported from legacy Gravity Forms data",
|
||||||
"changedAt": "2026-05-24T05:45:33.160Z",
|
"changedAt": "2026-06-15T21:24:42.302Z",
|
||||||
"changedBy": "legacy-import"
|
"changedBy": "legacy-import"
|
||||||
},
|
},
|
||||||
"legacy": {
|
"legacy": {
|
||||||
"source": "gravity-forms-csv",
|
"source": "gravity-forms-csv",
|
||||||
"importedAt": "2026-05-24T05:45:33.160Z",
|
"importedAt": "2026-06-15T21:24:42.302Z",
|
||||||
"onboardingEntryIds": [],
|
"onboardingEntryIds": [],
|
||||||
"contractEntryIds": [
|
"contractEntryIds": [
|
||||||
"29"
|
"29"
|
||||||
@@ -7887,12 +7887,12 @@
|
|||||||
"lifecycle": {
|
"lifecycle": {
|
||||||
"status": "archived",
|
"status": "archived",
|
||||||
"reason": "Imported from legacy Gravity Forms data",
|
"reason": "Imported from legacy Gravity Forms data",
|
||||||
"changedAt": "2026-05-24T05:45:33.160Z",
|
"changedAt": "2026-06-15T21:24:42.302Z",
|
||||||
"changedBy": "legacy-import"
|
"changedBy": "legacy-import"
|
||||||
},
|
},
|
||||||
"legacy": {
|
"legacy": {
|
||||||
"source": "gravity-forms-csv",
|
"source": "gravity-forms-csv",
|
||||||
"importedAt": "2026-05-24T05:45:33.160Z",
|
"importedAt": "2026-06-15T21:24:42.302Z",
|
||||||
"onboardingEntryIds": [
|
"onboardingEntryIds": [
|
||||||
"80"
|
"80"
|
||||||
],
|
],
|
||||||
|
|||||||
@@ -1437,3 +1437,657 @@ resend.exceptions.ResendError: API key is invalid
|
|||||||
19/05/2026 23:32:40 New Zealand Standard Time WARNING mail-api: Startup smoke: postgres disabled — activity/submissions will NOT be recorded
|
19/05/2026 23:32:40 New Zealand Standard Time WARNING mail-api: Startup smoke: postgres disabled — activity/submissions will NOT be recorded
|
||||||
19/05/2026 23:32:40 New Zealand Standard Time INFO mail-api: Startup test email skipped: OWNER_BCC is not set to a real address
|
19/05/2026 23:32:40 New Zealand Standard Time INFO mail-api: Startup test email skipped: OWNER_BCC is not set to a real address
|
||||||
19/05/2026 23:37:58 New Zealand Standard Time INFO mail-api: [e8c74fa0] GET /auth/verify → 401 (2ms)
|
19/05/2026 23:37:58 New Zealand Standard Time INFO mail-api: [e8c74fa0] GET /auth/verify → 401 (2ms)
|
||||||
|
15/06/2026 20:01:52 New Zealand Standard Time INFO mail-api: Logging initialised → console=INFO, file=logs\mail-api.log (DEBUG, rotating)
|
||||||
|
15/06/2026 20:01:52 New Zealand Standard Time INFO mail-api: Mail API config: version='unknown' timezone='system-default' from='GoodWalk <info@goodwalk.co.nz>' reply_to='info@goodwalk.co.nz' owner='info@goodwalk.co.nz' cp_admins=['info@goodwalk.co.nz'] owner_bcc='' client_bcc='' general_enquiries=False max_attempts=3 form_min=1s form_max=7200s rate_window=900s per_ip=50 per_email=50 min_interval=1s send_timeout=20s
|
||||||
|
15/06/2026 20:01:52 New Zealand Standard Time INFO mail-api: Auth: loaded 3 allowed email(s)
|
||||||
|
15/06/2026 20:01:53 New Zealand Standard Time ERROR mail-api: Startup smoke: WeasyPrint UNAVAILABLE — PDF attachments will be skipped (No module named 'weasyprint')
|
||||||
|
15/06/2026 20:01:53 New Zealand Standard Time WARNING mail-api: Startup smoke: postgres disabled — activity/submissions will NOT be recorded
|
||||||
|
15/06/2026 20:01:53 New Zealand Standard Time INFO mail-api: Startup test email skipped: OWNER_BCC is not set to a real address
|
||||||
|
15/06/2026 20:05:19 New Zealand Standard Time INFO mail-api: [ae1b0f7e] POST /auth/request-code → 400 (0ms)
|
||||||
|
15/06/2026 20:06:13 New Zealand Standard Time INFO mail-api: [faf603f0] POST /auth/request-code → 400 (0ms)
|
||||||
|
15/06/2026 20:06:14 New Zealand Standard Time INFO mail-api: [dd8ad9b9] POST /auth/request-code → 400 (0ms)
|
||||||
|
15/06/2026 20:06:14 New Zealand Standard Time INFO mail-api: [3fb682e8] POST /auth/request-code → 400 (0ms)
|
||||||
|
15/06/2026 20:06:14 New Zealand Standard Time INFO mail-api: [734a08d2] POST /auth/request-code → 400 (0ms)
|
||||||
|
15/06/2026 20:06:14 New Zealand Standard Time INFO mail-api: [658d0d93] POST /auth/request-code → 400 (0ms)
|
||||||
|
15/06/2026 20:06:15 New Zealand Standard Time INFO mail-api: [a66b3c9f] POST /auth/request-code → 400 (0ms)
|
||||||
|
15/06/2026 20:06:15 New Zealand Standard Time INFO mail-api: [28307070] POST /auth/request-code → 400 (0ms)
|
||||||
|
15/06/2026 20:06:29 New Zealand Standard Time INFO mail-api: Logging initialised → console=INFO, file=logs\mail-api.log (DEBUG, rotating)
|
||||||
|
15/06/2026 20:06:29 New Zealand Standard Time INFO mail-api: Mail API config: version='unknown' timezone='system-default' from='GoodWalk <info@goodwalk.co.nz>' reply_to='info@goodwalk.co.nz' owner='info@goodwalk.co.nz' cp_admins=['info@goodwalk.co.nz'] owner_bcc='' client_bcc='' general_enquiries=False max_attempts=3 form_min=1s form_max=7200s rate_window=900s per_ip=50 per_email=50 min_interval=1s send_timeout=20s
|
||||||
|
15/06/2026 20:06:29 New Zealand Standard Time INFO mail-api: Auth: loaded 3 allowed email(s)
|
||||||
|
15/06/2026 20:06:29 New Zealand Standard Time ERROR mail-api: Startup smoke: WeasyPrint UNAVAILABLE — PDF attachments will be skipped (No module named 'weasyprint')
|
||||||
|
15/06/2026 20:06:29 New Zealand Standard Time WARNING mail-api: Startup smoke: postgres disabled — activity/submissions will NOT be recorded
|
||||||
|
15/06/2026 20:06:29 New Zealand Standard Time INFO mail-api: Startup test email skipped: OWNER_BCC is not set to a real address
|
||||||
|
15/06/2026 20:06:36 New Zealand Standard Time INFO mail-api: Logging initialised → console=INFO, file=logs\mail-api.log (DEBUG, rotating)
|
||||||
|
15/06/2026 20:06:37 New Zealand Standard Time CRITICAL mail-api: Startup aborted: missing env vars: ['RESEND_API_KEY', 'OWNER_EMAIL']
|
||||||
|
15/06/2026 20:06:55 New Zealand Standard Time INFO mail-api: [78c69f47] POST /auth/request-code → 400 (0ms)
|
||||||
|
15/06/2026 20:07:00 New Zealand Standard Time INFO mail-api: Logging initialised → console=INFO, file=logs\mail-api.log (DEBUG, rotating)
|
||||||
|
15/06/2026 20:07:00 New Zealand Standard Time INFO mail-api: Mail API config: version='unknown' timezone='system-default' from='GoodWalk <info@goodwalk.co.nz>' reply_to='info@goodwalk.co.nz' owner='info@goodwalk.co.nz' cp_admins=['info@goodwalk.co.nz'] owner_bcc='' client_bcc='' general_enquiries=False max_attempts=3 form_min=1s form_max=7200s rate_window=900s per_ip=50 per_email=50 min_interval=1s send_timeout=20s
|
||||||
|
15/06/2026 20:07:00 New Zealand Standard Time INFO mail-api: Auth: loaded 3 allowed email(s)
|
||||||
|
15/06/2026 20:07:00 New Zealand Standard Time ERROR mail-api: Startup smoke: WeasyPrint UNAVAILABLE — PDF attachments will be skipped (No module named 'weasyprint')
|
||||||
|
15/06/2026 20:07:00 New Zealand Standard Time WARNING mail-api: Startup smoke: postgres disabled — activity/submissions will NOT be recorded
|
||||||
|
15/06/2026 20:07:00 New Zealand Standard Time INFO mail-api: Startup test email skipped: OWNER_BCC is not set to a real address
|
||||||
|
15/06/2026 20:07:02 New Zealand Standard Time INFO mail-api: [ccfc7849] POST /auth/request-code → 400 (0ms)
|
||||||
|
15/06/2026 20:08:31 New Zealand Standard Time INFO mail-api: Logging initialised → console=INFO, file=logs\mail-api.log (DEBUG, rotating)
|
||||||
|
15/06/2026 20:08:31 New Zealand Standard Time INFO mail-api: Mail API config: version='unknown' timezone='system-default' from='GoodWalk <info@goodwalk.co.nz>' reply_to='info@goodwalk.co.nz' owner='info@goodwalk.co.nz' cp_admins=['info@goodwalk.co.nz'] owner_bcc='' client_bcc='' general_enquiries=False max_attempts=3 form_min=1s form_max=7200s rate_window=900s per_ip=50 per_email=50 min_interval=1s send_timeout=20s
|
||||||
|
15/06/2026 20:08:31 New Zealand Standard Time INFO mail-api: Auth: loaded 3 allowed email(s)
|
||||||
|
15/06/2026 20:08:31 New Zealand Standard Time ERROR mail-api: Startup smoke: WeasyPrint UNAVAILABLE — PDF attachments will be skipped (No module named 'weasyprint')
|
||||||
|
15/06/2026 20:08:31 New Zealand Standard Time WARNING mail-api: Startup smoke: postgres disabled — activity/submissions will NOT be recorded
|
||||||
|
15/06/2026 20:08:31 New Zealand Standard Time INFO mail-api: Startup test email skipped: OWNER_BCC is not set to a real address
|
||||||
|
15/06/2026 20:08:36 New Zealand Standard Time INFO mail-api: Logging initialised → console=INFO, file=logs\mail-api.log (DEBUG, rotating)
|
||||||
|
15/06/2026 20:08:36 New Zealand Standard Time INFO mail-api: Mail API config: version='unknown' timezone='system-default' from='GoodWalk <info@goodwalk.co.nz>' reply_to='info@goodwalk.co.nz' owner='info@goodwalk.co.nz' cp_admins=['info@goodwalk.co.nz'] owner_bcc='' client_bcc='' general_enquiries=False max_attempts=3 form_min=1s form_max=7200s rate_window=900s per_ip=50 per_email=50 min_interval=1s send_timeout=20s
|
||||||
|
15/06/2026 20:08:36 New Zealand Standard Time INFO mail-api: Auth: loaded 3 allowed email(s)
|
||||||
|
15/06/2026 20:08:36 New Zealand Standard Time ERROR mail-api: Startup smoke: WeasyPrint UNAVAILABLE — PDF attachments will be skipped (No module named 'weasyprint')
|
||||||
|
15/06/2026 20:08:36 New Zealand Standard Time WARNING mail-api: Startup smoke: postgres disabled — activity/submissions will NOT be recorded
|
||||||
|
15/06/2026 20:08:36 New Zealand Standard Time INFO mail-api: Startup test email skipped: OWNER_BCC is not set to a real address
|
||||||
|
15/06/2026 20:08:40 New Zealand Standard Time INFO mail-api: [b2f2b0fc] POST /auth/request-code → 400 (0ms)
|
||||||
|
15/06/2026 20:09:25 New Zealand Standard Time ERROR mail-api: [0f10f6ec] POST /auth/request-code crashed after 3ms
|
||||||
|
Traceback (most recent call last):
|
||||||
|
File "C:\Users\mattc\gw-svelte\gw-svelte\mail-api\main.py", line 579, in _request_logging_middleware
|
||||||
|
response = await call_next(request)
|
||||||
|
^^^^^^^^^^^^^^^^^^^^^^^^
|
||||||
|
File "C:\Users\mattc\gw-svelte\gw-svelte\mail-api\.venv\Lib\site-packages\starlette\middleware\base.py", line 168, in call_next
|
||||||
|
raise app_exc from app_exc.__cause__ or app_exc.__context__
|
||||||
|
File "C:\Users\mattc\gw-svelte\gw-svelte\mail-api\.venv\Lib\site-packages\starlette\middleware\base.py", line 144, in coro
|
||||||
|
await self.app(scope, receive_or_disconnect, send_no_error)
|
||||||
|
File "C:\Users\mattc\gw-svelte\gw-svelte\mail-api\.venv\Lib\site-packages\starlette\middleware\cors.py", line 88, in __call__
|
||||||
|
await self.app(scope, receive, send)
|
||||||
|
File "C:\Users\mattc\gw-svelte\gw-svelte\mail-api\.venv\Lib\site-packages\starlette\middleware\trustedhost.py", line 51, in __call__
|
||||||
|
await self.app(scope, receive, send)
|
||||||
|
File "C:\Users\mattc\gw-svelte\gw-svelte\mail-api\main.py", line 546, in __call__
|
||||||
|
await self.app(scope, _wrapped_receive, send)
|
||||||
|
File "C:\Users\mattc\gw-svelte\gw-svelte\mail-api\.venv\Lib\site-packages\starlette\middleware\exceptions.py", line 63, in __call__
|
||||||
|
await wrap_app_handling_exceptions(self.app, conn)(scope, receive, send)
|
||||||
|
File "C:\Users\mattc\gw-svelte\gw-svelte\mail-api\.venv\Lib\site-packages\starlette\_exception_handler.py", line 53, in wrapped_app
|
||||||
|
raise exc
|
||||||
|
File "C:\Users\mattc\gw-svelte\gw-svelte\mail-api\.venv\Lib\site-packages\starlette\_exception_handler.py", line 42, in wrapped_app
|
||||||
|
await app(scope, receive, sender)
|
||||||
|
File "C:\Users\mattc\gw-svelte\gw-svelte\mail-api\.venv\Lib\site-packages\fastapi\middleware\asyncexitstack.py", line 18, in __call__
|
||||||
|
await self.app(scope, receive, send)
|
||||||
|
File "C:\Users\mattc\gw-svelte\gw-svelte\mail-api\.venv\Lib\site-packages\starlette\routing.py", line 660, in __call__
|
||||||
|
await self.middleware_stack(scope, receive, send)
|
||||||
|
File "C:\Users\mattc\gw-svelte\gw-svelte\mail-api\.venv\Lib\site-packages\starlette\routing.py", line 680, in app
|
||||||
|
await route.handle(scope, receive, send)
|
||||||
|
File "C:\Users\mattc\gw-svelte\gw-svelte\mail-api\.venv\Lib\site-packages\starlette\routing.py", line 276, in handle
|
||||||
|
await self.app(scope, receive, send)
|
||||||
|
File "C:\Users\mattc\gw-svelte\gw-svelte\mail-api\.venv\Lib\site-packages\fastapi\routing.py", line 134, in app
|
||||||
|
await wrap_app_handling_exceptions(app, request)(scope, receive, send)
|
||||||
|
File "C:\Users\mattc\gw-svelte\gw-svelte\mail-api\.venv\Lib\site-packages\starlette\_exception_handler.py", line 53, in wrapped_app
|
||||||
|
raise exc
|
||||||
|
File "C:\Users\mattc\gw-svelte\gw-svelte\mail-api\.venv\Lib\site-packages\starlette\_exception_handler.py", line 42, in wrapped_app
|
||||||
|
await app(scope, receive, sender)
|
||||||
|
File "C:\Users\mattc\gw-svelte\gw-svelte\mail-api\.venv\Lib\site-packages\fastapi\routing.py", line 120, in app
|
||||||
|
response = await f(request)
|
||||||
|
^^^^^^^^^^^^^^^^
|
||||||
|
File "C:\Users\mattc\gw-svelte\gw-svelte\mail-api\.venv\Lib\site-packages\fastapi\routing.py", line 674, in app
|
||||||
|
raw_response = await run_endpoint_function(
|
||||||
|
^^^^^^^^^^^^^^^^^^^^^^^^^^^^
|
||||||
|
...<3 lines>...
|
||||||
|
)
|
||||||
|
^
|
||||||
|
File "C:\Users\mattc\gw-svelte\gw-svelte\mail-api\.venv\Lib\site-packages\fastapi\routing.py", line 328, in run_endpoint_function
|
||||||
|
return await dependant.call(**values)
|
||||||
|
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
|
||||||
|
File "C:\Users\mattc\gw-svelte\gw-svelte\mail-api\main.py", line 2474, in auth_request_code
|
||||||
|
body = await request.json()
|
||||||
|
^^^^^^^^^^^^^^^^^^^^
|
||||||
|
File "C:\Users\mattc\gw-svelte\gw-svelte\mail-api\.venv\Lib\site-packages\starlette\requests.py", line 264, in json
|
||||||
|
self._json = json.loads(body)
|
||||||
|
~~~~~~~~~~^^^^^^
|
||||||
|
File "C:\Users\mattc\AppData\Local\Programs\Python\Python314\Lib\json\__init__.py", line 352, in loads
|
||||||
|
return _default_decoder.decode(s)
|
||||||
|
~~~~~~~~~~~~~~~~~~~~~~~^^^
|
||||||
|
File "C:\Users\mattc\AppData\Local\Programs\Python\Python314\Lib\json\decoder.py", line 345, in decode
|
||||||
|
obj, end = self.raw_decode(s, idx=_w(s, 0).end())
|
||||||
|
~~~~~~~~~~~~~~~^^^^^^^^^^^^^^^^^^^^^^^
|
||||||
|
File "C:\Users\mattc\AppData\Local\Programs\Python\Python314\Lib\json\decoder.py", line 361, in raw_decode
|
||||||
|
obj, end = self.scan_once(s, idx)
|
||||||
|
~~~~~~~~~~~~~~^^^^^^^^
|
||||||
|
json.decoder.JSONDecodeError: Expecting property name enclosed in double quotes: line 1 column 2 (char 1)
|
||||||
|
15/06/2026 20:09:38 New Zealand Standard Time ERROR mail-api: [251e2d22] POST /auth/request-code crashed after 1ms
|
||||||
|
Traceback (most recent call last):
|
||||||
|
File "C:\Users\mattc\gw-svelte\gw-svelte\mail-api\main.py", line 579, in _request_logging_middleware
|
||||||
|
response = await call_next(request)
|
||||||
|
^^^^^^^^^^^^^^^^^^^^^^^^
|
||||||
|
File "C:\Users\mattc\gw-svelte\gw-svelte\mail-api\.venv\Lib\site-packages\starlette\middleware\base.py", line 168, in call_next
|
||||||
|
raise app_exc from app_exc.__cause__ or app_exc.__context__
|
||||||
|
File "C:\Users\mattc\gw-svelte\gw-svelte\mail-api\.venv\Lib\site-packages\starlette\middleware\base.py", line 144, in coro
|
||||||
|
await self.app(scope, receive_or_disconnect, send_no_error)
|
||||||
|
File "C:\Users\mattc\gw-svelte\gw-svelte\mail-api\.venv\Lib\site-packages\starlette\middleware\cors.py", line 88, in __call__
|
||||||
|
await self.app(scope, receive, send)
|
||||||
|
File "C:\Users\mattc\gw-svelte\gw-svelte\mail-api\.venv\Lib\site-packages\starlette\middleware\trustedhost.py", line 51, in __call__
|
||||||
|
await self.app(scope, receive, send)
|
||||||
|
File "C:\Users\mattc\gw-svelte\gw-svelte\mail-api\main.py", line 546, in __call__
|
||||||
|
await self.app(scope, _wrapped_receive, send)
|
||||||
|
File "C:\Users\mattc\gw-svelte\gw-svelte\mail-api\.venv\Lib\site-packages\starlette\middleware\exceptions.py", line 63, in __call__
|
||||||
|
await wrap_app_handling_exceptions(self.app, conn)(scope, receive, send)
|
||||||
|
File "C:\Users\mattc\gw-svelte\gw-svelte\mail-api\.venv\Lib\site-packages\starlette\_exception_handler.py", line 53, in wrapped_app
|
||||||
|
raise exc
|
||||||
|
File "C:\Users\mattc\gw-svelte\gw-svelte\mail-api\.venv\Lib\site-packages\starlette\_exception_handler.py", line 42, in wrapped_app
|
||||||
|
await app(scope, receive, sender)
|
||||||
|
File "C:\Users\mattc\gw-svelte\gw-svelte\mail-api\.venv\Lib\site-packages\fastapi\middleware\asyncexitstack.py", line 18, in __call__
|
||||||
|
await self.app(scope, receive, send)
|
||||||
|
File "C:\Users\mattc\gw-svelte\gw-svelte\mail-api\.venv\Lib\site-packages\starlette\routing.py", line 660, in __call__
|
||||||
|
await self.middleware_stack(scope, receive, send)
|
||||||
|
File "C:\Users\mattc\gw-svelte\gw-svelte\mail-api\.venv\Lib\site-packages\starlette\routing.py", line 680, in app
|
||||||
|
await route.handle(scope, receive, send)
|
||||||
|
File "C:\Users\mattc\gw-svelte\gw-svelte\mail-api\.venv\Lib\site-packages\starlette\routing.py", line 276, in handle
|
||||||
|
await self.app(scope, receive, send)
|
||||||
|
File "C:\Users\mattc\gw-svelte\gw-svelte\mail-api\.venv\Lib\site-packages\fastapi\routing.py", line 134, in app
|
||||||
|
await wrap_app_handling_exceptions(app, request)(scope, receive, send)
|
||||||
|
File "C:\Users\mattc\gw-svelte\gw-svelte\mail-api\.venv\Lib\site-packages\starlette\_exception_handler.py", line 53, in wrapped_app
|
||||||
|
raise exc
|
||||||
|
File "C:\Users\mattc\gw-svelte\gw-svelte\mail-api\.venv\Lib\site-packages\starlette\_exception_handler.py", line 42, in wrapped_app
|
||||||
|
await app(scope, receive, sender)
|
||||||
|
File "C:\Users\mattc\gw-svelte\gw-svelte\mail-api\.venv\Lib\site-packages\fastapi\routing.py", line 120, in app
|
||||||
|
response = await f(request)
|
||||||
|
^^^^^^^^^^^^^^^^
|
||||||
|
File "C:\Users\mattc\gw-svelte\gw-svelte\mail-api\.venv\Lib\site-packages\fastapi\routing.py", line 674, in app
|
||||||
|
raw_response = await run_endpoint_function(
|
||||||
|
^^^^^^^^^^^^^^^^^^^^^^^^^^^^
|
||||||
|
...<3 lines>...
|
||||||
|
)
|
||||||
|
^
|
||||||
|
File "C:\Users\mattc\gw-svelte\gw-svelte\mail-api\.venv\Lib\site-packages\fastapi\routing.py", line 328, in run_endpoint_function
|
||||||
|
return await dependant.call(**values)
|
||||||
|
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
|
||||||
|
File "C:\Users\mattc\gw-svelte\gw-svelte\mail-api\main.py", line 2474, in auth_request_code
|
||||||
|
body = await request.json()
|
||||||
|
^^^^^^^^^^^^^^^^^^^^
|
||||||
|
File "C:\Users\mattc\gw-svelte\gw-svelte\mail-api\.venv\Lib\site-packages\starlette\requests.py", line 264, in json
|
||||||
|
self._json = json.loads(body)
|
||||||
|
~~~~~~~~~~^^^^^^
|
||||||
|
File "C:\Users\mattc\AppData\Local\Programs\Python\Python314\Lib\json\__init__.py", line 352, in loads
|
||||||
|
return _default_decoder.decode(s)
|
||||||
|
~~~~~~~~~~~~~~~~~~~~~~~^^^
|
||||||
|
File "C:\Users\mattc\AppData\Local\Programs\Python\Python314\Lib\json\decoder.py", line 345, in decode
|
||||||
|
obj, end = self.raw_decode(s, idx=_w(s, 0).end())
|
||||||
|
~~~~~~~~~~~~~~~^^^^^^^^^^^^^^^^^^^^^^^
|
||||||
|
File "C:\Users\mattc\AppData\Local\Programs\Python\Python314\Lib\json\decoder.py", line 361, in raw_decode
|
||||||
|
obj, end = self.scan_once(s, idx)
|
||||||
|
~~~~~~~~~~~~~~^^^^^^^^
|
||||||
|
json.decoder.JSONDecodeError: Expecting property name enclosed in double quotes: line 1 column 2 (char 1)
|
||||||
|
15/06/2026 20:09:53 New Zealand Standard Time INFO mail-api: Logging initialised → console=INFO, file=logs\mail-api.log (DEBUG, rotating)
|
||||||
|
15/06/2026 20:09:53 New Zealand Standard Time INFO mail-api: Mail API config: version='unknown' timezone='system-default' from='GoodWalk <info@goodwalk.co.nz>' reply_to='info@goodwalk.co.nz' owner='info@goodwalk.co.nz' cp_admins=['info@goodwalk.co.nz'] owner_bcc='' client_bcc='' general_enquiries=False max_attempts=3 form_min=1s form_max=7200s rate_window=900s per_ip=50 per_email=50 min_interval=1s send_timeout=20s
|
||||||
|
15/06/2026 20:09:53 New Zealand Standard Time INFO mail-api: Auth: loaded 3 allowed email(s)
|
||||||
|
15/06/2026 20:09:53 New Zealand Standard Time ERROR mail-api: Startup smoke: WeasyPrint UNAVAILABLE — PDF attachments will be skipped (No module named 'weasyprint')
|
||||||
|
15/06/2026 20:09:53 New Zealand Standard Time WARNING mail-api: Startup smoke: postgres disabled — activity/submissions will NOT be recorded
|
||||||
|
15/06/2026 20:09:53 New Zealand Standard Time INFO mail-api: Startup test email skipped: OWNER_BCC is not set to a real address
|
||||||
|
15/06/2026 20:10:15 New Zealand Standard Time INFO mail-api: Logging initialised → console=INFO, file=logs\mail-api.log (DEBUG, rotating)
|
||||||
|
15/06/2026 20:10:15 New Zealand Standard Time INFO mail-api: Mail API config: version='unknown' timezone='system-default' from='GoodWalk <info@goodwalk.co.nz>' reply_to='info@goodwalk.co.nz' owner='info@goodwalk.co.nz' cp_admins=['info@goodwalk.co.nz'] owner_bcc='' client_bcc='' general_enquiries=False max_attempts=3 form_min=1s form_max=7200s rate_window=900s per_ip=50 per_email=50 min_interval=1s send_timeout=20s
|
||||||
|
15/06/2026 20:10:15 New Zealand Standard Time INFO mail-api: Auth: loaded 3 allowed email(s)
|
||||||
|
15/06/2026 20:10:15 New Zealand Standard Time ERROR mail-api: Startup smoke: WeasyPrint UNAVAILABLE — PDF attachments will be skipped (No module named 'weasyprint')
|
||||||
|
15/06/2026 20:10:15 New Zealand Standard Time WARNING mail-api: Startup smoke: postgres disabled — activity/submissions will NOT be recorded
|
||||||
|
15/06/2026 20:10:15 New Zealand Standard Time INFO mail-api: Startup test email skipped: OWNER_BCC is not set to a real address
|
||||||
|
15/06/2026 20:10:20 New Zealand Standard Time INFO mail-api: [72b111d2] POST /auth/request-code → 400 (0ms)
|
||||||
|
15/06/2026 20:11:27 New Zealand Standard Time INFO mail-api: Logging initialised → console=INFO, file=logs\mail-api.log (DEBUG, rotating)
|
||||||
|
15/06/2026 20:11:27 New Zealand Standard Time INFO mail-api: Mail API config: version='unknown' timezone='system-default' from='GoodWalk <info@goodwalk.co.nz>' reply_to='info@goodwalk.co.nz' owner='info@goodwalk.co.nz' cp_admins=['info@goodwalk.co.nz'] owner_bcc='' client_bcc='' general_enquiries=False max_attempts=3 form_min=1s form_max=7200s rate_window=900s per_ip=50 per_email=50 min_interval=1s send_timeout=20s
|
||||||
|
15/06/2026 20:11:27 New Zealand Standard Time INFO mail-api: Auth: loaded 3 allowed email(s)
|
||||||
|
15/06/2026 20:11:27 New Zealand Standard Time ERROR mail-api: Startup smoke: WeasyPrint UNAVAILABLE — PDF attachments will be skipped (No module named 'weasyprint')
|
||||||
|
15/06/2026 20:11:27 New Zealand Standard Time WARNING mail-api: Startup smoke: postgres disabled — activity/submissions will NOT be recorded
|
||||||
|
15/06/2026 20:11:27 New Zealand Standard Time INFO mail-api: Startup test email skipped: OWNER_BCC is not set to a real address
|
||||||
|
15/06/2026 20:11:38 New Zealand Standard Time INFO mail-api: [6c351fe3] POST /auth/request-code → 400 (0ms)
|
||||||
|
15/06/2026 20:11:45 New Zealand Standard Time INFO mail-api: [912374c3] POST /auth/request-code → 400 (0ms)
|
||||||
|
15/06/2026 20:11:50 New Zealand Standard Time INFO mail-api: [ca16d00f] POST /auth/request-code → 400 (0ms)
|
||||||
|
15/06/2026 20:15:10 New Zealand Standard Time INFO mail-api: [90835023] auth: code issued for email=info@goodwalk.co.nz
|
||||||
|
15/06/2026 20:15:10 New Zealand Standard Time WARNING mail-api: [DEV] auth code for info@goodwalk.co.nz: 956187
|
||||||
|
15/06/2026 20:15:10 New Zealand Standard Time INFO mail-api: [90835023] POST /auth/request-code → 200 (11ms)
|
||||||
|
15/06/2026 20:15:18 New Zealand Standard Time INFO mail-api: [b5f581f9] auth: session created for email=info@goodwalk.co.nz
|
||||||
|
15/06/2026 20:15:18 New Zealand Standard Time INFO mail-api: [b5f581f9] POST /auth/verify-code → 200 (2ms)
|
||||||
|
15/06/2026 20:15:18 New Zealand Standard Time INFO mail-api: [9d58963f] GET /auth/verify → 200 (1ms)
|
||||||
|
15/06/2026 20:15:18 New Zealand Standard Time INFO mail-api: [6efc3cae] GET /owner/pending-onboarding → 200 (1ms)
|
||||||
|
15/06/2026 20:15:18 New Zealand Standard Time INFO mail-api: [f8fd2577] GET /owner/all-clients → 200 (2ms)
|
||||||
|
15/06/2026 20:15:18 New Zealand Standard Time INFO mail-api: [bd38d327] GET /owner/birthdays → 200 (11ms)
|
||||||
|
15/06/2026 20:15:20 New Zealand Standard Time INFO mail-api: [59319824] GET /auth/verify → 200 (0ms)
|
||||||
|
15/06/2026 20:15:20 New Zealand Standard Time INFO mail-api: [bb2fa1e3] GET /owner/pending-onboarding → 200 (1ms)
|
||||||
|
15/06/2026 20:15:20 New Zealand Standard Time INFO mail-api: [a7d68e07] GET /owner/all-clients → 200 (2ms)
|
||||||
|
15/06/2026 20:15:20 New Zealand Standard Time INFO mail-api: [3f420d48] GET /owner/birthdays → 200 (2ms)
|
||||||
|
15/06/2026 20:15:24 New Zealand Standard Time INFO mail-api: [9dee7759] GET /auth/verify → 200 (0ms)
|
||||||
|
15/06/2026 20:15:24 New Zealand Standard Time INFO mail-api: [bff1d27d] GET /owner/pending-onboarding → 200 (0ms)
|
||||||
|
15/06/2026 20:15:24 New Zealand Standard Time INFO mail-api: [573299f0] GET /owner/birthdays → 200 (1ms)
|
||||||
|
15/06/2026 20:15:24 New Zealand Standard Time INFO mail-api: [c1b2cbca] GET /owner/all-clients → 200 (1ms)
|
||||||
|
15/06/2026 20:15:31 New Zealand Standard Time INFO mail-api: [8ca00ff2] GET /owner/message-templates → 200 (1ms)
|
||||||
|
15/06/2026 20:15:32 New Zealand Standard Time INFO mail-api: [5d67ac5b] POST /owner/render-message → 200 (3ms)
|
||||||
|
15/06/2026 20:16:25 New Zealand Standard Time INFO mail-api: [a3d3d2d9] GET /owner/activity → 200 (1ms)
|
||||||
|
15/06/2026 20:17:24 New Zealand Standard Time INFO mail-api: [62384e2c] GET /owner/activity → 200 (0ms)
|
||||||
|
15/06/2026 20:17:46 New Zealand Standard Time INFO mail-api: [be53083f] GET /owner/activity → 200 (0ms)
|
||||||
|
15/06/2026 20:18:38 New Zealand Standard Time INFO mail-api: [5f777ef7] GET /owner/activity → 200 (0ms)
|
||||||
|
15/06/2026 20:19:19 New Zealand Standard Time INFO mail-api: [739f25e5] GET /owner/client-enquiry → 200 (8ms)
|
||||||
|
15/06/2026 20:19:28 New Zealand Standard Time INFO mail-api: [e70d718b] GET /owner/activity → 200 (1ms)
|
||||||
|
15/06/2026 20:20:46 New Zealand Standard Time INFO mail-api: [4b64a4ec] GET /owner/activity → 200 (0ms)
|
||||||
|
15/06/2026 20:21:06 New Zealand Standard Time INFO mail-api: [5206327c] GET /auth/verify → 200 (0ms)
|
||||||
|
15/06/2026 20:21:06 New Zealand Standard Time INFO mail-api: [0520fe2c] GET /owner/pending-onboarding → 200 (0ms)
|
||||||
|
15/06/2026 20:21:06 New Zealand Standard Time INFO mail-api: [659bc15b] GET /owner/all-clients → 200 (1ms)
|
||||||
|
15/06/2026 20:21:06 New Zealand Standard Time INFO mail-api: [b367235f] GET /owner/birthdays → 200 (1ms)
|
||||||
|
15/06/2026 20:27:58 New Zealand Standard Time INFO mail-api: Logging initialised → console=INFO, file=logs\mail-api.log (DEBUG, rotating)
|
||||||
|
15/06/2026 20:27:58 New Zealand Standard Time INFO mail-api: Mail API config: version='unknown' timezone='system-default' from='GoodWalk <info@goodwalk.co.nz>' reply_to='info@goodwalk.co.nz' owner='info@goodwalk.co.nz' cp_admins=['info@goodwalk.co.nz'] owner_bcc='' client_bcc='' general_enquiries=False max_attempts=3 form_min=1s form_max=7200s rate_window=900s per_ip=50 per_email=50 min_interval=1s send_timeout=20s
|
||||||
|
15/06/2026 20:27:58 New Zealand Standard Time INFO mail-api: Auth: loaded 3 allowed email(s)
|
||||||
|
15/06/2026 20:27:58 New Zealand Standard Time ERROR mail-api: Startup smoke: WeasyPrint UNAVAILABLE — PDF attachments will be skipped (No module named 'weasyprint')
|
||||||
|
15/06/2026 20:27:58 New Zealand Standard Time WARNING mail-api: Startup smoke: postgres disabled — activity/submissions will NOT be recorded
|
||||||
|
15/06/2026 20:27:58 New Zealand Standard Time INFO mail-api: Startup test email skipped: OWNER_BCC is not set to a real address
|
||||||
|
15/06/2026 20:28:06 New Zealand Standard Time INFO mail-api: [bc4610f2] GET /owner/message-templates → 401 (2ms)
|
||||||
|
15/06/2026 20:28:07 New Zealand Standard Time INFO mail-api: [0169b9e6] GET /owner/activity → 401 (1ms)
|
||||||
|
15/06/2026 20:30:00 New Zealand Standard Time INFO mail-api: [87be64e8] GET /owner/message-templates → 401 (0ms)
|
||||||
|
15/06/2026 20:30:01 New Zealand Standard Time INFO mail-api: [2822c7c6] GET /owner/activity → 401 (0ms)
|
||||||
|
15/06/2026 20:30:09 New Zealand Standard Time INFO mail-api: [f61a5c74] GET /auth/verify → 401 (1ms)
|
||||||
|
15/06/2026 20:31:51 New Zealand Standard Time INFO mail-api: [ee1b53c2] auth: code issued for email=info@goodwalk.co.nz
|
||||||
|
15/06/2026 20:31:51 New Zealand Standard Time WARNING mail-api: [DEV] auth code for info@goodwalk.co.nz: 546348
|
||||||
|
15/06/2026 20:31:51 New Zealand Standard Time INFO mail-api: [ee1b53c2] POST /auth/request-code → 200 (2ms)
|
||||||
|
15/06/2026 20:32:01 New Zealand Standard Time INFO mail-api: [ac46d159] auth: session created for email=info@goodwalk.co.nz
|
||||||
|
15/06/2026 20:32:01 New Zealand Standard Time INFO mail-api: [ac46d159] POST /auth/verify-code → 200 (1ms)
|
||||||
|
15/06/2026 20:32:01 New Zealand Standard Time INFO mail-api: [60c7fd10] GET /auth/verify → 200 (0ms)
|
||||||
|
15/06/2026 20:32:01 New Zealand Standard Time INFO mail-api: [abfa833f] GET /owner/all-clients → 200 (2ms)
|
||||||
|
15/06/2026 20:32:01 New Zealand Standard Time INFO mail-api: [71044bf5] GET /owner/pending-onboarding → 200 (3ms)
|
||||||
|
15/06/2026 20:32:01 New Zealand Standard Time INFO mail-api: [2ec29972] GET /owner/birthdays → 200 (6ms)
|
||||||
|
15/06/2026 20:32:03 New Zealand Standard Time INFO mail-api: [6c0a3799] GET /owner/client-onboarding-view → 200 (1ms)
|
||||||
|
15/06/2026 20:32:10 New Zealand Standard Time INFO mail-api: [64cdfdf6] GET /auth/verify → 200 (0ms)
|
||||||
|
15/06/2026 20:32:10 New Zealand Standard Time INFO mail-api: [b8478985] GET /owner/pending-onboarding → 200 (1ms)
|
||||||
|
15/06/2026 20:32:10 New Zealand Standard Time INFO mail-api: [d5ec31df] GET /owner/all-clients → 200 (1ms)
|
||||||
|
15/06/2026 20:32:10 New Zealand Standard Time INFO mail-api: [0ebeb971] GET /owner/birthdays → 200 (1ms)
|
||||||
|
15/06/2026 20:32:11 New Zealand Standard Time INFO mail-api: [3b2f4ee0] GET /owner/client-onboarding-view → 200 (0ms)
|
||||||
|
15/06/2026 20:32:28 New Zealand Standard Time INFO mail-api: [fdc6a9fa] GET /owner/client-onboarding-view → 200 (0ms)
|
||||||
|
15/06/2026 20:32:53 New Zealand Standard Time INFO mail-api: [07b910be] GET /auth/verify → 200 (0ms)
|
||||||
|
15/06/2026 20:32:53 New Zealand Standard Time INFO mail-api: [eb7646b6] GET /owner/pending-onboarding → 200 (1ms)
|
||||||
|
15/06/2026 20:32:53 New Zealand Standard Time INFO mail-api: [e8b1a226] GET /owner/all-clients → 200 (1ms)
|
||||||
|
15/06/2026 20:32:53 New Zealand Standard Time INFO mail-api: [882b2a08] GET /owner/birthdays → 200 (0ms)
|
||||||
|
15/06/2026 20:33:23 New Zealand Standard Time INFO mail-api: [783d78c3] GET /auth/verify → 200 (0ms)
|
||||||
|
15/06/2026 20:33:23 New Zealand Standard Time INFO mail-api: [e8b66a3c] GET /owner/pending-onboarding → 200 (1ms)
|
||||||
|
15/06/2026 20:33:23 New Zealand Standard Time INFO mail-api: [020c91d9] GET /owner/all-clients → 200 (1ms)
|
||||||
|
15/06/2026 20:33:23 New Zealand Standard Time INFO mail-api: [91679cb8] GET /owner/birthdays → 200 (1ms)
|
||||||
|
15/06/2026 20:33:28 New Zealand Standard Time INFO mail-api: [e764f088] GET /owner/client-enquiry → 200 (1ms)
|
||||||
|
15/06/2026 20:33:29 New Zealand Standard Time INFO mail-api: [5089b29e] GET /owner/client-onboarding-view → 200 (0ms)
|
||||||
|
15/06/2026 20:33:34 New Zealand Standard Time INFO mail-api: [2c30ae12] GET /owner/message-templates → 200 (1ms)
|
||||||
|
15/06/2026 20:33:34 New Zealand Standard Time INFO mail-api: [2f086a39] POST /owner/render-message → 200 (2ms)
|
||||||
|
15/06/2026 20:33:36 New Zealand Standard Time INFO mail-api: [c383d4c2] GET /owner/activity → 200 (0ms)
|
||||||
|
15/06/2026 20:33:46 New Zealand Standard Time INFO mail-api: [8ce6215d] GET /auth/verify → 200 (0ms)
|
||||||
|
15/06/2026 20:33:46 New Zealand Standard Time INFO mail-api: [77dd8afc] GET /owner/all-clients → 200 (1ms)
|
||||||
|
15/06/2026 20:33:46 New Zealand Standard Time INFO mail-api: [5be1fa50] GET /owner/pending-onboarding → 200 (1ms)
|
||||||
|
15/06/2026 20:33:46 New Zealand Standard Time INFO mail-api: [69163ab8] GET /owner/birthdays → 200 (1ms)
|
||||||
|
15/06/2026 20:34:03 New Zealand Standard Time INFO mail-api: [ef3c519f] GET /owner/client-enquiry → 200 (0ms)
|
||||||
|
15/06/2026 20:34:05 New Zealand Standard Time INFO mail-api: [d547ff1d] GET /owner/client-onboarding-view → 200 (0ms)
|
||||||
|
15/06/2026 20:36:48 New Zealand Standard Time INFO mail-api: [0d37e5dc] GET /owner/client-enquiry → 200 (0ms)
|
||||||
|
15/06/2026 20:36:50 New Zealand Standard Time INFO mail-api: [24d3194d] GET /owner/client-onboarding-view → 200 (0ms)
|
||||||
|
15/06/2026 20:37:56 New Zealand Standard Time INFO mail-api: [1145dc87] GET /owner/message-templates → 200 (1ms)
|
||||||
|
15/06/2026 20:37:56 New Zealand Standard Time INFO mail-api: [681ad27d] POST /owner/render-message → 200 (1ms)
|
||||||
|
15/06/2026 20:37:57 New Zealand Standard Time INFO mail-api: [99d12925] GET /owner/activity → 200 (0ms)
|
||||||
|
15/06/2026 20:38:16 New Zealand Standard Time INFO mail-api: [823c2f81] GET /owner/client-onboarding-view → 200 (0ms)
|
||||||
|
15/06/2026 20:38:22 New Zealand Standard Time INFO mail-api: [1ee7e7fc] GET /owner/client-onboarding-view → 200 (1ms)
|
||||||
|
15/06/2026 20:38:37 New Zealand Standard Time INFO mail-api: [f3eed04b] GET /auth/verify → 200 (0ms)
|
||||||
|
15/06/2026 20:38:37 New Zealand Standard Time INFO mail-api: [2fcb1297] GET /owner/pending-onboarding → 200 (1ms)
|
||||||
|
15/06/2026 20:38:37 New Zealand Standard Time INFO mail-api: [f8c4996f] GET /owner/birthdays → 200 (2ms)
|
||||||
|
15/06/2026 20:38:37 New Zealand Standard Time INFO mail-api: [99f8e950] GET /owner/all-clients → 200 (2ms)
|
||||||
|
15/06/2026 20:38:41 New Zealand Standard Time INFO mail-api: [9da1e72a] GET /auth/verify → 200 (0ms)
|
||||||
|
15/06/2026 20:38:41 New Zealand Standard Time INFO mail-api: [697cad6a] GET /owner/pending-onboarding → 200 (1ms)
|
||||||
|
15/06/2026 20:38:41 New Zealand Standard Time INFO mail-api: [b5c6c30b] GET /owner/all-clients → 200 (1ms)
|
||||||
|
15/06/2026 20:38:41 New Zealand Standard Time INFO mail-api: [1c67730b] GET /owner/birthdays → 200 (0ms)
|
||||||
|
15/06/2026 20:38:50 New Zealand Standard Time INFO mail-api: [4b3db25d] GET /auth/verify → 200 (0ms)
|
||||||
|
15/06/2026 20:38:50 New Zealand Standard Time INFO mail-api: [b3feec86] GET /owner/pending-onboarding → 200 (1ms)
|
||||||
|
15/06/2026 20:38:50 New Zealand Standard Time INFO mail-api: [f47f7889] GET /owner/all-clients → 200 (1ms)
|
||||||
|
15/06/2026 20:38:50 New Zealand Standard Time INFO mail-api: [f15da397] GET /owner/birthdays → 200 (1ms)
|
||||||
|
15/06/2026 20:38:55 New Zealand Standard Time INFO mail-api: [65cae4c6] GET /auth/verify → 200 (1ms)
|
||||||
|
15/06/2026 20:38:55 New Zealand Standard Time INFO mail-api: [21ab02ac] GET /owner/pending-onboarding → 200 (1ms)
|
||||||
|
15/06/2026 20:38:55 New Zealand Standard Time INFO mail-api: [5a7c1435] GET /owner/all-clients → 200 (1ms)
|
||||||
|
15/06/2026 20:38:55 New Zealand Standard Time INFO mail-api: [e64d71b6] GET /owner/birthdays → 200 (2ms)
|
||||||
|
15/06/2026 20:39:01 New Zealand Standard Time INFO mail-api: [251265ca] GET /auth/verify → 200 (0ms)
|
||||||
|
15/06/2026 20:39:01 New Zealand Standard Time INFO mail-api: [40b2e542] GET /owner/pending-onboarding → 200 (1ms)
|
||||||
|
15/06/2026 20:39:01 New Zealand Standard Time INFO mail-api: [c9af86f0] GET /owner/birthdays → 200 (1ms)
|
||||||
|
15/06/2026 20:39:01 New Zealand Standard Time INFO mail-api: [e9c26722] GET /owner/all-clients → 200 (1ms)
|
||||||
|
15/06/2026 20:39:13 New Zealand Standard Time INFO mail-api: [a2afb433] GET /auth/verify → 200 (0ms)
|
||||||
|
15/06/2026 20:39:13 New Zealand Standard Time INFO mail-api: [195ded67] GET /owner/pending-onboarding → 200 (1ms)
|
||||||
|
15/06/2026 20:39:13 New Zealand Standard Time INFO mail-api: [212985b9] GET /owner/all-clients → 200 (1ms)
|
||||||
|
15/06/2026 20:39:13 New Zealand Standard Time INFO mail-api: [9cdd4476] GET /owner/birthdays → 200 (1ms)
|
||||||
|
15/06/2026 20:39:23 New Zealand Standard Time INFO mail-api: [0ba57c08] GET /auth/verify → 200 (0ms)
|
||||||
|
15/06/2026 20:39:23 New Zealand Standard Time INFO mail-api: [6eec0e7f] GET /owner/pending-onboarding → 200 (1ms)
|
||||||
|
15/06/2026 20:39:23 New Zealand Standard Time INFO mail-api: [f12a42d6] GET /owner/all-clients → 200 (1ms)
|
||||||
|
15/06/2026 20:39:23 New Zealand Standard Time INFO mail-api: [99a199e6] GET /owner/birthdays → 200 (0ms)
|
||||||
|
15/06/2026 20:39:39 New Zealand Standard Time INFO mail-api: [0a1bd987] GET /auth/verify → 200 (0ms)
|
||||||
|
15/06/2026 20:39:39 New Zealand Standard Time INFO mail-api: [261ccb15] GET /owner/pending-onboarding → 200 (0ms)
|
||||||
|
15/06/2026 20:39:39 New Zealand Standard Time INFO mail-api: [ef0c4698] GET /owner/all-clients → 200 (1ms)
|
||||||
|
15/06/2026 20:39:39 New Zealand Standard Time INFO mail-api: [819915a2] GET /owner/birthdays → 200 (1ms)
|
||||||
|
15/06/2026 20:39:57 New Zealand Standard Time INFO mail-api: [7f594e14] GET /owner/message-templates → 200 (1ms)
|
||||||
|
15/06/2026 20:39:57 New Zealand Standard Time INFO mail-api: [99e918ff] POST /owner/render-message → 200 (1ms)
|
||||||
|
15/06/2026 20:39:57 New Zealand Standard Time INFO mail-api: [c387447a] GET /owner/activity → 200 (0ms)
|
||||||
|
15/06/2026 20:40:03 New Zealand Standard Time INFO mail-api: [e225bdd8] GET /owner/activity → 200 (0ms)
|
||||||
|
15/06/2026 20:41:27 New Zealand Standard Time INFO mail-api: [fea1334d] GET /owner/activity → 200 (0ms)
|
||||||
|
15/06/2026 20:41:35 New Zealand Standard Time INFO mail-api: [0476565e] GET /auth/verify → 200 (0ms)
|
||||||
|
15/06/2026 20:41:35 New Zealand Standard Time INFO mail-api: [637f7274] GET /owner/pending-onboarding → 200 (1ms)
|
||||||
|
15/06/2026 20:41:35 New Zealand Standard Time INFO mail-api: [e2f2deb3] GET /owner/all-clients → 200 (1ms)
|
||||||
|
15/06/2026 20:41:35 New Zealand Standard Time INFO mail-api: [ada6cda9] GET /owner/birthdays → 200 (1ms)
|
||||||
|
15/06/2026 20:41:40 New Zealand Standard Time INFO mail-api: [8ed96de9] GET /owner/message-templates → 200 (1ms)
|
||||||
|
15/06/2026 20:41:40 New Zealand Standard Time INFO mail-api: [99ebfb37] POST /owner/render-message → 200 (1ms)
|
||||||
|
15/06/2026 20:41:43 New Zealand Standard Time INFO mail-api: [054a22e5] GET /owner/client-onboarding-view → 200 (1ms)
|
||||||
|
15/06/2026 20:41:50 New Zealand Standard Time INFO mail-api: [0331ffb5] GET /auth/verify → 200 (0ms)
|
||||||
|
15/06/2026 20:41:50 New Zealand Standard Time INFO mail-api: [0bfa7c1d] GET /owner/pending-onboarding → 200 (1ms)
|
||||||
|
15/06/2026 20:41:50 New Zealand Standard Time INFO mail-api: [7bff5a04] GET /owner/birthdays → 200 (1ms)
|
||||||
|
15/06/2026 20:41:50 New Zealand Standard Time INFO mail-api: [b3b90343] GET /owner/all-clients → 200 (2ms)
|
||||||
|
15/06/2026 20:41:55 New Zealand Standard Time INFO mail-api: [e7e42f0d] GET /owner/client-onboarding-view → 200 (0ms)
|
||||||
|
15/06/2026 20:42:09 New Zealand Standard Time INFO mail-api: [06c17820] GET /owner/client-onboarding-view → 200 (0ms)
|
||||||
|
15/06/2026 20:42:27 New Zealand Standard Time INFO mail-api: [fb40d429] GET /auth/verify → 200 (1ms)
|
||||||
|
15/06/2026 20:42:27 New Zealand Standard Time INFO mail-api: [93be80d1] GET /owner/pending-onboarding → 200 (1ms)
|
||||||
|
15/06/2026 20:42:27 New Zealand Standard Time INFO mail-api: [aacd6e15] GET /owner/all-clients → 200 (1ms)
|
||||||
|
15/06/2026 20:42:27 New Zealand Standard Time INFO mail-api: [1f59b652] GET /owner/birthdays → 200 (2ms)
|
||||||
|
15/06/2026 20:42:35 New Zealand Standard Time INFO mail-api: [f63506a6] GET /owner/message-templates → 200 (1ms)
|
||||||
|
15/06/2026 20:42:35 New Zealand Standard Time INFO mail-api: [388261bd] POST /owner/render-message → 200 (1ms)
|
||||||
|
15/06/2026 20:42:36 New Zealand Standard Time INFO mail-api: [b360a242] GET /owner/activity → 200 (1ms)
|
||||||
|
15/06/2026 20:42:41 New Zealand Standard Time INFO mail-api: [158bdb8d] GET /owner/client-enquiry → 200 (0ms)
|
||||||
|
15/06/2026 20:48:29 New Zealand Standard Time INFO mail-api: Logging initialised → console=INFO, file=logs\mail-api.log (DEBUG, rotating)
|
||||||
|
15/06/2026 20:48:29 New Zealand Standard Time INFO mail-api: Mail API config: version='unknown' timezone='system-default' from='GoodWalk <info@goodwalk.co.nz>' reply_to='info@goodwalk.co.nz' owner='info@goodwalk.co.nz' cp_admins=['info@goodwalk.co.nz'] owner_bcc='' client_bcc='' general_enquiries=False max_attempts=3 form_min=1s form_max=7200s rate_window=900s per_ip=50 per_email=50 min_interval=1s send_timeout=20s
|
||||||
|
15/06/2026 20:48:29 New Zealand Standard Time INFO mail-api: Auth: loaded 3 allowed email(s)
|
||||||
|
15/06/2026 20:48:30 New Zealand Standard Time ERROR mail-api: Startup smoke: WeasyPrint UNAVAILABLE — PDF attachments will be skipped (No module named 'weasyprint')
|
||||||
|
15/06/2026 20:48:30 New Zealand Standard Time WARNING mail-api: Startup smoke: postgres disabled — activity/submissions will NOT be recorded
|
||||||
|
15/06/2026 20:48:30 New Zealand Standard Time INFO mail-api: Startup test email skipped: OWNER_BCC is not set to a real address
|
||||||
|
15/06/2026 20:49:48 New Zealand Standard Time INFO mail-api: [545f781f] GET /auth/verify → 401 (2ms)
|
||||||
|
15/06/2026 20:52:31 New Zealand Standard Time INFO mail-api: [7ac75d9d] auth: code issued for email=info@goodwalk.co.nz
|
||||||
|
15/06/2026 20:52:31 New Zealand Standard Time WARNING mail-api: [DEV] auth code for info@goodwalk.co.nz: 718390
|
||||||
|
15/06/2026 20:52:31 New Zealand Standard Time INFO mail-api: [7ac75d9d] POST /auth/request-code → 200 (2ms)
|
||||||
|
15/06/2026 20:52:38 New Zealand Standard Time INFO mail-api: [48f12e62] auth: session created for email=info@goodwalk.co.nz
|
||||||
|
15/06/2026 20:52:38 New Zealand Standard Time INFO mail-api: [48f12e62] POST /auth/verify-code → 200 (2ms)
|
||||||
|
15/06/2026 20:52:38 New Zealand Standard Time INFO mail-api: [6057c23b] GET /auth/verify → 200 (0ms)
|
||||||
|
15/06/2026 20:52:38 New Zealand Standard Time INFO mail-api: [96abc272] GET /owner/pending-onboarding → 200 (2ms)
|
||||||
|
15/06/2026 20:52:38 New Zealand Standard Time INFO mail-api: [92cdf868] GET /owner/all-clients → 200 (2ms)
|
||||||
|
15/06/2026 20:52:38 New Zealand Standard Time INFO mail-api: [5fb5fdce] GET /owner/birthdays → 200 (12ms)
|
||||||
|
15/06/2026 20:52:50 New Zealand Standard Time INFO mail-api: [12d01bfe] GET /auth/verify → 200 (1ms)
|
||||||
|
15/06/2026 20:52:50 New Zealand Standard Time INFO mail-api: [3841ff56] GET /owner/pending-onboarding → 200 (1ms)
|
||||||
|
15/06/2026 20:52:50 New Zealand Standard Time INFO mail-api: [f920aade] GET /owner/all-clients → 200 (1ms)
|
||||||
|
15/06/2026 20:52:50 New Zealand Standard Time INFO mail-api: [7852a9d2] GET /owner/birthdays → 200 (1ms)
|
||||||
|
15/06/2026 20:53:03 New Zealand Standard Time INFO mail-api: [230b4b2b] GET /auth/verify → 200 (0ms)
|
||||||
|
15/06/2026 20:53:03 New Zealand Standard Time INFO mail-api: [4ba82776] GET /owner/pending-onboarding → 200 (1ms)
|
||||||
|
15/06/2026 20:53:03 New Zealand Standard Time INFO mail-api: [6fefad3f] GET /owner/all-clients → 200 (2ms)
|
||||||
|
15/06/2026 20:53:03 New Zealand Standard Time INFO mail-api: [acb28200] GET /owner/birthdays → 200 (2ms)
|
||||||
|
15/06/2026 20:53:22 New Zealand Standard Time INFO mail-api: [9839fe15] GET /owner/message-templates → 200 (1ms)
|
||||||
|
15/06/2026 20:53:22 New Zealand Standard Time INFO mail-api: [26e12e0e] POST /owner/render-message → 200 (1ms)
|
||||||
|
15/06/2026 20:53:23 New Zealand Standard Time INFO mail-api: [a4614459] GET /owner/activity → 200 (1ms)
|
||||||
|
15/06/2026 21:06:24 New Zealand Standard Time INFO mail-api: Logging initialised → console=INFO, file=logs\mail-api.log (DEBUG, rotating)
|
||||||
|
15/06/2026 21:06:24 New Zealand Standard Time INFO mail-api: Mail API config: version='unknown' timezone='system-default' from='GoodWalk <info@goodwalk.co.nz>' reply_to='info@goodwalk.co.nz' owner='info@goodwalk.co.nz' cp_admins=['info@goodwalk.co.nz'] owner_bcc='' client_bcc='' general_enquiries=False max_attempts=3 form_min=1s form_max=7200s rate_window=900s per_ip=50 per_email=50 min_interval=1s send_timeout=20s
|
||||||
|
15/06/2026 21:06:24 New Zealand Standard Time INFO mail-api: Auth: loaded 3 allowed email(s)
|
||||||
|
15/06/2026 21:06:24 New Zealand Standard Time ERROR mail-api: Startup smoke: WeasyPrint UNAVAILABLE — PDF attachments will be skipped (No module named 'weasyprint')
|
||||||
|
15/06/2026 21:06:24 New Zealand Standard Time WARNING mail-api: Startup smoke: postgres disabled — activity/submissions will NOT be recorded
|
||||||
|
15/06/2026 21:06:24 New Zealand Standard Time INFO mail-api: Startup test email skipped: OWNER_BCC is not set to a real address
|
||||||
|
15/06/2026 21:44:37 New Zealand Standard Time INFO mail-api: Logging initialised → console=INFO, file=logs\mail-api.log (DEBUG, rotating)
|
||||||
|
15/06/2026 21:44:37 New Zealand Standard Time INFO mail-api: Mail API config: version='unknown' timezone='system-default' from='GoodWalk <info@goodwalk.co.nz>' reply_to='info@goodwalk.co.nz' owner='info@goodwalk.co.nz' cp_admins=['info@goodwalk.co.nz'] owner_bcc='' client_bcc='' general_enquiries=False max_attempts=3 form_min=1s form_max=7200s rate_window=900s per_ip=50 per_email=50 min_interval=1s send_timeout=20s
|
||||||
|
15/06/2026 21:44:37 New Zealand Standard Time INFO mail-api: Auth: loaded 3 allowed email(s)
|
||||||
|
15/06/2026 21:44:37 New Zealand Standard Time ERROR mail-api: Startup smoke: WeasyPrint UNAVAILABLE — PDF attachments will be skipped (No module named 'weasyprint')
|
||||||
|
15/06/2026 21:44:37 New Zealand Standard Time WARNING mail-api: Startup smoke: postgres disabled — activity/submissions will NOT be recorded
|
||||||
|
15/06/2026 21:44:37 New Zealand Standard Time INFO mail-api: Startup test email skipped: OWNER_BCC is not set to a real address
|
||||||
|
15/06/2026 21:44:45 New Zealand Standard Time INFO mail-api: Logging initialised → console=INFO, file=logs\mail-api.log (DEBUG, rotating)
|
||||||
|
15/06/2026 21:44:45 New Zealand Standard Time INFO mail-api: Mail API config: version='unknown' timezone='system-default' from='GoodWalk <info@goodwalk.co.nz>' reply_to='info@goodwalk.co.nz' owner='info@goodwalk.co.nz' cp_admins=['info@goodwalk.co.nz'] owner_bcc='' client_bcc='' general_enquiries=False max_attempts=3 form_min=1s form_max=7200s rate_window=900s per_ip=50 per_email=50 min_interval=1s send_timeout=20s
|
||||||
|
15/06/2026 21:44:45 New Zealand Standard Time INFO mail-api: Auth: loaded 3 allowed email(s)
|
||||||
|
15/06/2026 21:44:45 New Zealand Standard Time ERROR mail-api: Startup smoke: WeasyPrint UNAVAILABLE — PDF attachments will be skipped (No module named 'weasyprint')
|
||||||
|
15/06/2026 21:44:45 New Zealand Standard Time WARNING mail-api: Startup smoke: postgres disabled — activity/submissions will NOT be recorded
|
||||||
|
15/06/2026 21:44:45 New Zealand Standard Time INFO mail-api: Startup test email skipped: OWNER_BCC is not set to a real address
|
||||||
|
15/06/2026 21:44:56 New Zealand Standard Time INFO mail-api: Logging initialised → console=INFO, file=logs\mail-api.log (DEBUG, rotating)
|
||||||
|
15/06/2026 21:44:56 New Zealand Standard Time INFO mail-api: Mail API config: version='unknown' timezone='system-default' from='GoodWalk <info@goodwalk.co.nz>' reply_to='info@goodwalk.co.nz' owner='info@goodwalk.co.nz' cp_admins=['info@goodwalk.co.nz'] owner_bcc='' client_bcc='' general_enquiries=False max_attempts=3 form_min=1s form_max=7200s rate_window=900s per_ip=50 per_email=50 min_interval=1s send_timeout=20s
|
||||||
|
15/06/2026 21:44:56 New Zealand Standard Time INFO mail-api: Auth: loaded 3 allowed email(s)
|
||||||
|
15/06/2026 21:44:56 New Zealand Standard Time ERROR mail-api: Startup smoke: WeasyPrint UNAVAILABLE — PDF attachments will be skipped (No module named 'weasyprint')
|
||||||
|
15/06/2026 21:44:56 New Zealand Standard Time WARNING mail-api: Startup smoke: postgres disabled — activity/submissions will NOT be recorded
|
||||||
|
15/06/2026 21:44:56 New Zealand Standard Time INFO mail-api: Startup test email skipped: OWNER_BCC is not set to a real address
|
||||||
|
15/06/2026 21:47:36 New Zealand Standard Time INFO mail-api: [68b31f4e] GET /auth/verify → 401 (2ms)
|
||||||
|
15/06/2026 21:47:40 New Zealand Standard Time INFO mail-api: [b6c5eb6f] auth: code issued for email=info@goodwalk.co.nz
|
||||||
|
15/06/2026 21:47:40 New Zealand Standard Time WARNING mail-api: [DEV] auth code for info@goodwalk.co.nz: 719493
|
||||||
|
15/06/2026 21:47:40 New Zealand Standard Time INFO mail-api: [b6c5eb6f] POST /auth/request-code → 200 (2ms)
|
||||||
|
15/06/2026 21:47:53 New Zealand Standard Time INFO mail-api: [d53657f2] auth: session created for email=info@goodwalk.co.nz
|
||||||
|
15/06/2026 21:47:53 New Zealand Standard Time INFO mail-api: [d53657f2] POST /auth/verify-code → 200 (1ms)
|
||||||
|
15/06/2026 21:47:53 New Zealand Standard Time INFO mail-api: [f183b75a] GET /auth/verify → 200 (0ms)
|
||||||
|
15/06/2026 21:47:53 New Zealand Standard Time INFO mail-api: [cb914009] GET /owner/all-clients → 200 (2ms)
|
||||||
|
15/06/2026 21:47:53 New Zealand Standard Time INFO mail-api: [e4eeae45] GET /owner/pending-onboarding → 200 (2ms)
|
||||||
|
15/06/2026 21:47:53 New Zealand Standard Time INFO mail-api: [05e1416f] GET /owner/birthdays → 200 (9ms)
|
||||||
|
15/06/2026 21:47:58 New Zealand Standard Time INFO mail-api: [bc6b815a] GET /owner/client-onboarding-view → 200 (8ms)
|
||||||
|
15/06/2026 21:49:47 New Zealand Standard Time INFO mail-api: Logging initialised → console=INFO, file=logs\mail-api.log (DEBUG, rotating)
|
||||||
|
15/06/2026 21:49:47 New Zealand Standard Time INFO mail-api: Mail API config: version='unknown' timezone='system-default' from='GoodWalk <info@goodwalk.co.nz>' reply_to='info@goodwalk.co.nz' owner='info@goodwalk.co.nz' cp_admins=['info@goodwalk.co.nz'] owner_bcc='' client_bcc='' general_enquiries=False max_attempts=3 form_min=1s form_max=7200s rate_window=900s per_ip=50 per_email=50 min_interval=1s send_timeout=20s
|
||||||
|
15/06/2026 21:49:47 New Zealand Standard Time INFO mail-api: Auth: loaded 3 allowed email(s)
|
||||||
|
15/06/2026 21:49:47 New Zealand Standard Time ERROR mail-api: Startup smoke: WeasyPrint UNAVAILABLE — PDF attachments will be skipped (No module named 'weasyprint')
|
||||||
|
15/06/2026 21:49:47 New Zealand Standard Time WARNING mail-api: Startup smoke: postgres disabled — activity/submissions will NOT be recorded
|
||||||
|
15/06/2026 21:49:47 New Zealand Standard Time INFO mail-api: Startup test email skipped: OWNER_BCC is not set to a real address
|
||||||
|
15/06/2026 21:50:00 New Zealand Standard Time INFO mail-api: Logging initialised → console=INFO, file=logs\mail-api.log (DEBUG, rotating)
|
||||||
|
15/06/2026 21:50:00 New Zealand Standard Time INFO mail-api: Mail API config: version='unknown' timezone='system-default' from='GoodWalk <info@goodwalk.co.nz>' reply_to='info@goodwalk.co.nz' owner='info@goodwalk.co.nz' cp_admins=['info@goodwalk.co.nz'] owner_bcc='' client_bcc='' general_enquiries=False max_attempts=3 form_min=1s form_max=7200s rate_window=900s per_ip=50 per_email=50 min_interval=1s send_timeout=20s
|
||||||
|
15/06/2026 21:50:00 New Zealand Standard Time INFO mail-api: Auth: loaded 3 allowed email(s)
|
||||||
|
15/06/2026 21:50:00 New Zealand Standard Time ERROR mail-api: Startup smoke: WeasyPrint UNAVAILABLE — PDF attachments will be skipped (No module named 'weasyprint')
|
||||||
|
15/06/2026 21:50:00 New Zealand Standard Time WARNING mail-api: Startup smoke: postgres disabled — activity/submissions will NOT be recorded
|
||||||
|
15/06/2026 21:50:00 New Zealand Standard Time INFO mail-api: Startup test email skipped: OWNER_BCC is not set to a real address
|
||||||
|
15/06/2026 21:52:22 New Zealand Standard Time INFO mail-api: [d28300fe] GET /owner/message-templates → 401 (3ms)
|
||||||
|
15/06/2026 21:52:23 New Zealand Standard Time INFO mail-api: [0ea10897] GET /owner/activity → 401 (1ms)
|
||||||
|
15/06/2026 21:52:24 New Zealand Standard Time INFO mail-api: [82beeffb] GET /owner/message-templates → 401 (0ms)
|
||||||
|
15/06/2026 21:52:28 New Zealand Standard Time INFO mail-api: [d4e90d6f] GET /auth/verify → 401 (1ms)
|
||||||
|
16/06/2026 06:29:29 New Zealand Standard Time INFO mail-api: Logging initialised → console=INFO, file=logs\mail-api.log (DEBUG, rotating)
|
||||||
|
16/06/2026 06:29:29 New Zealand Standard Time INFO mail-api: Mail API config: version='unknown' timezone='system-default' from='GoodWalk <info@goodwalk.co.nz>' reply_to='info@goodwalk.co.nz' owner='info@goodwalk.co.nz' cp_admins=['info@goodwalk.co.nz'] owner_bcc='' client_bcc='' general_enquiries=False max_attempts=3 form_min=1s form_max=7200s rate_window=900s per_ip=50 per_email=50 min_interval=1s send_timeout=20s
|
||||||
|
16/06/2026 06:29:29 New Zealand Standard Time INFO mail-api: Auth: loaded 3 allowed email(s)
|
||||||
|
16/06/2026 06:29:29 New Zealand Standard Time ERROR mail-api: Startup smoke: WeasyPrint UNAVAILABLE — PDF attachments will be skipped (No module named 'weasyprint')
|
||||||
|
16/06/2026 06:29:29 New Zealand Standard Time WARNING mail-api: Startup smoke: postgres disabled — activity/submissions will NOT be recorded
|
||||||
|
16/06/2026 06:29:29 New Zealand Standard Time INFO mail-api: Startup test email skipped: OWNER_BCC is not set to a real address
|
||||||
|
16/06/2026 06:30:42 New Zealand Standard Time INFO mail-api: Logging initialised -> console=INFO, file=logs\mail-api.log (DEBUG, rotating)
|
||||||
|
16/06/2026 06:30:42 New Zealand Standard Time INFO mail-api: Mail API config: version='unknown' timezone='system-default' from='GoodWalk <info@goodwalk.co.nz>' reply_to='info@goodwalk.co.nz' owner='info@goodwalk.co.nz' cp_admins=['info@goodwalk.co.nz'] owner_bcc='' client_bcc='' general_enquiries=False max_attempts=3 form_min=1s form_max=7200s rate_window=900s per_ip=50 per_email=50 min_interval=1s send_timeout=20s
|
||||||
|
16/06/2026 06:30:42 New Zealand Standard Time INFO mail-api: Auth: loaded 3 allowed email(s)
|
||||||
|
16/06/2026 06:30:42 New Zealand Standard Time ERROR mail-api: Startup smoke: WeasyPrint UNAVAILABLE — PDF attachments will be skipped (No module named 'weasyprint')
|
||||||
|
16/06/2026 06:30:42 New Zealand Standard Time WARNING mail-api: Startup smoke: postgres disabled — activity/submissions will NOT be recorded
|
||||||
|
16/06/2026 06:30:42 New Zealand Standard Time INFO mail-api: Startup test email skipped: OWNER_BCC is not set to a real address
|
||||||
|
16/06/2026 06:31:28 New Zealand Standard Time INFO mail-api: Logging initialised -> console=INFO, file=logs\mail-api.log (DEBUG, rotating)
|
||||||
|
16/06/2026 06:31:28 New Zealand Standard Time INFO mail-api: Mail API config: version='unknown' timezone='system-default' from='GoodWalk <info@goodwalk.co.nz>' reply_to='info@goodwalk.co.nz' owner='info@goodwalk.co.nz' cp_admins=['info@goodwalk.co.nz'] owner_bcc='' client_bcc='' general_enquiries=False max_attempts=3 form_min=1s form_max=7200s rate_window=900s per_ip=50 per_email=50 min_interval=1s send_timeout=20s
|
||||||
|
16/06/2026 06:31:28 New Zealand Standard Time INFO mail-api: Auth: loaded 3 allowed email(s)
|
||||||
|
16/06/2026 06:31:28 New Zealand Standard Time ERROR mail-api: Startup smoke: WeasyPrint UNAVAILABLE — PDF attachments will be skipped (No module named 'weasyprint')
|
||||||
|
16/06/2026 06:31:28 New Zealand Standard Time WARNING mail-api: Startup smoke: postgres disabled — activity/submissions will NOT be recorded
|
||||||
|
16/06/2026 06:31:28 New Zealand Standard Time INFO mail-api: Startup test email skipped: OWNER_BCC is not set to a real address
|
||||||
|
16/06/2026 06:38:14 New Zealand Standard Time INFO mail-api: [48563882] auth: code issued for email=info@goodwalk.co.nz
|
||||||
|
16/06/2026 06:38:14 New Zealand Standard Time WARNING mail-api: [DEV] auth code for info@goodwalk.co.nz: 926365
|
||||||
|
16/06/2026 06:38:14 New Zealand Standard Time INFO mail-api: [48563882] POST /auth/request-code → 200 (3ms)
|
||||||
|
16/06/2026 06:38:25 New Zealand Standard Time INFO mail-api: [5664ed1d] auth: session created for email=info@goodwalk.co.nz
|
||||||
|
16/06/2026 06:38:25 New Zealand Standard Time INFO mail-api: [5664ed1d] POST /auth/verify-code → 200 (3ms)
|
||||||
|
16/06/2026 06:38:25 New Zealand Standard Time INFO mail-api: [0bb03468] GET /auth/verify → 200 (1ms)
|
||||||
|
16/06/2026 06:38:25 New Zealand Standard Time INFO mail-api: [6f38622c] GET /owner/pending-onboarding → 200 (2ms)
|
||||||
|
16/06/2026 06:38:25 New Zealand Standard Time INFO mail-api: [3d0c8bcf] GET /owner/all-clients → 200 (10ms)
|
||||||
|
16/06/2026 06:38:25 New Zealand Standard Time INFO mail-api: [0b3eb923] GET /owner/birthdays → 200 (11ms)
|
||||||
|
16/06/2026 06:38:49 New Zealand Standard Time INFO mail-api: [1ea3d806] GET /owner/message-templates → 200 (1ms)
|
||||||
|
16/06/2026 06:38:49 New Zealand Standard Time INFO mail-api: [88793213] POST /owner/render-message → 200 (4ms)
|
||||||
|
16/06/2026 06:38:51 New Zealand Standard Time INFO mail-api: [fb1a9f6e] GET /owner/activity → 200 (1ms)
|
||||||
|
16/06/2026 06:40:29 New Zealand Standard Time INFO mail-api: [31e4240c] GET /owner/client-enquiry → 200 (2ms)
|
||||||
|
16/06/2026 06:41:47 New Zealand Standard Time INFO mail-api: [121f9f98] GET /owner/activity → 200 (0ms)
|
||||||
|
16/06/2026 06:44:02 New Zealand Standard Time INFO mail-api: [5a77951c] GET /auth/verify → 200 (0ms)
|
||||||
|
16/06/2026 06:44:02 New Zealand Standard Time INFO mail-api: [737112c8] GET /owner/pending-onboarding → 200 (1ms)
|
||||||
|
16/06/2026 06:44:02 New Zealand Standard Time INFO mail-api: [d9a6813f] GET /owner/all-clients → 200 (1ms)
|
||||||
|
16/06/2026 06:44:02 New Zealand Standard Time INFO mail-api: [46903a39] GET /owner/birthdays → 200 (2ms)
|
||||||
|
16/06/2026 06:45:04 New Zealand Standard Time INFO mail-api: [a241498f] GET /auth/verify → 200 (0ms)
|
||||||
|
16/06/2026 06:45:04 New Zealand Standard Time INFO mail-api: [a306e4a4] GET /owner/pending-onboarding → 200 (1ms)
|
||||||
|
16/06/2026 06:45:04 New Zealand Standard Time INFO mail-api: [6e060204] GET /owner/all-clients → 200 (1ms)
|
||||||
|
16/06/2026 06:45:04 New Zealand Standard Time INFO mail-api: [58ffe4c7] GET /owner/birthdays → 200 (3ms)
|
||||||
|
16/06/2026 06:45:17 New Zealand Standard Time INFO mail-api: [56c5f41a] GET /auth/verify → 200 (0ms)
|
||||||
|
16/06/2026 06:45:17 New Zealand Standard Time INFO mail-api: [f49f41b6] GET /owner/pending-onboarding → 200 (1ms)
|
||||||
|
16/06/2026 06:45:17 New Zealand Standard Time INFO mail-api: [bd4fd420] GET /owner/all-clients → 200 (1ms)
|
||||||
|
16/06/2026 06:45:17 New Zealand Standard Time INFO mail-api: [d4b0ada0] GET /owner/birthdays → 200 (2ms)
|
||||||
|
16/06/2026 06:50:33 New Zealand Standard Time INFO mail-api: [e2163ae1] GET /owner/client-enquiry → 200 (0ms)
|
||||||
|
16/06/2026 06:51:46 New Zealand Standard Time INFO mail-api: [16d4acbc] GET /owner/message-templates → 200 (1ms)
|
||||||
|
16/06/2026 06:51:46 New Zealand Standard Time INFO mail-api: [049712df] POST /owner/render-message → 200 (1ms)
|
||||||
|
16/06/2026 06:51:47 New Zealand Standard Time INFO mail-api: [ff1f1f13] GET /owner/activity → 200 (0ms)
|
||||||
|
16/06/2026 06:52:05 New Zealand Standard Time INFO mail-api: [e3758e0f] GET /owner/all-clients → 200 (1ms)
|
||||||
|
16/06/2026 06:52:51 New Zealand Standard Time INFO mail-api: [80d0c427] GET /owner/activity → 200 (0ms)
|
||||||
|
16/06/2026 06:54:39 New Zealand Standard Time INFO mail-api: [ae3c4fd3] GET /owner/activity → 200 (1ms)
|
||||||
|
16/06/2026 06:55:26 New Zealand Standard Time INFO mail-api: [d6b285f1] GET /owner/all-clients → 200 (1ms)
|
||||||
|
16/06/2026 06:55:30 New Zealand Standard Time INFO mail-api: [24446f12] GET /owner/client-enquiry → 200 (0ms)
|
||||||
|
16/06/2026 06:55:43 New Zealand Standard Time INFO mail-api: [f47fdfc6] GET /owner/activity → 200 (0ms)
|
||||||
|
16/06/2026 06:56:04 New Zealand Standard Time INFO mail-api: [ef4b6c2a] GET /owner/client-enquiry → 200 (0ms)
|
||||||
|
16/06/2026 06:56:21 New Zealand Standard Time INFO mail-api: [59ef0c42] GET /owner/activity → 200 (1ms)
|
||||||
|
16/06/2026 06:57:43 New Zealand Standard Time INFO mail-api: [ce3133ef] GET /auth/verify → 200 (0ms)
|
||||||
|
16/06/2026 06:57:43 New Zealand Standard Time INFO mail-api: [c6a5dfeb] GET /owner/all-clients → 200 (1ms)
|
||||||
|
16/06/2026 06:57:43 New Zealand Standard Time INFO mail-api: [de7d24a0] GET /owner/pending-onboarding → 200 (1ms)
|
||||||
|
16/06/2026 06:57:43 New Zealand Standard Time INFO mail-api: [bf7f5696] GET /owner/birthdays → 200 (0ms)
|
||||||
|
16/06/2026 06:57:46 New Zealand Standard Time INFO mail-api: [08a9d0a6] GET /owner/message-templates → 200 (1ms)
|
||||||
|
16/06/2026 06:57:46 New Zealand Standard Time INFO mail-api: [9388927c] POST /owner/render-message → 200 (1ms)
|
||||||
|
16/06/2026 07:00:13 New Zealand Standard Time INFO mail-api: [39b85a36] GET /owner/activity → 200 (0ms)
|
||||||
|
16/06/2026 07:02:06 New Zealand Standard Time INFO mail-api: [2fbeef76] GET /owner/all-clients → 200 (1ms)
|
||||||
|
16/06/2026 07:06:11 New Zealand Standard Time INFO mail-api: [1c8a494a] GET /owner/activity → 200 (1ms)
|
||||||
|
16/06/2026 07:06:42 New Zealand Standard Time INFO mail-api: [41ddcc81] GET /owner/activity → 200 (0ms)
|
||||||
|
16/06/2026 07:13:10 New Zealand Standard Time INFO mail-api: [93328ec4] GET /owner/all-clients → 200 (1ms)
|
||||||
|
16/06/2026 07:14:02 New Zealand Standard Time INFO mail-api: Logging initialised -> console=INFO, file=logs\mail-api.log (DEBUG, rotating)
|
||||||
|
16/06/2026 07:14:02 New Zealand Standard Time INFO mail-api: Mail API config: version='unknown' timezone='system-default' from='GoodWalk <info@goodwalk.co.nz>' reply_to='info@goodwalk.co.nz' owner='info@goodwalk.co.nz' cp_admins=['info@goodwalk.co.nz'] owner_bcc='' client_bcc='' general_enquiries=False max_attempts=3 form_min=1s form_max=7200s rate_window=900s per_ip=50 per_email=50 min_interval=1s send_timeout=20s
|
||||||
|
16/06/2026 07:14:02 New Zealand Standard Time INFO mail-api: Auth: loaded 3 allowed email(s)
|
||||||
|
16/06/2026 07:14:02 New Zealand Standard Time ERROR mail-api: Startup smoke: WeasyPrint UNAVAILABLE — PDF attachments will be skipped (No module named 'weasyprint')
|
||||||
|
16/06/2026 07:14:02 New Zealand Standard Time WARNING mail-api: Startup smoke: postgres disabled — activity/submissions will NOT be recorded
|
||||||
|
16/06/2026 07:14:02 New Zealand Standard Time INFO mail-api: Startup test email skipped: OWNER_BCC is not set to a real address
|
||||||
|
16/06/2026 07:14:08 New Zealand Standard Time INFO mail-api: Logging initialised -> console=INFO, file=logs\mail-api.log (DEBUG, rotating)
|
||||||
|
16/06/2026 07:14:08 New Zealand Standard Time INFO mail-api: Mail API config: version='unknown' timezone='system-default' from='GoodWalk <info@goodwalk.co.nz>' reply_to='info@goodwalk.co.nz' owner='info@goodwalk.co.nz' cp_admins=['info@goodwalk.co.nz'] owner_bcc='' client_bcc='' general_enquiries=False max_attempts=3 form_min=1s form_max=7200s rate_window=900s per_ip=50 per_email=50 min_interval=1s send_timeout=20s
|
||||||
|
16/06/2026 07:14:08 New Zealand Standard Time INFO mail-api: Auth: loaded 3 allowed email(s)
|
||||||
|
16/06/2026 07:14:08 New Zealand Standard Time ERROR mail-api: Startup smoke: WeasyPrint UNAVAILABLE — PDF attachments will be skipped (No module named 'weasyprint')
|
||||||
|
16/06/2026 07:14:08 New Zealand Standard Time WARNING mail-api: Startup smoke: postgres disabled — activity/submissions will NOT be recorded
|
||||||
|
16/06/2026 07:14:08 New Zealand Standard Time INFO mail-api: Startup test email skipped: OWNER_BCC is not set to a real address
|
||||||
|
16/06/2026 07:14:25 New Zealand Standard Time INFO mail-api: Logging initialised -> console=INFO, file=logs\mail-api.log (DEBUG, rotating)
|
||||||
|
16/06/2026 07:14:25 New Zealand Standard Time INFO mail-api: Mail API config: version='unknown' timezone='system-default' from='GoodWalk <info@goodwalk.co.nz>' reply_to='info@goodwalk.co.nz' owner='info@goodwalk.co.nz' cp_admins=['info@goodwalk.co.nz'] owner_bcc='' client_bcc='' general_enquiries=False max_attempts=3 form_min=1s form_max=7200s rate_window=900s per_ip=50 per_email=50 min_interval=1s send_timeout=20s
|
||||||
|
16/06/2026 07:14:31 New Zealand Standard Time INFO mail-api: Logging initialised -> console=INFO, file=logs\mail-api.log (DEBUG, rotating)
|
||||||
|
16/06/2026 07:14:31 New Zealand Standard Time INFO mail-api: Mail API config: version='unknown' timezone='system-default' from='GoodWalk <info@goodwalk.co.nz>' reply_to='info@goodwalk.co.nz' owner='info@goodwalk.co.nz' cp_admins=['info@goodwalk.co.nz'] owner_bcc='' client_bcc='' general_enquiries=False max_attempts=3 form_min=1s form_max=7200s rate_window=900s per_ip=50 per_email=50 min_interval=1s send_timeout=20s
|
||||||
|
16/06/2026 07:14:31 New Zealand Standard Time INFO mail-api: Auth: loaded 3 allowed email(s)
|
||||||
|
16/06/2026 07:14:31 New Zealand Standard Time ERROR mail-api: Startup smoke: WeasyPrint UNAVAILABLE — PDF attachments will be skipped (No module named 'weasyprint')
|
||||||
|
16/06/2026 07:14:31 New Zealand Standard Time WARNING mail-api: Startup smoke: postgres disabled — activity/submissions will NOT be recorded
|
||||||
|
16/06/2026 07:14:31 New Zealand Standard Time INFO mail-api: Startup test email skipped: OWNER_BCC is not set to a real address
|
||||||
|
16/06/2026 07:14:46 New Zealand Standard Time INFO mail-api: Logging initialised -> console=INFO, file=logs\mail-api.log (DEBUG, rotating)
|
||||||
|
16/06/2026 07:14:46 New Zealand Standard Time INFO mail-api: Mail API config: version='unknown' timezone='system-default' from='GoodWalk <info@goodwalk.co.nz>' reply_to='info@goodwalk.co.nz' owner='info@goodwalk.co.nz' cp_admins=['info@goodwalk.co.nz'] owner_bcc='' client_bcc='' general_enquiries=False max_attempts=3 form_min=1s form_max=7200s rate_window=900s per_ip=50 per_email=50 min_interval=1s send_timeout=20s
|
||||||
|
16/06/2026 07:14:46 New Zealand Standard Time INFO mail-api: Auth: loaded 3 allowed email(s)
|
||||||
|
16/06/2026 07:14:46 New Zealand Standard Time ERROR mail-api: Startup smoke: WeasyPrint UNAVAILABLE — PDF attachments will be skipped (No module named 'weasyprint')
|
||||||
|
16/06/2026 07:14:46 New Zealand Standard Time WARNING mail-api: Startup smoke: postgres disabled — activity/submissions will NOT be recorded
|
||||||
|
16/06/2026 07:14:46 New Zealand Standard Time INFO mail-api: Startup test email skipped: OWNER_BCC is not set to a real address
|
||||||
|
16/06/2026 07:14:59 New Zealand Standard Time INFO mail-api: Logging initialised -> console=INFO, file=logs\mail-api.log (DEBUG, rotating)
|
||||||
|
16/06/2026 07:14:59 New Zealand Standard Time INFO mail-api: Mail API config: version='unknown' timezone='system-default' from='GoodWalk <info@goodwalk.co.nz>' reply_to='info@goodwalk.co.nz' owner='info@goodwalk.co.nz' cp_admins=['info@goodwalk.co.nz'] owner_bcc='' client_bcc='' general_enquiries=False max_attempts=3 form_min=1s form_max=7200s rate_window=900s per_ip=50 per_email=50 min_interval=1s send_timeout=20s
|
||||||
|
16/06/2026 07:14:59 New Zealand Standard Time INFO mail-api: Auth: loaded 3 allowed email(s)
|
||||||
|
16/06/2026 07:14:59 New Zealand Standard Time ERROR mail-api: Startup smoke: WeasyPrint UNAVAILABLE — PDF attachments will be skipped (No module named 'weasyprint')
|
||||||
|
16/06/2026 07:14:59 New Zealand Standard Time WARNING mail-api: Startup smoke: postgres disabled — activity/submissions will NOT be recorded
|
||||||
|
16/06/2026 07:14:59 New Zealand Standard Time INFO mail-api: Startup test email skipped: OWNER_BCC is not set to a real address
|
||||||
|
16/06/2026 07:15:06 New Zealand Standard Time INFO mail-api: Logging initialised -> console=INFO, file=logs\mail-api.log (DEBUG, rotating)
|
||||||
|
16/06/2026 07:15:06 New Zealand Standard Time INFO mail-api: Mail API config: version='unknown' timezone='system-default' from='GoodWalk <info@goodwalk.co.nz>' reply_to='info@goodwalk.co.nz' owner='info@goodwalk.co.nz' cp_admins=['info@goodwalk.co.nz'] owner_bcc='' client_bcc='' general_enquiries=False max_attempts=3 form_min=1s form_max=7200s rate_window=900s per_ip=50 per_email=50 min_interval=1s send_timeout=20s
|
||||||
|
16/06/2026 07:15:06 New Zealand Standard Time INFO mail-api: Auth: loaded 3 allowed email(s)
|
||||||
|
16/06/2026 07:15:06 New Zealand Standard Time ERROR mail-api: Startup smoke: WeasyPrint UNAVAILABLE — PDF attachments will be skipped (No module named 'weasyprint')
|
||||||
|
16/06/2026 07:15:06 New Zealand Standard Time WARNING mail-api: Startup smoke: postgres disabled — activity/submissions will NOT be recorded
|
||||||
|
16/06/2026 07:15:06 New Zealand Standard Time INFO mail-api: Startup test email skipped: OWNER_BCC is not set to a real address
|
||||||
|
16/06/2026 07:15:24 New Zealand Standard Time INFO mail-api: Logging initialised -> console=INFO, file=logs\mail-api.log (DEBUG, rotating)
|
||||||
|
16/06/2026 07:15:24 New Zealand Standard Time INFO mail-api: Mail API config: version='unknown' timezone='system-default' from='GoodWalk <info@goodwalk.co.nz>' reply_to='info@goodwalk.co.nz' owner='info@goodwalk.co.nz' cp_admins=['info@goodwalk.co.nz'] owner_bcc='' client_bcc='' general_enquiries=False max_attempts=3 form_min=1s form_max=7200s rate_window=900s per_ip=50 per_email=50 min_interval=1s send_timeout=20s
|
||||||
|
16/06/2026 07:15:24 New Zealand Standard Time INFO mail-api: Auth: loaded 3 allowed email(s)
|
||||||
|
16/06/2026 07:15:24 New Zealand Standard Time ERROR mail-api: Startup smoke: WeasyPrint UNAVAILABLE — PDF attachments will be skipped (No module named 'weasyprint')
|
||||||
|
16/06/2026 07:15:24 New Zealand Standard Time WARNING mail-api: Startup smoke: postgres disabled — activity/submissions will NOT be recorded
|
||||||
|
16/06/2026 07:15:24 New Zealand Standard Time INFO mail-api: Startup test email skipped: OWNER_BCC is not set to a real address
|
||||||
|
16/06/2026 07:15:50 New Zealand Standard Time INFO mail-api: [825c3ae9] GET /auth/verify → 401 (2ms)
|
||||||
|
16/06/2026 07:15:54 New Zealand Standard Time INFO mail-api: [d2197895] auth: code issued for email=info@goodwalk.co.nz
|
||||||
|
16/06/2026 07:15:54 New Zealand Standard Time WARNING mail-api: [DEV] auth code for info@goodwalk.co.nz: 269988
|
||||||
|
16/06/2026 07:15:54 New Zealand Standard Time INFO mail-api: [d2197895] POST /auth/request-code → 200 (2ms)
|
||||||
|
16/06/2026 07:16:05 New Zealand Standard Time INFO mail-api: [f3567591] auth: session created for email=info@goodwalk.co.nz
|
||||||
|
16/06/2026 07:16:05 New Zealand Standard Time INFO mail-api: [f3567591] POST /auth/verify-code → 200 (2ms)
|
||||||
|
16/06/2026 07:16:05 New Zealand Standard Time INFO mail-api: [f568d6aa] GET /auth/verify → 200 (0ms)
|
||||||
|
16/06/2026 07:16:05 New Zealand Standard Time INFO mail-api: [15422225] GET /owner/pending-onboarding → 200 (2ms)
|
||||||
|
16/06/2026 07:16:05 New Zealand Standard Time INFO mail-api: [560b7831] GET /owner/all-clients → 200 (5ms)
|
||||||
|
16/06/2026 07:16:05 New Zealand Standard Time INFO mail-api: [b7db7b94] GET /owner/birthdays → 200 (5ms)
|
||||||
|
16/06/2026 07:16:20 New Zealand Standard Time INFO mail-api: [b1f1abfd] GET /owner/all-clients → 200 (1ms)
|
||||||
|
16/06/2026 07:16:46 New Zealand Standard Time INFO mail-api: [c80114ed] GET /owner/all-clients → 200 (1ms)
|
||||||
|
16/06/2026 07:16:57 New Zealand Standard Time INFO mail-api: [f47002dd] GET /owner/message-templates → 200 (1ms)
|
||||||
|
16/06/2026 07:16:57 New Zealand Standard Time INFO mail-api: [949a4211] POST /owner/render-message → 200 (2ms)
|
||||||
|
16/06/2026 07:17:07 New Zealand Standard Time INFO mail-api: Logging initialised -> console=INFO, file=logs\mail-api.log (DEBUG, rotating)
|
||||||
|
16/06/2026 07:17:07 New Zealand Standard Time INFO mail-api: Mail API config: version='unknown' timezone='system-default' from='GoodWalk <info@goodwalk.co.nz>' reply_to='info@goodwalk.co.nz' owner='info@goodwalk.co.nz' cp_admins=['info@goodwalk.co.nz'] owner_bcc='' client_bcc='' general_enquiries=False max_attempts=3 form_min=1s form_max=7200s rate_window=900s per_ip=50 per_email=50 min_interval=1s send_timeout=20s
|
||||||
|
16/06/2026 07:17:07 New Zealand Standard Time INFO mail-api: Auth: loaded 3 allowed email(s)
|
||||||
|
16/06/2026 07:17:07 New Zealand Standard Time ERROR mail-api: Startup smoke: WeasyPrint UNAVAILABLE — PDF attachments will be skipped (No module named 'weasyprint')
|
||||||
|
16/06/2026 07:17:07 New Zealand Standard Time WARNING mail-api: Startup smoke: postgres disabled — activity/submissions will NOT be recorded
|
||||||
|
16/06/2026 07:17:07 New Zealand Standard Time INFO mail-api: Startup test email skipped: OWNER_BCC is not set to a real address
|
||||||
|
16/06/2026 07:17:13 New Zealand Standard Time INFO mail-api: Logging initialised -> console=INFO, file=logs\mail-api.log (DEBUG, rotating)
|
||||||
|
16/06/2026 07:17:13 New Zealand Standard Time INFO mail-api: Mail API config: version='unknown' timezone='system-default' from='GoodWalk <info@goodwalk.co.nz>' reply_to='info@goodwalk.co.nz' owner='info@goodwalk.co.nz' cp_admins=['info@goodwalk.co.nz'] owner_bcc='' client_bcc='' general_enquiries=False max_attempts=3 form_min=1s form_max=7200s rate_window=900s per_ip=50 per_email=50 min_interval=1s send_timeout=20s
|
||||||
|
16/06/2026 07:17:13 New Zealand Standard Time INFO mail-api: Auth: loaded 3 allowed email(s)
|
||||||
|
16/06/2026 07:17:13 New Zealand Standard Time ERROR mail-api: Startup smoke: WeasyPrint UNAVAILABLE — PDF attachments will be skipped (No module named 'weasyprint')
|
||||||
|
16/06/2026 07:17:13 New Zealand Standard Time WARNING mail-api: Startup smoke: postgres disabled — activity/submissions will NOT be recorded
|
||||||
|
16/06/2026 07:17:13 New Zealand Standard Time INFO mail-api: Startup test email skipped: OWNER_BCC is not set to a real address
|
||||||
|
16/06/2026 07:17:20 New Zealand Standard Time INFO mail-api: Logging initialised -> console=INFO, file=logs\mail-api.log (DEBUG, rotating)
|
||||||
|
16/06/2026 07:17:20 New Zealand Standard Time INFO mail-api: Mail API config: version='unknown' timezone='system-default' from='GoodWalk <info@goodwalk.co.nz>' reply_to='info@goodwalk.co.nz' owner='info@goodwalk.co.nz' cp_admins=['info@goodwalk.co.nz'] owner_bcc='' client_bcc='' general_enquiries=False max_attempts=3 form_min=1s form_max=7200s rate_window=900s per_ip=50 per_email=50 min_interval=1s send_timeout=20s
|
||||||
|
16/06/2026 07:17:20 New Zealand Standard Time INFO mail-api: Auth: loaded 3 allowed email(s)
|
||||||
|
16/06/2026 07:17:20 New Zealand Standard Time ERROR mail-api: Startup smoke: WeasyPrint UNAVAILABLE — PDF attachments will be skipped (No module named 'weasyprint')
|
||||||
|
16/06/2026 07:17:20 New Zealand Standard Time WARNING mail-api: Startup smoke: postgres disabled — activity/submissions will NOT be recorded
|
||||||
|
16/06/2026 07:17:20 New Zealand Standard Time INFO mail-api: Startup test email skipped: OWNER_BCC is not set to a real address
|
||||||
|
16/06/2026 07:17:27 New Zealand Standard Time INFO mail-api: Logging initialised -> console=INFO, file=logs\mail-api.log (DEBUG, rotating)
|
||||||
|
16/06/2026 07:17:27 New Zealand Standard Time INFO mail-api: Mail API config: version='unknown' timezone='system-default' from='GoodWalk <info@goodwalk.co.nz>' reply_to='info@goodwalk.co.nz' owner='info@goodwalk.co.nz' cp_admins=['info@goodwalk.co.nz'] owner_bcc='' client_bcc='' general_enquiries=False max_attempts=3 form_min=1s form_max=7200s rate_window=900s per_ip=50 per_email=50 min_interval=1s send_timeout=20s
|
||||||
|
16/06/2026 07:17:27 New Zealand Standard Time INFO mail-api: Auth: loaded 3 allowed email(s)
|
||||||
|
16/06/2026 07:17:27 New Zealand Standard Time ERROR mail-api: Startup smoke: WeasyPrint UNAVAILABLE — PDF attachments will be skipped (No module named 'weasyprint')
|
||||||
|
16/06/2026 07:17:27 New Zealand Standard Time WARNING mail-api: Startup smoke: postgres disabled — activity/submissions will NOT be recorded
|
||||||
|
16/06/2026 07:17:27 New Zealand Standard Time INFO mail-api: Startup test email skipped: OWNER_BCC is not set to a real address
|
||||||
|
16/06/2026 07:18:01 New Zealand Standard Time INFO mail-api: [8cdf225d] GET /auth/verify → 401 (2ms)
|
||||||
|
16/06/2026 07:18:05 New Zealand Standard Time INFO mail-api: [cbe1d0d9] auth: unknown email=info@goodwalk.co.z ip=127.0.0.1
|
||||||
|
16/06/2026 07:18:05 New Zealand Standard Time WARNING mail-api: [cbe1d0d9] auth: failure ip=127.0.0.1 reason='unknown_email' total_in_window=1
|
||||||
|
16/06/2026 07:18:05 New Zealand Standard Time INFO mail-api: [cbe1d0d9] POST /auth/request-code → 403 (2ms)
|
||||||
|
16/06/2026 07:18:08 New Zealand Standard Time INFO mail-api: [008f2e1a] auth: code issued for email=info@goodwalk.co.nz
|
||||||
|
16/06/2026 07:18:08 New Zealand Standard Time WARNING mail-api: [DEV] auth code for info@goodwalk.co.nz: 952701
|
||||||
|
16/06/2026 07:18:08 New Zealand Standard Time INFO mail-api: [008f2e1a] POST /auth/request-code → 200 (1ms)
|
||||||
|
16/06/2026 07:18:14 New Zealand Standard Time INFO mail-api: Logging initialised -> console=INFO, file=logs\mail-api.log (DEBUG, rotating)
|
||||||
|
16/06/2026 07:18:14 New Zealand Standard Time INFO mail-api: Mail API config: version='unknown' timezone='system-default' from='GoodWalk <info@goodwalk.co.nz>' reply_to='info@goodwalk.co.nz' owner='info@goodwalk.co.nz' cp_admins=['info@goodwalk.co.nz'] owner_bcc='' client_bcc='' general_enquiries=False max_attempts=3 form_min=1s form_max=7200s rate_window=900s per_ip=50 per_email=50 min_interval=1s send_timeout=20s
|
||||||
|
16/06/2026 07:18:14 New Zealand Standard Time INFO mail-api: Auth: loaded 3 allowed email(s)
|
||||||
|
16/06/2026 07:18:14 New Zealand Standard Time ERROR mail-api: Startup smoke: WeasyPrint UNAVAILABLE — PDF attachments will be skipped (No module named 'weasyprint')
|
||||||
|
16/06/2026 07:18:14 New Zealand Standard Time WARNING mail-api: Startup smoke: postgres disabled — activity/submissions will NOT be recorded
|
||||||
|
16/06/2026 07:18:14 New Zealand Standard Time INFO mail-api: Startup test email skipped: OWNER_BCC is not set to a real address
|
||||||
|
16/06/2026 07:18:20 New Zealand Standard Time INFO mail-api: [369f9969] auth: code issued for email=info@goodwalk.co.nz
|
||||||
|
16/06/2026 07:18:20 New Zealand Standard Time WARNING mail-api: [DEV] auth code for info@goodwalk.co.nz: 943074
|
||||||
|
16/06/2026 07:18:20 New Zealand Standard Time INFO mail-api: [369f9969] POST /auth/request-code → 200 (3ms)
|
||||||
|
16/06/2026 07:18:31 New Zealand Standard Time INFO mail-api: [ceb1fc90] auth: code issued for email=info@goodwalk.co.nz
|
||||||
|
16/06/2026 07:18:31 New Zealand Standard Time WARNING mail-api: [DEV] auth code for info@goodwalk.co.nz: 261983
|
||||||
|
16/06/2026 07:18:31 New Zealand Standard Time INFO mail-api: [ceb1fc90] POST /auth/request-code → 200 (1ms)
|
||||||
|
16/06/2026 07:25:18 New Zealand Standard Time INFO mail-api: Logging initialised -> console=INFO, file=logs\mail-api.log (DEBUG, rotating)
|
||||||
|
16/06/2026 07:25:18 New Zealand Standard Time INFO mail-api: Mail API config: version='unknown' timezone='system-default' from='GoodWalk <info@goodwalk.co.nz>' reply_to='info@goodwalk.co.nz' owner='info@goodwalk.co.nz' cp_admins=['info@goodwalk.co.nz'] owner_bcc='' client_bcc='' general_enquiries=False max_attempts=3 form_min=1s form_max=7200s rate_window=900s per_ip=50 per_email=50 min_interval=1s send_timeout=20s
|
||||||
|
16/06/2026 07:25:18 New Zealand Standard Time INFO mail-api: Auth: loaded 3 allowed email(s)
|
||||||
|
16/06/2026 07:25:18 New Zealand Standard Time ERROR mail-api: Startup smoke: WeasyPrint UNAVAILABLE — PDF attachments will be skipped (No module named 'weasyprint')
|
||||||
|
16/06/2026 07:25:18 New Zealand Standard Time WARNING mail-api: Startup smoke: postgres disabled — activity/submissions will NOT be recorded
|
||||||
|
16/06/2026 07:25:18 New Zealand Standard Time INFO mail-api: Startup test email skipped: OWNER_BCC is not set to a real address
|
||||||
|
16/06/2026 07:25:20 New Zealand Standard Time INFO mail-api: Logging initialised -> console=INFO, file=logs\mail-api.log (DEBUG, rotating)
|
||||||
|
16/06/2026 07:25:20 New Zealand Standard Time INFO mail-api: Mail API config: version='unknown' timezone='system-default' from='GoodWalk <info@goodwalk.co.nz>' reply_to='info@goodwalk.co.nz' owner='info@goodwalk.co.nz' cp_admins=['info@goodwalk.co.nz'] owner_bcc='' client_bcc='' general_enquiries=False max_attempts=3 form_min=1s form_max=7200s rate_window=900s per_ip=50 per_email=50 min_interval=1s send_timeout=20s
|
||||||
|
16/06/2026 07:25:20 New Zealand Standard Time INFO mail-api: Auth: loaded 3 allowed email(s)
|
||||||
|
16/06/2026 07:25:20 New Zealand Standard Time ERROR mail-api: Startup smoke: WeasyPrint UNAVAILABLE — PDF attachments will be skipped (No module named 'weasyprint')
|
||||||
|
16/06/2026 07:25:20 New Zealand Standard Time WARNING mail-api: Startup smoke: postgres disabled — activity/submissions will NOT be recorded
|
||||||
|
16/06/2026 07:25:20 New Zealand Standard Time INFO mail-api: Startup test email skipped: OWNER_BCC is not set to a real address
|
||||||
|
16/06/2026 07:25:28 New Zealand Standard Time INFO mail-api: [098d12dc] auth: code issued for email=info@goodwalk.co.nz
|
||||||
|
16/06/2026 07:25:28 New Zealand Standard Time WARNING mail-api: [DEV] auth code for info@goodwalk.co.nz: 496267
|
||||||
|
16/06/2026 07:25:28 New Zealand Standard Time INFO mail-api: [098d12dc] POST /auth/request-code → 200 (4ms)
|
||||||
|
16/06/2026 07:25:35 New Zealand Standard Time INFO mail-api: [8eb8c518] auth: session created for email=info@goodwalk.co.nz
|
||||||
|
16/06/2026 07:25:35 New Zealand Standard Time INFO mail-api: [8eb8c518] POST /auth/verify-code → 200 (2ms)
|
||||||
|
16/06/2026 07:25:35 New Zealand Standard Time INFO mail-api: [b4bcbfbd] GET /auth/verify → 200 (1ms)
|
||||||
|
16/06/2026 07:25:35 New Zealand Standard Time INFO mail-api: [37f1c7a9] GET /owner/pending-onboarding → 200 (2ms)
|
||||||
|
16/06/2026 07:25:35 New Zealand Standard Time INFO mail-api: [0daa6a9c] GET /owner/all-clients → 200 (7ms)
|
||||||
|
16/06/2026 07:25:35 New Zealand Standard Time INFO mail-api: [40680736] GET /owner/birthdays → 200 (7ms)
|
||||||
|
16/06/2026 07:25:37 New Zealand Standard Time INFO mail-api: [e9db9842] POST /owner/render-welcome-pack → 200 (14ms)
|
||||||
|
16/06/2026 07:25:44 New Zealand Standard Time INFO mail-api: [13fbb03a] POST /owner/render-welcome-pack → 200 (1ms)
|
||||||
|
16/06/2026 07:25:49 New Zealand Standard Time INFO mail-api: [fb8e73e3] POST /owner/render-welcome-pack → 200 (1ms)
|
||||||
|
16/06/2026 07:25:51 New Zealand Standard Time INFO mail-api: [22db529c] POST /owner/render-welcome-pack → 200 (1ms)
|
||||||
|
16/06/2026 07:26:13 New Zealand Standard Time INFO mail-api: [806f9017] GET /owner/scheduled-emails → 200 (2ms)
|
||||||
|
16/06/2026 07:26:15 New Zealand Standard Time INFO mail-api: [2864b4ca] GET /owner/message-templates → 200 (1ms)
|
||||||
|
16/06/2026 07:26:15 New Zealand Standard Time INFO mail-api: [f133698c] POST /owner/render-message → 200 (3ms)
|
||||||
|
16/06/2026 07:47:45 New Zealand Standard Time INFO mail-api: [11b1a1dd] POST /owner/render-welcome-pack → 200 (1ms)
|
||||||
|
16/06/2026 07:48:20 New Zealand Standard Time INFO mail-api: [03817871] GET /auth/verify → 200 (0ms)
|
||||||
|
16/06/2026 07:48:20 New Zealand Standard Time INFO mail-api: [1b5b54f8] GET /owner/pending-onboarding → 200 (1ms)
|
||||||
|
16/06/2026 07:48:20 New Zealand Standard Time INFO mail-api: [d0736f04] GET /owner/all-clients → 200 (1ms)
|
||||||
|
16/06/2026 07:48:20 New Zealand Standard Time INFO mail-api: [4919bec5] GET /owner/birthdays → 200 (1ms)
|
||||||
|
16/06/2026 07:48:38 New Zealand Standard Time INFO mail-api: [3c6b1f63] GET /auth/verify → 200 (0ms)
|
||||||
|
16/06/2026 07:48:38 New Zealand Standard Time INFO mail-api: [c1ea7cf1] GET /owner/pending-onboarding → 200 (1ms)
|
||||||
|
16/06/2026 07:48:38 New Zealand Standard Time INFO mail-api: [a3a06d7a] GET /owner/all-clients → 200 (1ms)
|
||||||
|
16/06/2026 07:48:38 New Zealand Standard Time INFO mail-api: [c9a5683b] GET /owner/birthdays → 200 (2ms)
|
||||||
|
16/06/2026 07:51:48 New Zealand Standard Time INFO mail-api: Logging initialised -> console=INFO, file=logs\mail-api.log (DEBUG, rotating)
|
||||||
|
16/06/2026 07:51:48 New Zealand Standard Time INFO mail-api: Mail API config: version='unknown' timezone='system-default' from='GoodWalk <info@goodwalk.co.nz>' reply_to='info@goodwalk.co.nz' owner='info@goodwalk.co.nz' cp_admins=['info@goodwalk.co.nz'] owner_bcc='' client_bcc='' general_enquiries=False max_attempts=3 form_min=1s form_max=7200s rate_window=900s per_ip=50 per_email=50 min_interval=1s send_timeout=20s
|
||||||
|
16/06/2026 07:51:48 New Zealand Standard Time INFO mail-api: Auth: loaded 3 allowed email(s)
|
||||||
|
16/06/2026 07:51:48 New Zealand Standard Time ERROR mail-api: Startup smoke: WeasyPrint UNAVAILABLE — PDF attachments will be skipped (No module named 'weasyprint')
|
||||||
|
16/06/2026 07:51:48 New Zealand Standard Time WARNING mail-api: Startup smoke: postgres disabled — activity/submissions will NOT be recorded
|
||||||
|
16/06/2026 07:51:48 New Zealand Standard Time INFO mail-api: Startup test email skipped: OWNER_BCC is not set to a real address
|
||||||
|
16/06/2026 07:51:52 New Zealand Standard Time INFO mail-api: Logging initialised -> console=INFO, file=logs\mail-api.log (DEBUG, rotating)
|
||||||
|
16/06/2026 07:51:52 New Zealand Standard Time INFO mail-api: Mail API config: version='unknown' timezone='system-default' from='GoodWalk <info@goodwalk.co.nz>' reply_to='info@goodwalk.co.nz' owner='info@goodwalk.co.nz' cp_admins=['info@goodwalk.co.nz'] owner_bcc='' client_bcc='' general_enquiries=False max_attempts=3 form_min=1s form_max=7200s rate_window=900s per_ip=50 per_email=50 min_interval=1s send_timeout=20s
|
||||||
|
16/06/2026 07:51:53 New Zealand Standard Time INFO mail-api: Auth: loaded 3 allowed email(s)
|
||||||
|
16/06/2026 07:51:53 New Zealand Standard Time ERROR mail-api: Startup smoke: WeasyPrint UNAVAILABLE — PDF attachments will be skipped (No module named 'weasyprint')
|
||||||
|
16/06/2026 07:51:53 New Zealand Standard Time WARNING mail-api: Startup smoke: postgres disabled — activity/submissions will NOT be recorded
|
||||||
|
16/06/2026 07:51:53 New Zealand Standard Time INFO mail-api: Startup test email skipped: OWNER_BCC is not set to a real address
|
||||||
|
16/06/2026 07:51:56 New Zealand Standard Time INFO mail-api: Logging initialised -> console=INFO, file=logs\mail-api.log (DEBUG, rotating)
|
||||||
|
16/06/2026 07:51:56 New Zealand Standard Time INFO mail-api: Mail API config: version='unknown' timezone='system-default' from='GoodWalk <info@goodwalk.co.nz>' reply_to='info@goodwalk.co.nz' owner='info@goodwalk.co.nz' cp_admins=['info@goodwalk.co.nz'] owner_bcc='' client_bcc='' general_enquiries=False max_attempts=3 form_min=1s form_max=7200s rate_window=900s per_ip=50 per_email=50 min_interval=1s send_timeout=20s
|
||||||
|
16/06/2026 07:51:56 New Zealand Standard Time INFO mail-api: Auth: loaded 3 allowed email(s)
|
||||||
|
16/06/2026 07:51:56 New Zealand Standard Time ERROR mail-api: Startup smoke: WeasyPrint UNAVAILABLE — PDF attachments will be skipped (No module named 'weasyprint')
|
||||||
|
16/06/2026 07:51:56 New Zealand Standard Time WARNING mail-api: Startup smoke: postgres disabled — activity/submissions will NOT be recorded
|
||||||
|
16/06/2026 07:51:56 New Zealand Standard Time INFO mail-api: Startup test email skipped: OWNER_BCC is not set to a real address
|
||||||
|
16/06/2026 07:52:00 New Zealand Standard Time INFO mail-api: Logging initialised -> console=INFO, file=logs\mail-api.log (DEBUG, rotating)
|
||||||
|
16/06/2026 07:52:00 New Zealand Standard Time INFO mail-api: Mail API config: version='unknown' timezone='system-default' from='GoodWalk <info@goodwalk.co.nz>' reply_to='info@goodwalk.co.nz' owner='info@goodwalk.co.nz' cp_admins=['info@goodwalk.co.nz'] owner_bcc='' client_bcc='' general_enquiries=False max_attempts=3 form_min=1s form_max=7200s rate_window=900s per_ip=50 per_email=50 min_interval=1s send_timeout=20s
|
||||||
|
16/06/2026 07:52:00 New Zealand Standard Time INFO mail-api: Auth: loaded 3 allowed email(s)
|
||||||
|
16/06/2026 07:52:00 New Zealand Standard Time ERROR mail-api: Startup smoke: WeasyPrint UNAVAILABLE — PDF attachments will be skipped (No module named 'weasyprint')
|
||||||
|
16/06/2026 07:52:00 New Zealand Standard Time WARNING mail-api: Startup smoke: postgres disabled — activity/submissions will NOT be recorded
|
||||||
|
16/06/2026 07:52:00 New Zealand Standard Time INFO mail-api: Startup test email skipped: OWNER_BCC is not set to a real address
|
||||||
|
16/06/2026 07:52:54 New Zealand Standard Time INFO mail-api: [01084365] GET /owner/client-onboarding-view → 401 (3ms)
|
||||||
|
16/06/2026 07:52:55 New Zealand Standard Time INFO mail-api: [117b9eb3] GET /auth/verify → 401 (1ms)
|
||||||
|
16/06/2026 07:52:59 New Zealand Standard Time INFO mail-api: [bee5ac03] auth: code issued for email=info@goodwalk.co.nz
|
||||||
|
16/06/2026 07:52:59 New Zealand Standard Time WARNING mail-api: [DEV] auth code for info@goodwalk.co.nz: 948798
|
||||||
|
16/06/2026 07:52:59 New Zealand Standard Time INFO mail-api: [bee5ac03] POST /auth/request-code → 200 (2ms)
|
||||||
|
16/06/2026 07:53:09 New Zealand Standard Time INFO mail-api: [d782d75d] auth: session created for email=info@goodwalk.co.nz
|
||||||
|
16/06/2026 07:53:09 New Zealand Standard Time INFO mail-api: [d782d75d] POST /auth/verify-code → 200 (3ms)
|
||||||
|
16/06/2026 07:53:09 New Zealand Standard Time INFO mail-api: [a8c2db4a] GET /auth/verify → 200 (0ms)
|
||||||
|
16/06/2026 07:53:09 New Zealand Standard Time INFO mail-api: [a2bc9b29] GET /owner/pending-onboarding → 200 (9ms)
|
||||||
|
16/06/2026 07:53:09 New Zealand Standard Time INFO mail-api: [110111bd] GET /owner/birthdays → 200 (9ms)
|
||||||
|
16/06/2026 07:53:09 New Zealand Standard Time INFO mail-api: [2fe1e0d1] GET /owner/all-clients → 200 (10ms)
|
||||||
|
16/06/2026 07:53:12 New Zealand Standard Time INFO mail-api: [3f755a79] GET /owner/client-onboarding-view → 200 (0ms)
|
||||||
|
17/06/2026 07:22:07 New Zealand Standard Time INFO mail-api: Logging initialised -> console=INFO, file=logs\mail-api.log (DEBUG, rotating)
|
||||||
|
17/06/2026 07:22:07 New Zealand Standard Time INFO mail-api: Mail API config: version='unknown' timezone='system-default' from='GoodWalk <info@goodwalk.co.nz>' reply_to='info@goodwalk.co.nz' owner='dev@localhost' cp_admins=['dev@localhost'] owner_bcc='' client_bcc='' general_enquiries=False max_attempts=3 form_min=1s form_max=7200s rate_window=900s per_ip=50 per_email=50 min_interval=1s send_timeout=20s
|
||||||
|
17/06/2026 07:22:07 New Zealand Standard Time INFO mail-api: MYOB integration: disabled (set MYOB_* env vars to enable)
|
||||||
|
|||||||
Binary file not shown.
Binary file not shown.
Binary file not shown.
@@ -48,7 +48,7 @@ def setup_logging() -> logging.Logger:
|
|||||||
root.addHandler(rotating)
|
root.addHandler(rotating)
|
||||||
|
|
||||||
log = logging.getLogger("mail-api")
|
log = logging.getLogger("mail-api")
|
||||||
log.info("Logging initialised → console=INFO, file=%s (DEBUG, rotating)", log_file)
|
log.info("Logging initialised -> console=INFO, file=%s (DEBUG, rotating)", log_file)
|
||||||
return log
|
return log
|
||||||
|
|
||||||
|
|
||||||
@@ -185,6 +185,10 @@ AUTH_IP_MAX_FAILURES = max(3, int(os.environ.get("AUTH_IP_MAX_FAILURES", "10")))
|
|||||||
AUTH_IP_FAILURE_WINDOW = max(60, int(os.environ.get("AUTH_IP_FAILURE_WINDOW", "600")))
|
AUTH_IP_FAILURE_WINDOW = max(60, int(os.environ.get("AUTH_IP_FAILURE_WINDOW", "600")))
|
||||||
AUTH_IP_BLOCK_DURATION = max(60, int(os.environ.get("AUTH_IP_BLOCK_DURATION", "3600")))
|
AUTH_IP_BLOCK_DURATION = max(60, int(os.environ.get("AUTH_IP_BLOCK_DURATION", "3600")))
|
||||||
BIRTHDAY_CHECK_INTERVAL_SECONDS = max(3600, int(os.environ.get("BIRTHDAY_CHECK_INTERVAL_SECONDS", str(12 * 3600))))
|
BIRTHDAY_CHECK_INTERVAL_SECONDS = max(3600, int(os.environ.get("BIRTHDAY_CHECK_INTERVAL_SECONDS", str(12 * 3600))))
|
||||||
|
# How often the scheduled-email worker wakes to dispatch any sends that have
|
||||||
|
# come due. Scheduled welcome emails need finer granularity than the birthday
|
||||||
|
# sweep, so this defaults to one minute (floored at 15s).
|
||||||
|
SCHEDULED_CHECK_INTERVAL_SECONDS = max(15, int(os.environ.get("SCHEDULED_CHECK_INTERVAL_SECONDS", "60")))
|
||||||
|
|
||||||
|
|
||||||
def _split_csv_env(name: str, default: str) -> tuple[str, ...]:
|
def _split_csv_env(name: str, default: str) -> tuple[str, ...]:
|
||||||
@@ -211,6 +215,10 @@ _DATA_DIR = Path(os.environ.get("DATA_DIR", "data"))
|
|||||||
ALLOWED_EMAILS_FILE = _DATA_DIR / "allowed_emails.json"
|
ALLOWED_EMAILS_FILE = _DATA_DIR / "allowed_emails.json"
|
||||||
CLIENT_PROFILES_FILE = _DATA_DIR / "client_profiles.json"
|
CLIENT_PROFILES_FILE = _DATA_DIR / "client_profiles.json"
|
||||||
DRAFTS_FILE = _DATA_DIR / "drafts.json"
|
DRAFTS_FILE = _DATA_DIR / "drafts.json"
|
||||||
|
# Durable queue of owner-scheduled emails (welcome packs queued for later send).
|
||||||
|
# Mirrors the client_profiles persistence model: postgres admin_kv is canonical
|
||||||
|
# in production, with this JSON file as the dev/local fallback and seed source.
|
||||||
|
SCHEDULED_EMAILS_FILE = _DATA_DIR / "scheduled_emails.json"
|
||||||
# Legacy seed lives OUTSIDE the data volume — it's shipped in the image so a
|
# Legacy seed lives OUTSIDE the data volume — it's shipped in the image so a
|
||||||
# fresh deploy always carries the same baked-in copy. On boot the mail-api
|
# fresh deploy always carries the same baked-in copy. On boot the mail-api
|
||||||
# merges this dict into _client_profiles, adding any emails that aren't
|
# merges this dict into _client_profiles, adding any emails that aren't
|
||||||
@@ -221,6 +229,27 @@ LEGACY_SEED_FILE = Path(
|
|||||||
|
|
||||||
LOGO_URL = "https://www.goodwalk.co.nz/images/goodwalk-auckland-dog-walking-logo.png"
|
LOGO_URL = "https://www.goodwalk.co.nz/images/goodwalk-auckland-dog-walking-logo.png"
|
||||||
|
|
||||||
|
# ── MYOB integration ───────────────────────────────────────────────────────────
|
||||||
|
# Optional: create new clients as customer contacts in MYOB AccountRight Live.
|
||||||
|
# The integration stays disabled until every required value is present, so an
|
||||||
|
# unconfigured environment behaves exactly as before.
|
||||||
|
#
|
||||||
|
# MYOB_API_KEY / MYOB_API_SECRET the developer app's OAuth client id/secret
|
||||||
|
# MYOB_REFRESH_TOKEN long-lived token used to mint access tokens
|
||||||
|
# MYOB_COMPANY_FILE_URI base API URI of the target company file,
|
||||||
|
# e.g. https://api.myob.com/accountright/<guid>
|
||||||
|
# MYOB_CF_USERNAME / MYOB_CF_PASSWORD company-file login (password may be blank)
|
||||||
|
# MYOB_TOKEN_URL OAuth token endpoint (override only for tests)
|
||||||
|
MYOB_API_KEY = (os.environ.get("MYOB_API_KEY") or "").strip()
|
||||||
|
MYOB_API_SECRET = (os.environ.get("MYOB_API_SECRET") or "").strip()
|
||||||
|
MYOB_REFRESH_TOKEN = (os.environ.get("MYOB_REFRESH_TOKEN") or "").strip()
|
||||||
|
MYOB_COMPANY_FILE_URI = (os.environ.get("MYOB_COMPANY_FILE_URI") or "").strip().rstrip("/")
|
||||||
|
MYOB_CF_USERNAME = (os.environ.get("MYOB_CF_USERNAME") or "Administrator").strip()
|
||||||
|
MYOB_CF_PASSWORD = os.environ.get("MYOB_CF_PASSWORD", "")
|
||||||
|
MYOB_TOKEN_URL = (os.environ.get("MYOB_TOKEN_URL") or "https://secure.myob.com/oauth2/v1/authorize").strip()
|
||||||
|
MYOB_HTTP_TIMEOUT_SECONDS = max(5, int(os.environ.get("MYOB_HTTP_TIMEOUT_SECONDS", "20")))
|
||||||
|
MYOB_ENABLED = bool(MYOB_API_KEY and MYOB_API_SECRET and MYOB_REFRESH_TOKEN and MYOB_COMPANY_FILE_URI)
|
||||||
|
|
||||||
# ── Legacy module constants (kept for compatibility with the existing main.py) ──
|
# ── Legacy module constants (kept for compatibility with the existing main.py) ──
|
||||||
|
|
||||||
OWNER_EMAIL = settings.owner_email
|
OWNER_EMAIL = settings.owner_email
|
||||||
@@ -268,3 +297,4 @@ logger.info(
|
|||||||
RATE_LIMIT_MIN_INTERVAL_SECONDS,
|
RATE_LIMIT_MIN_INTERVAL_SECONDS,
|
||||||
EMAIL_SEND_TIMEOUT_SECONDS,
|
EMAIL_SEND_TIMEOUT_SECONDS,
|
||||||
)
|
)
|
||||||
|
logger.info("MYOB integration: %s", "enabled" if MYOB_ENABLED else "disabled (set MYOB_* env vars to enable)")
|
||||||
|
|||||||
@@ -41,9 +41,13 @@ class OnboardingSubmission(BaseSubmission):
|
|||||||
medicalNotes: str = ""
|
medicalNotes: str = ""
|
||||||
accessInstructions: str = ""
|
accessInstructions: str = ""
|
||||||
vetName: str
|
vetName: str
|
||||||
|
vetAddress: str
|
||||||
vetPhone: str
|
vetPhone: str
|
||||||
emergencyContactName: str
|
emergencyContactName: str
|
||||||
emergencyContactPhone: str
|
emergencyContactPhone: str
|
||||||
|
regularFleaTickTreatment: str = ""
|
||||||
|
petInsurance: str = ""
|
||||||
|
petInsuranceOwnerExpenseAccepted: bool = False
|
||||||
councilRegistrationConfirmed: bool = False
|
councilRegistrationConfirmed: bool = False
|
||||||
vaccinationsConfirmed: bool = False
|
vaccinationsConfirmed: bool = False
|
||||||
emergencyVetConsent: bool = False
|
emergencyVetConsent: bool = False
|
||||||
@@ -54,15 +58,33 @@ class OnboardingSubmission(BaseSubmission):
|
|||||||
|
|
||||||
class WelcomePackEmailRequest(BaseModel):
|
class WelcomePackEmailRequest(BaseModel):
|
||||||
email: EmailStr
|
email: EmailStr
|
||||||
|
# Owner-set subject line. Defaults to "Goodwalk Onboarding - <Dog Name>" when
|
||||||
|
# blank (resolved server-side so scheduled sends keep a sensible subject too).
|
||||||
|
subject: str = ""
|
||||||
serviceType: str
|
serviceType: str
|
||||||
priceDetails: str
|
priceDetails: str
|
||||||
startDate: str
|
startDate: str
|
||||||
preview: bool = False
|
preview: bool = False
|
||||||
|
previewRecipients: list[EmailStr] = []
|
||||||
|
# ISO 8601 local datetime (e.g. "2026-06-20T14:30"). When set on a non-preview
|
||||||
|
# request, the welcome email is queued for reliable delivery at that time
|
||||||
|
# instead of being sent immediately.
|
||||||
|
scheduledFor: str | None = None
|
||||||
|
|
||||||
|
|
||||||
|
class ScheduledEmailCancelRequest(BaseModel):
|
||||||
|
id: str
|
||||||
|
|
||||||
|
|
||||||
|
class ScheduledEmailRescheduleRequest(BaseModel):
|
||||||
|
id: str
|
||||||
|
scheduledFor: str
|
||||||
|
|
||||||
|
|
||||||
class BirthdayEmailRequest(BaseModel):
|
class BirthdayEmailRequest(BaseModel):
|
||||||
email: EmailStr
|
email: EmailStr
|
||||||
preview: bool = False
|
preview: bool = False
|
||||||
|
previewRecipients: list[EmailStr] = []
|
||||||
|
|
||||||
|
|
||||||
class BirthdayAutoSendRequest(BaseModel):
|
class BirthdayAutoSendRequest(BaseModel):
|
||||||
@@ -81,6 +103,42 @@ class ClientStatusUpdate(BaseModel):
|
|||||||
reason: str = ""
|
reason: str = ""
|
||||||
|
|
||||||
|
|
||||||
|
class ClientProfileUpdate(BaseModel):
|
||||||
|
email: EmailStr
|
||||||
|
nextEmail: EmailStr
|
||||||
|
fullName: str
|
||||||
|
phone: str = ""
|
||||||
|
address: str = ""
|
||||||
|
dogName: str
|
||||||
|
|
||||||
|
|
||||||
|
class ResetOnboardingRequest(BaseModel):
|
||||||
|
"""Owner-initiated reset: keep the client's saved details but mark their
|
||||||
|
onboarding incomplete so they can sign in and complete the new form
|
||||||
|
(used for clients imported from the legacy Gravity Forms data)."""
|
||||||
|
|
||||||
|
email: EmailStr
|
||||||
|
|
||||||
|
|
||||||
|
class NewClientRequest(BaseModel):
|
||||||
|
"""Owner-initiated client creation (e.g. leads from Instagram / Facebook
|
||||||
|
that never came through the public enquiry form). Registers the email so
|
||||||
|
the client can access the onboarding form, and seeds a profile."""
|
||||||
|
|
||||||
|
email: EmailStr
|
||||||
|
fullName: str
|
||||||
|
phone: str = ""
|
||||||
|
address: str = ""
|
||||||
|
dogName: str = ""
|
||||||
|
dogBreed: str = ""
|
||||||
|
# Where the client came from, e.g. "Instagram", "Facebook", "Referral".
|
||||||
|
source: str = ""
|
||||||
|
# When true (and the MYOB integration is configured), also create the client
|
||||||
|
# as a customer contact in MYOB. Non-fatal: a MYOB failure never blocks the
|
||||||
|
# local client from being created.
|
||||||
|
createInMyob: bool = False
|
||||||
|
|
||||||
|
|
||||||
class ContractSubmission(BaseSubmission):
|
class ContractSubmission(BaseSubmission):
|
||||||
address: str
|
address: str
|
||||||
dogName: str
|
dogName: str
|
||||||
@@ -126,3 +184,4 @@ class SendMessageRequest(BaseModel):
|
|||||||
fontId: str = "system"
|
fontId: str = "system"
|
||||||
recipients: list[EmailStr] = []
|
recipients: list[EmailStr] = []
|
||||||
preview: bool = False
|
preview: bool = False
|
||||||
|
previewRecipients: list[EmailStr] = []
|
||||||
|
|||||||
@@ -0,0 +1,212 @@
|
|||||||
|
"""MYOB AccountRight Live integration — create customer contacts.
|
||||||
|
|
||||||
|
Used when the owner ticks "Also create in MYOB" while adding a new client in the
|
||||||
|
control panel. The integration is optional: when the required environment
|
||||||
|
variables are not set, :func:`is_configured` returns ``False`` and the caller
|
||||||
|
skips MYOB entirely (the local client is still created).
|
||||||
|
|
||||||
|
Auth model (MYOB OAuth 2.0):
|
||||||
|
* A developer key/secret (``client_id`` / ``client_secret``) identifies this
|
||||||
|
app to MYOB.
|
||||||
|
* A long-lived refresh token mints short-lived bearer access tokens. We cache
|
||||||
|
the access token in memory and refresh it shortly before it expires.
|
||||||
|
* Each AccountRight company file lives at its own base URI and is gated by a
|
||||||
|
company-file token: ``base64("username:password")`` sent as the
|
||||||
|
``x-myobapi-cftoken`` header (password may be blank).
|
||||||
|
|
||||||
|
All HTTP calls are synchronous (``requests``); the async app invokes the public
|
||||||
|
helpers via ``asyncio.to_thread`` so the event loop is never blocked.
|
||||||
|
"""
|
||||||
|
|
||||||
|
from __future__ import annotations
|
||||||
|
|
||||||
|
import base64
|
||||||
|
import threading
|
||||||
|
import time
|
||||||
|
|
||||||
|
import requests
|
||||||
|
|
||||||
|
from mail_api.config import (
|
||||||
|
MYOB_API_KEY,
|
||||||
|
MYOB_API_SECRET,
|
||||||
|
MYOB_CF_PASSWORD,
|
||||||
|
MYOB_CF_USERNAME,
|
||||||
|
MYOB_COMPANY_FILE_URI,
|
||||||
|
MYOB_ENABLED,
|
||||||
|
MYOB_HTTP_TIMEOUT_SECONDS,
|
||||||
|
MYOB_REFRESH_TOKEN,
|
||||||
|
MYOB_TOKEN_URL,
|
||||||
|
logger,
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
|
class MyobError(Exception):
|
||||||
|
"""Raised when a MYOB API call cannot be completed."""
|
||||||
|
|
||||||
|
|
||||||
|
# Access tokens are short-lived (MYOB returns ~20 min). Cache the current token
|
||||||
|
# and its expiry so back-to-back client creations reuse a single refresh.
|
||||||
|
_token_lock = threading.Lock()
|
||||||
|
_cached_token: str = ""
|
||||||
|
_cached_token_expires_at: float = 0.0
|
||||||
|
# Refresh a little early so a token never expires mid-request.
|
||||||
|
_TOKEN_EXPIRY_SKEW_SECONDS = 60
|
||||||
|
|
||||||
|
|
||||||
|
def is_configured() -> bool:
|
||||||
|
"""True when every credential needed to talk to MYOB is present."""
|
||||||
|
return MYOB_ENABLED
|
||||||
|
|
||||||
|
|
||||||
|
def _cf_token() -> str:
|
||||||
|
raw = f"{MYOB_CF_USERNAME}:{MYOB_CF_PASSWORD}".encode("utf-8")
|
||||||
|
return base64.b64encode(raw).decode("ascii")
|
||||||
|
|
||||||
|
|
||||||
|
def _refresh_access_token() -> str:
|
||||||
|
"""Exchange the stored refresh token for a fresh access token.
|
||||||
|
|
||||||
|
Caches the result in module state. Raises :class:`MyobError` on failure.
|
||||||
|
"""
|
||||||
|
global _cached_token, _cached_token_expires_at
|
||||||
|
|
||||||
|
try:
|
||||||
|
resp = requests.post(
|
||||||
|
MYOB_TOKEN_URL,
|
||||||
|
data={
|
||||||
|
"client_id": MYOB_API_KEY,
|
||||||
|
"client_secret": MYOB_API_SECRET,
|
||||||
|
"grant_type": "refresh_token",
|
||||||
|
"refresh_token": MYOB_REFRESH_TOKEN,
|
||||||
|
},
|
||||||
|
headers={"Content-Type": "application/x-www-form-urlencoded"},
|
||||||
|
timeout=MYOB_HTTP_TIMEOUT_SECONDS,
|
||||||
|
)
|
||||||
|
except requests.RequestException as exc:
|
||||||
|
raise MyobError(f"Could not reach the MYOB token service: {exc}") from exc
|
||||||
|
|
||||||
|
if resp.status_code != 200:
|
||||||
|
raise MyobError(
|
||||||
|
f"MYOB token refresh failed (HTTP {resp.status_code}). "
|
||||||
|
"Check MYOB_API_KEY / MYOB_API_SECRET / MYOB_REFRESH_TOKEN."
|
||||||
|
)
|
||||||
|
|
||||||
|
try:
|
||||||
|
payload = resp.json()
|
||||||
|
except ValueError as exc:
|
||||||
|
raise MyobError("MYOB token service returned an unreadable response.") from exc
|
||||||
|
|
||||||
|
token = str(payload.get("access_token") or "").strip()
|
||||||
|
if not token:
|
||||||
|
raise MyobError("MYOB token service did not return an access token.")
|
||||||
|
|
||||||
|
try:
|
||||||
|
expires_in = int(payload.get("expires_in", 1200))
|
||||||
|
except (TypeError, ValueError):
|
||||||
|
expires_in = 1200
|
||||||
|
|
||||||
|
_cached_token = token
|
||||||
|
_cached_token_expires_at = time.monotonic() + max(0, expires_in - _TOKEN_EXPIRY_SKEW_SECONDS)
|
||||||
|
logger.info("MYOB: access token refreshed (valid ~%ds)", expires_in)
|
||||||
|
return token
|
||||||
|
|
||||||
|
|
||||||
|
def _access_token() -> str:
|
||||||
|
with _token_lock:
|
||||||
|
if _cached_token and time.monotonic() < _cached_token_expires_at:
|
||||||
|
return _cached_token
|
||||||
|
return _refresh_access_token()
|
||||||
|
|
||||||
|
|
||||||
|
def _api_headers() -> dict[str, str]:
|
||||||
|
return {
|
||||||
|
"Authorization": f"Bearer {_access_token()}",
|
||||||
|
"x-myobapi-key": MYOB_API_KEY,
|
||||||
|
"x-myobapi-version": "v2",
|
||||||
|
"x-myobapi-cftoken": _cf_token(),
|
||||||
|
"Content-Type": "application/json",
|
||||||
|
"Accept": "application/json",
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
def _split_name(full_name: str) -> tuple[str, str]:
|
||||||
|
"""Best-effort split of a single owner name into first/last for MYOB."""
|
||||||
|
parts = full_name.split()
|
||||||
|
if not parts:
|
||||||
|
return ("", "")
|
||||||
|
if len(parts) == 1:
|
||||||
|
return (parts[0], "")
|
||||||
|
return (parts[0], " ".join(parts[1:]))
|
||||||
|
|
||||||
|
|
||||||
|
def create_customer(
|
||||||
|
*,
|
||||||
|
email: str,
|
||||||
|
full_name: str,
|
||||||
|
phone: str = "",
|
||||||
|
street: str = "",
|
||||||
|
source: str = "",
|
||||||
|
) -> dict[str, str]:
|
||||||
|
"""Create an individual customer contact in MYOB.
|
||||||
|
|
||||||
|
Returns ``{"uid": ..., "uri": ...}`` parsed from the ``Location`` header MYOB
|
||||||
|
returns on a successful create. Raises :class:`MyobError` on any failure so
|
||||||
|
the caller can record it without aborting the local client creation.
|
||||||
|
"""
|
||||||
|
if not is_configured():
|
||||||
|
raise MyobError("MYOB integration is not configured.")
|
||||||
|
|
||||||
|
first_name, last_name = _split_name(full_name)
|
||||||
|
body = {
|
||||||
|
"IsIndividual": True,
|
||||||
|
"FirstName": first_name,
|
||||||
|
"LastName": last_name,
|
||||||
|
"IsActive": True,
|
||||||
|
"Addresses": [
|
||||||
|
{
|
||||||
|
"Location": 1,
|
||||||
|
"Email": email,
|
||||||
|
"Phone1": phone,
|
||||||
|
"Street": street,
|
||||||
|
}
|
||||||
|
],
|
||||||
|
}
|
||||||
|
if source:
|
||||||
|
body["Notes"] = f"Added via Goodwalk control panel — source: {source}"
|
||||||
|
|
||||||
|
url = f"{MYOB_COMPANY_FILE_URI}/Contact/Customer"
|
||||||
|
try:
|
||||||
|
resp = requests.post(
|
||||||
|
url,
|
||||||
|
json=body,
|
||||||
|
headers=_api_headers(),
|
||||||
|
timeout=MYOB_HTTP_TIMEOUT_SECONDS,
|
||||||
|
)
|
||||||
|
except requests.RequestException as exc:
|
||||||
|
raise MyobError(f"Could not reach MYOB: {exc}") from exc
|
||||||
|
|
||||||
|
# 200/201 both indicate success depending on API version; MYOB returns the
|
||||||
|
# new record's URI in the Location header.
|
||||||
|
if resp.status_code not in (200, 201):
|
||||||
|
detail = _error_detail(resp)
|
||||||
|
raise MyobError(f"MYOB rejected the customer (HTTP {resp.status_code}): {detail}")
|
||||||
|
|
||||||
|
location = resp.headers.get("Location", "")
|
||||||
|
uid = location.rstrip("/").rsplit("/", 1)[-1] if location else ""
|
||||||
|
logger.info("MYOB: created customer uid=%s for %s", uid or "?", email)
|
||||||
|
return {"uid": uid, "uri": location}
|
||||||
|
|
||||||
|
|
||||||
|
def _error_detail(resp: requests.Response) -> str:
|
||||||
|
"""Extract a human-readable error message from a MYOB error response."""
|
||||||
|
try:
|
||||||
|
data = resp.json()
|
||||||
|
except ValueError:
|
||||||
|
return (resp.text or "").strip()[:300] or "no detail provided"
|
||||||
|
errors = data.get("Errors") if isinstance(data, dict) else None
|
||||||
|
if isinstance(errors, list) and errors:
|
||||||
|
messages = [str(e.get("Message", "")).strip() for e in errors if isinstance(e, dict)]
|
||||||
|
joined = "; ".join(m for m in messages if m)
|
||||||
|
if joined:
|
||||||
|
return joined
|
||||||
|
return str(data)[:300]
|
||||||
+910
-66
File diff suppressed because it is too large
Load Diff
@@ -4,3 +4,4 @@ resend>=2.0
|
|||||||
pydantic[email]>=2.10
|
pydantic[email]>=2.10
|
||||||
asyncpg>=0.30
|
asyncpg>=0.30
|
||||||
weasyprint>=63
|
weasyprint>=63
|
||||||
|
requests>=2.32
|
||||||
|
|||||||
@@ -0,0 +1,15 @@
|
|||||||
|
$ErrorActionPreference = 'Stop'
|
||||||
|
|
||||||
|
Set-Location $PSScriptRoot
|
||||||
|
|
||||||
|
$env:DEV_MODE = '1'
|
||||||
|
if (-not $env:OWNER_EMAIL) {
|
||||||
|
$env:OWNER_EMAIL = 'info@goodwalk.co.nz'
|
||||||
|
}
|
||||||
|
|
||||||
|
$python = Join-Path $PSScriptRoot '.venv\Scripts\python.exe'
|
||||||
|
if (-not (Test-Path $python)) {
|
||||||
|
throw "Missing virtualenv interpreter at $python. Create it first with: py -3.14 -m venv .venv"
|
||||||
|
}
|
||||||
|
|
||||||
|
& $python -m uvicorn main:app --host 127.0.0.1 --port 8000 --reload
|
||||||
+54
-132
@@ -4,14 +4,19 @@
|
|||||||
<meta charset="utf-8" />
|
<meta charset="utf-8" />
|
||||||
<meta name="viewport" content="width=device-width, initial-scale=1" />
|
<meta name="viewport" content="width=device-width, initial-scale=1" />
|
||||||
<meta name="robots" content="noindex" />
|
<meta name="robots" content="noindex" />
|
||||||
<title>Be right back | Goodwalk</title>
|
<title>Back soon | Goodwalk</title>
|
||||||
|
<link rel="preconnect" href="https://fonts.googleapis.com" />
|
||||||
|
<link rel="preconnect" href="https://fonts.gstatic.com" crossorigin />
|
||||||
|
<link
|
||||||
|
href="https://fonts.googleapis.com/css2?family=Readex+Pro:wght@400;500&family=Unbounded:wght@700;800&display=swap"
|
||||||
|
rel="stylesheet"
|
||||||
|
/>
|
||||||
<style>
|
<style>
|
||||||
:root {
|
:root {
|
||||||
--green: #213021;
|
--green: #213021;
|
||||||
--green-soft: #2c3f2c;
|
|
||||||
--yellow: #ffd100;
|
--yellow: #ffd100;
|
||||||
--ink: #1a1a1a;
|
--off-white: #f8f7f2;
|
||||||
--muted: #4c5056;
|
--line: rgba(255, 255, 255, 0.16);
|
||||||
}
|
}
|
||||||
|
|
||||||
* {
|
* {
|
||||||
@@ -21,166 +26,83 @@
|
|||||||
html,
|
html,
|
||||||
body {
|
body {
|
||||||
margin: 0;
|
margin: 0;
|
||||||
padding: 0;
|
min-height: 100%;
|
||||||
height: 100%;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
body {
|
body {
|
||||||
font-family: ui-sans-serif, system-ui, -apple-system, "Segoe UI", Roboto, "Helvetica Neue", Arial, sans-serif;
|
|
||||||
color: var(--ink);
|
|
||||||
background:
|
|
||||||
radial-gradient(circle at top left, rgba(255, 209, 0, 0.12), transparent 55%),
|
|
||||||
radial-gradient(circle at bottom right, rgba(255, 209, 0, 0.08), transparent 60%),
|
|
||||||
var(--green);
|
|
||||||
min-height: 100vh;
|
|
||||||
display: grid;
|
display: grid;
|
||||||
place-items: center;
|
place-items: center;
|
||||||
padding: 32px;
|
padding: 24px;
|
||||||
line-height: 1.5;
|
background: var(--green);
|
||||||
|
color: var(--off-white);
|
||||||
|
font-family: "Readex Pro", -apple-system, BlinkMacSystemFont, "Segoe UI", Arial, sans-serif;
|
||||||
|
line-height: 1.6;
|
||||||
}
|
}
|
||||||
|
|
||||||
.stage {
|
main {
|
||||||
width: 100%;
|
width: min(100%, 30rem);
|
||||||
max-width: 560px;
|
|
||||||
}
|
|
||||||
|
|
||||||
.brand-bar {
|
|
||||||
display: flex;
|
|
||||||
justify-content: center;
|
|
||||||
margin-bottom: 28px;
|
|
||||||
}
|
|
||||||
|
|
||||||
.brand-bar img {
|
|
||||||
display: block;
|
|
||||||
height: 44px;
|
|
||||||
width: auto;
|
|
||||||
max-width: 100%;
|
|
||||||
filter: brightness(0) invert(1);
|
|
||||||
}
|
|
||||||
|
|
||||||
.card {
|
|
||||||
position: relative;
|
|
||||||
background: #fff;
|
|
||||||
border-radius: 28px;
|
|
||||||
padding: 44px 40px 38px;
|
|
||||||
box-shadow: 0 24px 60px rgba(0, 0, 0, 0.25);
|
|
||||||
text-align: center;
|
text-align: center;
|
||||||
overflow: hidden;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
.card::before {
|
.logo {
|
||||||
content: "";
|
height: 36px;
|
||||||
position: absolute;
|
width: auto;
|
||||||
inset: 0 0 auto 0;
|
margin-bottom: 40px;
|
||||||
height: 6px;
|
|
||||||
background: var(--yellow);
|
|
||||||
}
|
|
||||||
|
|
||||||
.paw {
|
|
||||||
display: inline-flex;
|
|
||||||
align-items: center;
|
|
||||||
justify-content: center;
|
|
||||||
width: 64px;
|
|
||||||
height: 64px;
|
|
||||||
margin: 4px auto 18px;
|
|
||||||
border-radius: 50%;
|
|
||||||
background: rgba(33, 48, 33, 0.08);
|
|
||||||
color: var(--green);
|
|
||||||
}
|
|
||||||
|
|
||||||
.paw svg {
|
|
||||||
width: 32px;
|
|
||||||
height: 32px;
|
|
||||||
}
|
|
||||||
|
|
||||||
.eyebrow {
|
|
||||||
font-size: 12px;
|
|
||||||
font-weight: 700;
|
|
||||||
letter-spacing: 0.18em;
|
|
||||||
text-transform: uppercase;
|
|
||||||
color: var(--green);
|
|
||||||
margin: 0 0 10px;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
h1 {
|
h1 {
|
||||||
margin: 0 0 14px;
|
margin: 0 0 16px;
|
||||||
font-size: clamp(28px, 4vw, 38px);
|
font-family: "Unbounded", sans-serif;
|
||||||
|
font-size: clamp(1.6rem, 5vw, 2.1rem);
|
||||||
|
font-weight: 800;
|
||||||
line-height: 1.1;
|
line-height: 1.1;
|
||||||
letter-spacing: -0.02em;
|
letter-spacing: -0.03em;
|
||||||
color: var(--ink);
|
color: var(--off-white);
|
||||||
}
|
}
|
||||||
|
|
||||||
p {
|
p {
|
||||||
margin: 0 0 12px;
|
margin: 0 auto;
|
||||||
color: var(--muted);
|
max-width: 26rem;
|
||||||
font-size: 16px;
|
color: rgba(248, 247, 242, 0.82);
|
||||||
|
font-size: 1rem;
|
||||||
}
|
}
|
||||||
|
|
||||||
.meta {
|
.contact {
|
||||||
margin-top: 24px;
|
margin-top: 32px;
|
||||||
padding-top: 22px;
|
padding-top: 28px;
|
||||||
border-top: 1px solid rgba(17, 20, 24, 0.08);
|
border-top: 1px solid var(--line);
|
||||||
font-size: 14px;
|
font-size: 0.95rem;
|
||||||
color: var(--muted);
|
color: rgba(248, 247, 242, 0.7);
|
||||||
}
|
}
|
||||||
|
|
||||||
.meta a {
|
.contact a {
|
||||||
color: var(--green);
|
color: var(--yellow);
|
||||||
font-weight: 700;
|
font-weight: 500;
|
||||||
text-decoration: none;
|
text-decoration: none;
|
||||||
|
border-bottom: 1px solid rgba(255, 209, 0, 0.4);
|
||||||
|
padding-bottom: 1px;
|
||||||
}
|
}
|
||||||
|
|
||||||
.meta a:hover {
|
.contact a:hover {
|
||||||
text-decoration: underline;
|
border-bottom-color: var(--yellow);
|
||||||
}
|
|
||||||
|
|
||||||
.footnote {
|
|
||||||
margin-top: 22px;
|
|
||||||
text-align: center;
|
|
||||||
font-size: 13px;
|
|
||||||
color: rgba(255, 255, 255, 0.7);
|
|
||||||
}
|
|
||||||
|
|
||||||
@media (max-width: 480px) {
|
|
||||||
.card {
|
|
||||||
padding: 36px 24px 28px;
|
|
||||||
border-radius: 22px;
|
|
||||||
}
|
|
||||||
|
|
||||||
.brand-bar img {
|
|
||||||
height: 36px;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
</style>
|
</style>
|
||||||
</head>
|
</head>
|
||||||
<body>
|
<body>
|
||||||
<div class="stage">
|
<main>
|
||||||
<div class="brand-bar">
|
<img class="logo" src="/m/logo.png" alt="Goodwalk" width="241" height="48" />
|
||||||
<img src="/m/logo.png" alt="Goodwalk" width="241" height="48" />
|
<h1>Back on the lead shortly</h1>
|
||||||
</div>
|
<p>
|
||||||
|
We’re making a few quick updates. The site will be back to normal in a moment — thanks for your patience.
|
||||||
<main class="card" role="main">
|
</p>
|
||||||
<span class="paw" aria-hidden="true">
|
<p class="contact">
|
||||||
<svg viewBox="0 0 24 24" fill="currentColor" xmlns="http://www.w3.org/2000/svg">
|
Need us now? Email
|
||||||
<path d="M7.5 10.5c1.1 0 2-1.3 2-3s-.9-3-2-3-2 1.3-2 3 .9 3 2 3Zm9 0c1.1 0 2-1.3 2-3s-.9-3-2-3-2 1.3-2 3 .9 3 2 3ZM4 13.5c1 0 1.8-1.1 1.8-2.5S5 8.5 4 8.5s-1.8 1.1-1.8 2.5S3 13.5 4 13.5Zm16 0c1 0 1.8-1.1 1.8-2.5S21 8.5 20 8.5s-1.8 1.1-1.8 2.5.8 2.5 1.8 2.5ZM12 13.5c-3.3 0-6 2.5-6 5.4 0 1.4 1.1 2.6 2.5 2.6 1 0 1.5-.4 2.2-.8.4-.2.8-.4 1.3-.4s.9.2 1.3.4c.7.4 1.2.8 2.2.8 1.4 0 2.5-1.2 2.5-2.6 0-2.9-2.7-5.4-6-5.4Z"/>
|
<a href="mailto:info@goodwalk.co.nz">info@goodwalk.co.nz</a>
|
||||||
</svg>
|
|
||||||
</span>
|
|
||||||
|
|
||||||
<p class="eyebrow">Goodwalk</p>
|
|
||||||
<h1>Be right back!</h1>
|
|
||||||
<p>We're updating the site — should only take a minute. Thanks for your patience.</p>
|
|
||||||
|
|
||||||
<div class="meta">
|
|
||||||
Need us now? Email <a href="mailto:info@goodwalk.co.nz">info@goodwalk.co.nz</a>
|
|
||||||
or call <a href="tel:+64226421011">(022) 642 1011</a>.
|
or call <a href="tel:+64226421011">(022) 642 1011</a>.
|
||||||
</div>
|
</p>
|
||||||
</main>
|
</main>
|
||||||
|
|
||||||
<p class="footnote">Auckland Central dog walking · Tiny Gang pack walks · 1:1 walks · Puppy visits</p>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<script>
|
<script>
|
||||||
// Auto-recheck once a minute so visitors don't sit on this page forever.
|
|
||||||
setTimeout(function () {
|
setTimeout(function () {
|
||||||
window.location.reload();
|
window.location.reload();
|
||||||
}, 60000);
|
}, 60000);
|
||||||
|
|||||||
+1
-1
@@ -1,6 +1,6 @@
|
|||||||
{
|
{
|
||||||
"name": "gw-svelte",
|
"name": "gw-svelte",
|
||||||
"version": "4.0.1",
|
"version": "4.0.5",
|
||||||
"private": true,
|
"private": true,
|
||||||
"type": "module",
|
"type": "module",
|
||||||
"scripts": {
|
"scripts": {
|
||||||
|
|||||||
@@ -277,6 +277,17 @@ if [[ -f "$DEPLOY_PATH/.env" ]]; then
|
|||||||
echo "[deploy-remote] Preserving existing $DEPLOY_PATH/.env"
|
echo "[deploy-remote] Preserving existing $DEPLOY_PATH/.env"
|
||||||
fi
|
fi
|
||||||
|
|
||||||
|
# The application source tree is fully owned by the repo and is only used as
|
||||||
|
# Docker build context — it holds no runtime data. The rsync below uses no
|
||||||
|
# --delete, so a file removed or renamed in the repo (e.g. a moved SvelteKit
|
||||||
|
# route) would otherwise linger here and break the build with a route conflict.
|
||||||
|
# Wipe src/ first so it is rebuilt exactly from the archive. Runtime data,
|
||||||
|
# .env, and Docker volumes live outside src/ and are untouched.
|
||||||
|
if [[ -d "$DEPLOY_PATH/src" ]]; then
|
||||||
|
echo "[deploy-remote] Refreshing $DEPLOY_PATH/src from archive (pruning stale files)"
|
||||||
|
rm -rf "$DEPLOY_PATH/src"
|
||||||
|
fi
|
||||||
|
|
||||||
echo "[deploy-remote] Copying application files into $DEPLOY_PATH"
|
echo "[deploy-remote] Copying application files into $DEPLOY_PATH"
|
||||||
if command -v rsync >/dev/null 2>&1; then
|
if command -v rsync >/dev/null 2>&1; then
|
||||||
rsync -a \
|
rsync -a \
|
||||||
|
|||||||
+7
-2
@@ -1,4 +1,5 @@
|
|||||||
import type { Handle } from '@sveltejs/kit';
|
import type { Handle } from '@sveltejs/kit';
|
||||||
|
import { dev } from '$app/environment';
|
||||||
import { resolveSurface } from '$lib/server/surface';
|
import { resolveSurface } from '$lib/server/surface';
|
||||||
import { resolveAnonId, resolveHeroVariant } from '$lib/server/ab';
|
import { resolveAnonId, resolveHeroVariant } from '$lib/server/ab';
|
||||||
|
|
||||||
@@ -16,8 +17,12 @@ export const handle: Handle = async ({ event, resolve }) => {
|
|||||||
event.locals.abHero = resolveHeroVariant(event.url, event.cookies);
|
event.locals.abHero = resolveHeroVariant(event.url, event.cookies);
|
||||||
}
|
}
|
||||||
|
|
||||||
// The admin host (cp.*) serves the dashboard at its root.
|
// The admin host (cp.*) serves the dashboard at its root. In production the
|
||||||
if (surface === 'cp' && (path === '/' || path === '')) {
|
// universal `reroute` hook maps / (and /clients, /birthdays, …) onto the
|
||||||
|
// internal /owner/welcome routes without changing the URL. In dev the cp
|
||||||
|
// surface is reached via ?preview=cp on localhost, where reroute can't detect
|
||||||
|
// the host, so fall back to redirecting the root to the dashboard path.
|
||||||
|
if (dev && surface === 'cp' && (path === '/' || path === '')) {
|
||||||
return new Response(null, {
|
return new Response(null, {
|
||||||
status: 302,
|
status: 302,
|
||||||
headers: { location: ADMIN_PATH },
|
headers: { location: ADMIN_PATH },
|
||||||
|
|||||||
@@ -0,0 +1,31 @@
|
|||||||
|
import type { Reroute } from '@sveltejs/kit';
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Clean URLs for the control panel.
|
||||||
|
*
|
||||||
|
* The owner dashboard lives internally at /owner/welcome (home) and
|
||||||
|
* /owner/welcome/<tab>. On the cp.* / admin.* hosts we serve it at the root so
|
||||||
|
* the visible URLs are /, /clients, /birthdays, /messaging, /scheduled and
|
||||||
|
* /activity. This hook rewrites those clean paths to the real route without
|
||||||
|
* changing the URL in the browser, and runs on both the server and the client
|
||||||
|
* so SSR and client-side navigation stay in sync.
|
||||||
|
*
|
||||||
|
* Keep CP_HOSTS in sync with $lib/server/surface.ts — it can't be imported here
|
||||||
|
* because this hook also runs in the browser and $lib/server is server-only.
|
||||||
|
*/
|
||||||
|
const CP_HOSTS = new Set(['cp.goodwalk.co.nz', 'admin.goodwalk.co.nz']);
|
||||||
|
const ADMIN_BASE = '/owner/welcome';
|
||||||
|
const CP_TABS = new Set(['clients', 'birthdays', 'messaging', 'scheduled', 'activity']);
|
||||||
|
|
||||||
|
export const reroute: Reroute = ({ url }) => {
|
||||||
|
if (!CP_HOSTS.has(url.hostname.toLowerCase())) return;
|
||||||
|
|
||||||
|
// Already addressing the internal admin routes (e.g. a direct hit) — leave it.
|
||||||
|
if (url.pathname === ADMIN_BASE || url.pathname.startsWith(`${ADMIN_BASE}/`)) return;
|
||||||
|
|
||||||
|
const segment = url.pathname.replace(/^\/+/, '').split('/')[0];
|
||||||
|
if (segment === '') return ADMIN_BASE; // / → dashboard home
|
||||||
|
if (CP_TABS.has(segment)) return `${ADMIN_BASE}/${segment}`;
|
||||||
|
|
||||||
|
// Anything else (static assets, /owner/experiments, …) routes normally.
|
||||||
|
};
|
||||||
File diff suppressed because it is too large
Load Diff
@@ -1,9 +1,9 @@
|
|||||||
<script lang="ts">
|
<script lang="ts">
|
||||||
import { accordion } from '$lib/actions/accordion';
|
import { accordion } from '$lib/actions/accordion';
|
||||||
import { reveal } from '$lib/actions/reveal';
|
import { reveal } from '$lib/actions/reveal';
|
||||||
import CtaCard from '$lib/components/CtaCard.svelte';
|
import CtaCard from '$lib/components/ui/CtaCard.svelte';
|
||||||
import Icon from '$lib/components/Icon.svelte';
|
import Icon from '$lib/components/ui/Icon.svelte';
|
||||||
import ServiceHero from '$lib/components/ServiceHero.svelte';
|
import ServiceHero from '$lib/components/sections/ServiceHero.svelte';
|
||||||
import { getEnhancedImage } from '$lib/enhanced-images';
|
import { getEnhancedImage } from '$lib/enhanced-images';
|
||||||
import type { AboutPageContent } from '$lib/types';
|
import type { AboutPageContent } from '$lib/types';
|
||||||
|
|
||||||
@@ -1,8 +1,8 @@
|
|||||||
<script lang="ts">
|
<script lang="ts">
|
||||||
import BookingWizard from '$lib/components/BookingWizard.svelte';
|
import BookingWizard from '$lib/components/sections/BookingWizard.svelte';
|
||||||
import Icon from '$lib/components/Icon.svelte';
|
import Icon from '$lib/components/ui/Icon.svelte';
|
||||||
import InfoSection from '$lib/components/InfoSection.svelte';
|
import InfoSection from '$lib/components/sections/InfoSection.svelte';
|
||||||
import ServiceHero from '$lib/components/ServiceHero.svelte';
|
import ServiceHero from '$lib/components/sections/ServiceHero.svelte';
|
||||||
import type { BookingContent, InfoContent } from '$lib/types';
|
import type { BookingContent, InfoContent } from '$lib/types';
|
||||||
|
|
||||||
export let booking: BookingContent;
|
export let booking: BookingContent;
|
||||||
+4
-4
@@ -1,9 +1,9 @@
|
|||||||
<script lang="ts">
|
<script lang="ts">
|
||||||
import { onMount } from 'svelte';
|
import { onMount } from 'svelte';
|
||||||
import Icon from '$lib/components/Icon.svelte';
|
import Icon from '$lib/components/ui/Icon.svelte';
|
||||||
import OnboardingSignaturePad from '$lib/components/OnboardingSignaturePad.svelte';
|
import OnboardingSignaturePad from '$lib/components/ui/OnboardingSignaturePad.svelte';
|
||||||
import OnboardingAuth from '$lib/components/OnboardingAuth.svelte';
|
import OnboardingAuth from '$lib/components/sections/OnboardingAuth.svelte';
|
||||||
import OnboardingFooter from '$lib/components/OnboardingFooter.svelte';
|
import OnboardingFooter from '$lib/components/ui/OnboardingFooter.svelte';
|
||||||
import logoDesktop from '$lib/images/goodwalk-auckland-dog-walking-logo.png?enhanced';
|
import logoDesktop from '$lib/images/goodwalk-auckland-dog-walking-logo.png?enhanced';
|
||||||
import type { Picture } from '@sveltejs/enhanced-img';
|
import type { Picture } from '@sveltejs/enhanced-img';
|
||||||
|
|
||||||
+2
-2
@@ -1,8 +1,8 @@
|
|||||||
<script lang="ts">
|
<script lang="ts">
|
||||||
import { sharedServices } from '$lib/content/services';
|
import { sharedServices } from '$lib/content/services';
|
||||||
import { reveal } from '$lib/actions/reveal';
|
import { reveal } from '$lib/actions/reveal';
|
||||||
import CtaCard from '$lib/components/CtaCard.svelte';
|
import CtaCard from '$lib/components/ui/CtaCard.svelte';
|
||||||
import Icon from '$lib/components/Icon.svelte';
|
import Icon from '$lib/components/ui/Icon.svelte';
|
||||||
import { getEnhancedImage } from '$lib/enhanced-images';
|
import { getEnhancedImage } from '$lib/enhanced-images';
|
||||||
import { getSeededTestimonialIndex } from '$lib/testimonials';
|
import { getSeededTestimonialIndex } from '$lib/testimonials';
|
||||||
import type { LocationPageContent, TestimonialContent } from '$lib/types';
|
import type { LocationPageContent, TestimonialContent } from '$lib/types';
|
||||||
+127
-311
@@ -1,9 +1,9 @@
|
|||||||
<script lang="ts">
|
<script lang="ts">
|
||||||
import { onMount } from 'svelte';
|
import { onMount } from 'svelte';
|
||||||
import Icon from '$lib/components/Icon.svelte';
|
import Icon from '$lib/components/ui/Icon.svelte';
|
||||||
import OnboardingSignaturePad from '$lib/components/OnboardingSignaturePad.svelte';
|
import OnboardingSignaturePad from '$lib/components/ui/OnboardingSignaturePad.svelte';
|
||||||
import OnboardingAuth from '$lib/components/OnboardingAuth.svelte';
|
import OnboardingAuth from '$lib/components/sections/OnboardingAuth.svelte';
|
||||||
import OnboardingFooter from '$lib/components/OnboardingFooter.svelte';
|
import OnboardingFooter from '$lib/components/ui/OnboardingFooter.svelte';
|
||||||
import logoDesktop from '$lib/images/goodwalk-auckland-dog-walking-logo.png?enhanced';
|
import logoDesktop from '$lib/images/goodwalk-auckland-dog-walking-logo.png?enhanced';
|
||||||
import type { Picture } from '@sveltejs/enhanced-img';
|
import type { Picture } from '@sveltejs/enhanced-img';
|
||||||
|
|
||||||
@@ -11,6 +11,25 @@
|
|||||||
|
|
||||||
export const preview = false;
|
export const preview = false;
|
||||||
|
|
||||||
|
// Owner "view as client" impersonation. When `impersonation` is true the
|
||||||
|
// page renders the given client's onboarding view from injected data (no
|
||||||
|
// /auth/verify call) and the owner can walk through every step interactively.
|
||||||
|
// It still never touches the real client: client-side persistence is
|
||||||
|
// neutralised (saveDraft / the localStorage reactive block are no-ops) and
|
||||||
|
// the final submit is routed to `onImpersonationSubmit` instead of the real
|
||||||
|
// endpoint. Defaults keep the normal client flow intact.
|
||||||
|
export let impersonation = false;
|
||||||
|
export let impersonationData: {
|
||||||
|
email?: string;
|
||||||
|
profile?: Record<string, unknown>;
|
||||||
|
draft?: Record<string, unknown>;
|
||||||
|
} | null = null;
|
||||||
|
// Called on final submit while impersonating. The parent decides whether to
|
||||||
|
// email the run-through to a preview address or do nothing (dry run); either
|
||||||
|
// way the real client is never contacted or updated. Throwing surfaces an
|
||||||
|
// error in the form just like a real failed submit.
|
||||||
|
export let onImpersonationSubmit: ((payload: Record<string, unknown>) => Promise<void>) | null = null;
|
||||||
|
|
||||||
const ownerEmail = 'info@goodwalk.co.nz';
|
const ownerEmail = 'info@goodwalk.co.nz';
|
||||||
const ownerPhone = '(022) 642 1011';
|
const ownerPhone = '(022) 642 1011';
|
||||||
const services = ['Tiny Gang Pack Walks', 'Solo Walks', 'Puppy Visits', 'Unsure yet'];
|
const services = ['Tiny Gang Pack Walks', 'Solo Walks', 'Puppy Visits', 'Unsure yet'];
|
||||||
@@ -56,6 +75,9 @@
|
|||||||
let vetPhone = '';
|
let vetPhone = '';
|
||||||
let emergencyContactName = '';
|
let emergencyContactName = '';
|
||||||
let emergencyContactPhone = '';
|
let emergencyContactPhone = '';
|
||||||
|
let regularFleaTickTreatment = '';
|
||||||
|
let petInsurance = '';
|
||||||
|
let petInsuranceOwnerExpenseAccepted = false;
|
||||||
|
|
||||||
// Health & diet
|
// Health & diet
|
||||||
let isVaccinated = ''; // 'yes' | 'no'
|
let isVaccinated = ''; // 'yes' | 'no'
|
||||||
@@ -75,6 +97,7 @@
|
|||||||
let visitsDogParks = '';
|
let visitsDogParks = '';
|
||||||
let dogParksFrequency = '';
|
let dogParksFrequency = '';
|
||||||
let biteHistory = '';
|
let biteHistory = '';
|
||||||
|
let resourceGuards = '';
|
||||||
let reactiveToDogs = '';
|
let reactiveToDogs = '';
|
||||||
let reactiveToAnimals = '';
|
let reactiveToAnimals = '';
|
||||||
let reactiveToChildren = '';
|
let reactiveToChildren = '';
|
||||||
@@ -211,6 +234,7 @@
|
|||||||
servicesNeeded, temperament, accessInstructions,
|
servicesNeeded, temperament, accessInstructions,
|
||||||
vetName, vetAddress, vetPhone,
|
vetName, vetAddress, vetPhone,
|
||||||
emergencyContactName, emergencyContactPhone,
|
emergencyContactName, emergencyContactPhone,
|
||||||
|
regularFleaTickTreatment, petInsurance, petInsuranceOwnerExpenseAccepted,
|
||||||
isVaccinated,
|
isVaccinated,
|
||||||
hasFoodAllergies, foodAllergiesDetail,
|
hasFoodAllergies, foodAllergiesDetail,
|
||||||
hasEnvAllergies, envAllergiesDetail,
|
hasEnvAllergies, envAllergiesDetail,
|
||||||
@@ -218,7 +242,7 @@
|
|||||||
onMedication, medicationDetail,
|
onMedication, medicationDetail,
|
||||||
wellSocialised, dogsInteractedWeekly,
|
wellSocialised, dogsInteractedWeekly,
|
||||||
visitsBeach, visitsDogParks, dogParksFrequency,
|
visitsBeach, visitsDogParks, dogParksFrequency,
|
||||||
biteHistory,
|
biteHistory, resourceGuards,
|
||||||
reactiveToDogs, reactiveToAnimals, reactiveToChildren, reactiveToPeople,
|
reactiveToDogs, reactiveToAnimals, reactiveToChildren, reactiveToPeople,
|
||||||
isDesexed, isRegistered, leashTrained,
|
isDesexed, isRegistered, leashTrained,
|
||||||
recallRating, ranAwayBefore,
|
recallRating, ranAwayBefore,
|
||||||
@@ -229,6 +253,7 @@
|
|||||||
}
|
}
|
||||||
|
|
||||||
async function saveDraft() {
|
async function saveDraft() {
|
||||||
|
if (impersonation) return; // read-only replica never persists drafts
|
||||||
try {
|
try {
|
||||||
const token = window.localStorage.getItem('gw_onboarding_session');
|
const token = window.localStorage.getItem('gw_onboarding_session');
|
||||||
if (!token) return;
|
if (!token) return;
|
||||||
@@ -280,6 +305,9 @@
|
|||||||
vetPhone = pickString(draft, 'vetPhone', vetPhone);
|
vetPhone = pickString(draft, 'vetPhone', vetPhone);
|
||||||
emergencyContactName = pickString(draft, 'emergencyContactName', emergencyContactName);
|
emergencyContactName = pickString(draft, 'emergencyContactName', emergencyContactName);
|
||||||
emergencyContactPhone = pickString(draft, 'emergencyContactPhone', emergencyContactPhone);
|
emergencyContactPhone = pickString(draft, 'emergencyContactPhone', emergencyContactPhone);
|
||||||
|
regularFleaTickTreatment = pickString(draft, 'regularFleaTickTreatment', regularFleaTickTreatment);
|
||||||
|
petInsurance = pickString(draft, 'petInsurance', petInsurance);
|
||||||
|
petInsuranceOwnerExpenseAccepted = pickBool(draft, 'petInsuranceOwnerExpenseAccepted', petInsuranceOwnerExpenseAccepted);
|
||||||
isVaccinated = pickString(draft, 'isVaccinated', isVaccinated);
|
isVaccinated = pickString(draft, 'isVaccinated', isVaccinated);
|
||||||
hasFoodAllergies = pickString(draft, 'hasFoodAllergies', hasFoodAllergies);
|
hasFoodAllergies = pickString(draft, 'hasFoodAllergies', hasFoodAllergies);
|
||||||
foodAllergiesDetail = pickString(draft, 'foodAllergiesDetail', foodAllergiesDetail);
|
foodAllergiesDetail = pickString(draft, 'foodAllergiesDetail', foodAllergiesDetail);
|
||||||
@@ -295,6 +323,7 @@
|
|||||||
visitsDogParks = pickString(draft, 'visitsDogParks', visitsDogParks);
|
visitsDogParks = pickString(draft, 'visitsDogParks', visitsDogParks);
|
||||||
dogParksFrequency = pickString(draft, 'dogParksFrequency', dogParksFrequency);
|
dogParksFrequency = pickString(draft, 'dogParksFrequency', dogParksFrequency);
|
||||||
biteHistory = pickString(draft, 'biteHistory', biteHistory);
|
biteHistory = pickString(draft, 'biteHistory', biteHistory);
|
||||||
|
resourceGuards = pickString(draft, 'resourceGuards', resourceGuards);
|
||||||
reactiveToDogs = pickString(draft, 'reactiveToDogs', reactiveToDogs);
|
reactiveToDogs = pickString(draft, 'reactiveToDogs', reactiveToDogs);
|
||||||
reactiveToAnimals = pickString(draft, 'reactiveToAnimals', reactiveToAnimals);
|
reactiveToAnimals = pickString(draft, 'reactiveToAnimals', reactiveToAnimals);
|
||||||
reactiveToChildren = pickString(draft, 'reactiveToChildren', reactiveToChildren);
|
reactiveToChildren = pickString(draft, 'reactiveToChildren', reactiveToChildren);
|
||||||
@@ -313,7 +342,7 @@
|
|||||||
termsAccepted = pickBool(draft, 'termsAccepted', termsAccepted);
|
termsAccepted = pickBool(draft, 'termsAccepted', termsAccepted);
|
||||||
}
|
}
|
||||||
|
|
||||||
$: if (typeof window !== 'undefined') {
|
$: if (typeof window !== 'undefined' && !impersonation) {
|
||||||
try {
|
try {
|
||||||
window.localStorage.setItem(draftStorageKey, JSON.stringify(getDraftSnapshot()));
|
window.localStorage.setItem(draftStorageKey, JSON.stringify(getDraftSnapshot()));
|
||||||
} catch { /* storage unavailable */ }
|
} catch { /* storage unavailable */ }
|
||||||
@@ -379,7 +408,30 @@
|
|||||||
userEmail = '';
|
userEmail = '';
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function initImpersonation() {
|
||||||
|
// Render straight from the owner-supplied snapshot. No tokens are read,
|
||||||
|
// no network calls are made, and no timers are started.
|
||||||
|
const data = impersonationData ?? {};
|
||||||
|
const serverEmail = typeof data.email === 'string' ? data.email : '';
|
||||||
|
isAuthenticated = true;
|
||||||
|
userEmail = serverEmail;
|
||||||
|
applyProfile(serverEmail, (data.profile ?? {}) as Record<string, unknown>);
|
||||||
|
const onboardingDraft =
|
||||||
|
data.draft && typeof data.draft === 'object'
|
||||||
|
? (data.draft as Record<string, unknown>).onboarding
|
||||||
|
: null;
|
||||||
|
if (!onboardingCompleted && onboardingDraft && typeof onboardingDraft === 'object') {
|
||||||
|
applyDraft(onboardingDraft as Record<string, unknown>);
|
||||||
|
}
|
||||||
|
authChecking = false;
|
||||||
|
}
|
||||||
|
|
||||||
onMount(async () => {
|
onMount(async () => {
|
||||||
|
if (impersonation) {
|
||||||
|
initImpersonation();
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
await checkAuth();
|
await checkAuth();
|
||||||
|
|
||||||
if (!isAuthenticated) return;
|
if (!isAuthenticated) return;
|
||||||
@@ -433,96 +485,12 @@
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
function validateEmail(raw: string): string {
|
// The form is intentionally free-fill: no fields are required and it can be
|
||||||
const value = raw.trim();
|
// submitted incomplete. Owners often onboard before they've named a dog or
|
||||||
if (!value) return 'Please enter your email address';
|
// sorted council registration, and we follow up on the gaps ourselves.
|
||||||
if (!/^[A-Za-z0-9._%+-]+@[A-Za-z0-9-]+(?:\.[A-Za-z0-9-]+)*\.[A-Za-z]{2,}$/.test(value)) {
|
|
||||||
return 'Please enter a valid email address';
|
|
||||||
}
|
|
||||||
return '';
|
|
||||||
}
|
|
||||||
|
|
||||||
type RequirementCheck = { key: string; label: string; met: boolean; step: number };
|
|
||||||
|
|
||||||
$: contactDetailsMet =
|
|
||||||
Boolean(ownerFirstName.trim()) &&
|
|
||||||
Boolean(ownerLastName.trim()) &&
|
|
||||||
!validateEmail(email) &&
|
|
||||||
Boolean(phone.trim()) &&
|
|
||||||
Boolean(address.trim());
|
|
||||||
|
|
||||||
$: requirementChecks = [
|
|
||||||
{ key: 'contact', label: 'Your contact details (name, email, phone, address)', met: contactDetailsMet, step: 1 },
|
|
||||||
{ key: 'services', label: 'At least one service selected', met: servicesNeeded.length > 0, step: 2 },
|
|
||||||
{ key: 'vaccination', label: 'Vaccinations confirmed', met: isVaccinated === 'yes', step: 3 },
|
|
||||||
{ key: 'council', label: 'Council registration confirmed', met: isRegistered === 'yes', step: 4 },
|
|
||||||
{ key: 'vetConsent', label: 'Emergency vet treatment consent', met: emergencyVetConsent, step: 5 },
|
|
||||||
{ key: 'declaration', label: 'Onboarding declaration ticked', met: termsAccepted, step: 5 },
|
|
||||||
{ key: 'signature', label: 'Valid signature added', met: Boolean(signatureDataUrl), step: 5 },
|
|
||||||
] as RequirementCheck[];
|
|
||||||
|
|
||||||
$: requirementsAllMet = requirementChecks.every((r) => r.met);
|
|
||||||
|
|
||||||
function goToStep(step: number) {
|
|
||||||
if (step < 1 || step > steps.length) return;
|
|
||||||
errors = {};
|
|
||||||
currentStep = step;
|
|
||||||
window.scrollTo({ top: 0, behavior: 'smooth' });
|
|
||||||
}
|
|
||||||
|
|
||||||
function validateStep(step: number): boolean {
|
|
||||||
const next: Record<string, string> = {};
|
|
||||||
|
|
||||||
if (step === 1) {
|
|
||||||
if (!ownerFirstName.trim()) next.ownerFirstName = 'Please enter your first name';
|
|
||||||
if (!ownerLastName.trim()) next.ownerLastName = 'Please enter your last name';
|
|
||||||
const emailError = validateEmail(email);
|
|
||||||
if (emailError) next.email = emailError;
|
|
||||||
if (!phone.trim()) next.phone = 'Please enter your phone number';
|
|
||||||
if (!address.trim()) next.address = 'Please enter your address';
|
|
||||||
} else if (step === 2) {
|
|
||||||
if (!dogName.trim()) next.dogName = "Please enter your dog's name";
|
|
||||||
if (!dogBreed.trim()) next.dogBreed = "Please enter your dog's breed";
|
|
||||||
if (!dogDateOfBirth.trim()) next.dogDateOfBirth = "Please enter your dog's date of birth";
|
|
||||||
if (!servicesNeeded.length) next.servicesNeeded = 'Choose at least one service';
|
|
||||||
} else if (step === 3) {
|
|
||||||
if (!vetName.trim()) next.vetName = 'Please enter your vet clinic name';
|
|
||||||
if (!vetAddress.trim()) next.vetAddress = 'Please enter the vet address';
|
|
||||||
if (!vetPhone.trim()) next.vetPhone = 'Please enter your vet phone number';
|
|
||||||
if (!emergencyContactName.trim()) next.emergencyContactName = 'Please add an emergency contact';
|
|
||||||
if (!emergencyContactPhone.trim()) next.emergencyContactPhone = 'Please add an emergency contact number';
|
|
||||||
if (!isVaccinated) next.isVaccinated = 'Please answer the vaccination question';
|
|
||||||
if (!hasFoodAllergies) next.hasFoodAllergies = 'Please answer the food allergy question';
|
|
||||||
if (hasFoodAllergies === 'yes' && !foodAllergiesDetail.trim()) {
|
|
||||||
next.foodAllergiesDetail = 'Please describe the food allergy';
|
|
||||||
}
|
|
||||||
if (!hasEnvAllergies) next.hasEnvAllergies = 'Please answer the environmental allergy question';
|
|
||||||
if (hasEnvAllergies === 'yes' && !envAllergiesDetail.trim()) {
|
|
||||||
next.envAllergiesDetail = 'Please describe the environmental allergy';
|
|
||||||
}
|
|
||||||
if (!onSpecialDiet) next.onSpecialDiet = 'Please answer the special diet question';
|
|
||||||
if (onSpecialDiet === 'yes' && !specialDietDetail.trim()) {
|
|
||||||
next.specialDietDetail = 'Please describe the special diet';
|
|
||||||
}
|
|
||||||
if (!onMedication) next.onMedication = 'Please answer the medication question';
|
|
||||||
if (onMedication === 'yes' && !medicationDetail.trim()) {
|
|
||||||
next.medicationDetail = 'Please describe the medication';
|
|
||||||
}
|
|
||||||
} else if (step === 4) {
|
|
||||||
if (!isRegistered) next.isRegistered = 'Please confirm whether your dog is registered with council';
|
|
||||||
} else if (step === 5) {
|
|
||||||
if (!emergencyVetConsent) next.emergencyVetConsent = 'Please confirm emergency treatment consent';
|
|
||||||
if (!termsAccepted) next.termsAccepted = 'Please confirm the onboarding declaration';
|
|
||||||
if (!signatureDataUrl) next.signature = 'Please add your signature before sending';
|
|
||||||
}
|
|
||||||
|
|
||||||
errors = next;
|
|
||||||
return Object.keys(next).length === 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
function nextStep() {
|
function nextStep() {
|
||||||
noteInteraction();
|
noteInteraction();
|
||||||
if (!validateStep(currentStep)) return;
|
|
||||||
currentStep = Math.min(currentStep + 1, steps.length);
|
currentStep = Math.min(currentStep + 1, steps.length);
|
||||||
void saveDraft();
|
void saveDraft();
|
||||||
window.scrollTo({ top: 0, behavior: 'smooth' });
|
window.scrollTo({ top: 0, behavior: 'smooth' });
|
||||||
@@ -547,6 +515,7 @@ function buildSubmissionSnapshot(submittedAt: string): SubmissionSummary {
|
|||||||
{ label: 'Email', value: fieldValue(email) },
|
{ label: 'Email', value: fieldValue(email) },
|
||||||
{ label: 'Contact Number', value: fieldValue(phone) },
|
{ label: 'Contact Number', value: fieldValue(phone) },
|
||||||
{ label: 'Home Address', value: fieldValue(address) },
|
{ label: 'Home Address', value: fieldValue(address) },
|
||||||
|
{ label: 'Home Access Instructions', value: fieldValue(accessInstructions) },
|
||||||
],
|
],
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
@@ -570,7 +539,9 @@ function buildSubmissionSnapshot(submittedAt: string): SubmissionSummary {
|
|||||||
{ label: 'Vet Address', value: fieldValue(vetAddress) },
|
{ label: 'Vet Address', value: fieldValue(vetAddress) },
|
||||||
{ label: 'Emergency Contact Name', value: fieldValue(emergencyContactName) },
|
{ label: 'Emergency Contact Name', value: fieldValue(emergencyContactName) },
|
||||||
{ label: 'Emergency Contact Number', value: fieldValue(emergencyContactPhone) },
|
{ label: 'Emergency Contact Number', value: fieldValue(emergencyContactPhone) },
|
||||||
{ label: 'Home Access Instructions', value: fieldValue(accessInstructions) },
|
{ label: 'Regular Flea and Tick Treatment?', value: yesNoText(regularFleaTickTreatment) },
|
||||||
|
{ label: 'Pet Insurance?', value: yesNoText(petInsurance) },
|
||||||
|
{ label: 'Owner Covers All Vet Costs if Uninsured', value: petInsurance === 'no' ? yesNoText(petInsuranceOwnerExpenseAccepted) : '—' },
|
||||||
{ label: 'Are Vaccinations Current?', value: yesNoText(isVaccinated) },
|
{ label: 'Are Vaccinations Current?', value: yesNoText(isVaccinated) },
|
||||||
{ label: 'Any Food Allergies?', value: hasFoodAllergies === 'yes' ? fieldValue(foodAllergiesDetail) : yesNoText(hasFoodAllergies) },
|
{ label: 'Any Food Allergies?', value: hasFoodAllergies === 'yes' ? fieldValue(foodAllergiesDetail) : yesNoText(hasFoodAllergies) },
|
||||||
{ label: 'Any Environmental Allergies?', value: hasEnvAllergies === 'yes' ? fieldValue(envAllergiesDetail) : yesNoText(hasEnvAllergies) },
|
{ label: 'Any Environmental Allergies?', value: hasEnvAllergies === 'yes' ? fieldValue(envAllergiesDetail) : yesNoText(hasEnvAllergies) },
|
||||||
@@ -587,6 +558,7 @@ function buildSubmissionSnapshot(submittedAt: string): SubmissionSummary {
|
|||||||
{ label: 'Visits the Beach?', value: yesNoText(visitsBeach) },
|
{ label: 'Visits the Beach?', value: yesNoText(visitsBeach) },
|
||||||
{ label: 'Visits Dog Parks Frequently?', value: visitsDogParks === 'yes' ? fieldValue(dogParksFrequency ? `${dogParksFrequency} per week` : 'Yes') : yesNoText(visitsDogParks) },
|
{ label: 'Visits Dog Parks Frequently?', value: visitsDogParks === 'yes' ? fieldValue(dogParksFrequency ? `${dogParksFrequency} per week` : 'Yes') : yesNoText(visitsDogParks) },
|
||||||
{ label: 'Any Bite History?', value: yesNoText(biteHistory) },
|
{ label: 'Any Bite History?', value: yesNoText(biteHistory) },
|
||||||
|
{ label: 'Resource Guards?', value: yesNoText(resourceGuards) },
|
||||||
{ label: 'Reactive to Other Dogs?', value: yesNoText(reactiveToDogs) },
|
{ label: 'Reactive to Other Dogs?', value: yesNoText(reactiveToDogs) },
|
||||||
{ label: 'Reactive to Other Animals?', value: yesNoText(reactiveToAnimals) },
|
{ label: 'Reactive to Other Animals?', value: yesNoText(reactiveToAnimals) },
|
||||||
{ label: 'Reactive to Children?', value: yesNoText(reactiveToChildren) },
|
{ label: 'Reactive to Children?', value: yesNoText(reactiveToChildren) },
|
||||||
@@ -621,15 +593,6 @@ function buildSubmissionSnapshot(submittedAt: string): SubmissionSummary {
|
|||||||
sendClickedAt = Date.now();
|
sendClickedAt = Date.now();
|
||||||
submitError = '';
|
submitError = '';
|
||||||
|
|
||||||
if (!validateStep(5)) return;
|
|
||||||
|
|
||||||
const firstUnmet = requirementChecks.find((r) => !r.met);
|
|
||||||
if (firstUnmet) {
|
|
||||||
submitError = `Please complete: ${firstUnmet.label}.`;
|
|
||||||
if (firstUnmet.step !== currentStep) goToStep(firstUnmet.step);
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
submitting = true;
|
submitting = true;
|
||||||
|
|
||||||
const controller = new AbortController();
|
const controller = new AbortController();
|
||||||
@@ -648,15 +611,14 @@ function buildSubmissionSnapshot(submittedAt: string): SubmissionSummary {
|
|||||||
toStr(additionalNotes).trim(),
|
toStr(additionalNotes).trim(),
|
||||||
].filter(Boolean).join('\n');
|
].filter(Boolean).join('\n');
|
||||||
|
|
||||||
const response = await fetch('/api/onboarding-submit', {
|
const payload = {
|
||||||
method: 'POST',
|
|
||||||
headers: { 'Content-Type': 'application/json' },
|
|
||||||
signal: controller.signal,
|
|
||||||
body: JSON.stringify({
|
|
||||||
...getDraftSnapshot(),
|
...getDraftSnapshot(),
|
||||||
fullName,
|
fullName,
|
||||||
dogAge: dogDateOfBirth,
|
dogAge: dogDateOfBirth,
|
||||||
medicalNotes,
|
medicalNotes,
|
||||||
|
regularFleaTickTreatment,
|
||||||
|
petInsurance,
|
||||||
|
petInsuranceOwnerExpenseAccepted,
|
||||||
councilRegistrationConfirmed: isRegistered === 'yes',
|
councilRegistrationConfirmed: isRegistered === 'yes',
|
||||||
vaccinationsConfirmed: isVaccinated === 'yes',
|
vaccinationsConfirmed: isVaccinated === 'yes',
|
||||||
signatureDataUrl,
|
signatureDataUrl,
|
||||||
@@ -666,12 +628,29 @@ function buildSubmissionSnapshot(submittedAt: string): SubmissionSummary {
|
|||||||
formStartedAt, visitStartedAt, pageEnteredAt, firstInteractionAt, sendClickedAt,
|
formStartedAt, visitStartedAt, pageEnteredAt, firstInteractionAt, sendClickedAt,
|
||||||
referrer: document.referrer,
|
referrer: document.referrer,
|
||||||
page: window.location.href,
|
page: window.location.href,
|
||||||
})
|
};
|
||||||
|
|
||||||
|
if (impersonation) {
|
||||||
|
// Owner run-through: hand off to the parent (preview email or dry run).
|
||||||
|
// Never hits the real client endpoint or the owner's own saved draft.
|
||||||
|
if (onImpersonationSubmit) await onImpersonationSubmit(payload);
|
||||||
|
onboardingCompleted = true;
|
||||||
|
onboardingSummary = submissionSnapshot;
|
||||||
|
onboardingSubmittedAt = signedOnDate;
|
||||||
|
window.scrollTo({ top: 0, behavior: 'smooth' });
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
const response = await fetch('/api/onboarding-submit', {
|
||||||
|
method: 'POST',
|
||||||
|
headers: { 'Content-Type': 'application/json' },
|
||||||
|
signal: controller.signal,
|
||||||
|
body: JSON.stringify(payload)
|
||||||
});
|
});
|
||||||
|
|
||||||
if (!response.ok) {
|
if (!response.ok) {
|
||||||
const payload = await response.json().catch(() => null);
|
const errorPayload = await response.json().catch(() => null);
|
||||||
throw new Error(payload?.detail?.message ?? payload?.detail ?? payload?.error ?? `Submission failed (${response.status})`);
|
throw new Error(errorPayload?.detail?.message ?? errorPayload?.detail ?? errorPayload?.error ?? `Submission failed (${response.status})`);
|
||||||
}
|
}
|
||||||
|
|
||||||
onboardingCompleted = true;
|
onboardingCompleted = true;
|
||||||
@@ -970,6 +949,16 @@ function buildSubmissionSnapshot(submittedAt: string): SubmissionSummary {
|
|||||||
<input bind:value={address} on:input={noteInteraction} autocomplete="street-address" placeholder="Street, suburb, city" />
|
<input bind:value={address} on:input={noteInteraction} autocomplete="street-address" placeholder="Street, suburb, city" />
|
||||||
{#if errors.address}<small>{errors.address}</small>{/if}
|
{#if errors.address}<small>{errors.address}</small>{/if}
|
||||||
</label>
|
</label>
|
||||||
|
|
||||||
|
<label class="field field-full">
|
||||||
|
<span>Home access instructions</span>
|
||||||
|
<textarea
|
||||||
|
bind:value={accessInstructions}
|
||||||
|
rows="3"
|
||||||
|
placeholder="Keys, gate code, where to collect your dog, alarm notes."
|
||||||
|
on:input={noteInteraction}
|
||||||
|
></textarea>
|
||||||
|
</label>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div class="panel-nav">
|
<div class="panel-nav">
|
||||||
@@ -1125,19 +1114,33 @@ function buildSubmissionSnapshot(submittedAt: string): SubmissionSummary {
|
|||||||
<div class="panel-subsection-icon"><Icon name="fas fa-notes-medical" /></div>
|
<div class="panel-subsection-icon"><Icon name="fas fa-notes-medical" /></div>
|
||||||
<div>
|
<div>
|
||||||
<h3>Health notes</h3>
|
<h3>Health notes</h3>
|
||||||
<p>Day-to-day care, access, and anything we should know. Vaccination status is required below.</p>
|
<p>Day-to-day care and anything we should know. Vaccination status is required below.</p>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<label class="field">
|
{@render yesNo('Does your dog get regular flea and tick treatment?', 'regularFleaTickTreatment', regularFleaTickTreatment, (v) => (regularFleaTickTreatment = v))}
|
||||||
<span>Home access instructions</span>
|
|
||||||
<textarea
|
{@render yesNo('Does your dog have pet insurance?', 'petInsurance', petInsurance, (v) => {
|
||||||
bind:value={accessInstructions}
|
petInsurance = v;
|
||||||
rows="3"
|
if (v !== 'no') {
|
||||||
placeholder="Keys, gate code, where to collect your dog, alarm notes."
|
petInsuranceOwnerExpenseAccepted = false;
|
||||||
on:input={noteInteraction}
|
if (errors.petInsuranceOwnerExpenseAccepted) {
|
||||||
></textarea>
|
errors = { ...errors, petInsuranceOwnerExpenseAccepted: '' };
|
||||||
|
}
|
||||||
|
}
|
||||||
|
})}
|
||||||
|
{#if petInsurance === 'no'}
|
||||||
|
<label class="confirm-row">
|
||||||
|
<input bind:checked={petInsuranceOwnerExpenseAccepted} type="checkbox" on:change={() => {
|
||||||
|
noteInteraction();
|
||||||
|
if (errors.petInsuranceOwnerExpenseAccepted) {
|
||||||
|
errors = { ...errors, petInsuranceOwnerExpenseAccepted: '' };
|
||||||
|
}
|
||||||
|
}} />
|
||||||
|
<span>If my dog does not have pet insurance, I agree that Goodwalk takes no responsibility and all vet costs are incurred at the owner's expense.</span>
|
||||||
</label>
|
</label>
|
||||||
|
{#if errors.petInsuranceOwnerExpenseAccepted}<small>{errors.petInsuranceOwnerExpenseAccepted}</small>{/if}
|
||||||
|
{/if}
|
||||||
|
|
||||||
{@render yesNo("Are your dog's vaccinations up to date?", 'isVaccinated', isVaccinated, (v) => (isVaccinated = v))}
|
{@render yesNo("Are your dog's vaccinations up to date?", 'isVaccinated', isVaccinated, (v) => (isVaccinated = v))}
|
||||||
|
|
||||||
@@ -1218,6 +1221,7 @@ function buildSubmissionSnapshot(submittedAt: string): SubmissionSummary {
|
|||||||
{/if}
|
{/if}
|
||||||
|
|
||||||
{@render yesNo('Any bite history?', 'biteHistory', biteHistory, (v) => (biteHistory = v))}
|
{@render yesNo('Any bite history?', 'biteHistory', biteHistory, (v) => (biteHistory = v))}
|
||||||
|
{@render yesNo('Does your dog resource guard?', 'resourceGuards', resourceGuards, (v) => (resourceGuards = v))}
|
||||||
{@render yesNo('Reactive to other dogs?', 'reactiveToDogs', reactiveToDogs, (v) => (reactiveToDogs = v))}
|
{@render yesNo('Reactive to other dogs?', 'reactiveToDogs', reactiveToDogs, (v) => (reactiveToDogs = v))}
|
||||||
{@render yesNo('Reactive to other animals?', 'reactiveToAnimals', reactiveToAnimals, (v) => (reactiveToAnimals = v))}
|
{@render yesNo('Reactive to other animals?', 'reactiveToAnimals', reactiveToAnimals, (v) => (reactiveToAnimals = v))}
|
||||||
{@render yesNo('Reactive to children?', 'reactiveToChildren', reactiveToChildren, (v) => (reactiveToChildren = v))}
|
{@render yesNo('Reactive to children?', 'reactiveToChildren', reactiveToChildren, (v) => (reactiveToChildren = v))}
|
||||||
@@ -1300,41 +1304,6 @@ function buildSubmissionSnapshot(submittedAt: string): SubmissionSummary {
|
|||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div class="requirements-panel" class:requirements-complete={requirementsAllMet} aria-label="Onboarding requirements">
|
|
||||||
<div class="requirements-head">
|
|
||||||
<h3>Before you send</h3>
|
|
||||||
<p>
|
|
||||||
{#if requirementsAllMet}
|
|
||||||
All set. You're ready to sign and send.
|
|
||||||
{:else}
|
|
||||||
We need each of these before you submit. Tap any item to jump back to that step.
|
|
||||||
{/if}
|
|
||||||
</p>
|
|
||||||
</div>
|
|
||||||
<ul class="requirements-list">
|
|
||||||
{#each requirementChecks as req (req.key)}
|
|
||||||
<li class="requirement-row" class:requirement-met={req.met}>
|
|
||||||
<button
|
|
||||||
type="button"
|
|
||||||
class="requirement-button"
|
|
||||||
on:click={() => goToStep(req.step)}
|
|
||||||
aria-label="{req.met ? 'Met' : 'Not met'}: {req.label}. Go to step {req.step}."
|
|
||||||
>
|
|
||||||
<span class="requirement-status" aria-hidden="true">
|
|
||||||
{#if req.met}
|
|
||||||
<Icon name="fas fa-circle-check" />
|
|
||||||
{:else}
|
|
||||||
<Icon name="fas fa-circle" />
|
|
||||||
{/if}
|
|
||||||
</span>
|
|
||||||
<span class="requirement-label">{req.label}</span>
|
|
||||||
<span class="requirement-step">Step {req.step}</span>
|
|
||||||
</button>
|
|
||||||
</li>
|
|
||||||
{/each}
|
|
||||||
</ul>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<label class="field">
|
<label class="field">
|
||||||
<span>Anything else we should know?</span>
|
<span>Anything else we should know?</span>
|
||||||
<textarea
|
<textarea
|
||||||
@@ -1347,7 +1316,7 @@ function buildSubmissionSnapshot(submittedAt: string): SubmissionSummary {
|
|||||||
|
|
||||||
<div class="onboarding-fields two-up">
|
<div class="onboarding-fields two-up">
|
||||||
<label class="field">
|
<label class="field">
|
||||||
<span>Instagram or social handle <span class="field-hint">Optional</span></span>
|
<span>Instagram or social handle</span>
|
||||||
<input
|
<input
|
||||||
bind:value={socialMediaAccount}
|
bind:value={socialMediaAccount}
|
||||||
on:input={() => {
|
on:input={() => {
|
||||||
@@ -1415,7 +1384,7 @@ function buildSubmissionSnapshot(submittedAt: string): SubmissionSummary {
|
|||||||
<Icon name="fas fa-arrow-left" /> Back
|
<Icon name="fas fa-arrow-left" /> Back
|
||||||
</button>
|
</button>
|
||||||
<div class="panel-nav-submit-group">
|
<div class="panel-nav-submit-group">
|
||||||
<button class="btn btn-yellow onboarding-submit" type="submit" disabled={submitting || !requirementsAllMet}>
|
<button class="btn btn-yellow onboarding-submit" type="submit" disabled={submitting}>
|
||||||
{#if submitting}Sending…{:else}Send onboarding form{/if}
|
{#if submitting}Sending…{:else}Send onboarding form{/if}
|
||||||
</button>
|
</button>
|
||||||
<p>This goes directly to Aless for review.</p>
|
<p>This goes directly to Aless for review.</p>
|
||||||
@@ -2356,157 +2325,6 @@ function buildSubmissionSnapshot(submittedAt: string): SubmissionSummary {
|
|||||||
transform: translateY(-1px);
|
transform: translateY(-1px);
|
||||||
}
|
}
|
||||||
|
|
||||||
/* ── Requirements summary (step 5) ── */
|
|
||||||
.requirements-panel {
|
|
||||||
margin: 4px 0 28px;
|
|
||||||
padding: 22px 22px 18px;
|
|
||||||
border-radius: 24px;
|
|
||||||
background: linear-gradient(180deg, #fffefb 0%, #faf8f1 100%);
|
|
||||||
border: 1px solid rgba(33, 48, 33, 0.12);
|
|
||||||
box-shadow:
|
|
||||||
inset 0 0 0 1px rgba(255, 255, 255, 0.5),
|
|
||||||
0 8px 20px rgba(17, 20, 24, 0.04);
|
|
||||||
transition: border-color 0.25s ease, box-shadow 0.25s ease, background 0.25s ease;
|
|
||||||
}
|
|
||||||
|
|
||||||
.requirements-panel.requirements-complete {
|
|
||||||
border-color: rgba(33, 48, 33, 0.22);
|
|
||||||
background: linear-gradient(180deg, #fffbe8 0%, #fff5c9 100%);
|
|
||||||
box-shadow:
|
|
||||||
inset 0 0 0 1px rgba(255, 255, 255, 0.6),
|
|
||||||
0 10px 26px rgba(255, 209, 0, 0.18);
|
|
||||||
}
|
|
||||||
|
|
||||||
.requirements-head {
|
|
||||||
display: flex;
|
|
||||||
align-items: baseline;
|
|
||||||
justify-content: space-between;
|
|
||||||
gap: 12px;
|
|
||||||
margin-bottom: 14px;
|
|
||||||
}
|
|
||||||
|
|
||||||
.requirements-head h3 {
|
|
||||||
margin: 0;
|
|
||||||
font-family: var(--font-head);
|
|
||||||
font-size: 17px;
|
|
||||||
font-weight: 700;
|
|
||||||
letter-spacing: -0.02em;
|
|
||||||
color: var(--gw-green);
|
|
||||||
}
|
|
||||||
|
|
||||||
.requirements-head p {
|
|
||||||
margin: 0;
|
|
||||||
max-width: 56ch;
|
|
||||||
font-size: 12px;
|
|
||||||
font-weight: 700;
|
|
||||||
line-height: 1.5;
|
|
||||||
letter-spacing: 0.005em;
|
|
||||||
color: rgba(33, 48, 33, 0.62);
|
|
||||||
}
|
|
||||||
|
|
||||||
.requirements-list {
|
|
||||||
list-style: none;
|
|
||||||
margin: 0;
|
|
||||||
padding: 0;
|
|
||||||
display: grid;
|
|
||||||
gap: 8px;
|
|
||||||
}
|
|
||||||
|
|
||||||
.requirement-button {
|
|
||||||
width: 100%;
|
|
||||||
display: grid;
|
|
||||||
grid-template-columns: 24px 1fr auto;
|
|
||||||
gap: 14px;
|
|
||||||
align-items: center;
|
|
||||||
padding: 12px 14px;
|
|
||||||
border-radius: 14px;
|
|
||||||
border: 1px solid rgba(33, 48, 33, 0.1);
|
|
||||||
background: rgba(255, 255, 255, 0.85);
|
|
||||||
color: var(--gw-green);
|
|
||||||
font-family: var(--font-body);
|
|
||||||
font-size: 14px;
|
|
||||||
font-weight: 700;
|
|
||||||
text-align: left;
|
|
||||||
cursor: pointer;
|
|
||||||
transition:
|
|
||||||
background 0.18s ease,
|
|
||||||
border-color 0.18s ease,
|
|
||||||
transform 0.18s cubic-bezier(0.22, 1, 0.36, 1),
|
|
||||||
box-shadow 0.18s ease;
|
|
||||||
}
|
|
||||||
|
|
||||||
.requirement-button:hover {
|
|
||||||
border-color: rgba(33, 48, 33, 0.3);
|
|
||||||
background: #fff;
|
|
||||||
transform: translateY(-1px);
|
|
||||||
box-shadow: 0 6px 14px rgba(17, 20, 24, 0.05);
|
|
||||||
}
|
|
||||||
|
|
||||||
.requirement-button:focus-visible {
|
|
||||||
outline: none;
|
|
||||||
border-color: var(--gw-green);
|
|
||||||
box-shadow: 0 0 0 4px rgba(255, 209, 0, 0.32);
|
|
||||||
}
|
|
||||||
|
|
||||||
.requirement-status {
|
|
||||||
display: inline-flex;
|
|
||||||
width: 24px;
|
|
||||||
height: 24px;
|
|
||||||
align-items: center;
|
|
||||||
justify-content: center;
|
|
||||||
color: rgba(33, 48, 33, 0.3);
|
|
||||||
font-size: 20px;
|
|
||||||
}
|
|
||||||
|
|
||||||
.requirement-met .requirement-status {
|
|
||||||
color: #2f7a3a;
|
|
||||||
}
|
|
||||||
|
|
||||||
.requirement-met .requirement-button {
|
|
||||||
background: rgba(47, 122, 58, 0.07);
|
|
||||||
border-color: rgba(47, 122, 58, 0.28);
|
|
||||||
}
|
|
||||||
|
|
||||||
.requirement-label {
|
|
||||||
line-height: 1.4;
|
|
||||||
color: #171b20;
|
|
||||||
}
|
|
||||||
|
|
||||||
.requirement-met .requirement-label {
|
|
||||||
color: var(--gw-green);
|
|
||||||
}
|
|
||||||
|
|
||||||
.requirement-step {
|
|
||||||
font-family: var(--font-head);
|
|
||||||
font-size: 11px;
|
|
||||||
font-weight: 700;
|
|
||||||
letter-spacing: 0.08em;
|
|
||||||
text-transform: uppercase;
|
|
||||||
color: rgba(33, 48, 33, 0.5);
|
|
||||||
}
|
|
||||||
|
|
||||||
.requirement-met .requirement-step {
|
|
||||||
color: rgba(47, 122, 58, 0.7);
|
|
||||||
}
|
|
||||||
|
|
||||||
@media (max-width: 540px) {
|
|
||||||
.requirements-head {
|
|
||||||
flex-direction: column;
|
|
||||||
align-items: flex-start;
|
|
||||||
gap: 6px;
|
|
||||||
}
|
|
||||||
|
|
||||||
.requirement-button {
|
|
||||||
grid-template-columns: 22px 1fr;
|
|
||||||
grid-template-rows: auto auto;
|
|
||||||
row-gap: 4px;
|
|
||||||
}
|
|
||||||
|
|
||||||
.requirement-step {
|
|
||||||
grid-column: 2;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/* ── Declaration ── */
|
/* ── Declaration ── */
|
||||||
.onboarding-confirm-list {
|
.onboarding-confirm-list {
|
||||||
display: grid;
|
display: grid;
|
||||||
@@ -2730,7 +2548,6 @@ function buildSubmissionSnapshot(submittedAt: string): SubmissionSummary {
|
|||||||
@media (prefers-reduced-motion: reduce) {
|
@media (prefers-reduced-motion: reduce) {
|
||||||
.field input,
|
.field input,
|
||||||
.field textarea,
|
.field textarea,
|
||||||
.requirement-button,
|
|
||||||
.panel-nav-back,
|
.panel-nav-back,
|
||||||
.panel-nav-next,
|
.panel-nav-next,
|
||||||
.onboarding-submit,
|
.onboarding-submit,
|
||||||
@@ -2744,8 +2561,7 @@ function buildSubmissionSnapshot(submittedAt: string): SubmissionSummary {
|
|||||||
|
|
||||||
.panel-nav-back:hover,
|
.panel-nav-back:hover,
|
||||||
.panel-nav-next:hover,
|
.panel-nav-next:hover,
|
||||||
.onboarding-submit:not(:disabled):hover,
|
.onboarding-submit:not(:disabled):hover {
|
||||||
.requirement-button:hover {
|
|
||||||
transform: none;
|
transform: none;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -1,11 +1,11 @@
|
|||||||
<script lang="ts">
|
<script lang="ts">
|
||||||
import { onMount } from 'svelte';
|
import { onMount } from 'svelte';
|
||||||
import { reveal } from '$lib/actions/reveal';
|
import { reveal } from '$lib/actions/reveal';
|
||||||
import BookingWizard from '$lib/components/BookingWizard.svelte';
|
import BookingWizard from '$lib/components/sections/BookingWizard.svelte';
|
||||||
import Icon from '$lib/components/Icon.svelte';
|
import Icon from '$lib/components/ui/Icon.svelte';
|
||||||
import PricingPlanCard from '$lib/components/PricingPlanCard.svelte';
|
import PricingPlanCard from '$lib/components/ui/PricingPlanCard.svelte';
|
||||||
import ServiceHero from '$lib/components/ServiceHero.svelte';
|
import ServiceHero from '$lib/components/sections/ServiceHero.svelte';
|
||||||
import TestimonialsSection from '$lib/components/TestimonialsSection.svelte';
|
import TestimonialsSection from '$lib/components/sections/TestimonialsSection.svelte';
|
||||||
import { decoratePlans } from '$lib/utils/pricing';
|
import { decoratePlans } from '$lib/utils/pricing';
|
||||||
import type { PricingPageContent, SiteSharedContent } from '$lib/types';
|
import type { PricingPageContent, SiteSharedContent } from '$lib/types';
|
||||||
|
|
||||||
+7
-7
@@ -1,13 +1,13 @@
|
|||||||
<script lang="ts">
|
<script lang="ts">
|
||||||
import { onMount, tick } from 'svelte';
|
import { onMount, tick } from 'svelte';
|
||||||
import Icon from '$lib/components/Icon.svelte';
|
import Icon from '$lib/components/ui/Icon.svelte';
|
||||||
import { reveal } from '$lib/actions/reveal';
|
import { reveal } from '$lib/actions/reveal';
|
||||||
import BookingWizard from '$lib/components/BookingWizard.svelte';
|
import BookingWizard from '$lib/components/sections/BookingWizard.svelte';
|
||||||
import PricingPlanCard from '$lib/components/PricingPlanCard.svelte';
|
import PricingPlanCard from '$lib/components/ui/PricingPlanCard.svelte';
|
||||||
import ServiceHero from '$lib/components/ServiceHero.svelte';
|
import ServiceHero from '$lib/components/sections/ServiceHero.svelte';
|
||||||
import TestimonialsSection from '$lib/components/TestimonialsSection.svelte';
|
import TestimonialsSection from '$lib/components/sections/TestimonialsSection.svelte';
|
||||||
import FaqSection from '$lib/components/FaqSection.svelte';
|
import FaqSection from '$lib/components/sections/FaqSection.svelte';
|
||||||
import ServiceAreaMap from '$lib/components/ServiceAreaMap.svelte';
|
import ServiceAreaMap from '$lib/components/sections/ServiceAreaMap.svelte';
|
||||||
import { getEnhancedImage } from '$lib/enhanced-images';
|
import { getEnhancedImage } from '$lib/enhanced-images';
|
||||||
import { decoratePlans } from '$lib/utils/pricing';
|
import { decoratePlans } from '$lib/utils/pricing';
|
||||||
import { locationPages } from '$lib/content/locations';
|
import { locationPages } from '$lib/content/locations';
|
||||||
+3
-3
@@ -1,9 +1,9 @@
|
|||||||
<script lang="ts">
|
<script lang="ts">
|
||||||
import BookingWizard from '$lib/components/BookingWizard.svelte';
|
import BookingWizard from '$lib/components/sections/BookingWizard.svelte';
|
||||||
import Icon from '$lib/components/Icon.svelte';
|
import Icon from '$lib/components/ui/Icon.svelte';
|
||||||
import { getEnhancedImage } from '$lib/enhanced-images';
|
import { getEnhancedImage } from '$lib/enhanced-images';
|
||||||
import { reveal } from '$lib/actions/reveal';
|
import { reveal } from '$lib/actions/reveal';
|
||||||
import ServiceHero from '$lib/components/ServiceHero.svelte';
|
import ServiceHero from '$lib/components/sections/ServiceHero.svelte';
|
||||||
import type { SiteSharedContent, TestimonialContent } from '$lib/types';
|
import type { SiteSharedContent, TestimonialContent } from '$lib/types';
|
||||||
|
|
||||||
export let content: SiteSharedContent;
|
export let content: SiteSharedContent;
|
||||||
+5
-5
@@ -1,10 +1,10 @@
|
|||||||
<script lang="ts">
|
<script lang="ts">
|
||||||
import { onMount } from 'svelte';
|
import { onMount } from 'svelte';
|
||||||
import Icon from '$lib/components/Icon.svelte';
|
import Icon from '$lib/components/ui/Icon.svelte';
|
||||||
import { reveal } from '$lib/actions/reveal';
|
import { reveal } from '$lib/actions/reveal';
|
||||||
import type { BookingContent } from '$lib/types';
|
import type { BookingContent } from '$lib/types';
|
||||||
type SuccessModalComponentType = typeof import('$lib/components/SuccessModal.svelte').default;
|
type SuccessModalComponentType = typeof import('$lib/components/ui/SuccessModal.svelte').default;
|
||||||
type ErrorModalComponentType = typeof import('$lib/components/ErrorModal.svelte').default;
|
type ErrorModalComponentType = typeof import('$lib/components/ui/ErrorModal.svelte').default;
|
||||||
|
|
||||||
export let booking: BookingContent;
|
export let booking: BookingContent;
|
||||||
export let allowGeneralEnquiry = false;
|
export let allowGeneralEnquiry = false;
|
||||||
@@ -87,12 +87,12 @@
|
|||||||
|
|
||||||
async function ensureSuccessModal() {
|
async function ensureSuccessModal() {
|
||||||
if (SuccessModalComponent) return;
|
if (SuccessModalComponent) return;
|
||||||
SuccessModalComponent = (await import('$lib/components/SuccessModal.svelte')).default;
|
SuccessModalComponent = (await import('$lib/components/ui/SuccessModal.svelte')).default;
|
||||||
}
|
}
|
||||||
|
|
||||||
async function ensureErrorModal() {
|
async function ensureErrorModal() {
|
||||||
if (ErrorModalComponent) return;
|
if (ErrorModalComponent) return;
|
||||||
ErrorModalComponent = (await import('$lib/components/ErrorModal.svelte')).default;
|
ErrorModalComponent = (await import('$lib/components/ui/ErrorModal.svelte')).default;
|
||||||
}
|
}
|
||||||
|
|
||||||
function validateEmail(raw: string): string {
|
function validateEmail(raw: string): string {
|
||||||
+5
-5
@@ -1,14 +1,14 @@
|
|||||||
<script lang="ts">
|
<script lang="ts">
|
||||||
import { onMount } from 'svelte';
|
import { onMount } from 'svelte';
|
||||||
import { fade } from 'svelte/transition';
|
import { fade } from 'svelte/transition';
|
||||||
import Icon from '$lib/components/Icon.svelte';
|
import Icon from '$lib/components/ui/Icon.svelte';
|
||||||
import { reveal } from '$lib/actions/reveal';
|
import { reveal } from '$lib/actions/reveal';
|
||||||
import type { BookingContent } from '$lib/types';
|
import type { BookingContent } from '$lib/types';
|
||||||
import { promoteJourney, trackEvent } from '$lib/analytics';
|
import { promoteJourney, trackEvent } from '$lib/analytics';
|
||||||
import { trackAb, type AbContext } from '$lib/ab';
|
import { trackAb, type AbContext } from '$lib/ab';
|
||||||
|
|
||||||
type SuccessModalComponentType = typeof import('$lib/components/SuccessModal.svelte').default;
|
type SuccessModalComponentType = typeof import('$lib/components/ui/SuccessModal.svelte').default;
|
||||||
type ErrorModalComponentType = typeof import('$lib/components/ErrorModal.svelte').default;
|
type ErrorModalComponentType = typeof import('$lib/components/ui/ErrorModal.svelte').default;
|
||||||
|
|
||||||
export let booking: BookingContent;
|
export let booking: BookingContent;
|
||||||
export let pagePath = '';
|
export let pagePath = '';
|
||||||
@@ -156,12 +156,12 @@
|
|||||||
|
|
||||||
async function ensureSuccessModal() {
|
async function ensureSuccessModal() {
|
||||||
if (SuccessModalComponent) return;
|
if (SuccessModalComponent) return;
|
||||||
SuccessModalComponent = (await import('$lib/components/SuccessModal.svelte')).default;
|
SuccessModalComponent = (await import('$lib/components/ui/SuccessModal.svelte')).default;
|
||||||
}
|
}
|
||||||
|
|
||||||
async function ensureErrorModal() {
|
async function ensureErrorModal() {
|
||||||
if (ErrorModalComponent) return;
|
if (ErrorModalComponent) return;
|
||||||
ErrorModalComponent = (await import('$lib/components/ErrorModal.svelte')).default;
|
ErrorModalComponent = (await import('$lib/components/ui/ErrorModal.svelte')).default;
|
||||||
}
|
}
|
||||||
|
|
||||||
function noteInteraction() {
|
function noteInteraction() {
|
||||||
+1
-1
@@ -1,6 +1,6 @@
|
|||||||
<script lang="ts">
|
<script lang="ts">
|
||||||
import { accordion } from '$lib/actions/accordion';
|
import { accordion } from '$lib/actions/accordion';
|
||||||
import Icon from '$lib/components/Icon.svelte';
|
import Icon from '$lib/components/ui/Icon.svelte';
|
||||||
import type { FaqItem } from '$lib/types';
|
import type { FaqItem } from '$lib/types';
|
||||||
|
|
||||||
export let title = 'FAQs';
|
export let title = 'FAQs';
|
||||||
+1
-1
@@ -1,6 +1,6 @@
|
|||||||
<script lang="ts">
|
<script lang="ts">
|
||||||
import { reveal } from '$lib/actions/reveal';
|
import { reveal } from '$lib/actions/reveal';
|
||||||
import Icon from '$lib/components/Icon.svelte';
|
import Icon from '$lib/components/ui/Icon.svelte';
|
||||||
import { getEnhancedImage } from '$lib/enhanced-images';
|
import { getEnhancedImage } from '$lib/enhanced-images';
|
||||||
import type { FounderStoryContent } from '$lib/types';
|
import type { FounderStoryContent } from '$lib/types';
|
||||||
|
|
||||||
+1
-1
@@ -1,6 +1,6 @@
|
|||||||
<script lang="ts">
|
<script lang="ts">
|
||||||
import { onMount } from 'svelte';
|
import { onMount } from 'svelte';
|
||||||
import Icon from '$lib/components/Icon.svelte';
|
import Icon from '$lib/components/ui/Icon.svelte';
|
||||||
import type { CallToAction, HeroContent } from '$lib/types';
|
import type { CallToAction, HeroContent } from '$lib/types';
|
||||||
import { trackAb, type AbContext } from '$lib/ab';
|
import { trackAb, type AbContext } from '$lib/ab';
|
||||||
|
|
||||||
+1
-1
@@ -1,6 +1,6 @@
|
|||||||
<script lang="ts">
|
<script lang="ts">
|
||||||
import { reveal } from '$lib/actions/reveal';
|
import { reveal } from '$lib/actions/reveal';
|
||||||
import Icon from '$lib/components/Icon.svelte';
|
import Icon from '$lib/components/ui/Icon.svelte';
|
||||||
import type { HowItWorksContent } from '$lib/types';
|
import type { HowItWorksContent } from '$lib/types';
|
||||||
|
|
||||||
export let content: HowItWorksContent;
|
export let content: HowItWorksContent;
|
||||||
+2
-2
@@ -1,6 +1,6 @@
|
|||||||
<script lang="ts">
|
<script lang="ts">
|
||||||
import Icon from '$lib/components/Icon.svelte';
|
import Icon from '$lib/components/ui/Icon.svelte';
|
||||||
import FaqSection from '$lib/components/FaqSection.svelte';
|
import FaqSection from '$lib/components/sections/FaqSection.svelte';
|
||||||
import { locationPages } from '$lib/content/locations';
|
import { locationPages } from '$lib/content/locations';
|
||||||
import type { InfoContent } from '$lib/types';
|
import type { InfoContent } from '$lib/types';
|
||||||
|
|
||||||
+1
-1
@@ -1,5 +1,5 @@
|
|||||||
<script lang="ts">
|
<script lang="ts">
|
||||||
import Icon from '$lib/components/Icon.svelte';
|
import Icon from '$lib/components/ui/Icon.svelte';
|
||||||
import type { HomePageContent } from '$lib/types';
|
import type { HomePageContent } from '$lib/types';
|
||||||
|
|
||||||
export let instagram: HomePageContent['instagram'];
|
export let instagram: HomePageContent['instagram'];
|
||||||
+1
-1
@@ -1,5 +1,5 @@
|
|||||||
<script lang="ts">
|
<script lang="ts">
|
||||||
import Icon from '$lib/components/Icon.svelte';
|
import Icon from '$lib/components/ui/Icon.svelte';
|
||||||
import type { IntroContent } from '$lib/types';
|
import type { IntroContent } from '$lib/types';
|
||||||
|
|
||||||
export let intro: IntroContent;
|
export let intro: IntroContent;
|
||||||
+5
-3
@@ -1,6 +1,6 @@
|
|||||||
<script lang="ts">
|
<script lang="ts">
|
||||||
import { createEventDispatcher } from 'svelte';
|
import { createEventDispatcher } from 'svelte';
|
||||||
import Icon from '$lib/components/Icon.svelte';
|
import Icon from '$lib/components/ui/Icon.svelte';
|
||||||
|
|
||||||
export let context: 'onboarding' | 'contract' | 'owner' = 'onboarding';
|
export let context: 'onboarding' | 'contract' | 'owner' = 'onboarding';
|
||||||
$: introText =
|
$: introText =
|
||||||
@@ -8,7 +8,7 @@
|
|||||||
? "Enter the email address you used when enquiring with Goodwalk. We'll send you a one-time code to continue your contract."
|
? "Enter the email address you used when enquiring with Goodwalk. We'll send you a one-time code to continue your contract."
|
||||||
: "Enter the email address you used when enquiring with Goodwalk. We'll send you a one-time code to continue your onboarding.";
|
: "Enter the email address you used when enquiring with Goodwalk. We'll send you a one-time code to continue your onboarding.";
|
||||||
|
|
||||||
const dispatch = createEventDispatcher<{ authenticated: { email: string; profile: Record<string, unknown>; draft: Record<string, unknown>; cpAdmin?: boolean; ownerEmail?: string } }>();
|
const dispatch = createEventDispatcher<{ authenticated: { email: string; profile: Record<string, unknown>; draft: Record<string, unknown>; cpAdmin?: boolean; ownerEmail?: string; previewEmails?: string[] } }>();
|
||||||
|
|
||||||
const ownerEmail = 'info@goodwalk.co.nz';
|
const ownerEmail = 'info@goodwalk.co.nz';
|
||||||
const ownerPhone = '(022) 642 1011';
|
const ownerPhone = '(022) 642 1011';
|
||||||
@@ -58,6 +58,7 @@
|
|||||||
let draft: Record<string, unknown> = {};
|
let draft: Record<string, unknown> = {};
|
||||||
let cpAdmin = false;
|
let cpAdmin = false;
|
||||||
let verifiedOwnerEmail = '';
|
let verifiedOwnerEmail = '';
|
||||||
|
let previewEmails: string[] = [];
|
||||||
try {
|
try {
|
||||||
const verifyRes = await fetch('/api/auth/verify', {
|
const verifyRes = await fetch('/api/auth/verify', {
|
||||||
headers: { Authorization: `Bearer ${data.token}` },
|
headers: { Authorization: `Bearer ${data.token}` },
|
||||||
@@ -68,9 +69,10 @@
|
|||||||
draft = verifyData.draft ?? {};
|
draft = verifyData.draft ?? {};
|
||||||
cpAdmin = Boolean(verifyData.cpAdmin);
|
cpAdmin = Boolean(verifyData.cpAdmin);
|
||||||
verifiedOwnerEmail = typeof verifyData.ownerEmail === 'string' ? verifyData.ownerEmail : '';
|
verifiedOwnerEmail = typeof verifyData.ownerEmail === 'string' ? verifyData.ownerEmail : '';
|
||||||
|
previewEmails = Array.isArray(verifyData.previewEmails) ? verifyData.previewEmails.filter((value: unknown): value is string => typeof value === 'string') : [];
|
||||||
}
|
}
|
||||||
} catch { /* ignore */ }
|
} catch { /* ignore */ }
|
||||||
dispatch('authenticated', { email: data.email, profile, draft, cpAdmin, ownerEmail: verifiedOwnerEmail });
|
dispatch('authenticated', { email: data.email, profile, draft, cpAdmin, ownerEmail: verifiedOwnerEmail, previewEmails });
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
error = e instanceof Error ? e.message : 'Something went wrong';
|
error = e instanceof Error ? e.message : 'Something went wrong';
|
||||||
} finally {
|
} finally {
|
||||||
+1
-1
@@ -1,5 +1,5 @@
|
|||||||
<script lang="ts">
|
<script lang="ts">
|
||||||
import Icon from '$lib/components/Icon.svelte';
|
import Icon from '$lib/components/ui/Icon.svelte';
|
||||||
import { getEnhancedImage } from '$lib/enhanced-images';
|
import { getEnhancedImage } from '$lib/enhanced-images';
|
||||||
import type { CallToAction, HeroChip } from '$lib/types';
|
import type { CallToAction, HeroChip } from '$lib/types';
|
||||||
|
|
||||||
+1
-1
@@ -1,6 +1,6 @@
|
|||||||
<script lang="ts">
|
<script lang="ts">
|
||||||
import { reveal } from '$lib/actions/reveal';
|
import { reveal } from '$lib/actions/reveal';
|
||||||
import Icon from '$lib/components/Icon.svelte';
|
import Icon from '$lib/components/ui/Icon.svelte';
|
||||||
import type { IconCard } from '$lib/types';
|
import type { IconCard } from '$lib/types';
|
||||||
|
|
||||||
export let services: IconCard[];
|
export let services: IconCard[];
|
||||||
+1
-1
@@ -1,7 +1,7 @@
|
|||||||
<script lang="ts">
|
<script lang="ts">
|
||||||
import { onMount, tick } from 'svelte';
|
import { onMount, tick } from 'svelte';
|
||||||
import { reveal } from '$lib/actions/reveal';
|
import { reveal } from '$lib/actions/reveal';
|
||||||
import Icon from '$lib/components/Icon.svelte';
|
import Icon from '$lib/components/ui/Icon.svelte';
|
||||||
import { getEnhancedImage } from '$lib/enhanced-images';
|
import { getEnhancedImage } from '$lib/enhanced-images';
|
||||||
import { getSeededTestimonialIndex } from '$lib/testimonials';
|
import { getSeededTestimonialIndex } from '$lib/testimonials';
|
||||||
import type { TestimonialContent } from '$lib/types';
|
import type { TestimonialContent } from '$lib/types';
|
||||||
+1
-1
@@ -1,7 +1,7 @@
|
|||||||
<script lang="ts">
|
<script lang="ts">
|
||||||
import { reveal } from '$lib/actions/reveal';
|
import { reveal } from '$lib/actions/reveal';
|
||||||
import { getEnhancedImage } from '$lib/enhanced-images';
|
import { getEnhancedImage } from '$lib/enhanced-images';
|
||||||
import Icon from '$lib/components/Icon.svelte';
|
import Icon from '$lib/components/ui/Icon.svelte';
|
||||||
import type { IconCard } from '$lib/types';
|
import type { IconCard } from '$lib/types';
|
||||||
|
|
||||||
export let values: IconCard[];
|
export let values: IconCard[];
|
||||||
@@ -1,5 +1,5 @@
|
|||||||
<script lang="ts">
|
<script lang="ts">
|
||||||
import Icon from '$lib/components/Icon.svelte';
|
import Icon from '$lib/components/ui/Icon.svelte';
|
||||||
|
|
||||||
export let eyebrow = 'Get in touch';
|
export let eyebrow = 'Get in touch';
|
||||||
export let title: string;
|
export let title: string;
|
||||||
@@ -1,5 +1,5 @@
|
|||||||
<script lang="ts">
|
<script lang="ts">
|
||||||
import ModalShell from '$lib/components/ModalShell.svelte';
|
import ModalShell from '$lib/components/ui/ModalShell.svelte';
|
||||||
|
|
||||||
export let email = 'info@goodwalk.co.nz';
|
export let email = 'info@goodwalk.co.nz';
|
||||||
export let enquiryType: 'booking' | 'general' = 'booking';
|
export let enquiryType: 'booking' | 'general' = 'booking';
|
||||||
@@ -1,5 +1,5 @@
|
|||||||
<script lang="ts">
|
<script lang="ts">
|
||||||
import Icon from '$lib/components/Icon.svelte';
|
import Icon from '$lib/components/ui/Icon.svelte';
|
||||||
import type { FooterContent, LinkItem } from '$lib/types';
|
import type { FooterContent, LinkItem } from '$lib/types';
|
||||||
import { locationPages } from '$lib/content/locations';
|
import { locationPages } from '$lib/content/locations';
|
||||||
|
|
||||||
@@ -1,7 +1,7 @@
|
|||||||
<script lang="ts">
|
<script lang="ts">
|
||||||
import { page } from '$app/stores';
|
import { page } from '$app/stores';
|
||||||
import { onMount } from 'svelte';
|
import { onMount } from 'svelte';
|
||||||
import Icon from '$lib/components/Icon.svelte';
|
import Icon from '$lib/components/ui/Icon.svelte';
|
||||||
import type { NavigationContent } from '$lib/types';
|
import type { NavigationContent } from '$lib/types';
|
||||||
import type { Picture } from '@sveltejs/enhanced-img';
|
import type { Picture } from '@sveltejs/enhanced-img';
|
||||||
import logoDesktop from '$lib/images/goodwalk-auckland-dog-walking-logo.png?enhanced';
|
import logoDesktop from '$lib/images/goodwalk-auckland-dog-walking-logo.png?enhanced';
|
||||||
@@ -2,7 +2,7 @@ import { fireEvent, render } from '@testing-library/svelte';
|
|||||||
import { describe, expect, it } from 'vitest';
|
import { describe, expect, it } from 'vitest';
|
||||||
import Header from './Header.svelte';
|
import Header from './Header.svelte';
|
||||||
import { homepageContent } from '$lib/content/homepage';
|
import { homepageContent } from '$lib/content/homepage';
|
||||||
import { setMockPage } from '../../test/mocks/app-stores';
|
import { setMockPage } from '../../../test/mocks/app-stores';
|
||||||
|
|
||||||
describe('Header', () => {
|
describe('Header', () => {
|
||||||
it('marks the services link active for service detail pages', () => {
|
it('marks the services link active for service detail pages', () => {
|
||||||
+1
-1
@@ -2,7 +2,7 @@
|
|||||||
import { onMount, tick } from 'svelte';
|
import { onMount, tick } from 'svelte';
|
||||||
import { afterNavigate } from '$app/navigation';
|
import { afterNavigate } from '$app/navigation';
|
||||||
import { page } from '$app/stores';
|
import { page } from '$app/stores';
|
||||||
import Icon from '$lib/components/Icon.svelte';
|
import Icon from '$lib/components/ui/Icon.svelte';
|
||||||
import { isMobileCtaButtonEnabled } from '$lib/feature-flags';
|
import { isMobileCtaButtonEnabled } from '$lib/feature-flags';
|
||||||
|
|
||||||
/*
|
/*
|
||||||
+1
-1
@@ -1,6 +1,6 @@
|
|||||||
<script lang="ts">
|
<script lang="ts">
|
||||||
import { createEventDispatcher } from 'svelte';
|
import { createEventDispatcher } from 'svelte';
|
||||||
import Icon from '$lib/components/Icon.svelte';
|
import Icon from '$lib/components/ui/Icon.svelte';
|
||||||
|
|
||||||
export let email = '';
|
export let email = '';
|
||||||
|
|
||||||
+1
-1
@@ -1,5 +1,5 @@
|
|||||||
<script lang="ts">
|
<script lang="ts">
|
||||||
import Icon from '$lib/components/Icon.svelte';
|
import Icon from '$lib/components/ui/Icon.svelte';
|
||||||
|
|
||||||
export let plan: {
|
export let plan: {
|
||||||
title: string;
|
title: string;
|
||||||
@@ -1,6 +1,6 @@
|
|||||||
<script lang="ts">
|
<script lang="ts">
|
||||||
import { onMount } from 'svelte';
|
import { onMount } from 'svelte';
|
||||||
import ModalShell from '$lib/components/ModalShell.svelte';
|
import ModalShell from '$lib/components/ui/ModalShell.svelte';
|
||||||
|
|
||||||
export let firstName: string;
|
export let firstName: string;
|
||||||
export let petName: string;
|
export let petName: string;
|
||||||
@@ -36,9 +36,9 @@ export const homepageContent: HomePageContent = {
|
|||||||
title: 'Come home to a',
|
title: 'Come home to a',
|
||||||
highlight: 'calm, happy dog',
|
highlight: 'calm, happy dog',
|
||||||
mobileTitle: 'Come home to a\ncalm, happy dog',
|
mobileTitle: 'Come home to a\ncalm, happy dog',
|
||||||
seoHeading: 'Dog walking across Auckland Central',
|
seoHeading: 'Dog walking for Auckland\'s small & medium dogs',
|
||||||
subtitle:
|
subtitle:
|
||||||
'Trusted dog walking for Auckland\'s small and medium dogs. Thoughtful matching, familiar routines, and calmer evenings at home.',
|
'The same trusted walker every time, in small groups. Never a rotating roster.',
|
||||||
primaryCta: { label: 'Book a Meet & Greet', href: '#newlead', variant: 'yellow' },
|
primaryCta: { label: 'Book a Meet & Greet', href: '#newlead', variant: 'yellow' },
|
||||||
secondaryCta: {
|
secondaryCta: {
|
||||||
label: 'See how it works',
|
label: 'See how it works',
|
||||||
|
|||||||
@@ -260,35 +260,32 @@
|
|||||||
/* ── Full-bleed mobile hero ── */
|
/* ── Full-bleed mobile hero ── */
|
||||||
|
|
||||||
#hero {
|
#hero {
|
||||||
|
--hero-photo-band: 46svh;
|
||||||
min-height: 90svh;
|
min-height: 90svh;
|
||||||
padding: 0;
|
padding: 0;
|
||||||
gap: 0;
|
gap: 0;
|
||||||
flex-direction: column;
|
flex-direction: column;
|
||||||
justify-content: flex-end;
|
justify-content: flex-start;
|
||||||
}
|
}
|
||||||
|
|
||||||
/* Full-section gradient: transparent top → Goodwalk green bottom.
|
/* Two-zone mobile hero: the photo sits in its own clear band at the top so
|
||||||
hero-img is now a sibling of hero-inner (direct child of #hero),
|
Maya's face is never covered, and the copy sits below on solid Goodwalk
|
||||||
so this ::after correctly overlays the full section. */
|
green. This ::after only softens the seam where the photo meets the green
|
||||||
|
text zone; it no longer veils the dog. */
|
||||||
#hero::after {
|
#hero::after {
|
||||||
inset: 0;
|
inset: auto 0 auto 0;
|
||||||
height: auto;
|
top: calc(var(--hero-photo-band) - 72px);
|
||||||
background: linear-gradient(
|
height: 72px;
|
||||||
to bottom,
|
background: linear-gradient(to bottom, transparent, var(--surface-brand));
|
||||||
transparent 22%,
|
|
||||||
rgba(var(--brand-rgb), 0.45) 48%,
|
|
||||||
rgba(var(--brand-rgb), 0.88) 65%,
|
|
||||||
var(--surface-brand) 78%
|
|
||||||
);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/* Dog photo fills the entire section as a background layer */
|
/* Dog photo occupies a dedicated band at the top, not a full-bleed layer */
|
||||||
.hero-img {
|
.hero-img {
|
||||||
position: absolute;
|
position: relative;
|
||||||
inset: 0;
|
inset: auto;
|
||||||
z-index: 0;
|
z-index: 0;
|
||||||
width: 100%;
|
width: 100%;
|
||||||
height: 100%;
|
height: var(--hero-photo-band);
|
||||||
margin: 0;
|
margin: 0;
|
||||||
flex: none;
|
flex: none;
|
||||||
display: block;
|
display: block;
|
||||||
@@ -306,7 +303,7 @@
|
|||||||
max-width: none;
|
max-width: none;
|
||||||
margin: 0;
|
margin: 0;
|
||||||
object-fit: cover;
|
object-fit: cover;
|
||||||
object-position: 37% center;
|
object-position: 35% 30%;
|
||||||
transform: none;
|
transform: none;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -319,7 +316,7 @@
|
|||||||
width: 100%;
|
width: 100%;
|
||||||
max-width: none;
|
max-width: none;
|
||||||
margin: 0;
|
margin: 0;
|
||||||
padding: 0 var(--space-container-x-mobile) 36px;
|
padding: 24px var(--space-container-x-mobile) 36px;
|
||||||
}
|
}
|
||||||
|
|
||||||
.hero-text {
|
.hero-text {
|
||||||
|
|||||||
@@ -3,8 +3,8 @@
|
|||||||
import { navigating, page } from '$app/stores';
|
import { navigating, page } from '$app/stores';
|
||||||
import { afterNavigate, disableScrollHandling } from '$app/navigation';
|
import { afterNavigate, disableScrollHandling } from '$app/navigation';
|
||||||
import { initClickTracking, trackPageView } from '$lib/analytics';
|
import { initClickTracking, trackPageView } from '$lib/analytics';
|
||||||
import MobileBookBar from '$lib/components/MobileBookBar.svelte';
|
import MobileBookBar from '$lib/components/ui/MobileBookBar.svelte';
|
||||||
import RouteSkeleton from '$lib/components/RouteSkeleton.svelte';
|
import RouteSkeleton from '$lib/components/ui/RouteSkeleton.svelte';
|
||||||
import { isMobileCtaButtonEnabled } from '$lib/feature-flags';
|
import { isMobileCtaButtonEnabled } from '$lib/feature-flags';
|
||||||
import readex400Woff2 from '@fontsource/readex-pro/files/readex-pro-latin-400-normal.woff2?url';
|
import readex400Woff2 from '@fontsource/readex-pro/files/readex-pro-latin-400-normal.woff2?url';
|
||||||
import readex700Woff2 from '@fontsource/readex-pro/files/readex-pro-latin-700-normal.woff2?url';
|
import readex700Woff2 from '@fontsource/readex-pro/files/readex-pro-latin-700-normal.woff2?url';
|
||||||
|
|||||||
+13
-26
@@ -1,17 +1,17 @@
|
|||||||
<script lang="ts">
|
<script lang="ts">
|
||||||
import SeoHead from '$lib/components/SeoHead.svelte';
|
import SeoHead from '$lib/components/ui/SeoHead.svelte';
|
||||||
import Footer from '$lib/components/Footer.svelte';
|
import Footer from '$lib/components/ui/Footer.svelte';
|
||||||
import Header from '$lib/components/Header.svelte';
|
import Header from '$lib/components/ui/Header.svelte';
|
||||||
import HeroSection from '$lib/components/HeroSection.svelte';
|
import HeroSection from '$lib/components/sections/HeroSection.svelte';
|
||||||
import HowItWorksSection from '$lib/components/HowItWorksSection.svelte';
|
import HowItWorksSection from '$lib/components/sections/HowItWorksSection.svelte';
|
||||||
import InfoSection from '$lib/components/InfoSection.svelte';
|
import InfoSection from '$lib/components/sections/InfoSection.svelte';
|
||||||
import InstagramSection from '$lib/components/InstagramSection.svelte';
|
import InstagramSection from '$lib/components/sections/InstagramSection.svelte';
|
||||||
import BookingWizard from '$lib/components/BookingWizard.svelte';
|
import BookingWizard from '$lib/components/sections/BookingWizard.svelte';
|
||||||
import FounderStorySection from '$lib/components/FounderStorySection.svelte';
|
import FounderStorySection from '$lib/components/sections/FounderStorySection.svelte';
|
||||||
import ServicesSection from '$lib/components/ServicesSection.svelte';
|
import ServicesSection from '$lib/components/sections/ServicesSection.svelte';
|
||||||
import TestimonialsSection from '$lib/components/TestimonialsSection.svelte';
|
import TestimonialsSection from '$lib/components/sections/TestimonialsSection.svelte';
|
||||||
import ValuesSection from '$lib/components/ValuesSection.svelte';
|
import ValuesSection from '$lib/components/sections/ValuesSection.svelte';
|
||||||
import OnboardingPage from '$lib/components/OnboardingPage.svelte';
|
import OnboardingPage from '$lib/components/pages/OnboardingPage.svelte';
|
||||||
import { buildAreaServed } from '$lib/seo';
|
import { buildAreaServed } from '$lib/seo';
|
||||||
import type { PageData } from './$types';
|
import type { PageData } from './$types';
|
||||||
import type { CallToAction } from '$lib/types';
|
import type { CallToAction } from '$lib/types';
|
||||||
@@ -139,19 +139,6 @@
|
|||||||
name: 'Public Liability Insurance'
|
name: 'Public Liability Insurance'
|
||||||
}
|
}
|
||||||
]
|
]
|
||||||
},
|
|
||||||
{
|
|
||||||
'@context': 'https://schema.org',
|
|
||||||
'@type': 'HowTo',
|
|
||||||
name: 'How to start with Goodwalk',
|
|
||||||
description: data.content.howItWorks.intro,
|
|
||||||
totalTime: 'P14D',
|
|
||||||
step: data.content.howItWorks.steps.map((step, index) => ({
|
|
||||||
'@type': 'HowToStep',
|
|
||||||
position: index + 1,
|
|
||||||
name: step.title,
|
|
||||||
text: step.body
|
|
||||||
}))
|
|
||||||
}
|
}
|
||||||
]
|
]
|
||||||
: [];
|
: [];
|
||||||
|
|||||||
@@ -1,15 +1,15 @@
|
|||||||
<script lang="ts">
|
<script lang="ts">
|
||||||
import SeoHead from '$lib/components/SeoHead.svelte';
|
import SeoHead from '$lib/components/ui/SeoHead.svelte';
|
||||||
import AboutPage from '$lib/components/AboutPage.svelte';
|
import AboutPage from '$lib/components/pages/AboutPage.svelte';
|
||||||
import Footer from '$lib/components/Footer.svelte';
|
import Footer from '$lib/components/ui/Footer.svelte';
|
||||||
import Header from '$lib/components/Header.svelte';
|
import Header from '$lib/components/ui/Header.svelte';
|
||||||
import BookingPage from '$lib/components/BookingPage.svelte';
|
import BookingPage from '$lib/components/pages/BookingPage.svelte';
|
||||||
import LegalPage from '$lib/components/LegalPage.svelte';
|
import LegalPage from '$lib/components/pages/LegalPage.svelte';
|
||||||
import PageHeader from '$lib/components/PageHeader.svelte';
|
import PageHeader from '$lib/components/ui/PageHeader.svelte';
|
||||||
import PricingPage from '$lib/components/PricingPage.svelte';
|
import PricingPage from '$lib/components/pages/PricingPage.svelte';
|
||||||
import TestimonialsPage from '$lib/components/TestimonialsPage.svelte';
|
import TestimonialsPage from '$lib/components/pages/TestimonialsPage.svelte';
|
||||||
import { aboutPageContent } from '$lib/content/about';
|
import { aboutPageContent } from '$lib/content/about';
|
||||||
import ServiceLandingPage from '$lib/components/ServiceLandingPage.svelte';
|
import ServiceLandingPage from '$lib/components/pages/ServiceLandingPage.svelte';
|
||||||
import { dogWalkingContent } from '$lib/content/dog-walking';
|
import { dogWalkingContent } from '$lib/content/dog-walking';
|
||||||
import { ourPricingContent } from '$lib/content/our-pricing';
|
import { ourPricingContent } from '$lib/content/our-pricing';
|
||||||
import { packWalksContent } from '$lib/content/pack-walks';
|
import { packWalksContent } from '$lib/content/pack-walks';
|
||||||
|
|||||||
@@ -1,5 +1,5 @@
|
|||||||
<script lang="ts">
|
<script lang="ts">
|
||||||
import ContractPage from '$lib/components/ContractPage.svelte';
|
import ContractPage from '$lib/components/pages/ContractPage.svelte';
|
||||||
import type { PageData } from './$types';
|
import type { PageData } from './$types';
|
||||||
|
|
||||||
export let data: PageData;
|
export let data: PageData;
|
||||||
|
|||||||
@@ -1,7 +1,7 @@
|
|||||||
<script lang="ts">
|
<script lang="ts">
|
||||||
import Header from '$lib/components/Header.svelte';
|
import Header from '$lib/components/ui/Header.svelte';
|
||||||
import Footer from '$lib/components/Footer.svelte';
|
import Footer from '$lib/components/ui/Footer.svelte';
|
||||||
import SeoHead from '$lib/components/SeoHead.svelte';
|
import SeoHead from '$lib/components/ui/SeoHead.svelte';
|
||||||
import { locationPages } from '$lib/content/locations';
|
import { locationPages } from '$lib/content/locations';
|
||||||
import { buildBreadcrumb } from '$lib/seo';
|
import { buildBreadcrumb } from '$lib/seo';
|
||||||
import type { PageData } from './$types';
|
import type { PageData } from './$types';
|
||||||
|
|||||||
@@ -1,8 +1,8 @@
|
|||||||
<script lang="ts">
|
<script lang="ts">
|
||||||
import Header from '$lib/components/Header.svelte';
|
import Header from '$lib/components/ui/Header.svelte';
|
||||||
import Footer from '$lib/components/Footer.svelte';
|
import Footer from '$lib/components/ui/Footer.svelte';
|
||||||
import SeoHead from '$lib/components/SeoHead.svelte';
|
import SeoHead from '$lib/components/ui/SeoHead.svelte';
|
||||||
import LocationPage from '$lib/components/LocationPage.svelte';
|
import LocationPage from '$lib/components/pages/LocationPage.svelte';
|
||||||
import { buildLocationSeo } from '$lib/seo';
|
import { buildLocationSeo } from '$lib/seo';
|
||||||
import type { PageData } from './$types';
|
import type { PageData } from './$types';
|
||||||
|
|
||||||
|
|||||||
@@ -10,13 +10,13 @@
|
|||||||
<script lang="ts">
|
<script lang="ts">
|
||||||
import { onMount } from 'svelte';
|
import { onMount } from 'svelte';
|
||||||
import { fade } from 'svelte/transition';
|
import { fade } from 'svelte/transition';
|
||||||
import Footer from '$lib/components/Footer.svelte';
|
import Footer from '$lib/components/ui/Footer.svelte';
|
||||||
import Header from '$lib/components/Header.svelte';
|
import Header from '$lib/components/ui/Header.svelte';
|
||||||
import Icon from '$lib/components/Icon.svelte';
|
import Icon from '$lib/components/ui/Icon.svelte';
|
||||||
import { homepageContent } from '$lib/content/homepage';
|
import { homepageContent } from '$lib/content/homepage';
|
||||||
|
|
||||||
type SuccessModalComponentType = typeof import('$lib/components/SuccessModal.svelte').default;
|
type SuccessModalComponentType = typeof import('$lib/components/ui/SuccessModal.svelte').default;
|
||||||
type ErrorModalComponentType = typeof import('$lib/components/ErrorModal.svelte').default;
|
type ErrorModalComponentType = typeof import('$lib/components/ui/ErrorModal.svelte').default;
|
||||||
|
|
||||||
const navigation = homepageContent.navigation;
|
const navigation = homepageContent.navigation;
|
||||||
const footerContent = homepageContent.footer;
|
const footerContent = homepageContent.footer;
|
||||||
@@ -131,11 +131,11 @@
|
|||||||
|
|
||||||
async function ensureSuccessModal() {
|
async function ensureSuccessModal() {
|
||||||
if (SuccessModalComponent) return;
|
if (SuccessModalComponent) return;
|
||||||
SuccessModalComponent = (await import('$lib/components/SuccessModal.svelte')).default;
|
SuccessModalComponent = (await import('$lib/components/ui/SuccessModal.svelte')).default;
|
||||||
}
|
}
|
||||||
async function ensureErrorModal() {
|
async function ensureErrorModal() {
|
||||||
if (ErrorModalComponent) return;
|
if (ErrorModalComponent) return;
|
||||||
ErrorModalComponent = (await import('$lib/components/ErrorModal.svelte')).default;
|
ErrorModalComponent = (await import('$lib/components/ui/ErrorModal.svelte')).default;
|
||||||
}
|
}
|
||||||
|
|
||||||
function noteInteraction() {
|
function noteInteraction() {
|
||||||
|
|||||||
@@ -1,7 +1,7 @@
|
|||||||
<script lang="ts">
|
<script lang="ts">
|
||||||
import { onMount } from 'svelte';
|
import { onMount } from 'svelte';
|
||||||
import Icon from '$lib/components/Icon.svelte';
|
import Icon from '$lib/components/ui/Icon.svelte';
|
||||||
import PageHeader from '$lib/components/PageHeader.svelte';
|
import PageHeader from '$lib/components/ui/PageHeader.svelte';
|
||||||
import VariantApplicationFields from '$lib/components/contact-form-variants/VariantApplicationFields.svelte';
|
import VariantApplicationFields from '$lib/components/contact-form-variants/VariantApplicationFields.svelte';
|
||||||
|
|
||||||
type VariantTab = {
|
type VariantTab = {
|
||||||
|
|||||||
@@ -1,5 +1,5 @@
|
|||||||
<script lang="ts">
|
<script lang="ts">
|
||||||
import Icon from '$lib/components/Icon.svelte';
|
import Icon from '$lib/components/ui/Icon.svelte';
|
||||||
|
|
||||||
const heroImage = '/images/smiling-happy-dog-maya.webp';
|
const heroImage = '/images/smiling-happy-dog-maya.webp';
|
||||||
|
|
||||||
|
|||||||
@@ -1,5 +1,5 @@
|
|||||||
<script lang="ts">
|
<script lang="ts">
|
||||||
import Icon from '$lib/components/Icon.svelte';
|
import Icon from '$lib/components/ui/Icon.svelte';
|
||||||
|
|
||||||
const instagramHref = 'https://www.instagram.com/goodwalk.nz/';
|
const instagramHref = 'https://www.instagram.com/goodwalk.nz/';
|
||||||
|
|
||||||
|
|||||||
@@ -1,16 +1,16 @@
|
|||||||
<script lang="ts">
|
<script lang="ts">
|
||||||
import SeoHead from '$lib/components/SeoHead.svelte';
|
import SeoHead from '$lib/components/ui/SeoHead.svelte';
|
||||||
import Footer from '$lib/components/Footer.svelte';
|
import Footer from '$lib/components/ui/Footer.svelte';
|
||||||
import Header from '$lib/components/Header.svelte';
|
import Header from '$lib/components/ui/Header.svelte';
|
||||||
import HeroSection from '$lib/components/HeroSection.svelte';
|
import HeroSection from '$lib/components/sections/HeroSection.svelte';
|
||||||
import HowItWorksSection from '$lib/components/HowItWorksSection.svelte';
|
import HowItWorksSection from '$lib/components/sections/HowItWorksSection.svelte';
|
||||||
import InfoSection from '$lib/components/InfoSection.svelte';
|
import InfoSection from '$lib/components/sections/InfoSection.svelte';
|
||||||
import InstagramSection from '$lib/components/InstagramSection.svelte';
|
import InstagramSection from '$lib/components/sections/InstagramSection.svelte';
|
||||||
import BookingWizard from '$lib/components/BookingWizard.svelte';
|
import BookingWizard from '$lib/components/sections/BookingWizard.svelte';
|
||||||
import FounderStorySection from '$lib/components/FounderStorySection.svelte';
|
import FounderStorySection from '$lib/components/sections/FounderStorySection.svelte';
|
||||||
import ServicesSection from '$lib/components/ServicesSection.svelte';
|
import ServicesSection from '$lib/components/sections/ServicesSection.svelte';
|
||||||
import TestimonialsSection from '$lib/components/TestimonialsSection.svelte';
|
import TestimonialsSection from '$lib/components/sections/TestimonialsSection.svelte';
|
||||||
import ValuesSection from '$lib/components/ValuesSection.svelte';
|
import ValuesSection from '$lib/components/sections/ValuesSection.svelte';
|
||||||
import type { PageData } from './$types';
|
import type { PageData } from './$types';
|
||||||
|
|
||||||
export let data: PageData;
|
export let data: PageData;
|
||||||
|
|||||||
@@ -169,6 +169,7 @@ export default defineConfig(() => {
|
|||||||
|
|
||||||
const createMailProxy = (apiPath: keyof typeof proxyRoutes): ProxyOptions => ({
|
const createMailProxy = (apiPath: keyof typeof proxyRoutes): ProxyOptions => ({
|
||||||
target: submitProxyTarget,
|
target: submitProxyTarget,
|
||||||
|
changeOrigin: true,
|
||||||
rewrite: (path) => path.replace(apiPath, proxyRoutes[apiPath]),
|
rewrite: (path) => path.replace(apiPath, proxyRoutes[apiPath]),
|
||||||
configure(proxy) {
|
configure(proxy) {
|
||||||
proxy.on('error', (error: NodeJS.ErrnoException, req, res) => {
|
proxy.on('error', (error: NodeJS.ErrnoException, req, res) => {
|
||||||
@@ -213,14 +214,17 @@ export default defineConfig(() => {
|
|||||||
'/api/contract-submit': createMailProxy('/api/contract-submit'),
|
'/api/contract-submit': createMailProxy('/api/contract-submit'),
|
||||||
'/api/auth': {
|
'/api/auth': {
|
||||||
target: submitProxyTarget,
|
target: submitProxyTarget,
|
||||||
|
changeOrigin: true,
|
||||||
rewrite: (path) => path.replace('/api/auth', '/auth'),
|
rewrite: (path) => path.replace('/api/auth', '/auth'),
|
||||||
},
|
},
|
||||||
'/api/owner': {
|
'/api/owner': {
|
||||||
target: submitProxyTarget,
|
target: submitProxyTarget,
|
||||||
|
changeOrigin: true,
|
||||||
rewrite: (path) => path.replace('/api/owner', '/owner'),
|
rewrite: (path) => path.replace('/api/owner', '/owner'),
|
||||||
},
|
},
|
||||||
'/api/save-draft': {
|
'/api/save-draft': {
|
||||||
target: submitProxyTarget,
|
target: submitProxyTarget,
|
||||||
|
changeOrigin: true,
|
||||||
rewrite: (path) => path.replace('/api/save-draft', '/auth/save-draft'),
|
rewrite: (path) => path.replace('/api/save-draft', '/auth/save-draft'),
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user