Skip to content

Where people and agents ship software together. The open-source git platform for the whole job: issues, agents, checks and deploys to the edge.

g1t.sh
87 linesCodeBlameRaw
1import { Form, data, redirect } from "react-router";
2
3import type { Route } from "./+types/blocked-domains";
4import { Button, EmptyState, Field, Input, Notice, PageHeader, Section, When } from "~/components/ui";
5import { text } from "~/lib/forms";
6import { identity } from "~/lib/services.server";
7import { settle } from "~/lib/settle";
8import { requireStaff } from "~/lib/staff";
9import { parseReason } from "~/lib/suspension";
10
11export const meta: Route.MetaFunction = () => [{ title: "Blocked domains · sudo" }, { name: "robots", content: "noindex, nofollow" }];
12
13export async function loader({ request, context }: Route.LoaderArgs) {
14 requireStaff(context);
15 const domains = await settle(identity.blockedDomains());
16 const done = new URL(request.url).searchParams.get("done");
17 return {
18 domains: domains.ok ? domains.value : [],
19 error: domains.ok ? null : domains.error,
20 done: done === "blocked" ? "Blocked. No new account can sign up with an address there." : done === "unblocked" ? "Unblocked." : null,
21 };
22}
23
24export async function action({ request, context }: Route.ActionArgs) {
25 const staff = requireStaff(context);
26 const form = await request.formData();
27 const block = text(form, "intent") === "block";
28 const reason = block ? parseReason(text(form, "reason")) : { ok: true as const, value: "" };
29 if (!reason.ok) return data({ error: reason.error }, { status: 422 });
30 const result = await identity.blockDomain(text(form, "domain"), block, reason.value, staff.email);
31 if (!result.ok) return data({ error: result.error.message }, { status: 422 });
32 throw redirect(`/blocked-domains?done=${block ? "blocked" : "unblocked"}`);
33}
34
35export default function BlockedDomains({ loaderData, actionData }: Route.ComponentProps) {
36 const { domains, error, done } = loaderData;
37 return (
38 <main className="mx-auto max-w-4xl px-4 py-8 sm:py-10">
39 <PageHeader
40 title="Blocked domains"
41 description="Email domains no new account may sign up with, with a password or GitHub, and that no account may add. Each domain's subdomains are blocked too. Accounts already there are not touched: suspend them one at a time from their page."
42 />
43 <div className="mt-5 space-y-3 empty:hidden">
44 {done && <Notice tone="ok">{done}</Notice>}
45 {error && <Notice tone="warn">Identity did not answer: {error}</Notice>}
46 {actionData && "error" in actionData && <Notice tone="error">{actionData.error}</Notice>}
47 </div>
48 <Section id="block" title="Block a domain" className="mt-6">
49 <Form method="post" className="grid gap-3 sm:grid-cols-[minmax(0,1fr)_minmax(0,2fr)_auto] sm:items-end">
50 <Field label="Domain">
51 <Input name="domain" required maxLength={253} placeholder="spam.example" spellCheck={false} autoCapitalize="none" />
52 </Field>
53 <Field label="Why">
54 <Input name="reason" required maxLength={500} placeholder="e.g. Throwaway addresses used for sign-up abuse" />
55 </Field>
56 <Button type="submit" name="intent" value="block" variant="danger">
57 Block
58 </Button>
59 </Form>
60 </Section>
61 <Section id="list" title="Blocked" className="mt-6">
62 {domains.length === 0 ? (
63 <EmptyState title="None blocked">Every domain can sign up.</EmptyState>
64 ) : (
65 <ul className="space-y-2">
66 {domains.map((domain) => (
67 <li key={domain.domain} className="flex flex-wrap items-center justify-between gap-2 rounded-lg border border-line bg-surface px-3 py-2 text-sm">
68 <span className="min-w-0">
69 <span className="font-mono">{domain.domain}</span>
70 <span className="block text-xs text-faint">
71 {domain.reason} · {domain.blockedBy} · <When at={domain.createdAt} />
72 </span>
73 </span>
74 <Form method="post">
75 <input type="hidden" name="domain" value={domain.domain} />
76 <Button type="submit" name="intent" value="unblock" variant="quiet">
77 Unblock
78 </Button>
79 </Form>
80 </li>
81 ))}
82 </ul>
83 )}
84 </Section>
85 </main>
86 );
87}