From 91846f3fe2d040daee3bcce1555b56158c9e0f92 Mon Sep 17 00:00:00 2001 From: DevCalebR Date: Sun, 1 Mar 2026 21:19:31 -0500 Subject: [PATCH 1/4] hardening: G12 add public legal pages --- README.md | 3 ++ app/page.tsx | 80 ++++++++++++++++++------------- app/privacy/page.tsx | 51 ++++++++++++++++++++ app/refund/page.tsx | 51 ++++++++++++++++++++ app/terms/page.tsx | 51 ++++++++++++++++++++ docs/PRODUCTION_READINESS_GAPS.md | 34 +++++++++++++ tests/legal-pages.test.ts | 18 +++++++ 7 files changed, 255 insertions(+), 33 deletions(-) create mode 100644 app/privacy/page.tsx create mode 100644 app/refund/page.tsx create mode 100644 app/terms/page.tsx create mode 100644 tests/legal-pages.test.ts diff --git a/README.md b/README.md index fd9beeb..94fb231 100644 --- a/README.md +++ b/README.md @@ -306,6 +306,9 @@ Prisma models included: ## Useful Routes - `/` - landing page +- `/terms` - terms of service +- `/privacy` - privacy policy +- `/refund` - refund policy - `/sign-in` - Clerk sign-in - `/sign-up` - Clerk sign-up - `/app/onboarding` - create business record diff --git a/app/page.tsx b/app/page.tsx index 85e34f1..bd4a1d7 100644 --- a/app/page.tsx +++ b/app/page.tsx @@ -6,39 +6,53 @@ import { Card, CardContent, CardDescription, CardHeader, CardTitle } from '@/com export default function LandingPage() { return (
-
-
-

- CallbackCloser -

-

- Missed Call to Booked Job with automated SMS follow-up. -

-

- When a customer calls and nobody answers, CallbackCloser texts them instantly, captures the job details, and alerts the owner with a lead summary. -

-
- - - - - - -
-
- - - How it works - Built for home service businesses using Twilio, Stripe, Clerk, and Prisma. - - -
1. Incoming call hits your Twilio number and forwards to your business line.
-
2. If unanswered, a lead is created and SMS qualification starts automatically.
-
3. Owner gets a summary text and can track leads inside the dashboard.
-
-
+
+
+
+

+ CallbackCloser +

+

+ Missed Call to Booked Job with automated SMS follow-up. +

+

+ When a customer calls and nobody answers, CallbackCloser texts them instantly, captures the job details, and alerts the owner with a lead summary. +

