Merge pull request #1431 from sahil-tgs/ui-updates

ui-update 2
This commit is contained in:
KoalaSat 2024-10-18 21:17:10 +00:00 committed by GitHub
commit ec1b4f2576
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
59 changed files with 2451 additions and 1471 deletions

View File

@ -478,6 +478,7 @@ class LNDNode:
payment_request=lnpayment.invoice,
fee_limit_sat=fee_limit_sat,
timeout_seconds=timeout_seconds,
amp=True,
)
routerstub = router_pb2_grpc.RouterStub(cls.channel)
@ -536,6 +537,7 @@ class LNDNode:
fee_limit_sat=fee_limit_sat,
timeout_seconds=timeout_seconds,
allow_self_payment=True,
amp=True,
)
order = lnpayment.order_paid_LN

View File

@ -50,16 +50,16 @@ class Command(BaseCommand):
parts = message.split(" ")
if len(parts) < 2:
self.notifications.send_telegram_message(
chat_id=result["message"]["from"]["id"],
text='You must enable the notifications bot using the RoboSats client. Click on your "Robot robot" -> "Enable Telegram" and follow the link or scan the QR code.',
result["message"]["from"]["id"],
'You must enable the notifications bot using the RoboSats client. Click on your "Robot robot" -> "Enable Telegram" and follow the link or scan the QR code.',
)
continue
token = parts[-1]
robot = Robot.objects.filter(telegram_token=token).first()
if not robot:
self.notifications.send_telegram_message(
chat_id=result["message"]["from"]["id"],
text=f'Wops, invalid token! There is no Robot with telegram chat token "{token}"',
result["message"]["from"]["id"],
f'Wops, invalid token! There is no Robot with telegram chat token "{token}"',
)
continue

View File

@ -0,0 +1,18 @@
# Generated by Django 5.0.8 on 2024-08-15 18:06
from django.db import migrations, models
class Migration(migrations.Migration):
dependencies = [
('api', '0048_alter_order_reference'),
]
operations = [
migrations.AlterField(
model_name='currency',
name='currency',
field=models.PositiveSmallIntegerField(choices=[(1, 'USD'), (2, 'EUR'), (3, 'JPY'), (4, 'GBP'), (5, 'AUD'), (6, 'CAD'), (7, 'CHF'), (8, 'CNY'), (9, 'HKD'), (10, 'NZD'), (11, 'SEK'), (12, 'KRW'), (13, 'SGD'), (14, 'NOK'), (15, 'MXN'), (16, 'BYN'), (17, 'RUB'), (18, 'ZAR'), (19, 'TRY'), (20, 'BRL'), (21, 'CLP'), (22, 'CZK'), (23, 'DKK'), (24, 'HRK'), (25, 'HUF'), (26, 'INR'), (27, 'ISK'), (28, 'PLN'), (29, 'RON'), (30, 'ARS'), (31, 'VES'), (32, 'COP'), (33, 'PEN'), (34, 'UYU'), (35, 'PYG'), (36, 'BOB'), (37, 'IDR'), (38, 'ANG'), (39, 'CRC'), (40, 'CUP'), (41, 'DOP'), (42, 'GHS'), (43, 'GTQ'), (44, 'ILS'), (45, 'JMD'), (46, 'KES'), (47, 'KZT'), (48, 'MYR'), (49, 'NAD'), (50, 'NGN'), (51, 'AZN'), (52, 'PAB'), (53, 'PHP'), (54, 'PKR'), (55, 'QAR'), (56, 'SAR'), (57, 'THB'), (58, 'TTD'), (59, 'VND'), (60, 'XOF'), (61, 'TWD'), (62, 'TZS'), (63, 'XAF'), (64, 'UAH'), (65, 'EGP'), (66, 'LKR'), (67, 'MAD'), (68, 'AED'), (69, 'TND'), (70, 'ETB'), (71, 'GEL'), (72, 'UGX'), (73, 'RSD'), (74, 'IRT'), (75, 'BDT'), (76, 'ALL'), (77, 'DZD'), (300, 'XAU'), (1000, 'BTC')], unique=True),
),
]

View File

@ -0,0 +1,18 @@
# Generated by Django 5.0.8 on 2024-08-22 08:30
from django.db import migrations, models
class Migration(migrations.Migration):
dependencies = [
('api', '0049_alter_currency_currency'),
]
operations = [
migrations.AlterField(
model_name='order',
name='status',
field=models.PositiveSmallIntegerField(choices=[(0, 'Waiting for maker bond'), (1, 'Public'), (2, 'Paused'), (3, 'Waiting for taker bond'), (4, 'Cancelled'), (5, 'Expired'), (6, 'Waiting for trade collateral and buyer invoice'), (7, 'Waiting only for seller trade collateral'), (8, 'Waiting only for buyer invoice'), (9, 'Sending fiat - In chatroom'), (10, 'Fiat sent - In chatroom'), (11, 'In dispute'), (12, 'Collaboratively cancelled'), (13, 'Sending satoshis to buyer'), (14, 'Successful trade'), (15, 'Failed lightning network routing'), (16, 'Wait for dispute resolution'), (17, 'Maker lost dispute'), (18, 'Taker lost dispute')], default=0),
),
]

