import { useEffect, useState } from 'react'; import { FontAwesomeIcon } from '@fortawesome/react-fontawesome'; import { faGlobe, faCopy, faArrowsRotate } from '@fortawesome/pro-duotone-svg-icons'; import { Button } from '@/components/base/buttons/button'; import { TopBar } from '@/components/layout/top-bar'; import { WidgetForm, emptyWidgetFormValues, type WidgetFormValues, } from '@/components/forms/widget-form'; import { apiClient } from '@/lib/api-client'; import { notify } from '@/lib/toast'; // /settings/widget — Pattern B page for the website widget config. Uses the // admin endpoint GET /api/config/widget/admin (the plain /api/config/widget // endpoint returns only the public subset and is used by the embed page). // // The site key and site ID are read-only here — generated and rotated by the // backend. The copy-to-clipboard button on the key helps the admin paste it // into their website's embed snippet. type ServerWidgetConfig = { enabled: boolean; key: string; siteId: string; url: string; allowedOrigins: string[]; embed: { loginPage: boolean }; version?: number; updatedAt?: string; }; export const WidgetSettingsPage = () => { const [values, setValues] = useState(emptyWidgetFormValues); const [key, setKey] = useState(''); const [siteId, setSiteId] = useState(''); const [loading, setLoading] = useState(true); const [isSaving, setIsSaving] = useState(false); const [isRotating, setIsRotating] = useState(false); const loadConfig = async () => { try { const data = await apiClient.get('/api/config/widget/admin'); setValues({ enabled: data.enabled, url: data.url ?? '', allowedOrigins: data.allowedOrigins ?? [], embed: { loginPage: data.embed?.loginPage ?? false, }, }); setKey(data.key ?? ''); setSiteId(data.siteId ?? ''); } catch { // toast already shown } finally { setLoading(false); } }; useEffect(() => { loadConfig(); }, []); const handleSave = async () => { setIsSaving(true); try { await apiClient.put('/api/config/widget', { enabled: values.enabled, url: values.url, allowedOrigins: values.allowedOrigins, embed: values.embed, }); notify.success('Widget updated', 'Changes take effect on next widget load.'); await loadConfig(); } catch (err) { console.error('[widget] save failed', err); } finally { setIsSaving(false); } }; const handleRotateKey = async () => { if ( !confirm( 'Rotate the widget key? Any website embed using the old key will stop working until you update it.', ) ) { return; } setIsRotating(true); try { await apiClient.post('/api/config/widget/rotate-key'); notify.success('Key rotated', 'Update every embed snippet with the new key.'); await loadConfig(); } catch (err) { console.error('[widget] rotate failed', err); } finally { setIsRotating(false); } }; const handleCopyKey = async () => { try { await navigator.clipboard.writeText(key); notify.success('Copied', 'Widget key copied to clipboard.'); } catch { notify.error('Copy failed', 'Select the key manually and copy it.'); } }; return (

One-line embed snippet

Drop the script below into your hospital website's <head> to enable chat and appointment booking. Changing the key requires re-embedding.

{loading ? (

Loading widget settings...

) : (
{/* Site key card — read-only with copy + rotate */}

Site key

Site ID: {siteId || '—'}

{key || '— no key yet —'}
{/* Editable config */}
)}
); };