feat: 10 quick wins — account deletion, health check, debug cleanup, UX polish, graceful shutdown

- backend/handlers/user/account.go: Wire DELETE /api/user/account to call
  anonymize_user() for registered users and delete_guest_user() for guests,
  with CardDAV contact cleanup
- backend/handlers/user/profile_test.go: Add TestAccount_DeleteGuest and
  enhance TestAccount_Delete to verify anonymization results
- backend/main.go: Add GET /api/health endpoint with DB ping and S3 status
  check; add HSTS and Referrer-Policy security headers; replace
  http.ListenAndServe with http.Server + graceful SIGTERM/SIGINT shutdown
- frontend/routes/+layout.svelte: Replace alert() with toast notifications
  for email verification flow
- frontend/routes/login/+page.svelte: Replace alert() with toast.info for
  social login prototype buttons
- frontend/booking/BookingFlow.svelte: Remove 2 console.log debug calls;
  add cancellation policy note in Step 3; add timezone policy comment
- frontend/ImageUpload.svelte: Comment out debug console.log
- init-scripts/init-script.sql: Add delete_guest_user() SQL function
- docs: Update README.md and Obsidian notes to reflect completed items
This commit is contained in:
2026-05-01 11:33:27 +01:00
parent 7b396b7a9d
commit bff86a6660
11 changed files with 673 additions and 435 deletions
@@ -440,13 +440,14 @@
fd.append('tags', tags.join(','));
}
// Debug: log what we're sending
/* Debug: log what we're sending
console.log(`Uploading ${fileKey}:`, {
baseName,
thumbName,
thumbMimeType: thumbBlob.type,
fullMimeType: resizedBlob.type
});
*/
/* -------- 5. Call the API ------------------------------------- */
uploadStatus[fileKey] = 'Uploading...';
const response = await fetch('/api/portfolio/images', {
@@ -5,6 +5,11 @@
import { Label } from '$lib/components/ui/label/index.js';
import { Textarea } from '$lib/components/ui/textarea/index.js';
import { Separator } from '$lib/components/ui/separator/index.js';
// INTENTIONAL: We use the browser's local timezone (getLocalTimeZone) because Crussell is a UK-only
// salon app. All customers are physically in the UK and book UK appointment slots. We do NOT
// auto-adjust for international timezones — the slot time shown is the actual UK salon time.
// Cloudflare geo-blocking prevents non-UK access. BST/GMT transitions are handled manually by
// staff adjusting working hours; the app does not need timezone-aware scheduling logic.
import { CalendarDate, getLocalTimeZone, type DateValue } from '@internationalized/date';
import { authStore } from '$lib/stores/auth.svelte';
import { toast } from 'svelte-sonner';
@@ -856,8 +861,6 @@
requestBody.user_id = guestUserId;
}
console.log('Submitting booking:', requestBody);
const headers: Record<string, string> = { 'Content-Type': 'application/json' };
if (authStore.currentToken) {
headers['Authorization'] = `Bearer ${authStore.currentToken}`;
@@ -871,8 +874,7 @@
if (response.ok) {
const booking = await response.json();
console.log('Booking created:', booking);
// Show success message with booking details
const bookingDateStr = new Date(booking.start_time).toLocaleDateString('en-GB', {
weekday: 'long',
@@ -1186,6 +1188,10 @@
By booking, you agree to our Terms & Conditions and Privacy Policy. We'll send you
appointment reminders via email and/or SMS.
</p>
<p class="mt-2 text-xs text-gray-500">
<strong>Cancellation Policy:</strong> Free cancellation up to 24 hours before your
appointment. Cancellations within 24 hours may incur a deposit penalty.
</p>
</div>
</Card.Content>
<Card.Footer class="flex justify-between">
+4 -2
View File
@@ -3,6 +3,7 @@
import favicon from '$lib/assets/favicon.svg';
import NavBar from '$lib/components/layout/NavBar.svelte';
import { Toaster } from '$lib/components/ui/sonner/index.js';
import { toast } from 'svelte-sonner';
import { authStore } from '$lib/stores/auth.svelte';
import { onMount } from 'svelte';
@@ -30,15 +31,16 @@
// Verify email address
async function verify_email() {
alert('Verifying email address...');
const loadingToast = toast.loading('Verifying email address...');
const response = await fetch('/api/verify-email', {
method: 'POST'
});
if (response.ok) {
toast.success('Email verified successfully!', { id: loadingToast });
window.location.reload();
} else {
alert('Failed to verify email address');
toast.error('Failed to verify email address', { id: loadingToast });
}
}
</script>
+1 -1
View File
@@ -253,7 +253,7 @@
}
function handleSocialLogin(provider: string) {
alert(`${provider} login clicked! (This is just a prototype)`);
toast.info(`${provider} login coming soon`);
}
// when password changes, re-compute strength