feat: disciple link

This commit is contained in:
gosangam
2026-08-07 22:46:41 +05:30
parent 5c9a689306
commit 7ff5610477
3 changed files with 332 additions and 0 deletions
+52
View File
@@ -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>
);
}