feat(cordn): expose and copy the Cordn group share link
This commit is contained in:
parent
74249e304e
commit
27f3434475
2 changed files with 55 additions and 10 deletions
19
AGENTS.md
19
AGENTS.md
|
|
@ -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
|
||||||
|
|
|
||||||
|
|
@ -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,8 +814,9 @@ function AdvancedPanel({
|
||||||
{copied ? t('cordnUi.copied') : t('cordnUi.copyRef')}
|
{copied ? t('cordnUi.copied') : t('cordnUi.copyRef')}
|
||||||
</Button>
|
</Button>
|
||||||
</div>
|
</div>
|
||||||
|
<div className="flex flex-wrap items-center gap-2">
|
||||||
<a
|
<a
|
||||||
href={`https://cordn.net/chat/${groupRef}`}
|
href={cordnUrl ?? '#'}
|
||||||
target="_blank"
|
target="_blank"
|
||||||
rel="noopener noreferrer"
|
rel="noopener noreferrer"
|
||||||
className="inline-flex items-center gap-1 text-[11px] font-medium text-teal-300 transition-colors hover:text-teal-200"
|
className="inline-flex items-center gap-1 text-[11px] font-medium text-teal-300 transition-colors hover:text-teal-200"
|
||||||
|
|
@ -810,6 +824,20 @@ function AdvancedPanel({
|
||||||
<ExternalLink className="h-3 w-3" />{' '}
|
<ExternalLink className="h-3 w-3" />{' '}
|
||||||
{t('socialUi.crewCoordinator.openCordnGroup')}
|
{t('socialUi.crewCoordinator.openCordnGroup')}
|
||||||
</a>
|
</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>
|
||||||
|
|
|
||||||
Loading…
Reference in a new issue