Files
helix-engage/src/components/setup/wizard-step.tsx
saridsa2 f57fbc1f24 feat(onboarding/phase-6): setup wizard polish, seed script alignment, doctor visit slots
- 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>
2026-04-10 08:37:34 +05:30

143 lines
6.1 KiB
TypeScript

import { useContext, type ReactNode } from 'react';
import { createPortal } from 'react-dom';
import { FontAwesomeIcon } from '@fortawesome/react-fontawesome';
import { faArrowLeft, faArrowRight, faCircleCheck } from '@fortawesome/pro-duotone-svg-icons';
import { Button } from '@/components/base/buttons/button';
import { SETUP_STEP_LABELS, type SetupStepName } from '@/lib/setup-state';
import { WizardLayoutContext } from './wizard-layout-context';
type WizardStepProps = {
step: SetupStepName;
isCompleted: boolean;
isLast: boolean;
onPrev: (() => void) | null;
onNext: (() => void) | null;
onMarkComplete: () => void;
onFinish: () => void;
saving?: boolean;
children: ReactNode;
// Optional content for the wizard shell's right preview pane.
// Portaled into the shell's <aside> via WizardLayoutContext when
// both are mounted. Each step component declares this inline so
// the per-step data fetching stays in one place.
rightPane?: ReactNode;
};
// Single-step wrapper. The parent picks which step is active and supplies
// the form content as children. The step provides title, description,
// "mark complete" CTA, and prev/next/finish navigation. In Phase 5 the
// children will be real form components from the corresponding settings
// pages — for now they're placeholders.
export const WizardStep = ({
step,
isCompleted,
isLast,
onPrev,
onNext,
onMarkComplete,
onFinish,
saving = false,
children,
rightPane,
}: WizardStepProps) => {
const meta = SETUP_STEP_LABELS[step];
const { rightPaneEl } = useContext(WizardLayoutContext);
return (
<>
{rightPane && rightPaneEl && createPortal(rightPane, rightPaneEl)}
<div className="rounded-xl border border-secondary bg-primary p-8 shadow-xs">
<div className="mb-6 flex items-start justify-between gap-4">
<div>
<h2 className="text-2xl font-bold text-primary">{meta.title}</h2>
<p className="mt-1 text-sm text-tertiary">{meta.description}</p>
</div>
{isCompleted && (
<span className="inline-flex items-center gap-2 rounded-full bg-success-primary px-3 py-1 text-xs font-medium text-success-primary">
<FontAwesomeIcon icon={faCircleCheck} className="size-3.5" />
Complete
</span>
)}
</div>
<div className="mb-8">{children}</div>
<div className="flex items-center justify-between gap-4 border-t border-secondary pt-6">
<Button
color="secondary"
size="md"
isDisabled={!onPrev}
onClick={onPrev ?? undefined}
iconLeading={({ className }: { className?: string }) => (
<FontAwesomeIcon icon={faArrowLeft} className={className} />
)}
>
Previous
</Button>
{/* One primary action at the bottom — never two
competing buttons. Previously the wizard showed
Mark complete + Next side-by-side, and users
naturally clicked Next (rightmost = "continue"),
skipping the save+complete chain entirely. Result
was every step staying at 0/6.
New behaviour: a single button whose label and
handler depend on completion state.
- !isCompleted, not last → "Save and continue"
calls onMarkComplete (which does save +
complete + advance via the step component's
handleSave). Forces the agent through the
completion path.
- !isCompleted, last → "Save and finish"
same chain, plus onFinish at the end.
- isCompleted, not last → "Continue"
calls onNext (pure navigation).
- isCompleted, last → "Finish setup"
calls onFinish.
Free-form navigation is still available via the
left-side step nav, so users can revisit completed
steps without re-saving. */}
<div className="flex items-center gap-3">
{!isCompleted ? (
<Button
color="primary"
size="md"
isLoading={saving}
showTextWhileLoading
onClick={onMarkComplete}
iconTrailing={
isLast
? undefined
: ({ className }: { className?: string }) => (
<FontAwesomeIcon icon={faArrowRight} className={className} />
)
}
>
{isLast ? 'Save and finish' : 'Save and continue'}
</Button>
) : isLast ? (
<Button color="primary" size="md" onClick={onFinish}>
Finish setup
</Button>
) : (
<Button
color="primary"
size="md"
isDisabled={!onNext}
onClick={onNext ?? undefined}
iconTrailing={({ className }: { className?: string }) => (
<FontAwesomeIcon icon={faArrowRight} className={className} />
)}
>
Continue
</Button>
)}
</div>
</div>
</div>
</>
);
};