Skip to content

Commit

Permalink
feat(wallet-dashboard): style migration flow (#4510)
Browse files Browse the repository at this point in the history
* feat(dashboard): add migration overview

* feat: refine values

* fix: update summarizeMigratableObjectValues function

* feat(dashboard): add migratable object details

* feat: add object details

* fix: add missing type

* feat: use react query to cache data

* refactor: show expiration label correctly

* feat: add missing asset fallback

* feat: simplify code, add correct timestamps

* fix: remove package id

* fix: bring back missing logic

* fix: remove unnecesary memos and improve skeleton

* feat: create migration dialog

* feat: style

* perf: fetch objects in chunks

* fix props

* feat: refine dialog

* feat: refine dialog styles

* cleanup

* fix import

* feat: add totalStorageDepositReturnAmount

* feat: fix virtual list and remove duplicated import

* feat: update storage deposit return amount

* fix format

* feat: improve naming and make onSuccess not optional

* feat: rmeove debris

* feat: update names

* feat: remove debris after merge

* chore: rename function

---------

Co-authored-by: JCNoguera <[email protected]>
Co-authored-by: Marc Espin <[email protected]>
Co-authored-by: Begoña Alvarez <[email protected]>
  • Loading branch information
4 people authored and miker83z committed Dec 20, 2024
1 parent 8b779b9 commit 26a5c05
Show file tree
Hide file tree
Showing 19 changed files with 241 additions and 252 deletions.
34 changes: 19 additions & 15 deletions apps/wallet-dashboard/app/(protected)/migrations/page.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -6,8 +6,7 @@
import { useState, useMemo, useCallback } from 'react';
import { useQueryClient } from '@tanstack/react-query';
import clsx from 'clsx';
import MigratePopup from '@/components/Popup/Popups/MigratePopup';
import { useGetStardustMigratableObjects, usePopups } from '@/hooks';
import { useGetStardustMigratableObjects } from '@/hooks';
import { summarizeMigratableObjectValues, summarizeUnmigratableObjectValues } from '@/lib/utils';
import {
Button,
Expand All @@ -25,15 +24,14 @@ import { useCurrentAccount, useIotaClient } from '@iota/dapp-kit';
import { STARDUST_BASIC_OUTPUT_TYPE, STARDUST_NFT_OUTPUT_TYPE, useFormatCoin } from '@iota/core';
import { IOTA_TYPE_ARG } from '@iota/iota-sdk/utils';
import { StardustOutputMigrationStatus } from '@/lib/enums';
import { MigrationObjectsPanel } from '@/components';
import { MigrationObjectsPanel, MigrationDialog } from '@/components';

function MigrationDashboardPage(): JSX.Element {
const account = useCurrentAccount();
const address = account?.address || '';
const { openPopup, closePopup } = usePopups();
const queryClient = useQueryClient();
const iotaClient = useIotaClient();

const [isMigrationDialogOpen, setIsMigrationDialogOpen] = useState(false);
const [selectedStardustObjectsCategory, setSelectedStardustObjectsCategory] = useState<
StardustOutputMigrationStatus | undefined
>(undefined);
Expand Down Expand Up @@ -133,15 +131,8 @@ function MigrationDashboardPage(): JSX.Element {
return [];
}, [selectedStardustObjectsCategory, stardustMigrationObjects]);

function openMigratePopup(): void {
openPopup(
<MigratePopup
basicOutputObjects={migratableBasicOutputs}
nftOutputObjects={migratableNftOutputs}
closePopup={closePopup}
onSuccess={handleOnSuccess}
/>,
);
function openMigrationDialog(): void {
setIsMigrationDialogOpen(true);
}

function handleCloseDetailsPanel() {
Expand All @@ -157,14 +148,27 @@ function MigrationDashboardPage(): JSX.Element {
)}
>
<div className="flex w-1/3 flex-col gap-md--rs">
{isMigrationDialogOpen && (
<MigrationDialog
basicOutputObjects={migratableBasicOutputs}
nftOutputObjects={migratableNftOutputs}
onSuccess={handleOnSuccess}
open={isMigrationDialogOpen}
setOpen={setIsMigrationDialogOpen}
isTimelocked={
selectedStardustObjectsCategory ===
StardustOutputMigrationStatus.TimeLocked
}
/>
)}
<Panel>
<Title
title="Migration"
trailingElement={
<Button
text="Migrate All"
disabled={!hasMigratableObjects}
onClick={openMigratePopup}
onClick={openMigrationDialog}
size={ButtonSize.Small}
/>
}
Expand Down
4 changes: 2 additions & 2 deletions apps/wallet-dashboard/app/(protected)/vesting/page.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -240,7 +240,7 @@ export default function VestingDashboardPage(): JSX.Element {
openUnstakeDialog(UnstakeDialogView.TimelockedUnstake);
}

function openReceiveTokenPopup(): void {
function openReceiveTokenDialog(): void {
setIsVestingScheduleDialogOpen(true);
}

Expand Down Expand Up @@ -326,7 +326,7 @@ export default function VestingDashboardPage(): JSX.Element {
/>
<CardAction
type={CardActionType.Button}
onClick={openReceiveTokenPopup}
onClick={openReceiveTokenDialog}
title="See All"
buttonType={ButtonType.Secondary}
buttonDisabled={!vestingPortfolio}
Expand Down
176 changes: 176 additions & 0 deletions apps/wallet-dashboard/components/Dialogs/MigrationDialog.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,176 @@
// Copyright (c) 2024 IOTA Stiftung
// SPDX-License-Identifier: Apache-2.0

import React from 'react';
import { VirtualList } from '@/components';
import { useCurrentAccount, useSignAndExecuteTransaction } from '@iota/dapp-kit';
import { IotaObjectData } from '@iota/iota-sdk/client';
import { useMigrationTransaction } from '@/hooks/useMigrationTransaction';
import {
Button,
Dialog,
Header,
InfoBox,
InfoBoxStyle,
InfoBoxType,
KeyValueInfo,
LoadingIndicator,
Panel,
Title,
TitleSize,
} from '@iota/apps-ui-kit';
import { useGroupedMigrationObjectsByExpirationDate } from '@/hooks';
import { Loader, Warning } from '@iota/ui-icons';
import { DialogLayout, DialogLayoutBody, DialogLayoutFooter } from './layout';
import { MigrationObjectDetailsCard } from '../migration/migration-object-details-card';
import { Collapsible, useFormatCoin } from '@iota/core';
import { IOTA_TYPE_ARG } from '@iota/iota-sdk/utils';
import { summarizeMigratableObjectValues } from '@/lib/utils';
import toast from 'react-hot-toast';

interface MigrationDialogProps {
basicOutputObjects: IotaObjectData[] | undefined;
nftOutputObjects: IotaObjectData[] | undefined;
onSuccess: (digest: string) => void;
setOpen: (bool: boolean) => void;
open: boolean;
isTimelocked: boolean;
}

export function MigrationDialog({
basicOutputObjects = [],
nftOutputObjects = [],
onSuccess,
open,
setOpen,
isTimelocked,
}: MigrationDialogProps): JSX.Element {
const account = useCurrentAccount();
const {
data: migrateData,
isPending: isMigrationPending,
isError: isMigrationError,
} = useMigrationTransaction(account?.address || '', basicOutputObjects, nftOutputObjects);

const {
data: resolvedObjects = [],
isLoading,
error: isGroupedMigrationError,
} = useGroupedMigrationObjectsByExpirationDate(
[...basicOutputObjects, ...nftOutputObjects],
isTimelocked,
);

const { mutateAsync: signAndExecuteTransaction, isPending: isSendingTransaction } =
useSignAndExecuteTransaction();
const { totalNotOwnedStorageDepositReturnAmount } = summarizeMigratableObjectValues({
basicOutputs: basicOutputObjects,
nftOutputs: nftOutputObjects,
address: account?.address || '',
});

const [gasFee, gasFeeSymbol] = useFormatCoin(migrateData?.gasBudget, IOTA_TYPE_ARG);
const [totalStorageDepositReturnAmountFormatted, totalStorageDepositReturnAmountSymbol] =
useFormatCoin(totalNotOwnedStorageDepositReturnAmount.toString(), IOTA_TYPE_ARG);

async function handleMigrate(): Promise<void> {
if (!migrateData) return;
signAndExecuteTransaction(
{
transaction: migrateData.transaction,
},
{
onSuccess: (tx) => {
onSuccess(tx.digest);
},
},
)
.then(() => {
toast.success('Migration transaction has been sent');
})
.catch(() => {
toast.error('Migration transaction was not sent');
});
}

return (
<Dialog open={open} onOpenChange={setOpen}>
<DialogLayout>
<Header title="Confirmation" onClose={() => setOpen(false)} titleCentered />
<DialogLayoutBody>
<div className="flex h-full flex-col gap-y-md overflow-y-auto">
{isGroupedMigrationError && !isLoading && (
<InfoBox
title="Error"
supportingText="Failed to load migration objects"
style={InfoBoxStyle.Elevated}
type={InfoBoxType.Error}
icon={<Warning />}
/>
)}
{isLoading ? (
<LoadingIndicator text="Loading migration objects" />
) : (
<>
<Collapsible
defaultOpen
render={() => (
<Title size={TitleSize.Small} title="Assets to Migrate" />
)}
>
<div className="h-[600px] pb-md--rs">
<VirtualList
heightClassName="h-full"
overflowClassName="overflow-y-auto"
items={resolvedObjects}
estimateSize={() => 58}
render={(migrationObject) => (
<MigrationObjectDetailsCard
migrationObject={migrationObject}
isTimelocked={isTimelocked}
/>
)}
/>
</div>
</Collapsible>
<Panel hasBorder>
<div className="flex flex-col gap-y-sm p-md">
<KeyValueInfo
keyText="Legacy storage deposit"
value={totalStorageDepositReturnAmountFormatted || '-'}
supportingLabel={totalStorageDepositReturnAmountSymbol}
fullwidth
/>
<KeyValueInfo
keyText="Gas Fees"
value={gasFee || '-'}
supportingLabel={gasFeeSymbol}
fullwidth
/>
</div>
</Panel>
</>
)}
</div>
</DialogLayoutBody>
<DialogLayoutFooter>
<Button
text="Migrate"
disabled={isMigrationPending || isMigrationError || isSendingTransaction}
onClick={handleMigrate}
icon={
isMigrationPending || isSendingTransaction ? (
<Loader
className="h-4 w-4 animate-spin"
data-testid="loading-indicator"
/>
) : null
}
iconAfterText
fullWidth
/>
</DialogLayoutFooter>
</DialogLayout>
</Dialog>
);
}
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,7 @@ import { INITIAL_VALUES } from './constants';
import { IOTA_TYPE_ARG } from '@iota/iota-sdk/utils';
import { useTransferTransactionMutation } from '@/hooks';

interface SendCoinPopupProps {
interface SendCoinDialogProps {
coin: CoinBalance;
activeAddress: string;
setOpen: (bool: boolean) => void;
Expand All @@ -30,7 +30,7 @@ function SendTokenDialogBody({
coin,
activeAddress,
setOpen,
}: SendCoinPopupProps): React.JSX.Element {
}: SendCoinDialogProps): React.JSX.Element {
const [step, setStep] = useState<FormStep>(FormStep.EnterValues);
const [selectedCoin, setSelectedCoin] = useState<CoinBalance>(coin);
const [formData, setFormData] = useState<FormDataValues>(INITIAL_VALUES);
Expand Down Expand Up @@ -125,7 +125,7 @@ function SendTokenDialogBody({
);
}

export function SendTokenDialog(props: SendCoinPopupProps) {
export function SendTokenDialog(props: SendCoinDialogProps) {
return (
<Dialog open={props.open} onOpenChange={props.setOpen}>
<DialogContent containerId="overlay-portal-container" position={DialogPosition.Right}>
Expand Down
1 change: 1 addition & 0 deletions apps/wallet-dashboard/components/Dialogs/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -6,3 +6,4 @@ export * from './ReceiveFundsDialog';
export * from './Staking';
export * from './unstake';
export * from './vesting';
export * from './MigrationDialog';
31 changes: 0 additions & 31 deletions apps/wallet-dashboard/components/Popup/Popup.tsx

This file was deleted.

29 changes: 0 additions & 29 deletions apps/wallet-dashboard/components/Popup/PopupProvider.tsx

This file was deleted.

Loading

0 comments on commit 26a5c05

Please sign in to comment.