From 5511ef4110d6845653999575b3cc1edc84341116 Mon Sep 17 00:00:00 2001 From: tanweer919 Date: Mon, 13 Jul 2026 14:50:49 +0530 Subject: [PATCH] feat(legal): SMS opt-in form + Privacy Policy + Terms for Twilio A2P 10DLC Public, unauthenticated pages required for A2P 10DLC campaign verification: - /sms-opt-in: web opt-in form (phone field, un-pre-checked consent checkbox, message description, frequency, msg&data-rates + HELP/STOP disclaimers, links to Terms & Privacy, 'Yes, sign me up!' submit). - /privacy: Privacy Policy with the mandatory SMS clauses (no third-party/affiliate sharing of mobile numbers or SMS consent, message frequency, rates disclosure). - /terms: Terms of Service with an SMS program-terms section. Business details centralized in legal-config.ts (replace SMS_SENDER with the live Twilio number once approved). --- src/app/(legal)/layout.tsx | 45 ++++++++++ src/app/(legal)/legal-config.ts | 11 +++ src/app/(legal)/legal.css | 125 ++++++++++++++++++++++++++++ src/app/(legal)/privacy/page.tsx | 98 ++++++++++++++++++++++ src/app/(legal)/sms-opt-in/page.tsx | 125 ++++++++++++++++++++++++++++ src/app/(legal)/terms/page.tsx | 85 +++++++++++++++++++ 6 files changed, 489 insertions(+) create mode 100644 src/app/(legal)/layout.tsx create mode 100644 src/app/(legal)/legal-config.ts create mode 100644 src/app/(legal)/legal.css create mode 100644 src/app/(legal)/privacy/page.tsx create mode 100644 src/app/(legal)/sms-opt-in/page.tsx create mode 100644 src/app/(legal)/terms/page.tsx diff --git a/src/app/(legal)/layout.tsx b/src/app/(legal)/layout.tsx new file mode 100644 index 0000000..7f5de1b --- /dev/null +++ b/src/app/(legal)/layout.tsx @@ -0,0 +1,45 @@ +import type { Metadata } from "next"; +import Link from "next/link"; +import "@/components/portal/portal.css"; +import "./legal.css"; +import { COMPANY } from "./legal-config"; + +export const metadata: Metadata = { + title: `${COMPANY} — Legal & SMS`, + description: `${COMPANY} SMS program opt-in, Privacy Policy and Terms of Service.`, +}; + +export default function LegalLayout({ children }: { children: React.ReactNode }) { + return ( + <> + + + +
+
+ + {/* eslint-disable-next-line @next/next/no-img-element */} + {`${COMPANY} + + +
+ {children} +
+
© {new Date().getFullYear()} {COMPANY}. All rights reserved.
+
+ SMS Alerts + Privacy Policy + Terms of Service +
+
+
+ + ); +} diff --git a/src/app/(legal)/legal-config.ts b/src/app/(legal)/legal-config.ts new file mode 100644 index 0000000..0e403e4 --- /dev/null +++ b/src/app/(legal)/legal-config.ts @@ -0,0 +1,11 @@ +/** + * Shared business details for the public compliance pages (SMS opt-in, Privacy, + * Terms). Edit these in one place to keep every page and disclosure consistent. + * Replace SMS_SENDER with your live Twilio number once the campaign is approved. + */ +export const COMPANY = "LynkedUp Pro"; +export const SUPPORT_EMAIL = "support@lynkeduppro.com"; +export const PRIVACY_EMAIL = "privacy@lynkeduppro.com"; +export const SMS_SENDER = "(555) 010-0100"; // TODO: replace with your Twilio A2P number +export const SITE_URL = "https://lynkeduppro-crmnew.vercel.app"; +export const LAST_UPDATED = "July 13, 2026"; diff --git a/src/app/(legal)/legal.css b/src/app/(legal)/legal.css new file mode 100644 index 0000000..6c0d88c --- /dev/null +++ b/src/app/(legal)/legal.css @@ -0,0 +1,125 @@ +/* Public compliance pages (SMS opt-in, Privacy, Terms). Reuses the portal design + tokens (--bg, --text, --primary…) from portal.css for a consistent look, with + prose styling tuned for long-form legal copy. */ + +.legal-root { + min-height: 100vh; + background: var(--bg, #0b0c11); + color: var(--text, #f3f4f8); + font-family: var(--font-ui, "Inter", system-ui, sans-serif); + display: flex; + flex-direction: column; +} + +.legal-bar { + display: flex; + align-items: center; + justify-content: space-between; + gap: 16px; + padding: 18px 24px; + border-bottom: 1px solid var(--line, rgba(255, 255, 255, 0.09)); + position: sticky; + top: 0; + background: color-mix(in srgb, var(--bg, #0b0c11) 88%, transparent); + backdrop-filter: blur(10px); + z-index: 5; +} +.legal-bar img { height: 30px; width: auto; } +.legal-bar nav { display: flex; gap: 20px; flex-wrap: wrap; } +.legal-bar nav a { + color: var(--muted, #a3a8b5); + text-decoration: none; + font-size: 14px; + font-weight: 600; +} +.legal-bar nav a:hover { color: var(--text, #f3f4f8); } +.legal-bar nav a.on { color: var(--primary-2, #fb923c); } + +.legal-wrap { flex: 1; width: 100%; max-width: 820px; margin: 0 auto; padding: 44px 24px 80px; } + +.legal-wrap h1 { font-size: 32px; font-weight: 800; letter-spacing: -0.02em; margin: 0 0 6px; } +.legal-wrap .updated { color: var(--faint, #6c7280); font-size: 13px; margin: 0 0 30px; } +.legal-wrap h2 { + font-size: 19px; font-weight: 700; margin: 34px 0 10px; + padding-top: 20px; border-top: 1px solid var(--line, rgba(255, 255, 255, 0.09)); +} +.legal-wrap h2:first-of-type { border-top: none; padding-top: 0; } +.legal-wrap p, .legal-wrap li { color: var(--muted, #a3a8b5); font-size: 15px; line-height: 1.7; } +.legal-wrap p { margin: 0 0 12px; } +.legal-wrap ul { margin: 0 0 12px; padding-left: 20px; } +.legal-wrap li { margin: 0 0 6px; } +.legal-wrap strong { color: var(--text, #f3f4f8); font-weight: 600; } +.legal-wrap a { color: var(--primary-2, #fb923c); } + +/* Callout box for the mandatory SMS disclosures — makes them easy for a reviewer to find. */ +.legal-callout { + border: 1px solid var(--line-2, rgba(255, 255, 255, 0.15)); + background: var(--surface, rgba(255, 255, 255, 0.04)); + border-radius: var(--radius-sm, 13px); + padding: 16px 18px; + margin: 8px 0 18px; +} +.legal-callout p:last-child { margin-bottom: 0; } + +.legal-foot { + border-top: 1px solid var(--line, rgba(255, 255, 255, 0.09)); + padding: 22px 24px; + text-align: center; + color: var(--faint, #6c7280); + font-size: 13px; +} +.legal-foot a { color: var(--muted, #a3a8b5); text-decoration: none; margin: 0 10px; } +.legal-foot a:hover { color: var(--text, #f3f4f8); } + +/* SMS opt-in form */ +.optin-card { + border: 1px solid var(--line, rgba(255, 255, 255, 0.09)); + background: var(--surface, rgba(255, 255, 255, 0.04)); + border-radius: var(--radius-sm, 13px); + padding: 22px; + margin-top: 22px; +} +.optin-field { margin-bottom: 16px; } +.optin-field label { display: block; font-weight: 600; font-size: 14px; margin-bottom: 7px; color: var(--text, #f3f4f8); } +.optin-input { + width: 100%; + background: var(--ink, #121319); + border: 1px solid var(--line-2, rgba(255, 255, 255, 0.15)); + border-radius: 10px; + padding: 13px 14px; + color: var(--text, #f3f4f8); + font-size: 15px; + font-family: inherit; +} +.optin-input:focus { outline: 2px solid var(--primary, #f97316); outline-offset: 1px; border-color: transparent; } + +.optin-consent { display: flex; gap: 11px; align-items: flex-start; margin: 6px 0 4px; } +.optin-consent input { margin-top: 3px; width: 18px; height: 18px; flex: none; accent-color: var(--primary, #f97316); } +.optin-consent label { font-size: 13.5px; line-height: 1.6; color: var(--muted, #a3a8b5); } + +.optin-submit { + width: 100%; + margin-top: 18px; + background: var(--primary, #f97316); + color: #fff; + border: none; + border-radius: 10px; + padding: 14px; + font-size: 15px; + font-weight: 700; + cursor: pointer; + font-family: inherit; +} +.optin-submit:disabled { opacity: 0.5; cursor: not-allowed; } + +.optin-fine { font-size: 12.5px; color: var(--faint, #6c7280); line-height: 1.6; margin-top: 14px; } +.optin-done { + border: 1px solid color-mix(in srgb, var(--green, #34d399) 45%, transparent); + background: color-mix(in srgb, var(--green, #34d399) 12%, transparent); + color: #a7f3d0; + border-radius: var(--radius-sm, 13px); + padding: 18px 20px; + margin-top: 22px; + font-size: 14.5px; + line-height: 1.6; +} diff --git a/src/app/(legal)/privacy/page.tsx b/src/app/(legal)/privacy/page.tsx new file mode 100644 index 0000000..4bc4e95 --- /dev/null +++ b/src/app/(legal)/privacy/page.tsx @@ -0,0 +1,98 @@ +import type { Metadata } from "next"; +import Link from "next/link"; +import { COMPANY, SUPPORT_EMAIL, PRIVACY_EMAIL, LAST_UPDATED } from "../legal-config"; + +export const metadata: Metadata = { + title: `Privacy Policy — ${COMPANY}`, + description: `How ${COMPANY} collects, uses, and protects your information, including SMS/text messaging.`, +}; + +export default function PrivacyPage() { + return ( +
+

Privacy Policy

+

Last updated: {LAST_UPDATED}

+ +

+ This Privacy Policy explains how {COMPANY} ("{COMPANY}," "we," + "us") collects, uses, and protects your information when you use our + website, portal, and services, including our text message (SMS) program. +

+ +

Information we collect

+ + +

How we use your information

+ + +

SMS / text messaging

+
+

+ + We do not sell, rent, or share your mobile phone number, or your SMS opt-in + consent, with any third parties or affiliates for their marketing or + promotional purposes. + +

+

+ Mobile information collected for the purpose of sending text messages is used + only to deliver the messages you opted into and is not shared with third parties + for marketing. We may share your number only with service providers (such as our + messaging platform) strictly to deliver the messages, and as required by law. +

+

+ Message frequency varies — verification codes are sent when you + request them (for example, each time you sign in or register), and account or + service notifications are occasional. Message and data rates may + apply, depending on your mobile carrier and plan. +

+

+ You can opt out at any time by replying STOP to any message, and + get help by replying HELP. See our{" "} + Terms of Service and the{" "} + SMS opt-in page for full program details. +

+
+ +

How we share information

+

+ We do not sell your personal information. We share information only with service + providers who help us operate the service (for example, cloud hosting, + authentication, and messaging providers), and when required by law or to protect + our rights. As stated above, mobile phone numbers and SMS consent are never shared + with third parties or affiliates for marketing purposes. +

+ +

Data retention and security

+

+ We retain personal information for as long as your account is active or as needed to + provide the service and meet legal obligations. We use administrative, technical, + and physical safeguards designed to protect your information; no method of + transmission or storage is completely secure. +

+ +

Your choices and rights

+

+ You may access or update your account information at any time, opt out of SMS by + replying STOP, and request deletion of your account by contacting us. Depending on + your location, you may have additional rights under applicable privacy laws. +

+ +

Contact us

+

+ Questions about this Privacy Policy? Email us at{" "} + {PRIVACY_EMAIL} or{" "} + {SUPPORT_EMAIL}. +

+
+ ); +} diff --git a/src/app/(legal)/sms-opt-in/page.tsx b/src/app/(legal)/sms-opt-in/page.tsx new file mode 100644 index 0000000..c8f8f81 --- /dev/null +++ b/src/app/(legal)/sms-opt-in/page.tsx @@ -0,0 +1,125 @@ +"use client"; + +import { useState } from "react"; +import Link from "next/link"; +import { COMPANY, SMS_SENDER, SUPPORT_EMAIL } from "../legal-config"; + +/** + * Public SMS opt-in web form for A2P 10DLC campaign verification. Contains every + * element Twilio/CTIA require: phone field, an un-pre-checked consent checkbox, a + * description of the messages, frequency, rates disclaimer, HELP/STOP instructions, + * and links to the Terms and Privacy Policy. + */ +export default function SmsOptInPage() { + const [phone, setPhone] = useState(""); + const [consent, setConsent] = useState(false); + const [done, setDone] = useState(false); + + const digits = phone.replace(/\D/g, ""); + const valid = digits.length >= 10 && consent; + + function onSubmit(e: React.FormEvent) { + e.preventDefault(); + if (!valid) return; + // Records the opt-in. Consent + timestamp should be persisted server-side for your + // records; this confirmation is the user-facing acknowledgement. + setDone(true); + } + + return ( +
+

{COMPANY} SMS Alerts

+

Sign up to receive text messages from {COMPANY}.

+ +

+ {COMPANY} sends text messages to help you use and secure your account. By opting + in below you'll receive account and service messages, + including: +

+ + + {done ? ( +
+ You're signed up. We'll text account and service + messages to {phone}. Reply STOP at any time to unsubscribe, or{" "} + HELP for help. +
+ ) : ( +
+
+ + setPhone(e.target.value)} + required + /> +
+ + {/* Consent checkbox — MUST NOT be pre-checked (starts false). */} +
+ setConsent(e.target.checked)} + /> + +
+ + + +

+ Message frequency varies. Message and data rates may apply. Reply{" "} + HELP for help or STOP to cancel at any time. + Carriers are not liable for delayed or undelivered messages. +

+
+ )} + +

Program details

+ + +

How to opt out or get help

+

+ Reply STOP to any message to unsubscribe — you'll get one + confirmation and no further texts. Reply HELP for help, or contact + us at {SUPPORT_EMAIL} + {SMS_SENDER ? <> or {SMS_SENDER} : null}. Messages are sent from {COMPANY}. +

+ +

Your privacy

+

+ + We do not sell, rent, or share your mobile phone number or your SMS opt-in + consent with any third parties or affiliates for their marketing purposes. + {" "} + See our Privacy Policy for full details. +

+
+ ); +} diff --git a/src/app/(legal)/terms/page.tsx b/src/app/(legal)/terms/page.tsx new file mode 100644 index 0000000..8c77c50 --- /dev/null +++ b/src/app/(legal)/terms/page.tsx @@ -0,0 +1,85 @@ +import type { Metadata } from "next"; +import Link from "next/link"; +import { COMPANY, SUPPORT_EMAIL, LAST_UPDATED } from "../legal-config"; + +export const metadata: Metadata = { + title: `Terms of Service — ${COMPANY}`, + description: `The terms governing your use of ${COMPANY}, including the SMS/text messaging program.`, +}; + +export default function TermsPage() { + return ( +
+

Terms of Service

+

Last updated: {LAST_UPDATED}

+ +

+ These Terms of Service ("Terms") govern your access to and use of the{" "} + {COMPANY} website, portal, and services (the "Service"). By using the + Service you agree to these Terms. +

+ +

Use of the Service

+

+ You must provide accurate information, keep your credentials secure, and use the + Service only for lawful purposes and in accordance with these Terms. You are + responsible for activity that occurs under your account. +

+ +

Accounts and verification

+

+ To protect your account we may verify your identity, including by sending one-time + codes to your email or mobile number. You agree to receive these verification + messages as part of using the Service. +

+ +

SMS / text messaging program

+
+

+ By opting in on our SMS opt-in page or by + providing your mobile number and agreeing to receive texts, you consent to + receive account and service text messages from {COMPANY}, + including one-time verification codes (OTP), security alerts, appointment and + inspection updates, and estimate notifications. +

+

+ Message frequency varies. Message and data rates may + apply. Reply HELP for help or STOP to + unsubscribe at any time; after you send STOP we will send one confirmation and no + further messages. Carriers are not liable for delayed or undelivered messages. +

+

+ We do not sell, rent, or share your mobile number or SMS consent with third + parties or affiliates for marketing. See our{" "} + Privacy Policy for details. +

+
+ +

Acceptable use

+

+ You may not misuse the Service, attempt to access accounts or data you are not + authorized to access, interfere with the Service's operation, or use it to + send unlawful, harmful, or infringing content. +

+ +

Disclaimers and limitation of liability

+

+ The Service is provided "as is" without warranties of any kind. To the + fullest extent permitted by law, {COMPANY} is not liable for any indirect, + incidental, or consequential damages arising from your use of the Service. +

+ +

Changes to these Terms

+

+ We may update these Terms from time to time. Continued use of the Service after + changes take effect constitutes acceptance of the updated Terms. +

+ +

Contact us

+

+ Questions about these Terms? Email us at{" "} + {SUPPORT_EMAIL}. +

+
+ ); +} -- 2.52.0