|
|
|
|
@ -8,11 +8,8 @@
@@ -8,11 +8,8 @@
|
|
|
|
|
loginWithAmber, |
|
|
|
|
loginWithNpub |
|
|
|
|
} from "$lib/stores/userStore"; |
|
|
|
|
import { |
|
|
|
|
ArrowRightToBracketOutline, |
|
|
|
|
UserOutline, |
|
|
|
|
} from "flowbite-svelte-icons"; |
|
|
|
|
import { Avatar, Popover } from "flowbite-svelte"; |
|
|
|
|
import { Avatar, Popover, Dropdown, DropdownGroup, DropdownItem, DropdownHeader } from "flowbite-svelte"; |
|
|
|
|
import { Globe, Loader, Book, Smartphone } from "@lucide/svelte"; |
|
|
|
|
import { get } from "svelte/store"; |
|
|
|
|
import { goto } from "$app/navigation"; |
|
|
|
|
import NDK, { NDKNip46Signer, NDKPrivateKeySigner } from "@nostr-dev-kit/ndk"; |
|
|
|
|
@ -22,8 +19,6 @@
@@ -22,8 +19,6 @@
|
|
|
|
|
|
|
|
|
|
const ndk = getNdkContext(); |
|
|
|
|
|
|
|
|
|
let { isNav = false } = $props<{ isNav?: boolean }>(); |
|
|
|
|
|
|
|
|
|
// UI state for login functionality |
|
|
|
|
let isLoadingExtension: boolean = $state(false); |
|
|
|
|
let isLoadingAmber: boolean = $state(false); |
|
|
|
|
@ -31,7 +26,6 @@
@@ -31,7 +26,6 @@
|
|
|
|
|
let nostrConnectUri: string | undefined = $state(undefined); |
|
|
|
|
let showQrCode: boolean = $state(false); |
|
|
|
|
let qrCodeDataUrl: string | undefined = $state(undefined); |
|
|
|
|
let loginButtonRef: HTMLElement | undefined = $state(); |
|
|
|
|
let resultTimeout: ReturnType<typeof setTimeout> | null = null; |
|
|
|
|
let profileAvatarId = "profile-avatar-btn"; |
|
|
|
|
let showAmberFallback = $state(false); |
|
|
|
|
@ -390,57 +384,44 @@
@@ -390,57 +384,44 @@
|
|
|
|
|
<div class="relative h-fit my-auto"> |
|
|
|
|
{#if !userState.signedIn} |
|
|
|
|
<!-- Login button --> |
|
|
|
|
<div class="group"> |
|
|
|
|
<button |
|
|
|
|
bind:this={loginButtonRef} |
|
|
|
|
id="login-avatar" |
|
|
|
|
class="h-6 w-6 rounded-full bg-gray-300 flex items-center justify-center cursor-pointer hover:bg-gray-400 transition-colors" |
|
|
|
|
> |
|
|
|
|
<UserOutline class="h-4 w-4 text-gray-600" /> |
|
|
|
|
</button> |
|
|
|
|
<Popover |
|
|
|
|
placement="bottom" |
|
|
|
|
triggeredBy="#login-avatar" |
|
|
|
|
class="popover-leather w-[200px]" |
|
|
|
|
trigger="click" |
|
|
|
|
> |
|
|
|
|
<div class="flex flex-col space-y-2"> |
|
|
|
|
<h3 class="text-lg font-bold mb-2">Login with...</h3> |
|
|
|
|
<button |
|
|
|
|
class="btn-leather text-nowrap flex self-stretch align-middle hover:text-primary-400 dark:hover:text-primary-500 disabled:opacity-50" |
|
|
|
|
<Avatar size="xs" id="login-menu"/> |
|
|
|
|
<Dropdown placement="bottom" triggeredBy="#login-menu" class="min-w-xs"> |
|
|
|
|
<DropdownGroup> |
|
|
|
|
<DropdownHeader>Login with...</DropdownHeader> |
|
|
|
|
<DropdownItem |
|
|
|
|
class="w-full" |
|
|
|
|
onclick={handleBrowserExtensionLogin} |
|
|
|
|
disabled={isLoadingExtension || isLoadingAmber} |
|
|
|
|
> |
|
|
|
|
disabled={isLoadingExtension || isLoadingAmber}> |
|
|
|
|
<span class="w-full flex items-center justify-start gap-3"> |
|
|
|
|
{#if isLoadingExtension} |
|
|
|
|
🔄 Connecting... |
|
|
|
|
<Loader size={16} class="inline" /> Connecting... |
|
|
|
|
{:else} |
|
|
|
|
🌐 Browser extension |
|
|
|
|
<Globe size={16} class="inline" /> Browser extension |
|
|
|
|
{/if} |
|
|
|
|
</button> |
|
|
|
|
<button |
|
|
|
|
class="btn-leather text-nowrap flex self-stretch align-middle hover:text-primary-400 dark:hover:text-primary-500 disabled:opacity-50" |
|
|
|
|
</span></DropdownItem> |
|
|
|
|
<DropdownItem |
|
|
|
|
class="w-full" |
|
|
|
|
onclick={handleAmberLogin} |
|
|
|
|
disabled={isLoadingAmber || isLoadingExtension} |
|
|
|
|
> |
|
|
|
|
<span class="w-full flex items-center justify-start gap-3"> |
|
|
|
|
{#if isLoadingAmber} |
|
|
|
|
🔄 Connecting... |
|
|
|
|
<Loader size={16} class="inline" /> Connecting... |
|
|
|
|
{:else} |
|
|
|
|
📱 Amber: NostrConnect |
|
|
|
|
<Smartphone size={16} class="inline" /> Amber: NostrConnect |
|
|
|
|
{/if} |
|
|
|
|
</button> |
|
|
|
|
<button |
|
|
|
|
class="btn-leather text-nowrap flex self-stretch align-middle hover:text-primary-400 dark:hover:text-primary-500" |
|
|
|
|
</span> |
|
|
|
|
</DropdownItem> |
|
|
|
|
<DropdownItem |
|
|
|
|
class="w-full" |
|
|
|
|
onclick={handleReadOnlyLogin} |
|
|
|
|
> |
|
|
|
|
📖 npub (read only) |
|
|
|
|
</button> |
|
|
|
|
<div class="border-t border-gray-200 pt-2 mt-2"> |
|
|
|
|
<div class="text-xs text-gray-500 mb-1">Network Status:</div> |
|
|
|
|
<NetworkStatus /> |
|
|
|
|
</div> |
|
|
|
|
</div> |
|
|
|
|
</Popover> |
|
|
|
|
<span class="w-full flex items-center justify-start gap-3"> |
|
|
|
|
<Book size={16} class="inline" /> npub (read only) |
|
|
|
|
</span> |
|
|
|
|
</DropdownItem> |
|
|
|
|
{#if result} |
|
|
|
|
<DropdownHeader> |
|
|
|
|
<div |
|
|
|
|
class="absolute right-0 top-10 z-50 bg-gray-100 p-3 rounded text-sm break-words whitespace-pre-line max-w-lg shadow-lg border border-gray-300" |
|
|
|
|
> |
|
|
|
|
@ -450,63 +431,63 @@
@@ -450,63 +431,63 @@
|
|
|
|
|
onclick={() => (result = null)}>✖</button |
|
|
|
|
> |
|
|
|
|
</div> |
|
|
|
|
</DropdownHeader> |
|
|
|
|
{/if} |
|
|
|
|
</div> |
|
|
|
|
</DropdownGroup> |
|
|
|
|
<DropdownGroup> |
|
|
|
|
<DropdownHeader> |
|
|
|
|
<NetworkStatus /> |
|
|
|
|
</DropdownHeader> |
|
|
|
|
</DropdownGroup> |
|
|
|
|
</Dropdown> |
|
|
|
|
{:else} |
|
|
|
|
<!-- User profile --> |
|
|
|
|
<div class="group"> |
|
|
|
|
<button |
|
|
|
|
class="h-6 w-6 rounded-full p-0 border-0 bg-transparent cursor-pointer" |
|
|
|
|
id={profileAvatarId} |
|
|
|
|
type="button" |
|
|
|
|
aria-label="Open profile menu" |
|
|
|
|
> |
|
|
|
|
{#if !pfp} |
|
|
|
|
<div class="h-6 w-6 rounded-full bg-gray-300 animate-pulse cursor-pointer"></div> |
|
|
|
|
{:else} |
|
|
|
|
<Avatar |
|
|
|
|
rounded |
|
|
|
|
class="h-6 w-6 cursor-pointer" |
|
|
|
|
src={pfp} |
|
|
|
|
alt={username || "User"} |
|
|
|
|
/> |
|
|
|
|
{/if} |
|
|
|
|
</button> |
|
|
|
|
<Popover |
|
|
|
|
placement="bottom" |
|
|
|
|
triggeredBy={`#${profileAvatarId}`} |
|
|
|
|
class="popover-leather w-[220px]" |
|
|
|
|
trigger="click" |
|
|
|
|
> |
|
|
|
|
<div class="flex flex-row justify-between space-x-4"> |
|
|
|
|
<div class="flex flex-col"> |
|
|
|
|
aria-label="Open profile menu" |
|
|
|
|
size="xs" id={profileAvatarId}/> |
|
|
|
|
<Dropdown placement="bottom" triggeredBy="#{profileAvatarId}" class="min-w-xs"> |
|
|
|
|
<DropdownHeader> |
|
|
|
|
{#if username} |
|
|
|
|
<h3 class="text-lg font-bold">{username}</h3> |
|
|
|
|
{#if isNav}<h4 class="text-base">@{tag}</h4>{/if} |
|
|
|
|
<span class="block text-sm">{username}</span> |
|
|
|
|
<span class="block truncate text-sm font-medium">@{tag}</span> |
|
|
|
|
{:else if !pfp} |
|
|
|
|
<h3 class="text-lg font-bold">Loading profile...</h3> |
|
|
|
|
<span>Loading profile...</span> |
|
|
|
|
{:else} |
|
|
|
|
<h3 class="text-lg font-bold">Loading...</h3> |
|
|
|
|
<span>Loading...</span> |
|
|
|
|
{/if} |
|
|
|
|
<ul class="space-y-2 mt-2"> |
|
|
|
|
<li> |
|
|
|
|
</DropdownHeader> |
|
|
|
|
<DropdownGroup> |
|
|
|
|
<DropdownItem class="w-full"> |
|
|
|
|
<CopyToClipboard |
|
|
|
|
displayText={shortenNpub(npub) || "Loading..."} |
|
|
|
|
copyText={npub || ""} |
|
|
|
|
/> |
|
|
|
|
</li> |
|
|
|
|
<li> |
|
|
|
|
<button |
|
|
|
|
class="hover:text-primary-400 dark:hover:text-primary-500 text-nowrap mt-3 m-0 text-left" |
|
|
|
|
onclick={handleViewProfile} |
|
|
|
|
</DropdownItem> |
|
|
|
|
</DropdownGroup> |
|
|
|
|
<DropdownGroup> |
|
|
|
|
<DropdownItem |
|
|
|
|
class="w-full items-start" |
|
|
|
|
onclick={() => goto('/profile')} |
|
|
|
|
> |
|
|
|
|
<UserOutline |
|
|
|
|
class="mr-1 !h-6 !w-6 inline !fill-none dark:!fill-none" |
|
|
|
|
/><span class="underline">View notifications</span> |
|
|
|
|
</button> |
|
|
|
|
</li> |
|
|
|
|
|
|
|
|
|
<li class="text-xs text-gray-500"> |
|
|
|
|
View profile |
|
|
|
|
</DropdownItem> |
|
|
|
|
<DropdownItem |
|
|
|
|
class="w-full" |
|
|
|
|
onclick={() => goto('/profile/my-notes')} |
|
|
|
|
> |
|
|
|
|
My notes |
|
|
|
|
</DropdownItem> |
|
|
|
|
<DropdownItem |
|
|
|
|
class="w-full" |
|
|
|
|
onclick={() => goto('/profile/notifications')} |
|
|
|
|
> |
|
|
|
|
Notifications |
|
|
|
|
</DropdownItem> |
|
|
|
|
</DropdownGroup> |
|
|
|
|
<DropdownGroup> |
|
|
|
|
<DropdownHeader class="text-xs"> |
|
|
|
|
{#if userState.loginMethod === "extension"} |
|
|
|
|
Logged in with extension |
|
|
|
|
{:else if userState.loginMethod === "amber"} |
|
|
|
|
@ -516,36 +497,19 @@
@@ -516,36 +497,19 @@
|
|
|
|
|
{:else} |
|
|
|
|
Unknown login method |
|
|
|
|
{/if} |
|
|
|
|
</li> |
|
|
|
|
<li> |
|
|
|
|
<NetworkStatus /> |
|
|
|
|
</li> |
|
|
|
|
{#if isNav} |
|
|
|
|
<li> |
|
|
|
|
<button |
|
|
|
|
</DropdownHeader> |
|
|
|
|
<DropdownHeader><NetworkStatus /></DropdownHeader> |
|
|
|
|
</DropdownGroup> |
|
|
|
|
<DropdownGroup> |
|
|
|
|
<DropdownItem |
|
|
|
|
id="sign-out-button" |
|
|
|
|
class="btn-leather text-nowrap mt-3 flex self-stretch align-middle hover:text-primary-400 dark:hover:text-primary-500" |
|
|
|
|
class="w-full" |
|
|
|
|
onclick={handleSignOutClick} |
|
|
|
|
> |
|
|
|
|
<ArrowRightToBracketOutline |
|
|
|
|
class="mr-1 !h-6 !w-6 inline !fill-none dark:!fill-none" |
|
|
|
|
/> Sign out |
|
|
|
|
</button> |
|
|
|
|
</li> |
|
|
|
|
{:else} |
|
|
|
|
<!-- li> |
|
|
|
|
<button |
|
|
|
|
class='btn-leather text-nowrap mt-3 flex self-stretch align-middle hover:text-primary-400 dark:hover:text-primary-500' |
|
|
|
|
> |
|
|
|
|
<FileSearchOutline class='mr-1 !h-6 inline !fill-none dark:!fill-none' /> More content |
|
|
|
|
</button> |
|
|
|
|
</li --> |
|
|
|
|
{/if} |
|
|
|
|
</ul> |
|
|
|
|
</div> |
|
|
|
|
</div> |
|
|
|
|
</Popover> |
|
|
|
|
</div> |
|
|
|
|
Sign out |
|
|
|
|
</DropdownItem> |
|
|
|
|
</DropdownGroup> |
|
|
|
|
</Dropdown> |
|
|
|
|
{/if} |
|
|
|
|
</div> |
|
|
|
|
|
|
|
|
|
|