From 9d565ddbde88f8624ea395a4642b18842015e5f3 Mon Sep 17 00:00:00 2001 From: Bekbolsun Date: Tue, 6 Feb 2024 22:47:40 +0600 Subject: [PATCH] save --- .../ModalConfirmConnect.tsx | 33 ++-- .../ModalConfirmEvent/ModalConfirmEvent.tsx | 17 +- src/components/Modal/ModalLogin/const.ts | 12 +- .../Modal/ModalSignUp/ModalSignUp.tsx | 25 ++- src/hooks/useProfile.ts | 40 +++++ src/layout/Header/Header.tsx | 52 +++--- .../Header/components/ListItemProfile.tsx | 31 ++++ src/layout/Header/components/ListProfiles.tsx | 34 +--- src/pages/HomePage/components/ItemKey.tsx | 12 +- src/pages/KeyPage/Key.Page.tsx | 17 +- src/pages/KeyPage/hooks/useProfile.ts | 31 ---- src/routes/AppRoutes.tsx | 1 - src/store/index.ts | 4 + src/utils/helpers/helpers.ts | 153 +++++++++--------- 14 files changed, 231 insertions(+), 231 deletions(-) create mode 100644 src/hooks/useProfile.ts create mode 100644 src/layout/Header/components/ListItemProfile.tsx delete mode 100644 src/pages/KeyPage/hooks/useProfile.ts diff --git a/src/components/Modal/ModalConfirmConnect/ModalConfirmConnect.tsx b/src/components/Modal/ModalConfirmConnect/ModalConfirmConnect.tsx index 4377002..b3439de 100644 --- a/src/components/Modal/ModalConfirmConnect/ModalConfirmConnect.tsx +++ b/src/components/Modal/ModalConfirmConnect/ModalConfirmConnect.tsx @@ -12,7 +12,6 @@ import { useState } from 'react' import { swicCall } from '@/modules/swic' import { ACTION_TYPE } from '@/utils/consts' - export const ModalConfirmConnect = () => { const { getModalOpened, createHandleCloseReplace } = useModalSearchParams() const isModalOpened = getModalOpened(MODAL_PARAMS_KEYS.CONFIRM_CONNECT) @@ -45,7 +44,7 @@ export const ModalConfirmConnect = () => { sp.delete('appNpub') sp.delete('reqId') await swicCall('confirm', pendingReqId, false, false) - } + }, }, ) const closeModalAfterRequest = createHandleCloseReplace( @@ -55,27 +54,27 @@ export const ModalConfirmConnect = () => { sp.delete('appNpub') sp.delete('reqId') }, - } + }, ) async function confirmPending( id: string, allow: boolean, remember: boolean, - options?: any + options?: any, ) { call(async () => { await swicCall('confirm', id, allow, remember, options) console.log('confirmed', id, allow, remember, options) closeModalAfterRequest() }) - if (isPopup) window.close(); + if (isPopup) window.close() } const allow = () => { - const options: any = {}; + const options: any = {} if (selectedActionType === ACTION_TYPE.BASIC) - options.perms = [ACTION_TYPE.BASIC]; + options.perms = [ACTION_TYPE.BASIC] // else // options.perms = ['connect','get_public_key']; confirmPending(pendingReqId, true, true, options) @@ -86,16 +85,16 @@ export const ModalConfirmConnect = () => { } if (isPopup) { - document.addEventListener('visibilitychange', function() { - if (document.visibilityState == 'hidden') { - disallow(); + document.addEventListener('visibilitychange', function () { + if (document.visibilityState === 'hidden') { + disallow() } }) } return ( - @@ -147,16 +146,10 @@ export const ModalConfirmConnect = () => { /> - + Disallow - + {/* Allow {selectedActionType} actions */} Connect diff --git a/src/components/Modal/ModalConfirmEvent/ModalConfirmEvent.tsx b/src/components/Modal/ModalConfirmEvent/ModalConfirmEvent.tsx index 21550d2..8fe00ec 100644 --- a/src/components/Modal/ModalConfirmEvent/ModalConfirmEvent.tsx +++ b/src/components/Modal/ModalConfirmEvent/ModalConfirmEvent.tsx @@ -94,10 +94,11 @@ export const ModalConfirmEvent: FC = ({ sp.delete('appNpub') sp.delete('reqId') selectedPendingRequests.forEach( - async (req) => await swicCall('confirm', req.id, false, false), + async (req) => + await swicCall('confirm', req.id, false, false), ) - } - } + }, + }, ) const closeModalAfterRequest = createHandleCloseReplace( @@ -106,8 +107,8 @@ export const ModalConfirmEvent: FC = ({ onClose: (sp) => { sp.delete('appNpub') sp.delete('reqId') - } - } + }, + }, ) async function confirmPending(allow: boolean) { @@ -119,7 +120,7 @@ export const ModalConfirmEvent: FC = ({ }) }) closeModalAfterRequest() - if (isPopup) window.close(); + if (isPopup) window.close() } const handleChangeCheckbox = (reqId: string) => () => { @@ -141,8 +142,8 @@ export const ModalConfirmEvent: FC = ({ if (isPopup) { document.addEventListener('visibilitychange', () => { - if (document.visibilityState == 'hidden') { - confirmPending(false); + if (document.visibilityState === 'hidden') { + confirmPending(false) } }) } diff --git a/src/components/Modal/ModalLogin/const.ts b/src/components/Modal/ModalLogin/const.ts index ffba1f3..f8c6e1f 100644 --- a/src/components/Modal/ModalLogin/const.ts +++ b/src/components/Modal/ModalLogin/const.ts @@ -1,17 +1,7 @@ import * as yup from 'yup' export const schema = yup.object().shape({ - username: yup - .string() - .test('Domain validation', 'The domain is required!', function (value) { - if (!value || !value.trim().length) return false - - const USERNAME_WITH_DOMAIN_REGEXP = new RegExp( - /^[\w-.]+@([\w-]+\.)+[\w-]{2,8}$/g, - ) - return USERNAME_WITH_DOMAIN_REGEXP.test(value) - }) - .required(), + username: yup.string().required(), password: yup.string().required().min(4), }) diff --git a/src/components/Modal/ModalSignUp/ModalSignUp.tsx b/src/components/Modal/ModalSignUp/ModalSignUp.tsx index 4920b23..07ff73b 100644 --- a/src/components/Modal/ModalSignUp/ModalSignUp.tsx +++ b/src/components/Modal/ModalSignUp/ModalSignUp.tsx @@ -10,7 +10,7 @@ import { Button } from '@/shared/Button/Button' import { CheckmarkIcon } from '@/assets' import { swicCall } from '@/modules/swic' import { useNavigate } from 'react-router-dom' -import { DOMAIN, NOAUTHD_URL } from '@/utils/consts' +import { DOMAIN } from '@/utils/consts' import { fetchNip05 } from '@/utils/helpers/helpers' export const ModalSignUp = () => { @@ -36,20 +36,17 @@ export const ModalSignUp = () => { } } - const inputHelperText = enteredValue - ? ( + const inputHelperText = enteredValue ? ( isAvailable ? ( <> Available ) : ( - <> - Already taken - + <>Already taken ) ) : ( "Don't worry, username can be changed later." - ); + ) const handleSubmit = async (e: React.FormEvent) => { const name = enteredValue.trim() @@ -96,13 +93,13 @@ export const ModalSignUp = () => { helperTextProps={{ sx: { '&.helper_text': { - color: enteredValue && isAvailable - ? theme.palette.success.main - : (enteredValue && !isAvailable - ? theme.palette.error.main - : theme.palette.textSecondaryDecorate.main - ) - , + color: + enteredValue && isAvailable + ? theme.palette.success.main + : enteredValue && !isAvailable + ? theme.palette.error.main + : theme.palette + .textSecondaryDecorate.main, }, }, }} diff --git a/src/hooks/useProfile.ts b/src/hooks/useProfile.ts new file mode 100644 index 0000000..c3b4554 --- /dev/null +++ b/src/hooks/useProfile.ts @@ -0,0 +1,40 @@ +import { useCallback, useEffect, useState } from 'react' +import { fetchProfile } from '@/modules/nostr' +import { MetaEvent } from '@/types/meta-event' +import { getProfileUsername, getShortenNpub } from '@/utils/helpers/helpers' +import { useAppSelector } from '@/store/hooks/redux' +import { selectKeyByNpub } from '@/store' + +const getFirstLetter = (text: string | undefined): string | null => { + if (!text || text.trim().length === 0) return null + return text.substring(0, 1).toUpperCase() +} + +export const useProfile = (npub: string) => { + const [profile, setProfile] = useState(null) + const currentKey = useAppSelector((state) => selectKeyByNpub(state, npub)) + + const userName = getProfileUsername(profile) || currentKey?.name + const userAvatar = profile?.info?.picture || '' + const avatarTitle = getFirstLetter(userName) + + const loadProfile = useCallback(async () => { + try { + const response = await fetchProfile(npub) + setProfile(response) + } catch (error) { + console.error('Failed to fetch profile:', error) + } + }, [npub]) + + useEffect(() => { + loadProfile() + }, [loadProfile]) + + return { + profile, + userName: userName || getShortenNpub(npub), + userAvatar, + avatarTitle, + } +} diff --git a/src/layout/Header/Header.tsx b/src/layout/Header/Header.tsx index 2b770c8..d43ea00 100644 --- a/src/layout/Header/Header.tsx +++ b/src/layout/Header/Header.tsx @@ -2,35 +2,20 @@ import { Avatar, Stack, Toolbar, Typography } from '@mui/material' import { AppLogo } from '../../assets' import { StyledAppBar, StyledAppName } from './styled' import { Menu } from './components/Menu' -import { useParams } from 'react-router-dom' -import { useCallback, useEffect, useState } from 'react' -import { MetaEvent } from '@/types/meta-event' -import { fetchProfile } from '@/modules/nostr' +import { useNavigate, useParams } from 'react-router-dom' import { ProfileMenu } from './components/ProfileMenu' -import { getShortenNpub } from '@/utils/helpers/helpers' +import { useProfile } from '@/hooks/useProfile' export const Header = () => { const { npub = '' } = useParams<{ npub: string }>() - const [profile, setProfile] = useState(null) + const { userName, userAvatar, avatarTitle } = useProfile(npub) + const showProfile = Boolean(npub) - const load = useCallback(async () => { - if (!npub) return setProfile(null) + const navigate = useNavigate() - try { - const response = await fetchProfile(npub) - setProfile(response as any) - } catch (e) { - return setProfile(null) - } - }, [npub]) - - useEffect(() => { - load() - }, [load]) - - const showProfile = Boolean(npub || profile) - const userName = profile?.info?.name || getShortenNpub(npub) - const userAvatar = profile?.info?.picture || '' + const handleNavigate = () => { + navigate(`/key/${npub}`) + } return ( @@ -41,17 +26,30 @@ export const Header = () => { alignItems={'center'} width={'100%'} > - {showProfile ? ( + {showProfile && ( - - {userName} + + {avatarTitle} + + + {userName} + - ) : ( + )} + + {!showProfile && ( Nsec.app diff --git a/src/layout/Header/components/ListItemProfile.tsx b/src/layout/Header/components/ListItemProfile.tsx new file mode 100644 index 0000000..f0de331 --- /dev/null +++ b/src/layout/Header/components/ListItemProfile.tsx @@ -0,0 +1,31 @@ +import { useProfile } from '@/hooks/useProfile' +import { DbKey } from '@/modules/db' +import { Avatar, ListItemIcon, MenuItem, Typography } from '@mui/material' +import React, { FC } from 'react' + +type ListItemProfileProps = { + onClickItem: () => void +} & DbKey + +export const ListItemProfile: FC = ({ + onClickItem, + npub, +}) => { + const { userName, userAvatar, avatarTitle } = useProfile(npub) + return ( + + + + {avatarTitle} + + + + {userName} + + + ) +} diff --git a/src/layout/Header/components/ListProfiles.tsx b/src/layout/Header/components/ListProfiles.tsx index fbc1c2e..70318e1 100644 --- a/src/layout/Header/components/ListProfiles.tsx +++ b/src/layout/Header/components/ListProfiles.tsx @@ -1,13 +1,7 @@ import { DbKey } from '@/modules/db' -import { getShortenNpub } from '@/utils/helpers/helpers' -import { - Avatar, - ListItemIcon, - MenuItem, - Stack, - Typography, -} from '@mui/material' -import React, { FC } from 'react' +import { Stack } from '@mui/material' +import { FC } from 'react' +import { ListItemProfile } from './ListItemProfile' type ListProfilesProps = { keys: DbKey[] @@ -21,26 +15,12 @@ export const ListProfiles: FC = ({ return ( {keys.map((key) => { - const userName = - key?.profile?.info?.name || getShortenNpub(key.npub) - const userAvatar = key?.profile?.info?.picture || '' return ( - onClickItem(key)} + - - - - - {userName} - - + onClickItem={() => onClickItem(key)} + /> ) })} diff --git a/src/pages/HomePage/components/ItemKey.tsx b/src/pages/HomePage/components/ItemKey.tsx index 9c2a236..89b2802 100644 --- a/src/pages/HomePage/components/ItemKey.tsx +++ b/src/pages/HomePage/components/ItemKey.tsx @@ -8,26 +8,26 @@ import { TypographyProps, styled, } from '@mui/material' -import { getShortenNpub } from '../../../utils/helpers/helpers' import { useNavigate } from 'react-router-dom' +import { useProfile } from '@/hooks/useProfile' type ItemKeyProps = DbKey export const ItemKey: FC = (props) => { - const { npub, profile } = props + const { npub } = props const navigate = useNavigate() + const { userName, userAvatar, avatarTitle } = useProfile(npub) const handleNavigate = () => { navigate('/key/' + npub) } - const { name = '', picture = '' } = profile?.info || {} - const userName = name || getShortenNpub(npub) - const userAvatar = picture || '' return ( - + + {avatarTitle} + {userName} diff --git a/src/pages/KeyPage/Key.Page.tsx b/src/pages/KeyPage/Key.Page.tsx index 7909ccf..5f3dd4a 100644 --- a/src/pages/KeyPage/Key.Page.tsx +++ b/src/pages/KeyPage/Key.Page.tsx @@ -11,7 +11,6 @@ import { ModalSettings } from '@/components/Modal/ModalSettings/ModalSettings' import { ModalExplanation } from '@/components/Modal/ModalExplanation/ModalExplanation' import { ModalConfirmConnect } from '@/components/Modal/ModalConfirmConnect/ModalConfirmConnect' import { ModalConfirmEvent } from '@/components/Modal/ModalConfirmEvent/ModalConfirmEvent' -import { useProfile } from './hooks/useProfile' import { useBackgroundSigning } from './hooks/useBackgroundSigning' import { BackgroundSigningWarning } from './components/BackgroundSigningWarning' import UserValueSection from './components/UserValueSection' @@ -22,23 +21,23 @@ import { DOMAIN } from '@/utils/consts' const KeyPage = () => { const { npub = '' } = useParams<{ npub: string }>() - const { keys, apps, pending, perms } = useAppSelector((state) => state.content) + const { keys, apps, pending, perms } = useAppSelector( + (state) => state.content, + ) const isSynced = useLiveQuery(checkNpubSyncQuerier(npub), [npub], false) const { handleOpen } = useModalSearchParams() - // const { userNameWithPrefix } = useProfile(npub) const { handleEnableBackground, showWarning, isEnabling } = useBackgroundSigning() - const key = keys.find(k => k.npub === npub) + const key = keys.find((k) => k.npub === npub) + let username = '' if (key?.name) { - if (key.name.includes('@')) - username = key.name - else - username = `${key?.name}@${DOMAIN}` - } + if (key.name.includes('@')) username = key.name + else username = `${key?.name}@${DOMAIN}` + } const filteredApps = apps.filter((a) => a.npub === npub) const { prepareEventPendings } = useTriggerConfirmModal( diff --git a/src/pages/KeyPage/hooks/useProfile.ts b/src/pages/KeyPage/hooks/useProfile.ts deleted file mode 100644 index 32ff456..0000000 --- a/src/pages/KeyPage/hooks/useProfile.ts +++ /dev/null @@ -1,31 +0,0 @@ -import { useCallback, useEffect, useState } from 'react' -import { fetchProfile } from '@/modules/nostr' -import { MetaEvent } from '@/types/meta-event' -import { getProfileUsername } from '@/utils/helpers/helpers' -import { DOMAIN } from '@/utils/consts' - -export const useProfile = (npub: string) => { - const [profile, setProfile] = useState(null) - - const userName = getProfileUsername(profile, npub) - // FIXME use nip05? - const userNameWithPrefix = userName + '@' + DOMAIN - - const loadProfile = useCallback(async () => { - try { - const response = await fetchProfile(npub) - setProfile(response) - } catch (error) { - console.error('Failed to fetch profile:', error) - } - }, [npub]) - - useEffect(() => { - loadProfile() - }, [loadProfile]) - - return { - profile, - userNameWithPrefix, - } -} diff --git a/src/routes/AppRoutes.tsx b/src/routes/AppRoutes.tsx index ee3b6c9..4111569 100644 --- a/src/routes/AppRoutes.tsx +++ b/src/routes/AppRoutes.tsx @@ -1,7 +1,6 @@ import { Suspense, lazy } from 'react' import { Route, Routes, Navigate } from 'react-router-dom' import HomePage from '../pages/HomePage/Home.Page' -import WelcomePage from '../pages/Welcome.Page' import { Layout } from '../layout/Layout' import { CircularProgress, Stack } from '@mui/material' diff --git a/src/store/index.ts b/src/store/index.ts index a7b2938..e942507 100644 --- a/src/store/index.ts +++ b/src/store/index.ts @@ -44,6 +44,10 @@ export type AppDispatch = typeof store.dispatch export const selectKeys = (state: RootState) => state.content.keys +export const selectKeyByNpub = (state: RootState, npub: string) => { + return state.content.keys.find((key) => key.npub === npub) +} + export const selectAppsByNpub = memoizeOne((state: RootState, npub: string) => { return state.content.apps.filter((app) => app.npub === npub) }, isDeepEqual) diff --git a/src/utils/helpers/helpers.ts b/src/utils/helpers/helpers.ts index bc8d343..d8d6735 100644 --- a/src/utils/helpers/helpers.ts +++ b/src/utils/helpers/helpers.ts @@ -1,101 +1,100 @@ -import { nip19 } from "nostr-tools"; -import { ACTION_TYPE, NIP46_RELAYS } from "../consts"; -import { DbPending } from "@/modules/db"; -import { MetaEvent } from "@/types/meta-event"; +import { nip19 } from 'nostr-tools' +import { ACTION_TYPE, NIP46_RELAYS } from '../consts' +import { DbPending } from '@/modules/db' +import { MetaEvent } from '@/types/meta-event' export async function call(cb: () => any) { - try { - return await cb(); - } catch (e) { - console.log(`Error: ${e}`); - } + try { + return await cb() + } catch (e) { + console.log(`Error: ${e}`) + } } -export const getShortenNpub = (npub = "") => { - return npub.substring(0, 10) + "..." + npub.slice(-4); -}; +export const getShortenNpub = (npub = '') => { + return npub.substring(0, 10) + '...' + npub.slice(-4) +} -export const getProfileUsername = (profile: MetaEvent | null, npub: string) => { - return ( - profile?.info?.name || profile?.info?.display_name || getShortenNpub(npub) - ); -}; +export const getProfileUsername = (profile: MetaEvent | null) => { + if (!profile) return null + return profile?.info?.name || profile?.info?.display_name +} -export const getBunkerLink = (npub = "") => { - if (!npub) return ""; - const { data: pubkey } = nip19.decode(npub); - return `bunker://${pubkey}?relay=${NIP46_RELAYS[0]}`; -}; +export const getBunkerLink = (npub = '') => { + if (!npub) return '' + const { data: pubkey } = nip19.decode(npub) + return `bunker://${pubkey}?relay=${NIP46_RELAYS[0]}` +} export async function askNotificationPermission() { - return new Promise((ok, rej) => { - // Let's check if the browser supports notifications - if (!("Notification" in window)) { - rej("This browser does not support notifications."); - } else { - Notification.requestPermission().then(() => { - if (Notification.permission === "granted") ok(); - else rej(); - }); - } - }); + return new Promise((ok, rej) => { + // Let's check if the browser supports notifications + if (!('Notification' in window)) { + rej('This browser does not support notifications.') + } else { + Notification.requestPermission().then(() => { + if (Notification.permission === 'granted') ok() + else rej() + }) + } + }) } export function getSignReqKind(req: DbPending): number | undefined { - try { - const data = JSON.parse(JSON.parse(req.params)[0]); - return data.kind; - } catch {} - return undefined; + try { + const data = JSON.parse(JSON.parse(req.params)[0]) + return data.kind + } catch {} + return undefined } export function getReqPerm(req: DbPending): string { - if (req.method === "sign_event") { - const kind = getSignReqKind(req); - if (kind !== undefined) return `${req.method}:${kind}`; - } - return req.method; + if (req.method === 'sign_event') { + const kind = getSignReqKind(req) + if (kind !== undefined) return `${req.method}:${kind}` + } + return req.method } export function isPackagePerm(perm: string, reqPerm: string) { - if (perm === ACTION_TYPE.BASIC) { - switch (reqPerm) { - case "connect": - case "get_public_key": - case "nip04_decrypt": - case "nip04_encrypt": - case "sign_event:0": - case "sign_event:1": - case "sign_event:3": - case "sign_event:6": - case "sign_event:7": - case "sign_event:9734": - case "sign_event:10002": - case "sign_event:30023": - case "sign_event:10000": - return true; - } - } - return false; + if (perm === ACTION_TYPE.BASIC) { + switch (reqPerm) { + case 'connect': + case 'get_public_key': + case 'nip04_decrypt': + case 'nip04_encrypt': + case 'sign_event:0': + case 'sign_event:1': + case 'sign_event:3': + case 'sign_event:6': + case 'sign_event:7': + case 'sign_event:9734': + case 'sign_event:10002': + case 'sign_event:30023': + case 'sign_event:10000': + return true + } + } + return false } export async function fetchNip05(value: string, origin?: string) { - try { - const [username, domain] = value.split("@"); + try { + const [username, domain] = value.split('@') if (!origin) origin = `https://${domain}` - const response = await fetch( - `${origin}/.well-known/nostr.json?name=${username}` - ); - const getNpub: { - names: { - [name: string]: string; - }; - } = await response.json(); + const response = await fetch( + `${origin}/.well-known/nostr.json?name=${username}`, + ) + const getNpub: { + names: { + [name: string]: string + } + } = await response.json() - const pubkey = getNpub.names[username]; - return nip19.npubEncode(pubkey); - } catch (e) { - console.log("Failed to fetch nip05", value, "error: " + e); + const pubkey = getNpub.names[username] + return nip19.npubEncode(pubkey) + } catch (e) { + console.log('Failed to fetch nip05', value, 'error: ' + e) return '' - } + } }