feat(crew): clearer, consistent crew-creation form
- Coordinator block: shorter copy, a Remove (✕) button, and an honest "pointer only — real Cordn MLS isn't wired yet" note (the old paragraph was a wall of jargon). - Save now blocks on a non-empty but unparseable coordinator (was silently dropped) instead of publishing a crew with no coordinator. - One consistent block order for create (wizard "Extras") and edit, grouped: core -> squad/places -> identity (NIP-05, links, band) -> agent -> showcase (video, sponsor) -> treasury -> integrations (Lab link, coordinator).
This commit is contained in:
parent
127819e3da
commit
3cc16d18ca
3 changed files with 50 additions and 23 deletions
|
|
@ -428,6 +428,12 @@ export function CrewForm({ existing }: CrewFormProps) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// An invalid coordinator would be silently dropped on save — tell the user.
|
||||||
|
if (coordinatorInput.trim() && !parsedCoordinator) {
|
||||||
|
toast({ title: t('socialUi.coordinator.invalid'), variant: 'destructive' });
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
try {
|
try {
|
||||||
const identifier = existing?.identifier ?? `crew-${Date.now().toString(36)}`;
|
const identifier = existing?.identifier ?? `crew-${Date.now().toString(36)}`;
|
||||||
|
|
||||||
|
|
@ -1091,8 +1097,7 @@ export function CrewForm({ existing }: CrewFormProps) {
|
||||||
<Server className="h-4 w-4" /> {t('socialUi.coordinator.customTitle')} <span className="font-normal text-gray-500">{t('socialUi.coordinator.customBadge')}</span>
|
<Server className="h-4 w-4" /> {t('socialUi.coordinator.customTitle')} <span className="font-normal text-gray-500">{t('socialUi.coordinator.customBadge')}</span>
|
||||||
</Label>
|
</Label>
|
||||||
<p className="text-xs text-gray-500">
|
<p className="text-xs text-gray-500">
|
||||||
<span className="font-semibold text-gray-300">{t('socialUi.coordinator.customDescA')}</span> {t('socialUi.coordinator.customDescB')}{' '}
|
{t('socialUi.coordinator.customDescShort')}{' '}
|
||||||
<span className="font-semibold text-gray-300">{t('socialUi.coordinator.customRunOwn')}</span>{' '}
|
|
||||||
<a
|
<a
|
||||||
href="https://github.com/Cordn-msg/cordn"
|
href="https://github.com/Cordn-msg/cordn"
|
||||||
target="_blank"
|
target="_blank"
|
||||||
|
|
@ -1100,9 +1105,10 @@ export function CrewForm({ existing }: CrewFormProps) {
|
||||||
className="text-teal-400 underline underline-offset-2 hover:text-teal-300"
|
className="text-teal-400 underline underline-offset-2 hover:text-teal-300"
|
||||||
>
|
>
|
||||||
{t('socialUi.coordinator.cordnCoordinator')}
|
{t('socialUi.coordinator.cordnCoordinator')}
|
||||||
</a>{' '}
|
</a>
|
||||||
{t('socialUi.coordinator.customDescC')}
|
.
|
||||||
</p>
|
</p>
|
||||||
|
<div className="flex gap-2">
|
||||||
<Input
|
<Input
|
||||||
id="crew-coordinator"
|
id="crew-coordinator"
|
||||||
placeholder="nprofile1… · cordn.net/chat/coordinators?c=… · https://…"
|
placeholder="nprofile1… · cordn.net/chat/coordinators?c=… · https://…"
|
||||||
|
|
@ -1111,6 +1117,20 @@ export function CrewForm({ existing }: CrewFormProps) {
|
||||||
className="bg-gray-800 border-gray-700 font-mono text-xs"
|
className="bg-gray-800 border-gray-700 font-mono text-xs"
|
||||||
spellCheck={false}
|
spellCheck={false}
|
||||||
/>
|
/>
|
||||||
|
{coordinatorInput.trim() !== '' && (
|
||||||
|
<Button
|
||||||
|
type="button"
|
||||||
|
variant="outline"
|
||||||
|
size="icon"
|
||||||
|
className="shrink-0 border-gray-700 text-gray-400 hover:text-red-400"
|
||||||
|
onClick={() => setCoordinatorInput('')}
|
||||||
|
title={t('socialUi.coordinator.remove')}
|
||||||
|
aria-label={t('socialUi.coordinator.remove')}
|
||||||
|
>
|
||||||
|
<X className="h-4 w-4" />
|
||||||
|
</Button>
|
||||||
|
)}
|
||||||
|
</div>
|
||||||
{coordinatorInput.trim() !== '' &&
|
{coordinatorInput.trim() !== '' &&
|
||||||
(parsedCoordinator ? (
|
(parsedCoordinator ? (
|
||||||
<p className="break-all text-[11px] text-teal-400">
|
<p className="break-all text-[11px] text-teal-400">
|
||||||
|
|
@ -1134,6 +1154,7 @@ export function CrewForm({ existing }: CrewFormProps) {
|
||||||
{t('socialUi.coordinator.useMine')}
|
{t('socialUi.coordinator.useMine')}
|
||||||
</Button>
|
</Button>
|
||||||
)}
|
)}
|
||||||
|
<p className="text-[10px] leading-snug text-gray-600">{t('socialUi.coordinator.pointerNote')}</p>
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
|
|
||||||
|
|
@ -1281,19 +1302,19 @@ export function CrewForm({ existing }: CrewFormProps) {
|
||||||
{bioBlock}
|
{bioBlock}
|
||||||
{vibeBlock}
|
{vibeBlock}
|
||||||
{crewTypeBlock}
|
{crewTypeBlock}
|
||||||
{agentBlock}
|
|
||||||
{openCallBlock}
|
{openCallBlock}
|
||||||
{treasuryBlock}
|
|
||||||
{membersBlock}
|
{membersBlock}
|
||||||
{spotsBlock}
|
{spotsBlock}
|
||||||
{labLinkBlock}
|
|
||||||
{videoBlock}
|
|
||||||
{sponsorBlock}
|
|
||||||
{coordinatorBlock}
|
|
||||||
{locationBlock}
|
{locationBlock}
|
||||||
{nip05Block}
|
{nip05Block}
|
||||||
{linksBlock}
|
{linksBlock}
|
||||||
{bandBlock}
|
{bandBlock}
|
||||||
|
{agentBlock}
|
||||||
|
{videoBlock}
|
||||||
|
{sponsorBlock}
|
||||||
|
{treasuryBlock}
|
||||||
|
{labLinkBlock}
|
||||||
|
{coordinatorBlock}
|
||||||
<Button type="submit" className={cn('w-full uppercase font-black', TEAL)} disabled={busy}>
|
<Button type="submit" className={cn('w-full uppercase font-black', TEAL)} disabled={busy}>
|
||||||
{isPublishing ? (
|
{isPublishing ? (
|
||||||
<><Loader2 className="h-4 w-4 mr-2 animate-spin" />{t('socialUi.form.publishing')}</>
|
<><Loader2 className="h-4 w-4 mr-2 animate-spin" />{t('socialUi.form.publishing')}</>
|
||||||
|
|
@ -1370,14 +1391,14 @@ export function CrewForm({ existing }: CrewFormProps) {
|
||||||
filled: hasExtras,
|
filled: hasExtras,
|
||||||
content: (
|
content: (
|
||||||
<div className="space-y-6">
|
<div className="space-y-6">
|
||||||
{treasuryBlock}
|
{nip05Block}
|
||||||
{videoBlock}
|
|
||||||
{sponsorBlock}
|
|
||||||
{labLinkBlock}
|
|
||||||
{linksBlock}
|
{linksBlock}
|
||||||
{bandBlock}
|
{bandBlock}
|
||||||
{nip05Block}
|
|
||||||
{agentBlock}
|
{agentBlock}
|
||||||
|
{videoBlock}
|
||||||
|
{sponsorBlock}
|
||||||
|
{treasuryBlock}
|
||||||
|
{labLinkBlock}
|
||||||
{coordinatorBlock}
|
{coordinatorBlock}
|
||||||
</div>
|
</div>
|
||||||
),
|
),
|
||||||
|
|
|
||||||
|
|
@ -3600,6 +3600,9 @@ socialUi: {
|
||||||
privacyNote: '🔒 A coordinator sees ciphertext and metadata only — never message contents. Run your own to keep even the metadata yours.',
|
privacyNote: '🔒 A coordinator sees ciphertext and metadata only — never message contents. Run your own to keep even the metadata yours.',
|
||||||
customTitle: 'Custom Chat Coordinator',
|
customTitle: 'Custom Chat Coordinator',
|
||||||
customBadge: '· experimental · your own relay',
|
customBadge: '· experimental · your own relay',
|
||||||
|
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.",
|
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',
|
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',
|
customRunOwn: 'run their own',
|
||||||
|
|
|
||||||
|
|
@ -3600,6 +3600,9 @@ socialUi: {
|
||||||
privacyNote: '🔒 Un coordinador solo ve texto cifrado y metadatos — nunca el contenido de los mensajes. Monta el tuyo para que hasta los metadatos sean tuyos.',
|
privacyNote: '🔒 Un coordinador solo ve texto cifrado y metadatos — nunca el contenido de los mensajes. Monta el tuyo para que hasta los metadatos sean tuyos.',
|
||||||
customTitle: 'Coordinador de chat propio',
|
customTitle: 'Coordinador de chat propio',
|
||||||
customBadge: '· experimental · tu propio relé',
|
customBadge: '· experimental · tu propio relé',
|
||||||
|
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.',
|
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',
|
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',
|
customRunOwn: 'montan el suyo',
|
||||||
|
|
|
||||||
Loading…
Reference in a new issue