feat: cases (reports, rating reviews, removal and deletion requests, appeals) and notifications
Cases are one queue for what someone has to decide, each with a thread (internal notes for those deciding), an assignee and a decision. Reports about a product go to the teams of the catalogs hosting it and to moderators, and only the reporter and moderators know who reported. A creator lowering a rating opens a rating review; a catalog's owner asking to delete it opens a deletion request; a catalog's team can ask for a product's removal everywhere; the person an action affected can appeal it once, decided by another moderator (an admin may decide their own, flagged). Resolving carries the case out: the rating is lowered, the product removed, the catalog deleted. Notifications reach people on the website (a bell with the unread count) and their proxies (realtime): moderators' edits to their products, removals and restores, decisions and replies on their cases, membership decisions. Also: moderators can restore a removed product, and a product's creator and moderators can see it when it's adult without an adult account (getting and wearing it still need one). Co-Authored-By: Claude Opus 5.5 <[email protected]>
This commit is contained in:
1 parent
e07a6c6dac
commit
8af4b7b6a9
33 files changed
+5228
-44
No files matched your search
@@ -8,7 +8,9 @@ import { Card, CardContent } from "@/components/ui/card";
|
||||
import { Input } from "@/components/ui/input";
|
||||
import { NativeSelect, NativeSelectOption } from "@/components/ui/native-select";
|
||||
import { Table, TableBody, TableCell, TableHead, TableHeader, TableRow } from "@/components/ui/table";
|
||||
import { ActionForm, SubmitButton } from "@/components/forms";
|
||||
import { api, unwrap } from "@/lib/api";
|
||||
import { restoreProduct } from "../../products/actions";
|
||||
import { ConsoleNav, requireConsole } from "../console-nav";
|
||||
|
||||
export const metadata: Metadata = { title: "Products" };
|
||||
@@ -112,10 +114,21 @@ export default async function ConsoleProducts({ searchParams }: { searchParams:
|
||||
<DateTime iso={p.updatedAt} dateOnly />
|
||||
</TableCell>
|
||||
<TableCell className="text-right">
|
||||
{!p.removed && (
|
||||
{!p.removed ? (
|
||||
<Link href={`/products/${p.id}/edit`} className={buttonVariants({ size: "sm", variant: "outline" })}>
|
||||
{p.status === "draft" ? "Review" : "Edit"}
|
||||
</Link>
|
||||
) : (
|
||||
data.moderator && (
|
||||
<ActionForm action={restoreProduct} fields={{ id: p.id }}>
|
||||
<div className="flex justify-end gap-2">
|
||||
<Input name="reason" placeholder="Reason" aria-label="Reason" maxLength={500} className="h-8 w-36" />
|
||||
<SubmitButton size="sm" variant="outline" pendingLabel="Restoring…" confirm={`Restore ${p.name}?`}>
|
||||
Restore
|
||||
</SubmitButton>
|
||||
</div>
|
||||
</ActionForm>
|
||||
)
|
||||
)}
|
||||
</TableCell>
|
||||
</TableRow>
|
||||
|
||||
Reference in new issue
Block a user