Set up your company
Four short steps so leave balances and payroll are right from day one.
onboarding-steps.tsx
"use client";
import { useState, type ReactNode } from "react";
import { LuArrowRight, LuBuilding2 } from "react-icons/lu";
import { Alert, Button, Input, Modal, Select, SelectOption, Stepper } from "@nexera-ui/react";
const LABELS = ["Company", "Work week", "Leave policy", "Admins"];
/** Onboarding steps: a modal that guides company setup across four numbered steps. */
export default function OnboardingSteps() {
const [isOpen, setIsOpen] = useState(false);
const [step, setStep] = useState(1);
const [done, setDone] = useState(false);
const isLast = step === LABELS.length;
const panels: Record<number, ReactNode> = {
1: (
<>
<Input
label="Company name"
name="company"
defaultValue="Northwind Ltd"
autoComplete="organization"
/>
<Select label="Country of registration" name="country" defaultValue="gb">
<SelectOption id="gb" label="United Kingdom" />
<SelectOption id="pk" label="Pakistan" />
<SelectOption id="pt" label="Portugal" />
<SelectOption id="dk" label="Denmark" />
</Select>
</>
),
2: (
<>
<Select label="Working days" name="week" defaultValue="mon-fri">
<SelectOption id="mon-fri" label="Monday to Friday" />
<SelectOption id="sun-thu" label="Sunday to Thursday" />
<SelectOption id="mon-sat" label="Monday to Saturday" />
</Select>
<Input
label="Hours per week"
name="hours"
type="number"
defaultValue="37.5"
description="Used to work out part-time leave."
/>
</>
),
3: (
<>
<Input
label="Annual leave days"
name="leave"
type="number"
defaultValue="25"
description="Per full-time employee, before public holidays."
/>
<Select label="Leave year starts" name="leave-year" defaultValue="jan">
<SelectOption id="jan" label="1 January" />
<SelectOption id="apr" label="6 April (UK tax year)" />
<SelectOption id="jul" label="1 July" />
</Select>
</>
),
4: (
<Input
label="Admin emails"
name="admins"
defaultValue="sara.malik@northwind.io, ahmed.raza@northwind.io"
description="Separate addresses with commas. Admins can run payroll and change policies."
/>
),
};
return (
<div className="@container min-h-[480px] bg-page px-4 py-8 @3xl:px-8">
<section className="mx-auto grid max-w-2xl gap-4 rounded-xl border border-default bg-surface p-6">
<div className="flex items-start gap-3">
<span
className="grid size-10 shrink-0 place-items-center rounded-lg bg-brand-subtle text-brand-text"
aria-hidden="true"
>
<LuBuilding2 className="size-5" />
</span>
<div className="grid gap-1">
<h2 className="text-heading-h3 text-primary">Set up your company</h2>
<p className="text-body-default text-secondary">
Four short steps so leave balances and payroll are right from day one.
</p>
</div>
</div>
{done ? (
<Alert
tone="success"
title="Company set up"
description="You can change any of this later in Settings."
/>
) : null}
<Button
size="md"
className="justify-self-start"
trailingIcon={<LuArrowRight />}
onPress={() => {
setIsOpen(true);
}}
>
{done ? "Review setup" : `Continue setup (step ${step} of ${LABELS.length})`}
</Button>
</section>
<Modal
isOpen={isOpen}
onOpenChange={setIsOpen}
size="md"
title="Set up Northwind"
subtitle={`Step ${step}: ${LABELS[step - 1] ?? ""}`}
footerNote={`Step ${step} of ${LABELS.length}`}
secondaryAction={
<Button
variant="secondary"
size="md"
onPress={() => {
if (step === 1) setIsOpen(false);
else setStep(step - 1);
}}
>
{step === 1 ? "Finish later" : "Back"}
</Button>
}
primaryAction={
<Button
size="md"
onPress={() => {
if (!isLast) {
setStep(step + 1);
return;
}
setDone(true);
setIsOpen(false);
}}
>
{isLast ? "Finish setup" : "Continue"}
</Button>
}
>
<div className="grid gap-6">
<Stepper
aria-label="Company setup progress"
current={step}
steps={LABELS.map((label, index) => ({
label,
...(index + 1 < step
? {
onPress: () => {
setStep(index + 1);
},
}
: {}),
}))}
/>
{/* Every panel stays mounted (hidden when inactive) so typed values survive Back and Continue. */}
{Object.entries(panels).map(([number, panel]) => (
<div key={number} hidden={Number(number) !== step} className="grid gap-4">
{panel}
</div>
))}
</div>
</Modal>
</div>
);
}