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