mirror of
https://github.com/RoboSats/robosats.git
synced 2025-07-20 09:43:23 +00:00
Merge 289856a70d9d4d747f74ff17247549e3af81140d into 12a47d7184f76e2be06164988ff52701ac83b1cf
This commit is contained in:
102
frontend/src/components/Dialogs/ClaimRewardDialog.tsx
Normal file
102
frontend/src/components/Dialogs/ClaimRewardDialog.tsx
Normal file
@ -0,0 +1,102 @@
|
|||||||
|
import React, { useState } from 'react';
|
||||||
|
import {
|
||||||
|
Dialog,
|
||||||
|
DialogTitle,
|
||||||
|
DialogContent,
|
||||||
|
DialogActions,
|
||||||
|
Button,
|
||||||
|
Typography,
|
||||||
|
Alert,
|
||||||
|
TextField,
|
||||||
|
} from '@mui/material';
|
||||||
|
import RedeemIcon from '@mui/icons-material/Redeem';
|
||||||
|
|
||||||
|
interface Props {
|
||||||
|
open: boolean;
|
||||||
|
onClose: () => void;
|
||||||
|
t: (key: string) => string; // Pass the translation function
|
||||||
|
}
|
||||||
|
|
||||||
|
const ClaimRewardDialog = ({ open, onClose, t }: Props): JSX.Element => {
|
||||||
|
const [invoiceAmount, setInvoiceAmount] = useState<string>('');
|
||||||
|
const [showFailedClaimInfo, setShowFailedClaimInfo] = useState<boolean>(false);
|
||||||
|
const [claimLoading, setClaimLoading] = useState<boolean>(false);
|
||||||
|
|
||||||
|
const handleClaimSubmit = () => {
|
||||||
|
setClaimLoading(true);
|
||||||
|
setShowFailedClaimInfo(false); // Reset alert
|
||||||
|
// Simulate an API call for claiming rewards
|
||||||
|
setTimeout(() => {
|
||||||
|
setClaimLoading(false);
|
||||||
|
// In a real scenario, this would depend on the API response
|
||||||
|
const success = Math.random() > 0.5; // Simulate success/failure
|
||||||
|
if (!success) {
|
||||||
|
setShowFailedClaimInfo(true);
|
||||||
|
} else {
|
||||||
|
// Handle successful claim (e.g., show a success message, close dialog)
|
||||||
|
alert(t('Reward claimed successfully!'));
|
||||||
|
onClose();
|
||||||
|
}
|
||||||
|
}, 1500);
|
||||||
|
};
|
||||||
|
|
||||||
|
return (
|
||||||
|
<Dialog
|
||||||
|
open={open}
|
||||||
|
onClose={onClose}
|
||||||
|
aria-labelledby="claim-reward-title"
|
||||||
|
sx={{ '& .MuiDialog-paper': { minWidth: '300px', maxWidth: '400px', borderRadius: '8px' } }}
|
||||||
|
>
|
||||||
|
<DialogTitle sx={{ fontWeight: 600, color: '#333', paddingBottom: '10px' }}>
|
||||||
|
{t('Claim Your Rewards')}
|
||||||
|
</DialogTitle>
|
||||||
|
<DialogContent sx={{ padding: '0 20px 20px 20px' }}>
|
||||||
|
<Typography variant="body1" sx={{ marginBottom: '16px' }}>
|
||||||
|
{t('You have 0 Sats in compensations.')} {/* Placeholder for actual sats */}
|
||||||
|
</Typography>
|
||||||
|
|
||||||
|
{/* This would be a more complex form for invoice submission */}
|
||||||
|
<TextField
|
||||||
|
label={t('Invoice amount (Sats)')}
|
||||||
|
type="number"
|
||||||
|
fullWidth
|
||||||
|
value={invoiceAmount}
|
||||||
|
onChange={(e) => setInvoiceAmount(e.target.value)}
|
||||||
|
sx={{ marginBottom: '16px' }}
|
||||||
|
/>
|
||||||
|
|
||||||
|
{showFailedClaimInfo && (
|
||||||
|
<Alert
|
||||||
|
severity="info"
|
||||||
|
sx={{
|
||||||
|
marginBottom: '16px',
|
||||||
|
borderRadius: '8px',
|
||||||
|
backgroundColor: '#e3f2fd',
|
||||||
|
color: '#0d47a1',
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
{t(
|
||||||
|
'To claim your rewards, please contact the coordinator of your last order. If the payment fails, you must contact the coordinator - do not generate a new invoice.',
|
||||||
|
)}
|
||||||
|
</Alert>
|
||||||
|
)}
|
||||||
|
</DialogContent>
|
||||||
|
<DialogActions sx={{ padding: '10px 20px 20px 20px' }}>
|
||||||
|
<Button onClick={onClose} color="inherit" sx={{ textTransform: 'none' }}>
|
||||||
|
{t('Cancel')}
|
||||||
|
</Button>
|
||||||
|
<Button
|
||||||
|
onClick={handleClaimSubmit}
|
||||||
|
variant="contained"
|
||||||
|
startIcon={<RedeemIcon />}
|
||||||
|
disabled={claimLoading || !invoiceAmount}
|
||||||
|
sx={{ textTransform: 'none', backgroundColor: '#1976d2', '&:hover': { backgroundColor: '#1565c0' } }}
|
||||||
|
>
|
||||||
|
{claimLoading ? t('Claiming...') : t('Claim')}
|
||||||
|
</Button>
|
||||||
|
</DialogActions>
|
||||||
|
</Dialog>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
export default ClaimRewardDialog;
|
@ -1,6 +1,5 @@
|
|||||||
import React, { useContext, useEffect, useState } from 'react';
|
import React, { useContext, useEffect, useState } from 'react';
|
||||||
import { useTranslation } from 'react-i18next';
|
import { useTranslation } from 'react-i18next';
|
||||||
|
|
||||||
import {
|
import {
|
||||||
Dialog,
|
Dialog,
|
||||||
DialogContent,
|
DialogContent,
|
||||||
@ -11,24 +10,27 @@ import {
|
|||||||
ListItem,
|
ListItem,
|
||||||
Typography,
|
Typography,
|
||||||
LinearProgress,
|
LinearProgress,
|
||||||
|
Button,
|
||||||
} from '@mui/material';
|
} from '@mui/material';
|
||||||
|
|
||||||
import BoltIcon from '@mui/icons-material/Bolt';
|
import BoltIcon from '@mui/icons-material/Bolt';
|
||||||
|
import RedeemIcon from '@mui/icons-material/Redeem';
|
||||||
import RobotAvatar from '../RobotAvatar';
|
import RobotAvatar from '../RobotAvatar';
|
||||||
import RobotInfo from '../RobotInfo';
|
import RobotInfo from '../RobotInfo';
|
||||||
import { FederationContext, type UseFederationStoreType } from '../../contexts/FederationContext';
|
import { FederationContext, type UseFederationStoreType } from '../../contexts/FederationContext';
|
||||||
import { GarageContext, type UseGarageStoreType } from '../../contexts/GarageContext';
|
import { GarageContext, type UseGarageStoreType } from '../../contexts/GarageContext';
|
||||||
import { type Coordinator } from '../../models';
|
import { type Coordinator } from '../../models';
|
||||||
|
import ClaimRewardDialog from './ClaimRewardDialog'; // New import
|
||||||
|
|
||||||
interface Props {
|
interface Props {
|
||||||
open: boolean;
|
open: boolean;
|
||||||
onClose: () => void;
|
onClose: () => void;
|
||||||
}
|
}
|
||||||
|
|
||||||
const ProfileDialog = ({ open = false, onClose }: Props): React.JSX.Element => {
|
const ProfileDialog = ({ open = false, onClose }: Props): JSX.Element => {
|
||||||
const { federation } = useContext<UseFederationStoreType>(FederationContext);
|
const { federation } = useContext<UseFederationStoreType>(FederationContext);
|
||||||
const { garage, slotUpdatedAt } = useContext<UseGarageStoreType>(GarageContext);
|
const { garage, slotUpdatedAt } = useContext<UseGarageStoreType>(GarageContext);
|
||||||
const { t } = useTranslation();
|
const { t } = useTranslation();
|
||||||
|
const [isClaimRewardDialogOpen, setIsClaimRewardDialogOpen] = useState<boolean>(false);
|
||||||
|
|
||||||
const slot = garage.getSlot();
|
const slot = garage.getSlot();
|
||||||
|
|
||||||
@ -42,87 +44,157 @@ const ProfileDialog = ({ open = false, onClose }: Props): React.JSX.Element => {
|
|||||||
setLoadingRobots(Object.values(slot?.robots ?? {}).filter((robot) => robot.loading).length);
|
setLoadingRobots(Object.values(slot?.robots ?? {}).filter((robot) => robot.loading).length);
|
||||||
}, [slotUpdatedAt]);
|
}, [slotUpdatedAt]);
|
||||||
|
|
||||||
return (
|
const handleClaimReward = () => {
|
||||||
<Dialog
|
setIsClaimRewardDialogOpen(true);
|
||||||
open={open}
|
};
|
||||||
onClose={onClose}
|
|
||||||
aria-labelledby='profile-title'
|
|
||||||
aria-describedby='profile-description'
|
|
||||||
>
|
|
||||||
<div style={loading ? {} : { display: 'none' }}>
|
|
||||||
<LinearProgress />
|
|
||||||
</div>
|
|
||||||
<DialogContent>
|
|
||||||
<Typography component='h5' variant='h5'>
|
|
||||||
{t('Your Robot')}
|
|
||||||
</Typography>
|
|
||||||
<List>
|
|
||||||
<Divider />
|
|
||||||
|
|
||||||
<ListItem className='profileNickname'>
|
const handleCloseClaimRewardDialog = () => {
|
||||||
<ListItemText>
|
setIsClaimRewardDialogOpen(false);
|
||||||
<Typography component='h6' variant='h6'>
|
};
|
||||||
{!garage.getSlot()?.nickname && (
|
|
||||||
<div style={{ position: 'relative', left: '-7px' }}>
|
// Determine if there are any rewards to claim
|
||||||
|
const hasClaimableRewards = federation.getCoordinators().some((coordinator: Coordinator) => {
|
||||||
|
const coordinatorRobot = garage.getSlot()?.getRobot(coordinator.shortAlias);
|
||||||
|
// Assuming 'earnedRewards' property exists on the robot object
|
||||||
|
return coordinatorRobot && coordinatorRobot.earnedRewards > 0;
|
||||||
|
});
|
||||||
|
|
||||||
|
return (
|
||||||
|
<>
|
||||||
|
<Dialog
|
||||||
|
open={open}
|
||||||
|
onClose={onClose}
|
||||||
|
aria-labelledby="profile-title"
|
||||||
|
aria-describedby="profile-description"
|
||||||
|
sx={{ '& .MuiDialog-paper': { minWidth: '300px', maxWidth: '500px', borderRadius: '8px' } }}
|
||||||
|
>
|
||||||
|
<div style={{ display: loading ? 'block' : 'none', padding: '0' }}>
|
||||||
|
<LinearProgress sx={{ height: '6px', borderRadius: '4px' }} />
|
||||||
|
</div>
|
||||||
|
<DialogContent sx={{ padding: '20px' }}>
|
||||||
|
<div
|
||||||
|
style={{
|
||||||
|
display: 'flex',
|
||||||
|
justifyContent: 'space-between',
|
||||||
|
alignItems: 'center',
|
||||||
|
marginBottom: '16px',
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
<Typography variant="h5" sx={{ fontWeight: 600, color: '#333' }}>
|
||||||
|
{t('Your Robot')}
|
||||||
|
</Typography>
|
||||||
|
<Button
|
||||||
|
variant="contained"
|
||||||
|
color="primary"
|
||||||
|
startIcon={<RedeemIcon />}
|
||||||
|
onClick={handleClaimReward}
|
||||||
|
disabled={!hasClaimableRewards} // Button is disabled if no rewards to claim
|
||||||
|
sx={{
|
||||||
|
borderRadius: '20px',
|
||||||
|
textTransform: 'none',
|
||||||
|
padding: '6px 16px',
|
||||||
|
fontWeight: 500,
|
||||||
|
backgroundColor: '#1976d2', // Default primary color
|
||||||
|
'&:hover': { backgroundColor: '#1565c0' },
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
{t('Claim Reward')}
|
||||||
|
</Button>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<List sx={{ padding: 0 }}>
|
||||||
|
<Divider sx={{ margin: '12px 0', backgroundColor: '#e0e0e0' }} />
|
||||||
|
|
||||||
|
<ListItem
|
||||||
|
sx={{
|
||||||
|
padding: '12px 0',
|
||||||
|
alignItems: 'center',
|
||||||
|
'&:hover': { backgroundColor: '#f5f5f5', borderRadius: '4px' },
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
<ListItemText>
|
||||||
|
<Typography variant="h6" sx={{ fontWeight: 500, color: '#555' }}>
|
||||||
|
{garage.getSlot()?.nickname ? (
|
||||||
<div
|
<div
|
||||||
style={{
|
style={{
|
||||||
display: 'flex',
|
display: 'flex',
|
||||||
alignItems: 'center',
|
alignItems: 'center',
|
||||||
justifyContent: 'left',
|
gap: '8px',
|
||||||
flexWrap: 'wrap',
|
flexWrap: 'wrap',
|
||||||
width: 300,
|
|
||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
<BoltIcon sx={{ color: '#fcba03', height: '28px', width: '24px' }} />
|
<BoltIcon sx={{ color: '#fcba03', height: '24px', width: '24px' }} />
|
||||||
|
<span style={{ color: '#333', fontSize: '1.25rem' }}>
|
||||||
<a>{garage.getSlot()?.nickname}</a>
|
{garage.getSlot()?.nickname}
|
||||||
|
</span>
|
||||||
<BoltIcon sx={{ color: '#fcba03', height: '28px', width: '24px' }} />
|
<BoltIcon sx={{ color: '#fcba03', height: '24px', width: '24px' }} />
|
||||||
</div>
|
</div>
|
||||||
|
) : (
|
||||||
|
<span style={{ color: '#888', fontStyle: 'italic' }}>{t('No nickname set')}</span>
|
||||||
|
)}
|
||||||
|
</Typography>
|
||||||
|
|
||||||
|
{loadingRobots > 0 && (
|
||||||
|
<div style={{ marginTop: '8px' }}>
|
||||||
|
<Typography variant="body2" sx={{ fontWeight: 600, color: '#888' }}>
|
||||||
|
{t('Looking for your robot!')}
|
||||||
|
</Typography>
|
||||||
|
<LinearProgress
|
||||||
|
sx={{ marginTop: '4px', height: '4px', borderRadius: '2px', backgroundColor: '#e0e0e0' }}
|
||||||
|
/>
|
||||||
</div>
|
</div>
|
||||||
)}
|
)}
|
||||||
</Typography>
|
</ListItemText>
|
||||||
|
|
||||||
{loadingRobots > 0 ? (
|
<ListItemAvatar sx={{ minWidth: 0, marginLeft: '16px' }}> {/* Adjusted minWidth and marginLeft */}
|
||||||
<>
|
<RobotAvatar
|
||||||
<b>{t('Looking for your robot!')}</b>
|
avatarClass="profileAvatar"
|
||||||
<LinearProgress />
|
style={{
|
||||||
</>
|
width: 65,
|
||||||
) : (
|
height: 65,
|
||||||
<></>
|
border: '2px solid #fcba03',
|
||||||
)}
|
borderRadius: '50%',
|
||||||
</ListItemText>
|
flexShrink: 0, // Prevent shrinking
|
||||||
|
}}
|
||||||
|
hashId={garage.getSlot()?.hashId ?? ''}
|
||||||
|
/>
|
||||||
|
</ListItemAvatar>
|
||||||
|
</ListItem>
|
||||||
|
|
||||||
<ListItemAvatar>
|
<Divider sx={{ margin: '12px 0', backgroundColor: '#e0e0e0' }} />
|
||||||
<RobotAvatar
|
</List>
|
||||||
avatarClass='profileAvatar'
|
|
||||||
style={{ width: 65, height: 65 }}
|
|
||||||
hashId={garage.getSlot()?.hashId ?? ''}
|
|
||||||
/>
|
|
||||||
</ListItemAvatar>
|
|
||||||
</ListItem>
|
|
||||||
|
|
||||||
<Divider />
|
<Typography
|
||||||
</List>
|
variant="subtitle1"
|
||||||
|
sx={{ fontWeight: 600, color: '#333', marginBottom: '12px' }}
|
||||||
|
>
|
||||||
|
{t('Coordinators that know your robot:')}
|
||||||
|
</Typography>
|
||||||
|
|
||||||
<Typography>
|
{federation.getCoordinators().map((coordinator: Coordinator) => {
|
||||||
<b>{t('Coordinators that know your robot:')}</b>
|
const coordinatorRobot = garage.getSlot()?.getRobot(coordinator.shortAlias);
|
||||||
</Typography>
|
return (
|
||||||
|
<div
|
||||||
{federation.getCoordinators().map((coordinator: Coordinator): React.JSX.Element => {
|
key={coordinator.shortAlias}
|
||||||
const coordinatorRobot = garage.getSlot()?.getRobot(coordinator.shortAlias);
|
style={{ marginBottom: '12px', padding: '8px', borderRadius: '4px' }}
|
||||||
return (
|
>
|
||||||
<div key={coordinator.shortAlias}>
|
<RobotInfo
|
||||||
<RobotInfo
|
coordinator={coordinator}
|
||||||
coordinator={coordinator}
|
onClose={onClose}
|
||||||
onClose={onClose}
|
disabled={coordinatorRobot?.loading}
|
||||||
disabled={coordinatorRobot?.loading}
|
/>
|
||||||
/>
|
</div>
|
||||||
</div>
|
);
|
||||||
);
|
})}
|
||||||
})}
|
</DialogContent>
|
||||||
</DialogContent>
|
</Dialog>
|
||||||
</Dialog>
|
{/* New ClaimRewardDialog */}
|
||||||
|
<ClaimRewardDialog
|
||||||
|
open={isClaimRewardDialogOpen}
|
||||||
|
onClose={handleCloseClaimRewardDialog}
|
||||||
|
t={t} // Pass translation function
|
||||||
|
/>
|
||||||
|
</>
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|
||||||
|
Reference in New Issue
Block a user