mirror of
https://github.com/RoboSats/robosats.git
synced 2025-08-10 23:20:04 +00:00
356 lines
12 KiB
TypeScript
356 lines
12 KiB
TypeScript
import React, { useContext, useEffect, 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 } 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 {
|
|
coordinator: Coordinator;
|
|
onClose: () => void;
|
|
}
|
|
|
|
const RobotInfo: React.FC<Props> = ({ 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(() => {
|
|
void handleWebln();
|
|
}, []);
|
|
|
|
const handleWeblnInvoiceClicked = async (e: MouseEvent<HTMLButtonElement, MouseEvent>): void => {
|
|
e.preventDefault();
|
|
const robot = garage.getSlot()?.getRobot(coordinator.shortAlias);
|
|
if (robot != null && 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 slot = garage.getSlot();
|
|
const robot = slot?.getRobot(coordinator.shortAlias);
|
|
|
|
if (robot != null && slot?.token != null && robot.encPrivKey != null) {
|
|
void signCleartextMessage(rewardInvoice, robot.encPrivKey, slot?.token).then(
|
|
(signedInvoice) => {
|
|
void coordinator.fetchReward(signedInvoice, garage, slot?.token).then((data) => {
|
|
setBadInvoice(data.bad_invoice ?? '');
|
|
setShowRewardsSpinner(false);
|
|
setWithdrawn(data.successful_withdrawal);
|
|
setOpenClaimRewards(!(data.successful_withdrawal !== undefined));
|
|
});
|
|
},
|
|
);
|
|
}
|
|
e.preventDefault();
|
|
};
|
|
|
|
const setStealthInvoice = (wantsStealth: boolean): void => {
|
|
const slot = garage.getSlot();
|
|
if (slot?.token != null) {
|
|
void coordinator.fetchStealth(wantsStealth, garage, slot?.token);
|
|
}
|
|
};
|
|
|
|
const slot = garage.getSlot();
|
|
const robot = slot?.getRobot(coordinator.shortAlias);
|
|
|
|
return (
|
|
<Accordion>
|
|
<AccordionSummary expandIcon={<ExpandMore />}>
|
|
{`${coordinator.longAlias}:`}
|
|
{(robot?.earnedRewards ?? 0) > 0 && (
|
|
<Typography color='success'> {t('Claim Sats!')} </Typography>
|
|
)}
|
|
{slot?.activeShortAlias === coordinator.shortAlias && (
|
|
<Typography color='success'>
|
|
<b>{t('Active order!')}</b>
|
|
</Typography>
|
|
)}
|
|
{slot?.lastShortAlias === coordinator.shortAlias && (
|
|
<Typography color='warning'> {t('finished order')}</Typography>
|
|
)}
|
|
</AccordionSummary>
|
|
<AccordionDetails>
|
|
<List dense disablePadding={true}>
|
|
{slot?.activeShortAlias === coordinator.shortAlias ? (
|
|
<ListItemButton
|
|
onClick={() => {
|
|
navigate(
|
|
`/order/${String(slot?.activeShortAlias)}/${String(
|
|
slot?.getRobot(slot?.activeShortAlias ?? '')?.activeOrderId,
|
|
)}`,
|
|
);
|
|
onClose();
|
|
}}
|
|
>
|
|
<ListItemIcon>
|
|
<Badge badgeContent='' color='primary'>
|
|
<Numbers color='primary' />
|
|
</Badge>
|
|
</ListItemIcon>
|
|
<ListItemText
|
|
primary={t('One active order #{{orderID}}', {
|
|
orderID: slot?.getRobot(slot?.activeShortAlias ?? '')?.activeOrderId,
|
|
})}
|
|
secondary={t('Your current order')}
|
|
/>
|
|
</ListItemButton>
|
|
) : (robot?.lastOrderId ?? 0) > 0 && slot?.lastShortAlias === coordinator.shortAlias ? (
|
|
<ListItemButton
|
|
onClick={() => {
|
|
navigate(
|
|
`/order/${String(slot?.lastShortAlias)}/${String(
|
|
slot?.getRobot(slot?.lastShortAlias ?? '')?.lastOrderId,
|
|
)}`,
|
|
);
|
|
onClose();
|
|
}}
|
|
>
|
|
<ListItemIcon>
|
|
<Numbers color='primary' />
|
|
</ListItemIcon>
|
|
<ListItemText
|
|
primary={t('Your last order #{{orderID}}', {
|
|
orderID: slot?.getRobot(slot?.lastShortAlias ?? '')?.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>{`${String(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;
|