SkateSpots/src/components/RequestToJoinCollective.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

129 lines
4.9 KiB
TypeScript

import { useState } from 'react';
import { Card, CardContent } from '@/components/ui/card';
import { Button } from '@/components/ui/button';
import { Textarea } from '@/components/ui/textarea';
import { useCurrentUser } from '@/hooks/useCurrentUser';
import { useToast } from '@/hooks/useToast';
import { useTranslation } from '@/contexts/language';
import {
useSendCollectiveJoinRequest,
useMyCollectiveJoinRequest,
} from '@/hooks/useCollectiveJoinRequests';
import type { LabProfile } from '@/hooks/useLabProfiles';
import { Users, Hand, Loader2, Check, X } from 'lucide-react';
/**
* "Request to join this collective" — shown on a Lab collective's profile to
* logged-in skaters who aren't the owner and aren't already listed. Mirrors the
* crew join-request: it notifies the owner (who adds the member through their
* own editor). Owners never see it on their own profile.
*/
export function RequestToJoinCollective({
profile,
accentText = 'text-teal-400',
accentBorder = 'border-teal-500',
}: {
profile: LabProfile;
accentText?: string;
accentBorder?: string;
}) {
const { t } = useTranslation();
const { user } = useCurrentUser();
const { toast } = useToast();
const [open, setOpen] = useState(false);
const [note, setNote] = useState('');
const coord = `${profile.event.kind}:${profile.pubkey}:${profile.identifier}`;
const { mutateAsync: send, isPending: sending } = useSendCollectiveJoinRequest();
const { data: alreadyRequested, isLoading: checking } = useMyCollectiveJoinRequest(coord);
// Gate: must be logged in, not the owner, not already a listed member.
if (!user) return null;
const meLc = user.pubkey.toLowerCase();
if (meLc === profile.pubkey.toLowerCase()) return null;
if (profile.members.some((m) => m.toLowerCase() === meLc)) return null;
const handleSend = async () => {
try {
await send({ profile, note });
setOpen(false);
setNote('');
toast({
title: t('socialUi.requestCollective.sentTitle'),
description: t('socialUi.requestCollective.sentDesc', { name: profile.title }),
});
} catch (err) {
toast({
title: t('socialUi.requestCollective.couldNotSend'),
description: err instanceof Error ? err.message : t('common.tryAgain'),
variant: 'destructive',
});
}
};
return (
<Card className={`bg-black/90 border-2 ${accentBorder}`}>
<CardContent className="pt-6">
<div className="flex items-center gap-2 mb-3">
<Users className={`h-5 w-5 ${accentText}`} />
<h3 className="text-lg text-white uppercase font-black">{t('socialUi.requestCollective.title')}</h3>
</div>
{alreadyRequested ? (
<div className="flex items-center gap-2 rounded-lg border border-emerald-500/30 bg-emerald-500/10 px-3 py-2.5 text-sm text-emerald-300">
<Check className="h-4 w-4" />
{t('socialUi.requestCollective.waiting', { name: profile.title })}
</div>
) : !open ? (
<>
<p className="text-sm text-gray-400 mb-3">
{t('socialUi.requestCollective.askA')}{' '}
<span className="font-bold text-white">{profile.title}</span>{' '}
{t('socialUi.requestCollective.askB')}
</p>
<Button
onClick={() => setOpen(true)}
disabled={checking}
className="w-full gap-2 bg-white text-black font-black hover:bg-gray-200"
>
<Hand className="h-4 w-4" />
{t('socialUi.requestToJoin.trigger')}
</Button>
</>
) : (
<div className="space-y-3">
<Textarea
value={note}
onChange={(e) => setNote(e.target.value.slice(0, 500))}
placeholder={t('socialUi.requestCollective.placeholder')}
rows={3}
className="resize-none border-gray-700 bg-black/60 text-white placeholder:text-gray-600"
/>
<div className="flex items-center gap-2">
<Button
onClick={handleSend}
disabled={sending}
className="flex-1 gap-2 bg-white text-black font-black hover:bg-gray-200"
>
{sending ? <Loader2 className="h-4 w-4 animate-spin" /> : <Hand className="h-4 w-4" />}
{t('socialUi.requestCollective.sendButton')}
</Button>
<Button
variant="outline"
onClick={() => {
setOpen(false);
setNote('');
}}
disabled={sending}
className="gap-1.5 border-gray-700 text-gray-400 hover:bg-white/10 hover:text-white"
>
<X className="h-4 w-4" />
{t('common.cancel')}
</Button>
</div>
</div>
)}
</CardContent>
</Card>
);
}