From 0548a7a7c6a3ac96e3603766ebb1920b78ba00c3 Mon Sep 17 00:00:00 2001 From: SkateSpots Date: Sat, 3 Oct 2026 18:02:12 +0700 Subject: [PATCH] feat(crew): tidy the create-crew "Extras" step into collapsible groups MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit The Extras step stacked 9 ungrouped blocks. Now: - Identity (NIP-05, links) — open by default. - Sasquatch AI — open when the agent is on. - Showcase (video, sponsor, band). - Payments & treasury (treasury + the CLINK tip jar, moved out of "Links"). - Integrations (Lab link). - Advanced (Coordinator) — collapsed + last, so a new founder isn't asked about experimental plumbing. Other fixes: - The crew AI agent is now labelled "Sasquatch · Pro" (it runs on the same Pro-gated Sasquatch proxy, falling back to a free model), with an honest note. - crew founded year is clamped to 1954..current on save and bounded in the input. - "Skip to end" is no longer hidden on mobile. - Removed the coordinator copy keys left unused by the previous pass. --- src/components/CrewForm.tsx | 114 ++++++++++++++++++++++++++++-------- src/i18n/en.ts | 15 +++-- src/i18n/es.ts | 15 +++-- 3 files changed, 112 insertions(+), 32 deletions(-) diff --git a/src/components/CrewForm.tsx b/src/components/CrewForm.tsx index 8f067c1..7a6813d 100644 --- a/src/components/CrewForm.tsx +++ b/src/components/CrewForm.tsx @@ -3,6 +3,7 @@ import { nip19 } from 'nostr-tools'; import ngeohash from 'ngeohash'; import { useNavigate } from 'react-router-dom'; import { Card, CardContent, CardDescription, CardHeader, CardTitle } from '@/components/ui/card'; +import { Collapsible, CollapsibleTrigger, CollapsibleContent } from '@/components/ui/collapsible'; import { Button } from '@/components/ui/button'; import { Input } from '@/components/ui/input'; import { Textarea } from '@/components/ui/textarea'; @@ -29,7 +30,7 @@ import { Nip05HandlesEditor } from '@/components/Nip05HandlesEditor'; import { coordinatorAddUrl, crewCoordinatorInputValue, parseCrewCoordinatorInput, shortNpub } from '@/lib/coordinator'; import { useRelays } from '@/lib/relays'; import { cn } from '@/lib/utils'; -import { Users, Upload, X, Loader2, MapPin, Plus, ShieldCheck, AtSign, HardHat, Camera, GraduationCap, Sparkles, Target, Megaphone, Music, Palette, Dumbbell, Eye, Store, Film, Check, ArrowRight, ArrowLeft as ArrowLeftIcon, Server } from 'lucide-react'; +import { Users, Upload, X, Loader2, MapPin, Plus, ShieldCheck, AtSign, HardHat, Camera, GraduationCap, Sparkles, Target, Megaphone, Music, Palette, Dumbbell, Eye, Store, Film, Check, ArrowRight, ArrowLeft as ArrowLeftIcon, Server, ChevronDown, Zap } from 'lucide-react'; const FOCUS_ORDER: CrewFocus[] = ['mixed', 'builder', 'photo', 'coaching', 'art', 'music', 'wellness']; const FOCUS_ICONS: Record = { @@ -201,6 +202,37 @@ const HEALTH_LABEL: Record = { error: 'socialUi.coordinator.statusError', }; +/** + * A collapsible group for the "Extras" step, so optional power-ups don't wall off + * a new founder. Header is the group label; the blocks inside keep their own. + */ +function ExtrasGroup({ + icon: Icon, + title, + hint, + defaultOpen = false, + children, +}: { + icon: typeof Users; + title: string; + hint?: string; + defaultOpen?: boolean; + children: ReactNode; +}) { + const [open, setOpen] = useState(defaultOpen); + return ( + + + + {title} + {hint && {hint}} + + + {children} + + ); +} + export function CrewForm({ existing }: CrewFormProps) { const { t } = useTranslation(); const navigate = useNavigate(); @@ -510,7 +542,13 @@ export function CrewForm({ existing }: CrewFormProps) { if (focus !== 'mixed') tags.push(['t', CREW_FOCUS_TAG[focus]]); // Crew AI agent — queryable marker tag when enabled. if (agentEnabled) tags.push(['t', 'crew-agent']); - if (founded.trim()) tags.push(['founded', founded.trim()]); + if (founded.trim()) { + const year = parseInt(founded, 10); + const maxYear = new Date().getFullYear(); + if (Number.isFinite(year)) { + tags.push(['founded', String(Math.min(maxYear, Math.max(1954, year)))]); + } + } if (image) tags.push(['image', image]); if (website.trim()) tags.push(['website', website.trim()]); if (instagram.trim()) tags.push(['instagram', instagram.trim()]); @@ -671,7 +709,7 @@ export function CrewForm({ existing }: CrewFormProps) {
- setFounded(e.target.value)} className="bg-gray-800 border-gray-700" /> + setFounded(e.target.value)} className="bg-gray-800 border-gray-700" />
@@ -816,13 +854,15 @@ export function CrewForm({ existing }: CrewFormProps) { className="h-5 w-5 rounded-md object-cover" loading="lazy" /> - {t('socialUi.crew.agent')} + {t('socialUi.form.sasquatch')} + {t('socialUi.form.pro')} {t('socialUi.form.beta')}

{t('socialUi.form.agentDescA')} {t('socialUi.form.sasquatch')}{t('socialUi.form.agentDescB')}{' '} {t('socialUi.form.agentDescStrong')}{t('socialUi.form.agentDescC')}

+

{t('socialUi.form.agentProNote')}

diff --git a/src/i18n/en.ts b/src/i18n/en.ts index 8b8b4c9..ff24bec 100644 --- a/src/i18n/en.ts +++ b/src/i18n/en.ts @@ -3603,11 +3603,7 @@ socialUi: { customDescShort: "Optional — your crew already has end-to-end encrypted chat built in, so you don't need this. Only set it if you run your own", remove: 'Remove', pointerNote: "Pointer only for now: a wss:// endpoint is used as the crew's chat relay; an https:// one via the shared ContextVM relay. Real Cordn MLS isn't wired yet.", - customDescA: "You don't need this.", - customDescB: 'Your crew already has private, end-to-end encrypted group chat built in — no setup. This field is only for crews who', - customRunOwn: 'run their own', cordnCoordinator: 'Cordn coordinator', - customDescC: "— even on an old phone. Paste its nprofile, a cordn.net link, or an endpoint URL. A wss:// endpoint is used as the crew's own chat relay (point it at a private one to keep even the metadata off public relays); an https:// coordinator is addressed by its pubkey over the shared ContextVM relay. Leave blank to use the built-in chat.", invalid: "That doesn't look like a coordinator. Paste an nprofile, a cordn.net link, or an https/wss URL.", useMine: 'Use my coordinator', }, @@ -3899,6 +3895,17 @@ socialUi: { stepExtras: 'Extras', stepExtrasShort: 'Extras', stepExtrasHint: 'Power-ups for later — skip anything you’re unsure about.', + extrasAllOptional: 'Everything on this step is optional.', + groupIdentity: 'Identity', + groupIdentityHint: 'Handle, links', + groupAi: 'Sasquatch AI', + groupShowcase: 'Showcase', + groupPayments: 'Payments & treasury', + groupIntegrations: 'Integrations', + groupAdvanced: 'Advanced', + groupAdvancedHint: 'experimental', + pro: 'Pro', + agentProNote: 'Powered by Sasquatch. Pro gets the best models; without Pro it uses a free fallback.', stepOf: 'Step {{step}} of {{total}}', treasuryDescA: "Raise sats for a shared goal — fixing the local DIY spot, a crew trip, a comp prize pot. It's a transparent Lightning goal; zaps go", treasuryDescStrong: 'straight to your wallet', diff --git a/src/i18n/es.ts b/src/i18n/es.ts index a4caeab..ed88e74 100644 --- a/src/i18n/es.ts +++ b/src/i18n/es.ts @@ -3603,11 +3603,7 @@ socialUi: { customDescShort: 'Opcional: tu cuadrilla ya tiene chat cifrado de extremo a extremo integrado, así que no lo necesitas. Configúralo solo si usas tu propio', remove: 'Quitar', pointerNote: 'Por ahora es solo un puntero: un endpoint wss:// se usa como relé del chat de la cuadrilla y uno https:// a través del relé ContextVM compartido. El MLS real de Cordn aún no está conectado.', - customDescA: 'No lo necesitas.', - customDescB: 'Tu cuadrilla ya tiene chat de grupo privado y cifrado de extremo a extremo integrado — sin configurar nada. Este campo es solo para cuadrillas que', - customRunOwn: 'montan el suyo', cordnCoordinator: 'coordinador Cordn', - customDescC: '— incluso en un móvil viejo. Pega su nprofile, un enlace de cordn.net o una URL de endpoint. Un endpoint wss:// se usa como relé de chat propio de la cuadrilla (apúntalo a uno privado para mantener hasta los metadatos fuera de los relés públicos); un coordinador https:// se direcciona por su pubkey a través del relé ContextVM compartido. Déjalo en blanco para usar el chat integrado.', invalid: 'Eso no parece un coordinador. Pega un nprofile, un enlace de cordn.net o una URL https/wss.', useMine: 'Usar mi coordinador', }, @@ -3899,6 +3895,17 @@ socialUi: { stepExtras: 'Extras', stepExtrasShort: 'Extras', stepExtrasHint: 'Mejoras para más adelante — sáltate lo que no tengas claro.', + extrasAllOptional: 'Todo en este paso es opcional.', + groupIdentity: 'Identidad', + groupIdentityHint: 'Identificador, enlaces', + groupAi: 'IA Sasquatch', + groupShowcase: 'Escaparate', + groupPayments: 'Pagos y tesorería', + groupIntegrations: 'Integraciones', + groupAdvanced: 'Avanzado', + groupAdvancedHint: 'experimental', + pro: 'Pro', + agentProNote: 'Con la tecnología de Sasquatch. Pro usa los mejores modelos; sin Pro usa un respaldo gratuito.', stepOf: 'Paso {{step}} de {{total}}', treasuryDescA: 'Recauda sats para un objetivo común — arreglar el spot DIY local, un viaje de la cuadrilla, un bote para un concurso. Es un objetivo Lightning transparente; los zaps van', treasuryDescStrong: 'directos a tu monedero',