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">
|
<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
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|||||||
@@ -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">© {new Date().getFullYear()} Crussell Nails. All rights reserved.</div>
|
<div class="mt-2">© {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>
|
||||||
|
|
||||||
|
|||||||
@@ -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>
|
||||||
|
|||||||
@@ -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 — see
|
Saved-card references (tokenised, stored with our payment provider Square — see
|
||||||
§2.2)
|
§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) — necessary for the performance
|
<strong>Lawful basis:</strong> UK GDPR Article 6(1)(b) — 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’s in-app approval step (Strong Customer Authentication, SCA / 3-D Secure),
|
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
|
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> — our edge proxy and CDN. Cloudflare routes traffic to
|
<strong>Cloudflare</strong> — 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> — profile pictures are
|
<strong>Cloudflare R2</strong> — 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> — your profile photo is synchronised to a
|
<strong>CardDAV / sabre/dav sync</strong> — 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> — the Playfair Display typeface is loaded from
|
<strong>OpenStreetMap</strong> — map tiles on the contact page are served from
|
||||||
<code>fonts.googleapis.com</code>; Google’s servers see your IP address when your
|
<code>tile.openstreetmap.org</code>; OpenStreetMap’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> — 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.
|
|
||||||
</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> — 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 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’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 — 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’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’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.
|
|
||||||
</li>
|
</li>
|
||||||
<li>
|
<li>
|
||||||
<strong>More information:</strong> Square’s privacy policy (linked in §2.2)
|
<strong>More information:</strong> Square’s privacy policy (linked in §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 & Deletion Process
|
4. Data Retention & 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 & legal-claims defence)</td>
|
<td class="px-3 py-2">Legitimate interest (safety & 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) — 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>
|
||||||
|
|||||||
@@ -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);
|
||||||
|
|||||||
@@ -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 & Conditions (“Terms”) govern your use of the Crussell booking
|
These Terms & Conditions (“Terms”) govern your use of the Crussell booking
|
||||||
platform (“Platform”), accessible via our website and associated mobile
|
platform (“Platform”), 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>
|
||||||
|
|||||||
Reference in New Issue
Block a user