refactor: remove approve/reject, add per-asset Postiz push button

- Remove approve/reject buttons and status badge from asset cards
- Remove bulk approve/reject from gallery toolbar
- Remove status filter dropdown (no longer relevant)
- Add "Postiz" button on each image/video asset card for direct publishing
- Keep bulk "Push to Postiz" for multi-select in toolbar
- Update How This Works guide to remove approve/reject section

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
This commit is contained in:
Trey t
2026-03-23 22:57:57 -05:00
parent cee43980dc
commit b318798ca7
3 changed files with 22 additions and 114 deletions
+1 -9
View File
@@ -35,7 +35,7 @@ export default function GlobalAssetsPage() {
)}
<Dialog open={helpOpen} onOpenChange={setHelpOpen}>
<DialogContent className="max-w-lg">
<DialogContent className="sm:max-w-lg max-h-[85vh] overflow-y-auto">
<DialogHeader>
<DialogTitle>Asset Library Guide</DialogTitle>
<DialogDescription>
@@ -43,14 +43,6 @@ export default function GlobalAssetsPage() {
</DialogDescription>
</DialogHeader>
<div className="space-y-4 text-sm">
<section>
<h3 className="font-semibold mb-1">Review &amp; Approve</h3>
<p className="text-muted-foreground">
Every asset starts as a draft. Use <strong>Approve</strong> or <strong>Reject</strong> to
triage. Select multiple assets with checkboxes for bulk actions.
</p>
</section>
<section>
<h3 className="font-semibold mb-1">Repurpose</h3>
<p className="text-muted-foreground">
+14 -39
View File
@@ -3,7 +3,7 @@
import { useState } from "react";
import { Badge } from "@/components/ui/badge";
import { Button } from "@/components/ui/button";
import { Check, X, Play, Copy, Sparkles } from "lucide-react";
import { Play, Copy, Sparkles, Send } from "lucide-react";
import { RepurposeModal } from "./repurpose-modal";
import { VariationModal } from "./variation-modal";
@@ -24,16 +24,16 @@ interface Asset {
interface AssetCardProps {
asset: Asset;
onStatusChange: (id: string, status: string) => void;
selected?: boolean;
onSelect?: (id: string) => void;
onPushToPostiz?: (assetIds: string[]) => void;
}
export function AssetCard({
asset,
onStatusChange,
selected,
onSelect,
onPushToPostiz,
}: AssetCardProps) {
const [repurposeOpen, setRepurposeOpen] = useState(false);
const [variationOpen, setVariationOpen] = useState(false);
@@ -144,18 +144,6 @@ export function AssetCard({
{asset.dimensions}
</Badge>
)}
<Badge
variant={
asset.status === "approved"
? "default"
: asset.status === "rejected"
? "destructive"
: "secondary"
}
className="text-xs"
>
{asset.status}
</Badge>
</div>
{metadata.caption && (
@@ -184,33 +172,22 @@ export function AssetCard({
)}
</div>
{/* Actions — only for images and videos */}
{(isImage || isVideo) ? (
<div className="space-y-2">
<div className="flex gap-2">
{/* Actions */}
{(isImage || isVideo) && (
<div className="flex gap-2 flex-wrap">
{onPushToPostiz && (
<Button
size="sm"
variant="outline"
className="flex-1 min-w-0 overflow-hidden text-green-600 hover:text-green-700 hover:bg-green-50"
onClick={() => onStatusChange(asset.id, "approved")}
disabled={asset.status === "approved"}
className="flex-1 min-w-0 overflow-hidden"
onClick={() => onPushToPostiz([asset.id])}
>
<Check className="h-3 w-3 shrink-0" />
<span className="truncate">Approve</span>
<Send className="h-3 w-3 shrink-0" />
<span className="truncate">Postiz</span>
</Button>
<Button
size="sm"
variant="outline"
className="flex-1 min-w-0 overflow-hidden text-red-600 hover:text-red-700 hover:bg-red-50"
onClick={() => onStatusChange(asset.id, "rejected")}
disabled={asset.status === "rejected"}
>
<X className="h-3 w-3 shrink-0" />
<span className="truncate">Reject</span>
</Button>
</div>
)}
{isImage && (
<div className="flex gap-2">
<>
<Button
size="sm"
variant="outline"
@@ -229,11 +206,9 @@ export function AssetCard({
<Sparkles className="h-3 w-3 shrink-0" />
<span className="truncate">Variations</span>
</Button>
</div>
</>
)}
</div>
) : (
<p className="text-xs text-muted-foreground text-center">Auto-accepted</p>
)}
</div>
+5 -64
View File
@@ -31,7 +31,6 @@ export function AssetGallery({ campaignId, onPushToPostiz }: AssetGalleryProps)
const [filters, setFilters] = useState({
platform: "all",
type: "all",
status: "all",
});
const [search, setSearch] = useState("");
const [sort, setSort] = useState("newest");
@@ -41,7 +40,6 @@ export function AssetGallery({ campaignId, onPushToPostiz }: AssetGalleryProps)
if (campaignId) params.set("campaignId", campaignId);
if (filters.platform !== "all") params.set("platform", filters.platform);
if (filters.type !== "all") params.set("type", filters.type);
if (filters.status !== "all") params.set("status", filters.status);
if (search) params.set("search", search);
fetch(`/api/assets?${params}`)
@@ -54,17 +52,6 @@ export function AssetGallery({ campaignId, onPushToPostiz }: AssetGalleryProps)
fetchAssets();
}, [fetchAssets]);
async function handleStatusChange(id: string, status: string) {
await fetch(`/api/assets/${id}`, {
method: "PATCH",
headers: { "Content-Type": "application/json" },
body: JSON.stringify({ status }),
});
setAssets((prev) =>
prev.map((a) => (a.id === id ? { ...a, status } : a))
);
}
function toggleSelect(id: string) {
setSelectedIds((prev) => {
const next = new Set(prev);
@@ -74,22 +61,6 @@ export function AssetGallery({ campaignId, onPushToPostiz }: AssetGalleryProps)
});
}
async function bulkUpdateStatus(status: string) {
await Promise.all(
Array.from(selectedIds).map((id) =>
fetch(`/api/assets/${id}`, {
method: "PATCH",
headers: { "Content-Type": "application/json" },
body: JSON.stringify({ status }),
})
)
);
setAssets((prev) =>
prev.map((a) => (selectedIds.has(a.id) ? { ...a, status } : a))
);
setSelectedIds(new Set());
}
const sortedAssets = [...assets].sort((a, b) => {
switch (sort) {
case "oldest":
@@ -139,20 +110,6 @@ export function AssetGallery({ campaignId, onPushToPostiz }: AssetGalleryProps)
<option value="script">Scripts</option>
</select>
<select
className="h-9 rounded-md border px-3 text-sm"
value={filters.status}
onChange={(e) =>
setFilters((f) => ({ ...f, status: e.target.value }))
}
>
<option value="all">All Status</option>
<option value="draft">Draft</option>
<option value="approved">Approved</option>
<option value="rejected">Rejected</option>
<option value="published">Published</option>
</select>
<select
className="h-9 rounded-md border px-3 text-sm"
value={sort}
@@ -160,8 +117,8 @@ export function AssetGallery({ campaignId, onPushToPostiz }: AssetGalleryProps)
>
<option value="newest">Newest First</option>
<option value="oldest">Oldest First</option>
<option value="name-asc">Name AZ</option>
<option value="name-desc">Name ZA</option>
<option value="name-asc">Name A-Z</option>
<option value="name-desc">Name Z-A</option>
<option value="platform">Platform</option>
<option value="type">Type</option>
</select>
@@ -173,30 +130,14 @@ export function AssetGallery({ campaignId, onPushToPostiz }: AssetGalleryProps)
className="h-9 w-48"
/>
{selectedIds.size > 0 && (
<div className="flex gap-2 ml-auto">
<Button
size="sm"
variant="outline"
onClick={() => bulkUpdateStatus("approved")}
>
Approve ({selectedIds.size})
</Button>
<Button
size="sm"
variant="outline"
onClick={() => bulkUpdateStatus("rejected")}
>
Reject ({selectedIds.size})
</Button>
{onPushToPostiz && (
{selectedIds.size > 0 && onPushToPostiz && (
<div className="ml-auto">
<Button
size="sm"
onClick={() => onPushToPostiz(Array.from(selectedIds))}
>
Push to Postiz ({selectedIds.size})
</Button>
)}
</div>
)}
</div>
@@ -212,9 +153,9 @@ export function AssetGallery({ campaignId, onPushToPostiz }: AssetGalleryProps)
<AssetCard
key={asset.id}
asset={asset}
onStatusChange={handleStatusChange}
selected={selectedIds.has(asset.id)}
onSelect={toggleSelect}
onPushToPostiz={onPushToPostiz}
/>
))}
</div>