diff --git a/frontend/src/routes/account/+page.svelte b/frontend/src/routes/account/+page.svelte index 5ff5054..4452ad7 100644 --- a/frontend/src/routes/account/+page.svelte +++ b/frontend/src/routes/account/+page.svelte @@ -4,6 +4,7 @@ import { SvelteDate } from 'svelte/reactivity'; import { browser } from '$app/environment'; import { toast } from 'svelte-sonner'; + import { sanitizeText } from '$lib/utils/toast-safe'; import UserBookingModal from '$lib/components/account/UserBookingModal.svelte'; import { isValidUKPhone, formatPhoneDisplay, toE164UK } from '$lib/utils/phone'; import { savedCardsStore, type SavedCard } from '$lib/stores/savedCards.svelte'; @@ -33,6 +34,7 @@ import { Separator } from '$lib/components/ui/separator'; import * as AlertDialog from '$lib/components/ui/alert-dialog'; import { Skeleton } from '$lib/components/ui/skeleton'; + import PolicyPopover from '$lib/components/ui/policyPopover.svelte'; import * as Dialog from '$lib/components/ui/dialog'; import Cropper from 'svelte-easy-crop'; @@ -778,7 +780,7 @@ await fetchUserData(); } else { const text = await response.text(); - toast.error(text || 'Failed to update phone number', { id: loadingToast }); + toast.error(sanitizeText(text) || 'Failed to update phone number', { id: loadingToast }); } } catch (err) { console.error('Error updating phone:', err); @@ -846,7 +848,7 @@ if (!response.ok) { const text = await response.text(); - toast.error('Failed to load upcoming bookings: ' + text); + toast.error('Failed to load upcoming bookings: ' + sanitizeText(text)); return; } @@ -888,7 +890,7 @@ if (!response.ok) { const text = await response.text(); - toast.error('Failed to load past bookings: ' + text); + toast.error('Failed to load past bookings: ' + sanitizeText(text)); return; } @@ -1003,7 +1005,7 @@ passwordData = { current: '', new: '', confirm: '' }; } else { const text = await response.text(); - toast.error(text || 'Failed to change password', { id: loadingToast }); + toast.error(sanitizeText(text) || 'Failed to change password', { id: loadingToast }); } } catch (err) { console.error('Error changing password:', err); @@ -1051,7 +1053,7 @@ goto('/'); } else { const text = await response.text(); - toast.error(text || 'Failed to delete account', { id: loadingToast }); + toast.error(sanitizeText(text) || 'Failed to delete account', { id: loadingToast }); } } catch (err) { console.error('Error deleting account:', err); @@ -2303,6 +2305,23 @@ +
+

Policies

+

+ View our cancellation, deposit, and no-show policies +

+ + {#snippet trigger()} + + {/snippet} + +
+ + + {#if authStore.currentUser?.role !== 'admin'}
diff --git a/frontend/src/routes/admin/+page.svelte b/frontend/src/routes/admin/+page.svelte index 7083d05..5bb4ef3 100644 --- a/frontend/src/routes/admin/+page.svelte +++ b/frontend/src/routes/admin/+page.svelte @@ -292,12 +292,7 @@
- + diff --git a/frontend/src/routes/admin/notifications/+page.svelte b/frontend/src/routes/admin/notifications/+page.svelte index 4c44c7c..3e78aba 100644 --- a/frontend/src/routes/admin/notifications/+page.svelte +++ b/frontend/src/routes/admin/notifications/+page.svelte @@ -76,6 +76,11 @@ let showEditRequestModal = $state(false); let selectedEditRequest = $state(null); + function openBookingModal(bookingId: string) { + selectedBooking = { id: bookingId }; + showBookingModal = true; + } + let pageState = $state<'loading' | 'authorized' | 'unauthorized'>('loading'); $effect(() => { @@ -94,12 +99,12 @@ const reasonLabels: Record = { pending_booking: 'Booking Pending Approval', edit_request: 'Customer Requested Booking Change', - edit_requested: 'Booking Edit Requested', + edit_requested: 'Booking Edit/Reschedule Requested', new_booking: 'New Booking Received', cancelled_booking: 'Booking Cancelled', late_cancellation: 'Late Cancellation (< 24h)', - no_deposit: 'Deposit Issue', deposit_paid: 'Deposit Payment Received', + deposit_not_paid_by_deadline: 'Deposit Deadline Passed', affiliate_claim: 'Affiliate Referral Claimed', '1_month_no_pay': 'No Payments in 1 Month', '1_week_no_pay': 'No Payments in 1 Week' @@ -115,7 +120,7 @@ case 'edit_requested': return 'edit_approve'; case 'late_cancellation': - case 'no_deposit': + case 'deposit_not_paid_by_deadline': case '1_week_no_pay': case '1_month_no_pay': return 'see_user'; @@ -198,13 +203,13 @@ selectedEditRequest = data.edit_request; showEditRequestModal = true; } else if (response.status === 404) { - toast.error('This edit request has already been processed'); + toast.error('This edit/reschedule request has already been processed'); } else { - toast.error('Could not load edit request details'); + toast.error('Could not load edit/reschedule request details'); } } catch (err) { console.error('Error fetching edit request:', err); - toast.error('Network error loading edit request'); + toast.error('Network error loading edit/reschedule request'); } } else if (action === 'see_user' && notification.user_id) { selectedUserId = notification.user_id; @@ -504,7 +509,7 @@ - + {#if showEditRequestModal && selectedEditRequest} {:else} +
{service.service_name}
- {service.override_duration_minutes ?? service.duration_minutes} mins + {service.duration_minutes} mins
- {formatPounds(service.override_price ?? service.price)} + {formatPounds(service.price)}
{/each} diff --git a/frontend/src/routes/gdpr/+page.svelte b/frontend/src/routes/gdpr/+page.svelte index 088ffe3..bf707ed 100644 --- a/frontend/src/routes/gdpr/+page.svelte +++ b/frontend/src/routes/gdpr/+page.svelte @@ -29,6 +29,8 @@ data_consent_updated_at: string; created_at: string; updated_at: string; + failed_attempts?: number; + locked_until?: string; }; bookings: Array<{ booking_id: string; @@ -159,6 +161,18 @@ booking_id: string; created_at: string; }>; + login_audit?: Array<{ + attempt_type: string; + ip_address: string; + success: boolean; + created_at: string; + }>; + refresh_tokens?: Array<{ + role: string; + revoked: boolean; + created_at: string; + expires_at: string; + }>; export_metadata?: { exported_at: string; exported_by: string; @@ -538,9 +552,9 @@ - {#if computeBookingStats(gdprData)} - {@const stats = computeBookingStats(gdprData)} -

Summary

+ {#if computeBookingStats(gdprData)} + {@const stats = computeBookingStats(gdprData)!} +

Summary

Member Since @@ -678,6 +692,18 @@ Referral Code

{gdprData.user_profile.referral_code || '—'}

+ {#if gdprData.user_profile.failed_attempts != null && gdprData.user_profile.failed_attempts > 0} +
+ Failed Login Attempts +

{gdprData.user_profile.failed_attempts}

+
+ {/if} + {#if gdprData.user_profile.locked_until} +
+ Account Locked Until +

{fmtDateTime(gdprData.user_profile.locked_until)}

+
+ {/if}
Privacy Policy Consent

@@ -885,6 +911,76 @@ {/if} + + {#if gdprData.login_audit && gdprData.login_audit.length > 0} + + Login Activity + +

+ + + + {#each gdprData.login_audit as la (la.created_at)} + + + + + + {/each} + +
DateTypeSuccess
{fmtDateTime(la.created_at)}{la.attempt_type.replace(/_/g, ' ')} + {la.success ? 'Yes' : 'No'} +
+
+ + + {/if} + + + {#if gdprData.refresh_tokens && gdprData.refresh_tokens.length > 0} + + Session Tokens + +
+ + + + {#each gdprData.refresh_tokens as rt (rt.created_at)} + + + + + + {/each} + +
IssuedExpiresStatus
{fmtDateTime(rt.created_at)}{fmtDateTime(rt.expires_at)} + {rt.revoked ? 'Revoked' : 'Active'} +
+
+
+
+ {/if} + {#if gdprData.patch_tests && gdprData.patch_tests.length > 0} @@ -1240,7 +1336,7 @@