View File

@ -46,7 +46,7 @@ class Order(models.Model):
DIS = 11, "In dispute"
CCA = 12, "Collaboratively cancelled"
PAY = 13, "Sending satoshis to buyer"
SUC = 14, "Sucessful trade"
SUC = 14, "Successful trade"
FAI = 15, "Failed lightning network routing"
WFR = 16, "Wait for dispute resolution"
MLD = 17, "Maker lost dispute"

View File

@ -37,13 +37,13 @@ class Notifications:
if robot.telegram_enabled:
self.send_telegram_message(robot.telegram_chat_id, title, description)
def save_message(self, order, robot, title, description):
def save_message(self, order, robot, title, description=""):
"""Save a message for a user"""
Notification.objects.create(
title=title, description=description, robot=robot, order=order
)
def send_telegram_message(self, chat_id, title, description):
def send_telegram_message(self, chat_id, title, description=""):
"""sends a message to a user with telegram notifications enabled"""
bot_token = config("TELEGRAM_TOKEN")

View File

@ -112,7 +112,7 @@ class OrderViewSchema:
- `11` "In dispute"
- `12` "Collaboratively cancelled"
- `13` "Sending satoshis to buyer"
- `14` "Sucessful trade"
- `14` "Successful trade"
- `15` "Failed lightning network routing"
- `16` "Wait for dispute resolution"
- `17` "Maker lost dispute"

View File

