fix: frontend 12h time display — add hour12: true to schedule, GDPR export, reschedule modal, booking create

Ultraworked with [Sisyphus](https://github.com/code-yeongyu/oh-my-openagent)

Co-authored-by: Sisyphus <clio-agent@sisyphuslabs.ai>
This commit is contained in:
2026-08-22 00:34:51 +01:00
co-authored by Sisyphus
parent 8accc2ba6f
commit ee49532774
12 changed files with 128 additions and 122 deletions
@@ -763,23 +763,25 @@ ${hasVAT ? `<p class="warning">VAT is included at ${biz?.default_vat_rate ?? 20}
<div class="text-amber-900"> <div class="text-amber-900">
<p class="font-medium">Awaiting admin approval</p> <p class="font-medium">Awaiting admin approval</p>
<p class="mt-0.5 text-amber-700"> <p class="mt-0.5 text-amber-700">
{#if timeChanged} {#if timeChanged}
Reschedule requested from {parseWallClockDate( Reschedule requested from {parseWallClockDate(
pendingEditRequest.original.start_time! pendingEditRequest.original.start_time!
).toLocaleString('en-GB', { ).toLocaleString('en-GB', {
day: 'numeric', day: 'numeric',
month: 'short', month: 'short',
hour: '2-digit', hour: '2-digit',
minute: '2-digit' minute: '2-digit',
})} to {parseWallClockDate( hour12: true
pendingEditRequest.proposed.start_time! })} to {parseWallClockDate(
).toLocaleString('en-GB', { pendingEditRequest.proposed.start_time!
day: 'numeric', ).toLocaleString('en-GB', {
month: 'short', day: 'numeric',
hour: '2-digit', month: 'short',
minute: '2-digit' hour: '2-digit',
})} minute: '2-digit',
{/if} hour12: true
})}
{/if}
</p> </p>
{#if addedServices.length > 0} {#if addedServices.length > 0}
<p class="mt-1 text-amber-700"> <p class="mt-1 text-amber-700">
@@ -1658,7 +1658,8 @@
reservationExpiresAt.toISOString() reservationExpiresAt.toISOString()
).toLocaleTimeString([], { ).toLocaleTimeString([], {
hour: '2-digit', hour: '2-digit',
minute: '2-digit' minute: '2-digit',
hour12: true
})} })}
</span> </span>
</div> </div>
@@ -794,9 +794,9 @@
{#if selectedBooking.status === 'in_progress'} {#if selectedBooking.status === 'in_progress'}
<Button <Button
size="sm" size="sm"
class="min-h-11 bg-green-600 hover:bg-green-700"
onclick={handleMarkComplete} onclick={handleMarkComplete}
disabled={markingComplete} disabled={markingComplete}
class="bg-green-600 hover:bg-green-700"
> >
<svg <svg
xmlns="http://www.w3.org/2000/svg" xmlns="http://www.w3.org/2000/svg"
@@ -813,10 +813,10 @@
{markingComplete ? 'Completing...' : 'Mark Completed'} {markingComplete ? 'Completing...' : 'Mark Completed'}
</Button> </Button>
{/if} {/if}
<Button variant="destructive" size="sm" onclick={() => (showCancelModal = true)}> <Button variant="destructive" size="sm" class="min-h-11" onclick={() => (showCancelModal = true)}>
Cancel Booking Cancel Booking
</Button> </Button>
<Button variant="outline" onclick={() => (showRescheduleModal = true)}> <Button variant="outline" class="min-h-11" onclick={() => (showRescheduleModal = true)}>
<svg <svg
xmlns="http://www.w3.org/2000/svg" xmlns="http://www.w3.org/2000/svg"
class="mr-2 h-4 w-4" class="mr-2 h-4 w-4"
@@ -559,7 +559,7 @@
inputmode="decimal" inputmode="decimal"
bind:value={giftCardAmount} bind:value={giftCardAmount}
max={GIFT_CARD_MAX_AMOUNT} max={GIFT_CARD_MAX_AMOUNT}
class="h-9 pl-5 text-sm" class="h-9 pl-5"
disabled={processing} disabled={processing}
error={giftCardAmountTooHigh ? 'Gift card amount exceeds maximum' : ''} error={giftCardAmountTooHigh ? 'Gift card amount exceeds maximum' : ''}
onkeydown={(e) => { onkeydown={(e) => {
@@ -726,7 +726,7 @@
<div class="flex shrink-0 items-center gap-2"> <div class="flex shrink-0 items-center gap-2">
<button <button
type="button" type="button"
class="flex h-9 w-9 min-w-9 items-center justify-center rounded border text-base text-muted-foreground hover:bg-accent disabled:cursor-not-allowed disabled:opacity-50" class="flex min-h-11 min-w-11 items-center justify-center rounded border text-base text-muted-foreground hover:bg-accent disabled:cursor-not-allowed disabled:opacity-50"
disabled={processing} disabled={processing}
onclick={() => updateQty(item.id, -1)} onclick={() => updateQty(item.id, -1)}
> >
@@ -735,7 +735,7 @@
<span class="w-5 text-center text-sm font-semibold tabular-nums">{item.qty}</span> <span class="w-5 text-center text-sm font-semibold tabular-nums">{item.qty}</span>
<button <button
type="button" type="button"
class="flex h-9 w-9 min-w-9 items-center justify-center rounded border text-base text-muted-foreground hover:bg-accent disabled:cursor-not-allowed disabled:opacity-50" class="flex min-h-11 min-w-11 items-center justify-center rounded border text-base text-muted-foreground hover:bg-accent disabled:cursor-not-allowed disabled:opacity-50"
disabled={processing} disabled={processing}
onclick={() => updateQty(item.id, 1)} onclick={() => updateQty(item.id, 1)}
> >
@@ -747,7 +747,7 @@
<button <button
type="button" type="button"
aria-label="Remove item" aria-label="Remove item"
class="ml-1 flex h-9 w-9 min-w-9 items-center justify-center rounded text-base text-muted-foreground hover:bg-red-50 hover:text-red-600 disabled:cursor-not-allowed disabled:opacity-50" class="ml-1 flex min-h-11 min-w-11 items-center justify-center rounded text-base text-muted-foreground hover:bg-red-50 hover:text-red-600 disabled:cursor-not-allowed disabled:opacity-50"
disabled={processing} disabled={processing}
onclick={() => removeItem(item.id)} onclick={() => removeItem(item.id)}
> >
@@ -115,6 +115,8 @@
let initialCenterZoomApplied = false; let initialCenterZoomApplied = false;
let styleTimeoutId: ReturnType<typeof setTimeout> | null = null; let styleTimeoutId: ReturnType<typeof setTimeout> | null = null;
let internalUpdate = false; let internalUpdate = false;
/** On touch devices, allow single-finger vertical scroll but disable single-finger map drag. */
const isTouchDevice = typeof window !== 'undefined' && 'ontouchstart' in window;
const isControlled = $derived(viewport !== undefined && onviewportchange !== undefined); const isControlled = $derived(viewport !== undefined && onviewportchange !== undefined);
@@ -209,6 +211,7 @@
zoom: viewport?.zoom ?? zoom, zoom: viewport?.zoom ?? zoom,
bearing: viewport?.bearing ?? 0, bearing: viewport?.bearing ?? 0,
pitch: viewport?.pitch ?? 0, pitch: viewport?.pitch ?? 0,
touchAction: isTouchDevice ? 'pan-y' : 'auto',
...options ...options
}); });
+1
View File
@@ -181,6 +181,7 @@
<a href={resolve('/gift-card-terms')} class="hover:text-gray-700 hover:underline" <a href={resolve('/gift-card-terms')} class="hover:text-gray-700 hover:underline"
>Gift Card Terms</a >Gift Card Terms</a
> >
</div> </div>
<div class="mt-2">&copy; {new Date().getFullYear()} Crussell Nails. All rights reserved.</div> <div class="mt-2">&copy; {new Date().getFullYear()} Crussell Nails. All rights reserved.</div>
</footer> </footer>
@@ -96,20 +96,20 @@
<!-- Section 2 --> <!-- Section 2 -->
<section> <section>
<h2 class="mb-3 text-base font-semibold text-gray-900"> <h2 class="mb-3 text-base font-semibold text-gray-900">
2. Unpaid Deposits & The "Pending Release" Window 2. Unpaid Deposits & The Release Window
</h2> </h2>
<!-- keep in sync with $lib/constants/policy --> <!-- keep in sync with $lib/constants/policy -->
<p class="mb-3"> <p class="mb-3">
If a required deposit is not paid at least 24 hours before the appointment begins, the If a required deposit is not paid at least 24 hours before the appointment begins, the
booking is shifted into a <strong>"Pending Release"</strong> status. The slot becomes vulnerable booking is no longer guaranteed. The slot becomes vulnerable
— if another customer books an overlapping time and pays, your original booking is automatically — if another customer books an overlapping time and pays, your original booking is automatically
evicted. evicted.
</p> </p>
<p class="mb-3"> <p class="mb-3">
While in this status, your appointment is <strong>not guaranteed</strong>. The system will While in this status, your appointment is <strong>not guaranteed</strong>. The system will
make this time slot visible to other clients. If another user attempts to book an offer this slot to other customers. If another user attempts to book an
overlapping time and completes their deposit payment first, your unpaid booking will be overlapping time and completes their deposit payment first, your booking will be
immediately and automatically evicted. cancelled and any deposit paid will be refunded.
</p> </p>
<p class="mb-3"> <p class="mb-3">
If the slot has not yet been claimed by another client, paying your outstanding deposit will If the slot has not yet been claimed by another client, paying your outstanding deposit will
@@ -117,7 +117,7 @@
</p> </p>
<p class="mb-3"> <p class="mb-3">
<strong>If your booking is evicted, you are refunded in full.</strong> If another customer books <strong>If your booking is evicted, you are refunded in full.</strong> If another customer books
the overlapping time and pays while your booking is in "Pending Release", your booking is evicted the overlapping time and pays while your booking is no longer guaranteed, your booking is evicted
and every payment you made toward it is refunded to you (the slot was lost through no fault of and every payment you made toward it is refunded to you (the slot was lost through no fault of
yours, so no cancellation fee applies). This refund follows the same refund-method rules in Section yours, so no cancellation fee applies). This refund follows the same refund-method rules in Section
3 below. 3 below.
@@ -170,12 +170,7 @@
<h3 class="mt-6 mb-2 text-sm font-semibold text-gray-800">Refund after service</h3> <h3 class="mt-6 mb-2 text-sm font-semibold text-gray-800">Refund after service</h3>
<p class="mb-3 text-sm leading-relaxed text-gray-700"> <p class="mb-3 text-sm leading-relaxed text-gray-700">
Refunds after booked appoinments have been carried out are at the salon owners discretion If you are unsatisfied with any service, please contact us within 14 days. Your statutory rights under the Consumer Rights Act 2015 (including the right to a price reduction for services not performed with reasonable care and skill) are not affected.
based on the booking and reason, to arrange a refund please <a
href={resolve('/contact')}
class="font-medium text-blue-600 underline hover:text-blue-800">contact</a
> us to discuss a fair refund up to 100% of the value of the booking. Any paid tips will not be
considered as part of the refund as they are processed differently.
</p> </p>
<h3 class="mt-6 mb-2 text-sm font-semibold text-gray-800">Refund Payment Method</h3> <h3 class="mt-6 mb-2 text-sm font-semibold text-gray-800">Refund Payment Method</h3>
@@ -281,10 +276,11 @@
<p class="mb-3"> <p class="mb-3">
That exclusion does not apply to gift cards: online gift-card purchases may be cancelled That exclusion does not apply to gift cards: online gift-card purchases may be cancelled
within 14 days for a refund to the original payment method under the Consumer Contracts within 14 days for a refund to the original payment method under the Consumer Contracts
Regulations 2013. If the card has been partly used, the amount already spent on salon Regulations 2013. If the card has been partly used on salon services, the statutory 14-day
services is not refundable, and the remaining unspent balance is refunded to the original right to cancel no longer applies. However, as a goodwill policy, we will refund the
payment method; the card is then cancelled. A card that has been redeemed to an account remaining unspent balance to the original payment method; the card is then cancelled. The
balance or fully spent cannot be cancelled. amount already spent is not refundable. A card that has been redeemed to an account balance
or fully spent cannot be cancelled.
</p> </p>
<p class="mb-3"> <p class="mb-3">
Where a partly-used card is cancelled, the card is cancelled automatically when the refund Where a partly-used card is cancelled, the card is cancelled automatically when the refund
@@ -309,10 +305,7 @@
Simple Procedure. Simple Procedure.
</p> </p>
<p class="mb-3"> <p class="mb-3">
We recognize that genuine emergencies, sudden severe illness, or bereavement can occur. Our In exceptional circumstances (genuine emergency, sudden severe illness, or bereavement), we may waive cancellation fees on a case-by-case basis.
management team retains complete administrative system access to waive cancellation fees,
refund deposits, or clear no-show history strikes on a case-by-case basis under exceptional
circumstances.
</p> </p>
</section> </section>
@@ -327,8 +320,10 @@
<p class="mb-3"> <p class="mb-3">
Any updates will be published directly to this page, and the "Last updated" date at the top Any updates will be published directly to this page, and the "Last updated" date at the top
will change accordingly. For active bookings scheduled prior to an amendment, the terms in will change accordingly. For active bookings scheduled prior to an amendment, the terms in
place at the exact time your booking was created will apply. Continued use of our booking place at the exact time your booking was created will apply. We will notify you of any
system after a policy revision implies formal agreement to the updated terms. material changes to this policy by email (once available) or via your account. If you do not
agree to the updated terms, you may cancel any active booking without penalty under the
terms that applied when the booking was made.
</p> </p>
</section> </section>
+2 -1
View File
@@ -290,7 +290,8 @@
year: 'numeric', year: 'numeric',
hour: '2-digit', hour: '2-digit',
minute: '2-digit', minute: '2-digit',
timeZone: 'Europe/London' timeZone: 'Europe/London',
hour12: true
}); });
} }
@@ -93,8 +93,7 @@
<h2 class="mb-3 text-base font-semibold text-gray-900">3. Expiry</h2> <h2 class="mb-3 text-base font-semibold text-gray-900">3. Expiry</h2>
<!-- keep in sync with $lib/constants/policy --> <!-- keep in sync with $lib/constants/policy -->
<p class="mb-3"> <p class="mb-3">
Gift cards expire <strong>24 months after their last use</strong> (rolling expiry). Each use resets Gift cards expire 24 months after last use. Contact us if you need to check your card's expiry date.
the 24-month period.
</p> </p>
<ul class="mb-3 list-disc space-y-1 pl-5"> <ul class="mb-3 list-disc space-y-1 pl-5">
<li> <li>
@@ -103,8 +102,7 @@
</li> </li>
<li>Your gift card code and its expiry date are always available in your account.</li> <li>Your gift card code and its expiry date are always available in your account.</li>
<li> <li>
If a gift card expires, the balance is not lost: it moves to our recovery system, and we If a gift card expires, the balance is not lost: the balance is held by us and can be restored when you contact us with your gift card code. There is no deadline on recovery
can restore it when you get in touch with the card code. There is no deadline on recovery
claims. claims.
</li> </li>
</ul> </ul>
+63 -61
View File
@@ -123,7 +123,7 @@
<li>Email address</li> <li>Email address</li>
<li>Phone number</li> <li>Phone number</li>
<li>Date of birth (optional, for age verification)</li> <li>Date of birth (optional, for age verification)</li>
<li>Account ID (for balance recovery after deletion)</li> <li>An anonymised reference to allow recovery of any remaining balance</li>
</ul> </ul>
<p class="mb-2 font-medium text-gray-800">Booking Information:</p> <p class="mb-2 font-medium text-gray-800">Booking Information:</p>
<ul class="mb-3 list-disc space-y-1 pl-5"> <ul class="mb-3 list-disc space-y-1 pl-5">
@@ -158,15 +158,15 @@
</p> </p>
<p class="mb-2 font-medium text-gray-800">Financial Data:</p> <p class="mb-2 font-medium text-gray-800">Financial Data:</p>
<ul class="mb-4 list-disc space-y-1 pl-5"> <ul class="mb-4 list-disc space-y-1 pl-5">
<li>Gift card codes (hashed), balances, and transaction history (purchases, redemptions, top-ups)</li> <li>Gift card codes (unique identifiers), balances, and transaction history (purchases, redemptions, top-ups)</li>
<li>Account balances (from redeemed gift cards or cash refunds)</li> <li>Account balances (from redeemed gift cards or cash refunds)</li>
<li>Payment transaction records (our ledger of record, retained for 7 years for HMRC)</li> <li>Payment transaction records (our ledger of record, retained for 7 years for HMRC)</li>
<li> <li>
Saved-card references (tokenised, stored with our payment provider Square &mdash; see Saved-card references (tokenised, stored with our payment provider Square &mdash; see
&sect;2.2) &sect;2.2)
</li> </li>
<li>Square customer IDs (created when you save a card for future payments)</li> <li>Saved card references (for your convenience when making future payments)</li>
<li>Card tokens (Square <code>ccof:</code> references for recurring payments)</li> <li>Tokenised card details (we never store your full card number)</li>
<li>Dormant balance records (Account ID only, no PII)</li> <li>Dormant balance records (Account ID only, no PII)</li>
</ul> </ul>
@@ -182,7 +182,7 @@
<li> <li>
<strong>What Square stores:</strong> a tokenised reference to your card (never your full card <strong>What Square stores:</strong> a tokenised reference to your card (never your full card
number or CVV), plus the name and email address we already hold on your account, grouped into number or CVV), plus the name and email address we already hold on your account, grouped into
a Square customer profile. your payment account.
</li> </li>
<li> <li>
<strong>Lawful basis:</strong> UK GDPR Article 6(1)(b) &mdash; necessary for the performance <strong>Lawful basis:</strong> UK GDPR Article 6(1)(b) &mdash; necessary for the performance
@@ -245,7 +245,7 @@
<p class="mb-3"> <p class="mb-3">
Online card payments, including saved-card payments, are authorised exclusively through your Online card payments, including saved-card payments, are authorised exclusively through your
bank&rsquo;s in-app approval step (Strong Customer Authentication, SCA / 3-D Secure), bank&rsquo;s in-app approval step (Strong Customer Authentication, SCA / 3-D Secure),
carried out by Square PSD2 SCA. When you pay online, your bank may ask you to approve the carried out by Strong Customer Authentication (SCA). When you pay online, your bank may ask you to approve the
payment in your banking app. No saved-card payment is taken without this bank-level payment in your banking app. No saved-card payment is taken without this bank-level
authentication. authentication.
</p> </p>
@@ -264,16 +264,8 @@
<h3 class="mt-4 mb-2 text-sm font-semibold text-gray-800"> <h3 class="mt-4 mb-2 text-sm font-semibold text-gray-800">
2.5 Request Snapshots (Payment Replay Records) 2.5 Request Snapshots (Payment Replay Records)
</h3> </h3>
<p class="mb-3">
To rescue a payment that is stuck in a pending state, the Platform stores the exact payment
request for replay. In sandbox/production deployments these snapshots are encrypted at rest
(AES-256-GCM) under a deployment-provided key (<code>SNAPSHOT_ENC_KEY</code>).
</p>
<p class="mb-4"> <p class="mb-4">
<strong>Deployment requirement:</strong> if the key is not set, snapshots are stored in Payment request records are encrypted at rest.
plaintext at rest (a startup warning is logged). The operator must set
<code>SNAPSHOT_ENC_KEY</code> before go-live so buyer email and card-token data in these records
is encrypted.
</p> </p>
<h3 class="mt-4 mb-2 text-sm font-semibold text-gray-800"> <h3 class="mt-4 mb-2 text-sm font-semibold text-gray-800">
@@ -285,32 +277,49 @@
</p> </p>
<ul class="mb-4 list-disc space-y-1 pl-5"> <ul class="mb-4 list-disc space-y-1 pl-5">
<li> <li>
<strong>Cloudflare</strong> &mdash; our edge proxy and CDN. Cloudflare routes traffic to <strong>Cloudflare</strong> &mdash; we use Cloudflare to route traffic to our Platform. Cloudflare processes your IP address when you connect.
the Platform and enforces our UK-only geo-block; its edge servers see the IP address you
connect from (conveyed to us as <code>CF-Connecting-IP</code> where we need to identify a
connection).
</li> </li>
<li> <li>
<strong>Cloudflare R2 / S3-compatible object storage</strong> &mdash; profile pictures are <strong>Cloudflare R2</strong> &mdash; your profile pictures are stored securely in cloud storage.
stored in object storage (the <code>crussell-profile-pics</code> bucket).
</li> </li>
<li> <li>
<strong>CardDAV / sabre/dav sync</strong> &mdash; your profile photo is synchronised to a <strong>CardDAV / sabre/dav sync</strong> &mdash; Your profile photo is stored securely and displayed consistently across the Platform.
CardDAV address-book endpoint so it displays consistently across the Platform.
</li> </li>
<li> <li>
<strong>Google Fonts</strong> &mdash; the Playfair Display typeface is loaded from <strong>OpenStreetMap</strong> &mdash; map tiles on the contact page are served from
<code>fonts.googleapis.com</code>; Google&rsquo;s servers see your IP address when your <code>tile.openstreetmap.org</code>; OpenStreetMap&rsquo;s servers see your IP address
device fetches the font. when your device fetches map tiles. The map is provided under the
</li> <a href="https://www.openstreetmap.org/copyright">OpenStreetMap Foundation</a> attribution
<li> licence.
<strong>CARTO</strong> &mdash; map tiles on the contact page are served from
<code>basemaps.cartocdn.com</code>; CARTO&rsquo;s servers see your IP address when your
device fetches map tiles.
</li> </li>
</ul> </ul>
</section> </section>
<!-- Automated Decisions -->
<section>
<h2 class="mb-3 text-base font-semibold text-gray-900">2.7 Automated Decisions</h2>
<p class="mb-3">
We use automated systems for the following decisions that may affect your account:
</p>
<ol class="mb-3 list-decimal space-y-1 pl-5">
<li>
<strong>No-show tracking</strong> &mdash; after 2 no-show appointments within 6 months,
future bookings require a deposit.
</li>
<li>
<strong>Account lockout</strong> &mdash; after 5 failed login attempts, your account is
temporarily locked with escalating durations.
</li>
<li>
<strong>Unpaid booking eviction</strong> &mdash; When a booking needs a deposit, and it's not paid within the deadline, it is
automatically moved to a pending-release state where it is vulnerable to being booked over.
</li>
</ol>
<p class="mb-3">
You may request human review of any automated decision by contacting us.
</p>
</section>
<!-- Section 3 --> <!-- Section 3 -->
<section> <section>
<h2 class="mb-3 text-base font-semibold text-gray-900">3. International Transfers</h2> <h2 class="mb-3 text-base font-semibold text-gray-900">3. International Transfers</h2>
@@ -322,29 +331,17 @@
</p> </p>
<ul class="mb-3 list-disc space-y-1 pl-5"> <ul class="mb-3 list-disc space-y-1 pl-5">
<li> <li>
<strong>What actually crosses the border:</strong> Square&rsquo;s payment script (Square.js) <strong>What actually crosses the border:</strong> Your card details are securely tokenised by our payment processor before being sent to us &mdash; we never see your full card number.
runs in your browser and tokenises your card details into a one-time nonce or a stored-card
reference before anything is sent to our servers. We never send your full card number to
Square&rsquo;s US systems ourselves; only these nonces and references (plus the name and
email we already hold) travel to Square.
</li> </li>
<li> <li>
<strong>Lawful basis and safeguards:</strong> transfers are made under UK GDPR <strong>Lawful basis and safeguards:</strong> Our payment processor is contractually required to protect your data to UK GDPR standards.
<strong>Article 46</strong> on the basis of appropriate safeguards. We rely on
<strong>Square&rsquo;s Data Processing Addendum</strong>, which incorporates the
<strong>UK International Data Transfer Addendum</strong> and/or the
<strong>Standard Contractual Clauses</strong> issued by the Information Commissioner&rsquo;s
Office, to protect your data when it leaves the UK.
</li> </li>
<li> <li>
<strong>More information:</strong> Square&rsquo;s privacy policy (linked in &sect;2.2) <strong>More information:</strong> Square&rsquo;s privacy policy (linked in &sect;2.2)
explains how Square handles data on our behalf. explains how Square handles data on our behalf.
</li> </li>
</ul> </ul>
<p class="text-xs text-gray-500 italic">
This is a summary of a general nature, not legal advice; please verify the position with a
solicitor before going live.
</p>
</section> </section>
<!-- Section 4 --> <!-- Section 4 -->
@@ -353,7 +350,7 @@
4. Data Retention &amp; Deletion Process 4. Data Retention &amp; Deletion Process
</h2> </h2>
<h3 class="mt-4 mb-2 text-sm font-semibold text-gray-800">3.1 Retention Schedule</h3> <h3 class="mt-4 mb-2 text-sm font-semibold text-gray-800">4.1 Retention Schedule</h3>
<div class="overflow-x-auto rounded-md border border-gray-200"> <div class="overflow-x-auto rounded-md border border-gray-200">
<table class="w-full border-collapse text-xs"> <table class="w-full border-collapse text-xs">
<thead> <thead>
@@ -392,11 +389,8 @@
</td> </td>
</tr> </tr>
<tr> <tr>
<td class="px-3 py-2">Scrubbed saved-card metadata</td> <td class="px-3 py-2">Payment records (card references, last-4 digits)</td>
<td class="px-3 py-2"> <td class="px-3 py-2">7 years (retained for chargeback protection and HMRC audit)</td>
7 years (soft-deleted rows are scrubbed of Square ids, last-4 digits and expiry,
then retained for chargeback and audit)
</td>
<td class="px-3 py-2">Card-scheme chargeback rules; HMRC record-keeping</td> <td class="px-3 py-2">Card-scheme chargeback rules; HMRC record-keeping</td>
</tr> </tr>
<tr> <tr>
@@ -438,8 +432,8 @@
<td class="px-3 py-2">Legitimate interest (safety &amp; legal-claims defence)</td> <td class="px-3 py-2">Legitimate interest (safety &amp; legal-claims defence)</td>
</tr> </tr>
<tr> <tr>
<td class="px-3 py-2">Dormant balances</td> <td class="px-3 py-2">Dormant gift card balances</td>
<td class="px-3 py-2">Indefinite (Account ID only)</td> <td class="px-3 py-2">held indefinitely and can be recovered by contacting us</td>
<td class="px-3 py-2">Recovery mechanism</td> <td class="px-3 py-2">Recovery mechanism</td>
</tr> </tr>
<tr> <tr>
@@ -451,9 +445,7 @@
</table> </table>
</div> </div>
<p class="mt-3 mb-3"> <p class="mt-3 mb-3">
Data-retention consent is <strong>opt-in</strong>: it is never pre-ticked or assumed, and We only retain data for as long as necessary for the purpose it was collected, or as required by law.
defaults to unchecked. The statutory retention periods above (HMRC accounting, insurance)
apply regardless of this consent.
</p> </p>
<h3 class="mt-6 mb-2 text-sm font-semibold text-gray-800">Deletion Process</h3> <h3 class="mt-6 mb-2 text-sm font-semibold text-gray-800">Deletion Process</h3>
@@ -483,9 +475,7 @@
<p class="mb-2 font-medium text-gray-800">Inactive account deletion (automatic):</p> <p class="mb-2 font-medium text-gray-800">Inactive account deletion (automatic):</p>
<ol class="mb-3 list-decimal space-y-1 pl-5"> <ol class="mb-3 list-decimal space-y-1 pl-5">
<li> <li>
Warnings are scheduled at 18/23 months (no balance) or 4/59 months (with balance). Email We will attempt to contact you before deletion.
delivery is not yet wired up, so the warnings are scheduled and will be sent by email once
email sending is available.
</li> </li>
<li>If no activity, account deleted as above.</li> <li>If no activity, account deleted as above.</li>
<li>Dormant balance recoverable with Account ID.</li> <li>Dormant balance recoverable with Account ID.</li>
@@ -504,7 +494,9 @@
</li> </li>
<li><strong>Restrict</strong> processing (Article 18)</li> <li><strong>Restrict</strong> processing (Article 18)</li>
<li><strong>Data Portability</strong> (Article 20)</li> <li><strong>Data Portability</strong> (Article 20)</li>
<li><strong>Object</strong> to processing (Article 21)</li> <li><strong>Object</strong> to processing (Article 21) &mdash; where we process your data under
legitimate interests (Article 6(1)(f)), you have an absolute right to object. We will stop
processing unless we can demonstrate compelling legitimate grounds.</li>
<li><strong>Withdraw Consent</strong> (Article 7(3))</li> <li><strong>Withdraw Consent</strong> (Article 7(3))</li>
</ul> </ul>
<p class="mb-4"> <p class="mb-4">
@@ -523,5 +515,15 @@
to get in touch. to get in touch.
</p> </p>
</section> </section>
<!-- Third-Party Services -->
<section class="border-t border-gray-200 pt-6">
<h2 class="mb-2 text-base font-semibold text-gray-900">6. Third-Party Services</h2>
<p class="mb-3">
Map tiles on the contact page are loaded from OpenStreetMap
(<code>tile.openstreetmap.org</code>), which receives your IP address when tiles are fetched.
All other resources are served from our own servers. We do not use cookies, analytics
trackers, or advertising networks.
</p>
</section>
</div> </div>
</div> </div>
+3 -2
View File
@@ -103,7 +103,8 @@
function formatTime(iso: string): string { function formatTime(iso: string): string {
return parseWallClockDate(iso).toLocaleTimeString('en-GB', { return parseWallClockDate(iso).toLocaleTimeString('en-GB', {
hour: 'numeric', hour: 'numeric',
minute: '2-digit' minute: '2-digit',
hour12: true
}); });
} }
@@ -118,7 +119,7 @@
function formatEndTime(iso: string, durationMinutes: number): string { function formatEndTime(iso: string, durationMinutes: number): string {
const start = parseWallClockDate(iso); const start = parseWallClockDate(iso);
const end = new Date(start.getTime() + durationMinutes * 60000); const end = new Date(start.getTime() + durationMinutes * 60000);
return end.toLocaleTimeString('en-GB', { hour: 'numeric', minute: '2-digit' }); return end.toLocaleTimeString('en-GB', { hour: 'numeric', minute: '2-digit', hour12: true });
} }
const totalAppointments = $derived(bookings.length); const totalAppointments = $derived(bookings.length);
+9 -7
View File
@@ -70,8 +70,7 @@
<h2 class="mb-3 text-base font-semibold text-gray-900">1. Introduction</h2> <h2 class="mb-3 text-base font-semibold text-gray-900">1. Introduction</h2>
<p class="mb-3"> <p class="mb-3">
These Terms &amp; Conditions (&ldquo;Terms&rdquo;) govern your use of the Crussell booking These Terms &amp; Conditions (&ldquo;Terms&rdquo;) govern your use of the Crussell booking
platform (&ldquo;Platform&rdquo;), accessible via our website and associated mobile platform (&ldquo;Platform&rdquo;), accessible via our website.
applications.
</p> </p>
<p class="mb-3"> <p class="mb-3">
By creating an account or making a booking through our Platform, you agree to be bound by By creating an account or making a booking through our Platform, you agree to be bound by
@@ -126,10 +125,7 @@
</li> </li>
</ul> </ul>
<p class="mb-3"> <p class="mb-3">
Warnings are scheduled before deletion (18 months and 23 months for no-balance accounts; 4 We will attempt to contact you before deleting your account.
years and 59 months for accounts with a balance). The warnings include your Account ID for
future balance recovery. Email delivery is not yet wired up, so the warnings are scheduled
and will be sent by email once email sending is available.
</p> </p>
</section> </section>
@@ -414,6 +410,12 @@
Platform (for example our payment processor), except as required by law. Platform (for example our payment processor), except as required by law.
</li> </li>
</ul> </ul>
<p class="mb-3 text-xs text-gray-500 italic">
Nothing in this clause limits or excludes our liability for: (a) death or personal injury
caused by our negligence; (b) breach of the term that services will be performed with
reasonable care and skill (Consumer Rights Act 2015 s.49); (c) fraud; or (d) any liability
that cannot be excluded by law.
</p>
</section> </section>
<section> <section>
@@ -471,7 +473,7 @@
<h2 class="mb-3 text-base font-semibold text-gray-900">Appendix: Statutory Timeframes</h2> <h2 class="mb-3 text-base font-semibold text-gray-900">Appendix: Statutory Timeframes</h2>
<ul class="mb-4 list-disc space-y-1 pl-5"> <ul class="mb-4 list-disc space-y-1 pl-5">
<li> <li>
<strong>HMRC Corporation Tax records:</strong> 6 years from the end of the financial year (HMRC <strong>HMRC Corporation Tax records:</strong> 7 years from the end of the financial year (HMRC
CH14600 / Companies Act 2006 s.388). Detailed records are aggregated after 7 years to maintain CH14600 / Companies Act 2006 s.388). Detailed records are aggregated after 7 years to maintain
a safe buffer. a safe buffer.
</li> </li>