diff --git a/frontend/src/basic/RobotPage/RobotProfile.tsx b/frontend/src/basic/RobotPage/RobotProfile.tsx index 4095d41a..300288fb 100644 --- a/frontend/src/basic/RobotPage/RobotProfile.tsx +++ b/frontend/src/basic/RobotPage/RobotProfile.tsx @@ -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(AppContext); const { garage, robotUpdatedAt, orderUpdatedAt } = useContext(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(true); @@ -82,278 +89,309 @@ const RobotProfile = ({ ).length; return ( - - - - {slot?.nickname ? ( - -
- {width < 19 ? null : ( - - )} - {slot?.nickname} - {width < 19 ? null : ( - - )} -
-
- ) : ( - <> - {t('Building your robot!')} - - - )} -
- - - + + + + {slot?.nickname} + + + + + {loadingCoordinators > 0 && !robot?.activeOrderId ? t('Looking for orders!') : t('Ready to Trade')} + + {loadingCoordinators > 0 && !robot?.activeOrderId && } + + { + logoutRobot(); + setView('welcome'); + }}> + + + navigator.clipboard.writeText(inputToken)}> + + + ), }} - tooltip={t('This is your trading avatar')} - tooltipPosition='top' /> - {robot?.found && Boolean(slot?.lastShortAlias) ? ( - - {t('Welcome back!')} - + + + + + + + {t('Robot Garage')} + + + + {loading ? ( + + {t('Building...')} + ) : ( - <> + Object.values(garage.slots).map((slot: Slot, index: number) => ( + + + + {slot?.nickname} + + + )) )} - - - {loadingCoordinators > 0 && !robot?.activeOrderId ? ( - - {t('Looking for orders!')} - - - ) : null} - - {Boolean(robot?.activeOrderId) && Boolean(slot?.hashId) ? ( - - - - ) : null} - - {Boolean(robot?.lastOrderId) && Boolean(slot?.hashId) ? ( - - - - - - - - - {t( - 'Reusing trading identity degrades your privacy against other users, coordinators and observers.', - )} - - - - - - - - - ) : null} - - {!robot?.activeOrderId && - slot?.hashId && - !robot?.lastOrderId && - loadingCoordinators === 0 ? ( - {t('No existing orders found')} - ) : null} - - - + + - - - - - - null} - /> - - -
- - - - - {t('Robot Garage')} - - - - - - -
- {t('Add Robot')} - - - - {window.NativeRobosats === undefined ? ( - - - - ) : null} - - - - - - - - - + {t('ADD ROBOT')} + + {window.NativeRobosats === undefined && ( + garage.download()} + > + {t('DOWNLOAD')} + + )} + { + garage.delete(); + logoutRobot(); + setView('welcome'); + }} + > + {t('DELETE GARAGE')} + + + + ); }; -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; \ No newline at end of file diff --git a/frontend/src/basic/RobotPage/index.tsx b/frontend/src/basic/RobotPage/index.tsx index 5015c849..c0b4c993 100644 --- a/frontend/src/basic/RobotPage/index.tsx +++ b/frontend/src/basic/RobotPage/index.tsx @@ -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 ( - - {view === 'welcome' && ( - - )} + {view === 'welcome' && ( + + )} - {view === 'onboarding' && ( - - )} + {view === 'onboarding' && ( + + )} - {view === 'profile' && ( - - )} + {view === 'profile' && ( + + )} - {view === 'recovery' && ( - - )} - + {view === 'recovery' && ( + + )} ); } @@ -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; diff --git a/frontend/src/basic/SettingsPage/index.tsx b/frontend/src/basic/SettingsPage/index.tsx index b6be6be0..6bcf2362 100644 --- a/frontend/src/basic/SettingsPage/index.tsx +++ b/frontend/src/basic/SettingsPage/index.tsx @@ -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(AppContext); const { federation, addNewCoordinator } = useContext(FederationContext); - const maxHeight = (windowSize.height * 0.65) + const maxHeight = (windowSize.height * 0.65); const [newAlias, setNewAlias] = useState(''); const [newUrl, setNewUrl] = useState(''); const [error, setError] = useState(); - // 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 ( - + - + - - - - - - - {error} - - - - - + + + + + {error && ( + + {error} + + )} + + { setNewAlias(e.target.value); }} /> - { setNewUrl(e.target.value); }} /> - - - + + + - + ); }; -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; \ No newline at end of file diff --git a/frontend/src/components/FederationTable/index.tsx b/frontend/src/components/FederationTable/index.tsx index 9e64483b..8851230b 100644 --- a/frontend/src/components/FederationTable/index.tsx +++ b/frontend/src/components/FederationTable/index.tsx @@ -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(FederationContext); + const { federation, sortedCoordinators, coordinatorUpdatedAt } = useContext(FederationContext); const { setOpen, settings } = useContext(AppContext); - const theme = useTheme(); - const [pageSize, setPageSize] = useState(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 ( - { - onClickCoordinator(params.row.shortAlias); - }} - alignItems='center' + direction="row" + wrap="nowrap" + onClick={() => onClickCoordinator(params.row.shortAlias)} + alignItems="center" spacing={1} > @@ -87,31 +47,29 @@ const FederationTable = ({ {params.row.longAlias} - + ); }, }; - }, []); + }, [federation.coordinators]); const enabledObj = useCallback( (width: number) => { return { field: 'enabled', headerName: t('Enabled'), - width: width * fontSize, + width: width, renderCell: (params: any) => { return ( { - 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 ( -
{ - onClickCoordinator(params.row.shortAlias); - }} - > - {Boolean(params.row.loadingInfo) && Boolean(params.row.enabled) ? ( - + onClickCoordinator(params.row.shortAlias)}> + {params.row.loadingInfo && params.row.enabled ? ( + ) : params.row.info !== undefined ? ( - + ) : ( - + )} -
+ ); }, }; }, - [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>; 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> = 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 ( - - + 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 /> - + ); }; -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; \ No newline at end of file diff --git a/frontend/src/components/SettingsForm/index.tsx b/frontend/src/components/SettingsForm/index.tsx index 76f994b0..d7292798 100644 --- a/frontend/src/components/SettingsForm/index.tsx +++ b/frontend/src/components/SettingsForm/index.tsx @@ -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(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 ( - - + + - - - - - + + + + + + {t('Language Settings')} + + + { setSettings({ ...settings, language }); systemClient.setItem('settings_language', language); }} /> - + - - - - - - - - } - icon={ - - - - } - onChange={(e) => { - const mode = e.target.checked ? 'dark' : 'light'; - setSettings({ ...settings, mode }); - systemClient.setItem('settings_mode', mode); - }} - /> - } - /> - {settings.mode === 'dark' ? ( - <> - - - - + + + + + + {t('Appearance Settings')} + + + + { + const mode = e.target.checked ? 'dark' : 'light'; + setSettings({ ...settings, mode }); + systemClient.setItem('settings_mode', mode); + }} + /> + } + /> + {settings.mode === 'dark' && ( + - - - } - icon={ - - - - } onChange={(e) => { const lightQRs = !e.target.checked; setSettings({ ...settings, lightQRs }); @@ -165,17 +125,21 @@ const SettingsForm = ({ dense = false }: SettingsFormProps): JSX.Element => { /> } /> - - ) : ( - <> - )} - + )} + + - - - - - + + + + + + {t('Font Size')} + + + { setSettings({ ...settings, fontSize }); systemClient.setItem(`settings_fontsize_${settings.frontend}`, fontSize.toString()); }} - valueLabelDisplay='off' + valueLabelDisplay="off" + track={false} marks={fontSizes.map(({ label, value }) => ({ - label: {t(label)}, + label: {t(label)}, value: settings.frontend === 'basic' ? value.basic : value.pro, }))} - track={false} /> - + - - - - - + + + + + + {t('Currency Settings')} + + + { - setFav({ ...fav, mode, currency: mode === 'fiat' ? 0 : 1000 }); - }} + onChange={handleToggleChange} + fullWidth > - + {t('Fiat')} - - + + {t('Swaps')} - - - + + + - - - - - + + + + + + {t('Network Settings')} + + + { - setSettings({ ...settings, network }); - systemClient.setItem('settings_network', network); - }} + onChange={handleNetworkChange} + fullWidth > - + {t('Mainnet')} - - + + {t('Testnet')} - - - + + + + {/* Proxy Settings */} {window.NativeRobosats !== undefined && ( - - - - - + + + + + + {t('Proxy Settings')} + + + { - 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 > - - {t('Build-in')} - - + + {t('Built-in')} + + {t('Disabled')} - - - + + + )} @@ -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; \ No newline at end of file