mirror of
https://github.com/RoboSats/robosats.git
synced 2025-02-25 22:59:02 +00:00
ui-update 2
This commit is contained in:
parent
bae02688d8
commit
739e6e1345
@ -2,38 +2,45 @@ import React, { useState, useContext, useEffect } from 'react';
|
||||
import { useTranslation } from 'react-i18next';
|
||||
import { useNavigate } from 'react-router-dom';
|
||||
import {
|
||||
Button,
|
||||
Grid,
|
||||
LinearProgress,
|
||||
Typography,
|
||||
Alert,
|
||||
LinearProgress,
|
||||
Select,
|
||||
MenuItem,
|
||||
Box,
|
||||
TextField,
|
||||
SelectChangeEvent,
|
||||
useTheme,
|
||||
Tooltip,
|
||||
type SelectChangeEvent,
|
||||
useMediaQuery,
|
||||
styled,
|
||||
} 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 TokenInput from './TokenInput';
|
||||
import { type Slot, type Robot } from '../../models';
|
||||
import { AppContext, type UseAppStoreType } from '../../contexts/AppContext';
|
||||
import { AppContext, UseAppStoreType } from '../../contexts/AppContext';
|
||||
import { genBase62Token } from '../../utils';
|
||||
import { LoadingButton } from '@mui/lab';
|
||||
import { GarageContext, type UseGarageStoreType } from '../../contexts/GarageContext';
|
||||
import { FederationContext, type UseFederationStoreType } from '../../contexts/FederationContext';
|
||||
import { GarageContext, UseGarageStoreType } from '../../contexts/GarageContext';
|
||||
import { FederationContext, 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 {
|
||||
robot: Robot;
|
||||
setRobot: (state: Robot) => void;
|
||||
setView: (state: 'welcome' | 'onboarding' | 'recovery' | 'profile') => void;
|
||||
getGenerateRobot: (token: string, slot?: number) => void;
|
||||
inputToken: string;
|
||||
getGenerateRobot: (token: string) => void;
|
||||
setInputToken: (token: string) => void;
|
||||
logoutRobot: () => void;
|
||||
setInputToken: (state: string) => void;
|
||||
width: number;
|
||||
baseUrl: string;
|
||||
setView: (view: string) => void;
|
||||
}
|
||||
|
||||
const RobotProfile = ({
|
||||
@ -42,7 +49,6 @@ const RobotProfile = ({
|
||||
setInputToken,
|
||||
logoutRobot,
|
||||
setView,
|
||||
width,
|
||||
}: RobotProfileProps): JSX.Element => {
|
||||
const { windowSize } = useContext<UseAppStoreType>(AppContext);
|
||||
const { garage, robotUpdatedAt, orderUpdatedAt } = useContext<UseGarageStoreType>(GarageContext);
|
||||
@ -51,6 +57,7 @@ const RobotProfile = ({
|
||||
const { t } = useTranslation();
|
||||
const theme = useTheme();
|
||||
const navigate = useNavigate();
|
||||
const isMobile = useMediaQuery(theme.breakpoints.down('sm'));
|
||||
|
||||
const [loading, setLoading] = useState<boolean>(true);
|
||||
|
||||
@ -82,278 +89,309 @@ const RobotProfile = ({
|
||||
).length;
|
||||
|
||||
return (
|
||||
<Grid container direction='column' alignItems='center' spacing={1} padding={1} paddingTop={2}>
|
||||
<Grid
|
||||
item
|
||||
container
|
||||
direction='column'
|
||||
alignItems='center'
|
||||
spacing={1}
|
||||
sx={{ width: '100%' }}
|
||||
>
|
||||
<Grid item sx={{ height: '2.3em', position: 'relative' }}>
|
||||
{slot?.nickname ? (
|
||||
<Typography align='center' component='h5' variant='h5'>
|
||||
<div
|
||||
style={{
|
||||
display: 'flex',
|
||||
alignItems: 'center',
|
||||
justifyContent: 'center',
|
||||
flexWrap: 'wrap',
|
||||
}}
|
||||
>
|
||||
{width < 19 ? null : (
|
||||
<Bolt
|
||||
sx={{
|
||||
color: '#fcba03',
|
||||
height: '1.5em',
|
||||
width: '1.5em',
|
||||
}}
|
||||
/>
|
||||
)}
|
||||
<b>{slot?.nickname}</b>
|
||||
{width < 19 ? null : (
|
||||
<Bolt
|
||||
sx={{
|
||||
color: '#fcba03',
|
||||
height: '1.5em',
|
||||
width: '1.5em',
|
||||
}}
|
||||
/>
|
||||
)}
|
||||
</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`,
|
||||
<ProfileContainer $isMobile={isMobile}>
|
||||
<InfoSection colors={COLORS} $isMobile={isMobile}>
|
||||
<NicknameTypography variant={isMobile ? "h6" : "h5"} align="center" $isMobile={isMobile}>
|
||||
<BoltIcon $isMobile={isMobile} />
|
||||
{slot?.nickname}
|
||||
<BoltIcon $isMobile={isMobile} />
|
||||
</NicknameTypography>
|
||||
<StyledRobotAvatar
|
||||
hashId={slot?.hashId}
|
||||
smooth={true}
|
||||
placeholderType='generating'
|
||||
style={{ width: isMobile ? '80px' : '120px', height: isMobile ? '80px' : '120px' }}
|
||||
/>
|
||||
<StatusTypography variant={isMobile ? "body2" : "body1"} align="center" $isMobile={isMobile}>
|
||||
{loadingCoordinators > 0 && !robot?.activeOrderId ? t('Looking for orders!') : t('Ready to Trade')}
|
||||
</StatusTypography>
|
||||
{loadingCoordinators > 0 && !robot?.activeOrderId && <StyledLinearProgress $isMobile={isMobile} />}
|
||||
<TokenBox $isMobile={isMobile}>
|
||||
<CustomIconButton onClick={() => {
|
||||
logoutRobot();
|
||||
setView('welcome');
|
||||
}}>
|
||||
<StyledLogoutIcon $isMobile={isMobile} />
|
||||
</CustomIconButton>
|
||||
<StyledTextField
|
||||
fullWidth
|
||||
value={inputToken}
|
||||
variant="standard"
|
||||
$isMobile={isMobile}
|
||||
InputProps={{
|
||||
readOnly: true,
|
||||
disableUnderline: true,
|
||||
endAdornment: (
|
||||
<CustomIconButton onClick={() => navigator.clipboard.writeText(inputToken)}>
|
||||
<StyledFileCopyIcon $isMobile={isMobile} />
|
||||
</CustomIconButton>
|
||||
),
|
||||
}}
|
||||
tooltip={t('This is your trading avatar')}
|
||||
tooltipPosition='top'
|
||||
/>
|
||||
{robot?.found && Boolean(slot?.lastShortAlias) ? (
|
||||
<Typography align='center' variant='h6'>
|
||||
{t('Welcome back!')}
|
||||
</Typography>
|
||||
</TokenBox>
|
||||
</InfoSection>
|
||||
|
||||
<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>
|
||||
|
||||
{loadingCoordinators > 0 && !robot?.activeOrderId ? (
|
||||
<Grid>
|
||||
<b>{t('Looking for orders!')}</b>
|
||||
<LinearProgress />
|
||||
</Grid>
|
||||
) : null}
|
||||
|
||||
{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' }}
|
||||
</StyledSelect>
|
||||
<ButtonContainer>
|
||||
<StyledButton
|
||||
$buttonColor={BUTTON_COLORS.primary}
|
||||
$hoverColor={BUTTON_COLORS.hoverPrimary}
|
||||
$textColor={BUTTON_COLORS.text}
|
||||
$isMobile={isMobile}
|
||||
onClick={handleAddRobot}
|
||||
>
|
||||
<Tooltip enterTouchDelay={0} enterDelay={300} enterNextDelay={1000} title={t('Logout')}>
|
||||
<Button
|
||||
sx={{ minWidth: '2em', width: '100%' }}
|
||||
color='primary'
|
||||
variant='outlined'
|
||||
onClick={() => {
|
||||
logoutRobot();
|
||||
setView('welcome');
|
||||
}}
|
||||
>
|
||||
<Logout />
|
||||
</Button>
|
||||
</Tooltip>
|
||||
</Grid>
|
||||
<Grid item xs={10}>
|
||||
<TokenInput
|
||||
inputToken={inputToken}
|
||||
editable={false}
|
||||
label={t('Store your token safely')}
|
||||
setInputToken={setInputToken}
|
||||
onPressEnter={() => null}
|
||||
/>
|
||||
</Grid>
|
||||
</Grid>
|
||||
</Grid>
|
||||
<Grid item sx={{ width: '100%' }}>
|
||||
<Box
|
||||
sx={{
|
||||
backgroundColor: 'background.paper',
|
||||
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>
|
||||
<StyledAddIcon $isMobile={isMobile} /> {t('ADD ROBOT')}
|
||||
</StyledButton>
|
||||
{window.NativeRobosats === undefined && (
|
||||
<StyledButton
|
||||
$buttonColor={BUTTON_COLORS.secondary}
|
||||
$hoverColor={BUTTON_COLORS.hoverSecondary}
|
||||
$textColor={BUTTON_COLORS.text}
|
||||
$isMobile={isMobile}
|
||||
onClick={() => garage.download()}
|
||||
>
|
||||
<StyledDownloadIcon $isMobile={isMobile} /> {t('DOWNLOAD')}
|
||||
</StyledButton>
|
||||
)}
|
||||
<StyledButton
|
||||
$buttonColor="transparent"
|
||||
$hoverColor={BUTTON_COLORS.deleteHover}
|
||||
$textColor="red"
|
||||
$isMobile={isMobile}
|
||||
onClick={() => {
|
||||
garage.delete();
|
||||
logoutRobot();
|
||||
setView('welcome');
|
||||
}}
|
||||
>
|
||||
<StyledDeleteSweepIcon $isMobile={isMobile} /> {t('DELETE GARAGE')}
|
||||
</StyledButton>
|
||||
</ButtonContainer>
|
||||
</RightSection>
|
||||
</ProfileContainer>
|
||||
);
|
||||
};
|
||||
|
||||
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;
|
@ -1,7 +1,6 @@
|
||||
import React, { useContext, useEffect, useState } from 'react';
|
||||
import { useTranslation } from 'react-i18next';
|
||||
import {
|
||||
Paper,
|
||||
Grid,
|
||||
CircularProgress,
|
||||
Box,
|
||||
@ -129,42 +128,40 @@ const RobotPage = (): JSX.Element => {
|
||||
} else {
|
||||
return (
|
||||
<StyledMainBox>
|
||||
<StyledPaper>
|
||||
{view === 'welcome' && (
|
||||
<Welcome setView={setView} getGenerateRobot={getGenerateRobot} width={1200} />
|
||||
)}
|
||||
{view === 'welcome' && (
|
||||
<Welcome setView={setView} getGenerateRobot={getGenerateRobot} width={1200} />
|
||||
)}
|
||||
|
||||
{view === 'onboarding' && (
|
||||
<Onboarding
|
||||
setView={setView}
|
||||
badToken={badToken}
|
||||
inputToken={inputToken}
|
||||
setInputToken={setInputToken}
|
||||
getGenerateRobot={getGenerateRobot}
|
||||
/>
|
||||
)}
|
||||
{view === 'onboarding' && (
|
||||
<Onboarding
|
||||
setView={setView}
|
||||
badToken={badToken}
|
||||
inputToken={inputToken}
|
||||
setInputToken={setInputToken}
|
||||
getGenerateRobot={getGenerateRobot}
|
||||
/>
|
||||
)}
|
||||
|
||||
{view === 'profile' && (
|
||||
<RobotProfile
|
||||
setView={setView}
|
||||
logoutRobot={logoutRobot}
|
||||
width={1200}
|
||||
inputToken={inputToken}
|
||||
setInputToken={setInputToken}
|
||||
getGenerateRobot={getGenerateRobot}
|
||||
/>
|
||||
)}
|
||||
{view === 'profile' && (
|
||||
<RobotProfile
|
||||
setView={setView}
|
||||
logoutRobot={logoutRobot}
|
||||
width={1200}
|
||||
inputToken={inputToken}
|
||||
setInputToken={setInputToken}
|
||||
getGenerateRobot={getGenerateRobot}
|
||||
/>
|
||||
)}
|
||||
|
||||
{view === 'recovery' && (
|
||||
<Recovery
|
||||
setView={setView}
|
||||
badToken={badToken}
|
||||
inputToken={inputToken}
|
||||
setInputToken={setInputToken}
|
||||
getRecoverRobot={getGenerateRobot}
|
||||
/>
|
||||
)}
|
||||
</StyledPaper>
|
||||
{view === 'recovery' && (
|
||||
<Recovery
|
||||
setView={setView}
|
||||
badToken={badToken}
|
||||
inputToken={inputToken}
|
||||
setInputToken={setInputToken}
|
||||
getRecoverRobot={getGenerateRobot}
|
||||
/>
|
||||
)}
|
||||
</StyledMainBox>
|
||||
);
|
||||
}
|
||||
@ -174,11 +171,13 @@ const RobotPage = (): JSX.Element => {
|
||||
const StyledConnectingBox = styled(Box)({
|
||||
width: '100vw',
|
||||
height: 'auto',
|
||||
backgroundColor: 'transparent',
|
||||
});
|
||||
|
||||
const StyledTorIconBox = styled(Box)({
|
||||
position: 'fixed',
|
||||
top: '4.6em',
|
||||
backgroundColor: 'transparent',
|
||||
});
|
||||
|
||||
const StyledMainBox = styled(Box)({
|
||||
@ -188,18 +187,9 @@ const StyledMainBox = styled(Box)({
|
||||
justifyContent: 'center',
|
||||
alignItems: 'center',
|
||||
padding: '2em',
|
||||
});
|
||||
|
||||
const StyledPaper = styled(Paper)(({ theme }) => ({
|
||||
width: '80vw',
|
||||
maxWidth: '1200px',
|
||||
maxHeight: '85vh',
|
||||
overflow: 'auto',
|
||||
overflowX: 'clip',
|
||||
backgroundColor: 'transparent',
|
||||
border: 'none',
|
||||
boxShadow: 'none',
|
||||
padding: '1em',
|
||||
}));
|
||||
boxShadow: 'none',
|
||||
});
|
||||
|
||||
export default RobotPage;
|
||||
|
@ -1,19 +1,20 @@
|
||||
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 { AppContext, type UseAppStoreType } from '../../contexts/AppContext';
|
||||
import FederationTable from '../../components/FederationTable';
|
||||
import { t } from 'i18next';
|
||||
import { FederationContext, type UseFederationStoreType } from '../../contexts/FederationContext';
|
||||
import { styled } from '@mui/system';
|
||||
|
||||
const SettingsPage = (): JSX.Element => {
|
||||
const { windowSize, navbarHeight } = useContext<UseAppStoreType>(AppContext);
|
||||
const { federation, addNewCoordinator } = useContext<UseFederationStoreType>(FederationContext);
|
||||
const maxHeight = (windowSize.height * 0.65)
|
||||
const maxHeight = (windowSize.height * 0.65);
|
||||
const [newAlias, setNewAlias] = useState<string>('');
|
||||
const [newUrl, setNewUrl] = 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 addCoordinator: () => void = () => {
|
||||
@ -35,31 +36,22 @@ const SettingsPage = (): JSX.Element => {
|
||||
};
|
||||
|
||||
return (
|
||||
<Paper
|
||||
elevation={12}
|
||||
sx={{
|
||||
padding: '0.6em',
|
||||
width: '20.5em',
|
||||
maxHeight: `${maxHeight}em`,
|
||||
overflow: 'auto',
|
||||
overflowX: 'clip',
|
||||
}}
|
||||
>
|
||||
<SettingsContainer elevation={12}>
|
||||
<Grid container>
|
||||
<Grid item>
|
||||
<LeftGrid item xs={12} md={6}>
|
||||
<SettingsForm />
|
||||
</Grid>
|
||||
<Grid item>
|
||||
<FederationTable maxHeight={18} />
|
||||
</Grid>
|
||||
<Grid item>
|
||||
<Typography align='center' component='h2' variant='subtitle2' color='secondary'>
|
||||
{error}
|
||||
</Typography>
|
||||
</Grid>
|
||||
<List>
|
||||
<ListItem>
|
||||
<TextField
|
||||
</LeftGrid>
|
||||
<RightGrid item xs={12} md={6}>
|
||||
<FederationTableWrapper>
|
||||
<FederationTable maxHeight={18} />
|
||||
</FederationTableWrapper>
|
||||
{error && (
|
||||
<ErrorTypography align='center' component='h2' variant='subtitle2' color='secondary'>
|
||||
{error}
|
||||
</ErrorTypography>
|
||||
)}
|
||||
<InputContainer>
|
||||
<StyledTextField
|
||||
id='outlined-basic'
|
||||
label={t('Alias')}
|
||||
variant='outlined'
|
||||
@ -69,7 +61,7 @@ const SettingsPage = (): JSX.Element => {
|
||||
setNewAlias(e.target.value);
|
||||
}}
|
||||
/>
|
||||
<TextField
|
||||
<StyledTextField
|
||||
id='outlined-basic'
|
||||
label={t('URL')}
|
||||
variant='outlined'
|
||||
@ -79,8 +71,7 @@ const SettingsPage = (): JSX.Element => {
|
||||
setNewUrl(e.target.value);
|
||||
}}
|
||||
/>
|
||||
<Button
|
||||
sx={{ maxHeight: 38 }}
|
||||
<StyledButton
|
||||
disabled={false}
|
||||
onClick={addCoordinator}
|
||||
variant='contained'
|
||||
@ -89,12 +80,83 @@ const SettingsPage = (): JSX.Element => {
|
||||
type='submit'
|
||||
>
|
||||
{t('Add')}
|
||||
</Button>
|
||||
</ListItem>
|
||||
</List>
|
||||
</StyledButton>
|
||||
</InputContainer>
|
||||
</RightGrid>
|
||||
</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;
|
@ -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 { 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 { type Coordinator } from '../../models';
|
||||
import RobotAvatar from '../RobotAvatar';
|
||||
import { Link, LinkOff } from '@mui/icons-material';
|
||||
import { AppContext, type UseAppStoreType } from '../../contexts/AppContext';
|
||||
import { type UseFederationStoreType, FederationContext } from '../../contexts/FederationContext';
|
||||
import headerStyleFix from '../DataGrid/HeaderFix';
|
||||
import { styled } from '@mui/system';
|
||||
|
||||
interface FederationTableProps {
|
||||
maxWidth?: number;
|
||||
maxHeight?: number;
|
||||
fillContainer?: boolean;
|
||||
}
|
||||
|
||||
const FederationTable = ({
|
||||
maxWidth = 90,
|
||||
maxHeight = 50,
|
||||
fillContainer = false,
|
||||
}: FederationTableProps): JSX.Element => {
|
||||
const FederationTable = ({ maxWidth = 90, fillContainer = false }: FederationTableProps): JSX.Element => {
|
||||
const { t } = useTranslation();
|
||||
const { federation, sortedCoordinators, coordinatorUpdatedAt, federationUpdatedAt } =
|
||||
useContext<UseFederationStoreType>(FederationContext);
|
||||
const { federation, sortedCoordinators, coordinatorUpdatedAt } = useContext<UseFederationStoreType>(FederationContext);
|
||||
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) => {
|
||||
return {
|
||||
field: 'longAlias',
|
||||
headerName: t('Coordinator'),
|
||||
width: width * fontSize,
|
||||
width: width,
|
||||
renderCell: (params: any) => {
|
||||
const coordinator = federation.coordinators[params.row.shortAlias];
|
||||
return (
|
||||
<Grid
|
||||
<CoordinatorGrid
|
||||
container
|
||||
direction='row'
|
||||
sx={{ cursor: 'pointer', position: 'relative', left: '-0.3em', width: '50em' }}
|
||||
wrap='nowrap'
|
||||
onClick={() => {
|
||||
onClickCoordinator(params.row.shortAlias);
|
||||
}}
|
||||
alignItems='center'
|
||||
direction="row"
|
||||
wrap="nowrap"
|
||||
onClick={() => onClickCoordinator(params.row.shortAlias)}
|
||||
alignItems="center"
|
||||
spacing={1}
|
||||
>
|
||||
<Grid item>
|
||||
@ -87,31 +47,29 @@ const FederationTable = ({
|
||||
<Grid item>
|
||||
<Typography>{params.row.longAlias}</Typography>
|
||||
</Grid>
|
||||
</Grid>
|
||||
</CoordinatorGrid>
|
||||
);
|
||||
},
|
||||
};
|
||||
}, []);
|
||||
}, [federation.coordinators]);
|
||||
|
||||
const enabledObj = useCallback(
|
||||
(width: number) => {
|
||||
return {
|
||||
field: 'enabled',
|
||||
headerName: t('Enabled'),
|
||||
width: width * fontSize,
|
||||
width: width,
|
||||
renderCell: (params: any) => {
|
||||
return (
|
||||
<Checkbox
|
||||
checked={params.row.enabled}
|
||||
onClick={() => {
|
||||
onEnableChange(params.row.shortAlias);
|
||||
}}
|
||||
onClick={() => onEnableChange(params.row.shortAlias)}
|
||||
/>
|
||||
);
|
||||
},
|
||||
};
|
||||
},
|
||||
[coordinatorUpdatedAt],
|
||||
[coordinatorUpdatedAt]
|
||||
);
|
||||
|
||||
const upObj = useCallback(
|
||||
@ -119,52 +77,41 @@ const FederationTable = ({
|
||||
return {
|
||||
field: 'up',
|
||||
headerName: t('Up'),
|
||||
width: width * fontSize,
|
||||
width: width,
|
||||
renderCell: (params: any) => {
|
||||
return (
|
||||
<div
|
||||
style={{ cursor: 'pointer' }}
|
||||
onClick={() => {
|
||||
onClickCoordinator(params.row.shortAlias);
|
||||
}}
|
||||
>
|
||||
{Boolean(params.row.loadingInfo) && Boolean(params.row.enabled) ? (
|
||||
<CircularProgress thickness={0.35 * fontSize} size={1.5 * fontSize} />
|
||||
<UpStatusContainer onClick={() => onClickCoordinator(params.row.shortAlias)}>
|
||||
{params.row.loadingInfo && params.row.enabled ? (
|
||||
<CircularProgress thickness={2} size={24} />
|
||||
) : params.row.info !== undefined ? (
|
||||
<Link color='success' />
|
||||
<Link color="success" />
|
||||
) : (
|
||||
<LinkOff color='error' />
|
||||
<LinkOff color="error" />
|
||||
)}
|
||||
</div>
|
||||
</UpStatusContainer>
|
||||
);
|
||||
},
|
||||
};
|
||||
},
|
||||
[coordinatorUpdatedAt],
|
||||
[coordinatorUpdatedAt]
|
||||
);
|
||||
|
||||
const columnSpecs = {
|
||||
alias: {
|
||||
priority: 2,
|
||||
order: 1,
|
||||
normal: {
|
||||
width: 12.1,
|
||||
width: 200,
|
||||
object: aliasObj,
|
||||
},
|
||||
},
|
||||
up: {
|
||||
priority: 3,
|
||||
order: 2,
|
||||
normal: {
|
||||
width: 3.5,
|
||||
width: 70,
|
||||
object: upObj,
|
||||
},
|
||||
},
|
||||
enabled: {
|
||||
priority: 1,
|
||||
order: 3,
|
||||
normal: {
|
||||
width: 5,
|
||||
width: 90,
|
||||
object: enabledObj,
|
||||
},
|
||||
},
|
||||
@ -174,29 +121,17 @@ const FederationTable = ({
|
||||
columns: Array<GridColDef<GridValidRowModel>>;
|
||||
width: number;
|
||||
} {
|
||||
const useSmall = maxWidth < 30;
|
||||
const selectedColumns: object[] = [];
|
||||
let width: number = 0;
|
||||
|
||||
for (const value of Object.values(columnSpecs)) {
|
||||
const colWidth = Number(
|
||||
useSmall && Boolean(value.small) ? value.small.width : value.normal.width,
|
||||
);
|
||||
const colObject = useSmall && Boolean(value.small) ? value.small.object : value.normal.object;
|
||||
const colWidth = value.normal.width;
|
||||
const colObject = value.normal.object;
|
||||
|
||||
if (width + colWidth < maxWidth || selectedColumns.length < 2) {
|
||||
width = width + colWidth;
|
||||
selectedColumns.push([colObject(colWidth, false), value.order]);
|
||||
} else {
|
||||
selectedColumns.push([colObject(colWidth, true), value.order]);
|
||||
}
|
||||
width += colWidth;
|
||||
selectedColumns.push([colObject(colWidth), 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) {
|
||||
return item[0];
|
||||
});
|
||||
@ -204,7 +139,7 @@ const FederationTable = ({
|
||||
return { columns, width: width * 0.9 };
|
||||
};
|
||||
|
||||
const { columns, width } = filteredColumns();
|
||||
const { columns } = filteredColumns();
|
||||
|
||||
const onEnableChange = function (shortAlias: string): void {
|
||||
if (federation.getCoordinator(shortAlias).enabled === true) {
|
||||
@ -217,38 +152,65 @@ const FederationTable = ({
|
||||
const reorderedCoordinators = useMemo(() => {
|
||||
return sortedCoordinators.reduce((coordinators, key) => {
|
||||
coordinators[key] = federation.coordinators[key];
|
||||
|
||||
return coordinators;
|
||||
}, {});
|
||||
}, [settings.network, federationUpdatedAt]);
|
||||
}, [settings.network, coordinatorUpdatedAt]);
|
||||
|
||||
const onClickCoordinator = (shortAlias: string): void => {
|
||||
setOpen((open) => {
|
||||
return { ...open, coordinator: shortAlias };
|
||||
});
|
||||
};
|
||||
|
||||
return (
|
||||
<Box
|
||||
sx={
|
||||
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}
|
||||
<TableContainer fillContainer={fillContainer} maxWidth={maxWidth}>
|
||||
<StyledDataGrid
|
||||
rows={Object.values(reorderedCoordinators)}
|
||||
getRowId={(params: Coordinator) => params.shortAlias}
|
||||
columns={columns}
|
||||
checkboxSelection={false}
|
||||
pageSize={pageSize}
|
||||
rowsPerPageOptions={width < 22 ? [] : [0, pageSize, defaultPageSize * 2, 50, 100]}
|
||||
onPageSizeChange={(newPageSize) => {
|
||||
setPageSize(newPageSize);
|
||||
setUseDefaultPageSize(false);
|
||||
}}
|
||||
hideFooter={true}
|
||||
autoHeight
|
||||
hideFooter
|
||||
/>
|
||||
</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;
|
@ -1,36 +1,36 @@
|
||||
import React, { useContext } from 'react';
|
||||
import { useTranslation } from 'react-i18next';
|
||||
import { useTheme } from '@mui/material/styles';
|
||||
import { type UseAppStoreType, AppContext } from '../../contexts/AppContext';
|
||||
import {
|
||||
Grid,
|
||||
Paper,
|
||||
Switch,
|
||||
useTheme,
|
||||
FormControlLabel,
|
||||
List,
|
||||
ListItem,
|
||||
ListItemIcon,
|
||||
Slider,
|
||||
Typography,
|
||||
ToggleButtonGroup,
|
||||
ToggleButton,
|
||||
Box,
|
||||
Slider,
|
||||
} from '@mui/material';
|
||||
import SelectLanguage from './SelectLanguage';
|
||||
import {
|
||||
Translate,
|
||||
Palette,
|
||||
LightMode,
|
||||
DarkMode,
|
||||
SettingsOverscan,
|
||||
Link,
|
||||
AccountBalance,
|
||||
AttachMoney,
|
||||
QrCode,
|
||||
DarkMode,
|
||||
} from '@mui/icons-material';
|
||||
import { systemClient } from '../../services/System';
|
||||
import { TorIcon } from '../Icons';
|
||||
import SwapCalls from '@mui/icons-material/SwapCalls';
|
||||
import { apiClient } from '../../services/api';
|
||||
import { styled } from '@mui/system';
|
||||
|
||||
interface SettingsFormProps {
|
||||
dense?: boolean;
|
||||
@ -38,8 +38,9 @@ interface SettingsFormProps {
|
||||
|
||||
const SettingsForm = ({ dense = false }: SettingsFormProps): JSX.Element => {
|
||||
const { fav, setFav, settings, setSettings } = useContext<UseAppStoreType>(AppContext);
|
||||
const theme = useTheme();
|
||||
const { t } = useTranslation();
|
||||
const theme = useTheme();
|
||||
|
||||
const fontSizes = [
|
||||
{ label: 'XS', value: { basic: 12, pro: 10 } },
|
||||
{ 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 } },
|
||||
];
|
||||
|
||||
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 (
|
||||
<Grid container spacing={1}>
|
||||
<Grid item>
|
||||
<Grid container spacing={2}>
|
||||
<Grid item xs={12}>
|
||||
<List dense={dense}>
|
||||
<ListItem>
|
||||
<ListItemIcon>
|
||||
<Translate />
|
||||
</ListItemIcon>
|
||||
<SelectLanguage
|
||||
{/* Language Settings */}
|
||||
<StyledListItem>
|
||||
<SettingHeader>
|
||||
<ListItemIcon>
|
||||
<Translate />
|
||||
</ListItemIcon>
|
||||
<Typography variant="subtitle1">
|
||||
{t('Language Settings')}
|
||||
</Typography>
|
||||
</SettingHeader>
|
||||
<StyledSelectLanguage
|
||||
language={settings.language}
|
||||
setLanguage={(language) => {
|
||||
setSettings({ ...settings, language });
|
||||
systemClient.setItem('settings_language', language);
|
||||
}}
|
||||
/>
|
||||
</ListItem>
|
||||
</StyledListItem>
|
||||
|
||||
<ListItem>
|
||||
<ListItemIcon>
|
||||
<Palette />
|
||||
</ListItemIcon>
|
||||
<FormControlLabel
|
||||
labelPlacement='end'
|
||||
label={settings.mode === 'dark' ? t('Dark') : t('Light')}
|
||||
control={
|
||||
<Switch
|
||||
checked={settings.mode === 'dark'}
|
||||
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) => {
|
||||
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')}
|
||||
{/* Appearance Settings */}
|
||||
<StyledListItem>
|
||||
<SettingHeader>
|
||||
<ListItemIcon>
|
||||
<Palette />
|
||||
</ListItemIcon>
|
||||
<Typography variant="subtitle1">
|
||||
{t('Appearance Settings')}
|
||||
</Typography>
|
||||
</SettingHeader>
|
||||
<AppearanceSettingsBox>
|
||||
<FormControlLabel
|
||||
labelPlacement="end"
|
||||
label={t('Dark Mode')}
|
||||
control={
|
||||
<Switch
|
||||
checked={settings.mode === 'dark'}
|
||||
onChange={(e) => {
|
||||
const mode = e.target.checked ? 'dark' : 'light';
|
||||
setSettings({ ...settings, mode });
|
||||
systemClient.setItem('settings_mode', mode);
|
||||
}}
|
||||
/>
|
||||
}
|
||||
/>
|
||||
{settings.mode === 'dark' && (
|
||||
<QRCodeSwitch
|
||||
labelPlacement="end"
|
||||
label={t('QR Code Color')}
|
||||
control={
|
||||
<Switch
|
||||
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) => {
|
||||
const lightQRs = !e.target.checked;
|
||||
setSettings({ ...settings, lightQRs });
|
||||
@ -165,17 +125,21 @@ const SettingsForm = ({ dense = false }: SettingsFormProps): JSX.Element => {
|
||||
/>
|
||||
}
|
||||
/>
|
||||
</>
|
||||
) : (
|
||||
<></>
|
||||
)}
|
||||
</ListItem>
|
||||
)}
|
||||
</AppearanceSettingsBox>
|
||||
</StyledListItem>
|
||||
|
||||
<ListItem>
|
||||
<ListItemIcon>
|
||||
<SettingsOverscan />
|
||||
</ListItemIcon>
|
||||
<Slider
|
||||
{/* Font Size Settings */}
|
||||
<StyledListItem>
|
||||
<SettingHeader>
|
||||
<ListItemIcon>
|
||||
<SettingsOverscan />
|
||||
</ListItemIcon>
|
||||
<Typography variant="subtitle1">
|
||||
{t('Font Size')}
|
||||
</Typography>
|
||||
</SettingHeader>
|
||||
<StyledSlider
|
||||
value={settings.fontSize}
|
||||
min={settings.frontend === 'basic' ? 12 : 10}
|
||||
max={settings.frontend === 'basic' ? 16 : 14}
|
||||
@ -185,80 +149,98 @@ const SettingsForm = ({ dense = false }: SettingsFormProps): JSX.Element => {
|
||||
setSettings({ ...settings, fontSize });
|
||||
systemClient.setItem(`settings_fontsize_${settings.frontend}`, fontSize.toString());
|
||||
}}
|
||||
valueLabelDisplay='off'
|
||||
valueLabelDisplay="off"
|
||||
track={false}
|
||||
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,
|
||||
}))}
|
||||
track={false}
|
||||
/>
|
||||
</ListItem>
|
||||
</StyledListItem>
|
||||
|
||||
<ListItem>
|
||||
<ListItemIcon>
|
||||
<AccountBalance />
|
||||
</ListItemIcon>
|
||||
<ToggleButtonGroup
|
||||
{/* Currency Settings */}
|
||||
<StyledListItem>
|
||||
<SettingHeader>
|
||||
<ListItemIcon>
|
||||
<AccountBalance />
|
||||
</ListItemIcon>
|
||||
<Typography variant="subtitle1">
|
||||
{t('Currency Settings')}
|
||||
</Typography>
|
||||
</SettingHeader>
|
||||
<StyledToggleButtonGroup
|
||||
exclusive={true}
|
||||
value={fav.mode}
|
||||
onChange={(e, mode) => {
|
||||
setFav({ ...fav, mode, currency: mode === 'fiat' ? 0 : 1000 });
|
||||
}}
|
||||
onChange={handleToggleChange}
|
||||
fullWidth
|
||||
>
|
||||
<ToggleButton value='fiat' color='primary'>
|
||||
<StyledToggleButton value="fiat">
|
||||
<AttachMoney />
|
||||
{t('Fiat')}
|
||||
</ToggleButton>
|
||||
<ToggleButton value='swap' color='secondary'>
|
||||
</StyledToggleButton>
|
||||
<StyledToggleButton value="swap">
|
||||
<SwapCalls />
|
||||
{t('Swaps')}
|
||||
</ToggleButton>
|
||||
</ToggleButtonGroup>
|
||||
</ListItem>
|
||||
</StyledToggleButton>
|
||||
</StyledToggleButtonGroup>
|
||||
</StyledListItem>
|
||||
|
||||
<ListItem>
|
||||
<ListItemIcon>
|
||||
<Link />
|
||||
</ListItemIcon>
|
||||
<ToggleButtonGroup
|
||||
{/* Network Settings */}
|
||||
<StyledListItem>
|
||||
<SettingHeader>
|
||||
<ListItemIcon>
|
||||
<Link />
|
||||
</ListItemIcon>
|
||||
<Typography variant="subtitle1">
|
||||
{t('Network Settings')}
|
||||
</Typography>
|
||||
</SettingHeader>
|
||||
<StyledToggleButtonGroup
|
||||
exclusive={true}
|
||||
value={settings.network}
|
||||
onChange={(e, network) => {
|
||||
setSettings({ ...settings, network });
|
||||
systemClient.setItem('settings_network', network);
|
||||
}}
|
||||
onChange={handleNetworkChange}
|
||||
fullWidth
|
||||
>
|
||||
<ToggleButton value='mainnet' color='primary'>
|
||||
<StyledToggleButton value="mainnet">
|
||||
{t('Mainnet')}
|
||||
</ToggleButton>
|
||||
<ToggleButton value='testnet' color='secondary'>
|
||||
</StyledToggleButton>
|
||||
<StyledToggleButton value="testnet">
|
||||
{t('Testnet')}
|
||||
</ToggleButton>
|
||||
</ToggleButtonGroup>
|
||||
</ListItem>
|
||||
</StyledToggleButton>
|
||||
</StyledToggleButtonGroup>
|
||||
</StyledListItem>
|
||||
|
||||
{/* Proxy Settings */}
|
||||
{window.NativeRobosats !== undefined && (
|
||||
<ListItem>
|
||||
<ListItemIcon>
|
||||
<TorIcon />
|
||||
</ListItemIcon>
|
||||
<ToggleButtonGroup
|
||||
<StyledListItem>
|
||||
<SettingHeader>
|
||||
<ListItemIcon>
|
||||
<TorIcon />
|
||||
</ListItemIcon>
|
||||
<Typography variant="subtitle1">
|
||||
{t('Proxy Settings')}
|
||||
</Typography>
|
||||
</SettingHeader>
|
||||
<StyledToggleButtonGroup
|
||||
exclusive={true}
|
||||
value={settings.useProxy}
|
||||
onChange={(_e, useProxy) => {
|
||||
setSettings({ ...settings, useProxy });
|
||||
systemClient.setItem('settings_use_proxy', String(useProxy));
|
||||
apiClient.useProxy = useProxy;
|
||||
if (useProxy !== null) {
|
||||
setSettings({ ...settings, useProxy });
|
||||
systemClient.setItem('settings_use_proxy', String(useProxy));
|
||||
apiClient.useProxy = useProxy;
|
||||
}
|
||||
}}
|
||||
fullWidth
|
||||
>
|
||||
<ToggleButton value={true} color='primary'>
|
||||
{t('Build-in')}
|
||||
</ToggleButton>
|
||||
<ToggleButton value={false} color='secondary'>
|
||||
<StyledToggleButton value={true}>
|
||||
{t('Built-in')}
|
||||
</StyledToggleButton>
|
||||
<StyledToggleButton value={false}>
|
||||
{t('Disabled')}
|
||||
</ToggleButton>
|
||||
</ToggleButtonGroup>
|
||||
</ListItem>
|
||||
</StyledToggleButton>
|
||||
</StyledToggleButtonGroup>
|
||||
</StyledListItem>
|
||||
)}
|
||||
</List>
|
||||
</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;
|
Loading…
Reference in New Issue
Block a user