Organize/components/profile/pending-link-requests.tsx

158 lines
5.5 KiB
TypeScript

"use client";
import { useState } from "react";
import { toast } from "sonner";
import { Check, Loader2, ShieldBan, X } from "lucide-react";
import { Button } from "@/components/ui/button";
import {
Card,
CardContent,
CardDescription,
CardHeader,
CardTitle,
} from "@/components/ui/card";
import { ConfirmDeleteDialog } from "@/components/confirm-delete-dialog";
import { AccountAvatar } from "@/components/profile/account-avatar";
import {
createAccountLink,
denyAccountLink,
denyAndBlockAccountLink,
} from "@/lib/actions/account-links";
import type { PendingLinkRequestDTO } from "@/types/profile";
type DenyIntent = { request: PendingLinkRequestDTO; block: boolean };
/**
* "Requested Account Link" section: link requests this account received.
* Only the recipient acts on these -- the requester just sees
* "awaiting confirmation" on their own Profile page.
*/
export function PendingLinkRequests({ requests }: { requests: PendingLinkRequestDTO[] }) {
// Which request row (if any) is currently processing a button press.
const [busyRequestId, setBusyRequestId] = useState<string | null>(null);
const [denyIntent, setDenyIntent] = useState<DenyIntent | null>(null);
/** Runs a request action, toasting its error (if any) and reporting
* success so the caller can toast a specific confirmation. */
async function run(requestId: string, action: () => Promise<{ error?: string } | undefined>) {
setBusyRequestId(requestId);
let failed = false;
try {
const result = await action();
if (result?.error) {
toast.error(result.error);
failed = true;
}
} catch {
toast.error("That didn't work. Try again.");
failed = true;
} finally {
setBusyRequestId(null);
}
return !failed;
}
async function handleConfirmLink(request: PendingLinkRequestDTO) {
const ok = await run(request.requestId, () => createAccountLink(request.requestId));
if (ok) toast.success("Account link created.");
}
async function handleDeny() {
if (!denyIntent) return;
const { request, block } = denyIntent;
setDenyIntent(null);
const ok = await run(
request.requestId,
block
? () => denyAndBlockAccountLink(request.requestId)
: () => denyAccountLink(request.requestId)
);
if (!ok) return;
toast.success(
block
? "Request denied — that account can't send link requests to you anymore."
: "Request denied."
);
}
return (
<Card>
<CardHeader>
<CardTitle>Requested Account Link</CardTitle>
<CardDescription>
These accounts asked to link with this one. Linking lets each
account toggle the other in.
</CardDescription>
</CardHeader>
<CardContent className="flex flex-col gap-3">
{requests.map((request) => {
const busy = busyRequestId === request.requestId;
return (
<div
key={request.requestId}
className="flex flex-col gap-3 rounded-lg border p-3 sm:flex-row sm:items-center"
>
<div className="flex min-w-0 flex-1 items-center gap-3">
<AccountAvatar account={request} sizeClass="size-9" />
<div className="min-w-0">
<p className="truncate text-sm font-medium">{request.email}</p>
<p className="truncate text-xs text-muted-foreground">
{request.name ? `${request.name} · ` : ""}
requested {request.requestedAtLabel}
</p>
</div>
</div>
<div className="flex flex-wrap items-center gap-2">
<Button
size="sm"
onClick={() => handleConfirmLink(request)}
disabled={busyRequestId !== null}
>
{busy ? (
<Loader2 className="size-3.5 animate-spin" />
) : (
<Check className="size-3.5" />
)}
Create Account Link
</Button>
<Button
variant="outline"
size="sm"
disabled={busyRequestId !== null}
onClick={() => setDenyIntent({ request, block: false })}
>
<X className="size-3.5" />
Deny Account Link
</Button>
<Button
variant="destructive"
size="sm"
disabled={busyRequestId !== null}
onClick={() => setDenyIntent({ request, block: true })}
>
<ShieldBan className="size-3.5" />
Deny and Block Account Link
</Button>
</div>
</div>
);
})}
</CardContent>
<ConfirmDeleteDialog
open={denyIntent !== null}
onOpenChange={(open) => !open && setDenyIntent(null)}
title={denyIntent?.block ? "Deny and block this account?" : "Deny this request?"}
description={
denyIntent?.block
? `Deny the link request from ${denyIntent?.request?.email} and block that account from sending any more link requests to this one. You can lift the block later from the Blocked Account Link Requests section.`
: `Deny the link request from ${denyIntent?.request?.email}. They can request again later unless you block them.`
}
confirmLabel={denyIntent?.block ? "Deny and Block" : "Deny"}
onConfirm={handleDeny}
/>
</Card>
);
}