feat(cordn): web + Staircase MLS entry points on the crew coordinator card

Relabel the cordn.net add-link accurately as 'Cordn (web)' (real MLS in the browser) and add a 'Copy for Staircase' button that copies the coordinator nprofile for the native app. Our coordinator by default; a BYO coordinator's add-link when it has a pubkey.
This commit is contained in:
SkateSpots 2026-10-12 17:23:32 +07:00
parent e262aed938
commit 3d9234d65c
4 changed files with 51 additions and 18 deletions

View file

@ -2134,10 +2134,11 @@ The community relay moved off the hosted relay.tools instance
default; **Settings → Coordinator** shows a "Use SkateSpots coordinator" block default; **Settings → Coordinator** shows a "Use SkateSpots coordinator" block
and **CrewForm** prefills it for new crews. Disclosure copy lives in and **CrewForm** prefills it for new crews. Disclosure copy lives in
`socialUi.coordinator.ourDefault*` + the existing `privacyNote` (coordinators see `socialUi.coordinator.ourDefault*` + the existing `privacyNote` (coordinators see
ciphertext + metadata, never contents). `CrewCoordinatorCard` shows an **"Open in ciphertext + metadata, never contents). `CrewCoordinatorCard` offers two real-MLS
Staircase"** link (`coordinatorAddUrl`) in both branches (the native Cordn client entry points in both branches — **"Cordn (web)"** (the cordn.net add-link → real
for real MLS) — our coordinator by default, a BYO coordinator's add-link when it MLS in the browser) and **"Copy for Staircase"** (copies the nprofile to add in
has a pubkey. the native app) — our coordinator by default, a BYO coordinator's when it has a
pubkey.
- **Cutover (2026-10-12):** `SKATE_RELAY` (`src/lib/types.ts`) → the new relay; the - **Cutover (2026-10-12):** `SKATE_RELAY` (`src/lib/types.ts`) → the new relay; the
default pools, the DM/CVM/zap relay arrays, the `cvm-server` + `push-service` default pools, the DM/CVM/zap relay arrays, the `cvm-server` + `push-service`
defaults, and the archive default all point at the new relay. defaults, and the archive default all point at the new relay.

View file

@ -1,9 +1,10 @@
import { Link } from 'react-router-dom'; import { Link } from 'react-router-dom';
import { Lock, ShieldCheck, Server, ExternalLink, Settings } from 'lucide-react'; import { Lock, ShieldCheck, Server, ExternalLink, Settings, Copy } from 'lucide-react';
import { Card, CardContent, CardHeader, CardTitle } from '@/components/ui/card'; import { Card, CardContent, CardHeader, CardTitle } from '@/components/ui/card';
import { Avatar, AvatarFallback, AvatarImage } from '@/components/ui/avatar'; import { Avatar, AvatarFallback, AvatarImage } from '@/components/ui/avatar';
import { useAuthor } from '@/hooks/useAuthor'; import { useAuthor } from '@/hooks/useAuthor';
import { useTranslation } from '@/contexts/language'; import { useTranslation } from '@/contexts/language';
import { useToast } from '@/hooks/useToast';
import { genUserName } from '@/lib/genUserName'; import { genUserName } from '@/lib/genUserName';
import { sanitizeUrl, urlDomain } from '@/lib/sanitizeUrl'; import { sanitizeUrl, urlDomain } from '@/lib/sanitizeUrl';
import { resolveCordnEndpoint } from '@/lib/cordn/endpoint'; import { resolveCordnEndpoint } from '@/lib/cordn/endpoint';
@ -143,22 +144,51 @@ export function CrewCoordinatorCard({
); );
} }
/** Link to open a coordinator in Staircase (the native Cordn client) for real /** Real-MLS entry points for a coordinator: open the Cordn **web** client
* MLS chat. Falls back to the Cordn home when the coordinator has no pubkey. */ * (cordn.net add-link), or copy the nprofile to add it in the **Staircase**
* native app. Falls back to the Cordn home when there's no pubkey. */
function StaircaseLink({ coordinator }: { coordinator: { pubkey?: string; relays?: string[] } }) { function StaircaseLink({ coordinator }: { coordinator: { pubkey?: string; relays?: string[] } }) {
const { t } = useTranslation(); const { t } = useTranslation();
const href = coordinator.pubkey const { toast } = useToast();
const nprofile = coordinator.pubkey
? nip19.nprofileEncode({ pubkey: coordinator.pubkey, relays: coordinator.relays ?? [] })
: null;
const webUrl = coordinator.pubkey
? coordinatorAddUrl({ pubkey: coordinator.pubkey, relays: coordinator.relays ?? [] }) ? coordinatorAddUrl({ pubkey: coordinator.pubkey, relays: coordinator.relays ?? [] })
: CORDN_HOME; : CORDN_HOME;
const copyNprofile = async () => {
if (!nprofile) return;
try {
await navigator.clipboard.writeText(nprofile);
toast({ title: t('common.copied') });
} catch {
toast({ title: t('common.tryAgain'), variant: 'destructive' });
}
};
const btn =
'inline-flex items-center gap-1.5 rounded-lg border px-2.5 py-1.5 text-xs font-bold transition-colors';
return ( return (
<a <div className="flex flex-wrap items-center gap-2">
href={href} <a
target="_blank" href={webUrl}
rel="noopener noreferrer" target="_blank"
className="inline-flex items-center gap-1.5 rounded-lg border border-teal-500/30 bg-teal-500/10 px-2.5 py-1.5 text-xs font-bold text-teal-300 transition-colors hover:bg-teal-500/20" rel="noopener noreferrer"
> className={`${btn} border-teal-500/30 bg-teal-500/10 text-teal-300 hover:bg-teal-500/20`}
<ExternalLink className="h-3.5 w-3.5" /> {t('socialUi.crewCoordinator.openInStaircase')} >
</a> <ExternalLink className="h-3.5 w-3.5" /> {t('socialUi.crewCoordinator.openInCordnWeb')}
</a>
{nprofile && (
<button
type="button"
onClick={() => void copyNprofile()}
className={`${btn} border-white/15 bg-white/5 text-gray-300 hover:bg-white/10`}
>
<Copy className="h-3.5 w-3.5" /> {t('socialUi.crewCoordinator.copyForStaircase')}
</button>
)}
</div>
); );
} }

View file

@ -4045,7 +4045,8 @@ socialUi: {
ownRelaySafe: "Nothing crew-specific leaves your crew's own relay.", ownRelaySafe: "Nothing crew-specific leaves your crew's own relay.",
manage: 'Manage coordinator', manage: 'Manage coordinator',
open: 'Open {{domain}}', open: 'Open {{domain}}',
openInStaircase: 'Open in Staircase', openInCordnWeb: 'Cordn (web)',
copyForStaircase: 'Copy for Staircase',
runBy: 'Coordinator run by', runBy: 'Coordinator run by',
}, },
dms: { dms: {

View file

@ -4045,7 +4045,8 @@ socialUi: {
ownRelaySafe: 'Nada específico de la peña sale del relé propio de tu peña.', ownRelaySafe: 'Nada específico de la peña sale del relé propio de tu peña.',
manage: 'Gestionar coordinador', manage: 'Gestionar coordinador',
open: 'Abrir {{domain}}', open: 'Abrir {{domain}}',
openInStaircase: 'Abrir en Staircase', openInCordnWeb: 'Cordn (web)',
copyForStaircase: 'Copiar para Staircase',
runBy: 'Coordinador gestionado por', runBy: 'Coordinador gestionado por',
}, },
dms: { dms: {