diff --git a/src/__tests__/admin.test.ts b/src/__tests__/admin.test.ts index eab54d7..7e2a1f6 100644 --- a/src/__tests__/admin.test.ts +++ b/src/__tests__/admin.test.ts @@ -863,3 +863,21 @@ describe("admin music-track catalog", () => { expect(result).toEqual({ success: true }); }); }); + +describe("admin.listReports", () => { + it("includes the reported pet for profile reports so the panel can show its name", async () => { + vi.stubEnv("ADMIN_OWNER_ID", "admin-1"); + const { caller, mockPrisma } = await createAdminCaller(); + const createdAt = new Date("2026-10-05T08:00:00Z"); + mockPrisma.report.findMany.mockResolvedValue([ + { id: "r1", reason: "SPAM", targetPostId: null, targetPetId: "pet-9", createdAt, reporter: { id: "pet-1", name: "Mia" } }, + ]); + mockPrisma.pet.findMany.mockResolvedValue([{ id: "pet-9", name: "Bello", nickname: null, avatarKey: null }]); + + const [report] = await caller.admin.listReports(); + + expect(report!.targetPet).toEqual(expect.objectContaining({ id: "pet-9", name: "Bello" })); + expect(report!.post).toBeNull(); + expect(mockPrisma.post.findMany).not.toHaveBeenCalled(); + }); +}); diff --git a/src/app/p/[secret]/blacklist/page.tsx b/src/app/p/[secret]/blacklist/page.tsx index 089f7f5..d568f68 100644 --- a/src/app/p/[secret]/blacklist/page.tsx +++ b/src/app/p/[secret]/blacklist/page.tsx @@ -7,13 +7,14 @@ import { useTRPC } from "@/trpc/client"; import { Trash2, Loader2, Plus, Ban } from "lucide-react"; import { Button } from "@/components/ui/button"; import { Input } from "@/components/ui/input"; -import { Badge } from "@/components/ui/badge"; +import { AdminPageHeader } from "@/components/admin/AdminSection"; +import { formatEventTime } from "@/lib/admin/format"; import { Select, SelectContent, SelectItem, SelectTrigger, SelectValue } from "@/components/ui/select"; import { Dialog, DialogContent, DialogHeader, DialogTitle, DialogFooter } from "@/components/ui/dialog"; import { BlacklistEntryType } from "@prisma/client"; const TYPE_LABEL: Record = { - WORD: "Word", + WORD: "Wort", LINK: "Link", HASHTAG: "Hashtag", }; @@ -26,6 +27,8 @@ const TYPE_BADGE_CLASS: Record = { type TypeFilter = "all" | BlacklistEntryType; +const FILTER_LABEL: Record = { all: "Alle Arten", ...TYPE_LABEL }; + export default function AdminBlacklistPage() { const trpc = useTRPC(); const qc = useQueryClient(); @@ -84,34 +87,30 @@ export default function AdminBlacklistPage() { if (addedCount > 0) { const suffix = hiddenTotal > 0 - ? ` — ${hiddenTotal} existing post${hiddenTotal !== 1 ? "s" : ""} hidden retroactively.` + ? ` ${hiddenTotal} bestehende${hiddenTotal !== 1 ? "" : "r"} Beitr${hiddenTotal !== 1 ? "äge" : "ag"} nachträglich ausgeblendet.` : ""; - toast.success(`${addedCount} ${addedCount !== 1 ? "entries" : "entry"} added.${suffix}`); + toast.success(`${addedCount} ${addedCount !== 1 ? "Einträge" : "Eintrag"} hinzugefügt.${suffix}`); } if (failedCount > 0) { - toast.error(`${failedCount} ${failedCount !== 1 ? "entries" : "entry"} failed to add.`); + toast.error(`${failedCount} ${failedCount !== 1 ? "Einträge konnten" : "Eintrag konnte"} nicht hinzugefügt werden.`); } } return ( -
-
-

Sperrliste

-

- Words, links, and hashtags that auto-hide matching posts — new posts are checked on - creation, and adding an entry here retroactively scans + hides existing matches. - Separate multiple values with a comma to add them all at once. -

-
+
+ {/* Add entry */} -
+
setNewValue(e.target.value)} onKeyDown={(e) => { if (e.key === "Enter") void handleCreate(); }} - className="flex-1 min-w-[200px] bg-muted border-border text-foreground placeholder:text-muted-foreground" + aria-label="Neue Einträge" + className="h-9 min-w-[200px] flex-1" />
{/* Filter */} @@ -164,25 +164,22 @@ export default function AdminBlacklistPage() {
) : entries.length === 0 ? ( -
+
) : ( -
+
    {entries.map((entry) => ( -
    - +
  • + {TYPE_LABEL[entry.type]} - + {entry.type === "HASHTAG" ? `#${entry.value}` : entry.value} - {new Date(entry.createdAt).toLocaleDateString()} + {formatEventTime(new Date(entry.createdAt))} -
  • + ))} -
+ )} { if (!o) setDeleteTarget(null); }}> - Remove blacklist entry + Eintrag entfernen

