From da0b48c5df01db886e062ce99d74943b01b84d56 Mon Sep 17 00:00:00 2001 From: reya Date: Fri, 3 Nov 2023 15:38:58 +0700 Subject: [PATCH] update personal dashboard screen --- src/app.tsx | 23 +- .../auth/components/features/enableCircle.tsx | 2 +- .../auth/components/features/followList.tsx | 2 +- src/app/chats/hooks/useDecryptMessage.tsx | 2 +- src/app/explore/components/userWithDrawer.tsx | 12 +- src/app/personal/components/contactCard.tsx | 23 +- src/app/personal/components/postCard.tsx | 13 +- src/app/personal/components/profileCard.tsx | 14 +- src/app/personal/components/relayCard.tsx | 20 +- src/app/personal/components/zapCard.tsx | 2 +- src/app/personal/editContact.tsx | 55 +++ src/app/personal/editProfile.tsx | 323 ++++++++++++++++++ src/app/personal/index.tsx | 21 +- src/app/users/components/profile.tsx | 12 +- src/libs/ndk/instance.ts | 2 +- src/shared/accounts/active.tsx | 2 +- src/shared/user.tsx | 2 +- src/shared/userProfile.tsx | 12 +- src/shared/widgets/nostrBandUserProfile.tsx | 12 +- src/shared/widgets/notification.tsx | 2 +- src/utils/hooks/useNostr.ts | 2 +- src/utils/hooks/useProfile.ts | 2 +- 22 files changed, 499 insertions(+), 61 deletions(-) create mode 100644 src/app/personal/editContact.tsx create mode 100644 src/app/personal/editProfile.tsx diff --git a/src/app.tsx b/src/app.tsx index 80c86abb..04f75c39 100644 --- a/src/app.tsx +++ b/src/app.tsx @@ -110,13 +110,34 @@ export default function App() { }, ], }, + ], + }, + { + path: '/personal', + element: , + errorElement: , + children: [ { - path: 'personal', + path: '', async lazy() { const { PersonalScreen } = await import('@app/personal'); return { Component: PersonalScreen }; }, }, + { + path: 'edit-profile', + async lazy() { + const { EditProfileScreen } = await import('@app/personal/editProfile'); + return { Component: EditProfileScreen }; + }, + }, + { + path: 'edit-contact', + async lazy() { + const { EditContactScreen } = await import('@app/personal/editContact'); + return { Component: EditContactScreen }; + }, + }, ], }, { diff --git a/src/app/auth/components/features/enableCircle.tsx b/src/app/auth/components/features/enableCircle.tsx index ba653035..42694f7e 100644 --- a/src/app/auth/components/features/enableCircle.tsx +++ b/src/app/auth/components/features/enableCircle.tsx @@ -23,7 +23,7 @@ export function Circle() { const enableLinks = async () => { setLoading(true); - const users = ndk.getUser({ hexpubkey: db.account.pubkey }); + const users = ndk.getUser({ pubkey: db.account.pubkey }); const follows = await users.follows(); if (follows.size === 0) { diff --git a/src/app/auth/components/features/followList.tsx b/src/app/auth/components/features/followList.tsx index 1fcf7909..769c1f52 100644 --- a/src/app/auth/components/features/followList.tsx +++ b/src/app/auth/components/features/followList.tsx @@ -12,7 +12,7 @@ export function FollowList() { const { status, data } = useQuery({ queryKey: ['follows'], queryFn: async () => { - const user = ndk.getUser({ hexpubkey: db.account.pubkey }); + const user = ndk.getUser({ pubkey: db.account.pubkey }); const follows = await user.follows(); const followsAsArr = []; diff --git a/src/app/chats/hooks/useDecryptMessage.tsx b/src/app/chats/hooks/useDecryptMessage.tsx index 5da4ffa1..88e3d00e 100644 --- a/src/app/chats/hooks/useDecryptMessage.tsx +++ b/src/app/chats/hooks/useDecryptMessage.tsx @@ -14,7 +14,7 @@ export function useDecryptMessage(message: NDKEvent) { async function decryptContent() { try { const sender = new NDKUser({ - hexpubkey: + pubkey: db.account.pubkey === message.pubkey ? message.tags.find((el) => el[0] === 'p')[1] : message.pubkey, diff --git a/src/app/explore/components/userWithDrawer.tsx b/src/app/explore/components/userWithDrawer.tsx index a2271c4e..2ab72955 100644 --- a/src/app/explore/components/userWithDrawer.tsx +++ b/src/app/explore/components/userWithDrawer.tsx @@ -29,9 +29,9 @@ export const UserWithDrawer = memo(function UserWithDrawer({ const follow = async (pubkey: string) => { try { - const user = ndk.getUser({ hexpubkey: db.account.pubkey }); + const user = ndk.getUser({ pubkey: db.account.pubkey }); const contacts = await user.follows(); - const add = await user.follow(new NDKUser({ hexpubkey: pubkey }), contacts); + const add = await user.follow(new NDKUser({ pubkey: pubkey }), contacts); if (add) { setFollowed(true); @@ -45,14 +45,12 @@ export const UserWithDrawer = memo(function UserWithDrawer({ const unfollow = async (pubkey: string) => { try { - const user = ndk.getUser({ hexpubkey: db.account.pubkey }); + const user = ndk.getUser({ pubkey: db.account.pubkey }); const contacts = await user.follows(); - contacts.delete(new NDKUser({ hexpubkey: pubkey })); + contacts.delete(new NDKUser({ pubkey: pubkey })); let list: string[][]; - contacts.forEach((el) => - list.push(['p', el.pubkey, el.relayUrls?.[0] || '', el.profile?.name || '']) - ); + contacts.forEach((el) => list.push(['p', el.pubkey, el.relayUrls?.[0] || '', ''])); const event = new NDKEvent(ndk); event.content = ''; diff --git a/src/app/personal/components/contactCard.tsx b/src/app/personal/components/contactCard.tsx index 8d80dd8e..3bc4ba75 100644 --- a/src/app/personal/components/contactCard.tsx +++ b/src/app/personal/components/contactCard.tsx @@ -1,9 +1,10 @@ import { useQuery } from '@tanstack/react-query'; +import { Link } from 'react-router-dom'; import { useNDK } from '@libs/ndk/provider'; import { useStorage } from '@libs/storage/provider'; -import { LoaderIcon } from '@shared/icons'; +import { EditIcon, LoaderIcon } from '@shared/icons'; import { compactNumber } from '@utils/number'; @@ -13,8 +14,9 @@ export function ContactCard() { const { status, data } = useQuery({ queryKey: ['contacts'], queryFn: async () => { - const user = ndk.getUser({ hexpubkey: db.account.pubkey }); - return await user.follows(); + const user = ndk.getUser({ pubkey: db.account.pubkey }); + const follows = await user.follows(); + return [...follows]; }, refetchOnWindowFocus: false, }); @@ -28,10 +30,19 @@ export function ContactCard() { ) : (

- {compactNumber.format(data.size)} + {compactNumber.format(data.length)}

-
- Contacts +
+

+ Contacts +

+ + + Edit +
)} diff --git a/src/app/personal/components/postCard.tsx b/src/app/personal/components/postCard.tsx index 1a9fb05a..2c4a7ecf 100644 --- a/src/app/personal/components/postCard.tsx +++ b/src/app/personal/components/postCard.tsx @@ -1,4 +1,5 @@ import { useQuery } from '@tanstack/react-query'; +import { Link } from 'react-router-dom'; import { useStorage } from '@libs/storage/provider'; @@ -41,8 +42,16 @@ export function PostCard() {

{compactNumber.format(data.stats[db.account.pubkey].pub_note_count)}

-
- Posts +
+

+ Posts +

+ + View +
)} diff --git a/src/app/personal/components/profileCard.tsx b/src/app/personal/components/profileCard.tsx index 16ada6b1..34823d55 100644 --- a/src/app/personal/components/profileCard.tsx +++ b/src/app/personal/components/profileCard.tsx @@ -1,9 +1,10 @@ import * as Avatar from '@radix-ui/react-avatar'; import { minidenticon } from 'minidenticons'; +import { Link } from 'react-router-dom'; import { useStorage } from '@libs/storage/provider'; -import { LoaderIcon } from '@shared/icons'; +import { EditIcon, LoaderIcon } from '@shared/icons'; import { useProfile } from '@utils/hooks/useProfile'; import { displayNpub } from '@utils/shortenKey'; @@ -23,7 +24,16 @@ export function ProfileCard() {
) : ( -
+
+
+ + + Edit + +
{ - const user = ndk.getUser({ hexpubkey: db.account.pubkey }); + const user = ndk.getUser({ pubkey: db.account.pubkey }); return await user.relayList(); }, refetchOnWindowFocus: false, @@ -28,10 +29,19 @@ export function RelayCard() { ) : (

- {compactNumber.format(data.relays.length)} + {compactNumber.format(data?.relays?.length)}

-
- Relays +
+

+ Relays +

+ + + Edit +
)} diff --git a/src/app/personal/components/zapCard.tsx b/src/app/personal/components/zapCard.tsx index 7741553f..e2aa5309 100644 --- a/src/app/personal/components/zapCard.tsx +++ b/src/app/personal/components/zapCard.tsx @@ -43,7 +43,7 @@ export function ZapCard() { data.stats[db.account.pubkey].zaps_received.msats / 1000 )} -
+
Sats received
diff --git a/src/app/personal/editContact.tsx b/src/app/personal/editContact.tsx new file mode 100644 index 00000000..3cb871f1 --- /dev/null +++ b/src/app/personal/editContact.tsx @@ -0,0 +1,55 @@ +import { useQuery } from '@tanstack/react-query'; +import { Link } from 'react-router-dom'; + +import { useNDK } from '@libs/ndk/provider'; +import { useStorage } from '@libs/storage/provider'; + +import { ArrowLeftIcon, LoaderIcon } from '@shared/icons'; +import { User } from '@shared/user'; + +export function EditContactScreen() { + const { db } = useStorage(); + const { ndk } = useNDK(); + const { status, data } = useQuery({ + queryKey: ['contacts'], + queryFn: async () => { + const user = ndk.getUser({ pubkey: db.account.pubkey }); + + const follows = await user.follows(); + return [...follows]; + }, + refetchOnWindowFocus: false, + }); + + return ( +
+
+ + + Back + +

Contact Manager

+
+
+
+ {status === 'pending' ? ( +
+ +
+ ) : ( + data.map((item) => ( +
+ +
+ )) + )} +
+
+ ); +} diff --git a/src/app/personal/editProfile.tsx b/src/app/personal/editProfile.tsx new file mode 100644 index 00000000..8d2d1a5d --- /dev/null +++ b/src/app/personal/editProfile.tsx @@ -0,0 +1,323 @@ +import { NDKEvent, NDKKind, NDKUserProfile } from '@nostr-dev-kit/ndk'; +import { useQueryClient } from '@tanstack/react-query'; +import { message } from '@tauri-apps/plugin-dialog'; +import { useState } from 'react'; +import { useForm } from 'react-hook-form'; +import { Link } from 'react-router-dom'; + +import { useNDK } from '@libs/ndk/provider'; +import { useStorage } from '@libs/storage/provider'; + +import { + ArrowLeftIcon, + CheckCircleIcon, + LoaderIcon, + PlusIcon, + UnverifiedIcon, +} from '@shared/icons'; + +import { useNostr } from '@utils/hooks/useNostr'; + +export function EditProfileScreen() { + const queryClient = useQueryClient(); + + const [loading, setLoading] = useState(false); + const [picture, setPicture] = useState(''); + const [banner, setBanner] = useState(''); + const [nip05, setNIP05] = useState({ verified: true, text: '' }); + + const { db } = useStorage(); + const { ndk } = useNDK(); + const { upload } = 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 uploadAvatar = async () => { + try { + setLoading(true); + + const image = await upload(); + + if (image) { + setPicture(image); + setLoading(false); + } + } catch (e) { + setLoading(false); + await message(`Upload failed, error: ${e}`, { title: 'Lume', type: 'error' }); + } + }; + + const uploadBanner = async () => { + try { + setLoading(true); + + const image = await upload(); + + if (image) { + setBanner(image); + setLoading(false); + } + } catch (e) { + 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 user = ndk.getUser({ pubkey: db.account.pubkey }); + const verify = await user.validateNip05(data.nip05); + if (verify) { + 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); + setPicture(null); + setBanner(null); + } else { + setLoading(false); + } + }; + + return ( +
+
+ + + Back + +

Edit Profile

+
+
+
+
+ + +
+
+ {banner ? ( + user's banner + ) : ( +
+ )} +
+ +
+
+
+
+ user's avatar +
+ +
+
+
+
+
+
+ + +
+
+ + +
+
+ +
+ +
+ {nip05.verified ? ( + + + Verified + + ) : ( + + + Unverified + + )} +
+ {errors.nip05 && ( +

+ {errors.nip05.message.toString()} +

+ )} +
+
+
+ + +
+
+ + +
+
+ +