member-actions.tsx67 lines · main
1'use client';
2
3import { useState, useTransition } from 'react';
4
5type Role = 'owner' | 'admin' | 'developer' | 'viewer';
6
7interface Props {
8 userId: string;
9 role: Role;
10 onUpdateRole: (userId: string, role: Role) => Promise<void>;
11 onRemove: (userId: string) => Promise<void>;
12}
13
14const ASSIGNABLE: Role[] = ['admin', 'developer', 'viewer'];
15
16export function MemberActions({ userId, role, onUpdateRole, onRemove }: Props) {
17 const [pending, startTransition] = useTransition();
18 const [confirming, setConfirming] = useState(false);
19
20 if (role === 'owner') {
21 return <span className="font-mono text-xs text-[var(--color-text-muted)]">owner</span>;
22 }
23
24 return (
25 <div className="flex items-center gap-2">
26 <select
27 value={role}
28 disabled={pending}
29 onChange={(e) => startTransition(() => onUpdateRole(userId, e.currentTarget.value as Role))}
30 className="rounded-md border border-[var(--color-border)] bg-[var(--color-surface-raised)] px-2 py-1 font-mono text-xs"
31 >
32 {ASSIGNABLE.map((r) => (
33 <option key={r} value={r}>
34 {r}
35 </option>
36 ))}
37 </select>
38 {confirming ? (
39 <>
40 <button
41 type="button"
42 onClick={() => setConfirming(false)}
43 className="font-mono text-xs text-[var(--color-text-muted)]"
44 >
45 cancel
46 </button>
47 <button
48 type="button"
49 disabled={pending}
50 onClick={() => startTransition(() => onRemove(userId))}
51 className="rounded-md border border-red-400 px-2 py-1 font-mono text-xs text-red-400 disabled:opacity-50"
52 >
53 {pending ? 'removing...' : 'confirm remove'}
54 </button>
55 </>
56 ) : (
57 <button
58 type="button"
59 onClick={() => setConfirming(true)}
60 className="rounded-md border border-[var(--color-border)] px-2 py-1 font-mono text-xs text-[var(--color-text-muted)] hover:border-red-400 hover:text-red-400"
61 >
62 remove
63 </button>
64 )}
65 </div>
66 );
67}