Skip to content

Commit

Permalink
TW-1385: Gas token send / receive (#1189)
Browse files Browse the repository at this point in the history
* basic layout

* select token for tezos

* select token
modal logic

* dont show tags

* fix select asset modal opening after page reload

* fix e2e ts

* form refactor

* resolving evm address from domain

* max amount calculation

* fix logic for erc20 tokens

* recipient account select main logic

* address copying fixed

* recipient input / select acc state connection + truncation added

* tezos form validation fixes

* evm form validation + react-hook-form v7 used

* add active state for filter network option

* scroll to selected network

* add filter network search

* loader added

* always show converted amount

* show floating assetSymbol in input

* track other networks addresses

* show tezos error toast on form submit

* maxAmount calculation fixes

* fix pipeline

* confirm modal base + segmented control

* confirmation modal layout finished

* Evm / tezos component separation Header, DetailsTab

* fix after-merge conflicts

* fix network icon

* more after-merge fixes

* some more fixes

* some more fixes

* infoIcon

* iconBase memo

* viem update

* send evm transaction

* send evm transaction / add networks support

* fix various ui bugs

* fix fee options calculations

* fix some more bugs

* fix import cycle

* custom transaction params inputs + error handling

* fix ts-prune

* major refactoring and bug fixes

* tezos fee options calculation + ui fixes

* send tezos operations without old confirmation page

* added loading button + proper form reset

* edit gas fee and storageLimit

* show default evm form values

* some ui fixes

* non zero validation

* error tab

* fix ts-prune

* fix fee calculation with custom gas limit

* refactor

* refactor + minor ui fixes

* show default gas fee and storage limit + non zero gas fee validation

* storage limit handling

* fix ts-prune

* fix some after-merge issues

* show tezos raw transaction

* renaming

* raw transaction json view

* tezos submit errors handling

* fix some after-merge issues

* fix some after-merge issues

* fix ts-prune

* refactor

* more refactor

* apply suggestion

* fix audit

* minor bug fixes

* after merge fixes

* fix minor bugs

* some more after-merge fixes

* some more after-merge fixes

* fix info icon size

* fix fiat toggle

* Fix pipeline

---------

Co-authored-by: Alex <alex.seleznov@gmail.com>
  • Loading branch information
lendihop and alex-tsx authored Nov 4, 2024
1 parent 50a474d commit 914d1d2
Show file tree
Hide file tree
Showing 132 changed files with 4,961 additions and 2,098 deletions.
5 changes: 1 addition & 4 deletions e2e/src/page-objects/pages/send.page.ts
Original file line number Diff line number Diff line change
@@ -1,19 +1,16 @@
import { SendFormSelectors } from 'src/app/templates/SendForm/selectors';
import { SendFormSelectors } from 'src/app/pages/Send/form/selectors';

import { Page } from '../../classes/page.class';
import { createPageElement } from '../../utils/search.utils';

export class SendPage extends Page {
assetDropDown = createPageElement(SendFormSelectors.assetDropDown);
assetDropDownSearchInput = createPageElement(SendFormSelectors.assetDropDownSearchInput);
amountInput = createPageElement(SendFormSelectors.amountInput);
recipientInput = createPageElement(SendFormSelectors.recipientInput);
sendButton = createPageElement(SendFormSelectors.sendButton);
contactItemButton = createPageElement(SendFormSelectors.contactItemButton);
contactHashValue = createPageElement(SendFormSelectors.contactHashValue);

async isVisible() {
await this.assetDropDown.waitForDisplayed();
await this.recipientInput.waitForDisplayed();
await this.amountInput.waitForDisplayed();
}
Expand Down
2 changes: 1 addition & 1 deletion package.json
Original file line number Diff line number Diff line change
Expand Up @@ -204,7 +204,7 @@
"use-force-update": "1.0.7",
"use-onclickoutside": "0.4.1",
"util": "0.11.1",
"viem": "^2.15.1",
"viem": "^2.21.36",
"wasm-themis": "0.14.6",
"webextension-polyfill": "^0.10.0",
"webpack": "^5",
Expand Down
25 changes: 23 additions & 2 deletions public/_locales/en/messages.json
Original file line number Diff line number Diff line change
Expand Up @@ -353,6 +353,9 @@
"addAsset": {
"message": "Add Asset"
},
"available": {
"message": "Available"
},
"contractNotAvailable": {
"message": "The contract at this address is not available. Does it exist on this network?"
},
Expand Down Expand Up @@ -738,7 +741,7 @@
}
},
"maximalAmount": {
"message": "Maximal: $amount$",
"message": "Max: $amount$",
"placeholders": {
"amount": {
"content": "$1"
Expand Down Expand Up @@ -1103,6 +1106,9 @@
"asset": {
"message": "Asset"
},
"token": {
"message": "Token"
},
"selectAnotherAssetPrompt": {
"message": "Click on area to select another asset or token."
},
Expand Down Expand Up @@ -2498,7 +2504,7 @@
}
},
"gasFee": {
"message": "Gas fee"
"message": "Gas Fee"
},
"storageFee": {
"message": "Storage fee"
Expand Down Expand Up @@ -3598,6 +3604,21 @@
"allNetworks": {
"message": "All Networks"
},
"gasPrice": {
"message": "Gas Price"
},
"gasLimit": {
"message": "Gas Limit"
},
"nonce": {
"message": "Nonce"
},
"gasLimitInfoContent": {
"message": "Gas limit is the maximum units of gas you are willing to use. Units of gas are a multiplier to “Max priority fee” and “Max fee”."
},
"nonceInfoContent": {
"message": "The nonce is the number of transactions sent from a given address. Each time you send a transaction, the nonce value increases by 1."
},
"enLangName": {
"message": "English"
},
Expand Down
2 changes: 1 addition & 1 deletion public/_locales/en_GB/messages.json
Original file line number Diff line number Diff line change
Expand Up @@ -508,7 +508,7 @@
}
},
"maximalAmount": {
"message": "Maximal: $amount$",
"message": "Max: $amount$",
"placeholders": {
"amount": {
"content": "$1"
Expand Down
6 changes: 4 additions & 2 deletions src/app/PageRouter.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -78,8 +78,10 @@ const ROUTE_MAP = Woozie.createMap<RouteContext>([
['/connect-ledger', onlyReady(onlyInFullPage(() => <ConnectLedger />))],
['/receive', onlyReady(() => <Receive />)],
[
'/send/:chainKind?/:tezosChainId?/:assetSlug?',
onlyReady(({ tezosChainId, assetSlug }) => <Send tezosChainId={tezosChainId} assetSlug={assetSlug} />)
'/send/:chainKind?/:chainId?/:assetSlug?',
onlyReady(({ chainKind, chainId, assetSlug }) => (
<Send chainKind={chainKind} chainId={chainId} assetSlug={assetSlug} />
))
],
['/swap', onlyReady(() => <Swap />)],
['/delegate/:tezosChainId', onlyReady(({ tezosChainId }) => <Delegate tezosChainId={tezosChainId!} />)],
Expand Down
2 changes: 1 addition & 1 deletion src/app/atoms/AccLabel.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -30,7 +30,7 @@ export const AccLabel = memo<Props>(({ type }) => {
}, [type]);

return (
<div className="flex items-center gap-x-px py-1 pl-1.5 pr-2 bg-grey-4 rounded-md">
<div className="flex items-center gap-x-px py-1 pl-1.5 pr-2 bg-grey-4 rounded-md self-end">
<IconBase Icon={Icon} size={12} className="text-grey-2" />

<span className="text-font-num-bold-10 text-grey-1 uppercase">{title}</span>
Expand Down
16 changes: 10 additions & 6 deletions src/app/atoms/AssetField.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@ interface AssetFieldProps extends Omit<ComponentProps<typeof FormField>, 'onChan
max?: number;
assetSymbol?: ReactNode;
assetDecimals?: number;
onlyInteger?: boolean;
onChange?: (v?: string) => void;
}

Expand All @@ -22,14 +23,18 @@ const AssetField = forwardRef<HTMLInputElement, AssetFieldProps>(
max = Number.MAX_SAFE_INTEGER,
assetSymbol,
assetDecimals = 6,
onlyInteger = false,
onChange,
onFocus,
onBlur,
...rest
},
ref
) => {
const valueStr = useMemo(() => (value === undefined ? '' : new BigNumber(value).toFixed()), [value]);
const valueStr = useMemo(
() => (value === undefined || value === '' ? '' : new BigNumber(value).toFixed()),
[value]
);

const [localValue, setLocalValue] = useState(valueStr);

Expand All @@ -44,21 +49,20 @@ const AssetField = forwardRef<HTMLInputElement, AssetFieldProps>(
const handleChange = useCallback(
(evt: React.ChangeEvent<HTMLInputElement> & React.ChangeEvent<HTMLTextAreaElement>) => {
let val = evt.target.value.replace(/ /g, '').replace(/,/g, '.');
let numVal = new BigNumber(val || 0);
const indexOfDot = val.indexOf('.');
if (indexOfDot !== -1 && onlyInteger) return;
let numVal = new BigNumber(val || 0);
if (indexOfDot !== -1 && val.length - indexOfDot > assetDecimals + 1) {
val = val.substring(0, indexOfDot + assetDecimals + 1);
numVal = new BigNumber(val);
}

if (!numVal.isNaN() && numVal.isGreaterThanOrEqualTo(min) && numVal.isLessThanOrEqualTo(max)) {
setLocalValue(val);
if (onChange) {
onChange(val !== '' ? numVal.toFixed() : undefined);
}
onChange?.(val !== '' ? numVal.toFixed() : undefined);
}
},
[assetDecimals, setLocalValue, min, max, onChange]
[onlyInteger, assetDecimals, min, max, onChange]
);

return (
Expand Down
41 changes: 32 additions & 9 deletions src/app/atoms/AssetsSegmentControl.tsx
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
import React, { createContext, memo, RefObject, useContext } from 'react';
import React, { createContext, memo, RefObject, useCallback, useContext, useEffect, useRef, useState } from 'react';

import { SimpleSegmentControl } from './SimpleSegmentControl';
import SegmentedControl from './SegmentedControl';

interface AssetsSegmentControlProps {
tabSlug: string | null;
Expand All @@ -19,15 +19,38 @@ export const AssetsSegmentControl = memo<AssetsSegmentControlProps>(
({ tabSlug, onTokensTabClick, onCollectiblesTabClick, className }) => {
const ref = useAssetsSegmentControlRef();

const [tab, setTab] = useState(tabSlug ?? 'tokens');

useEffect(() => void setTab(tabSlug ?? 'tokens'), [tabSlug]);

const setActiveSegment = useCallback(
(val: string) => {
if (val === 'tokens') onTokensTabClick();
else onCollectiblesTabClick();
setTab(val);
},
[onTokensTabClick, onCollectiblesTabClick]
);

return (
<SimpleSegmentControl
ref={ref}
firstTitle="Tokens"
secondTitle="Collectibles"
activeSecond={tabSlug === 'collectibles'}
<SegmentedControl
name="assets-segment-control"
activeSegment={tab}
setActiveSegment={setActiveSegment}
controlRef={ref}
className={className}
onFirstClick={onTokensTabClick}
onSecondClick={onCollectiblesTabClick}
segments={[
{
label: 'Tokens',
value: 'tokens',
ref: useRef<HTMLDivElement>(null)
},
{
label: 'Collectibles',
value: 'collectibles',
ref: useRef<HTMLDivElement>(null)
}
]}
/>
);
}
Expand Down
5 changes: 3 additions & 2 deletions src/app/atoms/CaptionAlert.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@ interface Props {
type: CaptionAlertType;
message: string;
className?: string;
textClassName?: string;
}

const TYPE_CLASSES: Record<CaptionAlertType, string> = {
Expand All @@ -23,7 +24,7 @@ const TYPE_CLASSES: Record<CaptionAlertType, string> = {
};

/** Refer to `./Alert` for existing functionality */
export const CaptionAlert = memo<Props>(({ type, message, className }) => {
export const CaptionAlert = memo<Props>(({ type, message, className, textClassName }) => {
const Icon = (() => {
switch (type) {
case 'success':
Expand All @@ -41,7 +42,7 @@ export const CaptionAlert = memo<Props>(({ type, message, className }) => {
<div className={clsx('flex items-start p-3 gap-x-1 rounded-md', TYPE_CLASSES[type], className)}>
<Icon className="shrink-0 w-6 h-6" />

<p className="flex-1 text-font-description">{message}</p>
<p className={clsx('flex-1 text-font-description', textClassName)}>{message}</p>
</div>
);
});
2 changes: 1 addition & 1 deletion src/app/atoms/CleanButton.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -34,7 +34,7 @@ const CleanButton = memo<Props>(({ className, size = 12, showText, onClick }) =>
return (
<button
id={CLEAN_BUTTON_ID}
ref={buttonRef}
ref={showText ? undefined : buttonRef}
type="button"
className={clsx(className, 'flex items-center ease-in-out duration-200', showText && 'px-1 py-0.5')}
tabIndex={-1}
Expand Down
41 changes: 24 additions & 17 deletions src/app/atoms/ConvertedInputAssetAmount.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -3,40 +3,47 @@ import React, { memo } from 'react';
import BigNumber from 'bignumber.js';

import InFiat from 'app/templates/InFiat';
import { T } from 'lib/i18n';
import { AssetMetadataBase, getAssetSymbol } from 'lib/metadata';

import Money from './Money';

interface Props {
tezosChainId: string;
chainId: string | number;
assetSlug: string;
assetMetadata?: AssetMetadataBase;
assetSymbol?: string;
amountValue: string;
toFiat: boolean;
evm?: boolean;
}

export const ConvertedInputAssetAmount = memo<Props>(
({ tezosChainId, assetSlug, assetMetadata, amountValue, toFiat }) => {
({ chainId, assetSlug, assetSymbol, amountValue, toFiat, evm }) => {
if (toFiat)
return (
<InFiat chainId={tezosChainId} assetSlug={assetSlug} volume={amountValue} roundingMode={BigNumber.ROUND_FLOOR}>
<InFiat
chainId={chainId}
assetSlug={assetSlug}
volume={amountValue}
smallFractionFont={false}
roundingMode={BigNumber.ROUND_FLOOR}
evm={evm}
>
{({ balance, symbol }) => (
<div className="-mb-1 flex">
<span className="mr-1"></span>
<span className="font-normal text-gray-700 mr-1 flex items-baseline">
{balance}
<span className="pr-px">{symbol}</span>
</span>{' '}
<T id="inFiat" />
<div className="flex items-baseline text-font-num-12 text-grey-1">
<span></span>
<span className="mx-1">{balance}</span>
<span>{symbol}</span>
</div>
)}
</InFiat>
);

return (
<div className="-mb-3 flex">
<span className="mr-1"></span>
<span className="font-normal text-gray-700 mr-1">{amountValue}</span>{' '}
<T id="inAsset" substitutions={getAssetSymbol(assetMetadata, true)} />
<div className="flex items-baseline text-font-num-12 text-grey-1">
<span className="mr-0.5"></span>
<Money smallFractionFont={false} tooltipPlacement="bottom">
{amountValue}
</Money>
<span className="ml-1 truncate">{assetSymbol}</span>
</div>
);
}
Expand Down
25 changes: 17 additions & 8 deletions src/app/atoms/CopyButton.tsx
Original file line number Diff line number Diff line change
@@ -1,12 +1,14 @@
import React, { FC, HTMLAttributes, useCallback, useMemo } from 'react';
import React, { FC, HTMLAttributes, MouseEventHandler, useCallback, useMemo } from 'react';

import clsx from 'clsx';

import { toastSuccess } from 'app/toaster';
import { AnalyticsEventCategory, TestIDProps, setTestID, useAnalytics } from 'lib/analytics';
import { t } from 'lib/i18n';
import useCopyToClipboard from 'lib/ui/useCopyToClipboard';
import useTippy from 'lib/ui/useTippy';

interface CopyButtonProps extends HTMLAttributes<HTMLButtonElement>, TestIDProps {
export interface CopyButtonProps extends HTMLAttributes<HTMLButtonElement>, TestIDProps {
text: string;
isSecret?: boolean;
shouldShowTooltip?: boolean;
Expand All @@ -19,6 +21,8 @@ export const CopyButton: FC<CopyButtonProps> = ({
shouldShowTooltip,
children,
isSecret,
className,
onClick,
...rest
}) => {
const { trackEvent } = useAnalytics();
Expand All @@ -37,20 +41,25 @@ export const CopyButton: FC<CopyButtonProps> = ({

const buttonRef = useTippy<HTMLButtonElement>(tippyProps);

const handleCopyPress = useCallback(() => {
testID && trackEvent(testID, AnalyticsEventCategory.ButtonPress, testIDProperties);
const handleCopyPress = useCallback<MouseEventHandler<HTMLButtonElement>>(
e => {
testID && trackEvent(testID, AnalyticsEventCategory.ButtonPress, testIDProperties);

copy();
toastSuccess(t('copiedHash'));
}, [copy, testID, testIDProperties, trackEvent]);
copy();
toastSuccess(t('copiedHash'));
onClick?.(e);
},
[copy, testID, testIDProperties, trackEvent]
);

return (
<>
<button
ref={shouldShowTooltip ? buttonRef : undefined}
type="button"
{...rest}
className={clsx('w-fit', className)}
onClick={handleCopyPress}
{...rest}
{...setTestID(testID)}
>
{children}
Expand Down
2 changes: 1 addition & 1 deletion src/app/atoms/CustomModal.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@ import React, { FC } from 'react';
import classNames from 'clsx';
import Modal from 'react-modal';

export type CustomModalProps = Modal.Props & React.PropsWithChildren;
type CustomModalProps = Modal.Props & React.PropsWithChildren;

const CustomModal: FC<CustomModalProps> = props => {
const { className, overlayClassName, ...restProps } = props;
Expand Down
Loading

0 comments on commit 914d1d2

Please sign in to comment.