feat(cordn): expose and copy the Cordn group share link

This commit is contained in:
SkateSpots 2026-10-27 17:02:43 +07:00
parent 74249e304e
commit 27f3434475
2 changed files with 55 additions and 10 deletions

View file

@ -1486,7 +1486,14 @@ session with **no other memory of this project**. So:
`send`/`createChat`/`requestJoin`/`approve`, and **filters welcomes by gid** so `send`/`createChat`/`requestJoin`/`approve`, and **filters welcomes by gid** so
one crew can't adopt another crew's chat. Coordinator announce relays are now one crew can't adopt another crew's chat. Coordinator announce relays are now
`["wss://relay.skate-spots.org"]` only (removed purplepag.es/nostr.band/nos.lol `["wss://relay.skate-spots.org"]` only (removed purplepag.es/nostr.band/nos.lol
timeouts; logs show `local[10002=ok]`). timeouts; logs show `local[10002=ok]`). Advanced also exposes the Cordn share
link `https://cordn.net/chat/<cordn1…>` (open/copy). Per Cordn's group-ref
spec the `cordn1…` ref is a **share** primitive: a client can publish a key
package and submit a join request, then the founder approves. The reliable
"join us" link for normal users is still
`https://skate-spots.org/#/crew/<pk>/<id>?tab=mls`; use the Cordn link for
Staircase/Cordn clients. ⚠️ cordn.net's web client still stalls after NIP-42
(upstream issue), so it is not the primary onboarding path yet.
- **Map site-links strip (2026-10-05):** the desktop footer is hidden on the map - **Map site-links strip (2026-10-05):** the desktop footer is hidden on the map
(full-bleed), so `components/MapFooterStrip.tsx` surfaces the same links (full-bleed), so `components/MapFooterStrip.tsx` surfaces the same links
(Newsletter/License/Privacy/Collaborators + "not sponsored by OpenSats") as an (Newsletter/License/Privacy/Collaborators + "not sponsored by OpenSats") as an
@ -1688,6 +1695,16 @@ session with **no other memory of this project**. So:
propagation so Radix typeahead does not hijack typing) and the inline/mobile propagation so Radix typeahead does not hijack typing) and the inline/mobile
variant; keys `nav.searchLanguages`/`nav.noLanguages` live in en/es only (other variant; keys `nav.searchLanguages`/`nav.noLanguages` live in en/es only (other
locales fall back). The Settings `LanguageSelector` grid is searchless by design. locales fall back). The Settings `LanguageSelector` grid is searchless by design.
- **Menu audit (2026-10-27):** rendered EN/DE menus at 390×844 and 1280×900.
Alignment was already start-aligned with no clipped labels. Fixed the real
problems: the mobile burger's inline 28-language list made the sheet
**3,168px** tall and buried the picker — it is now a **Language row + dedicated
sub-view** (Back/search/list), cutting the main sheet to ~1,384px. Language
dropdown rows are `min-h-11`; the mobile user menu is 44px; desktop More is
40px. Language search only auto-focuses for `pointer: fine` (no phone keyboard
covering the list). Crew tabs use `1.1fr / 1fr / 1.3fr` + 13px labels so
“Private chat”/“Privater Chat” no longer truncate. Re-check with
`node scripts/smoke-test.mjs` + a menu screenshot if you change the header.
- **Community stats (`/stats`, 2026-10-01):** `useGrowthStats` + `CommunityStatsPage`. - **Community stats (`/stats`, 2026-10-01):** `useGrowthStats` + `CommunityStatsPage`.
- ⚠️ **A relay outage used to render a board of ZEROS as truth** — every - ⚠️ **A relay outage used to render a board of ZEROS as truth** — every
sub-query ended in `.catch(() => [])`. The queryFn now counts failures (a sub-query ended in `.catch(() => [])`. The queryFn now counts failures (a

View file

@ -761,6 +761,19 @@ function AdvancedPanel({
onCopyRef: () => void; onCopyRef: () => void;
}) { }) {
const { t } = useTranslation(); const { t } = useTranslation();
const [copiedCordn, setCopiedCordn] = useState(false);
const cordnUrl = groupRef ? `https://cordn.net/chat/${groupRef}` : null;
const copyCordn = async () => {
if (!cordnUrl) return;
try {
await navigator.clipboard.writeText(cordnUrl);
setCopiedCordn(true);
window.setTimeout(() => setCopiedCordn(false), 1500);
} catch {
// clipboard blocked — the link is visible to select.
}
};
return ( return (
<details className="group rounded-md border border-white/10 bg-black/20 px-2.5 py-2 text-left"> <details className="group rounded-md border border-white/10 bg-black/20 px-2.5 py-2 text-left">
<summary className="flex cursor-pointer list-none items-center gap-1.5 text-[11px] text-gray-500"> <summary className="flex cursor-pointer list-none items-center gap-1.5 text-[11px] text-gray-500">
@ -801,15 +814,30 @@ function AdvancedPanel({
{copied ? t('cordnUi.copied') : t('cordnUi.copyRef')} {copied ? t('cordnUi.copied') : t('cordnUi.copyRef')}
</Button> </Button>
</div> </div>
<a <div className="flex flex-wrap items-center gap-2">
href={`https://cordn.net/chat/${groupRef}`} <a
target="_blank" href={cordnUrl ?? '#'}
rel="noopener noreferrer" target="_blank"
className="inline-flex items-center gap-1 text-[11px] font-medium text-teal-300 transition-colors hover:text-teal-200" rel="noopener noreferrer"
> className="inline-flex items-center gap-1 text-[11px] font-medium text-teal-300 transition-colors hover:text-teal-200"
<ExternalLink className="h-3 w-3" />{' '} >
{t('socialUi.crewCoordinator.openCordnGroup')} <ExternalLink className="h-3 w-3" />{' '}
</a> {t('socialUi.crewCoordinator.openCordnGroup')}
</a>
<Button
size="sm"
variant="ghost"
className="h-7 gap-1 px-2 text-[11px] text-gray-400"
onClick={() => void copyCordn()}
>
{copiedCordn ? (
<Check className="h-3 w-3 text-teal-400" />
) : (
<Copy className="h-3 w-3" />
)}
{copiedCordn ? t('cordnUi.copied') : t('cordnUi.copyRef')}
</Button>
</div>
</> </>
)} )}
</div> </div>