~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).
85 lines
3.2 KiB
TypeScript
85 lines
3.2 KiB
TypeScript
import { useMemo } from 'react';
|
|
import { Label } from '@/components/ui/label';
|
|
import { Badge } from '@/components/ui/badge';
|
|
import { Users, X, Link2 } from 'lucide-react';
|
|
import { useCrews } from '@/hooks/useCrews';
|
|
import { useTranslation } from '@/contexts/language';
|
|
import { useCurrentUser } from '@/hooks/useCurrentUser';
|
|
import { SKATE_CREW_KIND } from '@/lib/types';
|
|
import { cn } from '@/lib/utils';
|
|
|
|
interface CrewLinkFieldProps {
|
|
/** Selected crew coordinate `35006:pubkey:d`, or undefined. */
|
|
value?: string;
|
|
onChange: (coord: string | undefined) => void;
|
|
accentText?: string;
|
|
accentBg?: string;
|
|
}
|
|
|
|
/**
|
|
* Lets a Lab-profile owner link ONE of their own crews. Cross-links a collective
|
|
* Lab profile (studio) to its crew so each surfaces the other.
|
|
*/
|
|
export function CrewLinkField({ value, onChange, accentText = 'text-teal-400', accentBg = 'bg-teal-500' }: CrewLinkFieldProps) {
|
|
const { t } = useTranslation();
|
|
const { user } = useCurrentUser();
|
|
const { data: allCrews } = useCrews(200);
|
|
|
|
// Only offer the crews this user founded — you can only vouch a link from a crew you control.
|
|
const myCrews = useMemo(
|
|
() => (allCrews ?? []).filter((c) => c.pubkey === user?.pubkey),
|
|
[allCrews, user?.pubkey]
|
|
);
|
|
|
|
const coordFor = (pubkey: string, identifier: string) => `${SKATE_CREW_KIND}:${pubkey}:${identifier}`;
|
|
|
|
return (
|
|
<div className="space-y-3 rounded-lg border border-gray-700 bg-gray-900/60 p-4">
|
|
<div>
|
|
<Label className={cn('flex items-center gap-2', accentText)}>
|
|
<Link2 className="h-4 w-4" /> {t('socialUi.linkField.title')}
|
|
</Label>
|
|
<p className="mt-1 text-xs text-gray-500">
|
|
{t('socialUi.linkField.description')}{' '}
|
|
<span className="font-bold text-green-400">{t('common.verified')}</span> {t('socialUi.linkField.descriptionRest')}
|
|
</p>
|
|
</div>
|
|
|
|
{myCrews.length === 0 ? (
|
|
<p className="text-xs text-gray-500 italic">
|
|
{t('socialUi.linkField.none')}
|
|
</p>
|
|
) : (
|
|
<div className="flex flex-wrap gap-2">
|
|
{myCrews.map((crew) => {
|
|
const coord = coordFor(crew.pubkey, crew.identifier);
|
|
const selected = value === coord;
|
|
return (
|
|
<button
|
|
key={coord}
|
|
type="button"
|
|
onClick={() => onChange(selected ? undefined : coord)}
|
|
className={cn(
|
|
'inline-flex items-center gap-1.5 rounded-xl border px-3 py-2 text-xs font-bold transition-all active:scale-95',
|
|
selected
|
|
? `${accentBg} border-transparent text-white`
|
|
: 'border-gray-700 bg-gray-800 text-gray-300 hover:bg-gray-700'
|
|
)}
|
|
>
|
|
<Users className="h-3.5 w-3.5" />
|
|
{crew.name}
|
|
{selected && <X className="h-3 w-3 opacity-80" />}
|
|
</button>
|
|
);
|
|
})}
|
|
</div>
|
|
)}
|
|
|
|
{value && !myCrews.some((c) => coordFor(c.pubkey, c.identifier) === value) && (
|
|
<Badge variant="outline" className="gap-1 text-[10px] text-gray-400">
|
|
<Users className="h-3 w-3" /> {t('socialUi.linkField.linkedCrew', { id: value.split(':')[2] })}
|
|
</Badge>
|
|
)}
|
|
</div>
|
|
);
|
|
}
|