- SkateCrew: add nip05 + admins fields; parse `admin` role marker on p tags (admins = subset of members) - CrewForm: NIP-05 input + per-member "Make admin" toggle; write nip05 tag + admin-marked p tags (works on create & edit via shared form) - useCrewNip05: verify crew handle on ANY domain against the founder pubkey (verified/mismatch/unreachable), CORS-proxy fallback + caching - CrewNip05Badge: green ✓ / red ⚠ / muted handle states with tooltips - CollectiveMembers: show Admin chips, sort admins first, admin count in summary - useCrewRole: centralized founder/admin/member/none roles (canManage, canEditProfile) — founder owns identity, admins get delegated powers with their own keys; egalitarian shared-editing intentionally unsupported - CrewDetailPage: NIP-05 badge by crew name, viewer role badges (admin/member) - NIP.md: document crew nip05 verification, admin role marker, and governance/conflict-resolution model Co-authored-by: shakespeare.diy <assistant@shakespeare.diy>
91 lines
2.7 KiB
TypeScript
91 lines
2.7 KiB
TypeScript
import { BadgeCheck, Loader2, AlertTriangle } from 'lucide-react';
|
|
import {
|
|
Tooltip,
|
|
TooltipContent,
|
|
TooltipProvider,
|
|
TooltipTrigger,
|
|
} from '@/components/ui/tooltip';
|
|
import { useCrewNip05 } from '@/hooks/useCrewNip05';
|
|
import { cn } from '@/lib/utils';
|
|
|
|
interface CrewNip05BadgeProps {
|
|
nip05?: string;
|
|
founderPubkey?: string;
|
|
className?: string;
|
|
}
|
|
|
|
/**
|
|
* Verified-handle badge for a crew's optional NIP-05.
|
|
*
|
|
* - `verified`: green check + the handle. The domain confirms the founder key.
|
|
* - `mismatch`: red warning — the domain points the handle at a different key.
|
|
* - `unreachable`: muted handle (shown but unverified) while the domain is down.
|
|
* - `none`: renders nothing.
|
|
*/
|
|
export function CrewNip05Badge({ nip05, founderPubkey, className }: CrewNip05BadgeProps) {
|
|
const { data, isLoading } = useCrewNip05(nip05, founderPubkey);
|
|
|
|
if (!nip05) return null;
|
|
|
|
const status = data?.status ?? (isLoading ? 'loading' : 'unreachable');
|
|
const handle = nip05.trim().toLowerCase();
|
|
|
|
if (status === 'mismatch') {
|
|
return (
|
|
<TooltipProvider>
|
|
<Tooltip>
|
|
<TooltipTrigger asChild>
|
|
<span
|
|
className={cn(
|
|
'inline-flex items-center gap-1 rounded-full bg-red-500/15 px-2 py-0.5 text-xs font-bold text-red-400',
|
|
className,
|
|
)}
|
|
>
|
|
<AlertTriangle className="h-3 w-3" /> {handle}
|
|
</span>
|
|
</TooltipTrigger>
|
|
<TooltipContent>
|
|
This domain points the handle at a different Nostr key than the crew founder. Not verified.
|
|
</TooltipContent>
|
|
</Tooltip>
|
|
</TooltipProvider>
|
|
);
|
|
}
|
|
|
|
if (status === 'verified') {
|
|
return (
|
|
<TooltipProvider>
|
|
<Tooltip>
|
|
<TooltipTrigger asChild>
|
|
<span
|
|
className={cn(
|
|
'inline-flex items-center gap-1 rounded-full bg-teal-500/15 px-2 py-0.5 text-xs font-bold text-teal-300',
|
|
className,
|
|
)}
|
|
>
|
|
<BadgeCheck className="h-3 w-3" /> {handle}
|
|
</span>
|
|
</TooltipTrigger>
|
|
<TooltipContent>
|
|
Verified — the domain confirms this handle belongs to the crew's founder key.
|
|
</TooltipContent>
|
|
</Tooltip>
|
|
</TooltipProvider>
|
|
);
|
|
}
|
|
|
|
// loading / unreachable — show the handle, muted.
|
|
return (
|
|
<span
|
|
className={cn(
|
|
'inline-flex items-center gap-1 rounded-full bg-white/5 px-2 py-0.5 text-xs font-medium text-gray-400',
|
|
className,
|
|
)}
|
|
>
|
|
{status === 'loading' ? (
|
|
<Loader2 className="h-3 w-3 animate-spin" />
|
|
) : null}
|
|
{handle}
|
|
</span>
|
|
);
|
|
}
|