Compare commits
6
Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
21214808f1 | ||
|
|
3583f22067 | ||
|
|
2ac5471710 | ||
|
|
98ce1d0a25 | ||
|
|
053775bef6 | ||
|
|
0660eda334 |
@@ -0,0 +1,112 @@
|
||||
import type { ApolloError } from '@apollo/client';
|
||||
import {
|
||||
PARTY_NOT_FOUND,
|
||||
filterAcceptableGraphqlErrors,
|
||||
isPartyNotFoundError,
|
||||
} from './party';
|
||||
import type { GraphQLError } from 'graphql';
|
||||
|
||||
/**
|
||||
*
|
||||
* @param message
|
||||
* @returns GraphQLError
|
||||
*/
|
||||
function createMockApolloErrors(message: string): GraphQLError {
|
||||
return {
|
||||
message,
|
||||
extensions: {
|
||||
code: message.toUpperCase().replace(/ /g, '_'),
|
||||
},
|
||||
locations: [],
|
||||
originalError: new Error(message),
|
||||
path: [],
|
||||
nodes: [],
|
||||
positions: [1],
|
||||
name: message,
|
||||
source: {
|
||||
body: message,
|
||||
name: message,
|
||||
locationOffset: {
|
||||
line: 1,
|
||||
column: 1,
|
||||
},
|
||||
},
|
||||
};
|
||||
}
|
||||
|
||||
describe('filterAcceptableGraphqlErrors', () => {
|
||||
it('should return undefined if the error is a party not found error', () => {
|
||||
const error: Partial<ApolloError> = {
|
||||
graphQLErrors: [createMockApolloErrors('failed to get party for ID')],
|
||||
};
|
||||
|
||||
const result = filterAcceptableGraphqlErrors(error as ApolloError);
|
||||
|
||||
expect(result).toBeUndefined();
|
||||
});
|
||||
|
||||
it('should return the error if it is not a party not found error', () => {
|
||||
const error: Partial<ApolloError> = {
|
||||
graphQLErrors: [createMockApolloErrors('Some other error')],
|
||||
};
|
||||
|
||||
const result = filterAcceptableGraphqlErrors(error as ApolloError);
|
||||
|
||||
expect(result).toEqual(error);
|
||||
});
|
||||
|
||||
it('should return the error if there are multiple errors', () => {
|
||||
const error: Partial<ApolloError> = {
|
||||
graphQLErrors: [
|
||||
createMockApolloErrors('failed to get party for ID'),
|
||||
createMockApolloErrors('Some other error'),
|
||||
],
|
||||
};
|
||||
|
||||
const result = filterAcceptableGraphqlErrors(error as ApolloError);
|
||||
|
||||
expect(result).toEqual(error);
|
||||
});
|
||||
|
||||
it('should return the error if there are no errors', () => {
|
||||
const error: Partial<ApolloError> = {
|
||||
graphQLErrors: [],
|
||||
};
|
||||
|
||||
const result = filterAcceptableGraphqlErrors(error as ApolloError);
|
||||
|
||||
expect(result).toEqual(error);
|
||||
});
|
||||
|
||||
it('should return undefined if the error is undefined', () => {
|
||||
const result = filterAcceptableGraphqlErrors(undefined);
|
||||
|
||||
expect(result).toBeUndefined();
|
||||
});
|
||||
});
|
||||
|
||||
describe('isPartyNotFoundError', () => {
|
||||
it('should return true if the error message includes PARTY_NOT_FOUND', () => {
|
||||
const error = { message: 'failed to get party for ID' };
|
||||
|
||||
const result = isPartyNotFoundError(error);
|
||||
|
||||
expect(result).toBe(true);
|
||||
});
|
||||
|
||||
it('should return false if the error message does not include PARTY_NOT_FOUND', () => {
|
||||
const error = { message: 'Some other error' };
|
||||
|
||||
const result = isPartyNotFoundError(error);
|
||||
|
||||
expect(result).toBe(false);
|
||||
});
|
||||
|
||||
// Will trip if the error message changes, which should not be a problem, but there
|
||||
// might be logic that depends on it
|
||||
it('expects party not found error to remain consistent', () => {
|
||||
const error = 'failed to get party for ID';
|
||||
|
||||
expect(PARTY_NOT_FOUND).toStrictEqual(error);
|
||||
});
|
||||
});
|
||||
@@ -1,3 +1,5 @@
|
||||
import type { ApolloError } from '@apollo/client';
|
||||
|
||||
export const PARTY_NOT_FOUND = 'failed to get party for ID';
|
||||
|
||||
export const isPartyNotFoundError = (error: { message: string }) => {
|
||||
@@ -6,3 +8,23 @@ export const isPartyNotFoundError = (error: { message: string }) => {
|
||||
}
|
||||
return false;
|
||||
};
|
||||
|
||||
/**
|
||||
* If a party has no accounts or data, then this GraphQL query believes it does not exist
|
||||
* Not having any rewards is a valid state, so in some cases we can filter this error out.
|
||||
*
|
||||
* @param error ApolloError | undefined
|
||||
* @returns ApolloError | undefined
|
||||
*/
|
||||
export function filterAcceptableGraphqlErrors(
|
||||
error?: ApolloError
|
||||
): ApolloError | undefined {
|
||||
// Currently the only error we expect is when a party has no accounts
|
||||
if (error && error.graphQLErrors.length === 1) {
|
||||
if (isPartyNotFoundError(error.graphQLErrors[0])) {
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
return error;
|
||||
}
|
||||
|
||||
@@ -18,6 +18,7 @@ import { ProposalMinRequirements, ProposalUserAction } from '../shared';
|
||||
import { VoteTransactionDialog } from './vote-transaction-dialog';
|
||||
import { useVoteButtonsQuery } from './__generated__/Stake';
|
||||
import type { DialogProps, VegaTxState } from '@vegaprotocol/proposals';
|
||||
import { filterAcceptableGraphqlErrors } from '../../../../lib/party';
|
||||
|
||||
interface VoteButtonsContainerProps {
|
||||
voteState: VoteState | null;
|
||||
@@ -42,8 +43,10 @@ export const VoteButtonsContainer = (props: VoteButtonsContainerProps) => {
|
||||
skip: !pubKey,
|
||||
});
|
||||
|
||||
const filteredErrors = filterAcceptableGraphqlErrors(error);
|
||||
|
||||
return (
|
||||
<AsyncRenderer loading={loading} error={error} data={data}>
|
||||
<AsyncRenderer loading={loading} error={filteredErrors} data={data}>
|
||||
<VoteButtons
|
||||
{...props}
|
||||
currentStakeAvailable={toBigNum(
|
||||
|
||||
+23
-13
@@ -10,6 +10,7 @@ import { EpochIndividualRewardsTable } from './epoch-individual-rewards-table';
|
||||
import { generateEpochIndividualRewardsList } from './generate-epoch-individual-rewards-list';
|
||||
import { calculateEpochOffset } from '../../../lib/epoch-pagination';
|
||||
import { useNetworkParam } from '@vegaprotocol/network-parameters';
|
||||
import { filterAcceptableGraphqlErrors } from '../../../lib/party';
|
||||
|
||||
const EPOCHS_PAGE_SIZE = 10;
|
||||
|
||||
@@ -99,17 +100,24 @@ export const EpochIndividualRewards = ({
|
||||
prevEpochIdRef.current = epochId;
|
||||
}, [epochId, refetchData]);
|
||||
|
||||
// Workarounds for the error handling of AsyncRenderer
|
||||
const filteredErrors = filterAcceptableGraphqlErrors(error);
|
||||
const filteredData = data || [];
|
||||
|
||||
return (
|
||||
<AsyncRenderer
|
||||
loading={loading}
|
||||
error={error}
|
||||
data={data}
|
||||
error={filteredErrors}
|
||||
data={filteredData}
|
||||
render={() => (
|
||||
<div>
|
||||
<p data-testid="connected-vega-key" className="mb-10">
|
||||
{t('Connected Vega key')}:{' '}
|
||||
<span className="text-white">{pubKey}</span>
|
||||
</p>
|
||||
{epochIndividualRewardSummaries.length === 0 && (
|
||||
<p>{t('No rewards for key')}</p>
|
||||
)}
|
||||
{epochIndividualRewardSummaries.map(
|
||||
(epochIndividualRewardSummary) => (
|
||||
<EpochIndividualRewardsTable
|
||||
@@ -118,17 +126,19 @@ export const EpochIndividualRewards = ({
|
||||
/>
|
||||
)
|
||||
)}
|
||||
<Pagination
|
||||
isLoading={loading}
|
||||
hasPrevPage={page > 1}
|
||||
hasNextPage={page < totalPages}
|
||||
onBack={() => refetchData(page - 1)}
|
||||
onNext={() => refetchData(page + 1)}
|
||||
onFirst={() => refetchData(1)}
|
||||
onLast={() => refetchData(totalPages)}
|
||||
>
|
||||
{t('Page')} {page}
|
||||
</Pagination>
|
||||
{epochIndividualRewardSummaries.length > 0 && (
|
||||
<Pagination
|
||||
isLoading={loading}
|
||||
hasPrevPage={page > 1}
|
||||
hasNextPage={page < totalPages}
|
||||
onBack={() => refetchData(page - 1)}
|
||||
onNext={() => refetchData(page + 1)}
|
||||
onFirst={() => refetchData(1)}
|
||||
onLast={() => refetchData(totalPages)}
|
||||
>
|
||||
{t('Page')} {page}
|
||||
</Pagination>
|
||||
)}
|
||||
</div>
|
||||
)}
|
||||
/>
|
||||
|
||||
@@ -29,5 +29,5 @@ NX_REFERRALS=true
|
||||
NX_TENDERMINT_URL=https://be.vega.community
|
||||
NX_TENDERMINT_WEBSOCKET_URL=wss://be.vega.community/websocket
|
||||
|
||||
NX_CHARTING_LIBRARY_PATH=https://assets.vega.community/trading-view-bundle/v0.0.1/
|
||||
NX_CHARTING_LIBRARY_HASH=PDjWaqPFndDp+LCvqbKvntWriaqNzNpZ5i9R/BULzCg=
|
||||
# NX_CHARTING_LIBRARY_PATH=https://assets.vega.community/trading-view-bundle/v0.0.1/
|
||||
# NX_CHARTING_LIBRARY_HASH=PDjWaqPFndDp+LCvqbKvntWriaqNzNpZ5i9R/BULzCg=
|
||||
|
||||
@@ -25,12 +25,12 @@ export const ChartContainer = ({ marketId }: { marketId: string }) => {
|
||||
overlays,
|
||||
studies,
|
||||
studySizes,
|
||||
tradingViewStudies,
|
||||
setInterval,
|
||||
setStudies,
|
||||
setStudySizes,
|
||||
setOverlays,
|
||||
setTradingViewStudies,
|
||||
state,
|
||||
setState,
|
||||
} = useChartSettings();
|
||||
|
||||
const pennantChart = (
|
||||
@@ -64,13 +64,13 @@ export const ChartContainer = ({ marketId }: { marketId: string }) => {
|
||||
libraryHash={CHARTING_LIBRARY_HASH}
|
||||
marketId={marketId}
|
||||
interval={toTradingViewResolution(interval)}
|
||||
studies={tradingViewStudies}
|
||||
onIntervalChange={(newInterval) => {
|
||||
setInterval(fromTradingViewResolution(newInterval));
|
||||
}}
|
||||
onAutoSaveNeeded={(data: { studies: string[] }) => {
|
||||
setTradingViewStudies(data.studies);
|
||||
onAutoSaveNeeded={(data) => {
|
||||
setState(data);
|
||||
}}
|
||||
state={state}
|
||||
/>
|
||||
);
|
||||
}
|
||||
|
||||
@@ -27,11 +27,11 @@ describe('ChartMenu', () => {
|
||||
|
||||
render(<ChartMenu />);
|
||||
|
||||
await userEvent.click(screen.getByRole('button', { name: 'Vega chart' }));
|
||||
expect(useChartSettingsStore.getState().chartlib).toEqual('pennant');
|
||||
|
||||
await userEvent.click(screen.getByRole('button', { name: 'TradingView' }));
|
||||
await userEvent.click(screen.getByTestId('chartlib-toggle-button'));
|
||||
expect(useChartSettingsStore.getState().chartlib).toEqual('tradingview');
|
||||
|
||||
await userEvent.click(screen.getByTestId('chartlib-toggle-button'));
|
||||
expect(useChartSettingsStore.getState().chartlib).toEqual('pennant');
|
||||
});
|
||||
|
||||
describe('tradingview', () => {
|
||||
|
||||
@@ -68,6 +68,7 @@ export const ChartMenu = () => {
|
||||
setChartlib(isPennant ? 'tradingview' : 'pennant');
|
||||
}}
|
||||
size="extra-small"
|
||||
testId="chartlib-toggle-button"
|
||||
>
|
||||
{isPennant ? 'TradingView' : t('Vega chart')}
|
||||
</TradingButton>
|
||||
|
||||
@@ -9,6 +9,7 @@ type StudySizes = { [S in Study]?: number };
|
||||
export type Chartlib = 'pennant' | 'tradingview';
|
||||
|
||||
interface StoredSettings {
|
||||
state: object | undefined; // Don't see a better type provided from TradingView type definitions
|
||||
chartlib: Chartlib;
|
||||
// For interval we use the enum from @vegaprotocol/types, this is to make mapping between different
|
||||
// chart types easier and more consistent
|
||||
@@ -17,7 +18,6 @@ interface StoredSettings {
|
||||
overlays: Overlay[];
|
||||
studies: Study[];
|
||||
studySizes: StudySizes;
|
||||
tradingViewStudies: string[];
|
||||
}
|
||||
|
||||
export const STUDY_SIZE = 90;
|
||||
@@ -30,13 +30,13 @@ const STUDY_ORDER: Study[] = [
|
||||
];
|
||||
|
||||
export const DEFAULT_CHART_SETTINGS = {
|
||||
chartlib: 'tradingview' as const,
|
||||
state: undefined,
|
||||
chartlib: 'pennant' as const,
|
||||
interval: Interval.INTERVAL_I15M,
|
||||
type: ChartType.CANDLE,
|
||||
overlays: [Overlay.MOVING_AVERAGE],
|
||||
studies: [Study.MACD, Study.VOLUME],
|
||||
studySizes: {},
|
||||
tradingViewStudies: ['Volume'],
|
||||
};
|
||||
|
||||
export const useChartSettingsStore = create<
|
||||
@@ -47,7 +47,7 @@ export const useChartSettingsStore = create<
|
||||
setStudies: (studies?: Study[]) => void;
|
||||
setStudySizes: (sizes: number[]) => void;
|
||||
setChartlib: (lib: Chartlib) => void;
|
||||
setTradingViewStudies: (studies: string[]) => void;
|
||||
setState: (state: object) => void;
|
||||
}
|
||||
>()(
|
||||
persist(
|
||||
@@ -95,10 +95,8 @@ export const useChartSettingsStore = create<
|
||||
state.chartlib = lib;
|
||||
});
|
||||
},
|
||||
setTradingViewStudies: (studies: string[]) => {
|
||||
set((state) => {
|
||||
state.tradingViewStudies = studies;
|
||||
});
|
||||
setState: (state) => {
|
||||
set({ state });
|
||||
},
|
||||
})),
|
||||
{
|
||||
@@ -147,13 +145,13 @@ export const useChartSettings = () => {
|
||||
overlays,
|
||||
studies,
|
||||
studySizes,
|
||||
tradingViewStudies: settings.tradingViewStudies,
|
||||
setInterval: settings.setInterval,
|
||||
setType: settings.setType,
|
||||
setStudies: settings.setStudies,
|
||||
setOverlays: settings.setOverlays,
|
||||
setStudySizes: settings.setStudySizes,
|
||||
setChartlib: settings.setChartlib,
|
||||
setTradingViewStudies: settings.setTradingViewStudies,
|
||||
state: settings.state,
|
||||
setState: settings.setState,
|
||||
};
|
||||
};
|
||||
|
||||
@@ -309,49 +309,29 @@ export const ActiveRewardCard = ({
|
||||
].includes(m.state)
|
||||
);
|
||||
|
||||
const assetInSettledMarket =
|
||||
allMarkets &&
|
||||
Object.values(allMarkets).some((m: MarketFieldsFragment | null) => {
|
||||
if (m && getAsset(m).id === dispatchStrategy.dispatchMetricAssetId) {
|
||||
return (
|
||||
m?.state &&
|
||||
[
|
||||
MarketState.STATE_TRADING_TERMINATED,
|
||||
MarketState.STATE_SETTLED,
|
||||
MarketState.STATE_CANCELLED,
|
||||
MarketState.STATE_CLOSED,
|
||||
].includes(m.state)
|
||||
);
|
||||
}
|
||||
return false;
|
||||
});
|
||||
|
||||
if (marketSettled) {
|
||||
return null;
|
||||
}
|
||||
|
||||
// Gray out the cards that are related to suspended markets
|
||||
const suspended = transferNode.markets?.some(
|
||||
const assetInActiveMarket =
|
||||
allMarkets &&
|
||||
Object.values(allMarkets).some((m: MarketFieldsFragment | null) => {
|
||||
if (m && getAsset(m).id === dispatchStrategy.dispatchMetricAssetId) {
|
||||
return m?.state && MarketState.STATE_ACTIVE === m.state;
|
||||
}
|
||||
return false;
|
||||
});
|
||||
|
||||
const marketSuspended = transferNode.markets?.some(
|
||||
(m) =>
|
||||
m?.state === MarketState.STATE_SUSPENDED ||
|
||||
m?.state === MarketState.STATE_SUSPENDED_VIA_GOVERNANCE
|
||||
);
|
||||
|
||||
const assetInSuspendedMarket =
|
||||
allMarkets &&
|
||||
Object.values(allMarkets).some((m: MarketFieldsFragment | null) => {
|
||||
if (m && getAsset(m).id === dispatchStrategy.dispatchMetricAssetId) {
|
||||
return (
|
||||
m?.state === MarketState.STATE_SUSPENDED ||
|
||||
m?.state === MarketState.STATE_SUSPENDED_VIA_GOVERNANCE
|
||||
);
|
||||
}
|
||||
return false;
|
||||
});
|
||||
|
||||
// Gray out the cards that are related to suspended markets
|
||||
// Or settlement assets in markets that are not active and eligible for rewards
|
||||
const { gradientClassName, mainClassName } =
|
||||
suspended || assetInSuspendedMarket || assetInSettledMarket
|
||||
marketSuspended || !assetInActiveMarket
|
||||
? {
|
||||
gradientClassName: 'from-vega-cdark-500 to-vega-clight-400',
|
||||
mainClassName: 'from-vega-cdark-400 dark:from-vega-cdark-600 to-20%',
|
||||
@@ -437,12 +417,12 @@ export const ActiveRewardCard = ({
|
||||
<span>
|
||||
{DispatchMetricLabels[dispatchStrategy.dispatchMetric]} •{' '}
|
||||
<Tooltip
|
||||
underline={suspended}
|
||||
underline={marketSuspended}
|
||||
description={
|
||||
(suspended || assetInSuspendedMarket) &&
|
||||
(marketSuspended || !assetInActiveMarket) &&
|
||||
(specificMarkets
|
||||
? t('Eligible market(s) currently suspended')
|
||||
: assetInSuspendedMarket
|
||||
: !assetInActiveMarket
|
||||
? t('Currently no markets eligible for reward')
|
||||
: '')
|
||||
}
|
||||
|
||||
@@ -96,11 +96,6 @@ describe('TransferForm', () => {
|
||||
});
|
||||
|
||||
it.each([
|
||||
{
|
||||
targetText: 'Include transfer fee',
|
||||
tooltipText:
|
||||
'The fee will be taken from the amount you are transferring.',
|
||||
},
|
||||
{
|
||||
targetText: 'Transfer fee',
|
||||
tooltipText: /transfer\.fee\.factor/,
|
||||
@@ -276,9 +271,6 @@ describe('TransferForm', () => {
|
||||
|
||||
const amountInput = screen.getByLabelText('Amount');
|
||||
|
||||
const checkbox = screen.getByTestId('include-transfer-fee');
|
||||
expect(checkbox).not.toBeChecked();
|
||||
|
||||
await userEvent.clear(amountInput);
|
||||
await userEvent.type(amountInput, '50');
|
||||
|
||||
@@ -288,10 +280,7 @@ describe('TransferForm', () => {
|
||||
await userEvent.click(screen.getByRole('button', { name: 'Use max' }));
|
||||
expect(amountInput).toHaveValue('100.00');
|
||||
|
||||
// If transfering from a vested account 'include fees' checkbox should
|
||||
// be disabled and fees should be 0
|
||||
expect(checkbox).not.toBeChecked();
|
||||
expect(checkbox).toBeDisabled();
|
||||
// If transfering from a vested account fees should be 0
|
||||
const expectedFee = '0';
|
||||
const total = new BigNumber(amount).plus(expectedFee).toFixed();
|
||||
|
||||
@@ -396,78 +385,7 @@ describe('TransferForm', () => {
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
describe('IncludeFeesCheckbox', () => {
|
||||
it('validates fields and submits when checkbox is checked', async () => {
|
||||
const mockSubmit = jest.fn();
|
||||
renderComponent({ ...props, submitTransfer: mockSubmit });
|
||||
|
||||
// check current pubkey not shown
|
||||
const keySelect = screen.getByLabelText<HTMLSelectElement>('To Vega key');
|
||||
const pubKeyOptions = ['', pubKey, props.pubKeys[1]];
|
||||
expect(keySelect.children).toHaveLength(pubKeyOptions.length);
|
||||
expect(Array.from(keySelect.options).map((o) => o.value)).toEqual(
|
||||
pubKeyOptions
|
||||
);
|
||||
|
||||
await submit();
|
||||
expect(await screen.findAllByText('Required')).toHaveLength(2); // pubkey set as default value
|
||||
|
||||
// Select a pubkey
|
||||
await userEvent.selectOptions(
|
||||
screen.getByLabelText('To Vega key'),
|
||||
props.pubKeys[1]
|
||||
);
|
||||
|
||||
// Select asset
|
||||
await selectAsset(asset);
|
||||
|
||||
await userEvent.selectOptions(
|
||||
screen.getByLabelText('From account'),
|
||||
`${AccountType.ACCOUNT_TYPE_GENERAL}-${asset.id}`
|
||||
);
|
||||
|
||||
const amountInput = screen.getByLabelText('Amount');
|
||||
const checkbox = screen.getByTestId('include-transfer-fee');
|
||||
|
||||
// 1003-TRAN-022
|
||||
expect(checkbox).not.toBeChecked();
|
||||
|
||||
await userEvent.clear(amountInput);
|
||||
await userEvent.type(amountInput, amount);
|
||||
await userEvent.click(checkbox);
|
||||
|
||||
expect(checkbox).toBeChecked();
|
||||
const expectedFee = new BigNumber(amount)
|
||||
.times(props.feeFactor)
|
||||
.toFixed();
|
||||
const expectedAmount = new BigNumber(amount).minus(expectedFee).toFixed();
|
||||
|
||||
// 1003-TRAN-020
|
||||
expect(screen.getByTestId('transfer-fee')).toHaveTextContent(expectedFee);
|
||||
expect(screen.getByTestId('transfer-amount')).toHaveTextContent(
|
||||
expectedAmount
|
||||
);
|
||||
expect(screen.getByTestId('total-transfer-fee')).toHaveTextContent(
|
||||
amount
|
||||
);
|
||||
|
||||
await submit();
|
||||
|
||||
await waitFor(() => {
|
||||
// 1003-TRAN-023
|
||||
expect(mockSubmit).toHaveBeenCalledTimes(1);
|
||||
expect(mockSubmit).toHaveBeenCalledWith({
|
||||
fromAccountType: AccountType.ACCOUNT_TYPE_GENERAL,
|
||||
toAccountType: AccountType.ACCOUNT_TYPE_GENERAL,
|
||||
to: props.pubKeys[1],
|
||||
asset: asset.id,
|
||||
amount: removeDecimal(expectedAmount, asset.decimals),
|
||||
oneOff: {},
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
it('validates fields when checkbox is not checked', async () => {
|
||||
renderComponent(props);
|
||||
|
||||
@@ -497,11 +415,8 @@ describe('TransferForm', () => {
|
||||
);
|
||||
|
||||
const amountInput = screen.getByLabelText('Amount');
|
||||
const checkbox = screen.getByTestId('include-transfer-fee');
|
||||
expect(checkbox).not.toBeChecked();
|
||||
|
||||
await userEvent.type(amountInput, amount);
|
||||
expect(checkbox).not.toBeChecked();
|
||||
const expectedFee = new BigNumber(amount)
|
||||
.times(props.feeFactor)
|
||||
.toFixed();
|
||||
|
||||
@@ -15,7 +15,6 @@ import {
|
||||
TradingRichSelect,
|
||||
TradingSelect,
|
||||
Tooltip,
|
||||
TradingCheckbox,
|
||||
TradingButton,
|
||||
} from '@vegaprotocol/ui-toolkit';
|
||||
import type { Transfer } from '@vegaprotocol/wallet';
|
||||
@@ -135,32 +134,17 @@ export const TransferForm = ({
|
||||
const accountBalance =
|
||||
account && addDecimal(account.balance, account.asset.decimals);
|
||||
|
||||
const [includeFee, setIncludeFee] = useState(false);
|
||||
|
||||
// Max amount given selected asset and from account
|
||||
const max = accountBalance ? new BigNumber(accountBalance) : new BigNumber(0);
|
||||
|
||||
const transferAmount = useMemo(() => {
|
||||
if (!amount) return undefined;
|
||||
if (includeFee && feeFactor) {
|
||||
return new BigNumber(1).minus(feeFactor).times(amount).toString();
|
||||
}
|
||||
return amount;
|
||||
}, [amount, includeFee, feeFactor]);
|
||||
|
||||
const fee = useMemo(() => {
|
||||
if (!transferAmount) return undefined;
|
||||
if (includeFee) {
|
||||
return new BigNumber(amount).minus(transferAmount).toString();
|
||||
}
|
||||
return (
|
||||
feeFactor && new BigNumber(feeFactor).times(transferAmount).toString()
|
||||
);
|
||||
}, [amount, includeFee, transferAmount, feeFactor]);
|
||||
const fee = useMemo(
|
||||
() => feeFactor && new BigNumber(feeFactor).times(amount).toString(),
|
||||
[amount, feeFactor]
|
||||
);
|
||||
|
||||
const onSubmit = useCallback(
|
||||
(fields: FormFields) => {
|
||||
if (!transferAmount) {
|
||||
if (!amount) {
|
||||
throw new Error('Submitted transfer with no amount selected');
|
||||
}
|
||||
|
||||
@@ -173,7 +157,7 @@ export const TransferForm = ({
|
||||
|
||||
const transfer = normalizeTransfer(
|
||||
fields.toVegaKey,
|
||||
transferAmount,
|
||||
amount,
|
||||
type,
|
||||
AccountType.ACCOUNT_TYPE_GENERAL, // field is readonly in the form
|
||||
{
|
||||
@@ -183,7 +167,7 @@ export const TransferForm = ({
|
||||
);
|
||||
submitTransfer(transfer);
|
||||
},
|
||||
[submitTransfer, transferAmount, assets]
|
||||
[submitTransfer, amount, assets]
|
||||
);
|
||||
|
||||
// reset for placeholder workaround https://github.com/radix-ui/primitives/issues/1569
|
||||
@@ -279,7 +263,6 @@ export const TransferForm = ({
|
||||
) {
|
||||
setValue('toVegaKey', pubKey);
|
||||
setToVegaKeyMode('select');
|
||||
setIncludeFee(false);
|
||||
}
|
||||
}}
|
||||
>
|
||||
@@ -449,27 +432,9 @@ export const TransferForm = ({
|
||||
</TradingInputError>
|
||||
)}
|
||||
</TradingFormGroup>
|
||||
<div className="mb-4">
|
||||
<Tooltip
|
||||
description={t(
|
||||
`The fee will be taken from the amount you are transferring.`
|
||||
)}
|
||||
>
|
||||
<div>
|
||||
<TradingCheckbox
|
||||
name="include-transfer-fee"
|
||||
disabled={!transferAmount || fromVested}
|
||||
label={t('Include transfer fee')}
|
||||
checked={includeFee}
|
||||
onCheckedChange={() => setIncludeFee((x) => !x)}
|
||||
/>
|
||||
</div>
|
||||
</Tooltip>
|
||||
</div>
|
||||
{transferAmount && fee && (
|
||||
{amount && fee && (
|
||||
<TransferFee
|
||||
amount={transferAmount}
|
||||
transferAmount={transferAmount}
|
||||
amount={amount}
|
||||
feeFactor={feeFactor}
|
||||
fee={fromVested ? '0' : fee}
|
||||
decimals={asset?.decimals}
|
||||
@@ -484,29 +449,22 @@ export const TransferForm = ({
|
||||
|
||||
export const TransferFee = ({
|
||||
amount,
|
||||
transferAmount,
|
||||
feeFactor,
|
||||
fee,
|
||||
decimals,
|
||||
}: {
|
||||
amount: string;
|
||||
transferAmount: string;
|
||||
feeFactor: string | null;
|
||||
fee?: string;
|
||||
decimals?: number;
|
||||
}) => {
|
||||
const t = useT();
|
||||
if (!feeFactor || !amount || !transferAmount || !fee) return null;
|
||||
if (
|
||||
isNaN(Number(feeFactor)) ||
|
||||
isNaN(Number(amount)) ||
|
||||
isNaN(Number(transferAmount)) ||
|
||||
isNaN(Number(fee))
|
||||
) {
|
||||
if (!feeFactor || !amount || !fee) return null;
|
||||
if (isNaN(Number(feeFactor)) || isNaN(Number(amount)) || isNaN(Number(fee))) {
|
||||
return null;
|
||||
}
|
||||
|
||||
const totalValue = new BigNumber(transferAmount).plus(fee).toString();
|
||||
const totalValue = new BigNumber(amount).plus(fee).toString();
|
||||
|
||||
return (
|
||||
<div className="mb-4 flex flex-col gap-2 text-xs">
|
||||
|
||||
@@ -21,7 +21,6 @@
|
||||
"Deposited on the network, but not allocated to a market. Free to use for placing orders or providing liquidity.": "Deposited on the network, but not allocated to a market. Free to use for placing orders or providing liquidity.",
|
||||
"Enter manually": "Enter manually",
|
||||
"From account": "From account",
|
||||
"Include transfer fee": "Include transfer fee",
|
||||
"initial level": "initial level",
|
||||
"maintenance level": "maintenance level",
|
||||
"Margin health": "Margin health",
|
||||
@@ -33,7 +32,6 @@
|
||||
"release level": "release level",
|
||||
"search level": "search level",
|
||||
"Select from wallet": "Select from wallet",
|
||||
"The fee will be taken from the amount you are transferring.": "The fee will be taken from the amount you are transferring.",
|
||||
"The total amount of each asset on this key. Includes used and available collateral.": "The total amount of each asset on this key. Includes used and available collateral.",
|
||||
"The total amount taken from your account. The amount to be transferred plus the fee.": "The total amount taken from your account. The amount to be transferred plus the fee.",
|
||||
"The total amount to be transferred (without the fee)": "The total amount to be transferred (without the fee)",
|
||||
|
||||
@@ -300,7 +300,7 @@
|
||||
"Unknown": "Unknown",
|
||||
"Unknown settlement date": "Unknown settlement date",
|
||||
"Vega Reward pot": "Vega Reward pot",
|
||||
"Vega Wallet <0>full featured<0>": "Vega Wallet <0>full featured<0>",
|
||||
"Vega Wallet <0>full featured</0>": "Vega Wallet <0>full featured</0>",
|
||||
"Vega chart": "Vega chart",
|
||||
"Vesting": "Vesting",
|
||||
"Vesting multiplier": "Vesting multiplier",
|
||||
|
||||
@@ -113,6 +113,8 @@ export const filterAndSortClosedMarkets = (markets: MarketMaybeWithData[]) => {
|
||||
return [
|
||||
MarketState.STATE_SETTLED,
|
||||
MarketState.STATE_TRADING_TERMINATED,
|
||||
MarketState.STATE_CLOSED,
|
||||
MarketState.STATE_CANCELLED,
|
||||
].includes(m.data?.marketState || m.state);
|
||||
});
|
||||
};
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"extends": ["plugin:@nx/react", "../../.eslintrc.json"],
|
||||
"ignorePatterns": ["!**/*", "__generated__"],
|
||||
"ignorePatterns": ["!**/*", "__generated__", "charting-library.d.ts"],
|
||||
"overrides": [
|
||||
{
|
||||
"files": ["*.ts", "*.tsx", "*.js", "*.jsx"],
|
||||
|
||||
+19258
File diff suppressed because it is too large
Load Diff
@@ -9,17 +9,17 @@ export const TradingViewContainer = ({
|
||||
libraryHash,
|
||||
marketId,
|
||||
interval,
|
||||
studies,
|
||||
onIntervalChange,
|
||||
onAutoSaveNeeded,
|
||||
state,
|
||||
}: {
|
||||
libraryPath: string;
|
||||
libraryHash: string;
|
||||
marketId: string;
|
||||
interval: ResolutionString;
|
||||
studies: string[];
|
||||
onIntervalChange: (interval: string) => void;
|
||||
onAutoSaveNeeded: OnAutoSaveNeededCallback;
|
||||
state: object | undefined;
|
||||
}) => {
|
||||
const t = useT();
|
||||
const scriptState = useScript(
|
||||
@@ -48,9 +48,9 @@ export const TradingViewContainer = ({
|
||||
libraryPath={libraryPath}
|
||||
marketId={marketId}
|
||||
interval={interval}
|
||||
studies={studies}
|
||||
onIntervalChange={onIntervalChange}
|
||||
onAutoSaveNeeded={onAutoSaveNeeded}
|
||||
state={state}
|
||||
/>
|
||||
);
|
||||
};
|
||||
|
||||
@@ -1,127 +1,167 @@
|
||||
import { useEffect, useRef } from 'react';
|
||||
import {
|
||||
usePrevious,
|
||||
useScreenDimensions,
|
||||
useThemeSwitcher,
|
||||
} from '@vegaprotocol/react-helpers';
|
||||
import { useLanguage } from './use-t';
|
||||
import { useDatafeed } from './use-datafeed';
|
||||
import { type ResolutionString } from './constants';
|
||||
import {
|
||||
type ChartingLibraryFeatureset,
|
||||
type LanguageCode,
|
||||
type ChartingLibraryWidgetOptions,
|
||||
type IChartingLibraryWidget,
|
||||
type ChartPropertiesOverrides,
|
||||
type ResolutionString as TVResolutionString,
|
||||
} from '../charting-library';
|
||||
|
||||
export type OnAutoSaveNeededCallback = (data: { studies: string[] }) => void;
|
||||
const noop = () => {};
|
||||
|
||||
export type OnAutoSaveNeededCallback = (data: object) => void;
|
||||
|
||||
export const TradingView = ({
|
||||
marketId,
|
||||
libraryPath,
|
||||
interval,
|
||||
studies,
|
||||
onIntervalChange,
|
||||
onAutoSaveNeeded,
|
||||
state,
|
||||
}: {
|
||||
marketId: string;
|
||||
libraryPath: string;
|
||||
interval: ResolutionString;
|
||||
studies: string[];
|
||||
onIntervalChange: (interval: string) => void;
|
||||
onAutoSaveNeeded: OnAutoSaveNeededCallback;
|
||||
state: object | undefined;
|
||||
}) => {
|
||||
const { isMobile } = useScreenDimensions();
|
||||
const { theme } = useThemeSwitcher();
|
||||
const language = useLanguage();
|
||||
const chartContainerRef =
|
||||
useRef<HTMLDivElement>() as React.MutableRefObject<HTMLInputElement>;
|
||||
// Cant get types as charting_library is externally loaded
|
||||
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
||||
const widgetRef = useRef<any>();
|
||||
const chartContainerRef = useRef<HTMLDivElement>(null);
|
||||
const widgetRef = useRef<IChartingLibraryWidget>();
|
||||
|
||||
const datafeed = useDatafeed();
|
||||
|
||||
useEffect(
|
||||
() => {
|
||||
const disableOnSmallScreens = isMobile ? ['left_toolbar'] : [];
|
||||
const prevMarketId = usePrevious(marketId);
|
||||
const prevTheme = usePrevious(theme);
|
||||
|
||||
const overrides = getOverrides(theme);
|
||||
|
||||
const widgetOptions = {
|
||||
symbol: marketId,
|
||||
datafeed,
|
||||
interval: interval,
|
||||
container: chartContainerRef.current,
|
||||
library_path: libraryPath,
|
||||
custom_css_url: 'vega_styles.css',
|
||||
// Trading view accepts just 'en' rather than 'en-US' which is what react-i18next provides
|
||||
// https://www.tradingview.com/charting-library-docs/latest/core_concepts/Localization?_highlight=language#supported-languages
|
||||
locale: language.split('-')[0],
|
||||
enabled_features: ['tick_resolution'],
|
||||
disabled_features: [
|
||||
'header_symbol_search',
|
||||
'header_compare',
|
||||
'show_object_tree',
|
||||
'timeframes_toolbar',
|
||||
...disableOnSmallScreens,
|
||||
],
|
||||
fullscreen: false,
|
||||
autosize: true,
|
||||
theme,
|
||||
overrides,
|
||||
loading_screen: {
|
||||
backgroundColor: overrides['paneProperties.background'],
|
||||
},
|
||||
};
|
||||
|
||||
// @ts-ignore parent component loads TradingView onto window obj
|
||||
widgetRef.current = new window.TradingView.widget(widgetOptions);
|
||||
|
||||
widgetRef.current.onChartReady(() => {
|
||||
widgetRef.current.applyOverrides(getOverrides(theme));
|
||||
|
||||
widgetRef.current.subscribe('onAutoSaveNeeded', () => {
|
||||
const studies = widgetRef.current
|
||||
.activeChart()
|
||||
.getAllStudies()
|
||||
.map((s: { id: string; name: string }) => s.name);
|
||||
onAutoSaveNeeded({ studies });
|
||||
});
|
||||
|
||||
const activeChart = widgetRef.current.activeChart();
|
||||
|
||||
// Show volume study by default, second bool arg adds it as a overlay on top of the chart
|
||||
studies.forEach((study) => {
|
||||
activeChart.createStudy(study);
|
||||
});
|
||||
|
||||
// Subscribe to interval changes so it can be persisted in chart settings
|
||||
activeChart.onIntervalChanged().subscribe(null, onIntervalChange);
|
||||
});
|
||||
|
||||
return () => {
|
||||
if (!widgetRef.current) return;
|
||||
widgetRef.current.remove();
|
||||
};
|
||||
},
|
||||
|
||||
// No theme in deps to avoid full chart reload when the theme changes
|
||||
// Instead the theme is changed programmatically in a separate useEffect
|
||||
// eslint-disable-next-line react-hooks/exhaustive-deps
|
||||
[datafeed, marketId, language, libraryPath, isMobile]
|
||||
);
|
||||
|
||||
// Update the trading view theme every time the app theme updates, done separately
|
||||
// to avoid full chart reload
|
||||
useEffect(() => {
|
||||
if (!widgetRef.current || !widgetRef.current._ready) return;
|
||||
// Widget already created
|
||||
if (widgetRef.current !== undefined) {
|
||||
// Update the symbol if changed
|
||||
if (marketId !== prevMarketId) {
|
||||
widgetRef.current.setSymbol(
|
||||
marketId,
|
||||
(interval ? interval : '15') as TVResolutionString,
|
||||
noop
|
||||
);
|
||||
}
|
||||
|
||||
// Calling changeTheme will reset the default dark/light background to the TV default
|
||||
// so we need to re-apply the pane bg override. A promise is also required
|
||||
// https://github.com/tradingview/charting_library/issues/6546#issuecomment-1139517908
|
||||
widgetRef.current.changeTheme(theme).then(() => {
|
||||
widgetRef.current.applyOverrides(getOverrides(theme));
|
||||
// Update theme theme if changed
|
||||
if (theme !== prevTheme) {
|
||||
widgetRef.current.changeTheme(theme).then(() => {
|
||||
if (!widgetRef.current) return;
|
||||
widgetRef.current.applyOverrides(getOverrides(theme));
|
||||
});
|
||||
}
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
if (!chartContainerRef.current) {
|
||||
return;
|
||||
}
|
||||
|
||||
// Create widget
|
||||
const overrides = getOverrides(theme);
|
||||
|
||||
const disabledOnSmallScreens: ChartingLibraryFeatureset[] = isMobile
|
||||
? ['left_toolbar']
|
||||
: [];
|
||||
const disabledFeatures: ChartingLibraryFeatureset[] = [
|
||||
'header_symbol_search',
|
||||
'header_compare',
|
||||
'show_object_tree',
|
||||
'timeframes_toolbar',
|
||||
...disabledOnSmallScreens,
|
||||
];
|
||||
|
||||
const widgetOptions: ChartingLibraryWidgetOptions = {
|
||||
symbol: marketId,
|
||||
datafeed,
|
||||
interval: interval as TVResolutionString,
|
||||
container: chartContainerRef.current,
|
||||
library_path: libraryPath,
|
||||
custom_css_url: 'vega_styles.css',
|
||||
// Trading view accepts just 'en' rather than 'en-US' which is what react-i18next provides
|
||||
// https://www.tradingview.com/charting-library-docs/latest/core_concepts/Localization?_highlight=language#supported-languages
|
||||
locale: language.split('-')[0] as LanguageCode,
|
||||
enabled_features: ['tick_resolution'],
|
||||
disabled_features: disabledFeatures,
|
||||
fullscreen: false,
|
||||
autosize: true,
|
||||
theme,
|
||||
overrides,
|
||||
loading_screen: {
|
||||
backgroundColor: overrides['paneProperties.background'],
|
||||
},
|
||||
auto_save_delay: 1,
|
||||
saved_data: state,
|
||||
};
|
||||
|
||||
widgetRef.current = new window.TradingView.widget(widgetOptions);
|
||||
|
||||
widgetRef.current.onChartReady(() => {
|
||||
if (!widgetRef.current) return;
|
||||
|
||||
const activeChart = widgetRef.current.activeChart();
|
||||
|
||||
if (!state) {
|
||||
// If chart has loaded with no state, create a volume study
|
||||
activeChart.createStudy('Volume');
|
||||
}
|
||||
|
||||
// Subscribe to interval changes so it can be persisted in chart settings
|
||||
activeChart.onIntervalChanged().subscribe(null, onIntervalChange);
|
||||
});
|
||||
}, [theme]);
|
||||
|
||||
widgetRef.current.subscribe('onAutoSaveNeeded', () => {
|
||||
if (!widgetRef.current) return;
|
||||
|
||||
widgetRef.current.save((newState) => {
|
||||
onAutoSaveNeeded(newState);
|
||||
});
|
||||
});
|
||||
}, [
|
||||
state,
|
||||
datafeed,
|
||||
interval,
|
||||
prevTheme,
|
||||
prevMarketId,
|
||||
marketId,
|
||||
theme,
|
||||
language,
|
||||
libraryPath,
|
||||
isMobile,
|
||||
onAutoSaveNeeded,
|
||||
onIntervalChange,
|
||||
]);
|
||||
|
||||
useEffect(() => {
|
||||
return () => {
|
||||
if (!widgetRef.current) return;
|
||||
widgetRef.current.remove();
|
||||
widgetRef.current = undefined;
|
||||
};
|
||||
}, []);
|
||||
|
||||
return <div ref={chartContainerRef} className="w-full h-full" />;
|
||||
};
|
||||
|
||||
const getOverrides = (theme: 'dark' | 'light') => {
|
||||
const getOverrides = (
|
||||
theme: 'dark' | 'light'
|
||||
): Partial<ChartPropertiesOverrides> => {
|
||||
return {
|
||||
// colors set here, trading view lets the user set a color
|
||||
'paneProperties.background': theme === 'dark' ? '#05060C' : '#fff',
|
||||
|
||||
@@ -2,15 +2,6 @@ import { useEffect, useMemo, useRef } from 'react';
|
||||
import compact from 'lodash/compact';
|
||||
import { useApolloClient } from '@apollo/client';
|
||||
import { type Subscription } from 'zen-observable-ts';
|
||||
/*
|
||||
* TODO: figure out how we can get the chart types
|
||||
import {
|
||||
type LibrarySymbolInfo,
|
||||
type IBasicDataFeed,
|
||||
type ResolutionString,
|
||||
type SeriesFormat,
|
||||
} from '../charting_library/charting_library';
|
||||
*/
|
||||
import {
|
||||
GetBarsDocument,
|
||||
LastBarDocument,
|
||||
@@ -27,6 +18,12 @@ import {
|
||||
type SymbolQueryVariables,
|
||||
} from './__generated__/Symbol';
|
||||
import { getMarketExpiryDate, toBigNum } from '@vegaprotocol/utils';
|
||||
import {
|
||||
type IBasicDataFeed,
|
||||
type DatafeedConfiguration,
|
||||
type LibrarySymbolInfo,
|
||||
type ResolutionString,
|
||||
} from '../charting-library';
|
||||
|
||||
const EXCHANGE = 'VEGA';
|
||||
|
||||
@@ -42,12 +39,8 @@ const resolutionMap: Record<string, Interval> = {
|
||||
|
||||
const supportedResolutions = Object.keys(resolutionMap);
|
||||
|
||||
const configurationData = {
|
||||
// only showing Vega ofc
|
||||
exchanges: [EXCHANGE],
|
||||
|
||||
const configurationData: DatafeedConfiguration = {
|
||||
// Represents the resolutions for bars supported by your datafeed
|
||||
// @ts-ignore cant import types as chartin_library is external
|
||||
supported_resolutions: supportedResolutions as ResolutionString[],
|
||||
} as const;
|
||||
|
||||
@@ -57,9 +50,7 @@ export const useDatafeed = () => {
|
||||
const client = useApolloClient();
|
||||
|
||||
const datafeed = useMemo(() => {
|
||||
// @ts-ignore cant import types as chartin_library is external
|
||||
const feed: IBasicDataFeed = {
|
||||
// @ts-ignore cant import types as chartin_library is external
|
||||
onReady: (callback) => {
|
||||
setTimeout(() => callback(configurationData));
|
||||
},
|
||||
@@ -69,11 +60,8 @@ export const useDatafeed = () => {
|
||||
},
|
||||
|
||||
resolveSymbol: async (
|
||||
// @ts-ignore cant import types as chartin_library is external
|
||||
marketId,
|
||||
// @ts-ignore cant import types as chartin_library is external
|
||||
onSymbolResolvedCallback,
|
||||
// @ts-ignore cant import types as chartin_library is external
|
||||
onResolveErrorCallback
|
||||
) => {
|
||||
try {
|
||||
@@ -110,9 +98,8 @@ export const useDatafeed = () => {
|
||||
const expirationDate = getMarketExpiryDate(instrument.metadata.tags);
|
||||
const expirationTimestamp = expirationDate
|
||||
? Math.floor(expirationDate.getTime() / 1000)
|
||||
: null;
|
||||
: undefined;
|
||||
|
||||
// @ts-ignore cant import types as chartin_library is external
|
||||
const symbolInfo: LibrarySymbolInfo = {
|
||||
ticker: market.id, // use ticker as our unique identifier so that code/name can be used for name/description
|
||||
name: instrument.code,
|
||||
@@ -120,10 +107,9 @@ export const useDatafeed = () => {
|
||||
description: instrument.name,
|
||||
listed_exchange: EXCHANGE,
|
||||
expired: productType === 'Perpetual' ? false : true,
|
||||
expirationDate: expirationTimestamp,
|
||||
expiration_date: expirationTimestamp,
|
||||
|
||||
// @ts-ignore cant import types as chartin_library is external
|
||||
format: 'price' as SeriesFormat,
|
||||
format: 'price',
|
||||
type,
|
||||
session: '24x7',
|
||||
timezone: 'Etc/UTC',
|
||||
@@ -151,15 +137,10 @@ export const useDatafeed = () => {
|
||||
},
|
||||
|
||||
getBars: async (
|
||||
// @ts-ignore cant import types as chartin_library is external
|
||||
symbolInfo,
|
||||
// @ts-ignore cant import types as chartin_library is external
|
||||
resolution,
|
||||
// @ts-ignore cant import types as chartin_library is external
|
||||
periodParams,
|
||||
// @ts-ignore cant import types as chartin_library is external
|
||||
onHistoryCallback,
|
||||
// @ts-ignore cant import types as chartin_library is external
|
||||
onErrorCallback
|
||||
) => {
|
||||
if (!symbolInfo.ticker) {
|
||||
@@ -211,13 +192,9 @@ export const useDatafeed = () => {
|
||||
},
|
||||
|
||||
subscribeBars: (
|
||||
// @ts-ignore cant import types as chartin_library is external
|
||||
symbolInfo,
|
||||
// @ts-ignore cant import types as chartin_library is external
|
||||
resolution,
|
||||
// @ts-ignore cant import types as chartin_library is external
|
||||
onTick
|
||||
|
||||
// subscriberUID, // chart will subscribe and unsbuscribe when the parent market of the page changes so we don't need to use subscriberUID as of now
|
||||
) => {
|
||||
if (!symbolInfo.ticker) {
|
||||
|
||||
@@ -16,6 +16,7 @@ type TradingButtonProps = {
|
||||
subLabel?: ReactNode;
|
||||
fill?: boolean;
|
||||
minimal?: boolean;
|
||||
testId?: string;
|
||||
};
|
||||
|
||||
const getClassName = (
|
||||
@@ -120,6 +121,7 @@ export const TradingButton = forwardRef<
|
||||
className,
|
||||
subLabel,
|
||||
fill,
|
||||
testId,
|
||||
...props
|
||||
},
|
||||
ref
|
||||
@@ -132,6 +134,7 @@ export const TradingButton = forwardRef<
|
||||
{ size, subLabel, intent, fill, minimal },
|
||||
className
|
||||
)}
|
||||
data-testid={testId}
|
||||
{...props}
|
||||
>
|
||||
<Content icon={icon} subLabel={subLabel} children={children} />
|
||||
|
||||
@@ -40,22 +40,12 @@
|
||||
|
||||
## Transfer
|
||||
|
||||
- **Must** can select include transfer fee (<a name="1003-TRAN-015" href="#1003-TRAN-015">1003-TRAN-015</a>)
|
||||
|
||||
- **Must** display tooltip for "Include transfer fee" when hovered over.(<a name="1003-TRAN-016" href="#1003-TRAN-016">1003-TRAN-016</a>)
|
||||
|
||||
- **Must** display tooltip for "Transfer fee when hovered over.(<a name="1003-TRAN-017" href="#1003-TRAN-017">1003-TRAN-017</a>)
|
||||
|
||||
- **Must** display tooltip for "Amount to be transferred" when hovered over.(<a name="1003-TRAN-018" href="#1003-TRAN-018">1003-TRAN-018</a>)
|
||||
|
||||
- **Must** display tooltip for "Total amount (with fee)" when hovered over.(<a name="1003-TRAN-019" href="#1003-TRAN-019">1003-TRAN-019</a>)
|
||||
|
||||
- **Must** amount to be transferred and transfer fee update correctly when include transfer fee is selected (<a name="1003-TRAN-020" href="#1003-TRAN-020">1003-TRAN-020</a>)
|
||||
|
||||
- **Must** total amount with fee is correct with and without "Include transfer fee" selected (<a name="1003-TRAN-021" href="#1003-TRAN-021">1003-TRAN-021</a>)
|
||||
|
||||
- **Must** i cannot select include transfer fee unless amount is entered (<a name="1003-TRAN-022" href="#1003-TRAN-022">1003-TRAN-022</a>)
|
||||
|
||||
- **Must** With all fields entered correctly, clicking "confirm transfer" button will start transaction(<a name="1003-TRAN-023" href="#1003-TRAN-023">1003-TRAN-023</a>)
|
||||
|
||||
### Transfer page
|
||||
|
||||
Reference in New Issue
Block a user