Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
102 changes: 102 additions & 0 deletions frontend/src/components/Dialogs/ClaimRewardDialog.tsx
Original file line number Diff line number Diff line change
@@ -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;
218 changes: 145 additions & 73 deletions frontend/src/components/Dialogs/Profile.tsx
Original file line number Diff line number Diff line change
@@ -1,6 +1,5 @@
import React, { useContext, useEffect, useState } from 'react';
import { useTranslation } from 'react-i18next';

import {
Dialog,
DialogContent,
Expand All @@ -11,24 +10,27 @@ import {
ListItem,
Typography,
LinearProgress,
Button,
} from '@mui/material';

import BoltIcon from '@mui/icons-material/Bolt';
import RedeemIcon from '@mui/icons-material/Redeem';
import RobotAvatar from '../RobotAvatar';
import RobotInfo from '../RobotInfo';
import { FederationContext, type UseFederationStoreType } from '../../contexts/FederationContext';
import { GarageContext, type UseGarageStoreType } from '../../contexts/GarageContext';
import { type Coordinator } from '../../models';
import ClaimRewardDialog from './ClaimRewardDialog'; // New import

interface Props {
open: boolean;
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 { garage, slotUpdatedAt } = useContext<UseGarageStoreType>(GarageContext);
const { t } = useTranslation();
const [isClaimRewardDialogOpen, setIsClaimRewardDialogOpen] = useState<boolean>(false);

const slot = garage.getSlot();

Expand All @@ -42,87 +44,157 @@ const ProfileDialog = ({ open = false, onClose }: Props): React.JSX.Element => {
setLoadingRobots(Object.values(slot?.robots ?? {}).filter((robot) => robot.loading).length);
}, [slotUpdatedAt]);

const handleClaimReward = () => {
setIsClaimRewardDialogOpen(true);
};

const handleCloseClaimRewardDialog = () => {
setIsClaimRewardDialogOpen(false);
};

// 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'
>
<div style={loading ? {} : { display: 'none' }}>
<LinearProgress />
</div>
<DialogContent>
<Typography component='h5' variant='h5'>
{t('Your Robot')}
</Typography>
<List>
<Divider />

<ListItem className='profileNickname'>
<ListItemText>
<Typography component='h6' variant='h6'>
{!garage.getSlot()?.nickname && (
<div style={{ position: 'relative', left: '-7px' }}>
<>
<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
style={{
display: 'flex',
alignItems: 'center',
justifyContent: 'left',
gap: '8px',
flexWrap: 'wrap',
width: 300,
}}
>
<BoltIcon sx={{ color: '#fcba03', height: '28px', width: '24px' }} />

<a>{garage.getSlot()?.nickname}</a>

<BoltIcon sx={{ color: '#fcba03', height: '28px', width: '24px' }} />
<BoltIcon sx={{ color: '#fcba03', height: '24px', width: '24px' }} />
<span style={{ color: '#333', fontSize: '1.25rem' }}>
{garage.getSlot()?.nickname}
</span>
<BoltIcon sx={{ color: '#fcba03', height: '24px', width: '24px' }} />
</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>
)}
</Typography>

{loadingRobots > 0 ? (
<>
<b>{t('Looking for your robot!')}</b>
<LinearProgress />
</>
) : (
<></>
)}
</ListItemText>

<ListItemAvatar>
<RobotAvatar
avatarClass='profileAvatar'
style={{ width: 65, height: 65 }}
hashId={garage.getSlot()?.hashId ?? ''}
/>
</ListItemAvatar>
</ListItem>

<Divider />
</List>

<Typography>
<b>{t('Coordinators that know your robot:')}</b>
</Typography>

{federation.getCoordinators().map((coordinator: Coordinator): React.JSX.Element => {
const coordinatorRobot = garage.getSlot()?.getRobot(coordinator.shortAlias);
return (
<div key={coordinator.shortAlias}>
<RobotInfo
coordinator={coordinator}
onClose={onClose}
disabled={coordinatorRobot?.loading}
/>
</div>
);
})}
</DialogContent>
</Dialog>
</ListItemText>

<ListItemAvatar sx={{ minWidth: 0, marginLeft: '16px' }}> {/* Adjusted minWidth and marginLeft */}
<RobotAvatar
avatarClass="profileAvatar"
style={{
width: 65,
height: 65,
border: '2px solid #fcba03',
borderRadius: '50%',
flexShrink: 0, // Prevent shrinking
}}
hashId={garage.getSlot()?.hashId ?? ''}
/>
</ListItemAvatar>
</ListItem>

<Divider sx={{ margin: '12px 0', backgroundColor: '#e0e0e0' }} />
</List>

<Typography
variant="subtitle1"
sx={{ fontWeight: 600, color: '#333', marginBottom: '12px' }}
>
{t('Coordinators that know your robot:')}
</Typography>

{federation.getCoordinators().map((coordinator: Coordinator) => {
const coordinatorRobot = garage.getSlot()?.getRobot(coordinator.shortAlias);
return (
<div
key={coordinator.shortAlias}
style={{ marginBottom: '12px', padding: '8px', borderRadius: '4px' }}
>
<RobotInfo
coordinator={coordinator}
onClose={onClose}
disabled={coordinatorRobot?.loading}
/>
</div>
);
})}
</DialogContent>
</Dialog>
{/* New ClaimRewardDialog */}
<ClaimRewardDialog
open={isClaimRewardDialogOpen}
onClose={handleCloseClaimRewardDialog}
t={t} // Pass translation function
/>
</>
);
};

Expand Down