Review page: add upload zone when latest revision has no asset

Surface the AssetUploadZone directly on the deliverable review page
when the latest revision is asset-less, plus a "Replace asset" toggle
when one is already uploaded. CLIENT_VIEWER stays read-only.

Closes the gap where producers were stuck on "No asset uploaded yet
for V0.2" with no way to upload from this page (they had to go back
to the deliverable detail panel).

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
This commit is contained in:
DJP 2026-05-12 20:35:34 -04:00
parent 45dfdcad23
commit 98ebe4796d

View file

@ -1,13 +1,16 @@
"use client";
import { use } from "react";
import { use, useState } from "react";
import Link from "next/link";
import { ArrowLeft, AlertCircle } from "lucide-react";
import { ArrowLeft } from "lucide-react";
import { useQueryClient } from "@tanstack/react-query";
import { useDeliverable } from "@/hooks/use-deliverables";
import { useRevisions } from "@/hooks/use-revisions";
import { useCurrentUser } from "@/hooks/use-current-user";
import { formatRevisionLabel } from "@/lib/format-revision-label";
import { Button } from "@/components/ui/button";
import { Badge } from "@/components/ui/badge";
import { AssetUploadZone } from "@/components/review/asset-upload-zone";
// Phase 5 transitional page. The HP-style annotation surface is being
// rebuilt against the single-asset revision model. For now, this page
@ -46,6 +49,17 @@ export default function DeliverableReviewPage({ params, searchParams }: PageProp
const revisions = (revData as RevisionLike[] | undefined) ?? [];
const latest = revisions[0];
const { data: me } = useCurrentUser();
const canEdit = !!me && (me as any).role !== "CLIENT_VIEWER";
const queryClient = useQueryClient();
const [showReplace, setShowReplace] = useState(false);
const refreshRevisions = () => {
queryClient.invalidateQueries({ queryKey: ["revisions", stageId] });
queryClient.invalidateQueries({ queryKey: ["deliverable", projectId, deliverableId] });
setShowReplace(false);
};
return (
<div className="flex flex-col gap-4 p-6">
<Link
@ -70,21 +84,28 @@ export default function DeliverableReviewPage({ params, searchParams }: PageProp
</div>
{!latest ? (
<div className="flex items-start gap-2 rounded-md border border-dashed border-[var(--border)] bg-[var(--accent)]/30 p-4 text-sm">
<AlertCircle className="mt-0.5 h-4 w-4 shrink-0 text-[var(--muted-foreground)]" />
<div>
No revision yet on this stage. Open the deliverable page and use
the in-row review panel to create one.
</div>
<div className="rounded-md border border-dashed border-[var(--border)] bg-[var(--accent)]/30 p-4 text-sm text-[var(--muted-foreground)]">
No revision yet on this stage. Open the deliverable page and use
the in-row review panel to create one.
</div>
) : (
<div className="flex flex-col gap-3">
<div className="flex items-center gap-2">
<Badge variant="outline">{formatRevisionLabel(latest)}</Badge>
<Badge variant="outline">{latest.status}</Badge>
{canEdit && latest.asset && !showReplace && (
<Button
variant="outline"
size="sm"
className="ml-auto h-7"
onClick={() => setShowReplace(true)}
>
Replace asset
</Button>
)}
</div>
{latest.asset ? (
{latest.asset && !showReplace ? (
<div className="rounded-md border bg-[var(--card)] p-2">
{latest.asset.kind === "image" ? (
/* eslint-disable-next-line @next/next/no-img-element */
@ -101,6 +122,23 @@ export default function DeliverableReviewPage({ params, searchParams }: PageProp
/>
)}
</div>
) : canEdit ? (
<div className="flex flex-col gap-2">
<AssetUploadZone
stageId={stageId}
revisionId={latest.id}
onUploaded={refreshRevisions}
/>
{showReplace && (
<button
type="button"
className="self-start text-[12px] text-[var(--muted-foreground)] underline-offset-2 hover:underline"
onClick={() => setShowReplace(false)}
>
Cancel
</button>
)}
</div>
) : (
<div className="rounded-md border border-dashed border-[var(--border)] bg-[var(--accent)]/30 p-4 text-sm text-[var(--muted-foreground)]">
No asset uploaded yet for {formatRevisionLabel(latest)}.
@ -117,9 +155,9 @@ export default function DeliverableReviewPage({ params, searchParams }: PageProp
<p className="text-[12px] italic text-[var(--muted-foreground)]">
Full annotation + comparison surface is being rebuilt against the
new single-asset revision model. For now, all review actions
(Approve, Request changes, Send to client, New revision) live in
the in-row panel on the deliverable detail page.
new single-asset revision model. For now, the in-row panel on the
deliverable detail page is where Approve / Request changes / Send
to client / New revision live.
</p>
</div>
)}