This commit is contained in:
Bekbolsun 2024-02-06 22:47:40 +06:00
parent 14940a4345
commit 9d565ddbde
14 changed files with 231 additions and 231 deletions

View File

@ -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 (
<Modal
open={isModalOpened}
<Modal
open={isModalOpened}
withCloseButton={!isPopup}
onClose={!isPopup ? handleCloseModal : undefined}
>
@ -147,16 +146,10 @@ export const ModalConfirmConnect = () => {
/>
</StyledToggleButtonsGroup>
<Stack direction={'row'} gap={'1rem'}>
<StyledButton
onClick={disallow}
varianttype='secondary'
>
<StyledButton onClick={disallow} varianttype='secondary'>
Disallow
</StyledButton>
<StyledButton
fullWidth
onClick={allow}
>
<StyledButton fullWidth onClick={allow}>
{/* Allow {selectedActionType} actions */}
Connect
</StyledButton>

View File

@ -94,10 +94,11 @@ export const ModalConfirmEvent: FC<ModalConfirmEventProps> = ({
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<ModalConfirmEventProps> = ({
onClose: (sp) => {
sp.delete('appNpub')
sp.delete('reqId')
}
}
},
},
)
async function confirmPending(allow: boolean) {
@ -119,7 +120,7 @@ export const ModalConfirmEvent: FC<ModalConfirmEventProps> = ({
})
})
closeModalAfterRequest()
if (isPopup) window.close();
if (isPopup) window.close()
}
const handleChangeCheckbox = (reqId: string) => () => {
@ -141,8 +142,8 @@ export const ModalConfirmEvent: FC<ModalConfirmEventProps> = ({
if (isPopup) {
document.addEventListener('visibilitychange', () => {
if (document.visibilityState == 'hidden') {
confirmPending(false);
if (document.visibilityState === 'hidden') {
confirmPending(false)
}
})
}

View File

@ -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),
})

View File

@ -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 ? (
<>
<CheckmarkIcon /> 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,
},
},
}}

40
src/hooks/useProfile.ts Normal file
View File

@ -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<MetaEvent | null>(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,
}
}

View File

@ -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<MetaEvent | null>(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 (
<StyledAppBar position='fixed'>
@ -41,17 +26,30 @@ export const Header = () => {
alignItems={'center'}
width={'100%'}
>
{showProfile ? (
{showProfile && (
<Stack
gap={'1rem'}
direction={'row'}
alignItems={'center'}
flex={1}
>
<Avatar src={userAvatar} alt={userName} />
<Typography fontWeight={600}>{userName}</Typography>
<Avatar
src={userAvatar}
alt={userName}
onClick={handleNavigate}
>
{avatarTitle}
</Avatar>
<Typography
fontWeight={600}
onClick={handleNavigate}
>
{userName}
</Typography>
</Stack>
) : (
)}
{!showProfile && (
<StyledAppName>
<AppLogo />
<span>Nsec.app</span>

View File

@ -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<ListItemProfileProps> = ({
onClickItem,
npub,
}) => {
const { userName, userAvatar, avatarTitle } = useProfile(npub)
return (
<MenuItem sx={{ gap: '0.5rem' }} onClick={onClickItem}>
<ListItemIcon>
<Avatar
src={userAvatar}
alt={userName}
sx={{ width: 36, height: 36 }}
>
{avatarTitle}
</Avatar>
</ListItemIcon>
<Typography variant='body2' noWrap>
{userName}
</Typography>
</MenuItem>
)
}

View File

@ -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<ListProfilesProps> = ({
return (
<Stack maxHeight={'10rem'} overflow={'auto'}>
{keys.map((key) => {
const userName =
key?.profile?.info?.name || getShortenNpub(key.npub)
const userAvatar = key?.profile?.info?.picture || ''
return (
<MenuItem
sx={{ gap: '0.5rem' }}
onClick={() => onClickItem(key)}
<ListItemProfile
{...key}
key={key.npub}
>
<ListItemIcon>
<Avatar
src={userAvatar}
alt={userName}
sx={{ width: 36, height: 36 }}
/>
</ListItemIcon>
<Typography variant='body2' noWrap>
{userName}
</Typography>
</MenuItem>
onClickItem={() => onClickItem(key)}
/>
)
})}
</Stack>

View File

@ -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<ItemKeyProps> = (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 (
<StyledKeyContainer onClick={handleNavigate}>
<Stack direction={'row'} alignItems={'center'} gap='1rem'>
<Avatar src={userAvatar} alt={userName} />
<Avatar src={userAvatar} alt={userName}>
{avatarTitle}
</Avatar>
<StyledText variant='body1'>{userName}</StyledText>
</Stack>
</StyledKeyContainer>

View File

@ -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(

View File

@ -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<MetaEvent | null>(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,
}
}

View File

@ -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'

View File

@ -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)

View File

@ -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<void>((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<void>((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 ''
}
}
}