- Already-hidden posts stay hidden — this only stops future matches. + Bereits ausgeblendete Beiträge bleiben ausgeblendet – es werden nur künftige Treffer nicht mehr erkannt.

diff --git a/src/app/p/[secret]/lost/page.tsx b/src/app/p/[secret]/lost/page.tsx index bc4505c..795509e 100644 --- a/src/app/p/[secret]/lost/page.tsx +++ b/src/app/p/[secret]/lost/page.tsx @@ -3,11 +3,12 @@ import { useState } from "react"; import Link from "next/link"; import { useMutation, useQuery, useQueryClient } from "@tanstack/react-query"; -import { ExternalLink, Loader2, Mail, MapPin, Phone, Search, Siren, Users } from "lucide-react"; +import { ExternalLink, Loader2, Mail, MapPin, Phone, Search, Users } from "lucide-react"; import { toast } from "sonner"; import { useTRPC } from "@/trpc/client"; import { Button } from "@/components/ui/button"; -import { Badge } from "@/components/ui/badge"; +import { AdminPageHeader } from "@/components/admin/AdminSection"; +import { StatusBadge } from "@/components/admin/StatusBadge"; import { Textarea } from "@/components/ui/textarea"; import { Avatar, AvatarFallback, AvatarImage } from "@/components/ui/avatar"; import { Dialog, DialogContent, DialogDescription, DialogFooter, DialogHeader, DialogTitle } from "@/components/ui/dialog"; @@ -45,30 +46,27 @@ export default function AdminLostReportsPage() { ); return ( -
-
-
-

- - Vermisst -

-

- {data ? `${data.activeCount} aktive Meldung${data.activeCount === 1 ? "" : "en"}` : "Vermisst-Meldungen der Nutzer"} -

-
-
- {(Object.keys(STATUS_LABEL) as Status[]).map((s) => ( - - ))} -
-
+
+ + {(Object.keys(STATUS_LABEL) as Status[]).map((s) => ( + + ))} +
+ } + /> {isLoading ? ( - +
+ +
) : !data?.reports.length ? ( -
+
Keine Meldungen in dieser Ansicht.
@@ -79,7 +77,7 @@ export default function AdminLostReportsPage() { return (
  • @@ -93,9 +91,9 @@ export default function AdminLostReportsPage() {

    gemeldet {DATE_FORMAT.format(new Date(r.createdAt))}

    {isActive ? ( - Aktiv + Aktiv ) : ( - Wieder da · {DATE_FORMAT.format(new Date(r.foundAt!))} + Wieder da · {DATE_FORMAT.format(new Date(r.foundAt!))} )}
  • @@ -108,31 +106,31 @@ export default function AdminLostReportsPage() {
    - - + + {r.notifiedCount} benachrichtigt - + {r.contactPhone && ( - - - Telefon - + + + Telefon angegeben + )} {r.contactEmail && ( - - - E-Mail - + + + E-Mail angegeben + )} - {r.allowIndexing ? "Suchmaschinen erlaubt" : "Nur per Link"} + {r.allowIndexing ? "In Suchmaschinen" : "Nur per Link"}
    -
    +
    Öffentliche Seite @@ -170,7 +168,7 @@ export default function AdminLostReportsPage() { : "Für vergessene Meldungen. Das Abzeichen verschwindet, die öffentliche Seite zeigt „wieder zu Hause“. Es geht keine Entwarnung raus."} -