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

View File

@ -50,16 +50,16 @@ class Command(BaseCommand):
parts = message.split(" ") parts = message.split(" ")
if len(parts) < 2: if len(parts) < 2:
self.notifications.send_telegram_message( self.notifications.send_telegram_message(
chat_id=result["message"]["from"]["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.', '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 continue
token = parts[-1] token = parts[-1]
robot = Robot.objects.filter(telegram_token=token).first() robot = Robot.objects.filter(telegram_token=token).first()
if not robot: if not robot:
self.notifications.send_telegram_message( self.notifications.send_telegram_message(
chat_id=result["message"]["from"]["id"], result["message"]["from"]["id"],
text=f'Wops, invalid token! There is no Robot with telegram chat token "{token}"', f'Wops, invalid token! There is no Robot with telegram chat token "{token}"',
) )
continue 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" DIS = 11, "In dispute"
CCA = 12, "Collaboratively cancelled" CCA = 12, "Collaboratively cancelled"
PAY = 13, "Sending satoshis to buyer" PAY = 13, "Sending satoshis to buyer"
SUC = 14, "Sucessful trade" SUC = 14, "Successful trade"
FAI = 15, "Failed lightning network routing" FAI = 15, "Failed lightning network routing"
WFR = 16, "Wait for dispute resolution" WFR = 16, "Wait for dispute resolution"
MLD = 17, "Maker lost dispute" MLD = 17, "Maker lost dispute"

View File

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

View File

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

View File

@ -141,7 +141,7 @@ def get_devfund_pubkey(network: str) -> str:
""" """
session = get_session() 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: try:
response = session.get(url) response = session.get(url)

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@ -1,22 +1,24 @@
import React, { useContext, useState } from 'react'; import React, { useContext, useState } from 'react';
import { Button, Grid, List, ListItem, Paper, TextField, Typography } from '@mui/material'; import { Button, Grid, Paper, TextField, Typography, Box } from '@mui/material';
import SettingsForm from '../../components/SettingsForm'; import SettingsForm from '../../components/SettingsForm';
import { AppContext, type UseAppStoreType } from '../../contexts/AppContext'; import { AppContext, type UseAppStoreType } from '../../contexts/AppContext';
import FederationTable from '../../components/FederationTable'; import FederationTable from '../../components/FederationTable';
import { t } from 'i18next'; import { t } from 'i18next';
import { FederationContext, UseFederationStoreType } from '../../contexts/FederationContext'; import { FederationContext, type UseFederationStoreType } from '../../contexts/FederationContext';
import { styled } from '@mui/system';
const SettingsPage = (): JSX.Element => { const SettingsPage = (): JSX.Element => {
const { windowSize, navbarHeight } = useContext<UseAppStoreType>(AppContext); const { windowSize, navbarHeight } = useContext<UseAppStoreType>(AppContext);
const { federation, addNewCoordinator } = useContext<UseFederationStoreType>(FederationContext); const { federation, addNewCoordinator } = useContext<UseFederationStoreType>(FederationContext);
const maxHeight = (windowSize.height * 0.65)
const maxHeight = (windowSize.height * 0.65);
const [newAlias, setNewAlias] = useState<string>(''); const [newAlias, setNewAlias] = useState<string>('');
const [newUrl, setNewUrl] = useState<string>(''); const [newUrl, setNewUrl] = useState<string>('');
const [error, setError] = useState<string>(); const [error, setError] = useState<string>();
// Regular expression to match a valid .onion URL
const onionUrlPattern = /^((http|https):\/\/)?[a-zA-Z2-7]{16,56}\.onion$/; const onionUrlPattern = /^((http|https):\/\/)?[a-zA-Z2-7]{16,56}\.onion$/;
const addCoordinator = () => { const addCoordinator: () => void = () => {
if (federation.coordinators[newAlias]) { if (federation.coordinators[newAlias]) {
setError(t('Alias already exists')); setError(t('Alias already exists'));
} else { } else {
@ -35,48 +37,42 @@ const SettingsPage = (): JSX.Element => {
}; };
return ( return (
<Paper <SettingsContainer elevation={12}>
elevation={12}
sx={{
padding: '0.6em',
width: '20.5em',
maxHeight: `${maxHeight}em`,
overflow: 'auto',
overflowX: 'clip',
}}
>
<Grid container> <Grid container>
<Grid item> <LeftGrid item xs={12} md={6}>
<SettingsForm /> <SettingsForm />
</Grid> </LeftGrid>
<Grid item> <RightGrid item xs={12} md={6}>
<FederationTable maxHeight={18} /> <FederationTableWrapper>
</Grid> <FederationTable maxHeight={18} />
<Grid item> </FederationTableWrapper>
<Typography align='center' component='h2' variant='subtitle2' color='secondary'> {error && (
{error} <ErrorTypography align='center' component='h2' variant='subtitle2' color='secondary'>
</Typography> {error}
</Grid> </ErrorTypography>
<List> )}
<ListItem> <InputContainer>
<TextField <StyledTextField
id='outlined-basic' id='outlined-basic'
label={t('Alias')} label={t('Alias')}
variant='outlined' variant='outlined'
size='small' size='small'
value={newAlias} value={newAlias}
onChange={(e) => setNewAlias(e.target.value)} onChange={(e) => {
setNewAlias(e.target.value);
}}
/> />
<TextField <StyledTextField
id='outlined-basic' id='outlined-basic'
label={t('URL')} label={t('URL')}
variant='outlined' variant='outlined'
size='small' size='small'
value={newUrl} value={newUrl}
onChange={(e) => setNewUrl(e.target.value)} onChange={(e) => {
setNewUrl(e.target.value);
}}
/> />
<Button <StyledButton
sx={{ maxHeight: 38 }}
disabled={false} disabled={false}
onClick={addCoordinator} onClick={addCoordinator}
variant='contained' variant='contained'
@ -85,12 +81,83 @@ const SettingsPage = (): JSX.Element => {
type='submit' type='submit'
> >
{t('Add')} {t('Add')}
</Button> </StyledButton>
</ListItem> </InputContainer>
</List> </RightGrid>
</Grid> </Grid>
</Paper> </SettingsContainer>
); );
}; };
// 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; export default SettingsPage;

View File

@ -14,7 +14,6 @@ import {
LinearProgress, LinearProgress,
IconButton, IconButton,
Tooltip, Tooltip,
type LinearProgressProps,
styled, styled,
} from '@mui/material'; } from '@mui/material';
import { import {
@ -24,7 +23,7 @@ import {
type GridPaginationModel, type GridPaginationModel,
type GridColDef, type GridColDef,
type GridValidRowModel, type GridValidRowModel,
GridSlotsComponent, type GridSlotsComponent,
} from '@mui/x-data-grid'; } from '@mui/x-data-grid';
import currencyDict from '../../../static/assets/currencies.json'; import currencyDict from '../../../static/assets/currencies.json';
import { type PublicOrder } from '../../models'; 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 { const NoResultsOverlay = function (): JSX.Element {
return ( return (
<Grid <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 { useTranslation } from 'react-i18next';
import { 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 { useTranslation } from 'react-i18next';
import { Box, useTheme, Checkbox, CircularProgress, Typography, Grid } from '@mui/material'; import { Box, Checkbox, CircularProgress, Grid, Typography } from '@mui/material';
import { DataGrid, type GridColDef, type GridValidRowModel } from '@mui/x-data-grid'; import { DataGrid, type GridColDef, type GridValidRowModel } from '@mui/x-data-grid';
import { type Coordinator } from '../../models'; import { type Coordinator } from '../../models';
import RobotAvatar from '../RobotAvatar'; import RobotAvatar from '../RobotAvatar';
import { Link, LinkOff } from '@mui/icons-material'; import { Link, LinkOff } from '@mui/icons-material';
import { AppContext, type UseAppStoreType } from '../../contexts/AppContext'; import { AppContext, type UseAppStoreType } from '../../contexts/AppContext';
import { type UseFederationStoreType, FederationContext } from '../../contexts/FederationContext'; import { type UseFederationStoreType, FederationContext } from '../../contexts/FederationContext';
import headerStyleFix from '../DataGrid/HeaderFix'; import { styled } from '@mui/system';
interface FederationTableProps { interface FederationTableProps {
maxWidth?: number; maxWidth?: number;
maxHeight?: number;
fillContainer?: boolean; fillContainer?: boolean;
} }
const FederationTable = ({ const FederationTable = ({ maxWidth = 90, fillContainer = false }: FederationTableProps): JSX.Element => {
maxWidth = 90,
maxHeight = 50,
fillContainer = false,
}: FederationTableProps): JSX.Element => {
const { t } = useTranslation(); const { t } = useTranslation();
const { federation, sortedCoordinators, coordinatorUpdatedAt, federationUpdatedAt } = const { federation, sortedCoordinators, coordinatorUpdatedAt } = useContext<UseFederationStoreType>(FederationContext);
useContext<UseFederationStoreType>(FederationContext);
const { setOpen, settings } = useContext<UseAppStoreType>(AppContext); const { setOpen, settings } = useContext<UseAppStoreType>(AppContext);
const theme = useTheme();
const [pageSize, setPageSize] = useState<number>(0);
// all sizes in 'em'
const fontSize = theme.typography.fontSize;
const verticalHeightFrame = 3.3;
const verticalHeightRow = 3.27;
const defaultPageSize = Math.max(
Math.floor((maxHeight - verticalHeightFrame) / verticalHeightRow),
1,
);
const height = defaultPageSize * verticalHeightRow + verticalHeightFrame;
const [useDefaultPageSize, setUseDefaultPageSize] = useState(true);
useEffect(() => {
if (useDefaultPageSize) {
setPageSize(defaultPageSize);
}
}, [coordinatorUpdatedAt, federationUpdatedAt]);
const localeText = {
MuiTablePagination: { labelRowsPerPage: t('Coordinators per page:') },
noResultsOverlayLabel: t('No coordinators found.'),
};
const onClickCoordinator = function (shortAlias: string): void {
setOpen((open) => {
return { ...open, coordinator: shortAlias };
});
};
const aliasObj = useCallback((width: number) => { const aliasObj = useCallback((width: number) => {
return { return {
field: 'longAlias', field: 'longAlias',
headerName: t('Coordinator'), headerName: t('Coordinator'),
width: width * fontSize, width: width,
renderCell: (params: any) => { renderCell: (params: any) => {
const coordinator = federation.coordinators[params.row.shortAlias]; const coordinator = federation.coordinators[params.row.shortAlias];
return ( return (
<Grid <CoordinatorGrid
container container
direction='row' direction="row"
sx={{ cursor: 'pointer', position: 'relative', left: '-0.3em', width: '50em' }} wrap="nowrap"
wrap='nowrap' onClick={() => onClickCoordinator(params.row.shortAlias)}
onClick={() => { alignItems="center"
onClickCoordinator(params.row.shortAlias);
}}
alignItems='center'
spacing={1} spacing={1}
> >
<Grid item> <Grid item>
@ -87,31 +47,29 @@ const FederationTable = ({
<Grid item> <Grid item>
<Typography>{params.row.longAlias}</Typography> <Typography>{params.row.longAlias}</Typography>
</Grid> </Grid>
</Grid> </CoordinatorGrid>
); );
}, },
}; };
}, []); }, [federation.coordinators]);
const enabledObj = useCallback( const enabledObj = useCallback(
(width: number) => { (width: number) => {
return { return {
field: 'enabled', field: 'enabled',
headerName: t('Enabled'), headerName: t('Enabled'),
width: width * fontSize, width: width,
renderCell: (params: any) => { renderCell: (params: any) => {
return ( return (
<Checkbox <Checkbox
checked={params.row.enabled} checked={params.row.enabled}
onClick={() => { onClick={() => onEnableChange(params.row.shortAlias)}
onEnableChange(params.row.shortAlias);
}}
/> />
); );
}, },
}; };
}, },
[coordinatorUpdatedAt], [coordinatorUpdatedAt]
); );
const upObj = useCallback( const upObj = useCallback(
@ -119,52 +77,41 @@ const FederationTable = ({
return { return {
field: 'up', field: 'up',
headerName: t('Up'), headerName: t('Up'),
width: width * fontSize, width: width,
renderCell: (params: any) => { renderCell: (params: any) => {
return ( return (
<div <UpStatusContainer onClick={() => onClickCoordinator(params.row.shortAlias)}>
style={{ cursor: 'pointer' }} {params.row.loadingInfo && params.row.enabled ? (
onClick={() => { <CircularProgress thickness={2} size={24} />
onClickCoordinator(params.row.shortAlias);
}}
>
{Boolean(params.row.loadingInfo) && Boolean(params.row.enabled) ? (
<CircularProgress thickness={0.35 * fontSize} size={1.5 * fontSize} />
) : params.row.info !== undefined ? ( ) : params.row.info !== undefined ? (
<Link color='success' /> <Link color="success" />
) : ( ) : (
<LinkOff color='error' /> <LinkOff color="error" />
)} )}
</div> </UpStatusContainer>
); );
}, },
}; };
}, },
[coordinatorUpdatedAt], [coordinatorUpdatedAt]
); );
const columnSpecs = { const columnSpecs = {
alias: { alias: {
priority: 2,
order: 1,
normal: { normal: {
width: 12.1, width: 200,
object: aliasObj, object: aliasObj,
}, },
}, },
up: { up: {
priority: 3,
order: 2,
normal: { normal: {
width: 3.5, width: 70,
object: upObj, object: upObj,
}, },
}, },
enabled: { enabled: {
priority: 1,
order: 3,
normal: { normal: {
width: 5, width: 90,
object: enabledObj, object: enabledObj,
}, },
}, },
@ -174,29 +121,17 @@ const FederationTable = ({
columns: Array<GridColDef<GridValidRowModel>>; columns: Array<GridColDef<GridValidRowModel>>;
width: number; width: number;
} { } {
const useSmall = maxWidth < 30;
const selectedColumns: object[] = []; const selectedColumns: object[] = [];
let width: number = 0; let width: number = 0;
for (const value of Object.values(columnSpecs)) { for (const value of Object.values(columnSpecs)) {
const colWidth = Number( const colWidth = value.normal.width;
useSmall && Boolean(value.small) ? value.small.width : value.normal.width, const colObject = value.normal.object;
);
const colObject = useSmall && Boolean(value.small) ? value.small.object : value.normal.object;
if (width + colWidth < maxWidth || selectedColumns.length < 2) { width += colWidth;
width = width + colWidth; selectedColumns.push([colObject(colWidth), value.order]);
selectedColumns.push([colObject(colWidth, false), value.order]);
} else {
selectedColumns.push([colObject(colWidth, true), value.order]);
}
} }
// sort columns by column.order value
selectedColumns.sort(function (first, second) {
return first[1] - second[1];
});
const columns: Array<GridColDef<GridValidRowModel>> = selectedColumns.map(function (item) { const columns: Array<GridColDef<GridValidRowModel>> = selectedColumns.map(function (item) {
return item[0]; return item[0];
}); });
@ -204,7 +139,7 @@ const FederationTable = ({
return { columns, width: width * 0.9 }; return { columns, width: width * 0.9 };
}; };
const { columns, width } = filteredColumns(); const { columns } = filteredColumns();
const onEnableChange = function (shortAlias: string): void { const onEnableChange = function (shortAlias: string): void {
if (federation.getCoordinator(shortAlias).enabled === true) { if (federation.getCoordinator(shortAlias).enabled === true) {
@ -217,38 +152,65 @@ const FederationTable = ({
const reorderedCoordinators = useMemo(() => { const reorderedCoordinators = useMemo(() => {
return sortedCoordinators.reduce((coordinators, key) => { return sortedCoordinators.reduce((coordinators, key) => {
coordinators[key] = federation.coordinators[key]; coordinators[key] = federation.coordinators[key];
return coordinators; return coordinators;
}, {}); }, {});
}, [settings.network, federationUpdatedAt]); }, [settings.network, coordinatorUpdatedAt]);
const onClickCoordinator = (shortAlias: string): void => {
setOpen((open) => {
return { ...open, coordinator: shortAlias };
});
};
return ( return (
<Box <TableContainer fillContainer={fillContainer} maxWidth={maxWidth}>
sx={ <StyledDataGrid
fillContainer
? { width: '100%', height: '100%' }
: { width: `${width}em`, height: `${height}em`, overflow: 'auto' }
}
>
<DataGrid
sx={headerStyleFix}
localeText={localeText}
rowHeight={3.714 * theme.typography.fontSize}
headerHeight={3.25 * theme.typography.fontSize}
rows={Object.values(reorderedCoordinators)} rows={Object.values(reorderedCoordinators)}
getRowId={(params: Coordinator) => params.shortAlias} getRowId={(params: Coordinator) => params.shortAlias}
columns={columns} columns={columns}
checkboxSelection={false} checkboxSelection={false}
pageSize={pageSize} autoHeight
rowsPerPageOptions={width < 22 ? [] : [0, pageSize, defaultPageSize * 2, 50, 100]} hideFooter
onPageSizeChange={(newPageSize) => {
setPageSize(newPageSize);
setUseDefaultPageSize(false);
}}
hideFooter={true}
/> />
</Box> </TableContainer>
); );
}; };
// 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; 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 === 'IRT') flag = <Flags.IR {...defaultProps} />;
if (code === 'BDT') flag = <Flags.BD {...defaultProps} />; if (code === 'BDT') flag = <Flags.BD {...defaultProps} />;
if (code === 'ALL') flag = <Flags.AL {...defaultProps} />; if (code === 'ALL') flag = <Flags.AL {...defaultProps} />;
if (code === 'DZD') flag = <Flags.DZ {...defaultProps} />;
if (code === 'ANY') flag = <EarthIcon {...defaultProps} />; if (code === 'ANY') flag = <EarthIcon {...defaultProps} />;
if (code === 'XAU') flag = <GoldIcon {...defaultProps} />; if (code === 'XAU') flag = <GoldIcon {...defaultProps} />;
if (code === 'BTC') flag = <SwapCallsIcon color='primary' />; if (code === 'BTC') flag = <SwapCallsIcon color='primary' />;

View File

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

View File

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

View File

@ -248,7 +248,7 @@ const Notifications = ({
// 11: 'In dispute' // 11: 'In dispute'
// 12: 'Collaboratively cancelled' // 12: 'Collaboratively cancelled'
// 13: 'Sending satoshis to buyer' // 13: 'Sending satoshis to buyer'
// 14: 'Sucessful trade' // 14: 'Successful trade'
// 15: 'Failed lightning network routing' // 15: 'Failed lightning network routing'
// 16: 'Wait for dispute resolution' // 16: 'Wait for dispute resolution'
// 17: 'Maker lost dispute' // 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 isBuyer = (order.type === 0 && order.is_maker) || (order.type === 1 && !order.is_maker);
const tradeFee = order.is_maker const tradeFee = order.is_maker
? coordinator.info?.maker_fee ?? 0 ? (coordinator.info?.maker_fee ?? 0)
: coordinator.info?.taker_fee ?? 0; : (coordinator.info?.taker_fee ?? 0);
const defaultRoutingBudget = 0.001; const defaultRoutingBudget = 0.001;
const btc_now = order.satoshis_now / 100000000; const btc_now = order.satoshis_now / 100000000;
const rate = Number(order.max_amount ?? order.amount) / btc_now; 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 { useTranslation } from 'react-i18next';
import { useTheme } from '@mui/material/styles';
import { type UseAppStoreType, AppContext } from '../../contexts/AppContext'; import { type UseAppStoreType, AppContext } from '../../contexts/AppContext';
import { import {
Grid, Grid,
Paper,
Switch, Switch,
useTheme,
FormControlLabel, FormControlLabel,
List, List,
ListItem, ListItem,
ListItemIcon, ListItemIcon,
Slider,
Typography, Typography,
ToggleButtonGroup, ToggleButtonGroup,
ToggleButton, ToggleButton,
Box,
Slider,
} from '@mui/material'; } from '@mui/material';
import SelectLanguage from './SelectLanguage'; import SelectLanguage from './SelectLanguage';
import { import {
Translate, Translate,
Palette, Palette,
LightMode,
DarkMode,
SettingsOverscan, SettingsOverscan,
Link, Link,
AccountBalance, AccountBalance,
AttachMoney, AttachMoney,
QrCode, QrCode,
ControlPoint, DarkMode,
} from '@mui/icons-material'; } from '@mui/icons-material';
import { systemClient } from '../../services/System'; import { systemClient } from '../../services/System';
import { TorIcon } from '../Icons'; import { TorIcon } from '../Icons';
import SwapCalls from '@mui/icons-material/SwapCalls'; import SwapCalls from '@mui/icons-material/SwapCalls';
import { FederationContext, type UseFederationStoreType } from '../../contexts/FederationContext';
import { GarageContext, UseGarageStoreType } from '../../contexts/GarageContext';
import { apiClient } from '../../services/api'; import { apiClient } from '../../services/api';
import { styled } from '@mui/system';
interface SettingsFormProps { interface SettingsFormProps {
dense?: boolean; dense?: boolean;
@ -41,10 +38,9 @@ interface SettingsFormProps {
const SettingsForm = ({ dense = false }: SettingsFormProps): JSX.Element => { const SettingsForm = ({ dense = false }: SettingsFormProps): JSX.Element => {
const { fav, setFav, settings, setSettings } = useContext<UseAppStoreType>(AppContext); const { fav, setFav, settings, setSettings } = useContext<UseAppStoreType>(AppContext);
const { federation } = useContext<UseFederationStoreType>(FederationContext);
const { garage } = useContext<UseGarageStoreType>(GarageContext);
const theme = useTheme();
const { t } = useTranslation(); const { t } = useTranslation();
const theme = useTheme();
const fontSizes = [ const fontSizes = [
{ label: 'XS', value: { basic: 12, pro: 10 } }, { label: 'XS', value: { basic: 12, pro: 10 } },
{ label: 'S', value: { basic: 13, pro: 11 } }, { label: 'S', value: { basic: 13, pro: 11 } },
@ -53,115 +49,74 @@ const SettingsForm = ({ dense = false }: SettingsFormProps): JSX.Element => {
{ label: 'XL', value: { basic: 16, pro: 14 } }, { label: 'XL', value: { basic: 16, pro: 14 } },
]; ];
const handleToggleChange = (e, newValue) => {
if (newValue !== null) {
setFav({ ...fav, mode: newValue, currency: newValue === 'fiat' ? 0 : 1000 });
}
};
const handleNetworkChange = (e, newValue) => {
if (newValue !== null) {
setSettings({ ...settings, network: newValue });
systemClient.setItem('settings_network', newValue);
}
};
return ( return (
<Grid container spacing={1}> <Grid container spacing={2}>
<Grid item> <Grid item xs={12}>
<List dense={dense}> <List dense={dense}>
<ListItem> {/* Language Settings */}
<ListItemIcon> <StyledListItem>
<Translate /> <SettingHeader>
</ListItemIcon> <ListItemIcon>
<SelectLanguage <Translate />
</ListItemIcon>
<Typography variant="subtitle1">
{t('Language Settings')}
</Typography>
</SettingHeader>
<StyledSelectLanguage
language={settings.language} language={settings.language}
setLanguage={(language) => { setLanguage={(language) => {
setSettings({ ...settings, language }); setSettings({ ...settings, language });
systemClient.setItem('settings_language', language); systemClient.setItem('settings_language', language);
}} }}
/> />
</ListItem> </StyledListItem>
<ListItem> {/* Appearance Settings */}
<ListItemIcon> <StyledListItem>
<Palette /> <SettingHeader>
</ListItemIcon> <ListItemIcon>
<FormControlLabel <Palette />
labelPlacement='end' </ListItemIcon>
label={settings.mode === 'dark' ? t('Dark') : t('Light')} <Typography variant="subtitle1">
control={ {t('Appearance Settings')}
<Switch </Typography>
checked={settings.mode === 'dark'} </SettingHeader>
checkedIcon={ <AppearanceSettingsBox>
<Paper <FormControlLabel
elevation={3} labelPlacement="end"
sx={{ label={t('Dark Mode')}
width: '1.2em', control={
height: '1.2em', <Switch
borderRadius: '0.4em', checked={settings.mode === 'dark'}
backgroundColor: 'white', onChange={(e) => {
position: 'relative', const mode = e.target.checked ? 'dark' : 'light';
top: `${7 - 0.5 * theme.typography.fontSize}px`, setSettings({ ...settings, mode });
}} systemClient.setItem('settings_mode', mode);
> }}
<DarkMode sx={{ width: '0.8em', height: '0.8em', color: '#666' }} /> />
</Paper> }
} />
icon={ {settings.mode === 'dark' && (
<Paper <QRCodeSwitch
elevation={3} labelPlacement="end"
sx={{ label={t('QR Code Color')}
width: '1.2em',
height: '1.2em',
borderRadius: '0.4em',
backgroundColor: 'white',
padding: '0.07em',
position: 'relative',
top: `${7 - 0.5 * theme.typography.fontSize}px`,
}}
>
<LightMode sx={{ width: '0.67em', height: '0.67em', color: '#666' }} />
</Paper>
}
onChange={(e) => {
const mode = e.target.checked ? 'dark' : 'light';
setSettings({ ...settings, mode });
systemClient.setItem('settings_mode', mode);
}}
/>
}
/>
{settings.mode === 'dark' ? (
<>
<ListItemIcon>
<QrCode />
</ListItemIcon>
<FormControlLabel
sx={{ position: 'relative', right: '1.5em', width: '3em' }}
labelPlacement='end'
label={settings.lightQRs ? t('Light') : t('Dark')}
control={ control={
<Switch <Switch
checked={!settings.lightQRs} checked={!settings.lightQRs}
checkedIcon={
<Paper
elevation={3}
sx={{
width: '1.2em',
height: '1.2em',
borderRadius: '0.4em',
backgroundColor: 'white',
position: 'relative',
top: `${7 - 0.5 * theme.typography.fontSize}px`,
}}
>
<DarkMode sx={{ width: '0.8em', height: '0.8em', color: '#666' }} />
</Paper>
}
icon={
<Paper
elevation={3}
sx={{
width: '1.2em',
height: '1.2em',
borderRadius: '0.4em',
backgroundColor: 'white',
padding: '0.07em',
position: 'relative',
top: `${7 - 0.5 * theme.typography.fontSize}px`,
}}
>
<LightMode sx={{ width: '0.67em', height: '0.67em', color: '#666' }} />
</Paper>
}
onChange={(e) => { onChange={(e) => {
const lightQRs = !e.target.checked; const lightQRs = !e.target.checked;
setSettings({ ...settings, lightQRs }); setSettings({ ...settings, lightQRs });
@ -170,17 +125,21 @@ const SettingsForm = ({ dense = false }: SettingsFormProps): JSX.Element => {
/> />
} }
/> />
</> )}
) : ( </AppearanceSettingsBox>
<></> </StyledListItem>
)}
</ListItem>
<ListItem> {/* Font Size Settings */}
<ListItemIcon> <StyledListItem>
<SettingsOverscan /> <SettingHeader>
</ListItemIcon> <ListItemIcon>
<Slider <SettingsOverscan />
</ListItemIcon>
<Typography variant="subtitle1">
{t('Font Size')}
</Typography>
</SettingHeader>
<StyledSlider
value={settings.fontSize} value={settings.fontSize}
min={settings.frontend === 'basic' ? 12 : 10} min={settings.frontend === 'basic' ? 12 : 10}
max={settings.frontend === 'basic' ? 16 : 14} max={settings.frontend === 'basic' ? 16 : 14}
@ -190,80 +149,98 @@ const SettingsForm = ({ dense = false }: SettingsFormProps): JSX.Element => {
setSettings({ ...settings, fontSize }); setSettings({ ...settings, fontSize });
systemClient.setItem(`settings_fontsize_${settings.frontend}`, fontSize.toString()); systemClient.setItem(`settings_fontsize_${settings.frontend}`, fontSize.toString());
}} }}
valueLabelDisplay='off' valueLabelDisplay="off"
track={false}
marks={fontSizes.map(({ label, value }) => ({ marks={fontSizes.map(({ label, value }) => ({
label: <Typography variant='caption'>{t(label)}</Typography>, label: <Typography variant="caption">{t(label)}</Typography>,
value: settings.frontend === 'basic' ? value.basic : value.pro, value: settings.frontend === 'basic' ? value.basic : value.pro,
}))} }))}
track={false}
/> />
</ListItem> </StyledListItem>
<ListItem> {/* Currency Settings */}
<ListItemIcon> <StyledListItem>
<AccountBalance /> <SettingHeader>
</ListItemIcon> <ListItemIcon>
<ToggleButtonGroup <AccountBalance />
</ListItemIcon>
<Typography variant="subtitle1">
{t('Currency Settings')}
</Typography>
</SettingHeader>
<StyledToggleButtonGroup
exclusive={true} exclusive={true}
value={fav.mode} value={fav.mode}
onChange={(e, mode) => { onChange={handleToggleChange}
setFav({ ...fav, mode, currency: mode === 'fiat' ? 0 : 1000 }); fullWidth
}}
> >
<ToggleButton value='fiat' color='primary'> <StyledToggleButton value="fiat">
<AttachMoney /> <AttachMoney />
{t('Fiat')} {t('Fiat')}
</ToggleButton> </StyledToggleButton>
<ToggleButton value='swap' color='secondary'> <StyledToggleButton value="swap">
<SwapCalls /> <SwapCalls />
{t('Swaps')} {t('Swaps')}
</ToggleButton> </StyledToggleButton>
</ToggleButtonGroup> </StyledToggleButtonGroup>
</ListItem> </StyledListItem>
<ListItem> {/* Network Settings */}
<ListItemIcon> <StyledListItem>
<Link /> <SettingHeader>
</ListItemIcon> <ListItemIcon>
<ToggleButtonGroup <Link />
</ListItemIcon>
<Typography variant="subtitle1">
{t('Network Settings')}
</Typography>
</SettingHeader>
<StyledToggleButtonGroup
exclusive={true} exclusive={true}
value={settings.network} value={settings.network}
onChange={(e, network) => { onChange={handleNetworkChange}
setSettings({ ...settings, network }); fullWidth
systemClient.setItem('settings_network', network);
}}
> >
<ToggleButton value='mainnet' color='primary'> <StyledToggleButton value="mainnet">
{t('Mainnet')} {t('Mainnet')}
</ToggleButton> </StyledToggleButton>
<ToggleButton value='testnet' color='secondary'> <StyledToggleButton value="testnet">
{t('Testnet')} {t('Testnet')}
</ToggleButton> </StyledToggleButton>
</ToggleButtonGroup> </StyledToggleButtonGroup>
</ListItem> </StyledListItem>
{/* Proxy Settings */}
{window.NativeRobosats !== undefined && ( {window.NativeRobosats !== undefined && (
<ListItem> <StyledListItem>
<ListItemIcon> <SettingHeader>
<TorIcon /> <ListItemIcon>
</ListItemIcon> <TorIcon />
<ToggleButtonGroup </ListItemIcon>
<Typography variant="subtitle1">
{t('Proxy Settings')}
</Typography>
</SettingHeader>
<StyledToggleButtonGroup
exclusive={true} exclusive={true}
value={settings.useProxy} value={settings.useProxy}
onChange={(_e, useProxy) => { onChange={(_e, useProxy) => {
setSettings({ ...settings, useProxy }); if (useProxy !== null) {
systemClient.setItem('settings_use_proxy', String(useProxy)); setSettings({ ...settings, useProxy });
apiClient.useProxy = useProxy; systemClient.setItem('settings_use_proxy', String(useProxy));
apiClient.useProxy = useProxy;
}
}} }}
fullWidth
> >
<ToggleButton value={true} color='primary'> <StyledToggleButton value={true}>
{t('Build-in')} {t('Built-in')}
</ToggleButton> </StyledToggleButton>
<ToggleButton value={false} color='secondary'> <StyledToggleButton value={false}>
{t('Disabled')} {t('Disabled')}
</ToggleButton> </StyledToggleButton>
</ToggleButtonGroup> </StyledToggleButtonGroup>
</ListItem> </StyledListItem>
)} )}
</List> </List>
</Grid> </Grid>
@ -271,4 +248,79 @@ const SettingsForm = ({ dense = false }: SettingsFormProps): JSX.Element => {
); );
}; };
// 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; export default SettingsForm;

View File

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

View File

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

View File

@ -210,7 +210,7 @@ export class Coordinator {
generateAllMakerAvatars = async (data: [PublicOrder]): Promise<void> => { generateAllMakerAvatars = async (data: [PublicOrder]): Promise<void> => {
for (const order of data) { 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, settings: Settings,
hostUrl: string, hostUrl: string,
attributes: Record<any, any>, attributes: Record<any, any>,
) => { ): void => {
const value = { const value = {
...coordinatorDefaultValues, ...coordinatorDefaultValues,
...attributes, ...attributes,
@ -111,7 +111,7 @@ export class Federation {
this.exchange.loadingCoordinators = Object.keys(this.coordinators).length; this.exchange.loadingCoordinators = Object.keys(this.coordinators).length;
this.updateEnabledCoordinators(); this.updateEnabledCoordinators();
for (const coor of Object.values(this.coordinators)) { for (const coor of Object.values(this.coordinators)) {
coor.update(() => { void coor.update(() => {
this.exchange.onlineCoordinators = this.exchange.onlineCoordinators + 1; this.exchange.onlineCoordinators = this.exchange.onlineCoordinators + 1;
this.onCoordinatorSaved(); this.onCoordinatorSaved();
}); });
@ -124,7 +124,7 @@ export class Federation {
this.triggerHook('onCoordinatorUpdate'); this.triggerHook('onCoordinatorUpdate');
this.exchange.loadingCoordinators = Object.keys(this.coordinators).length; this.exchange.loadingCoordinators = Object.keys(this.coordinators).length;
for (const coor of Object.values(this.coordinators)) { for (const coor of Object.values(this.coordinators)) {
coor.updateBook(() => { void coor.updateBook(() => {
this.onCoordinatorSaved(); 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 { systemClient } from '../services/System';
import { saveAsJson } from '../utils'; import { saveAsJson } from '../utils';
import Slot from './Slot.model'; import Slot from './Slot.model';
@ -59,8 +59,13 @@ class Garage {
const rawSlots = JSON.parse(slotsDump); const rawSlots = JSON.parse(slotsDump);
Object.values(rawSlots).forEach((rawSlot: Record<any, any>) => { Object.values(rawSlots).forEach((rawSlot: Record<any, any>) => {
if (rawSlot?.token) { if (rawSlot?.token) {
this.slots[rawSlot.token] = new Slot(rawSlot.token, Object.keys(rawSlot.robots), {}, () => this.slots[rawSlot.token] = new Slot(
this.triggerHook('onRobotUpdate'), rawSlot.token,
Object.keys(rawSlot.robots),
{},
() => {
this.triggerHook('onRobotUpdate');
},
); );
Object.keys(rawSlot.robots).forEach((shortAlias) => { Object.keys(rawSlot.robots).forEach((shortAlias) => {
const rawRobot = rawSlot.robots[shortAlias]; const rawRobot = rawSlot.robots[shortAlias];
@ -78,7 +83,7 @@ class Garage {
// Slots // Slots
getSlot: (token?: string) => Slot | null = (token) => { getSlot: (token?: string) => Slot | null = (token) => {
const currentToken = token ?? this.currentSlot; const currentToken = token ?? this.currentSlot;
return currentToken ? this.slots[currentToken] ?? null : null; return currentToken ? (this.slots[currentToken] ?? null) : null;
}; };
deleteSlot: (token?: string) => void = (token) => { deleteSlot: (token?: string) => void = (token) => {
@ -118,9 +123,9 @@ class Garage {
if (!token || !shortAliases) return; if (!token || !shortAliases) return;
if (this.getSlot(token) === null) { if (this.getSlot(token) === null) {
this.slots[token] = new Slot(token, shortAliases, attributes, () => this.slots[token] = new Slot(token, shortAliases, attributes, () => {
this.triggerHook('onRobotUpdate'), this.triggerHook('onRobotUpdate');
); });
this.save(); this.save();
} }
}; };

View File

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

View File

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

View File

@ -4,14 +4,14 @@ import { robohash } from './RobohashGenerator';
class RoboidentitiesClientWebClient implements RoboidentitiesClient { class RoboidentitiesClientWebClient implements RoboidentitiesClient {
public generateRoboname: (initialString: string) => Promise<string> = async (initialString) => { 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)); resolve(generate_roboname(initialString));
}); });
}; };
public generateRobohash: (initialString: string, size: 'small' | 'large') => Promise<string> = public generateRobohash: (initialString: string, size: 'small' | 'large') => Promise<string> =
async (initialString, size) => { 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 RoboidentitiesClientWebClient from './RoboidentitiesWebClient';
import { RoboidentitiesClient } from './type'; import { type RoboidentitiesClient } from './type';
export const roboidentitiesClient: RoboidentitiesClient = new RoboidentitiesClientWebClient(); export const roboidentitiesClient: RoboidentitiesClient = new RoboidentitiesClientWebClient();

View File

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

View File

@ -16,14 +16,6 @@ export interface AggregatedInfo {
version: Version; 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 => { export const weightedMean = (arrValues: number[], arrWeights: number[]): number => {
if (arrValues.length === 0) { if (arrValues.length === 0) {
return 0; return 0;

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@ -700,7 +700,7 @@
"In dispute": "In contestazione", "In dispute": "In contestazione",
"Collaboratively cancelled": "Annullato collaborativamente", "Collaboratively cancelled": "Annullato collaborativamente",
"Sending satoshis to buyer": "Invio satoshi all'acquirente", "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", "Failed lightning network routing": "Routing Lightning Network fallito",
"Wait for dispute resolution": "In attesa della risoluzione della contestazione", "Wait for dispute resolution": "In attesa della risoluzione della contestazione",
"Maker lost dispute": "Il maker ha perso la contestazione", "Maker lost dispute": "Il maker ha perso la contestazione",

View File

@ -700,7 +700,7 @@
"In dispute": "紛争中", "In dispute": "紛争中",
"Collaboratively cancelled": "共同でキャンセルされました", "Collaboratively cancelled": "共同でキャンセルされました",
"Sending satoshis to buyer": "買い手にSatsを送信しています", "Sending satoshis to buyer": "買い手にSatsを送信しています",
"Sucessful 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", "In dispute": "In dispute",
"Collaboratively cancelled": "Collaboratively cancelled", "Collaboratively cancelled": "Collaboratively cancelled",
"Sending satoshis to buyer": "Sending satoshis to buyer", "Sending satoshis to buyer": "Sending satoshis to buyer",
"Sucessful trade": "Successful trade", "Successful trade": "Successful trade",
"Failed lightning network routing": "Failed lightning network routing", "Failed lightning network routing": "Failed lightning network routing",
"Wait for dispute resolution": "Wait for dispute resolution", "Wait for dispute resolution": "Wait for dispute resolution",
"Maker lost dispute": "Maker lost dispute", "Maker lost dispute": "Maker lost dispute",

View File

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

View File

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

View File

@ -700,7 +700,7 @@
"In dispute": "Katika mzozo", "In dispute": "Katika mzozo",
"Collaboratively cancelled": "Kufutwa kwa ushirikiano", "Collaboratively cancelled": "Kufutwa kwa ushirikiano",
"Sending satoshis to buyer": "Inatumwa satoshis kwa mnunuzi", "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", "Failed lightning network routing": "Utaratibu wa mtandao wa umeme umeshindwa",
"Wait for dispute resolution": "Kusubiri suluhisho la mzozo", "Wait for dispute resolution": "Kusubiri suluhisho la mzozo",
"Maker lost dispute": "Mtengenezaji amepoteza mzozo", "Maker lost dispute": "Mtengenezaji amepoteza mzozo",

View File

@ -700,7 +700,7 @@
"In dispute": "In dispute", "In dispute": "In dispute",
"Collaboratively cancelled": "Collaboratively cancelled", "Collaboratively cancelled": "Collaboratively cancelled",
"Sending satoshis to buyer": "Sending satoshis to buyer", "Sending satoshis to buyer": "Sending satoshis to buyer",
"Sucessful trade": "Successful trade", "Successful trade": "Successful trade",
"Failed lightning network routing": "Failed lightning network routing", "Failed lightning network routing": "Failed lightning network routing",
"Wait for dispute resolution": "Wait for dispute resolution", "Wait for dispute resolution": "Wait for dispute resolution",
"Maker lost dispute": "Maker lost dispute", "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": "成功交易",
"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": "成功交易",
"Failed lightning network routing": "閃電路由失敗", "Failed lightning network routing": "閃電路由失敗",
"Wait for dispute resolution": "等待爭議解決", "Wait for dispute resolution": "等待爭議解決",
"Maker lost dispute": "掛單方失去了爭議", "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/jest": "^29.5.12",
"@types/react-native": "^0.71.3", "@types/react-native": "^0.71.3",
"@types/react-test-renderer": "^18.0.0", "@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", "@typescript-eslint/parser": "^5.59.6",
"babel-jest": "^29.7.0", "babel-jest": "^29.7.0",
"eslint": "^8.39.0", "eslint": "^8.39.0",
@ -35,13 +35,13 @@
"eslint-import-resolver-typescript": "^3.6.0", "eslint-import-resolver-typescript": "^3.6.0",
"eslint-plugin-import": "^2.27.5", "eslint-plugin-import": "^2.27.5",
"eslint-plugin-n": "^15.7.0", "eslint-plugin-n": "^15.7.0",
"eslint-plugin-prettier": "^5.1.3", "eslint-plugin-prettier": "^5.2.1",
"eslint-plugin-promise": "^6.1.1", "eslint-plugin-promise": "^6.6.0",
"eslint-plugin-react": "^7.32.2", "eslint-plugin-react": "^7.35.0",
"eslint-plugin-react-hooks": "^4.6.2", "eslint-plugin-react-hooks": "^4.6.2",
"jest": "^29.7.0", "jest": "^29.7.0",
"metro-react-native-babel-preset": "^0.75.1", "metro-react-native-babel-preset": "^0.75.1",
"prettier": "^3.3.2", "prettier": "^3.3.3",
"react-test-renderer": "18.2.0", "react-test-renderer": "18.2.0",
"typescript": "^5.4.5" "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-admin-relation-links==0.2.5
django-celery-beat==2.6.0 django-celery-beat==2.6.0
django-celery-results==2.5.1 django-celery-results==2.5.1
@ -19,9 +19,9 @@ ring==0.10.1
gunicorn==22.0.0 gunicorn==22.0.0
psycopg2==2.9.9 psycopg2==2.9.9
SQLAlchemy==2.0.16 SQLAlchemy==2.0.16
django-import-export==4.1.0 django-import-export==4.1.1
requests[socks] requests[socks]
shapely==2.0.4 shapely==2.0.5
python-gnupg==0.5.2 python-gnupg==0.5.2
daphne==4.1.2 daphne==4.1.2
drf-spectacular==0.27.2 drf-spectacular==0.27.2

View File

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