@ -141,7 +141,7 @@ def get_devfund_pubkey(network: str) -> str:
"""
session = get_session()
url = "https://raw.githubusercontent.com/RoboSats/robosats/main/devfund_pubey.json"
url = "https://raw.githubusercontent.com/RoboSats/robosats/main/devfund_pubkey.json"
try:
response = session.get(url)

View File

@ -365,7 +365,7 @@ paths:
- `11` "In dispute"
- `12` "Collaboratively cancelled"
- `13` "Sending satoshis to buyer"
- `14` "Sucessful trade"
- `14` "Successful trade"
- `15` "Failed lightning network routing"
- `16` "Wait for dispute resolution"
- `17` "Maker lost dispute"
@ -1833,7 +1833,7 @@ components:
* `11` - In dispute
* `12` - Collaboratively cancelled
* `13` - Sending satoshis to buyer
* `14` - Sucessful trade
* `14` - Successful trade
* `15` - Failed lightning network routing
* `16` - Wait for dispute resolution
* `17` - Maker lost dispute

View File

@ -23,11 +23,11 @@
"@nivo/core": "^0.86.0",
"@nivo/line": "^0.86.0",
"base-ex": "^0.8.1",
"country-flag-icons": "^1.5.11",
"country-flag-icons": "^1.5.13",
"date-fns": "^2.30.0",
"file-replace-loader": "^1.4.0",
"i18next": "^23.2.11",
"i18next-browser-languagedetector": "^7.2.1",
"i18next-browser-languagedetector": "^8.0.0",
"i18next-http-backend": "^2.5.0",
"install": "^0.13.0",
"js-sha256": "^0.11.0",
@ -81,7 +81,7 @@
"jest": "^29.6.1",
"prettier": "^3.3.2",
"ts-node": "^10.9.2",
"typescript": "^5.4.2",
"typescript": "^5.5.4",
"webpack": "^5.89.0",
"webpack-cli": "^5.1.4"
}
@ -6411,9 +6411,9 @@
}
},
"node_modules/country-flag-icons": {
"version": "1.5.11",
"resolved": "https://registry.npmjs.org/country-flag-icons/-/country-flag-icons-1.5.11.tgz",
"integrity": "sha512-B+mvFywunkRJs270k7kCBjhogvIA0uNn6GAXv6m2cPn3rrwqZzZVr2gBWcz+Cz7OGVWlcbERlYRIX0S6OGr8Bw=="
"version": "1.5.13",
"resolved": "https://registry.npmjs.org/country-flag-icons/-/country-flag-icons-1.5.13.tgz",
"integrity": "sha512-4JwHNqaKZ19doQoNcBjsoYA+I7NqCH/mC/6f5cBWvdKzcK5TMmzLpq3Z/syVHMHJuDGFwJ+rPpGizvrqJybJow=="
},
"node_modules/create-require": {
"version": "1.1.1",
@ -8823,9 +8823,9 @@
}
},
"node_modules/i18next-browser-languagedetector": {
"version": "7.2.1",
"resolved": "https://registry.npmjs.org/i18next-browser-languagedetector/-/i18next-browser-languagedetector-7.2.1.tgz",
"integrity": "sha512-h/pM34bcH6tbz8WgGXcmWauNpQupCGr25XPp9cZwZInR9XHSjIFDYp1SIok7zSPsTOMxdvuLyu86V+g2Kycnfw==",
"version": "8.0.0",
"resolved": "https://registry.npmjs.org/i18next-browser-languagedetector/-/i18next-browser-languagedetector-8.0.0.tgz",
"integrity": "sha512-zhXdJXTTCoG39QsrOCiOabnWj2jecouOqbchu3EfhtSHxIB5Uugnm9JaizenOy39h7ne3+fLikIjeW88+rgszw==",
"dependencies": {
"@babel/runtime": "^7.23.2"
}
@ -16730,9 +16730,9 @@
}
},
"node_modules/typescript": {
"version": "5.4.2",
"resolved": "https://registry.npmjs.org/typescript/-/typescript-5.4.2.tgz",
"integrity": "sha512-+2/g0Fds1ERlP6JsakQQDXjZdZMM+rqpamFZJEKh4kwTIn3iDkgKtby0CeNd5ATNZ4Ry1ax15TMx0W2V+miizQ==",
"version": "5.5.4",
"resolved": "https://registry.npmjs.org/typescript/-/typescript-5.5.4.tgz",
"integrity": "sha512-Mtq29sKDAEYP7aljRgtPOpTvOfbwRWlS6dPRzwjdE+C0R4brX/GUyhHSecbHMFLNBLcJIPt9nl9yG5TZ1weH+Q==",
"dev": true,
"bin": {
"tsc": "bin/tsc",

View File

@ -43,7 +43,7 @@
"jest": "^29.6.1",
"prettier": "^3.3.2",
"ts-node": "^10.9.2",
"typescript": "^5.4.2",
"typescript": "^5.5.4",
"webpack": "^5.89.0",
"webpack-cli": "^5.1.4"
},
@ -62,11 +62,11 @@
"@nivo/core": "^0.86.0",
"@nivo/line": "^0.86.0",
"base-ex": "^0.8.1",
"country-flag-icons": "^1.5.11",
"country-flag-icons": "^1.5.13",
"date-fns": "^2.30.0",
"file-replace-loader": "^1.4.0",
"i18next": "^23.2.11",
"i18next-browser-languagedetector": "^7.2.1",
"i18next-browser-languagedetector": "^8.0.0",
"i18next-http-backend": "^2.5.0",
"install": "^0.13.0",
"js-sha256": "^0.11.0",

View File

@ -40,14 +40,14 @@ const OrderPage = (): JSX.Element => {
const shortAlias = params.shortAlias;
const coordinator = federation.getCoordinator(shortAlias ?? '');
if (coordinator) {
const { url, basePath } = coordinator?.getEndpoint(
const endpoint = coordinator?.getEndpoint(
settings.network,
origin,
settings.selfhostedClient,
hostUrl,
);
setBaseUrl(`${url}${basePath}`);
if (endpoint) setBaseUrl(`${endpoint?.url}${endpoint?.basePath}`);
const orderId = Number(params.orderId);
if (

View File

@ -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 && !Boolean(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}
{!Boolean(robot?.activeOrderId) &&
slot?.hashId &&
!Boolean(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;

View File

@ -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;

View File

@ -1,22 +1,24 @@
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, UseFederationStoreType } from '../../contexts/FederationContext';
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 = () => {
const addCoordinator: () => void = () => {
if (federation.coordinators[newAlias]) {
setError(t('Alias already exists'));
} else {
@ -35,48 +37,42 @@ 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'
size='small'
value={newAlias}
onChange={(e) => setNewAlias(e.target.value)}
onChange={(e) => {
setNewAlias(e.target.value);
}}
/>
<TextField
<StyledTextField
id='outlined-basic'
label={t('URL')}
variant='outlined'
size='small'
value={newUrl}
onChange={(e) => setNewUrl(e.target.value)}
onChange={(e) => {
setNewUrl(e.target.value);
}}
/>
<Button
sx={{ maxHeight: 38 }}
<StyledButton
disabled={false}
onClick={addCoordinator}
variant='contained'
@ -85,12 +81,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;

View File

@ -14,7 +14,6 @@ import {
LinearProgress,
IconButton,
Tooltip,
type LinearProgressProps,
styled,
} from '@mui/material';
import {
@ -24,7 +23,7 @@ import {
type GridPaginationModel,
type GridColDef,
type GridValidRowModel,
GridSlotsComponent,
type GridSlotsComponent,
} from '@mui/x-data-grid';
import currencyDict from '../../../static/assets/currencies.json';
import { type PublicOrder } from '../../models';
@ -831,14 +830,6 @@ const BookTable = ({
);
};
interface GridComponentProps {
LoadingOverlay: (props: LinearProgressProps) => JSX.Element;
NoResultsOverlay?: (props: any) => JSX.Element;
NoRowsOverlay?: (props: any) => JSX.Element;
Footer?: (props: any) => JSX.Element;
Toolbar?: (props: any) => JSX.Element;
}
const NoResultsOverlay = function (): JSX.Element {
return (
<Grid

View File

@ -1,4 +1,4 @@
import React, { useContext, useEffect, useMemo, useState } from 'react';
import React, { useContext, useEffect, useState } from 'react';
import { useTranslation } from 'react-i18next';
import {

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 { 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;

View File

@ -90,6 +90,7 @@ const FlagWithProps = ({ code, width = '1.428em', height = '1.428em' }: Props):
if (code === 'IRT') flag = <Flags.IR {...defaultProps} />;
if (code === 'BDT') flag = <Flags.BD {...defaultProps} />;
if (code === 'ALL') flag = <Flags.AL {...defaultProps} />;
if (code === 'DZD') flag = <Flags.DZ {...defaultProps} />;
if (code === 'ANY') flag = <EarthIcon {...defaultProps} />;
if (code === 'XAU') flag = <GoldIcon {...defaultProps} />;
if (code === 'BTC') flag = <SwapCallsIcon color='primary' />;

View File

@ -409,9 +409,8 @@ const AutocompletePayments: React.FC<AutocompletePaymentsProps> = (props) => {
))}
{qttHiddenTags > 0 ? (
<StyledChip
sx={{ borderRadius: 1 }}
label={`+${qttHiddenTags}`}
sx={{ height: '1.6rem' }}
sx={{ borderRadius: 1, height: '1.6rem' }}
/>
) : null}
</>

View File

@ -1,4 +1,4 @@
import React, { useContext, useMemo } from 'react';
import React, { useContext } from 'react';
import {
Grid,
Select,

View File

@ -248,7 +248,7 @@ const Notifications = ({
// 11: 'In dispute'
// 12: 'Collaboratively cancelled'
// 13: 'Sending satoshis to buyer'
// 14: 'Sucessful trade'
// 14: 'Successful trade'
// 15: 'Failed lightning network routing'
// 16: 'Wait for dispute resolution'
// 17: 'Maker lost dispute'

View File

@ -174,8 +174,8 @@ const OrderDetails = ({
const isBuyer = (order.type === 0 && order.is_maker) || (order.type === 1 && !order.is_maker);
const tradeFee = order.is_maker
? coordinator.info?.maker_fee ?? 0
: coordinator.info?.taker_fee ?? 0;
? (coordinator.info?.maker_fee ?? 0)
: (coordinator.info?.taker_fee ?? 0);
const defaultRoutingBudget = 0.001;
const btc_now = order.satoshis_now / 100000000;
const rate = Number(order.max_amount ?? order.amount) / btc_now;

View File

@ -1,39 +1,36 @@
import React, { useContext, useEffect } from 'react';
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,
ControlPoint,
DarkMode,
} from '@mui/icons-material';
import { systemClient } from '../../services/System';
import { TorIcon } from '../Icons';
import SwapCalls from '@mui/icons-material/SwapCalls';
import { FederationContext, type UseFederationStoreType } from '../../contexts/FederationContext';
import { GarageContext, UseGarageStoreType } from '../../contexts/GarageContext';
import { apiClient } from '../../services/api';
import { styled } from '@mui/system';
interface SettingsFormProps {
dense?: boolean;
@ -41,10 +38,9 @@ interface SettingsFormProps {
const SettingsForm = ({ dense = false }: SettingsFormProps): JSX.Element => {
const { fav, setFav, settings, setSettings } = useContext<UseAppStoreType>(AppContext);
const { federation } = useContext<UseFederationStoreType>(FederationContext);
const { garage } = useContext<UseGarageStoreType>(GarageContext);
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 } },
@ -53,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 });
@ -170,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}
@ -190,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>
@ -271,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;

View File

@ -639,7 +639,7 @@ const TradeBox = ({ baseUrl, onStartAgain }: TradeBoxProps): JSX.Element => {
}
break;
// 14: 'Sucessful trade'
// 14: 'Successful trade'
case 14:
baseContract.title = 'Trade finished!';
baseContract.titleColor = 'success';

View File

@ -4,19 +4,17 @@ import React, {
useEffect,
useState,
type SetStateAction,
useMemo,
useContext,
type ReactNode,
} from 'react';
import { type Order, Federation, Settings, Coordinator } from '../models';
import { type Order, Federation, Settings } from '../models';
import { federationLottery } from '../utils';
import { AppContext, type UseAppStoreType } from './AppContext';
import { GarageContext, type UseGarageStoreType } from './GarageContext';
import NativeRobosats from '../services/Native';
import { Origins } from '../models/Coordinator.model';
import { type Origin, type Origins } from '../models/Coordinator.model';
// Refresh delays (ms) according to Order status
const defaultDelay = 5000;
@ -35,7 +33,7 @@ const statusToDelay = [
100000, // 'In dispute'
999999, // 'Collaboratively cancelled'
10000, // 'Sending satoshis to buyer'
60000, // 'Sucessful trade'
60000, // 'Successful trade'
30000, // 'Failed lightning network routing'
300000, // 'Wait for dispute resolution'
300000, // 'Maker lost dispute'
@ -175,14 +173,15 @@ export const FederationContextProvider = ({
federated: false,
enabled: true,
};
const origins: Origins = {
clearnet: undefined,
onion: url as Origin,
i2p: undefined,
};
if (settings.network === 'mainnet') {
attributes.mainnet = {
onion: url,
} as Origins;
attributes.mainnet = origins;
} else {
attributes.testnet = {
onion: url,
} as Origins;
attributes.testnet = origins;
}
federation.addCoordinator(origin, settings, hostUrl, attributes);
const newCoordinator = federation.coordinators[alias];

View File

@ -210,7 +210,7 @@ export class Coordinator {
generateAllMakerAvatars = async (data: [PublicOrder]): Promise<void> => {
for (const order of data) {
roboidentitiesClient.generateRobohash(order.maker_hash_id, 'small');
void roboidentitiesClient.generateRobohash(order.maker_hash_id, 'small');
}
};

View File

@ -55,7 +55,7 @@ export class Federation {
settings: Settings,
hostUrl: string,
attributes: Record<any, any>,
) => {
): void => {
const value = {
...coordinatorDefaultValues,
...attributes,
@ -111,7 +111,7 @@ export class Federation {
this.exchange.loadingCoordinators = Object.keys(this.coordinators).length;
this.updateEnabledCoordinators();
for (const coor of Object.values(this.coordinators)) {
coor.update(() => {
void coor.update(() => {
this.exchange.onlineCoordinators = this.exchange.onlineCoordinators + 1;
this.onCoordinatorSaved();
});
@ -124,7 +124,7 @@ export class Federation {
this.triggerHook('onCoordinatorUpdate');
this.exchange.loadingCoordinators = Object.keys(this.coordinators).length;
for (const coor of Object.values(this.coordinators)) {
coor.updateBook(() => {
void coor.updateBook(() => {
this.onCoordinatorSaved();
});
}

View File

@ -1,4 +1,4 @@
import { Coordinator, type Order } from '.';
import { type Coordinator, type Order } from '.';
import { systemClient } from '../services/System';
import { saveAsJson } from '../utils';
import Slot from './Slot.model';
@ -59,8 +59,13 @@ class Garage {
const rawSlots = JSON.parse(slotsDump);
Object.values(rawSlots).forEach((rawSlot: Record<any, any>) => {
if (rawSlot?.token) {
this.slots[rawSlot.token] = new Slot(rawSlot.token, Object.keys(rawSlot.robots), {}, () =>
this.triggerHook('onRobotUpdate'),
this.slots[rawSlot.token] = new Slot(
rawSlot.token,
Object.keys(rawSlot.robots),
{},
() => {
this.triggerHook('onRobotUpdate');
},
);
Object.keys(rawSlot.robots).forEach((shortAlias) => {
const rawRobot = rawSlot.robots[shortAlias];
@ -78,7 +83,7 @@ class Garage {
// Slots
getSlot: (token?: string) => Slot | null = (token) => {
const currentToken = token ?? this.currentSlot;
return currentToken ? this.slots[currentToken] ?? null : null;
return currentToken ? (this.slots[currentToken] ?? null) : null;
};
deleteSlot: (token?: string) => void = (token) => {
@ -118,9 +123,9 @@ class Garage {
if (!token || !shortAliases) return;
if (this.getSlot(token) === null) {
this.slots[token] = new Slot(token, shortAliases, attributes, () =>
this.triggerHook('onRobotUpdate'),
);
this.slots[token] = new Slot(token, shortAliases, attributes, () => {
this.triggerHook('onRobotUpdate');
});
this.save();
}
};

View File

@ -1,5 +1,5 @@
import { sha256 } from 'js-sha256';
import { Coordinator, Garage, Robot, type Order } from '.';
import { type Coordinator, type Garage, Robot, type Order } from '.';
import { roboidentitiesClient } from '../services/Roboidentities/Web';
class Slot {
@ -13,12 +13,12 @@ class Slot {
this.hashId = sha256(sha256(this.token));
this.nickname = null;
roboidentitiesClient.generateRoboname(this.hashId).then((nickname) => {
void roboidentitiesClient.generateRoboname(this.hashId).then((nickname) => {
this.nickname = nickname;
onRobotUpdate();
});
roboidentitiesClient.generateRobohash(this.hashId, 'small');
roboidentitiesClient.generateRobohash(this.hashId, 'large');
void roboidentitiesClient.generateRobohash(this.hashId, 'small');
void roboidentitiesClient.generateRobohash(this.hashId, 'large');
this.robots = shortAliases.reduce((acc: Record<string, Robot>, shortAlias: string) => {
acc[shortAlias] = new Robot(robotAttributes);
@ -82,7 +82,7 @@ class Slot {
pubKey: defaultRobot.pubKey,
encPrivKey: defaultRobot.encPrivKey,
});
coordinator.fetchRobot(garage, defaultRobot.token);
void coordinator.fetchRobot(garage, defaultRobot.token);
}
};
}

View File

@ -1,4 +1,4 @@
import RoboidentitiesClientNativeClient from './RoboidentitiesNativeClient';
import { RoboidentitiesClient } from './type';
import { type RoboidentitiesClient } from './type';
export const roboidentitiesClient: RoboidentitiesClient = new RoboidentitiesClientNativeClient();

View File

@ -31,8 +31,8 @@ class RoboidentitiesNativeClient implements RoboidentitiesClient {
type: 'robohash',
detail: key,
});
const result = response ? Object.values(response)[0] : '';
const image = `data:image/png;base64,${result}`;
const result: string = response ? Object.values(response)[0] : '';
const image: string = `data:image/png;base64,${result}`;
this.robohashes[key] = image;
return image;
}

View File

@ -4,14 +4,14 @@ import { robohash } from './RobohashGenerator';
class RoboidentitiesClientWebClient implements RoboidentitiesClient {
public generateRoboname: (initialString: string) => Promise<string> = async (initialString) => {
return new Promise<string>(async (resolve, _reject) => {
return await new Promise<string>((resolve, _reject) => {
resolve(generate_roboname(initialString));
});
};
public generateRobohash: (initialString: string, size: 'small' | 'large') => Promise<string> =
async (initialString, size) => {
return robohash.generate(initialString, size);
return await robohash.generate(initialString, size);
};
}

View File

@ -1,4 +1,4 @@
import RoboidentitiesClientWebClient from './RoboidentitiesWebClient';
import { RoboidentitiesClient } from './type';
import { type RoboidentitiesClient } from './type';
export const roboidentitiesClient: RoboidentitiesClient = new RoboidentitiesClientWebClient();

View File

@ -5,7 +5,7 @@ import ApiWebClient from '../ApiWebClient';
class ApiNativeClient implements ApiClient {
public useProxy = true;
private webClient: ApiClient = new ApiWebClient();
private readonly webClient: ApiClient = new ApiWebClient();
private readonly assetsPromises = new Map<string, Promise<string | undefined>>();
@ -55,7 +55,7 @@ class ApiNativeClient implements ApiClient {
public delete: (baseUrl: string, path: string, auth?: Auth) => Promise<object | undefined> =
async (baseUrl, path, auth) => {
if (!this.useProxy) return this.webClient.delete(baseUrl, path, auth);
if (!this.useProxy) return await this.webClient.delete(baseUrl, path, auth);
return await window.NativeRobosats?.postMessage({
category: 'http',
type: 'delete',
@ -71,7 +71,7 @@ class ApiNativeClient implements ApiClient {
body: object,
auth?: Auth,
) => Promise<object | undefined> = async (baseUrl, path, body, auth) => {
if (!this.useProxy) return this.webClient.post(baseUrl, path, body, auth);
if (!this.useProxy) return await this.webClient.post(baseUrl, path, body, auth);
return await window.NativeRobosats?.postMessage({
category: 'http',
type: 'post',
@ -87,7 +87,7 @@ class ApiNativeClient implements ApiClient {
path,
auth,
) => {
if (!this.useProxy) return this.webClient.get(baseUrl, path, auth);
if (!this.useProxy) return await this.webClient.get(baseUrl, path, auth);
return await window.NativeRobosats?.postMessage({
category: 'http',
type: 'get',

View File

@ -16,14 +16,6 @@ export interface AggregatedInfo {
version: Version;
}
type toAdd =
| 'num_public_buy_orders'
| 'num_public_sell_orders'
| 'book_liquidity'
| 'active_robots_today'
| 'last_day_volume'
| 'lifetime_volume';
export const weightedMean = (arrValues: number[], arrWeights: number[]): number => {
if (arrValues.length === 0) {
return 0;

View File

@ -75,6 +75,7 @@
"74": "IRT",
"75": "BDT",
"76": "ALL",
"77": "DZD",
"300": "XAU",
"1000": "BTC"
}

View File

@ -700,7 +700,7 @@
"In dispute": "En disputa",
"Collaboratively cancelled": "Cancel·lat col·laborativament",
"Sending satoshis to buyer": "Enviant satoshis al comprador",
"Sucessful trade": "Intercanvi exitós",
"Successful trade": "Intercanvi exitós",
"Failed lightning network routing": "L'enrrutament lightning network ha fallat",
"Wait for dispute resolution": "Esperant la resolució de la disputa",
"Maker lost dispute": "El creador ha perdut la disputa",

View File

@ -700,7 +700,7 @@
"In dispute": "In dispute",
"Collaboratively cancelled": "Collaboratively cancelled",
"Sending satoshis to buyer": "Sending satoshis to buyer",
"Sucessful trade": "Successful trade",
"Successful trade": "Successful trade",
"Failed lightning network routing": "Failed lightning network routing",
"Wait for dispute resolution": "Wait for dispute resolution",
"Maker lost dispute": "Maker lost dispute",

View File

@ -700,7 +700,7 @@
"In dispute": "In dispute",
"Collaboratively cancelled": "Collaboratively cancelled",
"Sending satoshis to buyer": "Sending satoshis to buyer",
"Sucessful trade": "Successful trade",
"Successful trade": "Successful trade",
"Failed lightning network routing": "Failed lightning network routing",
"Wait for dispute resolution": "Wait for dispute resolution",
"Maker lost dispute": "Maker lost dispute",

View File

@ -700,7 +700,7 @@
"In dispute": "In dispute",
"Collaboratively cancelled": "Collaboratively cancelled",
"Sending satoshis to buyer": "Sending satoshis to buyer",
"Sucessful trade": "Successful trade",
"Successful trade": "Successful trade",
"Failed lightning network routing": "Failed lightning network routing",
"Wait for dispute resolution": "Wait for dispute resolution",
"Maker lost dispute": "Maker lost dispute",

View File

@ -700,7 +700,7 @@
"In dispute": "En disputa",
"Collaboratively cancelled": "Cancelada colaborativamente",
"Sending satoshis to buyer": "Enviando Sats al comprador",
"Sucessful trade": "Intercambio exitoso",
"Successful trade": "Intercambio exitoso",
"Failed lightning network routing": "Enrutamiento fallido en la red Lightning",
"Wait for dispute resolution": "Espera a la resolución de la disputa",
"Maker lost dispute": "El creador ha perdido la disputa",

View File

@ -700,7 +700,7 @@
"In dispute": "In dispute",
"Collaboratively cancelled": "Collaboratively cancelled",
"Sending satoshis to buyer": "Sending satoshis to buyer",
"Sucessful trade": "Successful trade",
"Successful trade": "Successful trade",
"Failed lightning network routing": "Failed lightning network routing",
"Wait for dispute resolution": "Wait for dispute resolution",
"Maker lost dispute": "Maker lost dispute",

View File

@ -700,7 +700,7 @@
"In dispute": "En litige",
"Collaboratively cancelled": "Annulation commune",
"Sending satoshis to buyer": "Envoi des satoshis à l'acheteur",
"Sucessful trade": "Transaction réussie",
"Successful trade": "Transaction réussie",
"Failed lightning network routing": "Échec du routage du réseau lightning",
"Wait for dispute resolution": "Attendre la résolution du litige",
"Maker lost dispute": "Litige perdu par l'auteur",

View File

@ -32,7 +32,7 @@ phrases = OrderedDict(
("In dispute", "In dispute"),
("Collaboratively cancelled", "Collaboratively cancelled"),
("Sending satoshis to buyer", "Sending satoshis to buyer"),
("Sucessful trade", "Successful trade"),
("Successful trade", "Successful trade"),
("Failed lightning network routing", "Failed lightning network routing"),
("Wait for dispute resolution", "Wait for dispute resolution"),
("Maker lost dispute", "Maker lost dispute"),

View File

@ -700,7 +700,7 @@
"In dispute": "In contestazione",
"Collaboratively cancelled": "Annullato collaborativamente",
"Sending satoshis to buyer": "Invio satoshi all'acquirente",
"Sucessful trade": "Scambio completato con successo",
"Successful trade": "Scambio completato con successo",
"Failed lightning network routing": "Routing Lightning Network fallito",
"Wait for dispute resolution": "In attesa della risoluzione della contestazione",
"Maker lost dispute": "Il maker ha perso la contestazione",

View File

@ -700,7 +700,7 @@
"In dispute": "紛争中",
"Collaboratively cancelled": "共同でキャンセルされました",
"Sending satoshis to buyer": "買い手にSatsを送信しています",
"Sucessful trade": "成功した取引",
"Successful trade": "成功した取引",
"Failed lightning network routing": "ライトニングネットワークのルーティングに失敗しました",
"Wait for dispute resolution": "紛争の解決を待ってください",
"Maker lost dispute": "オーダーメイカーが紛争に負けました",

View File

@ -700,7 +700,7 @@
"In dispute": "In dispute",
"Collaboratively cancelled": "Collaboratively cancelled",
"Sending satoshis to buyer": "Sending satoshis to buyer",
"Sucessful trade": "Successful trade",
"Successful trade": "Successful trade",
"Failed lightning network routing": "Failed lightning network routing",
"Wait for dispute resolution": "Wait for dispute resolution",
"Maker lost dispute": "Maker lost dispute",

View File

@ -700,7 +700,7 @@
"In dispute": "In dispute",
"Collaboratively cancelled": "Collaboratively cancelled",
"Sending satoshis to buyer": "Sending satoshis to buyer",
"Sucessful trade": "Successful trade",
"Successful trade": "Successful trade",
"Failed lightning network routing": "Failed lightning network routing",
"Wait for dispute resolution": "Wait for dispute resolution",
"Maker lost dispute": "Maker lost dispute",

View File

@ -700,7 +700,7 @@
"In dispute": "В диспуте",
"Collaboratively cancelled": "Совместно отменено",
"Sending satoshis to buyer": "Отправка Сатоши покупателю",
"Sucessful trade": "Успешная торговля",
"Successful trade": "Успешная торговля",
"Failed lightning network routing": "Неудачная маршрутизация сети Lightning",
"Wait for dispute resolution": "Дождитесь разрешения диспута",
"Maker lost dispute": "Мейкер проиграл диспут",

View File

@ -700,7 +700,7 @@
"In dispute": "In dispute",
"Collaboratively cancelled": "Collaboratively cancelled",
"Sending satoshis to buyer": "Sending satoshis to buyer",
"Sucessful trade": "Successful trade",
"Successful trade": "Successful trade",
"Failed lightning network routing": "Failed lightning network routing",
"Wait for dispute resolution": "Wait for dispute resolution",
"Maker lost dispute": "Maker lost dispute",

View File

@ -700,7 +700,7 @@
"In dispute": "Katika mzozo",
"Collaboratively cancelled": "Kufutwa kwa ushirikiano",
"Sending satoshis to buyer": "Inatumwa satoshis kwa mnunuzi",
"Sucessful trade": "Biashara imefanikiwa",
"Successful trade": "Biashara imefanikiwa",
"Failed lightning network routing": "Utaratibu wa mtandao wa umeme umeshindwa",
"Wait for dispute resolution": "Kusubiri suluhisho la mzozo",
"Maker lost dispute": "Mtengenezaji amepoteza mzozo",

View File

@ -700,7 +700,7 @@
"In dispute": "In dispute",
"Collaboratively cancelled": "Collaboratively cancelled",
"Sending satoshis to buyer": "Sending satoshis to buyer",
"Sucessful trade": "Successful trade",
"Successful trade": "Successful trade",
"Failed lightning network routing": "Failed lightning network routing",
"Wait for dispute resolution": "Wait for dispute resolution",
"Maker lost dispute": "Maker lost dispute",

View File

@ -700,7 +700,7 @@
"In dispute": "正在争议中",
"Collaboratively cancelled": "已合作取消",
"Sending satoshis to buyer": "正在向买方发送聪",
"Sucessful trade": "成功交易",
"Successful trade": "成功交易",
"Failed lightning network routing": "闪电路由失败",
"Wait for dispute resolution": "等待争议解决",
"Maker lost dispute": "挂单方失去了争议",

View File

@ -700,7 +700,7 @@
"In dispute": "正在爭議中",
"Collaboratively cancelled": "已合作取消",
"Sending satoshis to buyer": "正在向買方發送聰",
"Sucessful trade": "成功交易",
"Successful trade": "成功交易",
"Failed lightning network routing": "閃電路由失敗",
"Wait for dispute resolution": "等待爭議解決",
"Maker lost dispute": "掛單方失去了爭議",

2229
mobile/package-lock.json generated

File diff suppressed because it is too large Load Diff

View File

@ -26,7 +26,7 @@
"@types/jest": "^29.5.12",
"@types/react-native": "^0.71.3",
"@types/react-test-renderer": "^18.0.0",
"@typescript-eslint/eslint-plugin": "^5.59.2",
"@typescript-eslint/eslint-plugin": "^8.0.1",
"@typescript-eslint/parser": "^5.59.6",
"babel-jest": "^29.7.0",
"eslint": "^8.39.0",
@ -35,13 +35,13 @@
"eslint-import-resolver-typescript": "^3.6.0",
"eslint-plugin-import": "^2.27.5",
"eslint-plugin-n": "^15.7.0",
"eslint-plugin-prettier": "^5.1.3",
"eslint-plugin-promise": "^6.1.1",
"eslint-plugin-react": "^7.32.2",
"eslint-plugin-prettier": "^5.2.1",
"eslint-plugin-promise": "^6.6.0",
"eslint-plugin-react": "^7.35.0",
"eslint-plugin-react-hooks": "^4.6.2",
"jest": "^29.7.0",
"metro-react-native-babel-preset": "^0.75.1",
"prettier": "^3.3.2",
"prettier": "^3.3.3",
"react-test-renderer": "18.2.0",
"typescript": "^5.4.5"
},

View File

@ -1,4 +1,4 @@
django==5.0.6
django==5.0.8
django-admin-relation-links==0.2.5
django-celery-beat==2.6.0
django-celery-results==2.5.1
@ -19,9 +19,9 @@ ring==0.10.1
gunicorn==22.0.0
psycopg2==2.9.9
SQLAlchemy==2.0.16
django-import-export==4.1.0
django-import-export==4.1.1
requests[socks]
shapely==2.0.4
shapely==2.0.5
python-gnupg==0.5.2
daphne==4.1.2
drf-spectacular==0.27.2

View File

@ -1,4 +1,4 @@
coverage==7.5.0
ruff==0.5.1
coverage==7.6.0
ruff==0.5.7
git+https://github.com/Reckless-Satoshi/drf-openapi-tester.git@soften-django-requirements
pre-commit==3.7.0
pre-commit==3.8.0