diff --git a/prisma/migrations/20261017120000_add_legal_display_settings/migration.sql b/prisma/migrations/20261017120000_add_legal_display_settings/migration.sql new file mode 100644 index 0000000..0b1a0fd --- /dev/null +++ b/prisma/migrations/20261017120000_add_legal_display_settings/migration.sql @@ -0,0 +1,3 @@ +-- AlterTable +ALTER TABLE `BusinessSettings` ADD COLUMN `legalContactEmail` ENUM('NEXTCHAIR', 'BUSINESS') NOT NULL DEFAULT 'NEXTCHAIR', + ADD COLUMN `legalNameDisplay` ENUM('NEXTCHAIR', 'BUSINESS', 'BOTH') NOT NULL DEFAULT 'NEXTCHAIR'; diff --git a/prisma/schema.prisma b/prisma/schema.prisma index 9a7f30a..87e411a 100644 --- a/prisma/schema.prisma +++ b/prisma/schema.prisma @@ -223,6 +223,17 @@ enum CalendarProvider { GOOGLE } +enum LegalNameDisplay { + NEXTCHAIR + BUSINESS + BOTH +} + +enum LegalContactEmail { + NEXTCHAIR + BUSINESS +} + /// Per-appointment sync outcome, not the connection's overall health (see /// StaffCalendarConnection.lastSyncError for that). enum CalendarSyncStatus { @@ -403,6 +414,14 @@ model BusinessSettings { bookingTerms String? @db.Text privacyPolicyUrl String? + /// Who the public Terms of Service / Privacy Policy pages name as "we" — + /// see src/features/legal/legal-identity.ts. NEXTCHAIR is the default so + /// existing businesses keep today's wording. + legalNameDisplay LegalNameDisplay @default(NEXTCHAIR) + /// Which inbox those pages give for legal/privacy requests. BUSINESS uses + /// Business.email and falls back to NextChair's when that is empty. + legalContactEmail LegalContactEmail @default(NEXTCHAIR) + /// Phase 9 — Google reviews visibility policy, applied on top of the /// per-review Review.isHidden flag. Null minRating = no floor. reviewsMinRating Int? diff --git a/src/app/(legal)/layout.tsx b/src/app/(legal)/layout.tsx index 84d9b85..22f6310 100644 --- a/src/app/(legal)/layout.tsx +++ b/src/app/(legal)/layout.tsx @@ -1,21 +1,23 @@ import { ArrowLeft } from "lucide-react"; import Link from "next/link"; -import { LEGAL } from "@/features/legal/legal-config"; +import { getLegalIdentity } from "@/features/legal/legal-identity"; /** - * Shell for the public legal pages (/terms, /privacy). Deliberately static - * and independent of any business's branding — these documents are the - * platform operator's, and must stay readable by guests, signed-in users - * and (via the proxy's install-exempt list) before installation completes. + * Shell for the public legal pages (/terms, /privacy). Readable by guests, + * signed-in users and (via the proxy's install-exempt list) before + * installation completes — getLegalIdentity() falls back to NextChair then. */ -export default function LegalLayout({ children }: { children: React.ReactNode }) { +export const dynamic = "force-dynamic"; + +export default async function LegalLayout({ children }: { children: React.ReactNode }) { + const { name } = await getLegalIdentity(); return (
); diff --git a/src/app/(legal)/privacy/page.tsx b/src/app/(legal)/privacy/page.tsx index 536341d..113e950 100644 --- a/src/app/(legal)/privacy/page.tsx +++ b/src/app/(legal)/privacy/page.tsx @@ -1,247 +1,275 @@ import type { Metadata } from "next"; import Link from "next/link"; import { LegalDocument, type LegalSectionDef } from "@/features/legal/components/legal-document"; -import { LEGAL } from "@/features/legal/legal-config"; +import { getLegalIdentity } from "@/features/legal/legal-identity"; +import type { LegalIdentity } from "@/features/legal/resolve-legal-identity"; export const metadata: Metadata = { title: "Privacy Policy" }; +// Names the business per its Policies settings, so it must be read per request. +export const dynamic = "force-dynamic"; -const sections: LegalSectionDef[] = [ - { - id: "who-we-are", - title: "Who is responsible for your data", - body: ( - <> -

