feat: disciple link
This commit is contained in:
@@ -20,6 +20,7 @@ import { Avatar } from "@/components/ui/avatar";
|
||||
import { useConfirm } from "@/components/confirm";
|
||||
import { Input, Textarea } from "@/components/ui/input";
|
||||
import { fmtDate } from "@/lib/utils";
|
||||
import { DiscipleLinkModal } from "./disciple-link-modal";
|
||||
|
||||
export default function UsersPage() {
|
||||
const [q, setQ] = useState("");
|
||||
@@ -31,6 +32,7 @@ export default function UsersPage() {
|
||||
const confirm = useConfirm();
|
||||
const [notify, setNotify] = useState<AdminUser | null>(null);
|
||||
const [limitsUser, setLimitsUser] = useState<AdminUser | null>(null);
|
||||
const [discipleUser, setDiscipleUser] = useState<AdminUser | null>(null);
|
||||
|
||||
async function toggleBan(u: AdminUser) {
|
||||
const banning = !u.banned_at;
|
||||
@@ -54,11 +56,42 @@ export default function UsersPage() {
|
||||
}
|
||||
}
|
||||
|
||||
async function removeDiscipleLink(u: AdminUser) {
|
||||
if (
|
||||
!(await confirm({
|
||||
title: `Remove disciple link from @${u.handle}?`,
|
||||
message:
|
||||
"Their profile badge and the highlight in the disciples directory will be removed. You can re-link them at any time.",
|
||||
confirmLabel: "Remove link",
|
||||
destructive: true,
|
||||
}))
|
||||
) {
|
||||
return;
|
||||
}
|
||||
try {
|
||||
await api.unlinkDisciple(u.id);
|
||||
toast.success("Disciple link removed.");
|
||||
list.refresh();
|
||||
} catch (err) {
|
||||
toast.error(err instanceof AdminError ? err.message : "Action failed.");
|
||||
}
|
||||
}
|
||||
|
||||
function menuItems(u: AdminUser): MenuItem[] {
|
||||
const items: MenuItem[] = [
|
||||
{ label: "Send notification", onClick: () => setNotify(u) },
|
||||
{ label: "Edit limits", onClick: () => setLimitsUser(u) },
|
||||
{
|
||||
label: u.disciple_id ? "Change disciple link" : "Link direct disciple",
|
||||
onClick: () => setDiscipleUser(u),
|
||||
},
|
||||
];
|
||||
if (u.disciple_id) {
|
||||
items.push({
|
||||
label: "Remove disciple link",
|
||||
onClick: () => removeDiscipleLink(u),
|
||||
});
|
||||
}
|
||||
// No ban option for fellow admins (avoid locking each other out).
|
||||
if (u.role !== "admin") {
|
||||
items.push({
|
||||
@@ -109,6 +142,20 @@ export default function UsersPage() {
|
||||
<div className="text-xs text-muted-foreground">
|
||||
@{u.handle}
|
||||
</div>
|
||||
{u.disciple_id ? (
|
||||
<div
|
||||
className="mt-1 inline-flex max-w-full items-center gap-1 rounded bg-primary/10 px-1.5 py-0.5 text-[10px] font-medium text-primary"
|
||||
title={
|
||||
u.disciple_name
|
||||
? `Linked to ${u.disciple_name}`
|
||||
: "Linked to an unknown disciple record"
|
||||
}
|
||||
>
|
||||
<span className="truncate">
|
||||
{u.disciple_name ?? "Direct disciple"}
|
||||
</span>
|
||||
</div>
|
||||
) : null}
|
||||
</div>
|
||||
</div>
|
||||
</TD>
|
||||
@@ -139,6 +186,11 @@ export default function UsersPage() {
|
||||
onClose={() => setLimitsUser(null)}
|
||||
onSaved={list.refresh}
|
||||
/>
|
||||
<DiscipleLinkModal
|
||||
user={discipleUser}
|
||||
onClose={() => setDiscipleUser(null)}
|
||||
onSaved={list.refresh}
|
||||
/>
|
||||
</ListShell>
|
||||
);
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user