Add profile info dialog
This commit is contained in:
parent
531717119e
commit
ac2b8ad13f
4 changed files with 224 additions and 87 deletions
|
|
@ -4,21 +4,19 @@
|
|||
import {displayProfileByPubkey, loadMessagingRelayList} from "@welshman/app"
|
||||
import AltArrowLeft from "@assets/icons/alt-arrow-left.svg?dataurl"
|
||||
import UserCircle from "@assets/icons/user-circle.svg?dataurl"
|
||||
import MenuDots from "@assets/icons/menu-dots.svg?dataurl"
|
||||
import MinusCircle from "@assets/icons/minus-circle.svg?dataurl"
|
||||
import UserMinus from "@assets/icons/user-minus.svg?dataurl"
|
||||
import Restart from "@assets/icons/restart.svg?dataurl"
|
||||
import {fly} from "@lib/transition"
|
||||
import Icon from "@lib/components/Icon.svelte"
|
||||
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 ProfileAbout from "@app/components/ProfileAbout.svelte"
|
||||
import ProfileBadges from "@app/components/ProfileBadges.svelte"
|
||||
import ProfileMenu from "@app/components/ProfileMenu.svelte"
|
||||
import {
|
||||
deriveUserIsSpaceAdmin,
|
||||
deriveSpaceBannedPubkeyItems,
|
||||
|
|
@ -47,14 +45,6 @@
|
|||
|
||||
const viewProfile = () => goto(makeProfilePath(pubkey), {replaceState: true})
|
||||
|
||||
const toggleMenu = () => {
|
||||
showMenu = !showMenu
|
||||
}
|
||||
|
||||
const closeMenu = () => {
|
||||
showMenu = false
|
||||
}
|
||||
|
||||
const banMember = () =>
|
||||
pushModal(Confirm, {
|
||||
title: "Ban User",
|
||||
|
|
@ -93,8 +83,6 @@
|
|||
}
|
||||
}
|
||||
|
||||
let showMenu = $state(false)
|
||||
|
||||
onMount(() => {
|
||||
loadMessagingRelayList(pubkey)
|
||||
})
|
||||
|
|
@ -105,42 +93,33 @@
|
|||
<div class="flex flex-col gap-4">
|
||||
<div class="flex justify-between">
|
||||
<Profile showPubkey avatarSize={14} {pubkey} {url} />
|
||||
{#if $userIsAdmin}
|
||||
<div class="relative">
|
||||
<Button class="button button-circle button-ghost button-sm" onclick={toggleMenu}>
|
||||
<Icon icon={MenuDots} />
|
||||
</Button>
|
||||
{#if showMenu}
|
||||
<Popover hideOnClick onClose={closeMenu}>
|
||||
<ul
|
||||
transition:fly
|
||||
class="menu bg-surface absolute right-0 z-popover w-48 gap-1 rounded-2xl p-2">
|
||||
{#if isBanned}
|
||||
<li>
|
||||
<Button onclick={restoreMember}>
|
||||
<Icon icon={Restart} />
|
||||
Restore Membership
|
||||
</Button>
|
||||
</li>
|
||||
{:else}
|
||||
<li>
|
||||
<Button onclick={removeMember}>
|
||||
<Icon icon={UserMinus} />
|
||||
Remove Member
|
||||
</Button>
|
||||
</li>
|
||||
<li>
|
||||
<Button class="text-error" onclick={banMember}>
|
||||
<Icon icon={MinusCircle} />
|
||||
Ban User
|
||||
</Button>
|
||||
</li>
|
||||
{/if}
|
||||
</ul>
|
||||
</Popover>
|
||||
<ProfileMenu {pubkey} {url}>
|
||||
{#snippet customActions()}
|
||||
{#if $userIsAdmin}
|
||||
{#if isBanned}
|
||||
<li>
|
||||
<Button onclick={restoreMember}>
|
||||
<Icon icon={Restart} />
|
||||
Restore Membership
|
||||
</Button>
|
||||
</li>
|
||||
{:else}
|
||||
<li>
|
||||
<Button onclick={removeMember}>
|
||||
<Icon icon={UserMinus} />
|
||||
Remove Member
|
||||
</Button>
|
||||
</li>
|
||||
<li>
|
||||
<Button class="text-error" onclick={banMember}>
|
||||
<Icon icon={MinusCircle} />
|
||||
Ban User
|
||||
</Button>
|
||||
</li>
|
||||
{/if}
|
||||
{/if}
|
||||
</div>
|
||||
{/if}
|
||||
{/snippet}
|
||||
</ProfileMenu>
|
||||
</div>
|
||||
<ProfileAbout {pubkey} {url} />
|
||||
<ProfileBadges {pubkey} {url} />
|
||||
|
|
|
|||
121
src/app/components/ProfileInfo.svelte
Normal file
121
src/app/components/ProfileInfo.svelte
Normal file
|
|
@ -0,0 +1,121 @@
|
|||
<script lang="ts">
|
||||
import * as nip19 from "nostr-tools/nip19"
|
||||
import {Router} from "@welshman/router"
|
||||
import {LOCALE, secondsToDate} from "@welshman/lib"
|
||||
import type {TrustedEvent} from "@welshman/util"
|
||||
import {displayRelayUrl, toNostrURI} from "@welshman/util"
|
||||
import {tracker} from "@welshman/app"
|
||||
import LinkRound from "@assets/icons/link-round.svg?dataurl"
|
||||
import Copy from "@assets/icons/copy.svg?dataurl"
|
||||
import UserId from "@assets/icons/user-id.svg?dataurl"
|
||||
import Icon from "@lib/components/Icon.svelte"
|
||||
import FieldInline from "@lib/components/FieldInline.svelte"
|
||||
import Button from "@lib/components/Button.svelte"
|
||||
import Badge from "@lib/components/Badge.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 ModalTitle from "@lib/components/ModalTitle.svelte"
|
||||
import ModalSubtitle from "@lib/components/ModalSubtitle.svelte"
|
||||
import {clip} from "@app/toast"
|
||||
|
||||
type Props = {
|
||||
url?: string
|
||||
event: TrustedEvent
|
||||
}
|
||||
|
||||
const {url, event}: Props = $props()
|
||||
|
||||
const relays = url ? [url] : Router.get().FromPubkeys([event.pubkey]).getUrls()
|
||||
const nprofile1 = toNostrURI(nip19.nprofileEncode({pubkey: event.pubkey, relays}))
|
||||
const npub1 = nip19.npubEncode(event.pubkey)
|
||||
const seenOn = tracker.getRelays(event.id)
|
||||
const json = (() => {
|
||||
try {
|
||||
return JSON.stringify(JSON.parse(event.content), null, 2)
|
||||
} catch {
|
||||
return event.content
|
||||
}
|
||||
})()
|
||||
const copyLink = () => clip(nprofile1)
|
||||
const copyPubkey = () => clip(npub1)
|
||||
const copyJson = () => clip(json)
|
||||
|
||||
const formatter = new Intl.DateTimeFormat(LOCALE, {
|
||||
dateStyle: "long",
|
||||
timeStyle: "long",
|
||||
})
|
||||
</script>
|
||||
|
||||
<Modal>
|
||||
<ModalBody>
|
||||
<ModalHeader>
|
||||
<ModalTitle>Profile Details</ModalTitle>
|
||||
<ModalSubtitle>The full details of this profile are shown below.</ModalSubtitle>
|
||||
</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>Profile Link</p>
|
||||
{/snippet}
|
||||
{#snippet input()}
|
||||
<label class="input flex w-full items-center gap-2">
|
||||
<Icon icon={LinkRound} />
|
||||
<input type="text" class="truncate min-w-0 grow" value={nprofile1} />
|
||||
<Button onclick={copyLink} class="flex items-center">
|
||||
<Icon icon={Copy} />
|
||||
</Button>
|
||||
</label>
|
||||
{/snippet}
|
||||
</FieldInline>
|
||||
<FieldInline>
|
||||
{#snippet label()}
|
||||
<p>Public Key</p>
|
||||
{/snippet}
|
||||
{#snippet input()}
|
||||
<label class="input flex w-full items-center gap-2">
|
||||
<Icon icon={UserId} />
|
||||
<input type="text" class="truncate 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)}
|
||||
<Badge class="bg-surface flex gap-1">
|
||||
{displayRelayUrl(url)}
|
||||
</Badge>
|
||||
{/each}
|
||||
</div>
|
||||
{/snippet}
|
||||
</FieldInline>
|
||||
{/if}
|
||||
<div class="relative">
|
||||
<pre class="card card-sm overflow-auto text-xs"><code>{json}</code></pre>
|
||||
<p class="absolute right-2 top-2 flex grow items-center justify-between">
|
||||
<Button onclick={copyJson} class="button button-neutral button-sm flex items-center">
|
||||
<Icon icon={Copy} /> Copy
|
||||
</Button>
|
||||
</p>
|
||||
</div>
|
||||
</ModalBody>
|
||||
<ModalFooter>
|
||||
<Button class="button button-primary grow" onclick={() => history.back()}>Got it</Button>
|
||||
</ModalFooter>
|
||||
</Modal>
|
||||
73
src/app/components/ProfileMenu.svelte
Normal file
73
src/app/components/ProfileMenu.svelte
Normal file
|
|
@ -0,0 +1,73 @@
|
|||
<script lang="ts">
|
||||
import type {Snippet} from "svelte"
|
||||
import {deriveProfile} from "@welshman/app"
|
||||
import MenuDots from "@assets/icons/menu-dots.svg?dataurl"
|
||||
import Code2 from "@assets/icons/code-2.svg?dataurl"
|
||||
import ShareCircle from "@assets/icons/share-circle.svg?dataurl"
|
||||
import {fly} from "@lib/transition"
|
||||
import Icon from "@lib/components/Icon.svelte"
|
||||
import Button from "@lib/components/Button.svelte"
|
||||
import Popover from "@lib/components/Popover.svelte"
|
||||
import ProfileInfo from "@app/components/ProfileInfo.svelte"
|
||||
import ProfileQrCode from "@app/components/ProfileQrCode.svelte"
|
||||
import {pushModal} from "@app/modal"
|
||||
|
||||
type Props = {
|
||||
pubkey: string
|
||||
url?: string
|
||||
customActions?: Snippet
|
||||
}
|
||||
|
||||
const {pubkey, url, customActions}: Props = $props()
|
||||
|
||||
const profile = deriveProfile(pubkey)
|
||||
|
||||
let showMenu = $state(false)
|
||||
|
||||
const toggleMenu = () => {
|
||||
showMenu = !showMenu
|
||||
}
|
||||
|
||||
const closeMenu = () => {
|
||||
showMenu = false
|
||||
}
|
||||
|
||||
const showInfo = () => {
|
||||
closeMenu()
|
||||
pushModal(ProfileInfo, {event: $profile!.event, url})
|
||||
}
|
||||
|
||||
const showShare = () => {
|
||||
closeMenu()
|
||||
pushModal(ProfileQrCode, {pubkey})
|
||||
}
|
||||
</script>
|
||||
|
||||
<div class="relative">
|
||||
<Button class="button button-circle button-ghost button-sm" onclick={toggleMenu}>
|
||||
<Icon icon={MenuDots} />
|
||||
</Button>
|
||||
{#if showMenu}
|
||||
<Popover hideOnClick onClose={closeMenu}>
|
||||
<ul
|
||||
transition:fly
|
||||
class="menu bg-surface absolute right-0 z-popover w-48 gap-1 rounded-2xl p-2">
|
||||
<li>
|
||||
<Button onclick={showShare}>
|
||||
<Icon icon={ShareCircle} />
|
||||
Share
|
||||
</Button>
|
||||
</li>
|
||||
{#if $profile}
|
||||
<li>
|
||||
<Button onclick={showInfo}>
|
||||
<Icon icon={Code2} />
|
||||
Profile Info
|
||||
</Button>
|
||||
</li>
|
||||
{/if}
|
||||
{@render customActions?.()}
|
||||
</ul>
|
||||
</Popover>
|
||||
{/if}
|
||||
</div>
|
||||
|
|
@ -18,21 +18,17 @@
|
|||
import Letter from "@assets/icons/letter-opened.svg?dataurl"
|
||||
import UserPlus from "@assets/icons/user-plus.svg?dataurl"
|
||||
import PenNewSquare from "@assets/icons/pen-new-square.svg?dataurl"
|
||||
import MenuDots from "@assets/icons/menu-dots.svg?dataurl"
|
||||
import ShareCircle from "@assets/icons/share-circle.svg?dataurl"
|
||||
import GallerySend from "@assets/icons/gallery-send.svg?dataurl"
|
||||
import {fly} from "@lib/transition"
|
||||
import Icon from "@lib/components/Icon.svelte"
|
||||
import Link from "@lib/components/Link.svelte"
|
||||
import Button from "@lib/components/Button.svelte"
|
||||
import Spinner from "@lib/components/Spinner.svelte"
|
||||
import Popover from "@lib/components/Popover.svelte"
|
||||
import ProfileCircle from "@app/components/ProfileCircle.svelte"
|
||||
import ProfileTrust from "@app/components/ProfileTrust.svelte"
|
||||
import ProfileSharedSpaces from "@app/components/ProfileSharedSpaces.svelte"
|
||||
import ProfilePageNotes from "@app/components/ProfilePageNotes.svelte"
|
||||
import ProfileQrCode from "@app/components/ProfileQrCode.svelte"
|
||||
import ProfileEdit from "@app/components/ProfileEdit.svelte"
|
||||
import ProfileMenu from "@app/components/ProfileMenu.svelte"
|
||||
import WotScore from "@app/components/WotScore.svelte"
|
||||
import Content from "@app/components/Content.svelte"
|
||||
import {updateProfile} from "@app/profiles"
|
||||
|
|
@ -62,27 +58,13 @@
|
|||
: `https://${$profile?.website || ""}`,
|
||||
)
|
||||
|
||||
let showMenu = $state(false)
|
||||
let bannerLoading = $state(false)
|
||||
let bannerInput: HTMLInputElement | undefined = $state()
|
||||
|
||||
const copyNpub = () => clip(nip19.npubEncode(target))
|
||||
|
||||
const showShare = () => {
|
||||
closeMenu()
|
||||
pushModal(ProfileQrCode, {pubkey: target})
|
||||
}
|
||||
|
||||
const startEdit = () => pushModal(ProfileEdit)
|
||||
|
||||
const toggleMenu = () => {
|
||||
showMenu = !showMenu
|
||||
}
|
||||
|
||||
const closeMenu = () => {
|
||||
showMenu = false
|
||||
}
|
||||
|
||||
const openChat = () => goToChat([target])
|
||||
|
||||
const toggleFollow = async () => {
|
||||
|
|
@ -193,26 +175,8 @@
|
|||
</Button>
|
||||
{/if}
|
||||
|
||||
<div class="relative shrink-0">
|
||||
<Button
|
||||
class="button button-circle button-ghost button-sm"
|
||||
onclick={toggleMenu}>
|
||||
<Icon icon={MenuDots} />
|
||||
</Button>
|
||||
{#if showMenu}
|
||||
<Popover hideOnClick onClose={closeMenu}>
|
||||
<ul
|
||||
transition:fly
|
||||
class="menu bg-surface absolute right-0 z-popover w-48 gap-1 rounded-2xl p-2">
|
||||
<li>
|
||||
<Button onclick={showShare}>
|
||||
<Icon icon={ShareCircle} />
|
||||
Share
|
||||
</Button>
|
||||
</li>
|
||||
</ul>
|
||||
</Popover>
|
||||
{/if}
|
||||
<div class="shrink-0">
|
||||
<ProfileMenu pubkey={target} />
|
||||
</div>
|
||||
</div>
|
||||
{/if}
|
||||
|
|
|
|||
Loading…
Reference in a new issue