diff --git a/package.json b/package.json index 37ae2a7f..9b12f64a 100644 --- a/package.json +++ b/package.json @@ -18,7 +18,6 @@ }, "dependencies": { "@ctrl/magnet-link": "^3.1.2", - "@headlessui/react": "^1.7.17", "@nostr-dev-kit/ndk": "^0.8.21", "@nostr-fetch/adapter-ndk": "^0.12.2", "@radix-ui/react-alert-dialog": "^1.0.4", diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index f5f4f05e..c41e43e7 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -4,9 +4,6 @@ dependencies: '@ctrl/magnet-link': specifier: ^3.1.2 version: 3.1.2 - '@headlessui/react': - specifier: ^1.7.17 - version: 1.7.17(react-dom@18.2.0)(react@18.2.0) '@nostr-dev-kit/ndk': specifier: ^0.8.21 version: 0.8.21(typescript@5.2.2) @@ -872,18 +869,6 @@ packages: resolution: {integrity: sha512-m0G6wlnhm/AX0H12IOWtK8gASEMffnX08RtKkCgTdHb9JpHKGloI7icFfLg9ZmQeavcvR0PKmzxClyuFPSjKWw==} dev: false - /@headlessui/react@1.7.17(react-dom@18.2.0)(react@18.2.0): - resolution: {integrity: sha512-4am+tzvkqDSSgiwrsEpGWqgGo9dz8qU5M3znCkC4PgkpY4HcCZzEDEvozltGGGHIKl9jbXbZPSH5TWn4sWJdow==} - engines: {node: '>=10'} - peerDependencies: - react: ^16 || ^17 || ^18 - react-dom: ^16 || ^17 || ^18 - dependencies: - client-only: 0.0.1 - react: 18.2.0 - react-dom: 18.2.0(react@18.2.0) - dev: false - /@humanwhocodes/config-array@0.11.10: resolution: {integrity: sha512-KVVjQmNUepDVGXNuoRRdmmEjruj0KfiGSbS8LVc12LMsWDQzRXJ0qdhN8L8uUigKpfEHRhlaQFY0ib1tnUbNeQ==} engines: {node: '>=10.10.0'} @@ -3064,10 +3049,6 @@ packages: string-width: 5.1.2 dev: true - /client-only@0.0.1: - resolution: {integrity: sha512-IV3Ou0jSMzZrd3pZ48nLkT9DA7Ag1pnPzaiQhpW7c3RbcqqzvzzVu+L8gfqMp/8IM2MQtSiqaCxrrcfu8I8rMA==} - dev: false - /clone-regexp@3.0.0: resolution: {integrity: sha512-ujdnoq2Kxb8s3ItNBtnYeXdm07FcU0u8ARAT1lQ2YdMwQC+cdiXX8KoqMVuglztILivceTtp4ivqGSmEmhBUJw==} engines: {node: '>=12'} diff --git a/src/app/chats/components/modal.tsx b/src/app/chats/components/modal.tsx index 4e140e33..b0a3747c 100644 --- a/src/app/chats/components/modal.tsx +++ b/src/app/chats/components/modal.tsx @@ -6,7 +6,7 @@ import { User } from '@app/auth/components/user'; import { useStorage } from '@libs/storage/provider'; -import { CancelIcon, LoaderIcon, PlusIcon } from '@shared/icons'; +import { CancelIcon, PlusIcon } from '@shared/icons'; export function NewMessageModal() { const navigate = useNavigate(); @@ -54,29 +54,23 @@ export function NewMessageModal() {
- {status === 'loading' ? ( -
- -
- ) : ( - db.account?.follows?.map((follow) => ( -
- -
- -
+ {db.account?.follows?.map((follow) => ( +
+ +
+
- )) - )} +
+ ))}
diff --git a/src/app/settings/components/autoStart.tsx b/src/app/settings/components/autoStart.tsx index 36877cc5..addaf7d7 100644 --- a/src/app/settings/components/autoStart.tsx +++ b/src/app/settings/components/autoStart.tsx @@ -1,56 +1,10 @@ -import { Switch } from '@headlessui/react'; -import { disable, enable, isEnabled } from '@tauri-apps/plugin-autostart'; -import { useEffect, useState } from 'react'; -import { twMerge } from 'tailwind-merge'; - export function AutoStartSetting() { - const [enabled, setEnabled] = useState(false); - - const toggle = async () => { - if (!enabled) { - await enable(); - // await updateSetting('auto_start', 1); - console.log(`registered for autostart? ${await isEnabled()}`); - } else { - await disable(); - // await updateSetting('auto_start', 0); - } - setEnabled(!enabled); - }; - - useEffect(() => { - async function getAppSetting() { - const setting = '0'; - if (parseInt(setting) === 0) { - setEnabled(false); - } else { - setEnabled(true); - } - } - getAppSetting(); - }, []); - return (
Auto start Auto start at login
- - -
); } diff --git a/src/app/space/components/modals/feed.tsx b/src/app/space/components/modals/feed.tsx deleted file mode 100644 index 0905d6a1..00000000 --- a/src/app/space/components/modals/feed.tsx +++ /dev/null @@ -1,190 +0,0 @@ -import { Combobox } from '@headlessui/react'; -import * as Dialog from '@radix-ui/react-dialog'; -import { nip19 } from 'nostr-tools'; -import { useState } from 'react'; -import { useForm } from 'react-hook-form'; - -import { User } from '@app/auth/components/user'; - -import { useStorage } from '@libs/storage/provider'; - -import { CancelIcon, CheckCircleIcon, CommandIcon, LoaderIcon } from '@shared/icons'; - -import { WidgetKinds, useWidgets } from '@stores/widgets'; - -export function FeedModal() { - const setWidget = useWidgets((state) => state.setWidget); - - const [open, setOpen] = useState(false); - const [loading, setLoading] = useState(false); - const [selected, setSelected] = useState([]); - const [query, setQuery] = useState(''); - - const { db } = useStorage(); - const { - register, - handleSubmit, - reset, - formState: { isDirty, isValid }, - } = useForm(); - - const onSubmit = (data: { kind: number; title: string; content: string }) => { - setLoading(true); - - selected.forEach((item, index) => { - if (item.substring(0, 4) === 'npub') { - selected[index] = nip19.decode(item).data; - } - }); - - // update state - setWidget(db, { - kind: WidgetKinds.feed, - title: data.title, - content: JSON.stringify(selected), - }); - - setLoading(false); - // reset form - reset(); - // close modal - setOpen(false); - }; - - return ( - - - - - - - -
-
-
-
- - Create feed block - - - - -
- - Specific newsfeed space for people you want to keep up to date - -
-
-
-
-
- - -
-
- - Choose at least 1 user * - -
-
- - setQuery(event.target.value)} - spellCheck={false} - placeholder="Enter pubkey or npub..." - className="relative mb-2 h-10 w-full rounded-md bg-white/10 px-3 py-2 text-white !outline-none placeholder:text-white/50" - /> - - {query.length > 0 && ( - - {({ selected }) => ( - <> -
- {query} -
- - {query} - -
-
- {selected && ( - - )} - - )} -
- )} - {db.account?.follows?.map((follow) => ( - - {({ selected }) => ( - <> - - {selected && ( - - )} - - )} - - ))} -
-
-
-
-
- -
-
-
-
-
-
- ); -} diff --git a/src/app/space/components/modals/hashtag.tsx b/src/app/space/components/modals/hashtag.tsx deleted file mode 100644 index 1dced02c..00000000 --- a/src/app/space/components/modals/hashtag.tsx +++ /dev/null @@ -1,119 +0,0 @@ -import * as Dialog from '@radix-ui/react-dialog'; -import { useState } from 'react'; -import { useForm } from 'react-hook-form'; - -import { useStorage } from '@libs/storage/provider'; - -import { CancelIcon, CommandIcon, LoaderIcon } from '@shared/icons'; - -import { WidgetKinds, useWidgets } from '@stores/widgets'; - -export function HashtagModal() { - const setWidget = useWidgets((state) => state.setWidget); - - const [loading, setLoading] = useState(false); - const [open, setOpen] = useState(false); - - const { db } = useStorage(); - const { - register, - handleSubmit, - reset, - formState: { isDirty, isValid }, - } = useForm(); - - const onSubmit = async (data: { hashtag: string }) => { - setLoading(true); - - // update state - setWidget(db, { - kind: WidgetKinds.hashtag, - title: data.hashtag, - content: data.hashtag.replace('#', ''), - }); - - setLoading(false); - // reset form - reset(); - // close modal - setOpen(false); - }; - - return ( - - - - - - - -
-
-
-
- - Create hashtag block - - - - -
- - Pin the hashtag you want to keep follow up - -
-
-
-
-
- - -
- -
-
-
-
-
-
- ); -} diff --git a/src/app/users/components/modal.tsx b/src/app/users/components/modal.tsx new file mode 100644 index 00000000..2cf76878 --- /dev/null +++ b/src/app/users/components/modal.tsx @@ -0,0 +1,295 @@ +import { NDKEvent, NDKUserProfile } from '@nostr-dev-kit/ndk'; +import * as Dialog from '@radix-ui/react-dialog'; +import { useQueryClient } from '@tanstack/react-query'; +import { useEffect, useState } from 'react'; +import { useForm } from 'react-hook-form'; + +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'; + +export function EditProfileModal() { + const queryClient = useQueryClient(); + + 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 [nip05, setNIP05] = useState({ verified: false, text: '' }); + + const { db } = useStorage(); + const { publish } = useNostr(); + 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 url = nip05.split('@'); + const username = url[0]; + const service = url[1]; + const verifyURL = `https://${service}/.well-known/nostr.json?name=${username}`; + + const res = await fetch(verifyURL, { + headers: { + 'Content-Type': 'application/json; charset=utf-8', + }, + }); + + console.log(res); + return true; + }; + + const onSubmit = async (data: NDKUserProfile) => { + // start loading + setLoading(true); + + let event: NDKEvent; + + const content = { + ...data, + username: data.name, + display_name: data.name, + bio: data.about, + image: data.picture, + }; + + if (data.nip05) { + const nip05IsVerified = await verifyNIP05(data.nip05); + if (nip05IsVerified) { + event = await publish({ + content: JSON.stringify({ ...content, nip05: data.nip05 }), + kind: 0, + tags: [], + }); + } else { + setNIP05((prev) => ({ ...prev, verified: false })); + setError('nip05', { + type: 'manual', + message: "Can't verify your Lume ID / NIP-05, please check again", + }); + } + } else { + event = await publish({ + content: JSON.stringify(content), + kind: 0, + tags: [], + }); + } + + if (event.id) { + // invalid cache + queryClient.invalidateQueries(['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()} +

+ )} +
+
+
+ +