mirror of
https://github.com/RoboSats/robosats.git
synced 2024-12-26 22:06:08 +03:00
Refactor contexts and models (#921)
* Add SVG icons for map pins * Add federation basis and new coordinator form (#793) * Add new coordinator entry issue form * Add Federation basis * Fix eslint errors from F2F and fix languages * Redo eslint @typescript-eslint/strict-boolean-expressions * Robot Page working * Contexts Working * Garage Working * CurrentOrder working * Federation model working --------- Co-authored-by: Reckless_Satoshi <reckless.satoshi@protonmail.com> Co-authored-by: Reckless_Satoshi <90936742+Reckless-Satoshi@users.noreply.github.com>
This commit is contained in:
parent
f6b0d34264
commit
293c0b604d
8
.github/workflows/js-linter.yml
vendored
8
.github/workflows/js-linter.yml
vendored
@ -8,7 +8,7 @@ on:
|
|||||||
- main
|
- main
|
||||||
paths:
|
paths:
|
||||||
- frontend
|
- frontend
|
||||||
pull_request:
|
pull_request_target:
|
||||||
branches:
|
branches:
|
||||||
- main
|
- main
|
||||||
paths:
|
paths:
|
||||||
@ -41,7 +41,5 @@ jobs:
|
|||||||
with:
|
with:
|
||||||
prettier: true
|
prettier: true
|
||||||
prettier_dir: frontend
|
prettier_dir: frontend
|
||||||
|
eslint: true
|
||||||
## Disabled due to error
|
eslint_dir: frontend
|
||||||
# eslint: true
|
|
||||||
# eslint_dir: frontend
|
|
@ -61,6 +61,7 @@ services:
|
|||||||
volumes:
|
volumes:
|
||||||
- ./nodeapp/:/usr/src/robosats/
|
- ./nodeapp/:/usr/src/robosats/
|
||||||
- ./nodeapp/nginx.conf:/etc/nginx/nginx.conf
|
- ./nodeapp/nginx.conf:/etc/nginx/nginx.conf
|
||||||
|
- ./nodeapp/coordinators/:/etc/nginx/conf.d/
|
||||||
- ./frontend/static:/usr/src/robosats/static
|
- ./frontend/static:/usr/src/robosats/static
|
||||||
|
|
||||||
clean-orders:
|
clean-orders:
|
||||||
|
@ -92,9 +92,15 @@ Each RoboSats coordinator has a profile in the RoboSats app. The profile contain
|
|||||||
- Large Limits - Can host orders with large limits.
|
- Large Limits - Can host orders with large limits.
|
||||||
- DevFund donator - Donates to the DevFund the default amount or more.
|
- DevFund donator - Donates to the DevFund the default amount or more.
|
||||||
|
|
||||||
|
<<<<<<< HEAD
|
||||||
Some of these badges can be objectively measured and awarded. Other badges rely on the subjectivity of the development team. These will be generously awarded and only taken away after a warning.
|
Some of these badges can be objectively measured and awarded. Other badges rely on the subjectivity of the development team. These will be generously awarded and only taken away after a warning.
|
||||||
|
|
||||||
We also envision more badges in the future, for example milestones by number of trades coordinated (200, 1K, 5K, 25K, 100K, etc).
|
We also envision more badges in the future, for example milestones by number of trades coordinated (200, 1K, 5K, 25K, 100K, etc).
|
||||||
|
=======
|
||||||
|
Some of these badges can be objectively measured and therefore awarded. Others badges rely on the subjectivity by the development team. These will be generously awarded and only taken away after a warning.
|
||||||
|
|
||||||
|
We also envision more badges in the future. For example milestones of number of trades coordinated (200, 1K, 5K, 25K, 100K, etc).
|
||||||
|
>>>>>>> 2254fa60 (Add federation basis and new coordinator form (#793))
|
||||||
|
|
||||||
## Timeline
|
## Timeline
|
||||||
|
|
||||||
|
@ -19,12 +19,21 @@
|
|||||||
"sourceType": "module",
|
"sourceType": "module",
|
||||||
"project": "./tsconfig.json"
|
"project": "./tsconfig.json"
|
||||||
},
|
},
|
||||||
|
"ignorePatterns": ["index.js", "**/PaymentMethods/Icons/code/code.js"],
|
||||||
"plugins": ["react", "react-hooks", "@typescript-eslint", "prettier"],
|
"plugins": ["react", "react-hooks", "@typescript-eslint", "prettier"],
|
||||||
"rules": {
|
"rules": {
|
||||||
"react-hooks/rules-of-hooks": "error",
|
"react-hooks/rules-of-hooks": "error",
|
||||||
"react-hooks/exhaustive-deps": "warn",
|
"react-hooks/exhaustive-deps": "off",
|
||||||
"react/prop-types": "off",
|
"react/prop-types": "off",
|
||||||
"react/react-in-jsx-scope": "off"
|
"react/react-in-jsx-scope": "off",
|
||||||
|
"@typescript-eslint/naming-convention": [
|
||||||
|
"error",
|
||||||
|
{
|
||||||
|
"selector": "variableLike",
|
||||||
|
"format": ["camelCase", "snake_case", "PascalCase", "UPPER_CASE"],
|
||||||
|
"leadingUnderscore": "allow"
|
||||||
|
}
|
||||||
|
]
|
||||||
},
|
},
|
||||||
"settings": {
|
"settings": {
|
||||||
"import/resolver": {
|
"import/resolver": {
|
||||||
|
@ -1,4 +1,4 @@
|
|||||||
import React, { StrictMode, Suspense } from 'react';
|
import React, { StrictMode, Suspense, useState } from 'react';
|
||||||
import ReactDOM from 'react-dom/client';
|
import ReactDOM from 'react-dom/client';
|
||||||
import Main from './basic/Main';
|
import Main from './basic/Main';
|
||||||
import { CssBaseline, ThemeProvider } from '@mui/material';
|
import { CssBaseline, ThemeProvider } from '@mui/material';
|
||||||
@ -11,20 +11,29 @@ import i18n from './i18n/Web';
|
|||||||
|
|
||||||
import { systemClient } from './services/System';
|
import { systemClient } from './services/System';
|
||||||
import ErrorBoundary from './components/ErrorBoundary';
|
import ErrorBoundary from './components/ErrorBoundary';
|
||||||
|
import { GarageContext, useGarageStore } from './contexts/GarageContext';
|
||||||
|
import { FederationContext, useFederationStore } from './contexts/FederationContext';
|
||||||
|
|
||||||
const App = (): JSX.Element => {
|
const App = (): JSX.Element => {
|
||||||
const store = useAppStore();
|
const appStore = useAppStore();
|
||||||
|
const garageStore = useGarageStore();
|
||||||
|
const federationStore = useFederationStore();
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<StrictMode>
|
<StrictMode>
|
||||||
<ErrorBoundary>
|
<ErrorBoundary>
|
||||||
<Suspense fallback='loading'>
|
<Suspense fallback='loading'>
|
||||||
<I18nextProvider i18n={i18n}>
|
<I18nextProvider i18n={i18n}>
|
||||||
<AppContext.Provider value={store}>
|
<AppContext.Provider value={appStore}>
|
||||||
<ThemeProvider theme={store.theme}>
|
<GarageContext.Provider value={garageStore}>
|
||||||
<CssBaseline />
|
<FederationContext.Provider value={federationStore}>
|
||||||
{window.NativeRobosats === undefined ? <HostAlert /> : <TorConnectionBadge />}
|
<ThemeProvider theme={appStore.theme}>
|
||||||
<Main />
|
<CssBaseline />
|
||||||
</ThemeProvider>
|
{window.NativeRobosats === undefined ? <HostAlert /> : <TorConnectionBadge />}
|
||||||
|
<Main />
|
||||||
|
</ThemeProvider>
|
||||||
|
</FederationContext.Provider>
|
||||||
|
</GarageContext.Provider>
|
||||||
</AppContext.Provider>
|
</AppContext.Provider>
|
||||||
</I18nextProvider>
|
</I18nextProvider>
|
||||||
</Suspense>
|
</Suspense>
|
||||||
@ -33,7 +42,7 @@ const App = (): JSX.Element => {
|
|||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|
||||||
const loadApp = () => {
|
const loadApp = (): void => {
|
||||||
// waits until the environment is ready for the Android WebView app
|
// waits until the environment is ready for the Android WebView app
|
||||||
if (systemClient.loading) {
|
if (systemClient.loading) {
|
||||||
setTimeout(loadApp, 200);
|
setTimeout(loadApp, 200);
|
||||||
|
@ -12,10 +12,13 @@ import BookTable from '../../components/BookTable';
|
|||||||
import { BarChart, FormatListBulleted, Map } from '@mui/icons-material';
|
import { BarChart, FormatListBulleted, Map } from '@mui/icons-material';
|
||||||
import { AppContext, type UseAppStoreType } from '../../contexts/AppContext';
|
import { AppContext, type UseAppStoreType } from '../../contexts/AppContext';
|
||||||
import MapChart from '../../components/Charts/MapChart';
|
import MapChart from '../../components/Charts/MapChart';
|
||||||
|
import { FederationContext, UseFederationStoreType } from '../../contexts/FederationContext';
|
||||||
|
import { GarageContext, UseGarageStoreType } from '../../contexts/GarageContext';
|
||||||
|
|
||||||
const BookPage = (): JSX.Element => {
|
const BookPage = (): JSX.Element => {
|
||||||
const { robot, fetchBook, windowSize, setDelay, setOrder } =
|
const { windowSize } = useContext<UseAppStoreType>(AppContext);
|
||||||
useContext<UseAppStoreType>(AppContext);
|
const { setDelay } = useContext<UseFederationStoreType>(FederationContext);
|
||||||
|
const { garage, clearOrder } = useContext<UseGarageStoreType>(GarageContext);
|
||||||
const { t } = useTranslation();
|
const { t } = useTranslation();
|
||||||
const navigate = useNavigate();
|
const navigate = useNavigate();
|
||||||
const [view, setView] = useState<'list' | 'depth' | 'map'>('list');
|
const [view, setView] = useState<'list' | 'depth' | 'map'>('list');
|
||||||
@ -27,25 +30,17 @@ const BookPage = (): JSX.Element => {
|
|||||||
const maxBookTableWidth = 85;
|
const maxBookTableWidth = 85;
|
||||||
const chartWidthEm = width - maxBookTableWidth;
|
const chartWidthEm = width - maxBookTableWidth;
|
||||||
|
|
||||||
useEffect(() => {
|
const onOrderClicked = function (id: number, shortAlias: string): void {
|
||||||
fetchBook();
|
if (garage.getRobot().avatarLoaded) {
|
||||||
}, []);
|
clearOrder();
|
||||||
|
setDelay(10000);
|
||||||
const onViewOrder = function () {
|
navigate(`/order/${shortAlias}/${id}`);
|
||||||
setOrder(undefined);
|
|
||||||
setDelay(10000);
|
|
||||||
};
|
|
||||||
|
|
||||||
const onOrderClicked = function (id: number) {
|
|
||||||
if (robot.avatarLoaded) {
|
|
||||||
navigate('/order/' + id);
|
|
||||||
onViewOrder();
|
|
||||||
} else {
|
} else {
|
||||||
setOpenNoRobot(true);
|
setOpenNoRobot(true);
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
const NavButtons = function () {
|
const NavButtons = function (): JSX.Element {
|
||||||
return (
|
return (
|
||||||
<ButtonGroup variant='contained' color='inherit'>
|
<ButtonGroup variant='contained' color='inherit'>
|
||||||
<Button
|
<Button
|
||||||
@ -60,13 +55,25 @@ const BookPage = (): JSX.Element => {
|
|||||||
<></>
|
<></>
|
||||||
) : (
|
) : (
|
||||||
<>
|
<>
|
||||||
<Button onClick={() => setView('list')}>
|
<Button
|
||||||
|
onClick={() => {
|
||||||
|
setView('list');
|
||||||
|
}}
|
||||||
|
>
|
||||||
<FormatListBulleted /> {t('List')}
|
<FormatListBulleted /> {t('List')}
|
||||||
</Button>
|
</Button>
|
||||||
<Button onClick={() => setView('depth')}>
|
<Button
|
||||||
|
onClick={() => {
|
||||||
|
setView('depth');
|
||||||
|
}}
|
||||||
|
>
|
||||||
<BarChart /> {t('Chart')}
|
<BarChart /> {t('Chart')}
|
||||||
</Button>
|
</Button>
|
||||||
<Button onClick={() => setView('map')}>
|
<Button
|
||||||
|
onClick={() => {
|
||||||
|
setView('map');
|
||||||
|
}}
|
||||||
|
>
|
||||||
<Map /> {t('Map')}
|
<Map /> {t('Map')}
|
||||||
</Button>
|
</Button>
|
||||||
</>
|
</>
|
||||||
@ -82,7 +89,9 @@ const BookPage = (): JSX.Element => {
|
|||||||
onClose={() => {
|
onClose={() => {
|
||||||
setOpenNoRobot(false);
|
setOpenNoRobot(false);
|
||||||
}}
|
}}
|
||||||
onClickGenerateRobot={() => navigate('/robot')}
|
onClickGenerateRobot={() => {
|
||||||
|
navigate('/robot');
|
||||||
|
}}
|
||||||
/>
|
/>
|
||||||
{openMaker ? (
|
{openMaker ? (
|
||||||
<Dialog
|
<Dialog
|
||||||
@ -94,9 +103,11 @@ const BookPage = (): JSX.Element => {
|
|||||||
<Box sx={{ maxWidth: '18em', padding: '0.5em' }}>
|
<Box sx={{ maxWidth: '18em', padding: '0.5em' }}>
|
||||||
<MakerForm
|
<MakerForm
|
||||||
onOrderCreated={(id) => {
|
onOrderCreated={(id) => {
|
||||||
navigate('/order/' + id);
|
navigate(`/order/${id}`);
|
||||||
|
}}
|
||||||
|
onClickGenerateRobot={() => {
|
||||||
|
navigate('/robot');
|
||||||
}}
|
}}
|
||||||
onClickGenerateRobot={() => navigate('/robot')}
|
|
||||||
/>
|
/>
|
||||||
</Box>
|
</Box>
|
||||||
</Dialog>
|
</Dialog>
|
||||||
|
@ -1,20 +1,15 @@
|
|||||||
import React, { useContext } from 'react';
|
import React, { useContext, useEffect, useState } from 'react';
|
||||||
import { MemoryRouter, BrowserRouter, Routes, Route } from 'react-router-dom';
|
import { MemoryRouter, BrowserRouter, Routes, Route } from 'react-router-dom';
|
||||||
import { Box, Slide, Typography, styled } from '@mui/material';
|
import { Box, Slide, Typography, styled } from '@mui/material';
|
||||||
|
import { type UseAppStoreType, AppContext, closeAll } from '../contexts/AppContext';
|
||||||
|
|
||||||
import RobotPage from './RobotPage';
|
import { RobotPage, MakerPage, BookPage, OrderPage, SettingsPage, NavBar, MainDialogs } from './';
|
||||||
import MakerPage from './MakerPage';
|
|
||||||
import BookPage from './BookPage';
|
|
||||||
import OrderPage from './OrderPage';
|
|
||||||
import SettingsPage from './SettingsPage';
|
|
||||||
import NavBar from './NavBar';
|
|
||||||
import MainDialogs from './MainDialogs';
|
|
||||||
|
|
||||||
import RobotAvatar from '../components/RobotAvatar';
|
import RobotAvatar from '../components/RobotAvatar';
|
||||||
|
import Notifications from '../components/Notifications';
|
||||||
|
|
||||||
import { useTranslation } from 'react-i18next';
|
import { useTranslation } from 'react-i18next';
|
||||||
import Notifications from '../components/Notifications';
|
import { FederationContext, UseFederationStoreType } from '../contexts/FederationContext';
|
||||||
import { type UseAppStoreType, AppContext, closeAll } from '../contexts/AppContext';
|
import { GarageContext, UseGarageStoreType } from '../contexts/GarageContext';
|
||||||
|
|
||||||
const Router = window.NativeRobosats === undefined ? BrowserRouter : MemoryRouter;
|
const Router = window.NativeRobosats === undefined ? BrowserRouter : MemoryRouter;
|
||||||
|
|
||||||
@ -35,39 +30,31 @@ const MainBox = styled(Box)<MainBoxProps>((props) => ({
|
|||||||
|
|
||||||
const Main: React.FC = () => {
|
const Main: React.FC = () => {
|
||||||
const { t } = useTranslation();
|
const { t } = useTranslation();
|
||||||
const {
|
const { settings, page, slideDirection, setOpen, windowSize, navbarHeight, hostUrl, origin } =
|
||||||
settings,
|
useContext<UseAppStoreType>(AppContext);
|
||||||
robot,
|
const { federation, sortedCoordinators } = useContext<UseFederationStoreType>(FederationContext);
|
||||||
setRobot,
|
const { garage } = useContext<UseGarageStoreType>(GarageContext);
|
||||||
baseUrl,
|
const [avatarBaseUrl, setAvatarBaseUrl] = useState<string>(hostUrl);
|
||||||
order,
|
|
||||||
page,
|
useEffect(() => {
|
||||||
slideDirection,
|
setAvatarBaseUrl(federation.getCoordinator(sortedCoordinators[0]).getBaseUrl());
|
||||||
setOpen,
|
}, [settings.network, settings.selfhostedClient, federation, sortedCoordinators]);
|
||||||
windowSize,
|
|
||||||
navbarHeight,
|
|
||||||
} = useContext<UseAppStoreType>(AppContext);
|
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Router>
|
<Router>
|
||||||
<RobotAvatar
|
<RobotAvatar
|
||||||
style={{ display: 'none' }}
|
style={{ display: 'none' }}
|
||||||
nickname={robot.nickname}
|
nickname={garage.getRobot().nickname}
|
||||||
baseUrl={baseUrl}
|
baseUrl={federation.getCoordinator(sortedCoordinators[0]).getBaseUrl()}
|
||||||
onLoad={() => {
|
onLoad={() => garage.updateRobot({ avatarLoaded: true })}
|
||||||
setRobot((robot) => {
|
|
||||||
return { ...robot, avatarLoaded: true };
|
|
||||||
});
|
|
||||||
}}
|
|
||||||
/>
|
/>
|
||||||
<Notifications
|
<Notifications
|
||||||
order={order}
|
|
||||||
page={page}
|
page={page}
|
||||||
openProfile={() => {
|
openProfile={() => {
|
||||||
setOpen({ ...closeAll, profile: true });
|
setOpen({ ...closeAll, profile: true });
|
||||||
}}
|
}}
|
||||||
rewards={robot.earnedRewards}
|
rewards={garage.getRobot().earnedRewards}
|
||||||
windowWidth={windowSize.width}
|
windowWidth={windowSize?.width}
|
||||||
/>
|
/>
|
||||||
{settings.network === 'testnet' ? (
|
{settings.network === 'testnet' ? (
|
||||||
<TestnetTypography color='secondary' align='center'>
|
<TestnetTypography color='secondary' align='center'>
|
||||||
@ -87,10 +74,10 @@ const Main: React.FC = () => {
|
|||||||
<Slide
|
<Slide
|
||||||
direction={page === 'robot' ? slideDirection.in : slideDirection.out}
|
direction={page === 'robot' ? slideDirection.in : slideDirection.out}
|
||||||
in={page === 'robot'}
|
in={page === 'robot'}
|
||||||
appear={slideDirection.in != undefined}
|
appear={slideDirection.in !== undefined}
|
||||||
>
|
>
|
||||||
<div>
|
<div>
|
||||||
<RobotPage />
|
<RobotPage avatarBaseUrl={avatarBaseUrl} />
|
||||||
</div>
|
</div>
|
||||||
</Slide>
|
</Slide>
|
||||||
}
|
}
|
||||||
@ -105,7 +92,7 @@ const Main: React.FC = () => {
|
|||||||
<Slide
|
<Slide
|
||||||
direction={page === 'offers' ? slideDirection.in : slideDirection.out}
|
direction={page === 'offers' ? slideDirection.in : slideDirection.out}
|
||||||
in={page === 'offers'}
|
in={page === 'offers'}
|
||||||
appear={slideDirection.in != undefined}
|
appear={slideDirection.in !== undefined}
|
||||||
>
|
>
|
||||||
<div>
|
<div>
|
||||||
<BookPage />
|
<BookPage />
|
||||||
@ -120,7 +107,7 @@ const Main: React.FC = () => {
|
|||||||
<Slide
|
<Slide
|
||||||
direction={page === 'create' ? slideDirection.in : slideDirection.out}
|
direction={page === 'create' ? slideDirection.in : slideDirection.out}
|
||||||
in={page === 'create'}
|
in={page === 'create'}
|
||||||
appear={slideDirection.in != undefined}
|
appear={slideDirection.in !== undefined}
|
||||||
>
|
>
|
||||||
<div>
|
<div>
|
||||||
<MakerPage />
|
<MakerPage />
|
||||||
@ -130,12 +117,12 @@ const Main: React.FC = () => {
|
|||||||
/>
|
/>
|
||||||
|
|
||||||
<Route
|
<Route
|
||||||
path='/order/:orderId'
|
path='/order/:shortAlias/:orderId'
|
||||||
element={
|
element={
|
||||||
<Slide
|
<Slide
|
||||||
direction={page === 'order' ? slideDirection.in : slideDirection.out}
|
direction={page === 'order' ? slideDirection.in : slideDirection.out}
|
||||||
in={page === 'order'}
|
in={page === 'order'}
|
||||||
appear={slideDirection.in != undefined}
|
appear={slideDirection.in !== undefined}
|
||||||
>
|
>
|
||||||
<div>
|
<div>
|
||||||
<OrderPage />
|
<OrderPage />
|
||||||
@ -150,7 +137,7 @@ const Main: React.FC = () => {
|
|||||||
<Slide
|
<Slide
|
||||||
direction={page === 'settings' ? slideDirection.in : slideDirection.out}
|
direction={page === 'settings' ? slideDirection.in : slideDirection.out}
|
||||||
in={page === 'settings'}
|
in={page === 'settings'}
|
||||||
appear={slideDirection.in != undefined}
|
appear={slideDirection.in !== undefined}
|
||||||
>
|
>
|
||||||
<div>
|
<div>
|
||||||
<SettingsPage />
|
<SettingsPage />
|
||||||
@ -160,7 +147,7 @@ const Main: React.FC = () => {
|
|||||||
/>
|
/>
|
||||||
</Routes>
|
</Routes>
|
||||||
</MainBox>
|
</MainBox>
|
||||||
<NavBar width={windowSize.width} height={navbarHeight} />
|
<NavBar />
|
||||||
<MainDialogs />
|
<MainDialogs />
|
||||||
</Router>
|
</Router>
|
||||||
);
|
);
|
||||||
|
@ -1,16 +1,17 @@
|
|||||||
import React, { useState, useContext, useEffect } from 'react';
|
import React, { useState, useContext, useEffect } from 'react';
|
||||||
import {
|
import {
|
||||||
CommunityDialog,
|
CommunityDialog,
|
||||||
CoordinatorSummaryDialog,
|
ExchangeDialog,
|
||||||
InfoDialog,
|
CoordinatorDialog,
|
||||||
|
AboutDialog,
|
||||||
LearnDialog,
|
LearnDialog,
|
||||||
ProfileDialog,
|
ProfileDialog,
|
||||||
StatsDialog,
|
ClientDialog,
|
||||||
UpdateClientDialog,
|
UpdateDialog,
|
||||||
NoticeDialog,
|
|
||||||
} from '../../components/Dialogs';
|
} from '../../components/Dialogs';
|
||||||
import { pn } from '../../utils';
|
import { pn } from '../../utils';
|
||||||
import { AppContext, type UseAppStoreType } from '../../contexts/AppContext';
|
import { AppContext, type UseAppStoreType, closeAll } from '../../contexts/AppContext';
|
||||||
|
import { FederationContext, UseFederationStoreType } from '../../contexts/FederationContext';
|
||||||
|
|
||||||
export interface OpenDialogs {
|
export interface OpenDialogs {
|
||||||
more: boolean;
|
more: boolean;
|
||||||
@ -18,100 +19,96 @@ export interface OpenDialogs {
|
|||||||
community: boolean;
|
community: boolean;
|
||||||
info: boolean;
|
info: boolean;
|
||||||
coordinator: boolean;
|
coordinator: boolean;
|
||||||
stats: boolean;
|
exchange: boolean;
|
||||||
|
client: boolean;
|
||||||
update: boolean;
|
update: boolean;
|
||||||
profile: boolean;
|
profile: boolean;
|
||||||
notice: boolean;
|
notice: boolean;
|
||||||
}
|
}
|
||||||
|
|
||||||
const MainDialogs = (): JSX.Element => {
|
const MainDialogs = (): JSX.Element => {
|
||||||
const { open, setOpen, info, limits, robot, setRobot, setCurrentOrder, baseUrl } =
|
const { open, setOpen, settings, clientVersion, hostUrl } =
|
||||||
useContext<UseAppStoreType>(AppContext);
|
useContext<UseAppStoreType>(AppContext);
|
||||||
|
const { federation, focusedCoordinator, coordinatorUpdatedAt } =
|
||||||
|
useContext<UseFederationStoreType>(FederationContext);
|
||||||
|
|
||||||
const [maxAmount, setMaxAmount] = useState<string>('...loading...');
|
const [maxAmount, setMaxAmount] = useState<string>('...loading...');
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
if (limits.list[1000]) {
|
if (focusedCoordinator) {
|
||||||
setMaxAmount(pn(limits.list[1000].max_amount * 100000000));
|
const limits = federation.getCoordinator(focusedCoordinator).limits;
|
||||||
|
if (limits[1000] !== undefined) {
|
||||||
|
setMaxAmount(pn(limits[1000].max_amount * 100000000));
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}, [limits.list]);
|
}, [coordinatorUpdatedAt]);
|
||||||
|
|
||||||
useEffect(() => {
|
|
||||||
if (info.openUpdateClient) {
|
|
||||||
setOpen((open) => {
|
|
||||||
return { ...open, update: true };
|
|
||||||
});
|
|
||||||
}
|
|
||||||
}, [info]);
|
|
||||||
|
|
||||||
useEffect(() => {
|
|
||||||
if (!info.loading && info.notice_severity !== 'none' && info.notice_message !== '') {
|
|
||||||
setOpen((open) => {
|
|
||||||
return { ...open, notice: true };
|
|
||||||
});
|
|
||||||
}
|
|
||||||
}, [info]);
|
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<>
|
<>
|
||||||
<UpdateClientDialog
|
<UpdateDialog
|
||||||
open={open.update}
|
coordinatorVersion={federation.exchange.info.version}
|
||||||
coordinatorVersion={info.coordinatorVersion}
|
clientVersion={clientVersion.semver}
|
||||||
clientVersion={info.clientVersion}
|
|
||||||
onClose={() => {
|
onClose={() => {
|
||||||
setOpen({ ...open, update: false });
|
setOpen((open) => {
|
||||||
|
return { ...open, update: false };
|
||||||
|
});
|
||||||
}}
|
}}
|
||||||
/>
|
/>
|
||||||
<NoticeDialog
|
<AboutDialog
|
||||||
open={open.notice}
|
|
||||||
severity={info.notice_severity}
|
|
||||||
message={info.notice_message}
|
|
||||||
onClose={() => {
|
|
||||||
setOpen({ ...open, notice: false });
|
|
||||||
}}
|
|
||||||
/>
|
|
||||||
<InfoDialog
|
|
||||||
open={open.info}
|
open={open.info}
|
||||||
maxAmount={maxAmount}
|
maxAmount={maxAmount}
|
||||||
onClose={() => {
|
onClose={() => {
|
||||||
setOpen({ ...open, info: false });
|
setOpen((open) => {
|
||||||
|
return { ...open, info: false };
|
||||||
|
});
|
||||||
}}
|
}}
|
||||||
/>
|
/>
|
||||||
<LearnDialog
|
<LearnDialog
|
||||||
open={open.learn}
|
open={open.learn}
|
||||||
onClose={() => {
|
onClose={() => {
|
||||||
setOpen({ ...open, learn: false });
|
setOpen((open) => {
|
||||||
|
return { ...open, learn: false };
|
||||||
|
});
|
||||||
}}
|
}}
|
||||||
/>
|
/>
|
||||||
<CommunityDialog
|
<CommunityDialog
|
||||||
open={open.community}
|
open={open.community}
|
||||||
onClose={() => {
|
onClose={() => {
|
||||||
setOpen({ ...open, community: false });
|
setOpen((open) => {
|
||||||
|
return { ...open, community: false };
|
||||||
|
});
|
||||||
}}
|
}}
|
||||||
/>
|
/>
|
||||||
<CoordinatorSummaryDialog
|
<ExchangeDialog
|
||||||
open={open.coordinator}
|
open={open.exchange}
|
||||||
onClose={() => {
|
onClose={() => {
|
||||||
setOpen({ ...open, coordinator: false });
|
setOpen((open) => {
|
||||||
|
return { ...open, exchange: false };
|
||||||
|
});
|
||||||
}}
|
}}
|
||||||
info={info}
|
|
||||||
/>
|
/>
|
||||||
<StatsDialog
|
<ClientDialog
|
||||||
open={open.stats}
|
open={open.client}
|
||||||
onClose={() => {
|
onClose={() => {
|
||||||
setOpen({ ...open, stats: false });
|
setOpen((open) => {
|
||||||
|
return { ...open, client: false };
|
||||||
|
});
|
||||||
}}
|
}}
|
||||||
info={info}
|
|
||||||
/>
|
/>
|
||||||
<ProfileDialog
|
<ProfileDialog
|
||||||
open={open.profile}
|
open={open.profile}
|
||||||
baseUrl={baseUrl}
|
baseUrl={hostUrl}
|
||||||
onClose={() => {
|
onClose={() => {
|
||||||
setOpen({ ...open, profile: false });
|
setOpen({ ...open, profile: false });
|
||||||
}}
|
}}
|
||||||
robot={robot}
|
/>
|
||||||
setRobot={setRobot}
|
<CoordinatorDialog
|
||||||
setCurrentOrder={setCurrentOrder}
|
open={open.coordinator}
|
||||||
|
network={settings.network}
|
||||||
|
onClose={() => {
|
||||||
|
setOpen(closeAll);
|
||||||
|
}}
|
||||||
|
coordinator={focusedCoordinator ? federation.getCoordinator(focusedCoordinator) : null}
|
||||||
/>
|
/>
|
||||||
</>
|
</>
|
||||||
);
|
);
|
||||||
|
@ -2,7 +2,6 @@ import React, { useContext, useMemo, useState } 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 { Grid, Paper, Collapse, Typography } from '@mui/material';
|
import { Grid, Paper, Collapse, Typography } from '@mui/material';
|
||||||
|
|
||||||
import { filterOrders } from '../../utils';
|
import { filterOrders } from '../../utils';
|
||||||
|
|
||||||
import MakerForm from '../../components/MakerForm';
|
import MakerForm from '../../components/MakerForm';
|
||||||
@ -10,10 +9,13 @@ import BookTable from '../../components/BookTable';
|
|||||||
|
|
||||||
import { AppContext, type UseAppStoreType } from '../../contexts/AppContext';
|
import { AppContext, type UseAppStoreType } from '../../contexts/AppContext';
|
||||||
import { NoRobotDialog } from '../../components/Dialogs';
|
import { NoRobotDialog } from '../../components/Dialogs';
|
||||||
|
import { FederationContext, UseFederationStoreType } from '../../contexts/FederationContext';
|
||||||
|
import { GarageContext, UseGarageStoreType } from '../../contexts/GarageContext';
|
||||||
|
|
||||||
const MakerPage = (): JSX.Element => {
|
const MakerPage = (): JSX.Element => {
|
||||||
const { robot, book, fav, maker, windowSize, navbarHeight, setOrder, setDelay } =
|
const { fav, windowSize, navbarHeight } = useContext<UseAppStoreType>(AppContext);
|
||||||
useContext<UseAppStoreType>(AppContext);
|
const { setDelay, federation } = useContext<UseFederationStoreType>(FederationContext);
|
||||||
|
const { garage, maker } = useContext<UseGarageStoreType>(GarageContext);
|
||||||
const { t } = useTranslation();
|
const { t } = useTranslation();
|
||||||
const navigate = useNavigate();
|
const navigate = useNavigate();
|
||||||
|
|
||||||
@ -23,7 +25,7 @@ const MakerPage = (): JSX.Element => {
|
|||||||
|
|
||||||
const matches = useMemo(() => {
|
const matches = useMemo(() => {
|
||||||
return filterOrders({
|
return filterOrders({
|
||||||
orders: book.orders,
|
orders: federation.book,
|
||||||
baseFilter: {
|
baseFilter: {
|
||||||
currency: fav.currency === 0 ? 1 : fav.currency,
|
currency: fav.currency === 0 ? 1 : fav.currency,
|
||||||
type: fav.type,
|
type: fav.type,
|
||||||
@ -39,7 +41,7 @@ const MakerPage = (): JSX.Element => {
|
|||||||
},
|
},
|
||||||
});
|
});
|
||||||
}, [
|
}, [
|
||||||
book.orders,
|
federation.book,
|
||||||
fav,
|
fav,
|
||||||
maker.premium,
|
maker.premium,
|
||||||
maker.amount,
|
maker.amount,
|
||||||
@ -48,14 +50,14 @@ const MakerPage = (): JSX.Element => {
|
|||||||
maker.paymentMethods,
|
maker.paymentMethods,
|
||||||
]);
|
]);
|
||||||
|
|
||||||
const onViewOrder = function () {
|
const onViewOrder = function (): void {
|
||||||
setOrder(undefined);
|
garage.updateOrder(null);
|
||||||
setDelay(10000);
|
setDelay(10000);
|
||||||
};
|
};
|
||||||
|
|
||||||
const onOrderClicked = function (id: number) {
|
const onOrderClicked = function (id: number): void {
|
||||||
if (robot.avatarLoaded) {
|
if (garage.getRobot().avatarLoaded) {
|
||||||
navigate('/order/' + id);
|
navigate(`/order/${id}`);
|
||||||
onViewOrder();
|
onViewOrder();
|
||||||
} else {
|
} else {
|
||||||
setOpenNoRobot(true);
|
setOpenNoRobot(true);
|
||||||
@ -69,7 +71,9 @@ const MakerPage = (): JSX.Element => {
|
|||||||
onClose={() => {
|
onClose={() => {
|
||||||
setOpenNoRobot(false);
|
setOpenNoRobot(false);
|
||||||
}}
|
}}
|
||||||
onClickGenerateRobot={() => navigate('/robot')}
|
onClickGenerateRobot={() => {
|
||||||
|
navigate('/robot');
|
||||||
|
}}
|
||||||
/>
|
/>
|
||||||
<Grid item>
|
<Grid item>
|
||||||
<Collapse in={matches.length > 0 && showMatches}>
|
<Collapse in={matches.length > 0 && showMatches}>
|
||||||
@ -103,8 +107,8 @@ const MakerPage = (): JSX.Element => {
|
|||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
<MakerForm
|
<MakerForm
|
||||||
onOrderCreated={(id) => {
|
onOrderCreated={(shortAlias, id) => {
|
||||||
navigate('/order/' + id);
|
navigate(`/order/${shortAlias}/${id}`);
|
||||||
}}
|
}}
|
||||||
disableRequest={matches.length > 0 && !showMatches}
|
disableRequest={matches.length > 0 && !showMatches}
|
||||||
collapseAll={showMatches}
|
collapseAll={showMatches}
|
||||||
@ -115,7 +119,9 @@ const MakerPage = (): JSX.Element => {
|
|||||||
setShowMatches(false);
|
setShowMatches(false);
|
||||||
}}
|
}}
|
||||||
submitButtonLabel={matches.length > 0 && !showMatches ? 'Submit' : 'Create order'}
|
submitButtonLabel={matches.length > 0 && !showMatches ? 'Submit' : 'Create order'}
|
||||||
onClickGenerateRobot={() => navigate('/robot')}
|
onClickGenerateRobot={() => {
|
||||||
|
navigate('/robot');
|
||||||
|
}}
|
||||||
/>
|
/>
|
||||||
</Paper>
|
</Paper>
|
||||||
</Grid>
|
</Grid>
|
||||||
|
@ -1,9 +1,8 @@
|
|||||||
import React from 'react';
|
import React, { useContext } from 'react';
|
||||||
import { useTranslation } from 'react-i18next';
|
import { useTranslation } from 'react-i18next';
|
||||||
import { useTheme, styled, Grid, IconButton } from '@mui/material';
|
import { useTheme, styled, Grid, IconButton } from '@mui/material';
|
||||||
import Tooltip, { type TooltipProps, tooltipClasses } from '@mui/material/Tooltip';
|
import Tooltip, { type TooltipProps, tooltipClasses } from '@mui/material/Tooltip';
|
||||||
import { closeAll } from '../../contexts/AppContext';
|
import { closeAll, type UseAppStoreType, AppContext } from '../../contexts/AppContext';
|
||||||
import { type OpenDialogs } from '../MainDialogs';
|
|
||||||
|
|
||||||
import { BubbleChart, Info, People, PriceChange, School } from '@mui/icons-material';
|
import { BubbleChart, Info, People, PriceChange, School } from '@mui/icons-material';
|
||||||
|
|
||||||
@ -20,13 +19,13 @@ const StyledTooltip = styled(({ className, ...props }: TooltipProps) => (
|
|||||||
}));
|
}));
|
||||||
|
|
||||||
interface MoreTooltipProps {
|
interface MoreTooltipProps {
|
||||||
open: OpenDialogs;
|
|
||||||
setOpen: (state: OpenDialogs) => void;
|
|
||||||
children: JSX.Element;
|
children: JSX.Element;
|
||||||
}
|
}
|
||||||
|
|
||||||
const MoreTooltip = ({ open, setOpen, children }: MoreTooltipProps): JSX.Element => {
|
const MoreTooltip = ({ children }: MoreTooltipProps): JSX.Element => {
|
||||||
const { t } = useTranslation();
|
const { t } = useTranslation();
|
||||||
|
const { open, setOpen } = useContext<UseAppStoreType>(AppContext);
|
||||||
|
|
||||||
const theme = useTheme();
|
const theme = useTheme();
|
||||||
return (
|
return (
|
||||||
<StyledTooltip
|
<StyledTooltip
|
||||||
@ -89,15 +88,13 @@ const MoreTooltip = ({ open, setOpen, children }: MoreTooltipProps): JSX.Element
|
|||||||
</Grid>
|
</Grid>
|
||||||
|
|
||||||
<Grid item sx={{ position: 'relative', right: '0.4em' }}>
|
<Grid item sx={{ position: 'relative', right: '0.4em' }}>
|
||||||
<Tooltip enterTouchDelay={250} placement='left' title={t('Coordinator summary')}>
|
<Tooltip enterTouchDelay={250} placement='left' title={t('Exchange summary')}>
|
||||||
<IconButton
|
<IconButton
|
||||||
sx={{
|
sx={{
|
||||||
color: open.coordinator
|
color: open.exchange ? theme.palette.primary.main : theme.palette.text.secondary,
|
||||||
? theme.palette.primary.main
|
|
||||||
: theme.palette.text.secondary,
|
|
||||||
}}
|
}}
|
||||||
onClick={() => {
|
onClick={() => {
|
||||||
setOpen({ ...closeAll, coordinator: !open.coordinator });
|
setOpen({ ...closeAll, exchange: !open.exchange });
|
||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
<PriceChange />
|
<PriceChange />
|
||||||
@ -106,13 +103,13 @@ const MoreTooltip = ({ open, setOpen, children }: MoreTooltipProps): JSX.Element
|
|||||||
</Grid>
|
</Grid>
|
||||||
|
|
||||||
<Grid item sx={{ position: 'relative', right: '0.4em' }}>
|
<Grid item sx={{ position: 'relative', right: '0.4em' }}>
|
||||||
<Tooltip enterTouchDelay={250} placement='left' title={t('Stats for nerds')}>
|
<Tooltip enterTouchDelay={250} placement='left' title={t('client for nerds')}>
|
||||||
<IconButton
|
<IconButton
|
||||||
sx={{
|
sx={{
|
||||||
color: open.stats ? theme.palette.primary.main : theme.palette.text.secondary,
|
color: open.client ? theme.palette.primary.main : theme.palette.text.secondary,
|
||||||
}}
|
}}
|
||||||
onClick={() => {
|
onClick={() => {
|
||||||
setOpen({ ...closeAll, stats: !open.stats });
|
setOpen({ ...closeAll, client: !open.client });
|
||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
<BubbleChart />
|
<BubbleChart />
|
||||||
|
@ -4,7 +4,7 @@ import { useLocation, useNavigate } from 'react-router-dom';
|
|||||||
import { Tabs, Tab, Paper, useTheme } from '@mui/material';
|
import { Tabs, Tab, Paper, useTheme } from '@mui/material';
|
||||||
import MoreTooltip from './MoreTooltip';
|
import MoreTooltip from './MoreTooltip';
|
||||||
|
|
||||||
import { type Page } from '.';
|
import { type Page, isPage } from '.';
|
||||||
|
|
||||||
import {
|
import {
|
||||||
SettingsApplications,
|
SettingsApplications,
|
||||||
@ -16,33 +16,37 @@ import {
|
|||||||
} from '@mui/icons-material';
|
} from '@mui/icons-material';
|
||||||
import RobotAvatar from '../../components/RobotAvatar';
|
import RobotAvatar from '../../components/RobotAvatar';
|
||||||
import { AppContext, type UseAppStoreType, closeAll } from '../../contexts/AppContext';
|
import { AppContext, type UseAppStoreType, closeAll } from '../../contexts/AppContext';
|
||||||
|
import { FederationContext, UseFederationStoreType } from '../../contexts/FederationContext';
|
||||||
|
import { GarageContext, UseGarageStoreType } from '../../contexts/GarageContext';
|
||||||
|
|
||||||
interface NavBarProps {
|
const NavBar = (): JSX.Element => {
|
||||||
width: number;
|
const theme = useTheme();
|
||||||
height: number;
|
const { t } = useTranslation();
|
||||||
}
|
|
||||||
|
|
||||||
const NavBar = ({ width, height }: NavBarProps): JSX.Element => {
|
|
||||||
const {
|
const {
|
||||||
robot,
|
|
||||||
page,
|
page,
|
||||||
settings,
|
|
||||||
setPage,
|
setPage,
|
||||||
|
settings,
|
||||||
setSlideDirection,
|
setSlideDirection,
|
||||||
open,
|
open,
|
||||||
setOpen,
|
setOpen,
|
||||||
currentOrder,
|
windowSize,
|
||||||
baseUrl,
|
navbarHeight,
|
||||||
|
hostUrl,
|
||||||
} = useContext<UseAppStoreType>(AppContext);
|
} = useContext<UseAppStoreType>(AppContext);
|
||||||
|
const { currentOrder } = useContext<UseFederationStoreType>(FederationContext);
|
||||||
|
const { garage } = useContext<UseGarageStoreType>(GarageContext);
|
||||||
|
|
||||||
const theme = useTheme();
|
|
||||||
const { t } = useTranslation();
|
|
||||||
const navigate = useNavigate();
|
const navigate = useNavigate();
|
||||||
const location = useLocation();
|
const location = useLocation();
|
||||||
const smallBar = width < 50;
|
const smallBar = windowSize?.width < 50;
|
||||||
|
const color = settings.network === 'mainnet' ? 'primary' : 'secondary';
|
||||||
|
|
||||||
const tabSx = smallBar
|
const tabSx = smallBar
|
||||||
? { position: 'relative', bottom: robot.avatarLoaded ? '0.9em' : '0.13em', minWidth: '1em' }
|
? {
|
||||||
|
position: 'relative',
|
||||||
|
bottom: garage.getRobot().avatarLoaded ? '0.9em' : '0.13em',
|
||||||
|
minWidth: '1em',
|
||||||
|
}
|
||||||
: { position: 'relative', bottom: '1em', minWidth: '2em' };
|
: { position: 'relative', bottom: '1em', minWidth: '2em' };
|
||||||
|
|
||||||
const pagesPosition = {
|
const pagesPosition = {
|
||||||
@ -55,17 +59,17 @@ const NavBar = ({ width, height }: NavBarProps): JSX.Element => {
|
|||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
// change tab (page) into the current route
|
// change tab (page) into the current route
|
||||||
const pathPage: Page = location.pathname.split('/')[1];
|
const pathPage: Page | string = location.pathname.split('/')[1];
|
||||||
if (pathPage == 'index.html') {
|
if (pathPage === 'index.html') {
|
||||||
navigate('/robot');
|
navigate('/robot');
|
||||||
setPage('robot');
|
setPage('robot');
|
||||||
}
|
}
|
||||||
if (pathPage) {
|
if (isPage(pathPage)) {
|
||||||
setPage(pathPage);
|
setPage(pathPage);
|
||||||
}
|
}
|
||||||
}, [location]);
|
}, [location, navigate, setPage]);
|
||||||
|
|
||||||
const handleSlideDirection = function (oldPage: Page, newPage: Page) {
|
const handleSlideDirection = function (oldPage: Page, newPage: Page): void {
|
||||||
const oldPos: number = pagesPosition[oldPage];
|
const oldPos: number = pagesPosition[oldPage];
|
||||||
const newPos: number = pagesPosition[newPage];
|
const newPos: number = pagesPosition[newPage];
|
||||||
setSlideDirection(
|
setSlideDirection(
|
||||||
@ -73,13 +77,14 @@ const NavBar = ({ width, height }: NavBarProps): JSX.Element => {
|
|||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|
||||||
const changePage = function (mouseEvent: any, newPage: Page) {
|
const changePage = function (mouseEvent: any, newPage: Page): void {
|
||||||
if (newPage === 'none') {
|
if (newPage === 'none') {
|
||||||
return null;
|
return;
|
||||||
} else {
|
} else {
|
||||||
handleSlideDirection(page, newPage);
|
handleSlideDirection(page, newPage);
|
||||||
setPage(newPage);
|
setPage(newPage);
|
||||||
const param = newPage === 'order' ? currentOrder ?? '' : '';
|
const param =
|
||||||
|
newPage === 'order' ? `${String(currentOrder.shortAlias)}/${String(currentOrder.id)}` : '';
|
||||||
setTimeout(() => {
|
setTimeout(() => {
|
||||||
navigate(`/${newPage}/${param}`);
|
navigate(`/${newPage}/${param}`);
|
||||||
}, theme.transitions.duration.leavingScreen * 3);
|
}, theme.transitions.duration.leavingScreen * 3);
|
||||||
@ -88,35 +93,41 @@ const NavBar = ({ width, height }: NavBarProps): JSX.Element => {
|
|||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
setOpen(closeAll);
|
setOpen(closeAll);
|
||||||
}, [page]);
|
}, [page, setOpen]);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Paper
|
<Paper
|
||||||
elevation={6}
|
elevation={6}
|
||||||
sx={{ height: `${height}em`, width: `100%`, position: 'fixed', bottom: 0, borderRadius: 0 }}
|
sx={{
|
||||||
|
height: `${navbarHeight}em`,
|
||||||
|
width: `100%`,
|
||||||
|
position: 'fixed',
|
||||||
|
bottom: 0,
|
||||||
|
borderRadius: 0,
|
||||||
|
}}
|
||||||
>
|
>
|
||||||
<Tabs
|
<Tabs
|
||||||
TabIndicatorProps={{ sx: { height: '0.3em', position: 'absolute', top: 0 } }}
|
TabIndicatorProps={{ sx: { height: '0.3em', position: 'absolute', top: 0 } }}
|
||||||
variant='fullWidth'
|
variant='fullWidth'
|
||||||
value={page}
|
value={page}
|
||||||
indicatorColor={settings.network === 'mainnet' ? 'primary' : 'secondary'}
|
indicatorColor={color}
|
||||||
textColor={settings.network === 'mainnet' ? 'primary' : 'secondary'}
|
textColor={color}
|
||||||
onChange={changePage}
|
onChange={changePage}
|
||||||
>
|
>
|
||||||
<Tab
|
<Tab
|
||||||
sx={{ ...tabSx, minWidth: '2.5em', width: '2.5em', maxWidth: '4em' }}
|
sx={{ ...tabSx, minWidth: '2.5em', width: '2.5em', maxWidth: '4em' }}
|
||||||
value='none'
|
value='none'
|
||||||
disabled={robot.nickname === null}
|
disabled={garage.getRobot().nickname === null}
|
||||||
onClick={() => {
|
onClick={() => {
|
||||||
setOpen({ ...closeAll, profile: !open.profile });
|
setOpen({ ...closeAll, profile: !open.profile });
|
||||||
}}
|
}}
|
||||||
icon={
|
icon={
|
||||||
robot.nickname && robot.avatarLoaded ? (
|
garage.getRobot().nickname && garage.getRobot().avatarLoaded ? (
|
||||||
<RobotAvatar
|
<RobotAvatar
|
||||||
style={{ width: '2.3em', height: '2.3em', position: 'relative', top: '0.2em' }}
|
style={{ width: '2.3em', height: '2.3em', position: 'relative', top: '0.2em' }}
|
||||||
avatarClass={theme.palette.mode === 'dark' ? 'navBarAvatarDark' : 'navBarAvatar'}
|
avatarClass={theme.palette.mode === 'dark' ? 'navBarAvatarDark' : 'navBarAvatar'}
|
||||||
nickname={robot.nickname}
|
nickname={garage.getRobot().nickname}
|
||||||
baseUrl={baseUrl}
|
baseUrl={hostUrl}
|
||||||
/>
|
/>
|
||||||
) : (
|
) : (
|
||||||
<></>
|
<></>
|
||||||
@ -150,7 +161,7 @@ const NavBar = ({ width, height }: NavBarProps): JSX.Element => {
|
|||||||
sx={tabSx}
|
sx={tabSx}
|
||||||
label={smallBar ? undefined : t('Order')}
|
label={smallBar ? undefined : t('Order')}
|
||||||
value='order'
|
value='order'
|
||||||
disabled={!robot.avatarLoaded || currentOrder == undefined}
|
disabled={!garage.getRobot().avatarLoaded || currentOrder.id == null}
|
||||||
icon={<Assignment />}
|
icon={<Assignment />}
|
||||||
iconPosition='start'
|
iconPosition='start'
|
||||||
/>
|
/>
|
||||||
@ -166,11 +177,13 @@ const NavBar = ({ width, height }: NavBarProps): JSX.Element => {
|
|||||||
sx={tabSx}
|
sx={tabSx}
|
||||||
label={smallBar ? undefined : t('More')}
|
label={smallBar ? undefined : t('More')}
|
||||||
value='none'
|
value='none'
|
||||||
onClick={(e) => {
|
onClick={() => {
|
||||||
open.more ? null : setOpen({ ...open, more: true });
|
setOpen((open) => {
|
||||||
|
return { ...open, more: !open.more };
|
||||||
|
});
|
||||||
}}
|
}}
|
||||||
icon={
|
icon={
|
||||||
<MoreTooltip open={open} setOpen={setOpen} closeAll={closeAll}>
|
<MoreTooltip>
|
||||||
<MoreHoriz />
|
<MoreHoriz />
|
||||||
</MoreTooltip>
|
</MoreTooltip>
|
||||||
}
|
}
|
||||||
|
@ -2,3 +2,7 @@ import NavBar from './NavBar';
|
|||||||
|
|
||||||
export type Page = 'robot' | 'order' | 'create' | 'offers' | 'settings' | 'none';
|
export type Page = 'robot' | 'order' | 'create' | 'offers' | 'settings' | 'none';
|
||||||
export default NavBar;
|
export default NavBar;
|
||||||
|
|
||||||
|
export function isPage(page: string): page is Page {
|
||||||
|
return ['robot', 'order', 'create', 'offers', 'settings', 'none'].includes(page);
|
||||||
|
}
|
||||||
|
@ -8,41 +8,54 @@ import OrderDetails from '../../components/OrderDetails';
|
|||||||
|
|
||||||
import { apiClient } from '../../services/api';
|
import { apiClient } from '../../services/api';
|
||||||
import { AppContext, type UseAppStoreType } from '../../contexts/AppContext';
|
import { AppContext, type UseAppStoreType } from '../../contexts/AppContext';
|
||||||
|
import { FederationContext, UseFederationStoreType } from '../../contexts/FederationContext';
|
||||||
|
import { GarageContext, UseGarageStoreType } from '../../contexts/GarageContext';
|
||||||
|
|
||||||
const OrderPage = (): JSX.Element => {
|
const OrderPage = (): JSX.Element => {
|
||||||
const {
|
const { windowSize, setOpen, settings, navbarHeight, hostUrl, origin } =
|
||||||
windowSize,
|
useContext<UseAppStoreType>(AppContext);
|
||||||
info,
|
const { setFocusedCoordinator, federation, currentOrder, setCurrentOrder, focusedCoordinator } =
|
||||||
order,
|
useContext<UseFederationStoreType>(FederationContext);
|
||||||
robot,
|
const { garage, badOrder, setBadOrder } = useContext<UseGarageStoreType>(GarageContext);
|
||||||
settings,
|
|
||||||
setOrder,
|
|
||||||
clearOrder,
|
|
||||||
currentOrder,
|
|
||||||
setCurrentOrder,
|
|
||||||
badOrder,
|
|
||||||
setBadOrder,
|
|
||||||
baseUrl,
|
|
||||||
navbarHeight,
|
|
||||||
} = useContext<UseAppStoreType>(AppContext);
|
|
||||||
const { t } = useTranslation();
|
const { t } = useTranslation();
|
||||||
const navigate = useNavigate();
|
const navigate = useNavigate();
|
||||||
const params = useParams();
|
const params = useParams();
|
||||||
|
|
||||||
const doublePageWidth: number = 50;
|
const doublePageWidth: number = 50;
|
||||||
const maxHeight: number = (windowSize.height - navbarHeight) * 0.85 - 3;
|
const maxHeight: number = (windowSize?.height - navbarHeight) * 0.85 - 3;
|
||||||
|
|
||||||
const [tab, setTab] = useState<'order' | 'contract'>('contract');
|
const [tab, setTab] = useState<'order' | 'contract'>('contract');
|
||||||
|
const [baseUrl, setBaseUrl] = useState<string>(hostUrl);
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
if (currentOrder != params.orderId) {
|
const newOrder = {
|
||||||
clearOrder();
|
shortAlias: params.shortAlias ?? '',
|
||||||
setCurrentOrder(Number(params.orderId));
|
id: Number(params.orderId) ?? null,
|
||||||
}
|
order: null,
|
||||||
}, [params.orderId]);
|
};
|
||||||
|
|
||||||
const renewOrder = function () {
|
const { url, basePath } = federation
|
||||||
if (order != undefined) {
|
.getCoordinator(newOrder.shortAlias)
|
||||||
|
.getEndpoint(settings.network, origin, settings.selfhostedClient, hostUrl);
|
||||||
|
setBaseUrl(`${url}${basePath}`);
|
||||||
|
|
||||||
|
if (currentOrder.id !== newOrder.id || currentOrder.shortAlias !== newOrder.shortAlias) {
|
||||||
|
setCurrentOrder(newOrder);
|
||||||
|
}
|
||||||
|
}, [params]);
|
||||||
|
|
||||||
|
const onClickCoordinator = function (): void {
|
||||||
|
if (currentOrder.shortAlias) {
|
||||||
|
setFocusedCoordinator(currentOrder.shortAlias);
|
||||||
|
}
|
||||||
|
setOpen((open) => {
|
||||||
|
return { ...open, coordinator: true };
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
|
const renewOrder = function (): void {
|
||||||
|
const order = currentOrder.order;
|
||||||
|
if (order !== null && focusedCoordinator) {
|
||||||
const body = {
|
const body = {
|
||||||
type: order.type,
|
type: order.type,
|
||||||
currency: order.currency,
|
currency: order.currency,
|
||||||
@ -60,32 +73,38 @@ const OrderPage = (): JSX.Element => {
|
|||||||
latitude: order.latitude,
|
latitude: order.latitude,
|
||||||
longitude: order.longitude,
|
longitude: order.longitude,
|
||||||
};
|
};
|
||||||
|
const { url } = federation
|
||||||
|
.getCoordinator(focusedCoordinator)
|
||||||
|
.getEndpoint(settings.network, origin, settings.selfhostedClient, hostUrl);
|
||||||
apiClient
|
apiClient
|
||||||
.post(baseUrl, '/api/make/', body, { tokenSHA256: robot.tokenSHA256 })
|
.post(url, '/api/make/', body, { tokenSHA256: garage.getRobot().tokenSHA256 })
|
||||||
.then((data: any) => {
|
.then((data: any) => {
|
||||||
if (data.bad_request) {
|
if (data.bad_request !== undefined) {
|
||||||
setBadOrder(data.bad_request);
|
setBadOrder(data.bad_request);
|
||||||
} else if (data.id) {
|
} else if (data.id !== undefined) {
|
||||||
navigate('/order/' + data.id);
|
navigate(`/order/${String(currentOrder.shortAlias)}/${String(data.id)}`);
|
||||||
}
|
}
|
||||||
|
})
|
||||||
|
.catch(() => {
|
||||||
|
setBadOrder('Request error');
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
const startAgain = () => {
|
const startAgain = (): void => {
|
||||||
navigate('/robot');
|
navigate('/robot');
|
||||||
};
|
};
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Box>
|
<Box>
|
||||||
{order == undefined && badOrder == undefined ? <CircularProgress /> : null}
|
{currentOrder.order === null && badOrder === undefined && <CircularProgress />}
|
||||||
{badOrder != undefined ? (
|
{badOrder !== undefined ? (
|
||||||
<Typography align='center' variant='subtitle2' color='secondary'>
|
<Typography align='center' variant='subtitle2' color='secondary'>
|
||||||
{t(badOrder)}
|
{t(badOrder)}
|
||||||
</Typography>
|
</Typography>
|
||||||
) : null}
|
) : null}
|
||||||
{order != undefined && badOrder == undefined ? (
|
{currentOrder.order !== null && badOrder === undefined ? (
|
||||||
order.is_participant ? (
|
currentOrder.order.is_participant ? (
|
||||||
windowSize.width > doublePageWidth ? (
|
windowSize.width > doublePageWidth ? (
|
||||||
// DOUBLE PAPER VIEW
|
// DOUBLE PAPER VIEW
|
||||||
<Grid
|
<Grid
|
||||||
@ -106,12 +125,12 @@ const OrderPage = (): JSX.Element => {
|
|||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
<OrderDetails
|
<OrderDetails
|
||||||
order={order}
|
coordinator={federation.getCoordinator(String(currentOrder.shortAlias))}
|
||||||
setOrder={setOrder}
|
onClickCoordinator={onClickCoordinator}
|
||||||
baseUrl={baseUrl}
|
baseUrl={baseUrl}
|
||||||
info={info}
|
onClickGenerateRobot={() => {
|
||||||
hasRobot={robot.avatarLoaded}
|
navigate('/robot');
|
||||||
onClickGenerateRobot={() => navigate('/robot')}
|
}}
|
||||||
/>
|
/>
|
||||||
</Paper>
|
</Paper>
|
||||||
</Grid>
|
</Grid>
|
||||||
@ -125,10 +144,8 @@ const OrderPage = (): JSX.Element => {
|
|||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
<TradeBox
|
<TradeBox
|
||||||
order={order}
|
robot={garage.getRobot()}
|
||||||
robot={robot}
|
|
||||||
settings={settings}
|
settings={settings}
|
||||||
setOrder={setOrder}
|
|
||||||
setBadOrder={setBadOrder}
|
setBadOrder={setBadOrder}
|
||||||
baseUrl={baseUrl}
|
baseUrl={baseUrl}
|
||||||
onRenewOrder={renewOrder}
|
onRenewOrder={renewOrder}
|
||||||
@ -160,22 +177,20 @@ const OrderPage = (): JSX.Element => {
|
|||||||
overflow: 'auto',
|
overflow: 'auto',
|
||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
<div style={{ display: tab == 'order' ? '' : 'none' }}>
|
<div style={{ display: tab === 'order' ? '' : 'none' }}>
|
||||||
<OrderDetails
|
<OrderDetails
|
||||||
order={order}
|
coordinator={federation.getCoordinator(String(currentOrder.shortAlias))}
|
||||||
setOrder={setOrder}
|
onClickCoordinator={onClickCoordinator}
|
||||||
baseUrl={baseUrl}
|
baseUrl={baseUrl}
|
||||||
info={info}
|
onClickGenerateRobot={() => {
|
||||||
hasRobot={robot.avatarLoaded}
|
navigate('/robot');
|
||||||
onClickGenerateRobot={() => navigate('/robot')}
|
}}
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
<div style={{ display: tab == 'contract' ? '' : 'none' }}>
|
<div style={{ display: tab === 'contract' ? '' : 'none' }}>
|
||||||
<TradeBox
|
<TradeBox
|
||||||
order={order}
|
robot={garage.getRobot()}
|
||||||
robot={robot}
|
|
||||||
settings={settings}
|
settings={settings}
|
||||||
setOrder={setOrder}
|
|
||||||
setBadOrder={setBadOrder}
|
setBadOrder={setBadOrder}
|
||||||
baseUrl={baseUrl}
|
baseUrl={baseUrl}
|
||||||
onRenewOrder={renewOrder}
|
onRenewOrder={renewOrder}
|
||||||
@ -195,12 +210,12 @@ const OrderPage = (): JSX.Element => {
|
|||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
<OrderDetails
|
<OrderDetails
|
||||||
order={order}
|
coordinator={federation.getCoordinator(String(currentOrder.shortAlias))}
|
||||||
setOrder={setOrder}
|
onClickCoordinator={onClickCoordinator}
|
||||||
baseUrl={baseUrl}
|
baseUrl={hostUrl}
|
||||||
info={info}
|
onClickGenerateRobot={() => {
|
||||||
hasRobot={robot.avatarLoaded}
|
navigate('/robot');
|
||||||
onClickGenerateRobot={() => navigate('/robot')}
|
}}
|
||||||
/>
|
/>
|
||||||
</Paper>
|
</Paper>
|
||||||
)
|
)
|
||||||
|
@ -1,4 +1,4 @@
|
|||||||
import React, { useState } from 'react';
|
import React, { useContext, useState } 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 {
|
||||||
@ -21,6 +21,8 @@ import RobotAvatar from '../../components/RobotAvatar';
|
|||||||
import TokenInput from './TokenInput';
|
import TokenInput from './TokenInput';
|
||||||
import { genBase62Token } from '../../utils';
|
import { genBase62Token } from '../../utils';
|
||||||
import { NewTabIcon } from '../../components/Icons';
|
import { NewTabIcon } from '../../components/Icons';
|
||||||
|
import { AppContext, UseAppStoreType } from '../../contexts/AppContext';
|
||||||
|
import { GarageContext, UseGarageStoreType } from '../../contexts/GarageContext';
|
||||||
|
|
||||||
interface OnboardingProps {
|
interface OnboardingProps {
|
||||||
setView: (state: 'welcome' | 'onboarding' | 'recovery' | 'profile') => void;
|
setView: (state: 'welcome' | 'onboarding' | 'recovery' | 'profile') => void;
|
||||||
@ -35,22 +37,22 @@ interface OnboardingProps {
|
|||||||
|
|
||||||
const Onboarding = ({
|
const Onboarding = ({
|
||||||
setView,
|
setView,
|
||||||
robot,
|
|
||||||
inputToken,
|
inputToken,
|
||||||
setInputToken,
|
setInputToken,
|
||||||
setRobot,
|
|
||||||
badToken,
|
badToken,
|
||||||
getGenerateRobot,
|
getGenerateRobot,
|
||||||
baseUrl,
|
|
||||||
}: OnboardingProps): JSX.Element => {
|
}: OnboardingProps): JSX.Element => {
|
||||||
const { t } = useTranslation();
|
const { t } = useTranslation();
|
||||||
const navigate = useNavigate();
|
const navigate = useNavigate();
|
||||||
|
|
||||||
|
const { hostUrl } = useContext<UseAppStoreType>(AppContext);
|
||||||
|
const { garage } = useContext<UseGarageStoreType>(GarageContext);
|
||||||
|
|
||||||
const [step, setStep] = useState<'1' | '2' | '3'>('1');
|
const [step, setStep] = useState<'1' | '2' | '3'>('1');
|
||||||
const [generatedToken, setGeneratedToken] = useState<boolean>(false);
|
const [generatedToken, setGeneratedToken] = useState<boolean>(false);
|
||||||
const [loading, setLoading] = useState<boolean>(false);
|
const [loading, setLoading] = useState<boolean>(false);
|
||||||
|
|
||||||
const generateToken = () => {
|
const generateToken = (): void => {
|
||||||
setGeneratedToken(true);
|
setGeneratedToken(true);
|
||||||
setInputToken(genBase62Token(36));
|
setInputToken(genBase62Token(36));
|
||||||
setLoading(true);
|
setLoading(true);
|
||||||
@ -63,7 +65,7 @@ const Onboarding = ({
|
|||||||
<Box>
|
<Box>
|
||||||
<Accordion expanded={step === '1'} disableGutters={true}>
|
<Accordion expanded={step === '1'} disableGutters={true}>
|
||||||
<AccordionSummary>
|
<AccordionSummary>
|
||||||
<Typography variant='h5' color={step == '1' ? 'text.primary' : 'text.disabled'}>
|
<Typography variant='h5' color={step === '1' ? 'text.primary' : 'text.disabled'}>
|
||||||
{t('1. Generate a token')}
|
{t('1. Generate a token')}
|
||||||
</Typography>
|
</Typography>
|
||||||
</AccordionSummary>
|
</AccordionSummary>
|
||||||
@ -101,9 +103,7 @@ const Onboarding = ({
|
|||||||
autoFocusTarget='copyButton'
|
autoFocusTarget='copyButton'
|
||||||
inputToken={inputToken}
|
inputToken={inputToken}
|
||||||
setInputToken={setInputToken}
|
setInputToken={setInputToken}
|
||||||
setRobot={setRobot}
|
|
||||||
badToken={badToken}
|
badToken={badToken}
|
||||||
robot={robot}
|
|
||||||
onPressEnter={() => null}
|
onPressEnter={() => null}
|
||||||
/>
|
/>
|
||||||
</Grid>
|
</Grid>
|
||||||
@ -122,7 +122,7 @@ const Onboarding = ({
|
|||||||
onClick={() => {
|
onClick={() => {
|
||||||
setStep('2');
|
setStep('2');
|
||||||
getGenerateRobot(inputToken);
|
getGenerateRobot(inputToken);
|
||||||
setRobot({ ...robot, nickname: undefined });
|
garage.updateRobot({ nickname: undefined });
|
||||||
}}
|
}}
|
||||||
variant='contained'
|
variant='contained'
|
||||||
size='large'
|
size='large'
|
||||||
@ -141,7 +141,7 @@ const Onboarding = ({
|
|||||||
|
|
||||||
<Accordion expanded={step === '2'} disableGutters={true}>
|
<Accordion expanded={step === '2'} disableGutters={true}>
|
||||||
<AccordionSummary>
|
<AccordionSummary>
|
||||||
<Typography variant='h5' color={step == '2' ? 'text.primary' : 'text.disabled'}>
|
<Typography variant='h5' color={step === '2' ? 'text.primary' : 'text.disabled'}>
|
||||||
{t('2. Meet your robot identity')}
|
{t('2. Meet your robot identity')}
|
||||||
</Typography>
|
</Typography>
|
||||||
</AccordionSummary>
|
</AccordionSummary>
|
||||||
@ -149,7 +149,7 @@ const Onboarding = ({
|
|||||||
<Grid container direction='column' alignItems='center' spacing={1}>
|
<Grid container direction='column' alignItems='center' spacing={1}>
|
||||||
<Grid item>
|
<Grid item>
|
||||||
<Typography>
|
<Typography>
|
||||||
{robot.avatarLoaded && robot.nickname ? (
|
{garage.getRobot().avatarLoaded && Boolean(garage.getRobot().nickname) ? (
|
||||||
t('This is your trading avatar')
|
t('This is your trading avatar')
|
||||||
) : (
|
) : (
|
||||||
<>
|
<>
|
||||||
@ -162,7 +162,7 @@ const Onboarding = ({
|
|||||||
|
|
||||||
<Grid item sx={{ width: '13.5em' }}>
|
<Grid item sx={{ width: '13.5em' }}>
|
||||||
<RobotAvatar
|
<RobotAvatar
|
||||||
nickname={robot.nickname}
|
nickname={garage.getRobot().nickname}
|
||||||
smooth={true}
|
smooth={true}
|
||||||
style={{ maxWidth: '12.5em', maxHeight: '12.5em' }}
|
style={{ maxWidth: '12.5em', maxHeight: '12.5em' }}
|
||||||
placeholderType='generating'
|
placeholderType='generating'
|
||||||
@ -174,11 +174,11 @@ const Onboarding = ({
|
|||||||
width: '12.4em',
|
width: '12.4em',
|
||||||
}}
|
}}
|
||||||
tooltipPosition='top'
|
tooltipPosition='top'
|
||||||
baseUrl={baseUrl}
|
baseUrl={hostUrl}
|
||||||
/>
|
/>
|
||||||
</Grid>
|
</Grid>
|
||||||
|
|
||||||
{robot.avatarLoaded && robot.nickname ? (
|
{garage.getRobot().avatarLoaded && Boolean(garage.getRobot().nickname) ? (
|
||||||
<Grid item>
|
<Grid item>
|
||||||
<Typography align='center'>{t('Hi! My name is')}</Typography>
|
<Typography align='center'>{t('Hi! My name is')}</Typography>
|
||||||
<Typography component='h5' variant='h5'>
|
<Typography component='h5' variant='h5'>
|
||||||
@ -197,7 +197,7 @@ const Onboarding = ({
|
|||||||
width: '1.5em',
|
width: '1.5em',
|
||||||
}}
|
}}
|
||||||
/>
|
/>
|
||||||
<b>{robot.nickname}</b>
|
<b>{garage.getRobot().nickname}</b>
|
||||||
<Bolt
|
<Bolt
|
||||||
sx={{
|
sx={{
|
||||||
color: '#fcba03',
|
color: '#fcba03',
|
||||||
@ -210,7 +210,9 @@ const Onboarding = ({
|
|||||||
</Grid>
|
</Grid>
|
||||||
) : null}
|
) : null}
|
||||||
<Grid item>
|
<Grid item>
|
||||||
<Collapse in={!!(robot.avatarLoaded && robot.nickname)}>
|
<Collapse
|
||||||
|
in={!!(garage.getRobot().avatarLoaded && Boolean(garage.getRobot().nickname))}
|
||||||
|
>
|
||||||
<Button
|
<Button
|
||||||
onClick={() => {
|
onClick={() => {
|
||||||
setStep('3');
|
setStep('3');
|
||||||
@ -229,7 +231,7 @@ const Onboarding = ({
|
|||||||
|
|
||||||
<Accordion expanded={step === '3'} disableGutters={true}>
|
<Accordion expanded={step === '3'} disableGutters={true}>
|
||||||
<AccordionSummary>
|
<AccordionSummary>
|
||||||
<Typography variant='h5' color={step == '3' ? 'text.primary' : 'text.disabled'}>
|
<Typography variant='h5' color={step === '3' ? 'text.primary' : 'text.disabled'}>
|
||||||
{t('3. Browse or create an order')}
|
{t('3. Browse or create an order')}
|
||||||
</Typography>
|
</Typography>
|
||||||
</AccordionSummary>
|
</AccordionSummary>
|
||||||
|
@ -6,8 +6,6 @@ import TokenInput from './TokenInput';
|
|||||||
import Key from '@mui/icons-material/Key';
|
import Key from '@mui/icons-material/Key';
|
||||||
|
|
||||||
interface RecoveryProps {
|
interface RecoveryProps {
|
||||||
robot: Robot;
|
|
||||||
setRobot: (state: Robot) => void;
|
|
||||||
setView: (state: 'welcome' | 'onboarding' | 'recovery' | 'profile') => void;
|
setView: (state: 'welcome' | 'onboarding' | 'recovery' | 'profile') => void;
|
||||||
inputToken: string;
|
inputToken: string;
|
||||||
badToken: string;
|
badToken: string;
|
||||||
@ -16,8 +14,6 @@ interface RecoveryProps {
|
|||||||
}
|
}
|
||||||
|
|
||||||
const Recovery = ({
|
const Recovery = ({
|
||||||
robot,
|
|
||||||
setRobot,
|
|
||||||
inputToken,
|
inputToken,
|
||||||
badToken,
|
badToken,
|
||||||
setView,
|
setView,
|
||||||
@ -26,7 +22,7 @@ const Recovery = ({
|
|||||||
}: RecoveryProps): JSX.Element => {
|
}: RecoveryProps): JSX.Element => {
|
||||||
const { t } = useTranslation();
|
const { t } = useTranslation();
|
||||||
|
|
||||||
const onClickRecover = () => {
|
const onClickRecover = (): void => {
|
||||||
getGenerateRobot(inputToken);
|
getGenerateRobot(inputToken);
|
||||||
setView('profile');
|
setView('profile');
|
||||||
};
|
};
|
||||||
@ -48,15 +44,18 @@ const Recovery = ({
|
|||||||
showCopy={false}
|
showCopy={false}
|
||||||
inputToken={inputToken}
|
inputToken={inputToken}
|
||||||
setInputToken={setInputToken}
|
setInputToken={setInputToken}
|
||||||
setRobot={setRobot}
|
|
||||||
label={t('Paste token here')}
|
label={t('Paste token here')}
|
||||||
robot={robot}
|
|
||||||
onPressEnter={onClickRecover}
|
onPressEnter={onClickRecover}
|
||||||
badToken={badToken}
|
badToken={badToken}
|
||||||
/>
|
/>
|
||||||
</Grid>
|
</Grid>
|
||||||
<Grid item>
|
<Grid item>
|
||||||
<Button variant='contained' size='large' disabled={!!badToken} onClick={onClickRecover}>
|
<Button
|
||||||
|
variant='contained'
|
||||||
|
size='large'
|
||||||
|
disabled={Boolean(badToken)}
|
||||||
|
onClick={onClickRecover}
|
||||||
|
>
|
||||||
<Key /> <div style={{ width: '0.5em' }} />
|
<Key /> <div style={{ width: '0.5em' }} />
|
||||||
{t('Recover')}
|
{t('Recover')}
|
||||||
</Button>
|
</Button>
|
||||||
|
@ -12,6 +12,7 @@ import {
|
|||||||
Box,
|
Box,
|
||||||
useTheme,
|
useTheme,
|
||||||
Tooltip,
|
Tooltip,
|
||||||
|
type SelectChangeEvent,
|
||||||
} from '@mui/material';
|
} from '@mui/material';
|
||||||
import { Bolt, Add, DeleteSweep, Logout, Download } from '@mui/icons-material';
|
import { Bolt, Add, DeleteSweep, Logout, Download } from '@mui/icons-material';
|
||||||
import RobotAvatar from '../../components/RobotAvatar';
|
import RobotAvatar from '../../components/RobotAvatar';
|
||||||
@ -20,6 +21,8 @@ import { type Slot, type Robot } from '../../models';
|
|||||||
import { AppContext, type UseAppStoreType } from '../../contexts/AppContext';
|
import { AppContext, type UseAppStoreType } from '../../contexts/AppContext';
|
||||||
import { genBase62Token } from '../../utils';
|
import { genBase62Token } from '../../utils';
|
||||||
import { LoadingButton } from '@mui/lab';
|
import { LoadingButton } from '@mui/lab';
|
||||||
|
import { GarageContext, UseGarageStoreType } from '../../contexts/GarageContext';
|
||||||
|
import { FederationContext, UseFederationStoreType } from '../../contexts/FederationContext';
|
||||||
|
|
||||||
interface RobotProfileProps {
|
interface RobotProfileProps {
|
||||||
robot: Robot;
|
robot: Robot;
|
||||||
@ -29,23 +32,22 @@ interface RobotProfileProps {
|
|||||||
inputToken: string;
|
inputToken: string;
|
||||||
logoutRobot: () => void;
|
logoutRobot: () => void;
|
||||||
setInputToken: (state: string) => void;
|
setInputToken: (state: string) => void;
|
||||||
baseUrl: string;
|
|
||||||
width: number;
|
width: number;
|
||||||
|
baseUrl: string;
|
||||||
}
|
}
|
||||||
|
|
||||||
const RobotProfile = ({
|
const RobotProfile = ({
|
||||||
robot,
|
|
||||||
setRobot,
|
|
||||||
inputToken,
|
inputToken,
|
||||||
getGenerateRobot,
|
getGenerateRobot,
|
||||||
setInputToken,
|
setInputToken,
|
||||||
logoutRobot,
|
logoutRobot,
|
||||||
setView,
|
setView,
|
||||||
baseUrl,
|
|
||||||
width,
|
width,
|
||||||
}: RobotProfileProps): JSX.Element => {
|
}: RobotProfileProps): JSX.Element => {
|
||||||
const { currentSlot, garage, setCurrentSlot, windowSize } =
|
const { windowSize, hostUrl } = useContext<UseAppStoreType>(AppContext);
|
||||||
useContext<UseAppStoreType>(AppContext);
|
const { currentOrder } = useContext<UseFederationStoreType>(FederationContext);
|
||||||
|
const { garage, robotUpdatedAt } = useContext<UseGarageStoreType>(GarageContext);
|
||||||
|
|
||||||
const { t } = useTranslation();
|
const { t } = useTranslation();
|
||||||
const theme = useTheme();
|
const theme = useTheme();
|
||||||
const navigate = useNavigate();
|
const navigate = useNavigate();
|
||||||
@ -53,19 +55,18 @@ const RobotProfile = ({
|
|||||||
const [loading, setLoading] = useState<boolean>(true);
|
const [loading, setLoading] = useState<boolean>(true);
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
if (robot.nickname && robot.avatarLoaded) {
|
if (garage.getRobot().nickname && garage.getRobot().avatarLoaded) {
|
||||||
setLoading(false);
|
setLoading(false);
|
||||||
}
|
}
|
||||||
}, [robot]);
|
}, [robotUpdatedAt]);
|
||||||
|
|
||||||
const handleAddRobot = () => {
|
const handleAddRobot = (): void => {
|
||||||
getGenerateRobot(genBase62Token(36), garage.slots.length);
|
getGenerateRobot(genBase62Token(36));
|
||||||
setLoading(true);
|
setLoading(true);
|
||||||
};
|
};
|
||||||
|
|
||||||
const handleChangeSlot = (e) => {
|
const handleChangeSlot = (e: SelectChangeEvent<number | 'loading'>): void => {
|
||||||
const slot = e.target.value;
|
garage.currentSlot = Number(e.target.value);
|
||||||
getGenerateRobot(garage.slots[slot].robot.token, slot);
|
|
||||||
setLoading(true);
|
setLoading(true);
|
||||||
};
|
};
|
||||||
|
|
||||||
@ -80,7 +81,7 @@ const RobotProfile = ({
|
|||||||
sx={{ width: '100%' }}
|
sx={{ width: '100%' }}
|
||||||
>
|
>
|
||||||
<Grid item sx={{ height: '2.3em', position: 'relative' }}>
|
<Grid item sx={{ height: '2.3em', position: 'relative' }}>
|
||||||
{robot.avatarLoaded && robot.nickname ? (
|
{garage.getRobot().avatarLoaded && garage.getRobot().nickname ? (
|
||||||
<Typography align='center' component='h5' variant='h5'>
|
<Typography align='center' component='h5' variant='h5'>
|
||||||
<div
|
<div
|
||||||
style={{
|
style={{
|
||||||
@ -99,7 +100,7 @@ const RobotProfile = ({
|
|||||||
}}
|
}}
|
||||||
/>
|
/>
|
||||||
)}
|
)}
|
||||||
<b>{robot.nickname}</b>
|
<b>{garage.getRobot().nickname}</b>
|
||||||
{width < 19 ? null : (
|
{width < 19 ? null : (
|
||||||
<Bolt
|
<Bolt
|
||||||
sx={{
|
sx={{
|
||||||
@ -121,7 +122,7 @@ const RobotProfile = ({
|
|||||||
|
|
||||||
<Grid item sx={{ width: `13.5em` }}>
|
<Grid item sx={{ width: `13.5em` }}>
|
||||||
<RobotAvatar
|
<RobotAvatar
|
||||||
nickname={robot.nickname}
|
nickname={garage.getRobot().nickname}
|
||||||
smooth={true}
|
smooth={true}
|
||||||
style={{ maxWidth: '12.5em', maxHeight: '12.5em' }}
|
style={{ maxWidth: '12.5em', maxHeight: '12.5em' }}
|
||||||
placeholderType='generating'
|
placeholderType='generating'
|
||||||
@ -134,9 +135,9 @@ const RobotProfile = ({
|
|||||||
}}
|
}}
|
||||||
tooltip={t('This is your trading avatar')}
|
tooltip={t('This is your trading avatar')}
|
||||||
tooltipPosition='top'
|
tooltipPosition='top'
|
||||||
baseUrl={baseUrl}
|
baseUrl={hostUrl}
|
||||||
/>
|
/>
|
||||||
{robot.found && !robot.lastOrderId ? (
|
{garage.getRobot().found && Number(garage.getRobot().lastOrderId) > 0 ? (
|
||||||
<Typography align='center' variant='h6'>
|
<Typography align='center' variant='h6'>
|
||||||
{t('Welcome back!')}
|
{t('Welcome back!')}
|
||||||
</Typography>
|
</Typography>
|
||||||
@ -145,27 +146,31 @@ const RobotProfile = ({
|
|||||||
)}
|
)}
|
||||||
</Grid>
|
</Grid>
|
||||||
|
|
||||||
{robot.activeOrderId && robot.avatarLoaded && robot.nickname ? (
|
{Boolean(garage.getRobot().activeOrderId) &&
|
||||||
|
garage.getRobot().avatarLoaded &&
|
||||||
|
Boolean(garage.getRobot().nickname) ? (
|
||||||
<Grid item>
|
<Grid item>
|
||||||
<Button
|
<Button
|
||||||
onClick={() => {
|
onClick={() => {
|
||||||
navigate(`/order/${robot.activeOrderId}`);
|
navigate(`/order/${String(currentOrder.shortAlias)}/${String(currentOrder.id)}`);
|
||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
{t('Active order #{{orderID}}', { orderID: robot.activeOrderId })}
|
{t('Active order #{{orderID}}', { orderID: garage.getRobot().activeOrderId })}
|
||||||
</Button>
|
</Button>
|
||||||
</Grid>
|
</Grid>
|
||||||
) : null}
|
) : null}
|
||||||
|
|
||||||
{robot.lastOrderId && robot.avatarLoaded && robot.nickname ? (
|
{Boolean(garage.getRobot().lastOrderId) &&
|
||||||
|
garage.getRobot().avatarLoaded &&
|
||||||
|
Boolean(garage.getRobot().nickname) ? (
|
||||||
<Grid item container direction='column' alignItems='center'>
|
<Grid item container direction='column' alignItems='center'>
|
||||||
<Grid item>
|
<Grid item>
|
||||||
<Button
|
<Button
|
||||||
onClick={() => {
|
onClick={() => {
|
||||||
navigate(`/order/${robot.lastOrderId}`);
|
navigate(`/order/${String(currentOrder.shortAlias)}/${String(currentOrder.id)}`);
|
||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
{t('Last order #{{orderID}}', { orderID: robot.lastOrderId })}
|
{t('Last order #{{orderID}}', { orderID: garage.getRobot().lastOrderId })}
|
||||||
</Button>
|
</Button>
|
||||||
</Grid>
|
</Grid>
|
||||||
<Grid item>
|
<Grid item>
|
||||||
@ -221,8 +226,6 @@ const RobotProfile = ({
|
|||||||
editable={false}
|
editable={false}
|
||||||
label={t('Store your token safely')}
|
label={t('Store your token safely')}
|
||||||
setInputToken={setInputToken}
|
setInputToken={setInputToken}
|
||||||
setRobot={setRobot}
|
|
||||||
robot={robot}
|
|
||||||
onPressEnter={() => null}
|
onPressEnter={() => null}
|
||||||
/>
|
/>
|
||||||
</Grid>
|
</Grid>
|
||||||
@ -246,7 +249,7 @@ const RobotProfile = ({
|
|||||||
inputProps={{
|
inputProps={{
|
||||||
style: { textAlign: 'center' },
|
style: { textAlign: 'center' },
|
||||||
}}
|
}}
|
||||||
value={loading ? 'loading' : currentSlot}
|
value={loading ? 'loading' : garage.currentSlot}
|
||||||
onChange={handleChangeSlot}
|
onChange={handleChangeSlot}
|
||||||
>
|
>
|
||||||
{loading ? (
|
{loading ? (
|
||||||
@ -271,7 +274,7 @@ const RobotProfile = ({
|
|||||||
smooth={true}
|
smooth={true}
|
||||||
style={{ width: '2.6em', height: '2.6em' }}
|
style={{ width: '2.6em', height: '2.6em' }}
|
||||||
placeholderType='loading'
|
placeholderType='loading'
|
||||||
baseUrl={baseUrl}
|
baseUrl={hostUrl}
|
||||||
small={true}
|
small={true}
|
||||||
/>
|
/>
|
||||||
</Grid>
|
</Grid>
|
||||||
@ -314,7 +317,7 @@ const RobotProfile = ({
|
|||||||
color='primary'
|
color='primary'
|
||||||
onClick={() => {
|
onClick={() => {
|
||||||
garage.delete();
|
garage.delete();
|
||||||
setCurrentSlot(0);
|
garage.currentSlot = 0;
|
||||||
logoutRobot();
|
logoutRobot();
|
||||||
setView('welcome');
|
setView('welcome');
|
||||||
}}
|
}}
|
||||||
|
@ -1,16 +1,14 @@
|
|||||||
import React, { useEffect, useState } from 'react';
|
import React, { useContext, useEffect, useState } from 'react';
|
||||||
import { useTranslation } from 'react-i18next';
|
import { useTranslation } from 'react-i18next';
|
||||||
import { IconButton, LinearProgress, TextField, Tooltip } from '@mui/material';
|
import { IconButton, LinearProgress, TextField, Tooltip } from '@mui/material';
|
||||||
import { type Robot } from '../../models';
|
|
||||||
import { ContentCopy } from '@mui/icons-material';
|
import { ContentCopy } from '@mui/icons-material';
|
||||||
import { systemClient } from '../../services/System';
|
import { systemClient } from '../../services/System';
|
||||||
|
import { UseGarageStoreType, GarageContext } from '../../contexts/GarageContext';
|
||||||
|
|
||||||
interface TokenInputProps {
|
interface TokenInputProps {
|
||||||
robot: Robot;
|
|
||||||
editable?: boolean;
|
editable?: boolean;
|
||||||
fullWidth?: boolean;
|
fullWidth?: boolean;
|
||||||
loading?: boolean;
|
loading?: boolean;
|
||||||
setRobot: (state: Robot) => void;
|
|
||||||
inputToken: string;
|
inputToken: string;
|
||||||
autoFocusTarget?: 'textfield' | 'copyButton' | 'none';
|
autoFocusTarget?: 'textfield' | 'copyButton' | 'none';
|
||||||
onPressEnter: () => void;
|
onPressEnter: () => void;
|
||||||
@ -21,11 +19,9 @@ interface TokenInputProps {
|
|||||||
}
|
}
|
||||||
|
|
||||||
const TokenInput = ({
|
const TokenInput = ({
|
||||||
robot,
|
|
||||||
editable = true,
|
editable = true,
|
||||||
showCopy = true,
|
showCopy = true,
|
||||||
label,
|
label,
|
||||||
setRobot,
|
|
||||||
fullWidth = true,
|
fullWidth = true,
|
||||||
onPressEnter,
|
onPressEnter,
|
||||||
autoFocusTarget = 'textfield',
|
autoFocusTarget = 'textfield',
|
||||||
@ -35,6 +31,7 @@ const TokenInput = ({
|
|||||||
setInputToken,
|
setInputToken,
|
||||||
}: TokenInputProps): JSX.Element => {
|
}: TokenInputProps): JSX.Element => {
|
||||||
const { t } = useTranslation();
|
const { t } = useTranslation();
|
||||||
|
const { garage } = useContext<UseGarageStoreType>(GarageContext);
|
||||||
const [showCopied, setShowCopied] = useState<boolean>(false);
|
const [showCopied, setShowCopied] = useState<boolean>(false);
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
@ -46,12 +43,12 @@ const TokenInput = ({
|
|||||||
} else {
|
} else {
|
||||||
return (
|
return (
|
||||||
<TextField
|
<TextField
|
||||||
error={inputToken.length > 20 ? !!badToken : false}
|
error={inputToken.length > 20 ? Boolean(badToken) : false}
|
||||||
disabled={!editable}
|
disabled={!editable}
|
||||||
required={true}
|
required={true}
|
||||||
label={label || undefined}
|
label={label ?? ''}
|
||||||
value={inputToken}
|
value={inputToken}
|
||||||
autoFocus={autoFocusTarget == 'textfield'}
|
autoFocus={autoFocusTarget === 'textfield'}
|
||||||
fullWidth={fullWidth}
|
fullWidth={fullWidth}
|
||||||
sx={{ borderColor: 'primary' }}
|
sx={{ borderColor: 'primary' }}
|
||||||
variant={editable ? 'outlined' : 'filled'}
|
variant={editable ? 'outlined' : 'filled'}
|
||||||
@ -69,17 +66,15 @@ const TokenInput = ({
|
|||||||
endAdornment: showCopy ? (
|
endAdornment: showCopy ? (
|
||||||
<Tooltip open={showCopied} title={t('Copied!')}>
|
<Tooltip open={showCopied} title={t('Copied!')}>
|
||||||
<IconButton
|
<IconButton
|
||||||
autoFocus={autoFocusTarget == 'copyButton'}
|
autoFocus={autoFocusTarget === 'copyButton'}
|
||||||
color={robot.copiedToken ? 'inherit' : 'primary'}
|
color={garage.getRobot().copiedToken ? 'inherit' : 'primary'}
|
||||||
onClick={() => {
|
onClick={() => {
|
||||||
systemClient.copyToClipboard(inputToken);
|
systemClient.copyToClipboard(inputToken);
|
||||||
setShowCopied(true);
|
setShowCopied(true);
|
||||||
setTimeout(() => {
|
setTimeout(() => {
|
||||||
setShowCopied(false);
|
setShowCopied(false);
|
||||||
}, 1000);
|
}, 1000);
|
||||||
setRobot((robot) => {
|
garage.updateRobot({ copiedToken: true });
|
||||||
return { ...robot, copiedToken: true };
|
|
||||||
});
|
|
||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
<ContentCopy sx={{ width: '1em', height: '1em' }} />
|
<ContentCopy sx={{ width: '1em', height: '1em' }} />
|
||||||
|
@ -1,4 +1,4 @@
|
|||||||
import React, { useState } from 'react';
|
import React from 'react';
|
||||||
import { useTranslation } from 'react-i18next';
|
import { useTranslation } from 'react-i18next';
|
||||||
import { Box, Button, Grid, Typography, useTheme } from '@mui/material';
|
import { Box, Button, Grid, Typography, useTheme } from '@mui/material';
|
||||||
import { RoboSatsTextIcon } from '../../components/Icons';
|
import { RoboSatsTextIcon } from '../../components/Icons';
|
||||||
|
@ -21,13 +21,20 @@ import { TorIcon } from '../../components/Icons';
|
|||||||
import { genKey } from '../../pgp';
|
import { genKey } from '../../pgp';
|
||||||
import { AppContext, type UseAppStoreType } from '../../contexts/AppContext';
|
import { AppContext, type UseAppStoreType } from '../../contexts/AppContext';
|
||||||
import { validateTokenEntropy } from '../../utils';
|
import { validateTokenEntropy } from '../../utils';
|
||||||
|
import { FederationContext, UseFederationStoreType } from '../../contexts/FederationContext';
|
||||||
|
import { GarageContext, UseGarageStoreType } from '../../contexts/GarageContext';
|
||||||
|
|
||||||
const RobotPage = (): JSX.Element => {
|
interface RobotPageProps {
|
||||||
const { robot, setRobot, fetchRobot, torStatus, windowSize, baseUrl, settings } =
|
avatarBaseUrl: string;
|
||||||
useContext<UseAppStoreType>(AppContext);
|
}
|
||||||
|
|
||||||
|
const RobotPage = ({ avatarBaseUrl }: RobotPageProps): JSX.Element => {
|
||||||
|
const { torStatus, windowSize, settings, page } = useContext<UseAppStoreType>(AppContext);
|
||||||
|
const { garage } = useContext<UseGarageStoreType>(GarageContext);
|
||||||
|
const { federation } = useContext<UseFederationStoreType>(FederationContext);
|
||||||
const { t } = useTranslation();
|
const { t } = useTranslation();
|
||||||
const params = useParams();
|
const params = useParams();
|
||||||
const url_token = settings.selfhostedClient ? params.token : null;
|
const urlToken = settings.selfhostedClient ? params.token : null;
|
||||||
const width = Math.min(windowSize.width * 0.8, 28);
|
const width = Math.min(windowSize.width * 0.8, 28);
|
||||||
const maxHeight = windowSize.height * 0.85 - 3;
|
const maxHeight = windowSize.height * 0.85 - 3;
|
||||||
const theme = useTheme();
|
const theme = useTheme();
|
||||||
@ -35,21 +42,21 @@ const RobotPage = (): JSX.Element => {
|
|||||||
const [badToken, setBadToken] = useState<string>('');
|
const [badToken, setBadToken] = useState<string>('');
|
||||||
const [inputToken, setInputToken] = useState<string>('');
|
const [inputToken, setInputToken] = useState<string>('');
|
||||||
const [view, setView] = useState<'welcome' | 'onboarding' | 'recovery' | 'profile'>(
|
const [view, setView] = useState<'welcome' | 'onboarding' | 'recovery' | 'profile'>(
|
||||||
robot.token ? 'profile' : 'welcome',
|
garage.getRobot().token !== undefined ? 'profile' : 'welcome',
|
||||||
);
|
);
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
if (robot.token) {
|
const token = urlToken ?? garage.getRobot().token;
|
||||||
setInputToken(robot.token);
|
if (token !== undefined) {
|
||||||
|
setInputToken(token);
|
||||||
}
|
}
|
||||||
const token = url_token ?? robot.token;
|
if (garage.getRobot().nickname !== undefined && token !== undefined) {
|
||||||
if (robot.nickname == null && token) {
|
if (window.NativeRobosats === undefined || torStatus === '"Done"') {
|
||||||
if (window.NativeRobosats === undefined || torStatus == '"Done"') {
|
|
||||||
getGenerateRobot(token);
|
getGenerateRobot(token);
|
||||||
setView('profile');
|
setView('profile');
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}, [torStatus]);
|
}, [torStatus, page]);
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
if (inputToken.length < 20) {
|
if (inputToken.length < 20) {
|
||||||
@ -61,26 +68,28 @@ const RobotPage = (): JSX.Element => {
|
|||||||
}
|
}
|
||||||
}, [inputToken]);
|
}, [inputToken]);
|
||||||
|
|
||||||
const getGenerateRobot = (token: string, slot?: number) => {
|
const getGenerateRobot = (token: string): void => {
|
||||||
setInputToken(token);
|
setInputToken(token);
|
||||||
genKey(token).then(function (key) {
|
genKey(token)
|
||||||
fetchRobot({
|
.then((key) => {
|
||||||
newKeys: {
|
const slot = garage.createRobot({
|
||||||
|
token,
|
||||||
pubKey: key.publicKeyArmored,
|
pubKey: key.publicKeyArmored,
|
||||||
encPrivKey: key.encryptedPrivateKeyArmored,
|
encPrivKey: key.encryptedPrivateKeyArmored,
|
||||||
},
|
});
|
||||||
newToken: token,
|
federation.fetchRobot(garage, slot);
|
||||||
slot,
|
})
|
||||||
|
.catch((error) => {
|
||||||
|
console.error('Error:', error);
|
||||||
});
|
});
|
||||||
});
|
|
||||||
};
|
};
|
||||||
|
|
||||||
const logoutRobot = () => {
|
const logoutRobot = (): void => {
|
||||||
setInputToken('');
|
setInputToken('');
|
||||||
setRobot(new Robot());
|
garage.deleteSlot(garage.currentSlot);
|
||||||
};
|
};
|
||||||
|
|
||||||
if (!(window.NativeRobosats === undefined) && !(torStatus == 'DONE' || torStatus == '"Done"')) {
|
if (!(window.NativeRobosats === undefined) && !(torStatus === 'DONE' || torStatus === '"Done"')) {
|
||||||
return (
|
return (
|
||||||
<Paper
|
<Paper
|
||||||
elevation={12}
|
elevation={12}
|
||||||
@ -146,35 +155,29 @@ const RobotPage = (): JSX.Element => {
|
|||||||
{view === 'onboarding' ? (
|
{view === 'onboarding' ? (
|
||||||
<Onboarding
|
<Onboarding
|
||||||
setView={setView}
|
setView={setView}
|
||||||
robot={robot}
|
|
||||||
setRobot={setRobot}
|
|
||||||
badToken={badToken}
|
badToken={badToken}
|
||||||
inputToken={inputToken}
|
inputToken={inputToken}
|
||||||
setInputToken={setInputToken}
|
setInputToken={setInputToken}
|
||||||
getGenerateRobot={getGenerateRobot}
|
getGenerateRobot={getGenerateRobot}
|
||||||
baseUrl={baseUrl}
|
avatarBaseUrl={avatarBaseUrl}
|
||||||
/>
|
/>
|
||||||
) : null}
|
) : null}
|
||||||
|
|
||||||
{view === 'profile' ? (
|
{view === 'profile' ? (
|
||||||
<RobotProfile
|
<RobotProfile
|
||||||
setView={setView}
|
setView={setView}
|
||||||
robot={robot}
|
|
||||||
setRobot={setRobot}
|
|
||||||
logoutRobot={logoutRobot}
|
logoutRobot={logoutRobot}
|
||||||
width={width}
|
width={width}
|
||||||
inputToken={inputToken}
|
inputToken={inputToken}
|
||||||
setInputToken={setInputToken}
|
setInputToken={setInputToken}
|
||||||
getGenerateRobot={getGenerateRobot}
|
getGenerateRobot={getGenerateRobot}
|
||||||
baseUrl={baseUrl}
|
avatarBaseUrl={avatarBaseUrl}
|
||||||
/>
|
/>
|
||||||
) : null}
|
) : null}
|
||||||
|
|
||||||
{view === 'recovery' ? (
|
{view === 'recovery' ? (
|
||||||
<Recovery
|
<Recovery
|
||||||
setView={setView}
|
setView={setView}
|
||||||
robot={robot}
|
|
||||||
setRobot={setRobot}
|
|
||||||
badToken={badToken}
|
badToken={badToken}
|
||||||
inputToken={inputToken}
|
inputToken={inputToken}
|
||||||
setInputToken={setInputToken}
|
setInputToken={setInputToken}
|
||||||
|
@ -1,10 +1,13 @@
|
|||||||
import React, { useContext } from 'react';
|
import React, { useContext } from 'react';
|
||||||
import { Grid, Paper } from '@mui/material';
|
import { Grid, Paper } from '@mui/material';
|
||||||
import SettingsForm from '../../components/SettingsForm';
|
import SettingsForm from '../../components/SettingsForm';
|
||||||
import { type UseAppStoreType, AppContext } from '../../contexts/AppContext';
|
import { AppContext, type UseAppStoreType } from '../../contexts/AppContext';
|
||||||
|
import FederationTable from '../../components/FederationTable';
|
||||||
|
import { FederationContext, UseFederationStoreType } from '../../contexts/FederationContext';
|
||||||
|
|
||||||
const SettingsPage = (): JSX.Element => {
|
const SettingsPage = (): JSX.Element => {
|
||||||
const { windowSize, navbarHeight } = useContext<UseAppStoreType>(AppContext);
|
const { windowSize, navbarHeight, settings, setOpen, open, hostUrl } =
|
||||||
|
useContext<UseAppStoreType>(AppContext);
|
||||||
const maxHeight = (windowSize.height - navbarHeight) * 0.85 - 3;
|
const maxHeight = (windowSize.height - navbarHeight) * 0.85 - 3;
|
||||||
|
|
||||||
return (
|
return (
|
||||||
@ -12,7 +15,7 @@ const SettingsPage = (): JSX.Element => {
|
|||||||
elevation={12}
|
elevation={12}
|
||||||
sx={{
|
sx={{
|
||||||
padding: '0.6em',
|
padding: '0.6em',
|
||||||
width: '21em',
|
width: '20.5em',
|
||||||
maxHeight: `${maxHeight}em`,
|
maxHeight: `${maxHeight}em`,
|
||||||
overflow: 'auto',
|
overflow: 'auto',
|
||||||
overflowX: 'clip',
|
overflowX: 'clip',
|
||||||
@ -22,6 +25,16 @@ const SettingsPage = (): JSX.Element => {
|
|||||||
<Grid item>
|
<Grid item>
|
||||||
<SettingsForm showNetwork={!(window.NativeRobosats === undefined)} />
|
<SettingsForm showNetwork={!(window.NativeRobosats === undefined)} />
|
||||||
</Grid>
|
</Grid>
|
||||||
|
<Grid item>
|
||||||
|
<FederationTable
|
||||||
|
openCoordinator={() => {
|
||||||
|
setOpen({ ...open, coordinator: true });
|
||||||
|
}}
|
||||||
|
baseUrl={hostUrl}
|
||||||
|
maxHeight={14}
|
||||||
|
network={settings.network}
|
||||||
|
/>
|
||||||
|
</Grid>
|
||||||
</Grid>
|
</Grid>
|
||||||
</Paper>
|
</Paper>
|
||||||
);
|
);
|
||||||
|
7
frontend/src/basic/index.ts
Normal file
7
frontend/src/basic/index.ts
Normal file
@ -0,0 +1,7 @@
|
|||||||
|
export { default as BookPage } from './BookPage';
|
||||||
|
export { default as MainDialogs } from './MainDialogs';
|
||||||
|
export { default as MakerPage } from './MakerPage';
|
||||||
|
export { default as NavBar } from './NavBar';
|
||||||
|
export { default as OrderPage } from './OrderPage';
|
||||||
|
export { default as RobotPage } from './RobotPage';
|
||||||
|
export { default as SettingsPage } from './SettingsPage';
|
@ -1,4 +1,4 @@
|
|||||||
import React, { useEffect, useMemo, useState } from 'react';
|
import React, { useMemo } from 'react';
|
||||||
import { useTranslation } from 'react-i18next';
|
import { useTranslation } from 'react-i18next';
|
||||||
import {
|
import {
|
||||||
Typography,
|
Typography,
|
||||||
@ -26,7 +26,7 @@ interface BookControlProps {
|
|||||||
fav: Favorites;
|
fav: Favorites;
|
||||||
setFav: (state: Favorites) => void;
|
setFav: (state: Favorites) => void;
|
||||||
paymentMethod: string[];
|
paymentMethod: string[];
|
||||||
setPaymentMethods: () => void;
|
setPaymentMethods: (state: string[]) => void;
|
||||||
}
|
}
|
||||||
|
|
||||||
const BookControl = ({
|
const BookControl = ({
|
||||||
@ -43,22 +43,22 @@ const BookControl = ({
|
|||||||
const typeZeroText = fav.mode === 'fiat' ? t('Buy') : t('Swap In');
|
const typeZeroText = fav.mode === 'fiat' ? t('Buy') : t('Swap In');
|
||||||
const typeOneText = fav.mode === 'fiat' ? t('Sell') : t('Swap Out');
|
const typeOneText = fav.mode === 'fiat' ? t('Sell') : t('Swap Out');
|
||||||
const small =
|
const small =
|
||||||
(typeZeroText.length + typeOneText.length) * 0.7 + (fav.mode == 'fiat' ? 16 : 7.5);
|
(typeZeroText.length + typeOneText.length) * 0.7 + (fav.mode === 'fiat' ? 16 : 7.5);
|
||||||
const medium = small + 13;
|
const medium = small + 13;
|
||||||
const large = medium + (t('and use').length + t('pay with').length) * 0.6 + 5;
|
const large = medium + (t('and use').length + t('pay with').length) * 0.6 + 5;
|
||||||
return [typeZeroText, typeOneText, small, medium, large];
|
return [typeZeroText, typeOneText, small, medium, large];
|
||||||
}, [i18n.language, fav.mode]);
|
}, [i18n.language, fav.mode]);
|
||||||
|
|
||||||
const handleCurrencyChange = function (e) {
|
const handleCurrencyChange = function (e: React.ChangeEvent<HTMLInputElement>): void {
|
||||||
const currency = e.target.value;
|
const currency = Number(e.target.value);
|
||||||
setFav({ ...fav, currency, mode: currency === 1000 ? 'swap' : 'fiat' });
|
setFav({ ...fav, currency, mode: currency === 1000 ? 'swap' : 'fiat' });
|
||||||
};
|
};
|
||||||
|
|
||||||
const handleTypeChange = function (mouseEvent, val) {
|
const handleTypeChange = function (mouseEvent: React.MouseEvent, val: number): void {
|
||||||
setFav({ ...fav, type: val });
|
setFav({ ...fav, type: val });
|
||||||
};
|
};
|
||||||
|
|
||||||
const handleModeChange = function (mouseEvent, val) {
|
const handleModeChange = function (mouseEvent: React.MouseEvent, val: number): void {
|
||||||
const mode = fav.mode === 'fiat' ? 'swap' : 'fiat';
|
const mode = fav.mode === 'fiat' ? 'swap' : 'fiat';
|
||||||
const currency = fav.mode === 'fiat' ? 1000 : 0;
|
const currency = fav.mode === 'fiat' ? 1000 : 0;
|
||||||
setFav({ ...fav, mode, currency });
|
setFav({ ...fav, mode, currency });
|
||||||
@ -197,7 +197,7 @@ const BookControl = ({
|
|||||||
{width > large ? (
|
{width > large ? (
|
||||||
<Grid item sx={{ position: 'relative', top: '0.5em' }}>
|
<Grid item sx={{ position: 'relative', top: '0.5em' }}>
|
||||||
<Typography variant='caption' color='text.secondary'>
|
<Typography variant='caption' color='text.secondary'>
|
||||||
{fav.currency == 1000 ? t(fav.type === 0 ? 'to' : 'from') : t('pay with')}
|
{fav.currency === 1000 ? t(fav.type === 0 ? 'to' : 'from') : t('pay with')}
|
||||||
</Typography>
|
</Typography>
|
||||||
</Grid>
|
</Grid>
|
||||||
) : null}
|
) : null}
|
||||||
@ -218,10 +218,10 @@ const BookControl = ({
|
|||||||
listBoxProps={{ sx: { width: '13em' } }}
|
listBoxProps={{ sx: { width: '13em' } }}
|
||||||
onAutocompleteChange={setPaymentMethods}
|
onAutocompleteChange={setPaymentMethods}
|
||||||
value={paymentMethod}
|
value={paymentMethod}
|
||||||
optionsType={fav.currency == 1000 ? 'swap' : 'fiat'}
|
optionsType={fav.currency === 1000 ? 'swap' : 'fiat'}
|
||||||
error={false}
|
error={false}
|
||||||
helperText={''}
|
helperText={''}
|
||||||
label={fav.currency == 1000 ? t('DESTINATION') : t('METHOD')}
|
label={fav.currency === 1000 ? t('DESTINATION') : t('METHOD')}
|
||||||
tooltipTitle=''
|
tooltipTitle=''
|
||||||
listHeaderText=''
|
listHeaderText=''
|
||||||
addNewButtonText=''
|
addNewButtonText=''
|
||||||
@ -247,7 +247,7 @@ const BookControl = ({
|
|||||||
label={t('Select Payment Method')}
|
label={t('Select Payment Method')}
|
||||||
required={true}
|
required={true}
|
||||||
renderValue={(value) =>
|
renderValue={(value) =>
|
||||||
value == 'ANY' ? (
|
value === 'ANY' ? (
|
||||||
<CheckBoxOutlineBlankIcon style={{ position: 'relative', top: '0.1em' }} />
|
<CheckBoxOutlineBlankIcon style={{ position: 'relative', top: '0.1em' }} />
|
||||||
) : (
|
) : (
|
||||||
<div style={{ display: 'flex', alignItems: 'center' }}>
|
<div style={{ display: 'flex', alignItems: 'center' }}>
|
||||||
@ -258,9 +258,9 @@ const BookControl = ({
|
|||||||
inputProps={{
|
inputProps={{
|
||||||
style: { textAlign: 'center' },
|
style: { textAlign: 'center' },
|
||||||
}}
|
}}
|
||||||
value={paymentMethod[0] ? paymentMethod[0] : 'ANY'}
|
value={paymentMethod[0] ?? 'ANY'}
|
||||||
onChange={(e) => {
|
onChange={(e) => {
|
||||||
setPaymentMethods(e.target.value == 'ANY' ? [] : [e.target.value]);
|
setPaymentMethods(e.target.value === 'ANY' ? [] : [e.target.value]);
|
||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
<MenuItem value={'ANY'}>
|
<MenuItem value={'ANY'}>
|
||||||
|
@ -22,6 +22,8 @@ import {
|
|||||||
type GridColumnVisibilityModel,
|
type GridColumnVisibilityModel,
|
||||||
GridPagination,
|
GridPagination,
|
||||||
type GridPaginationModel,
|
type GridPaginationModel,
|
||||||
|
type GridColDef,
|
||||||
|
type GridValidRowModel,
|
||||||
} 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';
|
||||||
@ -35,6 +37,7 @@ import RobotAvatar from '../RobotAvatar';
|
|||||||
// Icons
|
// Icons
|
||||||
import { Fullscreen, FullscreenExit, Refresh } from '@mui/icons-material';
|
import { Fullscreen, FullscreenExit, Refresh } from '@mui/icons-material';
|
||||||
import { AppContext, type UseAppStoreType } from '../../contexts/AppContext';
|
import { AppContext, type UseAppStoreType } from '../../contexts/AppContext';
|
||||||
|
import { FederationContext, UseFederationStoreType } from '../../contexts/FederationContext';
|
||||||
|
|
||||||
const ClickThroughDataGrid = styled(DataGrid)({
|
const ClickThroughDataGrid = styled(DataGrid)({
|
||||||
'& .MuiDataGrid-overlayWrapperInner': {
|
'& .MuiDataGrid-overlayWrapperInner': {
|
||||||
@ -62,15 +65,15 @@ const ClickThroughDataGrid = styled(DataGrid)({
|
|||||||
},
|
},
|
||||||
});
|
});
|
||||||
|
|
||||||
const premiumColor = function (baseColor: string, accentColor: string, point: number) {
|
const premiumColor = function (baseColor: string, accentColor: string, point: number): string {
|
||||||
const baseRGB = hexToRgb(baseColor);
|
const baseRGB = hexToRgb(baseColor);
|
||||||
const accentRGB = hexToRgb(accentColor);
|
const accentRGB = hexToRgb(accentColor);
|
||||||
const redDiff = accentRGB[0] - baseRGB[0];
|
const redDiff = accentRGB[0] - baseRGB[0];
|
||||||
const red = baseRGB[0] + redDiff * point;
|
const red = Number(baseRGB[0]) + redDiff * point;
|
||||||
const greenDiff = accentRGB[1] - baseRGB[1];
|
const greenDiff = accentRGB[1] - baseRGB[1];
|
||||||
const green = baseRGB[1] + greenDiff * point;
|
const green = Number(baseRGB[1]) + greenDiff * point;
|
||||||
const blueDiff = accentRGB[2] - baseRGB[2];
|
const blueDiff = accentRGB[2] - baseRGB[2];
|
||||||
const blue = baseRGB[2] + blueDiff * point;
|
const blue = Number(baseRGB[2]) + blueDiff * point;
|
||||||
return `rgb(${Math.round(red)}, ${Math.round(green)}, ${Math.round(blue)}, ${0.7 + point * 0.3})`;
|
return `rgb(${Math.round(red)}, ${Math.round(green)}, ${Math.round(blue)}, ${0.7 + point * 0.3})`;
|
||||||
};
|
};
|
||||||
|
|
||||||
@ -86,7 +89,7 @@ interface BookTableProps {
|
|||||||
showControls?: boolean;
|
showControls?: boolean;
|
||||||
showFooter?: boolean;
|
showFooter?: boolean;
|
||||||
showNoResults?: boolean;
|
showNoResults?: boolean;
|
||||||
onOrderClicked?: (id: number) => void;
|
onOrderClicked?: (id: number, shortAlias: string) => void;
|
||||||
}
|
}
|
||||||
|
|
||||||
const BookTable = ({
|
const BookTable = ({
|
||||||
@ -103,11 +106,18 @@ const BookTable = ({
|
|||||||
showNoResults = true,
|
showNoResults = true,
|
||||||
onOrderClicked = () => null,
|
onOrderClicked = () => null,
|
||||||
}: BookTableProps): JSX.Element => {
|
}: BookTableProps): JSX.Element => {
|
||||||
const { book, fetchBook, fav, setFav, baseUrl } = useContext<UseAppStoreType>(AppContext);
|
const { fav, setFav, settings, setOpen, hostUrl, origin } =
|
||||||
|
useContext<UseAppStoreType>(AppContext);
|
||||||
|
const { federation, setFocusedCoordinator, coordinatorUpdatedAt } =
|
||||||
|
useContext<UseFederationStoreType>(FederationContext);
|
||||||
|
|
||||||
const { t } = useTranslation();
|
const { t } = useTranslation();
|
||||||
const theme = useTheme();
|
const theme = useTheme();
|
||||||
const orders = orderList ?? book.orders;
|
const orders = orderList ?? federation.book;
|
||||||
|
const loadingProgress = useMemo(() => {
|
||||||
|
return (federation.exchange.onlineCoordinators / federation.exchange.totalCoordinators) * 100;
|
||||||
|
}, [coordinatorUpdatedAt]);
|
||||||
|
|
||||||
const [paginationModel, setPaginationModel] = useState<GridPaginationModel>({
|
const [paginationModel, setPaginationModel] = useState<GridPaginationModel>({
|
||||||
pageSize: 0,
|
pageSize: 0,
|
||||||
page: 0,
|
page: 0,
|
||||||
@ -133,10 +143,10 @@ const BookTable = ({
|
|||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
setPaginationModel({
|
setPaginationModel({
|
||||||
pageSize: book.loading && orders.length == 0 ? 0 : defaultPageSize,
|
pageSize: federation.loading && orders.length === 0 ? 0 : defaultPageSize,
|
||||||
page: paginationModel.page,
|
page: paginationModel.page,
|
||||||
});
|
});
|
||||||
}, [book.loading, orders, defaultPageSize]);
|
}, [coordinatorUpdatedAt, orders, defaultPageSize]);
|
||||||
|
|
||||||
const localeText = useMemo(() => {
|
const localeText = useMemo(() => {
|
||||||
return {
|
return {
|
||||||
@ -196,8 +206,17 @@ const BookTable = ({
|
|||||||
headerName: t('Robot'),
|
headerName: t('Robot'),
|
||||||
width: width * fontSize,
|
width: width * fontSize,
|
||||||
renderCell: (params: any) => {
|
renderCell: (params: any) => {
|
||||||
|
const { url, basePath } = federation
|
||||||
|
.getCoordinator(params.row.coordinatorShortAlias)
|
||||||
|
?.getEndpoint(settings.network, origin, settings.selfhostedClient, hostUrl);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<ListItemButton style={{ cursor: 'pointer', position: 'relative', left: '-1.3em' }}>
|
<ListItemButton
|
||||||
|
style={{ cursor: 'pointer', position: 'relative', left: '-1.3em' }}
|
||||||
|
onClick={() => {
|
||||||
|
onOrderClicked(params.row.id, params.row.coordinatorShortAlias);
|
||||||
|
}}
|
||||||
|
>
|
||||||
<ListItemAvatar>
|
<ListItemAvatar>
|
||||||
<RobotAvatar
|
<RobotAvatar
|
||||||
nickname={params.row.maker_nick}
|
nickname={params.row.maker_nick}
|
||||||
@ -207,7 +226,7 @@ const BookTable = ({
|
|||||||
orderType={params.row.type}
|
orderType={params.row.type}
|
||||||
statusColor={statusBadgeColor(params.row.maker_status)}
|
statusColor={statusBadgeColor(params.row.maker_status)}
|
||||||
tooltip={t(params.row.maker_status)}
|
tooltip={t(params.row.maker_status)}
|
||||||
baseUrl={baseUrl}
|
baseUrl={url + basePath}
|
||||||
small={true}
|
small={true}
|
||||||
/>
|
/>
|
||||||
</ListItemAvatar>
|
</ListItemAvatar>
|
||||||
@ -224,22 +243,65 @@ const BookTable = ({
|
|||||||
headerName: t('Robot'),
|
headerName: t('Robot'),
|
||||||
width: width * fontSize,
|
width: width * fontSize,
|
||||||
renderCell: (params: any) => {
|
renderCell: (params: any) => {
|
||||||
|
const { url, basePath } = federation
|
||||||
|
.getCoordinator(params.row.coordinatorShortAlias)
|
||||||
|
?.getEndpoint(settings.network, origin, settings.selfhostedClient, hostUrl);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div style={{ position: 'relative', left: '-1.64em' }}>
|
<div
|
||||||
<ListItemButton style={{ cursor: 'pointer' }}>
|
style={{ position: 'relative', left: '-0.34em', cursor: 'pointer' }}
|
||||||
|
onClick={() => {
|
||||||
|
onOrderClicked(params.row.id, params.row.coordinatorShortAlias);
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
<RobotAvatar
|
||||||
|
nickname={params.row.maker_nick}
|
||||||
|
smooth={true}
|
||||||
|
flipHorizontally={true}
|
||||||
|
style={{ width: '3.215em', height: '3.215em' }}
|
||||||
|
orderType={params.row.type}
|
||||||
|
statusColor={statusBadgeColor(params.row.maker_status)}
|
||||||
|
tooltip={t(params.row.maker_status)}
|
||||||
|
baseUrl={url + basePath}
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
},
|
||||||
|
};
|
||||||
|
}, []);
|
||||||
|
|
||||||
|
const onClickCoordinator = function (shortAlias: string): void {
|
||||||
|
setFocusedCoordinator(shortAlias);
|
||||||
|
setOpen((open) => {
|
||||||
|
return { ...open, coordinator: true };
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
|
const coordinatorObj = useCallback((width: number) => {
|
||||||
|
return {
|
||||||
|
field: 'coordinatorShortAlias',
|
||||||
|
headerName: t('Host'),
|
||||||
|
width: width * fontSize,
|
||||||
|
renderCell: (params: any) => {
|
||||||
|
return (
|
||||||
|
<ListItemButton
|
||||||
|
style={{ cursor: 'pointer', position: 'relative', left: '-1.54em' }}
|
||||||
|
onClick={() => {
|
||||||
|
onClickCoordinator(params.row.coordinatorShortAlias);
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
<ListItemAvatar>
|
||||||
<RobotAvatar
|
<RobotAvatar
|
||||||
nickname={params.row.maker_nick}
|
nickname={params.row.coordinatorShortAlias}
|
||||||
|
coordinator={true}
|
||||||
|
style={{ width: '3.215em', height: '3.215em' }}
|
||||||
smooth={true}
|
smooth={true}
|
||||||
flipHorizontally={true}
|
flipHorizontally={true}
|
||||||
style={{ width: '3.215em', height: '3.215em' }}
|
baseUrl={hostUrl}
|
||||||
orderType={params.row.type}
|
|
||||||
statusColor={statusBadgeColor(params.row.maker_status)}
|
|
||||||
tooltip={t(params.row.maker_status)}
|
|
||||||
baseUrl={baseUrl}
|
|
||||||
small={true}
|
small={true}
|
||||||
/>
|
/>
|
||||||
</ListItemButton>
|
</ListItemAvatar>
|
||||||
</div>
|
</ListItemButton>
|
||||||
);
|
);
|
||||||
},
|
},
|
||||||
};
|
};
|
||||||
@ -250,10 +312,20 @@ const BookTable = ({
|
|||||||
field: 'type',
|
field: 'type',
|
||||||
headerName: t('Is'),
|
headerName: t('Is'),
|
||||||
width: width * fontSize,
|
width: width * fontSize,
|
||||||
renderCell: (params: any) =>
|
renderCell: (params: any) => {
|
||||||
params.row.type
|
return (
|
||||||
? t(fav.mode === 'fiat' ? 'Seller' : 'Swapping Out')
|
<div
|
||||||
: t(fav.mode === 'fiat' ? 'Buyer' : 'Swapping In'),
|
style={{ cursor: 'pointer' }}
|
||||||
|
onClick={() => {
|
||||||
|
onOrderClicked(params.row.id, params.row.coordinatorShortAlias);
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
{params.row.type === 1
|
||||||
|
? t(fav.mode === 'fiat' ? 'Seller' : 'Swapping Out')
|
||||||
|
: t(fav.mode === 'fiat' ? 'Buyer' : 'Swapping In')}
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
},
|
||||||
};
|
};
|
||||||
}, []);
|
}, []);
|
||||||
|
|
||||||
@ -270,7 +342,12 @@ const BookTable = ({
|
|||||||
const maxAmount =
|
const maxAmount =
|
||||||
fav.mode === 'swap' ? params.row.max_amount * 100000 : params.row.max_amount;
|
fav.mode === 'swap' ? params.row.max_amount * 100000 : params.row.max_amount;
|
||||||
return (
|
return (
|
||||||
<div style={{ cursor: 'pointer' }}>
|
<div
|
||||||
|
style={{ cursor: 'pointer' }}
|
||||||
|
onClick={() => {
|
||||||
|
onOrderClicked(params.row.id, params.row.coordinatorShortAlias);
|
||||||
|
}}
|
||||||
|
>
|
||||||
{amountToString(amount, params.row.has_range, minAmount, maxAmount) +
|
{amountToString(amount, params.row.has_range, minAmount, maxAmount) +
|
||||||
(fav.mode === 'swap' ? 'K Sats' : '')}
|
(fav.mode === 'swap' ? 'K Sats' : '')}
|
||||||
</div>
|
</div>
|
||||||
@ -285,7 +362,7 @@ const BookTable = ({
|
|||||||
headerName: t('Currency'),
|
headerName: t('Currency'),
|
||||||
width: width * fontSize,
|
width: width * fontSize,
|
||||||
renderCell: (params: any) => {
|
renderCell: (params: any) => {
|
||||||
const currencyCode = currencyDict[params.row.currency.toString()];
|
const currencyCode = String(currencyDict[params.row.currency.toString()]);
|
||||||
return (
|
return (
|
||||||
<div
|
<div
|
||||||
style={{
|
style={{
|
||||||
@ -294,6 +371,9 @@ const BookTable = ({
|
|||||||
alignItems: 'center',
|
alignItems: 'center',
|
||||||
flexWrap: 'wrap',
|
flexWrap: 'wrap',
|
||||||
}}
|
}}
|
||||||
|
onClick={() => {
|
||||||
|
onOrderClicked(params.row.id, params.row.coordinatorShortAlias);
|
||||||
|
}}
|
||||||
>
|
>
|
||||||
{currencyCode}
|
{currencyCode}
|
||||||
<div style={{ width: '0.3em' }} />
|
<div style={{ width: '0.3em' }} />
|
||||||
@ -311,7 +391,12 @@ const BookTable = ({
|
|||||||
width: width * fontSize,
|
width: width * fontSize,
|
||||||
renderCell: (params: any) => {
|
renderCell: (params: any) => {
|
||||||
return (
|
return (
|
||||||
<div style={{ cursor: 'pointer' }}>
|
<div
|
||||||
|
style={{ cursor: 'pointer' }}
|
||||||
|
onClick={() => {
|
||||||
|
onOrderClicked(params.row.id, params.row.coordinatorShortAlias);
|
||||||
|
}}
|
||||||
|
>
|
||||||
<PaymentStringAsIcons
|
<PaymentStringAsIcons
|
||||||
othersText={t('Others')}
|
othersText={t('Others')}
|
||||||
verbose={true}
|
verbose={true}
|
||||||
@ -337,6 +422,9 @@ const BookTable = ({
|
|||||||
left: '-4px',
|
left: '-4px',
|
||||||
cursor: 'pointer',
|
cursor: 'pointer',
|
||||||
}}
|
}}
|
||||||
|
onClick={() => {
|
||||||
|
onOrderClicked(params.row.id, params.row.coordinatorShortAlias);
|
||||||
|
}}
|
||||||
>
|
>
|
||||||
<PaymentStringAsIcons
|
<PaymentStringAsIcons
|
||||||
othersText={t('Others')}
|
othersText={t('Others')}
|
||||||
@ -356,9 +444,16 @@ const BookTable = ({
|
|||||||
type: 'number',
|
type: 'number',
|
||||||
width: width * fontSize,
|
width: width * fontSize,
|
||||||
renderCell: (params: any) => {
|
renderCell: (params: any) => {
|
||||||
const currencyCode = currencyDict[params.row.currency.toString()];
|
const currencyCode = String(currencyDict[params.row.currency.toString()]);
|
||||||
return (
|
return (
|
||||||
<div style={{ cursor: 'pointer' }}>{`${pn(params.row.price)} ${currencyCode}/BTC`}</div>
|
<div
|
||||||
|
style={{ cursor: 'pointer' }}
|
||||||
|
onClick={() => {
|
||||||
|
onOrderClicked(params.row.id, params.row.coordinatorShortAlias);
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
{`${pn(params.row.price)} ${currencyCode}/BTC`}
|
||||||
|
</div>
|
||||||
);
|
);
|
||||||
},
|
},
|
||||||
};
|
};
|
||||||
@ -377,10 +472,11 @@ const BookTable = ({
|
|||||||
type: 'number',
|
type: 'number',
|
||||||
width: width * fontSize,
|
width: width * fontSize,
|
||||||
renderCell: (params: any) => {
|
renderCell: (params: any) => {
|
||||||
const currencyCode = currencyDict[params.row.currency.toString()];
|
const currencyCode = String(currencyDict[params.row.currency.toString()]);
|
||||||
let fontColor = `rgb(0,0,0)`;
|
let fontColor = `rgb(0,0,0)`;
|
||||||
|
let premiumPoint = 0;
|
||||||
if (params.row.type === 0) {
|
if (params.row.type === 0) {
|
||||||
var premiumPoint = params.row.premium / buyOutstandingPremium;
|
premiumPoint = params.row.premium / buyOutstandingPremium;
|
||||||
premiumPoint = premiumPoint < 0 ? 0 : premiumPoint > 1 ? 1 : premiumPoint;
|
premiumPoint = premiumPoint < 0 ? 0 : premiumPoint > 1 ? 1 : premiumPoint;
|
||||||
fontColor = premiumColor(
|
fontColor = premiumColor(
|
||||||
theme.palette.text.primary,
|
theme.palette.text.primary,
|
||||||
@ -388,7 +484,7 @@ const BookTable = ({
|
|||||||
premiumPoint,
|
premiumPoint,
|
||||||
);
|
);
|
||||||
} else {
|
} else {
|
||||||
var premiumPoint = (sellStandardPremium - params.row.premium) / sellStandardPremium;
|
premiumPoint = (sellStandardPremium - params.row.premium) / sellStandardPremium;
|
||||||
premiumPoint = premiumPoint < 0 ? 0 : premiumPoint > 1 ? 1 : premiumPoint;
|
premiumPoint = premiumPoint < 0 ? 0 : premiumPoint > 1 ? 1 : premiumPoint;
|
||||||
fontColor = premiumColor(
|
fontColor = premiumColor(
|
||||||
theme.palette.text.primary,
|
theme.palette.text.primary,
|
||||||
@ -401,11 +497,16 @@ const BookTable = ({
|
|||||||
<Tooltip
|
<Tooltip
|
||||||
placement='left'
|
placement='left'
|
||||||
enterTouchDelay={0}
|
enterTouchDelay={0}
|
||||||
title={pn(params.row.price) + ' ' + currencyCode + '/BTC'}
|
title={`${pn(params.row.price)} ${currencyCode}/BTC`}
|
||||||
>
|
>
|
||||||
<div style={{ cursor: 'pointer' }}>
|
<div
|
||||||
|
style={{ cursor: 'pointer' }}
|
||||||
|
onClick={() => {
|
||||||
|
onOrderClicked(params.row.id, params.row.coordinatorShortAlias);
|
||||||
|
}}
|
||||||
|
>
|
||||||
<Typography variant='inherit' color={fontColor} sx={{ fontWeight }}>
|
<Typography variant='inherit' color={fontColor} sx={{ fontWeight }}>
|
||||||
{parseFloat(parseFloat(params.row.premium).toFixed(4)) + '%'}
|
{`${parseFloat(parseFloat(params.row.premium).toFixed(4))}%`}
|
||||||
</Typography>
|
</Typography>
|
||||||
</div>
|
</div>
|
||||||
</Tooltip>
|
</Tooltip>
|
||||||
@ -425,7 +526,16 @@ const BookTable = ({
|
|||||||
renderCell: (params: any) => {
|
renderCell: (params: any) => {
|
||||||
const hours = Math.round(params.row.escrow_duration / 3600);
|
const hours = Math.round(params.row.escrow_duration / 3600);
|
||||||
const minutes = Math.round((params.row.escrow_duration - hours * 3600) / 60);
|
const minutes = Math.round((params.row.escrow_duration - hours * 3600) / 60);
|
||||||
return <div style={{ cursor: 'pointer' }}>{hours > 0 ? `${hours}h` : `${minutes}m`}</div>;
|
return (
|
||||||
|
<div
|
||||||
|
style={{ cursor: 'pointer' }}
|
||||||
|
onClick={() => {
|
||||||
|
onOrderClicked(params.row.id, params.row.coordinatorShortAlias);
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
{hours > 0 ? `${hours}h` : `${minutes}m`}
|
||||||
|
</div>
|
||||||
|
);
|
||||||
},
|
},
|
||||||
};
|
};
|
||||||
}, []);
|
}, []);
|
||||||
@ -443,7 +553,12 @@ const BookTable = ({
|
|||||||
const hours = Math.round(timeToExpiry / (3600 * 1000));
|
const hours = Math.round(timeToExpiry / (3600 * 1000));
|
||||||
const minutes = Math.round((timeToExpiry - hours * (3600 * 1000)) / 60000);
|
const minutes = Math.round((timeToExpiry - hours * (3600 * 1000)) / 60000);
|
||||||
return (
|
return (
|
||||||
<Box sx={{ position: 'relative', display: 'inline-flex', left: '0.3em' }}>
|
<Box
|
||||||
|
sx={{ position: 'relative', display: 'inline-flex', left: '0.3em' }}
|
||||||
|
onClick={() => {
|
||||||
|
onOrderClicked(params.row.id, params.row.coordinatorShortAlias);
|
||||||
|
}}
|
||||||
|
>
|
||||||
<CircularProgress
|
<CircularProgress
|
||||||
value={percent}
|
value={percent}
|
||||||
color={percent < 15 ? 'error' : percent < 30 ? 'warning' : 'success'}
|
color={percent < 15 ? 'error' : percent < 30 ? 'warning' : 'success'}
|
||||||
@ -481,7 +596,12 @@ const BookTable = ({
|
|||||||
width: width * fontSize,
|
width: width * fontSize,
|
||||||
renderCell: (params: any) => {
|
renderCell: (params: any) => {
|
||||||
return (
|
return (
|
||||||
<div style={{ cursor: 'pointer' }}>
|
<div
|
||||||
|
style={{ cursor: 'pointer' }}
|
||||||
|
onClick={() => {
|
||||||
|
onOrderClicked(params.row.id, params.row.coordinatorShortAlias);
|
||||||
|
}}
|
||||||
|
>
|
||||||
{params.row.satoshis_now > 1000000
|
{params.row.satoshis_now > 1000000
|
||||||
? `${pn(Math.round(params.row.satoshis_now / 10000) / 100)} M`
|
? `${pn(Math.round(params.row.satoshis_now / 10000) / 100)} M`
|
||||||
: `${pn(Math.round(params.row.satoshis_now / 1000))} K`}
|
: `${pn(Math.round(params.row.satoshis_now / 1000))} K`}
|
||||||
@ -498,9 +618,14 @@ const BookTable = ({
|
|||||||
width: width * fontSize,
|
width: width * fontSize,
|
||||||
renderCell: (params: any) => {
|
renderCell: (params: any) => {
|
||||||
return (
|
return (
|
||||||
<div style={{ cursor: 'pointer' }}>
|
<div
|
||||||
|
style={{ cursor: 'pointer' }}
|
||||||
|
onClick={() => {
|
||||||
|
onOrderClicked(params.row.id, params.row.coordinatorShortAlias);
|
||||||
|
}}
|
||||||
|
>
|
||||||
<Typography variant='caption' color='text.secondary'>
|
<Typography variant='caption' color='text.secondary'>
|
||||||
{`#${params.row.id}`}
|
{`#${String(params.row.id)}`}
|
||||||
</Typography>
|
</Typography>
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
@ -515,7 +640,14 @@ const BookTable = ({
|
|||||||
type: 'number',
|
type: 'number',
|
||||||
width: width * fontSize,
|
width: width * fontSize,
|
||||||
renderCell: (params: any) => {
|
renderCell: (params: any) => {
|
||||||
return <div style={{ cursor: 'pointer' }}>{`${Number(params.row.bond_size)}%`}</div>;
|
return (
|
||||||
|
<div
|
||||||
|
style={{ cursor: 'pointer' }}
|
||||||
|
onClick={() => {
|
||||||
|
onOrderClicked(params.row.id, params.row.coordinatorShortAlias);
|
||||||
|
}}
|
||||||
|
>{`${Number(params.row.bond_size)}%`}</div>
|
||||||
|
);
|
||||||
},
|
},
|
||||||
};
|
};
|
||||||
}, []);
|
}, []);
|
||||||
@ -524,7 +656,7 @@ const BookTable = ({
|
|||||||
return {
|
return {
|
||||||
amount: {
|
amount: {
|
||||||
priority: 1,
|
priority: 1,
|
||||||
order: 4,
|
order: 5,
|
||||||
normal: {
|
normal: {
|
||||||
width: fav.mode === 'swap' ? 9.5 : 6.5,
|
width: fav.mode === 'swap' ? 9.5 : 6.5,
|
||||||
object: amountObj,
|
object: amountObj,
|
||||||
@ -532,7 +664,7 @@ const BookTable = ({
|
|||||||
},
|
},
|
||||||
currency: {
|
currency: {
|
||||||
priority: 2,
|
priority: 2,
|
||||||
order: 5,
|
order: 6,
|
||||||
normal: {
|
normal: {
|
||||||
width: fav.mode === 'swap' ? 0 : 5.9,
|
width: fav.mode === 'swap' ? 0 : 5.9,
|
||||||
object: currencyObj,
|
object: currencyObj,
|
||||||
@ -540,7 +672,7 @@ const BookTable = ({
|
|||||||
},
|
},
|
||||||
premium: {
|
premium: {
|
||||||
priority: 3,
|
priority: 3,
|
||||||
order: 11,
|
order: 12,
|
||||||
normal: {
|
normal: {
|
||||||
width: 6,
|
width: 6,
|
||||||
object: premiumObj,
|
object: premiumObj,
|
||||||
@ -548,7 +680,7 @@ const BookTable = ({
|
|||||||
},
|
},
|
||||||
payment_method: {
|
payment_method: {
|
||||||
priority: 4,
|
priority: 4,
|
||||||
order: 6,
|
order: 7,
|
||||||
normal: {
|
normal: {
|
||||||
width: 12.85,
|
width: 12.85,
|
||||||
object: paymentObj,
|
object: paymentObj,
|
||||||
@ -570,9 +702,17 @@ const BookTable = ({
|
|||||||
object: robotSmallObj,
|
object: robotSmallObj,
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
|
coordinatorShortAlias: {
|
||||||
|
priority: 5,
|
||||||
|
order: 3,
|
||||||
|
normal: {
|
||||||
|
width: 4.1,
|
||||||
|
object: coordinatorObj,
|
||||||
|
},
|
||||||
|
},
|
||||||
price: {
|
price: {
|
||||||
priority: 6,
|
priority: 6,
|
||||||
order: 10,
|
order: 11,
|
||||||
normal: {
|
normal: {
|
||||||
width: 10,
|
width: 10,
|
||||||
object: priceObj,
|
object: priceObj,
|
||||||
@ -580,7 +720,7 @@ const BookTable = ({
|
|||||||
},
|
},
|
||||||
expires_at: {
|
expires_at: {
|
||||||
priority: 7,
|
priority: 7,
|
||||||
order: 7,
|
order: 8,
|
||||||
normal: {
|
normal: {
|
||||||
width: 5,
|
width: 5,
|
||||||
object: expiryObj,
|
object: expiryObj,
|
||||||
@ -588,7 +728,7 @@ const BookTable = ({
|
|||||||
},
|
},
|
||||||
escrow_duration: {
|
escrow_duration: {
|
||||||
priority: 8,
|
priority: 8,
|
||||||
order: 8,
|
order: 9,
|
||||||
normal: {
|
normal: {
|
||||||
width: 4.8,
|
width: 4.8,
|
||||||
object: timerObj,
|
object: timerObj,
|
||||||
@ -596,7 +736,7 @@ const BookTable = ({
|
|||||||
},
|
},
|
||||||
satoshis_now: {
|
satoshis_now: {
|
||||||
priority: 9,
|
priority: 9,
|
||||||
order: 9,
|
order: 10,
|
||||||
normal: {
|
normal: {
|
||||||
width: 6,
|
width: 6,
|
||||||
object: satoshisObj,
|
object: satoshisObj,
|
||||||
@ -612,7 +752,7 @@ const BookTable = ({
|
|||||||
},
|
},
|
||||||
bond_size: {
|
bond_size: {
|
||||||
priority: 11,
|
priority: 11,
|
||||||
order: 10,
|
order: 11,
|
||||||
normal: {
|
normal: {
|
||||||
width: 4.2,
|
width: 4.2,
|
||||||
object: bondObj,
|
object: bondObj,
|
||||||
@ -620,7 +760,7 @@ const BookTable = ({
|
|||||||
},
|
},
|
||||||
id: {
|
id: {
|
||||||
priority: 12,
|
priority: 12,
|
||||||
order: 12,
|
order: 13,
|
||||||
normal: {
|
normal: {
|
||||||
width: 4.8,
|
width: 4.8,
|
||||||
object: idObj,
|
object: idObj,
|
||||||
@ -629,7 +769,10 @@ const BookTable = ({
|
|||||||
};
|
};
|
||||||
}, [fav.mode]);
|
}, [fav.mode]);
|
||||||
|
|
||||||
const filteredColumns = function (maxWidth: number) {
|
const filteredColumns = function (maxWidth: number): {
|
||||||
|
columns: Array<GridColDef<GridValidRowModel>>;
|
||||||
|
width: number;
|
||||||
|
} {
|
||||||
const useSmall = maxWidth < 70;
|
const useSmall = maxWidth < 70;
|
||||||
const selectedColumns: object[] = [];
|
const selectedColumns: object[] = [];
|
||||||
const columnVisibilityModel: GridColumnVisibilityModel = {};
|
const columnVisibilityModel: GridColumnVisibilityModel = {};
|
||||||
@ -641,8 +784,10 @@ const BookTable = ({
|
|||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
|
|
||||||
const colWidth = useSmall && value.small ? value.small.width : value.normal.width;
|
const colWidth = Number(
|
||||||
const colObject = useSmall && value.small ? value.small.object : value.normal.object;
|
useSmall && Boolean(value.small) ? value.small.width : value.normal.width,
|
||||||
|
);
|
||||||
|
const colObject = useSmall && Boolean(value.small) ? value.small.object : value.normal.object;
|
||||||
|
|
||||||
if (width + colWidth < maxWidth || selectedColumns.length < 2) {
|
if (width + colWidth < maxWidth || selectedColumns.length < 2) {
|
||||||
width = width + colWidth;
|
width = width + colWidth;
|
||||||
@ -659,19 +804,19 @@ const BookTable = ({
|
|||||||
return first[1] - second[1];
|
return first[1] - second[1];
|
||||||
});
|
});
|
||||||
|
|
||||||
const columns = selectedColumns.map(function (item) {
|
const columns: Array<GridColDef<GridValidRowModel>> = selectedColumns.map(function (item) {
|
||||||
return item[0];
|
return item[0];
|
||||||
});
|
});
|
||||||
|
|
||||||
setColumnVisibilityModel(columnVisibilityModel);
|
setColumnVisibilityModel(columnVisibilityModel);
|
||||||
return [columns, width * 0.875 + 0.15];
|
return { columns, width: width * 0.875 + 0.15 };
|
||||||
};
|
};
|
||||||
|
|
||||||
const [columns, width] = useMemo(() => {
|
const { columns, width } = useMemo(() => {
|
||||||
return filteredColumns(fullscreen ? fullWidth : maxWidth);
|
return filteredColumns(fullscreen ? fullWidth : maxWidth);
|
||||||
}, [maxWidth, fullscreen, fullWidth, fav.mode]);
|
}, [maxWidth, fullscreen, fullWidth, fav.mode]);
|
||||||
|
|
||||||
const Footer = function () {
|
const Footer = function (): JSX.Element {
|
||||||
return (
|
return (
|
||||||
<Grid container alignItems='center' direction='row' justifyContent='space-between'>
|
<Grid container alignItems='center' direction='row' justifyContent='space-between'>
|
||||||
<Grid item>
|
<Grid item>
|
||||||
@ -686,11 +831,7 @@ const BookTable = ({
|
|||||||
</IconButton>
|
</IconButton>
|
||||||
</Grid>
|
</Grid>
|
||||||
<Grid item xs={6}>
|
<Grid item xs={6}>
|
||||||
<IconButton
|
<IconButton onClick={() => federation.update()}>
|
||||||
onClick={() => {
|
|
||||||
fetchBook();
|
|
||||||
}}
|
|
||||||
>
|
|
||||||
<Refresh />
|
<Refresh />
|
||||||
</IconButton>
|
</IconButton>
|
||||||
</Grid>
|
</Grid>
|
||||||
@ -712,7 +853,7 @@ const BookTable = ({
|
|||||||
Toolbar?: (props: any) => JSX.Element;
|
Toolbar?: (props: any) => JSX.Element;
|
||||||
}
|
}
|
||||||
|
|
||||||
const NoResultsOverlay = function () {
|
const NoResultsOverlay = function (): JSX.Element {
|
||||||
return (
|
return (
|
||||||
<Grid
|
<Grid
|
||||||
container
|
container
|
||||||
@ -723,14 +864,14 @@ const BookTable = ({
|
|||||||
>
|
>
|
||||||
<Grid item>
|
<Grid item>
|
||||||
<Typography align='center' component='h5' variant='h5'>
|
<Typography align='center' component='h5' variant='h5'>
|
||||||
{fav.type == 0
|
{fav.type === 0
|
||||||
? t('No orders found to sell BTC for {{currencyCode}}', {
|
? t('No orders found to sell BTC for {{currencyCode}}', {
|
||||||
currencyCode:
|
currencyCode:
|
||||||
fav.currency == 0 ? t('ANY') : currencyDict[fav.currency.toString()],
|
fav.currency === 0 ? t('ANY') : currencyDict[fav.currency.toString()],
|
||||||
})
|
})
|
||||||
: t('No orders found to buy BTC for {{currencyCode}}', {
|
: t('No orders found to buy BTC for {{currencyCode}}', {
|
||||||
currencyCode:
|
currencyCode:
|
||||||
fav.currency == 0 ? t('ANY') : currencyDict[fav.currency.toString()],
|
fav.currency === 0 ? t('ANY') : currencyDict[fav.currency.toString()],
|
||||||
})}
|
})}
|
||||||
</Typography>
|
</Typography>
|
||||||
</Grid>
|
</Grid>
|
||||||
@ -786,7 +927,8 @@ const BookTable = ({
|
|||||||
rowHeight={3.714 * theme.typography.fontSize}
|
rowHeight={3.714 * theme.typography.fontSize}
|
||||||
headerHeight={3.25 * theme.typography.fontSize}
|
headerHeight={3.25 * theme.typography.fontSize}
|
||||||
rows={filteredOrders}
|
rows={filteredOrders}
|
||||||
loading={book.loading}
|
getRowId={(params: PublicOrder) => `${String(params.coordinatorShortAlias)}/${params.id}`}
|
||||||
|
loading={federation.loading}
|
||||||
columns={columns}
|
columns={columns}
|
||||||
columnVisibilityModel={columnVisibilityModel}
|
columnVisibilityModel={columnVisibilityModel}
|
||||||
onColumnVisibilityModelChange={(newColumnVisibilityModel) => {
|
onColumnVisibilityModelChange={(newColumnVisibilityModel) => {
|
||||||
@ -802,15 +944,16 @@ const BookTable = ({
|
|||||||
paymentMethod: paymentMethods,
|
paymentMethod: paymentMethods,
|
||||||
setPaymentMethods,
|
setPaymentMethods,
|
||||||
},
|
},
|
||||||
|
loadingOverlay: {
|
||||||
|
variant: 'determinate',
|
||||||
|
value: loadingProgress,
|
||||||
|
},
|
||||||
}}
|
}}
|
||||||
paginationModel={paginationModel}
|
paginationModel={paginationModel}
|
||||||
pageSizeOptions={width < 22 ? [] : [0, defaultPageSize, defaultPageSize * 2, 50, 100]}
|
pageSizeOptions={width < 22 ? [] : [0, defaultPageSize, defaultPageSize * 2, 50, 100]}
|
||||||
onPaginationModelChange={(newPaginationModel) => {
|
onPaginationModelChange={(newPaginationModel) => {
|
||||||
setPaginationModel(newPaginationModel);
|
setPaginationModel(newPaginationModel);
|
||||||
}}
|
}}
|
||||||
onRowClick={(params: any) => {
|
|
||||||
onOrderClicked(params.row.id);
|
|
||||||
}}
|
|
||||||
/>
|
/>
|
||||||
</Paper>
|
</Paper>
|
||||||
);
|
);
|
||||||
@ -823,7 +966,7 @@ const BookTable = ({
|
|||||||
rowHeight={3.714 * theme.typography.fontSize}
|
rowHeight={3.714 * theme.typography.fontSize}
|
||||||
headerHeight={3.25 * theme.typography.fontSize}
|
headerHeight={3.25 * theme.typography.fontSize}
|
||||||
rows={filteredOrders}
|
rows={filteredOrders}
|
||||||
loading={book.loading}
|
loading={federation.loading}
|
||||||
columns={columns}
|
columns={columns}
|
||||||
hideFooter={!showFooter}
|
hideFooter={!showFooter}
|
||||||
components={gridComponents}
|
components={gridComponents}
|
||||||
@ -845,9 +988,6 @@ const BookTable = ({
|
|||||||
onPaginationModelChange={(newPaginationModel) => {
|
onPaginationModelChange={(newPaginationModel) => {
|
||||||
setPaginationModel(newPaginationModel);
|
setPaginationModel(newPaginationModel);
|
||||||
}}
|
}}
|
||||||
onRowClick={(params: any) => {
|
|
||||||
onOrderClicked(params.row.id);
|
|
||||||
}}
|
|
||||||
/>
|
/>
|
||||||
</Paper>
|
</Paper>
|
||||||
</Dialog>
|
</Dialog>
|
||||||
|
@ -24,15 +24,16 @@ import { type PublicOrder, type Order } from '../../../models';
|
|||||||
import { matchMedian } from '../../../utils';
|
import { matchMedian } from '../../../utils';
|
||||||
import currencyDict from '../../../../static/assets/currencies.json';
|
import currencyDict from '../../../../static/assets/currencies.json';
|
||||||
import getNivoScheme from '../NivoScheme';
|
import getNivoScheme from '../NivoScheme';
|
||||||
import { type UseAppStoreType, AppContext } from '../../../contexts/AppContext';
|
|
||||||
import OrderTooltip from '../helpers/OrderTooltip';
|
import OrderTooltip from '../helpers/OrderTooltip';
|
||||||
|
import { type UseAppStoreType, AppContext } from '../../../contexts/AppContext';
|
||||||
|
import { FederationContext, UseFederationStoreType } from '../../../contexts/FederationContext';
|
||||||
|
|
||||||
interface DepthChartProps {
|
interface DepthChartProps {
|
||||||
maxWidth: number;
|
maxWidth: number;
|
||||||
maxHeight: number;
|
maxHeight: number;
|
||||||
fillContainer?: boolean;
|
fillContainer?: boolean;
|
||||||
elevation?: number;
|
elevation?: number;
|
||||||
onOrderClicked?: (id: number) => void;
|
onOrderClicked?: (id: number, shortAlias: string) => void;
|
||||||
}
|
}
|
||||||
|
|
||||||
const DepthChart: React.FC<DepthChartProps> = ({
|
const DepthChart: React.FC<DepthChartProps> = ({
|
||||||
@ -42,10 +43,12 @@ const DepthChart: React.FC<DepthChartProps> = ({
|
|||||||
elevation = 6,
|
elevation = 6,
|
||||||
onOrderClicked = () => null,
|
onOrderClicked = () => null,
|
||||||
}) => {
|
}) => {
|
||||||
const { book, fav, info, limits, baseUrl } = useContext<UseAppStoreType>(AppContext);
|
const { fav } = useContext<UseAppStoreType>(AppContext);
|
||||||
|
const { federation, coordinatorUpdatedAt, federationUpdatedAt } =
|
||||||
|
useContext<UseFederationStoreType>(FederationContext);
|
||||||
const { t } = useTranslation();
|
const { t } = useTranslation();
|
||||||
const theme = useTheme();
|
const theme = useTheme();
|
||||||
const [enrichedOrders, setEnrichedOrders] = useState<Order[]>([]);
|
const [enrichedOrders, setEnrichedOrders] = useState<PublicOrder[]>([]);
|
||||||
const [series, setSeries] = useState<Serie[]>([]);
|
const [series, setSeries] = useState<Serie[]>([]);
|
||||||
const [rangeSteps, setRangeSteps] = useState<number>(8);
|
const [rangeSteps, setRangeSteps] = useState<number>(8);
|
||||||
const [xRange, setXRange] = useState<number>(8);
|
const [xRange, setXRange] = useState<number>(8);
|
||||||
@ -61,18 +64,21 @@ const DepthChart: React.FC<DepthChartProps> = ({
|
|||||||
}, [fav.currency]);
|
}, [fav.currency]);
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
if (Object.keys(limits.list).length > 0) {
|
if (federation.book.length > 0) {
|
||||||
const enriched = book.orders.map((order) => {
|
const enriched = federation.book.map((order) => {
|
||||||
// We need to transform all currencies to the same base (ex. USD), we don't have the exchange rate
|
// We need to transform all currencies to the same base (ex. USD), we don't have the exchange rate
|
||||||
// for EUR -> USD, but we know the rate of both to BTC, so we get advantage of it and apply a
|
// for EUR -> USD, but we know the rate of both to BTC, so we get advantage of it and apply a
|
||||||
// simple rule of three
|
// simple rule of three
|
||||||
order.base_amount =
|
if (order.coordinatorShortAlias) {
|
||||||
(order.price * limits.list[currencyCode].price) / limits.list[order.currency].price;
|
const limits = federation.getCoordinator(order.coordinatorShortAlias).limits;
|
||||||
|
order.base_amount =
|
||||||
|
(order.price * limits[currencyCode].price) / limits[order.currency].price;
|
||||||
|
}
|
||||||
return order;
|
return order;
|
||||||
});
|
});
|
||||||
setEnrichedOrders(enriched);
|
setEnrichedOrders(enriched);
|
||||||
}
|
}
|
||||||
}, [limits.list, book.orders, currencyCode]);
|
}, [coordinatorUpdatedAt, currencyCode]);
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
if (enrichedOrders.length > 0) {
|
if (enrichedOrders.length > 0) {
|
||||||
@ -82,10 +88,10 @@ const DepthChart: React.FC<DepthChartProps> = ({
|
|||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
if (xType === 'base_amount') {
|
if (xType === 'base_amount') {
|
||||||
const prices: number[] = enrichedOrders.map((order) => order?.base_amount || 0);
|
const prices: number[] = enrichedOrders.map((order) => order?.base_amount ?? 0);
|
||||||
|
|
||||||
const medianValue = ~~matchMedian(prices);
|
const medianValue = ~~matchMedian(prices);
|
||||||
const maxValue = prices.sort((a, b) => b - a).slice(0, 1)[0] || 1500;
|
const maxValue = prices.sort((a, b) => b - a).slice(0, 1)[0] ?? 1500;
|
||||||
const maxRange = maxValue - medianValue;
|
const maxRange = maxValue - medianValue;
|
||||||
const rangeSteps = maxRange / 10;
|
const rangeSteps = maxRange / 10;
|
||||||
|
|
||||||
@ -93,35 +99,35 @@ const DepthChart: React.FC<DepthChartProps> = ({
|
|||||||
setXRange(maxRange);
|
setXRange(maxRange);
|
||||||
setRangeSteps(rangeSteps);
|
setRangeSteps(rangeSteps);
|
||||||
} else {
|
} else {
|
||||||
if (info.last_day_nonkyc_btc_premium === undefined) {
|
if (federation.exchange.info?.last_day_nonkyc_btc_premium === undefined) {
|
||||||
const premiums: number[] = enrichedOrders.map((order) => order?.premium || 0);
|
const premiums: number[] = enrichedOrders.map((order) => order?.premium ?? 0);
|
||||||
setCenter(~~matchMedian(premiums));
|
setCenter(~~matchMedian(premiums));
|
||||||
} else {
|
} else {
|
||||||
setCenter(info.last_day_nonkyc_btc_premium);
|
setCenter(federation.exchange.info?.last_day_nonkyc_btc_premium);
|
||||||
}
|
}
|
||||||
setXRange(8);
|
setXRange(8);
|
||||||
setRangeSteps(0.5);
|
setRangeSteps(0.5);
|
||||||
}
|
}
|
||||||
}, [enrichedOrders, xType, info.last_day_nonkyc_btc_premium, currencyCode]);
|
}, [enrichedOrders, xType, federationUpdatedAt, currencyCode]);
|
||||||
|
|
||||||
const generateSeries: () => void = () => {
|
const generateSeries: () => void = () => {
|
||||||
const sortedOrders: PublicOrder[] =
|
const sortedOrders: PublicOrder[] =
|
||||||
xType === 'base_amount'
|
xType === 'base_amount'
|
||||||
? enrichedOrders.sort(
|
? enrichedOrders.sort(
|
||||||
(order1, order2) => (order1?.base_amount || 0) - (order2?.base_amount || 0),
|
(order1, order2) => (order1?.base_amount ?? 0) - (order2?.base_amount ?? 0),
|
||||||
)
|
)
|
||||||
: enrichedOrders.sort((order1, order2) => order1.premium - order2.premium);
|
: enrichedOrders.sort((order1, order2) => order1.premium - order2.premium);
|
||||||
|
|
||||||
const sortedBuyOrders: PublicOrder[] = sortedOrders
|
const sortedBuyOrders: PublicOrder[] = sortedOrders
|
||||||
.filter((order) => order.type == 0)
|
.filter((order) => order.type === 0)
|
||||||
.reverse();
|
.reverse();
|
||||||
const sortedSellOrders: PublicOrder[] = sortedOrders.filter((order) => order.type == 1);
|
const sortedSellOrders: PublicOrder[] = sortedOrders.filter((order) => order.type === 1);
|
||||||
|
|
||||||
const buySerie: Datum[] = generateSerie(sortedBuyOrders);
|
const buySerie: Datum[] = generateSerie(sortedBuyOrders);
|
||||||
const sellSerie: Datum[] = generateSerie(sortedSellOrders);
|
const sellSerie: Datum[] = generateSerie(sortedSellOrders);
|
||||||
|
|
||||||
const maxX: number = (center || 0) + xRange;
|
const maxX: number = (center ?? 0) + xRange;
|
||||||
const minX: number = (center || 0) - xRange;
|
const minX: number = (center ?? 0) - xRange;
|
||||||
|
|
||||||
setSeries([
|
setSeries([
|
||||||
{
|
{
|
||||||
@ -136,7 +142,7 @@ const DepthChart: React.FC<DepthChartProps> = ({
|
|||||||
};
|
};
|
||||||
|
|
||||||
const generateSerie = (orders: PublicOrder[]): Datum[] => {
|
const generateSerie = (orders: PublicOrder[]): Datum[] => {
|
||||||
if (center == undefined) {
|
if (center === undefined) {
|
||||||
return [];
|
return [];
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -144,7 +150,7 @@ const DepthChart: React.FC<DepthChartProps> = ({
|
|||||||
let serie: Datum[] = [];
|
let serie: Datum[] = [];
|
||||||
orders.forEach((order) => {
|
orders.forEach((order) => {
|
||||||
const lastSumOrders = sumOrders;
|
const lastSumOrders = sumOrders;
|
||||||
sumOrders += (order.satoshis_now || 0) / 100000000;
|
sumOrders += (order.satoshis_now ?? 0) / 100000000;
|
||||||
const datum: Datum[] = [
|
const datum: Datum[] = [
|
||||||
{
|
{
|
||||||
// Vertical Line
|
// Vertical Line
|
||||||
@ -169,7 +175,7 @@ const DepthChart: React.FC<DepthChartProps> = ({
|
|||||||
};
|
};
|
||||||
|
|
||||||
const closeSerie = (serie: Datum[], limitBottom: number, limitTop: number): Datum[] => {
|
const closeSerie = (serie: Datum[], limitBottom: number, limitTop: number): Datum[] => {
|
||||||
if (serie.length == 0) {
|
if (serie.length === 0) {
|
||||||
return [];
|
return [];
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -197,11 +203,11 @@ const DepthChart: React.FC<DepthChartProps> = ({
|
|||||||
d={props.lineGenerator([
|
d={props.lineGenerator([
|
||||||
{
|
{
|
||||||
y: 0,
|
y: 0,
|
||||||
x: props.xScale(center || 0),
|
x: props.xScale(center ?? 0),
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
y: props.innerHeight,
|
y: props.innerHeight,
|
||||||
x: props.xScale(center || 0),
|
x: props.xScale(center ?? 0),
|
||||||
},
|
},
|
||||||
])}
|
])}
|
||||||
fill='none'
|
fill='none'
|
||||||
@ -225,7 +231,7 @@ const DepthChart: React.FC<DepthChartProps> = ({
|
|||||||
};
|
};
|
||||||
const formatAxisY = (value: number): string => `${value}BTC`;
|
const formatAxisY = (value: number): string => `${value}BTC`;
|
||||||
const handleOnClick: PointMouseHandler = (point: Point) => {
|
const handleOnClick: PointMouseHandler = (point: Point) => {
|
||||||
onOrderClicked(point.data?.order?.id);
|
onOrderClicked(point.data?.order?.id, point.data?.order?.coordinatorShortAlias);
|
||||||
};
|
};
|
||||||
|
|
||||||
const em = theme.typography.fontSize;
|
const em = theme.typography.fontSize;
|
||||||
@ -239,7 +245,7 @@ const DepthChart: React.FC<DepthChartProps> = ({
|
|||||||
}
|
}
|
||||||
>
|
>
|
||||||
<Paper variant='outlined' style={{ width: '100%', height: '100%' }}>
|
<Paper variant='outlined' style={{ width: '100%', height: '100%' }}>
|
||||||
{center == undefined || enrichedOrders.length < 1 ? (
|
{center === undefined || enrichedOrders.length < 1 ? (
|
||||||
<div
|
<div
|
||||||
style={{
|
style={{
|
||||||
display: 'flex',
|
display: 'flex',
|
||||||
@ -299,7 +305,7 @@ const DepthChart: React.FC<DepthChartProps> = ({
|
|||||||
<Grid item>
|
<Grid item>
|
||||||
<Box justifyContent='center'>
|
<Box justifyContent='center'>
|
||||||
{xType === 'base_amount'
|
{xType === 'base_amount'
|
||||||
? `${center} ${currencyDict[currencyCode]}`
|
? `${center} ${String(currencyDict[currencyCode])}`
|
||||||
: `${center}%`}
|
: `${center}%`}
|
||||||
</Box>
|
</Box>
|
||||||
</Grid>
|
</Grid>
|
||||||
|
@ -12,9 +12,10 @@ import {
|
|||||||
Tooltip,
|
Tooltip,
|
||||||
} from '@mui/material';
|
} from '@mui/material';
|
||||||
import Map from '../../Map';
|
import Map from '../../Map';
|
||||||
import { AppContext, UseAppStoreType } from '../../../contexts/AppContext';
|
import { AppContext, type UseAppStoreType } from '../../../contexts/AppContext';
|
||||||
import { PhotoSizeSelectActual } from '@mui/icons-material';
|
import { PhotoSizeSelectActual } from '@mui/icons-material';
|
||||||
import { useTranslation } from 'react-i18next';
|
import { useTranslation } from 'react-i18next';
|
||||||
|
import { FederationContext, UseFederationStoreType } from '../../../contexts/FederationContext';
|
||||||
|
|
||||||
interface MapChartProps {
|
interface MapChartProps {
|
||||||
maxWidth: number;
|
maxWidth: number;
|
||||||
@ -32,7 +33,7 @@ const MapChart: React.FC<MapChartProps> = ({
|
|||||||
onOrderClicked = () => {},
|
onOrderClicked = () => {},
|
||||||
}) => {
|
}) => {
|
||||||
const { t } = useTranslation();
|
const { t } = useTranslation();
|
||||||
const { book } = useContext<UseAppStoreType>(AppContext);
|
const { federation } = useContext<UseFederationStoreType>(FederationContext);
|
||||||
const [useTiles, setUseTiles] = useState<boolean>(false);
|
const [useTiles, setUseTiles] = useState<boolean>(false);
|
||||||
const [acceptedTilesWarning, setAcceptedTilesWarning] = useState<boolean>(false);
|
const [acceptedTilesWarning, setAcceptedTilesWarning] = useState<boolean>(false);
|
||||||
const [openWarningDialog, setOpenWarningDialog] = useState<boolean>(false);
|
const [openWarningDialog, setOpenWarningDialog] = useState<boolean>(false);
|
||||||
@ -81,7 +82,7 @@ const MapChart: React.FC<MapChartProps> = ({
|
|||||||
</DialogActions>
|
</DialogActions>
|
||||||
</Dialog>
|
</Dialog>
|
||||||
<Paper variant='outlined' style={{ width: '100%', height: '100%', justifyContent: 'center' }}>
|
<Paper variant='outlined' style={{ width: '100%', height: '100%', justifyContent: 'center' }}>
|
||||||
{false ? (
|
{federation.book.length < 1 ? (
|
||||||
<div
|
<div
|
||||||
style={{
|
style={{
|
||||||
display: 'flex',
|
display: 'flex',
|
||||||
@ -127,7 +128,7 @@ const MapChart: React.FC<MapChartProps> = ({
|
|||||||
</Tooltip>
|
</Tooltip>
|
||||||
</Grid>
|
</Grid>
|
||||||
<div style={{ height: `${height - 3.1}em` }}>
|
<div style={{ height: `${height - 3.1}em` }}>
|
||||||
<Map useTiles={useTiles} orders={book.orders} onOrderClicked={onOrderClicked} />
|
<Map useTiles={useTiles} orders={federation.book} onOrderClicked={onOrderClicked} />
|
||||||
</div>
|
</div>
|
||||||
</>
|
</>
|
||||||
)}
|
)}
|
||||||
|
@ -6,17 +6,24 @@ import { amountToString, statusBadgeColor } from '../../../../utils';
|
|||||||
import currencyDict from '../../../../../static/assets/currencies.json';
|
import currencyDict from '../../../../../static/assets/currencies.json';
|
||||||
import { PaymentStringAsIcons } from '../../../PaymentMethods';
|
import { PaymentStringAsIcons } from '../../../PaymentMethods';
|
||||||
import { useTranslation } from 'react-i18next';
|
import { useTranslation } from 'react-i18next';
|
||||||
import { AppContext, UseAppStoreType } from '../../../../contexts/AppContext';
|
import { AppContext, type UseAppStoreType } from '../../../../contexts/AppContext';
|
||||||
|
import { FederationContext, UseFederationStoreType } from '../../../../contexts/FederationContext';
|
||||||
|
|
||||||
interface OrderTooltipProps {
|
interface OrderTooltipProps {
|
||||||
order: PublicOrder;
|
order: PublicOrder;
|
||||||
}
|
}
|
||||||
|
|
||||||
const OrderTooltip: React.FC<OrderTooltipProps> = ({ order }) => {
|
const OrderTooltip: React.FC<OrderTooltipProps> = ({ order }) => {
|
||||||
const { baseUrl } = useContext<UseAppStoreType>(AppContext);
|
const { settings, origin } = useContext<UseAppStoreType>(AppContext);
|
||||||
|
const { federation } = useContext<UseFederationStoreType>(FederationContext);
|
||||||
const { t } = useTranslation();
|
const { t } = useTranslation();
|
||||||
|
|
||||||
return order ? (
|
const coordinatorAlias = order?.coordinatorShortAlias;
|
||||||
|
const network = settings.network;
|
||||||
|
const coordinator = federation.getCoordinator(coordinatorAlias);
|
||||||
|
const baseUrl = coordinator?.[network]?.[origin] ?? '';
|
||||||
|
|
||||||
|
return order?.id && baseUrl !== '' ? (
|
||||||
<Paper elevation={12} style={{ padding: 10, width: 250 }}>
|
<Paper elevation={12} style={{ padding: 10, width: 250 }}>
|
||||||
<Grid container justifyContent='space-between'>
|
<Grid container justifyContent='space-between'>
|
||||||
<Grid item xs={3}>
|
<Grid item xs={3}>
|
||||||
|
@ -22,7 +22,7 @@ interface Props {
|
|||||||
onClose: () => void;
|
onClose: () => void;
|
||||||
}
|
}
|
||||||
|
|
||||||
const InfoDialog = ({ maxAmount, open, onClose }: Props): JSX.Element => {
|
const AboutDialog = ({ maxAmount, open, onClose }: Props): JSX.Element => {
|
||||||
const { t } = useTranslation();
|
const { t } = useTranslation();
|
||||||
|
|
||||||
return (
|
return (
|
||||||
@ -263,4 +263,4 @@ const InfoDialog = ({ maxAmount, open, onClose }: Props): JSX.Element => {
|
|||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|
||||||
export default InfoDialog;
|
export default AboutDialog;
|
@ -23,7 +23,7 @@ import ContentCopy from '@mui/icons-material/ContentCopy';
|
|||||||
import ForumIcon from '@mui/icons-material/Forum';
|
import ForumIcon from '@mui/icons-material/Forum';
|
||||||
import { ExportIcon, NewTabIcon } from '../Icons';
|
import { ExportIcon, NewTabIcon } from '../Icons';
|
||||||
|
|
||||||
function CredentialTextfield(props) {
|
function CredentialTextfield(props): JSX.Element {
|
||||||
return (
|
return (
|
||||||
<Grid item align='center' xs={12}>
|
<Grid item align='center' xs={12}>
|
||||||
<Tooltip placement='top' enterTouchDelay={200} enterDelay={200} title={props.tooltipTitle}>
|
<Tooltip placement='top' enterTouchDelay={200} enterDelay={200} title={props.tooltipTitle}>
|
||||||
@ -58,9 +58,9 @@ interface Props {
|
|||||||
onClose: () => void;
|
onClose: () => void;
|
||||||
orderId: number;
|
orderId: number;
|
||||||
messages: array;
|
messages: array;
|
||||||
own_pub_key: string;
|
ownPubKey: string;
|
||||||
own_enc_priv_key: string;
|
ownEncPrivKey: string;
|
||||||
peer_pub_key: string;
|
peerPubKey: string;
|
||||||
passphrase: string;
|
passphrase: string;
|
||||||
onClickBack: () => void;
|
onClickBack: () => void;
|
||||||
}
|
}
|
||||||
@ -70,9 +70,9 @@ const AuditPGPDialog = ({
|
|||||||
onClose,
|
onClose,
|
||||||
orderId,
|
orderId,
|
||||||
messages,
|
messages,
|
||||||
own_pub_key,
|
ownPubKey,
|
||||||
own_enc_priv_key,
|
ownEncPrivKey,
|
||||||
peer_pub_key,
|
peerPubKey,
|
||||||
passphrase,
|
passphrase,
|
||||||
onClickBack,
|
onClickBack,
|
||||||
}: Props): JSX.Element => {
|
}: Props): JSX.Element => {
|
||||||
@ -104,7 +104,7 @@ const AuditPGPDialog = ({
|
|||||||
'Your PGP public key. Your peer uses it to encrypt messages only you can read.',
|
'Your PGP public key. Your peer uses it to encrypt messages only you can read.',
|
||||||
)}
|
)}
|
||||||
label={t('Your public key')}
|
label={t('Your public key')}
|
||||||
value={own_pub_key}
|
value={ownPubKey}
|
||||||
copiedTitle={t('Copied!')}
|
copiedTitle={t('Copied!')}
|
||||||
/>
|
/>
|
||||||
|
|
||||||
@ -113,7 +113,7 @@ const AuditPGPDialog = ({
|
|||||||
'Your peer PGP public key. You use it to encrypt messages only he can read and to verify your peer signed the incoming messages.',
|
'Your peer PGP public key. You use it to encrypt messages only he can read and to verify your peer signed the incoming messages.',
|
||||||
)}
|
)}
|
||||||
label={t('Peer public key')}
|
label={t('Peer public key')}
|
||||||
value={peer_pub_key}
|
value={peerPubKey}
|
||||||
copiedTitle={t('Copied!')}
|
copiedTitle={t('Copied!')}
|
||||||
/>
|
/>
|
||||||
|
|
||||||
@ -122,7 +122,7 @@ const AuditPGPDialog = ({
|
|||||||
'Your encrypted private key. You use it to decrypt the messages that your peer encrypted for you. You also use it to sign the messages you send.',
|
'Your encrypted private key. You use it to decrypt the messages that your peer encrypted for you. You also use it to sign the messages you send.',
|
||||||
)}
|
)}
|
||||||
label={t('Your encrypted private key')}
|
label={t('Your encrypted private key')}
|
||||||
value={own_enc_priv_key}
|
value={ownEncPrivKey}
|
||||||
copiedTitle={t('Copied!')}
|
copiedTitle={t('Copied!')}
|
||||||
/>
|
/>
|
||||||
|
|
||||||
@ -149,10 +149,10 @@ const AuditPGPDialog = ({
|
|||||||
color='primary'
|
color='primary'
|
||||||
variant='contained'
|
variant='contained'
|
||||||
onClick={() => {
|
onClick={() => {
|
||||||
saveAsJson('keys_' + orderId + '.json', {
|
saveAsJson(`keys_${orderId}.json`, {
|
||||||
own_public_key: own_pub_key,
|
own_public_key: ownPubKey,
|
||||||
peer_public_key: peer_pub_key,
|
peer_public_key: peerPubKey,
|
||||||
encrypted_private_key: own_enc_priv_key,
|
encrypted_private_key: ownEncPrivKey,
|
||||||
passphrase,
|
passphrase,
|
||||||
});
|
});
|
||||||
}}
|
}}
|
||||||
@ -181,7 +181,7 @@ const AuditPGPDialog = ({
|
|||||||
color='primary'
|
color='primary'
|
||||||
variant='contained'
|
variant='contained'
|
||||||
onClick={() => {
|
onClick={() => {
|
||||||
saveAsJson('messages_' + orderId + '.json', messages);
|
saveAsJson(`messages_${orderId}.json`, messages);
|
||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
<div style={{ width: 28, height: 20 }}>
|
<div style={{ width: 28, height: 20 }}>
|
||||||
|
80
frontend/src/components/Dialogs/Client.tsx
Normal file
80
frontend/src/components/Dialogs/Client.tsx
Normal file
@ -0,0 +1,80 @@
|
|||||||
|
import React, { useContext } from 'react';
|
||||||
|
import { useTranslation } from 'react-i18next';
|
||||||
|
|
||||||
|
import {
|
||||||
|
Dialog,
|
||||||
|
DialogContent,
|
||||||
|
Divider,
|
||||||
|
List,
|
||||||
|
ListItemText,
|
||||||
|
ListItem,
|
||||||
|
ListItemIcon,
|
||||||
|
Typography,
|
||||||
|
} from '@mui/material';
|
||||||
|
|
||||||
|
import BoltIcon from '@mui/icons-material/Bolt';
|
||||||
|
import PublicIcon from '@mui/icons-material/Public';
|
||||||
|
import FavoriteIcon from '@mui/icons-material/Favorite';
|
||||||
|
|
||||||
|
import { RoboSatsNoTextIcon } from '../Icons';
|
||||||
|
import { AppContext, type AppContextProps } from '../../contexts/AppContext';
|
||||||
|
|
||||||
|
interface Props {
|
||||||
|
open: boolean;
|
||||||
|
onClose: () => void;
|
||||||
|
}
|
||||||
|
|
||||||
|
const ClientDialog = ({ open = false, onClose }: Props): JSX.Element => {
|
||||||
|
const { t } = useTranslation();
|
||||||
|
const { clientVersion } = useContext<AppContextProps>(AppContext);
|
||||||
|
|
||||||
|
return (
|
||||||
|
<Dialog open={open} onClose={onClose}>
|
||||||
|
<DialogContent>
|
||||||
|
<Typography component='h5' variant='h5'>
|
||||||
|
{t('Client info')}
|
||||||
|
</Typography>
|
||||||
|
|
||||||
|
<List dense>
|
||||||
|
<Divider />
|
||||||
|
|
||||||
|
<ListItem>
|
||||||
|
<ListItemIcon>
|
||||||
|
<RoboSatsNoTextIcon
|
||||||
|
sx={{ width: '1.4em', height: '1.4em', right: '0.2em', position: 'relative' }}
|
||||||
|
/>
|
||||||
|
</ListItemIcon>
|
||||||
|
<ListItemText primary={clientVersion.long} secondary={t('RoboSats client version')} />
|
||||||
|
</ListItem>
|
||||||
|
|
||||||
|
<Divider />
|
||||||
|
<ListItem>
|
||||||
|
<ListItemIcon>
|
||||||
|
<PublicIcon />
|
||||||
|
</ListItemIcon>
|
||||||
|
<ListItemText
|
||||||
|
primary={
|
||||||
|
<div
|
||||||
|
style={{
|
||||||
|
display: 'flex',
|
||||||
|
alignItems: 'center',
|
||||||
|
justifyContent: 'left',
|
||||||
|
flexWrap: 'wrap',
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
<span>{`${t('Made with')} `}</span>
|
||||||
|
<FavoriteIcon sx={{ color: '#ff0000', height: '22px', width: '22px' }} />
|
||||||
|
<span>{` ${t('and')} `}</span>
|
||||||
|
<BoltIcon sx={{ color: '#fcba03', height: '23px', width: '23px' }} />
|
||||||
|
</div>
|
||||||
|
}
|
||||||
|
secondary={t('... somewhere on Earth!')}
|
||||||
|
/>
|
||||||
|
</ListItem>
|
||||||
|
</List>
|
||||||
|
</DialogContent>
|
||||||
|
</Dialog>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
export default ClientDialog;
|
@ -156,7 +156,7 @@ const CommunityDialog = ({ open = false, onClose }: Props): JSX.Element => {
|
|||||||
</ListItemIcon>
|
</ListItemIcon>
|
||||||
|
|
||||||
<ListItemText secondary={t('We are abandoning Telegram! Our old TG groups')}>
|
<ListItemText secondary={t('We are abandoning Telegram! Our old TG groups')}>
|
||||||
<Tooltip title={t('Join RoboSats Spanish speaking community!') || ''}>
|
<Tooltip title={t('Join RoboSats Spanish speaking community!')}>
|
||||||
<IconButton
|
<IconButton
|
||||||
component='a'
|
component='a'
|
||||||
target='_blank'
|
target='_blank'
|
||||||
@ -167,7 +167,7 @@ const CommunityDialog = ({ open = false, onClose }: Props): JSX.Element => {
|
|||||||
</IconButton>
|
</IconButton>
|
||||||
</Tooltip>
|
</Tooltip>
|
||||||
|
|
||||||
<Tooltip title={t('Join RoboSats English speaking community!') || ''}>
|
<Tooltip title={t('Join RoboSats English speaking community!')}>
|
||||||
<IconButton
|
<IconButton
|
||||||
component='a'
|
component='a'
|
||||||
target='_blank'
|
target='_blank'
|
||||||
|
754
frontend/src/components/Dialogs/Coordinator.tsx
Normal file
754
frontend/src/components/Dialogs/Coordinator.tsx
Normal file
@ -0,0 +1,754 @@
|
|||||||
|
import React, { useContext, useState } from 'react';
|
||||||
|
import { useTranslation } from 'react-i18next';
|
||||||
|
|
||||||
|
import {
|
||||||
|
Dialog,
|
||||||
|
DialogContent,
|
||||||
|
Divider,
|
||||||
|
Grid,
|
||||||
|
List,
|
||||||
|
ListItemText,
|
||||||
|
ListItem,
|
||||||
|
ListItemIcon,
|
||||||
|
Typography,
|
||||||
|
IconButton,
|
||||||
|
Tooltip,
|
||||||
|
Link,
|
||||||
|
Box,
|
||||||
|
CircularProgress,
|
||||||
|
Accordion,
|
||||||
|
AccordionDetails,
|
||||||
|
AccordionSummary,
|
||||||
|
} from '@mui/material';
|
||||||
|
|
||||||
|
import {
|
||||||
|
Inventory,
|
||||||
|
Sell,
|
||||||
|
SmartToy,
|
||||||
|
Percent,
|
||||||
|
PriceChange,
|
||||||
|
Book,
|
||||||
|
Reddit,
|
||||||
|
Key,
|
||||||
|
Bolt,
|
||||||
|
Description,
|
||||||
|
Dns,
|
||||||
|
Email,
|
||||||
|
Equalizer,
|
||||||
|
ExpandMore,
|
||||||
|
GitHub,
|
||||||
|
Language,
|
||||||
|
Send,
|
||||||
|
Tag,
|
||||||
|
Twitter,
|
||||||
|
} from '@mui/icons-material';
|
||||||
|
import LinkIcon from '@mui/icons-material/Link';
|
||||||
|
|
||||||
|
import { pn } from '../../utils';
|
||||||
|
import { type Contact, type Coordinator } from '../../models';
|
||||||
|
import RobotAvatar from '../RobotAvatar';
|
||||||
|
import {
|
||||||
|
AmbossIcon,
|
||||||
|
BitcoinSignIcon,
|
||||||
|
RoboSatsNoTextIcon,
|
||||||
|
BadgeFounder,
|
||||||
|
BadgeDevFund,
|
||||||
|
BadgePrivacy,
|
||||||
|
BadgeLoved,
|
||||||
|
BadgeLimits,
|
||||||
|
NostrIcon,
|
||||||
|
} from '../Icons';
|
||||||
|
import { AppContext } from '../../contexts/AppContext';
|
||||||
|
import { systemClient } from '../../services/System';
|
||||||
|
import { type Badges } from '../../models/Coordinator.model';
|
||||||
|
|
||||||
|
interface Props {
|
||||||
|
open: boolean;
|
||||||
|
onClose: () => void;
|
||||||
|
coordinator: Coordinator | null;
|
||||||
|
network: 'mainnet' | 'testnet' | undefined;
|
||||||
|
}
|
||||||
|
|
||||||
|
const ContactButtons = ({
|
||||||
|
nostr,
|
||||||
|
pgp,
|
||||||
|
email,
|
||||||
|
telegram,
|
||||||
|
twitter,
|
||||||
|
matrix,
|
||||||
|
website,
|
||||||
|
reddit,
|
||||||
|
}: Contact): JSX.Element => {
|
||||||
|
const { t } = useTranslation();
|
||||||
|
const [showMatrix, setShowMatrix] = useState<boolean>(false);
|
||||||
|
const [showNostr, setShowNostr] = useState<boolean>(false);
|
||||||
|
|
||||||
|
return (
|
||||||
|
<Grid container direction='row' alignItems='center' justifyContent='center'>
|
||||||
|
{nostr !== undefined && (
|
||||||
|
<Grid item>
|
||||||
|
<Tooltip
|
||||||
|
title={
|
||||||
|
<div>
|
||||||
|
<Typography variant='body2'>
|
||||||
|
{t('...Opening on Nostr gateway. Pubkey copied!')}
|
||||||
|
</Typography>
|
||||||
|
<Typography variant='body2'>
|
||||||
|
<i>{nostr}</i>
|
||||||
|
</Typography>
|
||||||
|
</div>
|
||||||
|
}
|
||||||
|
open={showNostr}
|
||||||
|
>
|
||||||
|
<IconButton
|
||||||
|
onClick={() => {
|
||||||
|
setShowNostr(true);
|
||||||
|
setTimeout(() => window.open(`https://snort.social/p/${nostr}`, '_blank'), 1500);
|
||||||
|
setTimeout(() => {
|
||||||
|
setShowNostr(false);
|
||||||
|
}, 10000);
|
||||||
|
systemClient.copyToClipboard(nostr);
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
<NostrIcon />
|
||||||
|
</IconButton>
|
||||||
|
</Tooltip>
|
||||||
|
</Grid>
|
||||||
|
)}
|
||||||
|
|
||||||
|
{pgp !== undefined && (
|
||||||
|
<Grid item>
|
||||||
|
<Tooltip enterTouchDelay={0} enterNextDelay={2000} title={t('See PGP Key')}>
|
||||||
|
<IconButton component='a' target='_blank' href={`https://${pgp}`} rel='noreferrer'>
|
||||||
|
<Key />
|
||||||
|
</IconButton>
|
||||||
|
</Tooltip>
|
||||||
|
</Grid>
|
||||||
|
)}
|
||||||
|
|
||||||
|
{email !== undefined && (
|
||||||
|
<Grid item>
|
||||||
|
<Tooltip enterTouchDelay={0} enterNextDelay={2000} title={t('Send Email')}>
|
||||||
|
<IconButton component='a' href={`mailto: ${email}`}>
|
||||||
|
<Email />
|
||||||
|
</IconButton>
|
||||||
|
</Tooltip>
|
||||||
|
</Grid>
|
||||||
|
)}
|
||||||
|
|
||||||
|
{telegram !== undefined && (
|
||||||
|
<Grid item>
|
||||||
|
<Tooltip enterTouchDelay={0} enterNextDelay={2000} title={t('Telegram')}>
|
||||||
|
<IconButton
|
||||||
|
component='a'
|
||||||
|
target='_blank'
|
||||||
|
href={`https://t.me/${telegram}`}
|
||||||
|
rel='noreferrer'
|
||||||
|
>
|
||||||
|
<Send />
|
||||||
|
</IconButton>
|
||||||
|
</Tooltip>
|
||||||
|
</Grid>
|
||||||
|
)}
|
||||||
|
|
||||||
|
{twitter !== undefined && (
|
||||||
|
<Grid item>
|
||||||
|
<Tooltip enterTouchDelay={0} enterNextDelay={2000} title={t('Twitter')}>
|
||||||
|
<IconButton
|
||||||
|
component='a'
|
||||||
|
target='_blank'
|
||||||
|
href={`https://twitter.com/${twitter}`}
|
||||||
|
rel='noreferrer'
|
||||||
|
>
|
||||||
|
<Twitter />
|
||||||
|
</IconButton>
|
||||||
|
</Tooltip>
|
||||||
|
</Grid>
|
||||||
|
)}
|
||||||
|
|
||||||
|
{reddit !== undefined && (
|
||||||
|
<Grid item>
|
||||||
|
<Tooltip enterTouchDelay={0} enterNextDelay={2000} title={t('Reddit')}>
|
||||||
|
<IconButton
|
||||||
|
component='a'
|
||||||
|
target='_blank'
|
||||||
|
href={`https://reddit.com/${reddit}`}
|
||||||
|
rel='noreferrer'
|
||||||
|
>
|
||||||
|
<Reddit />
|
||||||
|
</IconButton>
|
||||||
|
</Tooltip>
|
||||||
|
</Grid>
|
||||||
|
)}
|
||||||
|
|
||||||
|
{website !== undefined && (
|
||||||
|
<Grid item>
|
||||||
|
<Tooltip enterTouchDelay={0} enterNextDelay={2000} title={t('Website')}>
|
||||||
|
<IconButton component='a' target='_blank' href={website} rel='noreferrer'>
|
||||||
|
<Language />
|
||||||
|
</IconButton>
|
||||||
|
</Tooltip>
|
||||||
|
</Grid>
|
||||||
|
)}
|
||||||
|
|
||||||
|
{matrix !== undefined && (
|
||||||
|
<Grid item>
|
||||||
|
<Tooltip
|
||||||
|
title={
|
||||||
|
<Typography variant='body2'>
|
||||||
|
{t('Matrix channel copied! {{matrix}}', { matrix })}
|
||||||
|
</Typography>
|
||||||
|
}
|
||||||
|
open={showMatrix}
|
||||||
|
>
|
||||||
|
<IconButton
|
||||||
|
onClick={() => {
|
||||||
|
setShowMatrix(true);
|
||||||
|
setTimeout(() => {
|
||||||
|
setShowMatrix(false);
|
||||||
|
}, 10000);
|
||||||
|
systemClient.copyToClipboard(matrix);
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
<Tag />
|
||||||
|
</IconButton>
|
||||||
|
</Tooltip>
|
||||||
|
</Grid>
|
||||||
|
)}
|
||||||
|
</Grid>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
interface BadgesProps {
|
||||||
|
badges: Badges | undefined;
|
||||||
|
}
|
||||||
|
|
||||||
|
const BadgesHall = ({ badges }: BadgesProps): JSX.Element => {
|
||||||
|
const { t } = useTranslation();
|
||||||
|
const sxProps = {
|
||||||
|
width: '3em',
|
||||||
|
height: '3em',
|
||||||
|
filter: 'drop-shadow(3px 3px 3px RGB(0,0,0,0.3))',
|
||||||
|
};
|
||||||
|
const tooltipProps = { enterTouchDelay: 0, enterNextDelay: 2000 };
|
||||||
|
return (
|
||||||
|
<Grid container direction='row' alignItems='center' justifyContent='center' spacing={1}>
|
||||||
|
<Tooltip
|
||||||
|
{...tooltipProps}
|
||||||
|
title={
|
||||||
|
<Typography align='center' variant='body2'>
|
||||||
|
{badges?.isFounder === true
|
||||||
|
? t('Founder: coordinating trades since the testnet federation.')
|
||||||
|
: t('Not a federation founder')}
|
||||||
|
</Typography>
|
||||||
|
}
|
||||||
|
>
|
||||||
|
<Grid item sx={{ filter: badges?.isFounder !== true ? 'grayscale(100%)' : undefined }}>
|
||||||
|
<BadgeFounder sx={sxProps} />
|
||||||
|
</Grid>
|
||||||
|
</Tooltip>
|
||||||
|
|
||||||
|
<Tooltip
|
||||||
|
{...tooltipProps}
|
||||||
|
title={
|
||||||
|
<Typography align='center' variant='body2'>
|
||||||
|
{t('Development fund supporter: donates {{percent}}% to make RoboSats better.', {
|
||||||
|
percent: badges?.donatesToDevFund,
|
||||||
|
})}
|
||||||
|
</Typography>
|
||||||
|
}
|
||||||
|
>
|
||||||
|
<Grid
|
||||||
|
item
|
||||||
|
sx={{ filter: Number(badges?.donatesToDevFund) >= 20 ? undefined : 'grayscale(100%)' }}
|
||||||
|
>
|
||||||
|
<BadgeDevFund sx={sxProps} />
|
||||||
|
</Grid>
|
||||||
|
</Tooltip>
|
||||||
|
|
||||||
|
<Tooltip
|
||||||
|
{...tooltipProps}
|
||||||
|
title={
|
||||||
|
<Typography align='center' variant='body2'>
|
||||||
|
{badges?.hasGoodOpSec === true
|
||||||
|
? t(
|
||||||
|
'Good OpSec: the coordinator follows best practices to protect his and your privacy.',
|
||||||
|
)
|
||||||
|
: t('The privacy practices of this coordinator could improve')}
|
||||||
|
</Typography>
|
||||||
|
}
|
||||||
|
>
|
||||||
|
<Grid item sx={{ filter: badges?.hasGoodOpSec === true ? undefined : 'grayscale(100%)' }}>
|
||||||
|
<BadgePrivacy sx={sxProps} />
|
||||||
|
</Grid>
|
||||||
|
</Tooltip>
|
||||||
|
|
||||||
|
<Tooltip
|
||||||
|
{...tooltipProps}
|
||||||
|
title={
|
||||||
|
<Typography align='center' variant='body2'>
|
||||||
|
{badges?.robotsLove === true
|
||||||
|
? t('Loved by robots: receives positive comments by robots over the internet.')
|
||||||
|
: t(
|
||||||
|
'The coordinator does not seem to receive exceptional love from robots over the internet',
|
||||||
|
)}
|
||||||
|
</Typography>
|
||||||
|
}
|
||||||
|
>
|
||||||
|
<Grid item sx={{ filter: badges?.robotsLove === true ? undefined : 'grayscale(100%)' }}>
|
||||||
|
<BadgeLoved sx={sxProps} />
|
||||||
|
</Grid>
|
||||||
|
</Tooltip>
|
||||||
|
|
||||||
|
<Tooltip
|
||||||
|
{...tooltipProps}
|
||||||
|
title={
|
||||||
|
<Typography align='center' variant='body2'>
|
||||||
|
{badges?.hasLargeLimits === true
|
||||||
|
? t('Large limits: the coordinator has large trade limits.')
|
||||||
|
: t('Does not have large trade limits.')}
|
||||||
|
</Typography>
|
||||||
|
}
|
||||||
|
>
|
||||||
|
<Grid item sx={{ filter: badges?.hasLargeLimits === true ? undefined : 'grayscale(100%)' }}>
|
||||||
|
<BadgeLimits sx={sxProps} />
|
||||||
|
</Grid>
|
||||||
|
</Tooltip>
|
||||||
|
</Grid>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
const CoordinatorDialog = ({ open = false, onClose, coordinator, network }: Props): JSX.Element => {
|
||||||
|
const { t } = useTranslation();
|
||||||
|
const { clientVersion, page, hostUrl } = useContext(AppContext);
|
||||||
|
|
||||||
|
const [expanded, setExpanded] = useState<'summary' | 'stats' | 'policies' | undefined>(undefined);
|
||||||
|
|
||||||
|
const listItemProps = { sx: { maxHeight: '3em', width: '100%' } };
|
||||||
|
const coordinatorVersion = `v${coordinator?.info?.version?.major ?? '?'}.${
|
||||||
|
coordinator?.info?.version?.minor ?? '?'
|
||||||
|
}.${coordinator?.info?.version?.patch ?? '?'}`;
|
||||||
|
|
||||||
|
return (
|
||||||
|
<Dialog open={open} onClose={onClose}>
|
||||||
|
<DialogContent>
|
||||||
|
<Typography align='center' component='h5' variant='h5'>
|
||||||
|
{String(coordinator?.longAlias)}
|
||||||
|
</Typography>
|
||||||
|
<List dense>
|
||||||
|
<ListItem sx={{ display: 'flex', justifyContent: 'center' }}>
|
||||||
|
<Grid container direction='column' alignItems='center' padding={0}>
|
||||||
|
<Grid item>
|
||||||
|
<RobotAvatar
|
||||||
|
nickname={coordinator?.shortAlias}
|
||||||
|
coordinator={true}
|
||||||
|
style={{ width: '7.5em', height: '7.5em' }}
|
||||||
|
smooth={true}
|
||||||
|
flipHorizontally={false}
|
||||||
|
baseUrl={hostUrl}
|
||||||
|
/>
|
||||||
|
</Grid>
|
||||||
|
<Grid item>
|
||||||
|
<Typography align='center' variant='body2'>
|
||||||
|
<i>{String(coordinator?.motto)}</i>
|
||||||
|
</Typography>
|
||||||
|
</Grid>
|
||||||
|
<Grid item>
|
||||||
|
<ContactButtons {...coordinator?.contact} />
|
||||||
|
</Grid>
|
||||||
|
</Grid>
|
||||||
|
</ListItem>
|
||||||
|
|
||||||
|
{['create'].includes(page) && (
|
||||||
|
<>
|
||||||
|
<ListItem {...listItemProps}>
|
||||||
|
<ListItemIcon>
|
||||||
|
<Percent />
|
||||||
|
</ListItemIcon>
|
||||||
|
|
||||||
|
<Grid container>
|
||||||
|
<Grid item xs={6}>
|
||||||
|
<ListItemText secondary={t('Maker fee')}>
|
||||||
|
{(coordinator?.info?.maker_fee ?? 0 * 100).toFixed(3)}%
|
||||||
|
</ListItemText>
|
||||||
|
</Grid>
|
||||||
|
|
||||||
|
<Grid item xs={6}>
|
||||||
|
<ListItemText secondary={t('Taker fee')}>
|
||||||
|
{(coordinator?.info?.taker_fee ?? 0 * 100).toFixed(3)}%
|
||||||
|
</ListItemText>
|
||||||
|
</Grid>
|
||||||
|
</Grid>
|
||||||
|
</ListItem>
|
||||||
|
|
||||||
|
<ListItem {...listItemProps}>
|
||||||
|
<ListItemIcon>
|
||||||
|
<LinkIcon />
|
||||||
|
</ListItemIcon>
|
||||||
|
|
||||||
|
<ListItemText
|
||||||
|
primary={`${String(coordinator?.info?.current_swap_fee_rate.toPrecision(3))}%`}
|
||||||
|
secondary={t('Current onchain payout fee')}
|
||||||
|
/>
|
||||||
|
</ListItem>
|
||||||
|
</>
|
||||||
|
)}
|
||||||
|
<ListItem>
|
||||||
|
<BadgesHall badges={coordinator?.badges} />
|
||||||
|
</ListItem>
|
||||||
|
|
||||||
|
<ListItem>
|
||||||
|
<ListItemIcon>
|
||||||
|
<Description />
|
||||||
|
</ListItemIcon>
|
||||||
|
|
||||||
|
<ListItemText
|
||||||
|
primary={coordinator?.description}
|
||||||
|
primaryTypographyProps={{ sx: { maxWidth: '20em' } }}
|
||||||
|
secondary={t('Coordinator description')}
|
||||||
|
/>
|
||||||
|
</ListItem>
|
||||||
|
|
||||||
|
{coordinator?.mainnetNodesPubkeys?.[0] !== undefined && network === 'mainnet' ? (
|
||||||
|
<ListItem>
|
||||||
|
<ListItemIcon>
|
||||||
|
<AmbossIcon />
|
||||||
|
</ListItemIcon>
|
||||||
|
<ListItemText secondary={t('Mainnet LN Node')}>
|
||||||
|
<Link
|
||||||
|
target='_blank'
|
||||||
|
href={`https://amboss.space/node/${coordinator?.mainnetNodesPubkeys[0]}`}
|
||||||
|
rel='noreferrer'
|
||||||
|
>
|
||||||
|
{`${coordinator?.mainnetNodesPubkeys?.[0].slice(0, 12)}... (AMBOSS)`}
|
||||||
|
</Link>
|
||||||
|
</ListItemText>
|
||||||
|
</ListItem>
|
||||||
|
) : (
|
||||||
|
<></>
|
||||||
|
)}
|
||||||
|
|
||||||
|
{coordinator?.testnetNodesPubkeys?.[0] !== undefined && network === 'testnet' ? (
|
||||||
|
<ListItem>
|
||||||
|
<ListItemIcon>
|
||||||
|
<Dns />
|
||||||
|
</ListItemIcon>
|
||||||
|
<ListItemText secondary={t('Testnet LN Node')}>
|
||||||
|
<Link
|
||||||
|
target='_blank'
|
||||||
|
href={`https://1ml.com/testnet/node/${coordinator?.testnetNodesPubkeys[0]}`}
|
||||||
|
rel='noreferrer'
|
||||||
|
>
|
||||||
|
{`${coordinator?.testnetNodesPubkeys[0].slice(0, 12)}... (1ML)`}
|
||||||
|
</Link>
|
||||||
|
</ListItemText>
|
||||||
|
</ListItem>
|
||||||
|
) : (
|
||||||
|
<></>
|
||||||
|
)}
|
||||||
|
</List>
|
||||||
|
|
||||||
|
{coordinator?.loadingInfo === true ? (
|
||||||
|
<Box style={{ display: 'flex', justifyContent: 'center' }}>
|
||||||
|
<CircularProgress />
|
||||||
|
</Box>
|
||||||
|
) : coordinator?.info !== undefined ? (
|
||||||
|
<Box>
|
||||||
|
{coordinator?.policies !== undefined && (
|
||||||
|
<Accordion
|
||||||
|
expanded={expanded === 'policies'}
|
||||||
|
onChange={() => {
|
||||||
|
setExpanded(expanded === 'policies' ? undefined : 'policies');
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
<AccordionSummary expandIcon={<ExpandMore />}>
|
||||||
|
<Typography>{t('Policies')}</Typography>
|
||||||
|
</AccordionSummary>
|
||||||
|
<AccordionDetails sx={{ padding: 0 }}>
|
||||||
|
<List dense>
|
||||||
|
{Object.keys(coordinator?.policies).map((key, index) => (
|
||||||
|
<ListItem key={index} sx={{ maxWidth: '24em' }}>
|
||||||
|
<ListItemIcon>{index + 1}</ListItemIcon>
|
||||||
|
<ListItemText primary={key} secondary={coordinator?.policies[key]} />
|
||||||
|
</ListItem>
|
||||||
|
))}
|
||||||
|
</List>
|
||||||
|
</AccordionDetails>
|
||||||
|
</Accordion>
|
||||||
|
)}
|
||||||
|
<Accordion
|
||||||
|
expanded={expanded === 'summary'}
|
||||||
|
onChange={() => {
|
||||||
|
setExpanded(expanded === 'summary' ? undefined : 'summary');
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
<AccordionSummary expandIcon={<ExpandMore />}>
|
||||||
|
<Typography>{t('Summary')}</Typography>
|
||||||
|
</AccordionSummary>
|
||||||
|
<AccordionDetails sx={{ padding: 0 }}>
|
||||||
|
<List dense>
|
||||||
|
<Divider />
|
||||||
|
<ListItem {...listItemProps}>
|
||||||
|
<ListItemIcon>
|
||||||
|
<Percent />
|
||||||
|
</ListItemIcon>
|
||||||
|
|
||||||
|
<Grid container>
|
||||||
|
<Grid item xs={6}>
|
||||||
|
<ListItemText secondary={t('Maker fee')}>
|
||||||
|
{(coordinator?.info?.maker_fee * 100).toFixed(3)}%
|
||||||
|
</ListItemText>
|
||||||
|
</Grid>
|
||||||
|
|
||||||
|
<Grid item xs={6}>
|
||||||
|
<ListItemText secondary={t('Taker fee')}>
|
||||||
|
{(coordinator?.info?.taker_fee * 100).toFixed(3)}%
|
||||||
|
</ListItemText>
|
||||||
|
</Grid>
|
||||||
|
</Grid>
|
||||||
|
</ListItem>
|
||||||
|
|
||||||
|
<Divider />
|
||||||
|
|
||||||
|
<ListItem {...listItemProps}>
|
||||||
|
<ListItemIcon>
|
||||||
|
<LinkIcon />
|
||||||
|
</ListItemIcon>
|
||||||
|
|
||||||
|
<ListItemText
|
||||||
|
primary={`${coordinator?.info?.current_swap_fee_rate.toPrecision(3)}%`}
|
||||||
|
secondary={t('Current onchain payout fee')}
|
||||||
|
/>
|
||||||
|
</ListItem>
|
||||||
|
|
||||||
|
<Divider />
|
||||||
|
|
||||||
|
<ListItem {...listItemProps}>
|
||||||
|
<ListItemIcon>
|
||||||
|
<Inventory />
|
||||||
|
</ListItemIcon>
|
||||||
|
|
||||||
|
<ListItemText
|
||||||
|
primary={coordinator?.info?.num_public_buy_orders}
|
||||||
|
secondary={t('Public buy orders')}
|
||||||
|
/>
|
||||||
|
</ListItem>
|
||||||
|
|
||||||
|
<Divider />
|
||||||
|
|
||||||
|
<ListItem {...listItemProps}>
|
||||||
|
<ListItemIcon>
|
||||||
|
<Sell />
|
||||||
|
</ListItemIcon>
|
||||||
|
|
||||||
|
<ListItemText
|
||||||
|
primary={coordinator?.info?.num_public_sell_orders}
|
||||||
|
secondary={t('Public sell orders')}
|
||||||
|
/>
|
||||||
|
</ListItem>
|
||||||
|
|
||||||
|
<Divider />
|
||||||
|
|
||||||
|
<ListItem {...listItemProps}>
|
||||||
|
<ListItemIcon>
|
||||||
|
<Book />
|
||||||
|
</ListItemIcon>
|
||||||
|
|
||||||
|
<ListItemText
|
||||||
|
primary={`${pn(coordinator?.info?.book_liquidity)} Sats`}
|
||||||
|
secondary={t('Book liquidity')}
|
||||||
|
/>
|
||||||
|
</ListItem>
|
||||||
|
|
||||||
|
<Divider />
|
||||||
|
|
||||||
|
<ListItem {...listItemProps}>
|
||||||
|
<ListItemIcon>
|
||||||
|
<SmartToy />
|
||||||
|
</ListItemIcon>
|
||||||
|
|
||||||
|
<ListItemText
|
||||||
|
primary={coordinator?.info?.active_robots_today}
|
||||||
|
secondary={t('Today active robots')}
|
||||||
|
/>
|
||||||
|
</ListItem>
|
||||||
|
|
||||||
|
<Divider />
|
||||||
|
|
||||||
|
<ListItem {...listItemProps}>
|
||||||
|
<ListItemIcon>
|
||||||
|
<PriceChange />
|
||||||
|
</ListItemIcon>
|
||||||
|
|
||||||
|
<ListItemText
|
||||||
|
primary={`${coordinator?.info?.last_day_nonkyc_btc_premium}%`}
|
||||||
|
secondary={t('24h non-KYC bitcoin premium')}
|
||||||
|
/>
|
||||||
|
</ListItem>
|
||||||
|
</List>
|
||||||
|
</AccordionDetails>
|
||||||
|
</Accordion>
|
||||||
|
|
||||||
|
<Accordion
|
||||||
|
expanded={expanded === 'stats'}
|
||||||
|
onChange={() => {
|
||||||
|
setExpanded(expanded === 'stats' ? undefined : 'stats');
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
<AccordionSummary expandIcon={<ExpandMore />}>
|
||||||
|
<Typography>{t('Stats for Nerds')}</Typography>
|
||||||
|
</AccordionSummary>
|
||||||
|
<AccordionDetails>
|
||||||
|
<List dense>
|
||||||
|
<ListItem {...listItemProps}>
|
||||||
|
<ListItemIcon>
|
||||||
|
<RoboSatsNoTextIcon
|
||||||
|
sx={{
|
||||||
|
width: '1.4em',
|
||||||
|
height: '1.4em',
|
||||||
|
right: '0.2em',
|
||||||
|
position: 'relative',
|
||||||
|
}}
|
||||||
|
/>
|
||||||
|
</ListItemIcon>
|
||||||
|
<ListItemText
|
||||||
|
primary={`${t('Coordinator')} ${coordinatorVersion} - ${t('Client')} ${String(
|
||||||
|
clientVersion.short,
|
||||||
|
)}`}
|
||||||
|
secondary={t('RoboSats version')}
|
||||||
|
/>
|
||||||
|
</ListItem>
|
||||||
|
|
||||||
|
<Divider />
|
||||||
|
|
||||||
|
{coordinator?.info?.lnd_version !== undefined && (
|
||||||
|
<ListItem {...listItemProps}>
|
||||||
|
<ListItemIcon>
|
||||||
|
<Bolt />
|
||||||
|
</ListItemIcon>
|
||||||
|
<ListItemText
|
||||||
|
primary={coordinator?.info?.lnd_version}
|
||||||
|
secondary={t('LND version')}
|
||||||
|
/>
|
||||||
|
</ListItem>
|
||||||
|
)}
|
||||||
|
|
||||||
|
{Boolean(coordinator?.info?.cln_version) && (
|
||||||
|
<ListItem {...listItemProps}>
|
||||||
|
<ListItemIcon>
|
||||||
|
<Bolt />
|
||||||
|
</ListItemIcon>
|
||||||
|
<ListItemText
|
||||||
|
primary={coordinator?.info?.cln_version}
|
||||||
|
secondary={t('CLN version')}
|
||||||
|
/>
|
||||||
|
</ListItem>
|
||||||
|
)}
|
||||||
|
|
||||||
|
<Divider />
|
||||||
|
|
||||||
|
{coordinator?.info?.network === 'testnet' ? (
|
||||||
|
<ListItem {...listItemProps}>
|
||||||
|
<ListItemIcon>
|
||||||
|
<Dns />
|
||||||
|
</ListItemIcon>
|
||||||
|
<ListItemText secondary={`${t('LN Node')}: ${coordinator?.info?.node_alias}`}>
|
||||||
|
<Link
|
||||||
|
target='_blank'
|
||||||
|
href={`https://1ml.com/testnet/node/${coordinator?.info?.node_id}`}
|
||||||
|
rel='noreferrer'
|
||||||
|
>
|
||||||
|
{`${coordinator?.info?.node_id.slice(0, 12)}... (1ML)`}
|
||||||
|
</Link>
|
||||||
|
</ListItemText>
|
||||||
|
</ListItem>
|
||||||
|
) : (
|
||||||
|
<ListItem {...listItemProps}>
|
||||||
|
<ListItemIcon>
|
||||||
|
<AmbossIcon />
|
||||||
|
</ListItemIcon>
|
||||||
|
<ListItemText secondary={coordinator?.info?.node_alias}>
|
||||||
|
<Link
|
||||||
|
target='_blank'
|
||||||
|
href={`https://amboss.space/node/${coordinator?.info?.node_id}`}
|
||||||
|
rel='noreferrer'
|
||||||
|
>
|
||||||
|
{`${coordinator?.info?.node_id.slice(0, 12)}... (AMBOSS)`}
|
||||||
|
</Link>
|
||||||
|
</ListItemText>
|
||||||
|
</ListItem>
|
||||||
|
)}
|
||||||
|
|
||||||
|
<Divider />
|
||||||
|
|
||||||
|
<ListItem {...listItemProps}>
|
||||||
|
<ListItemIcon>
|
||||||
|
<GitHub />
|
||||||
|
</ListItemIcon>
|
||||||
|
<ListItemText secondary={t('Coordinator commit hash')}>
|
||||||
|
<Link
|
||||||
|
target='_blank'
|
||||||
|
href={`https://github.com/Reckless-Satoshi/robosats/tree/${coordinator?.info?.robosats_running_commit_hash}`}
|
||||||
|
rel='noreferrer'
|
||||||
|
>
|
||||||
|
{`${coordinator?.info?.robosats_running_commit_hash.slice(0, 12)}...`}
|
||||||
|
</Link>
|
||||||
|
</ListItemText>
|
||||||
|
</ListItem>
|
||||||
|
|
||||||
|
<Divider />
|
||||||
|
|
||||||
|
<ListItem {...listItemProps}>
|
||||||
|
<ListItemIcon>
|
||||||
|
<Equalizer />
|
||||||
|
</ListItemIcon>
|
||||||
|
<ListItemText secondary={t('24h contracted volume')}>
|
||||||
|
<div
|
||||||
|
style={{
|
||||||
|
cursor: 'pointer',
|
||||||
|
display: 'flex',
|
||||||
|
alignItems: 'center',
|
||||||
|
flexWrap: 'wrap',
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
{pn(parseFloat(coordinator?.info?.last_day_volume).toFixed(8))}
|
||||||
|
<BitcoinSignIcon sx={{ width: 14, height: 14 }} color={'text.secondary'} />
|
||||||
|
</div>
|
||||||
|
</ListItemText>
|
||||||
|
</ListItem>
|
||||||
|
|
||||||
|
<Divider />
|
||||||
|
|
||||||
|
<ListItem {...listItemProps}>
|
||||||
|
<ListItemIcon>
|
||||||
|
<Equalizer />
|
||||||
|
</ListItemIcon>
|
||||||
|
<ListItemText secondary={t('Lifetime contracted volume')}>
|
||||||
|
<div
|
||||||
|
style={{
|
||||||
|
cursor: 'pointer',
|
||||||
|
display: 'flex',
|
||||||
|
alignItems: 'center',
|
||||||
|
flexWrap: 'wrap',
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
{pn(parseFloat(coordinator?.info?.lifetime_volume).toFixed(8))}
|
||||||
|
<BitcoinSignIcon sx={{ width: 14, height: 14 }} color={'text.secondary'} />
|
||||||
|
</div>
|
||||||
|
</ListItemText>
|
||||||
|
</ListItem>
|
||||||
|
</List>
|
||||||
|
</AccordionDetails>
|
||||||
|
</Accordion>
|
||||||
|
</Box>
|
||||||
|
) : (
|
||||||
|
<Typography align='center' variant='h6' color='error'>
|
||||||
|
{t('Coordinator offline')}
|
||||||
|
</Typography>
|
||||||
|
)}
|
||||||
|
</DialogContent>
|
||||||
|
</Dialog>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
export default CoordinatorDialog;
|
@ -1,174 +0,0 @@
|
|||||||
import React from 'react';
|
|
||||||
import { useTranslation } from 'react-i18next';
|
|
||||||
|
|
||||||
import {
|
|
||||||
Dialog,
|
|
||||||
DialogContent,
|
|
||||||
Divider,
|
|
||||||
Grid,
|
|
||||||
List,
|
|
||||||
ListItemText,
|
|
||||||
ListItem,
|
|
||||||
ListItemIcon,
|
|
||||||
Typography,
|
|
||||||
LinearProgress,
|
|
||||||
} from '@mui/material';
|
|
||||||
|
|
||||||
import InventoryIcon from '@mui/icons-material/Inventory';
|
|
||||||
import SellIcon from '@mui/icons-material/Sell';
|
|
||||||
import SmartToyIcon from '@mui/icons-material/SmartToy';
|
|
||||||
import PercentIcon from '@mui/icons-material/Percent';
|
|
||||||
import PriceChangeIcon from '@mui/icons-material/PriceChange';
|
|
||||||
import BookIcon from '@mui/icons-material/Book';
|
|
||||||
import LinkIcon from '@mui/icons-material/Link';
|
|
||||||
|
|
||||||
import { pn } from '../../utils';
|
|
||||||
import { type Info } from '../../models';
|
|
||||||
|
|
||||||
interface Props {
|
|
||||||
open: boolean;
|
|
||||||
onClose: () => void;
|
|
||||||
info: Info;
|
|
||||||
}
|
|
||||||
|
|
||||||
const CoordinatorSummaryDialog = ({ open = false, onClose, info }: Props): JSX.Element => {
|
|
||||||
const { t } = useTranslation();
|
|
||||||
if (info.current_swap_fee_rate === null || info.current_swap_fee_rate === undefined) {
|
|
||||||
info.current_swap_fee_rate = 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
return (
|
|
||||||
<Dialog open={open} onClose={onClose}>
|
|
||||||
<div style={info.loading ? {} : { display: 'none' }}>
|
|
||||||
<LinearProgress />
|
|
||||||
</div>
|
|
||||||
<DialogContent>
|
|
||||||
<Typography component='h5' variant='h5'>
|
|
||||||
{t('Coordinator Summary')}
|
|
||||||
</Typography>
|
|
||||||
|
|
||||||
<List dense>
|
|
||||||
<ListItem>
|
|
||||||
<ListItemIcon>
|
|
||||||
<InventoryIcon />
|
|
||||||
</ListItemIcon>
|
|
||||||
|
|
||||||
<ListItemText
|
|
||||||
primaryTypographyProps={{ fontSize: '14px' }}
|
|
||||||
secondaryTypographyProps={{ fontSize: '12px' }}
|
|
||||||
primary={info.num_public_buy_orders}
|
|
||||||
secondary={t('Public buy orders')}
|
|
||||||
/>
|
|
||||||
</ListItem>
|
|
||||||
|
|
||||||
<Divider />
|
|
||||||
|
|
||||||
<ListItem>
|
|
||||||
<ListItemIcon>
|
|
||||||
<SellIcon />
|
|
||||||
</ListItemIcon>
|
|
||||||
|
|
||||||
<ListItemText
|
|
||||||
primaryTypographyProps={{ fontSize: '14px' }}
|
|
||||||
secondaryTypographyProps={{ fontSize: '12px' }}
|
|
||||||
primary={info.num_public_sell_orders}
|
|
||||||
secondary={t('Public sell orders')}
|
|
||||||
/>
|
|
||||||
</ListItem>
|
|
||||||
|
|
||||||
<Divider />
|
|
||||||
|
|
||||||
<ListItem>
|
|
||||||
<ListItemIcon>
|
|
||||||
<BookIcon />
|
|
||||||
</ListItemIcon>
|
|
||||||
|
|
||||||
<ListItemText
|
|
||||||
primaryTypographyProps={{ fontSize: '14px' }}
|
|
||||||
secondaryTypographyProps={{ fontSize: '12px' }}
|
|
||||||
primary={`${pn(info.book_liquidity)} Sats`}
|
|
||||||
secondary={t('Book liquidity')}
|
|
||||||
/>
|
|
||||||
</ListItem>
|
|
||||||
|
|
||||||
<Divider />
|
|
||||||
|
|
||||||
<ListItem>
|
|
||||||
<ListItemIcon>
|
|
||||||
<SmartToyIcon />
|
|
||||||
</ListItemIcon>
|
|
||||||
|
|
||||||
<ListItemText
|
|
||||||
primaryTypographyProps={{ fontSize: '14px' }}
|
|
||||||
secondaryTypographyProps={{ fontSize: '12px' }}
|
|
||||||
primary={info.active_robots_today}
|
|
||||||
secondary={t('Today active robots')}
|
|
||||||
/>
|
|
||||||
</ListItem>
|
|
||||||
|
|
||||||
<Divider />
|
|
||||||
|
|
||||||
<ListItem>
|
|
||||||
<ListItemIcon>
|
|
||||||
<PriceChangeIcon />
|
|
||||||
</ListItemIcon>
|
|
||||||
|
|
||||||
<ListItemText
|
|
||||||
primaryTypographyProps={{ fontSize: '14px' }}
|
|
||||||
secondaryTypographyProps={{ fontSize: '12px' }}
|
|
||||||
primary={`${info.last_day_nonkyc_btc_premium}%`}
|
|
||||||
secondary={t('Last 24h mean premium')}
|
|
||||||
/>
|
|
||||||
</ListItem>
|
|
||||||
|
|
||||||
<Divider />
|
|
||||||
|
|
||||||
<ListItem>
|
|
||||||
<ListItemIcon>
|
|
||||||
<PercentIcon />
|
|
||||||
</ListItemIcon>
|
|
||||||
|
|
||||||
<Grid container>
|
|
||||||
<Grid item xs={6}>
|
|
||||||
<ListItemText
|
|
||||||
primaryTypographyProps={{ fontSize: '14px' }}
|
|
||||||
secondaryTypographyProps={{ fontSize: '12px' }}
|
|
||||||
secondary={t('Maker fee')}
|
|
||||||
>
|
|
||||||
{(info.maker_fee * 100).toFixed(3)}%
|
|
||||||
</ListItemText>
|
|
||||||
</Grid>
|
|
||||||
|
|
||||||
<Grid item xs={6}>
|
|
||||||
<ListItemText
|
|
||||||
primaryTypographyProps={{ fontSize: '14px' }}
|
|
||||||
secondaryTypographyProps={{ fontSize: '12px' }}
|
|
||||||
secondary={t('Taker fee')}
|
|
||||||
>
|
|
||||||
{(info.taker_fee * 100).toFixed(3)}%
|
|
||||||
</ListItemText>
|
|
||||||
</Grid>
|
|
||||||
</Grid>
|
|
||||||
</ListItem>
|
|
||||||
|
|
||||||
<Divider />
|
|
||||||
|
|
||||||
<ListItem>
|
|
||||||
<ListItemIcon>
|
|
||||||
<LinkIcon />
|
|
||||||
</ListItemIcon>
|
|
||||||
|
|
||||||
<ListItemText
|
|
||||||
primaryTypographyProps={{ fontSize: '14px' }}
|
|
||||||
secondaryTypographyProps={{ fontSize: '12px' }}
|
|
||||||
primary={`${info.current_swap_fee_rate.toPrecision(3)}%`}
|
|
||||||
secondary={t('Current onchain payout fee')}
|
|
||||||
/>
|
|
||||||
</ListItem>
|
|
||||||
</List>
|
|
||||||
</DialogContent>
|
|
||||||
</Dialog>
|
|
||||||
);
|
|
||||||
};
|
|
||||||
|
|
||||||
export default CoordinatorSummaryDialog;
|
|
@ -27,12 +27,12 @@ const EnableTelegramDialog = ({ open, onClose, tgBotName, tgToken }: Props): JSX
|
|||||||
const { t } = useTranslation();
|
const { t } = useTranslation();
|
||||||
const theme = useTheme();
|
const theme = useTheme();
|
||||||
|
|
||||||
const handleClickOpenBrowser = () => {
|
const handleClickOpenBrowser = (): void => {
|
||||||
window.open(`https://t.me/${tgBotName}?start=${tgToken}`, '_blank').focus();
|
window.open(`https://t.me/${tgBotName}?start=${tgToken}`, '_blank').focus();
|
||||||
setOpenEnableTelegram(false);
|
setOpenEnableTelegram(false);
|
||||||
};
|
};
|
||||||
|
|
||||||
const handleOpenTG = () => {
|
const handleOpenTG = (): void => {
|
||||||
window.open(`tg://resolve?domain=${tgBotName}&start=${tgToken}`);
|
window.open(`tg://resolve?domain=${tgBotName}&start=${tgToken}`);
|
||||||
};
|
};
|
||||||
|
|
||||||
|
190
frontend/src/components/Dialogs/Exchange.tsx
Normal file
190
frontend/src/components/Dialogs/Exchange.tsx
Normal file
@ -0,0 +1,190 @@
|
|||||||
|
import React, { useContext, useMemo } from 'react';
|
||||||
|
import { useTranslation } from 'react-i18next';
|
||||||
|
|
||||||
|
import {
|
||||||
|
Dialog,
|
||||||
|
DialogContent,
|
||||||
|
Divider,
|
||||||
|
List,
|
||||||
|
ListItemText,
|
||||||
|
ListItem,
|
||||||
|
ListItemIcon,
|
||||||
|
Typography,
|
||||||
|
LinearProgress,
|
||||||
|
} from '@mui/material';
|
||||||
|
|
||||||
|
import {
|
||||||
|
Inventory,
|
||||||
|
Sell,
|
||||||
|
SmartToy,
|
||||||
|
PriceChange,
|
||||||
|
Book,
|
||||||
|
Groups3,
|
||||||
|
Equalizer,
|
||||||
|
} from '@mui/icons-material';
|
||||||
|
|
||||||
|
import { pn } from '../../utils';
|
||||||
|
import { BitcoinSignIcon } from '../Icons';
|
||||||
|
import { FederationContext } from '../../contexts/FederationContext';
|
||||||
|
|
||||||
|
interface Props {
|
||||||
|
open: boolean;
|
||||||
|
onClose: () => void;
|
||||||
|
}
|
||||||
|
|
||||||
|
const ExchangeDialog = ({ open = false, onClose }: Props): JSX.Element => {
|
||||||
|
const { t } = useTranslation();
|
||||||
|
const { federation } = useContext(FederationContext);
|
||||||
|
|
||||||
|
const loadingProgress = useMemo(() => {
|
||||||
|
return (federation.exchange.onlineCoordinators / federation.exchange.totalCoordinators) * 100;
|
||||||
|
}, [federation.exchange.onlineCoordinators, federation.exchange.totalCoordinators]);
|
||||||
|
|
||||||
|
return (
|
||||||
|
<Dialog open={open} onClose={onClose}>
|
||||||
|
<div style={loadingProgress < 100 ? {} : { display: 'none' }}>
|
||||||
|
<LinearProgress variant='determinate' value={loadingProgress} />
|
||||||
|
</div>
|
||||||
|
<DialogContent>
|
||||||
|
<Typography component='h5' variant='h5'>
|
||||||
|
{t('Exchange Summary')}
|
||||||
|
</Typography>
|
||||||
|
|
||||||
|
<List dense>
|
||||||
|
<ListItem>
|
||||||
|
<ListItemIcon>
|
||||||
|
<Groups3 />
|
||||||
|
</ListItemIcon>
|
||||||
|
|
||||||
|
<ListItemText
|
||||||
|
primary={federation.exchange.onlineCoordinators}
|
||||||
|
secondary={t('Online RoboSats coordinators')}
|
||||||
|
/>
|
||||||
|
</ListItem>
|
||||||
|
|
||||||
|
<Divider />
|
||||||
|
<ListItem>
|
||||||
|
<ListItemIcon>
|
||||||
|
<Groups3 />
|
||||||
|
</ListItemIcon>
|
||||||
|
|
||||||
|
<ListItemText
|
||||||
|
primary={federation.exchange.totalCoordinators}
|
||||||
|
secondary={t('Enabled RoboSats coordinators')}
|
||||||
|
/>
|
||||||
|
</ListItem>
|
||||||
|
|
||||||
|
<Divider />
|
||||||
|
<ListItem>
|
||||||
|
<ListItemIcon>
|
||||||
|
<Inventory />
|
||||||
|
</ListItemIcon>
|
||||||
|
|
||||||
|
<ListItemText
|
||||||
|
primary={federation.exchange.info.num_public_buy_orders}
|
||||||
|
secondary={t('Public buy orders')}
|
||||||
|
/>
|
||||||
|
</ListItem>
|
||||||
|
|
||||||
|
<Divider />
|
||||||
|
|
||||||
|
<ListItem>
|
||||||
|
<ListItemIcon>
|
||||||
|
<Sell />
|
||||||
|
</ListItemIcon>
|
||||||
|
|
||||||
|
<ListItemText
|
||||||
|
primary={federation.exchange.info.num_public_sell_orders}
|
||||||
|
secondary={t('Public sell orders')}
|
||||||
|
/>
|
||||||
|
</ListItem>
|
||||||
|
|
||||||
|
<Divider />
|
||||||
|
|
||||||
|
<ListItem>
|
||||||
|
<ListItemIcon>
|
||||||
|
<Book />
|
||||||
|
</ListItemIcon>
|
||||||
|
|
||||||
|
<ListItemText
|
||||||
|
primary={`${pn(federation.exchange.info.book_liquidity)} Sats`}
|
||||||
|
secondary={t('Book liquidity')}
|
||||||
|
/>
|
||||||
|
</ListItem>
|
||||||
|
|
||||||
|
<Divider />
|
||||||
|
|
||||||
|
<ListItem>
|
||||||
|
<ListItemIcon>
|
||||||
|
<SmartToy />
|
||||||
|
</ListItemIcon>
|
||||||
|
|
||||||
|
<ListItemText
|
||||||
|
primary={federation.exchange.info.active_robots_today}
|
||||||
|
secondary={t('Today active robots')}
|
||||||
|
/>
|
||||||
|
</ListItem>
|
||||||
|
|
||||||
|
<Divider />
|
||||||
|
|
||||||
|
<ListItem>
|
||||||
|
<ListItemIcon>
|
||||||
|
<PriceChange />
|
||||||
|
</ListItemIcon>
|
||||||
|
|
||||||
|
<ListItemText
|
||||||
|
primary={`${String(
|
||||||
|
federation.exchange.info.last_day_nonkyc_btc_premium.toPrecision(3),
|
||||||
|
)}%`}
|
||||||
|
secondary={t('24h non-KYC bitcoin premium')}
|
||||||
|
/>
|
||||||
|
</ListItem>
|
||||||
|
|
||||||
|
<Divider />
|
||||||
|
|
||||||
|
<ListItem>
|
||||||
|
<ListItemIcon>
|
||||||
|
<Equalizer />
|
||||||
|
</ListItemIcon>
|
||||||
|
<ListItemText secondary={t('24h contracted volume')}>
|
||||||
|
<div
|
||||||
|
style={{
|
||||||
|
cursor: 'pointer',
|
||||||
|
display: 'flex',
|
||||||
|
alignItems: 'center',
|
||||||
|
flexWrap: 'wrap',
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
{pn(federation.exchange.info.last_day_volume)}
|
||||||
|
<BitcoinSignIcon sx={{ width: 14, height: 14 }} color='text.secondary' />
|
||||||
|
</div>
|
||||||
|
</ListItemText>
|
||||||
|
</ListItem>
|
||||||
|
|
||||||
|
<Divider />
|
||||||
|
|
||||||
|
<ListItem>
|
||||||
|
<ListItemIcon>
|
||||||
|
<Equalizer />
|
||||||
|
</ListItemIcon>
|
||||||
|
<ListItemText secondary={t('Lifetime contracted volume')}>
|
||||||
|
<div
|
||||||
|
style={{
|
||||||
|
cursor: 'pointer',
|
||||||
|
display: 'flex',
|
||||||
|
alignItems: 'center',
|
||||||
|
flexWrap: 'wrap',
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
{pn(federation.exchange.info.lifetime_volume)}
|
||||||
|
<BitcoinSignIcon sx={{ width: 14, height: 14 }} color='text.secondary' />
|
||||||
|
</div>
|
||||||
|
</ListItemText>
|
||||||
|
</ListItem>
|
||||||
|
</List>
|
||||||
|
</DialogContent>
|
||||||
|
</Dialog>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
export default ExchangeDialog;
|
@ -31,7 +31,7 @@ const F2fMapDialog = ({
|
|||||||
onClose = () => {},
|
onClose = () => {},
|
||||||
latitude,
|
latitude,
|
||||||
longitude,
|
longitude,
|
||||||
interactive,
|
interactive = false,
|
||||||
zoom,
|
zoom,
|
||||||
message = '',
|
message = '',
|
||||||
}: Props): JSX.Element => {
|
}: Props): JSX.Element => {
|
||||||
@ -41,8 +41,8 @@ const F2fMapDialog = ({
|
|||||||
const [acceptedTilesWarning, setAcceptedTilesWarning] = useState<boolean>(false);
|
const [acceptedTilesWarning, setAcceptedTilesWarning] = useState<boolean>(false);
|
||||||
const [openWarningDialog, setOpenWarningDialog] = useState<boolean>(false);
|
const [openWarningDialog, setOpenWarningDialog] = useState<boolean>(false);
|
||||||
|
|
||||||
const onSave = () => {
|
const onSave: () => void = () => {
|
||||||
if (position && position[0] && position[1]) {
|
if (position?.[0] && position?.[1]) {
|
||||||
onClose([position[0] + Math.random() * 0.1 - 0.05, position[1] + Math.random() * 0.1 - 0.05]);
|
onClose([position[0] + Math.random() * 0.1 - 0.05, position[1] + Math.random() * 0.1 - 0.05]);
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
@ -59,7 +59,9 @@ const F2fMapDialog = ({
|
|||||||
<Dialog
|
<Dialog
|
||||||
open={open}
|
open={open}
|
||||||
fullWidth
|
fullWidth
|
||||||
onClose={() => onClose()}
|
onClose={() => {
|
||||||
|
onClose();
|
||||||
|
}}
|
||||||
aria-labelledby='worldmap-dialog-title'
|
aria-labelledby='worldmap-dialog-title'
|
||||||
aria-describedby='worldmap-description'
|
aria-describedby='worldmap-description'
|
||||||
maxWidth={false}
|
maxWidth={false}
|
||||||
@ -154,15 +156,22 @@ const F2fMapDialog = ({
|
|||||||
</Grid>
|
</Grid>
|
||||||
<Grid item>
|
<Grid item>
|
||||||
{interactive ? (
|
{interactive ? (
|
||||||
<Button color='primary' variant='contained' onClick={onSave} disabled={!position}>
|
<Button
|
||||||
|
color='primary'
|
||||||
|
variant='contained'
|
||||||
|
onClick={onSave}
|
||||||
|
disabled={position == null}
|
||||||
|
>
|
||||||
{t('Save')}
|
{t('Save')}
|
||||||
</Button>
|
</Button>
|
||||||
) : (
|
) : (
|
||||||
<Button
|
<Button
|
||||||
color='primary'
|
color='primary'
|
||||||
variant='contained'
|
variant='contained'
|
||||||
onClick={() => onClose()}
|
onClick={() => {
|
||||||
disabled={!position}
|
onClose();
|
||||||
|
}}
|
||||||
|
disabled={position == null}
|
||||||
>
|
>
|
||||||
{t('Close')}
|
{t('Close')}
|
||||||
</Button>
|
</Button>
|
||||||
|
@ -1,126 +1,40 @@
|
|||||||
import React, { useEffect, useState } from 'react';
|
import React, { useContext, useEffect, useState } from 'react';
|
||||||
import { useTranslation } from 'react-i18next';
|
import { useTranslation } from 'react-i18next';
|
||||||
import { useTheme } from '@mui/material/styles';
|
|
||||||
import { useNavigate } from 'react-router-dom';
|
|
||||||
|
|
||||||
import {
|
import {
|
||||||
Badge,
|
|
||||||
Button,
|
|
||||||
CircularProgress,
|
|
||||||
Dialog,
|
Dialog,
|
||||||
DialogContent,
|
DialogContent,
|
||||||
Divider,
|
Divider,
|
||||||
FormControlLabel,
|
|
||||||
Grid,
|
|
||||||
List,
|
List,
|
||||||
ListItemAvatar,
|
ListItemAvatar,
|
||||||
ListItemButton,
|
|
||||||
ListItemText,
|
ListItemText,
|
||||||
ListItem,
|
ListItem,
|
||||||
ListItemIcon,
|
|
||||||
Switch,
|
|
||||||
TextField,
|
|
||||||
Tooltip,
|
|
||||||
Typography,
|
Typography,
|
||||||
LinearProgress,
|
LinearProgress,
|
||||||
} from '@mui/material';
|
} from '@mui/material';
|
||||||
|
|
||||||
import { EnableTelegramDialog } from '.';
|
|
||||||
import BoltIcon from '@mui/icons-material/Bolt';
|
import BoltIcon from '@mui/icons-material/Bolt';
|
||||||
import SendIcon from '@mui/icons-material/Send';
|
|
||||||
import NumbersIcon from '@mui/icons-material/Numbers';
|
|
||||||
import EmojiEventsIcon from '@mui/icons-material/EmojiEvents';
|
|
||||||
import { UserNinjaIcon } from '../Icons';
|
|
||||||
|
|
||||||
import { getWebln } from '../../utils';
|
|
||||||
import RobotAvatar from '../RobotAvatar';
|
import RobotAvatar from '../RobotAvatar';
|
||||||
import { apiClient } from '../../services/api';
|
import RobotInfo from '../RobotInfo';
|
||||||
import { type Robot } from '../../models';
|
import { FederationContext, UseFederationStoreType } from '../../contexts/FederationContext';
|
||||||
import { signCleartextMessage } from '../../pgp';
|
import { GarageContext, UseGarageStoreType } from '../../contexts/GarageContext';
|
||||||
|
import { Coordinator } from '../../models';
|
||||||
|
|
||||||
interface Props {
|
interface Props {
|
||||||
open: boolean;
|
open: boolean;
|
||||||
onClose: () => void;
|
onClose: () => void;
|
||||||
robot: Robot;
|
|
||||||
setRobot: (state: Robot) => void;
|
|
||||||
baseUrl: string;
|
baseUrl: string;
|
||||||
}
|
}
|
||||||
|
|
||||||
const ProfileDialog = ({ open = false, baseUrl, onClose, robot, setRobot }: Props): JSX.Element => {
|
const ProfileDialog = ({ open = false, baseUrl, onClose }: Props): JSX.Element => {
|
||||||
|
const { federation } = useContext<UseFederationStoreType>(FederationContext);
|
||||||
|
const { garage, robotUpdatedAt } = useContext<UseGarageStoreType>(GarageContext);
|
||||||
const { t } = useTranslation();
|
const { t } = useTranslation();
|
||||||
const navigate = useNavigate();
|
const [loading, setLoading] = useState<boolean>(true);
|
||||||
const theme = useTheme();
|
|
||||||
|
|
||||||
const [rewardInvoice, setRewardInvoice] = useState<string>('');
|
|
||||||
const [showRewardsSpinner, setShowRewardsSpinner] = useState<boolean>(false);
|
|
||||||
const [withdrawn, setWithdrawn] = useState<boolean>(false);
|
|
||||||
const [badInvoice, setBadInvoice] = useState<string>('');
|
|
||||||
const [openClaimRewards, setOpenClaimRewards] = useState<boolean>(false);
|
|
||||||
const [weblnEnabled, setWeblnEnabled] = useState<boolean>(false);
|
|
||||||
const [openEnableTelegram, setOpenEnableTelegram] = useState<boolean>(false);
|
|
||||||
|
|
||||||
const handleWebln = async () => {
|
|
||||||
const webln = await getWebln()
|
|
||||||
.then(() => {
|
|
||||||
setWeblnEnabled(true);
|
|
||||||
})
|
|
||||||
.catch(() => {
|
|
||||||
setWeblnEnabled(false);
|
|
||||||
console.log('WebLN not available');
|
|
||||||
});
|
|
||||||
return webln;
|
|
||||||
};
|
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
handleWebln();
|
setLoading(garage.getRobot().loading);
|
||||||
}, []);
|
}, [robotUpdatedAt]);
|
||||||
|
|
||||||
const handleWeblnInvoiceClicked = async (e: any) => {
|
|
||||||
e.preventDefault();
|
|
||||||
if (robot.earnedRewards) {
|
|
||||||
const webln = await getWebln();
|
|
||||||
const invoice = webln.makeInvoice(robot.earnedRewards).then(() => {
|
|
||||||
if (invoice) {
|
|
||||||
handleSubmitInvoiceClicked(e, invoice.paymentRequest);
|
|
||||||
}
|
|
||||||
});
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
const handleSubmitInvoiceClicked = (e: any, rewardInvoice: string) => {
|
|
||||||
setBadInvoice('');
|
|
||||||
setShowRewardsSpinner(true);
|
|
||||||
signCleartextMessage(rewardInvoice, robot.encPrivKey, robot.token).then((signedInvoice) => {
|
|
||||||
apiClient
|
|
||||||
.post(
|
|
||||||
baseUrl,
|
|
||||||
'/api/reward/',
|
|
||||||
{
|
|
||||||
invoice: signedInvoice,
|
|
||||||
},
|
|
||||||
{ tokenSHA256: robot.tokenSHA256 },
|
|
||||||
)
|
|
||||||
.then((data: any) => {
|
|
||||||
setBadInvoice(data.bad_invoice ?? '');
|
|
||||||
setShowRewardsSpinner(false);
|
|
||||||
setWithdrawn(data.successful_withdrawal);
|
|
||||||
setOpenClaimRewards(!data.successful_withdrawal);
|
|
||||||
setRobot({
|
|
||||||
...robot,
|
|
||||||
earnedRewards: data.successful_withdrawal ? 0 : robot.earnedRewards,
|
|
||||||
});
|
|
||||||
});
|
|
||||||
});
|
|
||||||
e.preventDefault();
|
|
||||||
};
|
|
||||||
|
|
||||||
const setStealthInvoice = (wantsStealth: boolean) => {
|
|
||||||
apiClient
|
|
||||||
.post(baseUrl, '/api/stealth/', { wantsStealth }, { tokenSHA256: robot.tokenSHA256 })
|
|
||||||
.then((data) => {
|
|
||||||
setRobot({ ...robot, stealthInvoices: data?.wantsStealth });
|
|
||||||
});
|
|
||||||
};
|
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Dialog
|
<Dialog
|
||||||
@ -129,7 +43,7 @@ const ProfileDialog = ({ open = false, baseUrl, onClose, robot, setRobot }: Prop
|
|||||||
aria-labelledby='profile-title'
|
aria-labelledby='profile-title'
|
||||||
aria-describedby='profile-description'
|
aria-describedby='profile-description'
|
||||||
>
|
>
|
||||||
<div style={robot.loading ? {} : { display: 'none' }}>
|
<div style={loading ? {} : { display: 'none' }}>
|
||||||
<LinearProgress />
|
<LinearProgress />
|
||||||
</div>
|
</div>
|
||||||
<DialogContent>
|
<DialogContent>
|
||||||
@ -143,7 +57,7 @@ const ProfileDialog = ({ open = false, baseUrl, onClose, robot, setRobot }: Prop
|
|||||||
<ListItem className='profileNickname'>
|
<ListItem className='profileNickname'>
|
||||||
<ListItemText secondary={t('Your robot')}>
|
<ListItemText secondary={t('Your robot')}>
|
||||||
<Typography component='h6' variant='h6'>
|
<Typography component='h6' variant='h6'>
|
||||||
{robot.nickname ? (
|
{garage.getRobot().nickname !== undefined && (
|
||||||
<div style={{ position: 'relative', left: '-7px' }}>
|
<div style={{ position: 'relative', left: '-7px' }}>
|
||||||
<div
|
<div
|
||||||
style={{
|
style={{
|
||||||
@ -156,12 +70,12 @@ const ProfileDialog = ({ open = false, baseUrl, onClose, robot, setRobot }: Prop
|
|||||||
>
|
>
|
||||||
<BoltIcon sx={{ color: '#fcba03', height: '28px', width: '24px' }} />
|
<BoltIcon sx={{ color: '#fcba03', height: '28px', width: '24px' }} />
|
||||||
|
|
||||||
<a>{robot.nickname}</a>
|
<a>{garage.getRobot().nickname}</a>
|
||||||
|
|
||||||
<BoltIcon sx={{ color: '#fcba03', height: '28px', width: '24px' }} />
|
<BoltIcon sx={{ color: '#fcba03', height: '28px', width: '24px' }} />
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
) : null}
|
)}
|
||||||
</Typography>
|
</Typography>
|
||||||
</ListItemText>
|
</ListItemText>
|
||||||
|
|
||||||
@ -169,219 +83,35 @@ const ProfileDialog = ({ open = false, baseUrl, onClose, robot, setRobot }: Prop
|
|||||||
<RobotAvatar
|
<RobotAvatar
|
||||||
avatarClass='profileAvatar'
|
avatarClass='profileAvatar'
|
||||||
style={{ width: 65, height: 65 }}
|
style={{ width: 65, height: 65 }}
|
||||||
nickname={robot.nickname}
|
nickname={garage.getRobot().nickname}
|
||||||
baseUrl={baseUrl}
|
baseUrl={baseUrl}
|
||||||
/>
|
/>
|
||||||
</ListItemAvatar>
|
</ListItemAvatar>
|
||||||
</ListItem>
|
</ListItem>
|
||||||
|
|
||||||
<Divider />
|
<Divider />
|
||||||
|
|
||||||
{robot.activeOrderId ? (
|
|
||||||
<ListItemButton
|
|
||||||
onClick={() => {
|
|
||||||
navigate(`/order/${robot.activeOrderId}`);
|
|
||||||
onClose();
|
|
||||||
}}
|
|
||||||
>
|
|
||||||
<ListItemIcon>
|
|
||||||
<Badge badgeContent='' color='primary'>
|
|
||||||
<NumbersIcon color='primary' />
|
|
||||||
</Badge>
|
|
||||||
</ListItemIcon>
|
|
||||||
<ListItemText
|
|
||||||
primary={t('One active order #{{orderID}}', { orderID: robot.activeOrderId })}
|
|
||||||
secondary={t('Your current order')}
|
|
||||||
/>
|
|
||||||
</ListItemButton>
|
|
||||||
) : robot.lastOrderId ? (
|
|
||||||
<ListItemButton
|
|
||||||
onClick={() => {
|
|
||||||
navigate(`/order/${robot.lastOrderId}`);
|
|
||||||
onClose();
|
|
||||||
}}
|
|
||||||
>
|
|
||||||
<ListItemIcon>
|
|
||||||
<NumbersIcon color='primary' />
|
|
||||||
</ListItemIcon>
|
|
||||||
<ListItemText
|
|
||||||
primary={t('Your last order #{{orderID}}', { orderID: robot.lastOrderId })}
|
|
||||||
secondary={t('Inactive order')}
|
|
||||||
/>
|
|
||||||
</ListItemButton>
|
|
||||||
) : (
|
|
||||||
<ListItem>
|
|
||||||
<ListItemIcon>
|
|
||||||
<NumbersIcon />
|
|
||||||
</ListItemIcon>
|
|
||||||
<ListItemText
|
|
||||||
primary={t('No active orders')}
|
|
||||||
secondary={t('You do not have previous orders')}
|
|
||||||
/>
|
|
||||||
</ListItem>
|
|
||||||
)}
|
|
||||||
|
|
||||||
<Divider />
|
|
||||||
|
|
||||||
<EnableTelegramDialog
|
|
||||||
open={openEnableTelegram}
|
|
||||||
onClose={() => {
|
|
||||||
setOpenEnableTelegram(false);
|
|
||||||
}}
|
|
||||||
tgBotName={robot.tgBotName}
|
|
||||||
tgToken={robot.tgToken}
|
|
||||||
/>
|
|
||||||
|
|
||||||
<ListItem>
|
|
||||||
<ListItemIcon>
|
|
||||||
<SendIcon />
|
|
||||||
</ListItemIcon>
|
|
||||||
|
|
||||||
<ListItemText>
|
|
||||||
{robot.tgEnabled ? (
|
|
||||||
<Typography color={theme.palette.success.main}>
|
|
||||||
<b>{t('Telegram enabled')}</b>
|
|
||||||
</Typography>
|
|
||||||
) : (
|
|
||||||
<Button
|
|
||||||
color='primary'
|
|
||||||
onClick={() => {
|
|
||||||
setOpenEnableTelegram(true);
|
|
||||||
}}
|
|
||||||
>
|
|
||||||
{t('Enable Telegram Notifications')}
|
|
||||||
</Button>
|
|
||||||
)}
|
|
||||||
</ListItemText>
|
|
||||||
</ListItem>
|
|
||||||
|
|
||||||
<ListItem>
|
|
||||||
<ListItemIcon>
|
|
||||||
<UserNinjaIcon />
|
|
||||||
</ListItemIcon>
|
|
||||||
|
|
||||||
<ListItemText>
|
|
||||||
<Tooltip
|
|
||||||
placement='bottom'
|
|
||||||
enterTouchDelay={0}
|
|
||||||
title={t(
|
|
||||||
"Stealth lightning invoices do not contain details about the trade except an order reference. Enable this setting if you don't want to disclose details to a custodial lightning wallet.",
|
|
||||||
)}
|
|
||||||
>
|
|
||||||
<Grid item>
|
|
||||||
<FormControlLabel
|
|
||||||
labelPlacement='end'
|
|
||||||
label={t('Use stealth invoices')}
|
|
||||||
control={
|
|
||||||
<Switch
|
|
||||||
checked={robot.stealthInvoices}
|
|
||||||
onChange={() => {
|
|
||||||
setStealthInvoice(!robot.stealthInvoices);
|
|
||||||
}}
|
|
||||||
/>
|
|
||||||
}
|
|
||||||
/>
|
|
||||||
</Grid>
|
|
||||||
</Tooltip>
|
|
||||||
</ListItemText>
|
|
||||||
</ListItem>
|
|
||||||
|
|
||||||
<ListItem>
|
|
||||||
<ListItemIcon>
|
|
||||||
<EmojiEventsIcon />
|
|
||||||
</ListItemIcon>
|
|
||||||
|
|
||||||
{!openClaimRewards ? (
|
|
||||||
<ListItemText secondary={t('Your earned rewards')}>
|
|
||||||
<Grid container>
|
|
||||||
<Grid item xs={9}>
|
|
||||||
<Typography>{`${robot.earnedRewards} Sats`}</Typography>
|
|
||||||
</Grid>
|
|
||||||
|
|
||||||
<Grid item xs={3}>
|
|
||||||
<Button
|
|
||||||
disabled={robot.earnedRewards === 0}
|
|
||||||
onClick={() => {
|
|
||||||
setOpenClaimRewards(true);
|
|
||||||
}}
|
|
||||||
variant='contained'
|
|
||||||
size='small'
|
|
||||||
>
|
|
||||||
{t('Claim')}
|
|
||||||
</Button>
|
|
||||||
</Grid>
|
|
||||||
</Grid>
|
|
||||||
</ListItemText>
|
|
||||||
) : (
|
|
||||||
<form noValidate style={{ maxWidth: 270 }}>
|
|
||||||
<Grid container style={{ display: 'flex', alignItems: 'stretch' }}>
|
|
||||||
<Grid item style={{ display: 'flex', maxWidth: 160 }}>
|
|
||||||
<TextField
|
|
||||||
error={!!badInvoice}
|
|
||||||
helperText={badInvoice || ''}
|
|
||||||
label={t('Invoice for {{amountSats}} Sats', {
|
|
||||||
amountSats: robot.earnedRewards,
|
|
||||||
})}
|
|
||||||
size='small'
|
|
||||||
value={rewardInvoice}
|
|
||||||
onChange={(e) => {
|
|
||||||
setRewardInvoice(e.target.value);
|
|
||||||
}}
|
|
||||||
/>
|
|
||||||
</Grid>
|
|
||||||
<Grid item alignItems='stretch' style={{ display: 'flex', maxWidth: 80 }}>
|
|
||||||
<Button
|
|
||||||
sx={{ maxHeight: 38 }}
|
|
||||||
onClick={(e) => {
|
|
||||||
handleSubmitInvoiceClicked(e, rewardInvoice);
|
|
||||||
}}
|
|
||||||
variant='contained'
|
|
||||||
color='primary'
|
|
||||||
size='small'
|
|
||||||
type='submit'
|
|
||||||
>
|
|
||||||
{t('Submit')}
|
|
||||||
</Button>
|
|
||||||
</Grid>
|
|
||||||
</Grid>
|
|
||||||
{weblnEnabled ? (
|
|
||||||
<Grid container style={{ display: 'flex', alignItems: 'stretch' }}>
|
|
||||||
<Grid item alignItems='stretch' style={{ display: 'flex', maxWidth: 240 }}>
|
|
||||||
<Button
|
|
||||||
sx={{ maxHeight: 38, minWidth: 230 }}
|
|
||||||
onClick={async (e) => {
|
|
||||||
await handleWeblnInvoiceClicked(e);
|
|
||||||
}}
|
|
||||||
variant='contained'
|
|
||||||
color='primary'
|
|
||||||
size='small'
|
|
||||||
type='submit'
|
|
||||||
>
|
|
||||||
{t('Generate with Webln')}
|
|
||||||
</Button>
|
|
||||||
</Grid>
|
|
||||||
</Grid>
|
|
||||||
) : (
|
|
||||||
<></>
|
|
||||||
)}
|
|
||||||
</form>
|
|
||||||
)}
|
|
||||||
</ListItem>
|
|
||||||
|
|
||||||
{showRewardsSpinner && (
|
|
||||||
<div style={{ display: 'flex', justifyContent: 'center' }}>
|
|
||||||
<CircularProgress />
|
|
||||||
</div>
|
|
||||||
)}
|
|
||||||
|
|
||||||
{withdrawn && (
|
|
||||||
<div style={{ display: 'flex', justifyContent: 'center' }}>
|
|
||||||
<Typography color='primary' variant='body2'>
|
|
||||||
<b>{t('There it goes, thank you!🥇')}</b>
|
|
||||||
</Typography>
|
|
||||||
</div>
|
|
||||||
)}
|
|
||||||
</List>
|
</List>
|
||||||
|
|
||||||
|
<Typography>
|
||||||
|
<b>{t('Coordinators that know your robots')}</b>
|
||||||
|
</Typography>
|
||||||
|
|
||||||
|
{Object.values(federation.coordinators).map((coordinator: Coordinator): JSX.Element => {
|
||||||
|
if (garage.getRobot()?.loading === false) {
|
||||||
|
return (
|
||||||
|
<div key={coordinator.shortAlias}>
|
||||||
|
<RobotInfo
|
||||||
|
coordinator={coordinator}
|
||||||
|
robot={garage.getRobot()}
|
||||||
|
slotIndex={garage.currentSlot}
|
||||||
|
onClose={onClose}
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
} else {
|
||||||
|
return <div key={coordinator.shortAlias} />;
|
||||||
|
}
|
||||||
|
})}
|
||||||
</DialogContent>
|
</DialogContent>
|
||||||
</Dialog>
|
</Dialog>
|
||||||
);
|
);
|
||||||
|
@ -1,225 +0,0 @@
|
|||||||
import React from 'react';
|
|
||||||
import { useTranslation } from 'react-i18next';
|
|
||||||
|
|
||||||
import {
|
|
||||||
Dialog,
|
|
||||||
DialogContent,
|
|
||||||
Divider,
|
|
||||||
Link,
|
|
||||||
List,
|
|
||||||
ListItemText,
|
|
||||||
ListItem,
|
|
||||||
ListItemIcon,
|
|
||||||
Typography,
|
|
||||||
LinearProgress,
|
|
||||||
} from '@mui/material';
|
|
||||||
|
|
||||||
import BoltIcon from '@mui/icons-material/Bolt';
|
|
||||||
import PublicIcon from '@mui/icons-material/Public';
|
|
||||||
import DnsIcon from '@mui/icons-material/Dns';
|
|
||||||
import WebIcon from '@mui/icons-material/Web';
|
|
||||||
import FavoriteIcon from '@mui/icons-material/Favorite';
|
|
||||||
import GitHubIcon from '@mui/icons-material/GitHub';
|
|
||||||
import EqualizerIcon from '@mui/icons-material/Equalizer';
|
|
||||||
|
|
||||||
import { AmbossIcon, BitcoinSignIcon, RoboSatsNoTextIcon } from '../Icons';
|
|
||||||
|
|
||||||
import { pn } from '../../utils';
|
|
||||||
import { type Info } from '../../models';
|
|
||||||
|
|
||||||
interface Props {
|
|
||||||
open: boolean;
|
|
||||||
onClose: () => void;
|
|
||||||
info: Info;
|
|
||||||
}
|
|
||||||
|
|
||||||
const StatsDialog = ({ open = false, onClose, info }: Props): JSX.Element => {
|
|
||||||
const { t } = useTranslation();
|
|
||||||
|
|
||||||
const isOnionAccess = window.location.hostname.endsWith('.onion');
|
|
||||||
|
|
||||||
const ambossURL = isOnionAccess
|
|
||||||
? `http://amboss5jfdzzblty5dr5zaig5twvkgsla6y5xuy6s5c5ogpjfcqgltid.onion/node/${info.node_id}`
|
|
||||||
: `https://amboss.space/node/${info.node_id}`;
|
|
||||||
|
|
||||||
return (
|
|
||||||
<Dialog open={open} onClose={onClose}>
|
|
||||||
<div style={info.loading ? {} : { display: 'none' }}>
|
|
||||||
<LinearProgress />
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<DialogContent>
|
|
||||||
<Typography component='h5' variant='h5'>
|
|
||||||
{t('Stats For Nerds')}
|
|
||||||
</Typography>
|
|
||||||
|
|
||||||
<List dense>
|
|
||||||
<Divider />
|
|
||||||
|
|
||||||
<ListItem>
|
|
||||||
<ListItemIcon>
|
|
||||||
<RoboSatsNoTextIcon
|
|
||||||
sx={{ width: '1.4em', height: '1.4em', right: '0.2em', position: 'relative' }}
|
|
||||||
/>
|
|
||||||
</ListItemIcon>
|
|
||||||
<ListItemText
|
|
||||||
primary={`${t('Client')} ${info.clientVersion} - ${t('Coordinator')} ${
|
|
||||||
info.coordinatorVersion
|
|
||||||
}`}
|
|
||||||
secondary={t('RoboSats version')}
|
|
||||||
/>
|
|
||||||
</ListItem>
|
|
||||||
|
|
||||||
<Divider />
|
|
||||||
|
|
||||||
{info.lnd_version ? (
|
|
||||||
<ListItem>
|
|
||||||
<ListItemIcon>
|
|
||||||
<BoltIcon />
|
|
||||||
</ListItemIcon>
|
|
||||||
<ListItemText primary={info.lnd_version} secondary={t('LND version')} />
|
|
||||||
</ListItem>
|
|
||||||
) : null}
|
|
||||||
|
|
||||||
{info.cln_version ? (
|
|
||||||
<ListItem>
|
|
||||||
<ListItemIcon>
|
|
||||||
<BoltIcon />
|
|
||||||
</ListItemIcon>
|
|
||||||
<ListItemText primary={info.cln_version} secondary={t('CLN version')} />
|
|
||||||
</ListItem>
|
|
||||||
) : null}
|
|
||||||
|
|
||||||
<Divider />
|
|
||||||
|
|
||||||
{info.network === 'testnet' ? (
|
|
||||||
<ListItem>
|
|
||||||
<ListItemIcon>
|
|
||||||
<DnsIcon />
|
|
||||||
</ListItemIcon>
|
|
||||||
<ListItemText secondary={`${t('LN Node')}: ${info.node_alias}`}>
|
|
||||||
<Link
|
|
||||||
target='_blank'
|
|
||||||
href={`https://1ml.com/testnet/node/${info.node_id}`}
|
|
||||||
rel='noreferrer'
|
|
||||||
>
|
|
||||||
{`${info.node_id.slice(0, 12)}... (1ML)`}
|
|
||||||
</Link>
|
|
||||||
</ListItemText>
|
|
||||||
</ListItem>
|
|
||||||
) : (
|
|
||||||
<ListItem>
|
|
||||||
<ListItemIcon>
|
|
||||||
<AmbossIcon />
|
|
||||||
</ListItemIcon>
|
|
||||||
<ListItemText secondary={info.node_alias}>
|
|
||||||
<Link target='_blank' href={ambossURL} rel='noreferrer'>
|
|
||||||
{`${info.node_id.slice(0, 12)}... (AMBOSS)`}
|
|
||||||
</Link>
|
|
||||||
</ListItemText>
|
|
||||||
</ListItem>
|
|
||||||
)}
|
|
||||||
|
|
||||||
<Divider />
|
|
||||||
|
|
||||||
<ListItem>
|
|
||||||
<ListItemIcon>
|
|
||||||
<WebIcon />
|
|
||||||
</ListItemIcon>
|
|
||||||
<ListItemText secondary={info.alternative_name}>
|
|
||||||
<Link target='_blank' href={`http://${info.alternative_site}`} rel='noreferrer'>
|
|
||||||
{`${info.alternative_site.slice(0, 12)}...onion`}
|
|
||||||
</Link>
|
|
||||||
</ListItemText>
|
|
||||||
</ListItem>
|
|
||||||
|
|
||||||
<Divider />
|
|
||||||
|
|
||||||
<ListItem>
|
|
||||||
<ListItemIcon>
|
|
||||||
<GitHubIcon />
|
|
||||||
</ListItemIcon>
|
|
||||||
<ListItemText secondary={t('Coordinator commit hash')}>
|
|
||||||
<Link
|
|
||||||
target='_blank'
|
|
||||||
href={`https://github.com/RoboSats/robosats/tree/${info.robosats_running_commit_hash}`}
|
|
||||||
rel='noreferrer'
|
|
||||||
>
|
|
||||||
{`${info.robosats_running_commit_hash.slice(0, 12)}...`}
|
|
||||||
</Link>
|
|
||||||
</ListItemText>
|
|
||||||
</ListItem>
|
|
||||||
|
|
||||||
<Divider />
|
|
||||||
|
|
||||||
<ListItem>
|
|
||||||
<ListItemIcon>
|
|
||||||
<EqualizerIcon />
|
|
||||||
</ListItemIcon>
|
|
||||||
<ListItemText secondary={t('24h contracted volume')}>
|
|
||||||
<div
|
|
||||||
style={{
|
|
||||||
cursor: 'pointer',
|
|
||||||
display: 'flex',
|
|
||||||
alignItems: 'center',
|
|
||||||
flexWrap: 'wrap',
|
|
||||||
}}
|
|
||||||
>
|
|
||||||
{pn(info.last_day_volume)}
|
|
||||||
<BitcoinSignIcon sx={{ width: 14, height: 14 }} color={'text.secondary'} />
|
|
||||||
</div>
|
|
||||||
</ListItemText>
|
|
||||||
</ListItem>
|
|
||||||
|
|
||||||
<Divider />
|
|
||||||
|
|
||||||
<ListItem>
|
|
||||||
<ListItemIcon>
|
|
||||||
<EqualizerIcon />
|
|
||||||
</ListItemIcon>
|
|
||||||
<ListItemText secondary={t('Lifetime contracted volume')}>
|
|
||||||
<div
|
|
||||||
style={{
|
|
||||||
cursor: 'pointer',
|
|
||||||
display: 'flex',
|
|
||||||
alignItems: 'center',
|
|
||||||
flexWrap: 'wrap',
|
|
||||||
}}
|
|
||||||
>
|
|
||||||
{pn(info.lifetime_volume)}
|
|
||||||
<BitcoinSignIcon sx={{ width: 14, height: 14 }} color={'text.secondary'} />
|
|
||||||
</div>
|
|
||||||
</ListItemText>
|
|
||||||
</ListItem>
|
|
||||||
|
|
||||||
<Divider />
|
|
||||||
<ListItem>
|
|
||||||
<ListItemIcon>
|
|
||||||
<PublicIcon />
|
|
||||||
</ListItemIcon>
|
|
||||||
<ListItemText
|
|
||||||
primary={
|
|
||||||
<div
|
|
||||||
style={{
|
|
||||||
display: 'flex',
|
|
||||||
alignItems: 'center',
|
|
||||||
justifyContent: 'left',
|
|
||||||
flexWrap: 'wrap',
|
|
||||||
}}
|
|
||||||
>
|
|
||||||
<span>{`${t('Made with')} `}</span>
|
|
||||||
<FavoriteIcon sx={{ color: '#ff0000', height: '22px', width: '22px' }} />
|
|
||||||
<span>{` ${t('and')} `}</span>
|
|
||||||
<BoltIcon sx={{ color: '#fcba03', height: '23px', width: '23px' }} />
|
|
||||||
</div>
|
|
||||||
}
|
|
||||||
secondary={t('... somewhere on Earth!')}
|
|
||||||
/>
|
|
||||||
</ListItem>
|
|
||||||
</List>
|
|
||||||
</DialogContent>
|
|
||||||
</Dialog>
|
|
||||||
);
|
|
||||||
};
|
|
||||||
|
|
||||||
export default StatsDialog;
|
|
@ -14,7 +14,7 @@ import {
|
|||||||
} from '@mui/material';
|
} from '@mui/material';
|
||||||
import { systemClient } from '../../services/System';
|
import { systemClient } from '../../services/System';
|
||||||
import ContentCopy from '@mui/icons-material/ContentCopy';
|
import ContentCopy from '@mui/icons-material/ContentCopy';
|
||||||
import { AppContext, type UseAppStoreType } from '../../contexts/AppContext';
|
import { GarageContext, type UseGarageStoreType } from '../../contexts/GarageContext';
|
||||||
|
|
||||||
interface Props {
|
interface Props {
|
||||||
open: boolean;
|
open: boolean;
|
||||||
@ -24,7 +24,7 @@ interface Props {
|
|||||||
}
|
}
|
||||||
|
|
||||||
const StoreTokenDialog = ({ open, onClose, onClickBack, onClickDone }: Props): JSX.Element => {
|
const StoreTokenDialog = ({ open, onClose, onClickBack, onClickDone }: Props): JSX.Element => {
|
||||||
const { robot } = useContext<UseAppStoreType>(AppContext);
|
const { garage } = useContext<UseGarageStoreType>(GarageContext);
|
||||||
const { t } = useTranslation();
|
const { t } = useTranslation();
|
||||||
|
|
||||||
return (
|
return (
|
||||||
@ -43,7 +43,7 @@ const StoreTokenDialog = ({ open, onClose, onClickBack, onClickDone }: Props): J
|
|||||||
sx={{ width: '100%', maxWidth: '550px' }}
|
sx={{ width: '100%', maxWidth: '550px' }}
|
||||||
disabled
|
disabled
|
||||||
label={t('Back it up!')}
|
label={t('Back it up!')}
|
||||||
value={robot.token}
|
value={garage.getRobot().token}
|
||||||
variant='filled'
|
variant='filled'
|
||||||
size='small'
|
size='small'
|
||||||
InputProps={{
|
InputProps={{
|
||||||
@ -51,7 +51,7 @@ const StoreTokenDialog = ({ open, onClose, onClickBack, onClickDone }: Props): J
|
|||||||
<Tooltip disableHoverListener enterTouchDelay={0} title={t('Copied!')}>
|
<Tooltip disableHoverListener enterTouchDelay={0} title={t('Copied!')}>
|
||||||
<IconButton
|
<IconButton
|
||||||
onClick={() => {
|
onClick={() => {
|
||||||
systemClient.copyToClipboard(robot.token);
|
systemClient.copyToClipboard(garage.getRobot().token);
|
||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
<ContentCopy color='primary' />
|
<ContentCopy color='primary' />
|
||||||
|
@ -1,4 +1,4 @@
|
|||||||
import React from 'react';
|
import React, { useEffect, useState } from 'react';
|
||||||
import { useTranslation } from 'react-i18next';
|
import { useTranslation } from 'react-i18next';
|
||||||
|
|
||||||
import {
|
import {
|
||||||
@ -17,24 +17,32 @@ import {
|
|||||||
import WebIcon from '@mui/icons-material/Web';
|
import WebIcon from '@mui/icons-material/Web';
|
||||||
import AndroidIcon from '@mui/icons-material/Android';
|
import AndroidIcon from '@mui/icons-material/Android';
|
||||||
import UpcomingIcon from '@mui/icons-material/Upcoming';
|
import UpcomingIcon from '@mui/icons-material/Upcoming';
|
||||||
|
import { checkVer } from '../../utils';
|
||||||
|
import { type Version } from '../../models';
|
||||||
|
|
||||||
interface Props {
|
interface Props {
|
||||||
open: boolean;
|
coordinatorVersion: Version;
|
||||||
clientVersion: string;
|
clientVersion: Version;
|
||||||
coordinatorVersion: string;
|
|
||||||
onClose: () => void;
|
onClose: () => void;
|
||||||
}
|
}
|
||||||
|
|
||||||
const UpdateClientDialog = ({
|
const UpdateDialog = ({ coordinatorVersion, clientVersion }: Props): JSX.Element => {
|
||||||
open = false,
|
|
||||||
clientVersion,
|
|
||||||
coordinatorVersion,
|
|
||||||
onClose,
|
|
||||||
}: Props): JSX.Element => {
|
|
||||||
const { t } = useTranslation();
|
const { t } = useTranslation();
|
||||||
|
const [open, setOpen] = useState<boolean>(() => checkVer(coordinatorVersion));
|
||||||
|
const coordinatorString = `v${coordinatorVersion.major}-${coordinatorVersion.minor}-${coordinatorVersion.patch}`;
|
||||||
|
const clientString = `v${clientVersion.major}-${clientVersion.minor}-${clientVersion.patch}`;
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
setOpen(checkVer(coordinatorVersion));
|
||||||
|
}, [coordinatorVersion]);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Dialog open={open} onClose={onClose}>
|
<Dialog
|
||||||
|
open={open}
|
||||||
|
onClose={() => {
|
||||||
|
setOpen(false);
|
||||||
|
}}
|
||||||
|
>
|
||||||
<DialogContent>
|
<DialogContent>
|
||||||
<Typography component='h5' variant='h5'>
|
<Typography component='h5' variant='h5'>
|
||||||
{t('Update your RoboSats client')}
|
{t('Update your RoboSats client')}
|
||||||
@ -45,7 +53,7 @@ const UpdateClientDialog = ({
|
|||||||
<Typography>
|
<Typography>
|
||||||
{t(
|
{t(
|
||||||
'The RoboSats coordinator is on version {{coordinatorVersion}}, but your client app is {{clientVersion}}. This version mismatch might lead to a bad user experience.',
|
'The RoboSats coordinator is on version {{coordinatorVersion}}, but your client app is {{clientVersion}}. This version mismatch might lead to a bad user experience.',
|
||||||
{ coordinatorVersion, clientVersion },
|
{ coordinatorString, clientString },
|
||||||
)}
|
)}
|
||||||
</Typography>
|
</Typography>
|
||||||
|
|
||||||
@ -53,7 +61,7 @@ const UpdateClientDialog = ({
|
|||||||
<ListItemButton
|
<ListItemButton
|
||||||
component='a'
|
component='a'
|
||||||
target='_blank'
|
target='_blank'
|
||||||
href={`https://github.com/RoboSats/robosats/releases/tag/${coordinatorVersion}-alpha`}
|
href={`https://github.com/RoboSats/robosats/releases/tag/${coordinatorString}-alpha`}
|
||||||
rel='noreferrer'
|
rel='noreferrer'
|
||||||
>
|
>
|
||||||
<ListItemIcon>
|
<ListItemIcon>
|
||||||
@ -107,7 +115,13 @@ const UpdateClientDialog = ({
|
|||||||
</ListItemButton>
|
</ListItemButton>
|
||||||
|
|
||||||
<DialogActions>
|
<DialogActions>
|
||||||
<Button onClick={onClose}>{t('Go away!')}</Button>
|
<Button
|
||||||
|
onClick={() => {
|
||||||
|
setOpen(false);
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
{t('Go away!')}
|
||||||
|
</Button>
|
||||||
</DialogActions>
|
</DialogActions>
|
||||||
</List>
|
</List>
|
||||||
</DialogContent>
|
</DialogContent>
|
||||||
@ -115,4 +129,4 @@ const UpdateClientDialog = ({
|
|||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|
||||||
export default UpdateClientDialog;
|
export default UpdateDialog;
|
@ -1,14 +1,15 @@
|
|||||||
export { default as AuditPGPDialog } from './AuditPGP';
|
export { default as AuditPGPDialog } from './AuditPGP';
|
||||||
export { default as CommunityDialog } from './Community';
|
export { default as CommunityDialog } from './Community';
|
||||||
export { default as InfoDialog } from './Info';
|
export { default as AboutDialog } from './About';
|
||||||
export { default as LearnDialog } from './Learn';
|
export { default as LearnDialog } from './Learn';
|
||||||
export { default as NoRobotDialog } from './NoRobot';
|
export { default as NoRobotDialog } from './NoRobot';
|
||||||
export { default as StoreTokenDialog } from './StoreToken';
|
export { default as StoreTokenDialog } from './StoreToken';
|
||||||
export { default as ConfirmationDialog } from './Confirmation';
|
export { default as ConfirmationDialog } from './Confirmation';
|
||||||
export { default as CoordinatorSummaryDialog } from './CoordinatorSummary';
|
export { default as ExchangeDialog } from './Exchange';
|
||||||
|
export { default as CoordinatorDialog } from './Coordinator';
|
||||||
export { default as ProfileDialog } from './Profile';
|
export { default as ProfileDialog } from './Profile';
|
||||||
export { default as StatsDialog } from './Stats';
|
export { default as ClientDialog } from './Client';
|
||||||
export { default as EnableTelegramDialog } from './EnableTelegram';
|
export { default as EnableTelegramDialog } from './EnableTelegram';
|
||||||
export { default as UpdateClientDialog } from './UpdateClient';
|
|
||||||
export { default as NoticeDialog } from './Notice';
|
export { default as NoticeDialog } from './Notice';
|
||||||
export { default as F2fMapDialog } from './F2fMap';
|
export { default as F2fMapDialog } from './F2fMap';
|
||||||
|
export { default as UpdateDialog } from './Update';
|
||||||
|
@ -30,7 +30,7 @@ export default class ErrorBoundary extends Component<ErrorBoundaryProps, ErrorBo
|
|||||||
}, 30000);
|
}, 30000);
|
||||||
}
|
}
|
||||||
|
|
||||||
render() {
|
render(): React.ReactNode {
|
||||||
if (this.state.hasError) {
|
if (this.state.hasError) {
|
||||||
return (
|
return (
|
||||||
<div style={{ overflow: 'auto', height: '100%', width: '100%', background: 'white' }}>
|
<div style={{ overflow: 'auto', height: '100%', width: '100%', background: 'white' }}>
|
||||||
|
250
frontend/src/components/FederationTable/index.tsx
Normal file
250
frontend/src/components/FederationTable/index.tsx
Normal file
@ -0,0 +1,250 @@
|
|||||||
|
import React, { useCallback, useEffect, useState, useContext } from 'react';
|
||||||
|
import { useTranslation } from 'react-i18next';
|
||||||
|
import { Box, useTheme, Checkbox, CircularProgress, Typography, Grid } from '@mui/material';
|
||||||
|
import { DataGrid, type GridColDef, type GridValidRowModel } from '@mui/x-data-grid';
|
||||||
|
import { type Coordinator } from '../../models';
|
||||||
|
import RobotAvatar from '../RobotAvatar';
|
||||||
|
import { Link, LinkOff } from '@mui/icons-material';
|
||||||
|
import { AppContext, UseAppStoreType } from '../../contexts/AppContext';
|
||||||
|
import {
|
||||||
|
ActionFederation,
|
||||||
|
UseFederationStoreType,
|
||||||
|
FederationContext,
|
||||||
|
} from '../../contexts/FederationContext';
|
||||||
|
|
||||||
|
interface FederationTableProps {
|
||||||
|
federation: Record<string, Coordinator>;
|
||||||
|
dispatchFederation: (action: ActionFederation) => void;
|
||||||
|
fetchCoordinatorInfo: (coordinator: Coordinator) => Promise<void>;
|
||||||
|
setFocusedCoordinator: (state: number) => void;
|
||||||
|
openCoordinator: () => void;
|
||||||
|
maxWidth?: number;
|
||||||
|
maxHeight?: number;
|
||||||
|
fillContainer?: boolean;
|
||||||
|
}
|
||||||
|
|
||||||
|
const FederationTable = ({
|
||||||
|
openCoordinator,
|
||||||
|
maxWidth = 90,
|
||||||
|
maxHeight = 50,
|
||||||
|
fillContainer = false,
|
||||||
|
}: FederationTableProps): JSX.Element => {
|
||||||
|
const { t } = useTranslation();
|
||||||
|
const { federation, setFocusedCoordinator } =
|
||||||
|
useContext<UseFederationStoreType>(FederationContext);
|
||||||
|
const { hostUrl } = useContext<UseAppStoreType>(AppContext);
|
||||||
|
const theme = useTheme();
|
||||||
|
const [pageSize, setPageSize] = useState<number>(0);
|
||||||
|
|
||||||
|
// all sizes in 'em'
|
||||||
|
const fontSize = theme.typography.fontSize;
|
||||||
|
const verticalHeightFrame = 3.25;
|
||||||
|
const verticalHeightRow = 3.25;
|
||||||
|
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);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
const localeText = {
|
||||||
|
MuiTablePagination: { labelRowsPerPage: t('Coordinators per page:') },
|
||||||
|
noResultsOverlayLabel: t('No coordinators found.'),
|
||||||
|
};
|
||||||
|
|
||||||
|
const onClickCoordinator = function (shortAlias: string): void {
|
||||||
|
setFocusedCoordinator(shortAlias);
|
||||||
|
openCoordinator();
|
||||||
|
};
|
||||||
|
|
||||||
|
const aliasObj = useCallback((width: number) => {
|
||||||
|
return {
|
||||||
|
field: 'longAlias',
|
||||||
|
headerName: t('Coordinator'),
|
||||||
|
width: width * fontSize,
|
||||||
|
renderCell: (params: any) => {
|
||||||
|
return (
|
||||||
|
<Grid
|
||||||
|
container
|
||||||
|
direction='row'
|
||||||
|
sx={{ cursor: 'pointer', position: 'relative', left: '-0.3em', width: '50em' }}
|
||||||
|
wrap='nowrap'
|
||||||
|
onClick={() => {
|
||||||
|
onClickCoordinator(params.row.shortAlias);
|
||||||
|
}}
|
||||||
|
alignItems='center'
|
||||||
|
spacing={1}
|
||||||
|
>
|
||||||
|
<Grid item>
|
||||||
|
<RobotAvatar
|
||||||
|
nickname={params.row.shortAlias}
|
||||||
|
coordinator={true}
|
||||||
|
style={{ width: '3.215em', height: '3.215em' }}
|
||||||
|
smooth={true}
|
||||||
|
flipHorizontally={true}
|
||||||
|
baseUrl={hostUrl}
|
||||||
|
small={true}
|
||||||
|
/>
|
||||||
|
</Grid>
|
||||||
|
<Grid item>
|
||||||
|
<Typography>{params.row.longAlias}</Typography>
|
||||||
|
</Grid>
|
||||||
|
</Grid>
|
||||||
|
);
|
||||||
|
},
|
||||||
|
};
|
||||||
|
}, []);
|
||||||
|
|
||||||
|
const enabledObj = useCallback(
|
||||||
|
(width: number) => {
|
||||||
|
return {
|
||||||
|
field: 'enabled',
|
||||||
|
headerName: t('Enabled'),
|
||||||
|
width: width * fontSize,
|
||||||
|
renderCell: (params: any) => {
|
||||||
|
return (
|
||||||
|
<Checkbox
|
||||||
|
checked={params.row.enabled}
|
||||||
|
onClick={() => {
|
||||||
|
onEnableChange(params.row.shortAlias);
|
||||||
|
}}
|
||||||
|
/>
|
||||||
|
);
|
||||||
|
},
|
||||||
|
};
|
||||||
|
},
|
||||||
|
[federation],
|
||||||
|
);
|
||||||
|
|
||||||
|
const upObj = useCallback((width: number) => {
|
||||||
|
return {
|
||||||
|
field: 'up',
|
||||||
|
headerName: t('Up'),
|
||||||
|
width: width * fontSize,
|
||||||
|
renderCell: (params: any) => {
|
||||||
|
return (
|
||||||
|
<div
|
||||||
|
style={{ cursor: 'pointer' }}
|
||||||
|
onClick={() => {
|
||||||
|
onClickCoordinator(params.row.shortAlias);
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
{Boolean(params.row.loadingInfo) && Boolean(params.row.enabled) ? (
|
||||||
|
<CircularProgress thickness={0.35 * fontSize} size={1.5 * fontSize} />
|
||||||
|
) : params.row.info !== undefined ? (
|
||||||
|
<Link color='success' />
|
||||||
|
) : (
|
||||||
|
<LinkOff color='error' />
|
||||||
|
)}
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
},
|
||||||
|
};
|
||||||
|
}, []);
|
||||||
|
|
||||||
|
const columnSpecs = {
|
||||||
|
alias: {
|
||||||
|
priority: 2,
|
||||||
|
order: 1,
|
||||||
|
normal: {
|
||||||
|
width: 12.1,
|
||||||
|
object: aliasObj,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
up: {
|
||||||
|
priority: 3,
|
||||||
|
order: 2,
|
||||||
|
normal: {
|
||||||
|
width: 3.5,
|
||||||
|
object: upObj,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
enabled: {
|
||||||
|
priority: 1,
|
||||||
|
order: 3,
|
||||||
|
normal: {
|
||||||
|
width: 5,
|
||||||
|
object: enabledObj,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
};
|
||||||
|
|
||||||
|
const filteredColumns = function (): {
|
||||||
|
columns: Array<GridColDef<GridValidRowModel>>;
|
||||||
|
width: number;
|
||||||
|
} {
|
||||||
|
const useSmall = maxWidth < 30;
|
||||||
|
const selectedColumns: object[] = [];
|
||||||
|
let width: number = 0;
|
||||||
|
|
||||||
|
for (const value of Object.values(columnSpecs)) {
|
||||||
|
const colWidth = Number(
|
||||||
|
useSmall && Boolean(value.small) ? value.small.width : value.normal.width,
|
||||||
|
);
|
||||||
|
const colObject = useSmall && Boolean(value.small) ? value.small.object : value.normal.object;
|
||||||
|
|
||||||
|
if (width + colWidth < maxWidth || selectedColumns.length < 2) {
|
||||||
|
width = width + colWidth;
|
||||||
|
selectedColumns.push([colObject(colWidth, false), value.order]);
|
||||||
|
} else {
|
||||||
|
selectedColumns.push([colObject(colWidth, true), value.order]);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// sort columns by column.order value
|
||||||
|
selectedColumns.sort(function (first, second) {
|
||||||
|
return first[1] - second[1];
|
||||||
|
});
|
||||||
|
|
||||||
|
const columns: Array<GridColDef<GridValidRowModel>> = selectedColumns.map(function (item) {
|
||||||
|
return item[0];
|
||||||
|
});
|
||||||
|
|
||||||
|
return { columns, width: width * 0.9 };
|
||||||
|
};
|
||||||
|
|
||||||
|
const { columns, width } = filteredColumns();
|
||||||
|
|
||||||
|
const onEnableChange = function (shortAlias: string): void {
|
||||||
|
if (federation.getCoordinator(shortAlias).enabled) {
|
||||||
|
federation.disbaleCoordinator(shortAlias);
|
||||||
|
} else {
|
||||||
|
federation.enaleCoordinator(shortAlias);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
return (
|
||||||
|
<Box
|
||||||
|
sx={
|
||||||
|
fillContainer
|
||||||
|
? { width: '100%', height: '100%' }
|
||||||
|
: { width: `${width}em`, height: `${height}em`, overflow: 'auto' }
|
||||||
|
}
|
||||||
|
>
|
||||||
|
<DataGrid
|
||||||
|
localeText={localeText}
|
||||||
|
rowHeight={3.714 * theme.typography.fontSize}
|
||||||
|
headerHeight={3.25 * theme.typography.fontSize}
|
||||||
|
rows={Object.values(federation.coordinators)}
|
||||||
|
getRowId={(params: Coordinator) => params.shortAlias}
|
||||||
|
columns={columns}
|
||||||
|
checkboxSelection={false}
|
||||||
|
pageSize={pageSize}
|
||||||
|
rowsPerPageOptions={width < 22 ? [] : [0, pageSize, defaultPageSize * 2, 50, 100]}
|
||||||
|
onPageSizeChange={(newPageSize) => {
|
||||||
|
setPageSize(newPageSize);
|
||||||
|
setUseDefaultPageSize(false);
|
||||||
|
}}
|
||||||
|
hideFooter={true}
|
||||||
|
/>
|
||||||
|
</Box>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
export default FederationTable;
|
@ -1,6 +1,6 @@
|
|||||||
import React, { useState } from 'react';
|
import React, { useState } from 'react';
|
||||||
import { useTranslation } from 'react-i18next';
|
import { useTranslation } from 'react-i18next';
|
||||||
import { Paper, Alert, AlertTitle, Button, Link } from '@mui/material';
|
import { Paper, Alert, AlertTitle, Button } from '@mui/material';
|
||||||
|
|
||||||
const SelfhostedAlert = (): JSX.Element => {
|
const SelfhostedAlert = (): JSX.Element => {
|
||||||
const { t } = useTranslation();
|
const { t } = useTranslation();
|
||||||
|
@ -12,7 +12,7 @@ const UnsafeAlert = (): JSX.Element => {
|
|||||||
const [unsafeClient, setUnsafeClient] = useState<boolean>(false);
|
const [unsafeClient, setUnsafeClient] = useState<boolean>(false);
|
||||||
|
|
||||||
// To do. Read from Coordinators Obj.
|
// To do. Read from Coordinators Obj.
|
||||||
const safe_urls = [
|
const safeUrls = [
|
||||||
'robosats6tkf3eva7x2voqso3a5wcorsnw34jveyxfqi2fu7oyheasid.onion',
|
'robosats6tkf3eva7x2voqso3a5wcorsnw34jveyxfqi2fu7oyheasid.onion',
|
||||||
'robotestagw3dcxmd66r4rgksb4nmmr43fh77bzn2ia2eucduyeafnyd.onion',
|
'robotestagw3dcxmd66r4rgksb4nmmr43fh77bzn2ia2eucduyeafnyd.onion',
|
||||||
'robodevs7ixniseezbv7uryxhamtz3hvcelzfwpx3rvoipttjomrmpqd.onion',
|
'robodevs7ixniseezbv7uryxhamtz3hvcelzfwpx3rvoipttjomrmpqd.onion',
|
||||||
@ -20,11 +20,8 @@ const UnsafeAlert = (): JSX.Element => {
|
|||||||
'r7r4sckft6ptmk4r2jajiuqbowqyxiwsle4iyg4fijtoordc6z7a.b32.i2p',
|
'r7r4sckft6ptmk4r2jajiuqbowqyxiwsle4iyg4fijtoordc6z7a.b32.i2p',
|
||||||
];
|
];
|
||||||
|
|
||||||
const checkClient = () => {
|
const checkClient = (): void => {
|
||||||
const http = new XMLHttpRequest();
|
const unsafe = !safeUrls.includes(getHost());
|
||||||
const h = getHost();
|
|
||||||
const unsafe = !safe_urls.includes(h);
|
|
||||||
|
|
||||||
setUnsafeClient(unsafe);
|
setUnsafeClient(unsafe);
|
||||||
};
|
};
|
||||||
|
|
||||||
@ -106,6 +103,8 @@ const UnsafeAlert = (): JSX.Element => {
|
|||||||
)}
|
)}
|
||||||
</Paper>
|
</Paper>
|
||||||
);
|
);
|
||||||
|
} else {
|
||||||
|
return <></>;
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
|
@ -1,4 +1,4 @@
|
|||||||
import React, { useContext } from 'react';
|
import { useContext } from 'react';
|
||||||
import { AppContext, type UseAppStoreType } from '../../contexts/AppContext';
|
import { AppContext, type UseAppStoreType } from '../../contexts/AppContext';
|
||||||
import SelfhostedAlert from './SelfhostedAlert';
|
import SelfhostedAlert from './SelfhostedAlert';
|
||||||
import UnsafeAlert from './UnsafeAlert';
|
import UnsafeAlert from './UnsafeAlert';
|
||||||
|
@ -1,7 +1,7 @@
|
|||||||
import React from 'react';
|
import React from 'react';
|
||||||
import { SvgIcon } from '@mui/material';
|
import { SvgIcon, type SvgIconProps } from '@mui/material';
|
||||||
|
|
||||||
export default function AmbossIcon(props) {
|
const AmbossIcon: React.FC<SvgIconProps> = (props) => {
|
||||||
return (
|
return (
|
||||||
<SvgIcon {...props} x='0px' y='0px' viewBox='0 0 95.7 84.9'>
|
<SvgIcon {...props} x='0px' y='0px' viewBox='0 0 95.7 84.9'>
|
||||||
<g id='Layer_2_00000052094167160547307180000012226084410257483709_'>
|
<g id='Layer_2_00000052094167160547307180000012226084410257483709_'>
|
||||||
@ -25,4 +25,6 @@ export default function AmbossIcon(props) {
|
|||||||
</g>
|
</g>
|
||||||
</SvgIcon>
|
</SvgIcon>
|
||||||
);
|
);
|
||||||
}
|
};
|
||||||
|
|
||||||
|
export default AmbossIcon;
|
||||||
|
152
frontend/src/components/Icons/BadgeDevFund.tsx
Normal file
152
frontend/src/components/Icons/BadgeDevFund.tsx
Normal file
@ -0,0 +1,152 @@
|
|||||||
|
import React from 'react';
|
||||||
|
import { SvgIcon, type SvgIconProps } from '@mui/material';
|
||||||
|
|
||||||
|
const BadgeDevFund: React.FC<SvgIconProps> = (props) => {
|
||||||
|
return (
|
||||||
|
<SvgIcon {...props} viewBox='0 0 100 100'>
|
||||||
|
<g>
|
||||||
|
<path
|
||||||
|
fill='#7BD3FF'
|
||||||
|
d='M100,12.893C100,5.535,93.686,0,86.33,0H13.44C6.085,0,0,5.535,0,12.893v72.879
|
||||||
|
C0,93.129,6.085,100,13.44,100h72.89c7.355,0,13.67-6.871,13.67-14.229V12.893z'
|
||||||
|
/>
|
||||||
|
<g>
|
||||||
|
<path
|
||||||
|
fill='none'
|
||||||
|
d='M60.172,65H38.148c-0.824,0-1.2,0.006-1.2,0.5c0,0.555,0.433,0.5,0.944,0.5h22.024
|
||||||
|
c0.514,0,0.946,0.055,0.946-0.5C60.863,65.053,60.528,65,60.172,65z'
|
||||||
|
/>
|
||||||
|
<g>
|
||||||
|
<path
|
||||||
|
fill='#0275BD'
|
||||||
|
d='M57.228,27.309l-5.355-0.375l-4.449-0.312l-1.681-0.117L41.5,27.358l-9.062,7.438
|
||||||
|
c-0.028,0.057-0.068,0.109-0.12,0.152c-0.022,0.018-0.046,0.033-0.068,0.047l-2.752,11.648c0.317,0.322,1.107,1.011,1.999,0.887
|
||||||
|
c0.912-0.126,1.784-1.099,2.522-2.813c0.486-1.128,0.922-2.109,1.309-2.961c0.037,0.284,0.082,0.565,0.136,0.845
|
||||||
|
c0.051,0.26,0.106,0.518,0.171,0.772c1.481,5.843,6.773,10.167,13.078,10.167c7.449,0,13.49-6.041,13.49-13.49
|
||||||
|
c0-0.873-0.084-1.725-0.243-2.55l-0.689,0.233l-0.692,0.232c0.12,0.678,0.182,1.373,0.182,2.084
|
||||||
|
c0,6.643-5.404,12.047-12.047,12.047c-6.315,0-11.342-4.883-11.842-11.07C36.846,40.705,37,40.379,37,40.051
|
||||||
|
c0-0.013,0-0.025,0-0.039c0-0.404-0.145-0.805-0.105-1.199c0.621-6.062,5.677-10.81,11.902-10.81
|
||||||
|
c2.838,0,5.406,0.987,7.469,2.636l0.202-0.74l0.213-0.74L57.228,27.309z'
|
||||||
|
/>
|
||||||
|
|
||||||
|
<rect
|
||||||
|
x='41.968'
|
||||||
|
y='43.084'
|
||||||
|
transform='matrix(0.9718 0.2357 -0.2357 0.9718 11.7218 -9.985)'
|
||||||
|
fill='#0275BD'
|
||||||
|
width='11.31'
|
||||||
|
height='1.9'
|
||||||
|
/>
|
||||||
|
<path
|
||||||
|
fill='#0275BD'
|
||||||
|
d='M42.923,39.859l5.267,1.277c-0.005-0.029-0.01-0.059-0.015-0.088c-0.139-1.016,0.423-1.721,0.445-1.749
|
||||||
|
l0.01-0.012l-5.259-1.276L42.923,39.859z'
|
||||||
|
/>
|
||||||
|
<path
|
||||||
|
fill='#0275BD'
|
||||||
|
d='M53.914,42.526l0.065-0.272c-0.133,0.068-0.263,0.133-0.39,0.195L53.914,42.526z'
|
||||||
|
/>
|
||||||
|
<path
|
||||||
|
fill='#0275BD'
|
||||||
|
d='M43.863,36.33l6.238,1.514c0.055-0.053,0.11-0.106,0.168-0.163c0.441-0.427,0.937-0.906,1.445-1.401
|
||||||
|
l-7.402-1.795L43.863,36.33z'
|
||||||
|
/>
|
||||||
|
|
||||||
|
<rect
|
||||||
|
x='45.277'
|
||||||
|
y='46.947'
|
||||||
|
transform='matrix(-0.2448 0.9696 -0.9696 -0.2448 104.3776 14.2907)'
|
||||||
|
fill='#0275BD'
|
||||||
|
width='2.692'
|
||||||
|
height='1.692'
|
||||||
|
/>
|
||||||
|
<polygon
|
||||||
|
fill='#0275BD'
|
||||||
|
points='50.902,34.197 51.019,33.735 51.136,33.274 51.532,31.698 49.893,31.285 49.435,33.104
|
||||||
|
49.317,33.565 49.235,33.895 50.134,34.121 50.874,34.308 '
|
||||||
|
/>
|
||||||
|
<path
|
||||||
|
fill='#0275BD'
|
||||||
|
d='M71.156,32.678l4.156-8.516c-0.048-0.015-0.096-0.029-0.142-0.049l-0.012,0.039l-0.789-0.244l-8.853-2.74
|
||||||
|
c-1.147,0.99-2.557,2.221-3.837,3.354c-0.119,0.107-0.239,0.215-0.357,0.32c-0.123,0.109-0.244,0.217-0.362,0.322
|
||||||
|
c-1.068,0.954-1.973,1.781-2.448,2.256c-0.175,0.175-0.294,0.01-0.338,0.075c0.001-0.002-0.056,0.001-0.099,0.019l-0.611,1.979
|
||||||
|
l-0.23,0.91l-0.231,0.842l-0.402,1.37l0.148-0.073l0.777,1.258l0.301,0.487l0.303,0.487l1.556,2.5l0.693-0.232l0.685-0.232
|
||||||
|
l0.687-0.23l5.118-1.729C69.259,34.021,70.797,32.943,71.156,32.678z M66.1,26.309c-0.34,0-0.646-0.146-0.858-0.379
|
||||||
|
c-0.193-0.209-0.312-0.486-0.312-0.791c0-0.646,0.524-1.17,1.17-1.17s1.169,0.523,1.169,1.17c0,0.123-0.019,0.242-0.055,0.354
|
||||||
|
C67.063,25.965,66.622,26.309,66.1,26.309z'
|
||||||
|
/>
|
||||||
|
<path
|
||||||
|
fill='#0275BD'
|
||||||
|
d='M26.862,36.773c0.965,0,2.087-0.457,3.338-1.357l1.13-0.927l-2.778-3.38
|
||||||
|
c-0.091-0.109-0.121-0.247-0.1-0.376l-4.283,4.135C24.44,35.45,25.235,36.773,26.862,36.773z'
|
||||||
|
/>
|
||||||
|
<path
|
||||||
|
fill='#0275BD'
|
||||||
|
d='M31.657,33.404l0.376,0.457l0.024,0.03l0.421-0.345l3.94-3.234l-1.29-1.811l-1.778-2.498l-4.496,4.341
|
||||||
|
c0.156-0.021,0.317,0.036,0.424,0.166L31.657,33.404z'
|
||||||
|
/>
|
||||||
|
<polygon
|
||||||
|
fill='#0275BD'
|
||||||
|
points='37.148,29.713 40.781,26.729 40.08,22.621 39.996,22.131 39.912,21.641 39.623,19.949
|
||||||
|
34.036,25.342 35.66,27.624 '
|
||||||
|
/>
|
||||||
|
<path
|
||||||
|
fill='#0275BD'
|
||||||
|
d='M46.396,19.289l0.491,5.988L48,25.604c0,0.013,0,0.024,0,0.036l10.742,0.791
|
||||||
|
c0.473-0.459,0.915-1.121,2.006-2.096c0.113-0.101,0.094-0.205,0.215-0.312c0.113-0.102,0.161-0.204,0.281-0.311
|
||||||
|
c0.032-0.029,0.028-0.055,0.061-0.084c1.894-1.676,3.794-3.328,3.814-3.345l0.188-0.171l9.377,2.905l-8.331-9.662l-0.448,0.257
|
||||||
|
l-0.077,0.016c-2.756,0.532-12.898,2.773-19.544,4.252l0.073,0.924L46.396,19.289z M64.687,15.846c0.646,0,1.17,0.523,1.17,1.17
|
||||||
|
c0,0.646-0.524,1.168-1.17,1.168s-1.169-0.522-1.169-1.168C63.518,16.369,64.041,15.846,64.687,15.846z M48.854,18.955
|
||||||
|
c0.215-0.245,0.529-0.401,0.881-0.401c0.338,0,0.641,0.144,0.854,0.372c0.194,0.209,0.315,0.488,0.315,0.797
|
||||||
|
c0,0.646-0.524,1.17-1.17,1.17s-1.169-0.523-1.169-1.17C48.565,19.43,48.675,19.16,48.854,18.955z'
|
||||||
|
/>
|
||||||
|
<path
|
||||||
|
fill='#0275BD'
|
||||||
|
d='M40.871,21.658l0.084,0.49l0.719,4.215l4.008-0.809l0.272,0.02l-0.439-5.49L45.476,19.6l-0.039-0.484
|
||||||
|
l-0.082-1.029c-2.497,0.557-4.357,0.973-4.905,1.096l0.339,1.986L40.871,21.658z'
|
||||||
|
/>
|
||||||
|
<path
|
||||||
|
fill='#0275BD'
|
||||||
|
d='M49.348,39.898c-0.037,0.051-0.323,0.459-0.237,1.039c0.022,0.153,0.068,0.306,0.139,0.457
|
||||||
|
c0.186,0.405,0.536,0.805,1.051,1.193c0.268-0.041,0.873-0.166,1.764-0.51c0.35-0.135,0.742-0.304,1.176-0.515
|
||||||
|
c-0.396-0.544-0.732-1.007-1.019-1.403c-0.659-0.915-1.05-1.473-1.283-1.816c-0.004,0.006-0.008,0.01-0.013,0.014
|
||||||
|
c-0.47,0.455-0.924,0.895-1.225,1.189C49.518,39.727,49.39,39.854,49.348,39.898z'
|
||||||
|
/>
|
||||||
|
<path
|
||||||
|
fill='#0275BD'
|
||||||
|
d='M54.281,41.015c1.28-0.724,2.82-1.774,4.57-3.298l-1.87-3.002l-0.306-0.491l-0.307-0.491l-0.291-0.467
|
||||||
|
c-0.121,0.127-0.254,0.266-0.399,0.414c-0.134,0.139-0.278,0.285-0.43,0.438c-0.14,0.141-0.284,0.287-0.436,0.438
|
||||||
|
c-0.618,0.617-1.322,1.308-2.018,1.986c-0.399,0.39-0.797,0.774-1.172,1.14c0.105,0.157,0.258,0.377,0.442,0.639
|
||||||
|
c0.41,0.582,0.979,1.372,1.567,2.182c0.149,0.208,0.301,0.416,0.452,0.623C54.15,41.089,54.216,41.051,54.281,41.015z'
|
||||||
|
/>
|
||||||
|
<polygon
|
||||||
|
fill='#0275BD'
|
||||||
|
points='79.825,16.213 69.762,10.616 67.608,12.816 75.157,21.717 '
|
||||||
|
/>
|
||||||
|
<polygon
|
||||||
|
fill='#0275BD'
|
||||||
|
points='70.018,9.68 79.255,14.817 80.446,15.48 80.866,15.714 81.285,15.947 81.969,16.328
|
||||||
|
83.609,12.775 70.943,5.73 69.304,9.283 69.591,9.443 '
|
||||||
|
/>
|
||||||
|
<path
|
||||||
|
fill='#0275BD'
|
||||||
|
d='M81.919,91.473c0.007-0.013,0.014-0.025,0.021-0.038c-0.006-0.017-0.01-0.036-0.015-0.055
|
||||||
|
C81.923,91.41,81.921,91.44,81.919,91.473z'
|
||||||
|
/>
|
||||||
|
<path
|
||||||
|
fill='#0275BD'
|
||||||
|
d='M77.68,59.103c-0.064-0.093-54.519-0.051-54.519-0.051L14,73.543V91.86v0.251v0.061
|
||||||
|
c0,0.013,0.137,0.025,0.138,0.038c0.002,0.004,0.069,0.041,0.073,0.044c0.051,0.4,0.448,0.746,0.851,0.746h0.016
|
||||||
|
C15.514,93,17,92.563,17,92.111V91.86c0-0.015-0.001-0.029-0.002-0.044C16.999,91.815,17,91.813,17,91.812V75h65v16.86v0.229
|
||||||
|
v0.021C82,92.563,82.178,93,82.614,93h0.016c0.437,0,1.37-0.437,1.37-0.889v-0.002V91.86V73.235
|
||||||
|
C84,72.922,77.68,59.103,77.68,59.103z M59.917,66H37.893c-0.512,0-0.944,0.055-0.944-0.5c0-0.494,0.376-0.5,1.2-0.5h22.023
|
||||||
|
c0.356,0,0.691,0.053,0.691,0.5C60.863,66.055,60.431,66,59.917,66z'
|
||||||
|
/>
|
||||||
|
</g>
|
||||||
|
</g>
|
||||||
|
</g>
|
||||||
|
</SvgIcon>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
export default BadgeDevFund;
|
79
frontend/src/components/Icons/BadgeFounder.tsx
Normal file
79
frontend/src/components/Icons/BadgeFounder.tsx
Normal file
@ -0,0 +1,79 @@
|
|||||||
|
import React from 'react';
|
||||||
|
import { SvgIcon, type SvgIconProps } from '@mui/material';
|
||||||
|
|
||||||
|
const BadgeFounder: React.FC<SvgIconProps> = (props) => {
|
||||||
|
return (
|
||||||
|
<SvgIcon {...props} viewBox='0 0 100 100'>
|
||||||
|
<g>
|
||||||
|
<path
|
||||||
|
fill='#DF87FF'
|
||||||
|
d='M100,12.543C100,5.194,93.416,0,86.036,0H13.239C5.858,0,0,5.194,0,12.543v72.912
|
||||||
|
C0,92.804,5.858,100,13.239,100h72.797C93.416,100,100,92.804,100,85.455V12.543z'
|
||||||
|
/>
|
||||||
|
<g>
|
||||||
|
<path
|
||||||
|
fill='#9517CE'
|
||||||
|
d='M47.421,31.641l0.003-0.967c0.009-1.817-1.459-3.296-3.275-3.303l-11.994-0.05
|
||||||
|
c-1.816-0.007-3.296,1.46-3.304,3.276l-0.004,0.965c-0.008,1.818,1.459,3.297,3.277,3.305l11.991,0.049
|
||||||
|
C45.934,34.924,47.413,33.456,47.421,31.641z'
|
||||||
|
/>
|
||||||
|
<path
|
||||||
|
fill='#9517CE'
|
||||||
|
d='M70,45.531v5.008l2.57-4.386c0.43,0.041,0.66,0.063,1.104,0.063c7.59,0,13.642-6.154,13.642-13.746
|
||||||
|
c0-7.59-6.206-13.743-13.797-13.743s-13.357,6.153-13.357,13.743C60.161,38.563,65,43.729,70,45.531z M71.773,41.121l-1.619-0.407
|
||||||
|
l0.649-2.58l1.62,0.409L71.773,41.121z M74.52,23.316l1.619,0.41l-0.65,2.577l-1.62-0.407L74.52,23.316z M69.005,26.477
|
||||||
|
l10.859,2.637l-0.443,1.824l-10.858-2.635L69.005,26.477z M68.075,29.965L78.935,32.6l-0.443,1.824l-10.858-2.635L68.075,29.965z
|
||||||
|
M67.067,33.683l10.86,2.636l-0.444,1.825l-10.858-2.636L67.067,33.683z'
|
||||||
|
/>
|
||||||
|
</g>
|
||||||
|
<path
|
||||||
|
fill='#9517CE'
|
||||||
|
d='M90.127,83.767c-0.516-0.809-0.598-1.344-1.545-1.613L89,83.285V83.18L87.997,82c0.001,0-0.034,0-0.034,0
|
||||||
|
l2.545-11.693c0-4.914-4.002-8.307-8.917-8.307H58.618c-3.943-2-7.946-4.107-11.954-4.908c-0.894-0.18-1.787-0.582-2.682-0.693
|
||||||
|
l-0.006,1.05l-0.004,1.604C46.879,59.443,49.97,60,53.21,62h-1.782c-4.914,0-8.292,2.769-8.897,8.406L40.29,82H22.418h-0.454
|
||||||
|
l-0.011-5.766c3.257-1.551,5.418-4.831,5.434-8.609c0.01-2.396-0.855-4.6-2.303-6.289c2.21-1.017,4.907-1.982,8.001-2.573
|
||||||
|
l-0.01,2.682c-0.01,2.455,1.979,4.505,4.435,4.516L39.49,66h0.019c2.444,0,4.441-2.021,4.451-4.467l0.013-3.036l0.004-1.173
|
||||||
|
l0.006-1.177l0.01-2.543c1.155-0.467,2.245-1.091,3.229-1.862c2.458-1.925,4.144-4.577,4.873-7.701
|
||||||
|
c2.039-8.738,2.42-27.79-9.423-30.905c-0.535-0.141-1.123-0.241-1.757-0.298l0.017-4.204c0.001-0.052-0.001-0.104-0.002-0.154
|
||||||
|
c0.62-0.569,1.009-1.387,1.009-2.295c0-1.719-1.394-3.112-3.112-3.112s-3.112,1.394-3.112,3.112c0,0.808,0.308,1.543,0.812,2.096
|
||||||
|
c-0.009,0.11-0.016,0.222-0.016,0.336l-0.017,4.195c-0.671,0.049-1.292,0.145-1.853,0.285c-11.868,3.02-11.645,22.07-9.677,30.826
|
||||||
|
c1.017,4.521,4.113,8.062,8.146,9.702l-0.011,2.784c-0.16,0.029-0.319,0.056-0.479,0.086c-3.233,0.619-6.439,2.215-9.396,3.664
|
||||||
|
C21.686,59.105,19.827,59,17.825,59h-0.04c-5.282,0-9.598,3.76-9.619,9.043c-0.013,3.072,1.427,5.592,3.668,7.36L11.806,82h-1.646
|
||||||
|
c-1.72,0-2.982,0.588-3.752,1.797c-0.572,0.896-0.839,2.066-0.839,3.706c0,1.639,0.267,2.799,0.839,3.696
|
||||||
|
C7.177,92.407,8.439,93,10.159,93h76.216c1.721,0,2.983-0.598,3.752-1.805c0.573-0.898,0.839-2.074,0.839-3.713
|
||||||
|
S90.7,84.664,90.127,83.767z M42,61.489c0,1.17-1.14,2.122-2.31,2.122l-1.779-0.008C36.737,63.598,36,62.639,36,61.464v-0.172
|
||||||
|
c1,0.363,1.648,0.784,3.059,0.784c0.898,0,1.941-0.171,2.941-0.637V61.489z M42,58.233l-0.182,1.609
|
||||||
|
c-0.329,0.164-0.74,0.299-1.048,0.405c-1.828,0.633-3.298,0.343-4.178-0.017c-0.609-0.248-0.979-0.528-1.019-0.561
|
||||||
|
c-0.045-0.038,0.025-0.724-0.026-0.749l0.031-1.297L36,57.664c0,0.206,0.605,0.43,1.234,0.585c0.486,0.121,0.891,0.2,1.504,0.2
|
||||||
|
c0.604,0,1.367-0.077,2.082-0.27c0.349-0.095,1.18-0.215,1.18-0.367V58.233z M41.661,55.924l-0.002,0.291
|
||||||
|
c-0.331,0.164-0.652,0.3-0.961,0.405c-1.828,0.633-3.255,0.343-4.137-0.017c-0.553-0.225-0.892-0.477-0.978-0.546
|
||||||
|
c-0.009-0.007-0.015-0.012-0.019-0.015c-0.045-0.038-0.093-0.068-0.144-0.094l0.007-1.616c0.91,0.19,1.852,0.292,2.815,0.292
|
||||||
|
c0.081,0,0.161-0.03,0.242-0.031c0.117,0.003,0.236-0.034,0.354-0.034l0,0c0.955,0,1.901-0.079,2.827-0.272L41.661,55.924z
|
||||||
|
M38.368,9.265c0.149,0.022,0.303,0.033,0.458,0.033c0.082,0,0.162-0.004,0.241-0.011l-0.014,3.504
|
||||||
|
c-0.131,0.004-0.263,0.009-0.396,0.016c-0.102-0.006-0.202-0.01-0.305-0.014L38.368,9.265z M31.256,49.9
|
||||||
|
c-2.044-1.615-3.437-3.856-4.026-6.481c-1.735-7.716-2.132-25.495,7.983-28.068c0.375-0.095,0.805-0.163,1.27-0.206
|
||||||
|
c0.297-0.025,0.609-0.043,0.932-0.047c0.303-0.004,0.613,0.002,0.931,0.018c0.077,0.004,0.155,0.009,0.232,0.014l0.071,0.004
|
||||||
|
l0.073-0.004c0.107-0.005,0.216-0.01,0.322-0.015c0.317-0.011,0.629-0.013,0.931-0.004c0.323,0.009,0.635,0.03,0.931,0.062
|
||||||
|
c0.429,0.047,0.825,0.115,1.176,0.208c10.094,2.655,9.55,20.431,7.752,28.132c-0.606,2.605-2.006,4.824-4.044,6.421
|
||||||
|
c-0.561,0.439-1.165,0.821-1.799,1.145c-0.374,0.19-0.758,0.36-1.15,0.508c-0.997,0.374-2.004,0.799-3.064,0.939
|
||||||
|
c-0.76,0.101-3.221-0.096-3.462-0.141c-0.71-0.131-1.361-0.363-2.035-0.614c-0.393-0.146-0.777-0.401-1.15-0.591
|
||||||
|
C32.467,50.844,31.839,50.361,31.256,49.9z M87.689,83.287L87.689,83.287l-0.012,0.055L87.689,83.287z M61.314,68h0.438h1.163
|
||||||
|
h1.161h7.311c1.37,0,1.305,1.201,1.305,2c0,0.801,0.065,2-1.305,2h-6.569h-0.863h-0.298h-0.863h-1.162h-0.316
|
||||||
|
c-0.278,0-0.497,0-0.669,0h-0.047C60.046,72,60,70.252,60,69.725c0-0.066,0-0.259,0-0.325C60,68.6,59.944,68,61.314,68z M17.475,82
|
||||||
|
h-3.346l0.007-1.979c0.674,0.299,1.604,0.56,2.743,0.56c0.222,0,0.449-0.02,0.686-0.043c0.642-0.061,1.34-0.183,2.086-0.462
|
||||||
|
L19.642,82H17.475z M14.143,78.358l-0.117-1.896C15.136,76.916,16.447,77,17.719,77h0.023h0.046c0.642,0,1.311-0.254,1.917-0.373
|
||||||
|
l-0.049,2.059c-0.229,0.104-0.452,0.082-0.67,0.158c-0.471,0.162-0.914,0.206-1.327,0.262c-1.2,0.16-2.157-0.097-2.812-0.363
|
||||||
|
C14.547,78.62,14.311,78.464,14.143,78.358z M10.488,67.889C10.504,63.881,13.778,61,17.785,61h0.029
|
||||||
|
c1.068,0,2.08-0.117,2.994,0.297c0.41,0.186,0.8,0.232,1.165,0.489c0.343,0.239,0.664,0.419,0.96,0.713
|
||||||
|
c1.333,1.324,2.157,3.116,2.149,5.144c-0.011,2.439-1.229,4.57-3.084,5.881c-1.422,1.005-3.479,1.598-5.262,1.443
|
||||||
|
c-0.509-0.044-1.47-0.17-1.94-0.374c-0.873-0.38-1.078-0.617-1.793-1.237c-0.433-0.375-0.821-0.771-1.155-1.236
|
||||||
|
C10.987,70.919,10.48,69.479,10.488,67.889z M86.375,91H10.159c-1.471,0-2.268,0.203-2.268-2.5c0-2.086,0.361-2.5,2.268-2.5h1.638
|
||||||
|
h0.733h0.428h1.16h1.839h1.369h0.464h1.843h1.161h1.161h0.463h17.441h13.731h1.014h1.161h1.161h0.521h3.624h0.008h1.361h0.953
|
||||||
|
h0.207h1.161h10.929h10.684C86.789,86,88,86,88,86v-1.584c1,0.37,0.918,1.279,0.918,3.223C88.918,89.723,88.283,91,86.375,91z'
|
||||||
|
/>
|
||||||
|
</g>
|
||||||
|
</SvgIcon>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
export default BadgeFounder;
|
186
frontend/src/components/Icons/BadgeLimits.tsx
Normal file
186
frontend/src/components/Icons/BadgeLimits.tsx
Normal file
@ -0,0 +1,186 @@
|
|||||||
|
import React from 'react';
|
||||||
|
import { SvgIcon, type SvgIconProps } from '@mui/material';
|
||||||
|
|
||||||
|
const BadgeLimits: React.FC<SvgIconProps> = (props) => {
|
||||||
|
return (
|
||||||
|
<SvgIcon {...props} viewBox='0 0 100 100'>
|
||||||
|
<g>
|
||||||
|
<path
|
||||||
|
fill='#FFE47B'
|
||||||
|
d='M100,12.146C100,4.874,94.133,0,86.904,0H13.079C5.852,0,0,4.874,0,12.146v73.666
|
||||||
|
C0,93.084,5.852,100,13.079,100h73.825C94.133,100,100,93.084,100,85.812V12.146z'
|
||||||
|
/>
|
||||||
|
<g>
|
||||||
|
<path
|
||||||
|
fill='#DD6C01'
|
||||||
|
d='M42.982,39.041c0.646,0.696,1.7,0.835,2.675,0.447c0.205-0.066,0.396-0.164,0.572-0.293
|
||||||
|
c0.166-0.107,0.327-0.23,0.479-0.371l0.062-0.056c0.089-0.083,0.17-0.169,0.247-0.257l22.319-20.703
|
||||||
|
c0.979-0.871,6.873-6.916,5.815-8.105l-0.055-0.061c-1.057-1.189-8.329,4.508-9.309,5.377L44.932,32.549l-8.202-7.813
|
||||||
|
c-0.891-0.962-4.807-4.321-5.973-3.239l-0.062,0.057c-1.166,1.081,2.524,5.685,3.415,6.646L42.982,39.041z'
|
||||||
|
/>
|
||||||
|
<path
|
||||||
|
fill='#DD6C01'
|
||||||
|
d='M28.018,55l0.004-0.051l0.3-1.146c-1.722-0.509-3.59-1.063-5.303-1.544l-4.585,15.679l5.682,1.619
|
||||||
|
L28.021,55H28.018z'
|
||||||
|
/>
|
||||||
|
<path
|
||||||
|
fill='#DD6C01'
|
||||||
|
d='M5,47.607v16.729l2.036,0.652l4.629-15.5C9.237,48.781,7,48.07,5,47.607z'
|
||||||
|
/>
|
||||||
|
<path
|
||||||
|
fill='#DD6C01'
|
||||||
|
d='M88.421,54.312l-0.216-0.305c-0.309-1.103-1.399-1.873-2.561-1.873c-0.242,0-0.517,0.033-0.751,0.099
|
||||||
|
l-5.704,1.59c-1.391,0.389-2.217,1.816-1.836,3.185l0.262,0.947c-0.757,0.206-1.498,0.431-2.188,0.666l-0.327,0.111l-0.08,0.335
|
||||||
|
c-0.438,1.807-1.39,3.402-2.684,4.49c-0.636,0.536-1.41,0.915-2.3,1.126l-0.054,0.013l-0.051,0.022
|
||||||
|
c-1.358,0.591-3.126,0.56-5.059-0.076c-0.404-0.133-0.792-0.292-1.164-0.469l-0.38,0.514l-0.378,0.511
|
||||||
|
c0.485,0.246,0.998,0.459,1.53,0.635c2.209,0.726,4.263,0.749,5.898,0.057c1.059-0.259,1.989-0.721,2.764-1.374
|
||||||
|
c1.414-1.189,2.472-2.89,3.006-4.816c0.574-0.19,1.181-0.371,1.796-0.539l3.626,12.974c-1.7,0.656-3.707,1.229-5.853,1.725
|
||||||
|
l0.438,0.291c0.087,0.057,0.167,0.12,0.244,0.187c0.181,0.156,0.333,0.336,0.456,0.532c1.826-0.451,3.544-0.964,5.057-1.541
|
||||||
|
c0.357,1.017,1.34,1.715,2.445,1.715c0.241,0,0.483-0.034,0.717-0.1l5.688-1.589c1.391-0.389,2.212-1.818,1.829-3.184l0.102-0.558
|
||||||
|
C93.041,69.504,94,69.395,94,69.285V68.63v-0.657c0,0.147-1.193,0.292-1.643,0.431l-3.471-12.885L94,53.958v-0.657v-0.656
|
||||||
|
L88.421,54.312z M90.994,68.746l0.169,0.604l0.169,0.604l0.146,0.526c0.21,0.753-0.25,1.541-1.025,1.758l-5.688,1.59
|
||||||
|
c-0.133,0.037-0.27,0.057-0.406,0.057c-0.633,0-1.191-0.407-1.372-0.994c-0.004-0.012-0.008-0.022-0.011-0.034l-0.16-0.568
|
||||||
|
l-0.166-0.6L79.473,60.32l-0.404-1.449l-0.169-0.602l-0.168-0.604l-0.104-0.368l-0.168-0.603c-0.211-0.753,0.25-1.542,1.026-1.759
|
||||||
|
l5.687-1.589c0.134-0.038,0.271-0.057,0.405-0.057c0.646,0,1.214,0.423,1.385,1.027l0.094,0.34l0.17,0.604l0.168,0.603
|
||||||
|
L90.994,68.746z'
|
||||||
|
/>
|
||||||
|
<path
|
||||||
|
fill='#DD6C01'
|
||||||
|
d='M21.812,51.913l0.004-0.014l0.107-0.299c0.029-0.1,0.077-0.198,0.077-0.298c0-0.004,0-0.008,0-0.013
|
||||||
|
c0-0.224-0.033-0.448-0.111-0.66c-0.024-0.074-0.066-0.146-0.104-0.217c-0.201-0.364-0.537-0.63-0.936-0.746l-6.075-1.776
|
||||||
|
c-0.143-0.041-0.292-0.062-0.438-0.062c-0.576,0-1.101,0.324-1.368,0.819c-0.053,0.095-0.096,0.196-0.127,0.302l-0.086,0.288
|
||||||
|
l-0.176,0.603L8.047,65.342l-0.176,0.601l-0.176,0.602l-0.012,0.038c-0.116,0.4-0.07,0.821,0.129,1.186
|
||||||
|
c0.201,0.367,0.53,0.632,0.931,0.748l6.071,1.775c0.144,0.042,0.291,0.063,0.439,0.063c0.687,0,1.302-0.461,1.495-1.122
|
||||||
|
l0.009-0.034l0.117-0.399l0.06-0.201l0.116-0.4l0.06-0.201l0.116-0.399l4.413-15.096L21.812,51.913z'
|
||||||
|
/>
|
||||||
|
<path
|
||||||
|
fill='#DD6C01'
|
||||||
|
d='M61.145,67.641l-1.209,1.634l1.351,0.822c0.037-0.069,0.078-0.139,0.122-0.204l0.986-1.489L61.145,67.641z
|
||||||
|
'
|
||||||
|
/>
|
||||||
|
<path
|
||||||
|
fill='#DD6C01'
|
||||||
|
d='M69.353,79.089l-2.999-1.985c-0.006,0.453-0.14,0.896-0.396,1.281l-1.141,1.722
|
||||||
|
c-0.274,0.416-0.667,0.728-1.116,0.904l3.043,2.015c0.22,0.145,0.473,0.222,0.733,0.222c0.447,0,0.862-0.223,1.108-0.596
|
||||||
|
l1.141-1.721C70.13,80.319,69.963,79.493,69.353,79.089z'
|
||||||
|
/>
|
||||||
|
<path
|
||||||
|
fill='#DD6C01'
|
||||||
|
d='M55.339,75.491l-0.376,0.51c0.032,0.031,0.067,0.062,0.103,0.091c0.036-0.002,0.072-0.003,0.108-0.003
|
||||||
|
c0.092,0,0.183,0.006,0.271,0.017c-0.059-0.196-0.091-0.401-0.099-0.606c0-0.006-0.001-0.012-0.001-0.018L55.339,75.491z'
|
||||||
|
/>
|
||||||
|
<path
|
||||||
|
fill='#DD6C01'
|
||||||
|
d='M63.935,79.521l1.14-1.723c0.17-0.256,0.244-0.556,0.214-0.858c-0.004-0.046-0.011-0.093-0.02-0.139
|
||||||
|
c-0.035-0.172-0.103-0.332-0.196-0.474c-0.037-0.055-0.079-0.106-0.124-0.155c-0.073-0.08-0.154-0.152-0.248-0.214l-0.277-0.184
|
||||||
|
l-1.188-0.787L62.647,74.6l-3.043-2.016c-0.219-0.145-0.472-0.221-0.732-0.221c-0.302,0-0.586,0.103-0.815,0.282
|
||||||
|
c-0.112,0.087-0.213,0.191-0.293,0.313l-0.115,0.172l-0.346,0.522l-0.68,1.026c-0.162,0.244-0.23,0.523-0.218,0.797
|
||||||
|
c0.021,0.408,0.226,0.803,0.593,1.045l0.03,0.021l0.846,0.559l0.061,0.041l0.782,0.52l0.975,0.645l0.304,0.201l0.562,0.372
|
||||||
|
l0.845,0.56l0.691,0.458c0.219,0.144,0.472,0.221,0.731,0.221C63.273,80.117,63.688,79.895,63.935,79.521z'
|
||||||
|
/>
|
||||||
|
<path
|
||||||
|
fill='#DD6C01'
|
||||||
|
d='M75.713,75.141c-0.045-0.039-0.09-0.076-0.141-0.109l-0.566-0.376l-0.726-0.479l-2.267-1.501
|
||||||
|
c0.043,0.53-0.089,1.057-0.388,1.507l-0.383,0.58l-0.476,0.718l-0.28,0.424c-0.073,0.109-0.154,0.212-0.244,0.308
|
||||||
|
c-0.195,0.21-0.424,0.381-0.676,0.508l3.397,2.249c0.219,0.146,0.473,0.222,0.733,0.222c0.447,0,0.862-0.223,1.108-0.596
|
||||||
|
l1.14-1.721C76.317,76.312,76.206,75.571,75.713,75.141z'
|
||||||
|
/>
|
||||||
|
<path
|
||||||
|
fill='#DD6C01'
|
||||||
|
d='M62.098,71.475c0.062,0.301,0.22,0.564,0.452,0.756c0.036,0.031,0.076,0.061,0.116,0.088l3.559,2.355
|
||||||
|
l0.621,0.411l0.459,0.305l0.457,0.302c0.146,0.097,0.307,0.162,0.475,0.195c0.033,0.007,0.065,0.012,0.099,0.017
|
||||||
|
c0.053,0.006,0.105,0.01,0.159,0.01c0.228,0,0.447-0.059,0.641-0.165c0.186-0.102,0.348-0.247,0.468-0.43l0.208-0.313l0.933-1.408
|
||||||
|
c0.195-0.296,0.265-0.65,0.194-0.998c-0.071-0.348-0.273-0.648-0.568-0.844l-0.467-0.309l-0.695-0.461l-0.537-0.354l-3.396-2.25
|
||||||
|
c-0.219-0.145-0.472-0.221-0.732-0.221c-0.046,0-0.09,0.004-0.134,0.008c-0.264,0.027-0.51,0.13-0.71,0.296
|
||||||
|
c-0.101,0.083-0.191,0.179-0.266,0.291l-0.133,0.2l-1.007,1.522c-0.037,0.056-0.068,0.114-0.097,0.174
|
||||||
|
c-0.097,0.207-0.137,0.434-0.119,0.662C62.08,71.366,62.086,71.421,62.098,71.475z'
|
||||||
|
/>
|
||||||
|
</g>
|
||||||
|
<path
|
||||||
|
fill='#DD6C01'
|
||||||
|
d='M73.355,57.131c-0.943-4.371-5.124-7.201-8.465-8.806l-0.038-0.019l-12.591-4.036
|
||||||
|
c-0.149-0.071-0.484-0.198-0.97-0.198c-0.864,0-1.964,0.376-3.217,1.648c-0.143,0.145-0.286,0.301-0.433,0.47
|
||||||
|
c-0.128,0.147-0.257,0.304-0.388,0.47c-0.006-0.003-0.01-0.006-0.017-0.009c-0.005,0.006-0.011,0.014-0.016,0.021
|
||||||
|
c-0.298-0.15-0.952-0.402-2.021-0.402c-2.321,0-6.247,1.131-12.321,6.044c3.521-0.469,7.893-0.591,11.463-0.593
|
||||||
|
c-0.141,0.346-0.33,0.811-0.476,1.173l-0.044,0.082c-3.187,0.013-7.911,0.137-11.5,0.684c-0.46,0.069-0.899,0.146-1.315,0.231
|
||||||
|
c-0.021,0.004-0.041,0.008-0.062,0.013c-0.434,0.089-0.839,0.187-1.213,0.293c-0.006,0.002-0.013,0.004-0.021,0.006
|
||||||
|
c-0.075,0.022-0.152,0.043-0.226,0.066l-0.05,0.185l-0.137,0.508l-0.166,0.626l-3.813,14.255l-0.104,0.393
|
||||||
|
c0.054,0.077,0.121,0.173,0.203,0.285c0.155,0.214,0.36,0.491,0.612,0.819c0.401,0.524,0.923,1.181,1.558,1.931l-0.398,0.51
|
||||||
|
l-0.394,0.504l-1.553,1.993c-0.809,1.039-0.623,2.543,0.415,3.354l1.629,1.269c0.424,0.33,0.931,0.505,1.466,0.505
|
||||||
|
c0.401,0,0.786-0.099,1.126-0.28c0.145,0.424,0.409,0.812,0.788,1.106l1.628,1.27c0.425,0.33,0.932,0.504,1.469,0.504
|
||||||
|
c0.74,0,1.428-0.335,1.884-0.919l0.218-0.28l0.321-0.412c0.112,0.428,0.337,0.814,0.658,1.12c0.059,0.055,0.119,0.107,0.182,0.156
|
||||||
|
l1.628,1.27c0.337,0.263,0.727,0.426,1.143,0.481c0.107,0.016,0.215,0.023,0.325,0.023c0.262,0,0.517-0.043,0.759-0.122
|
||||||
|
c0.238-0.079,0.463-0.195,0.665-0.347c0.194,0.105,0.389,0.212,0.586,0.314l1.602-0.501c0.12,0.157,0.259,0.302,0.421,0.429
|
||||||
|
l0.089,0.069l0.602,0.468l0.938,0.731c0.423,0.331,0.931,0.505,1.467,0.505c0.609,0,1.181-0.226,1.618-0.63
|
||||||
|
c0.096-0.089,0.186-0.186,0.267-0.29l0.088-0.112l0.326-0.419l3.291,2.179c0.219,0.146,0.472,0.222,0.731,0.222
|
||||||
|
c0.447,0,0.863-0.223,1.109-0.596l1.141-1.721c0.269-0.406,0.283-0.908,0.084-1.317c-0.102-0.206-0.255-0.389-0.458-0.524
|
||||||
|
l-1.304-0.863l-1.209-0.8l-0.474-0.314l0.239-0.306l0.087-0.113l0.052-0.065l0.054-0.068c0.047-0.062,0.091-0.123,0.131-0.186
|
||||||
|
c0.024-0.038,0.044-0.077,0.066-0.115l2.748,1.819l0.193,0.128c0.271,0.18,0.494,0.406,0.664,0.661l1.696,1.123
|
||||||
|
c0.22,0.145,0.473,0.222,0.732,0.222c0.447,0,0.862-0.223,1.109-0.596l1.14-1.723c0.404-0.61,0.235-1.436-0.374-1.84l-4.387-2.905
|
||||||
|
l-0.709-0.47c-0.054-0.035-0.109-0.066-0.168-0.094c-0.176-0.083-0.368-0.127-0.564-0.127c-0.13,0-0.257,0.021-0.378,0.057
|
||||||
|
c-0.124,0.037-0.24,0.091-0.35,0.161c-0.09,0.06-0.173,0.129-0.247,0.209c-0.048,0.053-0.095,0.107-0.136,0.169l-0.188,0.285
|
||||||
|
l-0.039,0.059l-0.581,0.878c-0.051-0.141-0.113-0.274-0.188-0.402c-0.028-0.047-0.058-0.093-0.089-0.14
|
||||||
|
c-0.092-0.132-0.196-0.256-0.314-0.368c-0.016-0.015-0.032-0.028-0.049-0.043c-0.027-0.025-0.059-0.051-0.088-0.075
|
||||||
|
c-0.016-0.014-0.03-0.028-0.047-0.041l-0.304-0.235l0.287-0.387l0.546-0.739l0.678-0.918l0.617-0.834l0.328-0.444l0.327-0.44
|
||||||
|
l0.619-0.838l0.042-0.057l0.656-0.888l2.046-2.767l0.44-0.597l1.516-2.049l0.439-0.597l1.146-1.547l0.23-0.312
|
||||||
|
c-0.119-0.083-0.236-0.166-0.351-0.249c-0.321-0.229-0.624-0.448-0.973-0.614c-0.515-0.242-1.034-0.52-1.566-0.831
|
||||||
|
c-0.328-0.193-0.661-0.4-1-0.621c-0.184-0.119-0.369-0.243-0.557-0.371c-0.182-0.126-0.365-0.256-0.552-0.389
|
||||||
|
c-0.094-0.067-0.188-0.134-0.281-0.203c-1.568-1.15-3.563-2.688-5.396-4.318c-0.036-0.031-0.071-0.062-0.106-0.094l0.37-0.511
|
||||||
|
l0.117-0.161l0.252-0.349l0.09-0.124l0.333,0.268l0.588,0.473l2.303,1.852c0.186,0.187,1.124,1.108,2.521,2.193
|
||||||
|
c0.089,0.068,0.178,0.138,0.271,0.207c0.181,0.138,0.367,0.276,0.562,0.416c0.304,0.219,0.623,0.441,0.956,0.661
|
||||||
|
c0.205,0.137,0.417,0.271,0.633,0.404c0.785,0.487,1.632,0.813,2.513,1.203c1.501,0.664,3.104,0.981,4.685,0.981c0,0,0,0,0.001,0
|
||||||
|
c2.106,0,3.911-0.646,5.367-2.231l0.062,0.021l0.041-0.049C73.016,61.731,73.934,59.805,73.355,57.131z M41.97,59.117l0.357-0.673
|
||||||
|
l0.388-0.725l1.294-2.43c0.06,0.009,0.12,0.009,0.183-0.002c0.012-0.002,0.467-0.069,1.121,0.024
|
||||||
|
c0.694,0.099,1.614,0.382,2.469,1.122c0.135,0.115,0.267,0.244,0.397,0.384c0.038,0.043,0.077,0.088,0.117,0.133
|
||||||
|
c0.139,0.16,0.274,0.335,0.404,0.526c0.065,0.096,0.128,0.194,0.19,0.297l-0.803,1.105l-0.026,0.041
|
||||||
|
c-1.482,2.415-3.013,3.64-4.549,3.64c-1.254,0-2.243-0.839-2.688-1.297L41.97,59.117z M30.647,78.373l-0.393,0.504
|
||||||
|
c-0.226,0.288-0.371,0.614-0.445,0.949l-0.004,0.005c-0.254,0.325-0.636,0.513-1.05,0.513c-0.297,0-0.578-0.098-0.814-0.281
|
||||||
|
l-1.629-1.269c-0.577-0.451-0.681-1.288-0.23-1.866l1.426-1.829l0.392-0.502l0.398-0.51l1.358-1.744l0.184-0.234
|
||||||
|
c0.086-0.11,0.188-0.204,0.301-0.281c0.217-0.147,0.476-0.23,0.748-0.23c0.299,0,0.581,0.179,0.816,0.362l0.732,0.654h0.001
|
||||||
|
l0.894,0.616c0.281,0.218,0.459,0.49,0.502,0.843c0.011,0.081,0.012,0.144,0.008,0.224l-2.415,3.088l-0.393,0.498L30.647,78.373z
|
||||||
|
M35.794,81.175l-0.389,0.498l-0.382,0.491l-0.209,0.269c-0.255,0.324-0.637,0.511-1.048,0.511c-0.299,0-0.581-0.097-0.817-0.28
|
||||||
|
l-1.628-1.27c-0.357-0.279-0.534-0.708-0.508-1.13c0.016-0.259,0.105-0.516,0.276-0.735l0.331-0.425l0.386-0.493l0.468-0.502
|
||||||
|
L35,74.711v-0.002c1-0.325,0.561-0.513,0.974-0.513c0.298,0,0.542,0.098,0.778,0.281l1.608,1.27
|
||||||
|
c0.279,0.219,0.448,0.533,0.491,0.885c0.044,0.352-0.057,0.7-0.275,0.98L35.794,81.175z M41.175,83.871
|
||||||
|
c-0.188,0.24-0.444,0.403-0.732,0.474c-0.103,0.024-0.209,0.038-0.317,0.038c-0.298,0-0.579-0.098-0.815-0.281l-1.628-1.27
|
||||||
|
c-0.191-0.148-0.334-0.342-0.42-0.562c-0.007-0.005-0.015-0.012-0.023-0.017c-0.035-0.098-0.054-0.2-0.067-0.305
|
||||||
|
c-0.044-0.352,0.058-0.7,0.274-0.98l3.76-4.82c0.254-0.324,0.637-0.512,1.051-0.512c0.298,0,0.581,0.098,0.816,0.281l1.628,1.27
|
||||||
|
c0.578,0.45,0.682,1.287,0.231,1.864L41.175,83.871z M51.46,78.445l0.087,0.068l0.282,0.219c0.07,0.056,0.135,0.117,0.191,0.184
|
||||||
|
c0.002,0.001,0.002,0.003,0.004,0.005c0.014,0.015,0.025,0.032,0.038,0.048c0.114,0.148,0.197,0.318,0.241,0.503
|
||||||
|
c0.002,0.01,0.005,0.02,0.007,0.027c0.005,0.024,0.008,0.048,0.013,0.071c0.002,0.017,0.006,0.031,0.008,0.048
|
||||||
|
c0.025,0.21,0,0.419-0.07,0.613c-0.016,0.044-0.033,0.088-0.055,0.131c-0.006,0.013-0.014,0.026-0.02,0.039
|
||||||
|
c-0.037,0.068-0.079,0.135-0.127,0.196l-0.13,0.166l-0.025,0.033l-0.131,0.167l-0.196,0.252l-0.038,0.049l-0.387,0.498
|
||||||
|
l-0.388,0.497l-1.253,1.605l-0.388,0.498l-0.388,0.497l-0.07,0.091l-0.327,0.419l-0.037,0.048
|
||||||
|
c-0.103,0.131-0.227,0.239-0.365,0.322c-0.203,0.121-0.438,0.189-0.684,0.189c-0.298,0-0.579-0.098-0.814-0.281l-0.361-0.28
|
||||||
|
l-0.601-0.469l-0.601-0.468L44.81,84.38c-0.578-0.45-0.682-1.287-0.23-1.866l3.756-4.818c0.255-0.325,0.638-0.513,1.05-0.513
|
||||||
|
c0.298,0,0.581,0.098,0.816,0.281l0.728,0.566L51.46,78.445z M52.808,50.553c-0.091,0.888-0.312,1.85-0.77,2.768
|
||||||
|
c-0.182,0.248-0.29,0.495-0.439,0.744l-0.08,0.106c0,0.002,0.006,0.003,0.011,0.005c-0.003,0.006-0.007,0.01-0.01,0.015
|
||||||
|
l-0.018-0.012c0.001-0.003,0.002-0.005,0.004-0.008l-0.374,0.516l-0.371,0.512l-0.312,0.428l-0.06,0.083l-0.563,0.777l-0.148,0.203
|
||||||
|
c-0.03-0.043-0.06-0.088-0.091-0.13c-0.143-0.194-0.288-0.374-0.438-0.54c-1.55-1.727-3.449-2.003-4.456-2.011l0.371-0.698
|
||||||
|
l0.026-0.058c0.039-0.093,0.077-0.185,0.114-0.275c0.091-0.214,0.179-0.423,0.268-0.627c0.094-0.213,0.187-0.422,0.279-0.623
|
||||||
|
c0.916-2.004,1.768-3.388,2.523-4.343c0.136-0.173,0.271-0.332,0.4-0.478c0.152-0.169,0.3-0.321,0.44-0.456
|
||||||
|
c0.995-0.951,1.755-1.127,2.176-1.127c0.132,0,0.235,0.019,0.307,0.037c-0.06-0.104-0.095-0.16-0.099-0.166l0.046-0.029
|
||||||
|
c0.003,0.004,0.007,0.007,0.012,0.011l-0.031,0.019c0.003,0.006,0.013,0.062,0.072,0.166c0.015,0.004,0.027,0.007,0.038,0.01
|
||||||
|
c0.096,0.172,0.209,0.46,0.438,0.846c0.232,0.678,0.671,1.648,0.79,2.784L53,49.045c0,0.004,0,0.011,0,0.018l-0.162-0.062
|
||||||
|
c0.027,0.259,0.006,0.524,0.011,0.798c0.002,0.241-0.026,0.486-0.047,0.737L52.808,50.553z M61.043,60.258
|
||||||
|
c-0.156,0.124-0.317,0.246-0.482,0.366c-0.191-0.125-0.378-0.251-0.561-0.378c-0.213-0.147-0.421-0.295-0.622-0.441
|
||||||
|
c-1.861-1.366-3.118-2.632-3.146-2.659l-1.748-1.423l-0.583-0.475l-0.598-0.486l-0.461-0.375c0.557-0.956,0.924-1.977,1.101-3.04
|
||||||
|
c0.039-0.229,0.068-0.461,0.088-0.693c0.022-0.246,0.031-0.493,0.032-0.741c0.005-0.711-0.07-1.434-0.23-2.164
|
||||||
|
c-0.156-0.711-0.371-1.336-0.579-1.84l11.133,3.57c0.061,0.029,0.119,0.059,0.179,0.089c0.04,0.102,0.117,0.312,0.2,0.611
|
||||||
|
c0.184,0.666,0.396,1.773,0.295,3.104c-0.154,2.033-0.965,3.868-2.419,5.485c-0.309,0.344-0.646,0.677-1.012,1
|
||||||
|
C61.441,59.934,61.245,60.098,61.043,60.258z M71.878,61.25c-0.609,0.648-1.276,1.133-2.01,1.455
|
||||||
|
c-0.437,0.191-0.896,0.399-1.382,0.476c-0.315,0.049-0.643,0.148-0.98,0.148h-0.001c-1.953,0-3.997-0.893-5.798-1.927
|
||||||
|
c0.19-0.147,0.372-0.335,0.549-0.485c0.161-0.139,0.318-0.297,0.469-0.437c0.158-0.146,0.307-0.305,0.453-0.453
|
||||||
|
c0.023-0.024,0.049-0.054,0.073-0.08c0.233-0.243,0.448-0.488,0.649-0.732c0.193-0.237,0.375-0.477,0.542-0.714
|
||||||
|
c1.306-1.862,1.759-3.693,1.867-5.123c0.091-1.188-0.039-2.222-0.203-2.986c3.353,1.955,5.418,4.335,6.008,6.943
|
||||||
|
C72.567,59.333,72.021,60.896,71.878,61.25z'
|
||||||
|
/>
|
||||||
|
</g>
|
||||||
|
<circle fill='#DD6C01' cx='81.483' cy='57.609' r='1.819' />
|
||||||
|
<circle fill='#DD6C01' cx='85.483' cy='70.609' r='1.819' />
|
||||||
|
<circle fill='#FFE47B' cx='23.483' cy='60.609' r='1.819' />
|
||||||
|
</SvgIcon>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
export default BadgeLimits;
|
98
frontend/src/components/Icons/BadgeLoved.tsx
Normal file
98
frontend/src/components/Icons/BadgeLoved.tsx
Normal file
@ -0,0 +1,98 @@
|
|||||||
|
import React from 'react';
|
||||||
|
import { SvgIcon, type SvgIconProps } from '@mui/material';
|
||||||
|
|
||||||
|
const BadgeLoved: React.FC<SvgIconProps> = (props) => {
|
||||||
|
return (
|
||||||
|
<SvgIcon {...props} viewBox='0 0 100 100'>
|
||||||
|
<path
|
||||||
|
fill='#FF9FB3'
|
||||||
|
d='M100,12.785C100,5.444,93.838,0,86.466,0H13.645C6.271,0,0,5.444,0,12.785v72.938
|
||||||
|
C0,93.064,6.271,100,13.645,100h72.821C93.838,100,100,93.064,100,85.723V12.785z'
|
||||||
|
/>
|
||||||
|
<g>
|
||||||
|
<path fill='#CF2B00' d='M17,30.843v-0.246C17,30.643,16.997,30.737,17,30.843z' />
|
||||||
|
<path
|
||||||
|
fill='#CF2B00'
|
||||||
|
d='M83.416,16H59.137H52.83h-1.967C50.387,16,50,16.387,50,16.863v0.273C50,17.613,50.387,18,50.863,18H54
|
||||||
|
h5.137h24.279C85.821,18,89,18.849,89,21.314v33.497C89,57.294,85.782,60,83.416,60h-7.754l-5.83,4.802L71.482,60H22.285
|
||||||
|
C19.841,60,19,57.172,19,54.812V33v-1v-1c0-0.553-0.447-1-1-1c-0.538,0-0.968,0.426-0.991,0.958
|
||||||
|
c-0.005-0.04-0.008-0.077-0.009-0.115V31v2v21.812C17,58.559,18.452,62,22.285,62H68.06l-3.349,9.739L76.538,62h6.878
|
||||||
|
C87.113,62,91,58.623,91,54.812V21.314C91,17.496,87.175,16,83.416,16z'
|
||||||
|
/>
|
||||||
|
<path
|
||||||
|
fill='#CF2B00'
|
||||||
|
d='M80.554,33H26.932c-0.451,0-0.814,0.551-0.814,1s0.363,1,0.814,1h53.622c0.449,0,0.814-0.551,0.814-1
|
||||||
|
S81.003,33,80.554,33z'
|
||||||
|
/>
|
||||||
|
<path
|
||||||
|
fill='#CF2B00'
|
||||||
|
d='M81.368,42c0-0.449-0.365-1-0.814-1H26.932c-0.451,0-0.814,0.551-0.814,1s0.363,1,0.814,1h53.622
|
||||||
|
C81.003,43,81.368,42.449,81.368,42z'
|
||||||
|
/>
|
||||||
|
<path
|
||||||
|
fill='#CF2B00'
|
||||||
|
d='M26.683,49c-0.284,0-0.512,0.545-0.512,1s0.228,1,0.512,1h33.216c0.282,0,0.511-0.545,0.511-1
|
||||||
|
s-0.229-1-0.511-1H26.683z'
|
||||||
|
/>
|
||||||
|
<path
|
||||||
|
fill='#CF2B00'
|
||||||
|
d='M31.007,16.333C31.022,16.35,31.015,16.314,31.007,16.333L31.007,16.333z'
|
||||||
|
/>
|
||||||
|
<path
|
||||||
|
fill='#CF2B00'
|
||||||
|
d='M18,24.707c0,0.415,0,0.83,0,1.245c0,0.38,0.078,0.595,0.234,0.767c0.219,0.24,0.234,0.188,0.426,0.281
|
||||||
|
h10.241c0.709,0,2.11-0.004,2.099-1.048v-8.894v-0.649c0-0.046,0.003-0.067,0.007-0.076c-0.006-0.006-0.015-0.017-0.03-0.049
|
||||||
|
C30.764,15.858,29.422,16,28.901,16h-8.143c0.01,0,0.021-0.687,0.03-0.824c0.115-1.422,0.311-2.85,0.8-4.197
|
||||||
|
c0.591-1.627,2.094-2.839,3.578-3.627c0.769-0.408,1.863-0.609,2.161-1.551c0.438-1.384-1.191-1.764-2.201-1.461
|
||||||
|
c-1.516,0.453-3.095,1.1-4.206,2.262c-1.041,1.09-1.793,2.518-2.247,3.946c-0.28,0.881-0.453,1.798-0.551,2.717
|
||||||
|
c-0.119,1.119-0.149,2.247-0.15,3.376c-0.002,1.063,0.023,2.127,0.024,3.186C17.999,21.453,18.003,23.08,18,24.707z'
|
||||||
|
/>
|
||||||
|
<path
|
||||||
|
fill='#CF2B00'
|
||||||
|
d='M36.129,76.546c-0.83-0.203-3.49-0.496-5.189-0.659c-0.685-1.565-1.717-3.837-2.148-4.533
|
||||||
|
c-0.274-0.44-0.654-0.98-1.23-0.965c-0.576-0.016-0.958,0.524-1.231,0.965c-0.43,0.696-1.465,2.968-2.148,4.533
|
||||||
|
c-1.698,0.163-4.359,0.456-5.188,0.659c-0.506,0.122-1.137,0.319-1.301,0.869c-0.191,0.542,0.203,1.073,0.538,1.47
|
||||||
|
c0.551,0.651,2.526,2.457,3.804,3.591c-0.465,1.645-1.145,4.195-1.256,5.035c-0.067,0.516-0.115,1.174,0.341,1.526
|
||||||
|
c0.434,0.375,1.071,0.199,1.563,0.031c0.835-0.286,3.373-1.547,4.879-2.323c1.504,0.776,4.045,2.037,4.879,2.323
|
||||||
|
c0.492,0.168,1.129,0.344,1.564-0.031c0.454-0.353,0.409-1.011,0.341-1.526c-0.112-0.84-0.793-3.391-1.258-5.035
|
||||||
|
c1.277-1.134,3.253-2.939,3.806-3.591c0.335-0.396,0.728-0.928,0.535-1.47C37.264,76.865,36.635,76.668,36.129,76.546z'
|
||||||
|
/>
|
||||||
|
<path
|
||||||
|
fill='#CF2B00'
|
||||||
|
d='M61.442,76.546c-0.828-0.203-3.49-0.496-5.188-0.659c-0.685-1.565-1.718-3.837-2.149-4.533
|
||||||
|
c-0.272-0.44-0.653-0.98-1.229-0.965c-0.575-0.016-0.957,0.524-1.23,0.965c-0.432,0.696-1.465,2.968-2.149,4.533
|
||||||
|
c-1.699,0.163-4.358,0.456-5.188,0.659c-0.505,0.122-1.137,0.319-1.3,0.869c-0.192,0.542,0.201,1.073,0.537,1.47
|
||||||
|
c0.551,0.651,2.525,2.457,3.805,3.591c-0.466,1.645-1.146,4.195-1.256,5.035c-0.068,0.516-0.115,1.174,0.34,1.526
|
||||||
|
c0.435,0.375,1.07,0.199,1.564,0.031c0.834-0.286,3.372-1.547,4.878-2.323c1.505,0.776,4.045,2.037,4.879,2.323
|
||||||
|
c0.491,0.168,1.129,0.344,1.564-0.031c0.454-0.353,0.409-1.011,0.34-1.526c-0.111-0.84-0.791-3.391-1.258-5.035
|
||||||
|
c1.278-1.134,3.254-2.939,3.806-3.591c0.336-0.396,0.729-0.928,0.537-1.47C62.579,76.865,61.949,76.668,61.442,76.546z'
|
||||||
|
/>
|
||||||
|
<path
|
||||||
|
fill='#CF2B00'
|
||||||
|
d='M86.758,76.546c-0.83-0.203-3.49-0.496-5.189-0.659c-0.685-1.565-1.718-3.837-2.148-4.533
|
||||||
|
c-0.273-0.44-0.654-0.98-1.23-0.965c-0.574-0.016-0.958,0.524-1.229,0.965c-0.433,0.696-1.465,2.968-2.149,4.533
|
||||||
|
c-1.699,0.163-4.359,0.456-5.188,0.659c-0.507,0.122-1.139,0.319-1.301,0.869c-0.191,0.542,0.202,1.073,0.538,1.47
|
||||||
|
c0.55,0.651,2.525,2.457,3.804,3.591c-0.466,1.645-1.145,4.195-1.255,5.035c-0.069,0.516-0.116,1.174,0.339,1.526
|
||||||
|
c0.435,0.375,1.071,0.199,1.563,0.031c0.835-0.286,3.374-1.547,4.879-2.323c1.505,0.776,4.045,2.037,4.879,2.323
|
||||||
|
c0.492,0.168,1.129,0.345,1.565-0.031c0.453-0.353,0.409-1.011,0.34-1.526c-0.111-0.84-0.792-3.391-1.258-5.035
|
||||||
|
c1.278-1.134,3.253-2.939,3.806-3.591c0.335-0.396,0.729-0.928,0.535-1.469C87.894,76.865,87.263,76.668,86.758,76.546z'
|
||||||
|
/>
|
||||||
|
<path
|
||||||
|
fill='#CF2B00'
|
||||||
|
d='M34,24.707c0,0.415,0,0.83,0,1.245c0,0.38,0.078,0.595,0.234,0.767c0.219,0.24,0.234,0.188,0.426,0.281
|
||||||
|
h10.241c0.709,0,2.11-0.004,2.099-1.048v-8.894v-0.649c0-0.046,0.003-0.067,0.007-0.076c-0.006-0.006-0.015-0.017-0.03-0.049
|
||||||
|
C46.764,15.858,45.422,16,44.901,16h-8.143c0.01,0,0.021-0.687,0.03-0.824c0.115-1.422,0.311-2.85,0.8-4.197
|
||||||
|
c0.591-1.627,2.094-2.839,3.578-3.627c0.769-0.408,1.863-0.609,2.161-1.551c0.438-1.384-1.191-1.764-2.201-1.461
|
||||||
|
c-1.516,0.453-3.095,1.1-4.206,2.262c-1.041,1.09-1.793,2.518-2.247,3.946c-0.28,0.881-0.453,1.798-0.551,2.717
|
||||||
|
c-0.119,1.119-0.149,2.247-0.15,3.376c-0.002,1.063,0.023,2.127,0.024,3.186C33.999,21.453,34.003,23.08,34,24.707z'
|
||||||
|
/>
|
||||||
|
<path
|
||||||
|
fill='#CF2B00'
|
||||||
|
d='M47.007,16.333C47.022,16.35,47.015,16.314,47.007,16.333L47.007,16.333z'
|
||||||
|
/>
|
||||||
|
</g>
|
||||||
|
</SvgIcon>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
export default BadgeLoved;
|
278
frontend/src/components/Icons/BadgePrivacy.tsx
Normal file
278
frontend/src/components/Icons/BadgePrivacy.tsx
Normal file
@ -0,0 +1,278 @@
|
|||||||
|
import React from 'react';
|
||||||
|
import { SvgIcon, type SvgIconProps } from '@mui/material';
|
||||||
|
|
||||||
|
const BadgePrivacy: React.FC<SvgIconProps> = (props) => {
|
||||||
|
return (
|
||||||
|
<SvgIcon {...props} viewBox='0 0 100 100'>
|
||||||
|
<g>
|
||||||
|
<path
|
||||||
|
fill='#91EA9A'
|
||||||
|
d='M100,13.478C100,6.049,93.421,0,85.938,0H12.428C4.946,0,0,6.049,0,13.478v73.705
|
||||||
|
C0,94.611,4.946,101,12.428,101h73.511C93.421,101,100,94.611,100,87.183V13.478z'
|
||||||
|
/>
|
||||||
|
<g>
|
||||||
|
<path
|
||||||
|
fill='none'
|
||||||
|
d='M50.406,53.726C47.937,53.604,45.774,55,45.007,58h0.562h9.139h0.49C54.551,55,52.688,53.848,50.406,53.726z'
|
||||||
|
/>
|
||||||
|
<path
|
||||||
|
fill='none'
|
||||||
|
d='M64.986,21.303c-1.371-1.979-2.342-3.919-3.026-5.637l-15.166,0.258l-8.718-0.207
|
||||||
|
c-0.685,1.707-1.652,3.635-3.013,5.597c-3.098,4.467-8.825,9.942-19.044,12.47c-0.262,4.29-0.197,11.291,1.855,19.046
|
||||||
|
c1.092,4.13,2.607,7.956,4.539,11.473c0.13-0.057,0.28-0.07,0.425-0.021c0.367,0.122,2.048,0.173,2.776,0.106
|
||||||
|
c0.045-0.954,0.102-3.551,0.141-5.713c-0.653-0.275-1.112-0.923-1.112-1.676c0-1.001,0.813-1.815,1.816-1.815
|
||||||
|
c1.002,0,1.816,0.814,1.816,1.815c0,0.836-0.567,1.539-1.336,1.752c-0.104,5.926-0.172,6.169-0.195,6.252
|
||||||
|
c-0.048,0.171-0.167,0.314-0.334,0.404c-0.262,0.138-0.942,0.192-1.672,0.192c-0.574,0-0.769-0.033-1.226-0.083
|
||||||
|
C25.943,69.641,29,73.322,33,76.551v-4.978l-2.953-0.19l-0.141-4.706c-0.695-0.256-1.295-0.922-1.295-1.706
|
||||||
|
c0-1.001,0.763-1.815,1.765-1.815s1.792,0.814,1.792,1.815c0,0.811-0.545,1.498-1.277,1.732l0.266,3.595L34,70.483v7.083
|
||||||
|
c1,0.481,1,0.95,2,1.41V62.271c-1-0.288-1.494-0.918-1.494-1.648c0-1.001,0.596-1.814,1.598-1.814c1,0,2.057,0.813,2.057,1.814
|
||||||
|
c0,0.86-0.16,1.581-1.16,1.769v17.456c4,2.877,8.383,5.308,13.529,7.283C55.652,85.165,61,82.748,64,79.89V62.32
|
||||||
|
c0-0.26-1.328-0.922-1.328-1.698c0-1.001,0.738-1.814,1.74-1.814s1.844,0.813,1.844,1.814c0,0.817-0.256,1.51-1.256,1.738v16.66
|
||||||
|
c1-0.425,1-0.861,2-1.307v-7.231l2.383-0.191l-0.113-3.664c-0.631-0.285-1.133-0.919-1.133-1.656c0-1.001,0.785-1.815,1.786-1.815
|
||||||
|
c1.003,0,1.802,0.814,1.802,1.815c0,0.854-0.599,1.571-1.394,1.766l0.142,4.652L68,71.576v5.132c4-3.262,6.622-6.99,9.09-11.176
|
||||||
|
c-0.429,0.04-1.029,0.066-1.533,0.066c-0.73,0-1.448-0.055-1.71-0.192c-0.167-0.09-0.307-0.233-0.355-0.405
|
||||||
|
c-0.022-0.082-0.098-0.329-0.203-6.251c-0.77-0.213-1.341-0.916-1.341-1.752c0-1.001,0.813-1.815,1.814-1.815
|
||||||
|
c1.003,0,1.815,0.814,1.815,1.815c0,0.753-0.46,1.4-1.114,1.676c0.039,2.162,0.096,4.758,0.14,5.709
|
||||||
|
c0.729,0.067,2.41,0.019,2.776-0.104c0.095-0.032,0.189-0.035,0.28-0.021c1.85-3.374,3.317-7.032,4.395-10.971
|
||||||
|
c2.152-7.86,2.224-15.104,1.964-19.505C73.806,31.252,68.081,25.771,64.986,21.303z M41.152,26.227
|
||||||
|
c0.148-0.506,0.729-0.686,1.191-0.799c0.76-0.185,3.198-0.454,4.754-0.602c0.628-1.437,1.573-3.518,1.97-4.154
|
||||||
|
c0.25-0.404,0.601-0.899,1.127-0.885c0.527-0.015,0.877,0.48,1.127,0.885c0.396,0.637,1.342,2.718,1.97,4.154
|
||||||
|
c1.556,0.147,3.995,0.417,4.754,0.602c0.464,0.113,1.041,0.293,1.191,0.799c0.132,0.372-0.039,0.738-0.262,1.053
|
||||||
|
c-0.074,0.104-0.153,0.203-0.23,0.293c-0.506,0.596-2.314,2.251-3.486,3.289c0.1,0.353,0.21,0.75,0.322,1.162
|
||||||
|
c0.368,1.353,0.753,2.862,0.831,3.452c0.045,0.342,0.079,0.754-0.064,1.077c-0.056,0.123-0.134,0.232-0.248,0.32
|
||||||
|
c-0.399,0.346-0.983,0.184-1.434,0.029c-0.764-0.262-3.092-1.416-4.471-2.129c-1.379,0.713-3.705,1.867-4.47,2.129
|
||||||
|
c-0.451,0.154-1.035,0.316-1.434-0.029c-0.114-0.088-0.193-0.197-0.247-0.32c-0.145-0.323-0.109-0.735-0.063-1.077
|
||||||
|
c0.077-0.59,0.461-2.1,0.828-3.452c0.112-0.412,0.223-0.81,0.322-1.162c-1.172-1.038-2.98-2.693-3.485-3.289
|
||||||
|
c-0.077-0.09-0.156-0.189-0.23-0.293C41.19,26.965,41.02,26.598,41.152,26.227z M38.479,48.123
|
||||||
|
c-0.054,0.123-0.133,0.232-0.247,0.322c-0.399,0.345-0.983,0.184-1.433,0.028c-0.766-0.261-3.093-1.417-4.473-2.128
|
||||||
|
c-1.379,0.711-3.704,1.867-4.47,2.128c-0.451,0.155-1.033,0.316-1.433-0.028c-0.114-0.09-0.193-0.199-0.248-0.322
|
||||||
|
c-0.143-0.323-0.109-0.734-0.064-1.077c0.078-0.591,0.463-2.099,0.829-3.45c0.112-0.412,0.223-0.811,0.322-1.163
|
||||||
|
c-1.171-1.037-2.98-2.692-3.484-3.29c-0.077-0.091-0.157-0.189-0.231-0.293c-0.223-0.313-0.394-0.68-0.263-1.053
|
||||||
|
c0.15-0.505,0.729-0.686,1.192-0.798c0.759-0.185,3.196-0.454,4.755-0.604c0.627-1.434,1.574-3.514,1.968-4.15
|
||||||
|
c0.252-0.406,0.601-0.9,1.127-0.887c0.528-0.014,0.877,0.48,1.127,0.887c0.396,0.637,1.342,2.717,1.97,4.15
|
||||||
|
c1.556,0.15,3.996,0.42,4.754,0.604c0.464,0.112,1.042,0.293,1.192,0.798c0.133,0.373-0.038,0.739-0.263,1.053
|
||||||
|
c-0.073,0.104-0.153,0.202-0.229,0.293c-0.505,0.598-2.316,2.253-3.487,3.29c0.1,0.353,0.21,0.751,0.322,1.163
|
||||||
|
c0.368,1.352,0.752,2.859,0.83,3.45C38.589,47.389,38.623,47.8,38.479,48.123z M61,59.447v14.64C61,74.849,59.623,76,58.862,76
|
||||||
|
H41.19C40.429,76,40,74.849,40,74.087v-14.64v-1.208c0-0.639,0.344-1.192,0.934-1.348c-0.002,0.003-0.155,0.01-0.155,0.015
|
||||||
|
c0.072-0.042,0.055,0.586,0.142,0.553C40.925,57.416,40.834,58,40.84,58c0.003,0,0.004,0,0.007,0c0.903-5,4.992-9.174,9.772-8.915
|
||||||
|
c4.448,0.235,7.996,3.735,8.729,8.325c0.006,0.035,0.007-0.094,0.013-0.06c0.07,0.042,0.128,0.004,0.189,0.054
|
||||||
|
c-0.013-0.123,0.202-0.286,0.181-0.409C60.251,57.192,61,57.651,61,58.239V59.447z M76.504,38.85
|
||||||
|
c-0.073,0.104-0.153,0.202-0.23,0.293c-0.504,0.598-2.314,2.252-3.485,3.29c0.1,0.353,0.21,0.751,0.322,1.163
|
||||||
|
c0.367,1.352,0.752,2.859,0.83,3.45c0.045,0.343,0.079,0.754-0.064,1.076c-0.056,0.124-0.134,0.233-0.248,0.323
|
||||||
|
c-0.397,0.345-0.982,0.184-1.433,0.028c-0.765-0.263-3.092-1.417-4.471-2.128c-1.378,0.711-3.705,1.865-4.47,2.128
|
||||||
|
c-0.451,0.155-1.035,0.316-1.434-0.028c-0.114-0.09-0.192-0.199-0.247-0.32c-0.145-0.324-0.11-0.736-0.063-1.079
|
||||||
|
c0.077-0.591,0.461-2.099,0.828-3.45c0.111-0.412,0.222-0.811,0.321-1.163c-1.171-1.038-2.979-2.692-3.485-3.29
|
||||||
|
c-0.077-0.091-0.156-0.188-0.23-0.293c-0.224-0.313-0.394-0.68-0.262-1.053c0.15-0.506,0.728-0.686,1.191-0.798
|
||||||
|
c0.76-0.185,3.197-0.456,4.754-0.603c0.628-1.436,1.574-3.516,1.97-4.152c0.251-0.406,0.601-0.9,1.127-0.887
|
||||||
|
c0.526-0.014,0.877,0.48,1.127,0.887c0.396,0.637,1.342,2.717,1.97,4.152c1.556,0.146,3.994,0.418,4.754,0.603
|
||||||
|
c0.462,0.112,1.041,0.292,1.191,0.798C76.899,38.17,76.729,38.536,76.504,38.85z'
|
||||||
|
/>
|
||||||
|
<path
|
||||||
|
fill='none'
|
||||||
|
d='M50.579,50.516c-0.063-0.002-0.137-0.349-0.21-0.364c-0.072-0.017-0.146,0.461-0.21,0.461
|
||||||
|
c-3.841,0-7.092,2.388-7.936,7.388h0.36h1.018c0.825-4,3.618-6.283,6.876-6.121c2.933,0.156,6.007,1.992,6.761,5.258
|
||||||
|
C57.262,57.135,58,56.795,58,56.792c0-0.001,0-0.018,0-0.018l-0.689,0.673c-0.01,0-0.709,0.553-0.717,0.553h1.048h0.229
|
||||||
|
C57,53,54.144,50.705,50.579,50.516z'
|
||||||
|
/>
|
||||||
|
<path
|
||||||
|
fill='none'
|
||||||
|
d='M65.187,60.622c0-0.468-0.381-0.849-0.851-0.849c-0.469,0-0.851,0.381-0.851,0.849
|
||||||
|
c0,0.246,0.105,0.467,0.272,0.622c0.151,0.143,0.354,0.229,0.578,0.229c0.186,0,0.357-0.061,0.497-0.161
|
||||||
|
C65.047,61.157,65.187,60.905,65.187,60.622z'
|
||||||
|
/>
|
||||||
|
<path
|
||||||
|
fill='none'
|
||||||
|
d='M36.733,60.622c0-0.468-0.382-0.849-0.852-0.849c-0.469,0-0.85,0.381-0.85,0.849
|
||||||
|
c0,0.208,0.075,0.397,0.198,0.546c0.156,0.187,0.391,0.306,0.651,0.306c0.145,0,0.279-0.036,0.398-0.099
|
||||||
|
C36.549,61.231,36.733,60.948,36.733,60.622z'
|
||||||
|
/>
|
||||||
|
<path
|
||||||
|
fill='none'
|
||||||
|
d='M52.479,61.55c-0.219-0.181-0.625-0.333-0.886-0.453c-0.419-0.189-0.346-0.297-0.834-0.297
|
||||||
|
c-1.814,0-2.76,1.479-2.76,3.291c0,0.001,0,0.001,0,0.003c0,0.202-0.585,0.398-0.55,0.592c-0.035,0.192-0.351,0.392-0.351,0.595
|
||||||
|
c0-0.203-0.135-0.517-0.1-0.709C47.215,65.76,47.933,67,49.052,67c0.001,0-0.009,0-0.008,0c-0.08,1-0.171,0.815-0.262,1.183
|
||||||
|
c-0.2,0.823-0.419,1.73-0.601,2.411c-0.237,0.896-0.254,1.277-0.254,1.277c0.023,0.117,0.079,0.501,0.152,0.573
|
||||||
|
C48.307,72.67,48.69,73,48.69,73h0.761h0.617h0.612h0.751c0,0,0.435-0.418,0.596-0.594l0,0c0.007-0.01,0.015-0.156,0.021-0.164
|
||||||
|
c0.012-0.017,0.429-0.101,0.438-0.118C52.555,72.022,53,71.841,53,71.688c0-0.01,0-0.02,0-0.03c0-0.159-0.447-0.528-0.525-0.825
|
||||||
|
C52.281,70.111,51.711,70,51.47,68c0.001,0-0.046,0-0.046,0c-0.096-1-0.213-0.604-0.298-0.965c0-0.004,0.339,0.077,0.338,0.072
|
||||||
|
C52.584,66.74,53,65.86,54,64.687C54,64.683,54,65,54,65h-0.638C53.398,65,54,64.286,54,64.079c0-0.001,0-0.001,0-0.002
|
||||||
|
C54,63.06,53.208,62.15,52.479,61.55z M50.058,60.726c0.001,0,0.001,0,0.002,0c0.351,0,0.687,0.058,1.004,0.16
|
||||||
|
C50.745,60.783,50.406,60.726,50.058,60.726z'
|
||||||
|
/>
|
||||||
|
<path
|
||||||
|
fill='none'
|
||||||
|
d='M73.759,56.149c-0.469,0-0.85,0.381-0.85,0.849c0,0.282,0.139,0.533,0.351,0.688
|
||||||
|
c0.142,0.102,0.312,0.163,0.499,0.163c0.28,0,0.529-0.137,0.684-0.347c0.104-0.142,0.167-0.315,0.167-0.505
|
||||||
|
C74.609,56.53,74.229,56.149,73.759,56.149z'
|
||||||
|
/>
|
||||||
|
<path
|
||||||
|
fill='none'
|
||||||
|
d='M31.176,64.971c0-0.468-0.381-0.849-0.851-0.849c-0.469,0-0.85,0.381-0.85,0.849
|
||||||
|
c0,0.231,0.092,0.442,0.241,0.595c0.155,0.158,0.37,0.258,0.608,0.258c0.218,0,0.416-0.084,0.567-0.219
|
||||||
|
C31.066,65.448,31.176,65.223,31.176,64.971z'
|
||||||
|
/>
|
||||||
|
<path
|
||||||
|
fill='none'
|
||||||
|
d='M26.958,57.687c0.213-0.155,0.352-0.406,0.352-0.688c0-0.468-0.381-0.849-0.85-0.849
|
||||||
|
c-0.47,0-0.851,0.381-0.851,0.849c0,0.189,0.062,0.363,0.167,0.505c0.154,0.21,0.403,0.347,0.684,0.347
|
||||||
|
C26.646,57.85,26.817,57.788,26.958,57.687z'
|
||||||
|
/>
|
||||||
|
<path
|
||||||
|
fill='none'
|
||||||
|
d='M69.205,18.381c-2.209-3.189-3.136-6.219-3.511-7.91L46.843,10.79l-12.504-0.294
|
||||||
|
c-0.964,4.14-5.284,16.263-23.051,19.004c-0.433,3.211-1.389,13.333,1.653,24.751c4.889,18.347,17.261,31.211,36.775,38.238
|
||||||
|
l0.287,0.072l0.348-0.078c19.505-7.027,31.871-19.891,36.759-38.23c3.041-11.418,2.085-21.54,1.654-24.75
|
||||||
|
C77.828,27.814,72.103,22.562,69.205,18.381z M83.506,53.687c-4.507,16.467-15.59,28.19-32.942,34.848l-0.538,0.207l-0.54-0.207
|
||||||
|
c-17.522-6.723-28.647-18.605-33.066-35.32c-2.077-7.854-2.182-14.95-1.902-19.521l0.067-1.104l1.074-0.265
|
||||||
|
c9.915-2.453,15.352-7.808,18.169-11.867c1.144-1.651,2.105-3.435,2.853-5.3l0.389-0.969l9.761,0.23l16.144-0.275l0.385,0.965
|
||||||
|
c0.749,1.877,1.713,3.674,2.866,5.339c2.812,4.062,8.247,9.419,18.156,11.877l1.075,0.265l0.065,1.105
|
||||||
|
C85.801,38.434,85.68,45.75,83.506,53.687z'
|
||||||
|
/>
|
||||||
|
<path
|
||||||
|
fill='none'
|
||||||
|
d='M70.324,65.703c0.252-0.147,0.419-0.42,0.419-0.732c0-0.468-0.381-0.849-0.85-0.849
|
||||||
|
c-0.47,0-0.851,0.381-0.851,0.849c0,0.15,0.039,0.291,0.106,0.413c0.146,0.262,0.424,0.439,0.744,0.439
|
||||||
|
C70.052,65.823,70.198,65.779,70.324,65.703z'
|
||||||
|
/>
|
||||||
|
</g>
|
||||||
|
</g>
|
||||||
|
<g>
|
||||||
|
<path
|
||||||
|
fill='none'
|
||||||
|
d='M50.406,53.726C47.937,53.604,45.774,55,45.007,58h0.562h9.139h0.49C54.551,55,52.688,53.848,50.406,53.726z'
|
||||||
|
/>
|
||||||
|
<path
|
||||||
|
fill='none'
|
||||||
|
d='M47.445,66.015c0.214,0.31,0.471,0.573,0.767,0.748C47.917,66.556,47.663,66.3,47.445,66.015z'
|
||||||
|
/>
|
||||||
|
<path
|
||||||
|
fill='none'
|
||||||
|
d='M49.017,67.176c-0.075,0.794-0.155,0.699-0.239,1.042c-0.201,0.823-0.417,1.821-0.598,2.503
|
||||||
|
c-0.239,0.894-0.254,1.32-0.254,1.32c0.023,0.116,0.08,0.346,0.153,0.418C48.306,72.684,48.69,73,48.69,73h0.761h0.617h0.612h0.751
|
||||||
|
c0,0,0.536-0.418,0.697-0.594c0.007-0.01,0.115-0.297,0.121-0.304c0.012-0.017,0.227-0.032,0.236-0.048
|
||||||
|
c0.068-0.104-1.303,0.041-1.303-0.112c0-0.011,0.463-0.101,0.452-0.101c-1.239,0.009-0.07-0.629-0.15-0.928
|
||||||
|
C51.292,70.195,51.616,70,51.376,68c0.001,0,0.003,0,0.003,0c-0.079,0-0.145-0.401-0.212-0.746
|
||||||
|
c-0.304,0.086-0.618,0.146-0.949,0.146C49.794,67.4,49.393,67.313,49.017,67.176z'
|
||||||
|
/>
|
||||||
|
<path
|
||||||
|
fill='none'
|
||||||
|
d='M50.579,50.023c-0.063-0.003-0.347,0.027-0.412,0.027c-3.841,0-7.1,2.949-7.944,7.949h0.36h1.018
|
||||||
|
c0.825-4,3.618-6.264,6.876-6.101c2.884,0.153,5.29,1.955,6.084,5.131c0.427-0.009,0.989-0.097,1.454-0.062
|
||||||
|
C57.144,53.209,54.143,50.213,50.579,50.023z'
|
||||||
|
/>
|
||||||
|
<g>
|
||||||
|
<path
|
||||||
|
fill='#018B21'
|
||||||
|
d='M91.562,27.889l-0.202-1.074l-1.086-0.139C70.261,24.119,68.516,9.418,68.452,8.799l-0.134-1.385
|
||||||
|
L46.854,7.779L31.735,7.423l-0.135,1.393c-0.004,0.038-0.395,3.771-3.229,7.858c-3.848,5.551-10.104,8.916-18.595,9.998
|
||||||
|
l-1.085,0.139l-0.204,1.076c-0.095,0.507-2.302,12.593,1.514,27.023C13.536,68.275,23.13,86.142,48.77,95.348l1.197,0.309
|
||||||
|
l1.139-0.258l0.176-0.051c25.64-9.206,35.233-27.071,38.77-40.437C93.864,40.482,91.659,28.396,91.562,27.889z M87.11,54.253
|
||||||
|
c-4.888,18.34-17.254,31.203-36.759,38.23l-0.348,0.078l-0.287-0.072C30.202,85.462,17.83,72.598,12.941,54.251
|
||||||
|
c-3.042-11.418-2.086-21.54-1.653-24.751c17.767-2.741,22.087-14.864,23.051-19.004l12.504,0.294l18.852-0.319
|
||||||
|
c0.375,1.691,1.302,4.721,3.511,7.91c2.897,4.182,8.623,9.434,19.56,11.122C89.195,32.713,90.151,42.835,87.11,54.253z'
|
||||||
|
/>
|
||||||
|
<path
|
||||||
|
fill='#018B21'
|
||||||
|
d='M85.455,32.588l-1.075-0.265c-9.909-2.458-15.344-7.814-18.156-11.877
|
||||||
|
c-1.153-1.665-2.117-3.462-2.866-5.339l-0.385-0.965l-16.144,0.275l-9.761-0.23l-0.389,0.969c-0.747,1.865-1.709,3.648-2.853,5.3
|
||||||
|
c-2.817,4.06-8.254,9.414-18.169,11.867l-1.074,0.265l-0.067,1.104c-0.279,4.571-0.175,11.668,1.902,19.521
|
||||||
|
c4.419,16.715,15.544,28.598,33.066,35.32l0.54,0.207l0.538-0.207c17.353-6.657,28.436-18.381,32.942-34.848
|
||||||
|
c2.174-7.937,2.295-15.253,2.015-19.993L85.455,32.588z M36.28,61.375c-0.119,0.062-0.254,0.099-0.398,0.099
|
||||||
|
c-0.261,0-0.495-0.119-0.651-0.306c-0.123-0.148-0.198-0.338-0.198-0.546c0-0.468,0.381-0.849,0.85-0.849
|
||||||
|
c0.47,0,0.852,0.381,0.852,0.849C36.733,60.948,36.549,61.231,36.28,61.375z M64.336,61.474c-0.224,0-0.427-0.087-0.578-0.229
|
||||||
|
c-0.167-0.155-0.272-0.376-0.272-0.622c0-0.468,0.382-0.849,0.851-0.849c0.47,0,0.851,0.381,0.851,0.849
|
||||||
|
c0,0.283-0.14,0.535-0.354,0.69C64.693,61.413,64.521,61.474,64.336,61.474z M82.054,53.288
|
||||||
|
c-1.077,3.938-2.545,7.597-4.395,10.971c-0.091-0.015-0.186-0.012-0.28,0.021c-0.366,0.122-2.047,0.171-2.776,0.104
|
||||||
|
c-0.044-0.951-0.101-3.547-0.14-5.709c0.654-0.275,1.113-0.923,1.113-1.676c0-1.001-0.815-1.815-1.817-1.815
|
||||||
|
s-1.817,0.814-1.817,1.815c0,0.836,0.568,1.539,1.337,1.752c0.105,5.922,0.173,6.169,0.195,6.251
|
||||||
|
c0.048,0.172,0.167,0.315,0.334,0.405c0.262,0.138,0.941,0.192,1.672,0.192c0.502,0,1.182-0.026,1.61-0.066
|
||||||
|
C74.622,69.718,72,73.446,68,76.708v-5.132l2.468-0.188L70.4,66.736c0.795-0.194,1.35-0.911,1.35-1.766
|
||||||
|
c0-1.001-0.836-1.815-1.838-1.815s-1.826,0.814-1.826,1.815c0,0.737,0.436,1.371,1.066,1.656l0.232,3.664L67,70.482v7.231
|
||||||
|
c-1,0.445-1,0.882-2,1.307V62.36c1-0.229,1.219-0.921,1.219-1.738c0-1.001-0.849-1.814-1.852-1.814
|
||||||
|
c-1.002,0-1.68,0.813-1.68,1.814c0,0.776,1.312,1.438,1.312,1.698V79.89c-3,2.858-8.348,5.275-13.471,7.24
|
||||||
|
C45.383,85.154,41,82.724,37,79.847V62.391c1-0.188,1.051-0.908,1.051-1.769c0-1.001-0.991-1.814-1.993-1.814
|
||||||
|
c-1,0-1.465,0.813-1.465,1.814c0,0.73,0.407,1.36,1.407,1.648v16.706c-1-0.46-1-0.929-2-1.41v-7.083l-2.851-0.186l-0.111-3.595
|
||||||
|
c0.732-0.234,1.184-0.922,1.184-1.732c0-1.001-0.854-1.815-1.856-1.815s-1.837,0.814-1.837,1.815c0,0.784,0.489,1.45,1.185,1.706
|
||||||
|
l0.339,4.706L33,71.573v4.978c-4-3.229-7.057-6.91-9.486-11.035c0.457,0.05,0.855,0.083,1.43,0.083c0.73,0,1.31-0.055,1.57-0.192
|
||||||
|
c0.168-0.09,0.234-0.233,0.281-0.404c0.024-0.083,0.066-0.326,0.171-6.252c0.769-0.213,1.324-0.916,1.324-1.752
|
||||||
|
c0-1.001-0.822-1.815-1.824-1.815s-1.819,0.814-1.819,1.815c0,0.753,0.457,1.4,1.11,1.676c-0.038,2.162-0.097,4.759-0.142,5.713
|
||||||
|
c-0.729,0.066-2.409,0.016-2.776-0.106c-0.145-0.049-0.295-0.035-0.425,0.021c-1.932-3.517-3.447-7.343-4.539-11.473
|
||||||
|
c-2.053-7.755-2.117-14.756-1.855-19.046c10.219-2.527,15.946-8.003,19.044-12.47c1.36-1.962,2.328-3.89,3.013-5.597l8.718,0.207
|
||||||
|
l15.166-0.258c0.685,1.718,1.655,3.658,3.026,5.637c3.095,4.469,8.819,9.949,19.031,12.48
|
||||||
|
C84.277,38.185,84.206,45.428,82.054,53.288z M74.442,57.503c-0.154,0.21-0.403,0.347-0.684,0.347
|
||||||
|
c-0.187,0-0.357-0.062-0.499-0.163c-0.212-0.155-0.351-0.406-0.351-0.688c0-0.468,0.381-0.849,0.85-0.849
|
||||||
|
c0.47,0,0.851,0.381,0.851,0.849C74.609,57.188,74.547,57.361,74.442,57.503z M69.894,65.823c-0.32,0-0.598-0.178-0.744-0.439
|
||||||
|
c-0.067-0.122-0.106-0.263-0.106-0.413c0-0.468,0.381-0.849,0.851-0.849c0.469,0,0.85,0.381,0.85,0.849
|
||||||
|
c0,0.312-0.167,0.585-0.419,0.732C70.198,65.779,70.052,65.823,69.894,65.823z M30.893,65.604
|
||||||
|
c-0.151,0.135-0.35,0.219-0.567,0.219c-0.238,0-0.453-0.1-0.608-0.258c-0.149-0.152-0.241-0.363-0.241-0.595
|
||||||
|
c0-0.468,0.381-0.849,0.85-0.849c0.47,0,0.851,0.381,0.851,0.849C31.176,65.223,31.066,65.448,30.893,65.604z M25.776,57.503
|
||||||
|
c-0.104-0.142-0.167-0.315-0.167-0.505c0-0.468,0.381-0.849,0.851-0.849c0.469,0,0.85,0.381,0.85,0.849
|
||||||
|
c0,0.282-0.139,0.533-0.352,0.688c-0.141,0.102-0.312,0.163-0.498,0.163C26.18,57.85,25.931,57.713,25.776,57.503z'
|
||||||
|
/>
|
||||||
|
<path
|
||||||
|
fill='#018B21'
|
||||||
|
d='M37.713,43.596c-0.112-0.412-0.223-0.811-0.322-1.163c1.171-1.037,2.982-2.692,3.487-3.29
|
||||||
|
c0.075-0.091,0.155-0.188,0.229-0.293c0.225-0.313,0.396-0.68,0.263-1.053c-0.15-0.505-0.729-0.686-1.192-0.798
|
||||||
|
c-0.758-0.185-3.198-0.454-4.754-0.604c-0.628-1.434-1.573-3.514-1.97-4.15c-0.25-0.406-0.599-0.9-1.127-0.887
|
||||||
|
c-0.526-0.014-0.875,0.48-1.127,0.887c-0.394,0.637-1.341,2.717-1.968,4.15c-1.559,0.15-3.996,0.42-4.755,0.604
|
||||||
|
c-0.464,0.112-1.042,0.293-1.192,0.798c-0.131,0.373,0.04,0.739,0.263,1.053c0.074,0.104,0.154,0.202,0.231,0.293
|
||||||
|
c0.504,0.598,2.313,2.253,3.484,3.29c-0.1,0.353-0.21,0.751-0.322,1.163c-0.366,1.352-0.751,2.859-0.829,3.45
|
||||||
|
c-0.045,0.343-0.078,0.754,0.064,1.077c0.055,0.123,0.134,0.232,0.248,0.322c0.399,0.345,0.981,0.184,1.433,0.028
|
||||||
|
c0.766-0.261,3.091-1.417,4.47-2.128c1.38,0.711,3.707,1.867,4.473,2.128c0.449,0.155,1.033,0.316,1.433-0.028
|
||||||
|
c0.114-0.09,0.193-0.199,0.247-0.322c0.145-0.323,0.11-0.734,0.064-1.077C38.465,46.455,38.081,44.947,37.713,43.596z'
|
||||||
|
/>
|
||||||
|
<path
|
||||||
|
fill='#018B21'
|
||||||
|
d='M75.575,36.999c-0.76-0.185-3.198-0.456-4.754-0.603c-0.628-1.436-1.574-3.516-1.97-4.152
|
||||||
|
c-0.25-0.406-0.601-0.9-1.127-0.887c-0.526-0.014-0.876,0.48-1.127,0.887c-0.396,0.637-1.342,2.717-1.97,4.152
|
||||||
|
c-1.557,0.146-3.994,0.418-4.754,0.603c-0.464,0.112-1.041,0.292-1.191,0.798c-0.132,0.373,0.038,0.739,0.262,1.053
|
||||||
|
c0.074,0.104,0.153,0.202,0.23,0.293c0.506,0.598,2.314,2.252,3.485,3.29c-0.1,0.353-0.21,0.751-0.321,1.163
|
||||||
|
c-0.367,1.352-0.751,2.859-0.828,3.45c-0.047,0.343-0.081,0.755,0.063,1.079c0.055,0.121,0.133,0.23,0.247,0.32
|
||||||
|
c0.398,0.345,0.982,0.184,1.434,0.028c0.765-0.263,3.092-1.417,4.47-2.128c1.379,0.711,3.706,1.865,4.471,2.128
|
||||||
|
c0.45,0.155,1.035,0.316,1.433-0.028c0.114-0.09,0.192-0.199,0.248-0.323c0.144-0.322,0.109-0.733,0.064-1.076
|
||||||
|
c-0.078-0.591-0.463-2.099-0.83-3.45c-0.112-0.412-0.223-0.811-0.322-1.163c1.171-1.038,2.981-2.692,3.485-3.29
|
||||||
|
c0.077-0.091,0.157-0.188,0.23-0.293c0.225-0.313,0.396-0.68,0.263-1.053C76.616,37.291,76.037,37.111,75.575,36.999z'
|
||||||
|
/>
|
||||||
|
<path
|
||||||
|
fill='#018B21'
|
||||||
|
d='M41.646,27.572c0.505,0.596,2.313,2.251,3.485,3.289c-0.1,0.353-0.21,0.75-0.322,1.162
|
||||||
|
c-0.367,1.353-0.751,2.862-0.828,3.452c-0.046,0.342-0.081,0.754,0.063,1.077c0.054,0.123,0.133,0.232,0.247,0.32
|
||||||
|
c0.398,0.346,0.982,0.184,1.434,0.029c0.765-0.262,3.091-1.416,4.47-2.129c1.379,0.713,3.707,1.867,4.471,2.129
|
||||||
|
c0.45,0.154,1.034,0.316,1.434-0.029c0.114-0.088,0.192-0.197,0.248-0.32c0.144-0.323,0.109-0.735,0.064-1.077
|
||||||
|
c-0.078-0.59-0.463-2.1-0.831-3.452c-0.112-0.412-0.223-0.81-0.322-1.162c1.172-1.038,2.98-2.693,3.486-3.289
|
||||||
|
c0.077-0.09,0.156-0.189,0.23-0.293c0.223-0.314,0.394-0.681,0.262-1.053c-0.15-0.506-0.728-0.686-1.191-0.799
|
||||||
|
c-0.759-0.185-3.198-0.454-4.754-0.602c-0.628-1.437-1.574-3.518-1.97-4.154c-0.25-0.404-0.6-0.899-1.127-0.885
|
||||||
|
c-0.526-0.015-0.877,0.48-1.127,0.885c-0.396,0.637-1.342,2.718-1.97,4.154c-1.556,0.147-3.994,0.417-4.754,0.602
|
||||||
|
c-0.463,0.113-1.043,0.293-1.191,0.799c-0.133,0.371,0.038,0.738,0.263,1.053C41.489,27.383,41.568,27.482,41.646,27.572z'
|
||||||
|
/>
|
||||||
|
<path
|
||||||
|
fill='#018B21'
|
||||||
|
d='M50.058,60.726c0.349,0,0.688,0.058,1.006,0.16c-0.317-0.103-0.653-0.16-1.004-0.16
|
||||||
|
C50.059,60.726,50.059,60.726,50.058,60.726z'
|
||||||
|
/>
|
||||||
|
<path
|
||||||
|
fill='#018B21'
|
||||||
|
d='M60,57h-0.164c-0.035-0.015-0.071-0.032-0.104-0.045c0.002,0.011-0.116,0.021-0.114,0.031
|
||||||
|
c-0.057-0.021-0.175-0.041-0.234-0.061c-0.789-4.52-4.337-7.609-8.736-7.841c-4.457-0.241-8.334,3.372-9.571,7.915H41
|
||||||
|
c-0.552,0-1,0.447-1,1c0,0.035,0.017,0.065,0.02,0.101C40.016,58.148,40,58.188,40,58.239v1.208v14.64
|
||||||
|
C40,74.849,40.429,76,41.19,76h17.672C59.623,76,61,74.849,61,74.087v-14.64v-1.208c0-0.046-0.012-0.091-0.021-0.136
|
||||||
|
C60.983,58.068,61,58.037,61,58C61,57.447,60.553,57,60,57z M50.167,50.051c0.064,0,0.348-0.03,0.412-0.027
|
||||||
|
c3.563,0.189,6.564,3.186,7.437,6.945c-0.316-0.023-0.664,0.005-0.997,0.031h-0.467c-0.802-3.156-3.2-4.947-6.074-5.101
|
||||||
|
c-2.974-0.148-5.556,1.729-6.611,5.101h-1.433C43.521,52.61,46.591,50.051,50.167,50.051z M45.341,57
|
||||||
|
c0.955-2.308,2.889-3.381,5.065-3.274c2.008,0.107,3.688,1.016,4.505,3.274H45.341z'
|
||||||
|
/>
|
||||||
|
</g>
|
||||||
|
</g>
|
||||||
|
<path
|
||||||
|
fill='#91EA9A'
|
||||||
|
d='M53.719,63.898c0-1.934-1.567-3.501-3.501-3.501c-1.934,0-3.501,1.567-3.501,3.501
|
||||||
|
c0,0.287,0.044,0.562,0.109,0.829c0.007-0.053,0.011-0.109,0.02-0.156c0.027,0.152,0.066,0.305,0.113,0.455
|
||||||
|
c0.111,0.354,0.279,0.69,0.486,0.988c0.218,0.285,0.473,0.541,0.767,0.748c0.222,0.131,0.466,0.205,0.724,0.223
|
||||||
|
c-0.125,0.683-1.04,5.233-1.04,5.233s-0.301,1.079,2.058,0.929l2.61-0.126c0,0,0.683,0.309-1.294-5.806
|
||||||
|
C52.685,66.767,53.719,65.462,53.719,63.898z'
|
||||||
|
/>
|
||||||
|
</SvgIcon>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
export default BadgePrivacy;
|
@ -1,7 +1,7 @@
|
|||||||
import React from 'react';
|
import React from 'react';
|
||||||
import { SvgIcon } from '@mui/material';
|
import { SvgIcon, type SvgIconProps } from '@mui/material';
|
||||||
|
|
||||||
export default function BasqueCountryFlag(props) {
|
const BasqueCountryFlag: React.FC<SvgIconProps> = (props) => {
|
||||||
return (
|
return (
|
||||||
<SvgIcon {...props} x='0px' y='0px' viewBox='0 0 50 28'>
|
<SvgIcon {...props} x='0px' y='0px' viewBox='0 0 50 28'>
|
||||||
<path d='M0,0 v28 h50 v-28 z' fill='#D52B1E' />
|
<path d='M0,0 v28 h50 v-28 z' fill='#D52B1E' />
|
||||||
@ -9,4 +9,6 @@ export default function BasqueCountryFlag(props) {
|
|||||||
<path d='M25,0 v28 M0,14 h50' stroke='#fff' strokeWidth='4.3' />
|
<path d='M25,0 v28 M0,14 h50' stroke='#fff' strokeWidth='4.3' />
|
||||||
</SvgIcon>
|
</SvgIcon>
|
||||||
);
|
);
|
||||||
}
|
};
|
||||||
|
|
||||||
|
export default BasqueCountryFlag;
|
||||||
|
@ -1,10 +1,12 @@
|
|||||||
import React from 'react';
|
import React from 'react';
|
||||||
import { SvgIcon } from '@mui/material';
|
import { SvgIcon, type SvgIconProps } from '@mui/material';
|
||||||
|
|
||||||
export default function BitcoinIcon(props) {
|
const Bitcoin: React.FC<SvgIconProps> = (props) => {
|
||||||
return (
|
return (
|
||||||
<SvgIcon sx={props.sx} color={props.color} viewBox='0 0 512 512'>
|
<SvgIcon sx={props.sx} color={props.color} viewBox='0 0 512 512'>
|
||||||
<path d='M504 256c0 136.967-111.033 248-248 248S8 392.967 8 256 119.033 8 256 8s248 111.033 248 248zm-141.651-35.33c4.937-32.999-20.191-50.739-54.55-62.573l11.146-44.702-27.213-6.781-10.851 43.524c-7.154-1.783-14.502-3.464-21.803-5.13l10.929-43.81-27.198-6.781-11.153 44.686c-5.922-1.349-11.735-2.682-17.377-4.084l.031-.14-37.53-9.37-7.239 29.062s20.191 4.627 19.765 4.913c11.022 2.751 13.014 10.044 12.68 15.825l-12.696 50.925c.76.194 1.744.473 2.829.907-.907-.225-1.876-.473-2.876-.713l-17.796 71.338c-1.349 3.348-4.767 8.37-12.471 6.464.271.395-19.78-4.937-19.78-4.937l-13.51 31.147 35.414 8.827c6.588 1.651 13.045 3.379 19.4 5.006l-11.262 45.213 27.182 6.781 11.153-44.733a1038.209 1038.209 0 0 0 21.687 5.627l-11.115 44.523 27.213 6.781 11.262-45.128c46.404 8.781 81.299 5.239 95.986-36.727 11.836-33.79-.589-53.281-25.004-65.991 17.78-4.098 31.174-15.792 34.747-39.949zm-62.177 87.179c-8.41 33.79-65.308 15.523-83.755 10.943l14.944-59.899c18.446 4.603 77.6 13.717 68.811 48.956zm8.417-87.667c-7.673 30.736-55.031 15.12-70.393 11.292l13.548-54.327c15.363 3.828 64.836 10.973 56.845 43.035z' />
|
<path d='M504 256c0 136.967-111.033 248-248 248S8 392.967 8 256 119.033 8 256 8s248 111.033 248 248zm-141.651-35.33c4.937-32.999-20.191-50.739-54.55-62.573l11.146-44.702-27.213-6.781-10.851 43.524c-7.154-1.783-14.502-3.464-21.803-5.13l10.929-43.81-27.198-6.781-11.153 44.686c-5.922-1.349-11.735-2.682-17.377-4.084l.031-.14-37.53-9.37-7.239 29.062s20.191 4.627 19.765 4.913c11.022 2.751 13.014 10.044 12.68 15.825l-12.696 50.925c.76.194 1.744.473 2.829.907-.907-.225-1.876-.473-2.876-.713l-17.796 71.338c-1.349 3.348-4.767 8.37-12.471 6.464.271.395-19.78-4.937-19.78-4.937l-13.51 31.147 35.414 8.827c6.588 1.651 13.045 3.379 19.4 5.006l-11.262 45.213 27.182 6.781 11.153-44.733a1038.209 1038.209 0 0 0 21.687 5.627l-11.115 44.523 27.213 6.781 11.262-45.128c46.404 8.781 81.299 5.239 95.986-36.727 11.836-33.79-.589-53.281-25.004-65.991 17.78-4.098 31.174-15.792 34.747-39.949zm-62.177 87.179c-8.41 33.79-65.308 15.523-83.755 10.943l14.944-59.899c18.446 4.603 77.6 13.717 68.811 48.956zm8.417-87.667c-7.673 30.736-55.031 15.12-70.393 11.292l13.548-54.327c15.363 3.828 64.836 10.973 56.845 43.035z' />
|
||||||
</SvgIcon>
|
</SvgIcon>
|
||||||
);
|
);
|
||||||
}
|
};
|
||||||
|
|
||||||
|
export default Bitcoin;
|
||||||
|
@ -1,10 +1,12 @@
|
|||||||
import React from 'react';
|
import React from 'react';
|
||||||
import { SvgIcon } from '@mui/material';
|
import { SvgIcon, type SvgIconProps } from '@mui/material';
|
||||||
|
|
||||||
export default function BitcoinSignIcon(props) {
|
const BitcoinSign: React.FC<SvgIconProps> = (props) => {
|
||||||
return (
|
return (
|
||||||
<SvgIcon sx={props.sx} color={props.color} viewBox='0 0 320 512'>
|
<SvgIcon sx={props.sx} color={props.color} viewBox='0 0 320 512'>
|
||||||
<path d='M48 32C48 14.33 62.33 0 80 0C97.67 0 112 14.33 112 32V64H144V32C144 14.33 158.3 0 176 0C193.7 0 208 14.33 208 32V64C208 65.54 207.9 67.06 207.7 68.54C254.1 82.21 288 125.1 288 176C288 200.2 280.3 222.6 267.3 240.9C298.9 260.7 320 295.9 320 336C320 397.9 269.9 448 208 448V480C208 497.7 193.7 512 176 512C158.3 512 144 497.7 144 480V448H112V480C112 497.7 97.67 512 80 512C62.33 512 48 497.7 48 480V448H41.74C18.69 448 0 429.3 0 406.3V101.6C0 80.82 16.82 64 37.57 64H48V32zM176 224C202.5 224 224 202.5 224 176C224 149.5 202.5 128 176 128H64V224H176zM64 288V384H208C234.5 384 256 362.5 256 336C256 309.5 234.5 288 208 288H64z' />
|
<path d='M48 32C48 14.33 62.33 0 80 0C97.67 0 112 14.33 112 32V64H144V32C144 14.33 158.3 0 176 0C193.7 0 208 14.33 208 32V64C208 65.54 207.9 67.06 207.7 68.54C254.1 82.21 288 125.1 288 176C288 200.2 280.3 222.6 267.3 240.9C298.9 260.7 320 295.9 320 336C320 397.9 269.9 448 208 448V480C208 497.7 193.7 512 176 512C158.3 512 144 497.7 144 480V448H112V480C112 497.7 97.67 512 80 512C62.33 512 48 497.7 48 480V448H41.74C18.69 448 0 429.3 0 406.3V101.6C0 80.82 16.82 64 37.57 64H48V32zM176 224C202.5 224 224 202.5 224 176C224 149.5 202.5 128 176 128H64V224H176zM64 288V384H208C234.5 384 256 362.5 256 336C256 309.5 234.5 288 208 288H64z' />
|
||||||
</SvgIcon>
|
</SvgIcon>
|
||||||
);
|
);
|
||||||
}
|
};
|
||||||
|
|
||||||
|
export default BitcoinSign;
|
||||||
|
@ -1,7 +1,7 @@
|
|||||||
import React from 'react';
|
import React from 'react';
|
||||||
import { SvgIcon } from '@mui/material';
|
import { SvgIcon, type SvgIconProps } from '@mui/material';
|
||||||
|
|
||||||
export default function BuySatsIcon(props) {
|
const BuySats: React.FC<SvgIconProps> = (props) => {
|
||||||
return (
|
return (
|
||||||
<SvgIcon sx={props.sx} color={props.color} x='0px' y='0px' viewBox='0 0 300 300'>
|
<SvgIcon sx={props.sx} color={props.color} x='0px' y='0px' viewBox='0 0 300 300'>
|
||||||
<g>
|
<g>
|
||||||
@ -134,4 +134,6 @@ export default function BuySatsIcon(props) {
|
|||||||
</g>
|
</g>
|
||||||
</SvgIcon>
|
</SvgIcon>
|
||||||
);
|
);
|
||||||
}
|
};
|
||||||
|
|
||||||
|
export default BuySats;
|
||||||
|
@ -1,7 +1,7 @@
|
|||||||
import React from 'react';
|
import React from 'react';
|
||||||
import { SvgIcon } from '@mui/material';
|
import { SvgIcon, type SvgIconProps } from '@mui/material';
|
||||||
|
|
||||||
export default function BuySatsCheckedIcon(props) {
|
const BuySatsChecked: React.FC<SvgIconProps> = (props) => {
|
||||||
return (
|
return (
|
||||||
<SvgIcon sx={props.sx} color={props.color} x='0px' y='0px' viewBox='0 0 300 300'>
|
<SvgIcon sx={props.sx} color={props.color} x='0px' y='0px' viewBox='0 0 300 300'>
|
||||||
<g>
|
<g>
|
||||||
@ -76,4 +76,6 @@ export default function BuySatsCheckedIcon(props) {
|
|||||||
</g>
|
</g>
|
||||||
</SvgIcon>
|
</SvgIcon>
|
||||||
);
|
);
|
||||||
}
|
};
|
||||||
|
|
||||||
|
export default BuySatsChecked;
|
||||||
|
@ -1,11 +1,13 @@
|
|||||||
import React from 'react';
|
import React from 'react';
|
||||||
import { SvgIcon } from '@mui/material';
|
import { SvgIcon, type SvgIconProps } from '@mui/material';
|
||||||
|
|
||||||
export default function CataloniaFlag(props) {
|
const CataloniaFlag: React.FC<SvgIconProps> = (props) => {
|
||||||
return (
|
return (
|
||||||
<SvgIcon {...props} x='0px' y='0px' viewBox='0 0 810 540'>
|
<SvgIcon {...props} x='0px' y='0px' viewBox='0 0 810 540'>
|
||||||
<rect width='810' height='540' fill='#FCDD09' />
|
<rect width='810' height='540' fill='#FCDD09' />
|
||||||
<path stroke='#DA121A' strokeWidth='60' d='M0,90H810m0,120H0m0,120H810m0,120H0' />
|
<path stroke='#DA121A' strokeWidth='60' d='M0,90H810m0,120H0m0,120H810m0,120H0' />
|
||||||
</SvgIcon>
|
</SvgIcon>
|
||||||
);
|
);
|
||||||
}
|
};
|
||||||
|
|
||||||
|
export default CataloniaFlag;
|
||||||
|
@ -1,7 +1,7 @@
|
|||||||
import React from 'react';
|
import React from 'react';
|
||||||
import { SvgIcon } from '@mui/material';
|
import { SvgIcon, type SvgIconProps } from '@mui/material';
|
||||||
|
|
||||||
export default function EarthIcon(props) {
|
const Earth: React.FC<SvgIconProps> = (props) => {
|
||||||
return (
|
return (
|
||||||
<SvgIcon {...props} x='0px' y='0px' viewBox='0 0 440.45 440.45'>
|
<SvgIcon {...props} x='0px' y='0px' viewBox='0 0 440.45 440.45'>
|
||||||
<g id='XMLID_34_'>
|
<g id='XMLID_34_'>
|
||||||
@ -61,11 +61,13 @@ export default function EarthIcon(props) {
|
|||||||
</g>
|
</g>
|
||||||
</g>
|
</g>
|
||||||
<path
|
<path
|
||||||
style={{ opacity: '0.3', fill: '#808080', enableBackground: 'new' }}
|
style={{ opacity: '0.3', fill: '#808080' }}
|
||||||
d='M190.23,0.005c5.8,0,11.54,0.26,17.2,0.77
|
d='M190.23,0.005c5.8,0,11.54,0.26,17.2,0.77
|
||||||
C110.48,9.515,34.5,90.995,34.5,190.225c0,99.25,76.01,180.74,172.99,189.45c-5.68,0.51-11.44,0.77-17.26,0.77
|
C110.48,9.515,34.5,90.995,34.5,190.225c0,99.25,76.01,180.74,172.99,189.45c-5.68,0.51-11.44,0.77-17.26,0.77
|
||||||
C85.17,380.445,0,295.285,0,190.225S85.17,0.005,190.23,0.005z'
|
C85.17,380.445,0,295.285,0,190.225S85.17,0.005,190.23,0.005z'
|
||||||
/>
|
/>
|
||||||
</SvgIcon>
|
</SvgIcon>
|
||||||
);
|
);
|
||||||
}
|
};
|
||||||
|
|
||||||
|
export default Earth;
|
||||||
|
@ -1,10 +1,12 @@
|
|||||||
import React from 'react';
|
import React from 'react';
|
||||||
import { SvgIcon } from '@mui/material';
|
import { SvgIcon, type SvgIconProps } from '@mui/material';
|
||||||
|
|
||||||
export default function ExportIcon(props) {
|
const Export: React.FC<SvgIconProps> = (props) => {
|
||||||
return (
|
return (
|
||||||
<SvgIcon sx={props.sx} color={props.color} viewBox='0 0 576 512'>
|
<SvgIcon sx={props.sx} color={props.color} viewBox='0 0 576 512'>
|
||||||
<path d='M192 312C192 298.8 202.8 288 216 288H384V160H256c-17.67 0-32-14.33-32-32L224 0H48C21.49 0 0 21.49 0 48v416C0 490.5 21.49 512 48 512h288c26.51 0 48-21.49 48-48v-128H216C202.8 336 192 325.3 192 312zM256 0v128h128L256 0zM568.1 295l-80-80c-9.375-9.375-24.56-9.375-33.94 0s-9.375 24.56 0 33.94L494.1 288H384v48h110.1l-39.03 39.03C450.3 379.7 448 385.8 448 392s2.344 12.28 7.031 16.97c9.375 9.375 24.56 9.375 33.94 0l80-80C578.3 319.6 578.3 304.4 568.1 295z' />
|
<path d='M192 312C192 298.8 202.8 288 216 288H384V160H256c-17.67 0-32-14.33-32-32L224 0H48C21.49 0 0 21.49 0 48v416C0 490.5 21.49 512 48 512h288c26.51 0 48-21.49 48-48v-128H216C202.8 336 192 325.3 192 312zM256 0v128h128L256 0zM568.1 295l-80-80c-9.375-9.375-24.56-9.375-33.94 0s-9.375 24.56 0 33.94L494.1 288H384v48h110.1l-39.03 39.03C450.3 379.7 448 385.8 448 392s2.344 12.28 7.031 16.97c9.375 9.375 24.56 9.375 33.94 0l80-80C578.3 319.6 578.3 304.4 568.1 295z' />
|
||||||
</SvgIcon>
|
</SvgIcon>
|
||||||
);
|
);
|
||||||
}
|
};
|
||||||
|
|
||||||
|
export default Export;
|
||||||
|
@ -1,7 +1,7 @@
|
|||||||
import React from 'react';
|
import React from 'react';
|
||||||
import { SvgIcon } from '@mui/material';
|
import { SvgIcon, type SvgIconProps } from '@mui/material';
|
||||||
|
|
||||||
export default function GoldIcon(props) {
|
const Gold: React.FC<SvgIconProps> = (props) => {
|
||||||
return (
|
return (
|
||||||
<SvgIcon {...props} x='0px' y='0px' viewBox='0 0 511.882 511.882'>
|
<SvgIcon {...props} x='0px' y='0px' viewBox='0 0 511.882 511.882'>
|
||||||
<polygon
|
<polygon
|
||||||
@ -45,4 +45,6 @@ export default function GoldIcon(props) {
|
|||||||
/>
|
/>
|
||||||
</SvgIcon>
|
</SvgIcon>
|
||||||
);
|
);
|
||||||
}
|
};
|
||||||
|
|
||||||
|
export default Gold;
|
||||||
|
@ -1,10 +1,12 @@
|
|||||||
import React from 'react';
|
import React from 'react';
|
||||||
import { SvgIcon } from '@mui/material';
|
import { SvgIcon, type SvgIconProps } from '@mui/material';
|
||||||
|
|
||||||
export default function NewTabIcon(props) {
|
const NewTab: React.FC<SvgIconProps> = (props) => {
|
||||||
return (
|
return (
|
||||||
<SvgIcon sx={props.sx} color={props.color} viewBox='0 0 448 512'>
|
<SvgIcon sx={props.sx} color={props.color} viewBox='0 0 448 512'>
|
||||||
<path d='M256 64C256 46.33 270.3 32 288 32H415.1C415.1 32 415.1 32 415.1 32C420.3 32 424.5 32.86 428.2 34.43C431.1 35.98 435.5 38.27 438.6 41.3C438.6 41.35 438.6 41.4 438.7 41.44C444.9 47.66 447.1 55.78 448 63.9C448 63.94 448 63.97 448 64V192C448 209.7 433.7 224 416 224C398.3 224 384 209.7 384 192V141.3L214.6 310.6C202.1 323.1 181.9 323.1 169.4 310.6C156.9 298.1 156.9 277.9 169.4 265.4L338.7 96H288C270.3 96 256 81.67 256 64V64zM0 128C0 92.65 28.65 64 64 64H160C177.7 64 192 78.33 192 96C192 113.7 177.7 128 160 128H64V416H352V320C352 302.3 366.3 288 384 288C401.7 288 416 302.3 416 320V416C416 451.3 387.3 480 352 480H64C28.65 480 0 451.3 0 416V128z' />
|
<path d='M256 64C256 46.33 270.3 32 288 32H415.1C415.1 32 415.1 32 415.1 32C420.3 32 424.5 32.86 428.2 34.43C431.1 35.98 435.5 38.27 438.6 41.3C438.6 41.35 438.6 41.4 438.7 41.44C444.9 47.66 447.1 55.78 448 63.9C448 63.94 448 63.97 448 64V192C448 209.7 433.7 224 416 224C398.3 224 384 209.7 384 192V141.3L214.6 310.6C202.1 323.1 181.9 323.1 169.4 310.6C156.9 298.1 156.9 277.9 169.4 265.4L338.7 96H288C270.3 96 256 81.67 256 64V64zM0 128C0 92.65 28.65 64 64 64H160C177.7 64 192 78.33 192 96C192 113.7 177.7 128 160 128H64V416H352V320C352 302.3 366.3 288 384 288C401.7 288 416 302.3 416 320V416C416 451.3 387.3 480 352 480H64C28.65 480 0 451.3 0 416V128z' />
|
||||||
</SvgIcon>
|
</SvgIcon>
|
||||||
);
|
);
|
||||||
}
|
};
|
||||||
|
|
||||||
|
export default NewTab;
|
||||||
|
@ -1,11 +1,13 @@
|
|||||||
import React from 'react';
|
import React from 'react';
|
||||||
import { SvgIcon } from '@mui/material';
|
import { SvgIcon, type SvgIconProps } from '@mui/material';
|
||||||
|
|
||||||
// By SatsCoffee https://github.com/satscoffee/nostr_icons/blob/main/nostr_logo_blk.svg
|
// By SatsCoffee https://github.com/satscoffee/nostr_icons/blob/main/nostr_logo_blk.svg
|
||||||
export default function NostrIcon(props) {
|
const Nostr: React.FC<SvgIconProps> = (props) => {
|
||||||
return (
|
return (
|
||||||
<SvgIcon sx={props.sx} color={props.color} viewBox='0 0 875 875'>
|
<SvgIcon sx={props.sx} color={props.color} viewBox='0 0 875 875'>
|
||||||
<path d='m684.72,485.57c.22,12.59-11.93,51.47-38.67,81.3-26.74,29.83-56.02,20.85-58.42,20.16s-3.09-4.46-7.89-3.77-9.6,6.17-18.86,7.2-17.49,1.71-26.06-1.37c-4.46.69-5.14.71-7.2,2.24s-17.83,10.79-21.6,11.47c0,7.2-1.37,44.57,0,55.89s3.77,25.71,7.54,36c3.77,10.29,2.74,10.63,7.54,9.94s13.37.34,15.77,4.11c2.4,3.77,1.37,6.51,5.49,8.23s60.69,17.14,99.43,19.2c26.74.69,42.86,2.74,52.12,19.54,1.37,7.89,7.54,13.03,11.31,14.06s8.23,2.06,12,5.83,1.03,8.23,5.49,11.66c4.46,3.43,14.74,8.57,25.37,13.71,10.63,5.14,15.09,13.37,15.77,16.11s1.71,10.97,1.71,10.97c0,0-8.91,0-10.97-2.06s-2.74-5.83-2.74-5.83c0,0-6.17,1.03-7.54,3.43s.69,2.74-7.89.69-11.66-3.77-18.17-8.57c-6.51-4.8-16.46-17.14-25.03-16.8,4.11,8.23,5.83,8.23,10.63,10.97s8.23,5.83,8.23,5.83l-7.2,4.46s-4.46,2.06-14.74-.69-11.66-4.46-12.69-10.63,0-9.26-2.74-14.4-4.11-15.77-22.29-21.26c-18.17-5.49-66.52-21.26-100.12-24.69s-22.63-2.74-28.11-1.37-15.77,4.46-26.4-1.37c-10.63-5.83-16.8-13.71-17.49-20.23s-1.71-10.97,0-19.2,3.43-19.89,1.71-26.74-14.06-55.89-19.89-64.12c-13.03,1.03-50.74-.69-50.74-.69,0,0-2.4-.69-17.49,5.83s-36.48,13.76-46.77,19.93-14.4,9.7-16.12,13.13c.12,3-1.23,7.72-2.79,9.06s-12.48,2.42-12.48,2.42c0,0-5.85,5.86-8.25,9.97-6.86,9.6-55.2,125.14-66.52,149.83-13.54,32.57-9.77,27.43-37.71,27.43s-8.06.3-8.06.3c0,0-12.34,5.88-16.8,5.88s-18.86-2.4-26.4,0-16.46,9.26-23.31,10.29-4.95-1.34-8.38-3.74c-4-.21-14.27-.12-14.27-.12,0,0,1.74-6.51,7.91-10.88,8.23-5.83,25.37-16.11,34.63-21.26s17.49-7.89,23.31-9.26,18.51-6.17,30.51-9.94,19.54-8.23,29.83-31.54c10.29-23.31,50.4-111.43,51.43-116.23.63-2.96,3.73-6.48,4.8-15.09.66-5.35-2.49-13.04,1.71-22.63,10.97-25.03,21.6-20.23,26.4-20.23s17.14.34,26.4-1.37,15.43-2.74,24.69-7.89,11.31-8.91,11.31-8.91l-19.89-3.43s-18.51.69-25.03-4.46-15.43-15.77-15.43-15.77l-7.54-7.2,1.03,8.57s-5.14-8.91-6.51-10.29-8.57-6.51-11.31-11.31-7.54-25.03-7.54-25.03l-6.17,13.03-1.71-18.86-5.14,7.2-2.74-16.11-4.8,8.23-3.43-14.4-5.83,4.46-2.4-10.29-5.83-3.43s-14.06-9.26-16.46-9.6-4.46,3.43-4.46,3.43l1.37,12-12.2-6.27-7-11.9s2.36,4.01-9.62,7.53c-20.55,0-21.89-2.28-24.93-3.94-1.31-6.56-5.57-10.11-5.57-10.11h-20.57l-.34-6.86-7.89,3.09.69-10.29h-14.06l1.03-11.31h-8.91s3.09-9.26,25.71-22.97,25.03-16.46,46.29-17.14c21.26-.69,32.91,2.74,46.29,8.23s38.74,13.71,43.89,17.49c11.31-9.94,28.46-19.89,34.29-19.89,1.03-2.4,6.19-12.33,17.96-17.6,35.31-15.81,108.13-34,131.53-35.54,31.2-2.06,7.89-1.37,39.09,2.06,31.2,3.43,54.17,7.54,69.6,12.69,12.58,4.19,25.03,9.6,34.29,2.06,4.33-1.81,11.81-1.34,17.83-5.14,30.69-25.09,34.72-32.35,43.63-41.95s20.14-24.91,22.54-45.14,4.46-58.29-10.63-88.12-28.8-45.26-34.63-69.26c-5.83-24-8.23-61.03-6.17-73.03,2.06-12,5.14-22.29,6.86-30.51s9.94-14.74,19.89-16.46c9.94-1.71,17.83,1.37,22.29,4.8,4.46,3.43,11.65,6.28,13.37,10.29.34,1.71-1.37,6.51,8.23,8.23,9.6,1.71,16.05,4.16,16.05,4.16,0,0,15.64,4.29,3.11,7.73-12.69,2.06-20.52-.71-24.29,1.69s-7.21,10.08-9.61,11.1-7.2.34-12,4.11-9.6,6.86-12.69,14.4-5.49,15.77-3.43,26.74,8.57,31.54,14.4,43.2c5.83,11.66,20.23,40.8,24.34,47.66s15.77,29.49,16.8,53.83,1.03,44.23,0,54.86-10.84,51.65-35.53,85.94c-8.16,14.14-23.21,31.9-24.67,35.03-1.45,3.13-3.02,4.88-1.61,7.65,4.62,9.05,12.87,22.13,14.71,29.22,2.29,6.64,6.99,16.13,7.22,28.72Z' />
|
<path d='m684.72,485.57c.22,12.59-11.93,51.47-38.67,81.3-26.74,29.83-56.02,20.85-58.42,20.16s-3.09-4.46-7.89-3.77-9.6,6.17-18.86,7.2-17.49,1.71-26.06-1.37c-4.46.69-5.14.71-7.2,2.24s-17.83,10.79-21.6,11.47c0,7.2-1.37,44.57,0,55.89s3.77,25.71,7.54,36c3.77,10.29,2.74,10.63,7.54,9.94s13.37.34,15.77,4.11c2.4,3.77,1.37,6.51,5.49,8.23s60.69,17.14,99.43,19.2c26.74.69,42.86,2.74,52.12,19.54,1.37,7.89,7.54,13.03,11.31,14.06s8.23,2.06,12,5.83,1.03,8.23,5.49,11.66c4.46,3.43,14.74,8.57,25.37,13.71,10.63,5.14,15.09,13.37,15.77,16.11s1.71,10.97,1.71,10.97c0,0-8.91,0-10.97-2.06s-2.74-5.83-2.74-5.83c0,0-6.17,1.03-7.54,3.43s.69,2.74-7.89.69-11.66-3.77-18.17-8.57c-6.51-4.8-16.46-17.14-25.03-16.8,4.11,8.23,5.83,8.23,10.63,10.97s8.23,5.83,8.23,5.83l-7.2,4.46s-4.46,2.06-14.74-.69-11.66-4.46-12.69-10.63,0-9.26-2.74-14.4-4.11-15.77-22.29-21.26c-18.17-5.49-66.52-21.26-100.12-24.69s-22.63-2.74-28.11-1.37-15.77,4.46-26.4-1.37c-10.63-5.83-16.8-13.71-17.49-20.23s-1.71-10.97,0-19.2,3.43-19.89,1.71-26.74-14.06-55.89-19.89-64.12c-13.03,1.03-50.74-.69-50.74-.69,0,0-2.4-.69-17.49,5.83s-36.48,13.76-46.77,19.93-14.4,9.7-16.12,13.13c.12,3-1.23,7.72-2.79,9.06s-12.48,2.42-12.48,2.42c0,0-5.85,5.86-8.25,9.97-6.86,9.6-55.2,125.14-66.52,149.83-13.54,32.57-9.77,27.43-37.71,27.43s-8.06.3-8.06.3c0,0-12.34,5.88-16.8,5.88s-18.86-2.4-26.4,0-16.46,9.26-23.31,10.29-4.95-1.34-8.38-3.74c-4-.21-14.27-.12-14.27-.12,0,0,1.74-6.51,7.91-10.88,8.23-5.83,25.37-16.11,34.63-21.26s17.49-7.89,23.31-9.26,18.51-6.17,30.51-9.94,19.54-8.23,29.83-31.54c10.29-23.31,50.4-111.43,51.43-116.23.63-2.96,3.73-6.48,4.8-15.09.66-5.35-2.49-13.04,1.71-22.63,10.97-25.03,21.6-20.23,26.4-20.23s17.14.34,26.4-1.37,15.43-2.74,24.69-7.89,11.31-8.91,11.31-8.91l-19.89-3.43s-18.51.69-25.03-4.46-15.43-15.77-15.43-15.77l-7.54-7.2,1.03,8.57s-5.14-8.91-6.51-10.29-8.57-6.51-11.31-11.31-7.54-25.03-7.54-25.03l-6.17,13.03-1.71-18.86-5.14,7.2-2.74-16.11-4.8,8.23-3.43-14.4-5.83,4.46-2.4-10.29-5.83-3.43s-14.06-9.26-16.46-9.6-4.46,3.43-4.46,3.43l1.37,12-12.2-6.27-7-11.9s2.36,4.01-9.62,7.53c-20.55,0-21.89-2.28-24.93-3.94-1.31-6.56-5.57-10.11-5.57-10.11h-20.57l-.34-6.86-7.89,3.09.69-10.29h-14.06l1.03-11.31h-8.91s3.09-9.26,25.71-22.97,25.03-16.46,46.29-17.14c21.26-.69,32.91,2.74,46.29,8.23s38.74,13.71,43.89,17.49c11.31-9.94,28.46-19.89,34.29-19.89,1.03-2.4,6.19-12.33,17.96-17.6,35.31-15.81,108.13-34,131.53-35.54,31.2-2.06,7.89-1.37,39.09,2.06,31.2,3.43,54.17,7.54,69.6,12.69,12.58,4.19,25.03,9.6,34.29,2.06,4.33-1.81,11.81-1.34,17.83-5.14,30.69-25.09,34.72-32.35,43.63-41.95s20.14-24.91,22.54-45.14,4.46-58.29-10.63-88.12-28.8-45.26-34.63-69.26c-5.83-24-8.23-61.03-6.17-73.03,2.06-12,5.14-22.29,6.86-30.51s9.94-14.74,19.89-16.46c9.94-1.71,17.83,1.37,22.29,4.8,4.46,3.43,11.65,6.28,13.37,10.29.34,1.71-1.37,6.51,8.23,8.23,9.6,1.71,16.05,4.16,16.05,4.16,0,0,15.64,4.29,3.11,7.73-12.69,2.06-20.52-.71-24.29,1.69s-7.21,10.08-9.61,11.1-7.2.34-12,4.11-9.6,6.86-12.69,14.4-5.49,15.77-3.43,26.74,8.57,31.54,14.4,43.2c5.83,11.66,20.23,40.8,24.34,47.66s15.77,29.49,16.8,53.83,1.03,44.23,0,54.86-10.84,51.65-35.53,85.94c-8.16,14.14-23.21,31.9-24.67,35.03-1.45,3.13-3.02,4.88-1.61,7.65,4.62,9.05,12.87,22.13,14.71,29.22,2.29,6.64,6.99,16.13,7.22,28.72Z' />
|
||||||
</SvgIcon>
|
</SvgIcon>
|
||||||
);
|
);
|
||||||
}
|
};
|
||||||
|
|
||||||
|
export default Nostr;
|
||||||
|
@ -1,7 +1,7 @@
|
|||||||
import React from 'react';
|
import React from 'react';
|
||||||
import { SvgIcon } from '@mui/material';
|
import { SvgIcon, type SvgIconProps } from '@mui/material';
|
||||||
|
|
||||||
export default function RoboSatsIcon(props) {
|
const RoboSats: React.FC<SvgIconProps> = (props) => {
|
||||||
return (
|
return (
|
||||||
<SvgIcon {...props} x='0px' y='0px' width='1000px' height='1000px' viewBox='0 0 1000 900'>
|
<SvgIcon {...props} x='0px' y='0px' width='1000px' height='1000px' viewBox='0 0 1000 900'>
|
||||||
<g>
|
<g>
|
||||||
@ -102,4 +102,6 @@ export default function RoboSatsIcon(props) {
|
|||||||
</g>
|
</g>
|
||||||
</SvgIcon>
|
</SvgIcon>
|
||||||
);
|
);
|
||||||
}
|
};
|
||||||
|
|
||||||
|
export default RoboSats;
|
||||||
|
@ -1,7 +1,7 @@
|
|||||||
import React, { Component } from 'react';
|
import React from 'react';
|
||||||
import { SvgIcon } from '@mui/material';
|
import { SvgIcon, type SvgIconProps } from '@mui/material';
|
||||||
|
|
||||||
export default function RoboSatsNoTextIcon(props) {
|
const RoboSatsNoText: React.FC<SvgIconProps> = (props) => {
|
||||||
return (
|
return (
|
||||||
<SvgIcon {...props} x='0px' y='0px' width='1000px' height='1000px' viewBox='0 0 1000 800'>
|
<SvgIcon {...props} x='0px' y='0px' width='1000px' height='1000px' viewBox='0 0 1000 800'>
|
||||||
<g>
|
<g>
|
||||||
@ -39,4 +39,6 @@ export default function RoboSatsNoTextIcon(props) {
|
|||||||
</g>
|
</g>
|
||||||
</SvgIcon>
|
</SvgIcon>
|
||||||
);
|
);
|
||||||
}
|
};
|
||||||
|
|
||||||
|
export default RoboSatsNoText;
|
||||||
|
@ -1,7 +1,7 @@
|
|||||||
import React from 'react';
|
import React from 'react';
|
||||||
import { SvgIcon } from '@mui/material';
|
import { SvgIcon, type SvgIconProps } from '@mui/material';
|
||||||
|
|
||||||
export default function RoboSatsTextIcon(props) {
|
const RoboSatsText: React.FC<SvgIconProps> = (props) => {
|
||||||
return (
|
return (
|
||||||
<SvgIcon {...props} x='0px' y='0px' width='2000px' height='500px' viewBox='0 620 2000 1'>
|
<SvgIcon {...props} x='0px' y='0px' width='2000px' height='500px' viewBox='0 620 2000 1'>
|
||||||
<g>
|
<g>
|
||||||
@ -95,4 +95,6 @@ export default function RoboSatsTextIcon(props) {
|
|||||||
</g>
|
</g>
|
||||||
</SvgIcon>
|
</SvgIcon>
|
||||||
);
|
);
|
||||||
}
|
};
|
||||||
|
|
||||||
|
export default RoboSatsText;
|
||||||
|
@ -1,7 +1,7 @@
|
|||||||
import React from 'react';
|
import React from 'react';
|
||||||
import { SvgIcon } from '@mui/material';
|
import { SvgIcon, type SvgIconProps } from '@mui/material';
|
||||||
|
|
||||||
export default function SellSatsIcon(props) {
|
const SellSats: React.FC<SvgIconProps> = (props) => {
|
||||||
return (
|
return (
|
||||||
<SvgIcon sx={props.sx} color={props.color} x='0px' y='0px' viewBox='0 0 300 300'>
|
<SvgIcon sx={props.sx} color={props.color} x='0px' y='0px' viewBox='0 0 300 300'>
|
||||||
<g>
|
<g>
|
||||||
@ -135,4 +135,6 @@ export default function SellSatsIcon(props) {
|
|||||||
</g>
|
</g>
|
||||||
</SvgIcon>
|
</SvgIcon>
|
||||||
);
|
);
|
||||||
}
|
};
|
||||||
|
|
||||||
|
export default SellSats;
|
||||||
|
@ -1,7 +1,7 @@
|
|||||||
import React from 'react';
|
import React from 'react';
|
||||||
import { SvgIcon } from '@mui/material';
|
import { SvgIcon, type SvgIconProps } from '@mui/material';
|
||||||
|
|
||||||
export default function SellSatsCheckedIcon(props) {
|
const SellSatsChecked: React.FC<SvgIconProps> = (props) => {
|
||||||
return (
|
return (
|
||||||
<SvgIcon sx={props.sx} color={props.color} x='0px' y='0px' viewBox='0 0 300 300'>
|
<SvgIcon sx={props.sx} color={props.color} x='0px' y='0px' viewBox='0 0 300 300'>
|
||||||
<g>
|
<g>
|
||||||
@ -79,4 +79,6 @@ export default function SellSatsCheckedIcon(props) {
|
|||||||
</g>
|
</g>
|
||||||
</SvgIcon>
|
</SvgIcon>
|
||||||
);
|
);
|
||||||
}
|
};
|
||||||
|
|
||||||
|
export default SellSatsChecked;
|
||||||
|
@ -1,7 +1,7 @@
|
|||||||
import React from 'react';
|
import React from 'react';
|
||||||
import { SvgIcon } from '@mui/material';
|
import { SvgIcon, type SvgIconProps } from '@mui/material';
|
||||||
|
|
||||||
export default function SendReceiveIcon(props) {
|
const SendReceive: React.FC<SvgIconProps> = (props) => {
|
||||||
return (
|
return (
|
||||||
<SvgIcon sx={props.sx} color={props.color} x='0px' y='0px' viewBox='0 0 300 300'>
|
<SvgIcon sx={props.sx} color={props.color} x='0px' y='0px' viewBox='0 0 300 300'>
|
||||||
<g>
|
<g>
|
||||||
@ -20,4 +20,6 @@ export default function SendReceiveIcon(props) {
|
|||||||
</g>
|
</g>
|
||||||
</SvgIcon>
|
</SvgIcon>
|
||||||
);
|
);
|
||||||
}
|
};
|
||||||
|
|
||||||
|
export default SendReceive;
|
||||||
|
@ -1,7 +1,7 @@
|
|||||||
import React from 'react';
|
import React from 'react';
|
||||||
import { SvgIcon } from '@mui/material';
|
import { SvgIcon, type SvgIconProps } from '@mui/material';
|
||||||
|
|
||||||
export default function SimplexIcon(props) {
|
const Simplex: React.FC<SvgIconProps> = (props) => {
|
||||||
return (
|
return (
|
||||||
<SvgIcon sx={props.sx} color={props.color} x='0px' y='0px' viewBox='0 0 1080 1080'>
|
<SvgIcon sx={props.sx} color={props.color} x='0px' y='0px' viewBox='0 0 1080 1080'>
|
||||||
<g transform='matrix(4.68 0 0 4.68 668.81 540.67)'>
|
<g transform='matrix(4.68 0 0 4.68 668.81 540.67)'>
|
||||||
@ -18,4 +18,6 @@ export default function SimplexIcon(props) {
|
|||||||
</g>
|
</g>
|
||||||
</SvgIcon>
|
</SvgIcon>
|
||||||
);
|
);
|
||||||
}
|
};
|
||||||
|
|
||||||
|
export default Simplex;
|
||||||
|
@ -1,10 +1,12 @@
|
|||||||
import React from 'react';
|
import React from 'react';
|
||||||
import { SvgIcon } from '@mui/material';
|
import { SvgIcon, type SvgIconProps } from '@mui/material';
|
||||||
|
|
||||||
export default function TorIcon(props) {
|
const Tor: React.FC<SvgIconProps> = (props) => {
|
||||||
return (
|
return (
|
||||||
<SvgIcon sx={props.sx} color={props.color} x='0px' y='0px' viewBox='0 0 180 180'>
|
<SvgIcon sx={props.sx} color={props.color} x='0px' y='0px' viewBox='0 0 180 180'>
|
||||||
<path d='M90.1846205,163.631147 L90.1846205,152.721073 C124.743583,152.621278 152.726063,124.581416 152.726063,89.9975051 C152.726063,55.4160892 124.743583,27.3762266 90.1846205,27.2764318 L90.1846205,16.366358 C130.768698,16.4686478 163.633642,49.3909741 163.633642,89.9975051 C163.633642,130.606531 130.768698,163.531352 90.1846205,163.631147 Z M90.1846205,125.444642 C109.677053,125.342352 125.454621,109.517381 125.454621,89.9975051 C125.454621,70.4801242 109.677053,54.6551533 90.1846205,54.5528636 L90.1846205,43.6452847 C115.704663,43.7450796 136.364695,64.4550091 136.364695,89.9975051 C136.364695,115.542496 115.704663,136.252426 90.1846205,136.35222 L90.1846205,125.444642 Z M90.1846205,70.9167267 C100.640628,71.0165216 109.090758,79.5165493 109.090758,89.9975051 C109.090758,100.480956 100.640628,108.980984 90.1846205,109.080778 L90.1846205,70.9167267 Z M0,89.9975051 C0,139.705328 40.2921772,180 90,180 C139.705328,180 180,139.705328 180,89.9975051 C180,40.2921772 139.705328,0 90,0 C40.2921772,0 0,40.2921772 0,89.9975051 Z'></path>
|
<path d='M90.1846205,163.631147 L90.1846205,152.721073 C124.743583,152.621278 152.726063,124.581416 152.726063,89.9975051 C152.726063,55.4160892 124.743583,27.3762266 90.1846205,27.2764318 L90.1846205,16.366358 C130.768698,16.4686478 163.633642,49.3909741 163.633642,89.9975051 C163.633642,130.606531 130.768698,163.531352 90.1846205,163.631147 Z M90.1846205,125.444642 C109.677053,125.342352 125.454621,109.517381 125.454621,89.9975051 C125.454621,70.4801242 109.677053,54.6551533 90.1846205,54.5528636 L90.1846205,43.6452847 C115.704663,43.7450796 136.364695,64.4550091 136.364695,89.9975051 C136.364695,115.542496 115.704663,136.252426 90.1846205,136.35222 L90.1846205,125.444642 Z M90.1846205,70.9167267 C100.640628,71.0165216 109.090758,79.5165493 109.090758,89.9975051 C109.090758,100.480956 100.640628,108.980984 90.1846205,109.080778 L90.1846205,70.9167267 Z M0,89.9975051 C0,139.705328 40.2921772,180 90,180 C139.705328,180 180,139.705328 180,89.9975051 C180,40.2921772 139.705328,0 90,0 C40.2921772,0 0,40.2921772 0,89.9975051 Z'></path>
|
||||||
</SvgIcon>
|
</SvgIcon>
|
||||||
);
|
);
|
||||||
}
|
};
|
||||||
|
|
||||||
|
export default Tor;
|
||||||
|
@ -1,10 +1,12 @@
|
|||||||
import React from 'react';
|
import React from 'react';
|
||||||
import { SvgIcon } from '@mui/material';
|
import { SvgIcon, type SvgIconProps } from '@mui/material';
|
||||||
|
|
||||||
export default function UserNinjaIcon(props) {
|
const UserNinja: React.FC<SvgIconProps> = (props) => {
|
||||||
return (
|
return (
|
||||||
<SvgIcon {...props} x='0px' y='0px' viewBox='0 0 512 512'>
|
<SvgIcon {...props} x='0px' y='0px' viewBox='0 0 512 512'>
|
||||||
<path d='M64 192c27.25 0 51.75-11.5 69.25-29.75c15 54 64 93.75 122.8 93.75c70.75 0 127.1-57.25 127.1-128s-57.25-128-127.1-128c-50.38 0-93.63 29.38-114.5 71.75C124.1 47.75 96 32 64 32c0 33.37 17.12 62.75 43.13 80C81.13 129.3 64 158.6 64 192zM208 96h95.1C321.7 96 336 110.3 336 128h-160C176 110.3 190.3 96 208 96zM337.8 306.9L256 416L174.2 306.9C93.36 321.6 32 392.2 32 477.3c0 19.14 15.52 34.67 34.66 34.67H445.3c19.14 0 34.66-15.52 34.66-34.67C480 392.2 418.6 321.6 337.8 306.9z' />
|
<path d='M64 192c27.25 0 51.75-11.5 69.25-29.75c15 54 64 93.75 122.8 93.75c70.75 0 127.1-57.25 127.1-128s-57.25-128-127.1-128c-50.38 0-93.63 29.38-114.5 71.75C124.1 47.75 96 32 64 32c0 33.37 17.12 62.75 43.13 80C81.13 129.3 64 158.6 64 192zM208 96h95.1C321.7 96 336 110.3 336 128h-160C176 110.3 190.3 96 208 96zM337.8 306.9L256 416L174.2 306.9C93.36 321.6 32 392.2 32 477.3c0 19.14 15.52 34.67 34.66 34.67H445.3c19.14 0 34.66-15.52 34.66-34.67C480 392.2 418.6 321.6 337.8 306.9z' />
|
||||||
</SvgIcon>
|
</SvgIcon>
|
||||||
);
|
);
|
||||||
}
|
};
|
||||||
|
|
||||||
|
export default UserNinja;
|
||||||
|
@ -1,6 +1,7 @@
|
|||||||
export { default as AmbossIcon } from './Amboss';
|
export { default as AmbossIcon } from './Amboss';
|
||||||
export { default as BitcoinIcon } from './Bitcoin';
|
export { default as BitcoinIcon } from './Bitcoin';
|
||||||
export { default as BitcoinSignIcon } from './BitcoinSign';
|
export { default as BitcoinSignIcon } from './BitcoinSign';
|
||||||
|
export { default as NostrIcon } from './Nostr';
|
||||||
export { default as BuySatsIcon } from './BuySats';
|
export { default as BuySatsIcon } from './BuySats';
|
||||||
export { default as BuySatsCheckedIcon } from './BuySatsChecked';
|
export { default as BuySatsCheckedIcon } from './BuySatsChecked';
|
||||||
export { default as EarthIcon } from './Earth';
|
export { default as EarthIcon } from './Earth';
|
||||||
@ -16,7 +17,13 @@ export { default as ExportIcon } from './Export';
|
|||||||
export { default as UserNinjaIcon } from './UserNinja';
|
export { default as UserNinjaIcon } from './UserNinja';
|
||||||
export { default as TorIcon } from './Tor';
|
export { default as TorIcon } from './Tor';
|
||||||
export { default as SimplexIcon } from './Simplex';
|
export { default as SimplexIcon } from './Simplex';
|
||||||
export { default as NostrIcon } from './Nostr';
|
|
||||||
|
// Badges
|
||||||
|
export { default as BadgeFounder } from './BadgeFounder';
|
||||||
|
export { default as BadgeDevFund } from './BadgeDevFund';
|
||||||
|
export { default as BadgePrivacy } from './BadgePrivacy';
|
||||||
|
export { default as BadgeLimits } from './BadgeLimits';
|
||||||
|
export { default as BadgeLoved } from './BadgeLoved';
|
||||||
|
|
||||||
// Flags with props
|
// Flags with props
|
||||||
export { default as FlagWithProps } from './WorldFlags';
|
export { default as FlagWithProps } from './WorldFlags';
|
||||||
|
@ -16,7 +16,7 @@ import RangeSlider from './RangeSlider';
|
|||||||
import currencyDict from '../../../static/assets/currencies.json';
|
import currencyDict from '../../../static/assets/currencies.json';
|
||||||
import { pn } from '../../utils';
|
import { pn } from '../../utils';
|
||||||
|
|
||||||
const RangeThumbComponent = function (props: object) {
|
const RangeThumbComponent: React.FC<React.PropsWithChildren> = (props) => {
|
||||||
const { children, ...other } = props;
|
const { children, ...other } = props;
|
||||||
return (
|
return (
|
||||||
<SliderThumb {...other}>
|
<SliderThumb {...other}>
|
||||||
@ -34,16 +34,20 @@ interface AmountRangeProps {
|
|||||||
type: number;
|
type: number;
|
||||||
currency: number;
|
currency: number;
|
||||||
handleRangeAmountChange: (e: any, activeThumb: any) => void;
|
handleRangeAmountChange: (e: any, activeThumb: any) => void;
|
||||||
handleMaxAmountChange: () => void;
|
handleMaxAmountChange: (
|
||||||
handleMinAmountChange: () => void;
|
e: React.ChangeEventHandler<HTMLInputElement | HTMLTextAreaElement>,
|
||||||
handleCurrencyChange: () => void;
|
) => void;
|
||||||
|
handleMinAmountChange: (
|
||||||
|
e: React.ChangeEventHandler<HTMLInputElement | HTMLTextAreaElement>,
|
||||||
|
) => void;
|
||||||
|
handleCurrencyChange: (newCurrency: number) => void;
|
||||||
maxAmountError: boolean;
|
maxAmountError: boolean;
|
||||||
minAmountError: boolean;
|
minAmountError: boolean;
|
||||||
currencyCode: string;
|
currencyCode: string;
|
||||||
amountLimits: number[];
|
amountLimits: number[];
|
||||||
}
|
}
|
||||||
|
|
||||||
function AmountRange({
|
const AmountRange: React.FC<AmountRangeProps> = ({
|
||||||
minAmount,
|
minAmount,
|
||||||
handleRangeAmountChange,
|
handleRangeAmountChange,
|
||||||
currency,
|
currency,
|
||||||
@ -55,7 +59,7 @@ function AmountRange({
|
|||||||
maxAmountError,
|
maxAmountError,
|
||||||
handleMinAmountChange,
|
handleMinAmountChange,
|
||||||
handleMaxAmountChange,
|
handleMaxAmountChange,
|
||||||
}: AmountRangeProps) {
|
}) => {
|
||||||
const theme = useTheme();
|
const theme = useTheme();
|
||||||
const { t } = useTranslation();
|
const { t } = useTranslation();
|
||||||
|
|
||||||
@ -130,10 +134,10 @@ function AmountRange({
|
|||||||
inputProps={{
|
inputProps={{
|
||||||
style: { textAlign: 'center' },
|
style: { textAlign: 'center' },
|
||||||
}}
|
}}
|
||||||
value={currency == 0 ? 1 : currency}
|
value={currency === 0 ? 1 : currency}
|
||||||
renderValue={() => currencyCode}
|
renderValue={() => currencyCode}
|
||||||
onChange={(e) => {
|
onChange={(e) => {
|
||||||
handleCurrencyChange(e.target.value);
|
handleCurrencyChange(Number(e.target.value));
|
||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
{Object.entries(currencyDict).map(([key, value]) => (
|
{Object.entries(currencyDict).map(([key, value]) => (
|
||||||
@ -188,6 +192,6 @@ function AmountRange({
|
|||||||
</Box>
|
</Box>
|
||||||
</Grid>
|
</Grid>
|
||||||
);
|
);
|
||||||
}
|
};
|
||||||
|
|
||||||
export default AmountRange;
|
export default AmountRange;
|
||||||
|
@ -2,7 +2,16 @@ import React, { useState } from 'react';
|
|||||||
import { useTranslation } from 'react-i18next';
|
import { useTranslation } from 'react-i18next';
|
||||||
import useAutocomplete from '@mui/base/useAutocomplete';
|
import useAutocomplete from '@mui/base/useAutocomplete';
|
||||||
import { styled } from '@mui/material/styles';
|
import { styled } from '@mui/material/styles';
|
||||||
import { Button, Fade, Tooltip, Typography, Grow, useTheme } from '@mui/material';
|
import {
|
||||||
|
Button,
|
||||||
|
Fade,
|
||||||
|
Tooltip,
|
||||||
|
Typography,
|
||||||
|
Grow,
|
||||||
|
useTheme,
|
||||||
|
type SxProps,
|
||||||
|
type Theme,
|
||||||
|
} from '@mui/material';
|
||||||
import { fiatMethods, swapMethods, PaymentIcon } from '../PaymentMethods';
|
import { fiatMethods, swapMethods, PaymentIcon } from '../PaymentMethods';
|
||||||
|
|
||||||
// Icons
|
// Icons
|
||||||
@ -20,12 +29,18 @@ const Root = styled('div')(
|
|||||||
const Label = styled('label')(
|
const Label = styled('label')(
|
||||||
({ theme, error, sx }) => `
|
({ theme, error, sx }) => `
|
||||||
color: ${
|
color: ${
|
||||||
theme.palette.mode === 'dark' ? (error ? '#f44336' : '#cfcfcf') : error ? '#dd0000' : '#717171'
|
theme.palette.mode === 'dark'
|
||||||
|
? error === true
|
||||||
|
? '#f44336'
|
||||||
|
: '#cfcfcf'
|
||||||
|
: error === true
|
||||||
|
? '#dd0000'
|
||||||
|
: '#717171'
|
||||||
};
|
};
|
||||||
pointer-events: none;
|
pointer-events: none;
|
||||||
position: relative;
|
position: relative;
|
||||||
left: 1em;
|
left: 1em;
|
||||||
top: ${sx.top};
|
top: ${String(sx.top) ?? '0.72em'};
|
||||||
maxHeight: 0em;
|
maxHeight: 0em;
|
||||||
height: 0em;
|
height: 0em;
|
||||||
white-space: no-wrap;
|
white-space: no-wrap;
|
||||||
@ -35,14 +50,20 @@ const Label = styled('label')(
|
|||||||
|
|
||||||
const InputWrapper = styled('div')(
|
const InputWrapper = styled('div')(
|
||||||
({ theme, error, sx }) => `
|
({ theme, error, sx }) => `
|
||||||
min-height: ${sx.minHeight};
|
min-height: ${String(sx.minHeight)};
|
||||||
max-height: ${sx.maxHeight};
|
max-height: ${String(sx.maxHeight)};
|
||||||
border: 1px solid ${
|
border: 1px solid ${
|
||||||
theme.palette.mode === 'dark' ? (error ? '#f44336' : '#434343') : error ? '#dd0000' : '#c4c4c4'
|
theme.palette.mode === 'dark'
|
||||||
|
? error === ''
|
||||||
|
? '#f44336'
|
||||||
|
: '#434343'
|
||||||
|
: error === ''
|
||||||
|
? '#dd0000'
|
||||||
|
: '#c4c4c4'
|
||||||
};
|
};
|
||||||
background-color: ${theme.palette.mode === 'dark' ? '#141414' : '#fff'};
|
background-color: ${theme.palette.mode === 'dark' ? '#141414' : '#fff'};
|
||||||
border-radius: 4px;
|
border-radius: 4px;
|
||||||
border-color: ${sx.borderColor ? `border-color ${sx.borderColor}` : ''}
|
border-color: ${sx.borderColor !== undefined ? `border-color ${String(sx.borderColor)}` : ''}
|
||||||
padding: 1px;
|
padding: 1px;
|
||||||
display: flex;
|
display: flex;
|
||||||
flex-wrap: wrap;
|
flex-wrap: wrap;
|
||||||
@ -52,10 +73,10 @@ const InputWrapper = styled('div')(
|
|||||||
&:hover {
|
&:hover {
|
||||||
border-color: ${
|
border-color: ${
|
||||||
theme.palette.mode === 'dark'
|
theme.palette.mode === 'dark'
|
||||||
? error
|
? error === true
|
||||||
? '#f44336'
|
? '#f44336'
|
||||||
: sx.hoverBorderColor
|
: String(sx.hoverBorderColor)
|
||||||
: error
|
: error === true
|
||||||
? '#dd0000'
|
? '#dd0000'
|
||||||
: '#2f2f2f'
|
: '#2f2f2f'
|
||||||
};
|
};
|
||||||
@ -64,10 +85,10 @@ const InputWrapper = styled('div')(
|
|||||||
&.focused {
|
&.focused {
|
||||||
border: 2px solid ${
|
border: 2px solid ${
|
||||||
theme.palette.mode === 'dark'
|
theme.palette.mode === 'dark'
|
||||||
? error
|
? error === true
|
||||||
? '#f44336'
|
? '#f44336'
|
||||||
: '#90caf9'
|
: '#90caf9'
|
||||||
: error
|
: error === true
|
||||||
? '#dd0000'
|
? '#dd0000'
|
||||||
: '#1976d2'
|
: '#1976d2'
|
||||||
};
|
};
|
||||||
@ -97,7 +118,8 @@ interface TagProps {
|
|||||||
onDelete: () => void;
|
onDelete: () => void;
|
||||||
onClick: () => void;
|
onClick: () => void;
|
||||||
}
|
}
|
||||||
const Tag = ({ label, icon, onDelete, onClick, ...other }: TagProps) => {
|
|
||||||
|
const Tag: React.FC<TagProps> = ({ label, icon, onDelete, onClick, ...other }) => {
|
||||||
const theme = useTheme();
|
const theme = useTheme();
|
||||||
const iconSize = 1.5 * theme.typography.fontSize;
|
const iconSize = 1.5 * theme.typography.fontSize;
|
||||||
return (
|
return (
|
||||||
@ -117,7 +139,7 @@ const StyledTag = styled(Tag)(
|
|||||||
({ theme, sx }) => `
|
({ theme, sx }) => `
|
||||||
display: flex;
|
display: flex;
|
||||||
align-items: center;
|
align-items: center;
|
||||||
height: ${sx.height};
|
height: ${String(sx?.height ?? '2.1em')};
|
||||||
margin: 2px;
|
margin: 2px;
|
||||||
line-height: 1.5em;
|
line-height: 1.5em;
|
||||||
background-color: ${theme.palette.mode === 'dark' ? 'rgba(255,255,255,0.08)' : '#fafafa'};
|
background-color: ${theme.palette.mode === 'dark' ? 'rgba(255,255,255,0.08)' : '#fafafa'};
|
||||||
@ -163,7 +185,7 @@ const ListHeader = styled('span')(
|
|||||||
|
|
||||||
const Listbox = styled('ul')(
|
const Listbox = styled('ul')(
|
||||||
({ theme, sx }) => `
|
({ theme, sx }) => `
|
||||||
width: ${sx != null ? sx.width : '15.6em'};
|
width: ${String(sx?.width ?? '15.6em')};
|
||||||
margin: 2px 0 0;
|
margin: 2px 0 0;
|
||||||
padding: 0;
|
padding: 0;
|
||||||
position: absolute;
|
position: absolute;
|
||||||
@ -209,7 +231,23 @@ const Listbox = styled('ul')(
|
|||||||
`,
|
`,
|
||||||
);
|
);
|
||||||
|
|
||||||
export default function AutocompletePayments(props) {
|
interface AutocompletePaymentsProps {
|
||||||
|
value: string;
|
||||||
|
optionsType: 'fiat' | 'swap';
|
||||||
|
onAutocompleteChange: (value: string) => void;
|
||||||
|
tooltipTitle: string;
|
||||||
|
labelProps: any;
|
||||||
|
tagProps: any;
|
||||||
|
listBoxProps: any;
|
||||||
|
error: string;
|
||||||
|
label: string;
|
||||||
|
sx: SxProps<Theme>;
|
||||||
|
addNewButtonText: string;
|
||||||
|
isFilter: boolean;
|
||||||
|
listHeaderText: string;
|
||||||
|
}
|
||||||
|
|
||||||
|
const AutocompletePayments: React.FC<AutocompletePaymentsProps> = (props) => {
|
||||||
const { t } = useTranslation();
|
const { t } = useTranslation();
|
||||||
const {
|
const {
|
||||||
getRootProps,
|
getRootProps,
|
||||||
@ -220,19 +258,21 @@ export default function AutocompletePayments(props) {
|
|||||||
getOptionProps,
|
getOptionProps,
|
||||||
groupedOptions,
|
groupedOptions,
|
||||||
value,
|
value,
|
||||||
focused = 'true',
|
focused = true,
|
||||||
setAnchorEl,
|
setAnchorEl,
|
||||||
} = useAutocomplete({
|
} = useAutocomplete({
|
||||||
fullWidth: true,
|
fullWidth: true,
|
||||||
id: 'payment-methods',
|
id: 'payment-methods',
|
||||||
multiple: true,
|
multiple: true,
|
||||||
value: props.value,
|
value: props.value,
|
||||||
options: props.optionsType == 'fiat' ? fiatMethods : swapMethods,
|
options: props.optionsType === 'fiat' ? fiatMethods : swapMethods,
|
||||||
getOptionLabel: (option) => option.name,
|
getOptionLabel: (option) => option.name,
|
||||||
onInputChange: (e) => {
|
onInputChange: (e) => {
|
||||||
setVal(e ? (e.target.value ? e.target.value : '') : '');
|
setVal(e.target.value ?? '');
|
||||||
|
},
|
||||||
|
onChange: (event, value) => {
|
||||||
|
props.onAutocompleteChange(value);
|
||||||
},
|
},
|
||||||
onChange: (event, value) => props.onAutocompleteChange(value),
|
|
||||||
onClose: () => {
|
onClose: () => {
|
||||||
setVal(() => '');
|
setVal(() => '');
|
||||||
},
|
},
|
||||||
@ -243,36 +283,35 @@ export default function AutocompletePayments(props) {
|
|||||||
const theme = useTheme();
|
const theme = useTheme();
|
||||||
const iconSize = 1.5 * theme.typography.fontSize;
|
const iconSize = 1.5 * theme.typography.fontSize;
|
||||||
|
|
||||||
function handleAddNew(inputProps) {
|
function handleAddNew(inputProps: any): void {
|
||||||
fiatMethods.push({ name: inputProps.value, icon: 'custom' });
|
fiatMethods.push({ name: inputProps.value, icon: 'custom' });
|
||||||
const a = value.push({ name: inputProps.value, icon: 'custom' });
|
const a = value.push({ name: inputProps.value, icon: 'custom' });
|
||||||
setVal(() => '');
|
setVal(() => '');
|
||||||
|
|
||||||
if (a || a == null) {
|
if (a !== undefined) {
|
||||||
props.onAutocompleteChange(value);
|
props.onAutocompleteChange(value);
|
||||||
}
|
}
|
||||||
return false;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Root>
|
<Root>
|
||||||
<Tooltip
|
<Tooltip
|
||||||
placement='top'
|
placement='top'
|
||||||
enterTouchDelay={props.tooltipTitle == '' ? 99999 : 300}
|
enterTouchDelay={props.tooltipTitle === '' ? 99999 : 300}
|
||||||
enterDelay={props.tooltipTitle == '' ? 99999 : 700}
|
enterDelay={props.tooltipTitle === '' ? 99999 : 700}
|
||||||
enterNextDelay={2000}
|
enterNextDelay={2000}
|
||||||
title={props.tooltipTitle}
|
title={props.tooltipTitle}
|
||||||
>
|
>
|
||||||
<div {...getRootProps()}>
|
<div {...getRootProps()}>
|
||||||
<Fade
|
<Fade
|
||||||
appear={false}
|
appear={false}
|
||||||
in={fewerOptions.length == 0 && value.length == 0 && val.length == 0}
|
in={fewerOptions.length === 0 && value.length === 0 && val.length === 0}
|
||||||
>
|
>
|
||||||
<div style={{ height: 0, display: 'flex', alignItems: 'flex-start' }}>
|
<div style={{ height: 0, display: 'flex', alignItems: 'flex-start' }}>
|
||||||
<Label
|
<Label
|
||||||
{...getInputLabelProps()}
|
{...getInputLabelProps()}
|
||||||
sx={{ top: '0.72em', ...(props.labelProps ? props.labelProps.sx : {}) }}
|
sx={{ top: '0.72em', ...(props.labelProps?.sx ?? {}) }}
|
||||||
error={props.error ? 'error' : null}
|
error={Boolean(props.error)}
|
||||||
>
|
>
|
||||||
{props.label}
|
{props.label}
|
||||||
</Label>
|
</Label>
|
||||||
@ -280,7 +319,7 @@ export default function AutocompletePayments(props) {
|
|||||||
</Fade>
|
</Fade>
|
||||||
<InputWrapper
|
<InputWrapper
|
||||||
ref={setAnchorEl}
|
ref={setAnchorEl}
|
||||||
error={props.error ? 'error' : null}
|
error={Boolean(props.error)}
|
||||||
className={focused ? 'focused' : ''}
|
className={focused ? 'focused' : ''}
|
||||||
sx={{
|
sx={{
|
||||||
minHeight: '2.9em',
|
minHeight: '2.9em',
|
||||||
@ -291,10 +330,11 @@ export default function AutocompletePayments(props) {
|
|||||||
>
|
>
|
||||||
{value.map((option, index) => (
|
{value.map((option, index) => (
|
||||||
<StyledTag
|
<StyledTag
|
||||||
|
key={index}
|
||||||
label={t(option.name)}
|
label={t(option.name)}
|
||||||
icon={option.icon}
|
icon={option.icon}
|
||||||
sx={{ height: '2.1em', ...(props.tagProps ? props.tagProps.sx : {}) }}
|
|
||||||
onClick={props.onClick}
|
onClick={props.onClick}
|
||||||
|
sx={{ height: '2.1em', ...(props.tagProps ?? {}) }}
|
||||||
{...getTagProps({ index })}
|
{...getTagProps({ index })}
|
||||||
/>
|
/>
|
||||||
))}
|
))}
|
||||||
@ -303,7 +343,7 @@ export default function AutocompletePayments(props) {
|
|||||||
</div>
|
</div>
|
||||||
</Tooltip>
|
</Tooltip>
|
||||||
<Grow in={fewerOptions.length > 0}>
|
<Grow in={fewerOptions.length > 0}>
|
||||||
<Listbox sx={props.listBoxProps ? props.listBoxProps.sx : undefined} {...getListboxProps()}>
|
<Listbox sx={props.listBoxProps?.sx ?? undefined} {...getListboxProps()}>
|
||||||
{!props.isFilter ? (
|
{!props.isFilter ? (
|
||||||
<div
|
<div
|
||||||
style={{
|
style={{
|
||||||
@ -341,7 +381,13 @@ export default function AutocompletePayments(props) {
|
|||||||
))}
|
))}
|
||||||
{val != null || !props.isFilter ? (
|
{val != null || !props.isFilter ? (
|
||||||
val.length > 2 ? (
|
val.length > 2 ? (
|
||||||
<Button size='small' fullWidth={true} onClick={() => handleAddNew(getInputProps())}>
|
<Button
|
||||||
|
size='small'
|
||||||
|
fullWidth={true}
|
||||||
|
onClick={() => {
|
||||||
|
handleAddNew(getInputProps());
|
||||||
|
}}
|
||||||
|
>
|
||||||
<DashboardCustomizeIcon sx={{ width: '1em', height: '1em' }} />
|
<DashboardCustomizeIcon sx={{ width: '1em', height: '1em' }} />
|
||||||
{props.addNewButtonText}
|
{props.addNewButtonText}
|
||||||
</Button>
|
</Button>
|
||||||
@ -353,7 +399,12 @@ export default function AutocompletePayments(props) {
|
|||||||
{/* Here goes what happens if there is no fewerOptions */}
|
{/* Here goes what happens if there is no fewerOptions */}
|
||||||
<Grow in={getInputProps().value.length > 0 && !props.isFilter && fewerOptions.length === 0}>
|
<Grow in={getInputProps().value.length > 0 && !props.isFilter && fewerOptions.length === 0}>
|
||||||
<Listbox {...getListboxProps()}>
|
<Listbox {...getListboxProps()}>
|
||||||
<Button fullWidth={true} onClick={() => handleAddNew(getInputProps())}>
|
<Button
|
||||||
|
fullWidth={true}
|
||||||
|
onClick={() => {
|
||||||
|
handleAddNew(getInputProps());
|
||||||
|
}}
|
||||||
|
>
|
||||||
<DashboardCustomizeIcon sx={{ width: '1.28em', height: '1.28em' }} />
|
<DashboardCustomizeIcon sx={{ width: '1.28em', height: '1.28em' }} />
|
||||||
{props.addNewButtonText}
|
{props.addNewButtonText}
|
||||||
</Button>
|
</Button>
|
||||||
@ -361,4 +412,6 @@ export default function AutocompletePayments(props) {
|
|||||||
</Grow>
|
</Grow>
|
||||||
</Root>
|
</Root>
|
||||||
);
|
);
|
||||||
}
|
};
|
||||||
|
|
||||||
|
export default AutocompletePayments;
|
||||||
|
@ -40,8 +40,11 @@ import { amountToString, computeSats, pn } from '../../utils';
|
|||||||
|
|
||||||
import { SelfImprovement, Lock, HourglassTop, DeleteSweep, Edit, Map } from '@mui/icons-material';
|
import { SelfImprovement, Lock, HourglassTop, DeleteSweep, Edit, Map } from '@mui/icons-material';
|
||||||
import { LoadingButton } from '@mui/lab';
|
import { LoadingButton } from '@mui/lab';
|
||||||
import { AppContext, type UseAppStoreType } from '../../contexts/AppContext';
|
|
||||||
import { fiatMethods } from '../PaymentMethods';
|
import { fiatMethods } from '../PaymentMethods';
|
||||||
|
import { AppContext, type UseAppStoreType } from '../../contexts/AppContext';
|
||||||
|
import SelectCoordinator from './SelectCoordinator';
|
||||||
|
import { FederationContext, UseFederationStoreType } from '../../contexts/FederationContext';
|
||||||
|
import { GarageContext, UseGarageStoreType } from '../../contexts/GarageContext';
|
||||||
|
|
||||||
interface MakerFormProps {
|
interface MakerFormProps {
|
||||||
disableRequest?: boolean;
|
disableRequest?: boolean;
|
||||||
@ -50,7 +53,7 @@ interface MakerFormProps {
|
|||||||
onSubmit?: () => void;
|
onSubmit?: () => void;
|
||||||
onReset?: () => void;
|
onReset?: () => void;
|
||||||
submitButtonLabel?: string;
|
submitButtonLabel?: string;
|
||||||
onOrderCreated?: (id: number) => void;
|
onOrderCreated?: (shortAlias: string, id: number) => void;
|
||||||
onClickGenerateRobot?: () => void;
|
onClickGenerateRobot?: () => void;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -64,8 +67,10 @@ const MakerForm = ({
|
|||||||
onOrderCreated = () => null,
|
onOrderCreated = () => null,
|
||||||
onClickGenerateRobot = () => null,
|
onClickGenerateRobot = () => null,
|
||||||
}: MakerFormProps): JSX.Element => {
|
}: MakerFormProps): JSX.Element => {
|
||||||
const { fav, setFav, limits, fetchLimits, info, maker, setMaker, baseUrl, robot } =
|
const { fav, setFav, settings, hostUrl, origin } = useContext<UseAppStoreType>(AppContext);
|
||||||
useContext<UseAppStoreType>(AppContext);
|
const { federation, focusedCoordinator, coordinatorUpdatedAt, federationUpdatedAt } =
|
||||||
|
useContext<UseFederationStoreType>(FederationContext);
|
||||||
|
const { maker, setMaker, garage } = useContext<UseGarageStoreType>(GarageContext);
|
||||||
|
|
||||||
const { t } = useTranslation();
|
const { t } = useTranslation();
|
||||||
const theme = useTheme();
|
const theme = useTheme();
|
||||||
@ -80,30 +85,31 @@ const MakerForm = ({
|
|||||||
const [openWorldmap, setOpenWorldmap] = useState<boolean>(false);
|
const [openWorldmap, setOpenWorldmap] = useState<boolean>(false);
|
||||||
const [submittingRequest, setSubmittingRequest] = useState<boolean>(false);
|
const [submittingRequest, setSubmittingRequest] = useState<boolean>(false);
|
||||||
const [amountRangeEnabled, setAmountRangeEnabled] = useState<boolean>(true);
|
const [amountRangeEnabled, setAmountRangeEnabled] = useState<boolean>(true);
|
||||||
|
const [limits, setLimits] = useState<LimitList>({});
|
||||||
|
|
||||||
const maxRangeAmountMultiple = 14.8;
|
const maxRangeAmountMultiple = 14.8;
|
||||||
const minRangeAmountMultiple = 1.6;
|
const minRangeAmountMultiple = 1.6;
|
||||||
const amountSafeThresholds = [1.03, 0.98];
|
const amountSafeThresholds = [1.03, 0.98];
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
setCurrencyCode(currencyDict[fav.currency == 0 ? 1 : fav.currency]);
|
setCurrencyCode(currencyDict[fav.currency === 0 ? 1 : fav.currency]);
|
||||||
if (Object.keys(limits.list).length === 0) {
|
if (focusedCoordinator) {
|
||||||
fetchLimits().then((data) => {
|
const newLimits = federation.getCoordinator(focusedCoordinator).limits;
|
||||||
updateAmountLimits(data, fav.currency, maker.premium);
|
if (Object.keys(newLimits).length !== 0) {
|
||||||
updateCurrentPrice(data, fav.currency, maker.premium);
|
updateAmountLimits(newLimits, fav.currency, maker.premium);
|
||||||
updateSatoshisLimits(data);
|
updateCurrentPrice(newLimits, fav.currency, maker.premium);
|
||||||
});
|
updateSatoshisLimits(newLimits);
|
||||||
} else {
|
setLimits(newLimits);
|
||||||
updateAmountLimits(limits.list, fav.currency, maker.premium);
|
}
|
||||||
updateCurrentPrice(limits.list, fav.currency, maker.premium);
|
|
||||||
updateSatoshisLimits(limits.list);
|
|
||||||
|
|
||||||
fetchLimits();
|
|
||||||
}
|
}
|
||||||
}, []);
|
}, [coordinatorUpdatedAt]);
|
||||||
|
|
||||||
const updateAmountLimits = function (limitList: LimitList, currency: number, premium: number) {
|
const updateAmountLimits = function (
|
||||||
const index = currency == 0 ? 1 : currency;
|
limitList: LimitList,
|
||||||
|
currency: number,
|
||||||
|
premium: number,
|
||||||
|
): void {
|
||||||
|
const index = currency === 0 ? 1 : currency;
|
||||||
let minAmountLimit: number = limitList[index].min_amount * (1 + premium / 100);
|
let minAmountLimit: number = limitList[index].min_amount * (1 + premium / 100);
|
||||||
let maxAmountLimit: number = limitList[index].max_amount * (1 + premium / 100);
|
let maxAmountLimit: number = limitList[index].max_amount * (1 + premium / 100);
|
||||||
|
|
||||||
@ -113,24 +119,28 @@ const MakerForm = ({
|
|||||||
setAmountLimits([minAmountLimit, maxAmountLimit]);
|
setAmountLimits([minAmountLimit, maxAmountLimit]);
|
||||||
};
|
};
|
||||||
|
|
||||||
const updateSatoshisLimits = function (limitList: LimitList) {
|
const updateSatoshisLimits = function (limitList: LimitList): void {
|
||||||
const minAmount: number = limitList[1000].min_amount * 100000000;
|
const minAmount: number = limitList[1000].min_amount * 100000000;
|
||||||
const maxAmount: number = limitList[1000].max_amount * 100000000;
|
const maxAmount: number = limitList[1000].max_amount * 100000000;
|
||||||
setSatoshisLimits([minAmount, maxAmount]);
|
setSatoshisLimits([minAmount, maxAmount]);
|
||||||
};
|
};
|
||||||
|
|
||||||
const updateCurrentPrice = function (limitsList: LimitList, currency: number, premium: number) {
|
const updateCurrentPrice = function (
|
||||||
const index = currency == 0 ? 1 : currency;
|
limitsList: LimitList,
|
||||||
|
currency: number,
|
||||||
|
premium: number,
|
||||||
|
): void {
|
||||||
|
const index = currency === 0 ? 1 : currency;
|
||||||
let price = '...';
|
let price = '...';
|
||||||
if (maker.isExplicit && maker.amount > 0 && maker.satoshis > 0) {
|
if (maker.isExplicit && maker.amount > 0 && maker.satoshis > 0) {
|
||||||
price = maker.amount / (maker.satoshis / 100000000);
|
price = maker.amount / (maker.satoshis / 100000000);
|
||||||
} else if (!maker.is_explicit) {
|
} else if (!maker.isExplicit) {
|
||||||
price = limitsList[index].price * (1 + premium / 100);
|
price = limitsList[index].price * (1 + premium / 100);
|
||||||
}
|
}
|
||||||
setCurrentPrice(parseFloat(Number(price).toPrecision(5)));
|
setCurrentPrice(parseFloat(Number(price).toPrecision(5)));
|
||||||
};
|
};
|
||||||
|
|
||||||
const handleCurrencyChange = function (newCurrency: number) {
|
const handleCurrencyChange = function (newCurrency: number): void {
|
||||||
const currencyCode: string = currencyDict[newCurrency];
|
const currencyCode: string = currencyDict[newCurrency];
|
||||||
setCurrencyCode(currencyCode);
|
setCurrencyCode(currencyCode);
|
||||||
setFav({
|
setFav({
|
||||||
@ -138,12 +148,12 @@ const MakerForm = ({
|
|||||||
currency: newCurrency,
|
currency: newCurrency,
|
||||||
mode: newCurrency === 1000 ? 'swap' : 'fiat',
|
mode: newCurrency === 1000 ? 'swap' : 'fiat',
|
||||||
});
|
});
|
||||||
updateAmountLimits(limits.list, newCurrency, maker.premium);
|
updateAmountLimits(limits, newCurrency, maker.premium);
|
||||||
updateCurrentPrice(limits.list, newCurrency, maker.premium);
|
updateCurrentPrice(limits, newCurrency, maker.premium);
|
||||||
|
|
||||||
if (makerHasAmountRange) {
|
if (makerHasAmountRange) {
|
||||||
const minAmount = parseFloat(Number(limits.list[newCurrency].min_amount).toPrecision(2));
|
const minAmount = parseFloat(Number(limits[newCurrency].min_amount).toPrecision(2));
|
||||||
const maxAmount = parseFloat(Number(limits.list[newCurrency].max_amount).toPrecision(2));
|
const maxAmount = parseFloat(Number(limits[newCurrency].max_amount).toPrecision(2));
|
||||||
if (
|
if (
|
||||||
parseFloat(maker.minAmount) < minAmount ||
|
parseFloat(maker.minAmount) < minAmount ||
|
||||||
parseFloat(maker.minAmount) > maxAmount ||
|
parseFloat(maker.minAmount) > maxAmount ||
|
||||||
@ -163,10 +173,12 @@ const MakerForm = ({
|
|||||||
return maker.advancedOptions && amountRangeEnabled;
|
return maker.advancedOptions && amountRangeEnabled;
|
||||||
}, [maker.advancedOptions, amountRangeEnabled]);
|
}, [maker.advancedOptions, amountRangeEnabled]);
|
||||||
|
|
||||||
const handlePaymentMethodChange = function (paymentArray: { name: string; icon: string }[]) {
|
const handlePaymentMethodChange = function (
|
||||||
let includeCoordinates = false;
|
paymentArray: Array<{ name: string; icon: string }>,
|
||||||
|
): void {
|
||||||
let str = '';
|
let str = '';
|
||||||
const arrayLength = paymentArray.length;
|
const arrayLength = paymentArray.length;
|
||||||
|
let includeCoordinates = false;
|
||||||
|
|
||||||
for (let i = 0; i < arrayLength; i++) {
|
for (let i = 0; i < arrayLength; i++) {
|
||||||
str += paymentArray[i].name + ' ';
|
str += paymentArray[i].name + ' ';
|
||||||
@ -190,14 +202,18 @@ const MakerForm = ({
|
|||||||
});
|
});
|
||||||
};
|
};
|
||||||
|
|
||||||
const handleMinAmountChange = function (e) {
|
const handleMinAmountChange = function (
|
||||||
|
e: React.ChangeEventHandler<HTMLInputElement | HTMLTextAreaElement>,
|
||||||
|
): void {
|
||||||
setMaker({
|
setMaker({
|
||||||
...maker,
|
...maker,
|
||||||
minAmount: parseFloat(Number(e.target.value).toPrecision(e.target.value < 100 ? 2 : 3)),
|
minAmount: parseFloat(Number(e.target.value).toPrecision(e.target.value < 100 ? 2 : 3)),
|
||||||
});
|
});
|
||||||
};
|
};
|
||||||
|
|
||||||
const handleMaxAmountChange = function (e) {
|
const handleMaxAmountChange = function (
|
||||||
|
e: React.ChangeEventHandler<HTMLInputElement | HTMLTextAreaElement>,
|
||||||
|
): void {
|
||||||
setMaker({
|
setMaker({
|
||||||
...maker,
|
...maker,
|
||||||
maxAmount: parseFloat(Number(e.target.value).toPrecision(e.target.value < 100 ? 2 : 3)),
|
maxAmount: parseFloat(Number(e.target.value).toPrecision(e.target.value < 100 ? 2 : 3)),
|
||||||
@ -205,7 +221,7 @@ const MakerForm = ({
|
|||||||
};
|
};
|
||||||
|
|
||||||
const handlePremiumChange: React.ChangeEventHandler<HTMLInputElement | HTMLTextAreaElement> =
|
const handlePremiumChange: React.ChangeEventHandler<HTMLInputElement | HTMLTextAreaElement> =
|
||||||
function ({ target: { value } }) {
|
function ({ target: { value } }): void {
|
||||||
const max = fav.mode === 'fiat' ? 999 : 99;
|
const max = fav.mode === 'fiat' ? 999 : 99;
|
||||||
const min = -100;
|
const min = -100;
|
||||||
const newPremium = Math.floor(Number(value) * Math.pow(10, 2)) / Math.pow(10, 2);
|
const newPremium = Math.floor(Number(value) * Math.pow(10, 2)) / Math.pow(10, 2);
|
||||||
@ -218,8 +234,8 @@ const MakerForm = ({
|
|||||||
badPremiumText = t('Must be more than {{min}}%', { min });
|
badPremiumText = t('Must be more than {{min}}%', { min });
|
||||||
premium = -99.99;
|
premium = -99.99;
|
||||||
}
|
}
|
||||||
updateCurrentPrice(limits.list, fav.currency, premium);
|
updateCurrentPrice(limits, fav.currency, premium);
|
||||||
updateAmountLimits(limits.list, fav.currency, premium);
|
updateAmountLimits(limits, fav.currency, premium);
|
||||||
setMaker({
|
setMaker({
|
||||||
...maker,
|
...maker,
|
||||||
premium: isNaN(newPremium) || value === '' ? '' : premium,
|
premium: isNaN(newPremium) || value === '' ? '' : premium,
|
||||||
@ -227,7 +243,7 @@ const MakerForm = ({
|
|||||||
});
|
});
|
||||||
};
|
};
|
||||||
|
|
||||||
const handleSatoshisChange = function (e: object) {
|
const handleSatoshisChange = function (e: object): void {
|
||||||
const newSatoshis = e.target.value;
|
const newSatoshis = e.target.value;
|
||||||
let badSatoshisText: string = '';
|
let badSatoshisText: string = '';
|
||||||
let satoshis: string = newSatoshis;
|
let satoshis: string = newSatoshis;
|
||||||
@ -247,14 +263,14 @@ const MakerForm = ({
|
|||||||
});
|
});
|
||||||
};
|
};
|
||||||
|
|
||||||
const handleClickRelative = function () {
|
const handleClickRelative = function (): void {
|
||||||
setMaker({
|
setMaker({
|
||||||
...maker,
|
...maker,
|
||||||
isExplicit: false,
|
isExplicit: false,
|
||||||
});
|
});
|
||||||
};
|
};
|
||||||
|
|
||||||
const handleClickExplicit = function () {
|
const handleClickExplicit = function (): void {
|
||||||
if (!maker.advancedOptions) {
|
if (!maker.advancedOptions) {
|
||||||
setMaker({
|
setMaker({
|
||||||
...maker,
|
...maker,
|
||||||
@ -263,12 +279,24 @@ const MakerForm = ({
|
|||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
const handleCreateOrder = function () {
|
const handleCreateOrder = function (): void {
|
||||||
if (!disableRequest) {
|
const { url, basePath } = federation
|
||||||
|
.getCoordinator(maker.coordinator)
|
||||||
|
?.getEndpoint(settings.network, origin, settings.selfhostedClient, hostUrl);
|
||||||
|
|
||||||
|
const auth = {
|
||||||
|
tokenSHA256: garage.getRobot().tokenSHA256,
|
||||||
|
keys: {
|
||||||
|
pubKey: garage.getRobot().pubKey?.split('\n').join('\\'),
|
||||||
|
encPrivKey: garage.getRobot().encPrivKey?.split('\n').join('\\'),
|
||||||
|
},
|
||||||
|
};
|
||||||
|
|
||||||
|
if (!disableRequest && focusedCoordinator) {
|
||||||
setSubmittingRequest(true);
|
setSubmittingRequest(true);
|
||||||
const body = {
|
const body = {
|
||||||
type: fav.type == 0 ? 1 : 0,
|
type: fav.type === 0 ? 1 : 0,
|
||||||
currency: fav.currency == 0 ? 1 : fav.currency,
|
currency: fav.currency === 0 ? 1 : fav.currency,
|
||||||
amount: makerHasAmountRange ? null : maker.amount,
|
amount: makerHasAmountRange ? null : maker.amount,
|
||||||
has_range: makerHasAmountRange,
|
has_range: makerHasAmountRange,
|
||||||
min_amount: makerHasAmountRange ? maker.minAmount : null,
|
min_amount: makerHasAmountRange ? maker.minAmount : null,
|
||||||
@ -276,7 +304,7 @@ const MakerForm = ({
|
|||||||
payment_method:
|
payment_method:
|
||||||
maker.paymentMethodsText === '' ? 'not specified' : maker.paymentMethodsText,
|
maker.paymentMethodsText === '' ? 'not specified' : maker.paymentMethodsText,
|
||||||
is_explicit: maker.isExplicit,
|
is_explicit: maker.isExplicit,
|
||||||
premium: maker.isExplicit ? null : maker.premium == '' ? 0 : maker.premium,
|
premium: maker.isExplicit ? null : maker.premium === '' ? 0 : maker.premium,
|
||||||
satoshis: maker.isExplicit ? maker.satoshis : null,
|
satoshis: maker.isExplicit ? maker.satoshis : null,
|
||||||
public_duration: maker.publicDuration,
|
public_duration: maker.publicDuration,
|
||||||
escrow_duration: maker.escrowDuration,
|
escrow_duration: maker.escrowDuration,
|
||||||
@ -284,48 +312,54 @@ const MakerForm = ({
|
|||||||
latitude: maker.latitude,
|
latitude: maker.latitude,
|
||||||
longitude: maker.longitude,
|
longitude: maker.longitude,
|
||||||
};
|
};
|
||||||
|
const { url } = federation
|
||||||
|
.getCoordinator(focusedCoordinator)
|
||||||
|
.getEndpoint(settings.network, origin, settings.selfhostedClient, hostUrl);
|
||||||
apiClient
|
apiClient
|
||||||
.post(baseUrl, '/api/make/', body, { tokenSHA256: robot.tokenSHA256 })
|
.post(url, `${basePath}/api/make/`, body, auth)
|
||||||
.then((data: object) => {
|
.then((data: any) => {
|
||||||
setBadRequest(data.bad_request);
|
setBadRequest(data.bad_request);
|
||||||
if (data.id) {
|
if (data.id !== undefined) {
|
||||||
onOrderCreated(data.id);
|
onOrderCreated(maker.coordinator, data.id);
|
||||||
}
|
}
|
||||||
setSubmittingRequest(false);
|
setSubmittingRequest(false);
|
||||||
|
})
|
||||||
|
.catch(() => {
|
||||||
|
setBadRequest('Request error');
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
setOpenDialogs(false);
|
setOpenDialogs(false);
|
||||||
};
|
};
|
||||||
|
|
||||||
const handleChangePublicDuration = function (date: Date) {
|
const handleChangePublicDuration = function (date: Date): void {
|
||||||
const d = new Date(date);
|
const d = new Date(date);
|
||||||
const hours: number = d.getHours();
|
const hours: number = d.getHours();
|
||||||
const minutes: number = d.getMinutes();
|
const minutes: number = d.getMinutes();
|
||||||
|
|
||||||
const total_secs: number = hours * 60 * 60 + minutes * 60;
|
const totalSecs: number = hours * 60 * 60 + minutes * 60;
|
||||||
|
|
||||||
setMaker({
|
setMaker({
|
||||||
...maker,
|
...maker,
|
||||||
publicExpiryTime: date,
|
publicExpiryTime: date,
|
||||||
publicDuration: total_secs,
|
publicDuration: totalSecs,
|
||||||
});
|
});
|
||||||
};
|
};
|
||||||
|
|
||||||
const handleChangeEscrowDuration = function (date: Date) {
|
const handleChangeEscrowDuration = function (date: Date): void {
|
||||||
const d = new Date(date);
|
const d = new Date(date);
|
||||||
const hours: number = d.getHours();
|
const hours: number = d.getHours();
|
||||||
const minutes: number = d.getMinutes();
|
const minutes: number = d.getMinutes();
|
||||||
|
|
||||||
const total_secs: number = hours * 60 * 60 + minutes * 60;
|
const totalSecs: number = hours * 60 * 60 + minutes * 60;
|
||||||
|
|
||||||
setMaker({
|
setMaker({
|
||||||
...maker,
|
...maker,
|
||||||
escrowExpiryTime: date,
|
escrowExpiryTime: date,
|
||||||
escrowDuration: total_secs,
|
escrowDuration: totalSecs,
|
||||||
});
|
});
|
||||||
};
|
};
|
||||||
|
|
||||||
const handleClickAdvanced = function () {
|
const handleClickAdvanced = function (): void {
|
||||||
if (maker.advancedOptions) {
|
if (maker.advancedOptions) {
|
||||||
handleClickRelative();
|
handleClickRelative();
|
||||||
setMaker({ ...maker, advancedOptions: false });
|
setMaker({ ...maker, advancedOptions: false });
|
||||||
@ -352,14 +386,16 @@ const MakerForm = ({
|
|||||||
);
|
);
|
||||||
}, [maker.minAmount, maker.maxAmount, amountLimits]);
|
}, [maker.minAmount, maker.maxAmount, amountLimits]);
|
||||||
|
|
||||||
const resetRange = function (advancedOptions: boolean) {
|
const resetRange = function (advancedOptions: boolean): void {
|
||||||
const index = fav.currency === 0 ? 1 : fav.currency;
|
const index = fav.currency === 0 ? 1 : fav.currency;
|
||||||
const minAmount = maker.amount
|
const minAmount =
|
||||||
? parseFloat((maker.amount / 2).toPrecision(2))
|
maker.amount !== ''
|
||||||
: parseFloat(Number(limits.list[index].max_amount * 0.25).toPrecision(2));
|
? parseFloat((maker.amount / 2).toPrecision(2))
|
||||||
const maxAmount = maker.amount
|
: parseFloat(Number(limits[index].max_amount * 0.25).toPrecision(2));
|
||||||
? parseFloat(maker.amount)
|
const maxAmount =
|
||||||
: parseFloat(Number(limits.list[index].max_amount * 0.75).toPrecision(2));
|
maker.amount !== ''
|
||||||
|
? parseFloat(maker.amount)
|
||||||
|
: parseFloat(Number(limits[index].max_amount * 0.75).toPrecision(2));
|
||||||
|
|
||||||
setMaker({
|
setMaker({
|
||||||
...maker,
|
...maker,
|
||||||
@ -369,7 +405,7 @@ const MakerForm = ({
|
|||||||
});
|
});
|
||||||
};
|
};
|
||||||
|
|
||||||
const handleRangeAmountChange = function (e: any, newValue, activeThumb: number) {
|
const handleRangeAmountChange = function (e: any, newValue, activeThumb: number): void {
|
||||||
let minAmount = e.target.value[0];
|
let minAmount = e.target.value[0];
|
||||||
let maxAmount = e.target.value[1];
|
let maxAmount = e.target.value[1];
|
||||||
|
|
||||||
@ -406,11 +442,14 @@ const MakerForm = ({
|
|||||||
const handleClickAmountRangeEnabled = function (
|
const handleClickAmountRangeEnabled = function (
|
||||||
_e: React.ChangeEvent<HTMLInputElement>,
|
_e: React.ChangeEvent<HTMLInputElement>,
|
||||||
checked: boolean,
|
checked: boolean,
|
||||||
) {
|
): void {
|
||||||
setAmountRangeEnabled(checked);
|
setAmountRangeEnabled(checked);
|
||||||
};
|
};
|
||||||
|
|
||||||
const amountLabel = useMemo(() => {
|
const amountLabel = useMemo(() => {
|
||||||
|
if (!focusedCoordinator) return;
|
||||||
|
|
||||||
|
const info = federation.getCoordinator(focusedCoordinator)?.info;
|
||||||
const defaultRoutingBudget = 0.001;
|
const defaultRoutingBudget = 0.001;
|
||||||
let label = t('Amount');
|
let label = t('Amount');
|
||||||
let helper = '';
|
let helper = '';
|
||||||
@ -420,7 +459,7 @@ const MakerForm = ({
|
|||||||
swapSats = computeSats({
|
swapSats = computeSats({
|
||||||
amount: Number(maker.amount),
|
amount: Number(maker.amount),
|
||||||
premium: Number(maker.premium),
|
premium: Number(maker.premium),
|
||||||
fee: -info.maker_fee,
|
fee: -(info?.maker_fee ?? 0),
|
||||||
routingBudget: defaultRoutingBudget,
|
routingBudget: defaultRoutingBudget,
|
||||||
});
|
});
|
||||||
label = t('Onchain amount to send (BTC)');
|
label = t('Onchain amount to send (BTC)');
|
||||||
@ -431,7 +470,7 @@ const MakerForm = ({
|
|||||||
swapSats = computeSats({
|
swapSats = computeSats({
|
||||||
amount: Number(maker.amount),
|
amount: Number(maker.amount),
|
||||||
premium: Number(maker.premium),
|
premium: Number(maker.premium),
|
||||||
fee: info.maker_fee,
|
fee: info?.maker_fee ?? 0,
|
||||||
});
|
});
|
||||||
label = t('Onchain amount to receive (BTC)');
|
label = t('Onchain amount to receive (BTC)');
|
||||||
helper = t('You send approx {{swapSats}} LN Sats (fees might vary)', {
|
helper = t('You send approx {{swapSats}} LN Sats (fees might vary)', {
|
||||||
@ -440,30 +479,30 @@ const MakerForm = ({
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
return { label, helper, swapSats };
|
return { label, helper, swapSats };
|
||||||
}, [fav, maker.amount, maker.premium, info]);
|
}, [fav, maker.amount, maker.premium, federationUpdatedAt]);
|
||||||
|
|
||||||
const disableSubmit = useMemo(() => {
|
const disableSubmit = useMemo(() => {
|
||||||
return (
|
return (
|
||||||
fav.type == null ||
|
fav.type == null ||
|
||||||
(!makerHasAmountRange &&
|
(!makerHasAmountRange &&
|
||||||
maker.amount != '' &&
|
maker.amount !== '' &&
|
||||||
(maker.amount < amountLimits[0] || maker.amount > amountLimits[1])) ||
|
(maker.amount < amountLimits[0] || maker.amount > amountLimits[1])) ||
|
||||||
maker.badPaymentMethod ||
|
maker.badPaymentMethod ||
|
||||||
(maker.amount == null && (!makerHasAmountRange || limits.loading)) ||
|
(maker.amount == null && (!makerHasAmountRange || Object.keys(limits).lenght < 1)) ||
|
||||||
(makerHasAmountRange && (minAmountError || maxAmountError)) ||
|
(makerHasAmountRange && (minAmountError || maxAmountError)) ||
|
||||||
(!makerHasAmountRange && maker.amount <= 0) ||
|
(!makerHasAmountRange && maker.amount <= 0) ||
|
||||||
(maker.isExplicit && (maker.badSatoshisText != '' || maker.satoshis == '')) ||
|
(maker.isExplicit && (maker.badSatoshisText !== '' || maker.satoshis === '')) ||
|
||||||
(!maker.isExplicit && maker.badPremiumText != '')
|
(!maker.isExplicit && maker.badPremiumText !== '')
|
||||||
);
|
);
|
||||||
}, [maker, amountLimits, limits, fav.type, makerHasAmountRange]);
|
}, [maker, amountLimits, coordinatorUpdatedAt, fav.type, makerHasAmountRange]);
|
||||||
|
|
||||||
const clearMaker = function () {
|
const clearMaker = function (): void {
|
||||||
setFav({ ...fav, type: null });
|
setFav({ ...fav, type: null });
|
||||||
setMaker(defaultMaker);
|
setMaker(defaultMaker);
|
||||||
};
|
};
|
||||||
|
|
||||||
const handleAddLocation = (pos: [number, number]) => {
|
const handleAddLocation = (pos: [number, number]): void => {
|
||||||
if (pos && pos.length === 2) {
|
if (pos?.length === 2) {
|
||||||
setMaker((maker) => {
|
setMaker((maker) => {
|
||||||
return {
|
return {
|
||||||
...maker,
|
...maker,
|
||||||
@ -471,10 +510,11 @@ const MakerForm = ({
|
|||||||
longitude: parseFloat(pos[1].toPrecision(6)),
|
longitude: parseFloat(pos[1].toPrecision(6)),
|
||||||
};
|
};
|
||||||
});
|
});
|
||||||
if (!maker.paymentMethods.find((method) => method.icon === 'cash')) {
|
const cashMethod = maker.paymentMethods.find((method) => method.icon === 'cash');
|
||||||
|
if (cashMethod !== null) {
|
||||||
const newMethods = maker.paymentMethods;
|
const newMethods = maker.paymentMethods;
|
||||||
const cash = fiatMethods.find((method) => method.icon === 'cash');
|
const cash = fiatMethods.find((method) => method.icon === 'cash');
|
||||||
if (cash) {
|
if (cash !== null) {
|
||||||
newMethods.unshift(cash);
|
newMethods.unshift(cash);
|
||||||
handlePaymentMethodChange(newMethods);
|
handlePaymentMethodChange(newMethods);
|
||||||
}
|
}
|
||||||
@ -482,7 +522,7 @@ const MakerForm = ({
|
|||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
const SummaryText = function () {
|
const SummaryText = (): JSX.Element => {
|
||||||
return (
|
return (
|
||||||
<Typography
|
<Typography
|
||||||
component='h2'
|
component='h2'
|
||||||
@ -494,7 +534,7 @@ const MakerForm = ({
|
|||||||
? fav.mode === 'fiat'
|
? fav.mode === 'fiat'
|
||||||
? t('Order for ')
|
? t('Order for ')
|
||||||
: t('Swap of ')
|
: t('Swap of ')
|
||||||
: fav.type == 1
|
: fav.type === 1
|
||||||
? fav.mode === 'fiat'
|
? fav.mode === 'fiat'
|
||||||
? t('Buy BTC for ')
|
? t('Buy BTC for ')
|
||||||
: t('Swap into LN ')
|
: t('Swap into LN ')
|
||||||
@ -512,7 +552,7 @@ const MakerForm = ({
|
|||||||
{' ' + (fav.mode === 'fiat' ? currencyCode : 'Sats')}
|
{' ' + (fav.mode === 'fiat' ? currencyCode : 'Sats')}
|
||||||
{maker.isExplicit
|
{maker.isExplicit
|
||||||
? t(' of {{satoshis}} Satoshis', { satoshis: pn(maker.satoshis) })
|
? t(' of {{satoshis}} Satoshis', { satoshis: pn(maker.satoshis) })
|
||||||
: maker.premium == 0
|
: maker.premium === 0
|
||||||
? fav.mode === 'fiat'
|
? fav.mode === 'fiat'
|
||||||
? t(' at market price')
|
? t(' at market price')
|
||||||
: ''
|
: ''
|
||||||
@ -531,7 +571,7 @@ const MakerForm = ({
|
|||||||
setOpenDialogs(false);
|
setOpenDialogs(false);
|
||||||
}}
|
}}
|
||||||
onClickDone={handleCreateOrder}
|
onClickDone={handleCreateOrder}
|
||||||
hasRobot={robot.avatarLoaded}
|
hasRobot={garage.getRobot().avatarLoaded}
|
||||||
onClickGenerateRobot={onClickGenerateRobot}
|
onClickGenerateRobot={onClickGenerateRobot}
|
||||||
/>
|
/>
|
||||||
<F2fMapDialog
|
<F2fMapDialog
|
||||||
@ -542,19 +582,19 @@ const MakerForm = ({
|
|||||||
message={t(
|
message={t(
|
||||||
'To protect your privacy, the exact location you pin will be slightly randomized.',
|
'To protect your privacy, the exact location you pin will be slightly randomized.',
|
||||||
)}
|
)}
|
||||||
orderType={fav.type || 0}
|
orderType={fav?.type ?? 0}
|
||||||
onClose={(pos?: [number, number]) => {
|
onClose={(pos?: [number, number]) => {
|
||||||
if (pos) handleAddLocation(pos);
|
if (pos != null) handleAddLocation(pos);
|
||||||
setOpenWorldmap(false);
|
setOpenWorldmap(false);
|
||||||
}}
|
}}
|
||||||
zoom={maker.latitude && maker.longitude ? 6 : undefined}
|
zoom={maker.latitude && maker.longitude ? 6 : undefined}
|
||||||
/>
|
/>
|
||||||
<Collapse in={limits.list.length == 0}>
|
<Collapse in={Object.keys(limits).lenght === 0}>
|
||||||
<div style={{ display: limits.list.length == 0 ? '' : 'none' }}>
|
<div style={{ display: Object.keys(limits) === 0 ? '' : 'none' }}>
|
||||||
<LinearProgress />
|
<LinearProgress />
|
||||||
</div>
|
</div>
|
||||||
</Collapse>
|
</Collapse>
|
||||||
<Collapse in={!(limits.list.length == 0 || collapseAll)}>
|
<Collapse in={!(Object.keys(limits).lenght === 0 || collapseAll)}>
|
||||||
<Grid container justifyContent='space-between' spacing={0} sx={{ maxHeight: '1em' }}>
|
<Grid container justifyContent='space-between' spacing={0} sx={{ maxHeight: '1em' }}>
|
||||||
<Grid item>
|
<Grid item>
|
||||||
<IconButton
|
<IconButton
|
||||||
@ -590,7 +630,7 @@ const MakerForm = ({
|
|||||||
>
|
>
|
||||||
<Switch
|
<Switch
|
||||||
size='small'
|
size='small'
|
||||||
disabled={limits.list.length == 0}
|
disabled={Object.keys(limits).length === 0}
|
||||||
checked={maker.advancedOptions}
|
checked={maker.advancedOptions}
|
||||||
onChange={handleClickAdvanced}
|
onChange={handleClickAdvanced}
|
||||||
/>
|
/>
|
||||||
@ -611,9 +651,9 @@ const MakerForm = ({
|
|||||||
<FormHelperText sx={{ textAlign: 'center' }}>{t('Swap?')}</FormHelperText>
|
<FormHelperText sx={{ textAlign: 'center' }}>{t('Swap?')}</FormHelperText>
|
||||||
<Checkbox
|
<Checkbox
|
||||||
sx={{ position: 'relative', bottom: '0.3em' }}
|
sx={{ position: 'relative', bottom: '0.3em' }}
|
||||||
checked={fav.mode == 'swap'}
|
checked={fav.mode === 'swap'}
|
||||||
onClick={() => {
|
onClick={() => {
|
||||||
handleCurrencyChange(fav.mode == 'swap' ? 1 : 1000);
|
handleCurrencyChange(fav.mode === 'swap' ? 1 : 1000);
|
||||||
}}
|
}}
|
||||||
/>
|
/>
|
||||||
</FormControl>
|
</FormControl>
|
||||||
@ -636,10 +676,10 @@ const MakerForm = ({
|
|||||||
type: 1,
|
type: 1,
|
||||||
});
|
});
|
||||||
}}
|
}}
|
||||||
disableElevation={fav.type == 1}
|
disableElevation={fav.type === 1}
|
||||||
sx={{
|
sx={{
|
||||||
backgroundColor: fav.type == 1 ? 'primary.main' : 'background.paper',
|
backgroundColor: fav.type === 1 ? 'primary.main' : 'background.paper',
|
||||||
color: fav.type == 1 ? 'background.paper' : 'text.secondary',
|
color: fav.type === 1 ? 'background.paper' : 'text.secondary',
|
||||||
':hover': {
|
':hover': {
|
||||||
color: 'background.paper',
|
color: 'background.paper',
|
||||||
},
|
},
|
||||||
@ -656,11 +696,11 @@ const MakerForm = ({
|
|||||||
type: 0,
|
type: 0,
|
||||||
});
|
});
|
||||||
}}
|
}}
|
||||||
disableElevation={fav.type == 0}
|
disableElevation={fav.type === 0}
|
||||||
color='secondary'
|
color='secondary'
|
||||||
sx={{
|
sx={{
|
||||||
backgroundColor: fav.type == 0 ? 'secondary.main' : 'background.paper',
|
backgroundColor: fav.type === 0 ? 'secondary.main' : 'background.paper',
|
||||||
color: fav.type == 0 ? 'background.secondary' : 'text.secondary',
|
color: fav.type === 0 ? 'background.secondary' : 'text.secondary',
|
||||||
':hover': {
|
':hover': {
|
||||||
color: 'background.paper',
|
color: 'background.paper',
|
||||||
},
|
},
|
||||||
@ -730,15 +770,15 @@ const MakerForm = ({
|
|||||||
disabled={makerHasAmountRange}
|
disabled={makerHasAmountRange}
|
||||||
variant={makerHasAmountRange ? 'filled' : 'outlined'}
|
variant={makerHasAmountRange ? 'filled' : 'outlined'}
|
||||||
error={
|
error={
|
||||||
maker.amount != '' &&
|
maker.amount !== '' &&
|
||||||
(maker.amount < amountLimits[0] || maker.amount > amountLimits[1])
|
(maker.amount < amountLimits[0] || maker.amount > amountLimits[1])
|
||||||
}
|
}
|
||||||
helperText={
|
helperText={
|
||||||
maker.amount < amountLimits[0] && maker.amount != ''
|
maker.amount < amountLimits[0] && maker.amount !== ''
|
||||||
? t('Must be more than {{minAmount}}', {
|
? t('Must be more than {{minAmount}}', {
|
||||||
minAmount: pn(parseFloat(amountLimits[0].toPrecision(2))),
|
minAmount: pn(parseFloat(amountLimits[0].toPrecision(2))),
|
||||||
})
|
})
|
||||||
: maker.amount > amountLimits[1] && maker.amount != ''
|
: maker.amount > amountLimits[1] && maker.amount !== ''
|
||||||
? t('Must be less than {{maxAmount}}', {
|
? t('Must be less than {{maxAmount}}', {
|
||||||
maxAmount: pn(parseFloat(amountLimits[1].toPrecision(2))),
|
maxAmount: pn(parseFloat(amountLimits[1].toPrecision(2))),
|
||||||
})
|
})
|
||||||
@ -761,7 +801,7 @@ const MakerForm = ({
|
|||||||
}}
|
}}
|
||||||
/>
|
/>
|
||||||
</Tooltip>
|
</Tooltip>
|
||||||
{fav.mode === 'swap' && maker.amount != '' ? (
|
{fav.mode === 'swap' && maker.amount !== '' ? (
|
||||||
<FormHelperText sx={{ textAlign: 'center' }}>
|
<FormHelperText sx={{ textAlign: 'center' }}>
|
||||||
{amountLabel.helper}
|
{amountLabel.helper}
|
||||||
</FormHelperText>
|
</FormHelperText>
|
||||||
@ -780,7 +820,7 @@ const MakerForm = ({
|
|||||||
inputProps={{
|
inputProps={{
|
||||||
style: { textAlign: 'center' },
|
style: { textAlign: 'center' },
|
||||||
}}
|
}}
|
||||||
value={fav.currency == 0 ? 1 : fav.currency}
|
value={fav.currency === 0 ? 1 : fav.currency}
|
||||||
onChange={(e) => {
|
onChange={(e) => {
|
||||||
handleCurrencyChange(e.target.value);
|
handleCurrencyChange(e.target.value);
|
||||||
}}
|
}}
|
||||||
@ -806,13 +846,15 @@ const MakerForm = ({
|
|||||||
<Grid item xs={12}>
|
<Grid item xs={12}>
|
||||||
<AutocompletePayments
|
<AutocompletePayments
|
||||||
onAutocompleteChange={handlePaymentMethodChange}
|
onAutocompleteChange={handlePaymentMethodChange}
|
||||||
onClick={() => setOpenWorldmap(true)}
|
onClick={() => {
|
||||||
|
setOpenWorldmap(true);
|
||||||
|
}}
|
||||||
optionsType={fav.mode}
|
optionsType={fav.mode}
|
||||||
error={maker.badPaymentMethod}
|
error={maker.badPaymentMethod}
|
||||||
helperText={maker.badPaymentMethod ? t('Must be shorter than 65 characters') : ''}
|
helperText={maker.badPaymentMethod ? t('Must be shorter than 65 characters') : ''}
|
||||||
label={fav.mode == 'swap' ? t('Swap Destination(s)') : t('Fiat Payment Method(s)')}
|
label={fav.mode === 'swap' ? t('Swap Destination(s)') : t('Fiat Payment Method(s)')}
|
||||||
tooltipTitle={t(
|
tooltipTitle={t(
|
||||||
fav.mode == 'swap'
|
fav.mode === 'swap'
|
||||||
? t('Enter the destination of the Lightning swap')
|
? t('Enter the destination of the Lightning swap')
|
||||||
: 'Enter your preferred fiat payment methods. Fast methods are highly recommended.',
|
: 'Enter your preferred fiat payment methods. Fast methods are highly recommended.',
|
||||||
)}
|
)}
|
||||||
@ -844,7 +886,9 @@ const MakerForm = ({
|
|||||||
color: theme.palette.text.secondary,
|
color: theme.palette.text.secondary,
|
||||||
borderColor: theme.palette.text.disabled,
|
borderColor: theme.palette.text.disabled,
|
||||||
}}
|
}}
|
||||||
onClick={() => setOpenWorldmap(true)}
|
onClick={() => {
|
||||||
|
setOpenWorldmap(true);
|
||||||
|
}}
|
||||||
>
|
>
|
||||||
{t('Face to Face Location')}
|
{t('Face to Face Location')}
|
||||||
<Map style={{ paddingLeft: 5 }} />
|
<Map style={{ paddingLeft: 5 }} />
|
||||||
@ -913,7 +957,7 @@ const MakerForm = ({
|
|||||||
<TextField
|
<TextField
|
||||||
fullWidth
|
fullWidth
|
||||||
label={t('Satoshis')}
|
label={t('Satoshis')}
|
||||||
error={maker.badSatoshisText != ''}
|
error={maker.badSatoshisText !== ''}
|
||||||
helperText={maker.badSatoshisText === '' ? null : maker.badSatoshisText}
|
helperText={maker.badSatoshisText === '' ? null : maker.badSatoshisText}
|
||||||
type='number'
|
type='number'
|
||||||
required={true}
|
required={true}
|
||||||
@ -933,7 +977,7 @@ const MakerForm = ({
|
|||||||
<div style={{ display: maker.isExplicit ? 'none' : '' }}>
|
<div style={{ display: maker.isExplicit ? 'none' : '' }}>
|
||||||
<TextField
|
<TextField
|
||||||
fullWidth
|
fullWidth
|
||||||
error={maker.badPremiumText != ''}
|
error={maker.badPremiumText !== ''}
|
||||||
helperText={maker.badPremiumText === '' ? null : maker.badPremiumText}
|
helperText={maker.badPremiumText === '' ? null : maker.badPremiumText}
|
||||||
label={t('Premium over Market (%)')}
|
label={t('Premium over Market (%)')}
|
||||||
type='number'
|
type='number'
|
||||||
@ -1096,6 +1140,15 @@ const MakerForm = ({
|
|||||||
</Grid>
|
</Grid>
|
||||||
</Collapse>
|
</Collapse>
|
||||||
|
|
||||||
|
<SelectCoordinator
|
||||||
|
coordinator={maker.coordinator}
|
||||||
|
setCoordinator={(coordinator) => {
|
||||||
|
setMaker((maker) => {
|
||||||
|
return { ...maker, coordinator };
|
||||||
|
});
|
||||||
|
}}
|
||||||
|
/>
|
||||||
|
|
||||||
<Grid container direction='column' alignItems='center'>
|
<Grid container direction='column' alignItems='center'>
|
||||||
<Grid item>
|
<Grid item>
|
||||||
<SummaryText />
|
<SummaryText />
|
||||||
@ -1152,7 +1205,7 @@ const MakerForm = ({
|
|||||||
</Typography>
|
</Typography>
|
||||||
</Grid>
|
</Grid>
|
||||||
|
|
||||||
<Collapse in={!(limits.list.length == 0)}>
|
<Collapse in={!(Object.keys(limits).length === 0)}>
|
||||||
<Tooltip
|
<Tooltip
|
||||||
placement='top'
|
placement='top'
|
||||||
enterTouchDelay={0}
|
enterTouchDelay={0}
|
||||||
|
125
frontend/src/components/MakerForm/SelectCoordinator.tsx
Normal file
125
frontend/src/components/MakerForm/SelectCoordinator.tsx
Normal file
@ -0,0 +1,125 @@
|
|||||||
|
import React, { useContext } from 'react';
|
||||||
|
import {
|
||||||
|
Grid,
|
||||||
|
Select,
|
||||||
|
MenuItem,
|
||||||
|
Box,
|
||||||
|
Tooltip,
|
||||||
|
Typography,
|
||||||
|
type SelectChangeEvent,
|
||||||
|
} from '@mui/material';
|
||||||
|
|
||||||
|
import RobotAvatar from '../RobotAvatar';
|
||||||
|
import { AppContext, type UseAppStoreType } from '../../contexts/AppContext';
|
||||||
|
import { useTheme } from '@emotion/react';
|
||||||
|
import { useTranslation } from 'react-i18next';
|
||||||
|
import { FederationContext, UseFederationStoreType } from '../../contexts/FederationContext';
|
||||||
|
|
||||||
|
interface SelectCoordinatorProps {
|
||||||
|
coordinator: string;
|
||||||
|
setCoordinator: (coordinator: string) => void;
|
||||||
|
}
|
||||||
|
|
||||||
|
const SelectCoordinator: React.FC<SelectCoordinatorProps> = ({ coordinator, setCoordinator }) => {
|
||||||
|
const { setOpen, hostUrl } = useContext<UseAppStoreType>(AppContext);
|
||||||
|
const { federation, setFocusedCoordinator, sortedCoordinators } =
|
||||||
|
useContext<UseFederationStoreType>(FederationContext);
|
||||||
|
const theme = useTheme();
|
||||||
|
const { t } = useTranslation();
|
||||||
|
|
||||||
|
const onClickCurrentCoordinator = function (shortAlias: string): void {
|
||||||
|
setFocusedCoordinator(shortAlias);
|
||||||
|
setOpen((open) => {
|
||||||
|
return { ...open, coordinator: true };
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
|
const handleCoordinatorChange = (e: SelectChangeEvent<string>): void => {
|
||||||
|
setCoordinator(e.target.value);
|
||||||
|
};
|
||||||
|
|
||||||
|
return (
|
||||||
|
<Grid item>
|
||||||
|
<Tooltip
|
||||||
|
placement='top'
|
||||||
|
enterTouchDelay={500}
|
||||||
|
enterDelay={700}
|
||||||
|
enterNextDelay={2000}
|
||||||
|
title={t(
|
||||||
|
'The provider the lightning and communication infrastructure. The host will be in charge of providing support and solving disputes. The trade fees are set by the host. Make sure to only select order hosts that you trust!',
|
||||||
|
)}
|
||||||
|
>
|
||||||
|
<Box
|
||||||
|
sx={{
|
||||||
|
backgroundColor: 'background.paper',
|
||||||
|
border: '1px solid',
|
||||||
|
borderRadius: '4px',
|
||||||
|
borderColor: theme.palette.mode === 'dark' ? '#434343' : '#c4c4c4',
|
||||||
|
'&:hover': {
|
||||||
|
borderColor: theme.palette.mode === 'dark' ? '#ffffff' : '#2f2f2f',
|
||||||
|
},
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
<Typography variant='caption' color='text.secondary'>
|
||||||
|
{t('Order Host')}
|
||||||
|
</Typography>
|
||||||
|
|
||||||
|
<Grid container>
|
||||||
|
<Grid
|
||||||
|
item
|
||||||
|
xs={3}
|
||||||
|
sx={{ cursor: 'pointer', position: 'relative', left: '0.3em', bottom: '0.1em' }}
|
||||||
|
onClick={() => {
|
||||||
|
onClickCurrentCoordinator(coordinator);
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
<Grid item>
|
||||||
|
<RobotAvatar
|
||||||
|
nickname={coordinator}
|
||||||
|
coordinator={true}
|
||||||
|
style={{ width: '3em', height: '3em' }}
|
||||||
|
smooth={true}
|
||||||
|
flipHorizontally={true}
|
||||||
|
baseUrl={hostUrl}
|
||||||
|
small={true}
|
||||||
|
/>
|
||||||
|
</Grid>
|
||||||
|
</Grid>
|
||||||
|
|
||||||
|
<Grid item xs={9}>
|
||||||
|
<Select
|
||||||
|
variant='standard'
|
||||||
|
fullWidth
|
||||||
|
required={true}
|
||||||
|
inputProps={{
|
||||||
|
style: { textAlign: 'center' },
|
||||||
|
}}
|
||||||
|
value={coordinator}
|
||||||
|
onChange={handleCoordinatorChange}
|
||||||
|
disableUnderline
|
||||||
|
>
|
||||||
|
{sortedCoordinators.map((shortAlias: string): JSX.Element | null => {
|
||||||
|
let row: JSX.Element | null = null;
|
||||||
|
if (
|
||||||
|
shortAlias === coordinator ||
|
||||||
|
(federation.getCoordinator(shortAlias).enabled === true &&
|
||||||
|
federation.getCoordinator(shortAlias).info !== undefined)
|
||||||
|
) {
|
||||||
|
row = (
|
||||||
|
<MenuItem key={shortAlias} value={shortAlias}>
|
||||||
|
<Typography>{federation.getCoordinator(shortAlias).longAlias}</Typography>
|
||||||
|
</MenuItem>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
return row;
|
||||||
|
})}
|
||||||
|
</Select>
|
||||||
|
</Grid>
|
||||||
|
</Grid>
|
||||||
|
</Box>
|
||||||
|
</Tooltip>
|
||||||
|
</Grid>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
export default SelectCoordinator;
|
@ -2,13 +2,13 @@ import React, { useContext, useEffect, useState } from 'react';
|
|||||||
import { apiClient } from '../../services/api';
|
import { apiClient } from '../../services/api';
|
||||||
import { MapContainer, GeoJSON, useMapEvents, TileLayer, Tooltip, Marker } from 'react-leaflet';
|
import { MapContainer, GeoJSON, useMapEvents, TileLayer, Tooltip, Marker } from 'react-leaflet';
|
||||||
import { useTheme, LinearProgress } from '@mui/material';
|
import { useTheme, LinearProgress } from '@mui/material';
|
||||||
import { UseAppStoreType, AppContext } from '../../contexts/AppContext';
|
import { type GeoJsonObject } from 'geojson';
|
||||||
import { GeoJsonObject } from 'geojson';
|
import { DivIcon, type LeafletMouseEvent } from 'leaflet';
|
||||||
import { DivIcon, LeafletMouseEvent } from 'leaflet';
|
import { type PublicOrder } from '../../models';
|
||||||
import { PublicOrder } from '../../models';
|
|
||||||
import OrderTooltip from '../Charts/helpers/OrderTooltip';
|
import OrderTooltip from '../Charts/helpers/OrderTooltip';
|
||||||
import getWorldmapGeojson from '../../geo/Web';
|
import getWorldmapGeojson from '../../geo/Web';
|
||||||
import MarkerClusterGroup from '@christopherpickering/react-leaflet-markercluster';
|
import MarkerClusterGroup from '@christopherpickering/react-leaflet-markercluster';
|
||||||
|
import { AppContext, UseAppStoreType } from '../../contexts/AppContext';
|
||||||
|
|
||||||
interface MapPinProps {
|
interface MapPinProps {
|
||||||
fillColor: string;
|
fillColor: string;
|
||||||
@ -17,7 +17,6 @@ interface MapPinProps {
|
|||||||
}
|
}
|
||||||
|
|
||||||
const MapPin = ({ fillColor, outlineColor, eyesColor }: MapPinProps): string => {
|
const MapPin = ({ fillColor, outlineColor, eyesColor }: MapPinProps): string => {
|
||||||
console.log(fillColor, outlineColor);
|
|
||||||
return `<svg id='robot_pin' xmlns='http://www.w3.org/2000/svg' viewBox='0 0 18.66 29.68'><defs><style>.body{fill:${fillColor};}.eyes{fill:${eyesColor};}.outline{fill:${outlineColor};}</style></defs><path class='body' d='M18,8A9.13,9.13,0,0,0,10.89.62,10.88,10.88,0,0,0,9.33.49,10.88,10.88,0,0,0,7.77.62,9.13,9.13,0,0,0,.66,8a12.92,12.92,0,0,0,1.19,8.25C2.68,18.09,7.47,27.6,9.07,29c0,.12.11.19.19.19l.07,0,.07,0c.08,0,.15-.07.19-.19,1.6-1.41,6.39-10.92,7.22-12.8A12.92,12.92,0,0,0,18,8Z'/><path class='outline' d='M9.23,29.6a.57.57,0,0,1-.5-.35C7,27.57,2.24,18.09,1.48,16.38A13.57,13.57,0,0,1,.26,7.87C1.18,3.78,4,.92,7.7.23h0A8.38,8.38,0,0,1,11,.24h0c3.74.69,6.52,3.55,7.44,7.64a13.57,13.57,0,0,1-1.22,8.51c-.76,1.71-5.5,11.19-7.25,12.87a.57.57,0,0,1-.55.35H9.23ZM8,1,7.85,1a8.68,8.68,0,0,0-6.8,7C.5,10.52.86,13,2.22,16.05c.9,2,5.62,11.32,7.11,12.65,1.49-1.33,6.21-10.63,7.11-12.65,1.36-3.07,1.72-5.53,1.17-8h0a8.68,8.68,0,0,0-6.8-7l-.12,0A10.47,10.47,0,0,0,9.33.89,10.3,10.3,0,0,0,8,1Z'/><rect class='outline' x='3.12' y='6.34' width='12.53' height='7.76' rx='3.88'/><rect class='eyes' x='5.02' y='7.82' width='2.16' height='2.34' rx='1.02'/><rect class='eyes' x='11.25' y='7.82' width='2.16' height='2.34' rx='1.02'/><path class='eyes' d='M9.24,12.76A3.57,3.57,0,0,1,7,12a.4.4,0,1,1,.53-.61,2.78,2.78,0,0,0,3.49,0,.4.4,0,0,1,.48.65A3.71,3.71,0,0,1,9.24,12.76Z'/></svg>`;
|
return `<svg id='robot_pin' xmlns='http://www.w3.org/2000/svg' viewBox='0 0 18.66 29.68'><defs><style>.body{fill:${fillColor};}.eyes{fill:${eyesColor};}.outline{fill:${outlineColor};}</style></defs><path class='body' d='M18,8A9.13,9.13,0,0,0,10.89.62,10.88,10.88,0,0,0,9.33.49,10.88,10.88,0,0,0,7.77.62,9.13,9.13,0,0,0,.66,8a12.92,12.92,0,0,0,1.19,8.25C2.68,18.09,7.47,27.6,9.07,29c0,.12.11.19.19.19l.07,0,.07,0c.08,0,.15-.07.19-.19,1.6-1.41,6.39-10.92,7.22-12.8A12.92,12.92,0,0,0,18,8Z'/><path class='outline' d='M9.23,29.6a.57.57,0,0,1-.5-.35C7,27.57,2.24,18.09,1.48,16.38A13.57,13.57,0,0,1,.26,7.87C1.18,3.78,4,.92,7.7.23h0A8.38,8.38,0,0,1,11,.24h0c3.74.69,6.52,3.55,7.44,7.64a13.57,13.57,0,0,1-1.22,8.51c-.76,1.71-5.5,11.19-7.25,12.87a.57.57,0,0,1-.55.35H9.23ZM8,1,7.85,1a8.68,8.68,0,0,0-6.8,7C.5,10.52.86,13,2.22,16.05c.9,2,5.62,11.32,7.11,12.65,1.49-1.33,6.21-10.63,7.11-12.65,1.36-3.07,1.72-5.53,1.17-8h0a8.68,8.68,0,0,0-6.8-7l-.12,0A10.47,10.47,0,0,0,9.33.89,10.3,10.3,0,0,0,8,1Z'/><rect class='outline' x='3.12' y='6.34' width='12.53' height='7.76' rx='3.88'/><rect class='eyes' x='5.02' y='7.82' width='2.16' height='2.34' rx='1.02'/><rect class='eyes' x='11.25' y='7.82' width='2.16' height='2.34' rx='1.02'/><path class='eyes' d='M9.24,12.76A3.57,3.57,0,0,1,7,12a.4.4,0,1,1,.53-.61,2.78,2.78,0,0,0,3.49,0,.4.4,0,0,1,.48.65A3.71,3.71,0,0,1,9.24,12.76Z'/></svg>`;
|
||||||
};
|
};
|
||||||
|
|
||||||
@ -45,13 +44,17 @@ const Map = ({
|
|||||||
interactive = false,
|
interactive = false,
|
||||||
}: Props): JSX.Element => {
|
}: Props): JSX.Element => {
|
||||||
const theme = useTheme();
|
const theme = useTheme();
|
||||||
const { baseUrl } = useContext<UseAppStoreType>(AppContext);
|
const { hostUrl } = useContext<UseAppStoreType>(AppContext);
|
||||||
const [worldmap, setWorldmap] = useState<GeoJsonObject | undefined>();
|
const [worldmap, setWorldmap] = useState<GeoJsonObject | undefined>();
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
if (!worldmap) {
|
getWorldmapGeojson(apiClient, hostUrl)
|
||||||
getWorldmapGeojson(apiClient, baseUrl).then(setWorldmap);
|
.then((data) => {
|
||||||
}
|
setWorldmap(data);
|
||||||
|
})
|
||||||
|
.catch((error) => {
|
||||||
|
console.error('Error:', error);
|
||||||
|
});
|
||||||
}, []);
|
}, []);
|
||||||
|
|
||||||
const RobotMarker = (
|
const RobotMarker = (
|
||||||
@ -59,8 +62,8 @@ const Map = ({
|
|||||||
position: [number, number],
|
position: [number, number],
|
||||||
orderType: number,
|
orderType: number,
|
||||||
order?: PublicOrder,
|
order?: PublicOrder,
|
||||||
) => {
|
): JSX.Element => {
|
||||||
const fillColor = orderType == 1 ? theme.palette.primary.main : theme.palette.secondary.main;
|
const fillColor = orderType === 1 ? theme.palette.primary.main : theme.palette.secondary.main;
|
||||||
const outlineColor = 'black';
|
const outlineColor = 'black';
|
||||||
const eyesColor = 'white';
|
const eyesColor = 'white';
|
||||||
|
|
||||||
@ -77,10 +80,12 @@ const Map = ({
|
|||||||
})
|
})
|
||||||
}
|
}
|
||||||
eventHandlers={{
|
eventHandlers={{
|
||||||
click: (_event: LeafletMouseEvent) => order?.id && onOrderClicked(order.id),
|
click: (_event: LeafletMouseEvent) => {
|
||||||
|
order?.id && onOrderClicked(order.id);
|
||||||
|
},
|
||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
{order && (
|
{order != null && (
|
||||||
<Tooltip direction='top'>
|
<Tooltip direction='top'>
|
||||||
<OrderTooltip order={order} />
|
<OrderTooltip order={order} />
|
||||||
</Tooltip>
|
</Tooltip>
|
||||||
@ -89,7 +94,7 @@ const Map = ({
|
|||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|
||||||
const LocationMarker = () => {
|
const LocationMarker = (): JSX.Element => {
|
||||||
useMapEvents({
|
useMapEvents({
|
||||||
click(event: LeafletMouseEvent) {
|
click(event: LeafletMouseEvent) {
|
||||||
if (interactive) {
|
if (interactive) {
|
||||||
@ -98,16 +103,16 @@ const Map = ({
|
|||||||
},
|
},
|
||||||
});
|
});
|
||||||
|
|
||||||
return position ? RobotMarker('marker', position, orderType || 0) : <></>;
|
return position != null ? RobotMarker('marker', position, orderType ?? 0) : <></>;
|
||||||
};
|
};
|
||||||
|
|
||||||
const getOrderMarkers = () => {
|
const getOrderMarkers = (): JSX.Element => {
|
||||||
if (orders.length < 1) return <></>;
|
if (orders.length < 1) return <></>;
|
||||||
return (
|
return (
|
||||||
<MarkerClusterGroup showCoverageOnHover={true} disableClusteringAtZoom={14}>
|
<MarkerClusterGroup showCoverageOnHover={true} disableClusteringAtZoom={14}>
|
||||||
{orders.map((order) => {
|
{orders.map((order) => {
|
||||||
if (!order.latitude || !order.longitude) return <></>;
|
if (!order?.latitude || !order?.longitude) return <></>;
|
||||||
return RobotMarker(order.id, [order.latitude, order.longitude], order.type || 0, order);
|
return RobotMarker(order.id, [order.latitude, order.longitude], order.type ?? 0, order);
|
||||||
})}
|
})}
|
||||||
</MarkerClusterGroup>
|
</MarkerClusterGroup>
|
||||||
);
|
);
|
||||||
@ -117,12 +122,12 @@ const Map = ({
|
|||||||
<MapContainer
|
<MapContainer
|
||||||
maxZoom={15}
|
maxZoom={15}
|
||||||
center={center ?? [0, 0]}
|
center={center ?? [0, 0]}
|
||||||
zoom={zoom ? zoom : 2}
|
zoom={zoom ?? 2}
|
||||||
attributionControl={false}
|
attributionControl={false}
|
||||||
style={{ height: '100%', width: '100%', backgroundColor: theme.palette.background.paper }}
|
style={{ height: '100%', width: '100%', backgroundColor: theme.palette.background.paper }}
|
||||||
>
|
>
|
||||||
{!useTiles && !worldmap && <LinearProgress />}
|
{!useTiles && worldmap == null && <LinearProgress />}
|
||||||
{!useTiles && worldmap && (
|
{!useTiles && worldmap != null && (
|
||||||
<GeoJSON
|
<GeoJSON
|
||||||
data={worldmap}
|
data={worldmap}
|
||||||
style={{
|
style={{
|
||||||
|
@ -1,21 +1,19 @@
|
|||||||
import React, { useEffect, useState } from 'react';
|
import React, { useEffect, useState, useContext } from 'react';
|
||||||
import { useTranslation } from 'react-i18next';
|
import { useTranslation } from 'react-i18next';
|
||||||
import {
|
import {
|
||||||
Tooltip,
|
Tooltip,
|
||||||
Alert,
|
Alert,
|
||||||
useTheme,
|
|
||||||
IconButton,
|
IconButton,
|
||||||
type TooltipProps,
|
type TooltipProps,
|
||||||
styled,
|
styled,
|
||||||
tooltipClasses,
|
tooltipClasses,
|
||||||
} from '@mui/material';
|
} from '@mui/material';
|
||||||
import { useNavigate } from 'react-router-dom';
|
import { useNavigate } from 'react-router-dom';
|
||||||
import { type Order } from '../../models';
|
|
||||||
import Close from '@mui/icons-material/Close';
|
import Close from '@mui/icons-material/Close';
|
||||||
import { type Page } from '../../basic/NavBar';
|
import { type Page } from '../../basic/NavBar';
|
||||||
|
import { GarageContext, UseGarageStoreType } from '../../contexts/GarageContext';
|
||||||
|
|
||||||
interface NotificationsProps {
|
interface NotificationsProps {
|
||||||
order: Order | undefined;
|
|
||||||
rewards: number | undefined;
|
rewards: number | undefined;
|
||||||
page: Page;
|
page: Page;
|
||||||
openProfile: () => void;
|
openProfile: () => void;
|
||||||
@ -64,7 +62,6 @@ const StyledTooltip = styled(({ className, ...props }: TooltipProps) => (
|
|||||||
}));
|
}));
|
||||||
|
|
||||||
const Notifications = ({
|
const Notifications = ({
|
||||||
order,
|
|
||||||
rewards,
|
rewards,
|
||||||
page,
|
page,
|
||||||
windowWidth,
|
windowWidth,
|
||||||
@ -72,6 +69,7 @@ const Notifications = ({
|
|||||||
}: NotificationsProps): JSX.Element => {
|
}: NotificationsProps): JSX.Element => {
|
||||||
const { t } = useTranslation();
|
const { t } = useTranslation();
|
||||||
const navigate = useNavigate();
|
const navigate = useNavigate();
|
||||||
|
const { garage, orderUpdatedAt } = useContext<UseGarageStoreType>(GarageContext);
|
||||||
|
|
||||||
const [message, setMessage] = useState<NotificationMessage>(emptyNotificationMessage);
|
const [message, setMessage] = useState<NotificationMessage>(emptyNotificationMessage);
|
||||||
const [inFocus, setInFocus] = useState<boolean>(true);
|
const [inFocus, setInFocus] = useState<boolean>(true);
|
||||||
@ -86,8 +84,8 @@ const Notifications = ({
|
|||||||
const position = windowWidth > 60 ? { top: '4em', right: '0em' } : { top: '0.5em', left: '50%' };
|
const position = windowWidth > 60 ? { top: '4em', right: '0em' } : { top: '0.5em', left: '50%' };
|
||||||
const basePageTitle = t('RoboSats - Simple and Private Bitcoin Exchange');
|
const basePageTitle = t('RoboSats - Simple and Private Bitcoin Exchange');
|
||||||
|
|
||||||
const moveToOrderPage = function () {
|
const moveToOrderPage = function (): void {
|
||||||
navigate(`/order/${order?.id}`);
|
navigate(`/order/${String(garage.getOrder()?.id)}`);
|
||||||
setShow(false);
|
setShow(false);
|
||||||
};
|
};
|
||||||
|
|
||||||
@ -108,7 +106,7 @@ const Notifications = ({
|
|||||||
|
|
||||||
const Messages: MessagesProps = {
|
const Messages: MessagesProps = {
|
||||||
bondLocked: {
|
bondLocked: {
|
||||||
title: t(`${order?.is_maker ? 'Maker' : 'Taker'} bond locked`),
|
title: t(`${garage.getOrder()?.is_maker === true ? 'Maker' : 'Taker'} bond locked`),
|
||||||
severity: 'info',
|
severity: 'info',
|
||||||
onClick: moveToOrderPage,
|
onClick: moveToOrderPage,
|
||||||
sound: audio.ding,
|
sound: audio.ding,
|
||||||
@ -208,28 +206,32 @@ const Notifications = ({
|
|||||||
},
|
},
|
||||||
};
|
};
|
||||||
|
|
||||||
const notify = function (message: NotificationMessage) {
|
const notify = function (message: NotificationMessage): void {
|
||||||
if (message.title != '') {
|
if (message.title !== '') {
|
||||||
setMessage(message);
|
setMessage(message);
|
||||||
setShow(true);
|
setShow(true);
|
||||||
setTimeout(() => {
|
setTimeout(() => {
|
||||||
setShow(false);
|
setShow(false);
|
||||||
}, message.timeout);
|
}, message.timeout);
|
||||||
if (message.sound != null) {
|
if (message.sound != null) {
|
||||||
message.sound.play();
|
void message.sound.play();
|
||||||
}
|
}
|
||||||
if (!inFocus) {
|
if (!inFocus) {
|
||||||
setTitleAnimation(
|
setTitleAnimation(
|
||||||
setInterval(function () {
|
setInterval(function () {
|
||||||
const title = document.title;
|
const title = document.title;
|
||||||
document.title = title == basePageTitle ? message.pageTitle : basePageTitle;
|
document.title = title === basePageTitle ? message.pageTitle : basePageTitle;
|
||||||
}, 1000),
|
}, 1000),
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
const handleStatusChange = function (oldStatus: number | undefined, status: number) {
|
const handleStatusChange = function (oldStatus: number | undefined, status: number): void {
|
||||||
|
const order = garage.getOrder();
|
||||||
|
|
||||||
|
if (order === null) return;
|
||||||
|
|
||||||
let message = emptyNotificationMessage;
|
let message = emptyNotificationMessage;
|
||||||
|
|
||||||
// Order status descriptions:
|
// Order status descriptions:
|
||||||
@ -252,37 +254,35 @@ const Notifications = ({
|
|||||||
// 17: 'Maker lost dispute'
|
// 17: 'Maker lost dispute'
|
||||||
// 18: 'Taker lost dispute'
|
// 18: 'Taker lost dispute'
|
||||||
|
|
||||||
if (status == 5 && oldStatus != 5) {
|
if (status === 5 && oldStatus !== 5) {
|
||||||
message = Messages.expired;
|
message = Messages.expired;
|
||||||
} else if (oldStatus == undefined) {
|
} else if (oldStatus === undefined) {
|
||||||
message = emptyNotificationMessage;
|
message = emptyNotificationMessage;
|
||||||
} else if (order?.is_maker && status > 0 && oldStatus == 0) {
|
} else if (order.is_maker && status > 0 && oldStatus === 0) {
|
||||||
message = Messages.bondLocked;
|
message = Messages.bondLocked;
|
||||||
} else if (order?.is_taker && status > 5 && oldStatus <= 5) {
|
} else if (order.is_taker && status > 5 && oldStatus <= 5) {
|
||||||
message = Messages.bondLocked;
|
message = Messages.bondLocked;
|
||||||
} else if (order?.is_maker && status > 5 && oldStatus <= 5) {
|
} else if (order.is_maker && status > 5 && oldStatus <= 5) {
|
||||||
message = Messages.taken;
|
message = Messages.taken;
|
||||||
} else if (order?.is_seller && status > 7 && oldStatus < 7) {
|
} else if (order.is_seller && status > 7 && oldStatus < 7) {
|
||||||
message = Messages.escrowLocked;
|
message = Messages.escrowLocked;
|
||||||
} else if ([9, 10].includes(status) && oldStatus < 9) {
|
} else if ([9, 10].includes(status) && oldStatus < 9) {
|
||||||
message = Messages.chat;
|
message = Messages.chat;
|
||||||
} else if (order?.is_seller && [13, 14, 15].includes(status) && oldStatus < 13) {
|
} else if (order.is_seller && [13, 14, 15].includes(status) && oldStatus < 13) {
|
||||||
message = Messages.successful;
|
message = Messages.successful;
|
||||||
} else if (order?.is_buyer && status == 14 && oldStatus != 14) {
|
} else if (order.is_buyer && status === 14 && oldStatus !== 14) {
|
||||||
message = Messages.successful;
|
message = Messages.successful;
|
||||||
} else if (order?.is_buyer && status == 15 && oldStatus < 14) {
|
} else if (order.is_buyer && status === 15 && oldStatus < 14) {
|
||||||
message = Messages.routingFailed;
|
message = Messages.routingFailed;
|
||||||
} else if (status == 11 && oldStatus < 11) {
|
} else if (status === 11 && oldStatus < 11) {
|
||||||
message = Messages.dispute;
|
|
||||||
} else if (status == 11 && oldStatus < 11) {
|
|
||||||
message = Messages.dispute;
|
message = Messages.dispute;
|
||||||
} else if (
|
} else if (
|
||||||
((order?.is_maker && status == 18) || (order?.is_taker && status == 17)) &&
|
((order.is_maker && status === 18) || (order.is_taker && status === 17)) &&
|
||||||
oldStatus < 17
|
oldStatus < 17
|
||||||
) {
|
) {
|
||||||
message = Messages.disputeWinner;
|
message = Messages.disputeWinner;
|
||||||
} else if (
|
} else if (
|
||||||
((order?.is_maker && status == 17) || (order?.is_taker && status == 18)) &&
|
((order.is_maker && status === 17) || (order.is_taker && status === 18)) &&
|
||||||
oldStatus < 17
|
oldStatus < 17
|
||||||
) {
|
) {
|
||||||
message = Messages.disputeLoser;
|
message = Messages.disputeLoser;
|
||||||
@ -293,20 +293,23 @@ const Notifications = ({
|
|||||||
|
|
||||||
// Notify on order status change
|
// Notify on order status change
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
if (order != undefined && order.status != oldOrderStatus) {
|
const order = garage.getOrder();
|
||||||
handleStatusChange(oldOrderStatus, order.status);
|
if (order !== null) {
|
||||||
setOldOrderStatus(order.status);
|
if (order.status !== oldOrderStatus) {
|
||||||
} else if (order != undefined && order.chat_last_index > oldChatIndex) {
|
handleStatusChange(oldOrderStatus, order.status);
|
||||||
if (page != 'order') {
|
setOldOrderStatus(order.status);
|
||||||
notify(Messages.chatMessage);
|
} else if (order.chat_last_index > oldChatIndex) {
|
||||||
|
if (page !== 'order') {
|
||||||
|
notify(Messages.chatMessage);
|
||||||
|
}
|
||||||
|
setOldChatIndex(order.chat_last_index);
|
||||||
}
|
}
|
||||||
setOldChatIndex(order.chat_last_index);
|
|
||||||
}
|
}
|
||||||
}, [order]);
|
}, [orderUpdatedAt]);
|
||||||
|
|
||||||
// Notify on rewards change
|
// Notify on rewards change
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
if (rewards != undefined) {
|
if (rewards !== undefined) {
|
||||||
if (rewards > oldRewards) {
|
if (rewards > oldRewards) {
|
||||||
notify(Messages.rewards);
|
notify(Messages.rewards);
|
||||||
}
|
}
|
||||||
@ -316,7 +319,7 @@ const Notifications = ({
|
|||||||
|
|
||||||
// Set blinking page title and clear on visibility change > infocus
|
// Set blinking page title and clear on visibility change > infocus
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
if (titleAnimation != undefined && inFocus) {
|
if (titleAnimation !== undefined && inFocus) {
|
||||||
clearInterval(titleAnimation);
|
clearInterval(titleAnimation);
|
||||||
}
|
}
|
||||||
}, [inFocus]);
|
}, [inFocus]);
|
||||||
|
@ -7,9 +7,9 @@ interface Props {
|
|||||||
totalSecsExp: number;
|
totalSecsExp: number;
|
||||||
}
|
}
|
||||||
|
|
||||||
const LinearDeterminate = ({ expiresAt, totalSecsExp }: Props): JSX.Element => {
|
const LinearDeterminate: React.FC<Props> = ({ expiresAt, totalSecsExp }) => {
|
||||||
const timePercentLeft = function () {
|
const timePercentLeft = function (): number {
|
||||||
if (expiresAt && totalSecsExp) {
|
if (Boolean(expiresAt) && Boolean(totalSecsExp)) {
|
||||||
const lapseTime = calcTimeDelta(new Date(expiresAt)).total / 1000;
|
const lapseTime = calcTimeDelta(new Date(expiresAt)).total / 1000;
|
||||||
return (lapseTime / totalSecsExp) * 100;
|
return (lapseTime / totalSecsExp) * 100;
|
||||||
} else {
|
} else {
|
||||||
|
@ -25,13 +25,13 @@ import { type Order, type Info } from '../../models';
|
|||||||
import { ConfirmationDialog } from '../Dialogs';
|
import { ConfirmationDialog } from '../Dialogs';
|
||||||
import { LoadingButton } from '@mui/lab';
|
import { LoadingButton } from '@mui/lab';
|
||||||
import { computeSats } from '../../utils';
|
import { computeSats } from '../../utils';
|
||||||
import { AppContext, type UseAppStoreType } from '../../contexts/AppContext';
|
import { GarageContext, UseGarageStoreType } from '../../contexts/GarageContext';
|
||||||
|
import { UseAppStoreType, AppContext } from '../../contexts/AppContext';
|
||||||
|
import { UseFederationStoreType, FederationContext } from '../../contexts/FederationContext';
|
||||||
|
|
||||||
interface TakeButtonProps {
|
interface TakeButtonProps {
|
||||||
order: Order;
|
|
||||||
setOrder: (state: Order) => void;
|
|
||||||
baseUrl: string;
|
baseUrl: string;
|
||||||
info: Info;
|
info?: Info;
|
||||||
onClickGenerateRobot?: () => void;
|
onClickGenerateRobot?: () => void;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -42,15 +42,15 @@ interface OpenDialogsProps {
|
|||||||
const closeAll = { inactiveMaker: false, confirmation: false };
|
const closeAll = { inactiveMaker: false, confirmation: false };
|
||||||
|
|
||||||
const TakeButton = ({
|
const TakeButton = ({
|
||||||
order,
|
|
||||||
setOrder,
|
|
||||||
baseUrl,
|
baseUrl,
|
||||||
info,
|
info,
|
||||||
onClickGenerateRobot = () => null,
|
onClickGenerateRobot = () => null,
|
||||||
}: TakeButtonProps): JSX.Element => {
|
}: TakeButtonProps): JSX.Element => {
|
||||||
const { t } = useTranslation();
|
const { t } = useTranslation();
|
||||||
const theme = useTheme();
|
const theme = useTheme();
|
||||||
const { robot } = useContext<UseAppStoreType>(AppContext);
|
const { settings, origin, hostUrl } = useContext<UseAppStoreType>(AppContext);
|
||||||
|
const { garage, orderUpdatedAt } = useContext<UseGarageStoreType>(GarageContext);
|
||||||
|
const { federation, focusedCoordinator } = useContext<UseFederationStoreType>(FederationContext);
|
||||||
|
|
||||||
const [takeAmount, setTakeAmount] = useState<string>('');
|
const [takeAmount, setTakeAmount] = useState<string>('');
|
||||||
const [badRequest, setBadRequest] = useState<string>('');
|
const [badRequest, setBadRequest] = useState<string>('');
|
||||||
@ -58,11 +58,15 @@ const TakeButton = ({
|
|||||||
const [open, setOpen] = useState<OpenDialogsProps>(closeAll);
|
const [open, setOpen] = useState<OpenDialogsProps>(closeAll);
|
||||||
const [satoshis, setSatoshis] = useState<string>('');
|
const [satoshis, setSatoshis] = useState<string>('');
|
||||||
|
|
||||||
const satoshisNow = () => {
|
const satoshisNow = (): string | undefined => {
|
||||||
|
const order = garage.getOrder();
|
||||||
|
|
||||||
|
if (order === null) return;
|
||||||
|
|
||||||
const tradeFee = info?.taker_fee ?? 0;
|
const tradeFee = info?.taker_fee ?? 0;
|
||||||
const defaultRoutingBudget = 0.001;
|
const defaultRoutingBudget = 0.001;
|
||||||
const btc_now = order.satoshis_now / 100000000;
|
const btcNow = order.satoshis_now / 100000000;
|
||||||
const rate = order.amount ? order.amount / btc_now : order.max_amount / btc_now;
|
const rate = order.amount != null ? order.amount / btcNow : order.max_amount / btcNow;
|
||||||
const amount = order.currency === 1000 ? Number(takeAmount) / 100000000 : Number(takeAmount);
|
const amount = order.currency === 1000 ? Number(takeAmount) / 100000000 : Number(takeAmount);
|
||||||
const satoshis = computeSats({
|
const satoshis = computeSats({
|
||||||
amount,
|
amount,
|
||||||
@ -74,12 +78,13 @@ const TakeButton = ({
|
|||||||
};
|
};
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
setSatoshis(satoshisNow());
|
setSatoshis(satoshisNow() ?? '');
|
||||||
}, [order.satoshis_now, takeAmount, info]);
|
}, [orderUpdatedAt, takeAmount, info]);
|
||||||
|
|
||||||
const currencyCode: string = order.currency == 1000 ? 'Sats' : currencies[`${order.currency}`];
|
const currencyCode: string =
|
||||||
|
garage.getOrder()?.currency === 1000 ? 'Sats' : currencies[`${garage.getOrder()?.currency}`];
|
||||||
|
|
||||||
const InactiveMakerDialog = function () {
|
const InactiveMakerDialog = function (): JSX.Element {
|
||||||
return (
|
return (
|
||||||
<Dialog
|
<Dialog
|
||||||
open={open.inactiveMaker}
|
open={open.inactiveMaker}
|
||||||
@ -116,7 +121,15 @@ const TakeButton = ({
|
|||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|
||||||
const countdownTakeOrderRenderer = function ({ seconds, completed }) {
|
interface countdownTakeOrderRendererProps {
|
||||||
|
seconds: number;
|
||||||
|
completed: boolean;
|
||||||
|
}
|
||||||
|
|
||||||
|
const countdownTakeOrderRenderer = function ({
|
||||||
|
seconds,
|
||||||
|
completed,
|
||||||
|
}: countdownTakeOrderRendererProps): JSX.Element {
|
||||||
if (isNaN(seconds) || completed) {
|
if (isNaN(seconds) || completed) {
|
||||||
return takeOrderButton();
|
return takeOrderButton();
|
||||||
} else {
|
} else {
|
||||||
@ -132,8 +145,8 @@ const TakeButton = ({
|
|||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
const handleTakeAmountChange = function (e) {
|
const handleTakeAmountChange = function (e: React.ChangeEvent<HTMLInputElement>): void {
|
||||||
if (e.target.value != '' && e.target.value != null) {
|
if (e.target.value !== '' && e.target.value != null) {
|
||||||
setTakeAmount(`${parseFloat(e.target.value)}`);
|
setTakeAmount(`${parseFloat(e.target.value)}`);
|
||||||
} else {
|
} else {
|
||||||
setTakeAmount(e.target.value);
|
setTakeAmount(e.target.value);
|
||||||
@ -141,18 +154,22 @@ const TakeButton = ({
|
|||||||
};
|
};
|
||||||
|
|
||||||
const amountHelperText = useMemo(() => {
|
const amountHelperText = useMemo(() => {
|
||||||
const amount = order.currency == 1000 ? Number(takeAmount) / 100000000 : Number(takeAmount);
|
const order = garage.getOrder();
|
||||||
if (amount < Number(order.min_amount) && takeAmount != '') {
|
|
||||||
|
if (order === null) return;
|
||||||
|
|
||||||
|
const amount = order.currency === 1000 ? Number(takeAmount) / 100000000 : Number(takeAmount);
|
||||||
|
if (amount < Number(order.min_amount) && takeAmount !== '') {
|
||||||
return t('Too low');
|
return t('Too low');
|
||||||
} else if (amount > Number(order.max_amount) && takeAmount != '') {
|
} else if (amount > Number(order.max_amount) && takeAmount !== '') {
|
||||||
return t('Too high');
|
return t('Too high');
|
||||||
} else {
|
} else {
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
}, [order, takeAmount]);
|
}, [orderUpdatedAt, takeAmount]);
|
||||||
|
|
||||||
const onTakeOrderClicked = function () {
|
const onTakeOrderClicked = function (): void {
|
||||||
if (order.maker_status == 'Inactive') {
|
if (garage.getOrder()?.maker_status === 'Inactive') {
|
||||||
setOpen({ inactiveMaker: true, confirmation: false });
|
setOpen({ inactiveMaker: true, confirmation: false });
|
||||||
} else {
|
} else {
|
||||||
setOpen({ inactiveMaker: false, confirmation: true });
|
setOpen({ inactiveMaker: false, confirmation: true });
|
||||||
@ -160,17 +177,18 @@ const TakeButton = ({
|
|||||||
};
|
};
|
||||||
|
|
||||||
const invalidTakeAmount = useMemo(() => {
|
const invalidTakeAmount = useMemo(() => {
|
||||||
const amount = order.currency == 1000 ? Number(takeAmount) / 100000000 : Number(takeAmount);
|
const order = garage.getOrder();
|
||||||
|
const amount = order?.currency === 1000 ? Number(takeAmount) / 100000000 : Number(takeAmount);
|
||||||
return (
|
return (
|
||||||
amount < Number(order.min_amount) ||
|
amount < Number(order?.min_amount) ||
|
||||||
amount > Number(order.max_amount) ||
|
amount > Number(order?.max_amount) ||
|
||||||
takeAmount == '' ||
|
takeAmount === '' ||
|
||||||
takeAmount == null
|
takeAmount == null
|
||||||
);
|
);
|
||||||
}, [takeAmount, order]);
|
}, [takeAmount, orderUpdatedAt]);
|
||||||
|
|
||||||
const takeOrderButton = function () {
|
const takeOrderButton = function (): JSX.Element {
|
||||||
if (order.has_range) {
|
if (garage.getOrder()?.has_range) {
|
||||||
return (
|
return (
|
||||||
<Box
|
<Box
|
||||||
sx={{
|
sx={{
|
||||||
@ -209,8 +227,8 @@ const TakeButton = ({
|
|||||||
required={true}
|
required={true}
|
||||||
value={takeAmount}
|
value={takeAmount}
|
||||||
inputProps={{
|
inputProps={{
|
||||||
min: order.min_amount,
|
min: garage.getOrder()?.min_amount,
|
||||||
max: order.max_amount,
|
max: garage.getOrder()?.max_amount,
|
||||||
style: { textAlign: 'center' },
|
style: { textAlign: 'center' },
|
||||||
}}
|
}}
|
||||||
onChange={handleTakeAmountChange}
|
onChange={handleTakeAmountChange}
|
||||||
@ -260,10 +278,10 @@ const TakeButton = ({
|
|||||||
</div>
|
</div>
|
||||||
</Grid>
|
</Grid>
|
||||||
</Grid>
|
</Grid>
|
||||||
{satoshis != '0' && satoshis != '' && !invalidTakeAmount ? (
|
{satoshis !== '0' && satoshis !== '' && !invalidTakeAmount ? (
|
||||||
<Grid item>
|
<Grid item>
|
||||||
<FormHelperText sx={{ position: 'relative', top: '0.15em' }}>
|
<FormHelperText sx={{ position: 'relative', top: '0.15em' }}>
|
||||||
{order.type === 1
|
{garage.getOrder()?.type === 1
|
||||||
? t('You will receive {{satoshis}} Sats (Approx)', { satoshis })
|
? t('You will receive {{satoshis}} Sats (Approx)', { satoshis })
|
||||||
: t('You will send {{satoshis}} Sats (Approx)', { satoshis })}
|
: t('You will send {{satoshis}} Sats (Approx)', { satoshis })}
|
||||||
</FormHelperText>
|
</FormHelperText>
|
||||||
@ -296,33 +314,44 @@ const TakeButton = ({
|
|||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
const takeOrder = function () {
|
const takeOrder = function (): void {
|
||||||
|
if (!focusedCoordinator) return;
|
||||||
|
|
||||||
setLoadingTake(true);
|
setLoadingTake(true);
|
||||||
|
const { url } = federation
|
||||||
|
.getCoordinator(focusedCoordinator)
|
||||||
|
.getEndpoint(settings.network, origin, settings.selfhostedClient, hostUrl);
|
||||||
apiClient
|
apiClient
|
||||||
.post(
|
.post(
|
||||||
baseUrl,
|
url,
|
||||||
'/api/order/?order_id=' + order.id,
|
`/api/order/?order_id=${String(garage.getOrder()?.id)}`,
|
||||||
{
|
{
|
||||||
action: 'take',
|
action: 'take',
|
||||||
amount: order.currency == 1000 ? takeAmount / 100000000 : takeAmount,
|
amount: garage.getOrder()?.currency === 1000 ? takeAmount / 100000000 : takeAmount,
|
||||||
},
|
},
|
||||||
{ tokenSHA256: robot.tokenSHA256 },
|
{ tokenSHA256: garage.getRobot().tokenSHA256 },
|
||||||
)
|
)
|
||||||
.then((data) => {
|
.then((data) => {
|
||||||
setLoadingTake(false);
|
setLoadingTake(false);
|
||||||
if (data.bad_request) {
|
if (data?.bad_request !== undefined) {
|
||||||
setBadRequest(data.bad_request);
|
setBadRequest(data.bad_request);
|
||||||
} else {
|
} else {
|
||||||
setOrder(data);
|
garage.updateOrder(data as Order);
|
||||||
setBadRequest('');
|
setBadRequest('');
|
||||||
}
|
}
|
||||||
|
})
|
||||||
|
.catch(() => {
|
||||||
|
setBadRequest('Request error');
|
||||||
});
|
});
|
||||||
};
|
};
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Box>
|
<Box>
|
||||||
<Countdown date={new Date(order.penalty)} renderer={countdownTakeOrderRenderer} />
|
<Countdown
|
||||||
{badRequest != '' ? (
|
date={new Date(garage.getOrder()?.penalty ?? '')}
|
||||||
|
renderer={countdownTakeOrderRenderer}
|
||||||
|
/>
|
||||||
|
{badRequest !== '' ? (
|
||||||
<Box style={{ padding: '0.5em' }}>
|
<Box style={{ padding: '0.5em' }}>
|
||||||
<Typography align='center' color='secondary'>
|
<Typography align='center' color='secondary'>
|
||||||
{t(badRequest)}
|
{t(badRequest)}
|
||||||
@ -342,7 +371,7 @@ const TakeButton = ({
|
|||||||
setLoadingTake(true);
|
setLoadingTake(true);
|
||||||
setOpen(closeAll);
|
setOpen(closeAll);
|
||||||
}}
|
}}
|
||||||
hasRobot={robot.avatarLoaded}
|
hasRobot={garage.getRobot().avatarLoaded}
|
||||||
onClickGenerateRobot={onClickGenerateRobot}
|
onClickGenerateRobot={onClickGenerateRobot}
|
||||||
/>
|
/>
|
||||||
<InactiveMakerDialog />
|
<InactiveMakerDialog />
|
||||||
|
@ -1,4 +1,4 @@
|
|||||||
import React, { useState, useMemo } from 'react';
|
import React, { useState, useMemo, useContext } from 'react';
|
||||||
import { useTranslation } from 'react-i18next';
|
import { useTranslation } from 'react-i18next';
|
||||||
import {
|
import {
|
||||||
List,
|
List,
|
||||||
@ -15,7 +15,7 @@ import {
|
|||||||
Typography,
|
Typography,
|
||||||
IconButton,
|
IconButton,
|
||||||
Tooltip,
|
Tooltip,
|
||||||
Button,
|
ListItemButton,
|
||||||
} from '@mui/material';
|
} from '@mui/material';
|
||||||
|
|
||||||
import Countdown, { type CountdownRenderProps, zeroPad } from 'react-countdown';
|
import Countdown, { type CountdownRenderProps, zeroPad } from 'react-countdown';
|
||||||
@ -36,42 +36,48 @@ import { PaymentStringAsIcons } from '../../components/PaymentMethods';
|
|||||||
import { FlagWithProps, SendReceiveIcon } from '../Icons';
|
import { FlagWithProps, SendReceiveIcon } from '../Icons';
|
||||||
import LinearDeterminate from './LinearDeterminate';
|
import LinearDeterminate from './LinearDeterminate';
|
||||||
|
|
||||||
import { type Order, type Info } from '../../models';
|
import type { Order, Coordinator } from '../../models';
|
||||||
import { statusBadgeColor, pn, amountToString, computeSats } from '../../utils';
|
import { statusBadgeColor, pn, amountToString, computeSats } from '../../utils';
|
||||||
import TakeButton from './TakeButton';
|
import TakeButton from './TakeButton';
|
||||||
import { F2fMapDialog } from '../Dialogs';
|
import { F2fMapDialog } from '../Dialogs';
|
||||||
|
import { AppContext, UseAppStoreType } from '../../contexts/AppContext';
|
||||||
|
import { GarageContext, UseGarageStoreType } from '../../contexts/GarageContext';
|
||||||
|
import { UseFederationStoreType, FederationContext } from '../../contexts/FederationContext';
|
||||||
|
|
||||||
interface OrderDetailsProps {
|
interface OrderDetailsProps {
|
||||||
order: Order;
|
coordinator: Coordinator;
|
||||||
setOrder: (state: Order) => void;
|
onClickCoordinator?: () => void;
|
||||||
info: Info;
|
|
||||||
baseUrl: string;
|
baseUrl: string;
|
||||||
hasRobot: boolean;
|
|
||||||
onClickGenerateRobot?: () => void;
|
onClickGenerateRobot?: () => void;
|
||||||
}
|
}
|
||||||
|
|
||||||
const OrderDetails = ({
|
const OrderDetails = ({
|
||||||
order,
|
coordinator,
|
||||||
info,
|
onClickCoordinator = () => null,
|
||||||
setOrder,
|
|
||||||
baseUrl,
|
baseUrl,
|
||||||
hasRobot,
|
|
||||||
onClickGenerateRobot = () => null,
|
onClickGenerateRobot = () => null,
|
||||||
}: OrderDetailsProps): JSX.Element => {
|
}: OrderDetailsProps): JSX.Element => {
|
||||||
const { t } = useTranslation();
|
const { t } = useTranslation();
|
||||||
|
const { hostUrl } = useContext<UseAppStoreType>(AppContext);
|
||||||
const theme = useTheme();
|
const theme = useTheme();
|
||||||
|
const { orderUpdatedAt } = useContext<UseGarageStoreType>(GarageContext);
|
||||||
|
const { currentOrder } = useContext<UseFederationStoreType>(FederationContext);
|
||||||
|
|
||||||
const currencyCode: string = currencies[`${order.currency}`];
|
const currencyCode: string = currencies[(currentOrder.order?.currency ?? 1).toString()];
|
||||||
const [showSatsDetails, setShowSatsDetails] = useState<boolean>(false);
|
const [showSatsDetails, setShowSatsDetails] = useState<boolean>(false);
|
||||||
const [openWorldmap, setOpenWorldmap] = useState<boolean>(false);
|
const [openWorldmap, setOpenWorldmap] = useState<boolean>(false);
|
||||||
|
|
||||||
const amountString = useMemo(() => {
|
const amountString = useMemo(() => {
|
||||||
// precision to 8 decimal if currency is BTC otherwise 4 decimals
|
// precision to 8 decimal if currency is BTC otherwise 4 decimals
|
||||||
if (order.currency == 1000) {
|
const order = currentOrder.order;
|
||||||
|
|
||||||
|
if (order === null) return;
|
||||||
|
|
||||||
|
if (order.currency === 1000) {
|
||||||
return (
|
return (
|
||||||
amountToString(
|
amountToString(
|
||||||
order.amount * 100000000,
|
(order.amount * 100000000).toString(),
|
||||||
order.amount ? false : order.has_range,
|
order.amount > 0 ? false : order.has_range,
|
||||||
order.min_amount * 100000000,
|
order.min_amount * 100000000,
|
||||||
order.max_amount * 100000000,
|
order.max_amount * 100000000,
|
||||||
) + ' Sats'
|
) + ' Sats'
|
||||||
@ -79,14 +85,14 @@ const OrderDetails = ({
|
|||||||
} else {
|
} else {
|
||||||
return (
|
return (
|
||||||
amountToString(
|
amountToString(
|
||||||
order.amount,
|
order.amount.toString(),
|
||||||
order.amount ? false : order.has_range,
|
order.amount > 0 ? false : order.has_range,
|
||||||
order.min_amount,
|
order.min_amount,
|
||||||
order.max_amount,
|
order.max_amount,
|
||||||
) + ` ${currencyCode}`
|
) + ` ${currencyCode}`
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
}, [order.currency, order.amount, order.min_amount, order.max_amount, order.has_range]);
|
}, [orderUpdatedAt]);
|
||||||
|
|
||||||
// Countdown Renderer callback with condition
|
// Countdown Renderer callback with condition
|
||||||
const countdownRenderer = function ({
|
const countdownRenderer = function ({
|
||||||
@ -95,13 +101,13 @@ const OrderDetails = ({
|
|||||||
minutes,
|
minutes,
|
||||||
seconds,
|
seconds,
|
||||||
completed,
|
completed,
|
||||||
}: CountdownRenderProps) {
|
}: CountdownRenderProps): JSX.Element {
|
||||||
if (completed) {
|
if (completed) {
|
||||||
// Render a completed state
|
// Render a completed state
|
||||||
return <span> {t('The order has expired')}</span>;
|
return <span> {t('The order has expired')}</span>;
|
||||||
} else {
|
} else {
|
||||||
let color = 'inherit';
|
let color = 'inherit';
|
||||||
const fraction_left = total / 1000 / order.total_secs_exp;
|
const fraction_left = total / 1000 / (currentOrder.order?.total_secs_exp ?? 1);
|
||||||
// Make orange at 25% of time left
|
// Make orange at 25% of time left
|
||||||
if (fraction_left < 0.25) {
|
if (fraction_left < 0.25) {
|
||||||
color = theme.palette.warning.main;
|
color = theme.palette.warning.main;
|
||||||
@ -121,18 +127,26 @@ const OrderDetails = ({
|
|||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
const timerRenderer = function (seconds: number) {
|
const timerRenderer = function (seconds: number): JSX.Element {
|
||||||
const hours = Math.floor(seconds / 3600);
|
const hours = Math.floor(seconds / 3600);
|
||||||
const minutes = Math.floor((seconds - hours * 3600) / 60);
|
const minutes = Math.floor((seconds - hours * 3600) / 60);
|
||||||
return (
|
return (
|
||||||
<span>
|
<span>
|
||||||
{hours > 0 ? hours + 'h' : ''} {minutes > 0 ? zeroPad(minutes) + 'm' : ''}{' '}
|
{hours > 0 ? `${hours}h` : ''} {minutes > 0 ? `${zeroPad(minutes)}m` : ''}{' '}
|
||||||
</span>
|
</span>
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|
||||||
// Countdown Renderer callback with condition
|
// Countdown Renderer callback with condition
|
||||||
const countdownPenaltyRenderer = function ({ minutes, seconds, completed }) {
|
const countdownPenaltyRenderer = ({
|
||||||
|
minutes,
|
||||||
|
seconds,
|
||||||
|
completed,
|
||||||
|
}: {
|
||||||
|
minutes: number;
|
||||||
|
seconds: number;
|
||||||
|
completed: boolean;
|
||||||
|
}): JSX.Element => {
|
||||||
if (completed) {
|
if (completed) {
|
||||||
// Render a completed state
|
// Render a completed state
|
||||||
return <span> {t('Penalty lifted, good to go!')}</span>;
|
return <span> {t('Penalty lifted, good to go!')}</span>;
|
||||||
@ -153,15 +167,20 @@ const OrderDetails = ({
|
|||||||
let send: string = '';
|
let send: string = '';
|
||||||
let receive: string = '';
|
let receive: string = '';
|
||||||
let sats: string = '';
|
let sats: string = '';
|
||||||
|
const order = currentOrder.order;
|
||||||
|
|
||||||
const isBuyer = (order.type == 0 && order.is_maker) || (order.type == 1 && !order.is_maker);
|
if (order === null) return {};
|
||||||
const tradeFee = order.is_maker ? info?.maker_fee ?? 0 : info?.taker_fee ?? 0;
|
|
||||||
|
const isBuyer = (order.type === 0 && order.is_maker) || (order.type === 1 && !order.is_maker);
|
||||||
|
const tradeFee = order.is_maker
|
||||||
|
? coordinator.info?.maker_fee ?? 0
|
||||||
|
: coordinator.info?.taker_fee ?? 0;
|
||||||
const defaultRoutingBudget = 0.001;
|
const defaultRoutingBudget = 0.001;
|
||||||
const btc_now = order.satoshis_now / 100000000;
|
const btc_now = order.satoshis_now / 100000000;
|
||||||
const rate = order.amount ? order.amount / btc_now : order.max_amount / btc_now;
|
const rate = order.amount > 0 ? order.amount / btc_now : Number(order.max_amount) / btc_now;
|
||||||
|
|
||||||
if (isBuyer) {
|
if (isBuyer) {
|
||||||
if (order.amount) {
|
if (order.amount > 0) {
|
||||||
sats = computeSats({
|
sats = computeSats({
|
||||||
amount: order.amount,
|
amount: order.amount,
|
||||||
fee: -tradeFee,
|
fee: -tradeFee,
|
||||||
@ -181,7 +200,7 @@ const OrderDetails = ({
|
|||||||
routingBudget: defaultRoutingBudget,
|
routingBudget: defaultRoutingBudget,
|
||||||
rate,
|
rate,
|
||||||
});
|
});
|
||||||
sats = `${min}-${max}`;
|
sats = `${String(min)}-${String(max)}`;
|
||||||
}
|
}
|
||||||
send = t('You send via {{method}} {{amount}}', {
|
send = t('You send via {{method}} {{amount}}', {
|
||||||
amount: amountString,
|
amount: amountString,
|
||||||
@ -192,7 +211,7 @@ const OrderDetails = ({
|
|||||||
amount: sats,
|
amount: sats,
|
||||||
});
|
});
|
||||||
} else {
|
} else {
|
||||||
if (order.amount) {
|
if (order.amount > 0) {
|
||||||
sats = computeSats({
|
sats = computeSats({
|
||||||
amount: order.amount,
|
amount: order.amount,
|
||||||
fee: tradeFee,
|
fee: tradeFee,
|
||||||
@ -209,7 +228,7 @@ const OrderDetails = ({
|
|||||||
fee: tradeFee,
|
fee: tradeFee,
|
||||||
rate,
|
rate,
|
||||||
});
|
});
|
||||||
sats = `${min}-${max}`;
|
sats = `${String(min)}-${String(max)}`;
|
||||||
}
|
}
|
||||||
send = t('You send via Lightning {{amount}} Sats (Approx)', { amount: sats });
|
send = t('You send via Lightning {{amount}} Sats (Approx)', { amount: sats });
|
||||||
receive = t('You receive via {{method}} {{amount}}', {
|
receive = t('You receive via {{method}} {{amount}}', {
|
||||||
@ -218,62 +237,93 @@ const OrderDetails = ({
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
return { send, receive };
|
return { send, receive };
|
||||||
}, [order.currency, order.satoshis_now, order.amount, order.has_range]);
|
}, [orderUpdatedAt]);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Grid container spacing={0}>
|
<Grid container spacing={0}>
|
||||||
<F2fMapDialog
|
<F2fMapDialog
|
||||||
latitude={order.latitude}
|
latitude={currentOrder.order?.latitude}
|
||||||
longitude={order.longitude}
|
longitude={currentOrder.order?.longitude}
|
||||||
open={openWorldmap}
|
open={openWorldmap}
|
||||||
orderType={order.type || 0}
|
orderType={currentOrder.order?.type ?? 0}
|
||||||
zoom={6}
|
zoom={6}
|
||||||
message={t(
|
message={t(
|
||||||
'The pinned location is approximate. The exact location for the meeting place must be exchanged in the encrypted chat.',
|
'The pinned location is approximate. The exact location for the meeting place must be exchanged in the encrypted chat.',
|
||||||
)}
|
)}
|
||||||
onClose={() => setOpenWorldmap(false)}
|
onClose={() => {
|
||||||
|
setOpenWorldmap(false);
|
||||||
|
}}
|
||||||
/>
|
/>
|
||||||
<Grid item xs={12}>
|
<Grid item xs={12}>
|
||||||
<List dense={true}>
|
<List dense={true}>
|
||||||
|
<ListItemButton
|
||||||
|
onClick={() => {
|
||||||
|
onClickCoordinator();
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
{' '}
|
||||||
|
<Grid container direction='row' justifyContent='center' alignItems='center'>
|
||||||
|
<Grid item xs={2}>
|
||||||
|
<RobotAvatar
|
||||||
|
nickname={coordinator.shortAlias}
|
||||||
|
coordinator={true}
|
||||||
|
baseUrl={hostUrl}
|
||||||
|
small={true}
|
||||||
|
/>
|
||||||
|
</Grid>
|
||||||
|
<Grid item xs={4}>
|
||||||
|
<ListItemText primary={coordinator.longAlias} secondary={t('Order host')} />
|
||||||
|
</Grid>
|
||||||
|
</Grid>
|
||||||
|
</ListItemButton>
|
||||||
|
|
||||||
|
<Divider />
|
||||||
|
|
||||||
<ListItem>
|
<ListItem>
|
||||||
<ListItemAvatar sx={{ width: '4em', height: '4em' }}>
|
<ListItemAvatar sx={{ width: '4em', height: '4em' }}>
|
||||||
<RobotAvatar
|
<RobotAvatar
|
||||||
statusColor={statusBadgeColor(order.maker_status)}
|
statusColor={statusBadgeColor(currentOrder.order?.maker_status ?? '')}
|
||||||
nickname={order.maker_nick}
|
nickname={currentOrder.order?.maker_nick}
|
||||||
tooltip={t(order.maker_status)}
|
tooltip={t(currentOrder.order?.maker_status ?? '')}
|
||||||
orderType={order.type}
|
orderType={currentOrder.order?.type}
|
||||||
baseUrl={baseUrl}
|
baseUrl={baseUrl}
|
||||||
small={true}
|
small={true}
|
||||||
/>
|
/>
|
||||||
</ListItemAvatar>
|
</ListItemAvatar>
|
||||||
<ListItemText
|
<ListItemText
|
||||||
primary={`${order.maker_nick} (${
|
primary={`${currentOrder.order?.maker_nick} (${
|
||||||
order.type
|
currentOrder.order?.type === 1
|
||||||
? t(order.currency == 1000 ? 'Swapping Out' : 'Seller')
|
? t(currentOrder.order?.currency === 1000 ? 'Swapping Out' : 'Seller')
|
||||||
: t(order.currency == 1000 ? 'Swapping In' : 'Buyer')
|
: t(currentOrder.order?.currency === 1000 ? 'Swapping In' : 'Buyer')
|
||||||
})`}
|
})`}
|
||||||
secondary={t('Order maker')}
|
secondary={t('Order maker')}
|
||||||
/>
|
/>
|
||||||
</ListItem>
|
</ListItem>
|
||||||
|
|
||||||
<Collapse in={order.is_participant && order.taker_nick !== 'None'}>
|
<Collapse
|
||||||
|
in={currentOrder.order?.is_participant && currentOrder.order?.taker_nick !== 'None'}
|
||||||
|
>
|
||||||
<Divider />
|
<Divider />
|
||||||
<ListItem>
|
<ListItem>
|
||||||
<ListItemText
|
<ListItemText
|
||||||
primary={`${order.taker_nick} (${
|
primary={`${currentOrder.order?.taker_nick} (${
|
||||||
order.type
|
currentOrder.order?.type === 1
|
||||||
? t(order.currency == 1000 ? 'Swapping In' : 'Buyer')
|
? t(currentOrder.order?.currency === 1000 ? 'Swapping In' : 'Buyer')
|
||||||
: t(order.currency == 1000 ? 'Swapping Out' : 'Seller')
|
: t(currentOrder.order?.currency === 1000 ? 'Swapping Out' : 'Seller')
|
||||||
})`}
|
})`}
|
||||||
secondary={t('Order taker')}
|
secondary={t('Order taker')}
|
||||||
/>
|
/>
|
||||||
<ListItemAvatar>
|
<ListItemAvatar>
|
||||||
<RobotAvatar
|
<RobotAvatar
|
||||||
avatarClass='smallAvatar'
|
avatarClass='smallAvatar'
|
||||||
statusColor={statusBadgeColor(order.taker_status)}
|
statusColor={statusBadgeColor(currentOrder.order?.taker_status ?? '')}
|
||||||
nickname={order.taker_nick == 'None' ? undefined : order.taker_nick}
|
nickname={
|
||||||
tooltip={t(order.taker_status)}
|
currentOrder.order?.taker_nick === 'None'
|
||||||
orderType={order.type === 0 ? 1 : 0}
|
? undefined
|
||||||
|
: currentOrder.order?.taker_nick
|
||||||
|
}
|
||||||
|
tooltip={t(currentOrder.order?.taker_status ?? '')}
|
||||||
|
orderType={currentOrder.order?.type === 0 ? 1 : 0}
|
||||||
baseUrl={baseUrl}
|
baseUrl={baseUrl}
|
||||||
small={true}
|
small={true}
|
||||||
/>
|
/>
|
||||||
@ -284,12 +334,15 @@ const OrderDetails = ({
|
|||||||
<Chip label={t('Order Details')} />
|
<Chip label={t('Order Details')} />
|
||||||
</Divider>
|
</Divider>
|
||||||
|
|
||||||
<Collapse in={order.is_participant}>
|
<Collapse in={currentOrder.order?.is_participant}>
|
||||||
<ListItem>
|
<ListItem>
|
||||||
<ListItemIcon>
|
<ListItemIcon>
|
||||||
<Article />
|
<Article />
|
||||||
</ListItemIcon>
|
</ListItemIcon>
|
||||||
<ListItemText primary={t(order.status_message)} secondary={t('Order status')} />
|
<ListItemText
|
||||||
|
primary={t(currentOrder.order?.status_message ?? '')}
|
||||||
|
secondary={t('Order status')}
|
||||||
|
/>
|
||||||
</ListItem>
|
</ListItem>
|
||||||
<Divider />
|
<Divider />
|
||||||
</Collapse>
|
</Collapse>
|
||||||
@ -311,7 +364,7 @@ const OrderDetails = ({
|
|||||||
</ListItemIcon>
|
</ListItemIcon>
|
||||||
<ListItemText
|
<ListItemText
|
||||||
primary={amountString}
|
primary={amountString}
|
||||||
secondary={order.amount ? 'Amount' : 'Amount Range'}
|
secondary={(currentOrder.order?.amount ?? 0) > 0 ? 'Amount' : 'Amount Range'}
|
||||||
/>
|
/>
|
||||||
<ListItemIcon>
|
<ListItemIcon>
|
||||||
<IconButton
|
<IconButton
|
||||||
@ -360,18 +413,24 @@ const OrderDetails = ({
|
|||||||
size={1.42 * theme.typography.fontSize}
|
size={1.42 * theme.typography.fontSize}
|
||||||
othersText={t('Others')}
|
othersText={t('Others')}
|
||||||
verbose={true}
|
verbose={true}
|
||||||
text={order.payment_method}
|
text={currentOrder.order?.payment_method}
|
||||||
/>
|
/>
|
||||||
}
|
}
|
||||||
secondary={
|
secondary={
|
||||||
order.currency == 1000 ? t('Swap destination') : t('Accepted payment methods')
|
currentOrder.order?.currency === 1000
|
||||||
|
? t('Swap destination')
|
||||||
|
: t('Accepted payment methods')
|
||||||
}
|
}
|
||||||
/>
|
/>
|
||||||
{order.payment_method.includes('Cash F2F') && (
|
{currentOrder.order?.payment_method.includes('Cash F2F') && (
|
||||||
<ListItemIcon>
|
<ListItemIcon>
|
||||||
<Tooltip enterTouchDelay={0} title={t('F2F location')}>
|
<Tooltip enterTouchDelay={0} title={t('F2F location')}>
|
||||||
<div>
|
<div>
|
||||||
<IconButton onClick={() => setOpenWorldmap(true)}>
|
<IconButton
|
||||||
|
onClick={() => {
|
||||||
|
setOpenWorldmap(true);
|
||||||
|
}}
|
||||||
|
>
|
||||||
<Map />
|
<Map />
|
||||||
</IconButton>
|
</IconButton>
|
||||||
</div>
|
</div>
|
||||||
@ -387,24 +446,27 @@ const OrderDetails = ({
|
|||||||
<PriceChange />
|
<PriceChange />
|
||||||
</ListItemIcon>
|
</ListItemIcon>
|
||||||
|
|
||||||
{order.price_now !== undefined ? (
|
{currentOrder.order?.price_now !== undefined ? (
|
||||||
<ListItemText
|
<ListItemText
|
||||||
primary={t('{{price}} {{currencyCode}}/BTC - Premium: {{premium}}%', {
|
primary={t('{{price}} {{currencyCode}}/BTC - Premium: {{premium}}%', {
|
||||||
price: pn(order.price_now),
|
price: pn(currentOrder.order?.price_now),
|
||||||
currencyCode,
|
currencyCode,
|
||||||
premium: order.premium_now,
|
premium: currentOrder.order?.premium_now,
|
||||||
})}
|
})}
|
||||||
secondary={t('Price and Premium')}
|
secondary={t('Price and Premium')}
|
||||||
/>
|
/>
|
||||||
) : null}
|
) : null}
|
||||||
|
|
||||||
{!order.price_now && order.is_explicit ? (
|
{currentOrder.order?.price_now === undefined && currentOrder.order?.is_explicit ? (
|
||||||
<ListItemText primary={pn(order.satoshis)} secondary={t('Amount of Satoshis')} />
|
<ListItemText
|
||||||
|
primary={pn(currentOrder.order?.satoshis)}
|
||||||
|
secondary={t('Amount of Satoshis')}
|
||||||
|
/>
|
||||||
) : null}
|
) : null}
|
||||||
|
|
||||||
{!order.price_now && !order.is_explicit ? (
|
{currentOrder.order?.price_now === undefined && !currentOrder.order?.is_explicit ? (
|
||||||
<ListItemText
|
<ListItemText
|
||||||
primary={parseFloat(Number(order.premium).toFixed(2)) + '%'}
|
primary={`${parseFloat(Number(currentOrder.order?.premium).toFixed(2))}%`}
|
||||||
secondary={t('Premium over market price')}
|
secondary={t('Premium over market price')}
|
||||||
/>
|
/>
|
||||||
) : null}
|
) : null}
|
||||||
@ -418,7 +480,7 @@ const OrderDetails = ({
|
|||||||
</ListItemIcon>
|
</ListItemIcon>
|
||||||
<Grid container>
|
<Grid container>
|
||||||
<Grid item xs={4.5}>
|
<Grid item xs={4.5}>
|
||||||
<ListItemText primary={order.id} secondary={t('Order ID')} />
|
<ListItemText primary={currentOrder.order?.id} secondary={t('Order ID')} />
|
||||||
</Grid>
|
</Grid>
|
||||||
<Grid item xs={7.5}>
|
<Grid item xs={7.5}>
|
||||||
<Grid container>
|
<Grid container>
|
||||||
@ -429,7 +491,7 @@ const OrderDetails = ({
|
|||||||
</Grid>
|
</Grid>
|
||||||
<Grid item xs={10}>
|
<Grid item xs={10}>
|
||||||
<ListItemText
|
<ListItemText
|
||||||
primary={timerRenderer(order.escrow_duration)}
|
primary={timerRenderer(currentOrder.order?.escrow_duration)}
|
||||||
secondary={t('Deposit timer')}
|
secondary={t('Deposit timer')}
|
||||||
></ListItemText>
|
></ListItemText>
|
||||||
</Grid>
|
</Grid>
|
||||||
@ -439,39 +501,47 @@ const OrderDetails = ({
|
|||||||
</ListItem>
|
</ListItem>
|
||||||
|
|
||||||
{/* if order is in a status that does not expire, do not show countdown */}
|
{/* if order is in a status that does not expire, do not show countdown */}
|
||||||
<Collapse in={![4, 5, 12, 13, 14, 15, 16, 17, 18].includes(order.status)}>
|
<Collapse
|
||||||
|
in={![4, 5, 12, 13, 14, 15, 16, 17, 18].includes(currentOrder.order?.status ?? 0)}
|
||||||
|
>
|
||||||
<Divider />
|
<Divider />
|
||||||
<ListItem>
|
<ListItem>
|
||||||
<ListItemIcon>
|
<ListItemIcon>
|
||||||
<AccessTime />
|
<AccessTime />
|
||||||
</ListItemIcon>
|
</ListItemIcon>
|
||||||
<ListItemText secondary={t('Expires in')}>
|
<ListItemText secondary={t('Expires in')}>
|
||||||
<Countdown date={new Date(order.expires_at)} renderer={countdownRenderer} />
|
<Countdown
|
||||||
|
date={new Date(currentOrder.order?.expires_at ?? '')}
|
||||||
|
renderer={countdownRenderer}
|
||||||
|
/>
|
||||||
</ListItemText>
|
</ListItemText>
|
||||||
</ListItem>
|
</ListItem>
|
||||||
<LinearDeterminate totalSecsExp={order.total_secs_exp} expiresAt={order.expires_at} />
|
<LinearDeterminate
|
||||||
|
totalSecsExp={currentOrder.order?.total_secs_exp ?? 0}
|
||||||
|
expiresAt={currentOrder.order?.expires_at ?? ''}
|
||||||
|
/>
|
||||||
</Collapse>
|
</Collapse>
|
||||||
</List>
|
</List>
|
||||||
|
|
||||||
{/* If the user has a penalty/limit */}
|
{/* If the user has a penalty/limit */}
|
||||||
{order.penalty !== undefined ? (
|
{currentOrder.order?.penalty !== undefined ? (
|
||||||
<Grid item xs={12}>
|
<Grid item xs={12}>
|
||||||
<Alert severity='warning' sx={{ borderRadius: '0' }}>
|
<Alert severity='warning' sx={{ borderRadius: '0' }}>
|
||||||
<Countdown date={new Date(order.penalty)} renderer={countdownPenaltyRenderer} />
|
<Countdown
|
||||||
|
date={new Date(currentOrder.order?.penalty ?? '')}
|
||||||
|
renderer={countdownPenaltyRenderer}
|
||||||
|
/>
|
||||||
</Alert>
|
</Alert>
|
||||||
</Grid>
|
</Grid>
|
||||||
) : (
|
) : (
|
||||||
<></>
|
<></>
|
||||||
)}
|
)}
|
||||||
|
|
||||||
{!order.is_participant ? (
|
{!currentOrder.order?.is_participant ? (
|
||||||
<Grid item xs={12}>
|
<Grid item xs={12}>
|
||||||
<TakeButton
|
<TakeButton
|
||||||
order={order}
|
|
||||||
setOrder={setOrder}
|
|
||||||
baseUrl={baseUrl}
|
baseUrl={baseUrl}
|
||||||
hasRobot={hasRobot}
|
info={coordinator.info}
|
||||||
info={info}
|
|
||||||
onClickGenerateRobot={onClickGenerateRobot}
|
onClickGenerateRobot={onClickGenerateRobot}
|
||||||
/>
|
/>
|
||||||
</Grid>
|
</Grid>
|
||||||
|
@ -19,11 +19,11 @@ const StringAsIcons: React.FC = ({ othersText, verbose, size, text = '' }: Props
|
|||||||
|
|
||||||
const parsedText = useMemo(() => {
|
const parsedText = useMemo(() => {
|
||||||
const rows = [];
|
const rows = [];
|
||||||
let custom_methods = text;
|
let customMethods = text;
|
||||||
// Adds icons for each PaymentMethod that matches
|
// Adds icons for each PaymentMethod that matches
|
||||||
methods.forEach((method, i) => {
|
methods.forEach((method, i) => {
|
||||||
if (text.includes(method.name)) {
|
if (text.includes(method.name)) {
|
||||||
custom_methods = custom_methods.replace(method.name, '');
|
customMethods = customMethods.replace(method.name, '');
|
||||||
rows.push(
|
rows.push(
|
||||||
<Tooltip
|
<Tooltip
|
||||||
key={`${method.name}-${i}`}
|
key={`${method.name}-${i}`}
|
||||||
@ -46,20 +46,20 @@ const StringAsIcons: React.FC = ({ othersText, verbose, size, text = '' }: Props
|
|||||||
});
|
});
|
||||||
|
|
||||||
// Adds a Custom icon if there are words that do not match
|
// Adds a Custom icon if there are words that do not match
|
||||||
const chars_left = custom_methods
|
const charsLeft = customMethods
|
||||||
.replace(' ', '')
|
.replace(' ', '')
|
||||||
.replace(' ', '')
|
.replace(' ', '')
|
||||||
.replace(' ', '')
|
.replace(' ', '')
|
||||||
.replace(' ', '')
|
.replace(' ', '')
|
||||||
.replace(' ', '');
|
.replace(' ', '');
|
||||||
|
|
||||||
if (chars_left.length > 0) {
|
if (charsLeft.length > 0) {
|
||||||
rows.push(
|
rows.push(
|
||||||
<Tooltip
|
<Tooltip
|
||||||
key={'pushed'}
|
key={'pushed'}
|
||||||
placement='top'
|
placement='top'
|
||||||
enterTouchDelay={0}
|
enterTouchDelay={0}
|
||||||
title={verbose ? othersText : othersText + ': ' + custom_methods}
|
title={verbose ? othersText : othersText + ': ' + customMethods}
|
||||||
>
|
>
|
||||||
<div
|
<div
|
||||||
style={{
|
style={{
|
||||||
@ -82,7 +82,7 @@ const StringAsIcons: React.FC = ({ othersText, verbose, size, text = '' }: Props
|
|||||||
{rows}{' '}
|
{rows}{' '}
|
||||||
<div style={{ display: 'inline-block' }}>
|
<div style={{ display: 'inline-block' }}>
|
||||||
{' '}
|
{' '}
|
||||||
<span>{custom_methods}</span>
|
<span>{customMethods}</span>
|
||||||
</div>
|
</div>
|
||||||
</>
|
</>
|
||||||
);
|
);
|
||||||
|
@ -1,13 +1,16 @@
|
|||||||
import React, { useCallback, useEffect, useMemo, useState } from 'react';
|
import React, { useCallback, useContext, useEffect, useMemo, useState } from 'react';
|
||||||
import SmoothImage from 'react-smooth-image';
|
import SmoothImage from 'react-smooth-image';
|
||||||
import { Avatar, Badge, Tooltip, useTheme } from '@mui/material';
|
import { Avatar, Badge, Tooltip } from '@mui/material';
|
||||||
import { SendReceiveIcon } from '../Icons';
|
import { SendReceiveIcon } from '../Icons';
|
||||||
import { apiClient } from '../../services/api';
|
import { apiClient } from '../../services/api';
|
||||||
import placeholder from './placeholder.json';
|
import placeholder from './placeholder.json';
|
||||||
|
import { UseAppStoreType, AppContext } from '../../contexts/AppContext';
|
||||||
|
import { UseFederationStoreType, FederationContext } from '../../contexts/FederationContext';
|
||||||
|
|
||||||
interface Props {
|
interface Props {
|
||||||
nickname: string | undefined;
|
nickname: string | undefined;
|
||||||
smooth?: boolean;
|
smooth?: boolean;
|
||||||
|
coordinator?: boolean;
|
||||||
small?: boolean;
|
small?: boolean;
|
||||||
flipHorizontally?: boolean;
|
flipHorizontally?: boolean;
|
||||||
style?: object;
|
style?: object;
|
||||||
@ -41,27 +44,35 @@ const RobotAvatar: React.FC<Props> = ({
|
|||||||
avatarClass = 'flippedSmallAvatar',
|
avatarClass = 'flippedSmallAvatar',
|
||||||
imageStyle = {},
|
imageStyle = {},
|
||||||
onLoad = () => {},
|
onLoad = () => {},
|
||||||
|
coordinator = false,
|
||||||
baseUrl,
|
baseUrl,
|
||||||
}) => {
|
}) => {
|
||||||
|
const { settings, origin, hostUrl } = useContext<UseAppStoreType>(AppContext);
|
||||||
|
const { federation, focusedCoordinator } = useContext<UseFederationStoreType>(FederationContext);
|
||||||
|
|
||||||
const [avatarSrc, setAvatarSrc] = useState<string>();
|
const [avatarSrc, setAvatarSrc] = useState<string>();
|
||||||
const [nicknameReady, setNicknameReady] = useState<boolean>(false);
|
const [nicknameReady, setNicknameReady] = useState<boolean>(false);
|
||||||
const [activeBackground, setActiveBackground] = useState<boolean>(true);
|
const [activeBackground, setActiveBackground] = useState<boolean>(true);
|
||||||
|
|
||||||
|
const path = coordinator ? '/static/federation/' : '/static/assets/avatars/';
|
||||||
const [backgroundData] = useState<BackgroundData>(
|
const [backgroundData] = useState<BackgroundData>(
|
||||||
placeholderType == 'generating' ? placeholder.generating : placeholder.loading,
|
placeholderType === 'generating' ? placeholder.generating : placeholder.loading,
|
||||||
);
|
);
|
||||||
const backgroundImage = `url(data:${backgroundData.mime};base64,${backgroundData.data})`;
|
const backgroundImage = `url(data:${backgroundData.mime};base64,${backgroundData.data})`;
|
||||||
const className = placeholderType == 'loading' ? 'loadingAvatar' : 'generatingAvatar';
|
const className = placeholderType === 'loading' ? 'loadingAvatar' : 'generatingAvatar';
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
if (nickname != undefined) {
|
if (nickname !== undefined) {
|
||||||
if (window.NativeRobosats === undefined) {
|
if (window.NativeRobosats === undefined) {
|
||||||
setAvatarSrc(`${baseUrl}/static/assets/avatars/${nickname}${small ? '.small' : ''}.webp`);
|
setAvatarSrc(`${baseUrl}${path}${nickname}${small ? '.small' : ''}.webp`);
|
||||||
setNicknameReady(true);
|
setNicknameReady(true);
|
||||||
} else {
|
} else if (focusedCoordinator) {
|
||||||
setNicknameReady(true);
|
setNicknameReady(true);
|
||||||
apiClient
|
const { url } = federation
|
||||||
.fileImageUrl(baseUrl, `/static/assets/avatars/${nickname}${small ? '.small' : ''}.webp`)
|
.getCoordinator(focusedCoordinator)
|
||||||
|
.getEndpoint(settings.network, origin, settings.selfhostedClient, hostUrl);
|
||||||
|
void apiClient
|
||||||
|
.fileImageUrl(url, `${path}${nickname}${small ? '.small' : ''}.webp`)
|
||||||
.then(setAvatarSrc);
|
.then(setAvatarSrc);
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
@ -133,7 +144,7 @@ const RobotAvatar: React.FC<Props> = ({
|
|||||||
const getAvatarWithBadges = useCallback(() => {
|
const getAvatarWithBadges = useCallback(() => {
|
||||||
let component = avatar;
|
let component = avatar;
|
||||||
|
|
||||||
if (statusColor) {
|
if (statusColor !== undefined) {
|
||||||
component = (
|
component = (
|
||||||
<Badge variant='dot' overlap='circular' badgeContent='' color={statusColor}>
|
<Badge variant='dot' overlap='circular' badgeContent='' color={statusColor}>
|
||||||
{component}
|
{component}
|
||||||
@ -153,7 +164,7 @@ const RobotAvatar: React.FC<Props> = ({
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
if (tooltip) {
|
if (tooltip !== undefined) {
|
||||||
component = (
|
component = (
|
||||||
<Tooltip placement={tooltipPosition} enterTouchDelay={0} title={tooltip}>
|
<Tooltip placement={tooltipPosition} enterTouchDelay={0} title={tooltip}>
|
||||||
{component}
|
{component}
|
||||||
|
337
frontend/src/components/RobotInfo/index.tsx
Normal file
337
frontend/src/components/RobotInfo/index.tsx
Normal file
@ -0,0 +1,337 @@
|
|||||||
|
import React, { useContext, useEffect, useMemo, useState } from 'react';
|
||||||
|
|
||||||
|
import {
|
||||||
|
Tooltip,
|
||||||
|
List,
|
||||||
|
ListItem,
|
||||||
|
ListItemButton,
|
||||||
|
ListItemIcon,
|
||||||
|
ListItemText,
|
||||||
|
Grid,
|
||||||
|
useTheme,
|
||||||
|
Divider,
|
||||||
|
Typography,
|
||||||
|
Badge,
|
||||||
|
Button,
|
||||||
|
Switch,
|
||||||
|
FormControlLabel,
|
||||||
|
TextField,
|
||||||
|
CircularProgress,
|
||||||
|
Accordion,
|
||||||
|
AccordionDetails,
|
||||||
|
AccordionSummary,
|
||||||
|
} from '@mui/material';
|
||||||
|
import { Numbers, Send, EmojiEvents, ExpandMore } from '@mui/icons-material';
|
||||||
|
import { useNavigate } from 'react-router-dom';
|
||||||
|
import { type Coordinator, type Robot } from '../../models';
|
||||||
|
import { useTranslation } from 'react-i18next';
|
||||||
|
import { EnableTelegramDialog } from '../Dialogs';
|
||||||
|
import { UserNinjaIcon } from '../Icons';
|
||||||
|
|
||||||
|
import { getWebln } from '../../utils';
|
||||||
|
import { signCleartextMessage } from '../../pgp';
|
||||||
|
import { GarageContext, type UseGarageStoreType } from '../../contexts/GarageContext';
|
||||||
|
|
||||||
|
interface Props {
|
||||||
|
robot: Robot;
|
||||||
|
slotIndex: number;
|
||||||
|
coordinator: Coordinator;
|
||||||
|
onClose: () => void;
|
||||||
|
}
|
||||||
|
|
||||||
|
const RobotInfo: React.FC<Props> = ({ robot, slotIndex, coordinator, onClose }: Props) => {
|
||||||
|
const { garage } = useContext<UseGarageStoreType>(GarageContext);
|
||||||
|
const navigate = useNavigate();
|
||||||
|
const { t } = useTranslation();
|
||||||
|
|
||||||
|
const theme = useTheme();
|
||||||
|
|
||||||
|
const [rewardInvoice, setRewardInvoice] = useState<string>('');
|
||||||
|
const [showRewardsSpinner, setShowRewardsSpinner] = useState<boolean>(false);
|
||||||
|
const [withdrawn, setWithdrawn] = useState<boolean>(false);
|
||||||
|
const [badInvoice, setBadInvoice] = useState<string>('');
|
||||||
|
const [openClaimRewards, setOpenClaimRewards] = useState<boolean>(false);
|
||||||
|
const [weblnEnabled, setWeblnEnabled] = useState<boolean>(false);
|
||||||
|
const [openEnableTelegram, setOpenEnableTelegram] = useState<boolean>(false);
|
||||||
|
|
||||||
|
const handleWebln = async (): Promise<void> => {
|
||||||
|
void getWebln()
|
||||||
|
.then(() => {
|
||||||
|
setWeblnEnabled(true);
|
||||||
|
})
|
||||||
|
.catch(() => {
|
||||||
|
setWeblnEnabled(false);
|
||||||
|
console.log('WebLN not available');
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
handleWebln();
|
||||||
|
}, []);
|
||||||
|
|
||||||
|
const handleWeblnInvoiceClicked = async (e: MouseEvent<HTMLButtonElement, MouseEvent>): void => {
|
||||||
|
e.preventDefault();
|
||||||
|
if (robot.earnedRewards > 0) {
|
||||||
|
const webln = await getWebln();
|
||||||
|
const invoice = webln.makeInvoice(robot.earnedRewards).then(() => {
|
||||||
|
if (invoice != null) {
|
||||||
|
handleSubmitInvoiceClicked(e, invoice.paymentRequest);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
const handleSubmitInvoiceClicked = (e: any, rewardInvoice: string): void => {
|
||||||
|
setBadInvoice('');
|
||||||
|
setShowRewardsSpinner(true);
|
||||||
|
|
||||||
|
const robot = garage.getRobot(slotIndex);
|
||||||
|
|
||||||
|
if (robot.encPrivKey && robot.token) {
|
||||||
|
void signCleartextMessage(rewardInvoice, robot.encPrivKey, robot.token).then(
|
||||||
|
(signedInvoice) => {
|
||||||
|
coordinator.fetchReward(signedInvoice, garage, slotIndex).then((data) => {
|
||||||
|
setBadInvoice(data.bad_invoice ?? '');
|
||||||
|
setShowRewardsSpinner(false);
|
||||||
|
setWithdrawn(data.successful_withdrawal);
|
||||||
|
setOpenClaimRewards(!(data.successful_withdrawal !== undefined));
|
||||||
|
});
|
||||||
|
},
|
||||||
|
);
|
||||||
|
}
|
||||||
|
e.preventDefault();
|
||||||
|
};
|
||||||
|
|
||||||
|
const setStealthInvoice = (wantsStealth: boolean): void => {
|
||||||
|
coordinator.fetchStealth(wantsStealth, garage, slotIndex);
|
||||||
|
};
|
||||||
|
|
||||||
|
return (
|
||||||
|
<Accordion>
|
||||||
|
<AccordionSummary expandIcon={<ExpandMore />}>
|
||||||
|
{`${coordinator.longAlias}:`}
|
||||||
|
{garage.getRobot(slotIndex).earnedRewards > 0 && (
|
||||||
|
<Typography color='success'> {t('Claim Sats!')} </Typography>
|
||||||
|
)}
|
||||||
|
{(garage.getRobot(slotIndex).activeOrderId ?? 0) > 0 && (
|
||||||
|
<Typography color='success'>
|
||||||
|
<b>{t('Active order!')}</b>
|
||||||
|
</Typography>
|
||||||
|
)}
|
||||||
|
{(garage.getRobot(slotIndex).lastOrderId ?? 0) > 0 && robot.activeOrderId === undefined && (
|
||||||
|
<Typography color='warning'> {t('finished order')}</Typography>
|
||||||
|
)}
|
||||||
|
</AccordionSummary>
|
||||||
|
<AccordionDetails>
|
||||||
|
<List dense disablePadding={true}>
|
||||||
|
{(garage.getRobot(slotIndex).activeOrderId ?? 0) > 0 ? (
|
||||||
|
<ListItemButton
|
||||||
|
onClick={() => {
|
||||||
|
navigate(`/order/${coordinator.shortAlias}/${String(robot.activeOrderId)}`);
|
||||||
|
onClose();
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
<ListItemIcon>
|
||||||
|
<Badge badgeContent='' color='primary'>
|
||||||
|
<Numbers color='primary' />
|
||||||
|
</Badge>
|
||||||
|
</ListItemIcon>
|
||||||
|
<ListItemText
|
||||||
|
primary={t('One active order #{{orderID}}', { orderID: robot.activeOrderId })}
|
||||||
|
secondary={t('Your current order')}
|
||||||
|
/>
|
||||||
|
</ListItemButton>
|
||||||
|
) : (garage.getRobot(slotIndex).lastOrderId ?? 0) > 0 ? (
|
||||||
|
<ListItemButton
|
||||||
|
onClick={() => {
|
||||||
|
navigate(`/order/${coordinator.shortAlias}/${String(robot.lastOrderId)}`);
|
||||||
|
onClose();
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
<ListItemIcon>
|
||||||
|
<Numbers color='primary' />
|
||||||
|
</ListItemIcon>
|
||||||
|
<ListItemText
|
||||||
|
primary={t('Your last order #{{orderID}}', { orderID: robot.lastOrderId })}
|
||||||
|
secondary={t('Inactive order')}
|
||||||
|
/>
|
||||||
|
</ListItemButton>
|
||||||
|
) : (
|
||||||
|
<ListItem>
|
||||||
|
<ListItemIcon>
|
||||||
|
<Numbers />
|
||||||
|
</ListItemIcon>
|
||||||
|
<ListItemText
|
||||||
|
primary={t('No active orders')}
|
||||||
|
secondary={t('You do not have previous orders')}
|
||||||
|
/>
|
||||||
|
</ListItem>
|
||||||
|
)}
|
||||||
|
|
||||||
|
<Divider />
|
||||||
|
|
||||||
|
<EnableTelegramDialog
|
||||||
|
open={openEnableTelegram}
|
||||||
|
onClose={() => {
|
||||||
|
setOpenEnableTelegram(false);
|
||||||
|
}}
|
||||||
|
tgBotName={robot.tgBotName}
|
||||||
|
tgToken={robot.tgToken}
|
||||||
|
/>
|
||||||
|
|
||||||
|
<ListItem>
|
||||||
|
<ListItemIcon>
|
||||||
|
<Send />
|
||||||
|
</ListItemIcon>
|
||||||
|
|
||||||
|
<ListItemText>
|
||||||
|
{robot.tgEnabled ? (
|
||||||
|
<Typography color={theme.palette.success.main}>
|
||||||
|
<b>{t('Telegram enabled')}</b>
|
||||||
|
</Typography>
|
||||||
|
) : (
|
||||||
|
<Button
|
||||||
|
color='primary'
|
||||||
|
onClick={() => {
|
||||||
|
setOpenEnableTelegram(true);
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
{t('Enable Telegram Notifications')}
|
||||||
|
</Button>
|
||||||
|
)}
|
||||||
|
</ListItemText>
|
||||||
|
</ListItem>
|
||||||
|
|
||||||
|
<ListItem>
|
||||||
|
<ListItemIcon>
|
||||||
|
<UserNinjaIcon />
|
||||||
|
</ListItemIcon>
|
||||||
|
|
||||||
|
<ListItemText>
|
||||||
|
<Tooltip
|
||||||
|
placement='bottom'
|
||||||
|
enterTouchDelay={0}
|
||||||
|
title={t(
|
||||||
|
"Stealth lightning invoices do not contain details about the trade except an order reference. Enable this setting if you don't want to disclose details to a custodial lightning wallet.",
|
||||||
|
)}
|
||||||
|
>
|
||||||
|
<Grid item>
|
||||||
|
<FormControlLabel
|
||||||
|
labelPlacement='end'
|
||||||
|
label={t('Use stealth invoices')}
|
||||||
|
control={
|
||||||
|
<Switch
|
||||||
|
checked={robot.stealthInvoices}
|
||||||
|
onChange={() => {
|
||||||
|
setStealthInvoice(!robot.stealthInvoices);
|
||||||
|
}}
|
||||||
|
/>
|
||||||
|
}
|
||||||
|
/>
|
||||||
|
</Grid>
|
||||||
|
</Tooltip>
|
||||||
|
</ListItemText>
|
||||||
|
</ListItem>
|
||||||
|
|
||||||
|
<ListItem>
|
||||||
|
<ListItemIcon>
|
||||||
|
<EmojiEvents />
|
||||||
|
</ListItemIcon>
|
||||||
|
|
||||||
|
{!openClaimRewards ? (
|
||||||
|
<ListItemText secondary={t('Your compensations')}>
|
||||||
|
<Grid container>
|
||||||
|
<Grid item xs={9}>
|
||||||
|
<Typography>{`${robot.earnedRewards} Sats`}</Typography>
|
||||||
|
</Grid>
|
||||||
|
|
||||||
|
<Grid item xs={3}>
|
||||||
|
<Button
|
||||||
|
disabled={robot.earnedRewards === 0}
|
||||||
|
onClick={() => {
|
||||||
|
setOpenClaimRewards(true);
|
||||||
|
}}
|
||||||
|
variant='contained'
|
||||||
|
size='small'
|
||||||
|
>
|
||||||
|
{t('Claim')}
|
||||||
|
</Button>
|
||||||
|
</Grid>
|
||||||
|
</Grid>
|
||||||
|
</ListItemText>
|
||||||
|
) : (
|
||||||
|
<form noValidate style={{ maxWidth: 270 }}>
|
||||||
|
<Grid container style={{ display: 'flex', alignItems: 'stretch' }}>
|
||||||
|
<Grid item style={{ display: 'flex', maxWidth: 160 }}>
|
||||||
|
<TextField
|
||||||
|
error={Boolean(badInvoice)}
|
||||||
|
helperText={badInvoice ?? ''}
|
||||||
|
label={t('Invoice for {{amountSats}} Sats', {
|
||||||
|
amountSats: robot.earnedRewards,
|
||||||
|
})}
|
||||||
|
size='small'
|
||||||
|
value={rewardInvoice}
|
||||||
|
onChange={(e) => {
|
||||||
|
setRewardInvoice(e.target.value);
|
||||||
|
}}
|
||||||
|
/>
|
||||||
|
</Grid>
|
||||||
|
<Grid item alignItems='stretch' style={{ display: 'flex', maxWidth: 80 }}>
|
||||||
|
<Button
|
||||||
|
sx={{ maxHeight: 38 }}
|
||||||
|
onClick={(e) => {
|
||||||
|
handleSubmitInvoiceClicked(e, rewardInvoice);
|
||||||
|
}}
|
||||||
|
variant='contained'
|
||||||
|
color='primary'
|
||||||
|
size='small'
|
||||||
|
type='submit'
|
||||||
|
>
|
||||||
|
{t('Submit')}
|
||||||
|
</Button>
|
||||||
|
</Grid>
|
||||||
|
</Grid>
|
||||||
|
{weblnEnabled ? (
|
||||||
|
<Grid container style={{ display: 'flex', alignItems: 'stretch' }}>
|
||||||
|
<Grid item alignItems='stretch' style={{ display: 'flex', maxWidth: 240 }}>
|
||||||
|
<Button
|
||||||
|
sx={{ maxHeight: 38, minWidth: 230 }}
|
||||||
|
onClick={(e) => {
|
||||||
|
handleWeblnInvoiceClicked(e);
|
||||||
|
}}
|
||||||
|
variant='contained'
|
||||||
|
color='primary'
|
||||||
|
size='small'
|
||||||
|
type='submit'
|
||||||
|
>
|
||||||
|
{t('Generate with Webln')}
|
||||||
|
</Button>
|
||||||
|
</Grid>
|
||||||
|
</Grid>
|
||||||
|
) : (
|
||||||
|
<></>
|
||||||
|
)}
|
||||||
|
</form>
|
||||||
|
)}
|
||||||
|
</ListItem>
|
||||||
|
|
||||||
|
{showRewardsSpinner && (
|
||||||
|
<div style={{ display: 'flex', justifyContent: 'center' }}>
|
||||||
|
<CircularProgress />
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
|
|
||||||
|
{withdrawn && (
|
||||||
|
<div style={{ display: 'flex', justifyContent: 'center' }}>
|
||||||
|
<Typography color='primary' variant='body2'>
|
||||||
|
<b>{t('There it goes!')}</b>
|
||||||
|
</Typography>
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
|
</List>
|
||||||
|
</AccordionDetails>
|
||||||
|
</Accordion>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
export default RobotInfo;
|
@ -1,10 +1,17 @@
|
|||||||
import React from 'react';
|
import React from 'react';
|
||||||
import { useTranslation } from 'react-i18next';
|
import { useTranslation } from 'react-i18next';
|
||||||
import { Select, MenuItem, useTheme, Grid, Typography } from '@mui/material';
|
import {
|
||||||
import type Language from '../../models/Language.model';
|
Select,
|
||||||
|
MenuItem,
|
||||||
|
useTheme,
|
||||||
|
Grid,
|
||||||
|
Typography,
|
||||||
|
type SelectChangeEvent,
|
||||||
|
} from '@mui/material';
|
||||||
|
|
||||||
import Flags from 'country-flag-icons/react/3x2';
|
import Flags from 'country-flag-icons/react/3x2';
|
||||||
import { CataloniaFlag, BasqueCountryFlag } from '../Icons';
|
import { CataloniaFlag, BasqueCountryFlag } from '../Icons';
|
||||||
|
import type { Language } from '../../models';
|
||||||
|
|
||||||
const menuLanuguages = [
|
const menuLanuguages = [
|
||||||
{ name: 'English', i18nCode: 'en', flag: Flags.US },
|
{ name: 'English', i18nCode: 'en', flag: Flags.US },
|
||||||
@ -31,18 +38,18 @@ interface SelectLanguageProps {
|
|||||||
setLanguage: (lang: Language) => void;
|
setLanguage: (lang: Language) => void;
|
||||||
}
|
}
|
||||||
|
|
||||||
const SelectLanguage = ({ language, setLanguage }: SelectLanguageProps): JSX.Element => {
|
const SelectLanguage: React.FC<SelectLanguageProps> = ({ language, setLanguage }) => {
|
||||||
const theme = useTheme();
|
const theme = useTheme();
|
||||||
const { t, i18n } = useTranslation();
|
const { i18n } = useTranslation();
|
||||||
|
|
||||||
const flagProps = {
|
const flagProps = {
|
||||||
width: 1.5 * theme.typography.fontSize,
|
width: 1.5 * theme.typography.fontSize,
|
||||||
height: 1.5 * theme.typography.fontSize,
|
height: 1.5 * theme.typography.fontSize,
|
||||||
};
|
};
|
||||||
|
|
||||||
const handleChangeLang = function (e: any) {
|
const handleChangeLang = function (e: SelectChangeEvent): void {
|
||||||
setLanguage(e.target.value);
|
setLanguage(e.target.value);
|
||||||
i18n.changeLanguage(e.target.value);
|
void i18n.changeLanguage(e.target.value);
|
||||||
};
|
};
|
||||||
|
|
||||||
return (
|
return (
|
||||||
|
@ -32,10 +32,9 @@ import SwapCalls from '@mui/icons-material/SwapCalls';
|
|||||||
|
|
||||||
interface SettingsFormProps {
|
interface SettingsFormProps {
|
||||||
dense?: boolean;
|
dense?: boolean;
|
||||||
showNetwork?: boolean;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
const SettingsForm = ({ dense = false, showNetwork = false }: SettingsFormProps): JSX.Element => {
|
const SettingsForm = ({ dense = false }: SettingsFormProps): JSX.Element => {
|
||||||
const { fav, setFav, settings, setSettings } = useContext<UseAppStoreType>(AppContext);
|
const { fav, setFav, settings, setSettings } = useContext<UseAppStoreType>(AppContext);
|
||||||
const theme = useTheme();
|
const theme = useTheme();
|
||||||
const { t } = useTranslation();
|
const { t } = useTranslation();
|
||||||
@ -176,8 +175,8 @@ const SettingsForm = ({ dense = false, showNetwork = false }: SettingsFormProps)
|
|||||||
</ListItemIcon>
|
</ListItemIcon>
|
||||||
<Slider
|
<Slider
|
||||||
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}
|
||||||
step={1}
|
step={1}
|
||||||
onChange={(e) => {
|
onChange={(e) => {
|
||||||
const fontSize = e.target.value;
|
const fontSize = e.target.value;
|
||||||
@ -215,30 +214,26 @@ const SettingsForm = ({ dense = false, showNetwork = false }: SettingsFormProps)
|
|||||||
</ToggleButtonGroup>
|
</ToggleButtonGroup>
|
||||||
</ListItem>
|
</ListItem>
|
||||||
|
|
||||||
{showNetwork ? (
|
<ListItem>
|
||||||
<ListItem>
|
<ListItemIcon>
|
||||||
<ListItemIcon>
|
<Link />
|
||||||
<Link />
|
</ListItemIcon>
|
||||||
</ListItemIcon>
|
<ToggleButtonGroup
|
||||||
<ToggleButtonGroup
|
exclusive={true}
|
||||||
exclusive={true}
|
value={settings.network}
|
||||||
value={settings.network}
|
onChange={(e, network) => {
|
||||||
onChange={(e, network) => {
|
setSettings({ ...settings, network });
|
||||||
setSettings({ ...settings, network });
|
systemClient.setItem('settings_network', network);
|
||||||
systemClient.setItem('settings_network', network);
|
}}
|
||||||
}}
|
>
|
||||||
>
|
<ToggleButton value='mainnet' color='primary'>
|
||||||
<ToggleButton value='mainnet' color='primary'>
|
{t('Mainnet')}
|
||||||
{t('Mainnet')}
|
</ToggleButton>
|
||||||
</ToggleButton>
|
<ToggleButton value='testnet' color='secondary'>
|
||||||
<ToggleButton value='testnet' color='secondary'>
|
{t('Testnet')}
|
||||||
{t('Testnet')}
|
</ToggleButton>
|
||||||
</ToggleButton>
|
</ToggleButtonGroup>
|
||||||
</ToggleButtonGroup>
|
</ListItem>
|
||||||
</ListItem>
|
|
||||||
) : (
|
|
||||||
<></>
|
|
||||||
)}
|
|
||||||
</List>
|
</List>
|
||||||
</Grid>
|
</Grid>
|
||||||
</Grid>
|
</Grid>
|
||||||
|
97
frontend/src/components/TorConnection/index.tsx
Normal file
97
frontend/src/components/TorConnection/index.tsx
Normal file
@ -0,0 +1,97 @@
|
|||||||
|
import React, { useContext } from 'react';
|
||||||
|
import { Box, CircularProgress, Tooltip } from '@mui/material';
|
||||||
|
import { TorIcon } from '../Icons';
|
||||||
|
import { useTranslation } from 'react-i18next';
|
||||||
|
import { AppContext, type AppContextProps } from '../contexts/AppContext';
|
||||||
|
|
||||||
|
interface TorIndicatorProps {
|
||||||
|
color: 'inherit' | 'error' | 'warning' | 'success' | 'primary' | 'secondary' | 'info' | undefined;
|
||||||
|
tooltipOpen?: boolean | undefined;
|
||||||
|
title: string;
|
||||||
|
progress: boolean;
|
||||||
|
}
|
||||||
|
|
||||||
|
const TorIndicator = ({
|
||||||
|
color,
|
||||||
|
tooltipOpen = undefined,
|
||||||
|
title,
|
||||||
|
progress,
|
||||||
|
}: TorIndicatorProps): JSX.Element => {
|
||||||
|
return (
|
||||||
|
<Tooltip
|
||||||
|
open={tooltipOpen}
|
||||||
|
enterTouchDelay={0}
|
||||||
|
enterDelay={1000}
|
||||||
|
placement='right'
|
||||||
|
title={title}
|
||||||
|
>
|
||||||
|
<Box sx={{ display: 'inline-flex', position: 'fixed', left: '0.5em', top: '0.5em' }}>
|
||||||
|
{progress ? (
|
||||||
|
<>
|
||||||
|
<CircularProgress color={color} thickness={6} size={22} />
|
||||||
|
<Box
|
||||||
|
sx={{
|
||||||
|
top: 0,
|
||||||
|
left: 0,
|
||||||
|
bottom: 0,
|
||||||
|
right: 0,
|
||||||
|
position: 'absolute',
|
||||||
|
display: 'flex',
|
||||||
|
alignItems: 'center',
|
||||||
|
justifyContent: 'center',
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
<TorIcon color={color} sx={{ width: 20, height: 20 }} />
|
||||||
|
</Box>
|
||||||
|
</>
|
||||||
|
) : (
|
||||||
|
<Box>
|
||||||
|
<TorIcon color={color} sx={{ width: 20, height: 20 }} />
|
||||||
|
</Box>
|
||||||
|
)}
|
||||||
|
</Box>
|
||||||
|
</Tooltip>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
const TorConnectionBadge = (): JSX.Element => {
|
||||||
|
const { torStatus } = useContext<AppContextProps>(AppContext);
|
||||||
|
const { t } = useTranslation();
|
||||||
|
|
||||||
|
if (window?.NativeRobosats == null) {
|
||||||
|
return <></>;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (torStatus === 'NOTINIT') {
|
||||||
|
return (
|
||||||
|
<TorIndicator
|
||||||
|
color='primary'
|
||||||
|
progress={true}
|
||||||
|
tooltipOpen={true}
|
||||||
|
title={t('Initializing TOR daemon')}
|
||||||
|
/>
|
||||||
|
);
|
||||||
|
} else if (torStatus === 'STARTING') {
|
||||||
|
return (
|
||||||
|
<TorIndicator
|
||||||
|
color='warning'
|
||||||
|
progress={true}
|
||||||
|
tooltipOpen={true}
|
||||||
|
title={t('Connecting to TOR network')}
|
||||||
|
/>
|
||||||
|
);
|
||||||
|
} else if (torStatus === '"Done"' || torStatus === 'DONE') {
|
||||||
|
return <TorIndicator color='success' progress={false} title={t('Connected to TOR network')} />;
|
||||||
|
} else {
|
||||||
|
return (
|
||||||
|
<TorIndicator
|
||||||
|
color='error'
|
||||||
|
progress={false}
|
||||||
|
tooltipOpen={true}
|
||||||
|
title={t('Connection error')}
|
||||||
|
/>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
export default TorConnectionBadge;
|
@ -16,10 +16,10 @@ const BondStatus = ({ status, isMaker }: BondStatusProps): JSX.Element => {
|
|||||||
let color = 'primary';
|
let color = 'primary';
|
||||||
if (status === 'unlocked') {
|
if (status === 'unlocked') {
|
||||||
Icon = LockOpen;
|
Icon = LockOpen;
|
||||||
color = theme.palette.mode == 'dark' ? 'lightgreen' : 'green';
|
color = theme.palette.mode === 'dark' ? 'lightgreen' : 'green';
|
||||||
} else if (status === 'settled') {
|
} else if (status === 'settled') {
|
||||||
Icon = Balance;
|
Icon = Balance;
|
||||||
color = theme.palette.mode == 'dark' ? 'lightred' : 'red';
|
color = theme.palette.mode === 'dark' ? 'lightred' : 'red';
|
||||||
}
|
}
|
||||||
|
|
||||||
if (status === 'hide') {
|
if (status === 'hide') {
|
||||||
|
@ -5,7 +5,7 @@ import { type Order } from '../../models';
|
|||||||
import { LoadingButton } from '@mui/lab';
|
import { LoadingButton } from '@mui/lab';
|
||||||
|
|
||||||
interface CancelButtonProps {
|
interface CancelButtonProps {
|
||||||
order: Order;
|
order: Order | null;
|
||||||
onClickCancel: () => void;
|
onClickCancel: () => void;
|
||||||
openCancelDialog: () => void;
|
openCancelDialog: () => void;
|
||||||
openCollabCancelDialog: () => void;
|
openCollabCancelDialog: () => void;
|
||||||
@ -22,10 +22,12 @@ const CancelButton = ({
|
|||||||
const { t } = useTranslation();
|
const { t } = useTranslation();
|
||||||
|
|
||||||
const showCancelButton =
|
const showCancelButton =
|
||||||
(order.is_maker && [0, 1, 2].includes(order.status)) || [3, 6, 7].includes(order.status);
|
(order?.is_maker && [0, 1, 2].includes(order?.status)) ||
|
||||||
const showCollabCancelButton = [8, 9].includes(order.status) && !order.asked_for_cancel;
|
[3, 6, 7].includes(order?.status ?? -1);
|
||||||
|
const showCollabCancelButton = [8, 9].includes(order?.status ?? -1) && !order?.asked_for_cancel;
|
||||||
const noConfirmation =
|
const noConfirmation =
|
||||||
(order.is_maker && [0, 1, 2].includes(order.status)) || (order.is_taker && order.status === 3);
|
(order?.is_maker && [0, 1, 2].includes(order?.status ?? -1)) ||
|
||||||
|
(order?.is_taker && order?.status === 3);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Box>
|
<Box>
|
||||||
|
@ -4,21 +4,21 @@ import { Alert } from '@mui/material';
|
|||||||
import { type Order } from '../../models';
|
import { type Order } from '../../models';
|
||||||
|
|
||||||
interface CollabCancelAlertProps {
|
interface CollabCancelAlertProps {
|
||||||
order: Order;
|
order: Order | null;
|
||||||
}
|
}
|
||||||
|
|
||||||
const CollabCancelAlert = ({ order }: CollabCancelAlertProps): JSX.Element => {
|
const CollabCancelAlert = ({ order }: CollabCancelAlertProps): JSX.Element => {
|
||||||
const { t } = useTranslation();
|
const { t } = useTranslation();
|
||||||
let text = '';
|
let text = '';
|
||||||
if (order.pending_cancel) {
|
if (order?.pending_cancel) {
|
||||||
text = t('{{nickname}} is asking for a collaborative cancel', {
|
text = t('{{nickname}} is asking for a collaborative cancel', {
|
||||||
nickname: order.is_maker ? order.taker_nick : order.maker_nick,
|
nickname: order?.is_maker ? order?.taker_nick : order?.maker_nick,
|
||||||
});
|
});
|
||||||
} else if (order.asked_for_cancel) {
|
} else if (order?.asked_for_cancel) {
|
||||||
text = t('You asked for a collaborative cancellation');
|
text = t('You asked for a collaborative cancellation');
|
||||||
}
|
}
|
||||||
|
|
||||||
return text != '' ? (
|
return text !== '' ? (
|
||||||
<Alert severity='warning' style={{ width: '100%' }}>
|
<Alert severity='warning' style={{ width: '100%' }}>
|
||||||
{text}
|
{text}
|
||||||
</Alert>
|
</Alert>
|
||||||
|
@ -16,7 +16,7 @@ import { LoadingButton } from '@mui/lab';
|
|||||||
interface ConfirmFiatReceivedDialogProps {
|
interface ConfirmFiatReceivedDialogProps {
|
||||||
open: boolean;
|
open: boolean;
|
||||||
loadingButton: boolean;
|
loadingButton: boolean;
|
||||||
order: Order;
|
order: Order | null;
|
||||||
onClose: () => void;
|
onClose: () => void;
|
||||||
onConfirmClick: () => void;
|
onConfirmClick: () => void;
|
||||||
}
|
}
|
||||||
@ -29,8 +29,10 @@ export const ConfirmFiatReceivedDialog = ({
|
|||||||
onConfirmClick,
|
onConfirmClick,
|
||||||
}: ConfirmFiatReceivedDialogProps): JSX.Element => {
|
}: ConfirmFiatReceivedDialogProps): JSX.Element => {
|
||||||
const { t } = useTranslation();
|
const { t } = useTranslation();
|
||||||
const currencyCode = currencies[order.currency.toString()];
|
const currencyCode = currencies[order?.currency.toString()];
|
||||||
const amount = pn(parseFloat(parseFloat(order.amount).toFixed(order.currency == 1000 ? 8 : 4)));
|
const amount = pn(
|
||||||
|
parseFloat(parseFloat(order?.amount).toFixed(order?.currency === 1000 ? 8 : 4)),
|
||||||
|
);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Dialog open={open} onClose={onClose}>
|
<Dialog open={open} onClose={onClose}>
|
||||||
|
@ -16,7 +16,7 @@ import { LoadingButton } from '@mui/lab';
|
|||||||
interface ConfirmFiatSentDialogProps {
|
interface ConfirmFiatSentDialogProps {
|
||||||
open: boolean;
|
open: boolean;
|
||||||
loadingButton: boolean;
|
loadingButton: boolean;
|
||||||
order: Order;
|
order: Order | null;
|
||||||
onClose: () => void;
|
onClose: () => void;
|
||||||
onConfirmClick: () => void;
|
onConfirmClick: () => void;
|
||||||
}
|
}
|
||||||
@ -29,8 +29,10 @@ export const ConfirmFiatSentDialog = ({
|
|||||||
onConfirmClick,
|
onConfirmClick,
|
||||||
}: ConfirmFiatSentDialogProps): JSX.Element => {
|
}: ConfirmFiatSentDialogProps): JSX.Element => {
|
||||||
const { t } = useTranslation();
|
const { t } = useTranslation();
|
||||||
const currencyCode = currencies[order.currency.toString()];
|
const currencyCode = currencies[order?.currency.toString()];
|
||||||
const amount = pn(parseFloat(parseFloat(order.amount).toFixed(order.currency == 1000 ? 8 : 4)));
|
const amount = pn(
|
||||||
|
parseFloat(parseFloat(order?.amount).toFixed(order?.currency === 1000 ? 8 : 4)),
|
||||||
|
);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Dialog open={open} onClose={onClose}>
|
<Dialog open={open} onClose={onClose}>
|
||||||
|
@ -14,7 +14,6 @@ import { LoadingButton } from '@mui/lab';
|
|||||||
interface ConfirmUndoFiatSentDialogProps {
|
interface ConfirmUndoFiatSentDialogProps {
|
||||||
open: boolean;
|
open: boolean;
|
||||||
loadingButton: boolean;
|
loadingButton: boolean;
|
||||||
order: Order;
|
|
||||||
onClose: () => void;
|
onClose: () => void;
|
||||||
onConfirmClick: () => void;
|
onConfirmClick: () => void;
|
||||||
}
|
}
|
||||||
@ -36,7 +35,7 @@ export const ConfirmUndoFiatSentDialog = ({
|
|||||||
}
|
}
|
||||||
}, [time, open]);
|
}, [time, open]);
|
||||||
|
|
||||||
const onClick = () => {
|
const onClick = (): void => {
|
||||||
onConfirmClick();
|
onConfirmClick();
|
||||||
setTime(300);
|
setTime(300);
|
||||||
};
|
};
|
||||||
|
@ -1,5 +1,5 @@
|
|||||||
import React, { useState } from 'react';
|
import React from 'react';
|
||||||
import { Grid, useTheme, Tooltip, Button } from '@mui/material';
|
import { Grid, Tooltip, Button } from '@mui/material';
|
||||||
import { ExportIcon } from '../../../Icons';
|
import { ExportIcon } from '../../../Icons';
|
||||||
import KeyIcon from '@mui/icons-material/Key';
|
import KeyIcon from '@mui/icons-material/Key';
|
||||||
import { useTranslation } from 'react-i18next';
|
import { useTranslation } from 'react-i18next';
|
||||||
@ -15,7 +15,6 @@ interface Props {
|
|||||||
|
|
||||||
const ChatBottom: React.FC<Props> = ({ orderId, setAudit, audit, createJsonFile }) => {
|
const ChatBottom: React.FC<Props> = ({ orderId, setAudit, audit, createJsonFile }) => {
|
||||||
const { t } = useTranslation();
|
const { t } = useTranslation();
|
||||||
const theme = useTheme();
|
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Grid
|
<Grid
|
||||||
@ -62,7 +61,7 @@ const ChatBottom: React.FC<Props> = ({ orderId, setAudit, audit, createJsonFile
|
|||||||
color='primary'
|
color='primary'
|
||||||
variant='outlined'
|
variant='outlined'
|
||||||
onClick={() => {
|
onClick={() => {
|
||||||
saveAsJson('complete_log_chat_' + orderId + '.json', createJsonFile());
|
saveAsJson(`complete_log_chat_${orderId}.json`, createJsonFile());
|
||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
<div style={{ width: '1.4em', height: '1.4em' }}>
|
<div style={{ width: '1.4em', height: '1.4em' }}>
|
||||||
|
@ -1,6 +1,6 @@
|
|||||||
import React, { useEffect, useLayoutEffect, useState } from 'react';
|
import React, { useEffect, useLayoutEffect, useState } from 'react';
|
||||||
import { useTranslation } from 'react-i18next';
|
import { useTranslation } from 'react-i18next';
|
||||||
import { Button, Tooltip, TextField, Grid, Paper, Typography } from '@mui/material';
|
import { Button, TextField, Grid, Paper, Typography } from '@mui/material';
|
||||||
import { encryptMessage, decryptMessage } from '../../../../pgp';
|
import { encryptMessage, decryptMessage } from '../../../../pgp';
|
||||||
import { AuditPGPDialog } from '../../../Dialogs';
|
import { AuditPGPDialog } from '../../../Dialogs';
|
||||||
import { websocketClient, type WebsocketConnection } from '../../../../services/Websocket';
|
import { websocketClient, type WebsocketConnection } from '../../../../services/Websocket';
|
||||||
@ -48,6 +48,7 @@ const EncryptedSocketChat: React.FC<Props> = ({
|
|||||||
}: Props): JSX.Element => {
|
}: Props): JSX.Element => {
|
||||||
const { t } = useTranslation();
|
const { t } = useTranslation();
|
||||||
const theme = useTheme();
|
const theme = useTheme();
|
||||||
|
const { garage } = useContext<UseGarageStoreType>(GarageContext);
|
||||||
|
|
||||||
const [audio] = useState(() => new Audio(`${audioPath}/chat-open.mp3`));
|
const [audio] = useState(() => new Audio(`${audioPath}/chat-open.mp3`));
|
||||||
const [connected, setConnected] = useState<boolean>(false);
|
const [connected, setConnected] = useState<boolean>(false);
|
||||||
@ -64,7 +65,7 @@ const EncryptedSocketChat: React.FC<Props> = ({
|
|||||||
const [error, setError] = useState<string>('');
|
const [error, setError] = useState<string>('');
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
if (!connected && robot.avatarLoaded) {
|
if (!connected && garage.getRobot().avatarLoaded) {
|
||||||
connectWebsocket();
|
connectWebsocket();
|
||||||
}
|
}
|
||||||
}, [connected, robot]);
|
}, [connected, robot]);
|
||||||
@ -87,18 +88,18 @@ const EncryptedSocketChat: React.FC<Props> = ({
|
|||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
if (messages.length > messageCount) {
|
if (messages.length > messageCount) {
|
||||||
audio.play();
|
void audio.play();
|
||||||
setMessageCount(messages.length);
|
setMessageCount(messages.length);
|
||||||
}
|
}
|
||||||
}, [messages, messageCount]);
|
}, [messages, messageCount]);
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
if (serverMessages) {
|
if (serverMessages.length > 0) {
|
||||||
serverMessages.forEach(onMessage);
|
serverMessages.forEach(onMessage);
|
||||||
}
|
}
|
||||||
}, [serverMessages]);
|
}, [serverMessages]);
|
||||||
|
|
||||||
const connectWebsocket = () => {
|
const connectWebsocket = (): void => {
|
||||||
websocketClient
|
websocketClient
|
||||||
.open(
|
.open(
|
||||||
`ws://${window.location.host}/ws/chat/${orderId}/?token_sha256_hex=${sha256(robot.token)}`,
|
`ws://${window.location.host}/ws/chat/${orderId}/?token_sha256_hex=${sha256(robot.token)}`,
|
||||||
@ -121,6 +122,9 @@ const EncryptedSocketChat: React.FC<Props> = ({
|
|||||||
connection.onError(() => {
|
connection.onError(() => {
|
||||||
setConnected(false);
|
setConnected(false);
|
||||||
});
|
});
|
||||||
|
})
|
||||||
|
.catch(() => {
|
||||||
|
setConnected(false);
|
||||||
});
|
});
|
||||||
};
|
};
|
||||||
|
|
||||||
@ -139,14 +143,14 @@ const EncryptedSocketChat: React.FC<Props> = ({
|
|||||||
const onMessage: (message: any) => void = (message) => {
|
const onMessage: (message: any) => void = (message) => {
|
||||||
const dataFromServer = JSON.parse(message.data);
|
const dataFromServer = JSON.parse(message.data);
|
||||||
|
|
||||||
if (dataFromServer && !receivedIndexes.includes(dataFromServer.index)) {
|
if (dataFromServer != null && !receivedIndexes.includes(dataFromServer.index)) {
|
||||||
setReceivedIndexes((prev) => [...prev, dataFromServer.index]);
|
setReceivedIndexes((prev) => [...prev, dataFromServer.index]);
|
||||||
setPeerConnected(dataFromServer.peer_connected);
|
setPeerConnected(dataFromServer.peer_connected);
|
||||||
// If we receive a public key other than ours (our peer key!)
|
// If we receive a public key other than ours (our peer key!)
|
||||||
if (
|
if (
|
||||||
connection != null &&
|
connection != null &&
|
||||||
dataFromServer.message.substring(0, 36) == `-----BEGIN PGP PUBLIC KEY BLOCK-----` &&
|
dataFromServer.message.substring(0, 36) === `-----BEGIN PGP PUBLIC KEY BLOCK-----` &&
|
||||||
dataFromServer.message != robot.pubKey
|
dataFromServer.message !== robot.pubKey
|
||||||
) {
|
) {
|
||||||
setPeerPubKey(dataFromServer.message);
|
setPeerPubKey(dataFromServer.message);
|
||||||
connection.send({
|
connection.send({
|
||||||
@ -155,10 +159,10 @@ const EncryptedSocketChat: React.FC<Props> = ({
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
// If we receive an encrypted message
|
// If we receive an encrypted message
|
||||||
else if (dataFromServer.message.substring(0, 27) == `-----BEGIN PGP MESSAGE-----`) {
|
else if (dataFromServer.message.substring(0, 27) === `-----BEGIN PGP MESSAGE-----`) {
|
||||||
decryptMessage(
|
void decryptMessage(
|
||||||
dataFromServer.message.split('\\').join('\n'),
|
dataFromServer.message.split('\\').join('\n'),
|
||||||
dataFromServer.user_nick == userNick ? robot.pubKey : peerPubKey,
|
dataFromServer.user_nick === userNick ? robot.pubKey : peerPubKey,
|
||||||
robot.encPrivKey,
|
robot.encPrivKey,
|
||||||
robot.token,
|
robot.token,
|
||||||
).then((decryptedData) => {
|
).then((decryptedData) => {
|
||||||
@ -166,27 +170,25 @@ const EncryptedSocketChat: React.FC<Props> = ({
|
|||||||
setLastSent(decryptedData.decryptedMessage === lastSent ? '----BLANK----' : lastSent);
|
setLastSent(decryptedData.decryptedMessage === lastSent ? '----BLANK----' : lastSent);
|
||||||
setMessages((prev) => {
|
setMessages((prev) => {
|
||||||
const existingMessage = prev.find((item) => item.index === dataFromServer.index);
|
const existingMessage = prev.find((item) => item.index === dataFromServer.index);
|
||||||
if (existingMessage) {
|
if (existingMessage != null) {
|
||||||
return prev;
|
return prev;
|
||||||
} else {
|
} else {
|
||||||
return [
|
const x: EncryptedChatMessage = {
|
||||||
...prev,
|
index: dataFromServer.index,
|
||||||
{
|
encryptedMessage: dataFromServer.message.split('\\').join('\n'),
|
||||||
index: dataFromServer.index,
|
plainTextMessage: String(decryptedData.decryptedMessage),
|
||||||
encryptedMessage: dataFromServer.message.split('\\').join('\n'),
|
validSignature: decryptedData.validSignature,
|
||||||
plainTextMessage: decryptedData.decryptedMessage,
|
userNick: dataFromServer.user_nick,
|
||||||
validSignature: decryptedData.validSignature,
|
time: dataFromServer.time,
|
||||||
userNick: dataFromServer.user_nick,
|
};
|
||||||
time: dataFromServer.time,
|
return [...prev, x].sort((a, b) => a.index - b.index);
|
||||||
} as EncryptedChatMessage,
|
|
||||||
].sort((a, b) => a.index - b.index);
|
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
// We allow plaintext communication. The user must write # to start
|
// We allow plaintext communication. The user must write # to start
|
||||||
// If we receive an plaintext message
|
// If we receive an plaintext message
|
||||||
else if (dataFromServer.message.substring(0, 1) == '#') {
|
else if (dataFromServer.message.substring(0, 1) === '#') {
|
||||||
setMessages((prev: EncryptedChatMessage[]) => {
|
setMessages((prev: EncryptedChatMessage[]) => {
|
||||||
const existingMessage = prev.find(
|
const existingMessage = prev.find(
|
||||||
(item) => item.plainTextMessage === dataFromServer.message,
|
(item) => item.plainTextMessage === dataFromServer.message,
|
||||||
@ -194,32 +196,30 @@ const EncryptedSocketChat: React.FC<Props> = ({
|
|||||||
if (existingMessage != null) {
|
if (existingMessage != null) {
|
||||||
return prev;
|
return prev;
|
||||||
} else {
|
} else {
|
||||||
return [
|
const x: EncryptedChatMessage = {
|
||||||
...prev,
|
index: prev.length + 0.001,
|
||||||
{
|
encryptedMessage: dataFromServer.message,
|
||||||
index: prev.length + 0.001,
|
plainTextMessage: dataFromServer.message,
|
||||||
encryptedMessage: dataFromServer.message,
|
validSignature: false,
|
||||||
plainTextMessage: dataFromServer.message,
|
userNick: dataFromServer.user_nick,
|
||||||
validSignature: false,
|
time: new Date().toString(),
|
||||||
userNick: dataFromServer.user_nick,
|
};
|
||||||
time: new Date().toString(),
|
return [...prev, x].sort((a, b) => a.index - b.index);
|
||||||
} as EncryptedChatMessage,
|
|
||||||
].sort((a, b) => a.index - b.index);
|
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
const onButtonClicked = (e: any) => {
|
const onButtonClicked = (e: React.FormEvent<HTMLFormElement>): void => {
|
||||||
if (robot.token && value.includes(robot.token)) {
|
if (robot.token !== undefined && value.includes(robot.token)) {
|
||||||
alert(
|
alert(
|
||||||
`Aye! You just sent your own robot robot.token to your peer in chat, that's a catastrophic idea! So bad your message was blocked.`,
|
`Aye! You just sent your own robot robot.token to your peer in chat, that's a catastrophic idea! So bad your message was blocked.`,
|
||||||
);
|
);
|
||||||
setValue('');
|
setValue('');
|
||||||
}
|
}
|
||||||
// If input string contains '#' send unencrypted and unlogged message
|
// If input string contains '#' send unencrypted and unlogged message
|
||||||
else if (connection != null && value.substring(0, 1) == '#') {
|
else if (connection != null && value.substring(0, 1) === '#') {
|
||||||
connection.send({
|
connection.send({
|
||||||
message: value,
|
message: value,
|
||||||
nick: userNick,
|
nick: userNick,
|
||||||
@ -228,7 +228,7 @@ const EncryptedSocketChat: React.FC<Props> = ({
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Else if message is not empty send message
|
// Else if message is not empty send message
|
||||||
else if (value != '') {
|
else if (value !== '') {
|
||||||
setValue('');
|
setValue('');
|
||||||
setWaitingEcho(true);
|
setWaitingEcho(true);
|
||||||
setLastSent(value);
|
setLastSent(value);
|
||||||
@ -236,7 +236,7 @@ const EncryptedSocketChat: React.FC<Props> = ({
|
|||||||
.then((encryptedMessage) => {
|
.then((encryptedMessage) => {
|
||||||
if (connection != null) {
|
if (connection != null) {
|
||||||
connection.send({
|
connection.send({
|
||||||
message: encryptedMessage.toString().split('\n').join('\\'),
|
message: String(encryptedMessage).split('\n').join('\\'),
|
||||||
nick: userNick,
|
nick: userNick,
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
@ -263,17 +263,17 @@ const EncryptedSocketChat: React.FC<Props> = ({
|
|||||||
}}
|
}}
|
||||||
orderId={Number(orderId)}
|
orderId={Number(orderId)}
|
||||||
messages={messages}
|
messages={messages}
|
||||||
own_pub_key={robot.pubKey || ''}
|
ownPubKey={robot.pubKey ?? ''}
|
||||||
own_enc_priv_key={robot.encPrivKey || ''}
|
ownEncPrivKey={robot.encPrivKey ?? ''}
|
||||||
peer_pub_key={peerPubKey || 'Not received yet'}
|
peerPubKey={peerPubKey ?? 'Not received yet'}
|
||||||
passphrase={robot.token || ''}
|
passphrase={robot.token ?? ''}
|
||||||
onClickBack={() => {
|
onClickBack={() => {
|
||||||
setAudit(false);
|
setAudit(false);
|
||||||
}}
|
}}
|
||||||
/>
|
/>
|
||||||
<Grid item>
|
<Grid item>
|
||||||
<ChatHeader
|
<ChatHeader
|
||||||
connected={connected && (peerPubKey ? true : false)}
|
connected={connected && Boolean(peerPubKey)}
|
||||||
peerConnected={peerConnected}
|
peerConnected={peerConnected}
|
||||||
turtleMode={turtleMode}
|
turtleMode={turtleMode}
|
||||||
setTurtleMode={setTurtleMode}
|
setTurtleMode={setTurtleMode}
|
||||||
@ -326,7 +326,7 @@ const EncryptedSocketChat: React.FC<Props> = ({
|
|||||||
}}
|
}}
|
||||||
helperText={
|
helperText={
|
||||||
connected
|
connected
|
||||||
? peerPubKey
|
? peerPubKey !== undefined
|
||||||
? null
|
? null
|
||||||
: t('Waiting for peer public key...')
|
: t('Waiting for peer public key...')
|
||||||
: t('Connecting...')
|
: t('Connecting...')
|
||||||
@ -341,7 +341,7 @@ const EncryptedSocketChat: React.FC<Props> = ({
|
|||||||
<Grid item alignItems='stretch' style={{ display: 'flex' }} xs={3}>
|
<Grid item alignItems='stretch' style={{ display: 'flex' }} xs={3}>
|
||||||
<Button
|
<Button
|
||||||
fullWidth={true}
|
fullWidth={true}
|
||||||
disabled={!connected || waitingEcho || !peerPubKey}
|
disabled={!connected || waitingEcho || peerPubKey === undefined}
|
||||||
type='submit'
|
type='submit'
|
||||||
variant='contained'
|
variant='contained'
|
||||||
color='primary'
|
color='primary'
|
||||||
|
@ -47,6 +47,8 @@ const EncryptedTurtleChat: React.FC<Props> = ({
|
|||||||
}: Props): JSX.Element => {
|
}: Props): JSX.Element => {
|
||||||
const { t } = useTranslation();
|
const { t } = useTranslation();
|
||||||
const theme = useTheme();
|
const theme = useTheme();
|
||||||
|
const { origin, hostUrl, settings } = useContext<UseAppStoreType>(AppContext);
|
||||||
|
const { federation, focusedCoordinator } = useContext<UseFederationStoreType>(FederationContext);
|
||||||
|
|
||||||
const [audio] = useState(() => new Audio(`${audioPath}/chat-open.mp3`));
|
const [audio] = useState(() => new Audio(`${audioPath}/chat-open.mp3`));
|
||||||
const [peerConnected, setPeerConnected] = useState<boolean>(false);
|
const [peerConnected, setPeerConnected] = useState<boolean>(false);
|
||||||
@ -62,13 +64,13 @@ const EncryptedTurtleChat: React.FC<Props> = ({
|
|||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
if (messages.length > messageCount) {
|
if (messages.length > messageCount) {
|
||||||
audio.play();
|
void audio.play();
|
||||||
setMessageCount(messages.length);
|
setMessageCount(messages.length);
|
||||||
}
|
}
|
||||||
}, [messages, messageCount]);
|
}, [messages, messageCount]);
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
if (serverMessages.length > 0 && peerPubKey) {
|
if (serverMessages.length > 0 && peerPubKey !== undefined) {
|
||||||
serverMessages.forEach(onMessage);
|
serverMessages.forEach(onMessage);
|
||||||
}
|
}
|
||||||
}, [serverMessages, peerPubKey]);
|
}, [serverMessages, peerPubKey]);
|
||||||
@ -80,20 +82,26 @@ const EncryptedTurtleChat: React.FC<Props> = ({
|
|||||||
}, [chatOffset]);
|
}, [chatOffset]);
|
||||||
|
|
||||||
const loadMessages: () => void = () => {
|
const loadMessages: () => void = () => {
|
||||||
|
const { url } = federation
|
||||||
|
.getCoordinator(focusedCoordinator)
|
||||||
|
.getEndpoint(settings.network, origin, settings.selfhostedClient, hostUrl);
|
||||||
apiClient
|
apiClient
|
||||||
.get(baseUrl, `/api/chat/?order_id=${orderId}&offset=${lastIndex}`, {
|
.get(url, `/api/chat/?order_id=${orderId}&offset=${lastIndex}`, {
|
||||||
tokenSHA256: robot.tokenSHA256,
|
tokenSHA256: robot.tokenSHA256,
|
||||||
})
|
})
|
||||||
.then((results: any) => {
|
.then((results: any) => {
|
||||||
if (results) {
|
if (results != null) {
|
||||||
setPeerConnected(results.peer_connected);
|
setPeerConnected(results.peer_connected);
|
||||||
setPeerPubKey(results.peer_pubkey.split('\\').join('\n'));
|
setPeerPubKey(results.peer_pubkey.split('\\').join('\n'));
|
||||||
setServerMessages(results.messages);
|
setServerMessages(results.messages);
|
||||||
}
|
}
|
||||||
|
})
|
||||||
|
.catch((error) => {
|
||||||
|
setError(error.toString());
|
||||||
});
|
});
|
||||||
};
|
};
|
||||||
|
|
||||||
const createJsonFile: () => object = () => {
|
const createJsonFile = (): object => {
|
||||||
return {
|
return {
|
||||||
credentials: {
|
credentials: {
|
||||||
own_public_key: robot.pubKey,
|
own_public_key: robot.pubKey,
|
||||||
@ -105,13 +113,13 @@ const EncryptedTurtleChat: React.FC<Props> = ({
|
|||||||
};
|
};
|
||||||
};
|
};
|
||||||
|
|
||||||
const onMessage: (dataFromServer: ServerMessage) => void = (dataFromServer) => {
|
const onMessage = (dataFromServer: ServerMessage): void => {
|
||||||
if (dataFromServer) {
|
if (dataFromServer != null) {
|
||||||
// If we receive an encrypted message
|
// If we receive an encrypted message
|
||||||
if (dataFromServer.message.substring(0, 27) == `-----BEGIN PGP MESSAGE-----`) {
|
if (dataFromServer.message.substring(0, 27) === `-----BEGIN PGP MESSAGE-----`) {
|
||||||
decryptMessage(
|
void decryptMessage(
|
||||||
dataFromServer.message.split('\\').join('\n'),
|
dataFromServer.message.split('\\').join('\n'),
|
||||||
dataFromServer.nick == userNick ? robot.pubKey : peerPubKey,
|
dataFromServer.nick === userNick ? robot.pubKey : peerPubKey,
|
||||||
robot.encPrivKey,
|
robot.encPrivKey,
|
||||||
robot.token,
|
robot.token,
|
||||||
).then((decryptedData) => {
|
).then((decryptedData) => {
|
||||||
@ -122,60 +130,59 @@ const EncryptedTurtleChat: React.FC<Props> = ({
|
|||||||
if (existingMessage != null) {
|
if (existingMessage != null) {
|
||||||
return prev;
|
return prev;
|
||||||
} else {
|
} else {
|
||||||
return [
|
const x: EncryptedChatMessage = {
|
||||||
...prev,
|
index: dataFromServer.index,
|
||||||
{
|
encryptedMessage: dataFromServer.message.split('\\').join('\n'),
|
||||||
index: dataFromServer.index,
|
plainTextMessage: decryptedData.decryptedMessage,
|
||||||
encryptedMessage: dataFromServer.message.split('\\').join('\n'),
|
validSignature: decryptedData.validSignature,
|
||||||
plainTextMessage: decryptedData.decryptedMessage,
|
userNick: dataFromServer.nick,
|
||||||
validSignature: decryptedData.validSignature,
|
time: dataFromServer.time,
|
||||||
userNick: dataFromServer.nick,
|
};
|
||||||
time: dataFromServer.time,
|
return [...prev, x].sort((a, b) => a.index - b.index);
|
||||||
} as EncryptedChatMessage,
|
|
||||||
].sort((a, b) => a.index - b.index);
|
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
// We allow plaintext communication. The user must write # to start
|
// We allow plaintext communication. The user must write # to start
|
||||||
// If we receive an plaintext message
|
// If we receive an plaintext message
|
||||||
else if (dataFromServer.message.substring(0, 1) == '#') {
|
else if (dataFromServer.message.substring(0, 1) === '#') {
|
||||||
setMessages((prev) => {
|
setMessages((prev: EncryptedChatMessage[]) => {
|
||||||
const existingMessage = prev.find(
|
const existingMessage = prev.find(
|
||||||
(item) => item.plainTextMessage === dataFromServer.message,
|
(item) => item.plainTextMessage === dataFromServer.message,
|
||||||
);
|
);
|
||||||
if (existingMessage) {
|
if (existingMessage != null) {
|
||||||
return prev;
|
return prev;
|
||||||
} else {
|
} else {
|
||||||
return [
|
const x: EncryptedChatMessage = {
|
||||||
...prev,
|
index: prev.length + 0.001,
|
||||||
{
|
encryptedMessage: dataFromServer.message,
|
||||||
index: prev.length + 0.001,
|
plainTextMessage: dataFromServer.message,
|
||||||
encryptedMessage: dataFromServer.message,
|
validSignature: false,
|
||||||
plainTextMessage: dataFromServer.message,
|
userNick: dataFromServer.nick,
|
||||||
validSignature: false,
|
time: new Date().toString(),
|
||||||
userNick: dataFromServer.nick,
|
};
|
||||||
time: new Date().toString(),
|
return [...prev, x].sort((a, b) => a.index - b.index);
|
||||||
} as EncryptedChatMessage,
|
|
||||||
].sort((a, b) => a.index - b.index);
|
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
const onButtonClicked = (e: any) => {
|
const onButtonClicked = (e: React.FormEvent<HTMLFormElement>): void => {
|
||||||
if (robot.token && value.includes(robot.token)) {
|
if (robot.token !== undefined && value.includes(robot.token)) {
|
||||||
alert(
|
alert(
|
||||||
`Aye! You just sent your own robot robot.token to your peer in chat, that's a catastrophic idea! So bad your message was blocked.`,
|
`Aye! You just sent your own robot robot.token to your peer in chat, that's a catastrophic idea! So bad your message was blocked.`,
|
||||||
);
|
);
|
||||||
setValue('');
|
setValue('');
|
||||||
}
|
}
|
||||||
// If input string contains '#' send unencrypted and unlogged message
|
// If input string contains '#' send unencrypted and unlogged message
|
||||||
else if (value.substring(0, 1) == '#') {
|
else if (value.substring(0, 1) === '#') {
|
||||||
|
const { url } = federation
|
||||||
|
.getCoordinator(focusedCoordinator)
|
||||||
|
.getEndpoint(settings.network, origin, settings.selfhostedClient, hostUrl);
|
||||||
apiClient
|
apiClient
|
||||||
.post(
|
.post(
|
||||||
baseUrl,
|
url,
|
||||||
`/api/chat/`,
|
`/api/chat/`,
|
||||||
{
|
{
|
||||||
PGP_message: value,
|
PGP_message: value,
|
||||||
@ -186,7 +193,7 @@ const EncryptedTurtleChat: React.FC<Props> = ({
|
|||||||
)
|
)
|
||||||
.then((response) => {
|
.then((response) => {
|
||||||
if (response != null) {
|
if (response != null) {
|
||||||
if (response.messages) {
|
if (response.messages != null) {
|
||||||
setPeerConnected(response.peer_connected);
|
setPeerConnected(response.peer_connected);
|
||||||
setServerMessages(response.messages);
|
setServerMessages(response.messages);
|
||||||
}
|
}
|
||||||
@ -198,17 +205,20 @@ const EncryptedTurtleChat: React.FC<Props> = ({
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
// Else if message is not empty send message
|
// Else if message is not empty send message
|
||||||
else if (value != '') {
|
else if (value !== '') {
|
||||||
setWaitingEcho(true);
|
setWaitingEcho(true);
|
||||||
setLastSent(value);
|
setLastSent(value);
|
||||||
encryptMessage(value, robot.pubKey, peerPubKey, robot.encPrivKey, robot.token)
|
encryptMessage(value, robot.pubKey, peerPubKey, robot.encPrivKey, robot.token)
|
||||||
.then((encryptedMessage) => {
|
.then((encryptedMessage) => {
|
||||||
|
const { url } = federation
|
||||||
|
.getCoordinator(focusedCoordinator)
|
||||||
|
.getEndpoint(settings.network, origin, settings.selfhostedClient, hostUrl);
|
||||||
apiClient
|
apiClient
|
||||||
.post(
|
.post(
|
||||||
baseUrl,
|
url,
|
||||||
`/api/chat/`,
|
`/api/chat/`,
|
||||||
{
|
{
|
||||||
PGP_message: encryptedMessage.toString().split('\n').join('\\'),
|
PGP_message: String(encryptedMessage).split('\n').join('\\'),
|
||||||
order_id: orderId,
|
order_id: orderId,
|
||||||
offset: lastIndex,
|
offset: lastIndex,
|
||||||
},
|
},
|
||||||
@ -217,7 +227,7 @@ const EncryptedTurtleChat: React.FC<Props> = ({
|
|||||||
.then((response) => {
|
.then((response) => {
|
||||||
if (response != null) {
|
if (response != null) {
|
||||||
setPeerConnected(response.peer_connected);
|
setPeerConnected(response.peer_connected);
|
||||||
if (response.messages) {
|
if (response.messages != null) {
|
||||||
setServerMessages(response.messages);
|
setServerMessages(response.messages);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -249,10 +259,10 @@ const EncryptedTurtleChat: React.FC<Props> = ({
|
|||||||
}}
|
}}
|
||||||
orderId={Number(orderId)}
|
orderId={Number(orderId)}
|
||||||
messages={messages}
|
messages={messages}
|
||||||
own_pub_key={robot.pubKey || ''}
|
ownPubKey={robot.pubKey ?? ''}
|
||||||
own_enc_priv_key={robot.encPrivKey || ''}
|
ownEncPrivKey={robot.encPrivKey ?? ''}
|
||||||
peer_pub_key={peerPubKey || 'Not received yet'}
|
peerPubKey={peerPubKey ?? 'Not received yet'}
|
||||||
passphrase={robot.token || ''}
|
passphrase={robot.token ?? ''}
|
||||||
onClickBack={() => {
|
onClickBack={() => {
|
||||||
setAudit(false);
|
setAudit(false);
|
||||||
}}
|
}}
|
||||||
@ -260,7 +270,7 @@ const EncryptedTurtleChat: React.FC<Props> = ({
|
|||||||
|
|
||||||
<Grid item>
|
<Grid item>
|
||||||
<ChatHeader
|
<ChatHeader
|
||||||
connected={peerPubKey ? true : false}
|
connected={Boolean(peerPubKey)}
|
||||||
peerConnected={peerConnected}
|
peerConnected={peerConnected}
|
||||||
turtleMode={turtleMode}
|
turtleMode={turtleMode}
|
||||||
setTurtleMode={setTurtleMode}
|
setTurtleMode={setTurtleMode}
|
||||||
@ -320,7 +330,7 @@ const EncryptedTurtleChat: React.FC<Props> = ({
|
|||||||
</Grid>
|
</Grid>
|
||||||
<Grid item alignItems='stretch' style={{ display: 'flex' }} xs={3}>
|
<Grid item alignItems='stretch' style={{ display: 'flex' }} xs={3}>
|
||||||
<Button
|
<Button
|
||||||
disabled={waitingEcho || !peerPubKey}
|
disabled={waitingEcho || peerPubKey === undefined}
|
||||||
type='submit'
|
type='submit'
|
||||||
variant='contained'
|
variant='contained'
|
||||||
color='primary'
|
color='primary'
|
||||||
|
@ -19,7 +19,7 @@ interface Props {
|
|||||||
}
|
}
|
||||||
|
|
||||||
const MessageCard: React.FC<Props> = ({ message, isTaker, userConnected, baseUrl }) => {
|
const MessageCard: React.FC<Props> = ({ message, isTaker, userConnected, baseUrl }) => {
|
||||||
const [showPGP, setShowPGP] = useState<boolean>();
|
const [showPGP, setShowPGP] = useState<boolean>(false);
|
||||||
const { t } = useTranslation();
|
const { t } = useTranslation();
|
||||||
const theme = useTheme();
|
const theme = useTheme();
|
||||||
|
|
||||||
@ -122,7 +122,7 @@ const MessageCard: React.FC<Props> = ({ message, isTaker, userConnected, baseUrl
|
|||||||
showPGP ? (
|
showPGP ? (
|
||||||
<a>
|
<a>
|
||||||
{' '}
|
{' '}
|
||||||
{message.time} <br /> {'Valid signature: ' + message.validSignature} <br />{' '}
|
{message.time} <br /> {`Valid signature: ${String(message.validSignature)}`} <br />{' '}
|
||||||
{message.encryptedMessage}{' '}
|
{message.encryptedMessage}{' '}
|
||||||
</a>
|
</a>
|
||||||
) : (
|
) : (
|
||||||
|
@ -1,9 +1,7 @@
|
|||||||
import React from 'react';
|
import React from 'react';
|
||||||
import { useTranslation } from 'react-i18next';
|
import { useTranslation } from 'react-i18next';
|
||||||
import { Grid, TextField, Checkbox, Tooltip, FormControlLabel } from '@mui/material';
|
import { Grid, TextField, Checkbox, Tooltip, FormControlLabel } from '@mui/material';
|
||||||
import { Order } from '../../../models';
|
|
||||||
import { LoadingButton } from '@mui/lab';
|
import { LoadingButton } from '@mui/lab';
|
||||||
import { EncryptedChatMessage } from '../EncryptedChat';
|
|
||||||
|
|
||||||
export interface DisputeForm {
|
export interface DisputeForm {
|
||||||
statement: string;
|
statement: string;
|
||||||
@ -43,7 +41,7 @@ export const DisputeStatementForm = ({
|
|||||||
>
|
>
|
||||||
<Grid item>
|
<Grid item>
|
||||||
<TextField
|
<TextField
|
||||||
error={dispute.badStatement != ''}
|
error={dispute.badStatement !== ''}
|
||||||
helperText={dispute.badStatement}
|
helperText={dispute.badStatement}
|
||||||
label={t('Submit dispute statement')}
|
label={t('Submit dispute statement')}
|
||||||
required
|
required
|
||||||
|
@ -33,7 +33,7 @@ import { apiClient } from '../../../services/api';
|
|||||||
import { systemClient } from '../../../services/System';
|
import { systemClient } from '../../../services/System';
|
||||||
|
|
||||||
import lnproxies from '../../../../static/lnproxies.json';
|
import lnproxies from '../../../../static/lnproxies.json';
|
||||||
let filteredProxies: { [key: string]: any }[] = [];
|
let filteredProxies: Array<Record<string, any>> = [];
|
||||||
export interface LightningForm {
|
export interface LightningForm {
|
||||||
invoice: string;
|
invoice: string;
|
||||||
amount: number;
|
amount: number;
|
||||||
@ -95,23 +95,23 @@ export const LightningPayoutForm = ({
|
|||||||
const [loadingLnproxy, setLoadingLnproxy] = useState<boolean>(false);
|
const [loadingLnproxy, setLoadingLnproxy] = useState<boolean>(false);
|
||||||
const [noMatchingLnProxies, setNoMatchingLnProxies] = useState<string>('');
|
const [noMatchingLnProxies, setNoMatchingLnProxies] = useState<string>('');
|
||||||
|
|
||||||
const computeInvoiceAmount = function () {
|
const computeInvoiceAmount = (): number => {
|
||||||
const tradeAmount = order.trade_satoshis;
|
const tradeAmount = order.trade_satoshis;
|
||||||
return Math.floor(tradeAmount - tradeAmount * (lightning.routingBudgetPPM / 1000000));
|
return Math.floor(tradeAmount - tradeAmount * (lightning.routingBudgetPPM / 1000000));
|
||||||
};
|
};
|
||||||
|
|
||||||
const validateInvoice = function (invoice: string, targetAmount: number) {
|
const validateInvoice = (invoice: string, targetAmount: number): string => {
|
||||||
try {
|
try {
|
||||||
const decoded = decode(invoice);
|
const decoded = decode(invoice);
|
||||||
const invoiceAmount = Math.floor(decoded.sections[2].value / 1000);
|
const invoiceAmount = Math.floor(decoded.sections[2].value / 1000);
|
||||||
if (targetAmount != invoiceAmount) {
|
if (targetAmount !== invoiceAmount) {
|
||||||
return 'Invalid invoice amount';
|
return 'Invalid invoice amount';
|
||||||
} else {
|
} else {
|
||||||
return '';
|
return '';
|
||||||
}
|
}
|
||||||
} catch (err) {
|
} catch (err) {
|
||||||
const error = err.toString();
|
const error = err.toString();
|
||||||
return `${error.substring(0, 100)}${error.length > 100 ? '...' : ''}`;
|
return `${String(error).substring(0, 100)}${error.length > 100 ? '...' : ''}`;
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
@ -122,14 +122,14 @@ export const LightningPayoutForm = ({
|
|||||||
amount,
|
amount,
|
||||||
lnproxyAmount: amount - lightning.lnproxyBudgetSats,
|
lnproxyAmount: amount - lightning.lnproxyBudgetSats,
|
||||||
routingBudgetSats:
|
routingBudgetSats:
|
||||||
lightning.routingBudgetSats == undefined
|
lightning.routingBudgetSats === undefined
|
||||||
? Math.ceil((amount / 1000000) * lightning.routingBudgetPPM)
|
? Math.ceil((amount / 1000000) * lightning.routingBudgetPPM)
|
||||||
: lightning.routingBudgetSats,
|
: lightning.routingBudgetSats,
|
||||||
});
|
});
|
||||||
}, [lightning.routingBudgetPPM]);
|
}, [lightning.routingBudgetPPM]);
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
if (lightning.invoice != '') {
|
if (lightning.invoice !== '') {
|
||||||
setLightning({
|
setLightning({
|
||||||
...lightning,
|
...lightning,
|
||||||
badInvoice: validateInvoice(lightning.invoice, lightning.amount),
|
badInvoice: validateInvoice(lightning.invoice, lightning.amount),
|
||||||
@ -138,7 +138,7 @@ export const LightningPayoutForm = ({
|
|||||||
}, [lightning.invoice, lightning.amount]);
|
}, [lightning.invoice, lightning.amount]);
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
if (lightning.lnproxyInvoice != '') {
|
if (lightning.lnproxyInvoice !== '') {
|
||||||
setLightning({
|
setLightning({
|
||||||
...lightning,
|
...lightning,
|
||||||
badLnproxy: validateInvoice(lightning.lnproxyInvoice, lightning.lnproxyAmount),
|
badLnproxy: validateInvoice(lightning.lnproxyInvoice, lightning.lnproxyAmount),
|
||||||
@ -146,23 +146,23 @@ export const LightningPayoutForm = ({
|
|||||||
}
|
}
|
||||||
}, [lightning.lnproxyInvoice, lightning.lnproxyAmount]);
|
}, [lightning.lnproxyInvoice, lightning.lnproxyAmount]);
|
||||||
|
|
||||||
//filter lnproxies when the network settings are updated
|
// filter lnproxies when the network settings are updated
|
||||||
let bitcoinNetwork: string = 'mainnet';
|
let bitcoinNetwork: string = 'mainnet';
|
||||||
let internetNetwork: 'Clearnet' | 'I2P' | 'TOR' = 'Clearnet';
|
let internetNetwork: 'Clearnet' | 'I2P' | 'TOR' = 'Clearnet';
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
bitcoinNetwork = settings?.network ?? 'mainnet';
|
bitcoinNetwork = settings?.network ?? 'mainnet';
|
||||||
if (settings.host?.includes('.i2p')) {
|
if (settings.host?.includes('.i2p') === true) {
|
||||||
internetNetwork = 'I2P';
|
internetNetwork = 'I2P';
|
||||||
} else if (settings.host?.includes('.onion') || window.NativeRobosats != undefined) {
|
} else if (settings.host?.includes('.onion') === true || window.NativeRobosats !== undefined) {
|
||||||
internetNetwork = 'TOR';
|
internetNetwork = 'TOR';
|
||||||
}
|
}
|
||||||
|
|
||||||
filteredProxies = lnproxies
|
filteredProxies = lnproxies
|
||||||
.filter((node) => node.relayType == internetNetwork)
|
.filter((node) => node.relayType === internetNetwork)
|
||||||
.filter((node) => node.network == bitcoinNetwork);
|
.filter((node) => node.network === bitcoinNetwork);
|
||||||
}, [settings]);
|
}, [settings]);
|
||||||
|
|
||||||
//if "use lnproxy" checkbox is enabled, but there are no matching proxies, enter error state
|
// if "use lnproxy" checkbox is enabled, but there are no matching proxies, enter error state
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
setNoMatchingLnProxies('');
|
setNoMatchingLnProxies('');
|
||||||
if (filteredProxies.length === 0) {
|
if (filteredProxies.length === 0) {
|
||||||
@ -175,21 +175,21 @@ export const LightningPayoutForm = ({
|
|||||||
}
|
}
|
||||||
}, [lightning.useLnproxy]);
|
}, [lightning.useLnproxy]);
|
||||||
|
|
||||||
const fetchLnproxy = function () {
|
const fetchLnproxy = function (): void {
|
||||||
setLoadingLnproxy(true);
|
setLoadingLnproxy(true);
|
||||||
let body: { invoice: string; description: string; routing_msat?: string } = {
|
const body: { invoice: string; description: string; routing_msat?: string } = {
|
||||||
invoice: lightning.lnproxyInvoice,
|
invoice: lightning.lnproxyInvoice,
|
||||||
description: '',
|
description: '',
|
||||||
};
|
};
|
||||||
if (lightning.lnproxyBudgetSats > 0) {
|
if (lightning.lnproxyBudgetSats > 0) {
|
||||||
body['routing_msat'] = String(lightning.lnproxyBudgetSats * 1000);
|
body.routing_msat = String(lightning.lnproxyBudgetSats * 1000);
|
||||||
}
|
}
|
||||||
apiClient
|
apiClient
|
||||||
.post(filteredProxies[lightning.lnproxyServer]['url'], '', body)
|
.post(filteredProxies[lightning.lnproxyServer].url, '', body)
|
||||||
.then((data) => {
|
.then((data) => {
|
||||||
if (data.reason) {
|
if (data.reason !== undefined) {
|
||||||
setLightning({ ...lightning, badLnproxy: data.reason });
|
setLightning({ ...lightning, badLnproxy: data.reason });
|
||||||
} else if (data.proxy_invoice) {
|
} else if (data.proxy_invoice !== undefined) {
|
||||||
setLightning({ ...lightning, invoice: data.proxy_invoice, badLnproxy: '' });
|
setLightning({ ...lightning, invoice: data.proxy_invoice, badLnproxy: '' });
|
||||||
} else {
|
} else {
|
||||||
setLightning({ ...lightning, badLnproxy: 'Unknown lnproxy response' });
|
setLightning({ ...lightning, badLnproxy: 'Unknown lnproxy response' });
|
||||||
@ -203,7 +203,7 @@ export const LightningPayoutForm = ({
|
|||||||
});
|
});
|
||||||
};
|
};
|
||||||
|
|
||||||
const handleAdvancedOptions = function (checked: boolean) {
|
const handleAdvancedOptions = function (checked: boolean): void {
|
||||||
if (checked) {
|
if (checked) {
|
||||||
setLightning({
|
setLightning({
|
||||||
...lightning,
|
...lightning,
|
||||||
@ -218,7 +218,7 @@ export const LightningPayoutForm = ({
|
|||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
const onProxyBudgetChange = function (e) {
|
const onProxyBudgetChange = function (e: React.ChangeEventHandler<HTMLInputElement>): void {
|
||||||
if (isFinite(e.target.value) && e.target.value >= 0) {
|
if (isFinite(e.target.value) && e.target.value >= 0) {
|
||||||
let lnproxyBudgetSats;
|
let lnproxyBudgetSats;
|
||||||
let lnproxyBudgetPPM;
|
let lnproxyBudgetPPM;
|
||||||
@ -238,7 +238,7 @@ export const LightningPayoutForm = ({
|
|||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
const onRoutingBudgetChange = function (e) {
|
const onRoutingBudgetChange = function (e: React.ChangeEventHandler<HTMLInputElement>): void {
|
||||||
const tradeAmount = order.trade_satoshis;
|
const tradeAmount = order.trade_satoshis;
|
||||||
if (isFinite(e.target.value) && e.target.value >= 0) {
|
if (isFinite(e.target.value) && e.target.value >= 0) {
|
||||||
let routingBudgetSats;
|
let routingBudgetSats;
|
||||||
@ -261,7 +261,7 @@ export const LightningPayoutForm = ({
|
|||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
const lnProxyBudgetHelper = function () {
|
const lnProxyBudgetHelper = function (): string {
|
||||||
let text = '';
|
let text = '';
|
||||||
if (lightning.lnproxyBudgetSats < 0) {
|
if (lightning.lnproxyBudgetSats < 0) {
|
||||||
text = 'Must be positive';
|
text = 'Must be positive';
|
||||||
@ -271,7 +271,7 @@ export const LightningPayoutForm = ({
|
|||||||
return text;
|
return text;
|
||||||
};
|
};
|
||||||
|
|
||||||
const routingBudgetHelper = function () {
|
const routingBudgetHelper = function (): string {
|
||||||
let text = '';
|
let text = '';
|
||||||
if (lightning.routingBudgetSats < 0) {
|
if (lightning.routingBudgetSats < 0) {
|
||||||
text = 'Must be positive';
|
text = 'Must be positive';
|
||||||
@ -334,12 +334,12 @@ export const LightningPayoutForm = ({
|
|||||||
<TextField
|
<TextField
|
||||||
sx={{ width: '14em' }}
|
sx={{ width: '14em' }}
|
||||||
disabled={!lightning.advancedOptions}
|
disabled={!lightning.advancedOptions}
|
||||||
error={routingBudgetHelper() != ''}
|
error={routingBudgetHelper() !== ''}
|
||||||
helperText={routingBudgetHelper()}
|
helperText={routingBudgetHelper()}
|
||||||
label={t('Routing Budget')}
|
label={t('Routing Budget')}
|
||||||
required={true}
|
required={true}
|
||||||
value={
|
value={
|
||||||
lightning.routingBudgetUnit == 'PPM'
|
lightning.routingBudgetUnit === 'PPM'
|
||||||
? lightning.routingBudgetPPM
|
? lightning.routingBudgetPPM
|
||||||
: lightning.routingBudgetSats
|
: lightning.routingBudgetSats
|
||||||
}
|
}
|
||||||
@ -353,7 +353,7 @@ export const LightningPayoutForm = ({
|
|||||||
setLightning({
|
setLightning({
|
||||||
...lightning,
|
...lightning,
|
||||||
routingBudgetUnit:
|
routingBudgetUnit:
|
||||||
lightning.routingBudgetUnit == 'PPM' ? 'Sats' : 'PPM',
|
lightning.routingBudgetUnit === 'PPM' ? 'Sats' : 'PPM',
|
||||||
});
|
});
|
||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
@ -382,11 +382,11 @@ export const LightningPayoutForm = ({
|
|||||||
>
|
>
|
||||||
<div>
|
<div>
|
||||||
<FormControlLabel
|
<FormControlLabel
|
||||||
onChange={(e) => {
|
onChange={(e, checked) => {
|
||||||
setLightning({
|
setLightning({
|
||||||
...lightning,
|
...lightning,
|
||||||
useLnproxy: e.target.checked,
|
useLnproxy: checked,
|
||||||
invoice: e.target.checked ? '' : lightning.invoice,
|
invoice: checked ? '' : lightning.invoice,
|
||||||
});
|
});
|
||||||
}}
|
}}
|
||||||
checked={lightning.useLnproxy}
|
checked={lightning.useLnproxy}
|
||||||
@ -419,7 +419,7 @@ export const LightningPayoutForm = ({
|
|||||||
spacing={1}
|
spacing={1}
|
||||||
>
|
>
|
||||||
<Grid item>
|
<Grid item>
|
||||||
<FormControl error={noMatchingLnProxies != ''}>
|
<FormControl error={noMatchingLnProxies !== ''}>
|
||||||
<InputLabel id='select-label'>{t('Server')}</InputLabel>
|
<InputLabel id='select-label'>{t('Server')}</InputLabel>
|
||||||
<Select
|
<Select
|
||||||
sx={{ width: '14em' }}
|
sx={{ width: '14em' }}
|
||||||
@ -436,7 +436,7 @@ export const LightningPayoutForm = ({
|
|||||||
</MenuItem>
|
</MenuItem>
|
||||||
))}
|
))}
|
||||||
</Select>
|
</Select>
|
||||||
{noMatchingLnProxies != '' ? (
|
{noMatchingLnProxies !== '' ? (
|
||||||
<FormHelperText>{t(noMatchingLnProxies)}</FormHelperText>
|
<FormHelperText>{t(noMatchingLnProxies)}</FormHelperText>
|
||||||
) : (
|
) : (
|
||||||
<></>
|
<></>
|
||||||
@ -448,11 +448,11 @@ export const LightningPayoutForm = ({
|
|||||||
<TextField
|
<TextField
|
||||||
sx={{ width: '14em' }}
|
sx={{ width: '14em' }}
|
||||||
disabled={!lightning.useLnproxy}
|
disabled={!lightning.useLnproxy}
|
||||||
error={lnProxyBudgetHelper() != ''}
|
error={lnProxyBudgetHelper() !== ''}
|
||||||
helperText={lnProxyBudgetHelper()}
|
helperText={lnProxyBudgetHelper()}
|
||||||
label={t('Proxy Budget')}
|
label={t('Proxy Budget')}
|
||||||
value={
|
value={
|
||||||
lightning.lnproxyBudgetUnit == 'PPM'
|
lightning.lnproxyBudgetUnit === 'PPM'
|
||||||
? lightning.lnproxyBudgetPPM
|
? lightning.lnproxyBudgetPPM
|
||||||
: lightning.lnproxyBudgetSats
|
: lightning.lnproxyBudgetSats
|
||||||
}
|
}
|
||||||
@ -466,7 +466,7 @@ export const LightningPayoutForm = ({
|
|||||||
setLightning({
|
setLightning({
|
||||||
...lightning,
|
...lightning,
|
||||||
lnproxyBudgetUnit:
|
lnproxyBudgetUnit:
|
||||||
lightning.lnproxyBudgetUnit == 'PPM' ? 'Sats' : 'PPM',
|
lightning.lnproxyBudgetUnit === 'PPM' ? 'Sats' : 'PPM',
|
||||||
});
|
});
|
||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
@ -520,9 +520,9 @@ export const LightningPayoutForm = ({
|
|||||||
<TextField
|
<TextField
|
||||||
fullWidth={true}
|
fullWidth={true}
|
||||||
disabled={!lightning.useLnproxy}
|
disabled={!lightning.useLnproxy}
|
||||||
error={lightning.badLnproxy != ''}
|
error={lightning.badLnproxy !== ''}
|
||||||
FormHelperTextProps={{ style: { wordBreak: 'break-all' } }}
|
FormHelperTextProps={{ style: { wordBreak: 'break-all' } }}
|
||||||
helperText={lightning.badLnproxy ? t(lightning.badLnproxy) : ''}
|
helperText={lightning.badLnproxy !== '' ? t(lightning.badLnproxy) : ''}
|
||||||
label={t('Invoice to wrap')}
|
label={t('Invoice to wrap')}
|
||||||
required
|
required
|
||||||
value={lightning.lnproxyInvoice}
|
value={lightning.lnproxyInvoice}
|
||||||
@ -541,8 +541,8 @@ export const LightningPayoutForm = ({
|
|||||||
fullWidth={true}
|
fullWidth={true}
|
||||||
sx={lightning.useLnproxy ? { borderRadius: 0 } : {}}
|
sx={lightning.useLnproxy ? { borderRadius: 0 } : {}}
|
||||||
disabled={lightning.useLnproxy}
|
disabled={lightning.useLnproxy}
|
||||||
error={lightning.badInvoice != ''}
|
error={lightning.badInvoice !== ''}
|
||||||
helperText={lightning.badInvoice ? t(lightning.badInvoice) : ''}
|
helperText={lightning.badInvoice !== '' ? t(lightning.badInvoice) : ''}
|
||||||
FormHelperTextProps={{ style: { wordBreak: 'break-all' } }}
|
FormHelperTextProps={{ style: { wordBreak: 'break-all' } }}
|
||||||
label={lightning.useLnproxy ? t('Wrapped invoice') : t('Payout Lightning Invoice')}
|
label={lightning.useLnproxy ? t('Wrapped invoice') : t('Payout Lightning Invoice')}
|
||||||
required
|
required
|
||||||
@ -566,8 +566,8 @@ export const LightningPayoutForm = ({
|
|||||||
loading={loadingLnproxy}
|
loading={loadingLnproxy}
|
||||||
disabled={
|
disabled={
|
||||||
lightning.lnproxyInvoice.length < 20 ||
|
lightning.lnproxyInvoice.length < 20 ||
|
||||||
noMatchingLnProxies != '' ||
|
noMatchingLnProxies !== '' ||
|
||||||
lightning.badLnproxy != ''
|
lightning.badLnproxy !== ''
|
||||||
}
|
}
|
||||||
onClick={fetchLnproxy}
|
onClick={fetchLnproxy}
|
||||||
variant='outlined'
|
variant='outlined'
|
||||||
@ -580,7 +580,7 @@ export const LightningPayoutForm = ({
|
|||||||
)}
|
)}
|
||||||
<LoadingButton
|
<LoadingButton
|
||||||
loading={loading}
|
loading={loading}
|
||||||
disabled={lightning.invoice.length < 20 || lightning.badInvoice != ''}
|
disabled={lightning.invoice.length < 20 || lightning.badInvoice !== ''}
|
||||||
onClick={() => {
|
onClick={() => {
|
||||||
onClickSubmit(lightning.invoice);
|
onClickSubmit(lightning.invoice);
|
||||||
}}
|
}}
|
||||||
|
@ -39,7 +39,7 @@ export const OnchainPayoutForm = ({
|
|||||||
const invalidFee = onchain.miningFee < minMiningFee || onchain.miningFee > maxMiningFee;
|
const invalidFee = onchain.miningFee < minMiningFee || onchain.miningFee > maxMiningFee;
|
||||||
const costPerVByte = 280;
|
const costPerVByte = 280;
|
||||||
|
|
||||||
const handleMiningFeeChange = (e) => {
|
const handleMiningFeeChange = (e: React.ChangeEventHandler<HTMLInputElement>): void => {
|
||||||
const miningFee = Number(e.target.value);
|
const miningFee = Number(e.target.value);
|
||||||
setOnchain({ ...onchain, miningFee });
|
setOnchain({ ...onchain, miningFee });
|
||||||
};
|
};
|
||||||
@ -67,12 +67,9 @@ export const OnchainPayoutForm = ({
|
|||||||
|
|
||||||
<ListItem>
|
<ListItem>
|
||||||
<ListItemText
|
<ListItemText
|
||||||
primary={
|
primary={`${pn(Math.floor((order.invoice_amount * order.swap_fee_rate) / 100))} Sats (${
|
||||||
pn(Math.floor((order.invoice_amount * order.swap_fee_rate) / 100)) +
|
order.swap_fee_rate
|
||||||
' Sats (' +
|
}%)`}
|
||||||
order.swap_fee_rate +
|
|
||||||
'%)'
|
|
||||||
}
|
|
||||||
secondary={t('Swap fee')}
|
secondary={t('Swap fee')}
|
||||||
/>
|
/>
|
||||||
</ListItem>
|
</ListItem>
|
||||||
@ -81,12 +78,9 @@ export const OnchainPayoutForm = ({
|
|||||||
|
|
||||||
<ListItem>
|
<ListItem>
|
||||||
<ListItemText
|
<ListItemText
|
||||||
primary={
|
primary={`${pn(
|
||||||
pn(Math.floor(Math.max(minMiningFee, onchain.miningFee) * costPerVByte)) +
|
Math.floor(Math.max(minMiningFee, onchain.miningFee) * costPerVByte),
|
||||||
' Sats (' +
|
)} Sats (${Math.max(minMiningFee, onchain.miningFee)} Sats/vByte)`}
|
||||||
Math.max(minMiningFee, onchain.miningFee) +
|
|
||||||
' Sats/vByte)'
|
|
||||||
}
|
|
||||||
secondary={t('Mining fee')}
|
secondary={t('Mining fee')}
|
||||||
/>
|
/>
|
||||||
</ListItem>
|
</ListItem>
|
||||||
@ -115,8 +109,8 @@ export const OnchainPayoutForm = ({
|
|||||||
<Grid container direction='row' justifyContent='center' alignItems='flex-start' spacing={0}>
|
<Grid container direction='row' justifyContent='center' alignItems='flex-start' spacing={0}>
|
||||||
<Grid item xs={7}>
|
<Grid item xs={7}>
|
||||||
<TextField
|
<TextField
|
||||||
error={onchain.badAddress != ''}
|
error={onchain.badAddress !== ''}
|
||||||
helperText={onchain.badAddress ? t(onchain.badAddress) : ''}
|
helperText={onchain.badAddress !== '' ? t(onchain.badAddress) : ''}
|
||||||
label={t('Bitcoin Address')}
|
label={t('Bitcoin Address')}
|
||||||
required
|
required
|
||||||
value={onchain.address}
|
value={onchain.address}
|
||||||
|
Some files were not shown because too many files have changed in this diff Show More
Loading…
Reference in New Issue
Block a user