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
127 changes: 81 additions & 46 deletions apps/core/src/components/transaction/TransactionReceipt.tsx

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

im not sure we need this change for the transaction status 🙈 since is not really a toast 🙏

Original file line number Diff line number Diff line change
@@ -1,9 +1,10 @@
// Copyright (c) 2024 IOTA Stiftung
// SPDX-License-Identifier: Apache-2.0

import { useState } from 'react';
import { InfoBox, InfoBoxStyle, InfoBoxType } from '@iota/apps-ui-kit';
import { CheckmarkFilled, Close } from '@iota/apps-ui-icons';
import type { useTransactionSummary } from '../../hooks';
import { CheckmarkFilled } from '@iota/apps-ui-icons';
import { IotaTransactionBlockResponse } from '@iota/iota-sdk/client';
import { STAKING_REQUEST_EVENT, UNSTAKING_REQUEST_EVENT } from '../../constants';
import { StakeTransactionDetails } from './details';
Expand All @@ -18,60 +19,80 @@ interface TransactionReceiptProps {
activeAddress: string | null;
summary: Exclude<ReturnType<typeof useTransactionSummary>, null>;
renderExplorerLink: RenderExplorerLink;
stickyStatus?: boolean;
}

export function TransactionReceipt({
txn,
activeAddress,
summary,
renderExplorerLink,
stickyStatus,
}: TransactionReceiptProps) {
const { events } = txn;

const isSender = txn.transaction?.data.sender === activeAddress;
const isSuccess = summary.status === 'success';

const stakeTypeTransaction = events?.find(({ type }) => type === STAKING_REQUEST_EVENT);
const unstakeTypeTransaction = events?.find(({ type }) => type === UNSTAKING_REQUEST_EVENT);

return (
<div className="flex flex-col gap-md overflow-y-auto overflow-x-hidden">
<TransactionStatus
success={summary.status === 'success'}
timestamp={txn.timestampMs ?? undefined}
isIncoming={!isSender}
/>
{stakeTypeTransaction || unstakeTypeTransaction ? (
<>
{stakeTypeTransaction ? (
<StakeTransactionDetails
activeAddress={activeAddress}
events={events ?? []}
gasSummary={summary?.gas}
renderExplorerLink={renderExplorerLink}
/>
) : null}
const content =
stakeTypeTransaction || unstakeTypeTransaction ? (
<>
{stakeTypeTransaction ? (
<StakeTransactionDetails
activeAddress={activeAddress}
events={events ?? []}
gasSummary={summary?.gas}
renderExplorerLink={renderExplorerLink}
/>
) : null}
{unstakeTypeTransaction ? (
<UnstakeTransactionInfo
activeAddress={activeAddress}
events={events ?? []}
gasSummary={summary?.gas}
renderExplorerLink={renderExplorerLink}
/>
) : null}
</>
) : (
<>
<TransactionSummary summary={summary} renderExplorerLink={renderExplorerLink} />
{isSender && (
<GasFees
gasSummary={summary?.gas}
renderExplorerLink={renderExplorerLink}
activeAddress={activeAddress}
/>
)}
</>
);

{unstakeTypeTransaction ? (
<UnstakeTransactionInfo
activeAddress={activeAddress}
events={events ?? []}
gasSummary={summary?.gas}
renderExplorerLink={renderExplorerLink}
/>
) : null}
</>
) : (
<>
<TransactionSummary summary={summary} renderExplorerLink={renderExplorerLink} />
{isSender && (
<GasFees
gasSummary={summary?.gas}
renderExplorerLink={renderExplorerLink}
activeAddress={activeAddress}
/>
)}
</>
)}
const status = (
<TransactionStatus
success={isSuccess}
timestamp={txn.timestampMs ?? undefined}
isIncoming={!isSender}
/>
);

if (stickyStatus) {
return (
<div className="flex min-h-0 flex-1 flex-col">
<div className="flex min-h-0 flex-1 flex-col gap-md overflow-x-hidden overflow-y-auto">
{content}
</div>
{status}
</div>
);
}

return (
<div className="flex flex-col gap-md overflow-x-hidden overflow-y-auto">
{status}
{content}
</div>
);
}
Expand All @@ -83,17 +104,31 @@ interface TransactionStatusProps {
}

function TransactionStatus({ success, timestamp, isIncoming }: TransactionStatusProps) {
const [dismissed, setDismissed] = useState(false);

if (dismissed) return null;

const txnDate = timestamp
? formatDate(Number(timestamp), ['day', 'month', 'year', 'hour', 'minute'])
: '';
const successMessage = isIncoming ? 'Successfully received' : 'Successfully sent';

return (
<InfoBox
type={success ? InfoBoxType.Success : InfoBoxType.Error}
style={InfoBoxStyle.Elevated}
title={success ? successMessage : 'Transaction Failed'}
supportingText={timestamp ? txnDate : ''}
icon={<CheckmarkFilled />}
/>
<div className="relative">
<InfoBox
type={success ? InfoBoxType.Success : InfoBoxType.Error}
style={InfoBoxStyle.Elevated}
title={success ? successMessage : 'Transaction failed'}
supportingText={txnDate}
icon={<CheckmarkFilled />}
/>
<button
onClick={() => setDismissed(true)}
className="absolute right-2 top-1/2 -translate-y-1/2 p-xs text-iota-neutral-40 hover:text-iota-neutral-10 dark:text-iota-neutral-60 dark:hover:text-iota-neutral-92"
aria-label="Dismiss"
>
<Close className="h-4 w-4" />
</button>
</div>
);
}
1 change: 1 addition & 0 deletions apps/wallet/src/ui/app/components/receipt-card/index.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -40,6 +40,7 @@ export function ReceiptCard({ txn, activeAddress }: ReceiptCardProps) {
summary={summary}
activeAddress={activeAddress}
renderExplorerLink={ExplorerLinkHelper}
stickyStatus
/>
<div className="flex flex-row space-x-xs pt-sm" data-amp-mask>
<div className="flex w-full [&_a]:w-full">
Expand Down
56 changes: 12 additions & 44 deletions apps/wallet/src/ui/app/shared/toaster/index.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -2,57 +2,24 @@
// Modifications Copyright (c) 2024 IOTA Stiftung
// SPDX-License-Identifier: Apache-2.0

import { useMenuIsOpen } from '_components';
import { useAppSelector } from '_hooks';
import { getNavIsVisible } from '_redux/slices/app';
import cl from 'clsx';
import { useEffect } from 'react';
import { useLocation } from 'react-router-dom';
import { Portal } from '../Portal';
import { useEffect } from 'react';
import { Toaster as ToasterCore, toast, useToasterStore } from '@iota/core';

export type ToasterProps = {
bottomNavEnabled?: boolean;
};
const ROUTES_WITH_BOTTOM_CONTENT = [
'/send',
'/dapp/connect',
'/dapp/approve',
'/accounts/import-ledger-accounts',
'/accounts/manage/accounts-finder',
'/accounts/manage',
];

const LIMIT_MAX_TOASTS = 5;

function getBottomSpace(pathname: string, isMenuVisible: boolean, isBottomNavSpace: boolean) {
if (isMenuVisible) {
return '!bottom-28';
}

const overlayWithActionButton = [
'/auto-lock',
'/manage/accounts-finder',
'/accounts/import-ledger-accounts',
'/send',
'/accounts/forgot-password/recover-many',
'/accounts/manage',
].includes(pathname);

const matchDynamicPaths = ['/dapp/connect', '/dapp/approve'].some((path) =>
pathname.startsWith(path),
);

if (overlayWithActionButton || isBottomNavSpace || matchDynamicPaths) {
return '!bottom-20';
}

return '';
}

export function Toaster({ bottomNavEnabled = false }: ToasterProps) {
export function Toaster({ bottomNavEnabled = false }: { bottomNavEnabled?: boolean }) {
const { pathname } = useLocation();

const menuVisible = useMenuIsOpen();
const isBottomNavVisible = useAppSelector(getNavIsVisible);
const bottomSpace = getBottomSpace(
pathname,
menuVisible,
isBottomNavVisible && bottomNavEnabled,
);

const { toasts } = useToasterStore();

useEffect(() => {
Expand All @@ -62,10 +29,11 @@ export function Toaster({ bottomNavEnabled = false }: ToasterProps) {
.forEach((t) => toast.dismiss(t.id));
}, [toasts]);

const hasBottomContent = ROUTES_WITH_BOTTOM_CONTENT.some((path) => pathname.startsWith(path));
return (
<Portal containerId="toaster-portal-container">
<ToasterCore
containerClassName={cl('!absolute transition-all', bottomSpace)}
containerClassName={`!absolute transition-all${bottomNavEnabled || hasBottomContent ? ' !bottom-20' : ''}`}
snackbarWrapClassName="w-full break-words"
/>
</Portal>
Expand Down
Loading