feat: show live availability status on ContactCard

This commit is contained in:
2026-08-22 00:34:48 +01:00
parent bea1582088
commit 04d5c21f8c
@@ -1,31 +1,103 @@
<script lang="ts">
export let name: string = 'Chelsea Russell';
export let role: string = 'Owner / Beauty Specialist';
export let phone: string = '+44 8008135';
export let email: string = 'chelsea@emailaddress.com';
export let instagram: string = '@crussell';
export let address: string = 'Business Centre, Office Street, Work';
export let profileImage: string =
'https://images.icon-icons.com/5/PNG/256/MSN_messenger_user_156.png';
export let altText: string = 'Profile picture';
import { onMount, onDestroy } from 'svelte';
let {
name = 'Chelsea Russell',
role = 'Owner / Beauty Specialist',
phone = '+44 8008135',
email = 'chelsea@emailaddress.com',
instagram = '@crussell',
address = 'Business Centre, Office Street, Work',
profileImage = 'https://images.icon-icons.com/5/PNG/256/MSN_messenger_user_156.png',
altText = 'Profile picture'
}: {
name?: string;
role?: string;
phone?: string;
email?: string;
instagram?: string;
address?: string;
profileImage?: string;
altText?: string;
} = $props();
type AvailabilityState = 'sleeping' | 'with_client' | 'busy' | 'prepping' | 'available';
const STATUS_MAP: Record<
AvailabilityState,
{ label: string; color: 'green' | 'amber' | 'red' }
> = {
sleeping: { label: 'Unable to take calls right now', color: 'red' },
with_client: { label: 'With a client', color: 'red' },
busy: { label: 'Busy', color: 'amber' },
prepping: { label: 'Prepping for a client', color: 'amber' },
available: { label: 'Available to contact', color: 'green' }
};
let availabilityState = $state<AvailabilityState | null>(null);
let refreshInterval: ReturnType<typeof setInterval> | undefined;
onMount(() => {
fetchAvailability();
refreshInterval = setInterval(fetchAvailability, 30000);
});
onDestroy(() => {
if (refreshInterval) clearInterval(refreshInterval);
});
async function fetchAvailability() {
try {
const res = await fetch('/api/contact-availability');
if (res.ok) {
const data: { state: AvailabilityState } = await res.json();
availabilityState = data.state;
}
} catch {
// Silently fail
}
}
let contactStatus = $derived(
availabilityState ? STATUS_MAP[availabilityState] : null
);
</script>
<div class="mx-auto max-w-sm">
<div class="rounded-lg border-2 border-gray-200 bg-white p-6">
<div class="mx-auto h-full max-w-sm">
<div class="flex h-full flex-col rounded-lg border-2 border-gray-200 bg-white p-6">
<!-- Profile Picture -->
<div class="mb-4 flex justify-center">
<div class="mb-4 flex shrink-0 justify-center">
<div class="h-24 w-24 overflow-hidden rounded-full border-4 border-white shadow-lg">
<img src={profileImage} alt={altText} class="h-full w-full object-cover" />
</div>
</div>
<!-- Name and Role -->
<div class="mb-4 text-center">
<div class="mb-4 shrink-0 text-center">
<h2 class="text-xl font-semibold text-gray-900">{name}</h2>
<p class="text-gray-500">{role}</p>
</div>
<!-- Availability Status -->
{#if contactStatus}
<div class="mb-3 flex shrink-0 items-center justify-center gap-1.5 text-xs">
<span
class="inline-block h-2 w-2 rounded-full"
class:bg-green-500={contactStatus.color === 'green'}
class:bg-amber-500={contactStatus.color === 'amber'}
class:bg-red-500={contactStatus.color === 'red'}
></span>
<span
class="font-medium"
class:text-green-700={contactStatus.color === 'green'}
class:text-amber-700={contactStatus.color === 'amber'}
class:text-red-700={contactStatus.color === 'red'}
>{contactStatus.label}</span>
</div>
{/if}
<!-- Contact Info -->
<div class="space-y-2">
<div class="flex-1 space-y-2 overflow-y-auto">
<div class="flex items-center space-x-2">
<svg
xmlns="http://www.w3.org/2000/svg"