reject penging requests on close modals & confirm only selected pending reqs in ConfirmEvent

This commit is contained in:
Bekbolsun 2024-01-22 21:27:23 +06:00
parent cb70f41010
commit eff6792d64
16 changed files with 196 additions and 100 deletions

View File

@ -0,0 +1,4 @@
<svg width="26" height="26" viewBox="0 0 26 26" fill="none" xmlns="http://www.w3.org/2000/svg">
<rect x="1" y="1" width="24" height="24" rx="8" stroke="white" stroke-opacity="0.33" stroke-width="1.4"/>
<path d="M19 9L11 17L7 13" stroke="white" stroke-width="1.4" stroke-linecap="round" stroke-linejoin="round"/>
</svg>

After

Width:  |  Height:  |  Size: 319 B

View File

@ -1,4 +1,4 @@
<svg width="26" height="26" viewBox="0 0 26 26" fill="none" xmlns="http://www.w3.org/2000/svg">
<rect x="1" y="1" width="24" height="24" rx="8" stroke="white" stroke-opacity="0.33" stroke-width="1.4"/>
<path d="M19 9L11 17L7 13" stroke="white" stroke-width="1.4" stroke-linecap="round" stroke-linejoin="round"/>
<rect x="1" y="1" width="24" height="24" rx="8" stroke="black" stroke-opacity="0.33" stroke-width="1.4"/>
<path d="M19 9L11 17L7 13" stroke="black" stroke-width="1.4" stroke-linecap="round" stroke-linejoin="round"/>
</svg>

Before

Width:  |  Height:  |  Size: 319 B

After

Width:  |  Height:  |  Size: 319 B

View File

@ -0,0 +1,3 @@
<svg width="26" height="26" viewBox="0 0 26 26" fill="none" xmlns="http://www.w3.org/2000/svg">
<rect x="1" y="1" width="24" height="24" rx="8" stroke="white" stroke-opacity="0.33" stroke-width="1.4"/>
</svg>

After

Width:  |  Height:  |  Size: 209 B

View File

@ -1,3 +1,3 @@
<svg width="26" height="26" viewBox="0 0 26 26" fill="none" xmlns="http://www.w3.org/2000/svg">
<rect x="1" y="1" width="24" height="24" rx="8" stroke="white" stroke-opacity="0.33" stroke-width="1.4"/>
<rect x="1" y="1" width="24" height="24" rx="8" stroke="black" stroke-opacity="0.33" stroke-width="1.4"/>
</svg>

Before

Width:  |  Height:  |  Size: 209 B

After

Width:  |  Height:  |  Size: 209 B

View File

