New marks and settings refactor #323

Merged
enes merged 10 commits from fixes-7-3-25 into staging 2025-03-11 11:11:02 +00:00
8 changed files with 325 additions and 355 deletions
Showing only changes of commit c1a9475a89 - Show all commits

View File

@ -1,87 +1,82 @@
import AccountCircleIcon from '@mui/icons-material/AccountCircle'
import ArrowForwardIosIcon from '@mui/icons-material/ArrowForwardIos'
import RouterIcon from '@mui/icons-material/Router'
import { ListItem, useTheme } 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 { Button } from '@mui/material'
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 { Container } from '../../components/Container'
import { Footer } from '../../components/Footer/Footer'
import ExtensionIcon from '@mui/icons-material/Extension'
import { LoginMethod } from '../../store/auth/types'
import styles from './style.module.scss'
import { ReactNode } from 'react'
export const SettingsPage = () => {
const theme = useTheme()
const { usersPubkey, loginMethod } = useAppSelector((state) => state.auth)
const listItem = (label: string, disabled = false) => {
return (
<>
<ListItemText
primary={label}
const Item = (to: To, icon: ReactNode, label: string) => {
return (
<NavLink to={to}>
{({ isActive }) => (
<Button
fullWidth
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'}
>
{icon}
{label}
</Button>
)}
</NavLink>
)
}
{!disabled && (
<ArrowForwardIosIcon
style={{
color: theme.palette.action.active,
marginRight: -10
}}
/>
)}
</>
)
}
export const SettingsLayout = () => {
const { usersPubkey, loginMethod } = useAppSelector((state) => state.auth)
return (
<>
<Container>
<List
sx={{
width: '100%',
bgcolor: 'background.paper'
}}
subheader={
<ListSubheader
sx={{
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>
{loginMethod === LoginMethod.nostrLogin && (
<ListItem component={Link} to={appPrivateRoutes.nostrLogin}>
<ListItemIcon>
<ExtensionIcon />
</ListItemIcon>
{listItem('Nostr Login')}
</ListItem>
)}
</List>
<h2 className={styles.title}>Settings</h2>
<div className={styles.main}>
<div>
<aside className={styles.aside}>
{Item(
getProfileSettingsRoute(usersPubkey!),
<AccountCircleIcon />,
'Profile'
)}
{Item(appPrivateRoutes.relays, <RouterIcon />, 'Relays')}
{loginMethod === LoginMethod.nostrLogin &&
Item(
appPrivateRoutes.nostrLogin,
<ExtensionIcon />,
'Nostr Login'
)}
</aside>
</div>
<div className={styles.content}>
<Outlet />
</div>
</div>
</Container>
<Footer />
</>

View File

@ -3,11 +3,9 @@ import {
ListItemButton,
ListItemIcon,
ListItemText,
ListSubheader,
useTheme
} from '@mui/material'
import { launch as launchNostrLoginDialog } from 'nostr-login'
import { Container } from '../../../components/Container'
import PeopleIcon from '@mui/icons-material/People'
import ImportExportIcon from '@mui/icons-material/ImportExport'
import { useAppSelector } from '../../../hooks/store'
@ -20,59 +18,39 @@ export const NostrLoginPage = () => {
)
return (
<Container>
<List
sx={{
width: '100%',
bgcolor: 'background.paper'
<List>
<ListItemButton
onClick={() => {
launchNostrLoginDialog('switch-account')
}}
subheader={
<ListSubheader
sx={{
fontSize: '1.5rem',
borderBottom: '0.5px solid',
paddingBottom: 2,
paddingTop: 2,
zIndex: 2
}}
>
Nostr Settings
</ListSubheader>
}
>
<ListItemIcon>
<PeopleIcon />
</ListItemIcon>
<ListItemText
primary={'Nostr Login Accounts'}
sx={{
color: theme.palette.text.primary
}}
/>
</ListItemButton>
{nostrLoginAuthMethod === NostrLoginAuthMethod.Local && (
<ListItemButton
onClick={() => {
launchNostrLoginDialog('switch-account')
launchNostrLoginDialog('import')
}}
>
<ListItemIcon>
<PeopleIcon />
<ImportExportIcon />
</ListItemIcon>
<ListItemText
primary={'Nostr Login Accounts'}
primary={'Import / Export Keys'}
sx={{
color: theme.palette.text.primary
}}
/>
</ListItemButton>
{nostrLoginAuthMethod === NostrLoginAuthMethod.Local && (
<ListItemButton
onClick={() => {
launchNostrLoginDialog('import')
}}
>
<ListItemIcon>
<ImportExportIcon />
</ListItemIcon>
<ListItemText
primary={'Import / Export Keys'}
sx={{
color: theme.palette.text.primary
}}
/>
</ListItemButton>
)}
</List>
</Container>
)}
</List>
)
}

View File

@ -12,7 +12,6 @@ import {
InputProps,
List,
ListItem,
ListSubheader,
TextField,
Tooltip
} from '@mui/material'
@ -28,8 +27,6 @@ import { useAppDispatch, useAppSelector } from '../../../hooks/store'
import { getRoboHashPicture, unixNow } from '../../../utils'
import { Container } from '../../../components/Container'
import { Footer } from '../../../components/Footer/Footer'
import { LoadingSpinner } from '../../../components/LoadingSpinner'
import { setUserProfile as updateUserProfile } from '../../../store/actions'
@ -256,131 +253,111 @@ export const ProfileSettingsPage = () => {
return (
<>
{isLoading && <LoadingSpinner desc={loadingSpinnerDesc} />}
<Container className={styles.container}>
<List
sx={{
bgcolor: 'background.paper',
marginTop: 2
}}
subheader={
<ListSubheader
<List>
{userProfile && (
<div>
<ListItem
sx={{
paddingBottom: 1,
paddingTop: 1,
fontSize: '1.5rem',
zIndex: 2
marginTop: 1,
display: 'flex',
flexDirection: 'column'
}}
className={styles.subHeader}
>
Profile Settings
</ListSubheader>
}
>
{userProfile && (
<div>
<ListItem
sx={{
marginTop: 1,
display: 'flex',
flexDirection: 'column'
}}
>
{userProfile.banner ? (
<img
className={styles.bannerImg}
src={userProfile.banner}
alt="Banner Image"
/>
) : (
<Box className={styles.noBanner}> No banner found </Box>
)}
</ListItem>
{editItem('banner', 'Banner URL', undefined, undefined)}
<ListItem
sx={{
marginTop: 1,
display: 'flex',
flexDirection: 'column'
}}
>
{userProfile.banner ? (
<img
onError={(event: React.SyntheticEvent<HTMLImageElement>) => {
event.currentTarget.src = getRoboHashPicture(npub!)
}}
className={styles.img}
src={getProfileImage(userProfile)}
alt="Profile Image"
className={styles.bannerImg}
src={userProfile.banner}
alt="Banner Image"
/>
</ListItem>
{editItem('image', 'Picture URL', undefined, undefined, {
endAdornment: isUsersOwnProfile ? robohashButton() : undefined
})}
{editItem('name', 'Username')}
{editItem('displayName', 'Display Name')}
{editItem('nip05', 'Nostr Address (nip05)')}
{editItem('lud16', 'Lightning Address (lud16)')}
{editItem('about', 'About', true, 4)}
{editItem('website', 'Website')}
{isUsersOwnProfile && (
<>
{usersPubkey &&
copyItem(nip19.npubEncode(usersPubkey), 'Public Key')}
{loginMethod === LoginMethod.privateKey &&
keys &&
keys.private &&
copyItem(
'••••••••••••••••••••••••••••••••••••••••••••••••••',
'Private Key',
keys.private
)}
</>
) : (
<Box className={styles.noBanner}> No banner found </Box>
)}
{isUsersOwnProfile && (
<>
{loginMethod === LoginMethod.nostrLogin &&
nostrLoginAuthMethod === NostrLoginAuthMethod.Local && (
<ListItem
sx={{ marginTop: 1 }}
onClick={() => {
launchNostrLoginDialog('import')
</ListItem>
{editItem('banner', 'Banner URL', undefined, undefined)}
<ListItem
sx={{
marginTop: 1,
display: 'flex',
flexDirection: 'column'
}}
>
<img
onError={(event: React.SyntheticEvent<HTMLImageElement>) => {
event.currentTarget.src = getRoboHashPicture(npub!)
}}
className={styles.img}
src={getProfileImage(userProfile)}
alt="Profile Image"
/>
</ListItem>
{editItem('image', 'Picture URL', undefined, undefined, {
endAdornment: isUsersOwnProfile ? robohashButton() : undefined
})}
{editItem('name', 'Username')}
{editItem('displayName', 'Display Name')}
{editItem('nip05', 'Nostr Address (nip05)')}
{editItem('lud16', 'Lightning Address (lud16)')}
{editItem('about', 'About', true, 4)}
{editItem('website', 'Website')}
{isUsersOwnProfile && (
<>
{usersPubkey &&
copyItem(nip19.npubEncode(usersPubkey), 'Public Key')}
{loginMethod === LoginMethod.privateKey &&
keys &&
keys.private &&
copyItem(
'••••••••••••••••••••••••••••••••••••••••••••••••••',
'Private Key',
keys.private
)}
</>
)}
{isUsersOwnProfile && (
<>
{loginMethod === LoginMethod.nostrLogin &&
nostrLoginAuthMethod === NostrLoginAuthMethod.Local && (
<ListItem
sx={{ marginTop: 1 }}
onClick={() => {
launchNostrLoginDialog('import')
}}
>
<TextField
label="Private Key (nostr-login)"
defaultValue="••••••••••••••••••••••••••••••••••••••••••••••••••"
size="small"
className={styles.textField}
disabled
type={'password'}
InputProps={{
endAdornment: (
<LaunchIcon className={styles.copyItem} />
)
}}
>
<TextField
label="Private Key (nostr-login)"
defaultValue="••••••••••••••••••••••••••••••••••••••••••••••••••"
size="small"
className={styles.textField}
disabled
type={'password'}
InputProps={{
endAdornment: (
<LaunchIcon className={styles.copyItem} />
)
}}
/>
</ListItem>
)}
</>
)}
</div>
)}
</List>
{isUsersOwnProfile && (
<LoadingButton
loading={savingProfileMetadata}
variant="contained"
onClick={handleSaveMetadata}
>
SAVE
</LoadingButton>
/>
</ListItem>
)}
</>
)}
</div>
)}
</Container>
<Footer />
</List>
{isUsersOwnProfile && (
<LoadingButton
sx={{ maxWidth: '300px', alignSelf: 'center', width: '100%' }}
loading={savingProfileMetadata}
variant="contained"
onClick={handleSaveMetadata}
>
PUBLISH CHANGES
</LoadingButton>
)}
</>
)
}

View File

@ -1,9 +1,3 @@
.container {
display: flex;
flex-direction: column;
gap: 25px;
}
.textField {
width: 100%;
}

View File

@ -12,7 +12,6 @@ import ListItemText from '@mui/material/ListItemText'
import Switch from '@mui/material/Switch'
import { useEffect, useState } from 'react'
import { toast } from 'react-toastify'
import { Container } from '../../../components/Container'
import {
useAppDispatch,
useAppSelector,
@ -32,7 +31,6 @@ import {
timeout
} from '../../../utils'
import styles from './style.module.scss'
import { Footer } from '../../../components/Footer/Footer'
import {
getRelayListForUser,
NDKRelayList,
@ -246,7 +244,7 @@ export const RelaysPage = () => {
}
return (
<Container className={styles.container}>
<>
<Box className={styles.relayAddContainer}>
<TextField
label="Add new relay"
@ -291,8 +289,7 @@ export const RelaysPage = () => {
))}
</Box>
)}
<Footer />
</Container>
</>
)
}

View File

@ -1,107 +1,103 @@
@import '../../../styles/colors.scss';
.container {
color: $text-color;
.relayURItextfield {
width: 100%;
}
.relayURItextfield {
width: 100%;
.relayAddContainer {
display: flex;
flex-direction: row;
gap: 10px;
width: 100%;
align-items: start;
}
.sectionIcon {
font-size: 30px;
}
.sectionTitle {
margin-top: 35px;
margin-bottom: 10px;
display: flex;
flex-direction: row;
gap: 5px;
font-size: 1.5rem;
line-height: 2rem;
font-weight: 600;
}
.relaysContainer {
display: flex;
flex-direction: column;
gap: 15px;
}
.relay {
border: 1px solid rgba(0, 0, 0, 0.12);
border-radius: 4px;
.relayDivider {
margin-left: 10px;
margin-right: 10px;
}
.relayAddContainer {
.leaveRelayContainer {
display: flex;
flex-direction: row;
gap: 10px;
width: 100%;
align-items: start;
cursor: pointer;
}
.sectionIcon {
font-size: 30px;
.showInfo {
cursor: pointer;
}
.sectionTitle {
margin-top: 35px;
margin-bottom: 10px;
.showInfoIcon {
margin-right: 3px;
margin-bottom: auto;
vertical-align: middle;
}
.relayInfoContainer {
display: flex;
flex-direction: row;
flex-direction: column;
gap: 5px;
font-size: 1.5rem;
line-height: 2rem;
text-wrap: wrap;
}
.relayInfoTitle {
font-weight: 600;
}
.relaysContainer {
display: flex;
flex-direction: column;
gap: 15px;
.relayInfoSubTitle {
font-weight: 500;
}
.relay {
border: 1px solid rgba(0, 0, 0, 0.12);
border-radius: 4px;
.copyItem {
margin-left: 10px;
color: #34495e;
vertical-align: bottom;
cursor: pointer;
}
.relayDivider {
margin-left: 10px;
margin-right: 10px;
}
.connectionStatus {
border-radius: 9999px;
width: 10px;
height: 10px;
margin-right: 5px;
margin-top: 2px;
}
.leaveRelayContainer {
display: flex;
flex-direction: row;
gap: 10px;
cursor: pointer;
}
.connectionStatusConnected {
background-color: $relay-status-connected;
}
.showInfo {
cursor: pointer;
}
.connectionStatusNotConnected {
background-color: $relay-status-notconnected;
}
.showInfoIcon {
margin-right: 3px;
margin-bottom: auto;
vertical-align: middle;
}
.relayInfoContainer {
display: flex;
flex-direction: column;
gap: 5px;
text-wrap: wrap;
}
.relayInfoTitle {
font-weight: 600;
}
.relayInfoSubTitle {
font-weight: 500;
}
.copyItem {
margin-left: 10px;
color: #34495e;
vertical-align: bottom;
cursor: pointer;
}
.connectionStatus {
border-radius: 9999px;
width: 10px;
height: 10px;
margin-right: 5px;
margin-top: 2px;
}
.connectionStatusConnected {
background-color: $relay-status-connected;
}
.connectionStatusNotConnected {
background-color: $relay-status-notconnected;
}
.connectionStatusUnknown {
background-color: $input-text-color;
}
.connectionStatusUnknown {
background-color: $input-text-color;
}
}

View 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;
}

View File

@ -7,7 +7,7 @@ import { ProfilePage } from '../pages/profile'
import { NostrLoginPage } from '../pages/settings/nostrLogin'
import { ProfileSettingsPage } from '../pages/settings/profile'
import { RelaysPage } from '../pages/settings/relays'
import { SettingsPage } from '../pages/settings/Settings'
import { SettingsLayout } from '../pages/settings/Settings'
import { SignPage } from '../pages/sign'
import { VerifyPage } from '../pages/verify'
import { PrivateRoute } from './PrivateRoute'
@ -96,32 +96,22 @@ export const privateRoutes = [
path: appPrivateRoutes.settings,
element: (
<PrivateRoute>
<SettingsPage />
<SettingsLayout />
</PrivateRoute>
)
},
{
path: appPrivateRoutes.profileSettings,
element: (
<PrivateRoute>
<ProfileSettingsPage />
</PrivateRoute>
)
},
{
path: appPrivateRoutes.relays,
element: (
<PrivateRoute>
<RelaysPage />
</PrivateRoute>
)
},
{
path: appPrivateRoutes.nostrLogin,
element: (
<PrivateRoute>
<NostrLoginPage />
</PrivateRoute>
)
),
children: [
{
path: appPrivateRoutes.profileSettings,
element: <ProfileSettingsPage />
},
{
path: appPrivateRoutes.relays,
element: <RelaysPage />
},
{
path: appPrivateRoutes.nostrLogin,
element: <NostrLoginPage />
}
]
}
]