diff --git a/.env b/.env index 5c855f6f..f3416918 100644 --- a/.env +++ b/.env @@ -8,6 +8,7 @@ VITE_PLATFORM_PRIVACY=https://flotilla.social/privacy VITE_PLATFORM_NAME=Flotilla VITE_PLATFORM_LOGO=static/logo.png VITE_PLATFORM_RELAYS= +VITE_PLATFORM_RELAYS_STRICT=false VITE_PLATFORM_ACCENT="#7161FF" VITE_THEME="clay" VITE_PLATFORM_DESCRIPTION="Flotilla is nostr — for communities." diff --git a/README.md b/README.md index fc5ab611..618baaa4 100644 --- a/README.md +++ b/README.md @@ -22,7 +22,7 @@ You can also optionally create an `.env.local` file and populate it with the fol **Defaults** - `VITE_DEFAULT_PUBKEYS` - A comma-separated list of hex pubkeys for bootstrapping web of trust -- `VITE_DEFAULT_SPACES` - A comma-separated list of relay urls that new users will be automatically joined to on signup +- `VITE_DEFAULT_SPACES` - A comma-separated list of relay urls that new users will be automatically joined to on signup. Each one may optionally include an invite code, delimited by `|`, e.g. `my.space.com|CODE`. - `VITE_DEFAULT_RELAYS` - A comma-separated list of relay urls used as default outbox/inbox relays - `VITE_DEFAULT_MESSAGING_RELAYS` - A comma-separated list of relay urls used for encrypted direct messages - `VITE_DEFAULT_BLOSSOM_SERVERS` - A comma-separated list of blossom server urls used for file uploads diff --git a/src/app/components/LogInEmail.svelte b/src/app/components/LogInEmail.svelte index 419d9abb..ad8da6e8 100644 --- a/src/app/components/LogInEmail.svelte +++ b/src/app/components/LogInEmail.svelte @@ -114,7 +114,7 @@ {/snippet}

- Forgot your password? .

