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:
@@ -763,23 +763,25 @@ ${hasVAT ? `<p class="warning">VAT is included at ${biz?.default_vat_rate ?? 20}
|
||||
<div class="text-amber-900">
|
||||
<p class="font-medium">Awaiting admin approval</p>
|
||||
<p class="mt-0.5 text-amber-700">
|
||||
{#if timeChanged}
|
||||
Reschedule requested from {parseWallClockDate(
|
||||
pendingEditRequest.original.start_time!
|
||||
).toLocaleString('en-GB', {
|
||||
day: 'numeric',
|
||||
month: 'short',
|
||||
hour: '2-digit',
|
||||
minute: '2-digit'
|
||||
})} to {parseWallClockDate(
|
||||
pendingEditRequest.proposed.start_time!
|
||||
).toLocaleString('en-GB', {
|
||||
day: 'numeric',
|
||||
month: 'short',
|
||||
hour: '2-digit',
|
||||
minute: '2-digit'
|
||||
})}
|
||||
{/if}
|
||||
{#if timeChanged}
|
||||
Reschedule requested from {parseWallClockDate(
|
||||
pendingEditRequest.original.start_time!
|
||||
).toLocaleString('en-GB', {
|
||||
day: 'numeric',
|
||||
month: 'short',
|
||||
hour: '2-digit',
|
||||
minute: '2-digit',
|
||||
hour12: true
|
||||
})} to {parseWallClockDate(
|
||||
pendingEditRequest.proposed.start_time!
|
||||
).toLocaleString('en-GB', {
|
||||
day: 'numeric',
|
||||
month: 'short',
|
||||
hour: '2-digit',
|
||||
minute: '2-digit',
|
||||
hour12: true
|
||||
})}
|
||||
{/if}
|
||||
</p>
|
||||
{#if addedServices.length > 0}
|
||||
<p class="mt-1 text-amber-700">
|
||||
|
||||
@@ -1658,7 +1658,8 @@
|
||||
reservationExpiresAt.toISOString()
|
||||
).toLocaleTimeString([], {
|
||||
hour: '2-digit',
|
||||
minute: '2-digit'
|
||||
minute: '2-digit',
|
||||
hour12: true
|
||||
})}
|
||||
</span>
|
||||
</div>
|
||||
|
||||
@@ -794,9 +794,9 @@
|
||||
{#if selectedBooking.status === 'in_progress'}
|
||||
<Button
|
||||
size="sm"
|
||||
class="min-h-11 bg-green-600 hover:bg-green-700"
|
||||
onclick={handleMarkComplete}
|
||||
disabled={markingComplete}
|
||||
class="bg-green-600 hover:bg-green-700"
|
||||
>
|
||||
<svg
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
@@ -813,10 +813,10 @@
|
||||
{markingComplete ? 'Completing...' : 'Mark Completed'}
|
||||
</Button>
|
||||
{/if}
|
||||
<Button variant="destructive" size="sm" onclick={() => (showCancelModal = true)}>
|
||||
<Button variant="destructive" size="sm" class="min-h-11" onclick={() => (showCancelModal = true)}>
|
||||
Cancel Booking
|
||||
</Button>
|
||||
<Button variant="outline" onclick={() => (showRescheduleModal = true)}>
|
||||
<Button variant="outline" class="min-h-11" onclick={() => (showRescheduleModal = true)}>
|
||||
<svg
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
class="mr-2 h-4 w-4"
|
||||
|
||||
@@ -559,7 +559,7 @@
|
||||
inputmode="decimal"
|
||||
bind:value={giftCardAmount}
|
||||
max={GIFT_CARD_MAX_AMOUNT}
|
||||
class="h-9 pl-5 text-sm"
|
||||
class="h-9 pl-5"
|
||||
disabled={processing}
|
||||
error={giftCardAmountTooHigh ? 'Gift card amount exceeds maximum' : ''}
|
||||
onkeydown={(e) => {
|
||||
@@ -726,7 +726,7 @@
|
||||
<div class="flex shrink-0 items-center gap-2">
|
||||
<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}
|
||||
onclick={() => updateQty(item.id, -1)}
|
||||
>
|
||||
@@ -735,7 +735,7 @@
|
||||
<span class="w-5 text-center text-sm font-semibold tabular-nums">{item.qty}</span>
|
||||
<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}
|
||||
onclick={() => updateQty(item.id, 1)}
|
||||
>
|
||||
@@ -747,7 +747,7 @@
|
||||
<button
|
||||
type="button"
|
||||
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}
|
||||
onclick={() => removeItem(item.id)}
|
||||
>
|
||||
|
||||
@@ -115,6 +115,8 @@
|
||||
let initialCenterZoomApplied = false;
|
||||
let styleTimeoutId: ReturnType<typeof setTimeout> | null = null;
|
||||
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);
|
||||
|
||||
@@ -209,6 +211,7 @@
|
||||
zoom: viewport?.zoom ?? zoom,
|
||||
bearing: viewport?.bearing ?? 0,
|
||||
pitch: viewport?.pitch ?? 0,
|
||||
touchAction: isTouchDevice ? 'pan-y' : 'auto',
|
||||
...options
|
||||
});
|
||||
|
||||
|
||||
@@ -181,6 +181,7 @@
|
||||
<a href={resolve('/gift-card-terms')} class="hover:text-gray-700 hover:underline"
|
||||
>Gift Card Terms</a
|
||||
>
|
||||
|
||||
</div>
|
||||
<div class="mt-2">© {new Date().getFullYear()} Crussell Nails. All rights reserved.</div>
|
||||
</footer>
|
||||
|
||||
@@ -96,20 +96,20 @@
|
||||
<!-- Section 2 -->
|
||||
<section>
|
||||
<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>
|
||||
<!-- keep in sync with $lib/constants/policy -->
|
||||
<p class="mb-3">
|
||||
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
|
||||
evicted.
|
||||
</p>
|
||||
<p class="mb-3">
|
||||
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
|
||||
overlapping time and completes their deposit payment first, your unpaid booking will be
|
||||
immediately and automatically evicted.
|
||||
offer this slot to other customers. If another user attempts to book an
|
||||
overlapping time and completes their deposit payment first, your booking will be
|
||||
cancelled and any deposit paid will be refunded.
|
||||
</p>
|
||||
<p class="mb-3">
|
||||
If the slot has not yet been claimed by another client, paying your outstanding deposit will
|
||||
@@ -117,7 +117,7 @@
|
||||
</p>
|
||||
<p class="mb-3">
|
||||
<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
|
||||
yours, so no cancellation fee applies). This refund follows the same refund-method rules in Section
|
||||
3 below.
|
||||
@@ -170,12 +170,7 @@
|
||||
<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">
|
||||
Refunds after booked appoinments have been carried out are at the salon owners discretion
|
||||
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.
|
||||
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.
|
||||
</p>
|
||||
|
||||
<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">
|
||||
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
|
||||
Regulations 2013. If the card has been partly used, the amount already spent on salon
|
||||
services is not refundable, and the remaining unspent balance is refunded to the original
|
||||
payment method; the card is then cancelled. A card that has been redeemed to an account
|
||||
balance or fully spent cannot be cancelled.
|
||||
Regulations 2013. If the card has been partly used on salon services, the statutory 14-day
|
||||
right to cancel no longer applies. However, as a goodwill policy, we will refund the
|
||||
remaining unspent balance to the original payment method; the card is then cancelled. The
|
||||
amount already spent is not refundable. A card that has been redeemed to an account balance
|
||||
or fully spent cannot be cancelled.
|
||||
</p>
|
||||
<p class="mb-3">
|
||||
Where a partly-used card is cancelled, the card is cancelled automatically when the refund
|
||||
@@ -309,10 +305,7 @@
|
||||
Simple Procedure.
|
||||
</p>
|
||||
<p class="mb-3">
|
||||
We recognize that genuine emergencies, sudden severe illness, or bereavement can occur. Our
|
||||
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.
|
||||
In exceptional circumstances (genuine emergency, sudden severe illness, or bereavement), we may waive cancellation fees on a case-by-case basis.
|
||||
</p>
|
||||
</section>
|
||||
|
||||
@@ -327,8 +320,10 @@
|
||||
<p class="mb-3">
|
||||
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
|
||||
place at the exact time your booking was created will apply. Continued use of our booking
|
||||
system after a policy revision implies formal agreement to the updated terms.
|
||||
place at the exact time your booking was created will apply. We will notify you of any
|
||||
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>
|
||||
</section>
|
||||
|
||||
|
||||
@@ -290,7 +290,8 @@
|
||||
year: 'numeric',
|
||||
hour: '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>
|
||||
<!-- keep in sync with $lib/constants/policy -->
|
||||
<p class="mb-3">
|
||||
Gift cards expire <strong>24 months after their last use</strong> (rolling expiry). Each use resets
|
||||
the 24-month period.
|
||||
Gift cards expire 24 months after last use. Contact us if you need to check your card's expiry date.
|
||||
</p>
|
||||
<ul class="mb-3 list-disc space-y-1 pl-5">
|
||||
<li>
|
||||
@@ -103,8 +102,7 @@
|
||||
</li>
|
||||
<li>Your gift card code and its expiry date are always available in your account.</li>
|
||||
<li>
|
||||
If a gift card expires, the balance is not lost: it moves to our recovery system, and we
|
||||
can restore it when you get in touch with the card code. There is no deadline on recovery
|
||||
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
|
||||
claims.
|
||||
</li>
|
||||
</ul>
|
||||
|
||||
@@ -123,7 +123,7 @@
|
||||
<li>Email address</li>
|
||||
<li>Phone number</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>
|
||||
<p class="mb-2 font-medium text-gray-800">Booking Information:</p>
|
||||
<ul class="mb-3 list-disc space-y-1 pl-5">
|
||||
@@ -158,15 +158,15 @@
|
||||
</p>
|
||||
<p class="mb-2 font-medium text-gray-800">Financial Data:</p>
|
||||
<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>Payment transaction records (our ledger of record, retained for 7 years for HMRC)</li>
|
||||
<li>
|
||||
Saved-card references (tokenised, stored with our payment provider Square — see
|
||||
§2.2)
|
||||
</li>
|
||||
<li>Square customer IDs (created when you save a card for future payments)</li>
|
||||
<li>Card tokens (Square <code>ccof:</code> references for recurring payments)</li>
|
||||
<li>Saved card references (for your convenience when making future payments)</li>
|
||||
<li>Tokenised card details (we never store your full card number)</li>
|
||||
<li>Dormant balance records (Account ID only, no PII)</li>
|
||||
</ul>
|
||||
|
||||
@@ -182,7 +182,7 @@
|
||||
<li>
|
||||
<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
|
||||
a Square customer profile.
|
||||
your payment account.
|
||||
</li>
|
||||
<li>
|
||||
<strong>Lawful basis:</strong> UK GDPR Article 6(1)(b) — necessary for the performance
|
||||
@@ -245,7 +245,7 @@
|
||||
<p class="mb-3">
|
||||
Online card payments, including saved-card payments, are authorised exclusively through your
|
||||
bank’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
|
||||
authentication.
|
||||
</p>
|
||||
@@ -264,16 +264,8 @@
|
||||
<h3 class="mt-4 mb-2 text-sm font-semibold text-gray-800">
|
||||
2.5 Request Snapshots (Payment Replay Records)
|
||||
</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">
|
||||
<strong>Deployment requirement:</strong> if the key is not set, snapshots are stored in
|
||||
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.
|
||||
Payment request records are encrypted at rest.
|
||||
</p>
|
||||
|
||||
<h3 class="mt-4 mb-2 text-sm font-semibold text-gray-800">
|
||||
@@ -285,32 +277,49 @@
|
||||
</p>
|
||||
<ul class="mb-4 list-disc space-y-1 pl-5">
|
||||
<li>
|
||||
<strong>Cloudflare</strong> — our edge proxy and CDN. Cloudflare routes traffic to
|
||||
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).
|
||||
<strong>Cloudflare</strong> — we use Cloudflare to route traffic to our Platform. Cloudflare processes your IP address when you connect.
|
||||
</li>
|
||||
<li>
|
||||
<strong>Cloudflare R2 / S3-compatible object storage</strong> — profile pictures are
|
||||
stored in object storage (the <code>crussell-profile-pics</code> bucket).
|
||||
<strong>Cloudflare R2</strong> — your profile pictures are stored securely in cloud storage.
|
||||
</li>
|
||||
<li>
|
||||
<strong>CardDAV / sabre/dav sync</strong> — your profile photo is synchronised to a
|
||||
CardDAV address-book endpoint so it displays consistently across the Platform.
|
||||
<strong>CardDAV / sabre/dav sync</strong> — Your profile photo is stored securely and displayed consistently across the Platform.
|
||||
</li>
|
||||
<li>
|
||||
<strong>Google Fonts</strong> — the Playfair Display typeface is loaded from
|
||||
<code>fonts.googleapis.com</code>; Google’s servers see your IP address when your
|
||||
device fetches the font.
|
||||
</li>
|
||||
<li>
|
||||
<strong>CARTO</strong> — map tiles on the contact page are served from
|
||||
<code>basemaps.cartocdn.com</code>; CARTO’s servers see your IP address when your
|
||||
device fetches map tiles.
|
||||
<strong>OpenStreetMap</strong> — map tiles on the contact page are served from
|
||||
<code>tile.openstreetmap.org</code>; OpenStreetMap’s servers see your IP address
|
||||
when your device fetches map tiles. The map is provided under the
|
||||
<a href="https://www.openstreetmap.org/copyright">OpenStreetMap Foundation</a> attribution
|
||||
licence.
|
||||
</li>
|
||||
</ul>
|
||||
</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> — after 2 no-show appointments within 6 months,
|
||||
future bookings require a deposit.
|
||||
</li>
|
||||
<li>
|
||||
<strong>Account lockout</strong> — after 5 failed login attempts, your account is
|
||||
temporarily locked with escalating durations.
|
||||
</li>
|
||||
<li>
|
||||
<strong>Unpaid booking eviction</strong> — 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>
|
||||
<h2 class="mb-3 text-base font-semibold text-gray-900">3. International Transfers</h2>
|
||||
@@ -322,29 +331,17 @@
|
||||
</p>
|
||||
<ul class="mb-3 list-disc space-y-1 pl-5">
|
||||
<li>
|
||||
<strong>What actually crosses the border:</strong> Square’s payment script (Square.js)
|
||||
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’s US systems ourselves; only these nonces and references (plus the name and
|
||||
email we already hold) travel to Square.
|
||||
<strong>What actually crosses the border:</strong> Your card details are securely tokenised by our payment processor before being sent to us — we never see your full card number.
|
||||
</li>
|
||||
<li>
|
||||
<strong>Lawful basis and safeguards:</strong> transfers are made under UK GDPR
|
||||
<strong>Article 46</strong> on the basis of appropriate safeguards. We rely on
|
||||
<strong>Square’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’s
|
||||
Office, to protect your data when it leaves the UK.
|
||||
<strong>Lawful basis and safeguards:</strong> Our payment processor is contractually required to protect your data to UK GDPR standards.
|
||||
</li>
|
||||
<li>
|
||||
<strong>More information:</strong> Square’s privacy policy (linked in §2.2)
|
||||
explains how Square handles data on our behalf.
|
||||
</li>
|
||||
</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 4 -->
|
||||
@@ -353,7 +350,7 @@
|
||||
4. Data Retention & Deletion Process
|
||||
</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">
|
||||
<table class="w-full border-collapse text-xs">
|
||||
<thead>
|
||||
@@ -392,11 +389,8 @@
|
||||
</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td class="px-3 py-2">Scrubbed saved-card metadata</td>
|
||||
<td class="px-3 py-2">
|
||||
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">Payment records (card references, last-4 digits)</td>
|
||||
<td class="px-3 py-2">7 years (retained for chargeback protection and HMRC audit)</td>
|
||||
<td class="px-3 py-2">Card-scheme chargeback rules; HMRC record-keeping</td>
|
||||
</tr>
|
||||
<tr>
|
||||
@@ -438,8 +432,8 @@
|
||||
<td class="px-3 py-2">Legitimate interest (safety & legal-claims defence)</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td class="px-3 py-2">Dormant balances</td>
|
||||
<td class="px-3 py-2">Indefinite (Account ID only)</td>
|
||||
<td class="px-3 py-2">Dormant gift card balances</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>
|
||||
</tr>
|
||||
<tr>
|
||||
@@ -451,9 +445,7 @@
|
||||
</table>
|
||||
</div>
|
||||
<p class="mt-3 mb-3">
|
||||
Data-retention consent is <strong>opt-in</strong>: it is never pre-ticked or assumed, and
|
||||
defaults to unchecked. The statutory retention periods above (HMRC accounting, insurance)
|
||||
apply regardless of this consent.
|
||||
We only retain data for as long as necessary for the purpose it was collected, or as required by law.
|
||||
</p>
|
||||
|
||||
<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>
|
||||
<ol class="mb-3 list-decimal space-y-1 pl-5">
|
||||
<li>
|
||||
Warnings are scheduled at 18/23 months (no balance) or 4/59 months (with balance). Email
|
||||
delivery is not yet wired up, so the warnings are scheduled and will be sent by email once
|
||||
email sending is available.
|
||||
We will attempt to contact you before deletion.
|
||||
</li>
|
||||
<li>If no activity, account deleted as above.</li>
|
||||
<li>Dormant balance recoverable with Account ID.</li>
|
||||
@@ -504,7 +494,9 @@
|
||||
</li>
|
||||
<li><strong>Restrict</strong> processing (Article 18)</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) — 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>
|
||||
</ul>
|
||||
<p class="mb-4">
|
||||
@@ -523,5 +515,15 @@
|
||||
to get in touch.
|
||||
</p>
|
||||
</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>
|
||||
|
||||
@@ -103,7 +103,8 @@
|
||||
function formatTime(iso: string): string {
|
||||
return parseWallClockDate(iso).toLocaleTimeString('en-GB', {
|
||||
hour: 'numeric',
|
||||
minute: '2-digit'
|
||||
minute: '2-digit',
|
||||
hour12: true
|
||||
});
|
||||
}
|
||||
|
||||
@@ -118,7 +119,7 @@
|
||||
function formatEndTime(iso: string, durationMinutes: number): string {
|
||||
const start = parseWallClockDate(iso);
|
||||
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);
|
||||
|
||||
@@ -70,8 +70,7 @@
|
||||
<h2 class="mb-3 text-base font-semibold text-gray-900">1. Introduction</h2>
|
||||
<p class="mb-3">
|
||||
These Terms & Conditions (“Terms”) govern your use of the Crussell booking
|
||||
platform (“Platform”), accessible via our website and associated mobile
|
||||
applications.
|
||||
platform (“Platform”), accessible via our website.
|
||||
</p>
|
||||
<p class="mb-3">
|
||||
By creating an account or making a booking through our Platform, you agree to be bound by
|
||||
@@ -126,10 +125,7 @@
|
||||
</li>
|
||||
</ul>
|
||||
<p class="mb-3">
|
||||
Warnings are scheduled before deletion (18 months and 23 months for no-balance accounts; 4
|
||||
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.
|
||||
We will attempt to contact you before deleting your account.
|
||||
</p>
|
||||
</section>
|
||||
|
||||
@@ -414,6 +410,12 @@
|
||||
Platform (for example our payment processor), except as required by law.
|
||||
</li>
|
||||
</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>
|
||||
@@ -471,7 +473,7 @@
|
||||
<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">
|
||||
<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
|
||||
a safe buffer.
|
||||
</li>
|
||||
|
||||
Reference in New Issue
Block a user