From 3fa6e1cdaa235560cea8d12eff620bd85bf978b7 Mon Sep 17 00:00:00 2001 From: Bekbolsun Date: Mon, 29 Jan 2024 21:10:11 +0600 Subject: [PATCH] add close button in modals & add app details page --- package-lock.json | 32 +++++ package.json | 2 + .../ModalConfirmConnect.tsx | 2 +- .../ModalConfirmEvent/ModalConfirmEvent.tsx | 3 +- .../Modal/ModalConnectApp/ModalConnectApp.tsx | 2 +- .../Modal/ModalInitial/ModalInitial.tsx | 12 +- .../Modal/ModalLogin/ModalLogin.tsx | 11 +- .../Modal/ModalSettings/ModalSettings.tsx | 53 +++++-- src/components/Modal/ModalSettings/styled.tsx | 2 +- .../Modal/ModalSignUp/ModalSignUp.tsx | 17 ++- src/layout/Header/Header.tsx | 2 +- src/layout/Header/components/ListProfiles.tsx | 2 +- src/layout/Header/components/Menu.tsx | 10 +- src/layout/Header/components/ProfileMenu.tsx | 8 +- src/pages/App.Page.tsx | 7 - src/pages/AppPage/App.Page.tsx | 132 ++++++++++++++++++ .../AppPage/components/ItemPermissionMenu.tsx | 37 +++++ .../AppPage/components/PermissionsMenu.tsx | 26 ++++ src/pages/AppPage/components/styled.tsx | 13 ++ src/pages/AppPage/styled.tsx | 6 + src/pages/HomePage/Home.Page.tsx | 44 ++++-- src/pages/HomePage/components/ItemKey.tsx | 16 +-- src/pages/HomePage/styled.tsx | 10 ++ src/pages/KeyPage/Key.Page.tsx | 11 +- src/pages/KeyPage/components/Apps.tsx | 2 +- src/pages/KeyPage/components/ItemApp.tsx | 2 +- src/routes/AppRoutes.tsx | 2 +- src/shared/Modal/Modal.tsx | 38 ++++- src/shared/Modal/styled.tsx | 16 +++ src/store/index.ts | 16 +++ src/utils/helpers/date.ts | 11 ++ src/utils/{ => helpers}/helpers.ts | 2 +- 32 files changed, 474 insertions(+), 75 deletions(-) delete mode 100644 src/pages/App.Page.tsx create mode 100644 src/pages/AppPage/App.Page.tsx create mode 100644 src/pages/AppPage/components/ItemPermissionMenu.tsx create mode 100644 src/pages/AppPage/components/PermissionsMenu.tsx create mode 100644 src/pages/AppPage/components/styled.tsx create mode 100644 src/pages/AppPage/styled.tsx create mode 100644 src/pages/HomePage/styled.tsx create mode 100644 src/utils/helpers/date.ts rename src/utils/{ => helpers}/helpers.ts (96%) diff --git a/package-lock.json b/package-lock.json index 10db750..b6e9280 100644 --- a/package-lock.json +++ b/package-lock.json @@ -23,7 +23,9 @@ "@types/react-copy-to-clipboard": "^5.0.7", "@types/react-dom": "^18.2.17", "crypto": "^1.0.1", + "date-fns": "^3.3.1", "dexie": "^3.2.4", + "dexie-react-hooks": "^1.1.7", "lodash.isequal": "^4.5.0", "memoize-one": "^6.0.0", "nostr-tools": "^1.17.0", @@ -7245,6 +7247,15 @@ "node": ">=10" } }, + "node_modules/date-fns": { + "version": "3.3.1", + "resolved": "https://registry.npmjs.org/date-fns/-/date-fns-3.3.1.tgz", + "integrity": "sha512-y8e109LYGgoQDveiEBD3DYXKba1jWf5BA8YU1FL5Tvm0BTdEfy54WLCwnuYWZNnzzvALy/QQ4Hov+Q9RVRv+Zw==", + "funding": { + "type": "github", + "url": "https://github.com/sponsors/kossnocorp" + } + }, "node_modules/debug": { "version": "4.3.4", "resolved": "https://registry.npmjs.org/debug/-/debug-4.3.4.tgz", @@ -7465,6 +7476,16 @@ "node": ">=6.0" } }, + "node_modules/dexie-react-hooks": { + "version": "1.1.7", + "resolved": "https://registry.npmjs.org/dexie-react-hooks/-/dexie-react-hooks-1.1.7.tgz", + "integrity": "sha512-Lwv5W0Hk+uOW3kGnsU9GZoR1er1B7WQ5DSdonoNG+focTNeJbHW6vi6nBoX534VKI3/uwHebYzSw1fwY6a7mTw==", + "peerDependencies": { + "@types/react": ">=16", + "dexie": "^3.2 || ^4.0.1-alpha", + "react": ">=16" + } + }, "node_modules/didyoumean": { "version": "1.2.2", "resolved": "https://registry.npmjs.org/didyoumean/-/didyoumean-1.2.2.tgz", @@ -23335,6 +23356,11 @@ "whatwg-url": "^8.0.0" } }, + "date-fns": { + "version": "3.3.1", + "resolved": "https://registry.npmjs.org/date-fns/-/date-fns-3.3.1.tgz", + "integrity": "sha512-y8e109LYGgoQDveiEBD3DYXKba1jWf5BA8YU1FL5Tvm0BTdEfy54WLCwnuYWZNnzzvALy/QQ4Hov+Q9RVRv+Zw==" + }, "debug": { "version": "4.3.4", "resolved": "https://registry.npmjs.org/debug/-/debug-4.3.4.tgz", @@ -23496,6 +23522,12 @@ "resolved": "https://registry.npmjs.org/dexie/-/dexie-3.2.4.tgz", "integrity": "sha512-VKoTQRSv7+RnffpOJ3Dh6ozknBqzWw/F3iqMdsZg958R0AS8AnY9x9d1lbwENr0gzeGJHXKcGhAMRaqys6SxqA==" }, + "dexie-react-hooks": { + "version": "1.1.7", + "resolved": "https://registry.npmjs.org/dexie-react-hooks/-/dexie-react-hooks-1.1.7.tgz", + "integrity": "sha512-Lwv5W0Hk+uOW3kGnsU9GZoR1er1B7WQ5DSdonoNG+focTNeJbHW6vi6nBoX534VKI3/uwHebYzSw1fwY6a7mTw==", + "requires": {} + }, "didyoumean": { "version": "1.2.2", "resolved": "https://registry.npmjs.org/didyoumean/-/didyoumean-1.2.2.tgz", diff --git a/package.json b/package.json index 9a90e46..6aafd75 100644 --- a/package.json +++ b/package.json @@ -18,7 +18,9 @@ "@types/react-copy-to-clipboard": "^5.0.7", "@types/react-dom": "^18.2.17", "crypto": "^1.0.1", + "date-fns": "^3.3.1", "dexie": "^3.2.4", + "dexie-react-hooks": "^1.1.7", "lodash.isequal": "^4.5.0", "memoize-one": "^6.0.0", "nostr-tools": "^1.17.0", diff --git a/src/components/Modal/ModalConfirmConnect/ModalConfirmConnect.tsx b/src/components/Modal/ModalConfirmConnect/ModalConfirmConnect.tsx index 885152a..a7e04d3 100644 --- a/src/components/Modal/ModalConfirmConnect/ModalConfirmConnect.tsx +++ b/src/components/Modal/ModalConfirmConnect/ModalConfirmConnect.tsx @@ -1,7 +1,7 @@ import { useModalSearchParams } from '@/hooks/useModalSearchParams' import { Modal } from '@/shared/Modal/Modal' import { MODAL_PARAMS_KEYS } from '@/types/modal' -import { call, getShortenNpub } from '@/utils/helpers' +import { call, getShortenNpub } from '@/utils/helpers/helpers' import { Avatar, Box, Stack, Typography } from '@mui/material' import { useParams, useSearchParams } from 'react-router-dom' import { useAppSelector } from '@/store/hooks/redux' diff --git a/src/components/Modal/ModalConfirmEvent/ModalConfirmEvent.tsx b/src/components/Modal/ModalConfirmEvent/ModalConfirmEvent.tsx index 915344f..3f1df3e 100644 --- a/src/components/Modal/ModalConfirmEvent/ModalConfirmEvent.tsx +++ b/src/components/Modal/ModalConfirmEvent/ModalConfirmEvent.tsx @@ -1,7 +1,7 @@ import { useModalSearchParams } from '@/hooks/useModalSearchParams' import { Modal } from '@/shared/Modal/Modal' import { MODAL_PARAMS_KEYS } from '@/types/modal' -import { call, getShortenNpub } from '@/utils/helpers' +import { call, getShortenNpub } from '@/utils/helpers/helpers' import { Avatar, Box, @@ -47,6 +47,7 @@ type ModalConfirmEventProps = { export const ACTIONS: { [type: string]: string } = { get_public_key: 'Get public key', sign_event: 'Sign event', + connect: 'Connect', } type PendingRequest = DbPending & { checked: boolean } diff --git a/src/components/Modal/ModalConnectApp/ModalConnectApp.tsx b/src/components/Modal/ModalConnectApp/ModalConnectApp.tsx index 414e218..ff66369 100644 --- a/src/components/Modal/ModalConnectApp/ModalConnectApp.tsx +++ b/src/components/Modal/ModalConnectApp/ModalConnectApp.tsx @@ -6,7 +6,7 @@ import { Input } from '@/shared/Input/Input' import { InputCopyButton } from '@/shared/InputCopyButton/InputCopyButton' import { Modal } from '@/shared/Modal/Modal' import { MODAL_PARAMS_KEYS } from '@/types/modal' -import { getBunkerLink } from '@/utils/helpers' +import { getBunkerLink } from '@/utils/helpers/helpers' import { Stack, Typography } from '@mui/material' import { useRef } from 'react' import { useParams } from 'react-router-dom' diff --git a/src/components/Modal/ModalInitial/ModalInitial.tsx b/src/components/Modal/ModalInitial/ModalInitial.tsx index 25857e0..b89d879 100644 --- a/src/components/Modal/ModalInitial/ModalInitial.tsx +++ b/src/components/Modal/ModalInitial/ModalInitial.tsx @@ -1,4 +1,4 @@ -import React, { useState } from 'react' +import React, { useEffect, useState } from 'react' import { useModalSearchParams } from '@/hooks/useModalSearchParams' import { Button } from '@/shared/Button/Button' import { Modal } from '@/shared/Modal/Modal' @@ -18,9 +18,17 @@ export const ModalInitial = () => { setShowAdvancedContent(true) } + useEffect(() => { + return () => { + if (isModalOpened) { + setShowAdvancedContent(false) + } + } + }, [isModalOpened]) + return ( - + diff --git a/src/components/Modal/ModalLogin/ModalLogin.tsx b/src/components/Modal/ModalLogin/ModalLogin.tsx index e07b3e7..01a690b 100644 --- a/src/components/Modal/ModalLogin/ModalLogin.tsx +++ b/src/components/Modal/ModalLogin/ModalLogin.tsx @@ -11,6 +11,7 @@ import { Input } from '@/shared/Input/Input' import { Button } from '@/shared/Button/Button' import VisibilityOffOutlinedIcon from '@mui/icons-material/VisibilityOffOutlined' import VisibilityOutlinedIcon from '@mui/icons-material/VisibilityOutlined' +import { useNavigate } from 'react-router-dom' export const ModalLogin = () => { const { getModalOpened, handleClose } = useModalSearchParams() @@ -19,6 +20,8 @@ export const ModalLogin = () => { const notify = useEnqueueSnackbar() + const navigate = useNavigate() + const [enteredUsername, setEnteredUsername] = useState('') const [enteredPassword, setEnteredPassword] = useState('') const [isPasswordShown, setIsPasswordShown] = useState(false) @@ -37,9 +40,9 @@ export const ModalLogin = () => { const handleSubmit = async (e: React.FormEvent) => { e.preventDefault() try { - const user = enteredUsername.split('@')[0] + const [username, domain] = enteredUsername.split('@') const response = await fetch( - 'https://domain.com/.well-known/nostr.json?name=' + user, + `https://${domain}/.well-known/nostr.json?name=${username}`, ) const getNpub: { names: { @@ -47,13 +50,13 @@ export const ModalLogin = () => { } } = await response.json() - const pubkey = getNpub.names[user] + const pubkey = getNpub.names[username] const npub = nip19.npubEncode(pubkey) const passphrase = enteredPassword console.log('fetch', npub, passphrase) - const k: any = await swicCall('fetchKey', npub, passphrase) notify(`Fetched ${k.npub}`, 'success') + navigate(`/key/${k.npub}`) } catch (error: any) { notify(error.message, 'error') } diff --git a/src/components/Modal/ModalSettings/ModalSettings.tsx b/src/components/Modal/ModalSettings/ModalSettings.tsx index 58925e9..8f87c95 100644 --- a/src/components/Modal/ModalSettings/ModalSettings.tsx +++ b/src/components/Modal/ModalSettings/ModalSettings.tsx @@ -15,9 +15,15 @@ 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' +import { useEnqueueSnackbar } from '@/hooks/useEnqueueSnackbar' +import { swicCall } from '@/modules/swic' +import { useParams } from 'react-router-dom' export const ModalSettings = () => { const { getModalOpened, handleClose } = useModalSearchParams() + const { npub = '' } = useParams<{ npub: string }>() + + const notify = useEnqueueSnackbar() const isModalOpened = getModalOpened(MODAL_PARAMS_KEYS.SETTINGS) const handleCloseModal = handleClose(MODAL_PARAMS_KEYS.SETTINGS) @@ -27,22 +33,16 @@ export const ModalSettings = () => { const [isPasswordInvalid, setIsPasswordInvalid] = useState(false) const [isPasswordSynched, setIsPasswordSynched] = useState(false) + const [isChecked, setIsChecked] = useState(false) + const handlePasswordChange = (e: ChangeEvent) => { + setIsPasswordInvalid(false) setEnteredPassword(e.target.value) } const handlePasswordTypeChange = () => setIsPasswordShown((prevState) => !prevState) - const handleSync = () => { - setIsPasswordInvalid(false) - - if (enteredPassword.trim().length < 6) { - return setIsPasswordInvalid(true) - } - setIsPasswordSynched(true) - } - const onClose = () => { handleCloseModal() setEnteredPassword('') @@ -50,10 +50,33 @@ export const ModalSettings = () => { setIsPasswordSynched(false) } + const handleChangeCheckbox = (e: unknown, checked: boolean) => { + setIsChecked(checked) + } + + const handleSubmit = async (e: React.FormEvent) => { + e.preventDefault() + setIsPasswordInvalid(false) + setIsPasswordSynched(false) + + if (enteredPassword.trim().length < 6) { + return setIsPasswordInvalid(true) + } + try { + await swicCall('saveKey', npub, enteredPassword) + notify('Key saved', 'success') + setIsPasswordInvalid(false) + setIsPasswordSynched(true) + } catch (error) { + setIsPasswordInvalid(false) + setIsPasswordSynched(false) + } + } + return ( - + Cloud sync {isPasswordSynched && ( @@ -63,7 +86,10 @@ export const ModalSettings = () => { )} - + Use this login on multiple devices @@ -82,7 +108,7 @@ export const ModalSettings = () => { )} } - type={isPasswordShown ? 'password' : 'text'} + type={isPasswordShown ? 'text' : 'password'} onChange={handlePasswordChange} value={enteredPassword} helperText={isPasswordInvalid ? 'Invalid password' : ''} @@ -94,8 +120,9 @@ export const ModalSettings = () => { }, }, }} + disabled={!isChecked} /> - + Sync diff --git a/src/components/Modal/ModalSettings/styled.tsx b/src/components/Modal/ModalSettings/styled.tsx index 94fda42..0bcf339 100644 --- a/src/components/Modal/ModalSettings/styled.tsx +++ b/src/components/Modal/ModalSettings/styled.tsx @@ -8,7 +8,7 @@ import { } from '@mui/material' export const StyledSettingContainer = styled((props: StackProps) => ( - + ))(({ theme }) => ({ padding: '0.75rem', borderRadius: '1rem', diff --git a/src/components/Modal/ModalSignUp/ModalSignUp.tsx b/src/components/Modal/ModalSignUp/ModalSignUp.tsx index 9538bda..629f27d 100644 --- a/src/components/Modal/ModalSignUp/ModalSignUp.tsx +++ b/src/components/Modal/ModalSignUp/ModalSignUp.tsx @@ -8,16 +8,18 @@ import { StyledAppLogo } from './styled' import { Input } from '@/shared/Input/Input' import { Button } from '@/shared/Button/Button' import { CheckmarkIcon } from '@/assets' +import { swicCall } from '@/modules/swic' +import { useNavigate } from 'react-router-dom' export const ModalSignUp = () => { const { getModalOpened, handleClose } = useModalSearchParams() const isModalOpened = getModalOpened(MODAL_PARAMS_KEYS.SIGN_UP) const handleCloseModal = handleClose(MODAL_PARAMS_KEYS.SIGN_UP) - - // eslint-disable-next-line @typescript-eslint/no-unused-vars const notify = useEnqueueSnackbar() const theme = useTheme() + const navigate = useNavigate() + const [enteredValue, setEnteredValue] = useState('') const handleInputChange = (e: ChangeEvent) => { @@ -36,6 +38,13 @@ export const ModalSignUp = () => { const handleSubmit = async (e: React.FormEvent) => { e.preventDefault() + try { + const k: any = await swicCall('generateKey') + notify(`New key ${k.npub}`, 'success') + navigate(`/key/${k.npub}`) + } catch (error: any) { + notify(error.message, 'error') + } } return ( @@ -77,7 +86,9 @@ export const ModalSignUp = () => { }, }} /> - + ) diff --git a/src/layout/Header/Header.tsx b/src/layout/Header/Header.tsx index dcab97a..2b770c8 100644 --- a/src/layout/Header/Header.tsx +++ b/src/layout/Header/Header.tsx @@ -7,7 +7,7 @@ import { useCallback, useEffect, useState } from 'react' import { MetaEvent } from '@/types/meta-event' import { fetchProfile } from '@/modules/nostr' import { ProfileMenu } from './components/ProfileMenu' -import { getShortenNpub } from '@/utils/helpers' +import { getShortenNpub } from '@/utils/helpers/helpers' export const Header = () => { const { npub = '' } = useParams<{ npub: string }>() diff --git a/src/layout/Header/components/ListProfiles.tsx b/src/layout/Header/components/ListProfiles.tsx index 47db6f1..fbc1c2e 100644 --- a/src/layout/Header/components/ListProfiles.tsx +++ b/src/layout/Header/components/ListProfiles.tsx @@ -1,5 +1,5 @@ import { DbKey } from '@/modules/db' -import { getShortenNpub } from '@/utils/helpers' +import { getShortenNpub } from '@/utils/helpers/helpers' import { Avatar, ListItemIcon, diff --git a/src/layout/Header/components/Menu.tsx b/src/layout/Header/components/Menu.tsx index 0be671e..29174bf 100644 --- a/src/layout/Header/components/Menu.tsx +++ b/src/layout/Header/components/Menu.tsx @@ -2,6 +2,7 @@ import { Menu as MuiMenu } from '@mui/material' import DarkModeIcon from '@mui/icons-material/DarkMode' import LightModeIcon from '@mui/icons-material/LightMode' import LoginIcon from '@mui/icons-material/Login' +import PersonAddAltRoundedIcon from '@mui/icons-material/PersonAddAltRounded' import { setThemeMode } from '@/store/reducers/ui.slice' import { useAppDispatch, useAppSelector } from '@/store/hooks/redux' import { useModalSearchParams } from '@/hooks/useModalSearchParams' @@ -10,13 +11,16 @@ import { MenuButton } from './styled' import { useOpenMenu } from '@/hooks/useOpenMenu' import { MenuItem } from './MenuItem' import MenuRoundedIcon from '@mui/icons-material/MenuRounded' +import { selectKeys } from '@/store' export const Menu = () => { const themeMode = useAppSelector((state) => state.ui.themeMode) + const keys = useAppSelector(selectKeys) const dispatch = useAppDispatch() const { handleOpen } = useModalSearchParams() const isDarkMode = themeMode === 'dark' + const isNoKeys = !keys || keys.length === 0 const { anchorEl, @@ -53,9 +57,11 @@ export const Menu = () => { }} > } + Icon={ + isNoKeys ? : + } onClick={handleNavigateToAuth} - title='Sign up' + title={isNoKeys ? 'Sign up' : 'Add account'} /> { const { handleOpen } = useModalSearchParams() const keys = useAppSelector(selectKeys) + const isNoKeys = !keys || keys.length === 0 const themeMode = useAppSelector((state) => state.ui.themeMode) const isDarkMode = themeMode === 'dark' @@ -84,9 +86,11 @@ export const ProfileMenu = () => { title='Home' /> } + Icon={ + isNoKeys ? : + } onClick={handleNavigateToAuth} - title='Sign up' + title={isNoKeys ? 'Sign up' : 'Add account'} /> { - return
AppPage
-} - -export default AppPage diff --git a/src/pages/AppPage/App.Page.tsx b/src/pages/AppPage/App.Page.tsx new file mode 100644 index 0000000..36a8856 --- /dev/null +++ b/src/pages/AppPage/App.Page.tsx @@ -0,0 +1,132 @@ +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 { formatTimestampDate } from '@/utils/helpers/date' +import { Avatar, Box, IconButton, Stack, Typography } from '@mui/material' +import { SectionTitle } from '@/shared/SectionTitle/SectionTitle' +import { getShortenNpub } from '@/utils/helpers/helpers' +import { StyledButton } from './styled' +import { PermissionsMenu } from './components/PermissionsMenu' +import { useOpenMenu } from '@/hooks/useOpenMenu' +import { ACTIONS } from '@/components/Modal/ModalConfirmEvent/ModalConfirmEvent' +import MoreIcon from '@mui/icons-material/MoreVert' + +const getAppHistoryQuery = (appNpub: string) => + db.history.where('appNpub').equals(appNpub).toArray() + +const AppPage = () => { + const { appNpub = '', npub = '' } = useParams() + 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') + + if (!currentApp) { + return + } + + const { icon = '', name = '' } = currentApp || {} + const appName = name || getShortenNpub(appNpub) + const { timestamp } = connectPerm || {} + const connectedOn = + connectPerm && timestamp + ? `Connected at ${formatTimestampDate(timestamp)}` + : 'Not connected' + + return ( + + + + + + {appName} + + + {connectedOn} + + + + + + Permissions + + Basic/Advanced/Custom {perms.length} + + + + + Activity + + {history.map((h) => { + return ( + + + + {ACTIONS[h.method] || h.method} + + + {h.allowed ? 'allow' : 'disallow'} + + + + + + + {formatTimestampDate(h.timestamp)} + + + ) + })} + + + ) +} + +export default AppPage diff --git a/src/pages/AppPage/components/ItemPermissionMenu.tsx b/src/pages/AppPage/components/ItemPermissionMenu.tsx new file mode 100644 index 0000000..658384d --- /dev/null +++ b/src/pages/AppPage/components/ItemPermissionMenu.tsx @@ -0,0 +1,37 @@ +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/PermissionsMenu.tsx b/src/pages/AppPage/components/PermissionsMenu.tsx new file mode 100644 index 0000000..a10db4d --- /dev/null +++ b/src/pages/AppPage/components/PermissionsMenu.tsx @@ -0,0 +1,26 @@ +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 new file mode 100644 index 0000000..07337b6 --- /dev/null +++ b/src/pages/AppPage/components/styled.tsx @@ -0,0 +1,13 @@ +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, + }, +})) diff --git a/src/pages/AppPage/styled.tsx b/src/pages/AppPage/styled.tsx new file mode 100644 index 0000000..494265e --- /dev/null +++ b/src/pages/AppPage/styled.tsx @@ -0,0 +1,6 @@ +import { AppButtonProps, Button } from '@/shared/Button/Button' +import { styled } from '@mui/material' + +export const StyledButton = styled((props: AppButtonProps) => ( +