diff --git a/src/app/components/RelayDescription.svelte b/src/app/components/RelayDescription.svelte index 7e67c7ac..82d4789a 100644 --- a/src/app/components/RelayDescription.svelte +++ b/src/app/components/RelayDescription.svelte @@ -1,11 +1,16 @@ {#if $relay?.description} -

{$relay.description}

+ {/if} diff --git a/src/app/components/RelaySummary.svelte b/src/app/components/RelaySummary.svelte index 41375b0c..44a2cd99 100644 --- a/src/app/components/RelaySummary.svelte +++ b/src/app/components/RelaySummary.svelte @@ -24,7 +24,7 @@
- +
{#if $rooms.includes(url)} @@ -40,7 +40,9 @@

{url}

- +
+ +
{#if !hideFavorites && $favorited.size > 0}
diff --git a/src/app/components/SignUp.svelte b/src/app/components/SignUp.svelte index 9e5f6164..4fca9bae 100644 --- a/src/app/components/SignUp.svelte +++ b/src/app/components/SignUp.svelte @@ -2,8 +2,9 @@ import cx from "classnames" import type {ClientOptions} from "@pomade/core" import type {Profile} from "@welshman/util" + import {first} from "@welshman/lib" import {makeProfile, makeSecret, RELAYS, MESSAGING_RELAYS, makeEvent} from "@welshman/util" - import {loginWithNip01, publishThunk} from "@welshman/app" + import {loginWithNip01, publishThunk, waitForThunkCompletion} from "@welshman/app" import Key from "@assets/icons/key-minimalistic.svg?dataurl" import Letter from "@assets/icons/letter.svg?dataurl" import {getKey, setKey} from "@lib/implicit" @@ -17,6 +18,7 @@ import SignUpProfile from "@app/components/SignUpProfile.svelte" import SignUpComplete from "@app/components/SignUpComplete.svelte" import {initProfile} from "@app/profiles" + import {attemptRelayAccess} from "@app/relays" import { POMADE_SIGNERS, PLATFORM_NAME, @@ -39,24 +41,32 @@ const login = () => pushModal(LogIn) - const completeSignup = () => { - // Add default outbox/inbox relays - publishThunk({ - event: makeEvent(RELAYS, {tags: DEFAULT_RELAYS.map(url => ["r", url])}), - relays: [...INDEXER_RELAYS, ...DEFAULT_RELAYS], - }) + const completeSignup = async () => { + // Join all default spaces first + const defaultSpaceUrls = await Promise.all( + DEFAULT_SPACES.map(async ([url, claim]) => { + await attemptRelayAccess(url, claim) - // Add default messaging relays - publishThunk({ - event: makeEvent(MESSAGING_RELAYS, {tags: DEFAULT_MESSAGING_RELAYS.map(url => ["r", url])}), - relays: DEFAULT_RELAYS, - }) + return url + }) + ) - // Save the user's profile - initProfile(getKey("signup.profile")!) + // Add default outbox/inbox/messaging relays, profile, spaces + const thunks = await Promise.all([ + publishThunk({ + event: makeEvent(RELAYS, {tags: DEFAULT_RELAYS.map(url => ["r", url])}), + relays: [...INDEXER_RELAYS, ...DEFAULT_RELAYS], + }), + publishThunk({ + event: makeEvent(MESSAGING_RELAYS, {tags: DEFAULT_MESSAGING_RELAYS.map(url => ["r", url])}), + relays: DEFAULT_RELAYS, + }), + initProfile(getKey("signup.profile")!), + setSpaces(defaultSpaceUrls), + ]) - // Auto-join default spaces - setSpaces(DEFAULT_SPACES) + // Wait for all the thunks to complete + await Promise.all(thunks.map(waitForThunkCompletion)) // Don't show any notifications for old content setChecked("*") @@ -71,12 +81,12 @@ profile: () => pushModal(SignUpProfile, {next: flows.email.complete, step: 2, totalSteps: 3}), complete: () => pushModal(SignUpComplete, {next: flows.email.finalize, step: 3, totalSteps: 3}), - finalize: () => { + finalize: async () => { const email = getKey("signup.email")! const clientOptions = getKey("signup.clientOptions")! loginWithPomade(clientOptions, email) - completeSignup() + await completeSignup() }, }, nostr: { @@ -84,11 +94,11 @@ key: () => pushModal(SignUpKey, {next: flows.nostr.complete, step: 2, totalSteps: 3}), complete: () => pushModal(SignUpComplete, {next: flows.nostr.finalize, step: 3, totalSteps: 3}), - finalize: () => { + finalize: async () => { const secret = getKey("signup.secret")! loginWithNip01(secret) - completeSignup() + await completeSignup() }, }, } diff --git a/src/app/components/SignUpComplete.svelte b/src/app/components/SignUpComplete.svelte index 0b2324ca..9d63bd1d 100644 --- a/src/app/components/SignUpComplete.svelte +++ b/src/app/components/SignUpComplete.svelte @@ -4,6 +4,7 @@ import HomeSmile from "@assets/icons/home-smile.svg?dataurl" import Icon from "@lib/components/Icon.svelte" import Button from "@lib/components/Button.svelte" + import Spinner from "@lib/components/Spinner.svelte" import Modal from "@lib/components/Modal.svelte" import ModalBody from "@lib/components/ModalBody.svelte" import ModalHeader from "@lib/components/ModalHeader.svelte" @@ -20,9 +21,21 @@ const {next, step, totalSteps}: Props = $props() const back = () => history.back() + + const submit = async () => { + loading = true + + try { + await next() + } finally { + loading = false + } + } + + let loading = $state(false) - + You're all set! @@ -44,9 +57,13 @@ Go back - diff --git a/src/app/components/SpaceInvite.svelte b/src/app/components/SpaceInvite.svelte index 4cb26b1a..f9e81170 100644 --- a/src/app/components/SpaceInvite.svelte +++ b/src/app/components/SpaceInvite.svelte @@ -134,16 +134,16 @@
{#if loading} -

+

Requesting an invite link...

{:else if isGenericError} -

- Unable to reach the relay. Please check your connection and try again. +

+ Unable to reach the space. Please check your connection and try again.

{:else if isExplicitAuthError} -

- Oops! It looks like you're not a member of this relay. +

+ Oops! It looks like you're not a member of this space.

{:else}
diff --git a/src/app/components/SpaceTrustRelay.svelte b/src/app/components/SpaceTrustRelay.svelte index 39ef5568..08713c7e 100644 --- a/src/app/components/SpaceTrustRelay.svelte +++ b/src/app/components/SpaceTrustRelay.svelte @@ -66,7 +66,7 @@

This space has opted not to publish , which means that they have the ability to forge messages from other users.

diff --git a/src/app/env.ts b/src/app/env.ts index fa441c23..d0009779 100644 --- a/src/app/env.ts +++ b/src/app/env.ts @@ -51,7 +51,11 @@ export const POMADE_SIGNERS = fromCsv(import.meta.env.VITE_POMADE_SIGNERS) export const DEFAULT_BLOSSOM_SERVERS = fromCsv(import.meta.env.VITE_DEFAULT_BLOSSOM_SERVERS) -export const DEFAULT_SPACES = fromCsv(import.meta.env.VITE_DEFAULT_SPACES).map(normalizeRelayUrl) +export const DEFAULT_SPACES = fromCsv(import.meta.env.VITE_DEFAULT_SPACES).map(item => { + const [url, claim = ""] = item.split('|') + + return [normalizeRelayUrl(url), claim] +}) export const DEFAULT_PUBKEYS = import.meta.env.VITE_DEFAULT_PUBKEYS diff --git a/src/app/routes.ts b/src/app/routes.ts index 9938a119..1090fb98 100644 --- a/src/app/routes.ts +++ b/src/app/routes.ts @@ -83,22 +83,22 @@ export const makeSpacePath = (url: string, ...extra: (string | undefined)[]) => return path } -export const goToSpace = (url: string) => { +export const goToSpace = (url: string, hash = "") => { const prevPath = lastPageBySpaceUrl.get(encodeRelay(url)) if (prevPath && prevPath !== makeSpacePath(url)) { - return goto(prevPath, {replaceState: true}) + return goto(prevPath + hash, {replaceState: true}) } if (!hasNip29(getRelay(url))) { - return goto(makeSpaceChatPath(url), {replaceState: true}) + return goto(makeSpaceChatPath(url) + hash, {replaceState: true}) } if (window.matchMedia(`(min-width: ${theme.screens.md})`).matches) { - return goto(makeSpacePath(url, "about"), {replaceState: true}) + return goto(makeSpacePath(url, "about") + hash, {replaceState: true}) } - return goto(makeSpacePath(url), {replaceState: true}) + return goto(makeSpacePath(url) + hash, {replaceState: true}) } // Content types, events diff --git a/src/lib/components/card-button.css b/src/lib/components/card-button.css index 0751a229..914bf554 100644 --- a/src/lib/components/card-button.css +++ b/src/lib/components/card-button.css @@ -49,6 +49,10 @@ @apply border border-solid; } +[data-fl-theme="navy"] .card-button { + @apply border border-solid; +} + [data-fl-theme="clay"] .card-button { @apply border-2 border-solid shadow-sm; diff --git a/src/routes/+page.svelte b/src/routes/+page.svelte index 574a2184..90e8f865 100644 --- a/src/routes/+page.svelte +++ b/src/routes/+page.svelte @@ -1,14 +1,15 @@ diff --git a/src/routes/settings/profile/+page.svelte b/src/routes/settings/profile/+page.svelte index e68709ed..603c3c00 100644 --- a/src/routes/settings/profile/+page.svelte +++ b/src/routes/settings/profile/+page.svelte @@ -142,7 +142,7 @@ {/snippet} {#snippet info()}

- Your public key is your nostr user identifier. It also allows other people to authenticate + Your public key is your user identifier. It also allows other people to authenticate your messages.

{/snippet} diff --git a/src/routes/spaces/[relay]/+layout.svelte b/src/routes/spaces/[relay]/+layout.svelte index b1e26e21..e0a35964 100644 --- a/src/routes/spaces/[relay]/+layout.svelte +++ b/src/routes/spaces/[relay]/+layout.svelte @@ -13,7 +13,7 @@ import SpaceAuthError from "@app/components/SpaceAuthError.svelte" import SpaceTrustRelay from "@app/components/SpaceTrustRelay.svelte" import SpaceJoin from "@app/components/SpaceJoin.svelte" - import {pushModal} from "@app/modal" + import {modal, pushModal} from "@app/modal" import {makeSpacePath} from "@app/routes" import {decodeRelay, deriveRelayAuthError} from "@app/relays" import {userGroupList, userSpaceUrls} from "@app/groups" @@ -35,34 +35,19 @@ const showPendingTrust = once(() => pushModal(SpaceTrustRelay, {url}, {noEscape: true})) - // Watch for relay errors and notify the user - $effect(() => { - if ($authError) { - showAuthError() - } else if ($relaysPendingTrust.includes(url)) { - showPendingTrust() - } - }) - - // Direct links skip Discover — prompt to join when relay is not in the user's space list. - const shouldPromptJoin = $derived.by(() => { + // Watch for relay errors and notify the user + // Direct links skip Discover — prompt to join when relay is not in the user's space list. + $effect(() => { void $userGroupList - return ( - Boolean($pubkey) && - !$userSpaceUrls.includes(url) && - !$authError && - !$relaysPendingTrust.includes(url) - ) - }) + if ($modal) return + if ($authError) return showAuthError() + if ($relaysPendingTrust.includes(url)) return showPendingTrust() - $effect(() => { - if (!shouldPromptJoin || joinPrompted.has(url)) { - return + if (!$userSpaceUrls.includes(url)) { + joinPrompted.add(url) + pushModal(SpaceJoin, {url}) } - - joinPrompted.add(url) - pushModal(SpaceJoin, {url}) })