@ -4,7 +4,9 @@ import { ReactComponent as SettingsIcon } from './icons/settings.svg'
import { ReactComponent as CopyIcon } from './icons/copy.svg'
import { ReactComponent as CheckmarkIcon } from './icons/checkmark.svg'
import { ReactComponent as CheckedIcon } from './icons/checked.svg'
import { ReactComponent as CheckedLightIcon } from './icons/checked-light.svg'
import { ReactComponent as UnchekedIcon } from './icons/unchecked.svg'
import { ReactComponent as UnchekedLightIcon } from './icons/unchecked-light.svg'
import { default as AddImageIcon } from './icons/add-image.svg'
export {
@ -14,6 +16,8 @@ export {
CopyIcon,
CheckmarkIcon,
CheckedIcon,
CheckedLightIcon,
UnchekedIcon,
UnchekedLightIcon,
AddImageIcon,
}

View File

@ -5,7 +5,7 @@ import { call, getShortenNpub } from '@/utils/helpers'
import { Avatar, Box, Stack, Typography } from '@mui/material'
import { useParams, useSearchParams } from 'react-router-dom'
import { useAppSelector } from '@/store/hooks/redux'
import { selectAppsByNpub, selectPendingsByNpub } from '@/store'
import { selectAppsByNpub } from '@/store'
import { StyledButton, StyledToggleButtonsGroup } from './styled'
import { ActionToggleButton } from './сomponents/ActionToggleButton'
import { useState } from 'react'
@ -21,38 +21,34 @@ export const ModalConfirmConnect = () => {
const { getModalOpened, handleClose } = useModalSearchParams()
const isModalOpened = getModalOpened(MODAL_PARAMS_KEYS.CONFIRM_CONNECT)
const handleCloseModal = handleClose(
MODAL_PARAMS_KEYS.CONFIRM_CONNECT,
(sp) => {
sp.delete('appNpub')
},
)
const { npub = '' } = useParams<{ npub: string }>()
const apps = useAppSelector((state) => selectAppsByNpub(state, npub))
const [selectedActionType, setSelectedActionType] = useState<ACTION_TYPE>(
ACTION_TYPE.BASIC,
)
const { npub = '' } = useParams<{ npub: string }>()
const apps = useAppSelector((state) => selectAppsByNpub(state, npub))
const pending = useAppSelector((state) => selectPendingsByNpub(state, npub))
const [searchParams] = useSearchParams()
const appNpub = searchParams.get('appNpub') || ''
const pendingReqId = searchParams.get('reqId') || ''
const triggerApp = apps.find((app) => app.appNpub === appNpub)
const open = Boolean(isModalOpened)
const { name, icon = '' } = triggerApp || {}
const appName = name || getShortenNpub(appNpub)
const handleActionTypeChange = (_: any, value: ACTION_TYPE) => {
setSelectedActionType(value)
}
const handleCloseModal = handleClose(
MODAL_PARAMS_KEYS.CONFIRM_CONNECT,
async (sp) => {
sp.delete('appNpub')
sp.delete('reqId')
await swicCall('confirm', pendingReqId, false, false)
},
)
async function confirmPending(
id: string,
allow: boolean,
@ -62,11 +58,14 @@ export const ModalConfirmConnect = () => {
await swicCall('confirm', id, allow, remember)
console.log('confirmed', id, allow, remember)
})
handleCloseModal()
handleClose(MODAL_PARAMS_KEYS.CONFIRM_CONNECT, async (sp) => {
sp.delete('appNpub')
sp.delete('reqId')
})
}
return (
<Modal open={open} onClose={handleCloseModal}>
<Modal open={isModalOpened} onClose={handleCloseModal}>
<Stack gap={'1rem'} paddingTop={'1rem'}>
<Stack
direction={'row'}
@ -123,7 +122,9 @@ export const ModalConfirmConnect = () => {
</StyledButton>
<StyledButton
fullWidth
onClick={() => confirmPending(pendingReqId, true, true)}
onClick={() =>
confirmPending(pendingReqId, true, false)
}
>
Allow {selectedActionType} actions
</StyledButton>

View File

@ -5,7 +5,6 @@ import { call, getShortenNpub } from '@/utils/helpers'
import {
Avatar,
Box,
Checkbox,
List,
ListItem,
ListItemIcon,
@ -17,7 +16,7 @@ import { useParams, useSearchParams } from 'react-router-dom'
import { useAppSelector } from '@/store/hooks/redux'
import { selectAppsByNpub } from '@/store'
import { ActionToggleButton } from './сomponents/ActionToggleButton'
import { FC, useState } from 'react'
import { FC, useEffect, useMemo, useState } from 'react'
import {
StyledActionsListContainer,
StyledButton,
@ -26,6 +25,8 @@ import {
import { SectionTitle } from '@/shared/SectionTitle/SectionTitle'
import { swicCall } from '@/modules/swic'
import { IPendingsByAppNpub } from '@/pages/KeyPage/Key.Page'
import { Checkbox } from '@/shared/Checkbox/Checkbox'
import { DbPending } from '@/modules/db'
enum ACTION_TYPE {
ALWAYS = 'ALWAYS',
@ -48,59 +49,86 @@ export const ACTIONS: { [type: string]: string } = {
sign_event: 'Sign event',
}
type PendingRequest = DbPending & { checked: boolean }
export const ModalConfirmEvent: FC<ModalConfirmEventProps> = ({
confirmEventReqs,
}) => {
const { getModalOpened, handleClose } = useModalSearchParams()
const isModalOpened = getModalOpened(MODAL_PARAMS_KEYS.CONFIRM_EVENT)
const handleCloseModal = handleClose(
MODAL_PARAMS_KEYS.CONFIRM_EVENT,
(sp) => sp.delete('appNpub'),
)
const [selectedActionType, setSelectedActionType] = useState<ACTION_TYPE>(
ACTION_TYPE.ALWAYS,
)
const { npub = '' } = useParams<{ npub: string }>()
const apps = useAppSelector((state) => selectAppsByNpub(state, npub))
const [searchParams] = useSearchParams()
const appNpub = searchParams.get('appNpub') || ''
const pendingReqId = searchParams.get('reqId') || ''
const currentAppPendingReqs = confirmEventReqs[appNpub]?.pending || []
const { npub = '' } = useParams<{ npub: string }>()
const apps = useAppSelector((state) => selectAppsByNpub(state, npub))
const [selectedActionType, setSelectedActionType] = useState<ACTION_TYPE>(
ACTION_TYPE.ALWAYS,
)
const [pendingRequests, setPendingRequests] = useState<PendingRequest[]>([])
const currentAppPendingReqs = useMemo(
() => confirmEventReqs[appNpub]?.pending || [],
[confirmEventReqs, appNpub],
)
useEffect(() => {
setPendingRequests(
currentAppPendingReqs.map((pr) => ({ ...pr, checked: true })),
)
}, [currentAppPendingReqs])
const triggerApp = apps.find((app) => app.appNpub === appNpub)
const open = Boolean(isModalOpened)
const { name, icon = '' } = triggerApp || {}
const appName = name || getShortenNpub(appNpub)
const handleActionTypeChange = (_: any, value: ACTION_TYPE) => {
setSelectedActionType(value)
}
async function confirmPending(
id: string,
allow: boolean,
remember: boolean,
) {
currentAppPendingReqs.forEach((req) => {
const selectedPendingRequests = pendingRequests.filter((pr) => pr.checked)
const handleCloseModal = handleClose(
MODAL_PARAMS_KEYS.CONFIRM_EVENT,
(sp) => {
sp.delete('appNpub')
sp.delete('reqId')
selectedPendingRequests.forEach(
async (req) => await swicCall('confirm', req.id, false, false),
)
},
)
async function confirmPending(id: string) {
selectedPendingRequests.forEach((req) => {
call(async () => {
await swicCall('confirm', req.id, allow, remember)
console.log('confirmed', req.id, id, allow, remember)
if (selectedActionType === ACTION_TYPE.ONCE) {
await swicCall('confirm', req.id, true, false)
} else {
await swicCall('confirm', req.id, true, true)
}
console.log('confirmed', req.id, id, selectedActionType)
})
})
handleCloseModal()
handleClose(MODAL_PARAMS_KEYS.CONFIRM_EVENT, (sp) => {
sp.delete('appNpub')
sp.delete('reqId')
})
}
const handleChangeCheckbox = (reqId: string) => () => {
const newPendingRequests = pendingRequests.map((req) => {
if (req.id === reqId) return { ...req, checked: !req.checked }
return req
})
setPendingRequests(newPendingRequests)
}
return (
<Modal open={open} onClose={handleCloseModal}>
<Modal open={isModalOpened} onClose={handleCloseModal}>
<Stack gap={'1rem'} paddingTop={'1rem'}>
<Stack
direction={'row'}
@ -130,14 +158,19 @@ export const ModalConfirmEvent: FC<ModalConfirmEventProps> = ({
<StyledActionsListContainer marginBottom={'1rem'}>
<SectionTitle>Actions</SectionTitle>
<List>
{currentAppPendingReqs.map((perm) => {
{pendingRequests.map((req) => {
return (
<ListItem>
<ListItemIcon>
<Checkbox color='primary' />
<Checkbox
checked={req.checked}
onChange={handleChangeCheckbox(
req.id,
)}
/>
</ListItemIcon>
<ListItemText>
{ACTIONS[perm.method]}
{ACTIONS[req.method]}
</ListItemText>
</ListItem>
)
@ -173,7 +206,7 @@ export const ModalConfirmEvent: FC<ModalConfirmEventProps> = ({
</StyledButton>
<StyledButton
fullWidth
onClick={() => confirmPending(pendingReqId, true, true)}
onClick={() => confirmPending(pendingReqId)}
>
Allow {ACTION_LABELS[selectedActionType]}
</StyledButton>

View File

@ -8,12 +8,17 @@ import { Modal } from '@/shared/Modal/Modal'
import { MODAL_PARAMS_KEYS } from '@/types/modal'
import { getBunkerLink } from '@/utils/helpers'
import { Stack, Typography } from '@mui/material'
import { useRef } from 'react'
import { useParams } from 'react-router-dom'
export const ModalConnectApp = () => {
const { getModalOpened, handleClose, handleOpen } = useModalSearchParams()
const timerRef = useRef<NodeJS.Timeout>()
const isModalOpened = getModalOpened(MODAL_PARAMS_KEYS.CONNECT_APP)
const handleCloseModal = handleClose(MODAL_PARAMS_KEYS.CONNECT_APP)
const handleCloseModal = handleClose(MODAL_PARAMS_KEYS.CONNECT_APP, () => {
clearTimeout(timerRef.current)
})
const notify = useEnqueueSnackbar()
@ -37,6 +42,12 @@ export const ModalConnectApp = () => {
}
}
const handleCopy = () => {
timerRef.current = setTimeout(() => {
handleCloseModal()
}, 3000)
}
return (
<Modal
open={isModalOpened}
@ -53,7 +64,12 @@ export const ModalConnectApp = () => {
}}
fullWidth
value={bunkerStr}
endAdornment={<InputCopyButton value={bunkerStr} />}
endAdornment={
<InputCopyButton
value={bunkerStr}
onCopy={handleCopy}
/>
}
/>
<AppLink
title='What is this?'

View File

@ -2,18 +2,19 @@ import { useModalSearchParams } from '@/hooks/useModalSearchParams'
import { Button } from '@/shared/Button/Button'
import { Modal } from '@/shared/Modal/Modal'
import { MODAL_PARAMS_KEYS } from '@/types/modal'
import { Box, Checkbox, IconButton, Stack, Typography } from '@mui/material'
import { Box, IconButton, Stack, Typography } from '@mui/material'
import {
StyledButton,
StyledSettingContainer,
StyledSynchedText,
} from './styled'
import { SectionTitle } from '@/shared/SectionTitle/SectionTitle'
import { CheckedIcon, CheckmarkIcon, UnchekedIcon } from '@/assets'
import { CheckmarkIcon } from '@/assets'
import { Input } from '@/shared/Input/Input'
import VisibilityOffOutlinedIcon from '@mui/icons-material/VisibilityOffOutlined'
import VisibilityOutlinedIcon from '@mui/icons-material/VisibilityOutlined'
import { ChangeEvent, useState } from 'react'
import { Checkbox } from '@/shared/Checkbox/Checkbox'
export const ModalSettings = () => {
const { getModalOpened, handleClose } = useModalSearchParams()
@ -62,10 +63,7 @@ export const ModalSettings = () => {
)}
</Stack>
<Box>
<Checkbox
icon={<UnchekedIcon />}
checkedIcon={<CheckedIcon />}
/>
<Checkbox />
<Typography variant='caption'>
Use this login on multiple devices
</Typography>

View File

@ -9,11 +9,11 @@ import {
export const StyledSettingContainer = styled((props: StackProps) => (
<Stack {...props} gap={'1rem'} />
))(() => ({
))(({ theme }) => ({
padding: '0.75rem',
borderRadius: '1rem',
background: '#000000',
color: '#fff',
background: theme.palette.background.default,
color: theme.palette.text.primary,
}))
export const StyledButton = styled(Button)(({ theme }) => {

View File

@ -9,6 +9,7 @@ export const StyledContainer = styled((props: BoxProps) => <Box {...props} />)(
display: 'flex',
alignItems: 'center',
gap: '1rem',
cursor: 'pointer',
}
},
)

View File

@ -51,46 +51,42 @@ const KeyPage = () => {
const nofity = useEnqueueSnackbar()
const [profile, setProfile] = useState<MetaEvent | null>(null)
const userName = profile?.info?.name || getShortenNpub(npub)
const userNameWithPrefix = userName + '@nsec.app'
const [showWarning, setShowWarning] = useState(false)
const filteredApps = apps.filter((a) => a.npub === npub)
const filteredPendingReqs = pending.filter((p) => p.npub === npub)
const filteredPerms = perms.filter((p) => p.npub === npub)
// eslint-disable-next-line
const npubConnectPerms = filteredPerms.filter(
(perm) => perm.perm === 'connect',
)
console.log(npubConnectPerms, '=> npubConnectPerms')
const excludeConnectPeqs = filteredPendingReqs.filter(
(pr) => pr.method !== 'connect',
)
const prepareEventPendings = excludeConnectPeqs.reduce<IPendingsByAppNpub>(
(acc, current) => {
const isConnected = npubConnectPerms.some(
(cp) => cp.appNpub === current.appNpub,
)
if (!acc[current.appNpub]) {
acc[current.appNpub] = {
pending: [current],
isConnected: npubConnectPerms.some(
(cp) => cp.appNpub === current.appNpub,
),
isConnected,
}
return acc
}
acc[current.appNpub].pending.push(current)
acc[current.appNpub].isConnected = npubConnectPerms.some(
(cp) => cp.appNpub === current.appNpub,
)
acc[current.appNpub].isConnected = isConnected
return acc
},
{},
)
const [profile, setProfile] = useState<MetaEvent | null>(null)
const [showWarning, setShowWarning] = useState(false)
const userName = profile?.info?.name || getShortenNpub(npub)
const userNameWithPrefix = userName + '@nsec.app'
const load = useCallback(async () => {
try {
const npubToken = npub.includes('#') ? npub.split('#')[0] : npub
@ -122,27 +118,25 @@ const KeyPage = () => {
const handleOpenSettingsModal = () => handleOpen(MODAL_PARAMS_KEYS.SETTINGS)
useEffect(() => {
const checkBackgroundSigning = async () => {
if (swr) {
const isBackgroundEnable =
await swr.pushManager.getSubscription()
if (!isBackgroundEnable) {
setShowWarning(true)
} else {
setShowWarning(false)
}
}
const checkBackgroundSigning = useCallback(async () => {
if (swr) {
const isBackgroundEnable = await swr.pushManager.getSubscription()
if (!isBackgroundEnable) setShowWarning(true)
else setShowWarning(false)
}
checkBackgroundSigning()
}, [])
useEffect(() => {
checkBackgroundSigning()
}, [checkBackgroundSigning])
const handleEnableBackground = async () => {
await askNotificationPermission()
try {
const r = await swicCall('enablePush')
if (!r) return nofity(`Failed to enable push subscription`, 'error')
nofity('Enabled!', 'success')
checkBackgroundSigning()
} catch (e) {
nofity(`Failed to enable push subscription`, 'error')
}
@ -157,7 +151,7 @@ const KeyPage = () => {
shownConnectModals.current = {}
shownConfirmEventModals.current = {}
}
}, [npub])
}, [npub, pending.length])
const connectPendings = filteredPendingReqs.filter(
(pr) => pr.method === 'connect',

View File

@ -15,7 +15,7 @@ const WelcomePage = () => {
const npubInputRef = useRef<HTMLInputElement | null>(null)
const passwordInputRef = useRef<HTMLInputElement | null>(null)
if (isKeysExists) return <Navigate to={'/home'} />
// if (isKeysExists) return <Navigate to={'/home'} />
async function generateKey() {
try {

View File

@ -8,7 +8,6 @@ import { CircularProgress, Stack } from '@mui/material'
const KeyPage = lazy(() => import('../pages/KeyPage/Key.Page'))
const ConfirmPage = lazy(() => import('../pages/Confirm.Page'))
const AppPage = lazy(() => import('../pages/App.Page'))
// const AuthPage = lazy(() => import('../pages/AuthPage/Auth.Page'))
const LoadingSpinner = () => (
<Stack height={'100%'} justifyContent={'center'} alignItems={'center'}>
@ -24,7 +23,6 @@ const AppRoutes = () => {
<Route path='/' element={<Navigate to={'/home'} />} />
<Route path='/welcome' element={<WelcomePage />} />
<Route path='/home' element={<HomePage />} />
{/* <Route path='/sign-up' element={<AuthPage />} /> */}
<Route path='/key/:npub' element={<KeyPage />} />
<Route
path='/key/:npub/app/:appNpub'

View File

@ -0,0 +1,37 @@
import { forwardRef } from 'react'
import { Checkbox as MuiCheckbox, CheckboxProps, styled } from '@mui/material'
import {
CheckedIcon,
CheckedLightIcon,
UnchekedIcon,
UnchekedLightIcon,
} from '@/assets'
import { useAppSelector } from '@/store/hooks/redux'
export const Checkbox = forwardRef<HTMLButtonElement, CheckboxProps>(
(props, ref) => {
const { themeMode } = useAppSelector((state) => state.ui)
return <StyledCheckbox ref={ref} {...props} mode={themeMode} />
},
)
const StyledCheckbox = styled(
forwardRef<HTMLButtonElement, CheckboxProps & { mode: 'dark' | 'light' }>(
({ mode, ...restProps }, ref) => {
const isDarkMode = mode === 'dark'
return (
<MuiCheckbox
{...restProps}
ref={ref}
icon={isDarkMode ? <UnchekedLightIcon /> : <UnchekedIcon />}
checkedIcon={
isDarkMode ? <CheckedLightIcon /> : <CheckedIcon />
}
/>
)
},
),
)(() => ({
'& .MuiSvgIcon-root': { fontSize: '1.5rem' },
}))

View File

@ -6,12 +6,19 @@ import { StyledContainer } from './styled'
type InputCopyButtonProps = {
value: string
onCopy?: () => void
}
export const InputCopyButton: FC<InputCopyButtonProps> = ({ value }) => {
export const InputCopyButton: FC<InputCopyButtonProps> = ({
value,
onCopy = () => undefined,
}) => {
const [isCopied, setIsCopied] = useState(false)
const handleCopy = () => setIsCopied(true)
const handleCopy = () => {
setIsCopied(true)
onCopy && onCopy()
}
useEffect(() => {
let timerId: any