Skip to content

Commit 0ce80a6

Browse files
authored
Merge pull request #1403 from Ashborne267/onboarding
Onboarding
2 parents 2065097 + e17dbc9 commit 0ce80a6

2 files changed

Lines changed: 60 additions & 26 deletions

File tree

frontend/src/components/OnboardingProgressTracker.tsx

Lines changed: 38 additions & 20 deletions
Original file line numberDiff line numberDiff line change
@@ -181,13 +181,20 @@ const StatusBadge = memo(function StatusBadge({
181181
? "bg-pluto-200 text-pluto-900 dark:bg-pluto-800/50 dark:text-pluto-100"
182182
: "bg-pluto-50 text-pluto-700 dark:bg-pluto-900/20 dark:text-pluto-300 group-hover:bg-pluto-100 dark:group-hover:bg-pluto-900/40";
183183

184+
const dotClass = completed
185+
? "bg-pluto-600 dark:bg-pluto-300"
186+
: isCurrent
187+
? "bg-pluto-700 animate-pulse dark:bg-pluto-100"
188+
: "bg-pluto-400 dark:bg-pluto-500";
189+
184190
return (
185191
<span
186-
className={`inline-flex items-center rounded-full px-2 py-0.5 font-semibold transition-colors duration-200 ${
192+
className={`inline-flex items-center gap-1.5 rounded-full px-2.5 py-1 font-semibold tracking-wide transition-colors duration-200 ${
187193
compact ? "text-[0.65rem]" : "text-xs"
188194
} ${colorClass}`}
189195
aria-label={label}
190196
>
197+
<span className={`h-1.5 w-1.5 shrink-0 rounded-full ${dotClass}`} aria-hidden="true" />
191198
{label}
192199
</span>
193200
);
@@ -294,7 +301,7 @@ export const OnboardingProgressTracker = memo(function OnboardingProgressTracker
294301
`}
295302
>
296303
{/* ── Header ──────────────────────────────────────────────────────── */}
297-
<div className="mb-5">
304+
<div className="mb-6">
298305
<div className="flex items-baseline justify-between gap-2">
299306
<h2 className={`font-bold tracking-tight text-pluto-900 dark:text-pluto-50 ${compact ? "text-base" : "text-lg"}`}>
300307
{i18n.title}
@@ -335,7 +342,7 @@ export const OnboardingProgressTracker = memo(function OnboardingProgressTracker
335342
{i18n.allCompleted}
336343
</span>
337344
)}
338-
</p>
345+
</div>
339346
</div>
340347

341348
{/* ── Steps list ───────────────────────────────────────────────────── */}
@@ -391,15 +398,13 @@ export const OnboardingProgressTracker = memo(function OnboardingProgressTracker
391398
${indicatorColorClass}
392399
`}
393400
aria-label={i18n.stepAriaLabel(index + 1, step.title, step.completed, step.required)}
394-
aria-pressed={isCurrent}
395401
aria-current={isCurrent ? "step" : undefined}
396402
aria-setsize={sortedSteps.length}
397403
aria-posinset={index + 1}
398404
aria-roledescription="onboarding step"
399405
aria-describedby={stepDescId}
400406
aria-busy={isPending}
401-
aria-disabled={state.isPending ? "true" : undefined}
402-
disabled={state.isPending}
407+
aria-disabled={isPending || undefined}
403408
>
404409
<StepIcon
405410
completed={step.completed}
@@ -413,11 +418,12 @@ export const OnboardingProgressTracker = memo(function OnboardingProgressTracker
413418
</button>
414419

415420
{/* Step text */}
421+
<div className="flex min-w-0 flex-1 flex-col gap-1.5">
416422
<div className={`flex min-w-0 flex-1 flex-col gap-1 ${orientation === "horizontal" ? "md:text-center" : ""}`}>
417423
<h3
418424
id={stepDescId}
419425
className={`
420-
font-medium leading-tight transition-colors duration-200
426+
font-medium leading-snug tracking-tight transition-colors duration-200
421427
${step.completed
422428
? "text-pluto-600 line-through dark:text-pluto-400"
423429
: "text-pluto-900 dark:text-pluto-50 group-hover:text-pluto-800 dark:group-hover:text-white"}
@@ -432,7 +438,7 @@ export const OnboardingProgressTracker = memo(function OnboardingProgressTracker
432438
)}
433439
</h3>
434440

435-
<p className={`leading-snug text-[#6B6B6B] dark:text-pluto-400 transition-colors group-hover:text-pluto-700 dark:group-hover:text-pluto-300 ${compact ? "text-xs" : "text-sm"}`}>
441+
<p className={`leading-relaxed text-pluto-500 dark:text-pluto-400 transition-colors group-hover:text-pluto-700 dark:group-hover:text-pluto-300 ${compact ? "text-xs" : "text-sm"}`}>
436442
{step.description}
437443
</p>
438444

@@ -451,7 +457,15 @@ export const OnboardingProgressTracker = memo(function OnboardingProgressTracker
451457
{/* Vertical connector */}
452458
{orientation === "vertical" && index < sortedSteps.length - 1 && (
453459
<div
454-
className={`absolute left-[1.4375rem] top-[calc(100%-4px)] ${compact ? "h-2 w-px" : "h-3 w-px"} bg-pluto-200 dark:bg-pluto-700`}
460+
className={`absolute left-[1.6875rem] top-[calc(100%-4px)] ${compact ? "h-2 w-px" : "h-3 w-px"} bg-pluto-200 dark:bg-pluto-700`}
461+
aria-hidden="true"
462+
/>
463+
)}
464+
465+
{/* Horizontal connector */}
466+
{orientation === "horizontal" && index < sortedSteps.length - 1 && (
467+
<div
468+
className={`absolute right-[-0.75rem] hidden h-px w-3 bg-pluto-200 dark:bg-pluto-700 sm:block ${compact ? "top-[1.75rem]" : "top-[2rem]"}`}
455469
aria-hidden="true"
456470
/>
457471
)}
@@ -473,7 +487,7 @@ export const OnboardingProgressTracker = memo(function OnboardingProgressTracker
473487
<AnimatePresence>
474488
{isComplete && sortedSteps.length > 0 && (
475489
<MotionDiv
476-
className="mt-5 rounded-xl border border-pluto-200 bg-pluto-50 p-4 dark:border-pluto-700/60 dark:bg-pluto-900/60"
490+
className="mt-6 rounded-xl border border-pluto-200 border-l-4 border-l-pluto-500 bg-pluto-50 p-4 shadow-[0_2px_10px_rgba(74,111,165,0.1)] dark:border-pluto-700/60 dark:border-l-pluto-400 dark:bg-pluto-900/60"
477491
variants={completionVariants}
478492
initial="hidden"
479493
animate="visible"
@@ -484,17 +498,21 @@ export const OnboardingProgressTracker = memo(function OnboardingProgressTracker
484498
data-testid="completion-banner"
485499
>
486500
<div className="flex items-start gap-3">
487-
<MotionSvg
488-
className="mt-0.5 h-5 w-5 flex-shrink-0 text-pluto-500 dark:text-pluto-300"
489-
fill="currentColor"
490-
viewBox="0 0 20 20"
491-
aria-hidden="true"
492-
animate={prefersReducedMotion ? {} : { scale: [1, 1.2, 1] }}
493-
transition={{ duration: 0.45, delay: 0.25 }}
494-
>
495-
<path fillRule="evenodd" d="M10 18a8 8 0 100-16 8 8 0 000 16zm3.707-9.293a1 1 0 00-1.414-1.414L9 10.586 7.707 9.293a1 1 0 00-1.414 1.414l2 2a1 1 0 001.414 0l4-4z" clipRule="evenodd" />
496-
</MotionSvg>
501+
<span className="flex h-8 w-8 flex-shrink-0 items-center justify-center rounded-full bg-pluto-100 dark:bg-pluto-800/60">
502+
<MotionSvg
503+
className="h-5 w-5 text-pluto-600 dark:text-pluto-300"
504+
fill="currentColor"
505+
viewBox="0 0 20 20"
506+
aria-hidden="true"
507+
animate={prefersReducedMotion ? {} : { scale: [1, 1.2, 1] }}
508+
transition={{ duration: 0.45, delay: 0.25 }}
509+
>
510+
<path fillRule="evenodd" d="M10 18a8 8 0 100-16 8 8 0 000 16zm3.707-9.293a1 1 0 00-1.414-1.414L9 10.586 7.707 9.293a1 1 0 00-1.414 1.414l2 2a1 1 0 001.414 0l4-4z" clipRule="evenodd" />
511+
</MotionSvg>
512+
</span>
497513
<div>
514+
<h4 className="font-semibold tracking-tight text-pluto-900 dark:text-pluto-50">{i18n.successTitle}</h4>
515+
<p className="mt-1 text-sm leading-relaxed text-pluto-700 dark:text-pluto-300">{i18n.successMessage}</p>
498516
<h4 className="font-bold text-pluto-900 dark:text-pluto-50">{i18n.successTitle}</h4>
499517
<p className="mt-1 text-sm text-pluto-700 dark:text-pluto-300">{i18n.successMessage}</p>
500518
</div>

frontend/src/hooks/useOnboardingProgress.ts

Lines changed: 22 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -82,10 +82,20 @@ export function useOnboardingProgress({
8282
[sortedSteps],
8383
);
8484

85-
const isComplete = useMemo(() => {
86-
const required = sortedSteps.filter((s) => s.required);
87-
return required.length > 0 && required.every((s) => s.completed);
88-
}, [sortedSteps]);
85+
const requiredSteps = useMemo(
86+
() => sortedSteps.filter((s) => s.required),
87+
[sortedSteps],
88+
);
89+
90+
const completedRequiredCount = useMemo(
91+
() => requiredSteps.filter((s) => s.completed).length,
92+
[requiredSteps],
93+
);
94+
95+
const isComplete = useMemo(
96+
() => requiredSteps.length > 0 && completedRequiredCount === requiredSteps.length,
97+
[requiredSteps, completedRequiredCount],
98+
);
8999

90100
// ── Reducer ──────────────────────────────────────────────────────────────
91101

@@ -99,13 +109,19 @@ export function useOnboardingProgress({
99109
),
100110
);
101111

112+
// Progress is measured against required steps so the percentage always
113+
// reaches 100% exactly when `isComplete` flips true. Trackers with no
114+
// required steps fall back to counting all steps.
115+
const progressTotal = requiredSteps.length > 0 ? requiredSteps.length : sortedSteps.length;
116+
const progressCompleted = requiredSteps.length > 0 ? completedRequiredCount : completedCount;
117+
102118
// Sync step counts when the steps prop changes
103119
useEffect(() => {
104120
dispatch({
105121
type: "SYNC_STEPS",
106-
payload: { total: sortedSteps.length, completed: completedCount },
122+
payload: { total: progressTotal, completed: progressCompleted },
107123
});
108-
}, [sortedSteps.length, completedCount]);
124+
}, [progressTotal, progressCompleted]);
109125

110126
// Sync external currentStep prop
111127
const prevCurrentStepPropRef = useRef(currentStepProp);

0 commit comments

Comments
 (0)