One scorecard view, one adapter per brand.
A brand-adapter pattern in the existing TOFAC Next.js app: one shared scorecard view, one per-brand adapter class that knows how to query that brand’s GHL data (calendar vs. custom field vs. mixed). Three headline cards — Hours Saved MTD, $ Value Saved, Top Driver — rendered behind a signed embed token so it can drop into any GHL dashboard via iframe.
Architecture
GHL: RC GHL: RR (custom fields) GHL: WL TOFAC: Supabase
│ │ │ │
▼ ▼ ▼ ▼
adapter:RC adapter:RR adapter:WL adapter:TOFAC
│ │ │ │
└────────────────────── normalized event stream ─────────────────────┘
│
▼
Σ (event_count × min/event)
│
▼
[HOURS SAVED MTD] [$ VALUE SAVED] [TOP DRIVER]
│
▼
Signed embed → iframe inside each GHL dashboard
Stack
Next.js 16
GHL API (Private Integration Tokens)
Supabase
Signed embed tokens (HMAC)
Per-brand adapter classes
Config-driven multipliers
Vercel
Key decisions
- Brand-adapter pattern from day one. Considered RC-only first then refactor — rejected because the cost of writing one shared scorecard view is ~half a day and pays back the first time RR plugs in.
- RC ships first, RR second, WL third. RC uses native GHL Calendar (standard API path) and has the most data — biggest, most credible launch number. RR uses custom fields for appointments — bespoke adapter — better as brand #2 to validate the pattern works.
- Same embed pattern as TOFAC uptime widget, which is already in production at
golf.ryannreed.com/uptime/embed. Reused signed-token + iframe approach. Independent secrets per surface.
- Multipliers live in a config file, not hard-coded. Self-booked appointment = 12 min, auto-reply = 3 min, form contact = 3 min, tag from workflow = 1 min. Easy to tune as the model gets more honest.
- Read-only GHL scopes only. This thing reports. It does not touch.
- Phase 2 = portfolio-level “all-brands aggregate” — the sales-material money shot (“Saved $X across 4 clients last month”). Same adapters, one extra view.