From e5d2b8808bbb50183e3e8c210d020410418dcef1 Mon Sep 17 00:00:00 2001 From: Bekbolsun Date: Thu, 1 Feb 2024 18:33:19 +0600 Subject: [PATCH 1/3] add sync npub logic & change perms and activity history design & add delete app/perm requests --- src/App.tsx | 14 +- .../ModalConfirmEvent/ModalConfirmEvent.tsx | 13 +- .../Modal/ModalImportKeys/ModalImportKeys.tsx | 15 +- .../Modal/ModalInitial/ModalInitial.tsx | 2 +- .../Modal/ModalLogin/ModalLogin.tsx | 15 +- .../Modal/ModalSettings/ModalSettings.tsx | 110 ++++-- src/components/Modal/ModalSettings/styled.tsx | 7 +- src/hooks/useIsIOS.ts | 23 ++ src/hooks/useToggleConfirm.ts | 15 + src/modules/db.ts | 14 + src/pages/AppPage/App.Page.tsx | 141 +++++--- .../components/Activities/ItemActivity.tsx | 45 +++ .../components/Activities/ModalActivities.tsx | 39 +++ .../AppPage/components/Activities/styled.tsx | 12 + src/pages/AppPage/components/ActivityList.tsx | 53 --- .../AppPage/components/ItemPermissionMenu.tsx | 37 -- .../components/Permissions/ItemPermission.tsx | 59 ++++ .../Permissions/ItemPermissionMenu.tsx | 62 ++++ .../components/Permissions/Permissions.tsx | 28 ++ .../AppPage/components/Permissions/styled.tsx | 11 + .../AppPage/components/PermissionsMenu.tsx | 26 -- src/pages/AppPage/components/styled.tsx | 14 +- src/pages/AppPage/styled.tsx | 12 +- src/pages/AppPage/utils.ts | 15 + src/pages/HomePage/Home.Page.tsx | 7 +- src/pages/HomePage/components/ItemKey.tsx | 4 +- src/pages/KeyPage/Key.Page.tsx | 324 +++--------------- .../components/BackgroundSigningWarning.tsx | 27 ++ .../KeyPage/components/UserValueSection.tsx | 55 +++ src/pages/KeyPage/components/styled.tsx | 6 + .../KeyPage/hooks/useBackgroundSigning.ts | 40 +++ src/pages/KeyPage/hooks/useProfile.ts | 29 ++ .../KeyPage/hooks/useTriggerConfirmModal.ts | 143 ++++++++ src/pages/KeyPage/styled.tsx | 18 +- src/pages/KeyPage/utils.ts | 6 + src/pages/Welcome.Page.tsx | 2 +- src/shared/Button/Button.tsx | 6 + src/shared/Checkbox/Checkbox.tsx | 1 + src/shared/ConfirmModal/ConfirmModal.tsx | 65 ++++ src/shared/ConfirmModal/styled.tsx | 11 + src/shared/IOSBackButton/IOSBackButton.tsx | 25 ++ src/shared/IOSBackButton/styled.tsx | 21 ++ src/shared/Modal/Modal.tsx | 11 +- src/shared/Modal/styled.tsx | 68 ++-- src/types/modal.ts | 1 + src/utils/consts.ts | 10 +- src/utils/helpers/helpers.ts | 18 +- 47 files changed, 1084 insertions(+), 596 deletions(-) create mode 100644 src/hooks/useIsIOS.ts create mode 100644 src/hooks/useToggleConfirm.ts create mode 100644 src/pages/AppPage/components/Activities/ItemActivity.tsx create mode 100644 src/pages/AppPage/components/Activities/ModalActivities.tsx create mode 100644 src/pages/AppPage/components/Activities/styled.tsx delete mode 100644 src/pages/AppPage/components/ActivityList.tsx delete mode 100644 src/pages/AppPage/components/ItemPermissionMenu.tsx create mode 100644 src/pages/AppPage/components/Permissions/ItemPermission.tsx create mode 100644 src/pages/AppPage/components/Permissions/ItemPermissionMenu.tsx create mode 100644 src/pages/AppPage/components/Permissions/Permissions.tsx create mode 100644 src/pages/AppPage/components/Permissions/styled.tsx delete mode 100644 src/pages/AppPage/components/PermissionsMenu.tsx create mode 100644 src/pages/AppPage/utils.ts create mode 100644 src/pages/KeyPage/components/BackgroundSigningWarning.tsx create mode 100644 src/pages/KeyPage/components/UserValueSection.tsx create mode 100644 src/pages/KeyPage/hooks/useBackgroundSigning.ts create mode 100644 src/pages/KeyPage/hooks/useProfile.ts create mode 100644 src/pages/KeyPage/hooks/useTriggerConfirmModal.ts create mode 100644 src/pages/KeyPage/utils.ts create mode 100644 src/shared/ConfirmModal/ConfirmModal.tsx create mode 100644 src/shared/ConfirmModal/styled.tsx create mode 100644 src/shared/IOSBackButton/IOSBackButton.tsx create mode 100644 src/shared/IOSBackButton/styled.tsx diff --git a/src/App.tsx b/src/App.tsx index 3b8c205..3c8a329 100644 --- a/src/App.tsx +++ b/src/App.tsx @@ -1,4 +1,4 @@ -import { DbKey, DbPending, dbi } from './modules/db' +import { DbKey, dbi } from './modules/db' import { useCallback, useEffect, useState } from 'react' import { swicOnRender } from './modules/swic' import { useAppDispatch } from './store/hooks/redux' @@ -65,18 +65,14 @@ function App() { dispatch(setPending({ pending })) // rerender -// setRender((r) => r + 1) - - if (!keys.length) - handleOpen(MODAL_PARAMS_KEYS.INITIAL) + // setRender((r) => r + 1) + if (!keys.length) handleOpen(MODAL_PARAMS_KEYS.INITIAL) + // eslint-disable-next-line }, [dispatch]) useEffect(() => { - console.log('NDK is connected', isConnected) - if (isConnected) { - load() - } + if (isConnected) load() }, [render, isConnected, load]) useEffect(() => { diff --git a/src/components/Modal/ModalConfirmEvent/ModalConfirmEvent.tsx b/src/components/Modal/ModalConfirmEvent/ModalConfirmEvent.tsx index 26985c1..6bad46e 100644 --- a/src/components/Modal/ModalConfirmEvent/ModalConfirmEvent.tsx +++ b/src/components/Modal/ModalConfirmEvent/ModalConfirmEvent.tsx @@ -24,9 +24,10 @@ import { } from './styled' 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' +import { ACTIONS } from '@/utils/consts' +import { IPendingsByAppNpub } from '@/pages/KeyPage/hooks/useTriggerConfirmModal' enum ACTION_TYPE { ALWAYS = 'ALWAYS', @@ -44,14 +45,6 @@ type ModalConfirmEventProps = { confirmEventReqs: IPendingsByAppNpub } -export const ACTIONS: { [type: string]: string } = { - get_public_key: 'Get public key', - sign_event: 'Sign event', - connect: 'Connect', - nip04_encrypt: 'Encrypt message', - nip04_decrypt: 'Decrypt message', -} - type PendingRequest = DbPending & { checked: boolean } export const ModalConfirmEvent: FC = ({ @@ -173,7 +166,7 @@ export const ModalConfirmEvent: FC = ({ {pendingRequests.map((req) => { return ( - + { const { getModalOpened, handleClose } = useModalSearchParams() @@ -15,6 +16,7 @@ export const ModalImportKeys = () => { const handleCloseModal = handleClose(MODAL_PARAMS_KEYS.IMPORT_KEYS) const notify = useEnqueueSnackbar() + const navigate = useNavigate() const [enteredNsec, setEnteredNsec] = useState('') @@ -26,9 +28,9 @@ export const ModalImportKeys = () => { e.preventDefault() try { if (!enteredNsec.trim().length) return - await swicCall('importKey', enteredNsec) + const k: any = await swicCall('importKey', enteredNsec) notify('Key imported!', 'success') - handleCloseModal() + navigate(`/key/${k.npub}`) } catch (error: any) { notify(error.message, 'error') } @@ -36,12 +38,7 @@ export const ModalImportKeys = () => { return ( - + { onChange={handleNsecChange} fullWidth /> - + ) diff --git a/src/components/Modal/ModalInitial/ModalInitial.tsx b/src/components/Modal/ModalInitial/ModalInitial.tsx index b89d879..da27e5b 100644 --- a/src/components/Modal/ModalInitial/ModalInitial.tsx +++ b/src/components/Modal/ModalInitial/ModalInitial.tsx @@ -28,7 +28,7 @@ export const ModalInitial = () => { return ( - + diff --git a/src/components/Modal/ModalLogin/ModalLogin.tsx b/src/components/Modal/ModalLogin/ModalLogin.tsx index 01a690b..4debbc2 100644 --- a/src/components/Modal/ModalLogin/ModalLogin.tsx +++ b/src/components/Modal/ModalLogin/ModalLogin.tsx @@ -37,8 +37,12 @@ export const ModalLogin = () => { const handlePasswordTypeChange = () => setIsPasswordShown((prevState) => !prevState) + const isFormValid = + enteredUsername.trim().length > 0 && enteredPassword.trim().length > 0 + const handleSubmit = async (e: React.FormEvent) => { e.preventDefault() + if (!isFormValid) return undefined try { const [username, domain] = enteredUsername.split('@') const response = await fetch( @@ -63,12 +67,7 @@ export const ModalLogin = () => { } return ( - + { )} } - type={isPasswordShown ? 'password' : 'text'} + type={isPasswordShown ? 'text' : 'password'} /> - diff --git a/src/components/Modal/ModalSettings/ModalSettings.tsx b/src/components/Modal/ModalSettings/ModalSettings.tsx index 8f87c95..6556005 100644 --- a/src/components/Modal/ModalSettings/ModalSettings.tsx +++ b/src/components/Modal/ModalSettings/ModalSettings.tsx @@ -2,7 +2,13 @@ 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, IconButton, Stack, Typography } from '@mui/material' +import { + Box, + CircularProgress, + IconButton, + Stack, + Typography, +} from '@mui/material' import { StyledButton, StyledSettingContainer, @@ -13,13 +19,18 @@ 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 { ChangeEvent, FC, useState } from 'react' import { Checkbox } from '@/shared/Checkbox/Checkbox' import { useEnqueueSnackbar } from '@/hooks/useEnqueueSnackbar' import { swicCall } from '@/modules/swic' import { useParams } from 'react-router-dom' +import { dbi } from '@/modules/db' -export const ModalSettings = () => { +type ModalSettingsProps = { + isSynced: boolean +} + +export const ModalSettings: FC = ({ isSynced }) => { const { getModalOpened, handleClose } = useModalSearchParams() const { npub = '' } = useParams<{ npub: string }>() @@ -31,10 +42,11 @@ export const ModalSettings = () => { const [enteredPassword, setEnteredPassword] = useState('') const [isPasswordShown, setIsPasswordShown] = useState(false) const [isPasswordInvalid, setIsPasswordInvalid] = useState(false) - const [isPasswordSynched, setIsPasswordSynched] = useState(false) const [isChecked, setIsChecked] = useState(false) + const [isLoading, setIsLoading] = useState(false) + const handlePasswordChange = (e: ChangeEvent) => { setIsPasswordInvalid(false) setEnteredPassword(e.target.value) @@ -47,7 +59,6 @@ export const ModalSettings = () => { handleCloseModal() setEnteredPassword('') setIsPasswordInvalid(false) - setIsPasswordSynched(false) } const handleChangeCheckbox = (e: unknown, checked: boolean) => { @@ -57,19 +68,21 @@ export const ModalSettings = () => { const handleSubmit = async (e: React.FormEvent) => { e.preventDefault() setIsPasswordInvalid(false) - setIsPasswordSynched(false) if (enteredPassword.trim().length < 6) { return setIsPasswordInvalid(true) } try { + setIsLoading(true) await swicCall('saveKey', npub, enteredPassword) notify('Key saved', 'success') + dbi.addSynced(npub) // Sync npub + setEnteredPassword('') setIsPasswordInvalid(false) - setIsPasswordSynched(true) + setIsLoading(false) } catch (error) { setIsPasswordInvalid(false) - setIsPasswordSynched(false) + setIsLoading(false) } } @@ -79,7 +92,7 @@ export const ModalSettings = () => { Cloud sync - {isPasswordSynched && ( + {isSynced && ( Synched @@ -94,37 +107,58 @@ export const ModalSettings = () => { Use this login on multiple devices - + This uploads your private key, encrypted by + your password, to Nsec App's server. + + ) : ( + <> + + {isPasswordShown ? ( + + ) : ( + + )} + + } + type={isPasswordShown ? 'text' : 'password'} + onChange={handlePasswordChange} + value={enteredPassword} + helperText={ + isPasswordInvalid ? 'Invalid password' : '' + } + placeholder='Enter a password' + helperTextProps={{ + sx: { + '&.helper_text': { + color: 'red', + }, + }, + }} + disabled={!isChecked} + /> + - {isPasswordShown ? ( - - ) : ( - + Sync{' '} + {isLoading && ( + )} - - } - type={isPasswordShown ? 'text' : 'password'} - onChange={handlePasswordChange} - value={enteredPassword} - helperText={isPasswordInvalid ? 'Invalid password' : ''} - placeholder='Enter a password' - helperTextProps={{ - sx: { - '&.helper_text': { - color: 'red', - }, - }, - }} - disabled={!isChecked} - /> - - Sync - + + + )} diff --git a/src/components/Modal/ModalSettings/styled.tsx b/src/components/Modal/ModalSettings/styled.tsx index 0bcf339..c26f0f4 100644 --- a/src/components/Modal/ModalSettings/styled.tsx +++ b/src/components/Modal/ModalSettings/styled.tsx @@ -8,9 +8,9 @@ import { } from '@mui/material' export const StyledSettingContainer = styled((props: StackProps) => ( - + ))(({ theme }) => ({ - padding: '0.75rem', + padding: '1rem', borderRadius: '1rem', background: theme.palette.background.default, color: theme.palette.text.primary, @@ -22,6 +22,9 @@ export const StyledButton = styled(Button)(({ theme }) => { background: theme.palette.secondary.main, color: theme.palette.text.primary, }, + ':disabled': { + cursor: 'not-allowed', + }, } }) diff --git a/src/hooks/useIsIOS.ts b/src/hooks/useIsIOS.ts new file mode 100644 index 0000000..66f7349 --- /dev/null +++ b/src/hooks/useIsIOS.ts @@ -0,0 +1,23 @@ +import { useState, useEffect } from 'react' + +/** + * Custom hook to detect if the platform is iOS or not. + * @returns {boolean} True if the platform is iOS, false otherwise. + */ + +const iOSRegex = /iPad|iPhone|iPod/ + +function useIsIOS() { + const [isIOS, setIsIOS] = useState(false) + + useEffect(() => { + const isIOSUserAgent = + iOSRegex.test(navigator.userAgent) || + (navigator.userAgent.includes('Mac') && 'ontouchend' in document) + setIsIOS(isIOSUserAgent) + }, []) + + return isIOS +} + +export default useIsIOS diff --git a/src/hooks/useToggleConfirm.ts b/src/hooks/useToggleConfirm.ts new file mode 100644 index 0000000..78c68f9 --- /dev/null +++ b/src/hooks/useToggleConfirm.ts @@ -0,0 +1,15 @@ +import { useCallback, useState } from 'react' + +export const useToggleConfirm = () => { + const [showConfirm, setShowConfirm] = useState(false) + + const handleShow = useCallback(() => setShowConfirm(true), []) + + const handleClose = useCallback(() => setShowConfirm(false), []) + + return { + open: showConfirm, + handleShow, + handleClose, + } +} diff --git a/src/modules/db.ts b/src/modules/db.ts index 9a58556..f515b7b 100644 --- a/src/modules/db.ts +++ b/src/modules/db.ts @@ -48,12 +48,17 @@ export interface DbHistory { allowed: boolean } +export interface DbSyncHistory { + npub: string +} + export interface DbSchema extends Dexie { keys: Dexie.Table apps: Dexie.Table perms: Dexie.Table pending: Dexie.Table history: Dexie.Table + syncHistory: Dexie.Table } export const db = new Dexie('noauthdb') as DbSchema @@ -65,6 +70,7 @@ db.version(7).stores({ pending: 'id,npub,appNpub,timestamp,method', history: 'id,npub,appNpub,timestamp,method,allowed', requestHistory: 'id', + syncHistory: 'npub', }) export const dbi = { @@ -201,4 +207,12 @@ export const dbi = { return false } }, + addSynced: async (npub: string) => { + try { + await db.syncHistory.add({ npub }) + } catch (error) { + console.log(`db addSynced error: ${error}`) + return false + } + }, } diff --git a/src/pages/AppPage/App.Page.tsx b/src/pages/AppPage/App.Page.tsx index 7d1af8a..fdc43f7 100644 --- a/src/pages/AppPage/App.Page.tsx +++ b/src/pages/AppPage/App.Page.tsx @@ -1,40 +1,42 @@ -import { useLiveQuery } from 'dexie-react-hooks' -import { DbHistory, db } from '@/modules/db' import { useParams } from 'react-router' import { useAppSelector } from '@/store/hooks/redux' import { selectAppByAppNpub, selectPermsByNpubAndAppNpub } from '@/store' -import { Navigate } from 'react-router-dom' +import { Navigate, useNavigate } from 'react-router-dom' import { formatTimestampDate } from '@/utils/helpers/date' -import { Avatar, Box, Stack, Typography } from '@mui/material' +import { Box, Stack, Typography } from '@mui/material' import { SectionTitle } from '@/shared/SectionTitle/SectionTitle' import { getShortenNpub } from '@/utils/helpers/helpers' -import { PermissionMenuButton } from './styled' -import { PermissionsMenu } from './components/PermissionsMenu' -import { useOpenMenu } from '@/hooks/useOpenMenu' -import { ActivityList } from './components/ActivityList' - -const getAppHistoryQuery = (appNpub: string) => - db.history.where('appNpub').equals(appNpub).toArray() +import { Button } from '@/shared/Button/Button' +import { ACTION_TYPE } from '@/utils/consts' +import { Permissions } from './components/Permissions/Permissions' +import { StyledAppIcon } from './styled' +import { useToggleConfirm } from '@/hooks/useToggleConfirm' +import { ConfirmModal } from '@/shared/ConfirmModal/ConfirmModal' +import { swicCall } from '@/modules/swic' +import { useEnqueueSnackbar } from '@/hooks/useEnqueueSnackbar' +import { IOSBackButton } from '@/shared/IOSBackButton/IOSBackButton' +import { ModalActivities } from './components/Activities/ModalActivities' +import { useModalSearchParams } from '@/hooks/useModalSearchParams' +import { MODAL_PARAMS_KEYS } from '@/types/modal' const AppPage = () => { const { appNpub = '', npub = '' } = useParams() + const navigate = useNavigate() + const notify = useEnqueueSnackbar() + const perms = useAppSelector((state) => selectPermsByNpubAndAppNpub(state, npub, appNpub), ) const currentApp = useAppSelector((state) => selectAppByAppNpub(state, appNpub), ) - const history = useLiveQuery( - () => { - if (!appNpub.trim().length) return [] - return getAppHistoryQuery(appNpub) - }, - [], - [] as DbHistory[], - ) - const { anchorEl, handleClose, handleOpen, open } = useOpenMenu() - const connectPerm = perms.find((perm) => perm.perm === 'connect') + const { open, handleClose, handleShow } = useToggleConfirm() + const { handleOpen: handleOpenModal } = useModalSearchParams() + + const connectPerm = perms.find( + (perm) => perm.perm === 'connect' || perm.perm === ACTION_TYPE.BASIC, + ) if (!currentApp) { return @@ -43,52 +45,79 @@ const AppPage = () => { const { icon = '', name = '' } = currentApp || {} const appName = name || getShortenNpub(appNpub) const { timestamp } = connectPerm || {} + const connectedOn = connectPerm && timestamp ? `Connected at ${formatTimestampDate(timestamp)}` : 'Not connected' + const handleDeleteApp = async () => { + try { + await swicCall('deleteApp', appNpub) + notify(`App: «${appName}» successfully deleted!`, 'success') + navigate(`key/${npub}`) + } catch (error: any) { + notify(error?.message || 'Failed to delete app', 'error') + } + } + return ( - + <> - - - - {appName} - - - {connectedOn} - + navigate(`key/${npub}`)} /> + + + + + {appName} + + + {connectedOn} + + + + + + Disconnect + + + + + - - Permissions - - Basic/Advanced/Custom {perms.length} - - - - - - + + + ) } diff --git a/src/pages/AppPage/components/Activities/ItemActivity.tsx b/src/pages/AppPage/components/Activities/ItemActivity.tsx new file mode 100644 index 0000000..5233d7e --- /dev/null +++ b/src/pages/AppPage/components/Activities/ItemActivity.tsx @@ -0,0 +1,45 @@ +import React, { FC } from 'react' +import { DbHistory } from '@/modules/db' +import { Box, IconButton, Typography } from '@mui/material' +import { StyledActivityItem } from './styled' +import { formatTimestampDate } from '@/utils/helpers/date' +import ClearRoundedIcon from '@mui/icons-material/ClearRounded' +import DoneRoundedIcon from '@mui/icons-material/DoneRounded' +import MoreVertRoundedIcon from '@mui/icons-material/MoreVertRounded' +import { ACTIONS } from '@/utils/consts' + +type ItemActivityProps = DbHistory + +export const ItemActivity: FC = ({ + allowed, + method, + timestamp, +}) => { + return ( + + + + {ACTIONS[method] || method} + + + {formatTimestampDate(timestamp)} + + + + {allowed ? ( + + ) : ( + + )} + + + + + + ) +} diff --git a/src/pages/AppPage/components/Activities/ModalActivities.tsx b/src/pages/AppPage/components/Activities/ModalActivities.tsx new file mode 100644 index 0000000..579e226 --- /dev/null +++ b/src/pages/AppPage/components/Activities/ModalActivities.tsx @@ -0,0 +1,39 @@ +import React, { FC } from 'react' +import { Modal } from '@/shared/Modal/Modal' +import { Box } from '@mui/material' +import { useLiveQuery } from 'dexie-react-hooks' +import { HistoryDefaultValue, getActivityHistoryQuerier } from '../../utils' +import { ItemActivity } from './ItemActivity' +import { useModalSearchParams } from '@/hooks/useModalSearchParams' +import { MODAL_PARAMS_KEYS } from '@/types/modal' + +type ModalActivitiesProps = { + appNpub: string +} + +export const ModalActivities: FC = ({ appNpub }) => { + const { getModalOpened, handleClose } = useModalSearchParams() + const isModalOpened = getModalOpened(MODAL_PARAMS_KEYS.ACTIVITY) + const handleCloseModal = handleClose(MODAL_PARAMS_KEYS.ACTIVITY) + + const history = useLiveQuery( + getActivityHistoryQuerier(appNpub), + [], + HistoryDefaultValue, + ) + + return ( + + + {history.map((item) => { + return + })} + + + ) +} diff --git a/src/pages/AppPage/components/Activities/styled.tsx b/src/pages/AppPage/components/Activities/styled.tsx new file mode 100644 index 0000000..fc24619 --- /dev/null +++ b/src/pages/AppPage/components/Activities/styled.tsx @@ -0,0 +1,12 @@ +import styled from '@emotion/styled' +import { Box, BoxProps } from '@mui/material' + +export const StyledActivityItem = styled((props: BoxProps) => ( + +))(() => ({ + display: 'flex', + gap: '0.5rem', + justifyContent: 'space-between', + alignItems: 'center', + padding: '0.25rem', +})) diff --git a/src/pages/AppPage/components/ActivityList.tsx b/src/pages/AppPage/components/ActivityList.tsx deleted file mode 100644 index cd35714..0000000 --- a/src/pages/AppPage/components/ActivityList.tsx +++ /dev/null @@ -1,53 +0,0 @@ -import React, { FC } from 'react' -import { DbHistory } from '@/modules/db' -import { SectionTitle } from '@/shared/SectionTitle/SectionTitle' -import { Box, IconButton, Stack, Typography } from '@mui/material' -import MoreIcon from '@mui/icons-material/MoreVert' -import { ACTIONS } from '@/components/Modal/ModalConfirmEvent/ModalConfirmEvent' -import { formatTimestampDate } from '@/utils/helpers/date' -import { StyledButton } from './styled' - -type ActivityListProps = { - history: DbHistory[] -} - -export const ActivityList: FC = ({ history = [] }) => { - return ( - <> - Activity - - {history.map((h) => { - return ( - - - - {ACTIONS[h.method] || h.method} - - - {h.allowed ? 'allow' : 'disallow'} - - - - - - - {formatTimestampDate(h.timestamp)} - - - ) - })} - - - ) -} diff --git a/src/pages/AppPage/components/ItemPermissionMenu.tsx b/src/pages/AppPage/components/ItemPermissionMenu.tsx deleted file mode 100644 index 658384d..0000000 --- a/src/pages/AppPage/components/ItemPermissionMenu.tsx +++ /dev/null @@ -1,37 +0,0 @@ -import { FC } from 'react' -import { Box, Typography } from '@mui/material' -import { DbPerm } from '@/modules/db' -import { ACTIONS } from '@/components/Modal/ModalConfirmEvent/ModalConfirmEvent' -import { StyledMenuItem } from './styled' -import { formatTimestampDate } from '@/utils/helpers/date' - -type ItemPermissionMenuProps = { - permission: DbPerm -} - -export const ItemPermissionMenu: FC = ({ - permission, -}) => { - const { perm, value, timestamp } = permission || {} - - return ( - - - - {ACTIONS[perm] || perm} - - - {value === '1' ? 'allow' : 'disallow'} - - - - {formatTimestampDate(timestamp)} - - - ) -} diff --git a/src/pages/AppPage/components/Permissions/ItemPermission.tsx b/src/pages/AppPage/components/Permissions/ItemPermission.tsx new file mode 100644 index 0000000..a91ac6e --- /dev/null +++ b/src/pages/AppPage/components/Permissions/ItemPermission.tsx @@ -0,0 +1,59 @@ +import { FC } from 'react' +import { Box, IconButton, Typography } from '@mui/material' +import { DbPerm } from '@/modules/db' +import { formatTimestampDate } from '@/utils/helpers/date' +import { ACTIONS } from '@/utils/consts' +import { StyledPermissionItem } from './styled' +import ClearRoundedIcon from '@mui/icons-material/ClearRounded' +import DoneRoundedIcon from '@mui/icons-material/DoneRounded' +import MoreVertRoundedIcon from '@mui/icons-material/MoreVertRounded' +import { ItemPermissionMenu } from './ItemPermissionMenu' +import { useOpenMenu } from '@/hooks/useOpenMenu' + +type ItemPermissionProps = { + permission: DbPerm +} + +export const ItemPermission: FC = ({ permission }) => { + const { perm, value, timestamp, id } = permission || {} + + const { anchorEl, handleClose, handleOpen, open } = useOpenMenu() + + const isAllowed = value === '1' + + return ( + <> + + + + {ACTIONS[perm] || perm} + + + {formatTimestampDate(timestamp)} + + + + {isAllowed ? ( + + ) : ( + + )} + + + + + + + + ) +} diff --git a/src/pages/AppPage/components/Permissions/ItemPermissionMenu.tsx b/src/pages/AppPage/components/Permissions/ItemPermissionMenu.tsx new file mode 100644 index 0000000..bb880c3 --- /dev/null +++ b/src/pages/AppPage/components/Permissions/ItemPermissionMenu.tsx @@ -0,0 +1,62 @@ +import { FC, useState } from 'react' +import { Menu, MenuItem, MenuProps } from '@mui/material' +import { ConfirmModal } from '@/shared/ConfirmModal/ConfirmModal' +import { swicCall } from '@/modules/swic' +import { useEnqueueSnackbar } from '@/hooks/useEnqueueSnackbar' + +type ItemPermissionMenuProps = { + permId: string + handleClose: () => void +} & MenuProps + +export const ItemPermissionMenu: FC = ({ + open, + anchorEl, + handleClose, + permId, +}) => { + const [showConfirm, setShowConfirm] = useState(false) + const notify = useEnqueueSnackbar() + + const handleShowConfirm = () => { + setShowConfirm(true) + handleClose() + } + const handleCloseConfirm = () => setShowConfirm(false) + + const handleDeletePerm = async () => { + try { + await swicCall('deletePerm', permId) + notify('Permission successfully deleted!', 'success') + handleCloseConfirm() + } catch (error: any) { + notify(error?.message || 'Failed to delete permission', 'error') + } + } + + return ( + <> + + + Delete permission + + + + + ) +} diff --git a/src/pages/AppPage/components/Permissions/Permissions.tsx b/src/pages/AppPage/components/Permissions/Permissions.tsx new file mode 100644 index 0000000..6226a72 --- /dev/null +++ b/src/pages/AppPage/components/Permissions/Permissions.tsx @@ -0,0 +1,28 @@ +import { FC } from 'react' +import { DbPerm } from '@/modules/db' +import { SectionTitle } from '@/shared/SectionTitle/SectionTitle' +import { Box } from '@mui/material' +import { ItemPermission } from './ItemPermission' + +type PermissionsProps = { + perms: DbPerm[] +} + +export const Permissions: FC = ({ perms }) => { + return ( + + Permissions + + {perms.map((perm) => { + return + })} + + + ) +} diff --git a/src/pages/AppPage/components/Permissions/styled.tsx b/src/pages/AppPage/components/Permissions/styled.tsx new file mode 100644 index 0000000..2acb89c --- /dev/null +++ b/src/pages/AppPage/components/Permissions/styled.tsx @@ -0,0 +1,11 @@ +import { Box, BoxProps, styled } from '@mui/material' + +export const StyledPermissionItem = styled((props: BoxProps) => ( + +))(() => ({ + display: 'flex', + gap: '0.5rem', + justifyContent: 'space-between', + alignItems: 'center', + padding: '0.5rem', +})) diff --git a/src/pages/AppPage/components/PermissionsMenu.tsx b/src/pages/AppPage/components/PermissionsMenu.tsx deleted file mode 100644 index a10db4d..0000000 --- a/src/pages/AppPage/components/PermissionsMenu.tsx +++ /dev/null @@ -1,26 +0,0 @@ -import { DbPerm } from '@/modules/db' -import { Menu, MenuItem, MenuProps } from '@mui/material' -import { FC } from 'react' -import { ItemPermissionMenu } from './ItemPermissionMenu' - -type PermissionsMenuProps = { - perms: DbPerm[] -} & MenuProps - -export const PermissionsMenu: FC = ({ - perms, - open, - anchorEl, - onClose, -}) => { - const isNoPerms = perms.length === 0 - return ( - - {isNoPerms && No permissions} - {!isNoPerms && - perms.map((perm) => ( - - ))} - - ) -} diff --git a/src/pages/AppPage/components/styled.tsx b/src/pages/AppPage/components/styled.tsx index e6343f6..36d7006 100644 --- a/src/pages/AppPage/components/styled.tsx +++ b/src/pages/AppPage/components/styled.tsx @@ -1,17 +1,5 @@ import { Button } from '@/shared/Button/Button' -import { MenuItem, MenuItemProps, styled } from '@mui/material' - -export const StyledMenuItem = styled((props: MenuItemProps) => ( - -))(({ theme }) => ({ - display: 'flex', - flexDirection: 'column', - alignItems: 'flex-start', - gap: '0.5rem', - '&:not(:first-of-type)': { - borderTop: '1px solid ' + theme.palette.secondary.main, - }, -})) +import { styled } from '@mui/material' export const StyledButton = styled(Button)({ textTransform: 'capitalize', diff --git a/src/pages/AppPage/styled.tsx b/src/pages/AppPage/styled.tsx index 65157d7..e744b70 100644 --- a/src/pages/AppPage/styled.tsx +++ b/src/pages/AppPage/styled.tsx @@ -1,6 +1,8 @@ -import { AppButtonProps, Button } from '@/shared/Button/Button' -import { styled } from '@mui/material' +import { Avatar, AvatarProps, styled } from '@mui/material' -export const PermissionMenuButton = styled((props: AppButtonProps) => ( - + + + + ) +} diff --git a/src/shared/ConfirmModal/styled.tsx b/src/shared/ConfirmModal/styled.tsx new file mode 100644 index 0000000..6cfb7e8 --- /dev/null +++ b/src/shared/ConfirmModal/styled.tsx @@ -0,0 +1,11 @@ +import { + DialogContentText, + DialogContentTextProps, + styled, +} from '@mui/material' + +export const StyledDialogContentText = styled( + (props: DialogContentTextProps) => , +)(({ theme }) => ({ + color: theme.palette.primary.main, +})) diff --git a/src/shared/IOSBackButton/IOSBackButton.tsx b/src/shared/IOSBackButton/IOSBackButton.tsx new file mode 100644 index 0000000..3a82991 --- /dev/null +++ b/src/shared/IOSBackButton/IOSBackButton.tsx @@ -0,0 +1,25 @@ +import React, { FC } from 'react' +import { ButtonProps } from '@mui/material' +import { useNavigate } from 'react-router-dom' +import useIsIOS from '@/hooks/useIsIOS' +import { StyledButton } from './styled' + +type IOSBackButtonProps = ButtonProps & { + onNavigate?: () => void +} + +export const IOSBackButton: FC = ({ onNavigate }) => { + const isIOS = useIsIOS() + const navigate = useNavigate() + + const handleNavigateBack = () => { + if (onNavigate && typeof onNavigate === 'function') { + return onNavigate() + } + navigate(-1) + } + + if (!isIOS) return null + + return Back +} diff --git a/src/shared/IOSBackButton/styled.tsx b/src/shared/IOSBackButton/styled.tsx new file mode 100644 index 0000000..9cfe2f8 --- /dev/null +++ b/src/shared/IOSBackButton/styled.tsx @@ -0,0 +1,21 @@ +import { Button, ButtonProps, styled } from '@mui/material' +import GoBackIcon from '@mui/icons-material/KeyboardBackspaceRounded' + +export const StyledButton = styled((props: ButtonProps) => ( + diff --git a/src/components/Modal/ModalSettings/ModalSettings.tsx b/src/components/Modal/ModalSettings/ModalSettings.tsx index 6556005..25eb1cf 100644 --- a/src/components/Modal/ModalSettings/ModalSettings.tsx +++ b/src/components/Modal/ModalSettings/ModalSettings.tsx @@ -19,7 +19,7 @@ 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, FC, useState } from 'react' +import { ChangeEvent, FC, useEffect, useState } from 'react' import { Checkbox } from '@/shared/Checkbox/Checkbox' import { useEnqueueSnackbar } from '@/hooks/useEnqueueSnackbar' import { swicCall } from '@/modules/swic' @@ -47,6 +47,9 @@ export const ModalSettings: FC = ({ isSynced }) => { const [isLoading, setIsLoading] = useState(false) + + useEffect(() => setIsChecked(isSynced), [isModalOpened]) + const handlePasswordChange = (e: ChangeEvent) => { setIsPasswordInvalid(false) setEnteredPassword(e.target.value) @@ -104,61 +107,61 @@ export const ModalSettings: FC = ({ isSynced }) => { checked={isChecked} /> - Use this login on multiple devices + Use this key on multiple devices + + {isPasswordShown ? ( + + ) : ( + + )} + + } + type={isPasswordShown ? 'text' : 'password'} + onChange={handlePasswordChange} + value={enteredPassword} + helperText={ + isPasswordInvalid ? 'Invalid password' : '' + } + placeholder='Enter a password' + helperTextProps={{ + sx: { + '&.helper_text': { + color: 'red', + }, + }, + }} + disabled={!isChecked} + /> {isSynced ? ( - This uploads your private key, encrypted by - your password, to Nsec App's server. + To change your password, type a new one and sync. ) : ( - <> - - {isPasswordShown ? ( - - ) : ( - - )} - - } - type={isPasswordShown ? 'text' : 'password'} - onChange={handlePasswordChange} - value={enteredPassword} - helperText={ - isPasswordInvalid ? 'Invalid password' : '' - } - placeholder='Enter a password' - helperTextProps={{ - sx: { - '&.helper_text': { - color: 'red', - }, - }, - }} - disabled={!isChecked} - /> - - Sync{' '} - {isLoading && ( - - )} - - + + This key will be encrypted and stored on our server. You can use the password to download this key onto another device. + )} + + Sync{' '} + {isLoading && ( + + )} + diff --git a/src/modules/db.ts b/src/modules/db.ts index f515b7b..b3c8bb8 100644 --- a/src/modules/db.ts +++ b/src/modules/db.ts @@ -63,7 +63,7 @@ export interface DbSchema extends Dexie { export const db = new Dexie('noauthdb') as DbSchema -db.version(7).stores({ +db.version(8).stores({ keys: 'npub', apps: 'appNpub,npub,name,timestamp', perms: 'id,npub,appNpub,perm,value,timestamp', diff --git a/src/pages/HomePage/Home.Page.tsx b/src/pages/HomePage/Home.Page.tsx index b2cb15d..964cf3b 100644 --- a/src/pages/HomePage/Home.Page.tsx +++ b/src/pages/HomePage/Home.Page.tsx @@ -1,12 +1,13 @@ import { Fragment } from 'react' import { ItemKey } from './components/ItemKey' import { Box, Stack, Typography } from '@mui/material' -import { AddAccountButton } from './styled' +import { AddAccountButton, GetStartedButton, LearnMoreButton } from './styled' import { useAppSelector } from '@/store/hooks/redux' import { selectKeys } from '@/store' import { SectionTitle } from '@/shared/SectionTitle/SectionTitle' import { useModalSearchParams } from '@/hooks/useModalSearchParams' import { MODAL_PARAMS_KEYS } from '@/types/modal' +import { DOMAIN } from '@/utils/consts' const HomePage = () => { const keys = useAppSelector(selectKeys) @@ -15,16 +16,34 @@ const HomePage = () => { const { handleOpen } = useModalSearchParams() const handleClickAddAccount = () => handleOpen(MODAL_PARAMS_KEYS.INITIAL) + const handleLearnMore = () => { + // @ts-ignore + window.open(`https://info.${DOMAIN}`, '_blank').focus(); + } + return ( - {isNoKeys ? 'Welcome!' : 'Keys:'} + {isNoKeys ? 'Welcome' : 'Keys:'} {isNoKeys && ( - - Hello, this is a key storage app for Nostr - + <> + + Nsec.app is a novel key storage app for Nostr. + + + Get started + + + Your keys are stored in your browser and + can be used in many Nostr apps without the + need for a browser extension. + + + Learn more + + )} {!isNoKeys && ( diff --git a/src/pages/HomePage/styled.tsx b/src/pages/HomePage/styled.tsx index dbd6ce6..5706dcf 100644 --- a/src/pages/HomePage/styled.tsx +++ b/src/pages/HomePage/styled.tsx @@ -1,6 +1,8 @@ import { AppButtonProps, Button } from '@/shared/Button/Button' import { styled } from '@mui/material' import PersonAddAltRoundedIcon from '@mui/icons-material/PersonAddAltRounded' +import PlayArrowOutlinedIcon from '@mui/icons-material/PlayArrowOutlined' +import HelpOutlineOutlinedIcon from '@mui/icons-material/HelpOutlineOutlined' export const AddAccountButton = styled((props: AppButtonProps) => (