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",
|
"@better-auth/sso": "1.6.11",
|
||||||
"@hookform/resolvers": "^5.4.0",
|
"@hookform/resolvers": "^5.4.0",
|
||||||
"@modelcontextprotocol/sdk": "^1.29.0",
|
"@modelcontextprotocol/sdk": "^1.29.0",
|
||||||
|
"@onboardjs/react": "1.0.0-rc.5",
|
||||||
"@radix-ui/react-accordion": "^1.2.12",
|
"@radix-ui/react-accordion": "^1.2.12",
|
||||||
"@radix-ui/react-alert-dialog": "^1.1.15",
|
"@radix-ui/react-alert-dialog": "^1.1.15",
|
||||||
"@radix-ui/react-aspect-ratio": "^1.1.8",
|
"@radix-ui/react-aspect-ratio": "^1.1.8",
|
||||||
|
|||||||
Generated
+45
-7
@@ -29,6 +29,9 @@ importers:
|
|||||||
'@modelcontextprotocol/sdk':
|
'@modelcontextprotocol/sdk':
|
||||||
specifier: ^1.29.0
|
specifier: ^1.29.0
|
||||||
version: 1.29.0(zod@4.3.6)
|
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':
|
'@radix-ui/react-accordion':
|
||||||
specifier: ^1.2.12
|
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)
|
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':
|
'@octokit/types@16.0.0':
|
||||||
resolution: {integrity: sha512-sKq+9r1Mm4efXW1FCk7hFSeJo4QKreL/tTbR0rz/qx/r1Oa2VV83LTA/H/MuCOX7uCIJmQVRKBcbmWoySjAnSg==}
|
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':
|
'@opentelemetry/semantic-conventions@1.41.1':
|
||||||
resolution: {integrity: sha512-/UhIkaZgPutTFmQ7RnIJGgDXZmtEJ7Dvi86xNTFWcnRxVRNk/aotsqDJYeEvDP+FSMB2SdW+pQzNMcWP0rwuNA==}
|
resolution: {integrity: sha512-/UhIkaZgPutTFmQ7RnIJGgDXZmtEJ7Dvi86xNTFWcnRxVRNk/aotsqDJYeEvDP+FSMB2SdW+pQzNMcWP0rwuNA==}
|
||||||
engines: {node: '>=14'}
|
engines: {node: '>=14'}
|
||||||
@@ -6938,6 +6951,14 @@ packages:
|
|||||||
resolution: {integrity: sha512-LaNjtRWUBY++zB5nE/NwcaoMylSPk+S+ZHNB1TzdbMJMny6dynpAGt7X/tl/QYq3TIeE6nxHppbo2LGymrG5Pw==}
|
resolution: {integrity: sha512-LaNjtRWUBY++zB5nE/NwcaoMylSPk+S+ZHNB1TzdbMJMny6dynpAGt7X/tl/QYq3TIeE6nxHppbo2LGymrG5Pw==}
|
||||||
engines: {node: '>=10'}
|
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:
|
pac-proxy-agent@7.2.0:
|
||||||
resolution: {integrity: sha512-TEB8ESquiLMc0lV8vcd5Ql/JAKAoyzHFXaStwjkzpOpC5Yv+pIzLfHvjTSdf3vpa2bMiUQrg9i6276yn8666aA==}
|
resolution: {integrity: sha512-TEB8ESquiLMc0lV8vcd5Ql/JAKAoyzHFXaStwjkzpOpC5Yv+pIzLfHvjTSdf3vpa2bMiUQrg9i6276yn8666aA==}
|
||||||
engines: {node: '>= 14'}
|
engines: {node: '>= 14'}
|
||||||
@@ -9890,6 +9911,16 @@ snapshots:
|
|||||||
dependencies:
|
dependencies:
|
||||||
'@octokit/openapi-types': 27.0.0
|
'@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': {}
|
'@opentelemetry/semantic-conventions@1.41.1': {}
|
||||||
|
|
||||||
'@paralleldrive/cuid2@2.3.1':
|
'@paralleldrive/cuid2@2.3.1':
|
||||||
@@ -13498,7 +13529,7 @@ snapshots:
|
|||||||
'@next/eslint-plugin-next': 16.2.7
|
'@next/eslint-plugin-next': 16.2.7
|
||||||
eslint: 9.39.4(jiti@2.7.0)
|
eslint: 9.39.4(jiti@2.7.0)
|
||||||
eslint-import-resolver-node: 0.3.10
|
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-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-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))
|
eslint-plugin-react: 7.37.5(eslint@9.39.4(jiti@2.7.0))
|
||||||
@@ -13521,7 +13552,7 @@ snapshots:
|
|||||||
transitivePeerDependencies:
|
transitivePeerDependencies:
|
||||||
- supports-color
|
- 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:
|
dependencies:
|
||||||
'@nolyfill/is-core-module': 1.0.39
|
'@nolyfill/is-core-module': 1.0.39
|
||||||
debug: 4.4.3
|
debug: 4.4.3
|
||||||
@@ -13536,14 +13567,14 @@ snapshots:
|
|||||||
transitivePeerDependencies:
|
transitivePeerDependencies:
|
||||||
- supports-color
|
- 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:
|
dependencies:
|
||||||
debug: 3.2.7
|
debug: 3.2.7
|
||||||
optionalDependencies:
|
optionalDependencies:
|
||||||
'@typescript-eslint/parser': 8.60.1(eslint@9.39.4(jiti@2.7.0))(typescript@5.9.3)
|
'@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-import-resolver-node: 0.3.10
|
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:
|
transitivePeerDependencies:
|
||||||
- supports-color
|
- supports-color
|
||||||
|
|
||||||
@@ -13558,7 +13589,7 @@ snapshots:
|
|||||||
doctrine: 2.1.0
|
doctrine: 2.1.0
|
||||||
eslint: 9.39.4(jiti@2.7.0)
|
eslint: 9.39.4(jiti@2.7.0)
|
||||||
eslint-import-resolver-node: 0.3.10
|
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
|
hasown: 2.0.4
|
||||||
is-core-module: 2.16.2
|
is-core-module: 2.16.2
|
||||||
is-glob: 4.0.3
|
is-glob: 4.0.3
|
||||||
@@ -15178,6 +15209,13 @@ snapshots:
|
|||||||
dependencies:
|
dependencies:
|
||||||
p-limit: 3.1.0
|
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:
|
pac-proxy-agent@7.2.0:
|
||||||
dependencies:
|
dependencies:
|
||||||
'@tootallnate/quickjs-emscripten': 0.23.0
|
'@tootallnate/quickjs-emscripten': 0.23.0
|
||||||
@@ -16474,7 +16512,7 @@ snapshots:
|
|||||||
ansi-escapes: 4.3.2
|
ansi-escapes: 4.3.2
|
||||||
supports-hyperlinks: 2.3.0
|
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:
|
dependencies:
|
||||||
'@jridgewell/trace-mapping': 0.3.31
|
'@jridgewell/trace-mapping': 0.3.31
|
||||||
jest-worker: 27.5.1
|
jest-worker: 27.5.1
|
||||||
@@ -16899,7 +16937,7 @@ snapshots:
|
|||||||
neo-async: 2.6.2
|
neo-async: 2.6.2
|
||||||
schema-utils: 4.3.3
|
schema-utils: 4.3.3
|
||||||
tapable: 2.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
|
watchpack: 2.5.1
|
||||||
webpack-sources: 3.5.0
|
webpack-sources: 3.5.0
|
||||||
transitivePeerDependencies:
|
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