Skip to content

Commit ccf9933

Browse files
committed
Add wizard support to onboarding checklist and add company details step
1 parent d445d82 commit ccf9933

4 files changed

Lines changed: 104 additions & 50 deletions

File tree

apps/app/src/app/[locale]/(app)/(dashboard)/[orgId]/implementation/actions.ts

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -49,7 +49,9 @@ export async function getOnboardingStatus(
4949
}
5050

5151
const checklistItems = generateChecklistItems(onboarding, orgId);
52-
const completedItems = checklistItems.filter((item) => item.completed).length;
52+
const completedItems = checklistItems.filter(
53+
(item) => item.completed,
54+
).length;
5355
const totalItems = checklistItems.length;
5456

5557
return { checklistItems, completedItems, totalItems };
@@ -95,4 +97,4 @@ export async function markOnboardingStep({
9597
// Return the error key directly
9698
return { success: false, error: appErrors.UNEXPECTED_ERROR };
9799
}
98-
}
100+
}

apps/app/src/app/[locale]/(app)/(dashboard)/[orgId]/implementation/checklist-items.tsx

Lines changed: 20 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,13 +1,31 @@
11
import type { Onboarding } from "@comp/db/types";
22
import { Icons } from "@comp/ui/icons";
3-
import { ListCheck, NotebookText, Store, Users } from "lucide-react";
3+
import { Briefcase, ListCheck, NotebookText, Store, Users } from "lucide-react";
44
import type { ChecklistItemProps } from "./types";
5+
import { companyDetailsObjectSchema } from "./lib/models/CompanyDetails";
6+
import { z } from "zod";
57

68
export function generateChecklistItems(
79
onboarding: Onboarding,
810
orgId: string,
911
): ChecklistItemProps[] {
1012
return [
13+
{
14+
title: "Fill out company details",
15+
description:
16+
"In order to get started, you need to provide some basic details about how your company operates.",
17+
wizardPath: `/${orgId}/implementation/wizard/company-details`,
18+
completed:
19+
(
20+
onboarding.companyDetails as z.infer<
21+
typeof companyDetailsObjectSchema
22+
>
23+
)?.isCompleted || false,
24+
docs: "https://trycomp.ai/docs/details",
25+
buttonLabel: "Fill out details",
26+
icon: <Briefcase className="h-5 w-5" />,
27+
type: "wizard",
28+
},
1129
{
1230
title: "Check & Publish Policies",
1331
description:
@@ -64,4 +82,4 @@ export function generateChecklistItems(
6482
icon: <ListCheck className="h-5 w-5" />,
6583
},
6684
];
67-
}
85+
}
Lines changed: 10 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,5 @@
1-
import type { Onboarding } from "@comp/db/types";
1+
import { Onboarding } from "@comp/db/types";
22

3-
// Define valid onboarding steps based on the Onboarding model keys
43
export const onboardingSteps = [
54
"policies",
65
"employees",
@@ -13,11 +12,15 @@ export type OnboardingStep = (typeof onboardingSteps)[number];
1312

1413
export interface ChecklistItemProps {
1514
title: string;
16-
description: string;
17-
href: string;
18-
dbColumn: OnboardingStep; // Use the derived literal type
19-
completed: boolean;
15+
description?: string;
16+
href?: string;
2017
docs: string;
18+
dbColumn?: Exclude<keyof Onboarding, "organizationId">;
19+
completed?: boolean;
2120
buttonLabel: string;
2221
icon: React.ReactNode;
23-
}
22+
type?: "default" | "wizard";
23+
wizardPath?: string;
24+
// For wizards, allow specifying a completion boolean directly
25+
wizardCompleted?: boolean;
26+
}

apps/app/src/components/onboarding/FloatingOnboardingChecklist.tsx

Lines changed: 70 additions & 39 deletions
Original file line numberDiff line numberDiff line change
@@ -1,11 +1,13 @@
1-
'use client';
1+
"use client";
22

