feat(intake): wizard on the board with a plain-box fallback

This commit is contained in:
Dave 2026-09-16 18:05:20 +02:00
parent 58be48a4b0
commit d5cf51c944
3 changed files with 71 additions and 1 deletions

View file

@ -6,6 +6,7 @@ import { TopBar } from "@/components/shell/top-bar";
import { Board } from "@/components/board/board";
import { getSession } from "@/lib/auth/session";
import { loadBoard } from "@/lib/data/board";
import { aiConfigured } from "@/lib/ai/settings";
export default async function BoardPage({
params,
@ -57,6 +58,10 @@ export default async function BoardPage({
projectSlug={projectSlug}
columns={board.columns}
initialCards={board.cards}
// Pass the AI availability through so the client manager's Inbox shows
// the wizard when configured, the plain box when not. aiConfigured
// returns only whether a key exists — never the key itself.
assistantAvailable={await aiConfigured()}
/>
</AppShell>
);

View file

@ -22,6 +22,7 @@ import { AddCard } from "@/components/board/add-card";
import { BoardColumn } from "@/components/board/board-column";
import { CardTile } from "@/components/board/card-tile";
import { ClientCardControls } from "@/components/board/client-card-controls";
import { RequestFeature } from "@/components/intake/request-feature";
import { useRefreshOnFocus } from "@/components/board/use-refresh-on-focus";
import { computeDropPosition } from "@/lib/board/move";
import type { CardRecord, ColumnRecord, Role } from "@/lib/pb/types";
@ -91,6 +92,7 @@ export function Board({
projectSlug,
columns,
initialCards,
assistantAvailable,
}: {
role: Role;
projectId: string;
@ -98,6 +100,8 @@ export function Board({
projectSlug: string;
columns: ColumnRecord[];
initialCards: CardRecord[];
/** True when the AI provider is configured — controls the wizard vs plain box. */
assistantAvailable: boolean;
}) {
const [cards, setCards] = useState<CardRecord[]>(initialCards);
const [activeId, setActiveId] = useState<string | null>(null);
@ -234,7 +238,7 @@ export function Board({
/>
))}
</SortableContext>
{(role === "owner" || column.is_inbox) && (
{role === "owner" && (
<AddCard
role={role}
projectId={projectId}
@ -243,6 +247,16 @@ export function Board({
projectSlug={projectSlug}
/>
)}
{role === "client_manager" && column.is_inbox && (
<RequestFeature
projectId={projectId}
columnId={column.id}
clientSlug={clientSlug}
projectSlug={projectSlug}
assistantAvailable={assistantAvailable}
/>
)}
</Droppable>
);
})}

View file

@ -0,0 +1,51 @@
"use client";
import { useState } from "react";
import { IntakeWizard } from "@/components/intake/intake-wizard";
import { AddCard } from "@/components/board/add-card";
/**
* The client's way in: the wizard when the assistant is available, the plain
* request box from Plan 1c when it is not.
*
* The fallback is not a nicety. A client must always be able to file a request —
* an AI outage cannot be allowed to remove a capability they already had.
*/
export function RequestFeature({
projectId,
columnId,
clientSlug,
projectSlug,
assistantAvailable,
}: {
projectId: string;
columnId: string;
clientSlug: string;
projectSlug: string;
assistantAvailable: boolean;
}) {
// Flips if the interview reports the provider unavailable mid-flow.
const [fallback, setFallback] = useState(!assistantAvailable);
if (fallback) {
return (
<AddCard
role="client_manager"
projectId={projectId}
columnId={columnId}
clientSlug={clientSlug}
projectSlug={projectSlug}
/>
);
}
return (
<IntakeWizard
projectId={projectId}
clientSlug={clientSlug}
projectSlug={projectSlug}
onFallback={() => setFallback(true)}
/>
);
}