diff --git a/drb-frontend/app/privacy/page.tsx b/drb-frontend/app/privacy/page.tsx new file mode 100644 index 0000000..1abf63e --- /dev/null +++ b/drb-frontend/app/privacy/page.tsx @@ -0,0 +1,83 @@ +import Link from "next/link"; + +/** + * SAAS_PLAN.md B5: page structure only — see app/terms/page.tsx for why the + * agent building this did not write real legal text. Privacy Policy needs + * the same jurisdiction-aware legal review as Terms, plus specifics this + * agent cannot respond for on the owner's behalf: what a real DPA/CCPA/GDPR + * posture looks like, and what third-party processors (OpenAI, Gemini, + * Google Maps, Firebase/GCP, Stripe once chosen) actually receive and why. + */ + +const SECTIONS: { heading: string; note: string }[] = [ + { + heading: "1. What data this collects", + note: "TODO(legal): account data (email, org membership), field node telemetry (location, status), radio call audio and AI-generated transcripts/entities/incident data, and usage/session logs (drb-c2-core's audit_log and user_sessions collections already exist and hold some of this today).", + }, + { + heading: "2. Third parties this data is sent to, and why", + note: "TODO(legal): OpenAI (Whisper transcription), Google Gemini (incident extraction/summarization/embeddings), Google Maps (geocoding location strings extracted from transcripts), Google Cloud (Firestore + GCS storage, Firebase Auth), and — once a payment processor is chosen (SAAS_PLAN.md section 6.5, not yet decided) — that processor. Each of these is a real, already-integrated dependency, not a hypothetical one; this section needs to name them accurately, not generically.", + }, + { + heading: "3. Recorded radio traffic specifically", + note: "TODO(legal): this product's core function is recording, transcribing, and storing monitored radio audio — including public-safety traffic that may name individuals, locations, and in-progress incidents. This needs explicit treatment distinct from generic 'we collect usage data' privacy boilerplate, and needs to be read alongside the same legal review flagged in Terms section 3.", + }, + { + heading: "4. How long data is kept", + note: "TODO(legal): no retention enforcement exists in the product yet (no TTL, no sweep, no deletion job — see DEFERRED.md) — this section cannot promise a retention/deletion window the system doesn't actually implement.", + }, + { + heading: "5. Customer and end-user rights", + note: "TODO(legal): access/export/deletion requests, and who they're directed to — org owner vs. platform operator.", + }, + { + heading: "6. Cookies and session data", + note: "TODO(legal): drb_session is a client-set, non-httpOnly cookie used only for UI redirect logic (not an auth boundary — see CLAUDE.md); Firebase Auth sets its own session storage. No analytics/tracking cookies are set today.", + }, + { + heading: "7. Security practices", + note: "TODO(legal): at a level appropriate for public disclosure — Firestore security rules, per-node credentials, encrypted transport. Should be reviewed against SAAS_PLAN.md's actual findings before publishing any specific claim.", + }, + { + heading: "8. Changes to this policy", + note: "TODO(legal): how customers are notified.", + }, + { + heading: "9. Contact", + note: "TODO(legal): real company legal identity and contact address — not yet decided (SAAS_PLAN.md section 6.6).", + }, +]; + +export default function PrivacyPage() { + return ( +
+
+

+ Draft — not yet in force +

+

+ This page is a structural placeholder, not a real Privacy Policy. Every section below is a{" "} + TODO(legal) marker, not actual legal text. Nothing on this page + describes a binding commitment about how data is handled. +

+
+ +

Privacy Policy

+

Draft — last structured {new Date().getFullYear()}

+ +
+ {SECTIONS.map((s) => ( +
+

{s.heading}

+

{s.note}

+
+ ))} +
+ +

+ Questions in the meantime? Check the FAQ or{" "} + sign in to reach us directly. +

+
+ ); +} diff --git a/drb-frontend/app/terms/page.tsx b/drb-frontend/app/terms/page.tsx new file mode 100644 index 0000000..82a3f32 --- /dev/null +++ b/drb-frontend/app/terms/page.tsx @@ -0,0 +1,89 @@ +import Link from "next/link"; + +/** + * SAAS_PLAN.md B5: page structure only. The agent building this is + * explicitly instructed not to invent legal text — DRB records, stores, and + * transcribes public-safety radio traffic, and recording/rebroadcast + * legality varies by state (see SAAS_PLAN.md section 6.3), so this needs a + * human, and probably a lawyer, not a generated draft. Every section below + * is a placeholder marking what a real Terms of Service needs to cover, not + * actual terms — see the banner and every TODO(legal) marker. + */ + +const SECTIONS: { heading: string; note: string }[] = [ + { + heading: "1. Acceptance of terms", + note: "TODO(legal): standard acceptance clause — using the service means agreeing to these terms.", + }, + { + heading: "2. What the service does and does not do", + note: "TODO(legal): describe the product (SDR ingestion, transcription, AI correlation, Discord relay) and, importantly, disclaim accuracy — AI-generated transcripts and incident summaries are not guaranteed accurate and must not be relied on as the sole source for dispatch or safety decisions.", + }, + { + heading: "3. Radio recording and rebroadcast — the part that needs a lawyer", + note: "TODO(legal): this is the section that actually matters. Recording, storing, and rebroadcasting monitored radio traffic (including public-safety frequencies) has different legal treatment by state and by traffic type (encrypted vs. clear, dispatch vs. tactical). Needs jurisdiction-aware legal review before this product can be sold across state lines — do not ship this page live without it.", + }, + { + heading: "4. Customer responsibilities and acceptable use", + note: "TODO(legal): who owns the hardware, who's responsible for lawful operation of the field node, prohibited uses.", + }, + { + heading: "5. Data ownership and retention", + note: "TODO(legal): who owns the recorded audio/transcripts/incidents, how long they're kept, what happens on cancellation or account deletion. Note: no retention enforcement exists in the product yet either (see DEFERRED.md) — this section can't promise a retention window the system doesn't yet enforce.", + }, + { + heading: "6. Payment, billing, and cancellation", + note: "TODO(legal): once a billing model and pricing exist (SAAS_PLAN.md section 6, still undecided) — refunds, proration, what happens to data on non-payment.", + }, + { + heading: "7. Service availability and support", + note: "TODO(legal): whether any uptime/SLA commitment is made (today: none).", + }, + { + heading: "8. Limitation of liability", + note: "TODO(legal): standard limitation-of-liability language, reviewed against the fact that this product touches public-safety-adjacent data.", + }, + { + heading: "9. Changes to these terms", + note: "TODO(legal): how customers are notified of material changes.", + }, + { + heading: "10. Governing law and contact", + note: "TODO(legal): governing jurisdiction, and a real company legal identity and contact address (SAAS_PLAN.md section 6.6 — not yet decided).", + }, +]; + +export default function TermsPage() { + return ( +
+
+

+ Draft — not yet in force +

+

+ This page is a structural placeholder, not a real Terms of Service. Every section below is a{" "} + TODO(legal) marker, not actual legal text. Nothing on this page is + binding, and no self-serve signup should be considered subject to it until an actual attorney-reviewed + version replaces this content. +

+
+ +

Terms of Service

+

Draft — last structured {new Date().getFullYear()}

+ +
+ {SECTIONS.map((s) => ( +
+

{s.heading}

+

{s.note}

+
+ ))} +
+ +

+ Questions in the meantime? Check the FAQ or{" "} + sign in to reach us directly. +

+
+ ); +} diff --git a/drb-frontend/app/waitlist/page.tsx b/drb-frontend/app/waitlist/page.tsx new file mode 100644 index 0000000..e089a41 --- /dev/null +++ b/drb-frontend/app/waitlist/page.tsx @@ -0,0 +1,105 @@ +"use client"; + +import { useState } from "react"; +import Link from "next/link"; +import { c2api } from "@/lib/c2api"; + +/** + * SAAS_PLAN.md B6's waitlist form — POST /waitlist (routers/waitlist.py) is + * public, source-IP rate-limited, and deliberately not coupled to any plan + * or tier: the commercial model (who runs the node, what a customer + * actually buys) is still an open decision (SAAS_PLAN.md section 6.1), so + * this collects only {email, org_name, note} and makes no promise about + * price or plan. + */ +export default function WaitlistPage() { + const [email, setEmail] = useState(""); + const [orgName, setOrgName] = useState(""); + const [note, setNote] = useState(""); + const [submitting, setSubmitting] = useState(false); + const [error, setError] = useState(null); + const [done, setDone] = useState(false); + + async function handleSubmit(e: React.FormEvent) { + e.preventDefault(); + setSubmitting(true); + setError(null); + try { + await c2api.joinWaitlist({ email, org_name: orgName || undefined, note: note || undefined }); + setDone(true); + } catch (err) { + setError(err instanceof Error ? err.message : "Could not submit — try again in a moment."); + } finally { + setSubmitting(false); + } + } + + return ( +
+ + D + DRB + +
+ {done ? ( + <> +

You're on the list

+

+ Thanks — we'll reach out at the email you gave us. In the meantime, feel free to{" "} + read the FAQ. +

+ + ) : ( + <> +
+

Request access

+

+ Self-serve signup isn't open yet. Leave your details and we'll follow up to get your organization set up. +

+
+
+
+ + setEmail(e.target.value)} + required + autoComplete="email" + className="w-full bg-gray-800 border border-gray-700 rounded-lg px-3 py-2 text-white text-sm focus:outline-none focus:border-indigo-500" + /> +
+
+ + setOrgName(e.target.value)} + className="w-full bg-gray-800 border border-gray-700 rounded-lg px-3 py-2 text-white text-sm focus:outline-none focus:border-indigo-500" + /> +
+
+ +