feat(frontend): add former name display, referral discount type, and refunds to bookings
Update frontend types, stores, and components to support name history display and new API fields. - Add previousFirstName/previousLastName to User type and BookingUser type - Add referral discount_source type and refunds array to Booking type - Create nameDisplay.ts utility for rendering '(formerly ...)' labels - Create booking.ts utility for booking-related helpers - Update 25+ admin, payments, today, and account components to display former names on booking cards, modals, appointment views, and user lists Ultraworked with [Sisyphus](https://github.com/code-yeongyu/oh-my-openagent) Co-authored-by: Sisyphus <clio-agent@sisyphuslabs.ai>
This commit is contained in:
@@ -3,6 +3,7 @@
|
||||
import { SvelteDate } from 'svelte/reactivity';
|
||||
import { toast } from 'svelte-sonner';
|
||||
import { formatDuration } from '$lib/utils/format';
|
||||
import { formatUserName } from '$lib/utils/nameDisplay';
|
||||
import * as Card from '$lib/components/ui/card';
|
||||
import { Button } from '$lib/components/ui/button';
|
||||
import { Badge } from '$lib/components/ui/badge';
|
||||
@@ -35,6 +36,8 @@
|
||||
full_name: string;
|
||||
phone?: string;
|
||||
profile_pic_url?: string;
|
||||
previous_first_name?: string;
|
||||
previous_last_name?: string;
|
||||
};
|
||||
services: Array<{
|
||||
service_name?: string;
|
||||
@@ -645,7 +648,7 @@
|
||||
{#if activeAppointment.user?.profile_pic_url}
|
||||
<img
|
||||
src={activeAppointment.user.profile_pic_url}
|
||||
alt={activeAppointment.user.full_name}
|
||||
alt={formatUserName(activeAppointment.user.full_name, activeAppointment.user.previous_first_name, activeAppointment.user.previous_last_name)}
|
||||
class="h-14 w-14 shrink-0 rounded-full object-cover ring-2 ring-blue-200 sm:h-16 sm:w-16 sm:ring-4 md:h-20 md:w-20"
|
||||
/>
|
||||
{:else}
|
||||
@@ -660,11 +663,11 @@
|
||||
{initials}
|
||||
</div>
|
||||
{/if}
|
||||
<div class="min-w-0">
|
||||
<div class="truncate text-lg font-semibold">
|
||||
{activeAppointment.user?.full_name || 'Guest'}
|
||||
</div>
|
||||
<div class="truncate text-sm text-gray-600">{activeAppointment.user?.phone || '—'}</div>
|
||||
<div class="min-w-0">
|
||||
<div class="truncate text-lg font-semibold">
|
||||
{formatUserName(activeAppointment.user?.full_name || 'Guest', activeAppointment.user?.previous_first_name, activeAppointment.user?.previous_last_name)}
|
||||
</div>
|
||||
<div class="truncate text-sm text-gray-600">{activeAppointment.user?.phone || '—'}</div>
|
||||
{#if activeAppointment.user}
|
||||
<button
|
||||
type="button"
|
||||
|
||||
@@ -8,6 +8,7 @@
|
||||
import { Skeleton } from '$lib/components/ui/skeleton';
|
||||
import ApprovalModal from '$lib/components/admin/ApprovalModal.svelte';
|
||||
import EditRequestModal from '$lib/components/admin/EditRequestModal.svelte';
|
||||
import { formatUserName } from '$lib/utils/nameDisplay';
|
||||
|
||||
interface Props {
|
||||
openBookingModal?: (bookingId: string) => void;
|
||||
@@ -47,6 +48,8 @@
|
||||
full_name: string;
|
||||
email: string;
|
||||
phone: string;
|
||||
previous_first_name?: string | null;
|
||||
previous_last_name?: string | null;
|
||||
};
|
||||
}
|
||||
|
||||
@@ -56,6 +59,8 @@
|
||||
start_time: string;
|
||||
user_id: string;
|
||||
user_name: string; // This is a string, not an object
|
||||
previous_first_name?: string | null;
|
||||
previous_last_name?: string | null;
|
||||
services: string[]; // Array of service names
|
||||
duration_minutes: number;
|
||||
created_at: string;
|
||||
@@ -359,7 +364,7 @@
|
||||
>
|
||||
<div class="flex items-start justify-between gap-3">
|
||||
<div class="flex-1">
|
||||
<div class="font-medium">{approval.user_name || 'Guest'}</div>
|
||||
<div class="font-medium">{formatUserName(approval.user_name || 'Guest', approval.previous_first_name, approval.previous_last_name)}</div>
|
||||
<div class="mt-1 text-sm text-gray-600">
|
||||
{approval.services.join(', ')}
|
||||
</div>
|
||||
@@ -404,7 +409,7 @@
|
||||
<div class="flex items-start justify-between gap-3">
|
||||
<div class="flex-1">
|
||||
<div class="flex items-center gap-2">
|
||||
<span class="font-medium">{er.user?.full_name || 'Unknown'}</span>
|
||||
<span class="font-medium">{formatUserName(er.user?.full_name || 'Unknown', er.user?.previous_first_name, er.user?.previous_last_name)}</span>
|
||||
<span class="text-xs font-medium text-amber-600">Edit/Reschedule</span>
|
||||
</div>
|
||||
<div class="mt-1 text-sm text-gray-600">
|
||||
|
||||
@@ -3,6 +3,7 @@
|
||||
import { SvelteDate } from 'svelte/reactivity';
|
||||
import { toast } from 'svelte-sonner';
|
||||
import { sanitizeText } from '$lib/utils/toast-safe';
|
||||
import { formatUserName } from '$lib/utils/nameDisplay';
|
||||
import * as Card from '$lib/components/ui/card';
|
||||
import { Button } from '$lib/components/ui/button';
|
||||
import { Badge } from '$lib/components/ui/badge';
|
||||
@@ -35,6 +36,8 @@
|
||||
user_id: string;
|
||||
services: string[];
|
||||
duration_minutes: number;
|
||||
previous_first_name?: string | null;
|
||||
previous_last_name?: string | null;
|
||||
};
|
||||
|
||||
type TimeBlocker = {
|
||||
@@ -52,7 +55,7 @@
|
||||
start_time: string;
|
||||
duration_minutes: number;
|
||||
status: string;
|
||||
user: { id: string; full_name: string; email: string | null; phone: string | null } | null;
|
||||
user: { id: string; full_name: string; email: string | null; phone: string | null; previous_first_name?: string | null; previous_last_name?: string | null } | null;
|
||||
services: string[];
|
||||
};
|
||||
|
||||
@@ -742,7 +745,7 @@
|
||||
class="block max-w-full truncate font-medium hover:text-blue-600 hover:underline"
|
||||
onclick={() => openUserModal(item.data.user_id)}
|
||||
>
|
||||
{item.data.user_name}
|
||||
{formatUserName(item.data.user_name, item.data.previous_first_name, item.data.previous_last_name)}
|
||||
</button>
|
||||
<div class="flex flex-wrap items-center gap-x-1 text-sm text-gray-600">
|
||||
<span class="truncate">{(item.data.services ?? []).join(', ')}</span>
|
||||
@@ -997,7 +1000,7 @@
|
||||
{#each overlappingBookings as booking (booking.id)}
|
||||
<div class="rounded-md border border-amber-200 bg-white p-2">
|
||||
<div class="min-w-0">
|
||||
<div class="text-sm font-medium">{booking.user?.full_name || 'Unknown'}</div>
|
||||
<div class="text-sm font-medium">{formatUserName(booking.user?.full_name || 'Unknown', booking.user?.previous_first_name, booking.user?.previous_last_name)}</div>
|
||||
<div class="text-xs text-gray-500">
|
||||
{new SvelteDate(booking.start_time).toLocaleTimeString('en-GB', {
|
||||
hour: 'numeric',
|
||||
|
||||
@@ -12,6 +12,8 @@
|
||||
user_id: string;
|
||||
services: string[];
|
||||
duration_minutes: number;
|
||||
previous_first_name?: string | null;
|
||||
previous_last_name?: string | null;
|
||||
};
|
||||
|
||||
type DayWorkingHours = {
|
||||
|
||||
Reference in New Issue
Block a user