chore(git): merge pull request #323 from fixes-7-3-25 into staging
All checks were successful
Release to Staging / build_and_release (push) Successful in 1m59s
All checks were successful
Release to Staging / build_and_release (push) Successful in 1m59s
Reviewed-on: #323 Reviewed-by: s <s@noreply.git.nostrdev.com>
This commit is contained in:
commit
ec9c4dad5d
8
package-lock.json
generated
8
package-lock.json
generated
@ -24,7 +24,7 @@
|
|||||||
"@nostr-dev-kit/ndk-cache-dexie": "2.5.9",
|
"@nostr-dev-kit/ndk-cache-dexie": "2.5.9",
|
||||||
"@pdf-lib/fontkit": "^1.1.1",
|
"@pdf-lib/fontkit": "^1.1.1",
|
||||||
"@reduxjs/toolkit": "2.2.1",
|
"@reduxjs/toolkit": "2.2.1",
|
||||||
"axios": "^1.7.4",
|
"axios": "^1.8.2",
|
||||||
"crypto-hash": "3.0.0",
|
"crypto-hash": "3.0.0",
|
||||||
"crypto-js": "^4.2.0",
|
"crypto-js": "^4.2.0",
|
||||||
"dexie": "4.0.8",
|
"dexie": "4.0.8",
|
||||||
@ -4051,9 +4051,9 @@
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
"node_modules/axios": {
|
"node_modules/axios": {
|
||||||
"version": "1.7.4",
|
"version": "1.8.2",
|
||||||
"resolved": "https://registry.npmjs.org/axios/-/axios-1.7.4.tgz",
|
"resolved": "https://registry.npmjs.org/axios/-/axios-1.8.2.tgz",
|
||||||
"integrity": "sha512-DukmaFRnY6AzAALSH4J2M3k6PkaC+MfaAGdEERRWcC9q3/TWQwLpHR8ZRLKTdQ3aBDL64EdluRDjJqKw+BPZEw==",
|
"integrity": "sha512-ls4GYBm5aig9vWx8AWDSGLpnpDQRtWAfrjU+EuytuODrFBkqesN2RkOQCBzrA1RQNHw1SmRMSDDDSwzNAYQ6Rg==",
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"follow-redirects": "^1.15.6",
|
"follow-redirects": "^1.15.6",
|
||||||
|
@ -35,7 +35,7 @@
|
|||||||
"@nostr-dev-kit/ndk-cache-dexie": "2.5.9",
|
"@nostr-dev-kit/ndk-cache-dexie": "2.5.9",
|
||||||
"@pdf-lib/fontkit": "^1.1.1",
|
"@pdf-lib/fontkit": "^1.1.1",
|
||||||
"@reduxjs/toolkit": "2.2.1",
|
"@reduxjs/toolkit": "2.2.1",
|
||||||
"axios": "^1.7.4",
|
"axios": "^1.8.2",
|
||||||
"crypto-hash": "3.0.0",
|
"crypto-hash": "3.0.0",
|
||||||
"crypto-js": "^4.2.0",
|
"crypto-js": "^4.2.0",
|
||||||
"dexie": "4.0.8",
|
"dexie": "4.0.8",
|
||||||
|
@ -181,7 +181,7 @@ export const AppBar = () => {
|
|||||||
onClick={() => {
|
onClick={() => {
|
||||||
setAnchorElUser(null)
|
setAnchorElUser(null)
|
||||||
|
|
||||||
navigate(appPrivateRoutes.settings)
|
navigate(appPrivateRoutes.profileSettings)
|
||||||
}}
|
}}
|
||||||
sx={{
|
sx={{
|
||||||
justifyContent: 'center'
|
justifyContent: 'center'
|
||||||
|
24
src/components/MarkTypeStrategy/DateTime/Input.tsx
Normal file
24
src/components/MarkTypeStrategy/DateTime/Input.tsx
Normal file
@ -0,0 +1,24 @@
|
|||||||
|
import { MarkInputProps } from '../MarkStrategy'
|
||||||
|
import styles from '../../MarkFormField/style.module.scss'
|
||||||
|
import { useEffect, useRef } from 'react'
|
||||||
|
|
||||||
|
export const MarkInputDateTime = ({ handler, placeholder }: MarkInputProps) => {
|
||||||
|
const ref = useRef<HTMLInputElement>(null)
|
||||||
|
useEffect(() => {
|
||||||
|
if (ref.current) {
|
||||||
|
const date = new Date()
|
||||||
|
ref.current.value = date.toISOString().slice(0, 16)
|
||||||
|
handler(date.toUTCString())
|
||||||
|
}
|
||||||
|
}, [handler])
|
||||||
|
return (
|
||||||
|
<input
|
||||||
|
type="datetime-local"
|
||||||
|
ref={ref}
|
||||||
|
className={styles.input}
|
||||||
|
placeholder={placeholder}
|
||||||
|
readOnly={true}
|
||||||
|
disabled={true}
|
||||||
|
/>
|
||||||
|
)
|
||||||
|
}
|
7
src/components/MarkTypeStrategy/DateTime/index.tsx
Normal file
7
src/components/MarkTypeStrategy/DateTime/index.tsx
Normal file
@ -0,0 +1,7 @@
|
|||||||
|
import { MarkStrategy } from '../MarkStrategy'
|
||||||
|
import { MarkInputDateTime } from './Input'
|
||||||
|
|
||||||
|
export const DateTimeStrategy: MarkStrategy = {
|
||||||
|
input: MarkInputDateTime,
|
||||||
|
render: ({ value }) => <>{value}</>
|
||||||
|
}
|
20
src/components/MarkTypeStrategy/FullName/Input.tsx
Normal file
20
src/components/MarkTypeStrategy/FullName/Input.tsx
Normal file
@ -0,0 +1,20 @@
|
|||||||
|
import { useDidMount } from '../../../hooks'
|
||||||
|
import { useLocalStorage } from '../../../hooks/useLocalStorage'
|
||||||
|
import { MarkInputProps } from '../MarkStrategy'
|
||||||
|
import { MarkInputText } from '../Text/Input'
|
||||||
|
|
||||||
|
export const MarkInputFullName = (props: MarkInputProps) => {
|
||||||
|
const [fullName, setFullName] = useLocalStorage('mark-fullname', '')
|
||||||
|
useDidMount(() => {
|
||||||
|
props.handler(fullName)
|
||||||
|
})
|
||||||
|
return MarkInputText({
|
||||||
|
...props,
|
||||||
|
placeholder: 'Full Name',
|
||||||
|
value: fullName,
|
||||||
|
handler: (value) => {
|
||||||
|
setFullName(value)
|
||||||
|
props.handler(value)
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
7
src/components/MarkTypeStrategy/FullName/index.tsx
Normal file
7
src/components/MarkTypeStrategy/FullName/index.tsx
Normal file
@ -0,0 +1,7 @@
|
|||||||
|
import { MarkStrategy } from '../MarkStrategy'
|
||||||
|
import { MarkInputFullName } from './Input'
|
||||||
|
|
||||||
|
export const FullNameStrategy: MarkStrategy = {
|
||||||
|
input: MarkInputFullName,
|
||||||
|
render: ({ value }) => <>{value}</>
|
||||||
|
}
|
20
src/components/MarkTypeStrategy/JobTitle/Input.tsx
Normal file
20
src/components/MarkTypeStrategy/JobTitle/Input.tsx
Normal file
@ -0,0 +1,20 @@
|
|||||||
|
import { useDidMount } from '../../../hooks'
|
||||||
|
import { useLocalStorage } from '../../../hooks/useLocalStorage'
|
||||||
|
import { MarkInputProps } from '../MarkStrategy'
|
||||||
|
import { MarkInputText } from '../Text/Input'
|
||||||
|
|
||||||
|
export const MarkInputJobTitle = (props: MarkInputProps) => {
|
||||||
|
const [jobTitle, setjobTitle] = useLocalStorage('mark-jobtitle', '')
|
||||||
|
useDidMount(() => {
|
||||||
|
props.handler(jobTitle)
|
||||||
|
})
|
||||||
|
return MarkInputText({
|
||||||
|
...props,
|
||||||
|
placeholder: 'Job Title',
|
||||||
|
value: jobTitle,
|
||||||
|
handler: (value) => {
|
||||||
|
setjobTitle(value)
|
||||||
|
props.handler(value)
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
7
src/components/MarkTypeStrategy/JobTitle/index.tsx
Normal file
7
src/components/MarkTypeStrategy/JobTitle/index.tsx
Normal file
@ -0,0 +1,7 @@
|
|||||||
|
import { MarkStrategy } from '../MarkStrategy'
|
||||||
|
import { MarkInputJobTitle } from './Input'
|
||||||
|
|
||||||
|
export const JobTitleStrategy: MarkStrategy = {
|
||||||
|
input: MarkInputJobTitle,
|
||||||
|
render: ({ value }) => <>{value}</>
|
||||||
|
}
|
@ -2,6 +2,9 @@ import { MarkType } from '../../types/drawing'
|
|||||||
import { CurrentUserMark, Mark } from '../../types/mark'
|
import { CurrentUserMark, Mark } from '../../types/mark'
|
||||||
import { TextStrategy } from './Text'
|
import { TextStrategy } from './Text'
|
||||||
import { SignatureStrategy } from './Signature'
|
import { SignatureStrategy } from './Signature'
|
||||||
|
import { FullNameStrategy } from './FullName'
|
||||||
|
import { JobTitleStrategy } from './JobTitle'
|
||||||
|
import { DateTimeStrategy } from './DateTime'
|
||||||
|
|
||||||
export interface MarkInputProps {
|
export interface MarkInputProps {
|
||||||
value: string
|
value: string
|
||||||
@ -28,5 +31,8 @@ export type MarkStrategies = {
|
|||||||
|
|
||||||
export const MARK_TYPE_CONFIG: MarkStrategies = {
|
export const MARK_TYPE_CONFIG: MarkStrategies = {
|
||||||
[MarkType.TEXT]: TextStrategy,
|
[MarkType.TEXT]: TextStrategy,
|
||||||
[MarkType.SIGNATURE]: SignatureStrategy
|
[MarkType.SIGNATURE]: SignatureStrategy,
|
||||||
|
[MarkType.FULLNAME]: FullNameStrategy,
|
||||||
|
[MarkType.JOBTITLE]: JobTitleStrategy,
|
||||||
|
[MarkType.DATETIME]: DateTimeStrategy
|
||||||
}
|
}
|
||||||
|
@ -8,6 +8,4 @@
|
|||||||
position: absolute;
|
position: absolute;
|
||||||
z-index: 40;
|
z-index: 40;
|
||||||
display: flex;
|
display: flex;
|
||||||
justify-content: center;
|
|
||||||
align-items: center;
|
|
||||||
}
|
}
|
||||||
|
72
src/hooks/useLocalStorage.ts
Normal file
72
src/hooks/useLocalStorage.ts
Normal file
@ -0,0 +1,72 @@
|
|||||||
|
import React, { useMemo } from 'react'
|
||||||
|
import {
|
||||||
|
getLocalStorageItem,
|
||||||
|
mergeWithInitialValue,
|
||||||
|
removeLocalStorageItem,
|
||||||
|
setLocalStorageItem
|
||||||
|
} from '../utils'
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Subscribe to the Browser's storage event. Get the new value if any of the tabs changes it.
|
||||||
|
* @param callback - function to be called when the storage event is triggered
|
||||||
|
* @returns clean up function
|
||||||
|
*/
|
||||||
|
const useLocalStorageSubscribe = (callback: () => void) => {
|
||||||
|
window.addEventListener('storage', callback)
|
||||||
|
return () => window.removeEventListener('storage', callback)
|
||||||
|
}
|
||||||
|
|
||||||
|
export function useLocalStorage<T>(
|
||||||
|
key: string,
|
||||||
|
initialValue: T
|
||||||
|
): [T, React.Dispatch<React.SetStateAction<T>>] {
|
||||||
|
const getSnapshot = () => {
|
||||||
|
// Get the stored value
|
||||||
|
const storedValue = getLocalStorageItem(key, initialValue)
|
||||||
|
|
||||||
|
// Parse the value
|
||||||
|
const parsedStoredValue = JSON.parse(storedValue)
|
||||||
|
|
||||||
|
// Merge the default and the stored in case some of the required fields are missing
|
||||||
|
return JSON.stringify(
|
||||||
|
mergeWithInitialValue(parsedStoredValue, initialValue)
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
// https://react.dev/reference/react/useSyncExternalStore
|
||||||
|
// Returns the snapshot of the data and subscribes to the storage event
|
||||||
|
const data = React.useSyncExternalStore(useLocalStorageSubscribe, getSnapshot)
|
||||||
|
|
||||||
|
// Takes the value or a function that returns the value and updates the local storage
|
||||||
|
const setState: React.Dispatch<React.SetStateAction<T>> = React.useCallback(
|
||||||
|
(v: React.SetStateAction<T>) => {
|
||||||
|
try {
|
||||||
|
const nextState =
|
||||||
|
typeof v === 'function'
|
||||||
|
? (v as (prevState: T) => T)(JSON.parse(data))
|
||||||
|
: v
|
||||||
|
|
||||||
|
if (nextState === undefined || nextState === null) {
|
||||||
|
removeLocalStorageItem(key)
|
||||||
|
} else {
|
||||||
|
setLocalStorageItem(key, JSON.stringify(nextState))
|
||||||
|
}
|
||||||
|
} catch (e) {
|
||||||
|
console.warn(e)
|
||||||
|
}
|
||||||
|
},
|
||||||
|
[data, key]
|
||||||
|
)
|
||||||
|
|
||||||
|
React.useEffect(() => {
|
||||||
|
// Set local storage only when it's empty
|
||||||
|
const data = window.localStorage.getItem(key)
|
||||||
|
if (data === null) {
|
||||||
|
setLocalStorageItem(key, JSON.stringify(initialValue))
|
||||||
|
}
|
||||||
|
}, [key, initialValue])
|
||||||
|
|
||||||
|
const memoized = useMemo(() => JSON.parse(data) as T, [data])
|
||||||
|
|
||||||
|
return [memoized, setState]
|
||||||
|
}
|
@ -13,7 +13,7 @@ import { Footer } from '../../components/Footer/Footer'
|
|||||||
import { LoadingSpinner } from '../../components/LoadingSpinner'
|
import { LoadingSpinner } from '../../components/LoadingSpinner'
|
||||||
import { useAppSelector } from '../../hooks/store'
|
import { useAppSelector } from '../../hooks/store'
|
||||||
|
|
||||||
import { getProfileSettingsRoute } from '../../routes'
|
import { appPrivateRoutes } from '../../routes'
|
||||||
|
|
||||||
import {
|
import {
|
||||||
getProfileUsername,
|
getProfileUsername,
|
||||||
@ -168,7 +168,7 @@ export const ProfilePage = () => {
|
|||||||
<Box className={styles.right}>
|
<Box className={styles.right}>
|
||||||
{isUsersOwnProfile && (
|
{isUsersOwnProfile && (
|
||||||
<IconButton
|
<IconButton
|
||||||
onClick={() => navigate(getProfileSettingsRoute(pubkey))}
|
onClick={() => navigate(appPrivateRoutes.profileSettings)}
|
||||||
>
|
>
|
||||||
<EditIcon />
|
<EditIcon />
|
||||||
</IconButton>
|
</IconButton>
|
||||||
|
@ -1,94 +1,82 @@
|
|||||||
import AccountCircleIcon from '@mui/icons-material/AccountCircle'
|
import AccountCircleIcon from '@mui/icons-material/AccountCircle'
|
||||||
import ArrowForwardIosIcon from '@mui/icons-material/ArrowForwardIos'
|
|
||||||
import CachedIcon from '@mui/icons-material/Cached'
|
|
||||||
import RouterIcon from '@mui/icons-material/Router'
|
import RouterIcon from '@mui/icons-material/Router'
|
||||||
import { ListItem, useTheme } from '@mui/material'
|
import { Button } from '@mui/material'
|
||||||
import List from '@mui/material/List'
|
|
||||||
import ListItemIcon from '@mui/material/ListItemIcon'
|
|
||||||
import ListItemText from '@mui/material/ListItemText'
|
|
||||||
import ListSubheader from '@mui/material/ListSubheader'
|
|
||||||
import { useAppSelector } from '../../hooks/store'
|
import { useAppSelector } from '../../hooks/store'
|
||||||
import { Link } from 'react-router-dom'
|
import { NavLink, Outlet, To } from 'react-router-dom'
|
||||||
import { appPrivateRoutes, getProfileSettingsRoute } from '../../routes'
|
import { appPrivateRoutes } from '../../routes'
|
||||||
import { Container } from '../../components/Container'
|
import { Container } from '../../components/Container'
|
||||||
import { Footer } from '../../components/Footer/Footer'
|
import { Footer } from '../../components/Footer/Footer'
|
||||||
import ExtensionIcon from '@mui/icons-material/Extension'
|
import ExtensionIcon from '@mui/icons-material/Extension'
|
||||||
import { LoginMethod } from '../../store/auth/types'
|
import { LoginMethod } from '../../store/auth/types'
|
||||||
|
import styles from './style.module.scss'
|
||||||
|
import { ReactNode } from 'react'
|
||||||
|
|
||||||
export const SettingsPage = () => {
|
const Item = (to: To, icon: ReactNode, label: string) => {
|
||||||
const theme = useTheme()
|
|
||||||
const { usersPubkey, loginMethod } = useAppSelector((state) => state.auth)
|
|
||||||
const listItem = (label: string, disabled = false) => {
|
|
||||||
return (
|
return (
|
||||||
<>
|
<NavLink to={to}>
|
||||||
<ListItemText
|
{({ isActive }) => (
|
||||||
primary={label}
|
<Button
|
||||||
|
fullWidth
|
||||||
sx={{
|
sx={{
|
||||||
color: theme.palette.text.primary
|
transition: 'ease 0.3s',
|
||||||
|
justifyContent: 'start',
|
||||||
|
gap: '10px',
|
||||||
|
background: 'rgba(76,130,163,0)',
|
||||||
|
color: '#434343',
|
||||||
|
fontWeight: 600,
|
||||||
|
opacity: 0.75,
|
||||||
|
textTransform: 'none',
|
||||||
|
...(isActive
|
||||||
|
? {
|
||||||
|
background: '#447592',
|
||||||
|
color: 'white'
|
||||||
|
}
|
||||||
|
: {}),
|
||||||
|
'&:hover': {
|
||||||
|
opacity: 0.85,
|
||||||
|
gap: '15px',
|
||||||
|
background: '#5e8eab',
|
||||||
|
color: 'white'
|
||||||
|
}
|
||||||
}}
|
}}
|
||||||
/>
|
variant={'text'}
|
||||||
|
>
|
||||||
{!disabled && (
|
{icon}
|
||||||
<ArrowForwardIosIcon
|
{label}
|
||||||
style={{
|
</Button>
|
||||||
color: theme.palette.action.active,
|
|
||||||
marginRight: -10
|
|
||||||
}}
|
|
||||||
/>
|
|
||||||
)}
|
)}
|
||||||
</>
|
</NavLink>
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export const SettingsLayout = () => {
|
||||||
|
const { loginMethod } = useAppSelector((state) => state.auth)
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<>
|
<>
|
||||||
<Container>
|
<Container>
|
||||||
<List
|
<h2 className={styles.title}>Settings</h2>
|
||||||
sx={{
|
<div className={styles.main}>
|
||||||
width: '100%',
|
<div>
|
||||||
bgcolor: 'background.paper'
|
<aside className={styles.aside}>
|
||||||
}}
|
{Item(
|
||||||
subheader={
|
appPrivateRoutes.profileSettings,
|
||||||
<ListSubheader
|
<AccountCircleIcon />,
|
||||||
sx={{
|
'Profile'
|
||||||
fontSize: '1.5rem',
|
|
||||||
borderBottom: '0.5px solid',
|
|
||||||
paddingBottom: 2,
|
|
||||||
paddingTop: 2,
|
|
||||||
zIndex: 2
|
|
||||||
}}
|
|
||||||
>
|
|
||||||
Settings
|
|
||||||
</ListSubheader>
|
|
||||||
}
|
|
||||||
>
|
|
||||||
<ListItem component={Link} to={getProfileSettingsRoute(usersPubkey!)}>
|
|
||||||
<ListItemIcon>
|
|
||||||
<AccountCircleIcon />
|
|
||||||
</ListItemIcon>
|
|
||||||
{listItem('Profile')}
|
|
||||||
</ListItem>
|
|
||||||
<ListItem component={Link} to={appPrivateRoutes.relays}>
|
|
||||||
<ListItemIcon>
|
|
||||||
<RouterIcon />
|
|
||||||
</ListItemIcon>
|
|
||||||
{listItem('Relays')}
|
|
||||||
</ListItem>
|
|
||||||
<ListItem component={Link} to={appPrivateRoutes.cacheSettings}>
|
|
||||||
<ListItemIcon>
|
|
||||||
<CachedIcon />
|
|
||||||
</ListItemIcon>
|
|
||||||
{listItem('Local Cache')}
|
|
||||||
</ListItem>
|
|
||||||
{loginMethod === LoginMethod.nostrLogin && (
|
|
||||||
<ListItem component={Link} to={appPrivateRoutes.nostrLogin}>
|
|
||||||
<ListItemIcon>
|
|
||||||
<ExtensionIcon />
|
|
||||||
</ListItemIcon>
|
|
||||||
{listItem('Nostr Login')}
|
|
||||||
</ListItem>
|
|
||||||
)}
|
)}
|
||||||
</List>
|
{Item(appPrivateRoutes.relays, <RouterIcon />, 'Relays')}
|
||||||
|
{loginMethod === LoginMethod.nostrLogin &&
|
||||||
|
Item(
|
||||||
|
appPrivateRoutes.nostrLogin,
|
||||||
|
<ExtensionIcon />,
|
||||||
|
'Nostr Login'
|
||||||
|
)}
|
||||||
|
</aside>
|
||||||
|
</div>
|
||||||
|
<div className={styles.content}>
|
||||||
|
<Outlet />
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
</Container>
|
</Container>
|
||||||
<Footer />
|
<Footer />
|
||||||
</>
|
</>
|
||||||
|
69
src/pages/settings/cache/index.tsx
vendored
69
src/pages/settings/cache/index.tsx
vendored
@ -1,69 +0,0 @@
|
|||||||
import InputIcon from '@mui/icons-material/Input'
|
|
||||||
import IosShareIcon from '@mui/icons-material/IosShare'
|
|
||||||
import {
|
|
||||||
List,
|
|
||||||
ListItemButton,
|
|
||||||
ListItemIcon,
|
|
||||||
ListItemText,
|
|
||||||
ListSubheader,
|
|
||||||
useTheme
|
|
||||||
} from '@mui/material'
|
|
||||||
import { Container } from '../../../components/Container'
|
|
||||||
import { Footer } from '../../../components/Footer/Footer'
|
|
||||||
|
|
||||||
export const CacheSettingsPage = () => {
|
|
||||||
const theme = useTheme()
|
|
||||||
|
|
||||||
const listItem = (label: string) => {
|
|
||||||
return (
|
|
||||||
<ListItemText
|
|
||||||
primary={label}
|
|
||||||
sx={{
|
|
||||||
color: theme.palette.text.primary
|
|
||||||
}}
|
|
||||||
/>
|
|
||||||
)
|
|
||||||
}
|
|
||||||
|
|
||||||
return (
|
|
||||||
<>
|
|
||||||
<Container>
|
|
||||||
<List
|
|
||||||
sx={{
|
|
||||||
width: '100%',
|
|
||||||
bgcolor: 'background.paper',
|
|
||||||
marginTop: 2
|
|
||||||
}}
|
|
||||||
subheader={
|
|
||||||
<ListSubheader
|
|
||||||
sx={{
|
|
||||||
fontSize: '1.5rem',
|
|
||||||
borderBottom: '0.5px solid',
|
|
||||||
paddingBottom: 2,
|
|
||||||
paddingTop: 2,
|
|
||||||
zIndex: 2
|
|
||||||
}}
|
|
||||||
>
|
|
||||||
Cache Setting
|
|
||||||
</ListSubheader>
|
|
||||||
}
|
|
||||||
>
|
|
||||||
<ListItemButton disabled>
|
|
||||||
<ListItemIcon>
|
|
||||||
<IosShareIcon />
|
|
||||||
</ListItemIcon>
|
|
||||||
{listItem('Export (coming soon)')}
|
|
||||||
</ListItemButton>
|
|
||||||
|
|
||||||
<ListItemButton disabled>
|
|
||||||
<ListItemIcon>
|
|
||||||
<InputIcon />
|
|
||||||
</ListItemIcon>
|
|
||||||
{listItem('Import (coming soon)')}
|
|
||||||
</ListItemButton>
|
|
||||||
</List>
|
|
||||||
</Container>
|
|
||||||
<Footer />
|
|
||||||
</>
|
|
||||||
)
|
|
||||||
}
|
|
@ -3,11 +3,9 @@ import {
|
|||||||
ListItemButton,
|
ListItemButton,
|
||||||
ListItemIcon,
|
ListItemIcon,
|
||||||
ListItemText,
|
ListItemText,
|
||||||
ListSubheader,
|
|
||||||
useTheme
|
useTheme
|
||||||
} from '@mui/material'
|
} from '@mui/material'
|
||||||
import { launch as launchNostrLoginDialog } from 'nostr-login'
|
import { launch as launchNostrLoginDialog } from 'nostr-login'
|
||||||
import { Container } from '../../../components/Container'
|
|
||||||
import PeopleIcon from '@mui/icons-material/People'
|
import PeopleIcon from '@mui/icons-material/People'
|
||||||
import ImportExportIcon from '@mui/icons-material/ImportExport'
|
import ImportExportIcon from '@mui/icons-material/ImportExport'
|
||||||
import { useAppSelector } from '../../../hooks/store'
|
import { useAppSelector } from '../../../hooks/store'
|
||||||
@ -20,26 +18,7 @@ export const NostrLoginPage = () => {
|
|||||||
)
|
)
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Container>
|
<List>
|
||||||
<List
|
|
||||||
sx={{
|
|
||||||
width: '100%',
|
|
||||||
bgcolor: 'background.paper'
|
|
||||||
}}
|
|
||||||
subheader={
|
|
||||||
<ListSubheader
|
|
||||||
sx={{
|
|
||||||
fontSize: '1.5rem',
|
|
||||||
borderBottom: '0.5px solid',
|
|
||||||
paddingBottom: 2,
|
|
||||||
paddingTop: 2,
|
|
||||||
zIndex: 2
|
|
||||||
}}
|
|
||||||
>
|
|
||||||
Nostr Settings
|
|
||||||
</ListSubheader>
|
|
||||||
}
|
|
||||||
>
|
|
||||||
<ListItemButton
|
<ListItemButton
|
||||||
onClick={() => {
|
onClick={() => {
|
||||||
launchNostrLoginDialog('switch-account')
|
launchNostrLoginDialog('switch-account')
|
||||||
@ -73,6 +52,5 @@ export const NostrLoginPage = () => {
|
|||||||
</ListItemButton>
|
</ListItemButton>
|
||||||
)}
|
)}
|
||||||
</List>
|
</List>
|
||||||
</Container>
|
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
@ -1,5 +1,4 @@
|
|||||||
import React, { useEffect, useRef, useState } from 'react'
|
import React, { useEffect, useRef, useState } from 'react'
|
||||||
import { useParams } from 'react-router-dom'
|
|
||||||
import { toast } from 'react-toastify'
|
import { toast } from 'react-toastify'
|
||||||
|
|
||||||
import { SmartToy } from '@mui/icons-material'
|
import { SmartToy } from '@mui/icons-material'
|
||||||
@ -12,7 +11,6 @@ import {
|
|||||||
InputProps,
|
InputProps,
|
||||||
List,
|
List,
|
||||||
ListItem,
|
ListItem,
|
||||||
ListSubheader,
|
|
||||||
TextField,
|
TextField,
|
||||||
Tooltip
|
Tooltip
|
||||||
} from '@mui/material'
|
} from '@mui/material'
|
||||||
@ -28,8 +26,6 @@ import { useAppDispatch, useAppSelector } from '../../../hooks/store'
|
|||||||
|
|
||||||
import { getRoboHashPicture, unixNow } from '../../../utils'
|
import { getRoboHashPicture, unixNow } from '../../../utils'
|
||||||
|
|
||||||
import { Container } from '../../../components/Container'
|
|
||||||
import { Footer } from '../../../components/Footer/Footer'
|
|
||||||
import { LoadingSpinner } from '../../../components/LoadingSpinner'
|
import { LoadingSpinner } from '../../../components/LoadingSpinner'
|
||||||
|
|
||||||
import { setUserProfile as updateUserProfile } from '../../../store/actions'
|
import { setUserProfile as updateUserProfile } from '../../../store/actions'
|
||||||
@ -41,10 +37,8 @@ import styles from './style.module.scss'
|
|||||||
export const ProfileSettingsPage = () => {
|
export const ProfileSettingsPage = () => {
|
||||||
const dispatch: Dispatch = useAppDispatch()
|
const dispatch: Dispatch = useAppDispatch()
|
||||||
|
|
||||||
const { npub } = useParams()
|
|
||||||
const { ndk, findMetadata, publish } = useNDKContext()
|
const { ndk, findMetadata, publish } = useNDKContext()
|
||||||
|
|
||||||
const [pubkey, setPubkey] = useState<string>()
|
|
||||||
const [userProfile, setUserProfile] = useState<NDKUserProfile | null>(null)
|
const [userProfile, setUserProfile] = useState<NDKUserProfile | null>(null)
|
||||||
|
|
||||||
const userRobotImage = useAppSelector((state) => state.user.robotImage)
|
const userRobotImage = useAppSelector((state) => state.user.robotImage)
|
||||||
@ -55,27 +49,13 @@ export const ProfileSettingsPage = () => {
|
|||||||
)
|
)
|
||||||
|
|
||||||
const [savingProfileMetadata, setSavingProfileMetadata] = useState(false)
|
const [savingProfileMetadata, setSavingProfileMetadata] = useState(false)
|
||||||
const [isUsersOwnProfile, setIsUsersOwnProfile] = useState(false)
|
|
||||||
const [isLoading, setIsLoading] = useState(true)
|
const [isLoading, setIsLoading] = useState(true)
|
||||||
const [loadingSpinnerDesc] = useState('Fetching metadata')
|
const [loadingSpinnerDesc] = useState('Fetching metadata')
|
||||||
|
|
||||||
const robotSet = useRef(1)
|
const robotSet = useRef(1)
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
if (npub) {
|
if (usersPubkey && currentUserProfile) {
|
||||||
try {
|
|
||||||
const hexPubkey = nip19.decode(npub).data as string
|
|
||||||
setPubkey(hexPubkey)
|
|
||||||
|
|
||||||
if (hexPubkey === usersPubkey) setIsUsersOwnProfile(true)
|
|
||||||
} catch (error) {
|
|
||||||
toast.error('Error occurred in decoding npub' + error)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}, [npub, usersPubkey])
|
|
||||||
|
|
||||||
useEffect(() => {
|
|
||||||
if (isUsersOwnProfile && currentUserProfile) {
|
|
||||||
setUserProfile(currentUserProfile)
|
setUserProfile(currentUserProfile)
|
||||||
|
|
||||||
setIsLoading(false)
|
setIsLoading(false)
|
||||||
@ -83,8 +63,8 @@ export const ProfileSettingsPage = () => {
|
|||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
if (pubkey) {
|
if (usersPubkey) {
|
||||||
findMetadata(pubkey)
|
findMetadata(usersPubkey)
|
||||||
.then((profile) => {
|
.then((profile) => {
|
||||||
setUserProfile(profile)
|
setUserProfile(profile)
|
||||||
})
|
})
|
||||||
@ -95,7 +75,7 @@ export const ProfileSettingsPage = () => {
|
|||||||
setIsLoading(false)
|
setIsLoading(false)
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
}, [ndk, isUsersOwnProfile, currentUserProfile, pubkey, findMetadata])
|
}, [ndk, currentUserProfile, findMetadata, usersPubkey])
|
||||||
|
|
||||||
const editItem = (
|
const editItem = (
|
||||||
key: keyof NDKUserProfile,
|
key: keyof NDKUserProfile,
|
||||||
@ -113,7 +93,6 @@ export const ProfileSettingsPage = () => {
|
|||||||
multiline={multiline}
|
multiline={multiline}
|
||||||
rows={rows}
|
rows={rows}
|
||||||
className={styles.textField}
|
className={styles.textField}
|
||||||
disabled={!isUsersOwnProfile}
|
|
||||||
InputProps={inputProps}
|
InputProps={inputProps}
|
||||||
onChange={(event: React.ChangeEvent<HTMLInputElement>) => {
|
onChange={(event: React.ChangeEvent<HTMLInputElement>) => {
|
||||||
const { value } = event.target
|
const { value } = event.target
|
||||||
@ -170,7 +149,7 @@ export const ProfileSettingsPage = () => {
|
|||||||
content: serializedProfile,
|
content: serializedProfile,
|
||||||
created_at: unixNow(),
|
created_at: unixNow(),
|
||||||
kind: kinds.Metadata,
|
kind: kinds.Metadata,
|
||||||
pubkey: pubkey!,
|
pubkey: usersPubkey!,
|
||||||
tags: []
|
tags: []
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -215,7 +194,7 @@ export const ProfileSettingsPage = () => {
|
|||||||
robotSet.current++
|
robotSet.current++
|
||||||
if (robotSet.current > 5) robotSet.current = 1
|
if (robotSet.current > 5) robotSet.current = 1
|
||||||
|
|
||||||
const robotAvatarLink = getRoboHashPicture(npub!, robotSet.current)
|
const robotAvatarLink = getRoboHashPicture(usersPubkey!, robotSet.current)
|
||||||
|
|
||||||
setUserProfile((prev) => ({
|
setUserProfile((prev) => ({
|
||||||
...prev,
|
...prev,
|
||||||
@ -244,38 +223,15 @@ export const ProfileSettingsPage = () => {
|
|||||||
* @returns robohash image url
|
* @returns robohash image url
|
||||||
*/
|
*/
|
||||||
const getProfileImage = (profile: NDKUserProfile) => {
|
const getProfileImage = (profile: NDKUserProfile) => {
|
||||||
if (!isUsersOwnProfile) {
|
|
||||||
return profile.image || getRoboHashPicture(npub!)
|
|
||||||
}
|
|
||||||
|
|
||||||
// userRobotImage is used only when visiting own profile
|
// userRobotImage is used only when visiting own profile
|
||||||
// while kind 0 picture is not set
|
// while kind 0 picture is not set
|
||||||
return profile.image || userRobotImage || getRoboHashPicture(npub!)
|
return profile.image || userRobotImage || getRoboHashPicture(usersPubkey!)
|
||||||
}
|
}
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<>
|
<>
|
||||||
{isLoading && <LoadingSpinner desc={loadingSpinnerDesc} />}
|
{isLoading && <LoadingSpinner desc={loadingSpinnerDesc} />}
|
||||||
<Container className={styles.container}>
|
<List>
|
||||||
<List
|
|
||||||
sx={{
|
|
||||||
bgcolor: 'background.paper',
|
|
||||||
marginTop: 2
|
|
||||||
}}
|
|
||||||
subheader={
|
|
||||||
<ListSubheader
|
|
||||||
sx={{
|
|
||||||
paddingBottom: 1,
|
|
||||||
paddingTop: 1,
|
|
||||||
fontSize: '1.5rem',
|
|
||||||
zIndex: 2
|
|
||||||
}}
|
|
||||||
className={styles.subHeader}
|
|
||||||
>
|
|
||||||
Profile Settings
|
|
||||||
</ListSubheader>
|
|
||||||
}
|
|
||||||
>
|
|
||||||
{userProfile && (
|
{userProfile && (
|
||||||
<div>
|
<div>
|
||||||
<ListItem
|
<ListItem
|
||||||
@ -307,7 +263,7 @@ export const ProfileSettingsPage = () => {
|
|||||||
>
|
>
|
||||||
<img
|
<img
|
||||||
onError={(event: React.SyntheticEvent<HTMLImageElement>) => {
|
onError={(event: React.SyntheticEvent<HTMLImageElement>) => {
|
||||||
event.currentTarget.src = getRoboHashPicture(npub!)
|
event.currentTarget.src = getRoboHashPicture(usersPubkey!)
|
||||||
}}
|
}}
|
||||||
className={styles.img}
|
className={styles.img}
|
||||||
src={getProfileImage(userProfile)}
|
src={getProfileImage(userProfile)}
|
||||||
@ -316,7 +272,7 @@ export const ProfileSettingsPage = () => {
|
|||||||
</ListItem>
|
</ListItem>
|
||||||
|
|
||||||
{editItem('image', 'Picture URL', undefined, undefined, {
|
{editItem('image', 'Picture URL', undefined, undefined, {
|
||||||
endAdornment: isUsersOwnProfile ? robohashButton() : undefined
|
endAdornment: robohashButton()
|
||||||
})}
|
})}
|
||||||
|
|
||||||
{editItem('name', 'Username')}
|
{editItem('name', 'Username')}
|
||||||
@ -325,11 +281,8 @@ export const ProfileSettingsPage = () => {
|
|||||||
{editItem('lud16', 'Lightning Address (lud16)')}
|
{editItem('lud16', 'Lightning Address (lud16)')}
|
||||||
{editItem('about', 'About', true, 4)}
|
{editItem('about', 'About', true, 4)}
|
||||||
{editItem('website', 'Website')}
|
{editItem('website', 'Website')}
|
||||||
{isUsersOwnProfile && (
|
|
||||||
<>
|
|
||||||
{usersPubkey &&
|
{usersPubkey &&
|
||||||
copyItem(nip19.npubEncode(usersPubkey), 'Public Key')}
|
copyItem(nip19.npubEncode(usersPubkey), 'Public Key')}
|
||||||
|
|
||||||
{loginMethod === LoginMethod.privateKey &&
|
{loginMethod === LoginMethod.privateKey &&
|
||||||
keys &&
|
keys &&
|
||||||
keys.private &&
|
keys.private &&
|
||||||
@ -338,10 +291,6 @@ export const ProfileSettingsPage = () => {
|
|||||||
'Private Key',
|
'Private Key',
|
||||||
keys.private
|
keys.private
|
||||||
)}
|
)}
|
||||||
</>
|
|
||||||
)}
|
|
||||||
{isUsersOwnProfile && (
|
|
||||||
<>
|
|
||||||
{loginMethod === LoginMethod.nostrLogin &&
|
{loginMethod === LoginMethod.nostrLogin &&
|
||||||
nostrLoginAuthMethod === NostrLoginAuthMethod.Local && (
|
nostrLoginAuthMethod === NostrLoginAuthMethod.Local && (
|
||||||
<ListItem
|
<ListItem
|
||||||
@ -358,29 +307,22 @@ export const ProfileSettingsPage = () => {
|
|||||||
disabled
|
disabled
|
||||||
type={'password'}
|
type={'password'}
|
||||||
InputProps={{
|
InputProps={{
|
||||||
endAdornment: (
|
endAdornment: <LaunchIcon className={styles.copyItem} />
|
||||||
<LaunchIcon className={styles.copyItem} />
|
|
||||||
)
|
|
||||||
}}
|
}}
|
||||||
/>
|
/>
|
||||||
</ListItem>
|
</ListItem>
|
||||||
)}
|
)}
|
||||||
</>
|
|
||||||
)}
|
|
||||||
</div>
|
</div>
|
||||||
)}
|
)}
|
||||||
</List>
|
</List>
|
||||||
{isUsersOwnProfile && (
|
|
||||||
<LoadingButton
|
<LoadingButton
|
||||||
|
sx={{ maxWidth: '300px', alignSelf: 'center', width: '100%' }}
|
||||||
loading={savingProfileMetadata}
|
loading={savingProfileMetadata}
|
||||||
variant="contained"
|
variant="contained"
|
||||||
onClick={handleSaveMetadata}
|
onClick={handleSaveMetadata}
|
||||||
>
|
>
|
||||||
SAVE
|
PUBLISH CHANGES
|
||||||
</LoadingButton>
|
</LoadingButton>
|
||||||
)}
|
|
||||||
</Container>
|
|
||||||
<Footer />
|
|
||||||
</>
|
</>
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
@ -1,9 +1,3 @@
|
|||||||
.container {
|
|
||||||
display: flex;
|
|
||||||
flex-direction: column;
|
|
||||||
gap: 25px;
|
|
||||||
}
|
|
||||||
|
|
||||||
.textField {
|
.textField {
|
||||||
width: 100%;
|
width: 100%;
|
||||||
}
|
}
|
||||||
|
@ -12,7 +12,6 @@ import ListItemText from '@mui/material/ListItemText'
|
|||||||
import Switch from '@mui/material/Switch'
|
import Switch from '@mui/material/Switch'
|
||||||
import { useEffect, useState } from 'react'
|
import { useEffect, useState } from 'react'
|
||||||
import { toast } from 'react-toastify'
|
import { toast } from 'react-toastify'
|
||||||
import { Container } from '../../../components/Container'
|
|
||||||
import {
|
import {
|
||||||
useAppDispatch,
|
useAppDispatch,
|
||||||
useAppSelector,
|
useAppSelector,
|
||||||
@ -32,7 +31,6 @@ import {
|
|||||||
timeout
|
timeout
|
||||||
} from '../../../utils'
|
} from '../../../utils'
|
||||||
import styles from './style.module.scss'
|
import styles from './style.module.scss'
|
||||||
import { Footer } from '../../../components/Footer/Footer'
|
|
||||||
import {
|
import {
|
||||||
getRelayListForUser,
|
getRelayListForUser,
|
||||||
NDKRelayList,
|
NDKRelayList,
|
||||||
@ -246,7 +244,7 @@ export const RelaysPage = () => {
|
|||||||
}
|
}
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Container className={styles.container}>
|
<>
|
||||||
<Box className={styles.relayAddContainer}>
|
<Box className={styles.relayAddContainer}>
|
||||||
<TextField
|
<TextField
|
||||||
label="Add new relay"
|
label="Add new relay"
|
||||||
@ -291,8 +289,7 @@ export const RelaysPage = () => {
|
|||||||
))}
|
))}
|
||||||
</Box>
|
</Box>
|
||||||
)}
|
)}
|
||||||
<Footer />
|
</>
|
||||||
</Container>
|
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -1,8 +1,5 @@
|
|||||||
@import '../../../styles/colors.scss';
|
@import '../../../styles/colors.scss';
|
||||||
|
|
||||||
.container {
|
|
||||||
color: $text-color;
|
|
||||||
|
|
||||||
.relayURItextfield {
|
.relayURItextfield {
|
||||||
width: 100%;
|
width: 100%;
|
||||||
}
|
}
|
||||||
@ -104,4 +101,3 @@
|
|||||||
background-color: $input-text-color;
|
background-color: $input-text-color;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
43
src/pages/settings/style.module.scss
Normal file
43
src/pages/settings/style.module.scss
Normal file
@ -0,0 +1,43 @@
|
|||||||
|
.title {
|
||||||
|
margin: 0 0 15px 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
.main {
|
||||||
|
width: 100%;
|
||||||
|
display: grid;
|
||||||
|
grid-template-columns: 0.4fr 1.6fr;
|
||||||
|
position: relative;
|
||||||
|
grid-gap: 25px;
|
||||||
|
|
||||||
|
>* {
|
||||||
|
width: 100%;
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
grid-gap: 25px;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.aside {
|
||||||
|
width: 100%;
|
||||||
|
background: white;
|
||||||
|
padding: 15px;
|
||||||
|
border-radius: 5px;
|
||||||
|
box-shadow: 0 0 4px 0 rgb(0, 0, 0, 0.1);
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
grid-gap: 15px;
|
||||||
|
|
||||||
|
position: sticky;
|
||||||
|
top: 15px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.content {
|
||||||
|
width: 100%;
|
||||||
|
background: white;
|
||||||
|
padding: 15px;
|
||||||
|
border-radius: 5px;
|
||||||
|
box-shadow: 0 0 4px 0 rgb(0, 0, 0, 0.1);
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
grid-gap: 15px;
|
||||||
|
}
|
@ -4,9 +4,7 @@ export const appPrivateRoutes = {
|
|||||||
homePage: '/',
|
homePage: '/',
|
||||||
create: '/create',
|
create: '/create',
|
||||||
sign: '/sign',
|
sign: '/sign',
|
||||||
settings: '/settings',
|
profileSettings: '/settings/profile',
|
||||||
profileSettings: '/settings/profile/:npub',
|
|
||||||
cacheSettings: '/settings/cache',
|
|
||||||
relays: '/settings/relays',
|
relays: '/settings/relays',
|
||||||
nostrLogin: '/settings/nostrLogin'
|
nostrLogin: '/settings/nostrLogin'
|
||||||
}
|
}
|
||||||
@ -24,6 +22,3 @@ export const appPublicRoutes = {
|
|||||||
|
|
||||||
export const getProfileRoute = (hexKey: string) =>
|
export const getProfileRoute = (hexKey: string) =>
|
||||||
appPublicRoutes.profile.replace(':npub', hexToNpub(hexKey))
|
appPublicRoutes.profile.replace(':npub', hexToNpub(hexKey))
|
||||||
|
|
||||||
export const getProfileSettingsRoute = (hexKey: string) =>
|
|
||||||
appPrivateRoutes.profileSettings.replace(':npub', hexToNpub(hexKey))
|
|
||||||
|
@ -4,11 +4,10 @@ import { CreatePage } from '../pages/create'
|
|||||||
import { HomePage } from '../pages/home'
|
import { HomePage } from '../pages/home'
|
||||||
import { LandingPage } from '../pages/landing'
|
import { LandingPage } from '../pages/landing'
|
||||||
import { ProfilePage } from '../pages/profile'
|
import { ProfilePage } from '../pages/profile'
|
||||||
import { CacheSettingsPage } from '../pages/settings/cache'
|
|
||||||
import { NostrLoginPage } from '../pages/settings/nostrLogin'
|
import { NostrLoginPage } from '../pages/settings/nostrLogin'
|
||||||
import { ProfileSettingsPage } from '../pages/settings/profile'
|
import { ProfileSettingsPage } from '../pages/settings/profile'
|
||||||
import { RelaysPage } from '../pages/settings/relays'
|
import { RelaysPage } from '../pages/settings/relays'
|
||||||
import { SettingsPage } from '../pages/settings/Settings'
|
import { SettingsLayout } from '../pages/settings/Settings'
|
||||||
import { SignPage } from '../pages/sign'
|
import { SignPage } from '../pages/sign'
|
||||||
import { VerifyPage } from '../pages/verify'
|
import { VerifyPage } from '../pages/verify'
|
||||||
import { PrivateRoute } from './PrivateRoute'
|
import { PrivateRoute } from './PrivateRoute'
|
||||||
@ -38,7 +37,7 @@ export function recursiveRouteRenderer<T>(
|
|||||||
return routes.map((route, index) =>
|
return routes.map((route, index) =>
|
||||||
renderConditionCallbackFn(route) ? (
|
renderConditionCallbackFn(route) ? (
|
||||||
<Route
|
<Route
|
||||||
key={`${route.path}${index}`}
|
key={route.path ? `${route.path}${index}` : index}
|
||||||
path={route.path}
|
path={route.path}
|
||||||
element={route.element}
|
element={route.element}
|
||||||
>
|
>
|
||||||
@ -68,7 +67,7 @@ export const publicRoutes: PublicRouteProps[] = [
|
|||||||
}
|
}
|
||||||
]
|
]
|
||||||
|
|
||||||
export const privateRoutes = [
|
export const privateRoutes: CustomRouteProps<unknown>[] = [
|
||||||
{
|
{
|
||||||
path: appPrivateRoutes.homePage,
|
path: appPrivateRoutes.homePage,
|
||||||
element: (
|
element: (
|
||||||
@ -94,43 +93,24 @@ export const privateRoutes = [
|
|||||||
)
|
)
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
path: appPrivateRoutes.settings,
|
|
||||||
element: (
|
element: (
|
||||||
<PrivateRoute>
|
<PrivateRoute>
|
||||||
<SettingsPage />
|
<SettingsLayout />
|
||||||
</PrivateRoute>
|
</PrivateRoute>
|
||||||
)
|
),
|
||||||
},
|
children: [
|
||||||
{
|
{
|
||||||
path: appPrivateRoutes.profileSettings,
|
path: appPrivateRoutes.profileSettings,
|
||||||
element: (
|
element: <ProfileSettingsPage />
|
||||||
<PrivateRoute>
|
|
||||||
<ProfileSettingsPage />
|
|
||||||
</PrivateRoute>
|
|
||||||
)
|
|
||||||
},
|
|
||||||
{
|
|
||||||
path: appPrivateRoutes.cacheSettings,
|
|
||||||
element: (
|
|
||||||
<PrivateRoute>
|
|
||||||
<CacheSettingsPage />
|
|
||||||
</PrivateRoute>
|
|
||||||
)
|
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
path: appPrivateRoutes.relays,
|
path: appPrivateRoutes.relays,
|
||||||
element: (
|
element: <RelaysPage />
|
||||||
<PrivateRoute>
|
|
||||||
<RelaysPage />
|
|
||||||
</PrivateRoute>
|
|
||||||
)
|
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
path: appPrivateRoutes.nostrLogin,
|
path: appPrivateRoutes.nostrLogin,
|
||||||
element: (
|
element: <NostrLoginPage />
|
||||||
<PrivateRoute>
|
}
|
||||||
<NostrLoginPage />
|
]
|
||||||
</PrivateRoute>
|
|
||||||
)
|
|
||||||
}
|
}
|
||||||
]
|
]
|
||||||
|
@ -42,3 +42,47 @@ export const clear = () => {
|
|||||||
clearAuthToken()
|
clearAuthToken()
|
||||||
clearState()
|
clearState()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export function mergeWithInitialValue<T>(storedValue: T, initialValue: T): T {
|
||||||
|
if (
|
||||||
|
!Array.isArray(storedValue) &&
|
||||||
|
typeof storedValue === 'object' &&
|
||||||
|
storedValue !== null
|
||||||
|
) {
|
||||||
|
return { ...initialValue, ...storedValue }
|
||||||
|
}
|
||||||
|
return storedValue
|
||||||
|
}
|
||||||
|
|
||||||
|
export function getLocalStorageItem<T>(key: string, defaultValue: T): string {
|
||||||
|
try {
|
||||||
|
const data = window.localStorage.getItem(key)
|
||||||
|
if (data === null) return JSON.stringify(defaultValue)
|
||||||
|
return data
|
||||||
|
} catch (err) {
|
||||||
|
console.error(`Error while fetching local storage value: `, err)
|
||||||
|
return JSON.stringify(defaultValue)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export function setLocalStorageItem(key: string, value: string) {
|
||||||
|
try {
|
||||||
|
window.localStorage.setItem(key, value)
|
||||||
|
dispatchLocalStorageEvent(key, value)
|
||||||
|
} catch (err) {
|
||||||
|
console.error(`Error while saving local storage value: `, err)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export function removeLocalStorageItem(key: string) {
|
||||||
|
try {
|
||||||
|
window.localStorage.removeItem(key)
|
||||||
|
dispatchLocalStorageEvent(key, null)
|
||||||
|
} catch (err) {
|
||||||
|
console.error(`Error while deleting local storage value: `, err)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function dispatchLocalStorageEvent(key: string, newValue: string | null) {
|
||||||
|
window.dispatchEvent(new StorageEvent('storage', { key, newValue }))
|
||||||
|
}
|
||||||
|
@ -171,20 +171,17 @@ export const DEFAULT_TOOLBOX: DrawTool[] = [
|
|||||||
{
|
{
|
||||||
identifier: MarkType.FULLNAME,
|
identifier: MarkType.FULLNAME,
|
||||||
icon: faIdCard,
|
icon: faIdCard,
|
||||||
label: 'Full Name',
|
label: 'Full Name'
|
||||||
isComingSoon: true
|
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
identifier: MarkType.JOBTITLE,
|
identifier: MarkType.JOBTITLE,
|
||||||
icon: faBriefcase,
|
icon: faBriefcase,
|
||||||
label: 'Job Title',
|
label: 'Job Title'
|
||||||
isComingSoon: true
|
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
identifier: MarkType.DATETIME,
|
identifier: MarkType.DATETIME,
|
||||||
icon: faClock,
|
icon: faClock,
|
||||||
label: 'Date Time',
|
label: 'Date Time'
|
||||||
isComingSoon: true
|
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
identifier: MarkType.NUMBER,
|
identifier: MarkType.NUMBER,
|
||||||
|
Loading…
x
Reference in New Issue
Block a user