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 { CancelIcon, CheckCircleIcon, LoaderIcon, PlusIcon, UnverifiedIcon, } from '@shared/icons'; interface NIP05 { names: { [key: string]: string; }; } export function EditProfileModal() { const queryClient = useQueryClient(); const [isOpen, setIsOpen] = useState(false); const [loading, setLoading] = useState(false); const [picture, setPicture] = useState(''); const [banner, setBanner] = useState(''); const [nip05, setNIP05] = useState({ verified: false, text: '' }); const { db } = useStorage(); const { ndk } = useNDK(); const { register, handleSubmit, reset, setError, formState: { isValid, errors }, } = useForm({ defaultValues: async () => { const res: NDKUserProfile = queryClient.getQueryData(['user', db.account.pubkey]); if (res.image) { setPicture(res.image); } if (res.banner) { setBanner(res.banner); } if (res.nip05) { setNIP05((prev) => ({ ...prev, text: res.nip05 })); } return res; }, }); const verifyNIP05 = async (nip05: string) => { const localPath = nip05.split('@')[0]; const service = nip05.split('@')[1]; const verifyURL = `https://${service}/.well-known/nostr.json?name=${localPath}`; const res = await fetch(verifyURL, { method: 'GET', headers: { 'Content-Type': 'application/json; charset=utf-8', }, }); if (!res.ok) throw new Error(`Failed to fetch NIP-05 service: ${nip05}`); const data: NIP05 = await res.json(); if (data.names) { if (data.names[localPath] !== db.account.pubkey) return false; return true; } 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); const content = { ...data, username: data.name, display_name: data.name, bio: data.about, 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.content = JSON.stringify({ ...content, nip05: data.nip05 }); } else { setNIP05((prev) => ({ ...prev, verified: false })); setError('nip05', { type: 'manual', message: "Can't verify your Lume ID / NIP-05, please check again", }); } } else { event.content = JSON.stringify(content); } const publishedRelays = await event.publish(); if (publishedRelays) { // invalid cache queryClient.invalidateQueries({ queryKey: ['user', db.account.pubkey] }); // reset form reset(); // reset state setLoading(false); setIsOpen(false); setPicture('https://void.cat/d/5VKmKyuHyxrNMf9bWSVPih'); setBanner(null); } else { setLoading(false); } }; useEffect(() => { if (!nip05.verified && /\S+@\S+\.\S+/.test(nip05.text)) { verifyNIP05(nip05.text); } }, [nip05.text]); return (
Edit profile
{banner ? ( user's banner ) : (
)}
user's avatar
{nip05.verified ? ( Verified ) : ( Unverified )}
{errors.nip05 && (

{errors.nip05.message.toString()}

)}