Skip to content

Commit

Permalink
feat(wallet-dashboard): Add tx details state to the end of send coin …
Browse files Browse the repository at this point in the history
…flow (#4422)

* feat(wallet-dashboard): implement SentSuccess view and update SendTokenDialog flow

* feat(dashboard): enhance SendTokenDialog flow and improve transaction handling

* feat(dashboard): rename SentSuccessView to TransactionDetailsView and update flow

* fix(dashboard): improve error message for transaction info retrieval

* feat(dashboard): add loading state with spinner during transaction fetching

* feat(dashboard): implement transfer transaction mutation and refactor handling logic

* refactor(explorer, core): move useRecognizedPackages from explorer to core.

* feat(dashboard): integrate useRecognizedPackages for transaction details

* fix(dashboard): correct import path for useTransferTransaction

* remove with dialog content

* refactor: format

---------

Co-authored-by: marc2332 <[email protected]>
  • Loading branch information
panteleymonchuk and marc2332 authored Dec 17, 2024
1 parent 2d61a55 commit 3ab420c
Show file tree
Hide file tree
Showing 18 changed files with 402 additions and 298 deletions.
1 change: 1 addition & 0 deletions apps/core/src/hooks/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -47,5 +47,6 @@ export * from './useOwnedNFT';
export * from './useNftDetails';
export * from './useCountdownByTimestamp';
export * from './useStakeRewardStatus';
export * from './useRecognizedPackages';

export * from './stake';
Original file line number Diff line number Diff line change
Expand Up @@ -2,18 +2,11 @@
// Modifications Copyright (c) 2024 IOTA Stiftung
// SPDX-License-Identifier: Apache-2.0

import { Feature } from '@iota/core';
import { useFeatureValue } from '@growthbook/growthbook-react';
import { IOTA_FRAMEWORK_ADDRESS, IOTA_SYSTEM_ADDRESS } from '@iota/iota-sdk/utils';
import { Network } from '@iota/iota-sdk/client';
import { Feature, DEFAULT_RECOGNIZED_PACKAGES } from '../../';

import { useNetwork } from './';

const DEFAULT_RECOGNIZED_PACKAGES = [IOTA_FRAMEWORK_ADDRESS, IOTA_SYSTEM_ADDRESS];

export function useRecognizedPackages(): string[] {
const [network] = useNetwork();

export function useRecognizedPackages(network: Network): string[] {
const recognizedPackages = useFeatureValue(
Feature.RecognizedPackages,
DEFAULT_RECOGNIZED_PACKAGES,
Expand Down
9 changes: 5 additions & 4 deletions apps/explorer/src/components/owned-coins/OwnedCoins.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -2,14 +2,14 @@
// Modifications Copyright (c) 2024 IOTA Stiftung
// SPDX-License-Identifier: Apache-2.0

import { getCoinSymbol } from '@iota/core';
import { getCoinSymbol, useRecognizedPackages } from '@iota/core';
import { useIotaClientQuery } from '@iota/dapp-kit';
import { type CoinBalance } from '@iota/iota-sdk/client';
import { type CoinBalance, type Network } from '@iota/iota-sdk/client';
import { useNetwork } from '~/hooks';
import { normalizeIotaAddress } from '@iota/iota-sdk/utils';
import { FilterList, Warning } from '@iota/ui-icons';
import { useMemo, useState } from 'react';
import OwnedCoinView from './OwnedCoinView';
import { useRecognizedPackages } from '~/hooks/useRecognizedPackages';
import {
Button,
ButtonType,
Expand Down Expand Up @@ -47,7 +47,8 @@ export function OwnedCoins({ id }: OwnerCoinsProps): JSX.Element {
const { isPending, data, isError } = useIotaClientQuery('getAllBalances', {
owner: normalizeIotaAddress(id),
});
const recognizedPackages = useRecognizedPackages();
const [network] = useNetwork();
const recognizedPackages = useRecognizedPackages(network as Network);

const balances: Record<CoinFilter, CoinBalanceVerified[]> = useMemo(() => {
const balanceData = data?.reduce(
Expand Down
1 change: 0 additions & 1 deletion apps/explorer/src/hooks/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,6 @@ export * from './useInitialPageView';
export * from './useMediaQuery';
export * from './useNetwork';
export * from './useNormalizedMoveModule';
export * from './useRecognizedPackages';
export * from './useResolveVideo';
export * from './useSearch';
export * from './useVerifiedSourceCode';
8 changes: 5 additions & 3 deletions apps/explorer/src/hooks/useResolveVideo.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,12 +2,14 @@
// Modifications Copyright (c) 2024 IOTA Stiftung
// SPDX-License-Identifier: Apache-2.0

import { type IotaObjectResponse } from '@iota/iota-sdk/client';
import { type IotaObjectResponse, type Network } from '@iota/iota-sdk/client';

import { useRecognizedPackages } from './useRecognizedPackages';
import { useRecognizedPackages } from '@iota/core';
import { useNetwork } from '~/hooks';

export function useResolveVideo(object: IotaObjectResponse): string | undefined | null {
const recognizedPackages = useRecognizedPackages();
const [network] = useNetwork();
const recognizedPackages = useRecognizedPackages(network as Network);
const objectType =
(object.data?.type ?? object?.data?.content?.dataType === 'package')
? 'package'
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -2,13 +2,14 @@
// Modifications Copyright (c) 2024 IOTA Stiftung
// SPDX-License-Identifier: Apache-2.0

import { useTransactionSummary } from '@iota/core';
import { useTransactionSummary, useRecognizedPackages } from '@iota/core';
import {
type ProgrammableTransaction,
type IotaTransactionBlockResponse,
type Network,
} from '@iota/iota-sdk/client';
import { GasBreakdown } from '~/components';
import { useRecognizedPackages } from '~/hooks/useRecognizedPackages';
import { useNetwork } from '~/hooks/useNetwork';
import { InputsCard } from '~/pages/transaction-result/programmable-transaction-view/InputsCard';
import { TransactionsCard } from '~/pages/transaction-result/programmable-transaction-view/TransactionsCard';

Expand All @@ -17,7 +18,8 @@ interface TransactionDataProps {
}

export function TransactionData({ transaction }: TransactionDataProps): JSX.Element {
const recognizedPackagesList = useRecognizedPackages();
const [network] = useNetwork();
const recognizedPackagesList = useRecognizedPackages(network as Network);
const summary = useTransactionSummary({
transaction,
recognizedPackagesList,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@
// Modifications Copyright (c) 2024 IOTA Stiftung
// SPDX-License-Identifier: Apache-2.0

import { type IotaTransactionBlockResponse } from '@iota/iota-sdk/client';
import { type Network, type IotaTransactionBlockResponse } from '@iota/iota-sdk/client';
import clsx from 'clsx';
import { useState } from 'react';
import { ErrorBoundary, SplitPanes } from '~/components';
Expand All @@ -11,8 +11,8 @@ import { TransactionData } from '~/pages/transaction-result/TransactionData';
import { TransactionSummary } from '~/pages/transaction-result/transaction-summary';
import { Signatures } from './Signatures';
import { TransactionDetails } from './transaction-summary/TransactionDetails';
import { useTransactionSummary } from '@iota/core';
import { useBreakpoint, useRecognizedPackages } from '~/hooks';
import { useTransactionSummary, useRecognizedPackages } from '@iota/core';
import { useBreakpoint, useNetwork } from '~/hooks';
import {
ButtonSegment,
ButtonSegmentType,
Expand Down Expand Up @@ -42,7 +42,8 @@ export function TransactionView({ transaction }: TransactionViewProps): JSX.Elem
const transactionKindName = transaction.transaction?.data.transaction?.kind;
const isProgrammableTransaction = transactionKindName === 'ProgrammableTransaction';

const recognizedPackagesList = useRecognizedPackages();
const [network] = useNetwork();
const recognizedPackagesList = useRecognizedPackages(network as Network);
const summary = useTransactionSummary({
transaction,
recognizedPackagesList,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -2,20 +2,20 @@
// Modifications Copyright (c) 2024 IOTA Stiftung
// SPDX-License-Identifier: Apache-2.0

import { useTransactionSummary } from '@iota/core';
import { type IotaTransactionBlockResponse } from '@iota/iota-sdk/client';

import { useTransactionSummary, useRecognizedPackages } from '@iota/core';
import { type IotaTransactionBlockResponse, type Network } from '@iota/iota-sdk/client';
import { BalanceChanges } from './BalanceChanges';
import { ObjectChanges } from './ObjectChanges';
import { UpgradedSystemPackages } from './UpgradedSystemPackages';
import { useRecognizedPackages } from '~/hooks/useRecognizedPackages';
import { useNetwork } from '~/hooks';

interface TransactionSummaryProps {
transaction: IotaTransactionBlockResponse;
}

export function TransactionSummary({ transaction }: TransactionSummaryProps): JSX.Element {
const recognizedPackagesList = useRecognizedPackages();
const [network] = useNetwork();
const recognizedPackagesList = useRecognizedPackages(network as Network);
const summary = useTransactionSummary({
transaction,
recognizedPackagesList,
Expand Down
105 changes: 55 additions & 50 deletions apps/wallet-dashboard/components/Dialogs/SendToken/SendTokenDialog.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -2,16 +2,16 @@
// SPDX-License-Identifier: Apache-2.0

import React, { useState } from 'react';
import { EnterValuesFormView, ReviewValuesFormView } from './views';
import { EnterValuesFormView, ReviewValuesFormView, TransactionDetailsView } from './views';
import { CoinBalance } from '@iota/iota-sdk/client';
import { useSendCoinTransaction, useNotifications } from '@/hooks';
import { useSignAndExecuteTransaction } from '@iota/dapp-kit';
import { NotificationType } from '@/stores/notificationStore';
import { CoinFormat, useFormatCoin, useGetAllCoins } from '@iota/core';
import { Dialog, DialogBody, DialogContent, DialogPosition, Header } from '@iota/apps-ui-kit';
import { Dialog, DialogContent, DialogPosition } from '@iota/apps-ui-kit';
import { FormDataValues } from './interfaces';
import { INITIAL_VALUES } from './constants';
import { IOTA_TYPE_ARG } from '@iota/iota-sdk/utils';
import { useTransferTransactionMutation } from '@/hooks';

interface SendCoinPopupProps {
coin: CoinBalance;
Expand All @@ -23,6 +23,7 @@ interface SendCoinPopupProps {
enum FormStep {
EnterValues,
ReviewValues,
TransactionDetails,
}

function SendTokenDialogBody({
Expand All @@ -34,11 +35,9 @@ function SendTokenDialogBody({
const [selectedCoin, setSelectedCoin] = useState<CoinBalance>(coin);
const [formData, setFormData] = useState<FormDataValues>(INITIAL_VALUES);
const [fullAmount] = useFormatCoin(formData.amount, selectedCoin.coinType, CoinFormat.FULL);
const { addNotification } = useNotifications();

const { data: coinsData } = useGetAllCoins(selectedCoin.coinType, activeAddress);

const { mutateAsync: signAndExecuteTransaction, isPending } = useSignAndExecuteTransaction();
const { addNotification } = useNotifications();
const isPayAllIota =
selectedCoin.totalBalance === formData.amount && selectedCoin.coinType === IOTA_TYPE_ARG;

Expand All @@ -51,25 +50,28 @@ function SendTokenDialogBody({
isPayAllIota,
);

function handleTransfer() {
const {
mutate: transfer,
data,
isPending: isLoadingTransfer,
} = useTransferTransactionMutation();

async function handleTransfer() {
if (!transaction) {
addNotification('There was an error with the transaction', NotificationType.Error);
return;
} else {
signAndExecuteTransaction({
transaction,
})
.then(() => {
setOpen(false);
addNotification('Transfer transaction has been sent');
})
.catch(handleTransactionError);
}
}

function handleTransactionError() {
setOpen(false);
addNotification('There was an error with the transaction', NotificationType.Error);
transfer(transaction, {
onSuccess: () => {
setStep(FormStep.TransactionDetails);
addNotification('Transfer transaction has been sent', NotificationType.Success);
},
onError: () => {
setOpen(false);
addNotification('Transfer transaction failed', NotificationType.Error);
},
});
}

function onNext(): void {
Expand All @@ -87,40 +89,43 @@ function SendTokenDialogBody({

return (
<>
<Header
title={step === FormStep.EnterValues ? 'Send' : 'Review & Send'}
onClose={() => setOpen(false)}
onBack={step === FormStep.ReviewValues ? onBack : undefined}
/>
<div className="h-full [&>div]:h-full">
<DialogBody>
{step === FormStep.EnterValues && (
<EnterValuesFormView
coin={selectedCoin}
activeAddress={activeAddress}
setSelectedCoin={setSelectedCoin}
onNext={onNext}
setFormData={setFormData}
initialFormValues={formData}
/>
)}
{step === FormStep.ReviewValues && (
<ReviewValuesFormView
formData={formData}
executeTransfer={handleTransfer}
senderAddress={activeAddress}
isPending={isPending}
coinType={selectedCoin.coinType}
isPayAllIota={isPayAllIota}
/>
)}
</DialogBody>
</div>
{step === FormStep.EnterValues && (
<EnterValuesFormView
coin={selectedCoin}
activeAddress={activeAddress}
setSelectedCoin={setSelectedCoin}
onNext={onNext}
onClose={() => setOpen(false)}
setFormData={setFormData}
initialFormValues={formData}
/>
)}
{step === FormStep.ReviewValues && (
<ReviewValuesFormView
formData={formData}
executeTransfer={handleTransfer}
senderAddress={activeAddress}
isPending={isLoadingTransfer}
coinType={selectedCoin.coinType}
isPayAllIota={isPayAllIota}
onClose={() => setOpen(false)}
onBack={onBack}
/>
)}
{step === FormStep.TransactionDetails && data?.digest && (
<TransactionDetailsView
digest={data.digest}
onClose={() => {
setOpen(false);
setStep(FormStep.EnterValues);
}}
/>
)}
</>
);
}

export function SendTokenDialog(props: SendCoinPopupProps): React.JSX.Element {
export function SendTokenDialog(props: SendCoinPopupProps) {
return (
<Dialog open={props.open} onOpenChange={props.setOpen}>
<DialogContent containerId="overlay-portal-container" position={DialogPosition.Right}>
Expand Down
Loading

0 comments on commit 3ab420c

Please sign in to comment.