mirror of
https://github.com/RoboSats/robosats.git
synced 2025-01-31 10:31:35 +00:00
741fba11d5
* Add swappable baseurls (network and coordinators) * Add testnet bitcoin styles * Small fixes * Small fixes
473 lines
16 KiB
TypeScript
473 lines
16 KiB
TypeScript
import React, { useEffect, useState } from 'react';
|
|
import { useTranslation } from 'react-i18next';
|
|
import { useTheme } from '@mui/material/styles';
|
|
import { useHistory } from 'react-router-dom';
|
|
|
|
import {
|
|
Badge,
|
|
Button,
|
|
CircularProgress,
|
|
Dialog,
|
|
DialogContent,
|
|
Divider,
|
|
FormControlLabel,
|
|
Grid,
|
|
IconButton,
|
|
List,
|
|
ListItemAvatar,
|
|
ListItemButton,
|
|
ListItemText,
|
|
ListItem,
|
|
ListItemIcon,
|
|
Switch,
|
|
TextField,
|
|
Tooltip,
|
|
Typography,
|
|
LinearProgress,
|
|
} from '@mui/material';
|
|
|
|
import { EnableTelegramDialog } from '.';
|
|
import BoltIcon from '@mui/icons-material/Bolt';
|
|
import SendIcon from '@mui/icons-material/Send';
|
|
import NumbersIcon from '@mui/icons-material/Numbers';
|
|
import PasswordIcon from '@mui/icons-material/Password';
|
|
import ContentCopy from '@mui/icons-material/ContentCopy';
|
|
import PersonAddAltIcon from '@mui/icons-material/PersonAddAlt';
|
|
import EmojiEventsIcon from '@mui/icons-material/EmojiEvents';
|
|
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';
|
|
import { Page } from '../../basic/NavBar';
|
|
|
|
interface Props {
|
|
open: boolean;
|
|
onClose: () => void;
|
|
robot: Robot;
|
|
setRobot: (state: Robot) => void;
|
|
setPage: (state: Page) => void;
|
|
setCurrentOrder: (state: number) => void;
|
|
baseUrl: string;
|
|
}
|
|
|
|
const ProfileDialog = ({
|
|
open = false,
|
|
baseUrl,
|
|
onClose,
|
|
robot,
|
|
setRobot,
|
|
setPage,
|
|
setCurrentOrder,
|
|
}: Props): JSX.Element => {
|
|
const { t } = useTranslation();
|
|
const history = useHistory();
|
|
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);
|
|
|
|
useEffect(() => {
|
|
getWebln().then((webln) => {
|
|
setWeblnEnabled(webln !== undefined);
|
|
});
|
|
}, [showRewards]);
|
|
|
|
const copyTokenHandler = () => {
|
|
const robotToken = systemClient.getCookie('robot_token');
|
|
|
|
if (robotToken) {
|
|
systemClient.copyToClipboard(robotToken);
|
|
setRobot({ ...robot, copiedToken: true });
|
|
}
|
|
};
|
|
|
|
const copyReferralCodeHandler = () => {
|
|
systemClient.copyToClipboard(`http://${host}/ref/${robot.referralCode}`);
|
|
};
|
|
|
|
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);
|
|
|
|
apiClient
|
|
.post(baseUrl, '/api/reward/', {
|
|
invoice: rewardInvoice,
|
|
})
|
|
.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 handleClickEnableTelegram = () => {
|
|
window.open('https://t.me/' + robot.tgBotName + '?start=' + robot.tgToken, '_blank').focus();
|
|
setOpenEnableTelegram(false);
|
|
};
|
|
|
|
const setStealthInvoice = (wantsStealth: boolean) => {
|
|
apiClient
|
|
.put(baseUrl, '/api/stealth/', { wantsStealth })
|
|
.then((data) => setRobot({ ...robot, stealthInvoices: data?.wantsStealth }));
|
|
};
|
|
|
|
return (
|
|
<Dialog
|
|
open={open}
|
|
onClose={onClose}
|
|
aria-labelledby='profile-title'
|
|
aria-describedby='profile-description'
|
|
>
|
|
<div style={robot.loading ? {} : { display: 'none' }}>
|
|
<LinearProgress />
|
|
</div>
|
|
<DialogContent>
|
|
<Typography component='h5' variant='h5'>
|
|
{t('Your Profile')}
|
|
</Typography>
|
|
|
|
<List>
|
|
<Divider />
|
|
|
|
<ListItem className='profileNickname'>
|
|
<ListItemText secondary={t('Your robot')}>
|
|
<Typography component='h6' variant='h6'>
|
|
{robot.nickname ? (
|
|
<div style={{ position: 'relative', left: '-7px' }}>
|
|
<div
|
|
style={{
|
|
display: 'flex',
|
|
alignItems: 'center',
|
|
justifyContent: 'left',
|
|
flexWrap: 'wrap',
|
|
width: 300,
|
|
}}
|
|
>
|
|
<BoltIcon sx={{ color: '#fcba03', height: '28px', width: '24px' }} />
|
|
|
|
<a>{robot.nickname}</a>
|
|
|
|
<BoltIcon sx={{ color: '#fcba03', height: '28px', width: '24px' }} />
|
|
</div>
|
|
</div>
|
|
) : null}
|
|
</Typography>
|
|
</ListItemText>
|
|
|
|
<ListItemAvatar>
|
|
<RobotAvatar
|
|
avatarClass='profileAvatar'
|
|
style={{ width: 65, height: 65 }}
|
|
nickname={robot.nickname}
|
|
/>
|
|
</ListItemAvatar>
|
|
</ListItem>
|
|
|
|
<Divider />
|
|
|
|
{robot.activeOrderId ? (
|
|
<ListItemButton
|
|
onClick={() => {
|
|
history.push('/order/' + robot.activeOrderId);
|
|
setPage('order');
|
|
setCurrentOrder(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={() => {
|
|
history.push('/order/' + robot.lastOrderId);
|
|
setPage('order');
|
|
setCurrentOrder(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>
|
|
)}
|
|
|
|
<ListItem>
|
|
<ListItemIcon>
|
|
<PasswordIcon />
|
|
</ListItemIcon>
|
|
|
|
<ListItemText secondary={t('Your token (will not remain here)')}>
|
|
{systemClient.getCookie('robot_token') ? (
|
|
<TextField
|
|
disabled
|
|
sx={{ width: '100%', maxWidth: '450px' }}
|
|
label={t('Back it up!')}
|
|
value={systemClient.getCookie('robot_token')}
|
|
variant='filled'
|
|
size='small'
|
|
InputProps={{
|
|
endAdornment: (
|
|
<Tooltip disableHoverListener enterTouchDelay={0} title={t('Copied!') || ''}>
|
|
<IconButton onClick={copyTokenHandler}>
|
|
<ContentCopy color='inherit' />
|
|
</IconButton>
|
|
</Tooltip>
|
|
),
|
|
}}
|
|
/>
|
|
) : (
|
|
t('Cannot remember')
|
|
)}
|
|
</ListItemText>
|
|
</ListItem>
|
|
|
|
<Divider />
|
|
|
|
<EnableTelegramDialog
|
|
open={openEnableTelegram}
|
|
onClose={() => setOpenEnableTelegram(false)}
|
|
tgBotName={robot.tgBotName}
|
|
tgToken={robot.tgToken}
|
|
onClickEnable={handleClickEnableTelegram}
|
|
/>
|
|
|
|
<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>
|
|
<BitcoinIcon />
|
|
</ListItemIcon>
|
|
|
|
<ListItemText>
|
|
<FormControlLabel
|
|
labelPlacement='end'
|
|
label={
|
|
<div style={{ display: 'flex', alignItems: 'center' }}>
|
|
{t('Rewards and compensations')}
|
|
</div>
|
|
}
|
|
control={
|
|
<Switch checked={showRewards} onChange={() => setShowRewards(!showRewards)} />
|
|
}
|
|
/>
|
|
</ListItemText>
|
|
</ListItem>
|
|
|
|
{showRewards && (
|
|
<>
|
|
<ListItem>
|
|
<ListItemIcon>
|
|
<PersonAddAltIcon />
|
|
</ListItemIcon>
|
|
|
|
<ListItemText secondary={t('Share to earn 100 Sats per trade')}>
|
|
<TextField
|
|
label={t('Your referral link')}
|
|
value={host + '/ref/' + robot.referralCode}
|
|
size='small'
|
|
InputProps={{
|
|
endAdornment: (
|
|
<Tooltip
|
|
disableHoverListener
|
|
enterTouchDelay={0}
|
|
title={t('Copied!') || ''}
|
|
>
|
|
<IconButton onClick={copyReferralCodeHandler}>
|
|
<ContentCopy />
|
|
</IconButton>
|
|
</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>
|
|
</DialogContent>
|
|
</Dialog>
|
|
);
|
|
};
|
|
|
|
export default ProfileDialog;
|