SkateSpots/src/components/MemberVisibilityControl.tsx
SkateSpots 0b796440f1 feat(i18n): Spanish batch 2 — nav/chrome, events/shops/tours, crews/DMs, settings/auth, wallet/stash/newsletter/stats
~3,470 new keys (5,770 total, EN=ES), ~115 more files wired.
- nav + chromeUi: header, More menu, burger menu, footer, map toolbar widgets
  (sort/trust/weather), spot-type label helper.
- placesUi: events, shops, tours, food. socialUi: crews, DMs, notifications,
  collectives, crew chat/coordinator. settingsUi: settings, auth/signup,
  onboarding, profile edit, legal. walletUi: wallet/Cashu, stash, mints,
  app shop, newsletter, community stats, leaderboards, profile.
- Fixed 738 usages where an agent dropped the socialUi. prefix (caught by check:i18n).
2026-10-02 10:11:06 +07:00

112 lines
3.8 KiB
TypeScript

import { useEffect, useState } from 'react';
import { Loader2, EyeOff, Globe } from 'lucide-react';
import { Card, CardContent } from '@/components/ui/card';
import { Button } from '@/components/ui/button';
import { useCurrentUser } from '@/hooks/useCurrentUser';
import { useCollectiveMembers } from '@/hooks/useCollectiveMembers';
import { useConfirmMembership } from '@/hooks/useMembershipAttestation';
import { useToast } from '@/hooks/useToast';
import { useTranslation } from '@/contexts/language';
import { VisibilityPicker } from '@/components/ConfirmMembershipCard';
import { cn } from '@/lib/utils';
import type { MemberVisibility } from '@/lib/types';
interface Props {
kind: number;
founderPubkey: string;
identifier: string;
collectiveName: string;
claimedMembers: string[];
accentBg?: string;
}
/**
* Shown to a CONFIRMED member so they can change their visibility (public vs
* crew-only) at any time. Re-publishes their kind-1985 attestation with the new
* `visibility` tag. Hidden for the founder, non-members, and pending members
* (those use the ConfirmMembershipCard instead).
*/
export function MemberVisibilityControl({
kind,
founderPubkey,
identifier,
collectiveName,
claimedMembers,
accentBg = 'bg-teal-500 hover:bg-teal-600',
}: Props) {
const { t } = useTranslation();
const { user } = useCurrentUser();
const { toast } = useToast();
const { mutateAsync: republish, isPending } = useConfirmMembership();
const { data: members } = useCollectiveMembers({
kind,
founderPubkey,
identifier,
claimedMembers,
enabled: claimedMembers.length > 0,
});
const me = user?.pubkey.toLowerCase();
const mine = members?.find((m) => m.pubkey === me);
const [visibility, setVisibility] = useState<MemberVisibility>('public');
// Sync local state to the published value once it loads.
useEffect(() => {
if (mine?.visibility) setVisibility(mine.visibility);
}, [mine?.visibility]);
if (!user || !me) return null;
if (me === founderPubkey.toLowerCase()) return null;
// Only for confirmed members.
if (!mine || mine.status !== 'confirmed') return null;
const changed = visibility !== (mine.visibility ?? 'public');
const handleSave = async () => {
try {
await republish({ kind, founderPubkey, identifier, collectiveName, visibility });
toast({
title: t('socialUi.memberVisibility.updatedTitle'),
description:
visibility === 'crew'
? t('socialUi.memberVisibility.updatedCrew')
: t('socialUi.memberVisibility.updatedPublic'),
});
} catch (error) {
toast({
title: t('socialUi.memberVisibility.couldNotUpdate'),
description: error instanceof Error ? error.message : t('common.tryAgain'),
variant: 'destructive',
});
}
};
return (
<Card className="border border-white/10 bg-black/60">
<CardContent className="space-y-3 p-4">
<div className="flex items-center gap-2">
{(mine.visibility ?? 'public') === 'crew' ? (
<EyeOff className="h-4 w-4 text-gray-400" />
) : (
<Globe className="h-4 w-4 text-gray-400" />
)}
<p className="text-xs font-black uppercase tracking-widest text-gray-400">
{t('socialUi.memberVisibility.title')}
</p>
</div>
<VisibilityPicker value={visibility} onChange={setVisibility} accentBg={accentBg.split(' ')[0]} />
{changed && (
<Button
type="button"
onClick={handleSave}
disabled={isPending}
className={cn('w-full gap-2 font-bold', accentBg)}
>
{isPending ? <Loader2 className="h-4 w-4 animate-spin" /> : null}
{t('socialUi.memberVisibility.save')}
</Button>
)}
</CardContent>
</Card>
);
}