import { useState } from 'react';
import { nip19 } from 'nostr-tools';
import { User, Users, X, Plus, ShieldCheck } from 'lucide-react';
import { Button } from '@/components/ui/button';
import { Input } from '@/components/ui/input';
import { Label } from '@/components/ui/label';
import { Avatar, AvatarFallback, AvatarImage } from '@/components/ui/avatar';
import { useAuthor } from '@/hooks/useAuthor';
import { genUserName } from '@/lib/genUserName';
import type { EntityType } from '@/lib/types';
import { cn } from '@/lib/utils';
interface EntityTypeFieldProps {
entityType: EntityType;
onEntityTypeChange: (t: EntityType) => void;
/** Member pubkeys (hex). */
members: string[];
onMembersChange: (members: string[]) => void;
/** Accent color class set for the current category (e.g. red/blue/indigo). */
accentBg?: string;
accentText?: string;
}
function MemberRow({ pubkey, onRemove }: { pubkey: string; onRemove: () => void }) {
const author = useAuthor(pubkey);
const metadata = author.data?.metadata;
const name = metadata?.name ?? genUserName(pubkey);
const npub = nip19.npubEncode(pubkey);
return (
{name.slice(0, 2).toUpperCase()}
{name}
{npub.slice(0, 16)}…
);
}
/**
* Individual / Collective toggle plus, for collectives, a member npub picker.
* Members are stored as hex pubkeys. Membership only becomes "confirmed" once
* each member publishes a NIP-32 attestation with their own key (handled on
* the detail page).
*/
export function EntityTypeField({
entityType,
onEntityTypeChange,
members,
onMembersChange,
accentBg = 'bg-indigo-600',
accentText = 'text-indigo-400',
}: EntityTypeFieldProps) {
const [input, setInput] = useState('');
const [error, setError] = useState(null);
const addMember = () => {
const raw = input.trim();
if (!raw) return;
let hex: string | null = null;
try {
if (raw.startsWith('npub1') || raw.startsWith('nostr:npub1')) {
const decoded = nip19.decode(raw.replace(/^nostr:/, ''));
if (decoded.type === 'npub') hex = decoded.data;
} else if (raw.startsWith('nprofile1')) {
const decoded = nip19.decode(raw);
if (decoded.type === 'nprofile') hex = decoded.data.pubkey;
} else if (/^[0-9a-f]{64}$/i.test(raw)) {
hex = raw.toLowerCase();
}
} catch {
hex = null;
}
if (!hex) {
setError('Enter a valid npub, nprofile, or 64-char hex pubkey.');
return;
}
if (members.includes(hex)) {
setError('That member is already added.');
return;
}
onMembersChange([...members, hex]);
setInput('');
setError(null);
};
return (
{entityType === 'collective' && (
Add the Nostr accounts of your crew. Each member confirms their own
spot with their key — until then they show as “pending”.
{error &&
{error}
}
{members.length > 0 && (
{members.map((pk) => (
onMembersChange(members.filter((m) => m !== pk))} />
))}
)}
Membership is verified per member — nobody can be added to your crew
without confirming from their own account.
)}
);
}