Service Portal
Service Portal โ AI Handoff
Last updated: 2026-07-12
Complete AI context for the Service Portal and Castle Checkers deployment. An agent reading this can continue work from scratch.
Session History
| Date | What Happened |
|---|---|
| 2026-07-09 | UX redesign session. (1) Navy + Gold theme โ replaced all green #2d6a4f with navy #1a3050 in CSS variables, AdminApp sidebar inline style, EmployeeApp color fallbacks (including AptCard border). DB settings.primary_color also needs updating via wrangler (sidebar reads from DB). (2) Employee login portal โ added showEmployee state to App.tsx; "Employee" button in PublicSite.tsx nav triggers it; EmployeeApp.tsx already had EmployeeLogin screen (phone + PIN). (3) Secure Notes โ getSecureNotes/saveSecureNotes wired into api.ts (108 lines) and AppContext.tsx (523 lines). HEAD: b2767bf (push pending). Last confirmed pushed: 3fb1df1. |
| 2026-07-07 | 8 feature + polish enhancements: invoice delete (non-Paid only, 403 guard in Worker), mobile client full-screen overlay modal, contact strip vertical layout (phone + email on separate rows), service request inline edit form CSS (req-edit-wrap/section/row/field classes), appointment auto-switch via key={editing.id}, migrations 0003โ0006 (flight fields, phone index, call-back, blocked days), blocked days CRUD in Worker + AppContext + api.ts. Latest commit: 3d52f75. |
| 2026-07-03 (v2) | Full v2 rewrite. Connected frontend to Worker API. Real PBKDF2 auth. Config-driven public site. Service request wizard submits to API. All admin views (Requests, Clients, Appointments, Calendar, Invoices, Reports, Settings) connected. Full CRUD. Migration 0002 for Castle Checkers. Worker and Pages deployed. Live at castlecheckers.sonandigital.com. |
| Prior sessions | Initial scaffold built (React + Vite + Worker + D1 schema). Frontend was a disconnected demo โ all state in-memory, never called the API. |
Infrastructure
| Resource | Value |
|---|---|
| GitHub repo | sonantechai/caretaker-portal |
| Local path | E:\Claude_Projects\sonan-trackers\caretaker-portal-Claude\caretaker-portal\ |
| Bash path | /sessions/*/mnt/sonan-trackers/caretaker-portal-Claude/caretaker-portal/ |
| Latest commit (push pending) | b2767bfbfca5631fcd1370de3c79b701b41c55a6 |
| Push command | git push origin b2767bfbfca5631fcd1370de3c79b701b41c55a6:refs/heads/main |
| Cloudflare account | sonantechai (ID: a7bd7d4b54bb7cb5907f8ddd45c3e5d9) |
| Live frontend | castlecheckers.sonandigital.com |
| Live API | castlecheckers-api.sonandigital.com |
Castle Checkers Credentials
| Item | Value |
|---|---|
| Worker name | castlecheckers-api |
| Worker env | wrangler deploy --env castlecheckers |
| D1 database name | caretaker_db |
| D1 database ID | c7fe5daa-1f2d-46ea-9939-7bee847591ab |
| Default admin password | Admin@2026 (set in migration 0002, must change after first login) |
| Password hash (PBKDF2, salt=SONAN_SP_2026) | da004d5ed0e3915f754d8b79ba29c078aa4190a5d9e2d7bcd4188755fb9fb741 |
| JWT_SECRET | Set via npx wrangler secret put JWT_SECRET --env castlecheckers โ use random 32-byte hex |
| Subdomain | castlecheckers.sonandigital.com |
| VITE_API_URL | https://castlecheckers-api.sonantechai.workers.dev |
Auth Flow (How It Works)
Customer visits castlecheckers.sonandigital.com
โโ App.tsx: useEffect โ api.getConfig() โ GET /api/config
โโ Loads business name, service types, colors into AppContext
โโ Shows PublicSite (public, no auth)
Customer submits request:
โโ ServiceRequestWizard โ api.submitRequest() โ POST /api/requests
โโ Worker: INSERT into service_requests โ returns { success, id }
โโ Admin sees it in Requests inbox
Admin clicks "Admin" link:
โโ Enters password โ api.login() โ POST /api/admin/login
โโ Worker: PBKDF2(password, 'SONAN_SP_2026', 100k, SHA-256) โ compare hash
โโ Returns HMAC-signed token: base64(payload).signature
โโ AppContext stores token โ triggers loadAll() โ fetches all data
โโ Shows AdminApp with live data
All admin API calls:
โโ Header: Authorization: Bearer token
โโ Worker: split token โ HMAC verify โ check exp โ run query
โโ Returns camelCase JSON (snake_case in DB โ mapped in worker)
Password change:
โโ Settings page โ POST /api/admin/password { currentPassword, newPassword }
โโ Worker: verify current, hash new, UPDATE admin_users SET password_hash
Generating a New Password Hash
Run in browser console (Chrome) or Node 18+:
// In Chrome console or Node 18+ (--input-type=module):
async function hashPassword(pass) {
const salt = new TextEncoder().encode('SONAN_SP_2026');
const key = await crypto.subtle.importKey('raw', new TextEncoder().encode(pass), 'PBKDF2', false, ['deriveBits']);
const bits = await crypto.subtle.deriveBits({name:'PBKDF2',hash:'SHA-256',salt,iterations:100000}, key, 256);
return Array.from(new Uint8Array(bits)).map(b=>b.toString(16).padStart(2,'0')).join('');
}
hashPassword('YourNewPassword').then(console.log);
// Paste the output into migration or UPDATE admin_users SET password_hash = '...'
Adding Realtor (Next Client)
# 1. Create new D1 database
npx wrangler d1 create realtor_db
# Copy the database_id from output
# 2. Uncomment [env.realtor] in wrangler.toml and paste the database_id
# 3. Create migrations/0002_realtor_seed.sql with realtor-specific settings:
# INSERT OR REPLACE INTO settings (key, value) VALUES
# ('business_name', 'XYZ Realtor'),
# ('service_types', '["Buyer Inquiry","Listing Request","Property Valuation","Rental Inquiry"]'),
# ...
# 4. Apply migrations
npx wrangler d1 migrations apply realtor_db --remote --env realtor
# 5. Set secrets
npx wrangler secret put JWT_SECRET --env realtor
# 6. Deploy
npx wrangler deploy --env realtor
# 7. Create new Cloudflare Pages project pointing to same GitHub repo
# Set VITE_API_URL = https://realtor-api.sonantechai.workers.dev
# Add custom domain: xyz.sonandigital.com
Key Files to Know
| File | Purpose |
|---|---|
worker/index.ts | Entire API โ 542 lines, all routes, auth helpers, D1 queries, camelCase mapping |
migrations/0001_init.sql | Full DB schema |
migrations/0002_settings_seed.sql | Castle Checkers settings + admin password hash |
src/api.ts | All fetch() calls โ the only file that knows the API URL |
src/context/AppContext.tsx | All app state + API-connected actions. No hardcoded data. |
src/components/PublicSite.tsx | Config-driven public site; reads from state.config |
src/components/ServiceRequestWizard.tsx | Multi-step wizard; service types from getServiceTypes() hook |
wrangler.toml | castlecheckers env configured; realtor placeholder commented |
_redirects | /* /index.html 200 โ Cloudflare Pages SPA routing |
snake_case โ camelCase Mapping
D1 stores snake_case column names. The Worker maps to camelCase before returning JSON. Frontend always uses camelCase. Example: full_name โ fullName, appointment_date โ date, created_at โ createdAt.
If you add a new DB column, add the mapping in the corresponding map*() function in worker/index.ts.
Common Mistakes to Avoid
| Mistake | Fix |
|---|---|
| Pushing from sandbox โ Cloudflare returns 403 | All wrangler and git push from Windows terminal only |
| Editing worker without redeploying | Pages auto-deploys frontend on push; Worker needs npx wrangler deploy --env castlecheckers |
| Forgetting JWT_SECRET secret | If JWT_SECRET is not set, all login attempts fail with 500. Set it before deploying. |
| FUSE corruption on large file edits | Write to /tmp first, hash from /tmp, use git plumbing โ see CLAUDE.md |
| Invoice items not showing | Worker uses json_group_array SQLite function โ requires D1 compat date โฅ 2024-04-01 (already set in wrangler.toml) |