Merge remote-tracking branch 'origin/develop' into chore/3240-update-close-position-to-use-reduce-only-order

This commit is contained in:
maciek
2023-03-27 16:21:06 +02:00
29 changed files with 916 additions and 399 deletions
+4
View File
@@ -3,3 +3,7 @@ apps/**/node_modules/*
tmp/*
.dockerignore
dockerfiles
node_modules
.git
.github
.vscode
+22
View File
@@ -0,0 +1,22 @@
---
name: Release
about:
A template to outline the steps needed to for a successful release of our frontend apps
title: 'Release [add dapp version]-core-[add core version]'
labels:
assignees: ''
---
### Tasks
- [ ] Review [link to core release](xxx)
- [ ] Tag frontend-monorepo
- [ ] Create release and generate release notes
- [ ] Run `@smoke` tests
- [ ] Run `@regression` tests
- [ ] Run `@slow` tests
- [ ] Explorative testing of key flows
- [ ] Set `release/[network]` to tagged commit
- [ ] Verify builds (on Netlify and Fleek) are successful
- [ ] Verify build has been deployed
- [ ] Smoke testing on deployed app
+2
View File
@@ -1,3 +1,4 @@
name: Cypress Run
on:
workflow_call:
inputs:
@@ -17,6 +18,7 @@ jobs:
fail-fast: false
matrix:
project: ${{ fromJSON(inputs.projects) }}
name: ${{ matrix.project }}
runs-on: self-hosted-runner
timeout-minutes: 30
steps:
@@ -1,4 +1,4 @@
name: Cypress tests - PR
name: PR Validations
on:
push:
@@ -20,14 +20,16 @@ jobs:
with:
fetch-depth: 0
- name: Check node version
id: node-version
run: |
npmVersion=$(cat .nvmrc | head -n 1)
echo ::set-output name=npmVersion::${npmVersion}
- name: Setup node
uses: actions/setup-node@v3
- name: Remove package.json & yarn.lock to avoid installing everything
run: rm package.json yarn.lock
- name: Install nx
run: yarn add nx
with:
node-version: ${{ steps.node-version.outputs.npmVersion }}
# Check SHAs
- name: Derive appropriate SHAs for base and head for `nx affected` commands
@@ -39,38 +41,51 @@ jobs:
# See affected apps
- name: See affected apps
run: |
nx_version=$(cat package.json | grep '"nx"' | cut -d ':' -f 2 | tr -d '",[:space:]')
rm package.json yarn.lock
yarn add nx@$nx_version
affected=$(yarn nx print-affected --base=${{ env.NX_BASE }} --head=${{ env.NX_HEAD }} --select=projects)
echo -n "Affected projects: $affected"
projects=""
if [[ $affected == *"governance"* ]]; then projects+='"governance-e2e" '; fi
if [[ $affected == *"trading"* ]]; then projects+='"trading-e2e" '; fi
if [[ $affected == *"explorer"* ]]; then projects+='"explorer-e2e" '; fi
if [[ -z "$projects" ]]; then projects+='"governance-e2e" "trading-e2e" "explorer-e2e" '; fi
projects=${projects%?}
projects=[${projects// /,}]
echo PROJECTS=$projects >> $GITHUB_ENV
projects_e2e=""
if [[ $affected == *"governance"* ]]; then projects_e2e+='"governance-e2e" '; fi
if [[ $affected == *"trading"* ]]; then projects_e2e+='"trading-e2e" '; fi
if [[ $affected == *"explorer"* ]]; then projects_e2e+='"explorer-e2e" '; fi
if [[ -z "$projects_e2e" ]]; then projects_e2e+='"governance-e2e" "trading-e2e" "explorer-e2e" '; fi
projects_e2e=${projects_e2e%?}
projects_e2e=[${projects_e2e// /,}]
echo PROJECTS_E2E=$projects_e2e >> $GITHUB_ENV
echo PROJECTS=$(echo $projects_e2e | sed 's|-e2e||g') >> $GITHUB_ENV
outputs:
projects: ${{ env.PROJECTS }}
projects-e2e: ${{ env.PROJECTS_E2E }}
run:
run-cypress:
needs: pr
if: ${{ needs.pr.outputs.projects != '[]' }}
uses: ./.github/workflows/cypress-run.yml
secrets: inherit
with:
projects: ${{ needs.pr.outputs.projects }}
projects: ${{ needs.pr.outputs.projects-e2e }}
tags: '@smoke @regression'
run-docker-build:
needs: pr
if: ${{ needs.pr.outputs.projects != '[]' }}
uses: ./.github/workflows/publish-docker-containers.yml
secrets: inherit
with:
projects: ${{ needs.pr.outputs.projects }}
# Report single result at the end, to avoid mess with required checks in PR
result:
if: ${{ always() }}
needs: run
needs: run-cypress
runs-on: ubuntu-latest
name: Cypress result
steps:
- run: |
result="${{ needs.run.result }}"
result="${{ needs.run-cypress.result }}"
if [[ $result == "success" || $result == "skipped" ]]; then
exit 0
else
+17 -12
View File
@@ -1,16 +1,19 @@
name: Publish docker containers
name: Docker build
'on':
pull_request:
on:
workflow_call:
inputs:
projects:
required: true
type: string
jobs:
master:
strategy:
fail-fast: false
# to be replaced by nix: https://github.com/vegaprotocol/frontend-monorepo/blob/develop/tools/ipfs-deploy.js#L106-L108
matrix:
app: ${{ fromJson('["explorer"]') }}
name: Build the ${{ matrix.app }} image
app: ${{ fromJSON(inputs.projects) }}
name: ${{ matrix.app }}
runs-on: ubuntu-latest
steps:
- name: Check out code
@@ -45,13 +48,10 @@ jobs:
- name: Build and export to local Docker
uses: docker/build-push-action@v3
with:
context: .
push: false
file: dockerfiles/Dockerfile
load: true
build-args: |
APP=${{ matrix.app }}
NODE_VERSION=${{ steps.tags.outputs.npmVersion }}
load: true
tags: |
ghcr.io/vegaprotocol/frontend/${{ matrix.app }}:local
@@ -75,9 +75,7 @@ jobs:
id: docker_build
uses: docker/build-push-action@v3
with:
context: .
push: true
file: dockerfiles/Dockerfile
build-args: |
APP=${{ matrix.app }}
NODE_VERSION=${{ steps.tags.outputs.npmVersion }}
@@ -85,5 +83,12 @@ jobs:
ghcr.io/vegaprotocol/frontend/${{ matrix.app }}:latest
ghcr.io/vegaprotocol/frontend/${{ matrix.app }}:${{ steps.tags.outputs.version }}
- name: Add preview label
uses: actions-ecosystem/action-add-labels@v1
if: ${{ github.event_name == 'pull_request' }}
with:
labels: ${{ matrix.app }}-preview
number: ${{ github.event.number }}
- name: Image digest
run: echo ${{ steps.docker_build.outputs.digest }}
+8 -6
View File
@@ -4,13 +4,15 @@ FROM --platform=amd64 node:${NODE_VERSION}-alpine3.16 as build
WORKDIR /app
# Argument to allow building of different apps
ARG APP
ENV PATH /app/node_modules/.bin:$PATH
COPY package.json ./
COPY yarn.lock ./
RUN apk add --update --no-cache \
python3 \
make \
gcc \
g++
COPY . ./
RUN apk add python3 make gcc g++
RUN yarn --network-timeout 100000 --pure-lockfile
RUN yarn nx build ${APP} --network-timeout 100000 --pure-lockfile
# work around for different build process in trading
RUN sh ./docker-build.sh
# Server environment
# if this fails you need to docker pull nginx:1.23-alpine and pin new SHA
@@ -25,7 +27,7 @@ CMD ["/entrypoint.sh"]
# Copy dist
WORKDIR /usr/share/nginx/html
COPY --from=build /app/dist/apps/${APP} /usr/share/nginx/html
COPY nginx/nginx.conf /etc/nginx/conf.d/default.conf
COPY --from=build /app/dist/apps/${APP} /usr/share/nginx/html
COPY ./apps/${APP}/.env .env
RUN ipfs init && echo "$(ipfs add -rQ .)" > ipfs-hash
@@ -341,7 +341,7 @@ context(
'Currently expected to pass'
);
getProposalInformationFromTable('Expected to pass')
.contains('👍 by Token vote')
.contains('👍 by token vote')
.should('be.visible');
});
@@ -221,8 +221,9 @@
"votePending": "Casting vote",
"voteError": "Something went wrong, and your vote was not seen by the network",
"back": "back",
"byTokenVote": "by Token vote",
"byLiquidityVote": "by Liquidity vote",
"byTokenVote": "by token vote",
"byLiquidityVote": "by liquidity vote",
"byLPVote": "by LP vote",
"youDidNotVote": "Voting has ended. You did not vote",
"voteState_Yes": "For",
"voteState_No": "Against",
@@ -86,7 +86,7 @@ describe('Proposal Votes Table', () => {
it('displays if an update market proposal will pass by token vote', () => {
renderComponent(updateMarketProposal, updateMarketProposalType);
expect(screen.getByText('👍 by Token vote')).toBeInTheDocument();
expect(screen.getByText('👍 by token vote')).toBeInTheDocument();
});
it('displays if an update market proposal will pass by LP vote', () => {
@@ -110,6 +110,6 @@ describe('Proposal Votes Table', () => {
}),
updateMarketProposalType
);
expect(screen.getByText('👍 by Liquidity vote')).toBeInTheDocument();
expect(screen.getByText('👍 by liquidity vote')).toBeInTheDocument();
});
});
@@ -136,6 +136,82 @@ describe('Proposals list item details', () => {
);
});
it('Renders proposal state: Update market proposal - set to pass by LP vote', () => {
renderComponent(
generateProposal({
state: ProposalState.STATE_OPEN,
terms: {
change: {
__typename: 'UpdateMarket',
},
},
votes: {
yes: {
...generateYesVotes(0),
totalEquityLikeShareWeight: '1000',
},
no: {
...generateNoVotes(0),
totalEquityLikeShareWeight: '0',
},
},
})
);
expect(screen.getByTestId('vote-status')).toHaveTextContent(
'Set to pass by LP vote'
);
});
it('Renders proposal state: Update market proposal - set to pass by token vote', () => {
renderComponent(
generateProposal({
state: ProposalState.STATE_OPEN,
terms: {
change: {
__typename: 'UpdateMarket',
},
},
votes: {
yes: {
...generateYesVotes(1000, 1000),
totalEquityLikeShareWeight: '0',
},
no: {
...generateNoVotes(0),
totalEquityLikeShareWeight: '0',
},
},
})
);
expect(screen.getByTestId('vote-status')).toHaveTextContent(
'Set to pass by token vote'
);
});
it('Renders proposal state: Update market proposal - set to fail', () => {
renderComponent(
generateProposal({
state: ProposalState.STATE_OPEN,
terms: {
change: {
__typename: 'UpdateMarket',
},
},
votes: {
yes: {
...generateYesVotes(0),
totalEquityLikeShareWeight: '0',
},
no: {
...generateNoVotes(0),
totalEquityLikeShareWeight: '0',
},
},
})
);
expect(screen.getByTestId('vote-status')).toHaveTextContent('Set to fail');
});
it('Renders proposal state: Open - 5 minutes left to vote', () => {
renderComponent(
generateProposal({
@@ -41,12 +41,22 @@ export const ProposalsListItemDetails = ({
proposal: ProposalFieldsFragment | ProposalQuery['proposal'];
}) => {
const state = proposal?.state;
const { willPassByTokenVote, majorityMet, participationMet } =
useVoteInformation({
proposal,
});
const {
willPassByTokenVote,
willPassByLPVote,
majorityMet,
participationMet,
} = useVoteInformation({
proposal,
});
const { t } = useTranslation();
const { voteState } = useUserVote(proposal?.id);
const isUpdateMarket = proposal?.terms.change.__typename === 'UpdateMarket';
const updateMarketWillPass = willPassByTokenVote || willPassByLPVote;
const updateMarketVotePassMethod = willPassByTokenVote
? t('byTokenVote')
: t('byLPVote');
let proposalStatus: ReactNode;
let voteDetails: ReactNode;
let voteStatus: ReactNode;
@@ -128,6 +138,19 @@ export const ProposalsListItemDetails = ({
</>
);
voteStatus =
(isUpdateMarket &&
(updateMarketWillPass ? (
<>
{t('Set to')}{' '}
<StatusPass>
{t('pass')} {updateMarketVotePassMethod}
</StatusPass>
</>
) : (
<>
{t('Set to')} <StatusFail>{t('fail')}</StatusFail>
</>
))) ||
(!participationMet && <ParticipationNotReached />) ||
(!majorityMet && <MajorityNotReached />) ||
(willPassByTokenVote ? (
@@ -32,6 +32,7 @@ export const VoteDetails = ({
totalTokensPercentage,
participationMet,
totalTokensVoted,
totalLPTokensPercentage,
noPercentage,
noLPPercentage,
yesPercentage,
@@ -41,6 +42,8 @@ export const VoteDetails = ({
requiredMajorityPercentage,
requiredMajorityLPPercentage,
requiredParticipation,
requiredParticipationLP,
participationLPMet,
} = useVoteInformation({ proposal });
const { t } = useTranslation();
@@ -101,6 +104,25 @@ export const VoteDetails = ({
</tr>
</tbody>
</table>
<p className="mb-6">
{t('participation')}
{': '}
{participationLPMet ? (
<span className="text-vega-green mx-4">{t('met')}</span>
) : (
<span className="text-danger mx-4">{t('notMet')}</span>
)}{' '}
{formatNumber(totalLPTokensPercentage, defaultDecimals)}%
<span className="ml-4">
{requiredParticipationLP && (
<>
({formatNumber(requiredParticipationLP, defaultDecimals)}%{' '}
{t('governanceRequired')})
</>
)}
</span>
</p>
</section>
)}
<section data-testid="votes-table">
File diff suppressed because it is too large Load Diff
+13 -7
View File
@@ -135,7 +135,7 @@ describe('capsule - without MultiSign', { tags: '@slow' }, () => {
cy.getByTestId(transferForm).find(submitTransferBtn).click();
cy.getByTestId(toastContent).should(
'contain.text',
'Transfer completeYour transaction has been confirmed TransferTo 7f9cf0…c255351.00 tBTC'
'Transfer completeYour transaction has been confirmed View in block explorerTransferTo 7f9cf0…c255351.00 tBTC'
);
cy.getByTestId(toastCloseBtn).click();
});
@@ -283,7 +283,7 @@ describe('capsule', { tags: '@slow' }, () => {
});
});
// comment because of bug #2695
it.skip('can edit order', function () {
it('can edit order', function () {
cy.getByTestId(ordersTab).click();
cy.getByTestId('edit').first().should('be.visible').click();
cy.getByTestId('dialog-title').should('contain.text', 'Edit order');
@@ -422,7 +422,11 @@ describe('capsule', { tags: '@slow' }, () => {
cy.getByTestId(depositSubmit).click();
cy.getByTestId('input-error-text').should(
'contain.text',
'Amount is above approved amount.Update approve amount'
'Amount is above approved amount'
);
cy.getByTestId('reapprove-default').should(
'contain.text',
'Approve again to deposit more than'
);
});
@@ -437,9 +441,11 @@ describe('capsule', { tags: '@slow' }, () => {
cy.getByTestId('deposit-button').click();
connectEthereumWallet('Unknown');
cy.get(assetSelectField, txTimeout).select(vegaName, { force: true });
cy.getByTestId('deposit-submit').click();
cy.getByTestId('dialog-title').should('contain.text', 'Approve complete');
cy.get('[data-testid="Return to deposit"]').click();
cy.getByTestId('approve-submit').click();
cy.getByTestId('approve-confirmed').should(
'contain.text',
'You can now make deposits in VEGA, up to a maximum of'
);
cy.get(amountField).clear().type('10000');
cy.getByTestId('deposit-submit').click();
cy.getByTestId(toastContent, txTimeout).should(
@@ -447,7 +453,7 @@ describe('capsule', { tags: '@slow' }, () => {
`Your transaction has been confirmed.`,
{ matchCase: false }
);
cy.getByTestId(toastCloseBtn).click();
cy.getByTestId(toastCloseBtn).click({ multiple: true });
cy.getByTestId('Collateral').click();
cy.highlight('deposit verification');
@@ -2,7 +2,7 @@ import React, { useCallback } from 'react';
import { useMarketList } from '@vegaprotocol/market-list';
import { t } from '@vegaprotocol/i18n';
import { useAssetDetailsDialogStore } from '@vegaprotocol/assets';
import { Link as UILink } from '@vegaprotocol/ui-toolkit';
import { Link as UILink, TinyScroll } from '@vegaprotocol/ui-toolkit';
import type { OnCellClickHandler } from '../select-market';
import type { MarketMaybeWithDataAndCandles } from '@vegaprotocol/market-list';
import {
@@ -55,8 +55,8 @@ export const SelectMarketLandingTable = ({
const showProposed = (markets?.length || 0) <= 5;
return (
<>
<div
className="max-h-[60vh] overflow-x-auto"
<TinyScroll
className="max-h-[60vh] overflow-x-auto -mr-4 pr-4"
data-testid="select-market-list"
>
<p className="text-neutral-500 dark:text-neutral-400 mb-4">
@@ -78,7 +78,7 @@ export const SelectMarketLandingTable = ({
))}
</tbody>
</table>
</div>
</TinyScroll>
<div className="mt-4 text-md">
<Link
to={Links[Routes.MARKETS]()}
@@ -103,7 +103,9 @@ const EthTxPendingToastContent = ({ tx }: EthTxToastContentProps) => {
<>
<ToastHeading>{t('Awaiting confirmation')}</ToastHeading>
<p>{t('Please wait for your transaction to be confirmed.')}</p>
{tx.txHash && <EtherscanLink tx={tx.txHash} />}
{tx.txHash && (
<EtherscanLink tx={tx.txHash}>{t('View on Etherscan')}</EtherscanLink>
)}
<EthTransactionDetails tx={tx} />
</>
);
@@ -131,7 +133,9 @@ const EthTxConfirmedToastContent = ({ tx }: EthTxToastContentProps) => {
<>
<ToastHeading>{t('Transaction confirmed')}</ToastHeading>
<p>{t('Your transaction has been confirmed.')}</p>
{tx.txHash && <EtherscanLink tx={tx.txHash} />}
{tx.txHash && (
<EtherscanLink tx={tx.txHash}>{t('View on Etherscan')}</EtherscanLink>
)}
<EthTransactionDetails tx={tx} />
</>
);
@@ -148,7 +152,9 @@ const EthTxCompletedToastContent = ({ tx }: EthTxToastContentProps) => {
{t('Your transaction has been completed.')}{' '}
{isDeposit && t('Waiting for deposit confirmation.')}
</p>
{tx.txHash && <EtherscanLink tx={tx.txHash} />}
{tx.txHash && (
<EtherscanLink tx={tx.txHash}>{t('View on Etherscan')}</EtherscanLink>
)}
<EthTransactionDetails tx={tx} />
</>
);
+10
View File
@@ -0,0 +1,10 @@
#!/bin/sh -eux
export PATH="/app/node_modules/.bin:$PATH"
if [ "${APP}" = "trading" ]; then
yarn nx export ${APP} --network-timeout 100000 --pure-lockfile
mv /app/dist/apps/trading/exported/ /app/tmp
rm -rf /app/dist/apps/trading
mv /app/tmp /app/dist/apps/trading
else
yarn nx build ${APP} --network-timeout 100000 --pure-lockfile
fi
+7 -1
View File
@@ -7,6 +7,10 @@ mkdir -p $(dirname $env_file)
rm -rf $env_file || echo "no file to delete"
touch $env_file
env_vars_file=/usr/share/nginx/html/.env
sed -i '/^#/d' $env_vars_file # remove comment lines
sed -i '/^$/d' $env_vars_file # remove empty lines
# Add assignment
echo "window._env_ = {" >> $env_file
@@ -29,7 +33,9 @@ do
if [ ! -z "$varname" ]; then
echo " $varname: \"$value\"," >> $env_file
fi
done < /usr/share/nginx/html/.env
done < $env_vars_file
rm $env_vars_file
echo "}" >> $env_file
@@ -20,6 +20,7 @@ import {
InputError,
Intent,
Notification,
TinyScroll,
} from '@vegaprotocol/ui-toolkit';
import {
@@ -160,136 +161,140 @@ export const DealTicket = ({
if (!order || !normalizedOrder) return null;
return (
<form
onSubmit={isReadOnly ? undefined : handleSubmit(onSubmit)}
className="p-4"
noValidate
>
<Controller
name="type"
control={control}
rules={{
validate: validateType(
marketData.marketTradingMode,
marketData.trigger
),
}}
render={() => (
<TypeSelector
value={order.type}
onSelect={(type) => {
if (type === OrderType.TYPE_NETWORK) return;
update({
type,
// when changing type also update the tif to what was last used of new type
timeInForce: lastTIF[type] || order.timeInForce,
expiresAt: undefined,
});
clearErrors('expiresAt');
}}
market={market}
marketData={marketData}
errorMessage={errors.type?.message}
/>
)}
/>
<Controller
name="side"
control={control}
render={() => (
<SideSelector
value={order.side}
onSelect={(side) => {
update({ side });
}}
/>
)}
/>
<DealTicketAmount
control={control}
orderType={order.type}
market={market}
marketData={marketData}
sizeError={errors.size?.message}
priceError={errors.price?.message}
update={update}
size={order.size}
price={order.price}
/>
<Controller
name="timeInForce"
control={control}
rules={{
validate: validateTimeInForce(
marketData.marketTradingMode,
marketData.trigger
),
}}
render={() => (
<TimeInForceSelector
value={order.timeInForce}
orderType={order.type}
onSelect={(timeInForce) => {
update({ timeInForce });
// Set tif value for the given order type, so that when switching
// types we know the last used TIF for the given order type
setLastTIF((curr) => ({
...curr,
[order.type]: timeInForce,
expiresAt: undefined,
}));
clearErrors('expiresAt');
}}
market={market}
marketData={marketData}
errorMessage={errors.timeInForce?.message}
/>
)}
/>
{order.type === Schema.OrderType.TYPE_LIMIT &&
order.timeInForce === Schema.OrderTimeInForce.TIME_IN_FORCE_GTT && (
<Controller
name="expiresAt"
control={control}
rules={{
validate: validateExpiration,
}}
render={() => (
<ExpirySelector
value={order.expiresAt}
onSelect={(expiresAt) =>
update({
expiresAt: expiresAt || undefined,
})
}
errorMessage={errors.expiresAt?.message}
/>
)}
/>
)}
<SummaryMessage
errorMessage={errors.summary?.message}
asset={asset}
marketTradingMode={marketData.marketTradingMode}
balance={balance}
margin={totalMargin}
isReadOnly={isReadOnly}
pubKey={pubKey}
onClickCollateral={onClickCollateral}
/>
<DealTicketButton
disabled={Object.keys(errors).length >= 1 || isReadOnly}
variant={order.side === Schema.Side.SIDE_BUY ? 'ternary' : 'secondary'}
/>
<DealTicketFeeDetails
order={normalizedOrder}
market={market}
marketData={marketData}
margin={margin}
totalMargin={totalMargin}
balance={marginAccountBalance}
/>
</form>
<TinyScroll className="h-full overflow-auto">
<form
onSubmit={isReadOnly ? undefined : handleSubmit(onSubmit)}
className="p-4"
noValidate
>
<Controller
name="type"
control={control}
rules={{
validate: validateType(
marketData.marketTradingMode,
marketData.trigger
),
}}
render={() => (
<TypeSelector
value={order.type}
onSelect={(type) => {
if (type === OrderType.TYPE_NETWORK) return;
update({
type,
// when changing type also update the tif to what was last used of new type
timeInForce: lastTIF[type] || order.timeInForce,
expiresAt: undefined,
});
clearErrors('expiresAt');
}}
market={market}
marketData={marketData}
errorMessage={errors.type?.message}
/>
)}
/>
<Controller
name="side"
control={control}
render={() => (
<SideSelector
value={order.side}
onSelect={(side) => {
update({ side });
}}
/>
)}
/>
<DealTicketAmount
control={control}
orderType={order.type}
market={market}
marketData={marketData}
sizeError={errors.size?.message}
priceError={errors.price?.message}
update={update}
size={order.size}
price={order.price}
/>
<Controller
name="timeInForce"
control={control}
rules={{
validate: validateTimeInForce(
marketData.marketTradingMode,
marketData.trigger
),
}}
render={() => (
<TimeInForceSelector
value={order.timeInForce}
orderType={order.type}
onSelect={(timeInForce) => {
update({ timeInForce });
// Set tif value for the given order type, so that when switching
// types we know the last used TIF for the given order type
setLastTIF((curr) => ({
...curr,
[order.type]: timeInForce,
expiresAt: undefined,
}));
clearErrors('expiresAt');
}}
market={market}
marketData={marketData}
errorMessage={errors.timeInForce?.message}
/>
)}
/>
{order.type === Schema.OrderType.TYPE_LIMIT &&
order.timeInForce === Schema.OrderTimeInForce.TIME_IN_FORCE_GTT && (
<Controller
name="expiresAt"
control={control}
rules={{
validate: validateExpiration,
}}
render={() => (
<ExpirySelector
value={order.expiresAt}
onSelect={(expiresAt) =>
update({
expiresAt: expiresAt || undefined,
})
}
errorMessage={errors.expiresAt?.message}
/>
)}
/>
)}
<SummaryMessage
errorMessage={errors.summary?.message}
asset={asset}
marketTradingMode={marketData.marketTradingMode}
balance={balance}
margin={totalMargin}
isReadOnly={isReadOnly}
pubKey={pubKey}
onClickCollateral={onClickCollateral}
/>
<DealTicketButton
disabled={Object.keys(errors).length >= 1 || isReadOnly}
variant={
order.side === Schema.Side.SIDE_BUY ? 'ternary' : 'secondary'
}
/>
<DealTicketFeeDetails
order={normalizedOrder}
market={market}
marketData={marketData}
margin={margin}
totalMargin={totalMargin}
balance={marginAccountBalance}
/>
</form>
</TinyScroll>
);
};
@@ -16,6 +16,7 @@ interface ApproveNotificationProps {
balances: DepositBalances | null;
amount: string;
approveTxId: number | null;
intent?: Intent;
}
export const ApproveNotification = ({
@@ -26,6 +27,7 @@ export const ApproveNotification = ({
balances,
approved,
approveTxId,
intent = Intent.Warning,
}: ApproveNotificationProps) => {
const tx = useEthTransactionStore((state) => {
return state.transactions.find((t) => t?.id === approveTxId);
@@ -46,7 +48,7 @@ export const ApproveNotification = ({
const approvePrompt = (
<div className="mb-4">
<Notification
intent={Intent.Warning}
intent={intent}
testId="approve-default"
message={t(
`Before you can make a deposit of your chosen asset, ${selectedAsset?.symbol}, you need to approve its use in your Ethereum wallet`
@@ -63,7 +65,7 @@ export const ApproveNotification = ({
const reApprovePrompt = (
<div className="mb-4">
<Notification
intent={Intent.Warning}
intent={intent}
testId="reapprove-default"
message={t(
`Approve again to deposit more than ${formatNumber(
+12 -4
View File
@@ -83,6 +83,8 @@ export const DepositForm = ({
const openDialog = useWeb3ConnectStore((store) => store.open);
const { isActive, account } = useWeb3React();
const { pubKey, pubKeys: _pubKeys } = useVegaWallet();
const [approveNotificationIntent, setApproveNotificationIntent] =
useState<Intent>(Intent.Warning);
const {
register,
handleSubmit,
@@ -103,8 +105,10 @@ export const DepositForm = ({
if (!selectedAsset || selectedAsset.source.__typename !== 'ERC20') {
throw new Error('Invalid asset');
}
if (!approved) throw new Error('Deposits not approved');
if (!approved) {
setApproveNotificationIntent(Intent.Danger);
return;
}
submitDeposit({
assetSource: selectedAsset.source.contractAddress,
amount: fields.amount,
@@ -219,7 +223,7 @@ export const DepositForm = ({
{errors.asset.message}
</InputError>
)}
{isFaucetable && selectedAsset && (
{isActive && isFaucetable && selectedAsset && (
<UseButton onClick={submitFaucet}>
{t(`Get ${selectedAsset.symbol}`)}
</UseButton>
@@ -355,9 +359,13 @@ export const DepositForm = ({
isActive={isActive}
approveTxId={approveTxId}
selectedAsset={selectedAsset}
onApprove={submitApprove}
onApprove={() => {
submitApprove();
setApproveNotificationIntent(Intent.Warning);
}}
balances={balances}
approved={approved}
intent={approveNotificationIntent}
amount={amount}
/>
<FormButton approved={approved} selectedAsset={selectedAsset} />
+4 -5
View File
@@ -1,4 +1,3 @@
import styles from './orderbook.module.scss';
import colors from 'tailwindcss/colors';
import { useEffect, useRef, useState, useCallback, Fragment } from 'react';
import classNames from 'classnames';
@@ -14,7 +13,7 @@ import {
import * as Schema from '@vegaprotocol/types';
import { OrderbookRow } from './orderbook-row';
import { createRow } from './orderbook-data';
import { Checkbox, Icon, Splash } from '@vegaprotocol/ui-toolkit';
import { Checkbox, Icon, Splash, TinyScroll } from '@vegaprotocol/ui-toolkit';
import type { OrderbookData, OrderbookRowData } from './orderbook-data';
interface OrderbookProps extends OrderbookData {
@@ -547,8 +546,8 @@ export const Orderbook = ({
{t('Cumulative vol')}
</div>
</div>
<div
className={`h-full overflow-auto relative ${styles['scroll']}`}
<TinyScroll
className="h-full overflow-auto relative"
onScroll={onScroll}
ref={scrollElement}
data-testid="scroll"
@@ -580,7 +579,7 @@ export const Orderbook = ({
testId={'best-static-offer-price'}
/>
)}
</div>
</TinyScroll>
<div
className="absolute bottom-0 grid grid-cols-4 gap-2 border-t border-default mt-2 z-10 bg-white dark:bg-black w-full"
ref={footerElement}
@@ -9,6 +9,7 @@ import {
ExternalLink,
Link as UILink,
Splash,
TinyScroll,
} from '@vegaprotocol/ui-toolkit';
import { useMemo } from 'react';
import { generatePath, Link } from 'react-router-dom';
@@ -71,7 +72,9 @@ export const MarketInfoContainer = ({
return (
<AsyncRenderer data={data} loading={loading} error={error} reload={reload}>
{data ? (
<Info market={data} onSelect={(id) => onSelect?.(id)} />
<TinyScroll className="h-full overflow-auto">
<Info market={data} onSelect={(id) => onSelect?.(id)} />
</TinyScroll>
) : (
<Splash>
<p>{t('Could not load market')}</p>
+2 -6
View File
@@ -24,11 +24,7 @@ export const PositionsManager = ({
}: PositionsManagerProps) => {
const gridRef = useRef<AgGridReact | null>(null);
const [dataCount, setDataCount] = useState(0);
const { data, error, loading, reload } = usePositionsData(
partyId,
gridRef,
true
);
const { data, error, loading, reload } = usePositionsData(partyId, gridRef);
const create = useVegaTransactionStore((store) => store.create);
const onClose = ({
marketId,
@@ -73,7 +69,7 @@ export const PositionsManager = ({
});
useEffect(() => {
setDataCount(gridRef.current?.api?.getModel().getRowCount() ?? 0);
}, [data?.length]);
}, [data]);
const onFilterChanged = useCallback((event: FilterChangedEvent) => {
setDataCount(gridRef.current?.api?.getModel().getRowCount() ?? 0);
}, []);
+25 -4
View File
@@ -6,13 +6,13 @@ import { positionsMetricsProvider } from './positions-data-providers';
import type { PositionsQueryVariables } from './__generated__/Positions';
import { useDataProvider, updateGridData } from '@vegaprotocol/react-helpers';
import type { GetRowsParams } from '@vegaprotocol/datagrid';
import isEqual from 'lodash/isEqual';
export const getRowId = ({ data }: { data: Position }) => data.marketId;
export const usePositionsData = (
partyId: string,
gridRef: RefObject<AgGridReact>,
clientSideModel?: boolean
gridRef: RefObject<AgGridReact>
) => {
const variables = useMemo<PositionsQueryVariables>(
() => ({ partyId }),
@@ -21,9 +21,30 @@ export const usePositionsData = (
const dataRef = useRef<Position[] | null>(null);
const update = useCallback(
({ data }: { data: Position[] | null }) => {
return clientSideModel ? false : updateGridData(dataRef, data, gridRef);
if (gridRef.current?.api?.getModel().getType() === 'infinite') {
return updateGridData(dataRef, data, gridRef);
}
const update: Position[] = [];
const add: Position[] = [];
data?.forEach((d) => {
const rowNode = gridRef.current?.api?.getRowNode(d.marketId);
if (rowNode) {
if (!isEqual(rowNode.data, d)) {
update.push(d);
}
} else {
add.push(d);
}
});
gridRef.current?.api?.applyTransaction({
update,
add,
addIndex: 0,
});
return true;
},
[gridRef, clientSideModel]
[gridRef]
);
const { data, error, loading, reload } = useDataProvider({
dataProvider: positionsMetricsProvider,
+1
View File
@@ -40,6 +40,7 @@ export * from './tabs';
export * from './text-area';
export * from './theme-switcher';
export * from './thumbs';
export * from './tiny-scroll';
export * from './toast';
export * from './toggle';
export * from './tooltip';
@@ -0,0 +1 @@
export * from './tiny-scroll';
@@ -0,0 +1,20 @@
import styles from './tiny-scroll.module.scss';
import { forwardRef } from 'react';
import classNames from 'classnames';
import type { HTMLAttributes, ReactNode } from 'react';
export interface Props extends HTMLAttributes<HTMLDivElement> {
children: ReactNode;
}
export const TinyScroll = forwardRef<HTMLDivElement, Props>(
({ children, className, ...props }, ref) => (
<div
ref={ref}
className={classNames(className, styles['scroll'])}
{...props}
>
{children}
</div>
)
);