Welcome to Northwind, Ayesha
Take a one-minute tour of the four things people use most.
product-tour.tsx
"use client";
import { useId, useState } from "react";
import { LuCalendarCheck, LuFileText, LuInbox, LuSparkles, LuUsers } from "react-icons/lu";
import { Button, CarouselIndicator, Modal } from "@nexera-ui/react";
const SLIDES = [
{
icon: LuInbox,
title: "Every request in one inbox",
text: "Leave, expenses and document requests from your team land here. Approve or reply without leaving the page.",
},
{
icon: LuCalendarCheck,
title: "See who's out at a glance",
text: "The team calendar shows holidays, sick days and public holidays for London, Lahore and Lisbon side by side.",
},
{
icon: LuUsers,
title: "Your people, organised",
text: "Profiles, reporting lines and contracts live in the directory. Search by name, team or skill.",
},
{
icon: LuFileText,
title: "Payslips without the email chase",
text: "Everyone gets their payslip in Northwind on payday, with a year-to-date summary ready for tax season.",
},
];
/** Product tour: a short modal walkthrough of the main features, opened from a welcome card. */
export default function ProductTour() {
const [isOpen, setIsOpen] = useState(false);
const [step, setStep] = useState(0);
const [finished, setFinished] = useState(false);
const slidesId = useId();
const slide = SLIDES[step] ?? SLIDES[0];
if (!slide) return null;
const Icon = slide.icon;
const isLast = step === SLIDES.length - 1;
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 @3xl:grid-cols-[1fr_auto] @3xl:items-center">
<div className="grid gap-1">
<h2 className="text-heading-h3 text-primary">Welcome to Northwind, Ayesha</h2>
<p className="text-body-default text-secondary">
{finished
? "You've seen the highlights. Replay the tour any time from Help."
: "Take a one-minute tour of the four things people use most."}
</p>
</div>
<Button
size="md"
leadingIcon={<LuSparkles />}
onPress={() => {
setStep(0);
setIsOpen(true);
}}
>
{finished ? "Replay the tour" : "Start the tour"}
</Button>
</section>
<Modal
isOpen={isOpen}
onOpenChange={setIsOpen}
title="A quick tour of Northwind"
size="md"
secondaryAction={
<Button
variant="secondary"
size="md"
onPress={() => {
if (step === 0) setIsOpen(false);
else setStep(step - 1);
}}
>
{step === 0 ? "Skip tour" : "Back"}
</Button>
}
primaryAction={
<Button
size="md"
onPress={() => {
if (!isLast) {
setStep(step + 1);
return;
}
setFinished(true);
setIsOpen(false);
}}
>
{isLast ? "Get started" : "Next"}
</Button>
}
>
<div className="grid gap-5">
<div id={slidesId} aria-live="polite" className="grid gap-4">
<div
className="grid h-36 place-items-center rounded-lg bg-brand-subtle text-brand-text"
aria-hidden="true"
>
<Icon className="size-12" />
</div>
<div className="grid gap-1">
<h3 className="text-heading-h4 text-primary">{slide.title}</h3>
<p className="text-body-default text-secondary">{slide.text}</p>
</div>
</div>
<CarouselIndicator
className="justify-self-center"
count={SLIDES.length}
index={step}
onIndexChange={setStep}
slidesId={slidesId}
getItemLabel={(index, count) => `Go to tip ${index + 1} of ${count}`}
/>
</div>
</Modal>
</div>
);
}