- {LEGAL.productName} is a booking platform used by many independent Businesses. Two parties handle your data, in - different roles: -

+function buildSections({ name, email }: LegalIdentity): LegalSectionDef[] { + return [ + { + id: "who-we-are", + title: "Who is responsible for your data", + body: ( + <> +

+ This booking platform is used by independent Businesses. Two parties handle your data, + in different roles: +

+ +

+ Contact us about privacy at {email}. +

+ + ), + }, + { + id: "data-we-collect", + title: "What we collect", + body: ( + <> +

+ You give us: +

+ +

+ Created while you use the Platform: +

+ +

+ For Staff and owners: work schedule, services offered and, if + connected, the Google Calendar account used to sync appointments. +

+

+ Clients added by a Business: a Business may add or import its existing + clients (name, contact details, history) into the Platform. In that case the Business is + responsible for having the right to do so and for telling those people. +

+ + ), + }, + { + id: "why", + title: "How and why we use it", + body: ( + ), + }, + { + id: "sharing", + title: "Who we share it with", + body: ( + <> + +

We do not sell your personal data and we do not use it for third-party advertising.

+ + ), + }, + { + id: "cookies", + title: "Cookies and similar technologies", + body: ( + <> +

We use only what the Platform needs to work:

+ +

+ These are strictly necessary and do not need consent. We do not use advertising or + tracking cookies. If we ever add analytics, we will update this policy and ask for your + consent where the law requires it. +

+ + ), + }, + { + id: "retention", + title: "How long we keep it", + body: ( + <> +

+ We keep your account and booking data while your account is active and the Business + needs it to serve you. Sign-in codes expire within minutes and are cleared soon after. +

+

+ When you ask us to delete your account we remove or anonymise your data, except what we + must keep by law (for example, records needed for accounting or to resolve a dispute). A + Business may keep its own records of past appointments according to its legal duties. +

+ + ), + }, + { + id: "security", + title: "Security", + body: (

- Contact us about privacy at {LEGAL.contactEmail}. + We protect your data with encrypted connections, access controls limited to what each role + needs, and hashed sign-in codes. No system is perfectly secure; if a breach affecting you + occurs we will notify you and the relevant authority as the law requires.

- - ), - }, - { - id: "data-we-collect", - title: "What we collect", - body: ( - <> + ), + }, + { + id: "transfers", + title: "International transfers", + body: (

- You give us: + Some of our providers (for example Google or WhatsApp, when you use those features) may + process data outside your country. Where they do, we rely on appropriate safeguards such + as standard contractual clauses, as required by applicable data protection law.

- + ), + }, + { + id: "rights", + title: "Your rights", + body: ( + <> +

+ Depending on where you live (including under the GDPR and equivalent laws), you can: +

+ +

+ Email {email} to exercise any of these. For data a + Business controls, we may pass your request to that Business. We will reply within one + month. You also have the right to complain to your local data protection authority. +

+ + ), + }, + { + id: "children", + title: "Children", + body: (

- Created while you use the Platform: + The Platform is not directed at children under 16, and we do not knowingly collect their + data. If you believe a child has given us personal data, contact us and we will delete it.

- + ), + }, + { + id: "changes", + title: "Changes to this policy", + body: (

- For Staff and owners: work schedule, services offered and, if connected, the Google Calendar account used - to sync appointments. + We will post any update here with a new “last updated” date and tell you about + significant changes through the Platform or by email. See also our{" "} + Terms of Service.

-

- Clients added by a Business: a Business may add or import its existing clients (name, contact details, - history) into the Platform. In that case the Business is responsible for having the right to do so and for telling those - people. -

- - ), - }, - { - id: "why", - title: "How and why we use it", - body: ( - - ), - }, - { - id: "sharing", - title: "Who we share it with", - body: ( - <> - -

We do not sell your personal data and we do not use it for third-party advertising.

- - ), - }, - { - id: "cookies", - title: "Cookies and similar technologies", - body: ( - <> -

We use only what the Platform needs to work:

- -

- These are strictly necessary and do not need consent. We do not use advertising or tracking cookies. If we ever add - analytics, we will update this policy and ask for your consent where the law requires it. -

- - ), - }, - { - id: "retention", - title: "How long we keep it", - body: ( - <> -

- We keep your account and booking data while your account is active and the Business needs it to serve you. Sign-in codes - expire within minutes and are cleared soon after. -

-

- When you ask us to delete your account we remove or anonymise your data, except what we must keep by law (for example, - records needed for accounting or to resolve a dispute). A Business may keep its own records of past appointments - according to its legal duties. -

- - ), - }, - { - id: "security", - title: "Security", - body: ( -

- We protect your data with encrypted connections, access controls limited to what each role needs, and hashed sign-in codes. - No system is perfectly secure; if a breach affecting you occurs we will notify you and the relevant authority as the law - requires. -

- ), - }, - { - id: "transfers", - title: "International transfers", - body: ( -

- Some of our providers (for example Google or WhatsApp, when you use those features) may process data outside your - country. Where they do, we rely on appropriate safeguards such as standard contractual clauses, as required by applicable - data protection law. -

- ), - }, - { - id: "rights", - title: "Your rights", - body: ( - <> -

Depending on where you live (including under the GDPR and equivalent laws), you can:

- -

- Email {LEGAL.contactEmail} to exercise any of these. For data a Business - controls, we may pass your request to that Business. We will reply within one month. You also have the right to complain - to your local data protection authority. -

- - ), - }, - { - id: "children", - title: "Children", - body: ( -

- The Platform is not directed at children under 16, and we do not knowingly collect their data. If you believe a child has - given us personal data, contact us and we will delete it. -

- ), - }, - { - id: "changes", - title: "Changes to this policy", - body: ( -

- We will post any update here with a new “last updated” date and tell you about significant changes through the - Platform or by email. See also our Terms of Service. -

- ), - }, -]; + ), + }, + ]; +} -export default function PrivacyPage() { +export default async function PrivacyPage() { + const { name, email } = await getLegalIdentity(); return ( - This policy explains what personal data {LEGAL.productName} and the Businesses that use it collect when you book an - appointment, why, and the choices you have. We have tried to keep it plain and specific. + This policy explains what personal data {name} and the Businesses that use the Platform + collect when you book an appointment, why, and the choices you have. We have tried to keep + it plain and specific.

} - sections={sections} + sections={buildSections({ name, email })} /> ); } diff --git a/src/app/(legal)/terms/page.tsx b/src/app/(legal)/terms/page.tsx index 92ceef0..40f323f 100644 --- a/src/app/(legal)/terms/page.tsx +++ b/src/app/(legal)/terms/page.tsx @@ -1,277 +1,289 @@ import type { Metadata } from "next"; import Link from "next/link"; import { LegalDocument, type LegalSectionDef } from "@/features/legal/components/legal-document"; -import { LEGAL } from "@/features/legal/legal-config"; +import { getLegalIdentity } from "@/features/legal/legal-identity"; +import type { LegalIdentity } from "@/features/legal/resolve-legal-identity"; export const metadata: Metadata = { title: "Terms of Service" }; +// Names the business per its Policies settings, so it must be read per request. +export const dynamic = "force-dynamic"; -const sections: LegalSectionDef[] = [ - { - id: "definitions", - title: "Definitions", - body: ( -
    -
  • - “{LEGAL.productName}”, “we”, “us” — {LEGAL.legalName}, {LEGAL.address}. -
  • -
  • - “Platform” — the {LEGAL.productName} website, web app and installable app, through which - appointments are booked and managed. -
  • -
  • - “Business” — a salon, barbershop or other service provider that uses the Platform to take - bookings. -
  • -
  • - “Staff” — people who work for a Business and use the Platform to manage schedules and - appointments. -
  • -
  • - “Customer”, “you” — anyone who books an appointment through the Platform, with or - without an account. -
  • -
  • - “Services” — the haircuts, treatments and other services a Business offers. -
  • -
