refactor login (clean separation robot/info. Style navbar.

This commit is contained in:
Reckless_Satoshi 2022-10-29 13:36:59 -07:00
parent b9dc7f7c95
commit c660a5b0d1
No known key found for this signature in database
GPG Key ID: 9C4585B561315571
17 changed files with 276 additions and 214 deletions

View File

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

View File

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

View File

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

View File

@ -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}
/>
</>
);
};

View File

@ -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)}

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@ -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,
}}
/>

View File

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

View File

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

View File

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

View File

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