2024-10-16 17:14:33 +00:00
|
|
|
<script lang="ts">
|
2024-10-17 23:03:50 +00:00
|
|
|
import {onMount} from "svelte"
|
2026-04-14 13:57:23 +00:00
|
|
|
import {Share} from "@capacitor/share"
|
2026-07-28 16:16:26 +00:00
|
|
|
import {displayRelayUrl} from "@welshman/util"
|
2025-09-22 21:57:43 +00:00
|
|
|
import LinkRound from "@assets/icons/link-round.svg?dataurl"
|
2026-04-14 13:57:23 +00:00
|
|
|
import Upload from "@assets/icons/upload.svg?dataurl"
|
2025-09-22 21:57:43 +00:00
|
|
|
import Copy from "@assets/icons/copy.svg?dataurl"
|
2026-06-22 20:36:57 +00:00
|
|
|
import AltArrowLeft from "@assets/icons/alt-arrow-left.svg?dataurl"
|
2024-10-16 17:14:33 +00:00
|
|
|
import Spinner from "@lib/components/Spinner.svelte"
|
|
|
|
|
import Field from "@lib/components/Field.svelte"
|
|
|
|
|
import Button from "@lib/components/Button.svelte"
|
|
|
|
|
import Icon from "@lib/components/Icon.svelte"
|
2026-06-22 20:36:57 +00:00
|
|
|
import Divider from "@lib/components/Divider.svelte"
|
2026-01-30 22:14:12 +00:00
|
|
|
import Modal from "@lib/components/Modal.svelte"
|
|
|
|
|
import ModalBody from "@lib/components/ModalBody.svelte"
|
2024-10-16 17:14:33 +00:00
|
|
|
import ModalHeader from "@lib/components/ModalHeader.svelte"
|
2026-02-02 18:09:14 +00:00
|
|
|
import ModalTitle from "@lib/components/ModalTitle.svelte"
|
|
|
|
|
import ModalSubtitle from "@lib/components/ModalSubtitle.svelte"
|
2024-10-16 17:14:33 +00:00
|
|
|
import ModalFooter from "@lib/components/ModalFooter.svelte"
|
2026-06-22 20:36:57 +00:00
|
|
|
import ProfileMultiSelect from "@app/components/ProfileMultiSelect.svelte"
|
2026-07-28 16:16:26 +00:00
|
|
|
import {deriveSpaceSupportedMethods} from "@app/management"
|
2025-09-22 21:57:43 +00:00
|
|
|
import QRCode from "@app/components/QRCode.svelte"
|
2026-07-20 23:35:24 +00:00
|
|
|
import {Access, makeInviteLink} from "@app/access"
|
2026-07-28 16:16:26 +00:00
|
|
|
import {relayManagement, relayMemberLists} from "@app/core"
|
|
|
|
|
import {clip, pushToast} from "@app/toast"
|
|
|
|
|
|
|
|
|
|
type Props = {
|
|
|
|
|
url: string
|
|
|
|
|
}
|
2024-10-16 17:14:33 +00:00
|
|
|
|
2026-07-28 16:16:26 +00:00
|
|
|
const {url}: Props = $props()
|
2024-10-16 17:14:33 +00:00
|
|
|
|
2026-07-20 23:35:24 +00:00
|
|
|
const access = new Access(url)
|
|
|
|
|
const {claim, loading} = access
|
|
|
|
|
const inviteStatus = access.createInviteStatus(false)
|
2025-11-05 00:14:32 +00:00
|
|
|
|
2024-10-16 17:14:33 +00:00
|
|
|
const back = () => history.back()
|
|
|
|
|
const copyInvite = () => clip(invite)
|
|
|
|
|
|
2026-04-14 13:57:23 +00:00
|
|
|
const shareInvite = async () => {
|
|
|
|
|
if (!canShare) return
|
|
|
|
|
|
|
|
|
|
try {
|
|
|
|
|
await Share.share({url: invite})
|
|
|
|
|
} catch (e) {
|
|
|
|
|
console.error(e)
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
2026-06-22 20:36:57 +00:00
|
|
|
const addMembers = async () => {
|
|
|
|
|
adding = true
|
|
|
|
|
|
|
|
|
|
try {
|
2026-07-28 16:16:26 +00:00
|
|
|
const members = $relayMemberLists.get(url)
|
|
|
|
|
const management = $relayManagement.forUrl(url)
|
|
|
|
|
|
|
|
|
|
const responses = await Promise.all(
|
|
|
|
|
pubkeys.filter(pk => !members?.isMember(pk)).map(pk => management.allowPubkey(pk)),
|
|
|
|
|
)
|
|
|
|
|
|
|
|
|
|
const error = responses.map(response => response.error).find(Boolean)
|
2026-06-22 20:36:57 +00:00
|
|
|
|
|
|
|
|
if (error) {
|
|
|
|
|
pushToast({theme: "error", message: error})
|
|
|
|
|
} else {
|
|
|
|
|
pushToast({message: "Members have successfully been added!"})
|
|
|
|
|
back()
|
|
|
|
|
}
|
|
|
|
|
} finally {
|
|
|
|
|
adding = false
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
2026-07-28 16:16:26 +00:00
|
|
|
const supportedMethods = deriveSpaceSupportedMethods(url)
|
|
|
|
|
|
|
|
|
|
const canAddMembers = $derived($supportedMethods.includes("allowpubkey"))
|
|
|
|
|
|
|
|
|
|
let canShare = $state(false)
|
|
|
|
|
let invite = $state("")
|
|
|
|
|
|
|
|
|
|
let adding = $state(false)
|
|
|
|
|
let pubkeys: string[] = $state([])
|
|
|
|
|
|
2025-02-04 00:37:14 +00:00
|
|
|
$effect(() => {
|
2026-07-20 23:35:24 +00:00
|
|
|
invite = makeInviteLink({url, claim: $claim})
|
2025-02-04 00:37:14 +00:00
|
|
|
})
|
2024-10-16 17:14:33 +00:00
|
|
|
|
|
|
|
|
onMount(async () => {
|
2026-04-14 13:57:23 +00:00
|
|
|
try {
|
|
|
|
|
const {value} = await Share.canShare()
|
|
|
|
|
canShare = value
|
|
|
|
|
} catch {
|
|
|
|
|
canShare = false
|
|
|
|
|
}
|
|
|
|
|
|
2026-07-20 23:35:24 +00:00
|
|
|
await access.prepareInvite()
|
2024-10-16 17:14:33 +00:00
|
|
|
})
|
|
|
|
|
</script>
|
|
|
|
|
|
2026-01-30 22:14:12 +00:00
|
|
|
<Modal>
|
|
|
|
|
<ModalBody>
|
|
|
|
|
<ModalHeader>
|
2026-02-02 18:09:14 +00:00
|
|
|
<ModalTitle>Create an Invite</ModalTitle>
|
|
|
|
|
<ModalSubtitle>
|
|
|
|
|
Get a link that you can use to invite people to
|
|
|
|
|
<span class="text-primary">{displayRelayUrl(url)}</span>
|
|
|
|
|
</ModalSubtitle>
|
2026-01-30 22:14:12 +00:00
|
|
|
</ModalHeader>
|
|
|
|
|
<div>
|
2026-07-20 23:35:24 +00:00
|
|
|
{#if $inviteStatus === "loading"}
|
2026-06-30 17:49:17 +00:00
|
|
|
<p class="flex justify-center items-center">
|
2026-07-20 23:35:24 +00:00
|
|
|
<Spinner loading={$loading}>Requesting an invite link...</Spinner>
|
2026-01-30 22:14:12 +00:00
|
|
|
</p>
|
2026-07-20 23:35:24 +00:00
|
|
|
{:else if $inviteStatus === "network"}
|
2026-06-30 17:49:17 +00:00
|
|
|
<p class="flex justify-center items-center">
|
|
|
|
|
Unable to reach the space. Please check your connection and try again.
|
2026-04-17 18:24:55 +00:00
|
|
|
</p>
|
2026-07-20 23:35:24 +00:00
|
|
|
{:else if $inviteStatus === "auth"}
|
2026-06-30 17:49:17 +00:00
|
|
|
<p class="flex justify-center items-center">
|
|
|
|
|
Oops! It looks like you're not a member of this space.
|
2026-06-24 20:56:53 +00:00
|
|
|
</p>
|
2026-01-30 22:14:12 +00:00
|
|
|
{:else}
|
|
|
|
|
<div class="flex flex-col items-center gap-6">
|
2026-04-14 13:57:23 +00:00
|
|
|
<div class="w-48">
|
|
|
|
|
<QRCode code={invite} />
|
|
|
|
|
</div>
|
2026-01-30 22:14:12 +00:00
|
|
|
<Field>
|
|
|
|
|
{#snippet input()}
|
2026-04-14 13:57:23 +00:00
|
|
|
<div class="flex w-full gap-2">
|
|
|
|
|
{#if canShare}
|
|
|
|
|
<Button
|
2026-06-24 20:56:53 +00:00
|
|
|
class="input flex w-12 shrink-0 items-center justify-center p-0"
|
2026-04-14 13:57:23 +00:00
|
|
|
onclick={shareInvite}>
|
|
|
|
|
<Icon icon={Upload} />
|
|
|
|
|
</Button>
|
|
|
|
|
{/if}
|
|
|
|
|
|
2026-06-24 20:56:53 +00:00
|
|
|
<label class="input flex min-w-0 flex-1 items-center gap-2">
|
2026-04-14 13:57:23 +00:00
|
|
|
<Icon icon={LinkRound} class="shrink-0" />
|
|
|
|
|
<input bind:value={invite} class="min-w-0 flex-1 truncate" type="text" readonly />
|
|
|
|
|
<Button class="shrink-0" onclick={copyInvite}>
|
|
|
|
|
<Icon icon={Copy} />
|
|
|
|
|
</Button>
|
|
|
|
|
</label>
|
|
|
|
|
</div>
|
2026-01-30 22:14:12 +00:00
|
|
|
{/snippet}
|
|
|
|
|
{#snippet info()}
|
|
|
|
|
<p>
|
|
|
|
|
This invite link can be used by clicking "Add Space" and pasting it there.
|
2026-07-27 22:09:13 +00:00
|
|
|
{#if $inviteStatus === "noclaim"}
|
|
|
|
|
We weren't able to get an invite code from this space, so additional steps might
|
|
|
|
|
be required.
|
|
|
|
|
{:else if !$claim}
|
2026-01-30 22:14:12 +00:00
|
|
|
This space did not issue a claim for this link, so additional steps might be
|
|
|
|
|
required.
|
|
|
|
|
{/if}
|
|
|
|
|
</p>
|
|
|
|
|
{/snippet}
|
|
|
|
|
</Field>
|
|
|
|
|
</div>
|
|
|
|
|
{/if}
|
|
|
|
|
</div>
|
2026-06-22 20:36:57 +00:00
|
|
|
{#if canAddMembers}
|
|
|
|
|
<Divider>or</Divider>
|
|
|
|
|
<Field>
|
|
|
|
|
{#snippet label()}
|
|
|
|
|
<p>Add members directly</p>
|
|
|
|
|
{/snippet}
|
|
|
|
|
{#snippet input()}
|
|
|
|
|
<ProfileMultiSelect bind:value={pubkeys} />
|
|
|
|
|
{/snippet}
|
|
|
|
|
</Field>
|
|
|
|
|
{/if}
|
2026-01-30 22:14:12 +00:00
|
|
|
</ModalBody>
|
2024-10-16 17:14:33 +00:00
|
|
|
<ModalFooter>
|
2026-06-22 20:36:57 +00:00
|
|
|
{#if canAddMembers}
|
2026-06-24 20:56:53 +00:00
|
|
|
<Button class="button button-link" onclick={back}>
|
2026-06-22 20:36:57 +00:00
|
|
|
<Icon icon={AltArrowLeft} />
|
|
|
|
|
Go back
|
|
|
|
|
</Button>
|
|
|
|
|
<Button
|
2026-06-24 20:56:53 +00:00
|
|
|
class="button button-primary"
|
2026-06-22 20:36:57 +00:00
|
|
|
onclick={addMembers}
|
|
|
|
|
disabled={adding || pubkeys.length === 0}>
|
|
|
|
|
<Spinner loading={adding}>Save</Spinner>
|
|
|
|
|
</Button>
|
|
|
|
|
{:else}
|
2026-06-24 20:56:53 +00:00
|
|
|
<Button class="button button-primary grow" onclick={back}>Done</Button>
|
2026-06-22 20:36:57 +00:00
|
|
|
{/if}
|
2024-10-16 17:14:33 +00:00
|
|
|
</ModalFooter>
|
2026-01-30 22:14:12 +00:00
|
|
|
</Modal>
|