ui-update 2

This commit is contained in:
sahil-tgs 2024-08-31 04:26:49 +05:30
parent bae02688d8
commit 739e6e1345
5 changed files with 758 additions and 649 deletions

View File

@ -2,38 +2,45 @@ import React, { useState, useContext, useEffect } from 'react';
import { useTranslation } from 'react-i18next'; import { useTranslation } from 'react-i18next';
import { useNavigate } from 'react-router-dom'; import { useNavigate } from 'react-router-dom';
import { import {
Button,
Grid,
LinearProgress,
Typography, Typography,
Alert, LinearProgress,
Select, Select,
MenuItem, MenuItem,
Box, Box,
TextField,
SelectChangeEvent,
useTheme, useTheme,
Tooltip, useMediaQuery,
type SelectChangeEvent, styled,
} from '@mui/material'; } from '@mui/material';
import { Bolt, Add, DeleteSweep, Logout, Download } from '@mui/icons-material'; import { Bolt, Add, DeleteSweep, Logout, Download, FileCopy } from '@mui/icons-material';
import RobotAvatar from '../../components/RobotAvatar'; import RobotAvatar from '../../components/RobotAvatar';
import TokenInput from './TokenInput'; import { AppContext, UseAppStoreType } from '../../contexts/AppContext';
import { type Slot, type Robot } from '../../models';
import { AppContext, type UseAppStoreType } from '../../contexts/AppContext';
import { genBase62Token } from '../../utils'; import { genBase62Token } from '../../utils';
import { LoadingButton } from '@mui/lab'; import { GarageContext, UseGarageStoreType } from '../../contexts/GarageContext';
import { GarageContext, type UseGarageStoreType } from '../../contexts/GarageContext'; import { FederationContext, UseFederationStoreType } from '../../contexts/FederationContext';
import { FederationContext, type UseFederationStoreType } from '../../contexts/FederationContext';
const BUTTON_COLORS = {
primary: '#2196f3',
secondary: '#9c27b0',
text: '#ffffff',
hoverPrimary: '#4dabf5',
hoverSecondary: '#af52bf',
activePrimary: '#1976d2',
activeSecondary: '#7b1fa2',
deleteHover: '#ff6666',
};
const COLORS = {
shadow: '#000000',
};
interface RobotProfileProps { interface RobotProfileProps {
robot: Robot;
setRobot: (state: Robot) => void;
setView: (state: 'welcome' | 'onboarding' | 'recovery' | 'profile') => void;
getGenerateRobot: (token: string, slot?: number) => void;
inputToken: string; inputToken: string;
getGenerateRobot: (token: string) => void;
setInputToken: (token: string) => void;
logoutRobot: () => void; logoutRobot: () => void;
setInputToken: (state: string) => void; setView: (view: string) => void;
width: number;
baseUrl: string;
} }
const RobotProfile = ({ const RobotProfile = ({
@ -42,7 +49,6 @@ const RobotProfile = ({
setInputToken, setInputToken,
logoutRobot, logoutRobot,
setView, setView,
width,
}: RobotProfileProps): JSX.Element => { }: RobotProfileProps): JSX.Element => {
const { windowSize } = useContext<UseAppStoreType>(AppContext); const { windowSize } = useContext<UseAppStoreType>(AppContext);
const { garage, robotUpdatedAt, orderUpdatedAt } = useContext<UseGarageStoreType>(GarageContext); const { garage, robotUpdatedAt, orderUpdatedAt } = useContext<UseGarageStoreType>(GarageContext);
@ -51,6 +57,7 @@ const RobotProfile = ({
const { t } = useTranslation(); const { t } = useTranslation();
const theme = useTheme(); const theme = useTheme();
const navigate = useNavigate(); const navigate = useNavigate();
const isMobile = useMediaQuery(theme.breakpoints.down('sm'));
const [loading, setLoading] = useState<boolean>(true); const [loading, setLoading] = useState<boolean>(true);
@ -82,278 +89,309 @@ const RobotProfile = ({
).length; ).length;
return ( return (
<Grid container direction='column' alignItems='center' spacing={1} padding={1} paddingTop={2}> <ProfileContainer $isMobile={isMobile}>
<Grid <InfoSection colors={COLORS} $isMobile={isMobile}>
item <NicknameTypography variant={isMobile ? "h6" : "h5"} align="center" $isMobile={isMobile}>
container <BoltIcon $isMobile={isMobile} />
direction='column' {slot?.nickname}
alignItems='center' <BoltIcon $isMobile={isMobile} />
spacing={1} </NicknameTypography>
sx={{ width: '100%' }} <StyledRobotAvatar
> hashId={slot?.hashId}
<Grid item sx={{ height: '2.3em', position: 'relative' }}> smooth={true}
{slot?.nickname ? ( placeholderType='generating'
<Typography align='center' component='h5' variant='h5'> style={{ width: isMobile ? '80px' : '120px', height: isMobile ? '80px' : '120px' }}
<div />
style={{ <StatusTypography variant={isMobile ? "body2" : "body1"} align="center" $isMobile={isMobile}>
display: 'flex', {loadingCoordinators > 0 && !robot?.activeOrderId ? t('Looking for orders!') : t('Ready to Trade')}
alignItems: 'center', </StatusTypography>
justifyContent: 'center', {loadingCoordinators > 0 && !robot?.activeOrderId && <StyledLinearProgress $isMobile={isMobile} />}
flexWrap: 'wrap', <TokenBox $isMobile={isMobile}>
}} <CustomIconButton onClick={() => {
> logoutRobot();
{width < 19 ? null : ( setView('welcome');
<Bolt }}>
sx={{ <StyledLogoutIcon $isMobile={isMobile} />
color: '#fcba03', </CustomIconButton>
height: '1.5em', <StyledTextField
width: '1.5em', fullWidth
}} value={inputToken}
/> variant="standard"
)} $isMobile={isMobile}
<b>{slot?.nickname}</b> InputProps={{
{width < 19 ? null : ( readOnly: true,
<Bolt disableUnderline: true,
sx={{ endAdornment: (
color: '#fcba03', <CustomIconButton onClick={() => navigator.clipboard.writeText(inputToken)}>
height: '1.5em', <StyledFileCopyIcon $isMobile={isMobile} />
width: '1.5em', </CustomIconButton>
}} ),
/>
)}
</div>
</Typography>
) : (
<>
<b>{t('Building your robot!')}</b>
<LinearProgress />
</>
)}
</Grid>
<Grid item sx={{ width: `13.5em` }}>
<RobotAvatar
hashId={slot?.hashId}
smooth={true}
style={{ maxWidth: '12.5em', maxHeight: '12.5em' }}
placeholderType='generating'
imageStyle={{
transform: '',
border: '2px solid #555',
filter: 'drop-shadow(1px 1px 1px #000000)',
height: `12.4em`,
width: `12.4em`,
}} }}
tooltip={t('This is your trading avatar')}
tooltipPosition='top'
/> />
{robot?.found && Boolean(slot?.lastShortAlias) ? ( </TokenBox>
<Typography align='center' variant='h6'> </InfoSection>
{t('Welcome back!')}
</Typography> <RightSection $isMobile={isMobile}>
<TitleSection>
<TitleTypography variant={isMobile ? "subtitle1" : "h6"} align="center">
{t('Robot Garage')}
</TitleTypography>
</TitleSection>
<StyledSelect
value={loading ? 'loading' : garage.currentSlot}
onChange={handleChangeSlot}
$isMobile={isMobile}
>
{loading ? (
<MenuItem key={'loading'} value={'loading'}>
<Typography variant={isMobile ? "body2" : "body1"}>{t('Building...')}</Typography>
</MenuItem>
) : ( ) : (
<></> Object.values(garage.slots).map((slot: Slot, index: number) => (
<StyledMenuItem key={index} value={slot.token} $isMobile={isMobile}>
<MenuItemContent>
<StyledMenuItemAvatar
hashId={slot?.hashId}
smooth={true}
$isMobile={isMobile}
placeholderType='loading'
small={true}
/>
<Typography variant={isMobile ? "body2" : "body1"}>{slot?.nickname}</Typography>
</MenuItemContent>
</StyledMenuItem>
))
)} )}
</Grid> </StyledSelect>
<ButtonContainer>
{loadingCoordinators > 0 && !robot?.activeOrderId ? ( <StyledButton
<Grid> $buttonColor={BUTTON_COLORS.primary}
<b>{t('Looking for orders!')}</b> $hoverColor={BUTTON_COLORS.hoverPrimary}
<LinearProgress /> $textColor={BUTTON_COLORS.text}
</Grid> $isMobile={isMobile}
) : null} onClick={handleAddRobot}
{Boolean(robot?.activeOrderId) && Boolean(slot?.hashId) ? (
<Grid item>
<Button
onClick={() => {
setCurrentOrderId({ id: robot?.activeOrderId, shortAlias: slot?.activeShortAlias });
navigate(
`/order/${String(slot?.activeShortAlias)}/${String(robot?.activeOrderId)}`,
);
}}
>
{t('Active order #{{orderID}}', { orderID: robot?.activeOrderId })}
</Button>
</Grid>
) : null}
{Boolean(robot?.lastOrderId) && Boolean(slot?.hashId) ? (
<Grid item container direction='column' alignItems='center'>
<Grid item>
<Button
onClick={() => {
setCurrentOrderId({ id: robot?.lastOrderId, shortAlias: slot?.activeShortAlias });
navigate(`/order/${String(slot?.lastShortAlias)}/${String(robot?.lastOrderId)}`);
}}
>
{t('Last order #{{orderID}}', { orderID: robot?.lastOrderId })}
</Button>
</Grid>
<Grid item>
<Alert severity='warning'>
<Grid container direction='column' alignItems='center'>
<Grid item>
{t(
'Reusing trading identity degrades your privacy against other users, coordinators and observers.',
)}
</Grid>
<Grid item sx={{ position: 'relative', right: '1em' }}>
<Button color='success' size='small' onClick={handleAddRobot}>
<Add />
{t('Add a new Robot')}
</Button>
</Grid>
</Grid>
</Alert>
</Grid>
</Grid>
) : null}
{!robot?.activeOrderId &&
slot?.hashId &&
!robot?.lastOrderId &&
loadingCoordinators === 0 ? (
<Grid item>{t('No existing orders found')}</Grid>
) : null}
<Grid
item
container
direction='row'
justifyContent='stretch'
alignItems='stretch'
sx={{ width: '100%' }}
>
<Grid
item
xs={2}
sx={{ display: 'flex', justifyContent: 'stretch', alignItems: 'stretch' }}
> >
<Tooltip enterTouchDelay={0} enterDelay={300} enterNextDelay={1000} title={t('Logout')}> <StyledAddIcon $isMobile={isMobile} /> {t('ADD ROBOT')}
<Button </StyledButton>
sx={{ minWidth: '2em', width: '100%' }} {window.NativeRobosats === undefined && (
color='primary' <StyledButton
variant='outlined' $buttonColor={BUTTON_COLORS.secondary}
onClick={() => { $hoverColor={BUTTON_COLORS.hoverSecondary}
logoutRobot(); $textColor={BUTTON_COLORS.text}
setView('welcome'); $isMobile={isMobile}
}} onClick={() => garage.download()}
> >
<Logout /> <StyledDownloadIcon $isMobile={isMobile} /> {t('DOWNLOAD')}
</Button> </StyledButton>
</Tooltip> )}
</Grid> <StyledButton
<Grid item xs={10}> $buttonColor="transparent"
<TokenInput $hoverColor={BUTTON_COLORS.deleteHover}
inputToken={inputToken} $textColor="red"
editable={false} $isMobile={isMobile}
label={t('Store your token safely')} onClick={() => {
setInputToken={setInputToken} garage.delete();
onPressEnter={() => null} logoutRobot();
/> setView('welcome');
</Grid> }}
</Grid> >
</Grid> <StyledDeleteSweepIcon $isMobile={isMobile} /> {t('DELETE GARAGE')}
<Grid item sx={{ width: '100%' }}> </StyledButton>
<Box </ButtonContainer>
sx={{ </RightSection>
backgroundColor: 'background.paper', </ProfileContainer>
border: '1px solid',
borderRadius: '4px',
borderColor: theme.palette.mode === 'dark' ? '#434343' : '#c4c4c4',
}}
>
<Grid container direction='column' alignItems='center' spacing={2} padding={2}>
<Grid item sx={{ width: '100%' }}>
<Typography variant='caption'>{t('Robot Garage')}</Typography>
<Select
fullWidth
required={true}
inputProps={{
style: { textAlign: 'center' },
}}
value={loading ? 'loading' : garage.currentSlot}
onChange={handleChangeSlot}
>
{loading ? (
<MenuItem key={'loading'} value={'loading'}>
<Typography>{t('Building...')}</Typography>
</MenuItem>
) : (
Object.values(garage.slots).map((slot: Slot, index: number) => {
return (
<MenuItem key={index} value={slot.token}>
<Grid
container
direction='row'
justifyContent='flex-start'
alignItems='center'
style={{ height: '2.8em' }}
spacing={1}
>
<Grid item>
<RobotAvatar
hashId={slot?.hashId}
smooth={true}
style={{ width: '2.6em', height: '2.6em' }}
placeholderType='loading'
small={true}
/>
</Grid>
<Grid item>
<Typography variant={windowSize.width < 26 ? 'caption' : undefined}>
{slot?.nickname}
</Typography>
</Grid>
</Grid>
</MenuItem>
);
})
)}
</Select>
</Grid>
<Grid item container direction='row' alignItems='center' justifyContent='space-evenly'>
<Grid item>
<LoadingButton loading={loading} color='primary' onClick={handleAddRobot}>
<Add /> <div style={{ width: '0.5em' }} />
{t('Add Robot')}
</LoadingButton>
</Grid>
{window.NativeRobosats === undefined ? (
<Grid item>
<Button
color='primary'
onClick={() => {
garage.download();
}}
>
<Download />
</Button>
</Grid>
) : null}
<Grid item>
<Button
color='primary'
onClick={() => {
garage.delete();
logoutRobot();
setView('welcome');
}}
>
<DeleteSweep /> <div style={{ width: '0.5em' }} />
{t('Delete Garage')}
</Button>
</Grid>
</Grid>
</Grid>
</Box>
</Grid>
</Grid>
); );
}; };
export default RobotProfile; // Styled components
const ProfileContainer = styled(Box)<{ $isMobile: boolean }>(({ theme, $isMobile }) => ({
width: '100%',
maxWidth: $isMobile ? '100%' : 1000,
margin: '0 auto',
display: 'flex',
flexDirection: $isMobile ? 'column' : 'row',
border: $isMobile ? '1px solid #000' : '2px solid #000',
borderRadius: '8px',
overflow: 'hidden',
boxShadow: $isMobile ? '4px 4px 0px #000000' : '8px 8px 0px #000000',
}));
const InfoSection = styled(Box)<{ colors: typeof COLORS; $isMobile: boolean }>(({ theme, colors, $isMobile }) => ({
flexGrow: 1,
flexBasis: $isMobile ? 'auto' : 0,
backgroundColor: theme.palette.background.paper,
display: 'flex',
flexDirection: 'column',
justifyContent: 'center',
alignItems: 'center',
padding: $isMobile ? theme.spacing(2) : theme.spacing(4),
borderBottom: $isMobile ? '1px solid #000' : 'none',
borderRight: $isMobile ? 'none' : '2px solid #000',
}));
const NicknameTypography = styled(Typography)<{ $isMobile: boolean }>(({ $isMobile }) => ({
display: 'flex',
alignItems: 'center',
marginBottom: $isMobile ? '8px' : '16px',
}));
const BoltIcon = styled(Bolt)<{ $isMobile: boolean }>(({ $isMobile }) => ({
color: '#fcba03',
height: $isMobile ? '0.8em' : '1em',
width: $isMobile ? '0.8em' : '1em',
}));
const StyledRobotAvatar = styled(RobotAvatar)({
'& img': {
border: '2px solid #555',
borderRadius: '50%',
},
});
const StatusTypography = styled(Typography)<{ $isMobile: boolean }>(({ $isMobile }) => ({
marginBottom: $isMobile ? '8px' : '16px',
}));
const StyledLinearProgress = styled(LinearProgress)<{ $isMobile: boolean }>(({ $isMobile }) => ({
width: '100%',
marginBottom: $isMobile ? '8px' : '16px',
}));
const TokenBox = styled(Box)<{ $isMobile: boolean }>(({ $isMobile }) => ({
display: 'flex',
alignItems: 'center',
width: '100%',
border: $isMobile ? '1px solid #000' : '2px solid #000',
borderRadius: '4px',
}));
const StyledTextField = styled(TextField)<{ $isMobile: boolean }>(({ $isMobile }) => ({
'& .MuiInputBase-root': {
height: $isMobile ? '36px' : '48px',
padding: $isMobile ? '2px 4px' : '4px 8px',
fontSize: $isMobile ? '0.8rem' : '1rem',
}
}));
const RightSection = styled(Box)<{ $isMobile: boolean }>(({ $isMobile }) => ({
flexGrow: 1,
flexBasis: $isMobile ? 'auto' : 0,
display: 'flex',
flexDirection: 'column',
overflow: 'hidden',
}));
const TitleSection = styled(Box)(({ theme }) => ({
padding: theme.spacing(2),
borderBottom: `2px solid #000`,
}));
const TitleTypography = styled(Typography)({
fontWeight: 'bold',
});
const StyledSelect = styled(Select)<{ $isMobile: boolean }>(({ $isMobile }) => ({
width: '100%',
height: $isMobile ? '50px' : '80px',
borderBottom: $isMobile ? '1px solid #000' : '2px solid #000',
borderRadius: 0,
'& .MuiOutlinedInput-notchedOutline': {
border: 'none',
},
}));
const StyledMenuItem = styled(MenuItem)<{ $isMobile: boolean }>(({ $isMobile }) => ({
height: $isMobile ? '50px' : '80px',
}));
const MenuItemContent = styled(Box)({
display: 'flex',
alignItems: 'center',
});
const StyledMenuItemAvatar = styled(RobotAvatar)<{ $isMobile: boolean }>(({ $isMobile }) => ({
width: $isMobile ? '24px' : '30px',
height: $isMobile ? '24px' : '30px',
marginRight: '8px',
}));
const ButtonContainer = styled(Box)({
display: 'flex',
flexDirection: 'column',
width: '100%',
});
const StyledButton = styled('button')<{
$buttonColor: string;
$hoverColor: string;
$textColor: string;
$isMobile: boolean;
}>(({ theme, $buttonColor, $hoverColor, $textColor, $isMobile }) => ({
justifyContent: 'center',
alignItems: 'center',
textAlign: 'center',
padding: theme.spacing(2),
border: 'none',
borderRadius: 0,
backgroundColor: $buttonColor,
color: $textColor,
cursor: 'pointer',
display: 'flex',
transition: 'background-color 0.3s ease, color 0.3s ease',
width: '100%',
height: $isMobile ? '40px' : '60px',
borderBottom: $isMobile ? '1px solid #000' : '2px solid #000',
'&:hover': {
backgroundColor: $hoverColor,
color: $buttonColor === 'transparent' ? '#fff' : $textColor,
},
'&:active': {
backgroundColor: $buttonColor === BUTTON_COLORS.primary ? BUTTON_COLORS.activePrimary : BUTTON_COLORS.activeSecondary,
},
'&:focus': {
outline: 'none',
},
}));
const CustomIconButton = styled('button')({
background: 'transparent',
border: 'none',
color: '#1976d2',
padding: '4px',
cursor: 'pointer',
display: 'flex',
alignItems: 'center',
justifyContent: 'center',
transition: 'color 0.3s ease',
'&:hover': {
color: '#0d47a1',
},
'&:active': {
color: '#002171',
},
});
const StyledLogoutIcon = styled(Logout)<{ $isMobile: boolean }>(({ $isMobile }) => ({
fontSize: $isMobile ? '1rem' : '1.5rem',
}));
const StyledFileCopyIcon = styled(FileCopy)<{ $isMobile: boolean }>(({ $isMobile }) => ({
fontSize: $isMobile ? '1rem' : '1.5rem',
}));
const StyledAddIcon = styled(Add)<{ $isMobile: boolean }>(({ $isMobile }) => ({
marginRight: '8px',
fontSize: $isMobile ? '1rem' : '1.5rem',
}));
const StyledDownloadIcon = styled(Download)<{ $isMobile: boolean }>(({ $isMobile }) => ({
marginRight: '8px',
fontSize: $isMobile ? '1rem' : '1.5rem',
}));
const StyledDeleteSweepIcon = styled(DeleteSweep)<{ $isMobile: boolean }>(({ $isMobile }) => ({
marginRight: '8px',
fontSize: $isMobile ? '1rem' : '1.5rem',
}));
export default RobotProfile;

View File

@ -1,7 +1,6 @@
import React, { useContext, useEffect, useState } from 'react'; import React, { useContext, useEffect, useState } from 'react';
import { useTranslation } from 'react-i18next'; import { useTranslation } from 'react-i18next';
import { import {
Paper,
Grid, Grid,
CircularProgress, CircularProgress,
Box, Box,
@ -129,42 +128,40 @@ const RobotPage = (): JSX.Element => {
} else { } else {
return ( return (
<StyledMainBox> <StyledMainBox>
<StyledPaper> {view === 'welcome' && (
{view === 'welcome' && ( <Welcome setView={setView} getGenerateRobot={getGenerateRobot} width={1200} />
<Welcome setView={setView} getGenerateRobot={getGenerateRobot} width={1200} /> )}
)}
{view === 'onboarding' && ( {view === 'onboarding' && (
<Onboarding <Onboarding
setView={setView} setView={setView}
badToken={badToken} badToken={badToken}
inputToken={inputToken} inputToken={inputToken}
setInputToken={setInputToken} setInputToken={setInputToken}
getGenerateRobot={getGenerateRobot} getGenerateRobot={getGenerateRobot}
/> />
)} )}
{view === 'profile' && ( {view === 'profile' && (
<RobotProfile <RobotProfile
setView={setView} setView={setView}
logoutRobot={logoutRobot} logoutRobot={logoutRobot}
width={1200} width={1200}
inputToken={inputToken} inputToken={inputToken}
setInputToken={setInputToken} setInputToken={setInputToken}
getGenerateRobot={getGenerateRobot} getGenerateRobot={getGenerateRobot}
/> />
)} )}
{view === 'recovery' && ( {view === 'recovery' && (
<Recovery <Recovery
setView={setView} setView={setView}
badToken={badToken} badToken={badToken}
inputToken={inputToken} inputToken={inputToken}
setInputToken={setInputToken} setInputToken={setInputToken}
getRecoverRobot={getGenerateRobot} getRecoverRobot={getGenerateRobot}
/> />
)} )}
</StyledPaper>
</StyledMainBox> </StyledMainBox>
); );
} }
@ -174,11 +171,13 @@ const RobotPage = (): JSX.Element => {
const StyledConnectingBox = styled(Box)({ const StyledConnectingBox = styled(Box)({
width: '100vw', width: '100vw',
height: 'auto', height: 'auto',
backgroundColor: 'transparent',
}); });
const StyledTorIconBox = styled(Box)({ const StyledTorIconBox = styled(Box)({
position: 'fixed', position: 'fixed',
top: '4.6em', top: '4.6em',
backgroundColor: 'transparent',
}); });
const StyledMainBox = styled(Box)({ const StyledMainBox = styled(Box)({
@ -188,18 +187,9 @@ const StyledMainBox = styled(Box)({
justifyContent: 'center', justifyContent: 'center',
alignItems: 'center', alignItems: 'center',
padding: '2em', padding: '2em',
});
const StyledPaper = styled(Paper)(({ theme }) => ({
width: '80vw',
maxWidth: '1200px',
maxHeight: '85vh',
overflow: 'auto',
overflowX: 'clip',
backgroundColor: 'transparent', backgroundColor: 'transparent',
border: 'none', border: 'none',
boxShadow: 'none', boxShadow: 'none',
padding: '1em', });
}));
export default RobotPage; export default RobotPage;

View File

@ -1,19 +1,20 @@
import React, { useContext, useState } from 'react'; import React, { useContext, useState } from 'react';
import { Button, Grid, List, ListItem, Paper, TextField, Typography } from '@mui/material'; import { Button, Grid, Paper, TextField, Typography, Box } from '@mui/material';
import SettingsForm from '../../components/SettingsForm'; import SettingsForm from '../../components/SettingsForm';
import { AppContext, type UseAppStoreType } from '../../contexts/AppContext'; import { AppContext, type UseAppStoreType } from '../../contexts/AppContext';
import FederationTable from '../../components/FederationTable'; import FederationTable from '../../components/FederationTable';
import { t } from 'i18next'; import { t } from 'i18next';
import { FederationContext, type UseFederationStoreType } from '../../contexts/FederationContext'; import { FederationContext, type UseFederationStoreType } from '../../contexts/FederationContext';
import { styled } from '@mui/system';
const SettingsPage = (): JSX.Element => { const SettingsPage = (): JSX.Element => {
const { windowSize, navbarHeight } = useContext<UseAppStoreType>(AppContext); const { windowSize, navbarHeight } = useContext<UseAppStoreType>(AppContext);
const { federation, addNewCoordinator } = useContext<UseFederationStoreType>(FederationContext); const { federation, addNewCoordinator } = useContext<UseFederationStoreType>(FederationContext);
const maxHeight = (windowSize.height * 0.65) const maxHeight = (windowSize.height * 0.65);
const [newAlias, setNewAlias] = useState<string>(''); const [newAlias, setNewAlias] = useState<string>('');
const [newUrl, setNewUrl] = useState<string>(''); const [newUrl, setNewUrl] = useState<string>('');
const [error, setError] = useState<string>(); const [error, setError] = useState<string>();
// Regular expression to match a valid .onion URL
const onionUrlPattern = /^((http|https):\/\/)?[a-zA-Z2-7]{16,56}\.onion$/; const onionUrlPattern = /^((http|https):\/\/)?[a-zA-Z2-7]{16,56}\.onion$/;
const addCoordinator: () => void = () => { const addCoordinator: () => void = () => {
@ -35,31 +36,22 @@ const SettingsPage = (): JSX.Element => {
}; };
return ( return (
<Paper <SettingsContainer elevation={12}>
elevation={12}
sx={{
padding: '0.6em',
width: '20.5em',
maxHeight: `${maxHeight}em`,
overflow: 'auto',
overflowX: 'clip',
}}
>
<Grid container> <Grid container>
<Grid item> <LeftGrid item xs={12} md={6}>
<SettingsForm /> <SettingsForm />
</Grid> </LeftGrid>
<Grid item> <RightGrid item xs={12} md={6}>
<FederationTable maxHeight={18} /> <FederationTableWrapper>
</Grid> <FederationTable maxHeight={18} />
<Grid item> </FederationTableWrapper>
<Typography align='center' component='h2' variant='subtitle2' color='secondary'> {error && (
{error} <ErrorTypography align='center' component='h2' variant='subtitle2' color='secondary'>
</Typography> {error}
</Grid> </ErrorTypography>
<List> )}
<ListItem> <InputContainer>
<TextField <StyledTextField
id='outlined-basic' id='outlined-basic'
label={t('Alias')} label={t('Alias')}
variant='outlined' variant='outlined'
@ -69,7 +61,7 @@ const SettingsPage = (): JSX.Element => {
setNewAlias(e.target.value); setNewAlias(e.target.value);
}} }}
/> />
<TextField <StyledTextField
id='outlined-basic' id='outlined-basic'
label={t('URL')} label={t('URL')}
variant='outlined' variant='outlined'
@ -79,8 +71,7 @@ const SettingsPage = (): JSX.Element => {
setNewUrl(e.target.value); setNewUrl(e.target.value);
}} }}
/> />
<Button <StyledButton
sx={{ maxHeight: 38 }}
disabled={false} disabled={false}
onClick={addCoordinator} onClick={addCoordinator}
variant='contained' variant='contained'
@ -89,12 +80,83 @@ const SettingsPage = (): JSX.Element => {
type='submit' type='submit'
> >
{t('Add')} {t('Add')}
</Button> </StyledButton>
</ListItem> </InputContainer>
</List> </RightGrid>
</Grid> </Grid>
</Paper> </SettingsContainer>
); );
}; };
export default SettingsPage; // Styled Components
const SettingsContainer = styled(Paper)(({ theme }) => ({
display: 'flex',
width: '80vw',
height: '70vh',
margin: '0 auto',
border: '2px solid #000',
borderRadius: '8px',
overflow: 'hidden',
boxShadow: '8px 8px 0px #000',
[theme.breakpoints.down('md')]: {
flexDirection: 'column',
width: '90vw',
height: 'fit-content',
marginTop: '30rem',
},
}));
const LeftGrid = styled(Grid)(({ theme }) => ({
padding: '2rem',
borderRight: '2px solid #000',
[theme.breakpoints.down('md')]: {
padding: '1rem',
borderRight: 'none',
},
}));
const RightGrid = styled(Grid)(({ theme }) => ({
padding: '2rem',
display: 'flex',
flexDirection: 'column',
[theme.breakpoints.down('md')]: {
padding: '1rem',
},
}));
const FederationTableWrapper = styled(Box)({
flexGrow: 1,
'& > *': {
width: '100% !important',
},
});
const ErrorTypography = styled(Typography)({
// You can add specific styles for the error message here if needed
});
const InputContainer = styled(Box)(({ theme }) => ({
display: 'flex',
flexDirection: 'row',
gap: theme.spacing(1),
marginTop: theme.spacing(2),
[theme.breakpoints.down('sm')]: {
flexDirection: 'column',
},
}));
const StyledTextField = styled(TextField)(({ theme }) => ({
flexGrow: 1,
[theme.breakpoints.down('sm')]: {
marginBottom: theme.spacing(2),
},
}));
const StyledButton = styled(Button)(({ theme }) => ({
maxHeight: 40,
[theme.breakpoints.down('sm')]: {
width: '100%',
},
}));
export default SettingsPage;

View File

@ -1,78 +1,38 @@
import React, { useCallback, useEffect, useState, useContext, useMemo } from 'react'; import React, { useCallback, useContext, useMemo } from 'react';
import { useTranslation } from 'react-i18next'; import { useTranslation } from 'react-i18next';
import { Box, useTheme, Checkbox, CircularProgress, Typography, Grid } from '@mui/material'; import { Box, Checkbox, CircularProgress, Grid, Typography } from '@mui/material';
import { DataGrid, type GridColDef, type GridValidRowModel } from '@mui/x-data-grid'; import { DataGrid, type GridColDef, type GridValidRowModel } from '@mui/x-data-grid';
import { type Coordinator } from '../../models'; import { type Coordinator } from '../../models';
import RobotAvatar from '../RobotAvatar'; import RobotAvatar from '../RobotAvatar';
import { Link, LinkOff } from '@mui/icons-material'; import { Link, LinkOff } from '@mui/icons-material';
import { AppContext, type UseAppStoreType } from '../../contexts/AppContext'; import { AppContext, type UseAppStoreType } from '../../contexts/AppContext';
import { type UseFederationStoreType, FederationContext } from '../../contexts/FederationContext'; import { type UseFederationStoreType, FederationContext } from '../../contexts/FederationContext';
import headerStyleFix from '../DataGrid/HeaderFix'; import { styled } from '@mui/system';
interface FederationTableProps { interface FederationTableProps {
maxWidth?: number; maxWidth?: number;
maxHeight?: number;
fillContainer?: boolean; fillContainer?: boolean;
} }
const FederationTable = ({ const FederationTable = ({ maxWidth = 90, fillContainer = false }: FederationTableProps): JSX.Element => {
maxWidth = 90,
maxHeight = 50,
fillContainer = false,
}: FederationTableProps): JSX.Element => {
const { t } = useTranslation(); const { t } = useTranslation();
const { federation, sortedCoordinators, coordinatorUpdatedAt, federationUpdatedAt } = const { federation, sortedCoordinators, coordinatorUpdatedAt } = useContext<UseFederationStoreType>(FederationContext);
useContext<UseFederationStoreType>(FederationContext);
const { setOpen, settings } = useContext<UseAppStoreType>(AppContext); const { setOpen, settings } = useContext<UseAppStoreType>(AppContext);
const theme = useTheme();
const [pageSize, setPageSize] = useState<number>(0);
// all sizes in 'em'
const fontSize = theme.typography.fontSize;
const verticalHeightFrame = 3.3;
const verticalHeightRow = 3.27;
const defaultPageSize = Math.max(
Math.floor((maxHeight - verticalHeightFrame) / verticalHeightRow),
1,
);
const height = defaultPageSize * verticalHeightRow + verticalHeightFrame;
const [useDefaultPageSize, setUseDefaultPageSize] = useState(true);
useEffect(() => {
if (useDefaultPageSize) {
setPageSize(defaultPageSize);
}
}, [coordinatorUpdatedAt, federationUpdatedAt]);
const localeText = {
MuiTablePagination: { labelRowsPerPage: t('Coordinators per page:') },
noResultsOverlayLabel: t('No coordinators found.'),
};
const onClickCoordinator = function (shortAlias: string): void {
setOpen((open) => {
return { ...open, coordinator: shortAlias };
});
};
const aliasObj = useCallback((width: number) => { const aliasObj = useCallback((width: number) => {
return { return {
field: 'longAlias', field: 'longAlias',
headerName: t('Coordinator'), headerName: t('Coordinator'),
width: width * fontSize, width: width,
renderCell: (params: any) => { renderCell: (params: any) => {
const coordinator = federation.coordinators[params.row.shortAlias]; const coordinator = federation.coordinators[params.row.shortAlias];
return ( return (
<Grid <CoordinatorGrid
container container
direction='row' direction="row"
sx={{ cursor: 'pointer', position: 'relative', left: '-0.3em', width: '50em' }} wrap="nowrap"
wrap='nowrap' onClick={() => onClickCoordinator(params.row.shortAlias)}
onClick={() => { alignItems="center"
onClickCoordinator(params.row.shortAlias);
}}
alignItems='center'
spacing={1} spacing={1}
> >
<Grid item> <Grid item>
@ -87,31 +47,29 @@ const FederationTable = ({
<Grid item> <Grid item>
<Typography>{params.row.longAlias}</Typography> <Typography>{params.row.longAlias}</Typography>
</Grid> </Grid>
</Grid> </CoordinatorGrid>
); );
}, },
}; };
}, []); }, [federation.coordinators]);
const enabledObj = useCallback( const enabledObj = useCallback(
(width: number) => { (width: number) => {
return { return {
field: 'enabled', field: 'enabled',
headerName: t('Enabled'), headerName: t('Enabled'),
width: width * fontSize, width: width,
renderCell: (params: any) => { renderCell: (params: any) => {
return ( return (
<Checkbox <Checkbox
checked={params.row.enabled} checked={params.row.enabled}
onClick={() => { onClick={() => onEnableChange(params.row.shortAlias)}
onEnableChange(params.row.shortAlias);
}}
/> />
); );
}, },
}; };
}, },
[coordinatorUpdatedAt], [coordinatorUpdatedAt]
); );
const upObj = useCallback( const upObj = useCallback(
@ -119,52 +77,41 @@ const FederationTable = ({
return { return {
field: 'up', field: 'up',
headerName: t('Up'), headerName: t('Up'),
width: width * fontSize, width: width,
renderCell: (params: any) => { renderCell: (params: any) => {
return ( return (
<div <UpStatusContainer onClick={() => onClickCoordinator(params.row.shortAlias)}>
style={{ cursor: 'pointer' }} {params.row.loadingInfo && params.row.enabled ? (
onClick={() => { <CircularProgress thickness={2} size={24} />
onClickCoordinator(params.row.shortAlias);
}}
>
{Boolean(params.row.loadingInfo) && Boolean(params.row.enabled) ? (
<CircularProgress thickness={0.35 * fontSize} size={1.5 * fontSize} />
) : params.row.info !== undefined ? ( ) : params.row.info !== undefined ? (
<Link color='success' /> <Link color="success" />
) : ( ) : (
<LinkOff color='error' /> <LinkOff color="error" />
)} )}
</div> </UpStatusContainer>
); );
}, },
}; };
}, },
[coordinatorUpdatedAt], [coordinatorUpdatedAt]
); );
const columnSpecs = { const columnSpecs = {
alias: { alias: {
priority: 2,
order: 1,
normal: { normal: {
width: 12.1, width: 200,
object: aliasObj, object: aliasObj,
}, },
}, },
up: { up: {
priority: 3,
order: 2,
normal: { normal: {
width: 3.5, width: 70,
object: upObj, object: upObj,
}, },
}, },
enabled: { enabled: {
priority: 1,
order: 3,
normal: { normal: {
width: 5, width: 90,
object: enabledObj, object: enabledObj,
}, },
}, },
@ -174,29 +121,17 @@ const FederationTable = ({
columns: Array<GridColDef<GridValidRowModel>>; columns: Array<GridColDef<GridValidRowModel>>;
width: number; width: number;
} { } {
const useSmall = maxWidth < 30;
const selectedColumns: object[] = []; const selectedColumns: object[] = [];
let width: number = 0; let width: number = 0;
for (const value of Object.values(columnSpecs)) { for (const value of Object.values(columnSpecs)) {
const colWidth = Number( const colWidth = value.normal.width;
useSmall && Boolean(value.small) ? value.small.width : value.normal.width, const colObject = value.normal.object;
);
const colObject = useSmall && Boolean(value.small) ? value.small.object : value.normal.object;
if (width + colWidth < maxWidth || selectedColumns.length < 2) { width += colWidth;
width = width + colWidth; selectedColumns.push([colObject(colWidth), value.order]);
selectedColumns.push([colObject(colWidth, false), value.order]);
} else {
selectedColumns.push([colObject(colWidth, true), value.order]);
}
} }
// sort columns by column.order value
selectedColumns.sort(function (first, second) {
return first[1] - second[1];
});
const columns: Array<GridColDef<GridValidRowModel>> = selectedColumns.map(function (item) { const columns: Array<GridColDef<GridValidRowModel>> = selectedColumns.map(function (item) {
return item[0]; return item[0];
}); });
@ -204,7 +139,7 @@ const FederationTable = ({
return { columns, width: width * 0.9 }; return { columns, width: width * 0.9 };
}; };
const { columns, width } = filteredColumns(); const { columns } = filteredColumns();
const onEnableChange = function (shortAlias: string): void { const onEnableChange = function (shortAlias: string): void {
if (federation.getCoordinator(shortAlias).enabled === true) { if (federation.getCoordinator(shortAlias).enabled === true) {
@ -217,38 +152,65 @@ const FederationTable = ({
const reorderedCoordinators = useMemo(() => { const reorderedCoordinators = useMemo(() => {
return sortedCoordinators.reduce((coordinators, key) => { return sortedCoordinators.reduce((coordinators, key) => {
coordinators[key] = federation.coordinators[key]; coordinators[key] = federation.coordinators[key];
return coordinators; return coordinators;
}, {}); }, {});
}, [settings.network, federationUpdatedAt]); }, [settings.network, coordinatorUpdatedAt]);
const onClickCoordinator = (shortAlias: string): void => {
setOpen((open) => {
return { ...open, coordinator: shortAlias };
});
};
return ( return (
<Box <TableContainer fillContainer={fillContainer} maxWidth={maxWidth}>
sx={ <StyledDataGrid
fillContainer
? { width: '100%', height: '100%' }
: { width: `${width}em`, height: `${height}em`, overflow: 'auto' }
}
>
<DataGrid
sx={headerStyleFix}
localeText={localeText}
rowHeight={3.714 * theme.typography.fontSize}
headerHeight={3.25 * theme.typography.fontSize}
rows={Object.values(reorderedCoordinators)} rows={Object.values(reorderedCoordinators)}
getRowId={(params: Coordinator) => params.shortAlias} getRowId={(params: Coordinator) => params.shortAlias}
columns={columns} columns={columns}
checkboxSelection={false} checkboxSelection={false}
pageSize={pageSize} autoHeight
rowsPerPageOptions={width < 22 ? [] : [0, pageSize, defaultPageSize * 2, 50, 100]} hideFooter
onPageSizeChange={(newPageSize) => {
setPageSize(newPageSize);
setUseDefaultPageSize(false);
}}
hideFooter={true}
/> />
</Box> </TableContainer>
); );
}; };
export default FederationTable; // Styled Components
const TableContainer = styled(Box, {
shouldForwardProp: (prop) => prop !== 'fillContainer' && prop !== 'maxWidth',
})<{ fillContainer: boolean; maxWidth: number }>(({ theme, fillContainer, maxWidth }) => ({
width: fillContainer ? '100%' : `${maxWidth}em`,
height: 'fit-content',
border: '2px solid black',
overflow: 'hidden',
borderRadius: '8px',
boxShadow: 'none',
}));
const StyledDataGrid = styled(DataGrid)(({ theme }) => ({
'& .MuiDataGrid-root': {
fontSize: { xs: '0.8rem', sm: '0.9rem', md: '1rem' },
},
'& .MuiDataGrid-cell': {
padding: { xs: '0.5rem', sm: '1rem' },
},
'& .MuiDataGrid-columnHeaders': {
backgroundColor: theme.palette.background.default,
borderBottom: '2px solid black',
fontSize: { xs: '0.8rem', sm: '0.9rem', md: '1rem' },
},
}));
const CoordinatorGrid = styled(Grid)({
cursor: 'pointer',
position: 'relative',
left: '-0.3em',
width: '50em',
});
const UpStatusContainer = styled('div')({
cursor: 'pointer',
});
export default FederationTable;

View File

@ -1,36 +1,36 @@
import React, { useContext } from 'react'; import React, { useContext } from 'react';
import { useTranslation } from 'react-i18next'; import { useTranslation } from 'react-i18next';
import { useTheme } from '@mui/material/styles';
import { type UseAppStoreType, AppContext } from '../../contexts/AppContext'; import { type UseAppStoreType, AppContext } from '../../contexts/AppContext';
import { import {
Grid, Grid,
Paper,
Switch, Switch,
useTheme,
FormControlLabel, FormControlLabel,
List, List,
ListItem, ListItem,
ListItemIcon, ListItemIcon,
Slider,
Typography, Typography,
ToggleButtonGroup, ToggleButtonGroup,
ToggleButton, ToggleButton,
Box,
Slider,
} from '@mui/material'; } from '@mui/material';
import SelectLanguage from './SelectLanguage'; import SelectLanguage from './SelectLanguage';
import { import {
Translate, Translate,
Palette, Palette,
LightMode,
DarkMode,
SettingsOverscan, SettingsOverscan,
Link, Link,
AccountBalance, AccountBalance,
AttachMoney, AttachMoney,
QrCode, QrCode,
DarkMode,
} from '@mui/icons-material'; } from '@mui/icons-material';
import { systemClient } from '../../services/System'; import { systemClient } from '../../services/System';
import { TorIcon } from '../Icons'; import { TorIcon } from '../Icons';
import SwapCalls from '@mui/icons-material/SwapCalls'; import SwapCalls from '@mui/icons-material/SwapCalls';
import { apiClient } from '../../services/api'; import { apiClient } from '../../services/api';
import { styled } from '@mui/system';
interface SettingsFormProps { interface SettingsFormProps {
dense?: boolean; dense?: boolean;
@ -38,8 +38,9 @@ interface SettingsFormProps {
const SettingsForm = ({ dense = false }: SettingsFormProps): JSX.Element => { const SettingsForm = ({ dense = false }: SettingsFormProps): JSX.Element => {
const { fav, setFav, settings, setSettings } = useContext<UseAppStoreType>(AppContext); const { fav, setFav, settings, setSettings } = useContext<UseAppStoreType>(AppContext);
const theme = useTheme();
const { t } = useTranslation(); const { t } = useTranslation();
const theme = useTheme();
const fontSizes = [ const fontSizes = [
{ label: 'XS', value: { basic: 12, pro: 10 } }, { label: 'XS', value: { basic: 12, pro: 10 } },
{ label: 'S', value: { basic: 13, pro: 11 } }, { label: 'S', value: { basic: 13, pro: 11 } },
@ -48,115 +49,74 @@ const SettingsForm = ({ dense = false }: SettingsFormProps): JSX.Element => {
{ label: 'XL', value: { basic: 16, pro: 14 } }, { label: 'XL', value: { basic: 16, pro: 14 } },
]; ];
const handleToggleChange = (e, newValue) => {
if (newValue !== null) {
setFav({ ...fav, mode: newValue, currency: newValue === 'fiat' ? 0 : 1000 });
}
};
const handleNetworkChange = (e, newValue) => {
if (newValue !== null) {
setSettings({ ...settings, network: newValue });
systemClient.setItem('settings_network', newValue);
}
};
return ( return (
<Grid container spacing={1}> <Grid container spacing={2}>
<Grid item> <Grid item xs={12}>
<List dense={dense}> <List dense={dense}>
<ListItem> {/* Language Settings */}
<ListItemIcon> <StyledListItem>
<Translate /> <SettingHeader>
</ListItemIcon> <ListItemIcon>
<SelectLanguage <Translate />
</ListItemIcon>
<Typography variant="subtitle1">
{t('Language Settings')}
</Typography>
</SettingHeader>
<StyledSelectLanguage
language={settings.language} language={settings.language}
setLanguage={(language) => { setLanguage={(language) => {
setSettings({ ...settings, language }); setSettings({ ...settings, language });
systemClient.setItem('settings_language', language); systemClient.setItem('settings_language', language);
}} }}
/> />
</ListItem> </StyledListItem>
<ListItem> {/* Appearance Settings */}
<ListItemIcon> <StyledListItem>
<Palette /> <SettingHeader>
</ListItemIcon> <ListItemIcon>
<FormControlLabel <Palette />
labelPlacement='end' </ListItemIcon>
label={settings.mode === 'dark' ? t('Dark') : t('Light')} <Typography variant="subtitle1">
control={ {t('Appearance Settings')}
<Switch </Typography>
checked={settings.mode === 'dark'} </SettingHeader>
checkedIcon={ <AppearanceSettingsBox>
<Paper <FormControlLabel
elevation={3} labelPlacement="end"
sx={{ label={t('Dark Mode')}
width: '1.2em', control={
height: '1.2em', <Switch
borderRadius: '0.4em', checked={settings.mode === 'dark'}
backgroundColor: 'white', onChange={(e) => {
position: 'relative', const mode = e.target.checked ? 'dark' : 'light';
top: `${7 - 0.5 * theme.typography.fontSize}px`, setSettings({ ...settings, mode });
}} systemClient.setItem('settings_mode', mode);
> }}
<DarkMode sx={{ width: '0.8em', height: '0.8em', color: '#666' }} /> />
</Paper> }
} />
icon={ {settings.mode === 'dark' && (
<Paper <QRCodeSwitch
elevation={3} labelPlacement="end"
sx={{ label={t('QR Code Color')}
width: '1.2em',
height: '1.2em',
borderRadius: '0.4em',
backgroundColor: 'white',
padding: '0.07em',
position: 'relative',
top: `${7 - 0.5 * theme.typography.fontSize}px`,
}}
>
<LightMode sx={{ width: '0.67em', height: '0.67em', color: '#666' }} />
</Paper>
}
onChange={(e) => {
const mode = e.target.checked ? 'dark' : 'light';
setSettings({ ...settings, mode });
systemClient.setItem('settings_mode', mode);
}}
/>
}
/>
{settings.mode === 'dark' ? (
<>
<ListItemIcon>
<QrCode />
</ListItemIcon>
<FormControlLabel
sx={{ position: 'relative', right: '1.5em', width: '3em' }}
labelPlacement='end'
label={settings.lightQRs ? t('Light') : t('Dark')}
control={ control={
<Switch <Switch
checked={!settings.lightQRs} checked={!settings.lightQRs}
checkedIcon={
<Paper
elevation={3}
sx={{
width: '1.2em',
height: '1.2em',
borderRadius: '0.4em',
backgroundColor: 'white',
position: 'relative',
top: `${7 - 0.5 * theme.typography.fontSize}px`,
}}
>
<DarkMode sx={{ width: '0.8em', height: '0.8em', color: '#666' }} />
</Paper>
}
icon={
<Paper
elevation={3}
sx={{
width: '1.2em',
height: '1.2em',
borderRadius: '0.4em',
backgroundColor: 'white',
padding: '0.07em',
position: 'relative',
top: `${7 - 0.5 * theme.typography.fontSize}px`,
}}
>
<LightMode sx={{ width: '0.67em', height: '0.67em', color: '#666' }} />
</Paper>
}
onChange={(e) => { onChange={(e) => {
const lightQRs = !e.target.checked; const lightQRs = !e.target.checked;
setSettings({ ...settings, lightQRs }); setSettings({ ...settings, lightQRs });
@ -165,17 +125,21 @@ const SettingsForm = ({ dense = false }: SettingsFormProps): JSX.Element => {
/> />
} }
/> />
</> )}
) : ( </AppearanceSettingsBox>
<></> </StyledListItem>
)}
</ListItem>
<ListItem> {/* Font Size Settings */}
<ListItemIcon> <StyledListItem>
<SettingsOverscan /> <SettingHeader>
</ListItemIcon> <ListItemIcon>
<Slider <SettingsOverscan />
</ListItemIcon>
<Typography variant="subtitle1">
{t('Font Size')}
</Typography>
</SettingHeader>
<StyledSlider
value={settings.fontSize} value={settings.fontSize}
min={settings.frontend === 'basic' ? 12 : 10} min={settings.frontend === 'basic' ? 12 : 10}
max={settings.frontend === 'basic' ? 16 : 14} max={settings.frontend === 'basic' ? 16 : 14}
@ -185,80 +149,98 @@ const SettingsForm = ({ dense = false }: SettingsFormProps): JSX.Element => {
setSettings({ ...settings, fontSize }); setSettings({ ...settings, fontSize });
systemClient.setItem(`settings_fontsize_${settings.frontend}`, fontSize.toString()); systemClient.setItem(`settings_fontsize_${settings.frontend}`, fontSize.toString());
}} }}
valueLabelDisplay='off' valueLabelDisplay="off"
track={false}
marks={fontSizes.map(({ label, value }) => ({ marks={fontSizes.map(({ label, value }) => ({
label: <Typography variant='caption'>{t(label)}</Typography>, label: <Typography variant="caption">{t(label)}</Typography>,
value: settings.frontend === 'basic' ? value.basic : value.pro, value: settings.frontend === 'basic' ? value.basic : value.pro,
}))} }))}
track={false}
/> />
</ListItem> </StyledListItem>
<ListItem> {/* Currency Settings */}
<ListItemIcon> <StyledListItem>
<AccountBalance /> <SettingHeader>
</ListItemIcon> <ListItemIcon>
<ToggleButtonGroup <AccountBalance />
</ListItemIcon>
<Typography variant="subtitle1">
{t('Currency Settings')}
</Typography>
</SettingHeader>
<StyledToggleButtonGroup
exclusive={true} exclusive={true}
value={fav.mode} value={fav.mode}
onChange={(e, mode) => { onChange={handleToggleChange}
setFav({ ...fav, mode, currency: mode === 'fiat' ? 0 : 1000 }); fullWidth
}}
> >
<ToggleButton value='fiat' color='primary'> <StyledToggleButton value="fiat">
<AttachMoney /> <AttachMoney />
{t('Fiat')} {t('Fiat')}
</ToggleButton> </StyledToggleButton>
<ToggleButton value='swap' color='secondary'> <StyledToggleButton value="swap">
<SwapCalls /> <SwapCalls />
{t('Swaps')} {t('Swaps')}
</ToggleButton> </StyledToggleButton>
</ToggleButtonGroup> </StyledToggleButtonGroup>
</ListItem> </StyledListItem>
<ListItem> {/* Network Settings */}
<ListItemIcon> <StyledListItem>
<Link /> <SettingHeader>
</ListItemIcon> <ListItemIcon>
<ToggleButtonGroup <Link />
</ListItemIcon>
<Typography variant="subtitle1">
{t('Network Settings')}
</Typography>
</SettingHeader>
<StyledToggleButtonGroup
exclusive={true} exclusive={true}
value={settings.network} value={settings.network}
onChange={(e, network) => { onChange={handleNetworkChange}
setSettings({ ...settings, network }); fullWidth
systemClient.setItem('settings_network', network);
}}
> >
<ToggleButton value='mainnet' color='primary'> <StyledToggleButton value="mainnet">
{t('Mainnet')} {t('Mainnet')}
</ToggleButton> </StyledToggleButton>
<ToggleButton value='testnet' color='secondary'> <StyledToggleButton value="testnet">
{t('Testnet')} {t('Testnet')}
</ToggleButton> </StyledToggleButton>
</ToggleButtonGroup> </StyledToggleButtonGroup>
</ListItem> </StyledListItem>
{/* Proxy Settings */}
{window.NativeRobosats !== undefined && ( {window.NativeRobosats !== undefined && (
<ListItem> <StyledListItem>
<ListItemIcon> <SettingHeader>
<TorIcon /> <ListItemIcon>
</ListItemIcon> <TorIcon />
<ToggleButtonGroup </ListItemIcon>
<Typography variant="subtitle1">
{t('Proxy Settings')}
</Typography>
</SettingHeader>
<StyledToggleButtonGroup
exclusive={true} exclusive={true}
value={settings.useProxy} value={settings.useProxy}
onChange={(_e, useProxy) => { onChange={(_e, useProxy) => {
setSettings({ ...settings, useProxy }); if (useProxy !== null) {
systemClient.setItem('settings_use_proxy', String(useProxy)); setSettings({ ...settings, useProxy });
apiClient.useProxy = useProxy; systemClient.setItem('settings_use_proxy', String(useProxy));
apiClient.useProxy = useProxy;
}
}} }}
fullWidth
> >
<ToggleButton value={true} color='primary'> <StyledToggleButton value={true}>
{t('Build-in')} {t('Built-in')}
</ToggleButton> </StyledToggleButton>
<ToggleButton value={false} color='secondary'> <StyledToggleButton value={false}>
{t('Disabled')} {t('Disabled')}
</ToggleButton> </StyledToggleButton>
</ToggleButtonGroup> </StyledToggleButtonGroup>
</ListItem> </StyledListItem>
)} )}
</List> </List>
</Grid> </Grid>
@ -266,4 +248,79 @@ const SettingsForm = ({ dense = false }: SettingsFormProps): JSX.Element => {
); );
}; };
export default SettingsForm; // Styled Components
const StyledListItem = styled(ListItem)({
display: 'block',
});
const SettingHeader = styled(Box)({
display: 'flex',
alignItems: 'center',
marginBottom: '0.5em',
});
const StyledSelectLanguage = styled(SelectLanguage)(({ theme }) => ({
'& .MuiOutlinedInput-root': {
borderRadius: '8px',
border: '2px solid black',
boxShadow: '4px 4px 0px rgba(0, 0, 0, 1)',
width: '100%',
},
}));
const AppearanceSettingsBox = styled(Box)(({ theme }) => ({
display: 'flex',
alignItems: 'center',
flexDirection: 'column',
gap: theme.spacing(2),
[theme.breakpoints.up('sm')]: {
flexDirection: 'row',
gap: theme.spacing(1),
},
}));
const QRCodeSwitch = styled(FormControlLabel)(({ theme }) => ({
marginLeft: 0,
[theme.breakpoints.up('sm')]: {
marginLeft: theme.spacing(2),
},
}));
const StyledSlider = styled(Slider)(({ theme }) => ({
'& .MuiSlider-thumb': {
borderRadius: '8px',
border: '2px solid black',
},
'& .MuiSlider-track': {
borderRadius: '8px',
border: '2px solid black',
},
'& .MuiSlider-rail': {
borderRadius: '8px',
border: '2px solid black',
},
}));
const StyledToggleButtonGroup = styled(ToggleButtonGroup)({
width: '100%',
});
const StyledToggleButton = styled(ToggleButton)(({ theme }) => ({
borderRadius: '8px',
border: '2px solid black',
boxShadow: 'none',
fontWeight: 'bold',
width: '100%',
'&.Mui-selected': {
backgroundColor: theme.palette.primary.main,
color: theme.palette.primary.contrastText,
'&:hover': {
backgroundColor: theme.palette.primary.main,
},
},
'&:hover': {
backgroundColor: 'initial',
},
}));
export default SettingsForm;