mirror of
https://github.com/Portabase/portabase.git
synced 2026-07-14 11:16:13 +02:00
112 lines
3.8 KiB
Markdown
112 lines
3.8 KiB
Markdown
# Onboarding Feature Refactor — Design Spec
|
|||
|
|
_2026-06-19_
|
||
|
|
|
||
|
|
## Goal
|
||
|
|
|
||
|
|
Reorganise `src/features/onboarding/` into clean sub-directories. Replace ad-hoc `useState(loading)` + try/finally patterns with `useMutation`/`useQuery` hooks. Steps become pure JSX shells.
|
||
|
|
|
||
|
|
## Directory Structure (target)
|
||
|
|
|
||
|
|
```
|
||
|
|
onboarding/
|
||
|
|
actions/ ← server actions (unchanged)
|
||
|
|
constants/
|
||
|
|
steps.ts ← STEP_ORDER array + STEP_IDS const
|
||
|
|
hooks/
|
||
|
|
use-create-agent.ts
|
||
|
|
use-delete-agent.ts
|
||
|
|
use-agent-status.ts ← useQuery, 3s poll
|
||
|
|
use-create-org.ts
|
||
|
|
use-create-project.ts
|
||
|
|
use-update-account.ts
|
||
|
|
use-add-notifier.ts
|
||
|
|
use-remove-notifier.ts
|
||
|
|
use-add-storage.ts
|
||
|
|
use-remove-storage.ts
|
||
|
|
use-generate-edge-key.ts
|
||
|
|
use-mark-onboarding-done.ts
|
||
|
|
schemas/
|
||
|
|
account.schema.ts ← BaseSchema + WithPasswordSchema
|
||
|
|
types/
|
||
|
|
index.ts ← content of onboarding.types.ts (moved)
|
||
|
|
utils/ ← reserved, empty for now
|
||
|
|
steps/ ← existing components, thinned to JSX
|
||
|
|
onboarding-checklist.tsx
|
||
|
|
onboarding-shell.tsx
|
||
|
|
onboarding-state.ts
|
||
|
|
onboarding-stepper.tsx
|
||
|
|
onboarding-steps.tsx
|
||
|
|
is-onboarding-done.ts
|
||
|
|
onboarding.types.ts ← deleted once types/index.ts takes over
|
||
|
|
```
|
||
|
|
|
||
|
|
## Hook Contract
|
||
|
|
|
||
|
|
Every hook calls `useOnboarding()` internally. Steps pass nothing — they only import the hook.
|
||
|
|
|
||
|
|
**Mutation hooks** return `UseMutationResult` directly so steps get `mutate`, `isPending`, `isError` etc. from TanStack Query natively.
|
||
|
|
|
||
|
|
```ts
|
||
|
|
// Pattern
|
||
|
|
export const useCreateAgent = () => {
|
||
|
|
const { state, updateContext } = useOnboarding();
|
||
|
|
return useMutation({
|
||
|
|
mutationFn: async (name: string) => {
|
||
|
|
const orgId = (state?.context.flowData.org as any)?.id;
|
||
|
|
if (!orgId) throw new Error("Missing org ID");
|
||
|
|
const result = await createAgentAction({ organizationId: orgId, data: { name, description: "" } });
|
||
|
|
if (!result?.data?.data) throw new Error(result?.serverError ?? "Failed to create agent");
|
||
|
|
const newAgent: OnboardingAgent = { id: result.data.data.id, name: result.data.data.name };
|
||
|
|
const agents = [...((state?.context.flowData.agents ?? []) as OnboardingAgent[]), newAgent];
|
||
|
|
await updateContext({ flowData: { ...state?.context.flowData, agents } });
|
||
|
|
return newAgent;
|
||
|
|
},
|
||
|
|
onError: (err: Error) => toast.error(err.message),
|
||
|
|
});
|
||
|
|
};
|
||
|
|
```
|
||
|
|
|
||
|
|
**Query hooks** return `UseQueryResult`:
|
||
|
|
|
||
|
|
```ts
|
||
|
|
export const useAgentStatus = () => {
|
||
|
|
const { state } = useOnboarding();
|
||
|
|
const agentId = (state?.context.flowData.agents as OnboardingAgent[])?.[0]?.id;
|
||
|
|
return useQuery({
|
||
|
|
queryKey: ["onboarding-agent-status", agentId],
|
||
|
|
queryFn: async () => { /* getAgentStatusAction */ },
|
||
|
|
refetchInterval: 3_000,
|
||
|
|
enabled: !!agentId,
|
||
|
|
});
|
||
|
|
};
|
||
|
|
```
|
||
|
|
|
||
|
|
## Constants
|
||
|
|
|
||
|
|
`constants/steps.ts` exports:
|
||
|
|
- `STEP_ORDER: string[]` — the full ordered list used by the shell for Back/Next logic
|
||
|
|
- `STEP_IDS` — typed const object `{ LOGIN: "login", ... }` for safe step references
|
||
|
|
|
||
|
|
## Schemas
|
||
|
|
|
||
|
|
`schemas/account.schema.ts` exports `BaseSchema` and `WithPasswordSchema` (currently inline in `step-account-info.tsx`).
|
||
|
|
|
||
|
|
## Types
|
||
|
|
|
||
|
|
`types/index.ts` is a verbatim move of `onboarding.types.ts`. All existing imports updated from `@/features/onboarding/onboarding.types` → `@/features/onboarding/types`.
|
||
|
|
|
||
|
|
## Steps After Refactor
|
||
|
|
|
||
|
|
Each step file shrinks to: imports + JSX + hook call. No direct action imports, no manual loading state.
|
||
|
|
|
||
|
|
Example diff for `step-agent-create.tsx`:
|
||
|
|
- Remove: `createAgentAction`, `deleteAgentAction`, `useState(adding)`, manual try/finally
|
||
|
|
- Add: `useCreateAgent()`, `useDeleteAgent()`
|
||
|
|
|
||
|
|
## Out of Scope
|
||
|
|
|
||
|
|
- `onboarding-state.ts` server logic — untouched
|
||
|
|
- `onboarding-steps.tsx` step registry — untouched
|
||
|
|
- UI/UX changes to any step
|
||
|
|
- Adding new steps
|