d4a9016. Reproduces the same methodology we apply to paid client engagements. All findings verifiable in the
linked repo
· live demo.
Can the Dovito Ad Display demo become a multi-tenant SaaS? A 7-dimension gap analysis. Refreshed at commit d4a9016.
The demo is a frontend-only fork with no database, no auth provider, no billing, and no server. On the SaaS-readiness rubric, it scores 10% overall — a number that is not a failure, but a direct and expected consequence of the demo's scope. This refresh was produced after a round of targeted fixes that improved the demo's frontend posture (React correctness, asset paths, query client defaults) but did not — and could not — change anything about the seven SaaS dimensions below.
Can the system isolate data, users, and settings between multiple customer organizations?
The demo has no backend. All state lives in a single browser localStorage key (dovito-demo-v2 — bumped from v1 when the demo-image URLs were migrated to basePath-prefixed paths), which is globally scoped to the visitor. There is no concept of an organization, no tenant column on any mock "table," and every admin user sees every record. This is not a failing of the demo — it is structurally impossible to multi-tenant a static frontend without a backend.
src/lib/schema.ts) derived from the production codebase — include no tenant fieldsorganization or tenant entitytenant_id column on any recordorganizations table; add organization_id FK to every business record; implement a query-scoping layer (the production code's IStorage pattern is a good target); add platform-admin vs org-admin role tiers. Effort: 2–4 weeks of focused backend work.Every component below assumes single-tenant global state. Each would need to be rewired once a tenant context exists.
| Component / Page | Tenancy Assumption | Remediation |
|---|---|---|
admin/page.tsx | Lists all slides, applications, users globally | Scope queries by tenant; add platform-admin escape hatch |
dashboard/page.tsx | Shows "user's own" records, but "user" = browser session | Derive user from real session; scope by their org |
display/page.tsx | Single display surface, single brand | Per-tenant branding + display settings keyed on org |
brand-kit/page.tsx | Hardcoded to one brand | Per-tenant brand kit record |
mock-api.ts admin routes | All /api/admin/* endpoints return global collections | Enforce org scoping at the data layer |
mock-store.ts | Single root store object | Replace with real DB; ensure every query filters by tenant |
| Audit log viewer | Shows global audit entries | Scope entries by org; allow platform-admin cross-tenant view |
| Webhooks settings | Single global webhook list | Scope per org |
Identity, roles, permissions, SSO, MFA, invitations, audit.
The demo has a login form and a rudimentary role model — but role is determined by string-matching the email address in mock-auth.ts:21. The refreshed login page at src/app/login/page.tsx has been stripped down to a single "Sign in as Admin" button that is honest about demo mode, but the underlying identity model is unchanged: no password, no verification, no session token, no expiry.
user / admin / super_adminmock-auth.tsPayment processing, subscription lifecycle, invoicing, dunning, tax.
Stripe was explicitly removed from the demo as part of the backend amputation. The collect-payment/page.tsx page was rewritten to render a fake card input (it collects nothing, charges nothing). Stubs exist in mock-api.ts for create-checkout-session, create-payment-intent, create-subscription, and validate-coupon — all return canned success responses. One hardcoded demo coupon code (LAUNCH20) exists.
LAUNCH20 coupon for demoPer-tenant branding, display settings, email templates, custom domains.
Partial credit: the demo genuinely has a settings panel (/api/display-settings) backed by mock persistence. Users can change display behavior and those settings persist to localStorage. There is also a Brand Kit page (brand-kit/page.tsx) that surfaces the visual system. Both are single-tenant and global, but the mechanism for per-tenant customization exists and would extend naturally.
display_settings collection)next-themesorganization_id; add logo/color upload (needs storage backend); build email template overrides; add custom-domain DNS verification flow. Effort: 1 week once tenancy exists.Is the API surface well-structured, documented, versioned, and ready for third-party integration?
The mock router in mock-api.ts implements about 50 endpoints across slides, applications, display settings, users, webhooks, audit log, and billing stubs. The URL structure is RESTful and consistent (GET /api/slides, POST /api/slides/:id, etc.). What does not exist is a real API backed by anything durable, nor any form of API documentation, versioning, or authentication.
/api/v1)Self-service signup, activation, first-run experience, setup wizard, invitations.
A registration page exists and accepts input, but registration is a no-op in the mock layer. There is no organization creation, no invitation flow, no setup wizard, no activation email, no first-run tour. The demo is explicitly oriented around "look at the product while already signed in," not "experience account creation."
Logging, metrics, error tracking, uptime monitoring, alerting, runbook.
A static site on GitHub Pages has no logs, no metrics, no traces, no error reports, and no uptime instrumentation. There is no console.error forwarding, no Sentry, no PostHog, no OpenTelemetry. If a user hits a bug on the live demo, no one will ever know.
| Dimension | Score | Effort to 80% | Priority |
|---|---|---|---|
| Multi-Tenancy | 0% | 2–4 weeks | P0 — blocking |
| User Management | 5% | 1–2 weeks | P0 — blocking |
| Billing & Subscriptions | 0% | 1–2 weeks | P0 — blocking |
| Monitoring & Ops | 0% | 1–2 weeks | P1 |
| Onboarding | 15% | 1 week | P1 |
| API Design | 20% | 1–2 weeks | P2 |
| Customization | 30% | 1 week | P2 |
Phased so each layer unblocks the next. Phases 1 and 2 are non-negotiable prerequisites for any real launch; Phases 3 and 4 are productisation.
Rebuild the persistence layer the demo amputated. This phase unblocks every subsequent phase.
Turn the product into a multi-tenant system. Requires Phase 1.
Reintroduce the Stripe layer that was amputated, this time for platform-level subscriptions.
Everything that makes the product feel like a real SaaS rather than a multi-tenant version of the demo.