mirror of
https://github.com/multica-ai/multica.git
synced 2026-07-29 22:54:38 +02:00
PR #1868 conflated "has workspace" with "completed onboarding" — restore `onboarded_at` as the single signal, and route invited users through a dedicated /invitations page before they ever see onboarding. - Backend: CreateWorkspace + AcceptInvitation atomically set onboarded_at alongside the member insert, establishing the invariant "member row exists ↔ onboarded_at != null" at the DB layer. - Migration 065: one-shot backfill closes the dirty rows produced by PR #1868 (users with a workspace but onboarded_at == null). - Entry points (web callback, login, desktop App): if onboarded_at is null, look up pending invitations by email and route to the new batch /invitations page; otherwise the resolver picks workspace / new-workspace as before. - OnboardingPage: stops bouncing on hasWorkspaces; only hasOnboarded bounces. Unblocks the user from completing Step 3 (workspace creation) → Steps 4 / 5. - StarterContentPrompt: only shows when the user is the solo member of the workspace, so invited users never get prompted to import starter content into someone else's workspace. Co-authored-by: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
44 lines
1.5 KiB
TypeScript
44 lines
1.5 KiB
TypeScript
import type { Workspace } from "../types";
|
|
import { useAuthStore } from "../auth";
|
|
import { paths } from "./paths";
|
|
|
|
/**
|
|
* Priority:
|
|
* !hasOnboarded → /onboarding
|
|
* hasOnboarded && has workspace → /<first.slug>/issues
|
|
* hasOnboarded && zero workspaces → /workspaces/new
|
|
*
|
|
* `onboarded_at` is the single source of truth for whether the user has
|
|
* passed first-contact. Backend transactions (CreateWorkspace,
|
|
* AcceptInvitation) atomically set this field whenever a user joins a
|
|
* `member` row, so "has workspace but !onboarded" is now a
|
|
* physically impossible state — see migration 065 for the existing-data
|
|
* backfill that closed the door retroactively.
|
|
*
|
|
* Callers that need invitation-aware routing (callback / login) handle the
|
|
* "un-onboarded with pending invites" branch themselves before calling
|
|
* this resolver — this resolver only deals with the post-invite-check
|
|
* destination.
|
|
*/
|
|
export function resolvePostAuthDestination(
|
|
workspaces: Workspace[],
|
|
hasOnboarded: boolean,
|
|
): string {
|
|
if (!hasOnboarded) {
|
|
return paths.onboarding();
|
|
}
|
|
const first = workspaces[0];
|
|
if (first) {
|
|
return paths.workspace(first.slug).issues();
|
|
}
|
|
return paths.newWorkspace();
|
|
}
|
|
|
|
/**
|
|
* Single source of truth: backed by `users.onboarded_at`, which
|
|
* arrives with the user object on every auth response.
|
|
*/
|
|
export function useHasOnboarded(): boolean {
|
|
return useAuthStore((s) => s.user?.onboarded_at != null);
|
|
}
|