33
import { Progress } from "@comp/ui/progress";
44
import { cn } from "@comp/ui/cn";
55
import Link from "next/link";
66
import { Button } from "@comp/ui/button";
7-
import type { ChecklistItemProps, OnboardingStep } from "@/app/[locale]/(app)/(dashboard)/[orgId]/implementation/types";
8-
import { Circle, CheckCircle2 } from "lucide-react";
7+
import type {
8+
ChecklistItemProps,
9+
OnboardingStep,
10+
} from "@/app/[locale]/(app)/(dashboard)/[orgId]/implementation/types";
911
import { usePathname, useRouter } from "next/navigation";
1012
import { Checkbox } from "@comp/ui/checkbox";
1113
import { markOnboardingStep } from "@/app/[locale]/(app)/(dashboard)/[orgId]/implementation/actions";
@@ -39,17 +41,22 @@ export function FloatingOnboardingChecklist({
3941
const remainingItems = totalItems - completedItems;
4042
const progressPercentage = (completedItems / totalItems) * 100;
4143

42-
const implementationPathRegex = /\/[^/]+\/implementation$/;
44+
const implementationPathRegex = /\/[^/]+\/implementation(\/.*)?$/;
4345

4446
if (remainingItems === 0 || implementationPathRegex.test(pathname)) {
4547
return null;
4648
}
4749

48-
const handleCheckedChange = (step: OnboardingStep, newCompletedState: boolean) => {
50+
const handleCheckedChange = (
51+
step: OnboardingStep,
52+
newCompletedState: boolean,
53+
) => {
4954
// Optimistic update
5055
setChecklistItems((prevItems) =>
5156
prevItems.map((item) =>
52-
item.dbColumn === step ? { ...item, completed: newCompletedState } : item,
57+
item.dbColumn === step
58+
? { ...item, completed: newCompletedState }
59+
: item,
5360
),
5461
);
5562
setCompletedItems((prevCount) =>
@@ -58,15 +65,21 @@ export function FloatingOnboardingChecklist({
5865

5966
startTransition(async () => {
6067
try {
61-
const result = await markOnboardingStep({ orgId, step, completed: newCompletedState });
68+
const result = await markOnboardingStep({
69+
orgId,
70+
step,
71+
completed: newCompletedState,
72+
});
6273
if (!result.success) {
6374
throw new Error(result.error || "Failed to update step.");
6475
}
6576
// On successful action, refresh server data
66-
router.refresh();
77+
router.refresh();
6778
} catch (error) {
6879
console.error("Onboarding step update failed:", error);
69-
toast.error(`Error: ${error instanceof Error ? error.message : "Could not update step."}`);
80+
toast.error(
81+
`Error: ${error instanceof Error ? error.message : "Could not update step."}`,
82+
);
7083
// Revert optimistic update on error
7184
setChecklistItems(initialChecklistItems);
7285
setCompletedItems(initialCompletedItems);
@@ -82,7 +95,9 @@ export function FloatingOnboardingChecklist({
8295
)}
8396
>
8497
<div className="mb-3">
85-
<h4 className="mb-1 font-medium leading-none">Implementation Progress</h4>
98+
<h4 className="mb-1 font-medium leading-none">
99+
Implementation Progress
100+
</h4>
86101
<div className="flex justify-between text-xs text-muted-foreground mb-2">
87102
<span>{completedItems} Completed</span>
88103
<span>{remainingItems} Remaining</span>
@@ -91,35 +106,51 @@ export function FloatingOnboardingChecklist({
91106
</div>
92107

93108
<div className="mb-3 grid gap-2 max-h-40 overflow-y-auto">
94-
{checklistItems.map((item) => (
95-
<div
96-
key={item.dbColumn}
97-
className="group flex items-center gap-3"
98-
>
99-
<Checkbox
100-
id={`checklist-${item.dbColumn}`}
101-
checked={item.completed}
102-
onCheckedChange={(checked) => {
103-
handleCheckedChange(item.dbColumn, !!checked);
104-
}}
105-
disabled={isPending}
106-
aria-label={`Mark ${item.title} as ${item.completed ? 'incomplete' : 'complete'}`}
107-
className="shrink-0"
108-
/>
109-
<Link
110-
href={item.href}
111-
className={cn(
112-
"flex-1 cursor-pointer text-sm font-medium hover:text-primary",
113-
item.completed && "line-through text-muted-foreground hover:text-muted-foreground",
114-
isPending && "opacity-50 cursor-not-allowed"
115-
)}
116-
tabIndex={isPending ? -1 : 0}
117-
aria-disabled={isPending}
109+
{checklistItems.map((item) => {
110+
const isWizard = item.type === "wizard";
111+
const href = isWizard
112+
? (item.wizardPath ?? "#")
113+
: (item.href ?? "#");
114+
return (
115+
<div
116+
key={item.dbColumn}
117+
className="group flex items-center gap-3"
118118
>
119-
{item.title}
120-
</Link>
121-
</div>
122-
))}
119+
<Checkbox
120+
id={`checklist-${item.dbColumn}`}
121+
checked={item.completed}
122+
onCheckedChange={(checked) => {
123+
if (isWizard) return; // Prevent marking wizard as done/undone
124+
handleCheckedChange(
125+
item.dbColumn as OnboardingStep,
126+
!!checked,
127+
);
128+
}}
129+
disabled={isPending || isWizard}
130+
aria-label={
131+
isWizard
132+
? `${item.title} (complete in wizard)`
133+
: `Mark ${item.title} as ${item.completed ? "incomplete" : "complete"}`
134+
}
135+
className="shrink-0"
136+
/>
137+
<Link
138+
href={href}
139+
className={cn(
140+
"flex-1 cursor-pointer text-sm font-medium hover:text-primary",
141+
item.completed &&
142+
"line-through text-muted-foreground hover:text-muted-foreground",
143+
isPending &&
144+
"opacity-50 cursor-not-allowed",
145+
)}
146+
tabIndex={isPending ? -1 : 0}
147+
aria-disabled={isPending}
148+
>
149+
{item.title}
150+
</Link>
151+
</div>
152+
);
153+
})}
123154
</div>
124155

125156
<Link href={`/${orgId}/implementation`} passHref>
@@ -135,4 +166,4 @@ export function FloatingOnboardingChecklist({
135166
</Link>
136167
</div>
137168
);
138-
}
169+
}

0 commit comments

Comments
 (0)