From d5cf51c944ec25450fa669f6e15d40568fcbf6ab Mon Sep 17 00:00:00 2001 From: Dave Date: Wed, 16 Sep 2026 18:05:20 +0200 Subject: [PATCH] feat(intake): wizard on the board with a plain-box fallback --- src/app/p/[clientSlug]/[projectSlug]/page.tsx | 5 ++ src/components/board/board.tsx | 16 +++++- src/components/intake/request-feature.tsx | 51 +++++++++++++++++++ 3 files changed, 71 insertions(+), 1 deletion(-) create mode 100644 src/components/intake/request-feature.tsx diff --git a/src/app/p/[clientSlug]/[projectSlug]/page.tsx b/src/app/p/[clientSlug]/[projectSlug]/page.tsx index 2bac39f..4f6ab3b 100644 --- a/src/app/p/[clientSlug]/[projectSlug]/page.tsx +++ b/src/app/p/[clientSlug]/[projectSlug]/page.tsx @@ -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()} /> ); diff --git a/src/components/board/board.tsx b/src/components/board/board.tsx index d86e686..2f30e4c 100644 --- a/src/components/board/board.tsx +++ b/src/components/board/board.tsx @@ -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(initialCards); const [activeId, setActiveId] = useState(null); @@ -234,7 +238,7 @@ export function Board({ /> ))} - {(role === "owner" || column.is_inbox) && ( + {role === "owner" && ( )} + + {role === "client_manager" && column.is_inbox && ( + + )} ); })} diff --git a/src/components/intake/request-feature.tsx b/src/components/intake/request-feature.tsx new file mode 100644 index 0000000..ca7e37c --- /dev/null +++ b/src/components/intake/request-feature.tsx @@ -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 ( + + ); + } + + return ( + setFallback(true)} + /> + ); +}