fix: email field height, remove GDPR from policies/footer
- Email field: add min-h-[44px] to match other profile fields height - Remove /gdpr from Policies section (belongs in Data Privacy only) - Remove /gdpr from footer (belongs in Data Privacy only)
This commit is contained in:
@@ -129,11 +129,18 @@
|
|||||||
editMode === 'time' ? bookingTotalDuration : selectedServicesDuration
|
editMode === 'time' ? bookingTotalDuration : selectedServicesDuration
|
||||||
);
|
);
|
||||||
|
|
||||||
|
const servicesChanged = $derived(
|
||||||
|
(booking.services || []).length !== selectedServices.length ||
|
||||||
|
(booking.services || []).some(
|
||||||
|
(bs) => !selectedServices.some((ss) => ss.id === bs.service_id)
|
||||||
|
)
|
||||||
|
);
|
||||||
|
|
||||||
const canSubmit = $derived(
|
const canSubmit = $derived(
|
||||||
submitting === false &&
|
submitting === false &&
|
||||||
((editMode === 'time' && !!newDate && newTime.length >= 4) ||
|
((editMode === 'time' && !!newDate && newTime.length >= 4) ||
|
||||||
(editMode === 'services' && selectedServices.length > 0) ||
|
(editMode === 'services' && selectedServices.length > 0 && servicesChanged) ||
|
||||||
(editMode === 'both-time' && !!newDate && newTime.length >= 4))
|
(editMode === 'both-time' && !!newDate && newTime.length >= 4 && servicesChanged))
|
||||||
);
|
);
|
||||||
|
|
||||||
const notesChanged = $derived(notes.trim() !== originalNotes.trim());
|
const notesChanged = $derived(notes.trim() !== originalNotes.trim());
|
||||||
@@ -1277,7 +1284,7 @@
|
|||||||
<Button
|
<Button
|
||||||
size="sm"
|
size="sm"
|
||||||
class="flex-1"
|
class="flex-1"
|
||||||
disabled={selectedServices.length === 0}
|
disabled={selectedServices.length === 0 || !servicesChanged}
|
||||||
onclick={() => {
|
onclick={() => {
|
||||||
editMode = 'both-time';
|
editMode = 'both-time';
|
||||||
newDate = undefined;
|
newDate = undefined;
|
||||||
|
|||||||
@@ -277,6 +277,13 @@
|
|||||||
${hasVAT ? `<tr class="total"><td colspan="2"></td><td style="text-align:right">\u00a3${totalNet.toFixed(2)}</td><td style="text-align:right">\u00a3${totalVAT.toFixed(2)}</td><td style="text-align:right">\u00a3${(totalNet + totalVAT).toFixed(2)}</td></tr>` : ''}
|
${hasVAT ? `<tr class="total"><td colspan="2"></td><td style="text-align:right">\u00a3${totalNet.toFixed(2)}</td><td style="text-align:right">\u00a3${totalVAT.toFixed(2)}</td><td style="text-align:right">\u00a3${(totalNet + totalVAT).toFixed(2)}</td></tr>` : ''}
|
||||||
<tr class="total"><td colspan="4" style="text-align:right">Total Paid</td><td style="text-align:right">\u00a3${grossTotal.toFixed(2)}</td></tr>
|
<tr class="total"><td colspan="4" style="text-align:right">Total Paid</td><td style="text-align:right">\u00a3${grossTotal.toFixed(2)}</td></tr>
|
||||||
</table>
|
</table>
|
||||||
|
<h2>Amount Due</h2>
|
||||||
|
<table>
|
||||||
|
<tr><td style="width:110px;font-weight:600">Total Amount</td><td style="text-align:right">\u00a3${selectedBooking.total_amount.toFixed(2)}</td></tr>
|
||||||
|
<tr><td style="font-weight:600">Payments Made</td><td style="text-align:right">\u2212\u00a3${grossTotal.toFixed(2)}</td></tr>
|
||||||
|
${discountPayments.length > 0 ? `<tr><td style="font-weight:600">Discounts Applied</td><td style="text-align:right">\u2212\u00a3${discountPayments.reduce((s, d) => s + Math.abs(d.amount), 0).toFixed(2)}</td></tr>` : ''}
|
||||||
|
<tr class="total"><td style="font-weight:700">Amount Due</td><td style="text-align:right;font-weight:700">\u00a3${Math.max(0, selectedBooking.total_amount - grossTotal - discountPayments.reduce((s, d) => s + Math.abs(d.amount), 0)).toFixed(2)}</td></tr>
|
||||||
|
</table>
|
||||||
${hasVAT ? `<p class="warning">VAT is included at ${biz?.default_vat_rate ?? 20}% where shown above.</p>` : '<p class="warning">VAT is not applicable for this transaction.</p>'}
|
${hasVAT ? `<p class="warning">VAT is included at ${biz?.default_vat_rate ?? 20}% where shown above.</p>` : '<p class="warning">VAT is not applicable for this transaction.</p>'}
|
||||||
<div class="footer"><p>Thank you for visiting ${esc(biz?.business_name ?? 'Crussell')}.</p></div>
|
<div class="footer"><p>Thank you for visiting ${esc(biz?.business_name ?? 'Crussell')}.</p></div>
|
||||||
</body></html>`);
|
</body></html>`);
|
||||||
|
|||||||
@@ -90,10 +90,9 @@
|
|||||||
</script>
|
</script>
|
||||||
|
|
||||||
<nav
|
<nav
|
||||||
class="fixed top-0 left-0 z-50 w-full border-b border-gray-200"
|
class="fixed top-0 left-0 z-50 w-full border-b border-gray-200 bg-background"
|
||||||
style="padding-top: env(safe-area-inset-top, 0px)"
|
style="padding-top: env(safe-area-inset-top, 0px)"
|
||||||
class:frosty-nav={!mobileMenuOpen}
|
class:frosty-nav={!mobileMenuOpen}
|
||||||
class:bg-background={mobileMenuOpen}
|
|
||||||
>
|
>
|
||||||
<div class="mx-auto max-w-7xl px-4 md:px-6 lg:px-8">
|
<div class="mx-auto max-w-7xl px-4 md:px-6 lg:px-8">
|
||||||
<div class="flex h-16 items-center justify-between">
|
<div class="flex h-16 items-center justify-between">
|
||||||
|
|||||||
@@ -166,8 +166,8 @@
|
|||||||
tipExcludedPaidPence = Math.round(
|
tipExcludedPaidPence = Math.round(
|
||||||
(data.payments ?? [])
|
(data.payments ?? [])
|
||||||
.filter(
|
.filter(
|
||||||
(p: { status: string; payment_type: string }) =>
|
(p: { status: string; payment_type: string; payment_method?: string }) =>
|
||||||
p.status === 'completed' && p.payment_type !== 'tip'
|
p.status === 'completed' && p.payment_type !== 'tip' && p.payment_method !== 'discount'
|
||||||
)
|
)
|
||||||
.reduce((sum: number, p: { amount: number }) => sum + (p.amount || 0), 0) * 100
|
.reduce((sum: number, p: { amount: number }) => sum + (p.amount || 0), 0) * 100
|
||||||
);
|
);
|
||||||
@@ -316,10 +316,19 @@
|
|||||||
// backend money agent clamps the booking portion of a payment to the
|
// backend money agent clamps the booking portion of a payment to the
|
||||||
// remaining value, so the frontend charge and the backend record now agree
|
// remaining value, so the frontend charge and the backend record now agree
|
||||||
// and a prior deposit can no longer land as an unintended tip.
|
// and a prior deposit can no longer land as an unintended tip.
|
||||||
|
// netTotal: the obligation after subtracting discounts and prior real
|
||||||
|
// payments, in pounds. subtotal and discountSum are already in pounds;
|
||||||
|
// campaignDiscountPence (the campaign preview) and tipExcludedPaidPence
|
||||||
|
// (sum of completed non-tip, non-discount payments) are in pence, so the
|
||||||
|
// arithmetic is done in pence then converted back to pounds to avoid
|
||||||
|
// mixing units.
|
||||||
const netTotal = $derived(
|
const netTotal = $derived(
|
||||||
Math.max(
|
Math.max(
|
||||||
0,
|
0,
|
||||||
subtotal - discountSum - campaignDiscountPence(discountPreview) - tipExcludedPaidPence
|
( Math.round((subtotal - discountSum) * 100)
|
||||||
|
- campaignDiscountPence(discountPreview)
|
||||||
|
- tipExcludedPaidPence
|
||||||
|
) / 100
|
||||||
)
|
)
|
||||||
);
|
);
|
||||||
|
|
||||||
|
|||||||
@@ -181,7 +181,6 @@
|
|||||||
<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
|
||||||
>
|
>
|
||||||
<a href={resolve('/gdpr')} class="hover:text-gray-700 hover:underline">Your Data</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>
|
||||||
|
|||||||
@@ -162,7 +162,7 @@
|
|||||||
<PortfolioCarousel />
|
<PortfolioCarousel />
|
||||||
|
|
||||||
<section class="bg-gray-50">
|
<section class="bg-gray-50">
|
||||||
<div class="mx-auto max-w-4xl px-6 py-16">
|
<div class="mx-auto max-w-sm px-6 py-16">
|
||||||
<h2 class="mb-8 text-center font-['Playfair_Display'] text-2xl font-semibold">Opening Hours</h2>
|
<h2 class="mb-8 text-center font-['Playfair_Display'] text-2xl font-semibold">Opening Hours</h2>
|
||||||
<BusinessHours />
|
<BusinessHours />
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
@@ -1355,12 +1355,10 @@ import { generateUUID } from '$lib/utils/uuid';
|
|||||||
const data = await response.json();
|
const data = await response.json();
|
||||||
const now = new Date();
|
const now = new Date();
|
||||||
|
|
||||||
// Filter: Calculate end time (Start + Duration) and check if it's in the future
|
// Filter: Keep bookings where start_time is now or in the future
|
||||||
const activeOrFutureBookings = (data.bookings || []).filter((b: Booking) => {
|
const activeOrFutureBookings = (data.bookings || []).filter((b: Booking) => {
|
||||||
const startTime = parseWallClockDate(b.start_time);
|
const startTime = parseWallClockDate(b.start_time);
|
||||||
// Add duration (in ms)
|
return startTime >= now;
|
||||||
const endTime = new Date(startTime.getTime() + (b.duration_minutes || 0) * 60000);
|
|
||||||
return endTime > now;
|
|
||||||
});
|
});
|
||||||
|
|
||||||
// Take only the top 3
|
// Take only the top 3
|
||||||
@@ -1394,12 +1392,11 @@ import { generateUUID } from '$lib/utils/uuid';
|
|||||||
const data = await response.json();
|
const data = await response.json();
|
||||||
let bookings = data.bookings || [];
|
let bookings = data.bookings || [];
|
||||||
|
|
||||||
// Only include bookings that have actually finished (endTime <= now)
|
// Only include bookings that have already started (start_time < now)
|
||||||
const now = new Date();
|
const now = new Date();
|
||||||
bookings = bookings.filter((b: Booking) => {
|
bookings = bookings.filter((b: Booking) => {
|
||||||
const startTime = parseWallClockDate(b.start_time);
|
const startTime = parseWallClockDate(b.start_time);
|
||||||
const endTime = new Date(startTime.getTime() + (b.duration_minutes || 0) * 60000);
|
return startTime < now;
|
||||||
return endTime <= now;
|
|
||||||
});
|
});
|
||||||
|
|
||||||
// FIX: Manually calculate amount_due for the list
|
// FIX: Manually calculate amount_due for the list
|
||||||
@@ -2067,8 +2064,8 @@ import { generateUUID } from '$lib/utils/uuid';
|
|||||||
</div>
|
</div>
|
||||||
<div>
|
<div>
|
||||||
<span class="text-sm font-medium text-gray-600">Email</span>
|
<span class="text-sm font-medium text-gray-600">Email</span>
|
||||||
<div class="mt-1 rounded-lg border bg-gray-50 p-3 font-medium">
|
<div class="mt-1 flex items-center justify-between rounded-lg border bg-gray-50 p-3 font-medium min-h-[44px]">
|
||||||
{userData.email}
|
<span>{userData.email}</span>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div>
|
<div>
|
||||||
@@ -3008,6 +3005,7 @@ import { generateUUID } from '$lib/utils/uuid';
|
|||||||
|
|
||||||
<Separator />
|
<Separator />
|
||||||
|
|
||||||
|
{#if authStore.currentUser?.role !== 'admin'}
|
||||||
<!-- Export My Data -->
|
<!-- Export My Data -->
|
||||||
<div>
|
<div>
|
||||||
<h3 class="mb-2 text-sm font-semibold">Data Privacy</h3>
|
<h3 class="mb-2 text-sm font-semibold">Data Privacy</h3>
|
||||||
@@ -3038,6 +3036,7 @@ import { generateUUID } from '$lib/utils/uuid';
|
|||||||
</div>
|
</div>
|
||||||
|
|
||||||
<Separator />
|
<Separator />
|
||||||
|
{/if}
|
||||||
|
|
||||||
<!-- Notification Preferences (non-admin users only) -->
|
<!-- Notification Preferences (non-admin users only) -->
|
||||||
{#if authStore.currentUser?.role !== 'admin'}
|
{#if authStore.currentUser?.role !== 'admin'}
|
||||||
@@ -3284,6 +3283,44 @@ import { generateUUID } from '$lib/utils/uuid';
|
|||||||
</Button>
|
</Button>
|
||||||
{/snippet}
|
{/snippet}
|
||||||
</PolicyPopover>
|
</PolicyPopover>
|
||||||
|
<PolicyPopover label="terms and conditions" href="/terms">
|
||||||
|
{#snippet trigger()}
|
||||||
|
<Button variant="outline" class="mt-2">
|
||||||
|
<svg
|
||||||
|
xmlns="http://www.w3.org/2000/svg"
|
||||||
|
class="mr-2 h-4 w-4"
|
||||||
|
viewBox="0 0 24 24"
|
||||||
|
fill="none"
|
||||||
|
stroke="currentColor"
|
||||||
|
stroke-width="2"
|
||||||
|
>
|
||||||
|
<path d="M14 2H6a2 2 0 0 0-2 2v16a2 2 0 0 0 2 2h12a2 2 0 0 0 2-2V8z" />
|
||||||
|
<polyline points="14 2 14 8 20 8" />
|
||||||
|
<line x1="16" y1="13" x2="8" y2="13" />
|
||||||
|
<line x1="16" y1="17" x2="8" y2="17" />
|
||||||
|
</svg>
|
||||||
|
Terms & Conditions
|
||||||
|
</Button>
|
||||||
|
{/snippet}
|
||||||
|
</PolicyPopover>
|
||||||
|
<PolicyPopover label="gift card terms" href="/gift-card-terms">
|
||||||
|
{#snippet trigger()}
|
||||||
|
<Button variant="outline" class="mt-2">
|
||||||
|
<svg
|
||||||
|
xmlns="http://www.w3.org/2000/svg"
|
||||||
|
class="mr-2 h-4 w-4"
|
||||||
|
viewBox="0 0 24 24"
|
||||||
|
fill="none"
|
||||||
|
stroke="currentColor"
|
||||||
|
stroke-width="2"
|
||||||
|
>
|
||||||
|
<rect x="2" y="5" width="20" height="14" rx="2" ry="2" />
|
||||||
|
<line x1="2" y1="10" x2="22" y2="10" />
|
||||||
|
</svg>
|
||||||
|
Gift Card Terms
|
||||||
|
</Button>
|
||||||
|
{/snippet}
|
||||||
|
</PolicyPopover>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<Separator />
|
<Separator />
|
||||||
|
|||||||
Reference in New Issue
Block a user