mirror of
https://dev.azure.com/globalhealthx/EMR/_git/helix-engage
synced 2026-05-18 20:08:19 +00:00
feat(onboarding/phase-5): wire real forms into the setup wizard
Replaces the Phase 2 StepPlaceholder with six dedicated wizard step components, each wrapping the corresponding Phase 3/4 form. The parent setup-wizard.tsx is now a thin dispatcher that owns shell state + markSetupStepComplete wiring; each step owns its own data load, form state, validation, and save action. - src/components/setup/wizard-step-types.ts — shared WizardStepComponentProps shape - src/components/setup/wizard-step-identity.tsx — minimal brand form (hospital name + logo URL) hitting /api/config/theme, links out to /branding for full customisation - src/components/setup/wizard-step-clinics.tsx — ClinicForm + createClinic mutation, always presents an empty "add new" form - src/components/setup/wizard-step-doctors.tsx — DoctorForm with clinic dropdown, blocks with an inline warning when no clinics exist yet - src/components/setup/wizard-step-team.tsx — InviteMemberForm with real roles fetched from getRoles, sends invitations via sendInvitations - src/components/setup/wizard-step-telephony.tsx — loads masked config from /api/config/telephony, validates required Ozonetel fields on save - src/components/setup/wizard-step-ai.tsx — loads AI config, clamps temperature 0..2, doesn't auto-advance (last step, admin taps Finish) - src/pages/setup-wizard.tsx — dispatches to the right step component based on activeStep, passes a WizardStepComponentProps bundle Each step calls onComplete(step) after a successful save, which updates the shared SetupState so the left-nav badges reflect the new status immediately. Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
This commit is contained in:
120
src/components/setup/wizard-step-identity.tsx
Normal file
120
src/components/setup/wizard-step-identity.tsx
Normal file
@@ -0,0 +1,120 @@
|
||||
import { useEffect, useState } from 'react';
|
||||
import { Link } from 'react-router';
|
||||
import { Input } from '@/components/base/input/input';
|
||||
import { WizardStep } from './wizard-step';
|
||||
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}
|
||||
>
|
||||
{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>
|
||||
);
|
||||
};
|
||||
Reference in New Issue
Block a user