mirror of
https://github.com/RoboSats/robosats.git
synced 2024-12-13 19:06:26 +00:00
refactor login (clean separation robot/info. Style navbar.
This commit is contained in:
parent
b9dc7f7c95
commit
c660a5b0d1
@ -534,22 +534,23 @@ class UserGenSerializer(serializers.Serializer):
|
||||
required=True,
|
||||
help_text="SHA256 of user secret",
|
||||
)
|
||||
# Optional fields
|
||||
# (PGP keys are mandatory for new users, but optional for logins)
|
||||
public_key = serializers.CharField(
|
||||
max_length=2000,
|
||||
allow_null=False,
|
||||
allow_blank=False,
|
||||
required=True,
|
||||
required=False,
|
||||
help_text="Armored ASCII PGP public key block",
|
||||
)
|
||||
encrypted_private_key = serializers.CharField(
|
||||
max_length=2000,
|
||||
allow_null=False,
|
||||
allow_blank=False,
|
||||
required=True,
|
||||
required=False,
|
||||
help_text="Armored ASCII PGP encrypted private key block",
|
||||
)
|
||||
|
||||
# Optional fields
|
||||
ref_code = serializers.CharField(
|
||||
max_length=30,
|
||||
allow_null=True,
|
||||
|
46
api/views.py
46
api/views.py
@ -653,19 +653,6 @@ class UserView(APIView):
|
||||
encrypted_private_key = serializer.data.get("encrypted_private_key")
|
||||
ref_code = serializer.data.get("ref_code")
|
||||
|
||||
if not public_key or not encrypted_private_key:
|
||||
context["bad_request"] = "Must provide valid 'pub' and 'enc_priv' PGP keys"
|
||||
return Response(context, status.HTTP_400_BAD_REQUEST)
|
||||
|
||||
(
|
||||
valid,
|
||||
bad_keys_context,
|
||||
public_key,
|
||||
encrypted_private_key,
|
||||
) = Logics.validate_pgp_keys(public_key, encrypted_private_key)
|
||||
if not valid:
|
||||
return Response(bad_keys_context, status.HTTP_400_BAD_REQUEST)
|
||||
|
||||
# Now the server only receives a hash of the token. So server trusts the client
|
||||
# with computing length, counts and unique_values to confirm the high entropy of the token
|
||||
# In any case, it is up to the client if they want to create a bad high entropy token.
|
||||
@ -712,6 +699,20 @@ class UserView(APIView):
|
||||
|
||||
# Create new credentials and login if nickname is new
|
||||
if len(User.objects.filter(username=nickname)) == 0:
|
||||
if not public_key or not encrypted_private_key:
|
||||
context[
|
||||
"bad_request"
|
||||
] = "Must provide valid 'pub' and 'enc_priv' PGP keys"
|
||||
return Response(context, status.HTTP_400_BAD_REQUEST)
|
||||
(
|
||||
valid,
|
||||
bad_keys_context,
|
||||
public_key,
|
||||
encrypted_private_key,
|
||||
) = Logics.validate_pgp_keys(public_key, encrypted_private_key)
|
||||
if not valid:
|
||||
return Response(bad_keys_context, status.HTTP_400_BAD_REQUEST)
|
||||
|
||||
User.objects.create_user(
|
||||
username=nickname, password=token_sha256, is_staff=False
|
||||
)
|
||||
@ -931,25 +932,6 @@ class InfoView(ListAPIView):
|
||||
BalanceLog.objects.latest("time")
|
||||
)
|
||||
|
||||
if request.user.is_authenticated:
|
||||
context["nickname"] = request.user.username
|
||||
context["referral_code"] = str(request.user.profile.referral_code)
|
||||
context["earned_rewards"] = request.user.profile.earned_rewards
|
||||
context["wants_stealth"] = request.user.profile.wants_stealth
|
||||
# Adds/generate telegram token and whether it is enabled
|
||||
context = {**context, **Telegram.get_context(request.user)}
|
||||
has_no_active_order, _, order = Logics.validate_already_maker_or_taker(
|
||||
request.user
|
||||
)
|
||||
if not has_no_active_order:
|
||||
context["active_order_id"] = order.id
|
||||
else:
|
||||
last_order = Order.objects.filter(
|
||||
Q(maker=request.user) | Q(taker=request.user)
|
||||
).last()
|
||||
if last_order:
|
||||
context["last_order_id"] = last_order.id
|
||||
|
||||
return Response(context, status.HTTP_200_OK)
|
||||
|
||||
|
||||
|
@ -7,7 +7,7 @@ import MakerPage from './MakerPage';
|
||||
import BookPage from './BookPage';
|
||||
import OrderPage from './OrderPage';
|
||||
import SettingsPage from './SettingsPage';
|
||||
import NavBar from './NavBar';
|
||||
import NavBar, { Page } from './NavBar';
|
||||
import MainDialogs, { OpenDialogs } from './MainDialogs';
|
||||
|
||||
import { apiClient } from '../services/api';
|
||||
@ -25,6 +25,8 @@ import {
|
||||
defaultRobot,
|
||||
defaultInfo,
|
||||
} from '../models';
|
||||
import { sha256 } from 'js-sha256';
|
||||
import RobotAvatar from '../components/RobotAvatar';
|
||||
|
||||
const getWindowSize = function (fontSize: number) {
|
||||
// returns window size in EM units
|
||||
@ -34,7 +36,6 @@ const getWindowSize = function (fontSize: number) {
|
||||
};
|
||||
};
|
||||
|
||||
type Page = 'robot' | 'order' | 'create' | 'offers' | 'settings' | 'none';
|
||||
interface SlideDirection {
|
||||
in: 'left' | 'right' | undefined;
|
||||
out: 'left' | 'right' | undefined;
|
||||
@ -66,6 +67,7 @@ const Main = ({ settings, setSettings }: MainProps): JSX.Element => {
|
||||
in: undefined,
|
||||
out: undefined,
|
||||
});
|
||||
const [order, setOrder] = useState<number | null>(null);
|
||||
|
||||
const navbarHeight = 2.5;
|
||||
const closeAll = {
|
||||
@ -76,6 +78,7 @@ const Main = ({ settings, setSettings }: MainProps): JSX.Element => {
|
||||
coordinator: false,
|
||||
stats: false,
|
||||
update: false,
|
||||
profile: false,
|
||||
};
|
||||
const [open, setOpen] = useState<OpenDialogs>(closeAll);
|
||||
|
||||
@ -133,27 +136,60 @@ const Main = ({ settings, setSettings }: MainProps): JSX.Element => {
|
||||
coordinatorVersion: versionInfo.coordinatorVersion,
|
||||
clientVersion: versionInfo.clientVersion,
|
||||
});
|
||||
if (!robot.nickname && data.nickname && !robot.loading) {
|
||||
setRobot({
|
||||
...robot,
|
||||
nickname: data.nickname,
|
||||
loading: false,
|
||||
activeOrderId: data.active_order_id ?? null,
|
||||
lastOrderId: data.last_order_id ?? null,
|
||||
referralCode: data.referral_code,
|
||||
tgEnabled: data.tg_enabled,
|
||||
tgBotName: data.tg_bot_name,
|
||||
tgToken: data.tg_token,
|
||||
earnedRewards: data.earned_rewards ?? 0,
|
||||
stealthInvoices: data.wants_stealth,
|
||||
});
|
||||
}
|
||||
});
|
||||
};
|
||||
|
||||
console.log(page, slideDirection);
|
||||
const fetchRobot = function () {
|
||||
const requestBody = {
|
||||
token_sha256: sha256(robot.token),
|
||||
};
|
||||
|
||||
apiClient.post('/api/user/', requestBody).then((data: any) => {
|
||||
console.log(data);
|
||||
setOrder(
|
||||
data.active_order_id
|
||||
? data.active_order_id
|
||||
: data.last_order_id
|
||||
? data.last_order_id
|
||||
: order,
|
||||
);
|
||||
setRobot({
|
||||
...robot,
|
||||
nickname: data.nickname,
|
||||
token: robot.token,
|
||||
loading: false,
|
||||
avatarLoaded: false,
|
||||
activeOrderId: data.active_order_id ? data.active_order_id : null,
|
||||
lastOrderId: data.last_order_id ? data.last_order_id : null,
|
||||
referralCode: data.referral_code,
|
||||
earnedRewards: data.earned_rewards ?? 0,
|
||||
stealthInvoices: data.wants_stealth,
|
||||
tgEnabled: data.tg_enabled,
|
||||
tgBotName: data.tg_bot_name,
|
||||
tgToken: data.tg_token,
|
||||
bitsEntropy: data.token_bits_entropy,
|
||||
shannonEntropy: data.token_shannon_entropy,
|
||||
pub_key: data.public_key,
|
||||
enc_priv_key: data.encrypted_private_key,
|
||||
copiedToken: data.found ? true : robot.copiedToken,
|
||||
});
|
||||
});
|
||||
};
|
||||
|
||||
useEffect(() => {
|
||||
if (robot.token && robot.nickname === null) {
|
||||
fetchRobot();
|
||||
}
|
||||
}, []);
|
||||
|
||||
return (
|
||||
<Router basename={basename}>
|
||||
{/* load robot avatar image, set avatarLoaded: true */}
|
||||
<RobotAvatar
|
||||
style={{ display: 'none' }}
|
||||
nickname={robot.avatarLoaded ? robot.nickname : null}
|
||||
onLoad={() => setRobot({ ...robot, avatarLoaded: true })}
|
||||
/>
|
||||
<Box
|
||||
style={{
|
||||
position: 'absolute',
|
||||
@ -167,13 +203,25 @@ const Main = ({ settings, setSettings }: MainProps): JSX.Element => {
|
||||
exact
|
||||
path='/'
|
||||
render={(props: any) => (
|
||||
<UserGenPage match={props.match} theme={theme} robot={robot} setRobot={setRobot} />
|
||||
<UserGenPage
|
||||
setPage={setPage}
|
||||
match={props.match}
|
||||
theme={theme}
|
||||
robot={robot}
|
||||
setRobot={setRobot}
|
||||
/>
|
||||
)}
|
||||
/>
|
||||
<Route
|
||||
path='/robot/:refCode?'
|
||||
render={(props: any) => (
|
||||
<UserGenPage match={props.match} theme={theme} robot={robot} setRobot={setRobot} />
|
||||
<UserGenPage
|
||||
setPage={setPage}
|
||||
match={props.match}
|
||||
theme={theme}
|
||||
robot={robot}
|
||||
setRobot={setRobot}
|
||||
/>
|
||||
)}
|
||||
/>
|
||||
|
||||
@ -195,6 +243,7 @@ const Main = ({ settings, setSettings }: MainProps): JSX.Element => {
|
||||
setMaker={setMaker}
|
||||
lastDayPremium={info.last_day_nonkyc_btc_premium}
|
||||
windowSize={windowSize}
|
||||
hasRobot={robot.avatarLoaded}
|
||||
/>
|
||||
</div>
|
||||
</Slide>
|
||||
@ -213,9 +262,12 @@ const Main = ({ settings, setSettings }: MainProps): JSX.Element => {
|
||||
fetchLimits={fetchLimits}
|
||||
maker={maker}
|
||||
setMaker={setMaker}
|
||||
setPage={setPage}
|
||||
setOrder={setOrder}
|
||||
fav={fav}
|
||||
setFav={setFav}
|
||||
windowSize={{ ...windowSize, height: windowSize.height - navbarHeight }}
|
||||
hasRobot={robot.avatarLoaded}
|
||||
/>
|
||||
</div>
|
||||
</Slide>
|
||||
@ -244,6 +296,7 @@ const Main = ({ settings, setSettings }: MainProps): JSX.Element => {
|
||||
</Switch>
|
||||
</Box>
|
||||
<NavBar
|
||||
nickname={robot.nickname}
|
||||
width={windowSize.width}
|
||||
height={navbarHeight}
|
||||
page={page}
|
||||
@ -252,13 +305,17 @@ const Main = ({ settings, setSettings }: MainProps): JSX.Element => {
|
||||
setOpen={setOpen}
|
||||
closeAll={closeAll}
|
||||
setSlideDirection={setSlideDirection}
|
||||
robot={robot}
|
||||
order={order}
|
||||
hasRobot={robot.avatarLoaded}
|
||||
/>
|
||||
<MainDialogs
|
||||
open={open}
|
||||
setOpen={setOpen}
|
||||
setRobot={setRobot}
|
||||
info={info}
|
||||
setInfo={setInfo}
|
||||
fetchInfo={fetchInfo}
|
||||
robot={robot}
|
||||
closeAll={closeAll}
|
||||
/>
|
||||
<MainDialogs open={open} setOpen={setOpen} info={info} closeAll={closeAll} />
|
||||
</Router>
|
||||
);
|
||||
};
|
||||
|
@ -1,12 +1,11 @@
|
||||
import React, { useEffect, useState } from 'react';
|
||||
import { useTranslation } from 'react-i18next';
|
||||
import { useTheme, styled, Grid, IconButton } from '@mui/material';
|
||||
import { Info } from '../../models';
|
||||
import React, { useEffect } from 'react';
|
||||
import { Info, Robot } from '../../models';
|
||||
import {
|
||||
CommunityDialog,
|
||||
CoordinatorSummaryDialog,
|
||||
InfoDialog,
|
||||
LearnDialog,
|
||||
ProfileDialog,
|
||||
StatsDialog,
|
||||
UpdateClientDialog,
|
||||
} from '../../components/Dialogs';
|
||||
@ -19,19 +18,26 @@ export interface OpenDialogs {
|
||||
coordinator: boolean;
|
||||
stats: boolean;
|
||||
update: boolean;
|
||||
profile: boolean; // temporary until new Robot Page is ready
|
||||
}
|
||||
|
||||
interface MainDialogsProps {
|
||||
open: OpenDialogs;
|
||||
setOpen: (state: OpenDialogs) => void;
|
||||
info: Info;
|
||||
robot: Robot;
|
||||
setRobot: (state: Robot) => void;
|
||||
closeAll: OpenDialogs;
|
||||
}
|
||||
|
||||
const MainDialogs = ({ open, setOpen, info, closeAll }: MainDialogsProps): JSX.Element => {
|
||||
const { t } = useTranslation();
|
||||
const theme = useTheme();
|
||||
|
||||
const MainDialogs = ({
|
||||
open,
|
||||
setOpen,
|
||||
info,
|
||||
closeAll,
|
||||
robot,
|
||||
setRobot,
|
||||
}: MainDialogsProps): JSX.Element => {
|
||||
useEffect(() => {
|
||||
if (info.openUpdateClient) {
|
||||
setOpen({ ...closeAll, update: true });
|
||||
@ -83,6 +89,12 @@ const MainDialogs = ({ open, setOpen, info, closeAll }: MainDialogsProps): JSX.E
|
||||
lastDayVolume={info.last_day_volume}
|
||||
lifetimeVolume={info.lifetime_volume}
|
||||
/>
|
||||
<ProfileDialog
|
||||
open={open.profile}
|
||||
onClose={() => setOpen({ ...open, profile: false })}
|
||||
robot={robot}
|
||||
setRobot={setRobot}
|
||||
/>
|
||||
</>
|
||||
);
|
||||
};
|
||||
|
@ -8,6 +8,7 @@ import { filterOrders } from '../../utils';
|
||||
|
||||
import MakerForm from '../../components/MakerForm';
|
||||
import BookTable from '../../components/BookTable';
|
||||
import { Page } from '../NavBar';
|
||||
|
||||
interface MakerPageProps {
|
||||
limits: { list: LimitList; loading: boolean };
|
||||
@ -18,6 +19,9 @@ interface MakerPageProps {
|
||||
setFav: (state: Favorites) => void;
|
||||
setMaker: (state: Maker) => void;
|
||||
windowSize: { width: number; height: number };
|
||||
hasRobot: boolean;
|
||||
setOrder: (state: number) => void;
|
||||
setPage: (state: Page) => void;
|
||||
}
|
||||
|
||||
const MakerPage = ({
|
||||
@ -29,6 +33,9 @@ const MakerPage = ({
|
||||
setFav,
|
||||
setMaker,
|
||||
windowSize,
|
||||
setOrder,
|
||||
setPage,
|
||||
hasRobot = false,
|
||||
}: MakerPageProps): JSX.Element => {
|
||||
const { t } = useTranslation();
|
||||
|
||||
@ -86,6 +93,10 @@ const MakerPage = ({
|
||||
setFav={setFav}
|
||||
maker={maker}
|
||||
setMaker={setMaker}
|
||||
onOrderCreated={(id) => {
|
||||
setOrder(id);
|
||||
setPage('order');
|
||||
}}
|
||||
disableRequest={matches.length > 0 && !showMatches}
|
||||
collapseAll={showMatches}
|
||||
onSubmit={() => setShowMatches(matches.length > 0)}
|
||||
|
@ -19,24 +19,21 @@ const StyledTooltip = styled(({ className, ...props }: TooltipProps) => (
|
||||
},
|
||||
}));
|
||||
|
||||
export interface OpenDialogs {
|
||||
more: boolean;
|
||||
learn: boolean;
|
||||
community: boolean;
|
||||
info: boolean;
|
||||
coordinator: boolean;
|
||||
stats: boolean;
|
||||
update: boolean;
|
||||
}
|
||||
|
||||
interface MoreTooltipProps {
|
||||
open: OpenDialogs;
|
||||
nickname: string | null;
|
||||
setOpen: (state: OpenDialogs) => void;
|
||||
closeAll: OpenDialogs;
|
||||
children: JSX.Element;
|
||||
}
|
||||
|
||||
const MoreTooltip = ({ open, setOpen, closeAll, children }: MoreTooltipProps): JSX.Element => {
|
||||
const MoreTooltip = ({
|
||||
open,
|
||||
setOpen,
|
||||
closeAll,
|
||||
nickname,
|
||||
children,
|
||||
}: MoreTooltipProps): JSX.Element => {
|
||||
const { t } = useTranslation();
|
||||
const theme = useTheme();
|
||||
return (
|
||||
@ -56,7 +53,7 @@ const MoreTooltip = ({ open, setOpen, closeAll, children }: MoreTooltipProps): J
|
||||
sx={{
|
||||
color: open.info ? theme.palette.primary.main : theme.palette.text.secondary,
|
||||
}}
|
||||
onClick={() => setOpen({ ...closeAll, more: true, info: !open.info })}
|
||||
onClick={() => setOpen({ ...closeAll, info: !open.info })}
|
||||
>
|
||||
<Info />
|
||||
</IconButton>
|
||||
@ -69,7 +66,7 @@ const MoreTooltip = ({ open, setOpen, closeAll, children }: MoreTooltipProps): J
|
||||
sx={{
|
||||
color: open.learn ? theme.palette.primary.main : theme.palette.text.secondary,
|
||||
}}
|
||||
onClick={() => setOpen({ ...closeAll, more: true, learn: !open.learn })}
|
||||
onClick={() => setOpen({ ...closeAll, learn: !open.learn })}
|
||||
>
|
||||
<School />
|
||||
</IconButton>
|
||||
@ -86,7 +83,7 @@ const MoreTooltip = ({ open, setOpen, closeAll, children }: MoreTooltipProps): J
|
||||
sx={{
|
||||
color: open.community ? theme.palette.primary.main : theme.palette.text.secondary,
|
||||
}}
|
||||
onClick={() => setOpen({ ...closeAll, more: true, community: !open.community })}
|
||||
onClick={() => setOpen({ ...closeAll, community: !open.community })}
|
||||
>
|
||||
<People />
|
||||
</IconButton>
|
||||
@ -101,7 +98,7 @@ const MoreTooltip = ({ open, setOpen, closeAll, children }: MoreTooltipProps): J
|
||||
? theme.palette.primary.main
|
||||
: theme.palette.text.secondary,
|
||||
}}
|
||||
onClick={() => setOpen({ ...closeAll, more: true, coordinator: !open.coordinator })}
|
||||
onClick={() => setOpen({ ...closeAll, coordinator: !open.coordinator })}
|
||||
>
|
||||
<PriceChange />
|
||||
</IconButton>
|
||||
@ -114,7 +111,7 @@ const MoreTooltip = ({ open, setOpen, closeAll, children }: MoreTooltipProps): J
|
||||
sx={{
|
||||
color: open.stats ? theme.palette.primary.main : theme.palette.text.secondary,
|
||||
}}
|
||||
onClick={() => setOpen({ ...closeAll, more: true, stats: !open.stats })}
|
||||
onClick={() => setOpen({ ...closeAll, stats: !open.stats })}
|
||||
>
|
||||
<BubbleChart />
|
||||
</IconButton>
|
||||
|
@ -2,11 +2,12 @@ import React, { useEffect, useState } from 'react';
|
||||
import { useTranslation } from 'react-i18next';
|
||||
import { useHistory } from 'react-router-dom';
|
||||
import { Tabs, Tab, Paper, useTheme } from '@mui/material';
|
||||
import RobotAvatar from '../../components/RobotAvatar';
|
||||
import MoreTooltip from './MoreTooltip';
|
||||
|
||||
import { OpenDialogs } from '../MainDialogs';
|
||||
|
||||
import { Page } from '.';
|
||||
|
||||
import {
|
||||
SettingsApplications,
|
||||
SmartToy,
|
||||
@ -15,12 +16,13 @@ import {
|
||||
Assignment,
|
||||
MoreHoriz,
|
||||
} from '@mui/icons-material';
|
||||
import RobotAvatar from '../../components/RobotAvatar';
|
||||
|
||||
type Page = 'robot' | 'offers' | 'create' | 'order' | 'settings' | 'none';
|
||||
type Direction = 'left' | 'right' | undefined;
|
||||
|
||||
interface NavBarProps {
|
||||
page: Page;
|
||||
nickname?: string | null;
|
||||
setPage: (state: Page) => void;
|
||||
slideDirection: { in: Direction; out: Direction };
|
||||
setSlideDirection: (state: { in: Direction; out: Direction }) => void;
|
||||
@ -29,6 +31,8 @@ interface NavBarProps {
|
||||
open: OpenDialogs;
|
||||
setOpen: (state: OpenDialogs) => void;
|
||||
closeAll: OpenDialogs;
|
||||
order: number | null;
|
||||
hasRobot: boolean;
|
||||
}
|
||||
|
||||
const NavBar = ({
|
||||
@ -37,20 +41,24 @@ const NavBar = ({
|
||||
slideDirection,
|
||||
setSlideDirection,
|
||||
open,
|
||||
nickname = null,
|
||||
setOpen,
|
||||
closeAll,
|
||||
width,
|
||||
height,
|
||||
order,
|
||||
hasRobot = false,
|
||||
}: NavBarProps): JSX.Element => {
|
||||
const theme = useTheme();
|
||||
const { t } = useTranslation();
|
||||
const history = useHistory();
|
||||
const smallBar = width < 50;
|
||||
const tabWidth = smallBar ? 1 : 12;
|
||||
|
||||
const [newPage, setNewPage] = useState<Page>(history.location.pathname.split('/')[1]);
|
||||
|
||||
const tabSx = smallBar ? {} : { position: 'relative', bottom: '1em' };
|
||||
const tabSx = smallBar
|
||||
? { position: 'relative', bottom: nickname ? '0.8em' : '0em', minWidth: '1em' }
|
||||
: { position: 'relative', bottom: '1em', minWidth: '2em' };
|
||||
const pagesPosition = {
|
||||
robot: 1,
|
||||
offers: 2,
|
||||
@ -73,7 +81,11 @@ const NavBar = ({
|
||||
} else {
|
||||
handleSlideDirection(page, newPage);
|
||||
setNewPage(newPage);
|
||||
setTimeout(() => history.push(`/${newPage}`), theme.transitions.duration.leavingScreen * 3);
|
||||
const param = newPage === 'order' ? order ?? '' : '';
|
||||
setTimeout(
|
||||
() => history.push(`/${newPage}/${param}`),
|
||||
theme.transitions.duration.leavingScreen * 3,
|
||||
);
|
||||
}
|
||||
};
|
||||
|
||||
@ -92,16 +104,30 @@ const NavBar = ({
|
||||
>
|
||||
<Tabs
|
||||
TabIndicatorProps={{ sx: { height: '0.3em', position: 'absolute', top: 0 } }}
|
||||
variant={smallBar ? 'scrollable' : 'fullWidth'}
|
||||
centered={!smallBar}
|
||||
allowScrollButtonsMobile
|
||||
scrollButtons={smallBar}
|
||||
variant='fullWidth'
|
||||
value={page}
|
||||
onChange={changePage}
|
||||
>
|
||||
<Tab
|
||||
sx={{ ...tabSx, minWidth: '2.5em', width: '2.5em', maxWidth: '4em' }}
|
||||
value='none'
|
||||
onClick={() => setOpen({ ...closeAll, profile: !open.profile })}
|
||||
icon={
|
||||
nickname ? (
|
||||
<RobotAvatar
|
||||
style={{ width: '2.3em', height: '2.3em' }}
|
||||
avatarClass='phoneFlippedSmallAvatar'
|
||||
nickname={nickname}
|
||||
/>
|
||||
) : (
|
||||
<></>
|
||||
)
|
||||
}
|
||||
/>
|
||||
|
||||
<Tab
|
||||
label={smallBar ? undefined : t('Robot')}
|
||||
sx={tabSx}
|
||||
sx={{ ...tabSx, minWidth: '1em' }}
|
||||
value='robot'
|
||||
icon={<SmartToy />}
|
||||
iconPosition='start'
|
||||
@ -124,7 +150,8 @@ const NavBar = ({
|
||||
<Tab
|
||||
sx={tabSx}
|
||||
label={smallBar ? undefined : t('Order')}
|
||||
value='order/1'
|
||||
value='order'
|
||||
disabled={!hasRobot}
|
||||
icon={<Assignment />}
|
||||
iconPosition='start'
|
||||
/>
|
||||
@ -136,18 +163,20 @@ const NavBar = ({
|
||||
iconPosition='start'
|
||||
/>
|
||||
|
||||
<MoreTooltip open={open} setOpen={setOpen} closeAll={closeAll}>
|
||||
<Tab
|
||||
sx={tabSx}
|
||||
label={smallBar ? undefined : t('More')}
|
||||
value={'none'}
|
||||
onClick={() => {
|
||||
setOpen(open.more ? closeAll : { ...open, more: true });
|
||||
}}
|
||||
icon={<MoreHoriz />}
|
||||
iconPosition='start'
|
||||
/>
|
||||
</MoreTooltip>
|
||||
<Tab
|
||||
sx={tabSx}
|
||||
label={smallBar ? undefined : t('More')}
|
||||
value='none'
|
||||
onClick={(e) => {
|
||||
open.more ? null : setOpen({ ...open, more: true });
|
||||
}}
|
||||
icon={
|
||||
<MoreTooltip open={open} nickname={nickname} setOpen={setOpen} closeAll={closeAll}>
|
||||
<MoreHoriz />
|
||||
</MoreTooltip>
|
||||
}
|
||||
iconPosition='start'
|
||||
/>
|
||||
</Tabs>
|
||||
</Paper>
|
||||
);
|
||||
|
@ -1,4 +1,5 @@
|
||||
import NavBar from './NavBar';
|
||||
|
||||
export type Page = 'robot' | 'order' | 'create' | 'offers' | 'settings' | 'none';
|
||||
export type { OpenDialogs } from './MoreTooltip';
|
||||
export default NavBar;
|
||||
|
@ -18,7 +18,7 @@ const SettingsPage = ({ settings, setSettings, windowSize }: SettingsPageProps):
|
||||
return (
|
||||
<Paper
|
||||
elevation={12}
|
||||
sx={{ padding: '0.6em', width: '17.25em', maxHeight: `${maxHeight}em`, overflow: 'auto' }}
|
||||
sx={{ padding: '0.6em', width: '18em', maxHeight: `${maxHeight}em`, overflow: 'auto' }}
|
||||
>
|
||||
<Grid container>
|
||||
<Grid item>
|
||||
|
@ -10,8 +10,6 @@ import {
|
||||
CircularProgress,
|
||||
IconButton,
|
||||
} from '@mui/material';
|
||||
import { Link } from 'react-router-dom';
|
||||
import { InfoDialog } from '../components/Dialogs';
|
||||
|
||||
import SmartToyIcon from '@mui/icons-material/SmartToy';
|
||||
import CasinoIcon from '@mui/icons-material/Casino';
|
||||
@ -72,6 +70,7 @@ class UserGenPage extends Component {
|
||||
ref_code: refCode,
|
||||
};
|
||||
});
|
||||
requestBody.then((body) => console.log(body));
|
||||
requestBody.then((body) =>
|
||||
apiClient.post('/api/user/', body).then((data) => {
|
||||
this.setState({ found: data.found, bad_request: data.bad_request });
|
||||
@ -87,6 +86,9 @@ class UserGenPage extends Component {
|
||||
earnedRewards: data.earned_rewards ?? this.props.eartnedRewards,
|
||||
lastOrderId: data.last_order_id ?? this.props.lastOrderId,
|
||||
stealthInvoices: data.wants_stealth ?? this.props.stealthInvoices,
|
||||
tgEnabled: data.tg_enabled,
|
||||
tgBotName: data.tg_bot_name,
|
||||
tgToken: data.tg_token,
|
||||
})
|
||||
: this.props.setRobot({
|
||||
...this.props.robot,
|
||||
@ -359,36 +361,6 @@ class UserGenPage extends Component {
|
||||
</Tooltip>
|
||||
)}
|
||||
</Grid>
|
||||
<Grid item xs={12} align='center'>
|
||||
<ButtonGroup variant='contained' aria-label='outlined primary button group'>
|
||||
<Button
|
||||
disabled={
|
||||
this.props.robot.loading ||
|
||||
!(this.props.robot.token
|
||||
? systemClient.getCookie('robot_token') === this.props.robot.token
|
||||
: true)
|
||||
}
|
||||
color='primary'
|
||||
to='/make/'
|
||||
component={Link}
|
||||
>
|
||||
{t('Make Order')}
|
||||
</Button>
|
||||
<Button
|
||||
disabled={
|
||||
this.props.robot.loading ||
|
||||
!(this.props.robot.token
|
||||
? systemClient.getCookie('robot_token') == this.props.robot.token
|
||||
: true)
|
||||
}
|
||||
color='secondary'
|
||||
to='/book/'
|
||||
component={Link}
|
||||
>
|
||||
{t('View Book')}
|
||||
</Button>
|
||||
</ButtonGroup>
|
||||
</Grid>
|
||||
|
||||
<Grid item xs={12} align='center' sx={{ width: '26.43em' }}>
|
||||
<Grid item>
|
||||
|
@ -39,52 +39,26 @@ import { UserNinjaIcon, BitcoinIcon } from '../Icons';
|
||||
import { systemClient } from '../../services/System';
|
||||
import { getHost, getWebln } from '../../utils';
|
||||
import RobotAvatar from '../RobotAvatar';
|
||||
import { apiClient } from '../../services/api';
|
||||
import { Robot } from '../../models';
|
||||
|
||||
interface Props {
|
||||
open: boolean;
|
||||
handleClickCloseProfile: () => void;
|
||||
nickname: string;
|
||||
activeOrderId: string | number;
|
||||
lastOrderId: string | number;
|
||||
referralCode: string;
|
||||
tgEnabled: boolean;
|
||||
tgBotName: string;
|
||||
tgToken: string;
|
||||
handleSubmitInvoiceClicked: (e: any, invoice: string) => void;
|
||||
showRewardsSpinner: boolean;
|
||||
withdrawn: boolean;
|
||||
badInvoice: boolean | string;
|
||||
earnedRewards: number;
|
||||
stealthInvoices: boolean;
|
||||
handleSetStealthInvoice: (wantsStealth: boolean) => void;
|
||||
updateRobot: (state: any) => void; // TODO: move to a ContextProvider
|
||||
onClose: () => void;
|
||||
robot: Robot;
|
||||
setRobot: (state: Robot) => void;
|
||||
}
|
||||
|
||||
const ProfileDialog = ({
|
||||
open = false,
|
||||
handleClickCloseProfile,
|
||||
nickname,
|
||||
activeOrderId,
|
||||
lastOrderId,
|
||||
referralCode,
|
||||
tgEnabled,
|
||||
tgBotName,
|
||||
tgToken,
|
||||
handleSubmitInvoiceClicked,
|
||||
showRewardsSpinner,
|
||||
withdrawn,
|
||||
badInvoice,
|
||||
earnedRewards,
|
||||
updateRobot,
|
||||
stealthInvoices,
|
||||
handleSetStealthInvoice,
|
||||
}: Props): JSX.Element => {
|
||||
const ProfileDialog = ({ open = false, onClose, robot, setRobot }: Props): JSX.Element => {
|
||||
const { t } = useTranslation();
|
||||
const theme = useTheme();
|
||||
const host = getHost();
|
||||
|
||||
const [rewardInvoice, setRewardInvoice] = useState<string>('');
|
||||
const [showRewards, setShowRewards] = useState<boolean>(false);
|
||||
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);
|
||||
@ -100,19 +74,19 @@ const ProfileDialog = ({
|
||||
|
||||
if (robotToken) {
|
||||
systemClient.copyToClipboard(robotToken);
|
||||
updateRobot({ copiedToken: true });
|
||||
setRobot({ ...robot, copiedToken: true });
|
||||
}
|
||||
};
|
||||
|
||||
const copyReferralCodeHandler = () => {
|
||||
systemClient.copyToClipboard(`http://${host}/ref/${referralCode}`);
|
||||
systemClient.copyToClipboard(`http://${host}/ref/${robot.referralCode}`);
|
||||
};
|
||||
|
||||
const handleWeblnInvoiceClicked = async (e: any) => {
|
||||
e.preventDefault();
|
||||
if (earnedRewards) {
|
||||
if (robot.earnedRewards) {
|
||||
const webln = await getWebln();
|
||||
const invoice = webln.makeInvoice(earnedRewards).then(() => {
|
||||
const invoice = webln.makeInvoice(robot.earnedRewards).then(() => {
|
||||
if (invoice) {
|
||||
handleSubmitInvoiceClicked(e, invoice.paymentRequest);
|
||||
}
|
||||
@ -120,15 +94,39 @@ const ProfileDialog = ({
|
||||
}
|
||||
};
|
||||
|
||||
const handleSubmitInvoiceClicked = (e: any, rewardInvoice: string) => {
|
||||
setBadInvoice('');
|
||||
setShowRewardsSpinner(true);
|
||||
|
||||
apiClient
|
||||
.post('/api/reward/', {
|
||||
invoice: rewardInvoice,
|
||||
})
|
||||
.then((data) => {
|
||||
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 handleClickEnableTelegram = () => {
|
||||
window.open('https://t.me/' + tgBotName + '?start=' + tgToken, '_blank').focus();
|
||||
window.open('https://t.me/' + robot.tgBotName + '?start=' + robot.tgToken, '_blank').focus();
|
||||
setOpenEnableTelegram(false);
|
||||
};
|
||||
|
||||
const setStealthInvoice = (wantsStealth: boolean) => {
|
||||
apiClient
|
||||
.put('/api/stealth/', { wantsStealth })
|
||||
.then((data) => setRobot({ ...robot, stealthInvoices: data?.wantsStealth }));
|
||||
};
|
||||
|
||||
return (
|
||||
<Dialog
|
||||
open={open}
|
||||
onClose={handleClickCloseProfile}
|
||||
onClose={onClose}
|
||||
aria-labelledby='profile-title'
|
||||
aria-describedby='profile-description'
|
||||
>
|
||||
@ -143,7 +141,7 @@ const ProfileDialog = ({
|
||||
<ListItem className='profileNickname'>
|
||||
<ListItemText secondary={t('Your robot')}>
|
||||
<Typography component='h6' variant='h6'>
|
||||
{nickname ? (
|
||||
{robot.nickname ? (
|
||||
<div style={{ position: 'relative', left: '-7px' }}>
|
||||
<div
|
||||
style={{
|
||||
@ -156,7 +154,7 @@ const ProfileDialog = ({
|
||||
>
|
||||
<BoltIcon sx={{ color: '#fcba03', height: '28px', width: '24px' }} />
|
||||
|
||||
<a>{nickname}</a>
|
||||
<a>{robot.nickname}</a>
|
||||
|
||||
<BoltIcon sx={{ color: '#fcba03', height: '28px', width: '24px' }} />
|
||||
</div>
|
||||
@ -169,17 +167,17 @@ const ProfileDialog = ({
|
||||
<RobotAvatar
|
||||
avatarClass='profileAvatar'
|
||||
style={{ width: 65, height: 65 }}
|
||||
nickname={nickname}
|
||||
nickname={robot.nickname}
|
||||
/>
|
||||
</ListItemAvatar>
|
||||
</ListItem>
|
||||
|
||||
<Divider />
|
||||
|
||||
{activeOrderId ? (
|
||||
{robot.activeOrderId ? (
|
||||
<ListItemButton
|
||||
onClick={handleClickCloseProfile}
|
||||
to={`/order/${activeOrderId}`}
|
||||
onClick={onClose}
|
||||
to={`/order/${robot.activeOrderId}`}
|
||||
component={LinkRouter}
|
||||
>
|
||||
<ListItemIcon>
|
||||
@ -188,21 +186,21 @@ const ProfileDialog = ({
|
||||
</Badge>
|
||||
</ListItemIcon>
|
||||
<ListItemText
|
||||
primary={t('One active order #{{orderID}}', { orderID: activeOrderId })}
|
||||
primary={t('One active order #{{orderID}}', { orderID: robot.activeOrderId })}
|
||||
secondary={t('Your current order')}
|
||||
/>
|
||||
</ListItemButton>
|
||||
) : lastOrderId ? (
|
||||
) : robot.lastOrderId ? (
|
||||
<ListItemButton
|
||||
onClick={handleClickCloseProfile}
|
||||
to={`/order/${lastOrderId}`}
|
||||
onClick={onClose}
|
||||
to={`/order/${robot.lastOrderId}`}
|
||||
component={LinkRouter}
|
||||
>
|
||||
<ListItemIcon>
|
||||
<NumbersIcon color='primary' />
|
||||
</ListItemIcon>
|
||||
<ListItemText
|
||||
primary={t('Your last order #{{orderID}}', { orderID: lastOrderId })}
|
||||
primary={t('Your last order #{{orderID}}', { orderID: robot.lastOrderId })}
|
||||
secondary={t('Inactive order')}
|
||||
/>
|
||||
</ListItemButton>
|
||||
@ -253,8 +251,8 @@ const ProfileDialog = ({
|
||||
<EnableTelegramDialog
|
||||
open={openEnableTelegram}
|
||||
onClose={() => setOpenEnableTelegram(false)}
|
||||
tgBotName={tgBotName}
|
||||
tgToken={tgToken}
|
||||
tgBotName={robot.tgBotName}
|
||||
tgToken={robot.tgToken}
|
||||
onClickEnable={handleClickEnableTelegram}
|
||||
/>
|
||||
|
||||
@ -264,7 +262,7 @@ const ProfileDialog = ({
|
||||
</ListItemIcon>
|
||||
|
||||
<ListItemText>
|
||||
{tgEnabled ? (
|
||||
{robot.tgEnabled ? (
|
||||
<Typography color={theme.palette.success.main}>
|
||||
<b>{t('Telegram enabled')}</b>
|
||||
</Typography>
|
||||
@ -295,8 +293,8 @@ const ProfileDialog = ({
|
||||
label={t('Use stealth invoices')}
|
||||
control={
|
||||
<Switch
|
||||
checked={stealthInvoices}
|
||||
onChange={() => handleSetStealthInvoice(!stealthInvoices)}
|
||||
checked={robot.stealthInvoices}
|
||||
onChange={() => setStealthInvoice(!robot.stealthInvoices)}
|
||||
/>
|
||||
}
|
||||
/>
|
||||
@ -335,7 +333,7 @@ const ProfileDialog = ({
|
||||
<ListItemText secondary={t('Share to earn 100 Sats per trade')}>
|
||||
<TextField
|
||||
label={t('Your referral link')}
|
||||
value={host + '/ref/' + referralCode}
|
||||
value={host + '/ref/' + robot.referralCode}
|
||||
size='small'
|
||||
InputProps={{
|
||||
endAdornment: (
|
||||
@ -363,12 +361,12 @@ const ProfileDialog = ({
|
||||
<ListItemText secondary={t('Your earned rewards')}>
|
||||
<Grid container>
|
||||
<Grid item xs={9}>
|
||||
<Typography>{`${earnedRewards} Sats`}</Typography>
|
||||
<Typography>{`${robot.earnedRewards} Sats`}</Typography>
|
||||
</Grid>
|
||||
|
||||
<Grid item xs={3}>
|
||||
<Button
|
||||
disabled={earnedRewards === 0}
|
||||
disabled={robot.earnedRewards === 0}
|
||||
onClick={() => setOpenClaimRewards(true)}
|
||||
variant='contained'
|
||||
size='small'
|
||||
@ -386,7 +384,7 @@ const ProfileDialog = ({
|
||||
error={!!badInvoice}
|
||||
helperText={badInvoice || ''}
|
||||
label={t('Invoice for {{amountSats}} Sats', {
|
||||
amountSats: earnedRewards,
|
||||
amountSats: robot.earnedRewards,
|
||||
})}
|
||||
size='small'
|
||||
value={rewardInvoice}
|
||||
|
@ -55,6 +55,7 @@ interface MakerFormProps {
|
||||
onSubmit?: () => void;
|
||||
onReset?: () => void;
|
||||
submitButtonLabel?: string;
|
||||
onOrderCreated?: (id: number) => void;
|
||||
}
|
||||
|
||||
const MakerForm = ({
|
||||
@ -70,6 +71,7 @@ const MakerForm = ({
|
||||
onSubmit = () => {},
|
||||
onReset = () => {},
|
||||
submitButtonLabel = 'Create Order',
|
||||
onOrderCreated = () => null,
|
||||
}: MakerFormProps): JSX.Element => {
|
||||
const { t } = useTranslation();
|
||||
const theme = useTheme();
|
||||
@ -258,7 +260,10 @@ const MakerForm = ({
|
||||
};
|
||||
apiClient.post('/api/make/', body).then((data: object) => {
|
||||
setBadRequest(data.bad_request);
|
||||
data.id ? history.push('/order/' + data.id) : '';
|
||||
if (data.id) {
|
||||
history.push('/order/' + data.id);
|
||||
onOrderCreated(data.id);
|
||||
}
|
||||
setSubmittingRequest(false);
|
||||
});
|
||||
}
|
||||
|
@ -7,7 +7,7 @@ import { apiClient } from '../../services/api';
|
||||
import placeholder from './placeholder.json';
|
||||
|
||||
interface Props {
|
||||
nickname: string;
|
||||
nickname: string | null;
|
||||
smooth?: boolean;
|
||||
flipHorizontally?: boolean;
|
||||
style?: object;
|
||||
@ -38,7 +38,7 @@ const RobotAvatar: React.FC<Props> = ({
|
||||
const [avatarSrc, setAvatarSrc] = useState<string>();
|
||||
|
||||
useEffect(() => {
|
||||
if (nickname) {
|
||||
if (nickname != null) {
|
||||
apiClient.fileImageUrl('/static/assets/avatars/' + nickname + '.png').then(setAvatarSrc);
|
||||
}
|
||||
}, [nickname]);
|
||||
@ -92,6 +92,8 @@ const RobotAvatar: React.FC<Props> = ({
|
||||
alt={nickname}
|
||||
src={avatarSrc}
|
||||
imgProps={{
|
||||
sx: { transform: flipHorizontally ? 'scaleX(-1)' : '' },
|
||||
style: { transform: flipHorizontally ? 'scaleX(-1)' : '' },
|
||||
onLoad,
|
||||
}}
|
||||
/>
|
||||
|
@ -14,7 +14,7 @@ import {
|
||||
} from '@mui/material';
|
||||
import { Settings } from '../../models';
|
||||
import SelectLanguage from './SelectLanguage';
|
||||
import { Language, Palette, LightMode, DarkMode, FormatSize } from '@mui/icons-material';
|
||||
import { Language, Palette, LightMode, DarkMode, SettingsOverscan } from '@mui/icons-material';
|
||||
|
||||
interface SettingsFormProps {
|
||||
dense?: boolean;
|
||||
@ -113,7 +113,7 @@ const SettingsForm = ({ dense = false, settings, setSettings }: SettingsFormProp
|
||||
|
||||
<ListItem>
|
||||
<ListItemIcon>
|
||||
<FormatSize />
|
||||
<SettingsOverscan />
|
||||
</ListItemIcon>
|
||||
<Slider
|
||||
value={settings.fontSize}
|
||||
|
@ -23,14 +23,8 @@ export interface Robot {
|
||||
export const defaultRobot: Robot = {
|
||||
nickname: null,
|
||||
token: systemClient.getCookie('robot_token') ?? null,
|
||||
pub_key:
|
||||
systemClient.getCookie('pub_key') === undefined
|
||||
? null
|
||||
: systemClient.getCookie('pub_key').split('\\').join('\n'),
|
||||
enc_priv_key:
|
||||
systemClient.getCookie('enc_priv_key') === undefined
|
||||
? null
|
||||
: systemClient.getCookie('enc_priv_key').split('\\').join('\n'),
|
||||
pub_key: systemClient.getCookie('pub_key').split('\\').join('\n'),
|
||||
enc_priv_key: systemClient.getCookie('enc_priv_key').split('\\').join('\n'),
|
||||
bitsEntropy: null,
|
||||
shannonEntropy: null,
|
||||
stealthInvoices: true,
|
||||
|
@ -6,6 +6,6 @@ export { default as matchMedian } from './match';
|
||||
export { default as pn } from './prettyNumbers';
|
||||
export { amountToString } from './prettyNumbers';
|
||||
export { default as saveAsJson } from './saveFile';
|
||||
export { default as statusBadgeColor } from './saveFile';
|
||||
export { default as statusBadgeColor } from './statusBadgeColor';
|
||||
export { genBase62Token, tokenStrength } from './token';
|
||||
export { default as getWebln } from './webln';
|
||||
|
@ -4,6 +4,7 @@
|
||||
*/
|
||||
|
||||
const saveAsJson = (filename, dataObjToWrite) => {
|
||||
console.log(filename, dataObjToWrite);
|
||||
const blob = new Blob([JSON.stringify(dataObjToWrite, null, 2)], { type: 'text/json' });
|
||||
const link = document.createElement('a');
|
||||
|
||||
|
Loading…
Reference in New Issue
Block a user