slice 3 COMPLETE: onboarding wizard (complexity/risk/drawdown + starter watchlist + demand subscribe)

starter.ts by ornith (9 symbols w/ ticker-kind + ADR-0007 reasons, defaults, 12 tests).
onboarding.complete + onboarding.starter + me().onboarded by orchestrator; schema
+drawdown_tolerance. Wizard UI (complexity picker, starter list, disclaimer, finish).
onboarding subscribes starter symbols to the demand set (adapter fetches them). 87/87
backend + 2/2 lint green. Slice 3 done; slices 1-3 complete.
This commit is contained in:
Investor Flow Build
2026-06-29 21:31:59 -04:00
parent 4816b19976
commit 965f8d3b1c
8 changed files with 352 additions and 28 deletions
+14 -14
View File
@@ -42,6 +42,7 @@ export interface AuthUser {
complexity?: string;
riskTolerance?: string;
convexityPosture?: string;
onboarded?: boolean;
}
async function parse<T>(res: Response): Promise<T> {
@@ -52,25 +53,19 @@ async function parse<T>(res: Response): Promise<T> {
}
return json.result.data as T;
}
async function trpcQuery<T>(procedure: string, input?: unknown): Promise<T> {
const search = input ? `?input=${encodeURIComponent(JSON.stringify(input))}` : "";
const res = await fetch(`/api/trpc/${procedure}${search}`, {
headers: { "content-type": "application/json" },
credentials: "include",
});
const res = await fetch(`/api/trpc/${procedure}${search}`, { headers: { "content-type": "application/json" }, credentials: "include" });
return parse<T>(res);
}
async function trpcMutate<T>(procedure: string, input: unknown): Promise<T> {
const res = await fetch(`/api/trpc/${procedure}`, { method: "POST", headers: { "content-type": "application/json" }, credentials: "include", body: JSON.stringify(input) });
return parse<T>(res);
}
async function trpcMutate<T>(procedure: string, input: unknown): Promise<T> {
const res = await fetch(`/api/trpc/${procedure}`, {
method: "POST",
headers: { "content-type": "application/json" },
credentials: "include",
body: JSON.stringify(input),
});
return parse<T>(res);
}
export type Complexity = "beginner" | "intermediate" | "advanced";
export interface StarterEntry { symbol: string; tickerKind: string; reason: string; }
export interface PortfolioEntry { symbol: string; qty: number; avgCost: number; acquiredAt: string; }
export const api = {
market: { snapshot: (symbol: string) => trpcQuery<Snapshot>("market.snapshot", { symbol }) },
@@ -83,4 +78,9 @@ export const api = {
confirm2fa: (totp: string) => trpcMutate<{ ok: boolean }>("auth.confirm2fa", { totp }),
oauthStart: (provider: "github" | "google", redirectUri: string) => trpcMutate<{ redirectUrl: string; state: string }>("auth.oauthStart", { provider, redirectUri }),
},
onboarding: {
starter: () => trpcQuery<{ watchlist: StarterEntry[]; disclaimer: string }>("onboarding.starter"),
complete: (input: { complexity: Complexity; drawdownTolerancePct?: number; firstWatchlistSymbols?: string[]; portfolio?: PortfolioEntry[] }) =>
trpcMutate<{ ok: boolean; watchlistId: string }>("onboarding.complete", input),
},
};