mirror of
https://dev.azure.com/globalhealthx/EMR/_git/helix-engage
synced 2026-05-18 20:08:19 +00:00
- Setup wizard: 3-pane layout with right-side live previews, resume banner, edit/copy icons on team step, AI prompt configuration - Forms: employee-create replaces invite-member (no email invites), clinic form with address/hours/payment, doctor form with visit slots - Seed script: aligned to current SDK schema — doctors created as workspace members (HelixEngage Manager role), visitingHours replaced by doctorVisitSlot entity, clinics seeded, portalUserId linked dynamically, SUB/ORIGIN/GQL configurable via env vars - Pages: clinics + doctors CRUD updated for new schema, team settings with temp password + role assignment - New components: time-picker, day-selector, wizard-right-panes, wizard-layout-context, resume-setup-banner - Removed: invite-member-form (replaced by employee-create-form per no-email-invites rule) Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
123 lines
5.1 KiB
TypeScript
123 lines
5.1 KiB
TypeScript
import { useEffect, useState } from 'react';
|
|
import { Link } from 'react-router';
|
|
import { Input } from '@/components/base/input/input';
|
|
import { WizardStep } from './wizard-step';
|
|
import { IdentityRightPane } from './wizard-right-panes';
|
|
import { notify } from '@/lib/toast';
|
|
import type { WizardStepComponentProps } from './wizard-step-types';
|
|
|
|
// Minimal identity step — just the two most important fields (hospital name
|
|
// and logo URL). Full branding (colors, fonts, login copy) is handled on the
|
|
// /branding page and linked from here. Keeping the wizard lean means admins
|
|
// can clear setup in under ten minutes; the branding page is there whenever
|
|
// they want to polish further.
|
|
|
|
const THEME_API_URL =
|
|
import.meta.env.VITE_THEME_API_URL ?? import.meta.env.VITE_API_URL ?? 'http://localhost:4100';
|
|
|
|
export const WizardStepIdentity = (props: WizardStepComponentProps) => {
|
|
const [hospitalName, setHospitalName] = useState('');
|
|
const [logoUrl, setLogoUrl] = useState('');
|
|
const [loading, setLoading] = useState(true);
|
|
const [saving, setSaving] = useState(false);
|
|
|
|
useEffect(() => {
|
|
fetch(`${THEME_API_URL}/api/config/theme`)
|
|
.then((r) => (r.ok ? r.json() : null))
|
|
.then((data) => {
|
|
if (data?.brand) {
|
|
setHospitalName(data.brand.hospitalName ?? '');
|
|
setLogoUrl(data.brand.logo ?? '');
|
|
}
|
|
})
|
|
.catch(() => {
|
|
// non-fatal — admin can fill in fresh values
|
|
})
|
|
.finally(() => setLoading(false));
|
|
}, []);
|
|
|
|
const handleSave = async () => {
|
|
if (!hospitalName.trim()) {
|
|
notify.error('Hospital name is required');
|
|
return;
|
|
}
|
|
setSaving(true);
|
|
try {
|
|
const response = await fetch(`${THEME_API_URL}/api/config/theme`, {
|
|
method: 'PUT',
|
|
headers: { 'Content-Type': 'application/json' },
|
|
body: JSON.stringify({
|
|
brand: {
|
|
hospitalName: hospitalName.trim(),
|
|
logo: logoUrl.trim() || undefined,
|
|
},
|
|
}),
|
|
});
|
|
if (!response.ok) throw new Error(`PUT /api/config/theme failed: ${response.status}`);
|
|
notify.success('Identity saved', 'Hospital name and logo updated.');
|
|
await props.onComplete('identity');
|
|
props.onAdvance();
|
|
} catch (err) {
|
|
notify.error('Save failed', 'Could not update hospital identity. Please try again.');
|
|
console.error('[wizard/identity] save failed', err);
|
|
} finally {
|
|
setSaving(false);
|
|
}
|
|
};
|
|
|
|
return (
|
|
<WizardStep
|
|
step="identity"
|
|
isCompleted={props.isCompleted}
|
|
isLast={props.isLast}
|
|
onPrev={props.onPrev}
|
|
onNext={props.onNext}
|
|
onMarkComplete={handleSave}
|
|
onFinish={props.onFinish}
|
|
saving={saving}
|
|
rightPane={<IdentityRightPane />}
|
|
>
|
|
{loading ? (
|
|
<p className="text-sm text-tertiary">Loading current branding…</p>
|
|
) : (
|
|
<div className="flex flex-col gap-5">
|
|
<Input
|
|
label="Hospital name"
|
|
isRequired
|
|
placeholder="e.g. Ramaiah Memorial Hospital"
|
|
value={hospitalName}
|
|
onChange={setHospitalName}
|
|
/>
|
|
<Input
|
|
label="Logo URL"
|
|
placeholder="https://yourhospital.com/logo.png"
|
|
hint="Paste a URL to your hospital logo. Square images work best."
|
|
value={logoUrl}
|
|
onChange={setLogoUrl}
|
|
/>
|
|
{logoUrl && (
|
|
<div className="flex items-center gap-3 rounded-lg border border-secondary bg-secondary p-3">
|
|
<span className="text-xs font-semibold text-tertiary">Preview:</span>
|
|
<img
|
|
src={logoUrl}
|
|
alt="Logo preview"
|
|
className="size-10 rounded-lg border border-secondary bg-primary object-contain p-1"
|
|
onError={(e) => ((e.target as HTMLImageElement).style.display = 'none')}
|
|
/>
|
|
</div>
|
|
)}
|
|
<div className="rounded-lg border border-dashed border-secondary bg-secondary p-4">
|
|
<p className="text-xs text-tertiary">
|
|
Need to pick brand colors, fonts, or customise the login page copy? Open the full{' '}
|
|
<Link to="/branding" className="font-semibold text-brand-primary underline">
|
|
branding settings
|
|
</Link>{' '}
|
|
page after completing setup.
|
|
</p>
|
|
</div>
|
|
</div>
|
|
)}
|
|
</WizardStep>
|
|
);
|
|
};
|