Re work modal scrolling

This commit is contained in:
Jon Staab
2026-01-30 14:14:12 -08:00
parent 4169db33e6
commit 38c0a9d403
75 changed files with 2340 additions and 2030 deletions
+2 -1
View File
@@ -2,6 +2,7 @@
import type {Snippet} from "svelte"
import {page} from "$app/stores"
import {pubkey} from "@welshman/app"
import Dialog from "@lib/components/Dialog.svelte"
import Landing from "@app/components/Landing.svelte"
import Toast from "@app/components/Toast.svelte"
import PrimaryNav from "@app/components/PrimaryNav.svelte"
@@ -20,7 +21,7 @@
{@render children?.()}
</PrimaryNav>
{:else if !$modals[$page.url.hash.slice(1)]}
<Landing />
<Dialog children={{component: Landing, props: {}}} />
{/if}
</div>
<Toast />
+63 -58
View File
@@ -14,6 +14,8 @@
import Button from "@lib/components/Button.svelte"
import Spinner from "@lib/components/Spinner.svelte"
import ModalFooter from "@lib/components/ModalFooter.svelte"
import Modal from "@lib/components/Modal.svelte"
import ModalBody from "@lib/components/ModalBody.svelte"
import DateTimeInput from "@lib/components/DateTimeInput.svelte"
import EditorContent from "@app/editor/EditorContent.svelte"
import {PROTECTED} from "@app/core/state"
@@ -114,64 +116,67 @@
})
</script>
<form novalidate class="column gap-4" onsubmit={preventDefault(submit)}>
{@render header()}
<Field>
{#snippet label()}
<p>Title*</p>
{/snippet}
{#snippet input()}
<label class="input input-bordered flex w-full items-center gap-2">
<input bind:value={title} class="grow" type="text" />
</label>
{/snippet}
</Field>
<Field>
{#snippet label()}
<p>Summary</p>
{/snippet}
{#snippet input()}
<div class="relative z-feature flex gap-2 border-t border-solid border-base-100 bg-base-100">
<div class="input-editor flex-grow overflow-hidden">
<EditorContent {editor} />
<Modal tag="form" novalidate onsubmit={preventDefault(submit)}>
<ModalBody>
{@render header()}
<Field>
{#snippet label()}
<p>Title*</p>
{/snippet}
{#snippet input()}
<label class="input input-bordered flex w-full items-center gap-2">
<input bind:value={title} class="grow" type="text" />
</label>
{/snippet}
</Field>
<Field>
{#snippet label()}
<p>Summary</p>
{/snippet}
{#snippet input()}
<div
class="relative z-feature flex gap-2 border-t border-solid border-base-100 bg-base-100">
<div class="input-editor flex-grow overflow-hidden">
<EditorContent {editor} />
</div>
<Button data-tip="Add an image" class="center btn tooltip" onclick={selectFiles}>
{#if $uploading}
<span class="loading loading-spinner loading-xs"></span>
{:else}
<Icon icon={GallerySend} />
{/if}
</Button>
</div>
<Button data-tip="Add an image" class="center btn tooltip" onclick={selectFiles}>
{#if $uploading}
<span class="loading loading-spinner loading-xs"></span>
{:else}
<Icon icon={GallerySend} />
{/if}
</Button>
</div>
{/snippet}
</Field>
<Field>
{#snippet label()}
Start*
{/snippet}
{#snippet input()}
<DateTimeInput bind:value={start} />
{/snippet}
</Field>
<Field>
{#snippet label()}
End*
{/snippet}
{#snippet input()}
<DateTimeInput bind:value={end} />
{/snippet}
</Field>
<Field>
{#snippet label()}
<p>Location (optional)</p>
{/snippet}
{#snippet input()}
<label class="input input-bordered flex w-full items-center gap-2">
<Icon icon={MapPoint} />
<input bind:value={location} class="grow" type="text" />
</label>
{/snippet}
</Field>
{/snippet}
</Field>
<Field>
{#snippet label()}
Start*
{/snippet}
{#snippet input()}
<DateTimeInput bind:value={start} />
{/snippet}
</Field>
<Field>
{#snippet label()}
End*
{/snippet}
{#snippet input()}
<DateTimeInput bind:value={end} />
{/snippet}
</Field>
<Field>
{#snippet label()}
<p>Location (optional)</p>
{/snippet}
{#snippet input()}
<label class="input input-bordered flex w-full items-center gap-2">
<Icon icon={MapPoint} />
<input bind:value={location} class="grow" type="text" />
</label>
{/snippet}
</Field>
</ModalBody>
<ModalFooter>
<Button class="btn btn-link" onclick={back}>
<Icon icon={AltArrowLeft} />
@@ -181,4 +186,4 @@
<Spinner loading={$uploading}>Save Event</Spinner>
</Button>
</ModalFooter>
</form>
</Modal>
+22 -18
View File
@@ -7,6 +7,8 @@
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"
import ModalFooter from "@lib/components/ModalFooter.svelte"
import {PLATFORM_NAME} from "@app/core/state"
@@ -33,23 +35,25 @@
const back = () => history.back()
</script>
<form class="column gap-4" onsubmit={preventDefault(submit)}>
<ModalHeader>
{#snippet title()}
<div>Enable Messages</div>
{/snippet}
{#snippet info()}
<div>Do you want to enable direct messages?</div>
{/snippet}
</ModalHeader>
<p>
By default, direct messages are disabled, since loading them requires
{PLATFORM_NAME} to download and decrypt a lot of data.
</p>
<p>
If you'd like to enable them, please make sure your signer is set up to to auto-approve requests
to decrypt data.
</p>
<Modal tag="form" onsubmit={preventDefault(submit)}>
<ModalBody>
<ModalHeader>
{#snippet title()}
<div>Enable Messages</div>
{/snippet}
{#snippet info()}
<div>Do you want to enable direct messages?</div>
{/snippet}
</ModalHeader>
<p>
By default, direct messages are disabled, since loading them requires
{PLATFORM_NAME} to download and decrypt a lot of data.
</p>
<p>
If you'd like to enable them, please make sure your signer is set up to to auto-approve
requests to decrypt data.
</p>
</ModalBody>
<ModalFooter>
<Button class="btn btn-link" onclick={back}>
<Icon icon={AltArrowLeft} />
@@ -60,4 +64,4 @@
<Icon icon={AltArrowRight} />
</Button>
</ModalFooter>
</form>
</Modal>
+20 -13
View File
@@ -1,6 +1,9 @@
<script lang="ts">
import Button from "@lib/components/Button.svelte"
import ModalHeader from "@lib/components/ModalHeader.svelte"
import ModalFooter from "@lib/components/ModalFooter.svelte"
import Modal from "@lib/components/Modal.svelte"
import ModalBody from "@lib/components/ModalBody.svelte"
import Profile from "@app/components/Profile.svelte"
interface Props {
@@ -10,16 +13,20 @@
const {pubkeys}: Props = $props()
</script>
<div class="column gap-4">
<ModalHeader>
{#snippet title()}
<div>People in this conversation</div>
{/snippet}
</ModalHeader>
{#each pubkeys as pubkey (pubkey)}
<div class="card2 bg-alt">
<Profile {pubkey} />
</div>
{/each}
<Button class="btn btn-primary" onclick={() => history.back()}>Got it</Button>
</div>
<Modal>
<ModalBody>
<ModalHeader>
{#snippet title()}
<div>People in this conversation</div>
{/snippet}
</ModalHeader>
{#each pubkeys as pubkey (pubkey)}
<div class="card2 bg-alt">
<Profile {pubkey} />
</div>
{/each}
</ModalBody>
<ModalFooter>
<Button class="btn btn-primary" onclick={() => history.back()}>Got it</Button>
</ModalFooter>
</Modal>
+19 -15
View File
@@ -11,6 +11,8 @@
import AltArrowLeft from "@assets/icons/alt-arrow-left.svg?dataurl"
import AltArrowRight from "@assets/icons/alt-arrow-right.svg?dataurl"
import Icon from "@lib/components/Icon.svelte"
import Modal from "@lib/components/Modal.svelte"
import ModalBody from "@lib/components/ModalBody.svelte"
import ModalHeader from "@lib/components/ModalHeader.svelte"
import ModalFooter from "@lib/components/ModalFooter.svelte"
import ProfileMultiSelect from "@app/components/ProfileMultiSelect.svelte"
@@ -52,20 +54,22 @@
})
</script>
<form class="column gap-4" onsubmit={preventDefault(onSubmit)}>
<ModalHeader>
{#snippet title()}
<div>Start a Chat</div>
{/snippet}
{#snippet info()}
<div>Create an encrypted chat room for private conversations.</div>
{/snippet}
</ModalHeader>
<Field>
{#snippet input()}
<ProfileMultiSelect autofocus bind:value={pubkeys} {term} />
{/snippet}
</Field>
<Modal tag="form" onsubmit={preventDefault(onSubmit)}>
<ModalBody>
<ModalHeader>
{#snippet title()}
<div>Start a Chat</div>
{/snippet}
{#snippet info()}
<div>Create an encrypted chat room for private conversations.</div>
{/snippet}
</ModalHeader>
<Field>
{#snippet input()}
<ProfileMultiSelect autofocus bind:value={pubkeys} {term} />
{/snippet}
</Field>
</ModalBody>
<ModalFooter>
<Button class="btn btn-link" onclick={back}>
<Icon icon={AltArrowLeft} />
@@ -76,4 +80,4 @@
<Icon icon={AltArrowRight} />
</Button>
</ModalFooter>
</form>
</Modal>
+69 -65
View File
@@ -11,6 +11,8 @@
import Icon from "@lib/components/Icon.svelte"
import FieldInline from "@lib/components/FieldInline.svelte"
import Button from "@lib/components/Button.svelte"
import Modal from "@lib/components/Modal.svelte"
import ModalBody from "@lib/components/ModalBody.svelte"
import ModalHeader from "@lib/components/ModalHeader.svelte"
import {clip} from "@app/util/toast"
@@ -36,74 +38,76 @@
})
</script>
<div class="column gap-4">
<ModalHeader>
{#snippet title()}
<div>Event Details</div>
{/snippet}
{#snippet info()}
<div>The full details of this event are shown below.</div>
{/snippet}
</ModalHeader>
<FieldInline>
{#snippet label()}
<p>Created At</p>
{/snippet}
{#snippet input()}
<p>{formatter.format(secondsToDate(event.created_at))}</p>
{/snippet}
</FieldInline>
<FieldInline>
{#snippet label()}
<p>Event Link</p>
{/snippet}
{#snippet input()}
<label class="input input-bordered flex w-full items-center gap-2">
<Icon icon={FileText} />
<input type="text" class="ellipsize min-w-0 grow" value={nevent1} />
<Button onclick={copyLink} class="flex items-center">
<Icon icon={Copy} />
</Button>
</label>
{/snippet}
</FieldInline>
<FieldInline>
{#snippet label()}
<p>Author Pubkey</p>
{/snippet}
{#snippet input()}
<label class="input input-bordered flex w-full items-center gap-2">
<Icon icon={UserCircle} />
<input type="text" class="ellipsize min-w-0 grow" value={npub1} />
<Button onclick={copyPubkey} class="flex items-center">
<Icon icon={Copy} />
</Button>
</label>
{/snippet}
</FieldInline>
{#if !url && seenOn.size > 0}
<Modal>
<ModalBody>
<ModalHeader>
{#snippet title()}
<div>Event Details</div>
{/snippet}
{#snippet info()}
<div>The full details of this event are shown below.</div>
{/snippet}
</ModalHeader>
<FieldInline>
{#snippet label()}
<p>Seen On</p>
<p>Created At</p>
{/snippet}
{#snippet input()}
<div class="flex flex-wrap gap-2">
{#each seenOn as url, i (url)}
<span class="bg-alt badge flex gap-1">
{displayRelayUrl(url)}
</span>
{/each}
</div>
<p>{formatter.format(secondsToDate(event.created_at))}</p>
{/snippet}
</FieldInline>
{/if}
<div class="relative">
<pre class="card2 card2-sm bg-alt overflow-auto text-xs"><code>{json}</code></pre>
<p class="absolute right-2 top-2 flex flex-grow items-center justify-between">
<Button onclick={copyJson} class="btn btn-neutral btn-sm flex items-center">
<Icon icon={Copy} /> Copy
</Button>
</p>
</div>
<Button class="btn btn-primary" onclick={() => history.back()}>Got it</Button>
</div>
<FieldInline>
{#snippet label()}
<p>Event Link</p>
{/snippet}
{#snippet input()}
<label class="input input-bordered flex w-full items-center gap-2">
<Icon icon={FileText} />
<input type="text" class="ellipsize min-w-0 grow" value={nevent1} />
<Button onclick={copyLink} class="flex items-center">
<Icon icon={Copy} />
</Button>
</label>
{/snippet}
</FieldInline>
<FieldInline>
{#snippet label()}
<p>Author Pubkey</p>
{/snippet}
{#snippet input()}
<label class="input input-bordered flex w-full items-center gap-2">
<Icon icon={UserCircle} />
<input type="text" class="ellipsize min-w-0 grow" value={npub1} />
<Button onclick={copyPubkey} class="flex items-center">
<Icon icon={Copy} />
</Button>
</label>
{/snippet}
</FieldInline>
{#if !url && seenOn.size > 0}
<FieldInline>
{#snippet label()}
<p>Seen On</p>
{/snippet}
{#snippet input()}
<div class="flex flex-wrap gap-2">
{#each seenOn as url, i (url)}
<span class="bg-alt badge flex gap-1">
{displayRelayUrl(url)}
</span>
{/each}
</div>
{/snippet}
</FieldInline>
{/if}
<div class="relative">
<pre class="card2 card2-sm bg-alt overflow-auto text-xs"><code>{json}</code></pre>
<p class="absolute right-2 top-2 flex flex-grow items-center justify-between">
<Button onclick={copyJson} class="btn btn-neutral btn-sm flex items-center">
<Icon icon={Copy} /> Copy
</Button>
</p>
</div>
<Button class="btn btn-primary" onclick={() => history.back()}>Got it</Button>
</ModalBody>
</Modal>
+26 -22
View File
@@ -9,6 +9,8 @@
import Button from "@lib/components/Button.svelte"
import ModalHeader from "@lib/components/ModalHeader.svelte"
import ModalFooter from "@lib/components/ModalFooter.svelte"
import Modal from "@lib/components/Modal.svelte"
import ModalBody from "@lib/components/ModalBody.svelte"
import RoomName from "@app/components/RoomName.svelte"
import {roomsByUrl} from "@app/core/state"
import {makeRoomPath} from "@app/util/routes"
@@ -29,27 +31,29 @@
let selection = $state("")
</script>
<form class="column gap-4" onsubmit={preventDefault(onSubmit)}>
<ModalHeader>
{#snippet title()}
<div>Share {noun}</div>
{/snippet}
{#snippet info()}
<div>Which room would you like to share this event to?</div>
{/snippet}
</ModalHeader>
<div class="grid grid-cols-3 gap-2">
{#each $roomsByUrl.get(url) || [] as room (room.h)}
<button
type="button"
class="btn"
class:btn-neutral={selection !== room.h}
class:btn-primary={selection === room.h}
onclick={() => toggleRoom(room.h)}>
#<RoomName {...room} />
</button>
{/each}
</div>
<Modal tag="form" onsubmit={preventDefault(onSubmit)}>
<ModalBody>
<ModalHeader>
{#snippet title()}
<div>Share {noun}</div>
{/snippet}
{#snippet info()}
<div>Which room would you like to share this event to?</div>
{/snippet}
</ModalHeader>
<div class="grid grid-cols-3 gap-2">
{#each $roomsByUrl.get(url) || [] as room (room.h)}
<button
type="button"
class="btn"
class:btn-neutral={selection !== room.h}
class:btn-primary={selection === room.h}
onclick={() => toggleRoom(room.h)}>
#<RoomName {...room} />
</button>
{/each}
</div>
</ModalBody>
<ModalFooter>
<Button class="btn btn-link" onclick={back}>
<Icon icon={AltArrowLeft} />
@@ -60,4 +64,4 @@
<Icon icon={AltArrowRight} />
</Button>
</ModalFooter>
</form>
</Modal>
+68 -64
View File
@@ -12,6 +12,8 @@
import Button from "@lib/components/Button.svelte"
import ModalHeader from "@lib/components/ModalHeader.svelte"
import ModalFooter from "@lib/components/ModalFooter.svelte"
import Modal from "@lib/components/Modal.svelte"
import ModalBody from "@lib/components/ModalBody.svelte"
import EditorContent from "@app/editor/EditorContent.svelte"
import {pushToast} from "@app/util/toast"
import {PROTECTED} from "@app/core/state"
@@ -82,78 +84,80 @@
let amount = $state(1000)
</script>
<form class="column gap-4" onsubmit={preventDefault(submit)}>
<ModalHeader>
{#snippet title()}
<div>Create a Funding Goal</div>
{/snippet}
{#snippet info()}
<div>Request contributions for your fundraiser.</div>
{/snippet}
</ModalHeader>
<div class="col-8 relative">
<Field>
{#snippet label()}
<p>Title*</p>
<Modal tag="form" onsubmit={preventDefault(submit)}>
<ModalBody>
<ModalHeader>
{#snippet title()}
<div>Create a Funding Goal</div>
{/snippet}
{#snippet input()}
<label class="input input-bordered flex w-full items-center gap-2">
<!-- svelte-ignore a11y_autofocus -->
<input
autofocus={!isMobile}
bind:value={content}
class="grow"
type="text"
placeholder="What do funds go towards?" />
</label>
{#snippet info()}
<div>Request contributions for your fundraiser.</div>
{/snippet}
</Field>
<div class="relative">
</ModalHeader>
<div class="col-8 relative">
<Field>
{#snippet label()}
<p>Details*</p>
<p>Title*</p>
{/snippet}
{#snippet input()}
<div class="note-editor flex-grow overflow-hidden">
<EditorContent {editor} />
</div>
<label class="input input-bordered flex w-full items-center gap-2">
<!-- svelte-ignore a11y_autofocus -->
<input
autofocus={!isMobile}
bind:value={content}
class="grow"
type="text"
placeholder="What do funds go towards?" />
</label>
{/snippet}
</Field>
<Button
data-tip="Add an image"
class="tooltip tooltip-left absolute bottom-1 right-2"
onclick={selectFiles}>
{#if $uploading}
<span class="loading loading-spinner loading-xs"></span>
{:else}
<Icon icon={Paperclip} size={3} />
{/if}
</Button>
<div class="relative">
<Field>
{#snippet label()}
<p>Details*</p>
{/snippet}
{#snippet input()}
<div class="note-editor flex-grow overflow-hidden">
<EditorContent {editor} />
</div>
{/snippet}
</Field>
<Button
data-tip="Add an image"
class="tooltip tooltip-left absolute bottom-1 right-2"
onclick={selectFiles}>
{#if $uploading}
<span class="loading loading-spinner loading-xs"></span>
{:else}
<Icon icon={Paperclip} size={3} />
{/if}
</Button>
</div>
<div class="flex flex-col gap-1">
<FieldInline>
{#snippet label()}
Goal Amount (sats)*
{/snippet}
{#snippet input()}
<div class="flex flex-grow justify-end">
<label class="input input-bordered flex items-center gap-2">
<Icon icon={Bolt} />
<input bind:value={amount} type="number" class="w-28" />
<p class="opacity-50">sats</p>
</label>
</div>
{/snippet}
</FieldInline>
<input
class="range range-primary -mt-2"
type="range"
min="1000"
max="100000"
step="1000"
bind:value={amount} />
</div>
</div>
<div class="flex flex-col gap-1">
<FieldInline>
{#snippet label()}
Goal Amount (sats)*
{/snippet}
{#snippet input()}
<div class="flex flex-grow justify-end">
<label class="input input-bordered flex items-center gap-2">
<Icon icon={Bolt} />
<input bind:value={amount} type="number" class="w-28" />
<p class="opacity-50">sats</p>
</label>
</div>
{/snippet}
</FieldInline>
<input
class="range range-primary -mt-2"
type="range"
min="1000"
max="100000"
step="1000"
bind:value={amount} />
</div>
</div>
</ModalBody>
<ModalFooter>
<Button class="btn btn-link" onclick={back}>
<Icon icon={AltArrowLeft} />
@@ -161,4 +165,4 @@
</Button>
<Button type="submit" class="btn btn-primary">Create Goal</Button>
</ModalFooter>
</form>
</Modal>
+35 -32
View File
@@ -1,39 +1,42 @@
<script lang="ts">
import Link from "@lib/components/Link.svelte"
import Button from "@lib/components/Button.svelte"
import Modal from "@lib/components/Modal.svelte"
import ModalBody from "@lib/components/ModalBody.svelte"
import ModalHeader from "@lib/components/ModalHeader.svelte"
import {PLATFORM_NAME} from "@app/core/state"
</script>
<div class="column gap-4">
<ModalHeader>
{#snippet title()}
<div>What is a bunker link?</div>
{/snippet}
</ModalHeader>
<p>
<Link external class="link" href="https://nostr.com/">Nostr</Link> uses "keys" instead of passwords
to identify users. This allows users to own their social identity instead of renting it from a tech
company, and can bring it with them from app to app.
</p>
<p>
A good way to manage your keys is to use a remote signing application. These apps can hold your
keys and log you in remotely to as many applications as you like, without risking loss or theft
of your keys.
</p>
<p>
One way to log in with a remote signer is using a "bunker link" which is more secure and
decentralized than other solutions. Check your signer for a link beginning with "bunker://",
copy it into {PLATFORM_NAME}, and you should be good to go!
</p>
<p>
If you don't have a signer yet, <Link external class="link" href="https://nsec.app/"
>nsec.app</Link>
is a great way to get started. You can find more signers on <Link
external
class="link"
href="https://nostrapps.com#signers">nostrapps.com</Link
>.
</p>
<Button class="btn btn-primary" onclick={() => history.back()}>Got it</Button>
</div>
<Modal
><ModalBody>
<ModalHeader>
{#snippet title()}
<div>What is a bunker link?</div>
{/snippet}
</ModalHeader>
<p>
<Link external class="link" href="https://nostr.com/">Nostr</Link> uses "keys" instead of passwords
to identify users. This allows users to own their social identity instead of renting it from a tech
company, and can bring it with them from app to app.
</p>
<p>
A good way to manage your keys is to use a remote signing application. These apps can hold
your keys and log you in remotely to as many applications as you like, without risking loss or
theft of your keys.
</p>
<p>
One way to log in with a remote signer is using a "bunker link" which is more secure and
decentralized than other solutions. Check your signer for a link beginning with "bunker://",
copy it into {PLATFORM_NAME}, and you should be good to go!
</p>
<p>
If you don't have a signer yet, <Link external class="link" href="https://nsec.app/"
>nsec.app</Link>
is a great way to get started. You can find more signers on <Link
external
class="link"
href="https://nostrapps.com#signers">nostrapps.com</Link
>.
</p>
<Button class="btn btn-primary" onclick={() => history.back()}>Got it</Button>
</ModalBody></Modal>
+25 -22
View File
@@ -1,29 +1,32 @@
<script lang="ts">
import Button from "@lib/components/Button.svelte"
import Link from "@lib/components/Link.svelte"
import Modal from "@lib/components/Modal.svelte"
import ModalBody from "@lib/components/ModalBody.svelte"
import ModalHeader from "@lib/components/ModalHeader.svelte"
import {PLATFORM_NAME} from "@app/core/state"
</script>
<div class="column gap-4">
<ModalHeader>
{#snippet title()}
<div>What is a nostr address?</div>
{/snippet}
</ModalHeader>
<p>
{PLATFORM_NAME} hosts spaces on the <Link external href="https://nostr.com/" class="underline"
>Nostr protocol</Link
>. Nostr uses "nostr addresses" to make it easier for people to find you, without having to
memorize your public key (your user id).
</p>
<p>
There are several providers of nostr addresses, including several clients. You can find a list
and more information on <Link
external
href="https://nostr.how/en/guides/get-verified"
class="underline">nostr.how</Link
>.
</p>
<Button class="btn btn-primary" onclick={() => history.back()}>Got it</Button>
</div>
<Modal
><ModalBody>
<ModalHeader>
{#snippet title()}
<div>What is a nostr address?</div>
{/snippet}
</ModalHeader>
<p>
{PLATFORM_NAME} hosts spaces on the <Link external href="https://nostr.com/" class="underline"
>Nostr protocol</Link
>. Nostr uses "nostr addresses" to make it easier for people to find you, without having to
memorize your public key (your user id).
</p>
<p>
There are several providers of nostr addresses, including several clients. You can find a list
and more information on <Link
external
href="https://nostr.how/en/guides/get-verified"
class="underline">nostr.how</Link
>.
</p>
<Button class="btn btn-primary" onclick={() => history.back()}>Got it</Button>
</ModalBody></Modal>
+32 -24
View File
@@ -5,6 +5,8 @@
import CheckCircle from "@assets/icons/check-circle.svg?dataurl"
import Icon from "@lib/components/Icon.svelte"
import Button from "@lib/components/Button.svelte"
import Modal from "@lib/components/Modal.svelte"
import ModalBody from "@lib/components/ModalBody.svelte"
import ModalHeader from "@lib/components/ModalHeader.svelte"
import ModalFooter from "@lib/components/ModalFooter.svelte"
import KeyRecoveryRequest from "@app/components/KeyRecoveryRequest.svelte"
@@ -16,29 +18,37 @@
const startRecoveryRequest = () => pushModal(KeyRecoveryRequest)
</script>
<div class="column gap-4">
<ModalHeader>
{#snippet title()}
<div>What is a private key?</div>
{/snippet}
</ModalHeader>
<p>
Most online services keep track of users by giving them a username and password. This gives the
service <strong>total control</strong> over their users, allowing them to ban them at any time, or
sell their activity.
</p>
<p>
On <Link external href="https://nostr.com/">Nostr</Link>, <strong>you</strong> control your own
identity and social data, through the magic of cryptography. The basic idea is that you have a
<strong>public key</strong>, which acts as your user ID, and a
<strong>private key</strong> which allows you to prove your identity.
</p>
{#if $session?.email}
<Modal>
<ModalBody>
<ModalHeader>
{#snippet title()}
<div>What is a private key?</div>
{/snippet}
</ModalHeader>
<p>
It's very important to keep private keys safe, but this can sometimes be tricky, which is why {PLATFORM_NAME}
supports a traditional account-based login for new users.
Most online services keep track of users by giving them a username and password. This gives
the service <strong>total control</strong> over their users, allowing them to ban them at any time,
or sell their activity.
</p>
<p>If you'd like to switch to self-custody, please click below to get started.</p>
<p>
On <Link external href="https://nostr.com/">Nostr</Link>, <strong>you</strong> control your
own identity and social data, through the magic of cryptography. The basic idea is that you
have a
<strong>public key</strong>, which acts as your user ID, and a
<strong>private key</strong> which allows you to prove your identity.
</p>
{#if $session?.email}
<p>
It's very important to keep private keys safe, but this can sometimes be tricky, which is
why {PLATFORM_NAME}
supports a traditional account-based login for new users.
</p>
<p>If you'd like to switch to self-custody, please click below to get started.</p>
{:else}
<Button class="btn btn-primary" onclick={back}>Got it</Button>
{/if}
</ModalBody>
{#if $session?.email}
<ModalFooter>
<Button class="btn btn-link" onclick={back}>
<Icon icon={AltArrowLeft} />
@@ -49,7 +59,5 @@
I want to hold my own keys
</Button>
</ModalFooter>
{:else}
<Button class="btn btn-primary" onclick={back}>Got it</Button>
{/if}
</div>
</Modal>
+27 -24
View File
@@ -1,30 +1,33 @@
<script lang="ts">
import Link from "@lib/components/Link.svelte"
import Button from "@lib/components/Button.svelte"
import Modal from "@lib/components/Modal.svelte"
import ModalBody from "@lib/components/ModalBody.svelte"
import ModalHeader from "@lib/components/ModalHeader.svelte"
</script>
<div class="column gap-4">
<ModalHeader>
{#snippet title()}
<div>What is nostr?</div>
{/snippet}
</ModalHeader>
<p>
<Link external href="https://nostr.com/" class="link">Nostr</Link> is way to build social apps that
talk to each other. Users own their social identity instead of renting it from a tech company, and
can take it with them.
</p>
<p>
If you'd like to learn more about what other apps exist in the nostr ecosystem, please visit <Link
external
class="link"
href="https://nostrapps.com/">nostrapps.com</Link
>.
</p>
<p>
To learn more about how to manage your keys, or to set up an account, try
<Link external class="link" href="https://nosta.me/">nosta.me</Link>.
</p>
<Button class="btn btn-primary" onclick={() => history.back()}>Got it</Button>
</div>
<Modal
><ModalBody>
<ModalHeader>
{#snippet title()}
<div>What is nostr?</div>
{/snippet}
</ModalHeader>
<p>
<Link external href="https://nostr.com/" class="link">Nostr</Link> is way to build social apps that
talk to each other. Users own their social identity instead of renting it from a tech company, and
can take it with them.
</p>
<p>
If you'd like to learn more about what other apps exist in the nostr ecosystem, please visit <Link
external
class="link"
href="https://nostrapps.com/">nostrapps.com</Link
>.
</p>
<p>
To learn more about how to manage your keys, or to set up an account, try
<Link external class="link" href="https://nosta.me/">nosta.me</Link>.
</p>
<Button class="btn btn-primary" onclick={() => history.back()}>Got it</Button>
</ModalBody></Modal>
+26 -19
View File
@@ -2,25 +2,32 @@
import Button from "@lib/components/Button.svelte"
import Link from "@lib/components/Link.svelte"
import ModalHeader from "@lib/components/ModalHeader.svelte"
import ModalFooter from "@lib/components/ModalFooter.svelte"
import Modal from "@lib/components/Modal.svelte"
import ModalBody from "@lib/components/ModalBody.svelte"
import {PLATFORM_NAME} from "@app/core/state"
</script>
<div class="column gap-4">
<ModalHeader>
{#snippet title()}
<div>What is a relay?</div>
{/snippet}
</ModalHeader>
<p>
{PLATFORM_NAME} hosts spaces on the <Link external href="https://nostr.com/" class="underline"
>Nostr protocol</Link
>. Nostr uses "relays" to host data, which are special-purpose servers that speak nostr's
language. This means that anyone can host their own data, making the web more decentralized and
resilient.
</p>
<p>
Different relays have different policies for access control and content retention. Be sure to
double check that you have access to the relays you try to use by visiting their website.
</p>
<Button class="btn btn-primary" onclick={() => history.back()}>Got it</Button>
</div>
<Modal>
<ModalBody>
<ModalHeader>
{#snippet title()}
<div>What is a relay?</div>
{/snippet}
</ModalHeader>
<p>
{PLATFORM_NAME} hosts spaces on the <Link external href="https://nostr.com/" class="underline"
>Nostr protocol</Link
>. Nostr uses "relays" to host data, which are special-purpose servers that speak nostr's
language. This means that anyone can host their own data, making the web more decentralized
and resilient.
</p>
<p>
Different relays have different policies for access control and content retention. Be sure to
double check that you have access to the relays you try to use by visiting their website.
</p>
</ModalBody>
<ModalFooter>
<Button class="btn btn-primary" onclick={() => history.back()}>Got it</Button>
</ModalFooter>
</Modal>
+27 -24
View File
@@ -1,32 +1,35 @@
<script lang="ts">
import Link from "@lib/components/Link.svelte"
import Button from "@lib/components/Button.svelte"
import Modal from "@lib/components/Modal.svelte"
import ModalBody from "@lib/components/ModalBody.svelte"
import ModalHeader from "@lib/components/ModalHeader.svelte"
const back = () => history.back()
</script>
<div class="column gap-4">
<ModalHeader>
{#snippet title()}
<div>What are digital signatures?</div>
{/snippet}
</ModalHeader>
<p>
Most online services ask their users to trust them that they're being honest, and they usually
are. However, traditional social media platforms have the ability to <strong
>create forged content</strong> that can appear to be genuinely authored, but which are actually
counterfeit.
</p>
<p>
On <Link external href="https://nostr.com/">Nostr</Link>, all your content is authenticated
using <strong>digital signatures</strong>, which cryptographically tie a particular person to a
given post or message.
</p>
<p>
The result is that you don't normally have to trust service providers not to tamper with the
information flowing through the network — instead, your client software can prove that a given
piece of data is authentic.
</p>
<Button class="btn btn-primary" onclick={back}>Got it</Button>
</div>
<Modal
><ModalBody>
<ModalHeader>
{#snippet title()}
<div>What are digital signatures?</div>
{/snippet}
</ModalHeader>
<p>
Most online services ask their users to trust them that they're being honest, and they usually
are. However, traditional social media platforms have the ability to <strong
>create forged content</strong> that can appear to be genuinely authored, but which are actually
counterfeit.
</p>
<p>
On <Link external href="https://nostr.com/">Nostr</Link>, all your content is authenticated
using <strong>digital signatures</strong>, which cryptographically tie a particular person to
a given post or message.
</p>
<p>
The result is that you don't normally have to trust service providers not to tamper with the
information flowing through the network — instead, your client software can prove that a given
piece of data is authentic.
</p>
<Button class="btn btn-primary" onclick={back}>Got it</Button>
</ModalBody></Modal>
+19 -15
View File
@@ -5,6 +5,8 @@
import Button from "@lib/components/Button.svelte"
import ModalHeader from "@lib/components/ModalHeader.svelte"
import ModalFooter from "@lib/components/ModalFooter.svelte"
import Modal from "@lib/components/Modal.svelte"
import ModalBody from "@lib/components/ModalBody.svelte"
import ProfileLink from "@app/components/ProfileLink.svelte"
const {pubkey} = $props()
@@ -14,24 +16,26 @@
const back = () => history.back()
</script>
<div class="column gap-4">
<ModalHeader>
{#snippet title()}
<div>Unable to Zap</div>
{/snippet}
</ModalHeader>
<p>
Zapping <ProfileLink {pubkey} class="!text-primary" /> isn't possible right now because
{#if $zapper}
their zap receiver isn't correctly set up.
{:else}
they don't currently have a zap receiver set up.
{/if}
</p>
<Modal>
<ModalBody>
<ModalHeader>
{#snippet title()}
<div>Unable to Zap</div>
{/snippet}
</ModalHeader>
<p>
Zapping <ProfileLink {pubkey} class="!text-primary" /> isn't possible right now because
{#if $zapper}
their zap receiver isn't correctly set up.
{:else}
they don't currently have a zap receiver set up.
{/if}
</p>
</ModalBody>
<ModalFooter>
<Button class="btn btn-link" onclick={back}>
<Icon icon={AltArrowLeft} />
Go back
</Button>
</ModalFooter>
</div>
</Modal>
+47 -43
View File
@@ -10,6 +10,8 @@
import Icon from "@lib/components/Icon.svelte"
import Field from "@lib/components/Field.svelte"
import Button from "@lib/components/Button.svelte"
import Modal from "@lib/components/Modal.svelte"
import ModalBody from "@lib/components/ModalBody.svelte"
import ModalHeader from "@lib/components/ModalHeader.svelte"
import ModalFooter from "@lib/components/ModalFooter.svelte"
import {pushToast} from "@app/util/toast"
@@ -104,49 +106,51 @@
let didDownload = $state(false)
</script>
<form class="column gap-4" onsubmit={preventDefault(next)}>
<ModalHeader>
{#snippet title()}
<div>Your Keys are Ready!</div>
{/snippet}
</ModalHeader>
<p>
A cryptographic key pair has two parts: your <strong>public key</strong> identifies your
account, while your <strong>private key</strong> acts sort of like a master password.
</p>
<p>
Securing your private key is very important, so make sure to take the time to save your key in a
secure place (like a password manager).
</p>
{#if usePassword}
<Field>
{#snippet label()}
Password*
<Modal tag="form" onsubmit={preventDefault(next)}>
<ModalBody>
<ModalHeader>
{#snippet title()}
<div>Your Keys are Ready!</div>
{/snippet}
{#snippet input()}
<label class="input input-bordered flex w-full items-center gap-2">
<Icon icon={Key} />
<input bind:value={password} onchange={onPasswordChange} class="grow" type="password" />
</label>
{/snippet}
{#snippet info()}
<p>Passwords should be at least 12 characters long. Write this down!</p>
{/snippet}
</Field>
{/if}
<div class="flex flex-col">
<Button class="btn {didDownload ? 'btn-neutral' : 'btn-primary'}" onclick={downloadKey}>
Download my key
<Icon icon={ArrowDown} />
</Button>
<Button class="btn btn-link no-underline" onclick={toggleUsePassword}>
{#if usePassword}
Nevermind, I want to download the plain version
{:else}
I want to download an encrypted version
{/if}
</Button>
</div>
</ModalHeader>
<p>
A cryptographic key pair has two parts: your <strong>public key</strong> identifies your
account, while your <strong>private key</strong> acts sort of like a master password.
</p>
<p>
Securing your private key is very important, so make sure to take the time to save your key in
a secure place (like a password manager).
</p>
{#if usePassword}
<Field>
{#snippet label()}
Password*
{/snippet}
{#snippet input()}
<label class="input input-bordered flex w-full items-center gap-2">
<Icon icon={Key} />
<input bind:value={password} onchange={onPasswordChange} class="grow" type="password" />
</label>
{/snippet}
{#snippet info()}
<p>Passwords should be at least 12 characters long. Write this down!</p>
{/snippet}
</Field>
{/if}
<div class="flex flex-col">
<Button class="btn {didDownload ? 'btn-neutral' : 'btn-primary'}" onclick={downloadKey}>
Download my key
<Icon icon={ArrowDown} />
</Button>
<Button class="btn btn-link no-underline" onclick={toggleUsePassword}>
{#if usePassword}
Nevermind, I want to download the plain version
{:else}
I want to download an encrypted version
{/if}
</Button>
</div>
</ModalBody>
<ModalFooter>
<Button class="btn btn-link" onclick={back}>
<Icon icon={AltArrowLeft} />
@@ -157,4 +161,4 @@
<Icon icon={AltArrowRight} />
</Button>
</ModalFooter>
</form>
</Modal>
+23 -19
View File
@@ -9,6 +9,8 @@
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"
import ModalFooter from "@lib/components/ModalFooter.svelte"
import KeyDownload from "@app/components/KeyDownload.svelte"
@@ -81,24 +83,26 @@
let input = $state("")
</script>
<form class="column gap-4" onsubmit={preventDefault(submit)}>
<ModalHeader>
{#snippet title()}
Recover your Key
{/snippet}
{#snippet info()}
Take control over your cryptographic identity
{/snippet}
</ModalHeader>
<p>Your recovery codes have been sent!</p>
<p>
For security reasons, you may receive three or more emails with recovery codes in them. Please
paste <strong>all</strong> recovery codes into the text box below, on separate lines.
</p>
<textarea
rows={POMADE_SIGNERS.length + 1}
class="textarea textarea-bordered leading-4"
bind:value={input}></textarea>
<Modal tag="form" onsubmit={preventDefault(submit)}>
<ModalBody>
<ModalHeader>
{#snippet title()}
Recover your Key
{/snippet}
{#snippet info()}
Take control over your cryptographic identity
{/snippet}
</ModalHeader>
<p>Your recovery codes have been sent!</p>
<p>
For security reasons, you may receive three or more emails with recovery codes in them. Please
paste <strong>all</strong> recovery codes into the text box below, on separate lines.
</p>
<textarea
rows={POMADE_SIGNERS.length + 1}
class="textarea textarea-bordered leading-4"
bind:value={input}></textarea>
</ModalBody>
<ModalFooter>
<Button class="btn btn-link" onclick={back}>
<Icon icon={AltArrowLeft} />
@@ -109,4 +113,4 @@
<Icon icon={AltArrowRight} />
</Button>
</ModalFooter>
</form>
</Modal>
+22 -18
View File
@@ -8,6 +8,8 @@
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"
import ModalFooter from "@lib/components/ModalFooter.svelte"
import {pushModal} from "@app/util/modal"
@@ -39,23 +41,25 @@
let loading = $state(false)
</script>
<form class="column gap-4" onsubmit={preventDefault(submit)}>
<ModalHeader>
{#snippet title()}
Recover your Key
{/snippet}
{#snippet info()}
Take control over your cryptographic identity
{/snippet}
</ModalHeader>
<p>
When you signed up, your Nostr secret key was split into multiple pieces and stored on separate
third-party servers to keep it safe.
</p>
<p>
If you're ready to take control of your cryptographic identity, click below. We'll confirm your
email by sending you some recovery codes.
</p>
<Modal tag="form" onsubmit={preventDefault(submit)}>
<ModalBody>
<ModalHeader>
{#snippet title()}
Recover your Key
{/snippet}
{#snippet info()}
Take control over your cryptographic identity
{/snippet}
</ModalHeader>
<p>
When you signed up, your Nostr secret key was split into multiple pieces and stored on
separate third-party servers to keep it safe.
</p>
<p>
If you're ready to take control of your cryptographic identity, click below. We'll confirm
your email by sending you some recovery codes.
</p>
</ModalBody>
<ModalFooter>
<Button class="btn btn-link" onclick={back}>
<Icon icon={AltArrowLeft} />
@@ -66,4 +70,4 @@
<Icon icon={AltArrowRight} />
</Button>
</ModalFooter>
</form>
</Modal>
+6 -5
View File
@@ -4,7 +4,8 @@
import Icon from "@lib/components/Icon.svelte"
import Button from "@lib/components/Button.svelte"
import Link from "@lib/components/Link.svelte"
import Dialog from "@lib/components/Dialog.svelte"
import Modal from "@lib/components/Modal.svelte"
import ModalBody from "@lib/components/ModalBody.svelte"
import CardButton from "@lib/components/CardButton.svelte"
import LogIn from "@app/components/LogIn.svelte"
import SignUp from "@app/components/SignUp.svelte"
@@ -16,8 +17,8 @@
const signUp = () => pushModal(SignUp)
</script>
<Dialog>
<div class="column gap-4">
<Modal>
<ModalBody>
<div class="py-2">
<h1 class="heading">Welcome to {PLATFORM_NAME}!</h1>
<p class="text-center">The chat app built for self-hosted communities.</p>
@@ -53,5 +54,5 @@
<Link external class="link" href={PLATFORM_TERMS}>Terms of Service</Link> and
<Link external class="link" href={PLATFORM_PRIVACY}>Privacy Policy</Link>.
</p>
</div>
</Dialog>
</ModalBody>
</Modal>
+70 -66
View File
@@ -10,6 +10,8 @@
import Key from "@assets/icons/key.svg?dataurl"
import Icon from "@lib/components/Icon.svelte"
import Link from "@lib/components/Link.svelte"
import Modal from "@lib/components/Modal.svelte"
import ModalBody from "@lib/components/ModalBody.svelte"
import Button from "@lib/components/Button.svelte"
import SignUp from "@app/components/SignUp.svelte"
import InfoNostr from "@app/components/InfoNostr.svelte"
@@ -91,70 +93,72 @@
})
</script>
<div class="column gap-4">
<h1 class="heading">Log in with Nostr</h1>
<p class="m-auto max-w-sm text-center">
{PLATFORM_NAME} is built using the
<Button class="link" onclick={() => pushModal(InfoNostr)}>nostr protocol</Button>, which allows
you to own your social identity.
</p>
{#if getNip07()}
<Button {disabled} onclick={loginWithNip07} class="btn btn-primary">
{#if loading === "nip07"}
<span class="loading loading-spinner mr-3"></span>
{:else}
<Icon icon={Widget} />
{/if}
Log in with Extension
</Button>
{/if}
{#each signers as app}
<Button {disabled} class="btn btn-primary" onclick={() => loginWithNip55(app)}>
{#if loading === "nip55"}
<span class="loading loading-spinner mr-3"></span>
{:else}
<img src={app.iconUrl} alt={app.name} width="20" height="20" />
{/if}
Log in with {app.name}
</Button>
{/each}
{#if hasPomade && !hasSigner}
<Button {disabled} onclick={loginWithEmail} class="btn btn-primary">
<Icon icon={Letter} />
Log in with Email
</Button>
{/if}
<Button
onclick={loginWithBunker}
{disabled}
class="btn {hasSigner || hasPomade ? 'btn-neutral' : 'btn-primary'}">
<Icon icon={Cpu} />
Log in with Remote Signer
</Button>
{#if hasPomade && hasSigner}
<Button {disabled} onclick={loginWithEmail} class="btn">
<Icon icon={Letter} />
Log in with Email
</Button>
{/if}
{#if !hasSigner}
<Button {disabled} onclick={loginWithKey} class="btn btn-neutral">
<Icon icon={Key} />
Log in with Key
</Button>
{/if}
{#if !hasSigner || !hasPomade}
<Link
external
<Modal>
<ModalBody>
<h1 class="heading">Log in with Nostr</h1>
<p class="m-auto max-w-sm text-center">
{PLATFORM_NAME} is built using the
<Button class="link" onclick={() => pushModal(InfoNostr)}>nostr protocol</Button>, which
allows you to own your social identity.
</p>
{#if getNip07()}
<Button {disabled} onclick={loginWithNip07} class="btn btn-primary">
{#if loading === "nip07"}
<span class="loading loading-spinner mr-3"></span>
{:else}
<Icon icon={Widget} />
{/if}
Log in with Extension
</Button>
{/if}
{#each signers as app}
<Button {disabled} class="btn btn-primary" onclick={() => loginWithNip55(app)}>
{#if loading === "nip55"}
<span class="loading loading-spinner mr-3"></span>
{:else}
<img src={app.iconUrl} alt={app.name} width="20" height="20" />
{/if}
Log in with {app.name}
</Button>
{/each}
{#if hasPomade && !hasSigner}
<Button {disabled} onclick={loginWithEmail} class="btn btn-primary">
<Icon icon={Letter} />
Log in with Email
</Button>
{/if}
<Button
onclick={loginWithBunker}
{disabled}
href="https://nostrapps.com#signers"
class="btn {hasSigner || hasPomade ? '' : 'btn-neutral'}">
<Icon icon={Compass} />
Browse Signer Apps
</Link>
{/if}
<div class="text-sm">
Need an account?
<Button class="link" onclick={signUp}>Register instead</Button>
</div>
</div>
class="btn {hasSigner || hasPomade ? 'btn-neutral' : 'btn-primary'}">
<Icon icon={Cpu} />
Log in with Remote Signer
</Button>
{#if hasPomade && hasSigner}
<Button {disabled} onclick={loginWithEmail} class="btn">
<Icon icon={Letter} />
Log in with Email
</Button>
{/if}
{#if !hasSigner}
<Button {disabled} onclick={loginWithKey} class="btn btn-neutral">
<Icon icon={Key} />
Log in with Key
</Button>
{/if}
{#if !hasSigner || !hasPomade}
<Link
external
{disabled}
href="https://nostrapps.com#signers"
class="btn {hasSigner || hasPomade ? '' : 'btn-neutral'}">
<Icon icon={Compass} />
Browse Signer Apps
</Link>
{/if}
<div class="text-sm">
Need an account?
<Button class="link" onclick={signUp}>Register instead</Button>
</div>
</ModalBody>
</Modal>
+22 -18
View File
@@ -10,6 +10,8 @@
import AltArrowLeft from "@assets/icons/alt-arrow-left.svg?dataurl"
import AltArrowRight from "@assets/icons/alt-arrow-right.svg?dataurl"
import Icon from "@lib/components/Icon.svelte"
import Modal from "@lib/components/Modal.svelte"
import ModalBody from "@lib/components/ModalBody.svelte"
import ModalHeader from "@lib/components/ModalHeader.svelte"
import ModalFooter from "@lib/components/ModalFooter.svelte"
import BunkerConnect from "@app/components/BunkerConnect.svelte"
@@ -120,23 +122,25 @@
})
</script>
<form class="column gap-4" onsubmit={preventDefault(onSubmit)}>
<ModalHeader>
{#snippet title()}
<div>Log In with a Signer</div>
{/snippet}
{#snippet info()}
<div>Using a remote signer app helps you keep your keys safe.</div>
{/snippet}
</ModalHeader>
<div class:hidden={mode !== "bunker"}></div>
{#if mode === "connect"}
<BunkerConnect {controller} />
{:else}
<BunkerUrl {controller} />
<Button class="btn {$bunker ? 'btn-neutral' : 'btn-primary'}" onclick={selectConnect}
>Log in with a QR code instead</Button>
{/if}
<Modal tag="form" onsubmit={preventDefault(onSubmit)}>
<ModalBody>
<ModalHeader>
{#snippet title()}
<div>Log In with a Signer</div>
{/snippet}
{#snippet info()}
<div>Using a remote signer app helps you keep your keys safe.</div>
{/snippet}
</ModalHeader>
<div class:hidden={mode !== "bunker"}></div>
{#if mode === "connect"}
<BunkerConnect {controller} />
{:else}
<BunkerUrl {controller} />
<Button class="btn {$bunker ? 'btn-neutral' : 'btn-primary'}" onclick={selectConnect}
>Log in with a QR code instead</Button>
{/if}
</ModalBody>
<ModalFooter>
<Button class="btn btn-link" onclick={back} disabled={$loading}>
<Icon icon={AltArrowLeft} />
@@ -149,4 +153,4 @@
</Button>
{/if}
</ModalFooter>
</form>
</Modal>
+41 -37
View File
@@ -10,6 +10,8 @@
import AltArrowLeft from "@assets/icons/alt-arrow-left.svg?dataurl"
import AltArrowRight from "@assets/icons/alt-arrow-right.svg?dataurl"
import Icon from "@lib/components/Icon.svelte"
import Modal from "@lib/components/Modal.svelte"
import ModalBody from "@lib/components/ModalBody.svelte"
import ModalHeader from "@lib/components/ModalHeader.svelte"
import ModalFooter from "@lib/components/ModalFooter.svelte"
import LogInOTP from "@app/components/LogInOTP.svelte"
@@ -67,42 +69,44 @@
let password = $state("")
</script>
<form class="column gap-4" onsubmit={preventDefault(onSubmit)}>
<ModalHeader>
{#snippet title()}
<div>Log In</div>
{/snippet}
{#snippet info()}
<div>Log in using your email and password</div>
{/snippet}
</ModalHeader>
<FieldInline>
{#snippet label()}
<p>Email*</p>
{/snippet}
{#snippet input()}
<label class="input input-bordered flex w-full items-center gap-2">
<Icon icon={Letter} />
<input bind:value={email} />
</label>
{/snippet}
</FieldInline>
<FieldInline>
{#snippet label()}
<p>Password*</p>
{/snippet}
{#snippet input()}
<label class="input input-bordered flex w-full items-center gap-2">
<Icon icon={Key} />
<input type="password" bind:value={password} />
</label>
{/snippet}
</FieldInline>
<p class="text-sm">
Forgot your password? <Button class="link" onclick={loginWithOTP}
>Log in with a one-time access code</Button
>.
</p>
<Modal tag="form" onsubmit={preventDefault(onSubmit)}>
<ModalBody>
<ModalHeader>
{#snippet title()}
<div>Log In</div>
{/snippet}
{#snippet info()}
<div>Log in using your email and password</div>
{/snippet}
</ModalHeader>
<FieldInline>
{#snippet label()}
<p>Email*</p>
{/snippet}
{#snippet input()}
<label class="input input-bordered flex w-full items-center gap-2">
<Icon icon={Letter} />
<input bind:value={email} />
</label>
{/snippet}
</FieldInline>
<FieldInline>
{#snippet label()}
<p>Password*</p>
{/snippet}
{#snippet input()}
<label class="input input-bordered flex w-full items-center gap-2">
<Icon icon={Key} />
<input type="password" bind:value={password} />
</label>
{/snippet}
</FieldInline>
<p class="text-sm">
Forgot your password? <Button class="link" onclick={loginWithOTP}
>Log in with a one-time access code</Button
>.
</p>
</ModalBody>
<ModalFooter>
<Button class="btn btn-link" onclick={back} disabled={loading}>
<Icon icon={AltArrowLeft} />
@@ -113,4 +117,4 @@
<Icon icon={AltArrowRight} />
</Button>
</ModalFooter>
</form>
</Modal>
+40 -36
View File
@@ -13,6 +13,8 @@
import AltArrowLeft from "@assets/icons/alt-arrow-left.svg?dataurl"
import AltArrowRight from "@assets/icons/alt-arrow-right.svg?dataurl"
import Icon from "@lib/components/Icon.svelte"
import Modal from "@lib/components/Modal.svelte"
import ModalBody from "@lib/components/ModalBody.svelte"
import ModalHeader from "@lib/components/ModalHeader.svelte"
import ModalFooter from "@lib/components/ModalFooter.svelte"
import {clearModals} from "@app/util/modal"
@@ -71,50 +73,52 @@
}
</script>
<form class="column gap-4" onsubmit={preventDefault(onSubmit)}>
<ModalHeader>
{#snippet title()}
<div>Log In with Key</div>
{/snippet}
{#snippet info()}
<div>Enter your nostr private key to log in.</div>
{/snippet}
</ModalHeader>
<FieldInline>
{#snippet label()}
<p>Your Key*</p>
{/snippet}
{#snippet input()}
<label class="input input-bordered flex w-full items-center gap-2">
<Icon icon={Key} />
<input type="password" bind:value={keyInput} placeholder="nsec1..." />
</label>
{/snippet}
</FieldInline>
{#if isNcryptsec}
<Modal tag="form" onsubmit={preventDefault(onSubmit)}>
<ModalBody>
<ModalHeader>
{#snippet title()}
<div>Log In with Key</div>
{/snippet}
{#snippet info()}
<div>Enter your nostr private key to log in.</div>
{/snippet}
</ModalHeader>
<FieldInline>
{#snippet label()}
<p>Password*</p>
<p>Your Key*</p>
{/snippet}
{#snippet input()}
<label class="input input-bordered flex w-full items-center gap-2">
<Icon icon={Key} />
<input type="password" bind:value={password} placeholder="Your password" />
<input type="password" bind:value={keyInput} placeholder="nsec1..." />
</label>
{/snippet}
</FieldInline>
{/if}
<div class="card2 card2-sm bg-alt flex flex-col gap-2 text-sm">
<strong class="flex items-center gap-2">
<Icon icon={Danger} />
Please note!
</strong>
<p>
Logging in this way is not a best practice. For better security, please consider using a
<Link external href="https://nostrapps.com#signers" class="link">signer app</Link>
to keep your keys safe.
</p>
</div>
{#if isNcryptsec}
<FieldInline>
{#snippet label()}
<p>Password*</p>
{/snippet}
{#snippet input()}
<label class="input input-bordered flex w-full items-center gap-2">
<Icon icon={Key} />
<input type="password" bind:value={password} placeholder="Your password" />
</label>
{/snippet}
</FieldInline>
{/if}
<div class="card2 card2-sm bg-alt flex flex-col gap-2 text-sm">
<strong class="flex items-center gap-2">
<Icon icon={Danger} />
Please note!
</strong>
<p>
Logging in this way is not a best practice. For better security, please consider using a
<Link external href="https://nostrapps.com#signers" class="link">signer app</Link>
to keep your keys safe.
</p>
</div>
</ModalBody>
<ModalFooter>
<Button class="btn btn-link" onclick={back} disabled={loading}>
<Icon icon={AltArrowLeft} />
@@ -125,4 +129,4 @@
<Icon icon={AltArrowRight} />
</Button>
</ModalFooter>
</form>
</Modal>
+25 -21
View File
@@ -8,6 +8,8 @@
import AltArrowLeft from "@assets/icons/alt-arrow-left.svg?dataurl"
import AltArrowRight from "@assets/icons/alt-arrow-right.svg?dataurl"
import Icon from "@lib/components/Icon.svelte"
import Modal from "@lib/components/Modal.svelte"
import ModalBody from "@lib/components/ModalBody.svelte"
import ModalHeader from "@lib/components/ModalHeader.svelte"
import ModalFooter from "@lib/components/ModalFooter.svelte"
import LogInOTPConfirm from "@app/components/LogInOTPConfirm.svelte"
@@ -44,26 +46,28 @@
let loading = $state(false)
</script>
<form class="column gap-4" onsubmit={preventDefault(onSubmit)}>
<ModalHeader>
{#snippet title()}
<div>Log In</div>
{/snippet}
{#snippet info()}
<div>Log in using a one-time login code</div>
{/snippet}
</ModalHeader>
<FieldInline>
{#snippet label()}
<p>Email*</p>
{/snippet}
{#snippet input()}
<label class="input input-bordered flex w-full items-center gap-2">
<Icon icon={Letter} />
<input bind:value={email} />
</label>
{/snippet}
</FieldInline>
<Modal tag="form" onsubmit={preventDefault(onSubmit)}>
<ModalBody>
<ModalHeader>
{#snippet title()}
<div>Log In</div>
{/snippet}
{#snippet info()}
<div>Log in using a one-time login code</div>
{/snippet}
</ModalHeader>
<FieldInline>
{#snippet label()}
<p>Email*</p>
{/snippet}
{#snippet input()}
<label class="input input-bordered flex w-full items-center gap-2">
<Icon icon={Letter} />
<input bind:value={email} />
</label>
{/snippet}
</FieldInline>
</ModalBody>
<ModalFooter>
<Button class="btn btn-link" onclick={back} disabled={loading}>
<Icon icon={AltArrowLeft} />
@@ -74,4 +78,4 @@
<Icon icon={AltArrowRight} />
</Button>
</ModalFooter>
</form>
</Modal>
+23 -19
View File
@@ -7,6 +7,8 @@
import AltArrowLeft from "@assets/icons/alt-arrow-left.svg?dataurl"
import AltArrowRight from "@assets/icons/alt-arrow-right.svg?dataurl"
import Icon from "@lib/components/Icon.svelte"
import Modal from "@lib/components/Modal.svelte"
import ModalBody from "@lib/components/ModalBody.svelte"
import ModalHeader from "@lib/components/ModalHeader.svelte"
import ModalFooter from "@lib/components/ModalFooter.svelte"
import {clearModals} from "@app/util/modal"
@@ -79,24 +81,26 @@
let loading = $state(false)
</script>
<form class="column gap-4" onsubmit={preventDefault(onSubmit)}>
<ModalHeader>
{#snippet title()}
<div>Log In</div>
{/snippet}
{#snippet info()}
<div>Enter the login codes sent to your email</div>
{/snippet}
</ModalHeader>
<p>Your login codes have been sent!</p>
<p>
For security reasons, you may receive three or more emails with login codes in them. Please
paste <strong>all</strong> login codes into the text box below, on separate lines.
</p>
<textarea
rows={POMADE_SIGNERS.length + 1}
class="textarea textarea-bordered leading-4"
bind:value={input}></textarea>
<Modal tag="form" onsubmit={preventDefault(onSubmit)}>
<ModalBody>
<ModalHeader>
{#snippet title()}
<div>Log In</div>
{/snippet}
{#snippet info()}
<div>Enter the login codes sent to your email</div>
{/snippet}
</ModalHeader>
<p>Your login codes have been sent!</p>
<p>
For security reasons, you may receive three or more emails with login codes in them. Please
paste <strong>all</strong> login codes into the text box below, on separate lines.
</p>
<textarea
rows={POMADE_SIGNERS.length + 1}
class="textarea textarea-bordered leading-4"
bind:value={input}></textarea>
</ModalBody>
<ModalFooter>
<Button class="btn btn-link" onclick={back} disabled={loading}>
<Icon icon={AltArrowLeft} />
@@ -107,4 +111,4 @@
<Icon icon={AltArrowRight} />
</Button>
</ModalFooter>
</form>
</Modal>
+12 -8
View File
@@ -4,6 +4,8 @@
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"
import ModalFooter from "@lib/components/ModalFooter.svelte"
import {Push} from "@app/util/notifications"
@@ -31,13 +33,15 @@
let loading = $state(false)
</script>
<form class="column gap-4" onsubmit={preventDefault(doLogout)}>
<ModalHeader>
{#snippet title()}
<div>Are you sure you want<br />to log out?</div>
{/snippet}
</ModalHeader>
<p class="text-center">Your local database will be cleared.</p>
<Modal tag="form" onsubmit={preventDefault(doLogout)}>
<ModalBody>
<ModalHeader>
{#snippet title()}
<div>Are you sure you want<br />to log out?</div>
{/snippet}
</ModalHeader>
<p class="text-center">Your local database will be cleared.</p>
</ModalBody>
<ModalFooter>
<Button class="btn btn-link" onclick={back}>
<Icon icon={AltArrowLeft} />
@@ -47,4 +51,4 @@
<Spinner {loading}>Log Out</Spinner>
</Button>
</ModalFooter>
</form>
</Modal>
+1 -8
View File
@@ -35,14 +35,7 @@
props: {
onClose: closeModals,
fullscreen: options.fullscreen,
children: createRawSnippet(() => ({
render: () => "<div></div>",
setup: (target: Element) => {
const child = mount(component, {target, props})
return () => unmount(child)
},
})),
children: {component, props},
},
})
}
+37 -31
View File
@@ -18,6 +18,8 @@
import Spinner from "@lib/components/Spinner.svelte"
import ModalHeader from "@lib/components/ModalHeader.svelte"
import ModalFooter from "@lib/components/ModalFooter.svelte"
import Modal from "@lib/components/Modal.svelte"
import ModalBody from "@lib/components/ModalBody.svelte"
import {INDEXER_RELAYS, PLATFORM_NAME, userSpaceUrls} from "@app/core/state"
import {kv, db} from "@app/core/storage"
import {pushToast} from "@app/util/toast"
@@ -108,36 +110,40 @@
const back = () => history.back()
</script>
<form class="column gap-4" onsubmit={preventDefault(confirm)}>
<ModalHeader>
{#snippet title()}
Delete your account
{/snippet}
{#snippet info()}
From the Nostr network
{/snippet}
</ModalHeader>
{#if showProgress}
<p>
We are currently sending deletion requests to your relay selections and space hosts. Please
wait while we complete this process. Once we're done, you'll be automatically logged out.
</p>
<progress class="progress progress-primary w-full" value={progress! * 100} max="100"></progress>
{:else}
<p>
This will delete your nostr account everywhere, not just on {PLATFORM_NAME}.
</p>
<p>
To confirm, please type "{CONFIRM_TEXT}" into the text box below. This action can't be undone.
</p>
<label class="input input-bordered flex w-full items-center gap-2">
<input bind:value={confirmText} class="grow" type="text" />
</label>
<p>
<strong>Note:</strong> not all relays may honor your request for deletion. If you find that your
content continues to be available, please contact the offending relays directly.
</p>
{/if}
<Modal tag="form" onsubmit={preventDefault(confirm)}>
<ModalBody>
<ModalHeader>
{#snippet title()}
Delete your account
{/snippet}
{#snippet info()}
From the Nostr network
{/snippet}
</ModalHeader>
{#if showProgress}
<p>
We are currently sending deletion requests to your relay selections and space hosts. Please
wait while we complete this process. Once we're done, you'll be automatically logged out.
</p>
<progress class="progress progress-primary w-full" value={progress! * 100} max="100"
></progress>
{:else}
<p>
This will delete your nostr account everywhere, not just on {PLATFORM_NAME}.
</p>
<p>
To confirm, please type "{CONFIRM_TEXT}" into the text box below. This action can't be
undone.
</p>
<label class="input input-bordered flex w-full items-center gap-2">
<input bind:value={confirmText} class="grow" type="text" />
</label>
<p>
<strong>Note:</strong> not all relays may honor your request for deletion. If you find that your
content continues to be available, please contact the offending relays directly.
</p>
{/if}
</ModalBody>
<ModalFooter>
<Button class="btn btn-link" onclick={back}>
<Icon icon={AltArrowLeft} />
@@ -148,4 +154,4 @@
<Icon icon={AltArrowRight} />
</Button>
</ModalFooter>
</form>
</Modal>
+42 -36
View File
@@ -15,6 +15,8 @@
import Confirm from "@lib/components/Confirm.svelte"
import Button from "@lib/components/Button.svelte"
import Popover from "@lib/components/Popover.svelte"
import Modal from "@lib/components/Modal.svelte"
import ModalBody from "@lib/components/ModalBody.svelte"
import ModalFooter from "@lib/components/ModalFooter.svelte"
import Profile from "@app/components/Profile.svelte"
import ProfileInfo from "@app/components/ProfileInfo.svelte"
@@ -75,43 +77,47 @@
let showMenu = $state(false)
</script>
<div class="flex flex-col gap-4">
<div class="flex justify-between">
<Profile showPubkey avatarSize={14} {pubkey} {url} />
{#if $profile || $userIsAdmin}
<div class="relative">
<Button class="btn btn-circle btn-ghost btn-sm" onclick={() => toggleMenu(pubkey)}>
<Icon icon={MenuDots} />
</Button>
{#if showMenu}
<Popover hideOnClick onClose={closeMenu}>
<ul
transition:fly
class="bg-alt menu absolute right-0 z-popover w-48 gap-1 rounded-box p-2 shadow-md">
{#if $profile}
<li>
<Button onclick={showInfo}>
<Icon icon={Code2} />
User Details
</Button>
</li>
{/if}
{#if $userIsAdmin}
<li>
<Button class="text-error" onclick={banMember}>
<Icon icon={MinusCircle} />
Ban User
</Button>
</li>
{/if}
</ul>
</Popover>
<Modal>
<ModalBody>
<div class="flex flex-col gap-4">
<div class="flex justify-between">
<Profile showPubkey avatarSize={14} {pubkey} {url} />
{#if $profile || $userIsAdmin}
<div class="relative">
<Button class="btn btn-circle btn-ghost btn-sm" onclick={() => toggleMenu(pubkey)}>
<Icon icon={MenuDots} />
</Button>
{#if showMenu}
<Popover hideOnClick onClose={closeMenu}>
<ul
transition:fly
class="bg-alt menu absolute right-0 z-popover w-48 gap-1 rounded-box p-2 shadow-md">
{#if $profile}
<li>
<Button onclick={showInfo}>
<Icon icon={Code2} />
User Details
</Button>
</li>
{/if}
{#if $userIsAdmin}
<li>
<Button class="text-error" onclick={banMember}>
<Icon icon={MinusCircle} />
Ban User
</Button>
</li>
{/if}
</ul>
</Popover>
{/if}
</div>
{/if}
</div>
{/if}
</div>
<ProfileInfo {pubkey} {url} />
<ProfileBadges {pubkey} {url} />
<ProfileInfo {pubkey} {url} />
<ProfileBadges {pubkey} {url} />
</div>
</ModalBody>
<ModalFooter>
<Button onclick={back} class="hidden md:btn md:btn-link">
<Icon icon={AltArrowLeft} />
@@ -128,4 +134,4 @@
</Button>
</div>
</ModalFooter>
</div>
</Modal>
+72 -68
View File
@@ -8,6 +8,8 @@
import Field from "@lib/components/Field.svelte"
import FieldInline from "@lib/components/FieldInline.svelte"
import Button from "@lib/components/Button.svelte"
import Modal from "@lib/components/Modal.svelte"
import ModalBody from "@lib/components/ModalBody.svelte"
import InputProfilePicture from "@app/components/InputProfilePicture.svelte"
import InfoHandle from "@app/components/InfoHandle.svelte"
import {pushModal} from "@app/util/modal"
@@ -33,91 +35,93 @@
let file: File | undefined = $state()
</script>
<form class="col-4" onsubmit={preventDefault(submit)}>
{#if isSignup}
<div class="grid grid-cols-2">
<div class="flex flex-col gap-2">
<p class="text-2xl">Create a Profile</p>
<p class="text-sm">
Give people something to go on — but remember, privacy matters! Be careful about sharing
sensitive information.
</p>
<Modal tag="form" onsubmit={preventDefault(submit)}>
<ModalBody>
{#if isSignup}
<div class="grid grid-cols-2">
<div class="flex flex-col gap-2">
<p class="text-2xl">Create a Profile</p>
<p class="text-sm">
Give people something to go on — but remember, privacy matters! Be careful about sharing
sensitive information.
</p>
</div>
<div class="flex flex-col items-center justify-center gap-2">
<InputProfilePicture bind:file bind:url={values.profile.picture} />
<p class="text-xs">Upload an Avatar</p>
</div>
</div>
<div class="flex flex-col items-center justify-center gap-2">
{:else}
<div class="flex items-center justify-center py-4">
<InputProfilePicture bind:file bind:url={values.profile.picture} />
<p class="text-xs">Upload an Avatar</p>
</div>
</div>
{:else}
<div class="flex items-center justify-center py-4">
<InputProfilePicture bind:file bind:url={values.profile.picture} />
</div>
{/if}
<Field>
{#snippet label()}
<p>Nickname</p>
{/snippet}
{#snippet input()}
<label class="input input-bordered flex w-full items-center gap-2">
<Icon icon={UserCircle} />
<input bind:value={values.profile.name} class="grow" type="text" />
</label>
{/snippet}
{#snippet info()}
What would you like people to call you?
{/snippet}
</Field>
<Field>
{#snippet label()}
<p>About You</p>
{/snippet}
{#snippet input()}
<textarea
class="textarea textarea-bordered leading-4"
rows="5"
bind:value={values.profile.about}></textarea>
{/snippet}
{#snippet info()}
Give a brief introduction to why you're here.
{/snippet}
</Field>
{#if !isSignup}
{/if}
<Field>
{#snippet label()}
<p>Nostr Address</p>
<p>Nickname</p>
{/snippet}
{#snippet input()}
<label class="input input-bordered flex w-full items-center gap-2">
<Icon icon={MapPoint} />
<input bind:value={values.profile.nip05} class="grow" type="text" />
<Icon icon={UserCircle} />
<input bind:value={values.profile.name} class="grow" type="text" />
</label>
{/snippet}
{#snippet info()}
<p>
<Button class="link" onclick={() => pushModal(InfoHandle)}
>What is a nostr address?</Button>
</p>
What would you like people to call you?
{/snippet}
</Field>
{/if}
{#if !isSignup}
<FieldInline>
<Field>
{#snippet label()}
<p>Broadcast Profile</p>
<p>About You</p>
{/snippet}
{#snippet input()}
<input
type="checkbox"
class="toggle toggle-primary"
bind:checked={values.shouldBroadcast} />
<textarea
class="textarea textarea-bordered leading-4"
rows="5"
bind:value={values.profile.about}></textarea>
{/snippet}
{#snippet info()}
<p>
If enabled, changes will be published to the broader nostr network in addition to spaces
you are a member of.
</p>
Give a brief introduction to why you're here.
{/snippet}
</FieldInline>
{/if}
</Field>
{#if !isSignup}
<Field>
{#snippet label()}
<p>Nostr Address</p>
{/snippet}
{#snippet input()}
<label class="input input-bordered flex w-full items-center gap-2">
<Icon icon={MapPoint} />
<input bind:value={values.profile.nip05} class="grow" type="text" />
</label>
{/snippet}
{#snippet info()}
<p>
<Button class="link" onclick={() => pushModal(InfoHandle)}
>What is a nostr address?</Button>
</p>
{/snippet}
</Field>
{/if}
{#if !isSignup}
<FieldInline>
{#snippet label()}
<p>Broadcast Profile</p>
{/snippet}
{#snippet input()}
<input
type="checkbox"
class="toggle toggle-primary"
bind:checked={values.shouldBroadcast} />
{/snippet}
{#snippet info()}
<p>
If enabled, changes will be published to the broader nostr network in addition to spaces
you are a member of.
</p>
{/snippet}
</FieldInline>
{/if}
</ModalBody>
{@render footer()}
</form>
</Modal>
+23 -16
View File
@@ -1,6 +1,9 @@
<script lang="ts">
import Button from "@lib/components/Button.svelte"
import ModalHeader from "@lib/components/ModalHeader.svelte"
import ModalFooter from "@lib/components/ModalFooter.svelte"
import Modal from "@lib/components/Modal.svelte"
import ModalBody from "@lib/components/ModalBody.svelte"
import Profile from "@app/components/Profile.svelte"
interface Props {
@@ -13,19 +16,23 @@
const {subtitle = "", pubkeys, url, ...restProps}: Props = $props()
</script>
<div class="column gap-4">
<ModalHeader>
{#snippet title()}
<div>{restProps.title}</div>
{/snippet}
{#snippet info()}
<div>{subtitle}</div>
{/snippet}
</ModalHeader>
{#each pubkeys as pubkey (pubkey)}
<div class="card2 bg-alt">
<Profile {pubkey} {url} />
</div>
{/each}
<Button class="btn btn-primary" onclick={() => history.back()}>Got it</Button>
</div>
<Modal>
<ModalBody>
<ModalHeader>
{#snippet title()}
<div>{restProps.title}</div>
{/snippet}
{#snippet info()}
<div>{subtitle}</div>
{/snippet}
</ModalHeader>
{#each pubkeys as pubkey (pubkey)}
<div class="card2 bg-alt">
<Profile {pubkey} {url} />
</div>
{/each}
</ModalBody>
<ModalFooter>
<Button class="btn btn-primary" onclick={() => history.back()}>Got it</Button>
</ModalFooter>
</Modal>
+33 -25
View File
@@ -9,6 +9,8 @@
import AddCircle from "@assets/icons/add-circle.svg?dataurl"
import Icon from "@lib/components/Icon.svelte"
import Button from "@lib/components/Button.svelte"
import Modal from "@lib/components/Modal.svelte"
import ModalBody from "@lib/components/ModalBody.svelte"
import RelayItem from "@app/components/RelayItem.svelte"
interface Props {
@@ -39,28 +41,34 @@
})
</script>
<label class="input input-bordered flex w-full items-center gap-2">
<Icon icon={Magnifier} />
<input bind:value={term} class="grow" type="text" placeholder="Search for relays..." />
</label>
<div class="column -m-6 mt-0 h-[50vh] gap-2 overflow-auto p-6 pt-2" bind:this={element}>
{#if customUrl && isShareableRelayUrl(customUrl) && !$relays.includes(normalizeRelayUrl(customUrl))}
<RelayItem url={term}>
<Button class="btn btn-outline btn-sm flex items-center" onclick={() => addRelay(customUrl)}>
<Icon icon={AddCircle} />
Add Relay
</Button>
</RelayItem>
{/if}
{#each $relaySearch
.searchValues(term)
.filter(url => !$relays.includes(url))
.slice(0, limit) as url (url)}
<RelayItem {url}>
<Button class="btn btn-outline btn-sm flex items-center" onclick={() => addRelay(url)}>
<Icon icon={AddCircle} />
Add Relay
</Button>
</RelayItem>
{/each}
</div>
<Modal>
<ModalBody>
<label class="input input-bordered flex w-full items-center gap-2">
<Icon icon={Magnifier} />
<input bind:value={term} class="grow" type="text" placeholder="Search for relays..." />
</label>
<div class="column -m-6 mt-0 h-[50vh] gap-2 overflow-auto p-6 pt-2" bind:this={element}>
{#if customUrl && isShareableRelayUrl(customUrl) && !$relays.includes(normalizeRelayUrl(customUrl))}
<RelayItem url={term}>
<Button
class="btn btn-outline btn-sm flex items-center"
onclick={() => addRelay(customUrl)}>
<Icon icon={AddCircle} />
Add Relay
</Button>
</RelayItem>
{/if}
{#each $relaySearch
.searchValues(term)
.filter(url => !$relays.includes(url))
.slice(0, limit) as url (url)}
<RelayItem {url}>
<Button class="btn btn-outline btn-sm flex items-center" onclick={() => addRelay(url)}>
<Icon icon={AddCircle} />
Add Relay
</Button>
</RelayItem>
{/each}
</div>
</ModalBody>
</Modal>
+45 -41
View File
@@ -8,6 +8,8 @@
import Icon from "@lib/components/Icon.svelte"
import ModalHeader from "@lib/components/ModalHeader.svelte"
import ModalFooter from "@lib/components/ModalFooter.svelte"
import Modal from "@lib/components/Modal.svelte"
import ModalBody from "@lib/components/ModalBody.svelte"
import {pushToast} from "@app/util/toast"
import {publishReport} from "@app/core/commands"
@@ -38,46 +40,48 @@
let loading = $state(false)
</script>
<form class="column gap-4" onsubmit={preventDefault(confirm)}>
<ModalHeader>
{#snippet title()}
<div>Report Content</div>
{/snippet}
{#snippet info()}
<div>Flag inappropriate content.</div>
{/snippet}
</ModalHeader>
<Field>
{#snippet label()}
<p>Reason*</p>
{/snippet}
{#snippet input()}
<select class="select select-bordered" bind:value={reason}>
<option disabled selected>Choose a reason</option>
<option>Nudity</option>
<option>Malware</option>
<option>Profanity</option>
<option>Illegal</option>
<option>Spam</option>
<option>Impersonation</option>
<option>Other</option>
</select>
{/snippet}
{#snippet info()}
<p>Please select a reason for your report.</p>
{/snippet}
</Field>
<Field>
{#snippet label()}
<p>Details</p>
{/snippet}
{#snippet input()}
<textarea class="textarea textarea-bordered" bind:value={content}></textarea>
{/snippet}
{#snippet info()}
<p>Please provide any additional details relevant to your report.</p>
{/snippet}
</Field>
<Modal tag="form" onsubmit={preventDefault(confirm)}>
<ModalBody>
<ModalHeader>
{#snippet title()}
<div>Report Content</div>
{/snippet}
{#snippet info()}
<div>Flag inappropriate content.</div>
{/snippet}
</ModalHeader>
<Field>
{#snippet label()}
<p>Reason*</p>
{/snippet}
{#snippet input()}
<select class="select select-bordered" bind:value={reason}>
<option disabled selected>Choose a reason</option>
<option>Nudity</option>
<option>Malware</option>
<option>Profanity</option>
<option>Illegal</option>
<option>Spam</option>
<option>Impersonation</option>
<option>Other</option>
</select>
{/snippet}
{#snippet info()}
<p>Please select a reason for your report.</p>
{/snippet}
</Field>
<Field>
{#snippet label()}
<p>Details</p>
{/snippet}
{#snippet input()}
<textarea class="textarea textarea-bordered" bind:value={content}></textarea>
{/snippet}
{#snippet info()}
<p>Please provide any additional details relevant to your report.</p>
{/snippet}
</Field>
</ModalBody>
<ModalFooter>
<Button class="btn btn-link" onclick={back}>
<Icon icon={AltArrowLeft} />
@@ -88,4 +92,4 @@
<Icon icon={AltArrowRight} />
</Button>
</ModalFooter>
</form>
</Modal>
+23 -16
View File
@@ -4,7 +4,10 @@
import {deriveEventsById} from "@welshman/store"
import {repository} from "@welshman/app"
import ModalHeader from "@lib/components/ModalHeader.svelte"
import Modal from "@lib/components/Modal.svelte"
import ModalBody from "@lib/components/ModalBody.svelte"
import Button from "@lib/components/Button.svelte"
import ModalFooter from "@lib/components/ModalFooter.svelte"
import ReportItem from "@app/components/ReportItem.svelte"
type Props = {
@@ -28,19 +31,23 @@
}
</script>
<div class="column gap-4">
<ModalHeader>
{#snippet title()}
<div>Report Details</div>
{/snippet}
{#snippet info()}
<div>All reports for this event are shown below.</div>
{/snippet}
</ModalHeader>
{#each $reports.values() as report (report.id)}
<div class="card2 card2-sm bg-alt">
<ReportItem {url} event={report} {onDelete} />
</div>
{/each}
<Button class="btn btn-primary" onclick={back}>Got it</Button>
</div>
<Modal>
<ModalBody>
<ModalHeader>
{#snippet title()}
<div>Report Details</div>
{/snippet}
{#snippet info()}
<div>All reports for this event are shown below.</div>
{/snippet}
</ModalHeader>
{#each $reports.values() as report (report.id)}
<div class="card2 card2-sm bg-alt">
<ReportItem {url} event={report} {onDelete} />
</div>
{/each}
</ModalBody>
<ModalFooter>
<Button class="btn btn-primary" onclick={back}>Got it</Button>
</ModalFooter>
</Modal>
+82 -74
View File
@@ -20,6 +20,8 @@
import Icon from "@lib/components/Icon.svelte"
import Button from "@lib/components/Button.svelte"
import Confirm from "@lib/components/Confirm.svelte"
import Modal from "@lib/components/Modal.svelte"
import ModalBody from "@lib/components/ModalBody.svelte"
import ModalFooter from "@lib/components/ModalFooter.svelte"
import ProfileCircles from "@app/components/ProfileCircles.svelte"
import RoomMembers from "@app/components/RoomMembers.svelte"
@@ -36,7 +38,11 @@
deriveIsMuted,
MembershipStatus,
} from "@app/core/state"
import {addRoomMembership, removeRoomMembership, toggleRoomNotifications} from "@app/core/commands"
import {
addRoomMembership,
removeRoomMembership,
toggleRoomNotifications,
} from "@app/core/commands"
import {makeSpacePath} from "@app/util/routes"
import {pushModal} from "@app/util/modal"
import {pushToast} from "@app/util/toast"
@@ -114,85 +120,87 @@
let loading = $state(false)
</script>
<div class="flex flex-col gap-3">
<div class="flex justify-between">
<div class="flex gap-3">
<div class="pt-0.5">
<RoomImage {url} {h} size={8} />
<Modal>
<ModalBody>
<div class="flex justify-between">
<div class="flex gap-3">
<div class="pt-0.5">
<RoomImage {url} {h} size={8} />
</div>
<div class="flex min-w-0 flex-col">
<RoomName {url} {h} class="text-2xl" />
<span class="text-primary">{displayRelayUrl(url)}</span>
</div>
</div>
<div class="flex min-w-0 flex-col">
<RoomName {url} {h} class="text-2xl" />
<span class="text-primary">{displayRelayUrl(url)}</span>
<div class="grid grid-cols-2 gap-2">
{#if $room?.isRestricted}
<Button
class="btn btn-neutral btn-sm tooltip tooltip-left"
data-tip="Only members can send messages.">
<Icon size={4} icon={Microphone} />
</Button>
{/if}
{#if $room?.isPrivate}
<Button
class="btn btn-neutral btn-sm tooltip tooltip-left"
data-tip="Only members can view messages.">
<Icon size={4} icon={Lock} />
</Button>
{/if}
{#if $room?.isHidden}
<Button
class="btn btn-neutral btn-sm tooltip tooltip-left"
data-tip="This room is not visible to non-members.">
<Icon size={4} icon={EyeClosed} />
</Button>
{/if}
{#if $room?.isClosed}
<Button
class="btn btn-neutral btn-sm tooltip tooltip-left"
data-tip="Requests to join this room will be ignored.">
<Icon size={4} icon={MinusCircle} />
</Button>
{/if}
</div>
</div>
<div class="grid grid-cols-2 gap-2">
{#if $room?.isRestricted}
<Button
class="btn btn-neutral btn-sm tooltip tooltip-left"
data-tip="Only members can send messages.">
<Icon size={4} icon={Microphone} />
</Button>
{/if}
{#if $room?.isPrivate}
<Button
class="btn btn-neutral btn-sm tooltip tooltip-left"
data-tip="Only members can view messages.">
<Icon size={4} icon={Lock} />
</Button>
{/if}
{#if $room?.isHidden}
<Button
class="btn btn-neutral btn-sm tooltip tooltip-left"
data-tip="This room is not visible to non-members.">
<Icon size={4} icon={EyeClosed} />
</Button>
{/if}
{#if $room?.isClosed}
<Button
class="btn btn-neutral btn-sm tooltip tooltip-left"
data-tip="Requests to join this room will be ignored.">
<Icon size={4} icon={MinusCircle} />
</Button>
{/if}
</div>
</div>
{#if $room?.about}
<p>{$room.about}</p>
{/if}
{#if $members.length > 0}
<div class="card2 card2-sm bg-alt flex items-center justify-between gap-4">
<div class="flex items-center gap-4">
<span>Members:</span>
<ProfileCircles pubkeys={$members} />
{#if $room?.about}
<p>{$room.about}</p>
{/if}
{#if $members.length > 0}
<div class="card2 card2-sm bg-alt flex items-center justify-between gap-4">
<div class="flex items-center gap-4">
<span>Members:</span>
<ProfileCircles pubkeys={$members} />
</div>
<Button class="btn btn-neutral btn-sm" onclick={showMembers}>View All</Button>
</div>
<Button class="btn btn-neutral btn-sm" onclick={showMembers}>View All</Button>
</div>
{/if}
<div class="card2 card2-sm bg-alt col-4">
<strong class="text-lg">Room Settings</strong>
<div class="flex items-center justify-between">
<div class="flex items-center gap-2">
<Icon icon={VolumeLoud} />
<span>Notifications</span>
{/if}
<div class="card2 card2-sm bg-alt col-4">
<strong class="text-lg">Room Settings</strong>
<div class="flex items-center justify-between">
<div class="flex items-center gap-2">
<Icon icon={VolumeLoud} />
<span>Notifications</span>
</div>
<input
type="checkbox"
class="toggle toggle-primary"
checked={!isMuted}
onchange={toggleMute} />
</div>
<Button
class={cx("btn btn-sm", {"btn-primary": !isMuted, "btn-neutral": isMuted})}
onclick={toggleMute}>
{isMuted ? "Off" : "On"}
</Button>
</div>
<div class="flex items-center justify-between">
<div class="flex items-center gap-2">
<Icon icon={Bookmark} />
<span>Favorite</span>
<div class="flex items-center justify-between">
<div class="flex items-center gap-2">
<Icon icon={Bookmark} />
<span>Favorite</span>
</div>
<input
type="checkbox"
class="toggle toggle-primary"
checked={isFavorite}
onchange={toggleFavorite} />
</div>
<Button
class={cx("btn btn-sm", {"btn-primary": isFavorite, "btn-neutral": !isFavorite})}
onclick={toggleFavorite}>
{isFavorite ? "On" : "Off"}
</Button>
</div>
</div>
</ModalBody>
<ModalFooter>
<Button class="btn btn-link" onclick={back}>
<Icon icon={AltArrowLeft} />
@@ -234,4 +242,4 @@
{/if}
</div>
</ModalFooter>
</div>
</Modal>
+75 -71
View File
@@ -11,6 +11,8 @@
import Icon from "@lib/components/Icon.svelte"
import ImageIcon from "@lib/components/ImageIcon.svelte"
import IconPickerButton from "@lib/components/IconPickerButton.svelte"
import Modal from "@lib/components/Modal.svelte"
import ModalBody from "@lib/components/ModalBody.svelte"
import {pushToast} from "@app/util/toast"
import {uploadFile} from "@app/core/commands"
@@ -105,77 +107,79 @@
}
</script>
<form class="column gap-4" onsubmit={preventDefault(trySubmit)}>
{@render header()}
<FieldInline>
{#snippet label()}
<p>Icon</p>
{/snippet}
{#snippet input()}
<div class="flex flex-grow items-center justify-between gap-4">
{#if imagePreview}
<div class="flex items-center gap-2">
<span class="text-sm opacity-75">Selected:</span>
<ImageIcon src={imagePreview} alt="" class="rounded-lg" />
<Modal tag="form" onsubmit={preventDefault(trySubmit)}>
<ModalBody>
{@render header()}
<FieldInline>
{#snippet label()}
<p>Icon</p>
{/snippet}
{#snippet input()}
<div class="flex flex-grow items-center justify-between gap-4">
{#if imagePreview}
<div class="flex items-center gap-2">
<span class="text-sm opacity-75">Selected:</span>
<ImageIcon src={imagePreview} alt="" class="rounded-lg" />
</div>
{:else}
<span class="text-sm opacity-75">No icon selected</span>
{/if}
<div class="flex gap-2">
<IconPickerButton onSelect={handleIconSelect} class="btn btn-primary btn-xs">
<Icon icon={StickerSmileSquare} size={4} />
<span class="hidden sm:inline">Select</span>
</IconPickerButton>
<label class="btn btn-neutral btn-xs cursor-pointer">
<Icon icon={UploadMinimalistic} size={4} />
<span class="hidden sm:inline">Upload</span>
<input type="file" accept="image/*" class="hidden" onchange={handleImageUpload} />
</label>
</div>
{:else}
<span class="text-sm opacity-75">No icon selected</span>
{/if}
<div class="flex gap-2">
<IconPickerButton onSelect={handleIconSelect} class="btn btn-primary btn-xs">
<Icon icon={StickerSmileSquare} size={4} />
<span class="hidden sm:inline">Select</span>
</IconPickerButton>
<label class="btn btn-neutral btn-xs cursor-pointer">
<Icon icon={UploadMinimalistic} size={4} />
<span class="hidden sm:inline">Upload</span>
<input type="file" accept="image/*" class="hidden" onchange={handleImageUpload} />
</label>
</div>
</div>
{/snippet}
</FieldInline>
<FieldInline>
{#snippet label()}
<p>Name</p>
{/snippet}
{#snippet input()}
<label class="input input-bordered flex w-full items-center gap-2">
{#if imagePreview}
<ImageIcon src={imagePreview} alt="" class="rounded-lg" />
{:else}
<Icon icon={Hashtag} />
{/if}
<input bind:value={values.name} class="grow" type="text" />
</label>
{/snippet}
</FieldInline>
<FieldInline>
{#snippet label()}
<p>Description</p>
{/snippet}
{#snippet input()}
<label class="input input-bordered flex w-full items-center gap-2">
<input bind:value={values.about} class="grow" type="text" />
</label>
{/snippet}
</FieldInline>
<strong class="md:hidden">Permissions</strong>
<div class="flex items-center gap-2">
<input type="checkbox" class="checkbox" bind:checked={values.isRestricted} />
<span class="text-sm opacity-75">Only allow members to send messages</span>
</div>
<div class="flex items-center gap-2">
<input type="checkbox" class="checkbox" bind:checked={values.isPrivate} />
<span class="text-sm opacity-75">Only allow members to read messages</span>
</div>
<div class="flex items-center gap-2">
<input type="checkbox" class="checkbox" bind:checked={values.isHidden} />
<span class="text-sm opacity-75">Hide this group from non-members</span>
</div>
<div class="flex items-center gap-2">
<input type="checkbox" class="checkbox" bind:checked={values.isClosed} />
<span class="text-sm opacity-75">Ignore requests to join</span>
</div>
{/snippet}
</FieldInline>
<FieldInline>
{#snippet label()}
<p>Name</p>
{/snippet}
{#snippet input()}
<label class="input input-bordered flex w-full items-center gap-2">
{#if imagePreview}
<ImageIcon src={imagePreview} alt="" class="rounded-lg" />
{:else}
<Icon icon={Hashtag} />
{/if}
<input bind:value={values.name} class="grow" type="text" />
</label>
{/snippet}
</FieldInline>
<FieldInline>
{#snippet label()}
<p>Description</p>
{/snippet}
{#snippet input()}
<label class="input input-bordered flex w-full items-center gap-2">
<input bind:value={values.about} class="grow" type="text" />
</label>
{/snippet}
</FieldInline>
<strong class="md:hidden">Permissions</strong>
<div class="flex items-center gap-2">
<input type="checkbox" class="checkbox" bind:checked={values.isRestricted} />
<span class="text-sm opacity-75">Only allow members to send messages</span>
</div>
<div class="flex items-center gap-2">
<input type="checkbox" class="checkbox" bind:checked={values.isPrivate} />
<span class="text-sm opacity-75">Only allow members to read messages</span>
</div>
<div class="flex items-center gap-2">
<input type="checkbox" class="checkbox" bind:checked={values.isHidden} />
<span class="text-sm opacity-75">Hide this group from non-members</span>
</div>
<div class="flex items-center gap-2">
<input type="checkbox" class="checkbox" bind:checked={values.isClosed} />
<span class="text-sm opacity-75">Ignore requests to join</span>
</div>
</ModalBody>
{@render footer({loading})}
</form>
</Modal>
+43 -39
View File
@@ -9,6 +9,8 @@
import Icon from "@lib/components/Icon.svelte"
import Popover from "@lib/components/Popover.svelte"
import Confirm from "@lib/components/Confirm.svelte"
import Modal from "@lib/components/Modal.svelte"
import ModalBody from "@lib/components/ModalBody.svelte"
import ModalFooter from "@lib/components/ModalFooter.svelte"
import Profile from "@app/components/Profile.svelte"
import RoomName from "@app/components/RoomName.svelte"
@@ -59,51 +61,53 @@
let menuPubkey = $state<string | undefined>()
</script>
<div class="column gap-4">
<div class="flex min-w-0 flex-col gap-1">
<h1 class="ellipsize whitespace-nowrap text-2xl font-bold">Members</h1>
<p class="ellipsize text-sm opacity-75">of <RoomName {url} {h} /></p>
</div>
{#if $userIsAdmin}
<div class="flex gap-2">
<Button class="btn btn-primary" onclick={addMember}>
<Icon icon={AddCircle} />
Add members
</Button>
<Modal>
<ModalBody>
<div class="flex min-w-0 flex-col gap-1">
<h1 class="ellipsize whitespace-nowrap text-2xl font-bold">Members</h1>
<p class="ellipsize text-sm opacity-75">of <RoomName {url} {h} /></p>
</div>
{/if}
{#each $members as pubkey (pubkey)}
<div class="card2 bg-alt relative">
<div class="flex items-center justify-between gap-2">
<div class="min-w-0 flex-1">
<Profile {pubkey} {url} />
</div>
<div class="relative">
<Button class="btn btn-circle btn-ghost btn-sm" onclick={() => toggleMenu(pubkey)}>
<Icon icon={MenuDots} />
</Button>
{#if menuPubkey === pubkey}
<Popover hideOnClick onClose={closeMenu}>
<ul
transition:fly
class="menu absolute right-0 z-popover mt-2 w-48 gap-1 rounded-box bg-base-100 p-2 shadow-md">
<li>
<Button class="text-error" onclick={() => removeMember(pubkey)}>
<Icon icon={MinusCircle} />
Remove Member
</Button>
</li>
</ul>
</Popover>
{/if}
{#if $userIsAdmin}
<div class="flex gap-2">
<Button class="btn btn-primary" onclick={addMember}>
<Icon icon={AddCircle} />
Add members
</Button>
</div>
{/if}
{#each $members as pubkey (pubkey)}
<div class="card2 bg-alt relative">
<div class="flex items-center justify-between gap-2">
<div class="min-w-0 flex-1">
<Profile {pubkey} {url} />
</div>
<div class="relative">
<Button class="btn btn-circle btn-ghost btn-sm" onclick={() => toggleMenu(pubkey)}>
<Icon icon={MenuDots} />
</Button>
{#if menuPubkey === pubkey}
<Popover hideOnClick onClose={closeMenu}>
<ul
transition:fly
class="menu absolute right-0 z-popover mt-2 w-48 gap-1 rounded-box bg-base-100 p-2 shadow-md">
<li>
<Button class="text-error" onclick={() => removeMember(pubkey)}>
<Icon icon={MinusCircle} />
Remove Member
</Button>
</li>
</ul>
</Popover>
{/if}
</div>
</div>
</div>
</div>
{/each}
{/each}
</ModalBody>
<ModalFooter>
<Button class="btn btn-link" onclick={back}>
<Icon icon={AltArrowLeft} />
Go back
</Button>
</ModalFooter>
</div>
</Modal>
+22 -18
View File
@@ -7,6 +7,8 @@
import Icon from "@lib/components/Icon.svelte"
import ModalHeader from "@lib/components/ModalHeader.svelte"
import ModalFooter from "@lib/components/ModalFooter.svelte"
import Modal from "@lib/components/Modal.svelte"
import ModalBody from "@lib/components/ModalBody.svelte"
import RoomName from "@app/components/RoomName.svelte"
import ProfileMultiSelect from "@app/components/ProfileMultiSelect.svelte"
import {pushToast} from "@app/util/toast"
@@ -48,23 +50,25 @@
let pubkeys: string[] = $state([])
</script>
<div class="column gap-4">
<ModalHeader>
{#snippet title()}
<div>Add Members</div>
{/snippet}
{#snippet info()}
<div>to <RoomName {url} {h} /></div>
{/snippet}
</ModalHeader>
<Field>
{#snippet label()}
<p>Search for People</p>
{/snippet}
{#snippet input()}
<ProfileMultiSelect bind:value={pubkeys} />
{/snippet}
</Field>
<Modal>
<ModalBody>
<ModalHeader>
{#snippet title()}
<div>Add Members</div>
{/snippet}
{#snippet info()}
<div>to <RoomName {url} {h} /></div>
{/snippet}
</ModalHeader>
<Field>
{#snippet label()}
<p>Search for People</p>
{/snippet}
{#snippet input()}
<ProfileMultiSelect bind:value={pubkeys} />
{/snippet}
</Field>
</ModalBody>
<ModalFooter>
<Button class="btn btn-link" onclick={back}>
<Icon icon={AltArrowLeft} />
@@ -74,4 +78,4 @@
<Spinner {loading}>Save changes</Spinner>
</Button>
</ModalFooter>
</div>
</Modal>
+25 -21
View File
@@ -15,6 +15,8 @@
import {getKey, setKey} from "@lib/implicit"
import Icon from "@lib/components/Icon.svelte"
import Button from "@lib/components/Button.svelte"
import Modal from "@lib/components/Modal.svelte"
import ModalBody from "@lib/components/ModalBody.svelte"
import LogIn from "@app/components/LogIn.svelte"
import InfoNostr from "@app/components/InfoNostr.svelte"
import SignUpKey from "@app/components/SignUpKey.svelte"
@@ -92,25 +94,27 @@
}
</script>
<div class="column gap-4">
<h1 class="heading">Sign up with Nostr</h1>
<p class="m-auto max-w-sm text-center">
{PLATFORM_NAME} is built using the
<Button class="link" onclick={() => pushModal(InfoNostr)}>nostr protocol</Button>, which gives
users control over their digital identity using <strong>cryptographic key pairs</strong>.
</p>
{#if hasPomade}
<Button onclick={flows.email.start} class="btn btn-primary">
<Icon icon={Letter} />
Sign up with email
<Modal>
<ModalBody>
<h1 class="heading">Sign up with Nostr</h1>
<p class="m-auto max-w-sm text-center">
{PLATFORM_NAME} is built using the
<Button class="link" onclick={() => pushModal(InfoNostr)}>nostr protocol</Button>, which gives
users control over their digital identity using <strong>cryptographic key pairs</strong>.
</p>
{#if hasPomade}
<Button onclick={flows.email.start} class="btn btn-primary">
<Icon icon={Letter} />
Sign up with email
</Button>
{/if}
<Button onclick={flows.nostr.start} class="btn {hasPomade ? 'btn-neutral' : 'btn-primary'}">
<Icon icon={Key} />
Generate a key
</Button>
{/if}
<Button onclick={flows.nostr.start} class="btn {hasPomade ? 'btn-neutral' : 'btn-primary'}">
<Icon icon={Key} />
Generate a key
</Button>
<div class="text-sm">
Already have an account?
<Button class="link" onclick={login}>Log in instead</Button>
</div>
</div>
<div class="text-sm">
Already have an account?
<Button class="link" onclick={login}>Log in instead</Button>
</div>
</ModalBody>
</Modal>
+19 -15
View File
@@ -4,6 +4,8 @@
import HomeSmile from "@assets/icons/home-smile.svg?dataurl"
import Icon from "@lib/components/Icon.svelte"
import Button from "@lib/components/Button.svelte"
import Modal from "@lib/components/Modal.svelte"
import ModalBody from "@lib/components/ModalBody.svelte"
import ModalHeader from "@lib/components/ModalHeader.svelte"
import ModalFooter from "@lib/components/ModalFooter.svelte"
@@ -16,20 +18,22 @@
const back = () => history.back()
</script>
<form class="column gap-4" onsubmit={preventDefault(next)}>
<ModalHeader>
{#snippet title()}
<div>You're all set!</div>
{/snippet}
</ModalHeader>
<p>
You've created your profile, saved your keys, and now you're ready to start chatting — all
without asking permission!
</p>
<p>
From your dashboard, you can use invite links, discover community spaces, and keep up-to-date on
groups you've already joined. Click below to get started!
</p>
<Modal tag="form" onsubmit={preventDefault(next)}>
<ModalBody>
<ModalHeader>
{#snippet title()}
<div>You're all set!</div>
{/snippet}
</ModalHeader>
<p>
You've created your profile, saved your keys, and now you're ready to start chatting — all
without asking permission!
</p>
<p>
From your dashboard, you can use invite links, discover community spaces, and keep up-to-date
on groups you've already joined. Click below to get started!
</p>
</ModalBody>
<ModalFooter>
<Button class="btn btn-link" onclick={back}>
<Icon icon={AltArrowLeft} />
@@ -40,4 +44,4 @@
Go to Dashboard
</Button>
</ModalFooter>
</form>
</Modal>
+44 -40
View File
@@ -11,6 +11,8 @@
import FieldInline from "@lib/components/FieldInline.svelte"
import Spinner from "@lib/components/Spinner.svelte"
import Button from "@lib/components/Button.svelte"
import Modal from "@lib/components/Modal.svelte"
import ModalBody from "@lib/components/ModalBody.svelte"
import ModalHeader from "@lib/components/ModalHeader.svelte"
import ModalFooter from "@lib/components/ModalFooter.svelte"
import SignUpEmailConfirm from "@app/components/SignUpEmailConfirm.svelte"
@@ -92,45 +94,47 @@
let loading = $state(false)
</script>
<form class="column gap-4" onsubmit={preventDefault(onSubmit)}>
<ModalHeader>
{#snippet title()}
<div>Sign up with Email</div>
{/snippet}
{#snippet info()}
<div>Keep your keys safe using multi-signer key sharing</div>
{/snippet}
</ModalHeader>
<p>
Under the hood, nostr uses "cryptographic keypairs" to help you prove that your identity is
actually you.
</p>
<p>
If you you're not ready to take control of your keys though, that's ok! We'll keep them safe
until you are.
</p>
<FieldInline>
{#snippet label()}
<p>Email*</p>
{/snippet}
{#snippet input()}
<label class="input input-bordered flex w-full items-center gap-2">
<Icon icon={Letter} />
<input bind:value={email} />
</label>
{/snippet}
</FieldInline>
<FieldInline>
{#snippet label()}
<p>Password*</p>
{/snippet}
{#snippet input()}
<label class="input input-bordered flex w-full items-center gap-2">
<Icon icon={Key} />
<input type="password" bind:value={password} />
</label>
{/snippet}
</FieldInline>
<Modal tag="form" onsubmit={preventDefault(onSubmit)}>
<ModalBody>
<ModalHeader>
{#snippet title()}
<div>Sign up with Email</div>
{/snippet}
{#snippet info()}
<div>Keep your keys safe using multi-signer key sharing</div>
{/snippet}
</ModalHeader>
<p>
Under the hood, nostr uses "cryptographic keypairs" to help you prove that your identity is
actually you.
</p>
<p>
If you you're not ready to take control of your keys though, that's ok! We'll keep them safe
until you are.
</p>
<FieldInline>
{#snippet label()}
<p>Email*</p>
{/snippet}
{#snippet input()}
<label class="input input-bordered flex w-full items-center gap-2">
<Icon icon={Letter} />
<input bind:value={email} />
</label>
{/snippet}
</FieldInline>
<FieldInline>
{#snippet label()}
<p>Password*</p>
{/snippet}
{#snippet input()}
<label class="input input-bordered flex w-full items-center gap-2">
<Icon icon={Key} />
<input type="password" bind:value={password} />
</label>
{/snippet}
</FieldInline>
</ModalBody>
<ModalFooter>
<Button class="btn btn-link" onclick={back}>
<Icon icon={AltArrowLeft} />
@@ -141,4 +145,4 @@
<Icon icon={AltArrowRight} />
</Button>
</ModalFooter>
</form>
</Modal>
+29 -25
View File
@@ -9,6 +9,8 @@
import AltArrowLeft from "@assets/icons/alt-arrow-left.svg?dataurl"
import AltArrowRight from "@assets/icons/alt-arrow-right.svg?dataurl"
import Icon from "@lib/components/Icon.svelte"
import Modal from "@lib/components/Modal.svelte"
import ModalBody from "@lib/components/ModalBody.svelte"
import ModalHeader from "@lib/components/ModalHeader.svelte"
import ModalFooter from "@lib/components/ModalFooter.svelte"
@@ -35,30 +37,32 @@
let loading = $state(false)
</script>
<form class="column gap-4" onsubmit={preventDefault(onSubmit)}>
<ModalHeader>
{#snippet title()}
<div>Verify your Email Address</div>
{/snippet}
{#snippet info()}
<div>Enter the one-time confirmation code sent to your email</div>
{/snippet}
</ModalHeader>
<FieldInline>
{#snippet label()}
<p>Confirmation Code*</p>
{/snippet}
{#snippet input()}
<label class="input input-bordered flex w-full items-center gap-2">
<Icon icon={Key} />
<input bind:value={challenge} />
</label>
{/snippet}
</FieldInline>
<p class="text-sm">
We just sent a one-time confirmation code to {email}. Once you receive it, you can enter it
above.
</p>
<Modal tag="form" onsubmit={preventDefault(onSubmit)}>
<ModalBody>
<ModalHeader>
{#snippet title()}
<div>Verify your Email Address</div>
{/snippet}
{#snippet info()}
<div>Enter the one-time confirmation code sent to your email</div>
{/snippet}
</ModalHeader>
<FieldInline>
{#snippet label()}
<p>Confirmation Code*</p>
{/snippet}
{#snippet input()}
<label class="input input-bordered flex w-full items-center gap-2">
<Icon icon={Key} />
<input bind:value={challenge} />
</label>
{/snippet}
</FieldInline>
<p class="text-sm">
We just sent a one-time confirmation code to {email}. Once you receive it, you can enter it
above.
</p>
</ModalBody>
<ModalFooter>
<Button class="btn btn-link" onclick={back} disabled={loading}>
<Icon icon={AltArrowLeft} />
@@ -69,4 +73,4 @@
<Icon icon={AltArrowRight} />
</Button>
</ModalFooter>
</form>
</Modal>
+20 -16
View File
@@ -5,6 +5,8 @@
import {getKey, setKey} from "@lib/implicit"
import Icon from "@lib/components/Icon.svelte"
import Button from "@lib/components/Button.svelte"
import Modal from "@lib/components/Modal.svelte"
import ModalBody from "@lib/components/ModalBody.svelte"
import ModalFooter from "@lib/components/ModalFooter.svelte"
import ProfileEditForm from "@app/components/ProfileEditForm.svelte"
@@ -26,19 +28,21 @@
}
</script>
<div class="flex flex-col gap-4">
<ProfileEditForm isSignup {initialValues} {onsubmit}>
{#snippet footer()}
<ModalFooter>
<Button class="btn btn-link" onclick={back}>
<Icon icon={AltArrowLeft} />
Go back
</Button>
<Button class="btn btn-primary" type="submit">
Create Account
<Icon icon={AltArrowRight} />
</Button>
</ModalFooter>
{/snippet}
</ProfileEditForm>
</div>
<Modal>
<ModalBody>
<ProfileEditForm isSignup {initialValues} {onsubmit}>
{#snippet footer()}
<ModalFooter>
<Button class="btn btn-link" onclick={back}>
<Icon icon={AltArrowLeft} />
Go back
</Button>
<Button class="btn btn-primary" type="submit">
Create Account
<Icon icon={AltArrowRight} />
</Button>
</ModalFooter>
{/snippet}
</ProfileEditForm>
</ModalBody>
</Modal>
+25 -21
View File
@@ -9,6 +9,8 @@
import AltArrowLeft from "@assets/icons/alt-arrow-left.svg?dataurl"
import AltArrowRight from "@assets/icons/alt-arrow-right.svg?dataurl"
import Icon from "@lib/components/Icon.svelte"
import Modal from "@lib/components/Modal.svelte"
import ModalBody from "@lib/components/ModalBody.svelte"
import ModalHeader from "@lib/components/ModalHeader.svelte"
import ModalFooter from "@lib/components/ModalFooter.svelte"
import {pushToast} from "@app/util/toast"
@@ -48,26 +50,28 @@
let loading = $state(false)
</script>
<form class="column gap-4" onsubmit={preventDefault(join)}>
<ModalHeader>
{#snippet title()}
<div>Request Access</div>
{/snippet}
{#snippet info()}
<div>Enter an invite code below to request access to {displayUrl(url)}.</div>
{/snippet}
</ModalHeader>
<Field>
{#snippet label()}
<p>Invite code*</p>
{/snippet}
{#snippet input()}
<label class="input input-bordered flex w-full items-center gap-2">
<Icon icon={LinkRound} />
<input bind:value class="grow" type="text" />
</label>
{/snippet}
</Field>
<Modal tag="form" onsubmit={preventDefault(join)}>
<ModalBody>
<ModalHeader>
{#snippet title()}
<div>Request Access</div>
{/snippet}
{#snippet info()}
<div>Enter an invite code below to request access to {displayUrl(url)}.</div>
{/snippet}
</ModalHeader>
<Field>
{#snippet label()}
<p>Invite code*</p>
{/snippet}
{#snippet input()}
<label class="input input-bordered flex w-full items-center gap-2">
<Icon icon={LinkRound} />
<input bind:value class="grow" type="text" />
</label>
{/snippet}
</Field>
</ModalBody>
<ModalFooter>
<Button class="btn btn-link" onclick={back}>
<Icon icon={AltArrowLeft} />
@@ -78,4 +82,4 @@
<Icon icon={AltArrowRight} />
</Button>
</ModalFooter>
</form>
</Modal>
+47 -43
View File
@@ -6,6 +6,8 @@
import Link from "@lib/components/Link.svelte"
import Button from "@lib/components/Button.svelte"
import CardButton from "@lib/components/CardButton.svelte"
import Modal from "@lib/components/Modal.svelte"
import ModalBody from "@lib/components/ModalBody.svelte"
import ModalHeader from "@lib/components/ModalHeader.svelte"
import SpaceInviteAccept from "@app/components/SpaceInviteAccept.svelte"
import {pushModal} from "@app/util/modal"
@@ -19,54 +21,56 @@
const startJoin = () => pushModal(SpaceInviteAccept)
</script>
<div class="column gap-2">
<ModalHeader>
{#snippet title()}
<div>Add a Space</div>
{/snippet}
{#snippet info()}
<div>Spaces are places where communities come together to work, play, and hang out.</div>
{/snippet}
</ModalHeader>
{#if !hideDiscover}
<Link href="/discover">
<CardButton class="btn-primary">
<Modal>
<ModalBody>
<ModalHeader>
{#snippet title()}
<div>Add a Space</div>
{/snippet}
{#snippet info()}
<div>Spaces are places where communities come together to work, play, and hang out.</div>
{/snippet}
</ModalHeader>
{#if !hideDiscover}
<Link href="/discover">
<CardButton class="btn-primary">
{#snippet icon()}
<div><Icon icon={Compass} size={7} /></div>
{/snippet}
{#snippet title()}
<div>Explore Spaces</div>
{/snippet}
{#snippet info()}
<div>Join create, or browse spaces</div>
{/snippet}
</CardButton>
</Link>
{/if}
<Button onclick={startJoin}>
<CardButton class={hideDiscover ? "btn-primary" : "btn-neutral"}>
{#snippet icon()}
<div><Icon icon={Compass} size={7} /></div>
<div><Icon icon={Login} size={7} /></div>
{/snippet}
{#snippet title()}
<div>Explore Spaces</div>
<div>Join a space</div>
{/snippet}
{#snippet info()}
<div>Join create, or browse spaces</div>
<div>Enter an invite link to join an existing space.</div>
{/snippet}
</CardButton>
</Button>
<Link href="/spaces/create">
<CardButton class="btn-neutral">
{#snippet icon()}
<div><Icon icon={AddCircle} size={7} /></div>
{/snippet}
{#snippet title()}
<div>Create a space</div>
{/snippet}
{#snippet info()}
<div>Just a few questions and you'll be on your way.</div>
{/snippet}
</CardButton>
</Link>
{/if}
<Button onclick={startJoin}>
<CardButton class={hideDiscover ? "btn-primary" : "btn-neutral"}>
{#snippet icon()}
<div><Icon icon={Login} size={7} /></div>
{/snippet}
{#snippet title()}
<div>Join a space</div>
{/snippet}
{#snippet info()}
<div>Enter an invite link to join an existing space.</div>
{/snippet}
</CardButton>
</Button>
<Link href="/spaces/create">
<CardButton class="btn-neutral">
{#snippet icon()}
<div><Icon icon={AddCircle} size={7} /></div>
{/snippet}
{#snippet title()}
<div>Create a space</div>
{/snippet}
{#snippet info()}
<div>Just a few questions and you'll be on your way.</div>
{/snippet}
</CardButton>
</Link>
</div>
</ModalBody>
</Modal>
+22 -16
View File
@@ -8,6 +8,8 @@
import Icon from "@lib/components/Icon.svelte"
import {preventDefault} from "@lib/html"
import {ucFirst} from "@lib/util"
import Modal from "@lib/components/Modal.svelte"
import ModalBody from "@lib/components/ModalBody.svelte"
import ModalHeader from "@lib/components/ModalHeader.svelte"
import ModalFooter from "@lib/components/ModalFooter.svelte"
import SpaceAccessRequest from "@app/components/SpaceAccessRequest.svelte"
@@ -37,21 +39,25 @@
let loading = $state(false)
</script>
<form class="column gap-4" onsubmit={preventDefault(requestAccess)}>
<ModalHeader>
{#snippet title()}
<div>Access Error</div>
{/snippet}
{#snippet info()}
<div>We couldn't connect you to this space.</div>
{/snippet}
</ModalHeader>
<p>
We received an error from the relay indicating you don't have access to {displayRelayUrl(url)}:
</p>
<p class="bg-alt card2 welshman-content">
{@html renderAsHtml(parse({content: ucFirst(error)}))}
</p>
<Modal tag="form" onsubmit={preventDefault(requestAccess)}>
<ModalBody>
<ModalHeader>
{#snippet title()}
<div>Access Error</div>
{/snippet}
{#snippet info()}
<div>We couldn't connect you to this space.</div>
{/snippet}
</ModalHeader>
<p>
We received an error from the relay indicating you don't have access to {displayRelayUrl(
url,
)}:
</p>
<p class="bg-alt card2 welshman-content">
{@html renderAsHtml(parse({content: ucFirst(error)}))}
</p>
</ModalBody>
<ModalFooter>
<Button class="btn btn-link" onclick={back}>
<Icon icon={AltArrowLeft} />
@@ -67,4 +73,4 @@
</Button>
</div>
</ModalFooter>
</form>
</Modal>
+56 -52
View File
@@ -8,6 +8,8 @@
import BillList from "@assets/icons/bill-list.svg?dataurl"
import Icon from "@lib/components/Icon.svelte"
import Link from "@lib/components/Link.svelte"
import Modal from "@lib/components/Modal.svelte"
import ModalBody from "@lib/components/ModalBody.svelte"
import ModalFooter from "@lib/components/ModalFooter.svelte"
import Button from "@lib/components/Button.svelte"
import RelayName from "@app/components/RelayName.svelte"
@@ -33,68 +35,70 @@
const startEdit = () => pushModal(SpaceEdit, {url, initialValues: $relay || {url}})
</script>
<div class="column gap-4">
<div class="flex justify-between">
<div class="relative flex gap-4">
<div class="relative">
<div class="avatar relative">
<div
class="center !flex h-16 w-16 min-w-16 rounded-full border-2 border-solid border-base-300 bg-base-300">
<RelayIcon {url} size={10} />
<Modal>
<ModalBody>
<div class="flex justify-between">
<div class="relative flex gap-4">
<div class="relative">
<div class="avatar relative">
<div
class="center !flex h-16 w-16 min-w-16 rounded-full border-2 border-solid border-base-300 bg-base-300">
<RelayIcon {url} size={10} />
</div>
</div>
</div>
<div class="flex min-w-0 flex-col gap-1">
<h1 class="ellipsize whitespace-nowrap text-2xl font-bold">
<RelayName {url} />
</h1>
<p class="ellipsize text-sm opacity-75">{displayRelayUrl(url)}</p>
</div>
</div>
<div class="flex min-w-0 flex-col gap-1">
<h1 class="ellipsize whitespace-nowrap text-2xl font-bold">
<RelayName {url} />
</h1>
<p class="ellipsize text-sm opacity-75">{displayRelayUrl(url)}</p>
</div>
</div>
{#if $userIsAdmin}
<Button class="btn btn-primary" onclick={startEdit}>
<Icon icon={Pen} />
Edit
</Button>
{/if}
</div>
<RelayDescription {url} />
{#if $relay?.terms_of_service || $relay?.privacy_policy}
<div class="flex gap-3">
{#if $relay.terms_of_service}
<Link href={$relay.terms_of_service} class="badge badge-neutral flex gap-2">
<Icon icon={BillList} size={4} />
Terms of Service
</Link>
{/if}
{#if $relay.privacy_policy}
<Link href={$relay.privacy_policy} class="badge badge-neutral flex gap-2">
<Icon icon={ShieldUser} size={4} />
Privacy Policy
</Link>
{#if $userIsAdmin}
<Button class="btn btn-primary" onclick={startEdit}>
<Icon icon={Pen} />
Edit
</Button>
{/if}
</div>
{/if}
<SpaceRelayStatus {url} />
<div class="flex flex-col gap-2">
{#if owner}
<div class="card2 bg-alt">
<h3 class="mb-4 flex items-center gap-2 text-lg font-semibold">
<Icon icon={UserRounded} />
Latest Updates
</h3>
<ProfileLatest {url} pubkey={owner}>
{#snippet fallback()}
<p class="text-sm opacity-60">No recent posts from the relay admin</p>
{/snippet}
</ProfileLatest>
<RelayDescription {url} />
{#if $relay?.terms_of_service || $relay?.privacy_policy}
<div class="flex gap-3">
{#if $relay.terms_of_service}
<Link href={$relay.terms_of_service} class="badge badge-neutral flex gap-2">
<Icon icon={BillList} size={4} />
Terms of Service
</Link>
{/if}
{#if $relay.privacy_policy}
<Link href={$relay.privacy_policy} class="badge badge-neutral flex gap-2">
<Icon icon={ShieldUser} size={4} />
Privacy Policy
</Link>
{/if}
</div>
{/if}
</div>
<SpaceRelayStatus {url} />
<div class="flex flex-col gap-2">
{#if owner}
<div class="card2 bg-alt">
<h3 class="mb-4 flex items-center gap-2 text-lg font-semibold">
<Icon icon={UserRounded} />
Latest Updates
</h3>
<ProfileLatest {url} pubkey={owner}>
{#snippet fallback()}
<p class="text-sm opacity-60">No recent posts from the relay admin</p>
{/snippet}
</ProfileLatest>
</div>
{/if}
</div>
</ModalBody>
<ModalFooter>
<Button class="btn btn-link" onclick={back}>
<Icon icon={AltArrowLeft} />
Go back
</Button>
</ModalFooter>
</div>
</Modal>
+66 -62
View File
@@ -11,6 +11,8 @@
import Icon from "@lib/components/Icon.svelte"
import Spinner from "@lib/components/Spinner.svelte"
import Button from "@lib/components/Button.svelte"
import Modal from "@lib/components/Modal.svelte"
import ModalBody from "@lib/components/ModalBody.svelte"
import ModalHeader from "@lib/components/ModalHeader.svelte"
import ModalFooter from "@lib/components/ModalFooter.svelte"
import ImageIcon from "@lib/components/ImageIcon.svelte"
@@ -119,70 +121,72 @@
}
</script>
<form class="column gap-4" onsubmit={preventDefault(trySubmit)}>
<ModalHeader>
{#snippet title()}
<div>Edit a Space</div>
{/snippet}
{#snippet info()}
<span class="text-primary">{displayRelayUrl(url)}</span>
{/snippet}
</ModalHeader>
<FieldInline>
{#snippet label()}
<p>Icon</p>
{/snippet}
{#snippet input()}
<div class="flex items-center justify-between">
<div class="flex items-center gap-4">
{#if imagePreview}
<div class="flex items-center gap-2">
<span class="text-sm opacity-75">Selected:</span>
<ImageIcon src={imagePreview} alt="" />
<Modal tag="form" onsubmit={preventDefault(trySubmit)}>
<ModalBody>
<ModalHeader>
{#snippet title()}
<div>Edit a Space</div>
{/snippet}
{#snippet info()}
<span class="text-primary">{displayRelayUrl(url)}</span>
{/snippet}
</ModalHeader>
<FieldInline>
{#snippet label()}
<p>Icon</p>
{/snippet}
{#snippet input()}
<div class="flex items-center justify-between">
<div class="flex items-center gap-4">
{#if imagePreview}
<div class="flex items-center gap-2">
<span class="text-sm opacity-75">Selected:</span>
<ImageIcon src={imagePreview} alt="" />
</div>
{:else}
<span class="text-sm opacity-75">No icon selected</span>
{/if}
<div class="flex gap-2">
<IconPickerButton onSelect={handleIconSelect} class="btn btn-primary btn-xs">
<Icon icon={StickerSmileSquare} size={4} />
Select
</IconPickerButton>
<label class="btn btn-neutral btn-xs cursor-pointer">
<Icon icon={UploadMinimalistic} size={4} />
Upload
<input type="file" accept="image/*" class="hidden" onchange={handleImageUpload} />
</label>
</div>
{:else}
<span class="text-sm opacity-75">No icon selected</span>
{/if}
<div class="flex gap-2">
<IconPickerButton onSelect={handleIconSelect} class="btn btn-primary btn-xs">
<Icon icon={StickerSmileSquare} size={4} />
Select
</IconPickerButton>
<label class="btn btn-neutral btn-xs cursor-pointer">
<Icon icon={UploadMinimalistic} size={4} />
Upload
<input type="file" accept="image/*" class="hidden" onchange={handleImageUpload} />
</label>
</div>
</div>
</div>
{/snippet}
</FieldInline>
<FieldInline>
{#snippet label()}
<p>Name</p>
{/snippet}
{#snippet input()}
<label class="input input-bordered flex w-full items-center gap-2">
{#if imagePreview}
<ImageIcon src={imagePreview} alt="" />
{:else}
<Icon icon={SettingsMinimalistic} />
{/if}
<input bind:value={values.name} class="grow" type="text" />
</label>
{/snippet}
</FieldInline>
<FieldInline>
{#snippet label()}
<p>Description</p>
{/snippet}
{#snippet input()}
<label class="input input-bordered flex w-full items-center gap-2">
<input bind:value={values.description} class="grow" type="text" />
</label>
{/snippet}
</FieldInline>
{/snippet}
</FieldInline>
<FieldInline>
{#snippet label()}
<p>Name</p>
{/snippet}
{#snippet input()}
<label class="input input-bordered flex w-full items-center gap-2">
{#if imagePreview}
<ImageIcon src={imagePreview} alt="" />
{:else}
<Icon icon={SettingsMinimalistic} />
{/if}
<input bind:value={values.name} class="grow" type="text" />
</label>
{/snippet}
</FieldInline>
<FieldInline>
{#snippet label()}
<p>Description</p>
{/snippet}
{#snippet input()}
<label class="input input-bordered flex w-full items-center gap-2">
<input bind:value={values.description} class="grow" type="text" />
</label>
{/snippet}
</FieldInline>
</ModalBody>
<ModalFooter>
<Button class="btn btn-link" onclick={back}>
<Icon icon={AltArrowLeft} />
@@ -192,4 +196,4 @@
<Spinner {loading}>Save Changes</Spinner>
</Button>
</ModalFooter>
</form>
</Modal>
+14 -10
View File
@@ -6,6 +6,8 @@
import Button from "@lib/components/Button.svelte"
import AltArrowLeft from "@assets/icons/alt-arrow-left.svg?dataurl"
import Icon from "@lib/components/Icon.svelte"
import Modal from "@lib/components/Modal.svelte"
import ModalBody from "@lib/components/ModalBody.svelte"
import ModalHeader from "@lib/components/ModalHeader.svelte"
import ModalFooter from "@lib/components/ModalFooter.svelte"
import {removeSpaceMembership, publishLeaveRequest, removeTrustedRelay} from "@app/core/commands"
@@ -31,15 +33,17 @@
let loading = $state(false)
</script>
<form class="column gap-4" onsubmit={preventDefault(exit)}>
<ModalHeader>
{#snippet title()}
<div>
You are leaving<br /><span class="text-primary">{displayRelayUrl(url)}</span>
</div>
{/snippet}
</ModalHeader>
<p class="text-center">Are you sure you want to leave?</p>
<Modal tag="form" onsubmit={preventDefault(exit)}>
<ModalBody>
<ModalHeader>
{#snippet title()}
<div>
You are leaving<br /><span class="text-primary">{displayRelayUrl(url)}</span>
</div>
{/snippet}
</ModalHeader>
<p class="text-center">Are you sure you want to leave?</p>
</ModalBody>
<ModalFooter>
<Button class="btn btn-link" onclick={back}>
<Icon icon={AltArrowLeft} />
@@ -49,4 +53,4 @@
<Spinner {loading}>Confirm</Spinner>
</Button>
</ModalFooter>
</form>
</Modal>
+50 -46
View File
@@ -9,6 +9,8 @@
import Field from "@lib/components/Field.svelte"
import Button from "@lib/components/Button.svelte"
import Icon from "@lib/components/Icon.svelte"
import Modal from "@lib/components/Modal.svelte"
import ModalBody from "@lib/components/ModalBody.svelte"
import ModalHeader from "@lib/components/ModalHeader.svelte"
import ModalFooter from "@lib/components/ModalFooter.svelte"
import QRCode from "@app/components/QRCode.svelte"
@@ -52,52 +54,54 @@
})
</script>
<div class="col-4">
<ModalHeader>
{#snippet title()}
<div>Create an Invite</div>
{/snippet}
{#snippet info()}
<div>
Get a link that you can use to invite people to
<span class="text-primary">{displayRelayUrl(url)}</span>
</div>
{/snippet}
</ModalHeader>
<div>
{#if loading}
<p class="center">
<Spinner {loading}>Requesting an invite link...</Spinner>
</p>
{:else if $authError}
<p class="center">Oops! It looks like you're not a member of this relay.</p>
{:else}
<div class="flex flex-col items-center gap-6">
<QRCode code={invite} />
<Field>
{#snippet input()}
<label class="input input-bordered flex w-full items-center gap-2">
<Icon icon={LinkRound} />
<input bind:value={invite} class="grow" type="text" />
<Button onclick={copyInvite}>
<Icon icon={Copy} />
</Button>
</label>
{/snippet}
{#snippet info()}
<p>
This invite link can be used by clicking "Add Space" and pasting it there.
{#if !claim}
This space did not issue a claim for this link, so additional steps might be
required.
{/if}
</p>
{/snippet}
</Field>
</div>
{/if}
</div>
<Modal>
<ModalBody>
<ModalHeader>
{#snippet title()}
<div>Create an Invite</div>
{/snippet}
{#snippet info()}
<div>
Get a link that you can use to invite people to
<span class="text-primary">{displayRelayUrl(url)}</span>
</div>
{/snippet}
</ModalHeader>
<div>
{#if loading}
<p class="center">
<Spinner {loading}>Requesting an invite link...</Spinner>
</p>
{:else if $authError}
<p class="center">Oops! It looks like you're not a member of this relay.</p>
{:else}
<div class="flex flex-col items-center gap-6">
<QRCode code={invite} />
<Field>
{#snippet input()}
<label class="input input-bordered flex w-full items-center gap-2">
<Icon icon={LinkRound} />
<input bind:value={invite} class="grow" type="text" />
<Button onclick={copyInvite}>
<Icon icon={Copy} />
</Button>
</label>
{/snippet}
{#snippet info()}
<p>
This invite link can be used by clicking "Add Space" and pasting it there.
{#if !claim}
This space did not issue a claim for this link, so additional steps might be
required.
{/if}
</p>
{/snippet}
</Field>
</div>
{/if}
</div>
</ModalBody>
<ModalFooter>
<Button class="btn btn-primary flex-grow" onclick={back}>Done</Button>
</ModalFooter>
</div>
</Modal>
+37 -39
View File
@@ -12,6 +12,8 @@
import AltArrowLeft from "@assets/icons/alt-arrow-left.svg?dataurl"
import AltArrowRight from "@assets/icons/alt-arrow-right.svg?dataurl"
import Icon from "@lib/components/Icon.svelte"
import Modal from "@lib/components/Modal.svelte"
import ModalBody from "@lib/components/ModalBody.svelte"
import ModalHeader from "@lib/components/ModalHeader.svelte"
import ModalFooter from "@lib/components/ModalFooter.svelte"
import RelaySummary from "@app/components/RelaySummary.svelte"
@@ -24,12 +26,10 @@
type Props = {
invite: string
abortAction?: Snippet
back?: () => void
}
let {invite = "", abortAction}: Props = $props()
const back = () => history.back()
let {invite = "", back = () => history.back()}: Props = $props()
const joinRelay = async () => {
const {url, claim} = inviteData!
@@ -63,48 +63,46 @@
const inviteData = $derived(parseInviteLink(invite))
</script>
<form class="column gap-4" onsubmit={preventDefault(join)}>
<ModalHeader>
{#snippet title()}
<div>Join a Space</div>
{/snippet}
{#snippet info()}
<div>Enter a relay URL or invite link below to join an existing space.</div>
{/snippet}
</ModalHeader>
<Field>
{#snippet label()}
<p>Invite Link*</p>
{/snippet}
{#snippet input()}
<label class="input input-bordered flex w-full items-center gap-2">
<Icon icon={LinkRound} />
<input bind:value={invite} class="grow" type="text" />
</label>
{/snippet}
</Field>
<div class="-my-4">
<Modal tag="form" onsubmit={preventDefault(join)}>
<ModalBody>
<ModalHeader>
{#snippet title()}
<div>Join a Space</div>
{/snippet}
{#snippet info()}
<div>Enter a relay URL or invite link below to join an existing space.</div>
{/snippet}
</ModalHeader>
<Field>
{#snippet label()}
<p>Invite Link*</p>
{/snippet}
{#snippet input()}
<label class="input input-bordered flex w-full items-center gap-2">
<Icon icon={LinkRound} />
<input bind:value={invite} class="grow" type="text" />
</label>
{/snippet}
</Field>
{#if inviteData}
<div transition:slideAndFade class="flex flex-col gap-4 py-4">
<div class="card2 bg-alt flex flex-col gap-4">
<p class="opacity-75">You're about to join:</p>
<RelaySummary url={inviteData.url} />
<div class="-my-4">
<div transition:slideAndFade class="flex flex-col gap-4 py-4">
<div class="card2 bg-alt flex flex-col gap-4">
<p class="opacity-75">You're about to join:</p>
<RelaySummary url={inviteData.url} />
</div>
</div>
</div>
{/if}
</div>
</ModalBody>
<ModalFooter>
{#if abortAction}
{@render abortAction?.()}
{:else}
<Button class="btn btn-link" onclick={back}>
<Icon icon={AltArrowLeft} />
Go back
</Button>
{/if}
<Button class="btn btn-link" onclick={back}>
<Icon icon={AltArrowLeft} />
Go back
</Button>
<Button type="submit" class="btn btn-primary" disabled={!inviteData || loading}>
<Spinner {loading}>Join Space</Spinner>
<Icon icon={AltArrowRight} />
</Button>
</ModalFooter>
</form>
</Modal>
+36 -28
View File
@@ -12,14 +12,20 @@
import Icon from "@lib/components/Icon.svelte"
import Button from "@lib/components/Button.svelte"
import Spinner from "@lib/components/Spinner.svelte"
import ModalHeader from "@lib/components/ModalHeader.svelte"
import Modal from "@lib/components/Modal.svelte"
import ModalBody from "@lib/components/ModalBody.svelte"
import ModalFooter from "@lib/components/ModalFooter.svelte"
import FieldInline from "@lib/components/FieldInline.svelte"
import StatusIndicator from "@lib/components/StatusIndicator.svelte"
import RelaySummary from "@app/components/RelaySummary.svelte"
import SocketStatusIndicator from "@app/components/SocketStatusIndicator.svelte"
import SpaceAccessRequest from "@app/components/SpaceAccessRequest.svelte"
import {attemptRelayAccess, addSpaceMembership, broadcastUserData, setSpaceNotifications} from "@app/core/commands"
import {
attemptRelayAccess,
addSpaceMembership,
broadcastUserData,
setSpaceNotifications,
} from "@app/core/commands"
import {relaysMostlyRestricted, deriveSpaceMembers, notificationSettings} from "@app/core/state"
import {pushModal} from "@app/util/modal"
import {pushToast} from "@app/util/toast"
@@ -83,45 +89,47 @@
})
</script>
<form class="column gap-4" onsubmit={preventDefault(join)}>
<RelaySummary {url} />
<div class="card2 card2-sm bg-alt">
<div class="flex justify-between gap-12">
<div class="col-1">
<strong>Enable notifications for this space</strong>
<p class="text-xs opacity-75">
Get notified about new activity in this space. You can change this later in settings.
</p>
<Modal tag="form" onsubmit={preventDefault(join)}>
<ModalBody>
<RelaySummary {url} />
<div class="card2 card2-sm bg-alt">
<div class="flex justify-between gap-12">
<div class="col-1">
<strong>Enable notifications for this space</strong>
<p class="text-xs opacity-75">
Get notified about new activity in this space. You can change this later in settings.
</p>
</div>
<input type="checkbox" class="toggle toggle-primary" bind:checked={notifications} />
</div>
<input type="checkbox" class="toggle toggle-primary" bind:checked={notifications} />
</div>
</div>
<div class="card2 card2-sm bg-alt flex flex-col gap-2">
<div class="flex justify-between">
<strong>Connection Status</strong>
<div class="card2 card2-sm bg-alt flex flex-col gap-2">
<div class="flex justify-between">
<strong>Connection Status</strong>
{#if error}
<StatusIndicator class="bg-error">Error</StatusIndicator>
{:else}
<SocketStatusIndicator {url} />
{/if}
</div>
{#if error}
<StatusIndicator class="bg-error">Error</StatusIndicator>
{:else}
<SocketStatusIndicator {url} />
<div class="flex items-center gap-2">
<Icon icon={DangerTriangle} />
<p class="text-sm opacity-75">{error}</p>
</div>
{/if}
</div>
{#if error}
<div class="flex items-center gap-2">
<Icon icon={DangerTriangle} />
<p class="text-sm opacity-75">{error}</p>
</div>
{/if}
</div>
</ModalBody>
<ModalFooter>
<Button class="btn btn-link" onclick={back} disabled={loading}>
<Icon icon={AltArrowLeft} />
Go back
</Button>
<Button type="submit" class="btn btn-primary" disabled={loading}>
<Spinner loading={loading}>
<Spinner {loading}>
{error ? "Request Access" : "Join Space"}
</Spinner>
<Icon icon={AltArrowRight} />
</Button>
</ModalFooter>
</form>
</Modal>
+47 -43
View File
@@ -10,6 +10,8 @@
import Icon from "@lib/components/Icon.svelte"
import Popover from "@lib/components/Popover.svelte"
import Confirm from "@lib/components/Confirm.svelte"
import Modal from "@lib/components/Modal.svelte"
import ModalBody from "@lib/components/ModalBody.svelte"
import ModalFooter from "@lib/components/ModalFooter.svelte"
import Profile from "@app/components/Profile.svelte"
import SpaceMembersAdd from "@app/components/SpaceMembersAdd.svelte"
@@ -68,56 +70,58 @@
let menuPubkey = $state<string | undefined>()
</script>
<div class="column gap-4">
<div class="flex min-w-0 flex-col gap-1">
<h1 class="ellipsize whitespace-nowrap text-2xl font-bold">Members</h1>
<p class="ellipsize text-sm opacity-75">of {displayRelayUrl(url)}</p>
</div>
{#if $userIsAdmin}
<div class="flex gap-2">
<Button class="btn btn-primary" onclick={addMember}>
<Icon icon={AddCircle} />
Add members
</Button>
{#if $bans.length > 0}
<Button class="btn btn-neutral" onclick={showBannedPubkeyItems}>
Banned users ({$bans.length})
</Button>
{/if}
<Modal>
<ModalBody>
<div class="flex min-w-0 flex-col gap-1">
<h1 class="ellipsize whitespace-nowrap text-2xl font-bold">Members</h1>
<p class="ellipsize text-sm opacity-75">of {displayRelayUrl(url)}</p>
</div>
{/if}
{#each $members as pubkey (pubkey)}
<div class="card2 card2-sm bg-alt relative">
<div class="flex items-center justify-between gap-2">
<div class="min-w-0 flex-1">
<Profile {pubkey} {url} />
</div>
<div class="relative">
<Button class="btn btn-circle btn-ghost btn-sm" onclick={() => toggleMenu(pubkey)}>
<Icon icon={MenuDots} />
{#if $userIsAdmin}
<div class="flex gap-2">
<Button class="btn btn-primary" onclick={addMember}>
<Icon icon={AddCircle} />
Add members
</Button>
{#if $bans.length > 0}
<Button class="btn btn-neutral" onclick={showBannedPubkeyItems}>
Banned users ({$bans.length})
</Button>
{#if menuPubkey === pubkey}
<Popover hideOnClick onClose={closeMenu}>
<ul
transition:fly
class="menu absolute right-0 z-popover mt-2 w-48 gap-1 rounded-box bg-base-100 p-2 shadow-md">
<li>
<Button class="text-error" onclick={() => banMember(pubkey)}>
<Icon icon={MinusCircle} />
Ban User
</Button>
</li>
</ul>
</Popover>
{/if}
{/if}
</div>
{/if}
{#each $members as pubkey (pubkey)}
<div class="card2 card2-sm bg-alt relative">
<div class="flex items-center justify-between gap-2">
<div class="min-w-0 flex-1">
<Profile {pubkey} {url} />
</div>
<div class="relative">
<Button class="btn btn-circle btn-ghost btn-sm" onclick={() => toggleMenu(pubkey)}>
<Icon icon={MenuDots} />
</Button>
{#if menuPubkey === pubkey}
<Popover hideOnClick onClose={closeMenu}>
<ul
transition:fly
class="menu absolute right-0 z-popover mt-2 w-48 gap-1 rounded-box bg-base-100 p-2 shadow-md">
<li>
<Button class="text-error" onclick={() => banMember(pubkey)}>
<Icon icon={MinusCircle} />
Ban User
</Button>
</li>
</ul>
</Popover>
{/if}
</div>
</div>
</div>
</div>
{/each}
{/each}
</ModalBody>
<ModalFooter>
<Button class="btn btn-link" onclick={back}>
<Icon icon={AltArrowLeft} />
Go back
</Button>
</ModalFooter>
</div>
</Modal>
+22 -18
View File
@@ -6,6 +6,8 @@
import Button from "@lib/components/Button.svelte"
import Field from "@lib/components/Field.svelte"
import Icon from "@lib/components/Icon.svelte"
import Modal from "@lib/components/Modal.svelte"
import ModalBody from "@lib/components/ModalBody.svelte"
import ModalHeader from "@lib/components/ModalHeader.svelte"
import ModalFooter from "@lib/components/ModalFooter.svelte"
import ProfileMultiSelect from "@app/components/ProfileMultiSelect.svelte"
@@ -49,23 +51,25 @@
let pubkeys: string[] = $state([])
</script>
<div class="column gap-4">
<ModalHeader>
{#snippet title()}
<div>Add Members</div>
{/snippet}
{#snippet info()}
<div>to {displayRelayUrl(url)}</div>
{/snippet}
</ModalHeader>
<Field>
{#snippet label()}
<p>Search for People</p>
{/snippet}
{#snippet input()}
<ProfileMultiSelect bind:value={pubkeys} />
{/snippet}
</Field>
<Modal>
<ModalBody>
<ModalHeader>
{#snippet title()}
<div>Add Members</div>
{/snippet}
{#snippet info()}
<div>to {displayRelayUrl(url)}</div>
{/snippet}
</ModalHeader>
<Field>
{#snippet label()}
<p>Search for People</p>
{/snippet}
{#snippet input()}
<ProfileMultiSelect bind:value={pubkeys} />
{/snippet}
</Field>
</ModalBody>
<ModalFooter>
<Button class="btn btn-link" onclick={back}>
<Icon icon={AltArrowLeft} />
@@ -75,4 +79,4 @@
<Spinner {loading}>Save changes</Spinner>
</Button>
</ModalFooter>
</div>
</Modal>
+40 -36
View File
@@ -8,6 +8,8 @@
import Button from "@lib/components/Button.svelte"
import Icon from "@lib/components/Icon.svelte"
import Popover from "@lib/components/Popover.svelte"
import Modal from "@lib/components/Modal.svelte"
import ModalBody from "@lib/components/ModalBody.svelte"
import ModalHeader from "@lib/components/ModalHeader.svelte"
import ModalFooter from "@lib/components/ModalFooter.svelte"
import Profile from "@app/components/Profile.svelte"
@@ -49,47 +51,49 @@
let menuPubkey = $state<string | undefined>()
</script>
<div class="column gap-4">
<ModalHeader>
{#snippet title()}
<div>Banned users</div>
{/snippet}
{#snippet info()}
<div>on {displayRelayUrl(url)}</div>
{/snippet}
</ModalHeader>
{#each $bans as { pubkey, reason } (pubkey)}
<div class="card2 bg-alt relative">
<div class="flex items-center justify-between gap-2">
<div class="min-w-0 flex-1">
<Profile {pubkey} {url} />
</div>
<div class="relative">
<Button class="btn btn-circle btn-ghost btn-sm" onclick={() => toggleMenu(pubkey)}>
<Icon icon={MenuDots} />
</Button>
{#if menuPubkey === pubkey}
<Popover hideOnClick onClose={closeMenu}>
<ul
transition:fly
class="menu absolute right-0 z-popover mt-2 w-48 gap-1 rounded-box bg-base-100 p-2 shadow-md">
<li>
<Button onclick={() => restoreMember(pubkey)}>
<Icon icon={Restart} />
Restore User
</Button>
</li>
</ul>
</Popover>
{/if}
<Modal>
<ModalBody>
<ModalHeader>
{#snippet title()}
<div>Banned users</div>
{/snippet}
{#snippet info()}
<div>on {displayRelayUrl(url)}</div>
{/snippet}
</ModalHeader>
{#each $bans as { pubkey, reason } (pubkey)}
<div class="card2 bg-alt relative">
<div class="flex items-center justify-between gap-2">
<div class="min-w-0 flex-1">
<Profile {pubkey} {url} />
</div>
<div class="relative">
<Button class="btn btn-circle btn-ghost btn-sm" onclick={() => toggleMenu(pubkey)}>
<Icon icon={MenuDots} />
</Button>
{#if menuPubkey === pubkey}
<Popover hideOnClick onClose={closeMenu}>
<ul
transition:fly
class="menu absolute right-0 z-popover mt-2 w-48 gap-1 rounded-box bg-base-100 p-2 shadow-md">
<li>
<Button onclick={() => restoreMember(pubkey)}>
<Icon icon={Restart} />
Restore User
</Button>
</li>
</ul>
</Popover>
{/if}
</div>
</div>
</div>
</div>
{/each}
{/each}
</ModalBody>
<ModalFooter>
<Button class="btn btn-link" onclick={back}>
<Icon icon={AltArrowLeft} />
Got it
</Button>
</ModalFooter>
</div>
</Modal>
+13 -9
View File
@@ -3,6 +3,8 @@
import AltArrowLeft from "@assets/icons/alt-arrow-left.svg?dataurl"
import Button from "@lib/components/Button.svelte"
import Icon from "@lib/components/Icon.svelte"
import Modal from "@lib/components/Modal.svelte"
import ModalBody from "@lib/components/ModalBody.svelte"
import ModalFooter from "@lib/components/ModalFooter.svelte"
import ReportItem from "@app/components/ReportItem.svelte"
import {deriveEventsForUrl} from "@app/core/state"
@@ -18,18 +20,20 @@
const back = () => history.back()
</script>
<div class="column gap-4">
<div class="flex min-w-0 flex-col gap-1">
<h1 class="ellipsize whitespace-nowrap text-2xl font-bold">Reports</h1>
<p class="ellipsize text-sm opacity-75">on {displayRelayUrl(url)}</p>
</div>
{#each $reports as event (event.id)}
<ReportItem {url} {event} />
{/each}
<Modal>
<ModalBody>
<div class="flex min-w-0 flex-col gap-1">
<h1 class="ellipsize whitespace-nowrap text-2xl font-bold">Reports</h1>
<p class="ellipsize text-sm opacity-75">on {displayRelayUrl(url)}</p>
</div>
{#each $reports as event (event.id)}
<ReportItem {url} {event} />
{/each}
</ModalBody>
<ModalFooter>
<Button class="btn btn-link" onclick={back}>
<Icon icon={AltArrowLeft} />
Go back
</Button>
</ModalFooter>
</div>
</Modal>
+44 -37
View File
@@ -8,7 +8,10 @@
import Icon from "@lib/components/Icon.svelte"
import Spinner from "@lib/components/Spinner.svelte"
import Button from "@lib/components/Button.svelte"
import Modal from "@lib/components/Modal.svelte"
import ModalBody from "@lib/components/ModalBody.svelte"
import ModalHeader from "@lib/components/ModalHeader.svelte"
import ModalFooter from "@lib/components/ModalFooter.svelte"
import InfoSignatures from "@app/components/InfoSignatures.svelte"
import {relaysPendingTrust} from "@app/core/state"
import {removeSpaceMembership, addTrustedRelay, removeTrustedRelay} from "@app/core/commands"
@@ -49,40 +52,44 @@
let loading = $state(false)
</script>
<form class="column gap-4" onsubmit={preventDefault(trustSpace)}>
<ModalHeader>
{#snippet title()}
Do you trust this space?
{/snippet}
{#snippet info()}
<div>
Only join <span class="text-primary">{displayRelayUrl(url)}</span> if you trust the adminstrator
</div>
{/snippet}
</ModalHeader>
<div class="m-auto flex flex-col gap-4">
<p>
This space has opted not to publish <Button class="link" onclick={showInfoSignatures}
>digital signatures</Button
>, which means that they have the ability to forge messages from other users.
</p>
<p>
If you trust this space's admin, you can continue. Otherwise, it may be safer not to join this
space.
</p>
</div>
<div class="mt-4 flex flex-col gap-2 sm:flex-row sm:justify-between">
<Button class="btn btn-neutral" onclick={untrustSpace} disabled={loading}>
{#if !loading}
<Icon icon={CloseCircle} />
{/if}
<Spinner {loading}>I don't trust this space</Spinner>
</Button>
<Button type="submit" class="btn btn-primary" disabled={loading}>
{#if !loading}
<Icon icon={CheckCircle} />
{/if}
<Spinner {loading}>I trust this space, continue</Spinner>
</Button>
</div>
</form>
<Modal tag="form" onsubmit={preventDefault(trustSpace)}>
<ModalBody>
<ModalHeader>
{#snippet title()}
Do you trust this space?
{/snippet}
{#snippet info()}
<div>
Only join <span class="text-primary">{displayRelayUrl(url)}</span> if you trust the adminstrator
</div>
{/snippet}
</ModalHeader>
<div class="m-auto flex flex-col gap-4">
<p>
This space has opted not to publish <Button class="link" onclick={showInfoSignatures}
>digital signatures</Button
>, which means that they have the ability to forge messages from other users.
</p>
<p>
If you trust this space's admin, you can continue. Otherwise, it may be safer not to join
this space.
</p>
</div>
</ModalBody>
<ModalFooter>
<div class="flex flex-col gap-2 sm:flex-row sm:justify-between">
<Button class="btn btn-neutral" onclick={untrustSpace} disabled={loading}>
{#if !loading}
<Icon icon={CloseCircle} />
{/if}
<Spinner {loading}>I don't trust this space</Spinner>
</Button>
<Button type="submit" class="btn btn-primary" disabled={loading}>
{#if !loading}
<Icon icon={CheckCircle} />
{/if}
<Spinner {loading}>I trust this space, continue</Spinner>
</Button>
</div>
</ModalFooter>
</Modal>
+50 -46
View File
@@ -10,6 +10,8 @@
import Button from "@lib/components/Button.svelte"
import ModalHeader from "@lib/components/ModalHeader.svelte"
import ModalFooter from "@lib/components/ModalFooter.svelte"
import Modal from "@lib/components/Modal.svelte"
import ModalBody from "@lib/components/ModalBody.svelte"
import EditorContent from "@app/editor/EditorContent.svelte"
import {pushToast} from "@app/util/toast"
import {PROTECTED} from "@app/core/state"
@@ -74,53 +76,55 @@
let title: string = $state("")
</script>
<form class="column gap-4" onsubmit={preventDefault(submit)}>
<ModalHeader>
{#snippet title()}
<div>Create a Thread</div>
{/snippet}
{#snippet info()}
<div>Share a link, or start a discussion.</div>
{/snippet}
</ModalHeader>
<div class="col-8 relative">
<Field>
{#snippet label()}
<p>Title*</p>
<Modal tag="form" onsubmit={preventDefault(submit)}>
<ModalBody>
<ModalHeader>
{#snippet title()}
<div>Create a Thread</div>
{/snippet}
{#snippet input()}
<label class="input input-bordered flex w-full items-center gap-2">
<!-- svelte-ignore a11y_autofocus -->
<input
autofocus={!isMobile}
bind:value={title}
class="grow"
type="text"
placeholder="What is this thread about?" />
</label>
{#snippet info()}
<div>Share a link, or start a discussion.</div>
{/snippet}
</Field>
<Field>
{#snippet label()}
<p>Message*</p>
{/snippet}
{#snippet input()}
<div class="note-editor flex-grow overflow-hidden">
<EditorContent {editor} />
</div>
{/snippet}
</Field>
<Button
data-tip="Add an image"
class="tooltip tooltip-left absolute bottom-1 right-2"
onclick={selectFiles}>
{#if $uploading}
<span class="loading loading-spinner loading-xs"></span>
{:else}
<Icon icon={Paperclip} size={3} />
{/if}
</Button>
</div>
</ModalHeader>
<div class="col-8 relative">
<Field>
{#snippet label()}
<p>Title*</p>
{/snippet}
{#snippet input()}
<label class="input input-bordered flex w-full items-center gap-2">
<!-- svelte-ignore a11y_autofocus -->
<input
autofocus={!isMobile}
bind:value={title}
class="grow"
type="text"
placeholder="What is this thread about?" />
</label>
{/snippet}
</Field>
<Field>
{#snippet label()}
<p>Message*</p>
{/snippet}
{#snippet input()}
<div class="note-editor flex-grow overflow-hidden">
<EditorContent {editor} />
</div>
{/snippet}
</Field>
<Button
data-tip="Add an image"
class="tooltip tooltip-left absolute bottom-1 right-2"
onclick={selectFiles}>
{#if $uploading}
<span class="loading loading-spinner loading-xs"></span>
{:else}
<Icon icon={Paperclip} size={3} />
{/if}
</Button>
</div>
</ModalBody>
<ModalFooter>
<Button class="btn btn-link" onclick={back}>
<Icon icon={AltArrowLeft} />
@@ -128,4 +132,4 @@
</Button>
<Button type="submit" class="btn btn-primary">Create Thread</Button>
</ModalFooter>
</form>
</Modal>
@@ -2,6 +2,8 @@
import {getWalletAddress} from "@welshman/util"
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"
import ModalFooter from "@lib/components/ModalFooter.svelte"
import {updateProfile} from "@app/core/commands"
@@ -32,30 +34,33 @@
let loading = $state(false)
</script>
<div class="column gap-4">
<ModalHeader>
{#snippet title()}
Set as Receiving Address?
{/snippet}
</ModalHeader>
{#if $userProfile?.lud16}
<p>
Your current receiving address is different from the one provided by your connected wallet.
</p>
<p>
Would you like to update your receiving address to <span class="text-primary">{lud16}</span>?
</p>
{:else}
<p>
You don't currently have a receiving address set, which means other people can't send you
lightning payments.
</p>
<p>Would you like to use the one associated with your connected wallet?</p>
{/if}
<Modal>
<ModalBody>
<ModalHeader>
{#snippet title()}
Set as Receiving Address?
{/snippet}
</ModalHeader>
{#if $userProfile?.lud16}
<p>
Your current receiving address is different from the one provided by your connected wallet.
</p>
<p>
Would you like to update your receiving address to <span class="text-primary">{lud16}</span
>?
</p>
{:else}
<p>
You don't currently have a receiving address set, which means other people can't send you
lightning payments.
</p>
<p>Would you like to use the one associated with your connected wallet?</p>
{/if}
</ModalBody>
<ModalFooter>
<Button class="btn btn-neutral" onclick={cancel} disabled={loading}>No, skip this</Button>
<Button class="btn btn-primary" onclick={confirm} disabled={loading}>
<Spinner {loading}>Yes, set as receiving address</Spinner>
</Button>
</ModalFooter>
</div>
</Modal>
+60 -56
View File
@@ -15,6 +15,8 @@
import Scanner from "@lib/components/Scanner.svelte"
import Spinner from "@lib/components/Spinner.svelte"
import Field from "@lib/components/Field.svelte"
import Modal from "@lib/components/Modal.svelte"
import ModalBody from "@lib/components/ModalBody.svelte"
import ModalHeader from "@lib/components/ModalHeader.svelte"
import ModalFooter from "@lib/components/ModalFooter.svelte"
import {getWebLn} from "@app/core/commands"
@@ -109,61 +111,63 @@
let loading = $state(false)
</script>
<div class="column gap-4">
<ModalHeader>
{#snippet title()}
<div>Connect a Wallet</div>
{/snippet}
{#snippet info()}
Use Nostr Wallet Connect to send Bitcoin payments over lightning.
{/snippet}
</ModalHeader>
{#if getWebLn()}
<Button
class="btn btn-primary"
disabled={Boolean(nostrWalletConnectUrl || loading)}
onclick={connectWithWebLn}>
<Spinner loading={!nostrWalletConnectUrl && loading}>
{#if !nostrWalletConnectUrl && loading}
Connecting...
{:else}
<div class="flex items-center gap-2">
<Icon icon={Cpu} />
Connect with WebLN
</div>
{/if}
</Spinner>
</Button>
<Divider>Or</Divider>
{/if}
<Field>
{#snippet label()}
Connection Secret*
{/snippet}
{#snippet input()}
<label class="input input-bordered flex w-full items-center gap-2">
<Icon icon={Lock} />
<input
bind:value={nostrWalletConnectUrl}
autocomplete="off"
name="flotilla-nwc"
class="grow"
type="password" />
<Button onclick={toggleScanner}>
<Icon icon={QrCode} />
</Button>
</label>
{/snippet}
{#snippet info()}
You can find this in any wallet that supports
<Link external href="https://nwc.getalby.com/about" class="text-primary"
>Nostr Wallet Connect</Link
>.
{/snippet}
</Field>
{#if showScanner}
<Scanner onscan={onScan} />
{/if}
<Modal>
<ModalBody>
<ModalHeader>
{#snippet title()}
<div>Connect a Wallet</div>
{/snippet}
{#snippet info()}
Use Nostr Wallet Connect to send Bitcoin payments over lightning.
{/snippet}
</ModalHeader>
{#if getWebLn()}
<Button
class="btn btn-primary"
disabled={Boolean(nostrWalletConnectUrl || loading)}
onclick={connectWithWebLn}>
<Spinner loading={!nostrWalletConnectUrl && loading}>
{#if !nostrWalletConnectUrl && loading}
Connecting...
{:else}
<div class="flex items-center gap-2">
<Icon icon={Cpu} />
Connect with WebLN
</div>
{/if}
</Spinner>
</Button>
<Divider>Or</Divider>
{/if}
<Field>
{#snippet label()}
Connection Secret*
{/snippet}
{#snippet input()}
<label class="input input-bordered flex w-full items-center gap-2">
<Icon icon={Lock} />
<input
bind:value={nostrWalletConnectUrl}
autocomplete="off"
name="flotilla-nwc"
class="grow"
type="password" />
<Button onclick={toggleScanner}>
<Icon icon={QrCode} />
</Button>
</label>
{/snippet}
{#snippet info()}
You can find this in any wallet that supports
<Link external href="https://nwc.getalby.com/about" class="text-primary"
>Nostr Wallet Connect</Link
>.
{/snippet}
</Field>
{#if showScanner}
<Scanner onscan={onScan} />
{/if}
</ModalBody>
<ModalFooter>
<Button class="btn btn-link" onclick={back}>
<Icon icon={AltArrowLeft} />
@@ -185,4 +189,4 @@
</Spinner>
</Button>
</ModalFooter>
</div>
</Modal>
+51 -47
View File
@@ -8,6 +8,8 @@
import Button from "@lib/components/Button.svelte"
import FieldInline from "@lib/components/FieldInline.svelte"
import Scanner from "@lib/components/Scanner.svelte"
import Modal from "@lib/components/Modal.svelte"
import ModalBody from "@lib/components/ModalBody.svelte"
import ModalHeader from "@lib/components/ModalHeader.svelte"
import ModalFooter from "@lib/components/ModalFooter.svelte"
import {payInvoice} from "@app/core/commands"
@@ -48,52 +50,54 @@
let sats = $state(0)
</script>
<div class="column gap-4">
<ModalHeader>
{#snippet title()}
<div>Pay with Lightning</div>
{/snippet}
{#snippet info()}
Use your Nostr wallet to send Bitcoin payments over lightning.
{/snippet}
</ModalHeader>
{#if invoice}
<div class="card2 bg-alt flex flex-col gap-2">
{#if $session?.wallet?.type === "webln" && invoice.satoshi === 0}
<p class="text-sm opacity-75">
Uh oh! It looks like your current wallet doesn't support invoices without an amount. See
if you can get a lightning invoice with a pre-set amount.
</p>
{:else}
<FieldInline>
{#snippet label()}
Amount (satoshis)
{/snippet}
{#snippet input()}
<div class="flex flex-grow justify-end">
<label class="input input-bordered flex items-center gap-2">
<Icon icon={Bolt} />
<input
bind:value={sats}
type="number"
class="w-14"
disabled={invoice!.satoshi > 0} />
</label>
</div>
{/snippet}
</FieldInline>
<p class="text-sm opacity-75">
You're about to pay a bitcoin lightning invoice with the following description:
<strong>{invoice.description || "[no description]"}</strong>"
</p>
{/if}
</div>
{:else}
<Scanner onscan={onScan} />
<p class="text-center text-sm opacity-75">
To make a payment, scan a lightning invoice with your camera.
</p>
{/if}
<Modal>
<ModalBody>
<ModalHeader>
{#snippet title()}
<div>Pay with Lightning</div>
{/snippet}
{#snippet info()}
Use your Nostr wallet to send Bitcoin payments over lightning.
{/snippet}
</ModalHeader>
{#if invoice}
<div class="card2 bg-alt flex flex-col gap-2">
{#if $session?.wallet?.type === "webln" && invoice.satoshi === 0}
<p class="text-sm opacity-75">
Uh oh! It looks like your current wallet doesn't support invoices without an amount. See
if you can get a lightning invoice with a pre-set amount.
</p>
{:else}
<FieldInline>
{#snippet label()}
Amount (satoshis)
{/snippet}
{#snippet input()}
<div class="flex flex-grow justify-end">
<label class="input input-bordered flex items-center gap-2">
<Icon icon={Bolt} />
<input
bind:value={sats}
type="number"
class="w-14"
disabled={invoice!.satoshi > 0} />
</label>
</div>
{/snippet}
</FieldInline>
<p class="text-sm opacity-75">
You're about to pay a bitcoin lightning invoice with the following description:
<strong>{invoice.description || "[no description]"}</strong>"
</p>
{/if}
</div>
{:else}
<Scanner onscan={onScan} />
<p class="text-center text-sm opacity-75">
To make a payment, scan a lightning invoice with your camera.
</p>
{/if}
</ModalBody>
<ModalFooter>
<Button class="btn btn-link" onclick={back}>
<Icon icon={AltArrowLeft} />
@@ -108,4 +112,4 @@
Confirm Payment
</Button>
</ModalFooter>
</div>
</Modal>
@@ -5,6 +5,8 @@
import Button from "@lib/components/Button.svelte"
import ModalHeader from "@lib/components/ModalHeader.svelte"
import ModalFooter from "@lib/components/ModalFooter.svelte"
import Modal from "@lib/components/Modal.svelte"
import ModalBody from "@lib/components/ModalBody.svelte"
import Wallet from "@assets/icons/wallet.svg?dataurl"
import CheckCircle from "@assets/icons/check-circle.svg?dataurl"
import {updateProfile} from "@app/core/commands"
@@ -42,49 +44,50 @@
}
</script>
<div class="column gap-4">
<ModalHeader>
{#snippet title()}
Update Lightning Address
{/snippet}
{#snippet info()}
Update your lightning address for receiving payments.
{/snippet}
</ModalHeader>
<Modal>
<ModalBody>
<ModalHeader>
{#snippet title()}
Update Lightning Address
{/snippet}
{#snippet info()}
Update your lightning address for receiving payments.
{/snippet}
</ModalHeader>
<div class="column gap-4">
<div class="column gap-2">
<span> Lightning Address </span>
<input
type="text"
placeholder="user@domain.com"
bind:value={address}
class="input input-bordered flex w-full"
disabled={isLoading} />
<p class="text-xs opacity-75">
You can enter one manually or use your connected wallet's address (if available). Leave
empty to remove your lightning address
</p>
</div>
{#if walletLud16 && walletLud16 !== address}
<div class="card bg-base-200 p-4">
<div class="flex items-center justify-between gap-3">
<div class="column gap-1">
<div class="flex items-center gap-2">
<Icon icon={Wallet} size={4} />
<span class="text-sm font-medium">Wallet Address</span>
</div>
<p class="text-xs opacity-75">{walletLud16}</p>
</div>
<Button class="btn btn-outline btn-sm" onclick={useWalletAddress} disabled={isLoading}>
Use This
</Button>
</div>
<div class="column gap-4">
<div class="column gap-2">
<span> Lightning Address </span>
<input
type="text"
placeholder="user@domain.com"
bind:value={address}
class="input input-bordered flex w-full"
disabled={isLoading} />
<p class="text-xs opacity-75">
You can enter one manually or use your connected wallet's address (if available). Leave
empty to remove your lightning address
</p>
</div>
{/if}
</div>
{#if walletLud16 && walletLud16 !== address}
<div class="card bg-base-200 p-4">
<div class="flex items-center justify-between gap-3">
<div class="column gap-1">
<div class="flex items-center gap-2">
<Icon icon={Wallet} size={4} />
<span class="text-sm font-medium">Wallet Address</span>
</div>
<p class="text-xs opacity-75">{walletLud16}</p>
</div>
<Button class="btn btn-outline btn-sm" onclick={useWalletAddress} disabled={isLoading}>
Use This
</Button>
</div>
</div>
{/if}
</div>
</ModalBody>
<ModalFooter>
<Button class="btn btn-neutral" onclick={back} disabled={isLoading}>Cancel</Button>
<Button class="btn btn-primary" onclick={save} disabled={isLoading}>
@@ -96,4 +99,4 @@
Save Changes
</Button>
</ModalFooter>
</div>
</Modal>
+45 -41
View File
@@ -10,6 +10,8 @@
import Spinner from "@lib/components/Spinner.svelte"
import Button from "@lib/components/Button.svelte"
import FieldInline from "@lib/components/FieldInline.svelte"
import Modal from "@lib/components/Modal.svelte"
import ModalBody from "@lib/components/ModalBody.svelte"
import ModalHeader from "@lib/components/ModalHeader.svelte"
import ModalFooter from "@lib/components/ModalFooter.svelte"
import EmojiButton from "@lib/components/EmojiButton.svelte"
@@ -109,46 +111,48 @@
})
</script>
<div class="column gap-4">
<ModalHeader>
{#snippet title()}
<div>Send a Zap</div>
{/snippet}
{#snippet info()}
<div>To <ProfileLink {pubkey} class="!text-primary" /></div>
{/snippet}
</ModalHeader>
<FieldInline class="!grid-cols-3">
{#snippet label()}
Emoji Reaction
{/snippet}
{#snippet input()}
<div class="flex flex-grow items-center justify-end gap-4">
<EmojiButton {onEmoji} class="btn btn-neutral">
{content}
</EmojiButton>
</div>
{/snippet}
</FieldInline>
<FieldInline class="!grid-cols-3">
{#snippet label()}
Amount
{/snippet}
{#snippet input()}
<div class="flex flex-grow justify-end">
<label class="input input-bordered flex items-center gap-2">
<Icon icon={Bolt} />
<input bind:value={amount} type="number" class="w-24" />
</label>
</div>
{/snippet}
</FieldInline>
<input
class="range range-primary -mt-2"
type="range"
min={minPos}
max={maxPos}
bind:value={pos} />
<Modal>
<ModalBody>
<ModalHeader>
{#snippet title()}
<div>Send a Zap</div>
{/snippet}
{#snippet info()}
<div>To <ProfileLink {pubkey} class="!text-primary" /></div>
{/snippet}
</ModalHeader>
<FieldInline class="!grid-cols-3">
{#snippet label()}
Emoji Reaction
{/snippet}
{#snippet input()}
<div class="flex flex-grow items-center justify-end gap-4">
<EmojiButton {onEmoji} class="btn btn-neutral">
{content}
</EmojiButton>
</div>
{/snippet}
</FieldInline>
<FieldInline class="!grid-cols-3">
{#snippet label()}
Amount
{/snippet}
{#snippet input()}
<div class="flex flex-grow justify-end">
<label class="input input-bordered flex items-center gap-2">
<Icon icon={Bolt} />
<input bind:value={amount} type="number" class="w-24" />
</label>
</div>
{/snippet}
</FieldInline>
<input
class="range range-primary -mt-2"
type="range"
min={minPos}
max={maxPos}
bind:value={pos} />
</ModalBody>
<ModalFooter>
<Button class="btn btn-link" onclick={back}>
<Icon icon={AltArrowLeft} />
@@ -165,4 +169,4 @@
</Spinner>
</Button>
</ModalFooter>
</div>
</Modal>
+4 -4
View File
@@ -389,11 +389,11 @@ export const toggleRoomNotifications = async (url: string, h: string) => {
} else {
// Toggle exception status
const hasException = existing.exceptions.includes(h)
const exceptions = hasException ? remove(h, existing.exceptions) : append(h, existing.exceptions)
const exceptions = hasException
? remove(h, existing.exceptions)
: append(h, existing.exceptions)
updated = alerts.map((s: SpaceNotificationSettings) =>
s.url === url ? {...s, exceptions} : s,
)
updated = alerts.map((s: SpaceNotificationSettings) => (s.url === url ? {...s, exceptions} : s))
}
return publishSettings({alerts: updated})
+2 -6
View File
@@ -533,9 +533,7 @@ class CapacitorNotifications implements IPushAdapter {
} else {
// notify=false: exceptions are opt-in (only include those rooms)
if (exceptions.length > 0) {
filters.push(
...baseFilters.map(f => ({...f, "#h": exceptions})),
)
filters.push(...baseFilters.map(f => ({...f, "#h": exceptions})))
}
}
}
@@ -625,9 +623,7 @@ class CapacitorNotifications implements IPushAdapter {
await Promise.all(get(userSpaceUrls).map(url => this._unsyncRelay(url, "spaces")))
await Promise.all(
getRelaysFromList(get(userMessagingRelayList)).map(url =>
this._unsyncRelay(url, "messages"),
),
getRelaysFromList(get(userMessagingRelayList)).map(url => this._unsyncRelay(url, "messages")),
)
}
}
+15 -11
View File
@@ -7,6 +7,8 @@
import Spinner from "@lib/components/Spinner.svelte"
import ModalHeader from "@lib/components/ModalHeader.svelte"
import ModalFooter from "@lib/components/ModalFooter.svelte"
import Modal from "@lib/components/Modal.svelte"
import ModalBody from "@lib/components/ModalBody.svelte"
interface Props {
title?: string
@@ -32,16 +34,18 @@
const back = () => history.back()
</script>
<form class="column gap-4" onsubmit={preventDefault(tryConfirm)}>
<ModalHeader>
{#snippet title()}
<div>{restProps.title || "Are you sure?"}</div>
{/snippet}
{#snippet info()}
<div>{subtitle}</div>
{/snippet}
</ModalHeader>
<p class="text-center">{message}</p>
<Modal tag="form" onsubmit={preventDefault(tryConfirm)}>
<ModalBody>
<ModalHeader>
{#snippet title()}
<div>{restProps.title || "Are you sure?"}</div>
{/snippet}
{#snippet info()}
<div>{subtitle}</div>
{/snippet}
</ModalHeader>
<p class="text-center">{message}</p>
</ModalBody>
<ModalFooter>
<Button class="btn btn-link" onclick={back}>
<Icon icon={AltArrowLeft} />
@@ -52,4 +56,4 @@
<Icon icon={AltArrowRight} />
</Button>
</ModalFooter>
</form>
</Modal>
+10 -5
View File
@@ -1,12 +1,17 @@
<script lang="ts">
import type {Component} from "svelte"
import cx from "classnames"
import {onMount} from "svelte"
import {noop} from "@welshman/lib"
import {fade, fly} from "@lib/transition"
interface Props {
type Props = {
onClose?: any
fullscreen?: boolean
children?: import("svelte").Snippet
children: {
component: Component<any>
props: Record<string, any>
}
}
const {onClose = noop, fullscreen = false, children}: Props = $props()
@@ -21,9 +26,9 @@
const innerClass = $derived(
cx(
"relative text-base-content text-base-content flex-grow pointer-events-auto",
"px-4 py-6 rounded-t-box sm:p-6 sm:rounded-box sm:mt-0",
"rounded-t-box sm:rounded-box",
{
"bg-alt shadow-m max-h-[90vh] scroll-container overflow-auto": !fullscreen,
"bg-alt shadow-m max-h-[90vh] flex flex-col": !fullscreen,
},
),
)
@@ -39,7 +44,7 @@
</button>
<div class={wrapperClass}>
<div class={innerClass} transition:fly={{duration: 300}}>
{@render children?.()}
<children.component {...children.props} />
</div>
</div>
</div>
+11 -2
View File
@@ -1,7 +1,16 @@
<script lang="ts">
import type {Component} from "svelte"
import {fade, translate} from "@lib/transition"
const {onClose, children} = $props()
type Props = {
onClose?: any
children: {
component: Component
props: Record<string, any>
}
}
const {onClose, children}: Props = $props()
</script>
<div class="drawer fixed inset-0 z-modal">
@@ -14,6 +23,6 @@
<div
class="scroll-container py-sai pr-sair absolute bottom-0 right-0 top-0 w-72 overflow-auto bg-base-200 text-base-content lg:w-96"
transition:translate={{axis: "x", duration: 300}}>
{@render children?.()}
<children.component {...children.props} />
</div>
</div>
+8 -3
View File
@@ -58,7 +58,12 @@
</Tippy>
{#if showIconPicker}
<Dialog onClose={close}>
<IconPicker onSelect={onClick} />
</Dialog>
<Dialog
onClose={close}
children={{
component: IconPicker,
props: {
onSelect: onClick,
},
}} />
{/if}
+17
View File
@@ -0,0 +1,17 @@
<script lang="ts">
import cx from "classnames"
import type {Snippet} from "svelte"
interface Props {
tag?: string
class?: string
children?: Snippet
[key: string]: any
}
const {children, tag = "div", ...props}: Props = $props()
</script>
<svelte:element this={tag} {...props} class={cx("flex flex-col overflow-hidden pb-6", props.class)}>
{@render children?.()}
</svelte:element>
+16
View File
@@ -0,0 +1,16 @@
<script lang="ts">
import cx from "classnames"
import type {Snippet} from "svelte"
interface Props {
class?: string
children?: Snippet
}
const {children, ...props}: Props = $props()
</script>
<div
class={cx("scroll-container overflow-y-auto min-h-0 flex flex-col gap-4 p-6 pb-0", props.class)}>
{@render children?.()}
</div>
+5 -2
View File
@@ -8,6 +8,9 @@
const {children}: Props = $props()
</script>
<div class="row-4 mt-4 items-center justify-between">
{@render children?.()}
<div class="h-20 flex-shrink-0"></div>
<div class="flex absolute bottom-0 left-0 right-0 p-6 pt-2 rounded-b-box bg-base-100">
<div class="flex flex-grow gap-4 items-center justify-between">
{@render children?.()}
</div>
</div>
+1 -1
View File
@@ -7,7 +7,7 @@
const {title, info}: Props = $props()
</script>
<div class="column m-auto max-w-xs gap-2 py-4">
<div class="flex flex-col m-auto max-w-xs gap-2 py-4">
<h1 class="heading">{@render title?.()}</h1>
<p class="text-center text-sm opacity-75">{@render info?.()}</p>
</div>
+9 -13
View File
@@ -1,20 +1,16 @@
<script lang="ts">
import {page} from "$app/stores"
import {goto} from "$app/navigation"
import AltArrowLeft from "@assets/icons/alt-arrow-left.svg?dataurl"
import Icon from "@lib/components/Icon.svelte"
import Dialog from "@lib/components/Dialog.svelte"
import Button from "@lib/components/Button.svelte"
import SpaceInviteAccept from "@app/components/SpaceInviteAccept.svelte"
const children = {
component: SpaceInviteAccept,
props: {
invite: $page.url.href,
back: () => goto("/home"),
},
}
</script>
<Dialog>
<SpaceInviteAccept invite={$page.url.href}>
{#snippet abortAction()}
<Button class="btn btn-link" onclick={() => goto("/home")}>
<Icon icon={AltArrowLeft} />
Go back
</Button>
{/snippet}
</SpaceInviteAccept>
</Dialog>
<Dialog {children} />
+1 -5
View File
@@ -47,11 +47,7 @@
userSettingsValues,
} from "@app/core/state"
import {setChecked, checked} from "@app/util/notifications"
import {
canEnforceNip70,
prependParent,
publishDelete,
} from "@app/core/commands"
import {canEnforceNip70, prependParent, publishDelete} from "@app/core/commands"
import {makeFeed} from "@app/core/requests"
import {popKey} from "@lib/implicit"
import {pushToast} from "@app/util/toast"