TypeScript 97.5%
SQL 1.4%
Python 0.8%
1"use client";2import * as React from "react";3import { useRouter } from "next/navigation";4import { Check, Plus, Undo2 } from "lucide-react";5import { recordAbuseEvent, resolveAbuseEvent } from "@/actions/admin";6import { Button } from "@/components/ui/button";7import { Input, NativeSelect, Textarea } from "@/components/ui/input";8import { Field, Hint, Label } from "@/components/ui/label";9import { Dialog, DialogContent, DialogDescription, DialogFooter, DialogHeader, DialogTitle } from "@/components/ui/dialog";10import { ActionButton } from "./action-button";1112export function ResolveAbuseButton({ id, resolved }: { id: string; resolved: boolean }) {13 return resolved ? (14 <ActionButton variant="ghost" size="xs" action={() => resolveAbuseEvent(id, false)}>15 <Undo2 className="size-3" /> Reopen16 </ActionButton>17 ) : (18 <ActionButton variant="outline" size="xs" action={() => resolveAbuseEvent(id, true)}>19 <Check className="size-3" /> Resolve20 </ActionButton>21 );22}2324export function RecordAbuseDialog({ defaultOrgId }: { defaultOrgId?: string }) {25 const router = useRouter();26 const [open, setOpen] = React.useState(false);27 const [pending, start] = React.useTransition();28 const [error, setError] = React.useState<string | null>(null);29 const [form, setForm] = React.useState({ kind: "manual", severity: "medium", organizationId: defaultOrgId ?? "", projectId: "", requestId: "", detail: "" });30 const set = (k: keyof typeof form) => (e: React.ChangeEvent<HTMLInputElement | HTMLSelectElement | HTMLTextAreaElement>) => setForm((f) => ({ ...f, [k]: e.target.value }));31 return (32 <>33 <Button variant="primary" size="sm" onClick={() => setOpen(true)}>34 <Plus className="size-3.5" /> Record abuse event35 </Button>36 <Dialog open={open} onOpenChange={setOpen}>37 <DialogContent size="md">38 <DialogHeader>39 <DialogTitle>Record an abuse event</DialogTitle>40 <DialogDescription>Manual entries appear alongside automatically detected events and are linked to the organization when an id is given.</DialogDescription>41 </DialogHeader>42 <form43 className="grid gap-3"44 onSubmit={(e) => {45 e.preventDefault();46 start(async () => {47 setError(null);48 const r = await recordAbuseEvent(form as Parameters<typeof recordAbuseEvent>[0]);49 if (!r.ok) return setError(r.error);50 setOpen(false);51 setForm({ kind: "manual", severity: "medium", organizationId: defaultOrgId ?? "", projectId: "", requestId: "", detail: "" });52 router.refresh();53 });54 }}55 >56 <div className="grid gap-3 sm:grid-cols-2">57 <Field>58 <Label htmlFor="ab-kind">Kind</Label>59 <NativeSelect id="ab-kind" value={form.kind} onChange={set("kind")}>60 <option value="manual">manual</option>61 <option value="ssrf_attempt">ssrf_attempt</option>62 <option value="prohibited_target">prohibited_target</option>63 <option value="rate_abuse">rate_abuse</option>64 <option value="credential_abuse">credential_abuse</option>65 </NativeSelect>66 </Field>67 <Field>68 <Label htmlFor="ab-sev">Severity</Label>69 <NativeSelect id="ab-sev" value={form.severity} onChange={set("severity")}>70 <option value="low">low</option>71 <option value="medium">medium</option>72 <option value="high">high</option>73 <option value="critical">critical</option>74 </NativeSelect>75 </Field>76 </div>77 <Field>78 <Label htmlFor="ab-org">Organization id</Label>79 <Input id="ab-org" value={form.organizationId} onChange={set("organizationId")} placeholder="org_…" className="font-mono" />80 </Field>81 <div className="grid gap-3 sm:grid-cols-2">82 <Field>83 <Label htmlFor="ab-proj">Project id</Label>84 <Input id="ab-proj" value={form.projectId} onChange={set("projectId")} placeholder="proj_… (optional)" className="font-mono" />85 </Field>86 <Field>87 <Label htmlFor="ab-req">Request id</Label>88 <Input id="ab-req" value={form.requestId} onChange={set("requestId")} placeholder="req_… (optional)" className="font-mono" />89 </Field>90 </div>91 <Field>92 <Label htmlFor="ab-detail">Detail</Label>93 <Textarea id="ab-detail" value={form.detail} onChange={set("detail")} placeholder="What happened, evidence, links to requests…" required />94 <Hint>Internal only.</Hint>95 </Field>96 {error ? (97 <p role="alert" className="text-[13px] text-danger">98 {error}99 </p>100 ) : null}101 <DialogFooter>102 <Button type="button" variant="outline" onClick={() => setOpen(false)}>103 Cancel104 </Button>105 <Button type="submit" variant="primary" loading={pending}>106 Record107 </Button>108 </DialogFooter>109 </form>110 </DialogContent>111 </Dialog>112 </>113 );114}115