mirror of
https://github.com/Portabase/portabase.git
synced 2026-07-14 11:16:13 +02:00
feat(onboarding): add stepper, contextual preview and shell
This commit is contained in:
@@ -22,6 +22,7 @@
|
||||
"@better-auth/sso": "1.6.11",
|
||||
"@hookform/resolvers": "^5.4.0",
|
||||
"@modelcontextprotocol/sdk": "^1.29.0",
|
||||
"@onboardjs/react": "1.0.0-rc.5",
|
||||
"@radix-ui/react-accordion": "^1.2.12",
|
||||
"@radix-ui/react-alert-dialog": "^1.1.15",
|
||||
"@radix-ui/react-aspect-ratio": "^1.1.8",
|
||||
|
||||
Generated
+45
-7
@@ -29,6 +29,9 @@ importers:
|
||||
'@modelcontextprotocol/sdk':
|
||||
specifier: ^1.29.0
|
||||
version: 1.29.0(zod@4.3.6)
|
||||
'@onboardjs/react':
|
||||
specifier: 1.0.0-rc.5
|
||||
version: 1.0.0-rc.5(@onboardjs/core@1.0.0-rc.4)(react-dom@19.2.7(react@19.2.7))(react@19.2.7)
|
||||
'@radix-ui/react-accordion':
|
||||
specifier: ^1.2.12
|
||||
version: 1.2.13(@types/react-dom@19.2.3(@types/react@19.2.17))(@types/react@19.2.17)(react-dom@19.2.7(react@19.2.7))(react@19.2.7)
|
||||
@@ -2182,6 +2185,16 @@ packages:
|
||||
'@octokit/types@16.0.0':
|
||||
resolution: {integrity: sha512-sKq+9r1Mm4efXW1FCk7hFSeJo4QKreL/tTbR0rz/qx/r1Oa2VV83LTA/H/MuCOX7uCIJmQVRKBcbmWoySjAnSg==}
|
||||
|
||||
'@onboardjs/core@1.0.0-rc.4':
|
||||
resolution: {integrity: sha512-G22h1I2JUiEyDGf06YEUT2Afg26rDZM4Ku0PAZFqmr7HVukg9651UdPxZ1/9TVvAFFmW6Qq+u8oQ75KVJu80pw==}
|
||||
|
||||
'@onboardjs/react@1.0.0-rc.5':
|
||||
resolution: {integrity: sha512-iqC4MjrQe4VLi3HKCkczFigkjLxdjWTYrzoI1jEi3QpKOVmDAn4bmXngazW2nfPSOItkxdQq38VO7q8J2yVMhg==}
|
||||
peerDependencies:
|
||||
'@onboardjs/core': '>=1.0.0-rc.1'
|
||||
react: ^18.0.0 || ^19.0.0
|
||||
react-dom: ^18.0.0 || ^19.0.0
|
||||
|
||||
'@opentelemetry/semantic-conventions@1.41.1':
|
||||
resolution: {integrity: sha512-/UhIkaZgPutTFmQ7RnIJGgDXZmtEJ7Dvi86xNTFWcnRxVRNk/aotsqDJYeEvDP+FSMB2SdW+pQzNMcWP0rwuNA==}
|
||||
engines: {node: '>=14'}
|
||||
@@ -6938,6 +6951,14 @@ packages:
|
||||
resolution: {integrity: sha512-LaNjtRWUBY++zB5nE/NwcaoMylSPk+S+ZHNB1TzdbMJMny6dynpAGt7X/tl/QYq3TIeE6nxHppbo2LGymrG5Pw==}
|
||||
engines: {node: '>=10'}
|
||||
|
||||
p-queue@8.1.1:
|
||||
resolution: {integrity: sha512-aNZ+VfjobsWryoiPnEApGGmf5WmNsCo9xu8dfaYamG5qaLP7ClhLN6NgsFe6SwJ2UbLEBK5dv9x8Mn5+RVhMWQ==}
|
||||
engines: {node: '>=18'}
|
||||
|
||||
p-timeout@6.1.4:
|
||||
resolution: {integrity: sha512-MyIV3ZA/PmyBN/ud8vV9XzwTrNtR4jFrObymZYnZqMmW0zA8Z17vnT0rBgFE/TlohB+YCHqXMgZzb3Csp49vqg==}
|
||||
engines: {node: '>=14.16'}
|
||||
|
||||
pac-proxy-agent@7.2.0:
|
||||
resolution: {integrity: sha512-TEB8ESquiLMc0lV8vcd5Ql/JAKAoyzHFXaStwjkzpOpC5Yv+pIzLfHvjTSdf3vpa2bMiUQrg9i6276yn8666aA==}
|
||||
engines: {node: '>= 14'}
|
||||
@@ -9890,6 +9911,16 @@ snapshots:
|
||||
dependencies:
|
||||
'@octokit/openapi-types': 27.0.0
|
||||
|
||||
'@onboardjs/core@1.0.0-rc.4':
|
||||
dependencies:
|
||||
p-queue: 8.1.1
|
||||
|
||||
'@onboardjs/react@1.0.0-rc.5(@onboardjs/core@1.0.0-rc.4)(react-dom@19.2.7(react@19.2.7))(react@19.2.7)':
|
||||
dependencies:
|
||||
'@onboardjs/core': 1.0.0-rc.4
|
||||
react: 19.2.7
|
||||
react-dom: 19.2.7(react@19.2.7)
|
||||
|
||||
'@opentelemetry/semantic-conventions@1.41.1': {}
|
||||
|
||||
'@paralleldrive/cuid2@2.3.1':
|
||||
@@ -13498,7 +13529,7 @@ snapshots:
|
||||
'@next/eslint-plugin-next': 16.2.7
|
||||
eslint: 9.39.4(jiti@2.7.0)
|
||||
eslint-import-resolver-node: 0.3.10
|
||||
eslint-import-resolver-typescript: 3.10.1(eslint-plugin-import@2.32.0)(eslint@9.39.4(jiti@2.7.0))
|
||||
eslint-import-resolver-typescript: 3.10.1(eslint-plugin-import@2.32.0(@typescript-eslint/parser@8.60.1(eslint@9.39.4(jiti@2.7.0))(typescript@5.9.3))(eslint@9.39.4(jiti@2.7.0)))(eslint@9.39.4(jiti@2.7.0))
|
||||
eslint-plugin-import: 2.32.0(@typescript-eslint/parser@8.60.1(eslint@9.39.4(jiti@2.7.0))(typescript@5.9.3))(eslint-import-resolver-typescript@3.10.1)(eslint@9.39.4(jiti@2.7.0))
|
||||
eslint-plugin-jsx-a11y: 6.10.2(eslint@9.39.4(jiti@2.7.0))
|
||||
eslint-plugin-react: 7.37.5(eslint@9.39.4(jiti@2.7.0))
|
||||
@@ -13521,7 +13552,7 @@ snapshots:
|
||||
transitivePeerDependencies:
|
||||
- supports-color
|
||||
|
||||
eslint-import-resolver-typescript@3.10.1(eslint-plugin-import@2.32.0)(eslint@9.39.4(jiti@2.7.0)):
|
||||
eslint-import-resolver-typescript@3.10.1(eslint-plugin-import@2.32.0(@typescript-eslint/parser@8.60.1(eslint@9.39.4(jiti@2.7.0))(typescript@5.9.3))(eslint@9.39.4(jiti@2.7.0)))(eslint@9.39.4(jiti@2.7.0)):
|
||||
dependencies:
|
||||
'@nolyfill/is-core-module': 1.0.39
|
||||
debug: 4.4.3
|
||||
@@ -13536,14 +13567,14 @@ snapshots:
|
||||
transitivePeerDependencies:
|
||||
- supports-color
|
||||
|
||||
eslint-module-utils@2.13.0(@typescript-eslint/parser@8.60.1(eslint@9.39.4(jiti@2.7.0))(typescript@5.9.3))(eslint-import-resolver-node@0.3.10)(eslint-import-resolver-typescript@3.10.1)(eslint@9.39.4(jiti@2.7.0)):
|
||||
eslint-module-utils@2.13.0(@typescript-eslint/parser@8.60.1(eslint@9.39.4(jiti@2.7.0))(typescript@5.9.3))(eslint-import-resolver-node@0.3.10)(eslint-import-resolver-typescript@3.10.1(eslint-plugin-import@2.32.0(@typescript-eslint/parser@8.60.1(eslint@9.39.4(jiti@2.7.0))(typescript@5.9.3))(eslint@9.39.4(jiti@2.7.0)))(eslint@9.39.4(jiti@2.7.0)))(eslint@9.39.4(jiti@2.7.0)):
|
||||
dependencies:
|
||||
debug: 3.2.7
|
||||
optionalDependencies:
|
||||
'@typescript-eslint/parser': 8.60.1(eslint@9.39.4(jiti@2.7.0))(typescript@5.9.3)
|
||||
eslint: 9.39.4(jiti@2.7.0)
|
||||
eslint-import-resolver-node: 0.3.10
|
||||
eslint-import-resolver-typescript: 3.10.1(eslint-plugin-import@2.32.0)(eslint@9.39.4(jiti@2.7.0))
|
||||
eslint-import-resolver-typescript: 3.10.1(eslint-plugin-import@2.32.0(@typescript-eslint/parser@8.60.1(eslint@9.39.4(jiti@2.7.0))(typescript@5.9.3))(eslint@9.39.4(jiti@2.7.0)))(eslint@9.39.4(jiti@2.7.0))
|
||||
transitivePeerDependencies:
|
||||
- supports-color
|
||||
|
||||
@@ -13558,7 +13589,7 @@ snapshots:
|
||||
doctrine: 2.1.0
|
||||
eslint: 9.39.4(jiti@2.7.0)
|
||||
eslint-import-resolver-node: 0.3.10
|
||||
eslint-module-utils: 2.13.0(@typescript-eslint/parser@8.60.1(eslint@9.39.4(jiti@2.7.0))(typescript@5.9.3))(eslint-import-resolver-node@0.3.10)(eslint-import-resolver-typescript@3.10.1)(eslint@9.39.4(jiti@2.7.0))
|
||||
eslint-module-utils: 2.13.0(@typescript-eslint/parser@8.60.1(eslint@9.39.4(jiti@2.7.0))(typescript@5.9.3))(eslint-import-resolver-node@0.3.10)(eslint-import-resolver-typescript@3.10.1(eslint-plugin-import@2.32.0(@typescript-eslint/parser@8.60.1(eslint@9.39.4(jiti@2.7.0))(typescript@5.9.3))(eslint@9.39.4(jiti@2.7.0)))(eslint@9.39.4(jiti@2.7.0)))(eslint@9.39.4(jiti@2.7.0))
|
||||
hasown: 2.0.4
|
||||
is-core-module: 2.16.2
|
||||
is-glob: 4.0.3
|
||||
@@ -15178,6 +15209,13 @@ snapshots:
|
||||
dependencies:
|
||||
p-limit: 3.1.0
|
||||
|
||||
p-queue@8.1.1:
|
||||
dependencies:
|
||||
eventemitter3: 5.0.4
|
||||
p-timeout: 6.1.4
|
||||
|
||||
p-timeout@6.1.4: {}
|
||||
|
||||
pac-proxy-agent@7.2.0:
|
||||
dependencies:
|
||||
'@tootallnate/quickjs-emscripten': 0.23.0
|
||||
@@ -16474,7 +16512,7 @@ snapshots:
|
||||
ansi-escapes: 4.3.2
|
||||
supports-hyperlinks: 2.3.0
|
||||
|
||||
terser-webpack-plugin@5.6.1(esbuild@0.25.10)(postcss@8.5.10)(webpack@5.107.2(esbuild@0.27.7)(postcss@8.5.10)):
|
||||
terser-webpack-plugin@5.6.1(esbuild@0.25.10)(postcss@8.5.10)(webpack@5.107.2(esbuild@0.25.10)(postcss@8.5.10)):
|
||||
dependencies:
|
||||
'@jridgewell/trace-mapping': 0.3.31
|
||||
jest-worker: 27.5.1
|
||||
@@ -16899,7 +16937,7 @@ snapshots:
|
||||
neo-async: 2.6.2
|
||||
schema-utils: 4.3.3
|
||||
tapable: 2.3.3
|
||||
terser-webpack-plugin: 5.6.1(esbuild@0.25.10)(postcss@8.5.10)(webpack@5.107.2(esbuild@0.27.7)(postcss@8.5.10))
|
||||
terser-webpack-plugin: 5.6.1(esbuild@0.25.10)(postcss@8.5.10)(webpack@5.107.2(esbuild@0.25.10)(postcss@8.5.10))
|
||||
watchpack: 2.5.1
|
||||
webpack-sources: 3.5.0
|
||||
transitivePeerDependencies:
|
||||
|
||||
@@ -0,0 +1,65 @@
|
||||
"use client";
|
||||
|
||||
import { useOnboarding } from "@onboardjs/react";
|
||||
import { Loader2 } from "lucide-react";
|
||||
|
||||
const DASHBOARD_PREVIEW_STEP_IDS = [
|
||||
"account-info",
|
||||
"security",
|
||||
"preferences",
|
||||
"org-create",
|
||||
];
|
||||
|
||||
export const OnboardingPreview = () => {
|
||||
const { state } = useOnboarding();
|
||||
const stepId = state?.currentStep?.id;
|
||||
|
||||
if (stepId === "agent-waiting") {
|
||||
return (
|
||||
<div className="flex h-full items-center justify-center">
|
||||
<Loader2 className="size-10 animate-spin text-primary" />
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
if (stepId === "finish") {
|
||||
return null;
|
||||
}
|
||||
|
||||
const org = state?.context.flowData.org as { name?: string; logoDataUrl?: string } | undefined;
|
||||
const preferences = state?.context.flowData.preferences as { theme?: string; avatarDataUrl?: string } | undefined;
|
||||
|
||||
const isDashboardPreview = typeof stepId === "string" && DASHBOARD_PREVIEW_STEP_IDS.includes(stepId);
|
||||
|
||||
return (
|
||||
<div className="flex h-full w-full items-center justify-center p-6">
|
||||
<div
|
||||
className={`w-full max-w-sm rounded-lg border border-white/10 overflow-hidden ${preferences?.theme === "light" ? "bg-white text-black" : "bg-zinc-900 text-white"}`}
|
||||
>
|
||||
<div className="flex items-center gap-2 border-b border-white/10 px-4 py-3">
|
||||
{org?.logoDataUrl ? (
|
||||
// eslint-disable-next-line @next/next/no-img-element
|
||||
<img src={org.logoDataUrl} alt="" className="size-5 rounded" />
|
||||
) : (
|
||||
<div className="size-5 rounded bg-primary" />
|
||||
)}
|
||||
<span className="text-sm font-medium">{org?.name || "Your organisation"}</span>
|
||||
</div>
|
||||
{isDashboardPreview && (
|
||||
<div className="flex">
|
||||
<div className="w-16 border-r border-white/10 p-2 flex flex-col gap-2">
|
||||
{[...Array(5)].map((_, i) => (
|
||||
<div key={i} className="h-2 rounded bg-white/10" />
|
||||
))}
|
||||
</div>
|
||||
<div className="flex-1 p-3 flex flex-col gap-2">
|
||||
<div className="h-3 w-1/2 rounded bg-white/10" />
|
||||
<div className="h-16 rounded bg-white/5" />
|
||||
<div className="h-16 rounded bg-white/5" />
|
||||
</div>
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
@@ -0,0 +1,50 @@
|
||||
"use client";
|
||||
|
||||
import { useOnboarding } from "@onboardjs/react";
|
||||
import { Button } from "@/components/ui/button";
|
||||
import { OnboardingStepper } from "@/features/onboarding/onboarding-stepper";
|
||||
import { OnboardingPreview } from "@/features/onboarding/onboarding-preview";
|
||||
|
||||
export const OnboardingShell = () => {
|
||||
const { state, previous, skip, renderStep } = useOnboarding();
|
||||
|
||||
if (!state) return null;
|
||||
|
||||
const showSplit = state.currentStep?.id !== "agent-waiting" && state.currentStep?.id !== "finish";
|
||||
|
||||
return (
|
||||
<div className="min-h-screen bg-zinc-950 text-white flex items-center justify-center p-4">
|
||||
<div className="w-full max-w-4xl rounded-2xl bg-zinc-900 border border-white/10 shadow-2xl overflow-hidden flex flex-col md:flex-row min-h-[560px]">
|
||||
<div className="flex-1 flex flex-col gap-6 p-8">
|
||||
<OnboardingStepper />
|
||||
<div className="flex-1">{renderStep()}</div>
|
||||
<div className="flex justify-between">
|
||||
<Button
|
||||
type="button"
|
||||
variant="ghost"
|
||||
onClick={() => previous()}
|
||||
disabled={state.isFirstStep || state.isLoading}
|
||||
>
|
||||
Back
|
||||
</Button>
|
||||
{state.isSkippable && (
|
||||
<Button
|
||||
type="button"
|
||||
variant="ghost"
|
||||
onClick={() => skip()}
|
||||
disabled={state.isLoading}
|
||||
>
|
||||
Skip
|
||||
</Button>
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
{showSplit && (
|
||||
<div className="hidden md:block w-[360px] border-l border-white/10 bg-zinc-950">
|
||||
<OnboardingPreview />
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
@@ -0,0 +1,20 @@
|
||||
"use client";
|
||||
|
||||
import { useOnboarding } from "@onboardjs/react";
|
||||
import { Progress } from "@/components/ui/progress";
|
||||
|
||||
export const OnboardingStepper = () => {
|
||||
const { state } = useOnboarding();
|
||||
|
||||
if (!state) return null;
|
||||
|
||||
return (
|
||||
<div className="flex flex-col gap-2 w-full max-w-sm">
|
||||
<div className="flex justify-between text-xs text-muted-foreground">
|
||||
<span>Step {state.currentStepNumber} of {state.totalSteps}</span>
|
||||
<span>{Math.round(state.progressPercentage)}%</span>
|
||||
</div>
|
||||
<Progress value={state.progressPercentage} />
|
||||
</div>
|
||||
);
|
||||
};
|
||||
Reference in New Issue
Block a user