+
+ + + + + + +
+
+ + + How it works + Built for home service businesses using Twilio, Stripe, Clerk, and Prisma. + + +
1. Incoming call hits your Twilio number and forwards to your business line.
+
2. If unanswered, a lead is created and SMS qualification starts automatically.
+
3. Owner gets a summary text and can track leads inside the dashboard.
+
+
+
+
+ Legal: + + Terms + + + Privacy + + + Refund + +
); diff --git a/app/privacy/page.tsx b/app/privacy/page.tsx new file mode 100644 index 0000000..8e20b25 --- /dev/null +++ b/app/privacy/page.tsx @@ -0,0 +1,51 @@ +import Link from 'next/link'; + +const EFFECTIVE_DATE = 'March 2, 2026'; + +export default function PrivacyPage() { + return ( +
+
+
+

Privacy Policy

+

Effective date: {EFFECTIVE_DATE}

+
+ +
+

Information We Collect

+

+ We collect account details, call/message metadata, lead qualification responses, and billing-related identifiers needed to operate CallbackCloser. +

+
+ +
+

How We Use Data

+

+ Data is used to deliver automation workflows, surface leads in the dashboard, maintain service reliability, and support account operations. +

+
+ +
+

Data Sharing

+

+ CallbackCloser uses service providers (for example Twilio, Stripe, Clerk, and Neon) solely to provide the platform. We do not sell your data. +

+
+ +
+

Data Requests

+

+ For access, correction, or deletion requests, contact support and include your business name and account email. +

+
+ + +
+
+ ); +} diff --git a/app/refund/page.tsx b/app/refund/page.tsx new file mode 100644 index 0000000..2ba796e --- /dev/null +++ b/app/refund/page.tsx @@ -0,0 +1,51 @@ +import Link from 'next/link'; + +const EFFECTIVE_DATE = 'March 2, 2026'; + +export default function RefundPolicyPage() { + return ( +
+
+
+

Refund Policy

+

Effective date: {EFFECTIVE_DATE}

+
+ +
+

Subscription Charges

+

+ CallbackCloser is billed as a recurring subscription through Stripe. Charges apply according to your selected plan and billing cycle. +

+
+ +
+

Cancellation Timing

+

+ You may cancel at any time. Cancellation stops future renewals and access continues through the current paid period unless otherwise stated. +

+
+ +
+

Refund Requests

+

+ Refunds are reviewed case-by-case for duplicate billing, platform defects, or accidental charges. Approved refunds are issued to the original payment method. +

+
+ +
+

How to Request

+

+ Email support with your account email, business name, charge date, and the reason for your request. +

+
+ + +
+
+ ); +} diff --git a/app/terms/page.tsx b/app/terms/page.tsx new file mode 100644 index 0000000..e2202dc --- /dev/null +++ b/app/terms/page.tsx @@ -0,0 +1,51 @@ +import Link from 'next/link'; + +const EFFECTIVE_DATE = 'March 2, 2026'; + +export default function TermsPage() { + return ( +
+
+
+

Terms of Service

+

Effective date: {EFFECTIVE_DATE}

+
+ +
+

Service Scope

+

+ CallbackCloser provides automation tools for missed-call follow-up workflows, including SMS messaging and lead tracking. +

+
+ +
+

Acceptable Use

+

+ You are responsible for lawful use of the platform, including consent, opt-out compliance, and messaging rules required by your jurisdiction. +

+
+ +
+

Billing

+

+ Subscription charges are processed through Stripe. Plan changes and cancellations are handled through the billing portal. +

+
+ +
+

Limitation of Liability

+

+ The service is provided as-is. CallbackCloser is not liable for indirect or consequential damages arising from use of the platform. +

+
+ + +
+
+ ); +} diff --git a/docs/PRODUCTION_READINESS_GAPS.md b/docs/PRODUCTION_READINESS_GAPS.md index c78674a..52dacd6 100644 --- a/docs/PRODUCTION_READINESS_GAPS.md +++ b/docs/PRODUCTION_READINESS_GAPS.md @@ -419,3 +419,37 @@ Dependencies: G4 (recommended) - `docs/PRODUCTION_READINESS_GAPS.md` - Commit SHA: - `fd9ca8e` + +- 2026-03-02 - G12 (DONE) + - Branch: `hardening/g12-legal-pages` + - What changed: + - Added public legal pages: + - `app/terms/page.tsx` + - `app/privacy/page.tsx` + - `app/refund/page.tsx` + - Added legal links to the public landing page footer: + - `app/page.tsx` + - Added lightweight route/content coverage test: + - `tests/legal-pages.test.ts` + - Updated route docs: + - `README.md` + - Verification notes: + - Build output confirms static generation for `/terms`, `/privacy`, and `/refund`. + - Landing page now exposes direct legal navigation links for compliance visibility. + - Commands run + results: + - `npm test` -> PASS (34/34) + - `npm run lint` -> PASS + - `npm run build` -> PASS + - `npm run typecheck` -> PASS + - `npm run env:check` -> PASS + - `npm run db:validate` -> PASS + - Files touched: + - `app/terms/page.tsx` + - `app/privacy/page.tsx` + - `app/refund/page.tsx` + - `app/page.tsx` + - `tests/legal-pages.test.ts` + - `README.md` + - `docs/PRODUCTION_READINESS_GAPS.md` + - Commit SHA: + - `PENDING` diff --git a/tests/legal-pages.test.ts b/tests/legal-pages.test.ts new file mode 100644 index 0000000..ef57819 --- /dev/null +++ b/tests/legal-pages.test.ts @@ -0,0 +1,18 @@ +import assert from 'node:assert/strict'; +import { readFileSync } from 'node:fs'; +import path from 'node:path'; +import test from 'node:test'; + +function read(relativePath: string) { + return readFileSync(path.join(process.cwd(), relativePath), 'utf8'); +} + +test('legal public pages exist with required headings', () => { + const terms = read('app/terms/page.tsx'); + const privacy = read('app/privacy/page.tsx'); + const refund = read('app/refund/page.tsx'); + + assert.match(terms, /Terms of Service/); + assert.match(privacy, /Privacy Policy/); + assert.match(refund, /Refund Policy/); +}); From 7ce6880856be940a8439fc74a1248d052b38305b Mon Sep 17 00:00:00 2001 From: DevCalebR Date: Sun, 1 Mar 2026 21:19:42 -0500 Subject: [PATCH 2/4] docs: record G12 commit sha in gap changelog --- docs/PRODUCTION_READINESS_GAPS.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/PRODUCTION_READINESS_GAPS.md b/docs/PRODUCTION_READINESS_GAPS.md index 52dacd6..09e6e4d 100644 --- a/docs/PRODUCTION_READINESS_GAPS.md +++ b/docs/PRODUCTION_READINESS_GAPS.md @@ -452,4 +452,4 @@ Dependencies: G4 (recommended) - `README.md` - `docs/PRODUCTION_READINESS_GAPS.md` - Commit SHA: - - `PENDING` + - `91846f3` From 334eaaa5bae26bd8a6024e7945b89483d03b7d60 Mon Sep 17 00:00:00 2001 From: DevCalebR Date: Sun, 1 Mar 2026 23:22:34 -0500 Subject: [PATCH 3/4] ci: use valid Clerk placeholder key in non-prod builds --- .github/workflows/ci.yml | 2 +- app/layout.tsx | 23 ++++++++++++++++++++++- 2 files changed, 23 insertions(+), 2 deletions(-) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 870f441..737f197 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -14,7 +14,7 @@ jobs: NEXT_PUBLIC_APP_URL: https://example.com DATABASE_URL: postgresql://postgres:postgres@localhost:5432/callbackcloser?sslmode=require DIRECT_DATABASE_URL: postgresql://postgres:postgres@localhost:5432/callbackcloser?sslmode=require - NEXT_PUBLIC_CLERK_PUBLISHABLE_KEY: pk_test_placeholder + NEXT_PUBLIC_CLERK_PUBLISHABLE_KEY: pk_test_Y2xlcmsuZXhhbXBsZS5jb20k CLERK_SECRET_KEY: sk_test_placeholder STRIPE_SECRET_KEY: sk_test_placeholder STRIPE_WEBHOOK_SECRET: whsec_placeholder diff --git a/app/layout.tsx b/app/layout.tsx index 0266fed..6e718a7 100644 --- a/app/layout.tsx +++ b/app/layout.tsx @@ -6,6 +6,8 @@ import { validateServerEnv } from '@/lib/env.server'; import './globals.css'; +const CLERK_PREVIEW_FALLBACK_KEY = 'pk_test_Y2xlcmsuZXhhbXBsZS5jb20k'; + const manrope = Manrope({ subsets: ['latin'], variable: '--font-sans', @@ -16,11 +18,30 @@ export const metadata: Metadata = { description: 'Missed Call -> Booked Job SMS follow-up', }; +function isLikelyValidClerkPublishableKey(value: string) { + return /^pk_(test|live)_[A-Za-z0-9+/=_-]+$/.test(value); +} + +function resolveClerkPublishableKey() { + const configured = process.env.NEXT_PUBLIC_CLERK_PUBLISHABLE_KEY?.trim() ?? ''; + if (configured && isLikelyValidClerkPublishableKey(configured)) { + return configured; + } + + const allowPreviewFallback = process.env.NODE_ENV !== 'production' || process.env.VERCEL_ENV === 'preview'; + if (allowPreviewFallback) { + return CLERK_PREVIEW_FALLBACK_KEY; + } + + return configured; +} + export default function RootLayout({ children }: { children: React.ReactNode }) { validateServerEnv(); + const clerkPublishableKey = resolveClerkPublishableKey(); return ( - + {children} From 93a48dba4af18d733a4975bed01e642c6a237d0f Mon Sep 17 00:00:00 2001 From: DevCalebR Date: Sun, 1 Mar 2026 23:29:39 -0500 Subject: [PATCH 4/4] ci: run verify job on Node 22 for strip-types support --- .github/workflows/ci.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 737f197..664ea91 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -28,7 +28,7 @@ jobs: - uses: actions/setup-node@v4 with: - node-version: 20 + node-version: 22 cache: npm - run: npm ci