Join relay and group dynamically, dropping PUBLIC_JOINCODE

This commit is contained in:
dtonon 2026-08-13 22:54:26 +01:00
parent aaa9a0c1e5
commit 6ecd618bce
9 changed files with 453 additions and 178 deletions

View file

@ -15,9 +15,9 @@
} from "$lib/moderation.svelte";
import {
withJoin,
joinState,
membershipOf,
ensureMembershipChecked,
openJoinModal,
} from "$lib/join.svelte";
import { activeGroup } from "$lib/active.svelte";
import type { NostrUser } from "@nostr/gadgets/metadata";
@ -76,7 +76,7 @@
});
function onJoinToChat() {
openJoinModal(activeGroup.id, () => tick().then(() => inputEl?.focus()));
withJoin(activeGroup.id, () => tick().then(() => inputEl?.focus()));
}
function requestDeleteMessage(msg: ChatMessageData) {
@ -486,9 +486,10 @@
{:else if joinToChat}
<button
onclick={onJoinToChat}
class="bg-accent hover:bg-accent-hover w-full rounded px-3 py-2 text-sm font-medium text-white"
disabled={joinState.busy}
class="bg-accent hover:bg-accent-hover w-full rounded px-3 py-2 text-sm font-medium text-white disabled:opacity-50"
>
Join to chat
{joinState.busy ? "Joining…" : "Join to chat"}
</button>
{:else}
<div class="relative">

View file

@ -18,7 +18,8 @@
import {
membershipOf,
ensureMembershipChecked,
openJoinModal,
withJoin,
joinState,
} from "$lib/join.svelte";
import { sortPref } from "$lib/sort.svelte";
import { page } from "$app/state";
@ -71,12 +72,7 @@
openLogin(onNewTopic);
return;
}
await ensureMembershipChecked(groupId);
if (membershipOf(groupId) !== "member") {
openJoinModal(groupId, openDraft);
return;
}
openDraft();
await withJoin(groupId, openDraft);
}
function onPrivateJoin() {
@ -84,7 +80,7 @@
openLogin();
return;
}
openJoinModal(groupId, () => loadThreads(groupId, sort));
withJoin(groupId, () => loadThreads(groupId, sort));
}
function relativeTime(ts: number): string {
@ -138,9 +134,14 @@
<div class="flex items-center gap-2">
<button
onclick={onNewTopic}
class="bg-accent hover:bg-accent-hover rounded px-4 py-1.5 font-medium text-white md:px-6 md:text-sm"
disabled={joinState.busy}
class="bg-accent hover:bg-accent-hover rounded px-4 py-1.5 font-medium text-white disabled:opacity-50 md:px-6 md:text-sm"
>
{joinToPost ? "Join to post" : "New discussion"}
{joinState.busy
? "Joining…"
: joinToPost
? "Join to post"
: "New discussion"}
</button>
<SortToggle {sort} />
</div>
@ -165,9 +166,14 @@
</p>
<button
onclick={onPrivateJoin}
class="bg-accent hover:bg-accent-hover mt-5 rounded px-6 py-1.5 font-medium text-white"
disabled={joinState.busy}
class="bg-accent hover:bg-accent-hover mt-5 rounded px-6 py-1.5 font-medium text-white disabled:opacity-50"
>
{auth.user ? "Request to join" : "Log in to join"}
{joinState.busy
? "Joining…"
: auth.user
? "Request to join"
: "Log in to join"}
</button>
</div>
{:else}

View file

@ -1,19 +1,38 @@
<script lang="ts">
import { joinState, closeJoinModal, submitJoin } from "$lib/join.svelte";
import { joinState, closeJoinModal, submitJoinCode } from "$lib/join.svelte";
import { tick } from "svelte";
let code = $state("");
let codeInput = $state<HTMLInputElement | null>(null);
// Pending approval hides the code field behind a discreet link, so a group
// that doesn't hand out codes doesn't send people hunting for one.
let codeRevealed = $state(false);
const step = $derived(joinState.modalStep);
const showCode = $derived(
step === "relay-code" ||
(step === "group-code" && joinState.modalCodeHinted) ||
codeRevealed,
);
const canOfferCode = $derived(
step === "pending" || (step === "group-code" && !joinState.modalCodeHinted),
);
$effect(() => {
if (joinState.modalOpen && joinState.needsCode) {
if (!joinState.modalOpen) {
code = "";
codeRevealed = false;
}
});
$effect(() => {
if (joinState.modalOpen && showCode) {
tick().then(() => codeInput?.focus());
}
if (!joinState.modalOpen) code = "";
});
async function onSubmit() {
await submitJoin(code.trim() || undefined);
await submitJoinCode(code.trim() || undefined);
}
function onClose() {
@ -56,15 +75,28 @@
id="join-title"
class="mb-2 text-lg font-semibold text-neutral-900 dark:text-neutral-100"
>
Join this group
{#if step === "relay-code"}
Invite code required
{:else if step === "pending"}
Request sent
{:else if step === "error"}
Could not join
{:else}
Join this group
{/if}
</h2>
<p class="mb-4 text-sm text-neutral-600 dark:text-neutral-400">
{#if joinState.needsCode}
Join this group to participate. If you have an invite code, enter it
below — otherwise just send the request.
{#if step === "relay-code"}
This relay only accepts members. Enter an invite code to join.
{:else if step === "pending"}
Your request to join has been sent. An admin needs to approve it
before you can participate.
{:else if step === "error"}
The relay refused the request.
{:else if joinState.modalCodeHinted}
This group requires an invite code to join.
{:else}
Join this group to participate. Your request is sent to the relay and,
where needed, approved by an admin.
The group refused the join request.
{/if}
</p>
@ -77,13 +109,17 @@
</div>
{/if}
{#if joinState.needsCode}
<label for="join-code-input" class="sr-only">Invite code</label>
{#if showCode}
<label for="join-code-input" class="sr-only">
{step === "relay-code" ? "Relay invite code" : "Group invite code"}
</label>
<input
id="join-code-input"
bind:this={codeInput}
type="text"
placeholder="Invite code (optional)"
placeholder={step === "relay-code"
? "Relay invite code"
: "Group invite code"}
bind:value={code}
disabled={joinState.busy}
autocomplete="off"
@ -92,6 +128,14 @@
onkeydown={(e) => e.key === "Enter" && onSubmit()}
class="focus:ring-accent mb-3 w-full rounded border border-neutral-200 px-3 py-2 text-sm focus:ring-1 focus:outline-none disabled:opacity-50 dark:border-neutral-700"
/>
{:else if canOfferCode}
<button
type="button"
onclick={() => (codeRevealed = true)}
class="mb-3 text-sm text-neutral-500 underline hover:text-neutral-800 dark:text-neutral-400 dark:hover:text-neutral-200"
>
Have an invite code?
</button>
{/if}
<div class="flex justify-end gap-2">
@ -103,18 +147,16 @@
>
Close
</button>
<button
type="button"
onclick={onSubmit}
disabled={joinState.busy}
class="bg-accent hover:bg-accent-hover rounded px-3 py-2 text-sm font-medium text-white disabled:cursor-not-allowed disabled:opacity-50"
>
{joinState.busy
? "Joining…"
: joinState.needsCode
? "Request access"
: "Join"}
</button>
{#if showCode}
<button
type="button"
onclick={onSubmit}
disabled={joinState.busy || !code.trim()}
class="bg-accent hover:bg-accent-hover rounded px-3 py-2 text-sm font-medium text-white disabled:cursor-not-allowed disabled:opacity-50"
>
{joinState.busy ? "Joining…" : "Join"}
</button>
{/if}
</div>
</div>
</div>