- ), - }, - { - id: "about", - title: "About these Terms", - body: ( - <> -

- These Terms govern your use of the Platform. By creating an account, booking an appointment or otherwise using the - Platform, you agree to them. If you do not agree, please do not use the Platform. -

-

- You must be at least 16 years old to create an account or book on your own behalf. If you are younger, a parent or - guardian must book for you. -

- - ), - }, - { - id: "our-role", - title: "What NextChair does — and doesn’t do", - body: ( - <> -

- {LEGAL.productName} provides the software that connects Customers with Businesses: viewing services and availability, - booking and managing appointments, receiving reminders and, where a Business enables it, collecting loyalty points. -

-

- We are not the provider of the Services. Each appointment is an agreement between you and the Business. - The Business is responsible for the Services, their quality, their pricing, and any payment taken in person. We are an - intermediary and are not a party to that agreement. -

- - ), - }, - { - id: "accounts", - title: "Your account", - body: ( - <> -

- You can book as a guest where a Business allows it, or create an account. We sign you in with a one-time code sent to - your email address, so there is no password to remember — but you must keep access to that email address secure. -

-
    -
  • Give accurate contact details so the Business can reach you about your appointment.
  • -
  • You are responsible for activity under your account. Tell us promptly if you think it has been accessed without permission.
  • -
  • One person, one account. Do not share your account or create accounts for others without their knowledge.
  • -
- - ), - }, - { - id: "bookings", - title: "Bookings, changes and cancellations", - body: ( - <> -

- A booking is confirmed when the Platform shows it as confirmed or you receive a confirmation. Some Businesses approve - bookings manually; in that case the booking is pending until they accept it. -

+function buildSections({ name, email }: LegalIdentity): LegalSectionDef[] { + return [ + { + id: "definitions", + title: "Definitions", + body: (
  • - Each Business sets its own cancellation window, no-show policy and any deposits or fees. These are shown during booking - and apply to your appointment. + “we”, “us”, “our” — {name}.
  • - If you cannot attend, cancel or reschedule as early as you can, through the Platform or by contacting the Business. + “Platform” — the website, web app and installable app + through which appointments are booked and managed.
  • - A Business may need to change or cancel an appointment (for example due to illness). They will contact you and offer a - new time or a cancellation where possible. + “Business” — a salon, barbershop or other service provider + that uses the Platform to take bookings. +
  • +
  • + “Staff” — people who work for a Business and use the + Platform to manage schedules and appointments. +
  • +
  • + “Customer”, “you” — anyone who books an + appointment through the Platform, with or without an account. +
  • +
  • + “Services” — the haircuts, treatments and other services a + Business offers.
- - ), - }, - { - id: "payments", - title: "Prices and payments", - body: ( -

- Prices are set by each Business and shown at booking. Unless the Platform explicitly says otherwise, payment is made to the - Business directly, at the appointment. We do not process payments for Services and are not responsible for refunds or - payment disputes with a Business. -

- ), - }, - { - id: "reminders", - title: "Reminders and notifications", - body: ( - <> + ), + }, + { + id: "about", + title: "About these Terms", + body: ( + <> +

+ These Terms govern your use of the Platform. By creating an account, booking an + appointment or otherwise using the Platform, you agree to them. If you do not agree, + please do not use the Platform. +

+

+ You must be at least 16 years old to create an account or book on your own behalf. If + you are younger, a parent or guardian must book for you. +

+ + ), + }, + { + id: "our-role", + title: "What we do — and don’t do", + body: ( + <> +

+ We provide the software that connects Customers with Businesses: viewing services and + availability, booking and managing appointments, receiving reminders and, where a + Business enables it, collecting loyalty points. +

+

+ We are not the provider of the Services. Each appointment is an + agreement between you and the Business. The Business is responsible for the Services, + their quality, their pricing, and any payment taken in person. We are an intermediary + and are not a party to that agreement. +

+ + ), + }, + { + id: "accounts", + title: "Your account", + body: ( + <> +

+ You can book as a guest where a Business allows it, or create an account. We sign you in + with a one-time code sent to your email address, so there is no password to remember — + but you must keep access to that email address secure. +

+
    +
  • + Give accurate contact details so the Business can reach you about your appointment. +
  • +
  • + You are responsible for activity under your account. Tell us promptly if you think it + has been accessed without permission. +
  • +
  • + One person, one account. Do not share your account or create accounts for others + without their knowledge. +
  • +
+ + ), + }, + { + id: "bookings", + title: "Bookings, changes and cancellations", + body: ( + <> +

+ A booking is confirmed when the Platform shows it as confirmed or you receive a + confirmation. Some Businesses approve bookings manually; in that case the booking is + pending until they accept it. +

+
    +
  • + Each Business sets its own cancellation window, no-show policy and any deposits or + fees. These are shown during booking and apply to your appointment. +
  • +
  • + If you cannot attend, cancel or reschedule as early as you can, through the Platform + or by contacting the Business. +
  • +
  • + A Business may need to change or cancel an appointment (for example due to illness). + They will contact you and offer a new time or a cancellation where possible. +
  • +
+ + ), + }, + { + id: "payments", + title: "Prices and payments", + body: (

- To reduce missed appointments, the Platform and the Business may send you booking confirmations and reminders by email, - push notification, or a message the Business sends you on WhatsApp using the phone number you provided. + Prices are set by each Business and shown at booking. Unless the Platform explicitly says + otherwise, payment is made to the Business directly, at the appointment. We do not process + payments for Services and are not responsible for refunds or payment disputes with a + Business.

+ ), + }, + { + id: "reminders", + title: "Reminders and notifications", + body: ( + <> +

+ To reduce missed appointments, the Platform and the Business may send you booking + confirmations and reminders by email, push notification, or a message the Business sends + you on WhatsApp using the phone number you provided. +

+

+ These are service messages about your booking. You can turn off push notifications in + your device or account settings. Messages sent by a Business through WhatsApp are + governed by WhatsApp’s own terms. +

+ + ), + }, + { + id: "acceptable-use", + title: "Acceptable use", + body: ( + <> +

You agree not to:

+
    +
  • make false, spam or repeated bookings you do not intend to keep;
  • +
  • harass, threaten or abuse Staff, Businesses or other Customers;
  • +
  • attempt to access accounts, data or systems that are not yours;
  • +
  • + interfere with the Platform, probe it for vulnerabilities without our permission, or + use bots or scrapers on it; +
  • +
  • use the Platform for anything unlawful.
  • +
+

We may suspend or close accounts that break these rules.

+ + ), + }, + { + id: "reviews", + title: "Reviews and content", + body: (

- These are service messages about your booking. You can turn off push notifications in your device or account settings. - Messages sent by a Business through WhatsApp are governed by WhatsApp’s own terms. + Where the Platform lets you leave feedback or upload content such as a profile picture, + you keep ownership of it but give the Business and us a non-exclusive, worldwide licence + to display it on the Platform for as long as it is there. It must be honest, lawful and + not infringe anyone’s rights. We may remove content that breaks these Terms.

- - ), - }, - { - id: "acceptable-use", - title: "Acceptable use", - body: ( - <> -

You agree not to:

-
    -
  • make false, spam or repeated bookings you do not intend to keep;
  • -
  • harass, threaten or abuse Staff, Businesses or other Customers;
  • -
  • attempt to access accounts, data or systems that are not yours;
  • -
  • interfere with the Platform, probe it for vulnerabilities without our permission, or use bots or scrapers on it;
  • -
  • use the Platform for anything unlawful.
  • -
-

We may suspend or close accounts that break these rules.

- - ), - }, - { - id: "reviews", - title: "Reviews and content", - body: ( -

- Where the Platform lets you leave feedback or upload content such as a profile picture, you keep ownership of it but give - the Business and us a non-exclusive, worldwide licence to display it on the Platform for as long as it is there. It must be - honest, lawful and not infringe anyone’s rights. We may remove content that breaks these Terms. -

- ), - }, - { - id: "ip", - title: "Our intellectual property", - body: ( -

- The Platform, including its software, design, name and logo, belongs to us or our licensors. You may use it for its - intended purpose, but you may not copy, modify, resell or reverse-engineer it, except as the law allows. -

- ), - }, - { - id: "liability", - title: "Availability and liability", - body: ( - <> + ), + }, + { + id: "ip", + title: "Our intellectual property", + body: (

- We work to keep the Platform running and accurate, but it is provided “as is” and we cannot promise it will - always be available or error-free. Maintenance and outages happen. + The Platform, including its software, design, name and logo, belongs to us or our + licensors. You may use it for its intended purpose, but you may not copy, modify, resell + or reverse-engineer it, except as the law allows.

+ ), + }, + { + id: "liability", + title: "Availability and liability", + body: ( + <> +

+ We work to keep the Platform running and accurate, but it is provided “as + is” and we cannot promise it will always be available or error-free. Maintenance + and outages happen. +

+

+ To the extent the law allows, we are not liable for the Services a Business provides, + for a Business’s actions or omissions, or for indirect or consequential losses. Nothing + in these Terms limits liability that cannot be limited by law, including for death or + personal injury caused by negligence, or for fraud. Your statutory consumer rights are + not affected. +

+ + ), + }, + { + id: "privacy", + title: "Your personal data", + body: (

- To the extent the law allows, we are not liable for the Services a Business provides, for a Business’s actions or - omissions, or for indirect or consequential losses. Nothing in these Terms limits liability that cannot be limited by law, - including for death or personal injury caused by negligence, or for fraud. Your statutory consumer rights are not - affected. + How we and Businesses handle your personal data is explained in our{" "} + Privacy Policy.

- - ), - }, - { - id: "privacy", - title: "Your personal data", - body: ( -

- How we and Businesses handle your personal data is explained in our Privacy Policy. -

- ), - }, - { - id: "complaints", - title: "Complaints", - body: ( - <> -

- About a Service: raise it with the Business first — they are responsible for the Service. -

-

- About the Platform: email {LEGAL.contactEmail} with what - happened and, if relevant, the date and Business. We aim to reply within 14 days. -

- - ), - }, - { - id: "changes", - title: "Changes and termination", - body: ( - <> -

- We may update these Terms, for example when we add features or the law changes. We will post the new version here with a - new “last updated” date and, for significant changes, tell you through the Platform or by email. If you keep - using the Platform after the change, you accept the updated Terms; if you do not agree, you can stop using it and delete - your account. -

-

- You can stop using the Platform at any time. We may suspend or end your access if you break these Terms or if we are - required to by law. -

- - ), - }, - { - id: "law", - title: "Governing law", - body: ( -

- These Terms are governed by the law of {LEGAL.governingLaw}. This does not remove any mandatory consumer protections you - have in the country where you live. -

- ), - }, - { - id: "contact", - title: "Contact us", - body: ( -

- {LEGAL.legalName} -
- {LEGAL.address} -
- {LEGAL.contactEmail} -

- ), - }, -]; + ), + }, + { + id: "complaints", + title: "Complaints", + body: ( + <> +

+ About a Service: raise it with the Business first — they are + responsible for the Service. +

+

+ About the Platform: email {email} with + what happened and, if relevant, the date and Business. We aim to reply within 14 days. +

+ + ), + }, + { + id: "changes", + title: "Changes and termination", + body: ( + <> +

+ We may update these Terms, for example when we add features or the law changes. We will + post the new version here with a new “last updated” date and, for + significant changes, tell you through the Platform or by email. If you keep using the + Platform after the change, you accept the updated Terms; if you do not agree, you can + stop using it and delete your account. +

+

+ You can stop using the Platform at any time. We may suspend or end your access if you + break these Terms or if we are required to by law. +

+ + ), + }, + ]; +} -export default function TermsPage() { +export default async function TermsPage() { + const identity = await getLegalIdentity(); return ( - Welcome to {LEGAL.productName}. These Terms explain the rules for using our booking platform, whether you book - appointments as a Customer or work for a Business. Please read them — they are written to be readable. + Welcome to {identity.name}. These Terms explain the rules for using our booking platform, + whether you book appointments as a Customer or work for a Business. Please read them — + they are written to be readable.

} - sections={sections} + sections={buildSections(identity)} /> ); } diff --git a/src/app/(owner)/owner/settings/business/page.tsx b/src/app/(owner)/owner/settings/business/page.tsx index 6bae6e8..3fb0d3f 100644 --- a/src/app/(owner)/owner/settings/business/page.tsx +++ b/src/app/(owner)/owner/settings/business/page.tsx @@ -56,6 +56,8 @@ export default async function OwnerBusinessDetailsPage() { lateArrivalPolicy: business.settings?.lateArrivalPolicy ?? "", bookingTerms: business.settings?.bookingTerms ?? "", privacyPolicyUrl: business.settings?.privacyPolicyUrl ?? "", + legalNameDisplay: business.settings?.legalNameDisplay ?? "NEXTCHAIR", + legalContactEmail: business.settings?.legalContactEmail ?? "NEXTCHAIR", }} /> diff --git a/src/features/auth/components/sign-in-form.tsx b/src/features/auth/components/sign-in-form.tsx index 4e4414e..3ab431a 100644 --- a/src/features/auth/components/sign-in-form.tsx +++ b/src/features/auth/components/sign-in-form.tsx @@ -117,6 +117,18 @@ export function SignInForm() { Create one

+ +

+ By continuing, you agree to our{" "} + + Terms of Service + {" "} + and{" "} + + Privacy Policy + + . +

)}
diff --git a/src/features/legal/__tests__/resolve-legal-identity.test.ts b/src/features/legal/__tests__/resolve-legal-identity.test.ts new file mode 100644 index 0000000..27748a7 --- /dev/null +++ b/src/features/legal/__tests__/resolve-legal-identity.test.ts @@ -0,0 +1,46 @@ +import { describe, expect, it } from "vitest"; +import { LegalContactEmail, LegalNameDisplay } from "@/generated/prisma/enums"; +import { LEGAL } from "../legal-config"; +import { resolveLegalIdentity } from "../resolve-legal-identity"; + +const base = { + businessName: "Cut & Co", + businessEmail: "hello@cutandco.example", + nameDisplay: LegalNameDisplay.NEXTCHAIR, + contactEmail: LegalContactEmail.NEXTCHAIR, +}; + +describe("resolveLegalIdentity", () => { + it("defaults to NextChair's name and email", () => { + expect(resolveLegalIdentity(base)).toEqual({ name: "NextChair", email: LEGAL.contactEmail }); + }); + + it("can show the business name instead of NextChair", () => { + expect(resolveLegalIdentity({ ...base, nameDisplay: LegalNameDisplay.BUSINESS }).name).toBe( + "Cut & Co", + ); + }); + + it("can show both names together", () => { + expect(resolveLegalIdentity({ ...base, nameDisplay: LegalNameDisplay.BOTH }).name).toBe( + "NextChair and Cut & Co", + ); + }); + + it("can show the business email instead of NextChair's", () => { + expect(resolveLegalIdentity({ ...base, contactEmail: LegalContactEmail.BUSINESS }).email).toBe( + "hello@cutandco.example", + ); + }); + + it("falls back to NextChair when the business has no name or email to show", () => { + expect( + resolveLegalIdentity({ + businessName: null, + businessEmail: " ", + nameDisplay: LegalNameDisplay.BOTH, + contactEmail: LegalContactEmail.BUSINESS, + }), + ).toEqual({ name: "NextChair", email: LEGAL.contactEmail }); + }); +}); diff --git a/src/features/legal/components/legal-document.tsx b/src/features/legal/components/legal-document.tsx index 4bccfa7..6da49f2 100644 --- a/src/features/legal/components/legal-document.tsx +++ b/src/features/legal/components/legal-document.tsx @@ -27,7 +27,7 @@ export function LegalDocument({

Last updated {LEGAL.lastUpdated}

{intro}
-