diff --git a/app/(dashboard)/actions.ts b/app/(dashboard)/actions.ts index a48682b..d5318f2 100644 --- a/app/(dashboard)/actions.ts +++ b/app/(dashboard)/actions.ts @@ -228,9 +228,24 @@ export async function setAssetOwnersAction(assetId: string, productSlug: string, if (!accessible.some((a) => a.id === assetId)) throw new Error('Asset not found or not accessible') await setAssetOwners(assetId, userIds) revalidatePath(`/products/${productSlug}`) + revalidatePath(`/assets/${assetId}`) revalidatePath('/my-work') } +/** Update just the long-form content (description / notes) from the asset detail page. */ +export async function updateAssetContentAction( + assetId: string, + productSlug: string, + content: { description?: string; notes?: string }, +) { + const authUser = await requireUser() + const accessible = await getAssetOptions(authUser.id) + if (!accessible.some((a) => a.id === assetId)) throw new Error('Asset not found or not accessible') + await updateAsset(assetId, content) + revalidatePath(`/assets/${assetId}`) + revalidatePath(`/products/${productSlug}`) +} + // --------------------------------------------------------------------------- // Code Plans // --------------------------------------------------------------------------- diff --git a/app/(dashboard)/assets/[id]/asset-content-cards.tsx b/app/(dashboard)/assets/[id]/asset-content-cards.tsx new file mode 100644 index 0000000..736c9d2 --- /dev/null +++ b/app/(dashboard)/assets/[id]/asset-content-cards.tsx @@ -0,0 +1,93 @@ +'use client' + +import { useState, useTransition } from 'react' +import ReactMarkdown from 'react-markdown' +import { Card, CardContent, CardHeader, CardTitle } from '@/components/ui/card' +import { Button } from '@/components/ui/button' +import { Textarea } from '@/components/ui/textarea' +import { AlignLeft, NotebookPen, Pencil } from 'lucide-react' +import { toast } from 'sonner' +import { updateAssetContentAction } from '../../actions' + +type EditableCardProps = { + assetId: string + productSlug: string + field: 'description' | 'notes' + value: string +} + +const cardMeta = { + description: { title: 'Description', icon: AlignLeft, placeholder: 'What does this asset do?', empty: 'No description yet.' }, + notes: { + title: 'Notes & Ideation', + icon: NotebookPen, + placeholder: 'Design thinking, migration ideas, known quirks… (markdown)', + empty: 'No notes yet — capture design thinking, migration ideas, and known quirks here.', + }, +} as const + +/** Long-form asset content with click-to-edit. Notes render as markdown (markdown stays the canonical format). */ +export function AssetContentCard({ assetId, productSlug, field, value }: EditableCardProps) { + const [editing, setEditing] = useState(false) + const [draft, setDraft] = useState(value) + const [isPending, startTransition] = useTransition() + const meta = cardMeta[field] + const Icon = meta.icon + + function save() { + startTransition(async () => { + await updateAssetContentAction(assetId, productSlug, { [field]: draft }) + toast.success('Saved') + setEditing(false) + }) + } + + return ( + + + + + {meta.title} + {!editing && ( + + )} + + + + {editing ? ( +
+