Policies section New settings: two new dropdowns are under Policies in Manage Business: Name shown on Terms & Privacy: NextChair, My business name, or NextChair and my business name. Contact email on Terms & Privacy: NextChair (info@next-chair.co.uk) or My business email. Fallbacks: both default to NextChair. If you pick the business email but haven’t set one, the form warns you and the pages fall back to the NextChair address. Where the name appears: the choice changes the name in the header link, footer, “we/us” definition, privacy controller line and intro on both pages. Business email: it replaces the address in the complaints section and the privacy contact lines. Database change: this needed a new migration, 20261017120000_add_legal_display_settings. Existing businesses default to NextChair, so nothing changes for them. Run prisma migrate deploy on your server. Terms and placeholders Sections 15 (Governing law) and 16 (Contact us) are removed from terms/page.tsx, so the Terms now have 14 sections. The [LEGAL ENTITY NAME] and [REGISTERED ADDRESS] placeholders are gone, along with the unused governing-law setting. The contact email is now info@next-chair.co.uk in legal-config.ts. Login page sign-in-form.tsx now shows “By continuing, you agree to our Terms of Service and Privacy Policy”, with both as links.
This commit is contained in:
14 files changed
+400
-175
No files matched your search
@@ -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';
|
||||
@@ -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?
|
||||
|
||||
@@ -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 (
|
||||
<div className="bg-stone flex min-h-screen flex-col">
|
||||
<header className="border-line border-b bg-white">
|
||||
<div className="mx-auto flex max-w-3xl items-center justify-between px-6 py-4">
|
||||
<Link href="/" className="text-ink flex items-center gap-2 text-sm font-bold">
|
||||
<ArrowLeft className="h-4 w-4" aria-hidden="true" />
|
||||
Back to {LEGAL.productName}
|
||||
Back to {name}
|
||||
</Link>
|
||||
<nav aria-label="Legal" className="text-muted flex gap-5 text-sm">
|
||||
<Link href="/terms" className="hover:text-ink">
|
||||
@@ -31,7 +33,7 @@ export default function LegalLayout({ children }: { children: React.ReactNode })
|
||||
{children}
|
||||
</main>
|
||||
<footer className="border-line text-muted border-t px-6 py-6 text-center text-xs">
|
||||
© {new Date().getFullYear()} {LEGAL.legalName}
|
||||
© {new Date().getFullYear()} {name}
|
||||
</footer>
|
||||
</div>
|
||||
);
|
||||
|
||||
@@ -1,34 +1,40 @@
|
||||
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[] = [
|
||||
function buildSections({ name, email }: LegalIdentity): LegalSectionDef[] {
|
||||
return [
|
||||
{
|
||||
id: "who-we-are",
|
||||
title: "Who is responsible for your data",
|
||||
body: (
|
||||
<>
|
||||
<p>
|
||||
{LEGAL.productName} is a booking platform used by many independent Businesses. Two parties handle your data, in
|
||||
different roles:
|
||||
This booking platform is used by independent Businesses. Two parties handle your data,
|
||||
in different roles:
|
||||
</p>
|
||||
<ul>
|
||||
<li>
|
||||
<strong>The Business you book with</strong> decides why and how your booking details are used for your appointments —
|
||||
it is an independent <em>controller</em> of that data. It sees your name, contact details and booking history, and may
|
||||
keep notes about you.
|
||||
<strong>The Business you book with</strong> decides why and how your booking details
|
||||
are used for your appointments — it is an independent <em>controller</em> of that
|
||||
data. It sees your name, contact details and booking history, and may keep notes about
|
||||
you.
|
||||
</li>
|
||||
<li>
|
||||
<strong>{LEGAL.legalName}</strong> ({LEGAL.address}) runs the Platform. For your account, sign-in and the running and
|
||||
security of the Platform, we are a controller. For the booking data a Business keeps in the Platform, we act as its{" "}
|
||||
<strong>The operator of the Platform</strong> ({name}, “we”). For your
|
||||
account, sign-in and the running and security of the Platform, we are a controller.
|
||||
For the booking data a Business keeps in the Platform, we act as its{" "}
|
||||
<em>processor</em>, on its instructions.
|
||||
</li>
|
||||
</ul>
|
||||
<p>
|
||||
Contact us about privacy at <a href={`mailto:${LEGAL.contactEmail}`}>{LEGAL.contactEmail}</a>.
|
||||
Contact us about privacy at <a href={`mailto:${email}`}>{email}</a>.
|
||||
</p>
|
||||
</>
|
||||
),
|
||||
@@ -53,18 +59,26 @@ const sections: LegalSectionDef[] = [
|
||||
<ul>
|
||||
<li>Appointment history and status (confirmed, cancelled, completed, no-show).</li>
|
||||
<li>Loyalty points and rewards, if the Business runs a loyalty programme.</li>
|
||||
<li>Notes a Business keeps about its clients to deliver the service (for example, preferred style).</li>
|
||||
<li>Sign-in codes (short-lived and stored in hashed form) and session information that keeps you signed in.</li>
|
||||
<li>A push-notification address for your device, if you switch on push notifications.</li>
|
||||
<li>
|
||||
Notes a Business keeps about its clients to deliver the service (for example,
|
||||
preferred style).
|
||||
</li>
|
||||
<li>
|
||||
Sign-in codes (short-lived and stored in hashed form) and session information that
|
||||
keeps you signed in.
|
||||
</li>
|
||||
<li>
|
||||
A push-notification address for your device, if you switch on push notifications.
|
||||
</li>
|
||||
</ul>
|
||||
<p>
|
||||
<strong>For Staff and owners:</strong> work schedule, services offered and, if connected, the Google Calendar account used
|
||||
to sync appointments.
|
||||
<strong>For Staff and owners:</strong> work schedule, services offered and, if
|
||||
connected, the Google Calendar account used to sync appointments.
|
||||
</p>
|
||||
<p>
|
||||
<strong>Clients added by a Business:</strong> 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.
|
||||
<strong>Clients added by a Business:</strong> 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.
|
||||
</p>
|
||||
</>
|
||||
),
|
||||
@@ -75,25 +89,26 @@ const sections: LegalSectionDef[] = [
|
||||
body: (
|
||||
<ul>
|
||||
<li>
|
||||
<strong>To provide the booking service</strong> — create and manage appointments, show availability and sign you in.
|
||||
<strong>To provide the booking service</strong> — create and manage appointments, show
|
||||
availability and sign you in.
|
||||
<em> Basis:</em> performing our contract with you.
|
||||
</li>
|
||||
<li>
|
||||
<strong>To send booking messages</strong> — confirmations, reminders, changes and cancellations by email, push, or a
|
||||
WhatsApp message from the Business. <em>Basis:</em> performing the contract, and legitimate interest in reducing missed
|
||||
appointments.
|
||||
<strong>To send booking messages</strong> — confirmations, reminders, changes and
|
||||
cancellations by email, push, or a WhatsApp message from the Business. <em>Basis:</em>{" "}
|
||||
performing the contract, and legitimate interest in reducing missed appointments.
|
||||
</li>
|
||||
<li>
|
||||
<strong>To keep the Platform safe</strong> — prevent abuse, fraud and unauthorised access, and fix errors.{" "}
|
||||
<em>Basis:</em> legitimate interest.
|
||||
<strong>To keep the Platform safe</strong> — prevent abuse, fraud and unauthorised
|
||||
access, and fix errors. <em>Basis:</em> legitimate interest.
|
||||
</li>
|
||||
<li>
|
||||
<strong>To meet legal obligations</strong> — such as accounting, or responding to lawful requests. <em>Basis:</em> legal
|
||||
obligation.
|
||||
<strong>To meet legal obligations</strong> — such as accounting, or responding to lawful
|
||||
requests. <em>Basis:</em> legal obligation.
|
||||
</li>
|
||||
<li>
|
||||
<strong>Optional features</strong> — such as push notifications or Google Calendar sync. <em>Basis:</em> your consent,
|
||||
which you can withdraw at any time.
|
||||
<strong>Optional features</strong> — such as push notifications or Google Calendar sync.{" "}
|
||||
<em>Basis:</em> your consent, which you can withdraw at any time.
|
||||
</li>
|
||||
</ul>
|
||||
),
|
||||
@@ -105,19 +120,21 @@ const sections: LegalSectionDef[] = [
|
||||
<>
|
||||
<ul>
|
||||
<li>
|
||||
<strong>The Business you book with</strong>, and its Staff, who need your details to serve you.
|
||||
<strong>The Business you book with</strong>, and its Staff, who need your details to
|
||||
serve you.
|
||||
</li>
|
||||
<li>
|
||||
<strong>Service providers</strong> that run the Platform for us, such as hosting, email delivery and push-notification
|
||||
services, bound to use the data only for that purpose.
|
||||
<strong>Service providers</strong> that run the Platform for us, such as hosting,
|
||||
email delivery and push-notification services, bound to use the data only for that
|
||||
purpose.
|
||||
</li>
|
||||
<li>
|
||||
<strong>Google</strong>, if a Staff member or owner connects Google Calendar — appointment details are written to their
|
||||
calendar.
|
||||
<strong>Google</strong>, if a Staff member or owner connects Google Calendar —
|
||||
appointment details are written to their calendar.
|
||||
</li>
|
||||
<li>
|
||||
<strong>WhatsApp</strong>, if a Business messages you there — your phone number and the message are handled by WhatsApp
|
||||
under its own terms.
|
||||
<strong>WhatsApp</strong>, if a Business messages you there — your phone number and
|
||||
the message are handled by WhatsApp under its own terms.
|
||||
</li>
|
||||
<li>
|
||||
<strong>Authorities</strong>, where the law requires it.
|
||||
@@ -135,15 +152,18 @@ const sections: LegalSectionDef[] = [
|
||||
<p>We use only what the Platform needs to work:</p>
|
||||
<ul>
|
||||
<li>
|
||||
<strong>Session cookie</strong> — keeps you signed in. Without it you could not use your account.
|
||||
<strong>Session cookie</strong> — keeps you signed in. Without it you could not use
|
||||
your account.
|
||||
</li>
|
||||
<li>
|
||||
<strong>Local storage and the app cache</strong> — remember small settings and let the installable app load offline.
|
||||
<strong>Local storage and the app cache</strong> — remember small settings and let the
|
||||
installable app load offline.
|
||||
</li>
|
||||
</ul>
|
||||
<p>
|
||||
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.
|
||||
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.
|
||||
</p>
|
||||
</>
|
||||
),
|
||||
@@ -154,13 +174,13 @@ const sections: LegalSectionDef[] = [
|
||||
body: (
|
||||
<>
|
||||
<p>
|
||||
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.
|
||||
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.
|
||||
</p>
|
||||
<p>
|
||||
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.
|
||||
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.
|
||||
</p>
|
||||
</>
|
||||
),
|
||||
@@ -170,9 +190,9 @@ const sections: LegalSectionDef[] = [
|
||||
title: "Security",
|
||||
body: (
|
||||
<p>
|
||||
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.
|
||||
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.
|
||||
</p>
|
||||
),
|
||||
},
|
||||
@@ -181,9 +201,9 @@ const sections: LegalSectionDef[] = [
|
||||
title: "International transfers",
|
||||
body: (
|
||||
<p>
|
||||
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.
|
||||
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.
|
||||
</p>
|
||||
),
|
||||
},
|
||||
@@ -192,19 +212,23 @@ const sections: LegalSectionDef[] = [
|
||||
title: "Your rights",
|
||||
body: (
|
||||
<>
|
||||
<p>Depending on where you live (including under the GDPR and equivalent laws), you can:</p>
|
||||
<p>
|
||||
Depending on where you live (including under the GDPR and equivalent laws), you can:
|
||||
</p>
|
||||
<ul>
|
||||
<li>access the personal data we hold about you;</li>
|
||||
<li>correct data that is wrong or incomplete;</li>
|
||||
<li>ask us to delete your data;</li>
|
||||
<li>restrict or object to certain uses, including those based on legitimate interest;</li>
|
||||
<li>
|
||||
restrict or object to certain uses, including those based on legitimate interest;
|
||||
</li>
|
||||
<li>receive your data in a portable format;</li>
|
||||
<li>withdraw consent at any time, where we rely on it.</li>
|
||||
</ul>
|
||||
<p>
|
||||
Email <a href={`mailto:${LEGAL.contactEmail}`}>{LEGAL.contactEmail}</a> 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.
|
||||
Email <a href={`mailto:${email}`}>{email}</a> 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.
|
||||
</p>
|
||||
</>
|
||||
),
|
||||
@@ -214,8 +238,8 @@ const sections: LegalSectionDef[] = [
|
||||
title: "Children",
|
||||
body: (
|
||||
<p>
|
||||
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.
|
||||
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.
|
||||
</p>
|
||||
),
|
||||
},
|
||||
@@ -224,24 +248,28 @@ const sections: LegalSectionDef[] = [
|
||||
title: "Changes to this policy",
|
||||
body: (
|
||||
<p>
|
||||
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 <Link href="/terms">Terms of Service</Link>.
|
||||
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{" "}
|
||||
<Link href="/terms">Terms of Service</Link>.
|
||||
</p>
|
||||
),
|
||||
},
|
||||
];
|
||||
}
|
||||
|
||||
export default function PrivacyPage() {
|
||||
export default async function PrivacyPage() {
|
||||
const { name, email } = await getLegalIdentity();
|
||||
return (
|
||||
<LegalDocument
|
||||
title="Privacy Policy"
|
||||
intro={
|
||||
<p>
|
||||
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.
|
||||
</p>
|
||||
}
|
||||
sections={sections}
|
||||
sections={buildSections({ name, email })}
|
||||
/>
|
||||
);
|
||||
}
|
||||
+102
-90
@@ -1,37 +1,42 @@
|
||||
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[] = [
|
||||
function buildSections({ name, email }: LegalIdentity): LegalSectionDef[] {
|
||||
return [
|
||||
{
|
||||
id: "definitions",
|
||||
title: "Definitions",
|
||||
body: (
|
||||
<ul>
|
||||
<li>
|
||||
<strong>“{LEGAL.productName}”, “we”, “us”</strong> — {LEGAL.legalName}, {LEGAL.address}.
|
||||
<strong>“we”, “us”, “our”</strong> — {name}.
|
||||
</li>
|
||||
<li>
|
||||
<strong>“Platform”</strong> — the {LEGAL.productName} website, web app and installable app, through which
|
||||
appointments are booked and managed.
|
||||
<strong>“Platform”</strong> — the website, web app and installable app
|
||||
through which appointments are booked and managed.
|
||||
</li>
|
||||
<li>
|
||||
<strong>“Business”</strong> — a salon, barbershop or other service provider that uses the Platform to take
|
||||
bookings.
|
||||
<strong>“Business”</strong> — a salon, barbershop or other service provider
|
||||
that uses the Platform to take bookings.
|
||||
</li>
|
||||
<li>
|
||||
<strong>“Staff”</strong> — people who work for a Business and use the Platform to manage schedules and
|
||||
appointments.
|
||||
<strong>“Staff”</strong> — people who work for a Business and use the
|
||||
Platform to manage schedules and appointments.
|
||||
</li>
|
||||
<li>
|
||||
<strong>“Customer”, “you”</strong> — anyone who books an appointment through the Platform, with or
|
||||
without an account.
|
||||
<strong>“Customer”, “you”</strong> — anyone who books an
|
||||
appointment through the Platform, with or without an account.
|
||||
</li>
|
||||
<li>
|
||||
<strong>“Services”</strong> — the haircuts, treatments and other services a Business offers.
|
||||
<strong>“Services”</strong> — the haircuts, treatments and other services a
|
||||
Business offers.
|
||||
</li>
|
||||
</ul>
|
||||
),
|
||||
@@ -42,29 +47,32 @@ const sections: LegalSectionDef[] = [
|
||||
body: (
|
||||
<>
|
||||
<p>
|
||||
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.
|
||||
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.
|
||||
</p>
|
||||
<p>
|
||||
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.
|
||||
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.
|
||||
</p>
|
||||
</>
|
||||
),
|
||||
},
|
||||
{
|
||||
id: "our-role",
|
||||
title: "What NextChair does — and doesn’t do",
|
||||
title: "What we do — and don’t do",
|
||||
body: (
|
||||
<>
|
||||
<p>
|
||||
{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 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.
|
||||
</p>
|
||||
<p>
|
||||
<strong>We are not the provider of the Services.</strong> 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.
|
||||
<strong>We are not the provider of the Services.</strong> 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.
|
||||
</p>
|
||||
</>
|
||||
),
|
||||
@@ -75,13 +83,22 @@ const sections: LegalSectionDef[] = [
|
||||
body: (
|
||||
<>
|
||||
<p>
|
||||
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.
|
||||
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.
|
||||
</p>
|
||||
<ul>
|
||||
<li>Give accurate contact details so the Business can reach you about your appointment.</li>
|
||||
<li>You are responsible for activity under your account. Tell us promptly if you think it has been accessed without permission.</li>
|
||||
<li>One person, one account. Do not share your account or create accounts for others without their knowledge.</li>
|
||||
<li>
|
||||
Give accurate contact details so the Business can reach you about your appointment.
|
||||
</li>
|
||||
<li>
|
||||
You are responsible for activity under your account. Tell us promptly if you think it
|
||||
has been accessed without permission.
|
||||
</li>
|
||||
<li>
|
||||
One person, one account. Do not share your account or create accounts for others
|
||||
without their knowledge.
|
||||
</li>
|
||||
</ul>
|
||||
</>
|
||||
),
|
||||
@@ -92,20 +109,22 @@ const sections: LegalSectionDef[] = [
|
||||
body: (
|
||||
<>
|
||||
<p>
|
||||
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.
|
||||
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.
|
||||
</p>
|
||||
<ul>
|
||||
<li>
|
||||
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.
|
||||
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.
|
||||
</li>
|
||||
<li>
|
||||
If you cannot attend, cancel or reschedule as early as you can, through the Platform or by contacting the Business.
|
||||
If you cannot attend, cancel or reschedule as early as you can, through the Platform
|
||||
or by contacting the Business.
|
||||
</li>
|
||||
<li>
|
||||
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.
|
||||
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.
|
||||
</li>
|
||||
</ul>
|
||||
</>
|
||||
@@ -116,9 +135,10 @@ const sections: LegalSectionDef[] = [
|
||||
title: "Prices and payments",
|
||||
body: (
|
||||
<p>
|
||||
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.
|
||||
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.
|
||||
</p>
|
||||
),
|
||||
},
|
||||
@@ -128,12 +148,14 @@ const sections: LegalSectionDef[] = [
|
||||
body: (
|
||||
<>
|
||||
<p>
|
||||
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.
|
||||
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.
|
||||
</p>
|
||||
<p>
|
||||
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.
|
||||
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.
|
||||
</p>
|
||||
</>
|
||||
),
|
||||
@@ -148,7 +170,10 @@ const sections: LegalSectionDef[] = [
|
||||
<li>make false, spam or repeated bookings you do not intend to keep;</li>
|
||||
<li>harass, threaten or abuse Staff, Businesses or other Customers;</li>
|
||||
<li>attempt to access accounts, data or systems that are not yours;</li>
|
||||
<li>interfere with the Platform, probe it for vulnerabilities without our permission, or use bots or scrapers on it;</li>
|
||||
<li>
|
||||
interfere with the Platform, probe it for vulnerabilities without our permission, or
|
||||
use bots or scrapers on it;
|
||||
</li>
|
||||
<li>use the Platform for anything unlawful.</li>
|
||||
</ul>
|
||||
<p>We may suspend or close accounts that break these rules.</p>
|
||||
@@ -160,9 +185,10 @@ const sections: LegalSectionDef[] = [
|
||||
title: "Reviews and content",
|
||||
body: (
|
||||
<p>
|
||||
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.
|
||||
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.
|
||||
</p>
|
||||
),
|
||||
},
|
||||
@@ -171,8 +197,9 @@ const sections: LegalSectionDef[] = [
|
||||
title: "Our intellectual property",
|
||||
body: (
|
||||
<p>
|
||||
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.
|
||||
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.
|
||||
</p>
|
||||
),
|
||||
},
|
||||
@@ -182,14 +209,16 @@ const sections: LegalSectionDef[] = [
|
||||
body: (
|
||||
<>
|
||||
<p>
|
||||
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.
|
||||
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.
|
||||
</p>
|
||||
<p>
|
||||
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.
|
||||
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.
|
||||
</p>
|
||||
</>
|
||||
),
|
||||
@@ -199,7 +228,8 @@ const sections: LegalSectionDef[] = [
|
||||
title: "Your personal data",
|
||||
body: (
|
||||
<p>
|
||||
How we and Businesses handle your personal data is explained in our <Link href="/privacy">Privacy Policy</Link>.
|
||||
How we and Businesses handle your personal data is explained in our{" "}
|
||||
<Link href="/privacy">Privacy Policy</Link>.
|
||||
</p>
|
||||
),
|
||||
},
|
||||
@@ -209,11 +239,12 @@ const sections: LegalSectionDef[] = [
|
||||
body: (
|
||||
<>
|
||||
<p>
|
||||
<strong>About a Service:</strong> raise it with the Business first — they are responsible for the Service.
|
||||
<strong>About a Service:</strong> raise it with the Business first — they are
|
||||
responsible for the Service.
|
||||
</p>
|
||||
<p>
|
||||
<strong>About the Platform:</strong> email <a href={`mailto:${LEGAL.contactEmail}`}>{LEGAL.contactEmail}</a> with what
|
||||
happened and, if relevant, the date and Business. We aim to reply within 14 days.
|
||||
<strong>About the Platform:</strong> email <a href={`mailto:${email}`}>{email}</a> with
|
||||
what happened and, if relevant, the date and Business. We aim to reply within 14 days.
|
||||
</p>
|
||||
</>
|
||||
),
|
||||
@@ -224,54 +255,35 @@ const sections: LegalSectionDef[] = [
|
||||
body: (
|
||||
<>
|
||||
<p>
|
||||
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.
|
||||
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.
|
||||
</p>
|
||||
<p>
|
||||
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.
|
||||
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.
|
||||
</p>
|
||||
</>
|
||||
),
|
||||
},
|
||||
{
|
||||
id: "law",
|
||||
title: "Governing law",
|
||||
body: (
|
||||
<p>
|
||||
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.
|
||||
</p>
|
||||
),
|
||||
},
|
||||
{
|
||||
id: "contact",
|
||||
title: "Contact us",
|
||||
body: (
|
||||
<p>
|
||||
{LEGAL.legalName}
|
||||
<br />
|
||||
{LEGAL.address}
|
||||
<br />
|
||||
<a href={`mailto:${LEGAL.contactEmail}`}>{LEGAL.contactEmail}</a>
|
||||
</p>
|
||||
),
|
||||
},
|
||||
];
|
||||
}
|
||||
|
||||
export default function TermsPage() {
|
||||
export default async function TermsPage() {
|
||||
const identity = await getLegalIdentity();
|
||||
return (
|
||||
<LegalDocument
|
||||
title="Terms of Service"
|
||||
intro={
|
||||
<p>
|
||||
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.
|
||||
</p>
|
||||
}
|
||||
sections={sections}
|
||||
sections={buildSections(identity)}
|
||||
/>
|
||||
);
|
||||
}
|
||||
@@ -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",
|
||||
}}
|
||||
/>
|
||||
</SettingsPageShell>
|
||||
|
||||
@@ -117,6 +117,18 @@ export function SignInForm() {
|
||||
Create one
|
||||
</Link>
|
||||
</p>
|
||||
|
||||
<p className="text-muted mt-6 text-center text-xs leading-relaxed">
|
||||
By continuing, you agree to our{" "}
|
||||
<Link href="/terms" className="underline">
|
||||
Terms of Service
|
||||
</Link>{" "}
|
||||
and{" "}
|
||||
<Link href="/privacy" className="underline">
|
||||
Privacy Policy
|
||||
</Link>
|
||||
.
|
||||
</p>
|
||||
</>
|
||||
)}
|
||||
</div>
|
||||
|
||||
@@ -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 });
|
||||
});
|
||||
});
|
||||
@@ -27,7 +27,7 @@ export function LegalDocument({
|
||||
<p className="text-muted mt-2 text-sm">Last updated {LEGAL.lastUpdated}</p>
|
||||
<div className="text-ink mt-6 space-y-3 leading-relaxed">{intro}</div>
|
||||
|
||||
<nav aria-label="Contents" className="border-line mt-8 rounded-tile border bg-white p-5">
|
||||
<nav aria-label="Contents" className="border-line rounded-tile mt-8 border bg-white p-5">
|
||||
<p className="text-ink mb-3 text-sm font-bold">Contents</p>
|
||||
<ol className="text-muted grid gap-x-8 gap-y-1.5 text-sm sm:grid-cols-2">
|
||||
{sections.map((section, i) => (
|
||||
@@ -46,7 +46,7 @@ export function LegalDocument({
|
||||
<h2 className="text-ink mb-3 text-xl font-bold tracking-tight">
|
||||
{i + 1}. {section.title}
|
||||
</h2>
|
||||
<div className="text-ink space-y-3 leading-relaxed [&_a]:text-accent-foreground [&_a]:underline [&_li]:pl-1 [&_ul]:list-disc [&_ul]:space-y-1.5 [&_ul]:pl-6">
|
||||
<div className="text-ink [&_a]:text-accent-foreground space-y-3 leading-relaxed [&_a]:underline [&_li]:pl-1 [&_ul]:list-disc [&_ul]:space-y-1.5 [&_ul]:pl-6">
|
||||
{section.body}
|
||||
</div>
|
||||
</section>
|
||||
|
||||
@@ -1,19 +1,11 @@
|
||||
/**
|
||||
* Single source of truth for the facts the Terms of Service and Privacy
|
||||
* Policy pages state about the operator. Every value marked TODO is a
|
||||
* placeholder — fill it in (and have the documents reviewed by a lawyer)
|
||||
* before launch.
|
||||
* Platform-level facts the Terms of Service and Privacy Policy state. Which
|
||||
* of these a business actually shows is controlled per business in
|
||||
* Manage Business → Policies — see legal-identity.ts.
|
||||
*/
|
||||
export const LEGAL = {
|
||||
productName: "NextChair",
|
||||
/** TODO: registered legal entity name. */
|
||||
legalName: "NextChair [LEGAL ENTITY NAME]",
|
||||
/** TODO: registered address. */
|
||||
address: "[REGISTERED ADDRESS]",
|
||||
/** TODO: monitored inbox for legal and privacy requests. */
|
||||
contactEmail: "legal@nextchair.example",
|
||||
/** TODO: jurisdiction whose law governs the Terms and whose courts hear disputes. */
|
||||
governingLaw: "[COUNTRY]",
|
||||
contactEmail: "info@next-chair.co.uk",
|
||||
/** Bump whenever either document changes materially. */
|
||||
lastUpdated: "17 October 2026",
|
||||
} as const;
|
||||
@@ -0,0 +1,31 @@
|
||||
import { cache } from "react";
|
||||
import { LegalContactEmail, LegalNameDisplay } from "@/generated/prisma/enums";
|
||||
import { prisma } from "@/lib/db/prisma";
|
||||
import { LEGAL } from "./legal-config";
|
||||
import { resolveLegalIdentity, type LegalIdentity } from "./resolve-legal-identity";
|
||||
|
||||
/**
|
||||
* Identity for the public legal pages. Never throws: before installation
|
||||
* (no business row) or with an unreachable database it falls back to the
|
||||
* NextChair defaults, the same way getBranding() does.
|
||||
*/
|
||||
export const getLegalIdentity = cache(async (): Promise<LegalIdentity> => {
|
||||
try {
|
||||
const business = await prisma.business.findFirst({
|
||||
orderBy: { createdAt: "asc" },
|
||||
select: {
|
||||
name: true,
|
||||
email: true,
|
||||
settings: { select: { legalNameDisplay: true, legalContactEmail: true } },
|
||||
},
|
||||
});
|
||||
return resolveLegalIdentity({
|
||||
businessName: business?.name ?? null,
|
||||
businessEmail: business?.email ?? null,
|
||||
nameDisplay: business?.settings?.legalNameDisplay ?? LegalNameDisplay.NEXTCHAIR,
|
||||
contactEmail: business?.settings?.legalContactEmail ?? LegalContactEmail.NEXTCHAIR,
|
||||
});
|
||||
} catch {
|
||||
return { name: LEGAL.productName, email: LEGAL.contactEmail };
|
||||
}
|
||||
});
|
||||
@@ -0,0 +1,33 @@
|
||||
import { LegalContactEmail, LegalNameDisplay } from "@/generated/prisma/enums";
|
||||
import { LEGAL } from "./legal-config";
|
||||
|
||||
export interface LegalIdentity {
|
||||
/** The party named as "we" — NextChair, the business, or both. */
|
||||
name: string;
|
||||
/** Where privacy and legal requests go. */
|
||||
email: string;
|
||||
}
|
||||
|
||||
/** Pure so the Policies form and the pages agree on one set of rules. */
|
||||
export function resolveLegalIdentity(input: {
|
||||
businessName: string | null;
|
||||
businessEmail: string | null;
|
||||
nameDisplay: LegalNameDisplay;
|
||||
contactEmail: LegalContactEmail;
|
||||
}): LegalIdentity {
|
||||
const businessName = input.businessName?.trim() || null;
|
||||
const businessEmail = input.businessEmail?.trim() || null;
|
||||
|
||||
let name: string = LEGAL.productName;
|
||||
if (businessName) {
|
||||
if (input.nameDisplay === LegalNameDisplay.BUSINESS) name = businessName;
|
||||
else if (input.nameDisplay === LegalNameDisplay.BOTH)
|
||||
name = `${LEGAL.productName} and ${businessName}`;
|
||||
}
|
||||
|
||||
const email =
|
||||
input.contactEmail === LegalContactEmail.BUSINESS && businessEmail
|
||||
? businessEmail
|
||||
: LEGAL.contactEmail;
|
||||
return { name, email };
|
||||
}
|
||||
@@ -6,6 +6,7 @@ import { Button } from "@/components/ui/button";
|
||||
import { Input } from "@/components/ui/input";
|
||||
import { Select } from "@/components/ui/select";
|
||||
import { Textarea } from "@/components/ui/textarea";
|
||||
import { LegalContactEmail, LegalNameDisplay } from "@/generated/prisma/enums";
|
||||
import { MediaPicker } from "@/features/media/components/media-picker";
|
||||
import { updateBusinessProfileAction, updateBusinessSettingsAction } from "../actions/business";
|
||||
|
||||
@@ -39,6 +40,8 @@ export interface BusinessDetailsInitial {
|
||||
lateArrivalPolicy: string;
|
||||
bookingTerms: string;
|
||||
privacyPolicyUrl: string;
|
||||
legalNameDisplay: LegalNameDisplay;
|
||||
legalContactEmail: LegalContactEmail;
|
||||
}
|
||||
|
||||
export function BusinessDetailsForm({ initial }: { initial: BusinessDetailsInitial }) {
|
||||
@@ -93,6 +96,8 @@ export function BusinessDetailsForm({ initial }: { initial: BusinessDetailsIniti
|
||||
lateArrivalPolicy: form.lateArrivalPolicy || undefined,
|
||||
bookingTerms: form.bookingTerms || undefined,
|
||||
privacyPolicyUrl: form.privacyPolicyUrl || undefined,
|
||||
legalNameDisplay: form.legalNameDisplay,
|
||||
legalContactEmail: form.legalContactEmail,
|
||||
});
|
||||
if (!settingsResult.success) {
|
||||
setError(settingsResult.error ?? "Couldn't save business policies.");
|
||||
@@ -227,6 +232,41 @@ export function BusinessDetailsForm({ initial }: { initial: BusinessDetailsIniti
|
||||
value={form.privacyPolicyUrl}
|
||||
onChange={(e) => set("privacyPolicyUrl", e.target.value)}
|
||||
/>
|
||||
<div className="border-line space-y-4 border-t pt-4">
|
||||
<p className="text-muted ml-1 text-xs leading-relaxed">
|
||||
Your site's{" "}
|
||||
<a href="/terms" target="_blank" rel="noreferrer" className="underline">
|
||||
Terms of Service
|
||||
</a>{" "}
|
||||
and{" "}
|
||||
<a href="/privacy" target="_blank" rel="noreferrer" className="underline">
|
||||
Privacy Policy
|
||||
</a>{" "}
|
||||
pages say who is responsible and where to send requests. Choose what they show.
|
||||
</p>
|
||||
<Select
|
||||
label="Name shown on Terms & Privacy"
|
||||
value={form.legalNameDisplay}
|
||||
onChange={(e) => set("legalNameDisplay", e.target.value as LegalNameDisplay)}
|
||||
>
|
||||
<option value={LegalNameDisplay.NEXTCHAIR}>NextChair</option>
|
||||
<option value={LegalNameDisplay.BUSINESS}>My business name</option>
|
||||
<option value={LegalNameDisplay.BOTH}>NextChair and my business name</option>
|
||||
</Select>
|
||||
<Select
|
||||
label="Contact email on Terms & Privacy"
|
||||
value={form.legalContactEmail}
|
||||
onChange={(e) => set("legalContactEmail", e.target.value as LegalContactEmail)}
|
||||
>
|
||||
<option value={LegalContactEmail.NEXTCHAIR}>NextChair (info@next-chair.co.uk)</option>
|
||||
<option value={LegalContactEmail.BUSINESS}>My business email</option>
|
||||
</Select>
|
||||
{form.legalContactEmail === LegalContactEmail.BUSINESS && !form.email.trim() && (
|
||||
<p className="text-warning-foreground ml-1 text-xs">
|
||||
Add an Email under Contact & Address — until then the NextChair address is shown.
|
||||
</p>
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
</div>
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
import { Prisma } from "@/generated/prisma/client";
|
||||
import type { PrismaClient } from "@/generated/prisma/client";
|
||||
import { Weekday } from "@/generated/prisma/enums";
|
||||
import type { LegalContactEmail, LegalNameDisplay } from "@/generated/prisma/enums";
|
||||
import { calendarDateOf, minutesFromMidnight, weekdayOf } from "@/lib/time/business-time";
|
||||
import { fail, ok } from "@/lib/validation-result";
|
||||
import type { ValidationResult } from "@/lib/validation-result";
|
||||
@@ -65,6 +66,10 @@ export interface BusinessSettingsInput {
|
||||
lateArrivalPolicy?: string;
|
||||
bookingTerms?: string;
|
||||
privacyPolicyUrl?: string;
|
||||
/** Who the public Terms/Privacy pages name as "we" — see src/features/legal/legal-identity.ts. */
|
||||
legalNameDisplay?: LegalNameDisplay;
|
||||
/** Whose inbox those pages give for legal/privacy requests. */
|
||||
legalContactEmail?: LegalContactEmail;
|
||||
/** Reviews visibility policy — see src/services/reviews. Applied on top of each Review's own isHidden flag. */
|
||||
reviewsMinRating?: number | null;
|
||||
reviewsExcludeRatingOnly?: boolean;
|
||||
|
||||
Reference in new issue
Block a user