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