diff --git a/ui/src/components/studio/workshops.ts b/ui/src/components/studio/workshops.ts index 07938642..bdba1197 100644 --- a/ui/src/components/studio/workshops.ts +++ b/ui/src/components/studio/workshops.ts @@ -7,8 +7,8 @@ import { Award, Share2, Repeat, + type LucideIcon, } from "lucide-react"; -import type { ComponentType } from "react"; /** * Phase 10 — single source-of-truth for Studio workshops. @@ -35,7 +35,7 @@ export interface WorkshopDefinition { /** Inter 400 silver — single line. */ subtitle: string; /** Lucide icon component, rendered at 32x32 in volt, top-right of the card. */ - icon: ComponentType<{ className?: string }>; + icon: LucideIcon; /** * Key used by StudioWorkshopDetail to look up the concrete generator * component. Phase 10 does not rewrite any generator internals — each diff --git a/ui/src/pages/ContentStudio.tsx b/ui/src/pages/ContentStudio.tsx index 438df48e..00bf7e8a 100644 --- a/ui/src/pages/ContentStudio.tsx +++ b/ui/src/pages/ContentStudio.tsx @@ -1,155 +1,76 @@ -import { Tabs, TabsList, TabsTrigger, TabsContent } from "@/components/ui/tabs"; -import { useCompany } from "../context/CompanyContext"; -import { DiagramGeneratePanel } from "../components/DiagramGeneratePanel"; -import { IconGeneratePanel } from "../components/IconGeneratePanel"; -import { WallpaperGeneratePanel } from "../components/WallpaperGeneratePanel"; -import { SocialPostPanel } from "../components/SocialPostPanel"; -import { DocumentGeneratePanel } from "../components/DocumentGeneratePanel"; -import { BrandKitPanel } from "../components/BrandKitPanel"; -import { PresentationPanel } from "../components/PresentationPanel"; -import { ThemeSeedInput } from "../components/ThemeSeedInput"; -import { ThemePaletteGrid, type PaletteRole } from "../components/ThemePaletteGrid"; -import { ThemePreviewPanel } from "../components/ThemePreviewPanel"; -import { ThemeExportTabs } from "../components/ThemeExportTabs"; -import { ThemeApplyConfirmDialog } from "../components/ThemeApplyConfirmDialog"; -import { useContentJob } from "../hooks/useContentJob"; -import { useState, useEffect } from "react"; +import { useLocation, useNavigate } from "@/lib/router"; +import { WorkshopGrid } from "../components/studio/WorkshopGrid"; +import { StudioPromptBar } from "../components/studio/StudioPromptBar"; +import { WORKSHOPS, type WorkshopSlug } from "../components/studio/workshops"; +import { StudioWorkshopDetail } from "./StudioWorkshopDetail"; +/** + * Phase 10 — Studio home. + * + * Replaces the legacy 7-tab ContentStudio with a grid of 8 workshop cards + * plus a freeform prompt bar at the bottom. Clicking a card navigates to + * `//content-studio/`. The prompt bar routes classifiable + * prompts to the matching workshop (with the prompt as a query param) and + * falls through to the Assistant for anything unclassified. + * + * App.tsx currently only exposes a single `content-studio` route with no + * sub-route param (Phase 10 is forbidden from editing App.tsx). To render + * StudioWorkshopDetail without a dedicated sub-route, this component + * path-matches `location.pathname` and conditionally renders the detail + * view. The controller should replace this with a proper + * `content-studio/:workshopSlug` route after Wave 2. + */ export function ContentStudio() { - const { selectedCompanyId } = useCompany(); - const companyId = selectedCompanyId ?? ""; - const themeJob = useContentJob(companyId); - const [showApplyDialog, setShowApplyDialog] = useState(false); - const [seedColor, setSeedColor] = useState("var(--primary)"); - const [themeBundle, setThemeBundle] = useState<{ - palette: PaletteRole[]; - exports: { css: string; tailwind: string; vscode: string; json: string }; - } | null>(null); + const location = useLocation(); + const navigate = useNavigate(); - useEffect(() => { - if (themeJob.status === "done" && themeJob.resultAssetId && companyId) { - fetch(`/api/companies/${companyId}/assets/${themeJob.resultAssetId}`) - .then((r) => r.json()) - .then((data) => setThemeBundle(data as typeof themeBundle)) - .catch(() => {}); - } - }, [themeJob.status, themeJob.resultAssetId, companyId]); + const workshopSlug = matchWorkshopSlug(location.pathname); + + if (workshopSlug) { + return ; + } + + const handleSelectWorkshop = (slug: WorkshopSlug) => { + navigate(`content-studio/${slug}`); + }; + + const handleClassified = (slug: WorkshopSlug, prefilledPrompt: string) => { + navigate(`content-studio/${slug}?prompt=${encodeURIComponent(prefilledPrompt)}`); + }; + + const handleFallbackToAssistant = (prompt: string) => { + navigate(`assistant?prompt=${encodeURIComponent(prompt)}`); + }; return ( -
-

Content Studio

+
+
+

+ STUDIO +

+

+ Eight workshops. Pick one or describe what you need. +

+
- - - Diagrams - Icons - Themes - Wallpapers - Social - Documents - Brand - Presentations - + - - {companyId ? ( - - ) : ( -

Select a company to get started.

- )} -
- - - {companyId ? ( - - ) : ( -

Select a company to get started.

- )} -
- - - {companyId ? ( -
- - - {themeBundle && ( - <> - - - - - { - setShowApplyDialog(false); - }} - onCancel={() => setShowApplyDialog(false)} - /> - - )} -
- ) : ( -

Select a company to get started.

- )} -
- - - {companyId ? ( - - ) : ( -

Select a company to get started.

- )} -
- - - {companyId ? ( - - ) : ( -

Select a company to get started.

- )} -
- - - {companyId ? ( - - ) : ( -

Select a company to get started.

- )} -
- - - {companyId ? ( - - ) : ( -

Select a company to get started.

- )} -
- - - {companyId ? ( - - ) : ( -

Select a company to get started.

- )} -
-
+
+ +
); } + +/** + * Parse `/:companyPrefix/content-studio/` out of a pathname. Returns + * null for the Studio home. This is the Phase 10 workaround for the + * missing `content-studio/:workshopSlug` route in App.tsx. + */ +export function matchWorkshopSlug(pathname: string): string | null { + const match = pathname.match(/\/content-studio\/([^/?#]+)/); + return match ? match[1]! : null; +} diff --git a/ui/src/pages/StudioWorkshopDetail.tsx b/ui/src/pages/StudioWorkshopDetail.tsx index c7a1d8b0..1829a7c0 100644 --- a/ui/src/pages/StudioWorkshopDetail.tsx +++ b/ui/src/pages/StudioWorkshopDetail.tsx @@ -1,4 +1,4 @@ -import { useMemo, type ComponentType } from "react"; +import { useEffect, useMemo, useState, type ComponentType } from "react"; import { ArrowLeft, Save, Download, MessageCircle } from "lucide-react"; import { useNavigate, useLocation } from "@/lib/router"; import { useCompany } from "../context/CompanyContext"; @@ -17,7 +17,6 @@ import { ThemePreviewPanel } from "../components/ThemePreviewPanel"; import { ThemeExportTabs } from "../components/ThemeExportTabs"; import { ThemeApplyConfirmDialog } from "../components/ThemeApplyConfirmDialog"; import { useContentJob } from "../hooks/useContentJob"; -import { useEffect, useState } from "react"; interface StudioWorkshopDetailProps { /**