update ui for consistent in light and dark mode
This commit is contained in:
@@ -52,6 +52,8 @@ export function CreateAccountScreen() {
|
||||
name: data.name,
|
||||
display_name: data.name,
|
||||
bio: data.about,
|
||||
picture: picture,
|
||||
avatar: picture,
|
||||
};
|
||||
|
||||
const userPrivkey = generatePrivateKey();
|
||||
@@ -105,7 +107,7 @@ export function CreateAccountScreen() {
|
||||
if (filePath) {
|
||||
await writeTextFile(
|
||||
filePath,
|
||||
`Generated by Lume (lume.nu)\nPublic key: ${keys.npub}\nPrivate key: ${keys.nsec}`
|
||||
`Nostr account, generated by Lume (lume.nu)\nPublic key: ${keys.npub}\nPrivate key: ${keys.nsec}`
|
||||
);
|
||||
|
||||
setDownloaded(true);
|
||||
|
||||
@@ -1,150 +0,0 @@
|
||||
import { webln } from '@getalby/sdk';
|
||||
import * as Dialog from '@radix-ui/react-dialog';
|
||||
import { message } from '@tauri-apps/plugin-dialog';
|
||||
import { useState } from 'react';
|
||||
|
||||
import { useStorage } from '@libs/storage/provider';
|
||||
|
||||
import {
|
||||
AlbyIcon,
|
||||
ArrowRightCircleIcon,
|
||||
CancelIcon,
|
||||
CheckCircleIcon,
|
||||
LoaderIcon,
|
||||
} from '@shared/icons';
|
||||
|
||||
export function NWCAlby() {
|
||||
const { db } = useStorage();
|
||||
|
||||
const [isOpen, setIsOpen] = useState(false);
|
||||
const [isLoading, setIsloading] = useState(false);
|
||||
const [isConnected, setIsConnected] = useState(false);
|
||||
|
||||
const initAlby = async () => {
|
||||
try {
|
||||
setIsloading(true);
|
||||
|
||||
const provider = webln.NostrWebLNProvider.withNewSecret();
|
||||
const walletConnectURL = provider.getNostrWalletConnectUrl(true);
|
||||
|
||||
// get auth url
|
||||
const authURL = provider.getAuthorizationUrl({ name: 'Lume' });
|
||||
|
||||
// open auth window
|
||||
/*
|
||||
const webview = new WebviewWindow('alby', {
|
||||
title: 'Connect Alby',
|
||||
url: authURL.href,
|
||||
center: true,
|
||||
width: 400,
|
||||
height: 650,
|
||||
});
|
||||
|
||||
webview.listen('tauri://close-requested', async () => {
|
||||
await db.secureSave('nwc', walletConnectURL);
|
||||
setIsConnected(true);
|
||||
setIsloading(false);
|
||||
});
|
||||
*/
|
||||
} catch (e) {
|
||||
setIsloading(false);
|
||||
await message(e.toString(), { title: 'Connect Alby', type: 'error' });
|
||||
}
|
||||
};
|
||||
|
||||
return (
|
||||
<Dialog.Root open={isOpen} onOpenChange={setIsOpen}>
|
||||
<div className="flex items-center justify-between">
|
||||
<div className="inline-flex items-center gap-2.5">
|
||||
<div className="inline-flex h-11 w-11 items-center justify-center rounded-md bg-neutral-200">
|
||||
<AlbyIcon className="h-8 w-8" />
|
||||
</div>
|
||||
<div>
|
||||
<h5 className="font-semibold text-neutral-900 dark:text-neutral-100">Alby</h5>
|
||||
<p className="text-sm text-neutral-600 dark:text-neutral-400">
|
||||
Require alby account
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
<Dialog.Trigger asChild>
|
||||
<button
|
||||
type="button"
|
||||
className="inline-flex h-9 w-min items-center justify-center rounded-lg bg-neutral-300 px-3 text-sm font-medium hover:bg-blue-500 hover:text-white dark:bg-neutral-700"
|
||||
>
|
||||
Connect
|
||||
</button>
|
||||
</Dialog.Trigger>
|
||||
</div>
|
||||
<Dialog.Portal>
|
||||
<Dialog.Overlay className="fixed inset-0 z-50 bg-black/80 backdrop-blur-2xl" />
|
||||
<Dialog.Content className="fixed inset-0 z-50 flex min-h-full items-center justify-center">
|
||||
<div className="relative h-min w-full max-w-xl rounded-xl bg-neutral-400 dark:bg-neutral-600">
|
||||
<div className="h-min w-full shrink-0 rounded-t-xl border-b border-white/10 bg-white/5 px-5 py-5">
|
||||
<div className="flex flex-col gap-1">
|
||||
<div className="flex items-center justify-between">
|
||||
<Dialog.Title className="text-lg font-semibold leading-none text-white">
|
||||
Alby integration (Beta)
|
||||
</Dialog.Title>
|
||||
<Dialog.Close className="inline-flex h-6 w-6 items-center justify-center rounded-md backdrop-blur-xl hover:bg-white/10">
|
||||
<CancelIcon className="h-4 w-4 text-neutral-600 dark:text-neutral-400" />
|
||||
</Dialog.Close>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div className="flex flex-col gap-3 px-5 py-5">
|
||||
<div className="relative flex h-40 items-center justify-center gap-4">
|
||||
<div className="inline-flex h-16 w-16 items-end justify-center rounded-lg bg-black pb-2">
|
||||
<img src="/lume.png" className="w-1/3" alt="Lume Logo" />
|
||||
</div>
|
||||
<div className="w-20 border border-dashed border-white/5" />
|
||||
<div className="inline-flex h-16 w-16 items-center justify-center rounded-lg bg-white">
|
||||
<AlbyIcon className="h-8 w-8" />
|
||||
</div>
|
||||
{isConnected ? (
|
||||
<div className="absolute left-1/2 top-1/2 -translate-x-1/2 -translate-y-1/2 transform">
|
||||
<CheckCircleIcon className="h-5 w-5 text-green-500" />
|
||||
</div>
|
||||
) : null}
|
||||
</div>
|
||||
<div className="flex flex-col gap-2">
|
||||
<p className="text-sm text-neutral-600 dark:text-neutral-400">
|
||||
When you click "Connect", a new window will open and you need
|
||||
to click the "Connect Wallet" button to grant Lume permission
|
||||
to integrate with your Alby account.
|
||||
</p>
|
||||
<p className="text-sm text-neutral-600 dark:text-neutral-400">
|
||||
All information will be encrypted and stored on the local machine.
|
||||
</p>
|
||||
</div>
|
||||
<button
|
||||
type="button"
|
||||
onClick={() => initAlby()}
|
||||
className="inline-flex h-11 w-full items-center justify-between gap-2 rounded-lg bg-blue-500 px-6 font-medium leading-none text-white hover:bg-blue-600 focus:outline-none disabled:opacity-50"
|
||||
>
|
||||
{isLoading ? (
|
||||
<>
|
||||
<span className="w-5" />
|
||||
<span>Connecting...</span>
|
||||
<LoaderIcon className="h-5 w-5 animate-spin text-white" />
|
||||
</>
|
||||
) : isConnected ? (
|
||||
<>
|
||||
<span className="w-5" />
|
||||
<span>Connected</span>
|
||||
<CheckCircleIcon className="h-5 w-5" />
|
||||
</>
|
||||
) : (
|
||||
<>
|
||||
<span className="w-5" />
|
||||
<span>Connect</span>
|
||||
<ArrowRightCircleIcon className="h-5 w-5" />
|
||||
</>
|
||||
)}
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
</Dialog.Content>
|
||||
</Dialog.Portal>
|
||||
</Dialog.Root>
|
||||
);
|
||||
}
|
||||
66
src/app/nwc/components/form.tsx
Normal file
66
src/app/nwc/components/form.tsx
Normal file
@@ -0,0 +1,66 @@
|
||||
import { useState } from 'react';
|
||||
import { toast } from 'sonner';
|
||||
|
||||
import { useStorage } from '@libs/storage/provider';
|
||||
|
||||
import { LoaderIcon } from '@shared/icons';
|
||||
|
||||
export function NWCForm({ setWalletConnectURL }) {
|
||||
const { db } = useStorage();
|
||||
|
||||
const [uri, setUri] = useState('');
|
||||
const [loading, setLoading] = useState(false);
|
||||
|
||||
const submit = async () => {
|
||||
try {
|
||||
setLoading(true);
|
||||
|
||||
if (!uri.startsWith('nostr+walletconnect:')) {
|
||||
toast.error(
|
||||
'Connect URI is required and must start with format nostr+walletconnect:, please check again'
|
||||
);
|
||||
setLoading(false);
|
||||
return;
|
||||
}
|
||||
|
||||
const uriObj = new URL(uri);
|
||||
const params = new URLSearchParams(uriObj.search);
|
||||
|
||||
if (params.has('relay') && params.has('secret')) {
|
||||
await db.secureSave(`${db.account.pubkey}-nwc`, uri);
|
||||
setWalletConnectURL(uri);
|
||||
setLoading(false);
|
||||
} else {
|
||||
setLoading(false);
|
||||
toast.error('Connect URI is not valid, please check again');
|
||||
return;
|
||||
}
|
||||
} catch (e) {
|
||||
setLoading(false);
|
||||
toast.error(e);
|
||||
}
|
||||
};
|
||||
|
||||
return (
|
||||
<div className="flex flex-col gap-3 rounded-xl bg-neutral-100 p-3 dark:bg-neutral-900">
|
||||
<div className="flex flex-col gap-1.5">
|
||||
<textarea
|
||||
name="walletConnectURL"
|
||||
value={uri}
|
||||
// eslint-disable-next-line jsx-a11y/no-autofocus
|
||||
autoFocus={false}
|
||||
onChange={(e) => setUri(e.target.value)}
|
||||
placeholder="nostr+walletconnect://"
|
||||
className="h-40 w-full resize-none rounded-lg bg-neutral-200 px-3 py-3 text-neutral-900 !outline-none placeholder:text-neutral-600 dark:bg-neutral-800 dark:text-neutral-100 dark:placeholder:text-neutral-400"
|
||||
/>
|
||||
</div>
|
||||
<button
|
||||
type="button"
|
||||
onClick={submit}
|
||||
className="inline-flex h-9 w-full items-center justify-center rounded-lg bg-blue-500 font-medium text-white hover:bg-blue-600"
|
||||
>
|
||||
{loading ? <LoaderIcon className="h-4 w-4 animate-spin" /> : 'Connect'}
|
||||
</button>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
@@ -1,165 +0,0 @@
|
||||
import * as Dialog from '@radix-ui/react-dialog';
|
||||
import { useState } from 'react';
|
||||
import { Resolver, useForm } from 'react-hook-form';
|
||||
|
||||
import { useStorage } from '@libs/storage/provider';
|
||||
|
||||
import { ArrowRightCircleIcon, CancelIcon, LoaderIcon, WorldIcon } from '@shared/icons';
|
||||
|
||||
type FormValues = {
|
||||
uri: string;
|
||||
};
|
||||
|
||||
const resolver: Resolver<FormValues> = async (values) => {
|
||||
return {
|
||||
values: values.uri ? values : {},
|
||||
errors: !values.uri
|
||||
? {
|
||||
uri: {
|
||||
type: 'required',
|
||||
message: 'This is required.',
|
||||
},
|
||||
}
|
||||
: {},
|
||||
};
|
||||
};
|
||||
|
||||
export function NWCOther() {
|
||||
const { db } = useStorage();
|
||||
const {
|
||||
register,
|
||||
setError,
|
||||
handleSubmit,
|
||||
formState: { errors, isDirty, isValid },
|
||||
} = useForm<FormValues>({ resolver });
|
||||
|
||||
const [isOpen, setIsOpen] = useState(false);
|
||||
const [isLoading, setIsloading] = useState(false);
|
||||
|
||||
const onSubmit = async (data: { [x: string]: string }) => {
|
||||
try {
|
||||
if (!data.uri.startsWith('nostr+walletconnect:')) {
|
||||
setError('uri', {
|
||||
type: 'custom',
|
||||
message:
|
||||
'Connect URI is required and must start with format nostr+walletconnect:, please check again',
|
||||
});
|
||||
return;
|
||||
}
|
||||
|
||||
setIsloading(true);
|
||||
|
||||
const uriObj = new URL(data.uri);
|
||||
const params = new URLSearchParams(uriObj.search);
|
||||
|
||||
if (params.has('relay') && params.has('secret')) {
|
||||
await db.secureSave('nwc', data.uri);
|
||||
setIsloading(false);
|
||||
setIsOpen(false);
|
||||
}
|
||||
} catch (e) {
|
||||
setIsloading(false);
|
||||
setError('uri', {
|
||||
type: 'custom',
|
||||
message:
|
||||
'Connect URI is required and must start with format nostr+walletconnect:, please check again',
|
||||
});
|
||||
}
|
||||
};
|
||||
|
||||
return (
|
||||
<Dialog.Root open={isOpen} onOpenChange={setIsOpen}>
|
||||
<div className="flex items-center justify-between pt-4">
|
||||
<div className="inline-flex items-center gap-2.5">
|
||||
<div className="inline-flex h-11 w-11 items-center justify-center rounded-lg bg-neutral-200 dark:bg-neutral-700">
|
||||
<WorldIcon className="h-5 w-5" />
|
||||
</div>
|
||||
<div>
|
||||
<h5 className="font-semibold text-neutral-900 dark:text-neutral-100">
|
||||
URI String
|
||||
</h5>
|
||||
<p className="text-sm text-neutral-600 dark:text-neutral-400">
|
||||
Using format nostr+walletconnect:
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
<Dialog.Trigger asChild>
|
||||
<button
|
||||
type="button"
|
||||
className="inline-flex h-9 w-min items-center justify-center rounded-lg bg-neutral-300 px-3 text-sm font-medium hover:bg-blue-500 hover:text-white dark:bg-neutral-700"
|
||||
>
|
||||
Connect
|
||||
</button>
|
||||
</Dialog.Trigger>
|
||||
</div>
|
||||
<Dialog.Portal>
|
||||
<Dialog.Overlay className="fixed inset-0 z-50 bg-white dark:bg-black" />
|
||||
<Dialog.Content className="fixed inset-0 z-50 flex min-h-full items-center justify-center">
|
||||
<div className="relative h-min w-full max-w-xl rounded-xl bg-neutral-400 dark:bg-neutral-600">
|
||||
<div className="h-min w-full shrink-0 rounded-t-xl border-b border-white/10 bg-white/5 px-5 py-5">
|
||||
<div className="flex flex-col gap-1">
|
||||
<div className="flex items-center justify-between">
|
||||
<Dialog.Title className="text-lg font-semibold leading-none text-white">
|
||||
Nostr Wallet Connect
|
||||
</Dialog.Title>
|
||||
<Dialog.Close className="inline-flex h-6 w-6 items-center justify-center rounded-md backdrop-blur-xl hover:bg-white/10">
|
||||
<CancelIcon className="h-4 w-4 text-neutral-600 dark:text-neutral-400" />
|
||||
</Dialog.Close>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<form
|
||||
onSubmit={handleSubmit(onSubmit)}
|
||||
className="mb-0 flex flex-col gap-3 px-5 py-5"
|
||||
>
|
||||
<div className="flex flex-col gap-1">
|
||||
<label
|
||||
htmlFor="uri"
|
||||
className="text-sm font-semibold uppercase tracking-wider text-neutral-600 dark:text-neutral-400"
|
||||
>
|
||||
Connect URI
|
||||
</label>
|
||||
<input
|
||||
{...register('uri', { required: true })}
|
||||
placeholder="nostr+walletconnect:"
|
||||
spellCheck={false}
|
||||
autoComplete="off"
|
||||
autoCorrect="off"
|
||||
autoCapitalize="off"
|
||||
className="relative h-11 w-full rounded-lg bg-white/10 px-3 py-1 text-white !outline-none backdrop-blur-xl placeholder:text-neutral-600 dark:text-neutral-400"
|
||||
/>
|
||||
<span className="text-sm text-red-400">
|
||||
{errors.uri && <p>{errors.uri.message}</p>}
|
||||
</span>
|
||||
</div>
|
||||
<div className="flex flex-col gap-1 text-center">
|
||||
<button
|
||||
type="submit"
|
||||
disabled={!isDirty || !isValid}
|
||||
className="inline-flex h-11 w-full items-center justify-between gap-2 rounded-lg bg-blue-500 px-6 font-medium leading-none text-white hover:bg-blue-600 focus:outline-none disabled:opacity-50"
|
||||
>
|
||||
{isLoading ? (
|
||||
<>
|
||||
<span className="w-5" />
|
||||
<span>Connecting...</span>
|
||||
<LoaderIcon className="h-5 w-5 animate-spin text-white" />
|
||||
</>
|
||||
) : (
|
||||
<>
|
||||
<span className="w-5" />
|
||||
<span>Connect</span>
|
||||
<ArrowRightCircleIcon className="h-5 w-5" />
|
||||
</>
|
||||
)}
|
||||
</button>
|
||||
<span className="text-sm text-neutral-600 dark:text-neutral-400">
|
||||
All information will be encrypted and stored on the local machine.
|
||||
</span>
|
||||
</div>
|
||||
</form>
|
||||
</div>
|
||||
</Dialog.Content>
|
||||
</Dialog.Portal>
|
||||
</Dialog.Root>
|
||||
);
|
||||
}
|
||||
@@ -1,7 +1,6 @@
|
||||
import { useEffect, useState } from 'react';
|
||||
|
||||
import { NWCAlby } from '@app/nwc/components/alby';
|
||||
import { NWCOther } from '@app/nwc/components/other';
|
||||
import { NWCForm } from '@app/nwc/components/form';
|
||||
|
||||
import { useStorage } from '@libs/storage/provider';
|
||||
|
||||
@@ -18,10 +17,9 @@ export function NWCScreen() {
|
||||
|
||||
useEffect(() => {
|
||||
async function getNWC() {
|
||||
const nwc = await db.secureLoad('nwc');
|
||||
const nwc = await db.secureLoad(`${db.account.pubkey}-nwc`);
|
||||
if (nwc) setWalletConnectURL(nwc);
|
||||
}
|
||||
|
||||
getNWC();
|
||||
}, []);
|
||||
|
||||
@@ -29,24 +27,19 @@ export function NWCScreen() {
|
||||
<div className="flex h-full w-full items-center justify-center">
|
||||
<div className="flex w-full flex-col gap-5">
|
||||
<div className="text-center">
|
||||
<h3 className="text-2xl font-bold leading-tight">
|
||||
Nostr Wallet Connect (Beta)
|
||||
</h3>
|
||||
<h3 className="text-2xl font-bold leading-tight">Nostr Wallet Connect</h3>
|
||||
<p className="leading-tight text-neutral-600 dark:text-neutral-400">
|
||||
Sending tips easily via Bitcoin Lightning.
|
||||
Sending zap easily via Bitcoin Lightning.
|
||||
</p>
|
||||
</div>
|
||||
<div className="mx-auto max-w-lg">
|
||||
{!walletConnectURL ? (
|
||||
<div className="flex w-full flex-col gap-4 divide-y divide-neutral-200 rounded-xl bg-neutral-100 p-3 dark:divide-neutral-800 dark:bg-neutral-900">
|
||||
<NWCAlby />
|
||||
<NWCOther />
|
||||
</div>
|
||||
<NWCForm setWalletConnectURL={setWalletConnectURL} />
|
||||
) : (
|
||||
<div className="flex w-full flex-col rounded-xl bg-neutral-100 p-3 dark:bg-neutral-900">
|
||||
<div className="mb-1 inline-flex items-center gap-1.5 text-sm text-teal-500">
|
||||
<div className="flex w-full flex-col gap-3 rounded-xl bg-neutral-100 p-3 dark:bg-neutral-900">
|
||||
<div className="flex items-center justify-center gap-1.5 text-sm text-teal-500">
|
||||
<CheckCircleIcon className="h-4 w-4" />
|
||||
<p>You're using nostr wallet connect</p>
|
||||
<div>You're using nostr wallet connect</div>
|
||||
</div>
|
||||
<div className="flex flex-col gap-2">
|
||||
<textarea
|
||||
@@ -57,7 +50,7 @@ export function NWCScreen() {
|
||||
<button
|
||||
type="button"
|
||||
onClick={() => remove()}
|
||||
className="inline-flex h-11 w-full items-center justify-center gap-2 rounded-lg bg-neutral-200 px-6 font-medium text-red-500 hover:bg-red-500 hover:text-white focus:outline-none dark:bg-neutral-800 dark:text-neutral-100"
|
||||
className="inline-flex h-9 w-full items-center justify-center gap-2 rounded-lg bg-neutral-200 px-6 font-medium text-red-500 hover:bg-red-500 hover:text-white focus:outline-none dark:bg-neutral-800 dark:text-neutral-100"
|
||||
>
|
||||
Remove connection
|
||||
</button>
|
||||
@@ -76,7 +69,7 @@ export function NWCScreen() {
|
||||
<p className="text-sm text-neutral-600 dark:text-neutral-400">
|
||||
To learn more about the details have a look at{' '}
|
||||
<a
|
||||
href="https://github.com/getAlby/nips/blob/7-wallet-connect-patch/47.md"
|
||||
href="https://github.com/nostr-protocol/nips/blob/master/47.md"
|
||||
target="_blank"
|
||||
className="text-blue-500"
|
||||
rel="noreferrer"
|
||||
@@ -87,13 +80,10 @@ export function NWCScreen() {
|
||||
</div>
|
||||
<div className="flex flex-col gap-1.5">
|
||||
<h5 className="font-semibold text-neutral-900 dark:text-neutral-100">
|
||||
About tipping
|
||||
About zapping
|
||||
</h5>
|
||||
<p className="text-sm text-neutral-600 dark:text-neutral-400">
|
||||
Also known as Zap in other Nostr client.
|
||||
</p>
|
||||
<p className="text-sm text-neutral-600 dark:text-neutral-400">
|
||||
Lume doesn't take any commission or platform fees when you tip
|
||||
Lume doesn't take any commission or platform fees when you zap
|
||||
someone.
|
||||
</p>
|
||||
<p className="text-sm text-neutral-600 dark:text-neutral-400">
|
||||
|
||||
@@ -1,18 +1,22 @@
|
||||
import { NDKEvent, NDKUserProfile } from '@nostr-dev-kit/ndk';
|
||||
import { NDKEvent, NDKKind, NDKUserProfile } from '@nostr-dev-kit/ndk';
|
||||
import * as Dialog from '@radix-ui/react-dialog';
|
||||
import { useQueryClient } from '@tanstack/react-query';
|
||||
import { message, open } from '@tauri-apps/plugin-dialog';
|
||||
import { readBinaryFile } from '@tauri-apps/plugin-fs';
|
||||
import { fetch } from '@tauri-apps/plugin-http';
|
||||
import { useEffect, useState } from 'react';
|
||||
import { useForm } from 'react-hook-form';
|
||||
|
||||
import { useNDK } from '@libs/ndk/provider';
|
||||
import { useStorage } from '@libs/storage/provider';
|
||||
|
||||
import { AvatarUploader } from '@shared/avatarUploader';
|
||||
import { BannerUploader } from '@shared/bannerUploader';
|
||||
import { CancelIcon, CheckCircleIcon, LoaderIcon, UnverifiedIcon } from '@shared/icons';
|
||||
import { Image } from '@shared/image';
|
||||
|
||||
import { useNostr } from '@utils/hooks/useNostr';
|
||||
import {
|
||||
CancelIcon,
|
||||
CheckCircleIcon,
|
||||
LoaderIcon,
|
||||
PlusIcon,
|
||||
UnverifiedIcon,
|
||||
} from '@shared/icons';
|
||||
|
||||
interface NIP05 {
|
||||
names: {
|
||||
@@ -25,12 +29,12 @@ export function EditProfileModal() {
|
||||
|
||||
const [isOpen, setIsOpen] = useState(false);
|
||||
const [loading, setLoading] = useState(false);
|
||||
const [picture, setPicture] = useState('https://void.cat/d/5VKmKyuHyxrNMf9bWSVPih');
|
||||
const [banner, setBanner] = useState(null);
|
||||
const [picture, setPicture] = useState('');
|
||||
const [banner, setBanner] = useState('');
|
||||
const [nip05, setNIP05] = useState({ verified: false, text: '' });
|
||||
|
||||
const { db } = useStorage();
|
||||
const { publish } = useNostr();
|
||||
const { ndk } = useNDK();
|
||||
const {
|
||||
register,
|
||||
handleSubmit,
|
||||
@@ -75,12 +79,106 @@ export function EditProfileModal() {
|
||||
return false;
|
||||
};
|
||||
|
||||
const uploadAvatar = async () => {
|
||||
try {
|
||||
// start loading
|
||||
setLoading(true);
|
||||
|
||||
const selected = await open({
|
||||
multiple: false,
|
||||
filters: [
|
||||
{
|
||||
name: 'Image',
|
||||
extensions: ['png', 'jpeg', 'jpg', 'gif'],
|
||||
},
|
||||
],
|
||||
});
|
||||
|
||||
if (!selected) {
|
||||
setLoading(false);
|
||||
return;
|
||||
}
|
||||
|
||||
const file = await readBinaryFile(selected.path);
|
||||
const blob = new Blob([file]);
|
||||
|
||||
const data = new FormData();
|
||||
data.append('fileToUpload', blob);
|
||||
data.append('submit', 'Upload Image');
|
||||
|
||||
const res = await fetch('https://nostr.build/api/v2/upload/files', {
|
||||
method: 'POST',
|
||||
body: data,
|
||||
});
|
||||
|
||||
if (res.ok) {
|
||||
const json = await res.json();
|
||||
const content = json.data[0];
|
||||
|
||||
setPicture(content.url);
|
||||
|
||||
// stop loading
|
||||
setLoading(false);
|
||||
}
|
||||
} catch (e) {
|
||||
// stop loading
|
||||
setLoading(false);
|
||||
await message(`Upload failed, error: ${e}`, { title: 'Lume', type: 'error' });
|
||||
}
|
||||
};
|
||||
|
||||
const uploadBanner = async () => {
|
||||
try {
|
||||
// start loading
|
||||
setLoading(true);
|
||||
|
||||
const selected = await open({
|
||||
multiple: false,
|
||||
filters: [
|
||||
{
|
||||
name: 'Image',
|
||||
extensions: ['png', 'jpeg', 'jpg', 'gif'],
|
||||
},
|
||||
],
|
||||
});
|
||||
|
||||
if (!selected) {
|
||||
setLoading(false);
|
||||
return;
|
||||
}
|
||||
|
||||
const file = await readBinaryFile(selected.path);
|
||||
const blob = new Blob([file]);
|
||||
|
||||
const data = new FormData();
|
||||
data.append('fileToUpload', blob);
|
||||
data.append('submit', 'Upload Image');
|
||||
|
||||
const res = await fetch('https://nostr.build/api/v2/upload/files', {
|
||||
method: 'POST',
|
||||
body: data,
|
||||
});
|
||||
|
||||
if (res.ok) {
|
||||
const json = await res.json();
|
||||
const content = json.data[0];
|
||||
|
||||
setBanner(content.url);
|
||||
|
||||
// stop loading
|
||||
setLoading(false);
|
||||
}
|
||||
} catch (e) {
|
||||
// stop loading
|
||||
setLoading(false);
|
||||
await message(`Upload failed, error: ${e}`, { title: 'Lume', type: 'error' });
|
||||
}
|
||||
};
|
||||
|
||||
const onSubmit = async (data: NDKUserProfile) => {
|
||||
// start loading
|
||||
setLoading(true);
|
||||
|
||||
let event: NDKEvent;
|
||||
|
||||
const content = {
|
||||
...data,
|
||||
username: data.name,
|
||||
@@ -89,14 +187,14 @@ export function EditProfileModal() {
|
||||
image: data.picture,
|
||||
};
|
||||
|
||||
const event = new NDKEvent(ndk);
|
||||
event.kind = NDKKind.Metadata;
|
||||
event.tags = [];
|
||||
|
||||
if (data.nip05) {
|
||||
const nip05IsVerified = await verifyNIP05(data.nip05);
|
||||
if (nip05IsVerified) {
|
||||
event = await publish({
|
||||
content: JSON.stringify({ ...content, nip05: data.nip05 }),
|
||||
kind: 0,
|
||||
tags: [],
|
||||
});
|
||||
event.content = JSON.stringify({ ...content, nip05: data.nip05 });
|
||||
} else {
|
||||
setNIP05((prev) => ({ ...prev, verified: false }));
|
||||
setError('nip05', {
|
||||
@@ -105,14 +203,12 @@ export function EditProfileModal() {
|
||||
});
|
||||
}
|
||||
} else {
|
||||
event = await publish({
|
||||
content: JSON.stringify(content),
|
||||
kind: 0,
|
||||
tags: [],
|
||||
});
|
||||
event.content = JSON.stringify(content);
|
||||
}
|
||||
|
||||
if (event.id) {
|
||||
const publishedRelays = await event.publish();
|
||||
|
||||
if (publishedRelays) {
|
||||
// invalid cache
|
||||
queryClient.invalidateQueries(['user', db.account.pubkey]);
|
||||
// reset form
|
||||
@@ -144,7 +240,7 @@ export function EditProfileModal() {
|
||||
</button>
|
||||
</Dialog.Trigger>
|
||||
<Dialog.Portal>
|
||||
<Dialog.Overlay className="fixed inset-0 z-50 bg-white dark:bg-black" />
|
||||
<Dialog.Overlay className="fixed inset-0 z-50 bg-black/20 backdrop-blur-sm dark:bg-black/20" />
|
||||
<Dialog.Content className="fixed inset-0 z-50 flex min-h-full items-center justify-center">
|
||||
<div className="relative h-min w-full max-w-xl rounded-xl bg-neutral-100 dark:bg-neutral-900">
|
||||
<div className="h-min w-full shrink-0 rounded-t-xl border-b border-neutral-200 px-5 py-5 dark:border-neutral-800">
|
||||
@@ -173,18 +269,30 @@ export function EditProfileModal() {
|
||||
<div className="h-full w-full bg-black dark:bg-white" />
|
||||
)}
|
||||
<div className="absolute left-1/2 top-1/2 z-10 h-full w-full -translate-x-1/2 -translate-y-1/2 transform">
|
||||
<BannerUploader setBanner={setBanner} />
|
||||
<button
|
||||
type="button"
|
||||
onClick={() => uploadBanner()}
|
||||
className="inline-flex h-full w-full items-center justify-center bg-black/50"
|
||||
>
|
||||
<PlusIcon className="h-5 w-5" />
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
<div className="mb-5 px-4">
|
||||
<div className="relative z-10 -mt-7 h-14 w-14">
|
||||
<Image
|
||||
<div className="relative z-10 -mt-7 h-14 w-14 overflow-hidden rounded-xl ring-2 ring-neutral-900">
|
||||
<img
|
||||
src={picture}
|
||||
alt="user's avatar"
|
||||
className="h-14 w-14 rounded-lg object-cover ring-2 ring-neutral-900"
|
||||
className="h-14 w-14 rounded-xl object-cover"
|
||||
/>
|
||||
<div className="absolute left-1/2 top-1/2 z-10 h-full w-full -translate-x-1/2 -translate-y-1/2 transform">
|
||||
<AvatarUploader setPicture={setPicture} />
|
||||
<button
|
||||
type="button"
|
||||
onClick={() => uploadAvatar()}
|
||||
className="inline-flex h-full w-full items-center justify-center rounded-xl bg-black/50"
|
||||
>
|
||||
<PlusIcon className="h-5 w-5" />
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@@ -50,15 +50,15 @@ export function UserProfile({ pubkey }: { pubkey: string }) {
|
||||
|
||||
return (
|
||||
<>
|
||||
<div className="h-56 w-full">
|
||||
<div className="h-56 w-full overflow-hidden rounded-tl-lg">
|
||||
{user.banner ? (
|
||||
<img
|
||||
src={user.banner}
|
||||
alt="user banner"
|
||||
className="h-full w-full object-cover"
|
||||
className="h-full w-full rounded-tl-lg object-cover"
|
||||
/>
|
||||
) : (
|
||||
<div className="h-full w-full bg-neutral-100 dark:bg-neutral-900" />
|
||||
<div className="h-full w-full rounded-tl-lg bg-neutral-100 dark:bg-neutral-900" />
|
||||
)}
|
||||
</div>
|
||||
<div className="-mt-7 flex w-full flex-col items-center px-5">
|
||||
|
||||
Reference in New Issue
Block a user