Skip to content

Commit

Permalink
feat(wallet-dashboard): add styles for Review & Send screen (#3787)
Browse files Browse the repository at this point in the history
* feat(wallet-dashboard): add styles for Review & Send screen

* fix: move CoinIcon to core

* feat(wallet-dashboard): style send entry screen WIP

* feat(wallet-dashboard): style send entry screen WIP

* fix(wallet-dashboard): sort the dependencies

* feat(wallet-dashboard): includes icon coin in coin selector

* fix(wallet-dashboard): prettier

* fix(wallet-dashboard): update schema validation and share gas budget estimation logic

* fix(wallet-dashboard): some fixes

* fix(wallet-dashboard): some build errors

* fix(wallet-dashboard): fix change amount in send token input

* fix(wallet-dashboard): linter

* fix(wallet-dashboard): linter

* fix(wallet-dashboard): core prettier

* feat: add review dialog as view

* fix(wallet-dashboard): include interface with props and some fixes

* fix: update review comments

* fix(wallet-dashboard): fixes

* fix(wallet-dashboard): fixes

* fix(wallet-dashboard): move FormInputs to a standalone component

* fix(wallet-dashboard): improve AddressInputs props

* fix(wallet-dashboard): linter

* fix(wallet-dashboard): format core

* fix(wallet-dashboard): clean debris

* fix: add ExplorerLink component and add missing dialog styles

* fix: use correct values for keyvalue

* fix(wallet-dashboard): bring back the validation field

* fix(wallet-dashboard): bad merge removing duplicated image components

* fix(wallet-dashboard): remove unnecesary InputForm component

* fix(wallet-dashboard): adjust to full height the dialog body

* fix(wallet-dashboard): prettier

* fix: gas approximation

* fix(wallet-dashboard): max button disabled

* feat(wallet-dashboard): improvements

* fix(wallet-dashboard): improve formik props

* fix(wallet-dashboard): improvements

* refactor: Simplify SendTokenFormInput

* refactor: prettier:fix

* refactor: prettier:fix on apps/core

* refactor: Add missing license header to token.ts

* fix: linter

* fix(wallet-dashboard): linter

* fix(wallet-dashboard): linter

* fix: amount format

* feat: Improve validation flow of sent screen

* fmt

* fix: format gas outside of hook

* fix(wallet-dashboard): fixes

* fix(wallet-dashboard): linter

* fix(wallet-dashboard): error to click max button

* fix(wallet-dashboard): add setFieldValue in useEffect

* fix: gas ticker

* fix: lint

* fix: improve codebase as reviewed

* fix: remove log and use isPayAllIota from form values

* use formatted amount

---------

Co-authored-by: cpl121 <[email protected]>
Co-authored-by: marc2332 <[email protected]>
Co-authored-by: cpl121 <[email protected]>
Co-authored-by: Bran <[email protected]>
  • Loading branch information
5 people authored Nov 21, 2024
1 parent aaf7f0d commit c74d85f
Show file tree
Hide file tree
Showing 30 changed files with 246 additions and 209 deletions.
20 changes: 15 additions & 5 deletions apps/core/src/components/Inputs/SendTokenFormInput.tsx
Original file line number Diff line number Diff line change
@@ -1,12 +1,14 @@
// Copyright (c) 2024 IOTA Stiftung
// SPDX-License-Identifier: Apache-2.0

import React from 'react';
import { ButtonPill, Input, InputType } from '@iota/apps-ui-kit';
import { CoinStruct } from '@iota/iota-sdk/client';
import { useGasBudgetEstimation } from '../../hooks';
import { useFormatCoin, useGasBudgetEstimation } from '../../hooks';
import { useEffect } from 'react';
import { useField, useFormikContext } from 'formik';
import { TokenForm } from '../../forms';
import { IOTA_TYPE_ARG } from '@iota/iota-sdk/utils';

export interface SendTokenInputProps {
coins: CoinStruct[];
Expand All @@ -30,14 +32,18 @@ export function SendTokenFormInput({
name,
}: SendTokenInputProps) {
const { values, setFieldValue, isSubmitting, validateField } = useFormikContext<TokenForm>();
const gasBudgetEstimation = useGasBudgetEstimation({
const { data: gasBudgetEstimation } = useGasBudgetEstimation({
coinDecimals,
coins: coins ?? [],
activeAddress,
to: to,
amount: values.amount,
isPayAllIota: values.isPayAllIota,
});
const [formattedGasBudgetEstimation, gasToken] = useFormatCoin(
gasBudgetEstimation,
IOTA_TYPE_ARG,
);

const [field, meta, helpers] = useField<string>(name);
const errorMessage = meta.error;
Expand All @@ -49,10 +55,14 @@ export function SendTokenFormInput({
</ButtonPill>
);

const gasAmount = formattedGasBudgetEstimation
? formattedGasBudgetEstimation + ' ' + gasToken
: undefined;

// gasBudgetEstimation should change when the amount above changes
useEffect(() => {
setFieldValue('gasBudgetEst', gasBudgetEstimation, false);
}, [gasBudgetEstimation, setFieldValue, values.amount]);
setFieldValue('gasBudgetEst', formattedGasBudgetEstimation, false);
}, [formattedGasBudgetEstimation, setFieldValue, values.amount]);

return (
<Input
Expand All @@ -67,7 +77,7 @@ export function SendTokenFormInput({
prefix={values.isPayAllIota ? '~ ' : undefined}
allowNegative={false}
errorMessage={errorMessage}
amountCounter={!errorMessage ? (coins ? gasBudgetEstimation : '--') : undefined}
amountCounter={!errorMessage ? (coins ? gasAmount : '--') : undefined}
trailingElement={renderAction()}
decimalScale={coinDecimals ? undefined : 0}
thousandSeparator
Expand Down
35 changes: 31 additions & 4 deletions apps/core/src/components/coin/CoinIcon.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -33,14 +33,41 @@ export interface CoinIconProps {
coinType: string;
size?: ImageIconSize;
rounded?: boolean;
hasCoinWrapper?: boolean;
}

export function CoinIcon({ coinType, size = ImageIconSize.Full, rounded }: CoinIconProps) {
export function CoinIcon({
coinType,
size = ImageIconSize.Full,
rounded,
hasCoinWrapper,
}: CoinIconProps) {
const Component = hasCoinWrapper ? CoinIconWrapper : React.Fragment;
const coinWrapperProps = hasCoinWrapper ? { hasBorder: true, size: ImageIconSize.Large } : {};

return coinType === IOTA_TYPE_ARG ? (
<div className={cx(size, 'text-neutral-10')}>
<IotaLogoMark className="h-full w-full" />
</div>
<Component {...coinWrapperProps}>
<div className={cx(size, 'text-neutral-10')}>
<IotaLogoMark className="h-full w-full" />
</div>
</Component>
) : (
<NonIotaCoin rounded={rounded} size={size} coinType={coinType} />
);
}
type CoinIconWrapperProps = React.PropsWithChildren<Pick<CoinIconProps, 'size'>> & {
hasBorder?: boolean;
};
export function CoinIconWrapper({ children, size, hasBorder }: CoinIconWrapperProps) {
return (
<div
className={cx(
size,
hasBorder && 'border border-shader-neutral-light-8',
'flex items-center justify-center rounded-full bg-neutral-100',
)}
>
{children}
</div>
);
}
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
// Copyright (c) 2024 IOTA Stiftung
// SPDX-License-Identifier: Apache-2.0

import React from 'react';
import {
Card,
CardAction,
Expand All @@ -10,9 +11,10 @@ import {
CardType,
ImageType,
} from '@iota/apps-ui-kit';
import { CoinIcon, useFormatCoin, ImageIconSize } from '@iota/core';
import { CoinIcon, ImageIconSize } from '../';
import { IOTA_TYPE_ARG } from '@iota/iota-sdk/utils';
import { type ReactNode } from 'react';
import { useFormatCoin } from '../../hooks';

interface CoinItemProps {
coinType: string;
Expand All @@ -23,7 +25,7 @@ interface CoinItemProps {
usd?: number;
}

function CoinItem({
export function CoinItem({
coinType,
balance,
onClick,
Expand All @@ -37,8 +39,13 @@ function CoinItem({
return (
<Card type={CardType.Default} onClick={onClick}>
<CardImage type={ImageType.BgTransparent}>
<div className="flex h-10 w-10 items-center justify-center rounded-full ">
<CoinIcon coinType={coinType} rounded size={ImageIconSize.Small} />
<div className="flex h-10 w-10 items-center justify-center rounded-full">
<CoinIcon
coinType={coinType}
rounded
size={ImageIconSize.Small}
hasCoinWrapper
/>
</div>
</CardImage>
<CardBody
Expand All @@ -55,5 +62,3 @@ function CoinItem({
</Card>
);
}

export default CoinItem;
25 changes: 21 additions & 4 deletions apps/core/src/components/coin/CoinSelector.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,11 @@ import { useFormatCoin } from '../../hooks';
import { CoinIcon } from './CoinIcon';
import { ImageIconSize } from '../icon';

interface CoinSelectorProps {
interface CoinSelectorBaseProps {
hasCoinWrapper?: boolean;
}

interface CoinSelectorProps extends CoinSelectorBaseProps {
activeCoinType: string;
coins: CoinBalance[];
onClick: (coinType: string) => void;
Expand All @@ -18,13 +22,14 @@ export function CoinSelector({
activeCoinType = IOTA_TYPE_ARG,
coins,
onClick,
hasCoinWrapper,
}: CoinSelectorProps) {
const activeCoin = coins?.find(({ coinType }) => coinType === activeCoinType) ?? coins?.[0];
const initialValue = activeCoin?.coinType;
const coinsOptions: SelectOption[] =
coins?.map((coin) => ({
id: coin.coinType,
renderLabel: () => <CoinSelectOption coin={coin} />,
renderLabel: () => <CoinSelectOption hasCoinWrapper={hasCoinWrapper} coin={coin} />,
})) || [];

return (
Expand All @@ -39,15 +44,27 @@ export function CoinSelector({
);
}

function CoinSelectOption({ coin: { coinType, totalBalance } }: { coin: CoinBalance }) {
interface CoinSelectOptionProps extends CoinSelectorBaseProps {
coin: CoinBalance;
}

function CoinSelectOption({
coin: { coinType, totalBalance },
hasCoinWrapper,
}: CoinSelectOptionProps) {
const [formatted, symbol, { data: coinMeta }] = useFormatCoin(totalBalance, coinType);
const isIota = coinType === IOTA_TYPE_ARG;

return (
<div className="flex w-full flex-row items-center justify-between">
<div className="flex flex-row items-center gap-x-md">
<div className="flex h-6 w-6 items-center justify-center">
<CoinIcon size={ImageIconSize.Small} coinType={coinType} rounded />
<CoinIcon
size={ImageIconSize.Small}
coinType={coinType}
rounded
hasCoinWrapper={hasCoinWrapper}
/>
</div>
<span className="text-body-lg text-neutral-10">
{isIota ? (coinMeta?.name || '').toUpperCase() : coinMeta?.name || symbol}
Expand Down
1 change: 1 addition & 0 deletions apps/core/src/components/coin/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,3 +3,4 @@

export * from './CoinIcon';
export * from './CoinSelector';
export * from './CoinItem';
8 changes: 1 addition & 7 deletions apps/core/src/hooks/useGasBudgetEstimation.ts
Original file line number Diff line number Diff line change
Expand Up @@ -6,8 +6,6 @@ import { CoinStruct } from '@iota/iota-sdk/client';
import { useQuery } from '@tanstack/react-query';
import { createTokenTransferTransaction } from '../utils';
import { IOTA_TYPE_ARG } from '@iota/iota-sdk/utils';
import { useFormatCoin } from './useFormatCoin';
import { GAS_SYMBOL } from '../constants';

interface UseGasBudgetEstimationOptions {
coinDecimals: number;
Expand All @@ -27,7 +25,7 @@ export function useGasBudgetEstimation({
isPayAllIota,
}: UseGasBudgetEstimationOptions) {
const client = useIotaClient();
const { data: gasBudget } = useQuery({
return useQuery({
// eslint-disable-next-line @tanstack/query/exhaustive-deps
queryKey: [
'transaction-gas-budget-estimate',
Expand Down Expand Up @@ -58,8 +56,4 @@ export function useGasBudgetEstimation({
return tx.getData().gasData.budget;
},
});

const [formattedGas] = useFormatCoin(gasBudget, IOTA_TYPE_ARG);

return formattedGas ? formattedGas + ' ' + GAS_SYMBOL : '--';
}
4 changes: 2 additions & 2 deletions apps/ui-kit/src/lib/components/organisms/dialog/Dialog.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -21,7 +21,7 @@ const DialogOverlay = React.forwardRef<
>(({ showCloseIcon, ...props }, ref) => (
<RadixDialog.Overlay
ref={ref}
className="absolute inset-0 z-[99998] bg-shader-neutral-light-48 backdrop-blur-md"
className="fixed inset-0 z-[99998] bg-shader-neutral-light-48 backdrop-blur-md"
{...props}
>
<DialogClose className={cx('fixed right-3 top-3', { hidden: !showCloseIcon })}>
Expand Down Expand Up @@ -70,7 +70,7 @@ const DialogContent = React.forwardRef<
<RadixDialog.Content
ref={ref}
className={cx(
'absolute z-[99999] flex flex-col justify-center overflow-hidden bg-primary-100 dark:bg-neutral-6 md:w-96',
'fixed z-[99999] flex flex-col justify-center overflow-hidden bg-primary-100 dark:bg-neutral-6 md:w-96',
positionClass,
)}
{...props}
Expand Down
8 changes: 8 additions & 0 deletions apps/wallet-dashboard/app/globals.css
Original file line number Diff line number Diff line change
Expand Up @@ -85,4 +85,12 @@ body {
.grid-template-visual-assets {
grid-template-columns: repeat(auto-fill, minmax(150px, 1fr));
}

a {
@apply text-primary-30 dark:text-primary-80;
@apply transition-colors;
&:hover {
@apply text-opacity-80;
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -36,18 +36,14 @@ function SendTokenDialogBody({

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

const {
mutateAsync: signAndExecuteTransaction,
error,
isPending,
} = useSignAndExecuteTransaction();
const { mutateAsync: signAndExecuteTransaction, isPending } = useSignAndExecuteTransaction();

const { data: transaction } = useSendCoinTransaction(
coinsData || [],
selectedCoin?.coinType,
activeAddress,
formData.to,
formData.amount,
formData.formattedAmount,
selectedCoin?.totalBalance === formData.amount,
);

Expand All @@ -63,12 +59,15 @@ function SendTokenDialogBody({
setOpen(false);
addNotification('Transfer transaction has been sent');
})
.catch(() => {
addNotification('Transfer transaction was not sent', NotificationType.Error);
});
.catch(handleTransactionError);
}
}

function handleTransactionError() {
setOpen(false);
addNotification('There was an error with the transaction', NotificationType.Error);
}

function onNext(): void {
setStep(FormStep.ReviewValues);
}
Expand All @@ -82,6 +81,7 @@ function SendTokenDialogBody({
<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>
Expand All @@ -92,16 +92,16 @@ function SendTokenDialogBody({
setSelectedCoin={setSelectedCoin}
onNext={onNext}
setFormData={setFormData}
initialFormValues={formData}
/>
)}
{step === FormStep.ReviewValues && (
<ReviewValuesFormView
formData={formData}
onBack={onBack}
executeTransfer={handleTransfer}
senderAddress={activeAddress}
error={error?.message}
isPending={isPending}
coinType={selectedCoin.coinType}
/>
)}
</DialogBody>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@ import { FormDataValues } from '../interfaces';
export const INITIAL_VALUES: FormDataValues = {
to: '',
amount: '',
formattedAmount: '',
isPayAllIota: false,
gasBudgetEst: '',
};
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@
// SPDX-License-Identifier: Apache-2.0
export interface FormDataValues {
amount: string;
formattedAmount: string;
to: string;
isPayAllIota: boolean;
gasBudgetEst: string;
Expand Down
Loading

0 comments on commit c74d85f

Please sign in to comment.