Skip to content
Merged
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
Original file line number Diff line number Diff line change
Expand Up @@ -72,6 +72,10 @@ export const messages = Object.freeze(
id: 'airdrop.schedule.redeemed',
defaultMessage: '!!!Redeemed',
},
failed: {
id: 'airdrop.schedule.failed',
defaultMessage: '!!!Failed',
},
thawExplanation: {
id: 'airdrop.schedule.thawExplanation',
defaultMessage: '!!!Each thaw is 25% of your claimed NIGHT allocation',
Expand Down Expand Up @@ -279,6 +283,7 @@ export const useStrings = () => {
thawTitle: (index: number, total: number) => intl.formatMessage(messages.thawTitle, { index, total }),
notAvailable: intl.formatMessage(messages.notAvailable),
redeemed: intl.formatMessage(messages.redeemed),
failed: intl.formatMessage(messages.failed),
thawExplanation: intl.formatMessage(messages.thawExplanation),
statusLabel: intl.formatMessage(messages.statusLabel),
subTitle: intl.formatMessage(messages.subTitle),
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -158,16 +158,17 @@ function formatDate(dateString: string, locale: string): string {
}

function getCurrentThawIndex(schedule: Schedule): number {
const redeemableIndex = schedule.thaws.findIndex(thaw => thaw.status === 'redeemable');
const redeemableIndex = schedule.thaws.findIndex(thaw => thaw.status === 'redeemable' || thaw.status === 'failed');
if (redeemableIndex >= 0) return redeemableIndex;
const upcomingIndex = schedule.thaws.findIndex(thaw => thaw.status === 'upcoming');
const upcomingIndex = schedule.thaws.findIndex(thaw => thaw.status === 'upcoming' || thaw.status === 'failed');
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Redundant failed status check is dead code

Low Severity

The || thaw.status === 'failed' condition on line 163 (checking for 'upcoming' || 'failed') is unreachable. If any failed thaw exists, it will be found by the first findIndex on line 161 (which already checks for 'failed'), and the function returns before reaching line 163. This suggests confusion about how failed thaws should be handled for determining the current thaw index.

Fix in Cursor Fix in Web

if (upcomingIndex >= 0) return upcomingIndex;
return 0;
}

type ThawStatus = 'redeemable' | 'confirmed' | 'upcoming';
type ThawStatus = 'redeemable' | 'confirmed' | 'upcoming' | 'failed';

function getThawStatusType(status: string): ThawStatus {
if (status === 'failed') return 'failed';
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Failed status badge missing distinct visual styling

Low Severity

The getStatusBadgeStyle function has no case for 'failed' status, so it falls through to the default case and receives the same gray styling as 'upcoming' items. This creates an inconsistent UI where failed thaws display a distinct red X icon in the step indicator but their status badge looks identical to "Not available yet" badges. Given this PR specifically adds failed status display with distinct visual treatment elsewhere, the badge styling appears to be an incomplete implementation.

Fix in Cursor Fix in Web

if (status === 'redeemable') return 'redeemable';
if (status === 'confirmed') return 'confirmed';
return 'upcoming';
Expand Down Expand Up @@ -202,6 +203,8 @@ function getStatusMessage(status: ThawStatus, strings: ReturnType<typeof useStri
return strings.redeemable;
case 'confirmed':
return strings.redeemed;
case 'failed':
return strings.failed;
default:
return strings.notAvailable;
}
Expand Down Expand Up @@ -238,6 +241,7 @@ function ScheduleCard({ schedule }: { schedule: Schedule }) {
const isCurrent = index === currentThawIndex;
const isPast = index < currentThawIndex;
const isLast = index === totalThaws - 1;
const isFailed = thaw.status === 'failed';

return (
<Box key={index} sx={{ flex: 1, position: 'relative' }}>
Expand All @@ -251,15 +255,30 @@ function ScheduleCard({ schedule }: { schedule: Schedule }) {
display: 'flex',
alignItems: 'center',
justifyContent: 'center',
backgroundColor: isPast ? 'ds.primary_300' : isCurrent ? 'ds.primary_500' : 'ds.gray_200',
backgroundColor: isFailed
? 'ds.gray_200'
: isPast
? 'ds.primary_300'
: isCurrent
? 'ds.primary_500'
: 'ds.gray_200',
color: isPast || isCurrent ? 'ds.white_static' : 'ds.text_gray_min',
fontSize: '12px',
fontWeight: 500,
zIndex: 1,
flexShrink: 0,
}}
>
{isPast ? (
{isFailed ? (
<svg xmlns="http://www.w3.org/2000/svg" width="14" height="14" viewBox="0 0 16 16" fill="none">
<path
fill-rule="evenodd"
clip-rule="evenodd"
d="M4.29289 4.29289C4.68342 3.90237 5.31658 3.90237 5.70711 4.29289L8 6.58579L10.2929 4.29289C10.6834 3.90237 11.3166 3.90237 11.7071 4.29289C12.0976 4.68342 12.0976 5.31658 11.7071 5.70711L9.41421 8L11.7071 10.2929C12.0976 10.6834 12.0976 11.3166 11.7071 11.7071C11.3166 12.0976 10.6834 12.0976 10.2929 11.7071L8 9.41421L5.70711 11.7071C5.31658 12.0976 4.68342 12.0976 4.29289 11.7071C3.90237 11.3166 3.90237 10.6834 4.29289 10.2929L6.58579 8L4.29289 5.70711C3.90237 5.31658 3.90237 4.68342 4.29289 4.29289Z"
fill="#FF0000"
/>
</svg>
) : isPast ? (
<Box sx={{ display: 'flex', alignItems: 'center', justifyContent: 'center' }}>
<svg xmlns="http://www.w3.org/2000/svg" width="14" height="14" viewBox="0 0 16 16" fill="none">
<path
Expand Down
2 changes: 1 addition & 1 deletion packages/yoroi-extension/app/api/ada/midnightRedemption.ts
Original file line number Diff line number Diff line change
Expand Up @@ -27,7 +27,7 @@ export function formatNumberExactly(n: number): string {
export function getRedeemableAmount(schedule: Schedule): number {
return schedule.thaws
.slice(schedule.numberOfClaimedAllocations)
.filter(thaw => thaw.status === 'redeemable')
.filter(thaw => thaw.status === 'redeemable' || thaw.status === 'failed')
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Failed thaws incorrectly counted in redeemable amount

High Severity

The getRedeemableAmount function now includes thaws with 'failed' status in the redeemable total. This causes failed thaw amounts to be shown to users as redeemable, enables the Redeem button when only failed thaws exist, and marks the overall status as 'ready'. Failed thaws by definition have already failed and likely cannot be redeemed, making this amount misleading to users and potentially triggering redemption flows that will fail.

Fix in Cursor Fix in Web

.reduce((accu, thaw) => accu + thaw.amount, 0);
}

Expand Down
1 change: 1 addition & 0 deletions packages/yoroi-extension/app/i18n/locales/en-US.json
Original file line number Diff line number Diff line change
Expand Up @@ -52,6 +52,7 @@
"airdrop.redeem.reorgMessage": "Please re-orgnize the wallet for collateral UTxOs",
"airdrop.redeem.waitingForReorg": "Waiting for the re-organization transaction to be confirmed",
"airdrop.schedule.currentThaw": "Current thaw: {current}/{total}",
"airdrop.schedule.failed": "Failed",
"airdrop.schedule.notAvailable": "Not available yet",
"airdrop.schedule.redeemable": "Redeemable",
"airdrop.schedule.redeemed": "Redeemed",
Expand Down
Loading