Compare commits

..
88 changed files with 1906 additions and 398 deletions
+25 -6
View File
@@ -1,5 +1,8 @@
name: (CI) Console tests
env:
VEGA_VERSION: v0.72.14
on:
workflow_call:
inputs:
@@ -11,7 +14,7 @@ jobs:
run-tests:
name: run-tests
runs-on: console-test
timeout-minutes: 20
timeout-minutes: 40
steps:
#----------------------------------------------
# check-out frontend-monorepo
@@ -67,23 +70,39 @@ jobs:
repository: vegaprotocol/console-test
path: './console-test'
#----------------------------------------------
# install dependencies if cache does not exist
# install dependencies
#----------------------------------------------
- name: Install dependencies
working-directory: ./console-test
if: steps.cached-poetry-dependencies.outputs.cache-hit != 'true'
run: poetry install --no-interaction --no-root
#----------------------------------------------
# install vega binaries
# find vega binaries path
#----------------------------------------------
- name: Find vega binaries path
id: vega_bin_path
working-directory: ./console-test
run: echo path=$(poetry run python -c "import vega_sim; print(vega_sim.vega_bin_path)") >> $GITHUB_OUTPUT
#----------------------------------------------
# vega binaries cache
#----------------------------------------------
- name: Vega binaries cache
uses: actions/cache@v3
id: vega_binaries_cache
with:
path: ${{ steps.vega_bin_path.outputs.path }}
key: ${{ runner.os }}-vega-binaries-${{ env.VEGA_VERSION }}
#----------------------------------------------
# install vega binaries
#----------------------------------------------
- name: Install vega binaries
working-directory: ./console-test
run: poetry run python -m vega_sim.tools.load_binaries --force
if: steps.vega_binaries_cache.outputs.cache-hit != 'true'
run: poetry run python -m vega_sim.tools.load_binaries --force --version $VEGA_VERSION
#----------------------------------------------
# install playwright
#----------------------------------------------
- name: install playwright
run: poetry run playwright install
run: poetry run playwright install --with-deps chromium
working-directory: ./console-test
#----------------------------------------------
# run tests
+6 -8
View File
@@ -155,10 +155,10 @@ jobs:
- name: Sanity check docker image
run: |
echo "Check ipfs-hash"
docker run --rm ghcr.io/vegaprotocol/frontend/${{ matrix.app }}:local /bin/sh -c 'cat /ipfs-hash'
docker run --rm ghcr.io/vegaprotocol/frontend/${{ matrix.app }}:local /bin/sh -c 'cat /ipfs-hash' > ${{ matrix.app }}-ipfs-hash
docker run --rm --entrypoint /bin/sh ghcr.io/vegaprotocol/frontend/${{ matrix.app }}:local -c 'cat /ipfs-hash'
docker run --rm --entrypoint /bin/sh ghcr.io/vegaprotocol/frontend/${{ matrix.app }}:local -c 'cat /ipfs-hash' > ${{ matrix.app }}-ipfs-hash
echo "List html directory"
docker run --rm ghcr.io/vegaprotocol/frontend/${{ matrix.app }}:local /bin/sh -c 'apk add --update tree; tree /usr/share/nginx/html'
docker run --rm --entrypoint /bin/sh ghcr.io/vegaprotocol/frontend/${{ matrix.app }}:local -c 'apk add --update tree; tree /usr/share/nginx/html'
- name: Publish dist as docker image (ghcr)
uses: docker/build-push-action@v3
@@ -329,9 +329,7 @@ jobs:
fi
# create commit
if ! git diff --cached --exit-code; then
commit_msg="Automated hash update from ${{ github.ref }}"
git commit -m "$commit_msg"
git push -u origin "main"
fi
commit_msg="Automated hash update from ${{ github.ref }}"
git commit -m "$commit_msg"
git push -u origin "main"
)
+1 -1
View File
@@ -118,7 +118,7 @@ On top of that there are two possible scenarios for running docker image - using
to run ipfs on port 3000:
```bash
docker run -p 3000:80 [TAG] /run-ipfs.sh
docker run -p 3000:80 [TAG] ipfs
```
to run nginx on port 3000:
@@ -4,7 +4,7 @@ import { AssetTypeMapping, AssetStatusMapping } from '@vegaprotocol/assets';
import { t } from '@vegaprotocol/i18n';
import { ButtonLink } from '@vegaprotocol/ui-toolkit';
import type { AgGridReact } from 'ag-grid-react';
import { AgGrid } from '@vegaprotocol/datagrid';
import { AgGridLazy as AgGrid } from '@vegaprotocol/datagrid';
import type { VegaICellRendererParams } from '@vegaprotocol/datagrid';
import { useRef, useLayoutEffect } from 'react';
import { BREAKPOINT_MD } from '../../config/breakpoints';
@@ -4,7 +4,7 @@ import { t } from '@vegaprotocol/i18n';
import { ButtonLink } from '@vegaprotocol/ui-toolkit';
import type { AgGridReact } from 'ag-grid-react';
import type { ColDef } from 'ag-grid-community';
import { AgGrid } from '@vegaprotocol/datagrid';
import { AgGridLazy as AgGrid } from '@vegaprotocol/datagrid';
import type {
VegaICellRendererParams,
VegaValueGetterParams,
@@ -2,7 +2,7 @@ import type { ProposalListFieldsFragment } from '@vegaprotocol/proposals';
import { VoteProgress } from '@vegaprotocol/proposals';
import type { AgGridReact } from 'ag-grid-react';
import { ExternalLink } from '@vegaprotocol/ui-toolkit';
import { AgGrid } from '@vegaprotocol/datagrid';
import { AgGridLazy as AgGrid } from '@vegaprotocol/datagrid';
import type {
VegaICellRendererParams,
VegaValueFormatterParams,
@@ -105,7 +105,7 @@ export const ProposalsTable = ({ data }: ProposalsTableProps) => {
? new BigNumber(0)
: yesTokens.multipliedBy(100).dividedBy(totalTokensVoted);
return (
<div className="flex items-center justify-center h-full pt-2 uppercase">
<div className="uppercase flex h-full items-center justify-center pt-2">
<VoteProgress
threshold={requiredMajorityPercentage}
progress={yesPercentage}
@@ -1,4 +1,4 @@
import { useMemo, useEffect, useState, useCallback, useRef } from 'react';
import { useMemo, useEffect, useState, useCallback } from 'react';
import { useTranslation } from 'react-i18next';
import { AsyncRenderer, Pagination } from '@vegaprotocol/ui-toolkit';
import { removePaginationWrapper } from '@vegaprotocol/utils';
@@ -86,18 +86,12 @@ export const EpochIndividualRewards = ({
[epochId, page, refetch, delegationsPagination, pubKey]
);
const prevEpochIdRef = useRef<number | null>(null);
useEffect(() => {
if (prevEpochIdRef.current === null) {
prevEpochIdRef.current = epochId;
} else if (epochId !== prevEpochIdRef.current) {
// When the epoch changes, we want to refetch the data to update the current page
// when the epoch changes, we want to refetch the data to update the current page
if (data) {
refetchData();
}
prevEpochIdRef.current = epochId;
}, [epochId, refetchData]);
}, [epochId, data, refetchData]);
return (
<AsyncRenderer
@@ -3,7 +3,7 @@ import { forwardRef, useMemo, useRef, useState } from 'react';
import { useTranslation } from 'react-i18next';
import { useNavigate } from 'react-router-dom';
import { Button, Icon } from '@vegaprotocol/ui-toolkit';
import { AgGrid } from '@vegaprotocol/datagrid';
import { AgGridLazy as AgGrid } from '@vegaprotocol/datagrid';
import { useAppState } from '../../../../contexts/app-state/app-state-context';
import { BigNumber } from '../../../../lib/bignumber';
import {
@@ -85,17 +85,17 @@ const TopThirdCellRenderer = (
}}
className="grid grid-cols-[60px_1fr] w-full h-full py-4 px-0 text-sm text-white text-center overflow-scroll"
>
<div className="px-3 text-xs text-left">
<div className="text-xs text-left px-3">
{params?.data?.rankingDisplay}
</div>
<div className="px-3 whitespace-normal">
<div className="whitespace-normal px-3">
<div className="mb-4">
<Button
data-testid="show-all-validators"
rightIcon={
<Icon
name="arrow-right"
className="mr-2 align-text-top fill-current"
className="fill-current mr-2 align-text-top"
/>
}
className="inline-flex items-center"
@@ -103,7 +103,7 @@ const TopThirdCellRenderer = (
{t('Reveal top validators')}
</Button>
</div>
<p className="mb-0 font-semibold text-white">
<p className="font-semibold text-white mb-0">
{t(
'Validators with too great a stake share will have the staking rewards for their delegators penalised.'
)}
@@ -1,7 +1,7 @@
import { forwardRef, useMemo, useRef } from 'react';
import { useTranslation } from 'react-i18next';
import { useNavigate } from 'react-router-dom';
import { AgGrid } from '@vegaprotocol/datagrid';
import { AgGridLazy as AgGrid } from '@vegaprotocol/datagrid';
import { useAppState } from '../../../../contexts/app-state/app-state-context';
import { BigNumber } from '../../../../lib/bignumber';
import {
@@ -24,9 +24,7 @@ describe('deal ticker order validation', { tags: '@smoke' }, () => {
beforeEach(() => {
cy.mockTradingPage();
cy.getByTestId('deal-ticket-fee-margin-required').within(() => {
cy.get('button').click();
});
cy.getByTestId('deal-ticket-fee-margin-required').click();
});
describe('limit order', () => {
@@ -111,6 +109,7 @@ describe('deal ticker order validation', { tags: '@smoke' }, () => {
'Total margin available100.01 tDAI'
);
});
cy.getByTestId('deal-ticket-fee-margin-required').click();
});
it('must have current margin allocation', () => {
@@ -120,6 +119,7 @@ describe('deal ticker order validation', { tags: '@smoke' }, () => {
'Current margin allocation'
);
});
cy.getByTestId('deal-ticket-fee-margin-required').click();
});
it('should open usage breakdown dialog when clicked on current margin allocation', () => {
@@ -128,6 +128,7 @@ describe('deal ticker order validation', { tags: '@smoke' }, () => {
});
cy.getByTestId('usage-breakdown').should('exist');
cy.getByTestId('dialog-close').click();
cy.getByTestId('deal-ticket-fee-margin-required').click();
});
});
});
@@ -86,8 +86,8 @@ describe('trades', { tags: '@smoke' }, () => {
});
it('copy price to deal ticket form', () => {
cy.getByTestId('Order').click();
// 6005-THIS-007
cy.getByTestId('order-type-Limit').click();
cy.get(colIdPrice).last().should('be.visible').click();
cy.getByTestId('order-price').should('have.value', '171.16898');
});
+1 -1
View File
@@ -23,7 +23,7 @@ NX_SUCCESSOR_MARKETS=true
NX_STOP_ORDERS=true
NX_ICEBERG_ORDERS=true
# NX_PRODUCT_PERPETUALS
NX_METAMASK_SNAPS=true
NX_METAMASK_SNAPS=false
NX_TENDERMINT_URL=https://tm.be.testnet.vega.xyz
NX_TENDERMINT_WEBSOCKET_URL=wss://be.testnet.vega.xyz/websocket
+4
View File
@@ -17,6 +17,10 @@ NX_CHROME_EXTENSION_URL=https://chrome.google.com/webstore/detail/vega-wallet-ma
NX_MOZILLA_EXTENSION_URL=https://addons.mozilla.org/firefox/addon/vega-wallet-mainnet
NX_ORACLE_PROOFS_URL=https://raw.githubusercontent.com/vegaprotocol/well-known/main/__generated__/oracle-proofs.json
# TAG name of the current app version - TODO: bump to the latest upon release
NX_APP_VERSION=v0.21.2-core-0.72.14
# Cosmic elevator flags
NX_SUCCESSOR_MARKETS=true
NX_STOP_ORDERS=true
+1 -1
View File
@@ -4,7 +4,7 @@ import type {
VegaICellRendererParams,
VegaValueFormatterParams,
} from '@vegaprotocol/datagrid';
import { AgGrid, COL_DEFS } from '@vegaprotocol/datagrid';
import { AgGridLazy as AgGrid, COL_DEFS } from '@vegaprotocol/datagrid';
import { useMemo } from 'react';
import { t } from '@vegaprotocol/i18n';
import type { ProductType } from '@vegaprotocol/types';
@@ -1,5 +1,5 @@
import type { TypedDataAgGrid } from '@vegaprotocol/datagrid';
import { AgGrid, PriceFlashCell } from '@vegaprotocol/datagrid';
import { AgGridLazy as AgGrid, PriceFlashCell } from '@vegaprotocol/datagrid';
import type { MarketMaybeWithData } from '@vegaprotocol/markets';
import { useColumnDefs } from './use-column-defs';
@@ -0,0 +1,83 @@
import { Input, InputError } from '@vegaprotocol/ui-toolkit';
import type { FieldValues } from 'react-hook-form';
import { useForm } from 'react-hook-form';
import classNames from 'classnames';
import { useSearchParams } from 'react-router-dom';
import { useEffect } from 'react';
import { Button } from './buttons';
import { useVegaWallet } from '@vegaprotocol/wallet';
export const ApplyCodeForm = () => {
const { isReadOnly, pubKey, sendTx } = useVegaWallet();
const {
register,
handleSubmit,
formState: { errors },
setValue,
setError,
} = useForm();
const [params] = useSearchParams();
useEffect(() => {
const code = params.get('code');
if (code) setValue('code', code);
}, [params, setValue]);
const onSubmit = ({ code }: FieldValues) => {
if (isReadOnly || !pubKey || !code || code.length === 0) {
return;
}
sendTx(pubKey, {
applyReferralCode: {
id: code as string,
},
})
.then((res) => {
// TODO: Do something with response
})
.catch((err) => {
setError('code', {
type: 'required',
message: 'Your code has been rejected',
});
});
};
return (
<div className="w-1/2 mx-auto">
<h3 className="mb-5 text-xl text-center uppercase calt">
Apply a referral code
</h3>
<p className="mb-6 text-center">Enter a referral code</p>
<form
className={classNames('w-full flex flex-col gap-3', {
'animate-shake': Boolean(errors.code),
})}
onSubmit={handleSubmit(onSubmit)}
>
<label className="flex-grow">
<span className="block mb-1 text-sm text-vega-clight-100 dark:text-vega-cdark-100">
Your referral code
</span>
<Input
hasError={Boolean(errors.code)}
{...register('code', {
required: 'You have to provide a code to apply it.',
})}
/>
</label>
<Button
disabled={isReadOnly || !pubKey}
className="w-full"
type="submit"
>
Apply
</Button>
</form>
{errors.code && (
<InputError>{errors.code.message?.toString()}</InputError>
)}
</div>
);
};
@@ -0,0 +1,83 @@
import { Intent, TradingButton } from '@vegaprotocol/ui-toolkit';
import classNames from 'classnames';
import type { ComponentProps, ButtonHTMLAttributes } from 'react';
import { forwardRef } from 'react';
import { NavLink } from 'react-router-dom';
type RainbowButtonProps = {
variant?: 'full' | 'border';
};
export const RainbowButton = ({
variant = 'full',
children,
className,
...props
}: RainbowButtonProps & ButtonHTMLAttributes<HTMLButtonElement>) => (
<button
className={classNames(
'bg-rainbow hover:bg-none hover:bg-rainbow enabled:hover:bg-vega-pink-500 rounded-lg overflow-hidden disabled:opacity-40',
{
'px-5 py-3 text-white': variant === 'full',
'p-[0.125rem]': variant === 'border',
},
className
)}
{...props}
>
<div
className={classNames({
'bg-white dark:bg-vega-cdark-900 text-black dark:text-white px-5 py-3 rounded-[0.35rem] overflow-hidden':
variant === 'border',
})}
>
{children}
</div>
</button>
);
const RAINBOW_TAB_STYLE = classNames(
'inline-block',
'bg-vega-clight-500 dark:bg-vega-cdark-500 hover:bg-vega-clight-400 dark:hover:bg-vega-cdark-400',
'data-[state="active"]:text-white data-[state="active"]:bg-rainbow data-[state="active"]:hover:bg-none data-[state="active"]:hover:bg-vega-pink-500 dark:data-[state="active"]:hover:bg-vega-pink-500',
'[&.active]:text-white [&.active]:bg-rainbow [&.active]:hover:bg-none [&.active]:hover:bg-vega-pink-500 dark:[&.active]:hover:bg-vega-pink-500',
'px-5 py-3',
'first:rounded-tl-lg last:rounded-tr-lg'
);
export const RainbowTabButton = forwardRef<
HTMLButtonElement,
ButtonHTMLAttributes<HTMLButtonElement>
>(({ children, ...props }, ref) => (
<button ref={ref} className={RAINBOW_TAB_STYLE} {...props}>
{children}
</button>
));
RainbowTabButton.displayName = 'RainbowTabButton';
export const RainbowTabLink = ({
to,
children,
...props
}: ComponentProps<typeof NavLink>) => (
<NavLink to={to} className={RAINBOW_TAB_STYLE} {...props}>
{children}
</NavLink>
);
export const Button = forwardRef<
HTMLButtonElement,
ComponentProps<typeof TradingButton>
>(({ children, intent, type, ...props }, ref) => {
return (
<TradingButton
ref={ref}
intent={intent || type === 'submit' ? Intent.Primary : Intent.None}
type={type}
{...props}
>
{children}
</TradingButton>
);
});
Button.displayName = 'TradingButton';
@@ -0,0 +1,6 @@
export const BORDER_COLOR = 'border-vega-clight-500 dark:border-vega-cdark-500';
export const GRADIENT =
'bg-gradient-to-b from-vega-clight-800 dark:from-vega-cdark-800 to-transparent';
export const SKY_BACKGROUND =
'bg-[url(/sky-light.png)] dark:bg-[url(/sky-dark.png)] bg-[40%_0px] bg-[length:1440px] bg-no-repeat';
@@ -0,0 +1,248 @@
import {
useVegaWallet,
useVegaWalletDialogStore,
determineId,
} from '@vegaprotocol/wallet';
import { RainbowButton } from './buttons';
import { useState } from 'react';
import {
CopyWithTooltip,
Dialog,
ExternalLink,
InputError,
Intent,
TradingAnchorButton,
TradingButton,
VegaIcon,
VegaIconNames,
} from '@vegaprotocol/ui-toolkit';
import { gql, useQuery } from '@apollo/client';
import { addDecimalsFormatNumber } from '@vegaprotocol/utils';
import { DApp, TokenStaticLinks, useLinks } from '@vegaprotocol/environment';
const CREATE_CODE_QUERY = gql`
query CreateCode($partyId: ID!) {
party(id: $partyId) {
stakingSummary {
currentStakeAvailable
}
}
networkParameter(key: "referralProgram.minStakedVegaTokens") {
value
}
}
`;
export const CreateCodeContainer = () => {
const { pubKey } = useVegaWallet();
const { data, loading } = useQuery(CREATE_CODE_QUERY, {
variables: { partyId: pubKey || '' },
skip: !pubKey,
// TOOD: remove when network params available
errorPolicy: 'ignore',
});
if (loading) return null;
const requiredStake = data?.networkParameter?.value || '0';
const currentStakeAvailable =
data?.party?.stakingSummary.currentStakeAvailable || '0';
return (
<CreateCodeForm
currentStakeAvailable={currentStakeAvailable}
requiredStake={requiredStake}
/>
);
};
export const CreateCodeForm = ({
currentStakeAvailable,
requiredStake,
}: {
currentStakeAvailable: string;
requiredStake: string;
}) => {
const [dialogOpen, setDialogOpen] = useState(false);
const openWalletDialog = useVegaWalletDialogStore(
(store) => store.openVegaWalletDialog
);
const { pubKey } = useVegaWallet();
return (
<div className="w-1/2 mx-auto">
<h3 className="mb-5 text-xl text-center uppercase calt">
Create a referral code
</h3>
<p className="mb-6 text-center">
Generate a referral code to share with your friends and start earning
commission.
</p>
<div className="mb-5">
<div className="text-center">
<RainbowButton
variant="border"
onClick={() => {
if (pubKey) {
setDialogOpen(true);
} else {
openWalletDialog();
}
}}
>
{pubKey ? 'Create a referral code' : 'Connect wallet'}
</RainbowButton>
</div>
</div>
<Dialog
title="Create a referral code"
open={dialogOpen}
onChange={() => setDialogOpen(false)}
size="small"
>
<CreateCodeDialog
currentStakeAvailable={currentStakeAvailable}
setDialogOpen={setDialogOpen}
requiredStake={requiredStake}
/>
</Dialog>
</div>
);
};
const CreateCodeDialog = ({
setDialogOpen,
currentStakeAvailable,
requiredStake,
}: {
setDialogOpen: (open: boolean) => void;
currentStakeAvailable: string;
requiredStake: string;
}) => {
const createLink = useLinks(DApp.Governance);
const { isReadOnly, pubKey, sendTx } = useVegaWallet();
const [err, setErr] = useState<string | null>(null);
const [code, setCode] = useState<string | null>(null);
const [status, setStatus] = useState<
'idle' | 'loading' | 'success' | 'error'
>('idle');
const onSubmit = () => {
if (isReadOnly || !pubKey) {
setErr('Not connected');
} else {
setErr(null);
setStatus('loading');
setCode(null);
sendTx(pubKey, {
createReferralSet: {
isTeam: false,
},
})
.then((res) => {
if (!res) {
setErr(`Invalid response: ${JSON.stringify(res)}`);
return;
}
const code = determineId(res.signature);
setCode(code);
setStatus('success');
})
.catch((err) => {
if (err.message.includes('user rejected')) {
setStatus('idle');
return;
}
setStatus('error');
setErr(err.message);
});
}
};
const getButtonProps = () => {
if (status === 'idle' || status === 'error') {
return {
children: 'Generate code',
onClick: () => onSubmit(),
};
}
if (status === 'loading') {
return {
children: 'Confirm in wallet...',
disabled: true,
};
}
if (status === 'success') {
return {
children: 'Close',
intent: Intent.Success,
onClick: () => setDialogOpen(false),
};
}
};
// TODO: Add when network parameters are updated
if (
currentStakeAvailable === '0' ||
BigInt(currentStakeAvailable) < BigInt(requiredStake)
) {
return (
<div className="flex flex-col gap-4">
<p>
You need at least {addDecimalsFormatNumber(requiredStake, 18)} VEGA
staked to generate a referral code and participate in the referral
program.
</p>
<TradingAnchorButton
href={createLink(TokenStaticLinks.ASSOCIATE)}
intent={Intent.Primary}
target="_blank"
>
Stake some $VEGA now
</TradingAnchorButton>
</div>
);
}
return (
<div className="flex flex-col gap-4">
{(status === 'idle' || status === 'loading' || status === 'error') && (
<p>
Generate a referral code to share with your friends aand start enaring
commission.
</p>
)}
{status === 'success' && code && (
<div className="flex items-center gap-2">
<div className="flex-1 min-w-0 p-2 text-sm rounded bg-vega-clight-700 dark:bg-vega-cdark-700">
<p className="overflow-hidden whitespace-nowrap text-ellipsis">
{code}
</p>
</div>
<CopyWithTooltip text={code}>
<TradingButton
className="text-sm no-underline"
icon={<VegaIcon name={VegaIconNames.COPY} />}
>
<span>Copy</span>
</TradingButton>
</CopyWithTooltip>
</div>
)}
<TradingButton
fill={true}
intent={Intent.Primary}
{...getButtonProps()}
/>
{err && <InputError>{err}</InputError>}
{/* TODO: Add links */}
<div className="flex justify-center pt-5 mt-2 text-sm border-t gap-4 text-default border-default">
<ExternalLink>About the referral program</ExternalLink>
<ExternalLink>Disclaimer</ExternalLink>
</div>
</div>
);
};
@@ -0,0 +1,46 @@
import { isRouteErrorResponse, useNavigate, useRouteError } from 'react-router';
import { RainbowButton } from './buttons';
import { AnimatedDudeWithWire } from './graphics/dude';
import { LayoutWithSky } from './layout';
export const ErrorBoundary = () => {
const error = useRouteError();
const navigate = useNavigate();
const title = isRouteErrorResponse(error)
? `${error.status} ${error.statusText}`
: 'Something went wrong';
const code = isRouteErrorResponse(error) ? error.status : 0;
const messages: Record<number, string> = {
0: 'An unknown error occurred.',
404: "The page you're looking for doesn't exists.",
};
return (
<LayoutWithSky className="pt-32">
<div
aria-hidden
className="absolute top-64 right-[220px] md:right-[340px] max-sm:hidden"
>
<AnimatedDudeWithWire className="animate-spin" />
</div>
<h1 className="text-6xl font-alpha calt mb-10">{title}</h1>
{Object.keys(messages).includes(code.toString()) ? (
<p className="text-lg mb-10">{messages[code]}</p>
) : null}
<p className="text-lg mb-10">
<RainbowButton
onClick={() => navigate('..')}
variant="border"
className="text-xs"
>
Go back and try again
</RainbowButton>
</p>
</LayoutWithSky>
);
};
@@ -0,0 +1,70 @@
import classNames from 'classnames';
import type { HTMLAttributes } from 'react';
export const Dude = ({ className }: HTMLAttributes<SVGElement>) => {
return (
<svg
width="41"
height="47"
viewBox="0 0 41 47"
fill="none"
className={className}
>
<path
d="M21.1895 0.298767L5.08827 27.4101L8.96133 29.7103L4.36099 37.4564L8.23404 39.7566L12.8344 32.0105L16.7074 34.3107L12.1071 42.0568L15.9801 44.3569L20.5805 36.6108L24.4535 38.911L40.5547 11.7996L21.1895 0.298767Z"
className="fill-black dark:fill-white"
/>
<path
d="M35.9346 15.1683L20.4424 5.96765L14.3086 16.2958L29.8008 25.4965L35.9346 15.1683Z"
className="fill-white dark:fill-black"
/>
<path
d="M25.646 17.7895L23.064 16.2561L21.5305 18.8381L24.1126 20.3716L25.646 17.7895Z"
className="fill-black dark:fill-white"
/>
<path
d="M29.7612 16.7412L27.1792 15.2077L25.6458 17.7898L28.2278 19.3232L29.7612 16.7412Z"
className="fill-black dark:fill-white"
/>
<path
d="M33.877 15.6925L31.2949 14.159L29.7615 16.7411L32.3435 18.2745L33.877 15.6925Z"
className="fill-black dark:fill-white"
/>
<path
d="M29.0342 26.7874L26.4521 25.2539L24.9187 27.836L27.5007 29.3694L29.0342 26.7874Z"
fill="#FF077F"
/>
</svg>
);
};
export const Wire = ({ className }: HTMLAttributes<SVGElement>) => {
return (
<svg
width="157"
height="88"
viewBox="0 0 157 88"
fill="none"
className={className}
>
<path
d="M109.398 6.12235C127.37 -3.81898 146.791 1.45045 153.465 14.307C160.138 27.1636 154.195 43.9948 140.438 52.1164C126.68 60.238 105.767 54.9998 84.9212 43.464C64.0752 31.9281 32.2412 6.42016 18.8175 24.185C6.90871 40.719 41.9332 68.4495 29.2664 82.7049C23.187 88.4974 11.1379 88.2645 0.968295 80.3398"
className="stroke-black dark:stroke-white"
strokeWidth="1.5"
strokeMiterlimit="10"
/>
</svg>
);
};
export const AnimatedDudeWithWire = ({ className }: { className?: string }) => (
<div className="relative">
<Wire className="absolute top-[25px]" />
<Dude
className={classNames(
'absolute left-[96px] animate-[wave_20s_ease-in-out_infinite]',
className
)}
/>
</div>
);
@@ -0,0 +1,83 @@
import { gql } from '@apollo/client';
import { getNumberFormat } from '@vegaprotocol/utils';
import { addDays } from 'date-fns';
// TODO: Generate query
// eslint-disable-next-line
const REFERRAL_PROGRAM_QUERY = gql`
query ReferralProgram {
currentReferralProgram {
id
version
endOfProgramTimestamp
windowLength
endedAt
benefitTiers {
minimumEpochs
minimumRunningNotionalTakerVolume
referralDiscountFactor
referralRewardFactor
}
stakingTiers {
minimumStakedTokens
referralRewardMultiplier
}
}
}
`;
export const useReferralProgram = () => {
// TODO: get real data
// const { data, loading, error } = useQuery(REFERRAL_PROGRAM_QUERY, {
// fetchPolicy: 'cache-and-network',
// });
const dummyData = {
currentReferralProgram: {
id: 'abc',
version: 1,
endOfProgramTimestamp: addDays(new Date(), 10).toISOString(),
windowLength: 10,
benefitTiers: [
{
minimumEpochs: 5,
minimumRunningNotionalTakerVolume: '30000',
referralDiscountFactor: '0.01',
referralRewardFactor: '0.01',
},
{
minimumEpochs: 5,
minimumRunningNotionalTakerVolume: '20000',
referralDiscountFactor: '0.05',
referralRewardFactor: '0.05',
},
{
minimumEpochs: 5,
minimumRunningNotionalTakerVolume: '10000',
referralDiscountFactor: '0.001',
referralRewardFactor: '0.001',
},
],
},
} as const;
const benefitTiers = dummyData.currentReferralProgram.benefitTiers.map(
(t, i) => {
return {
tier: i + 1,
commission: Number(t.referralRewardFactor) * 100 + '%',
discount: Number(t.referralDiscountFactor) * 100 + '%',
volume: getNumberFormat(0).format(
Number(t.minimumRunningNotionalTakerVolume)
),
};
}
);
// TODO: return real loading, error values
return {
benefitTiers,
loading: false,
error: undefined,
};
};
@@ -0,0 +1,93 @@
import { gql, useQuery } from '@apollo/client';
const REFERRAL_QUERY = gql`
query ReferralSets($partyId: ID!) {
referralSets(referrer: $partyId) {
edges {
node {
id
referrer
createdAt
updatedAt
}
}
}
}
`;
const REFEREES_QUERY = gql`
query ReferralSets($code: ID!) {
referralSetReferees(id: $code) {
edges {
node {
referralSetId
refereeId
joinedAt
atEpoch
}
}
}
}
`;
// Fetches the current user's referral and then fetches all referees using
// the referral code
export const useReferral = (pubKey: string) => {
const {
data: referralData,
loading: referralLoading,
error: referralError,
} = useQuery(REFERRAL_QUERY, {
variables: {
partyId: pubKey,
},
fetchPolicy: 'cache-and-network',
});
// A user can only have 1 active referral program at a time
const referral = referralData?.referralSets.edges.length
? referralData.referralSets.edges[0].node
: undefined;
const {
data: refereesData,
loading: refereesLoading,
error: refereesError,
} = useQuery(REFEREES_QUERY, {
variables: {
code: referral?.id,
},
skip: !referral?.id,
fetchPolicy: 'cache-and-network',
});
const referees = refereesData?.referralSetReferees.edges?.map(
// TODO: generate types
// eslint-disable-next-line @typescript-eslint/no-explicit-any
(e: any) => e.node
);
const data =
referral && refereesData
? {
code: referral.id,
referees,
}
: undefined;
return {
// TODO: generate types after perps work is merged
data: data as
| {
code: string;
referees: Array<{
refereeId: string;
joinedAt: string;
atEpoch: number;
}>;
}
| undefined,
loading: referralLoading || refereesLoading,
error: referralError || refereesError,
};
};
@@ -0,0 +1,52 @@
import { Table } from './table';
export const HowItWorksTable = () => (
<Table
className="bg-none bg-vega-clight-800 dark:bg-vega-cdark-800"
noHeader
noCollapse
columns={[{ name: 'number', className: 'px-0 pl-5' }, { name: 'step' }]}
data={[
{
number: (
<span className="text-2xl calt text-vega-clight-100 dark:text-vega-cdark-100">
1
</span>
),
step: 'Referrers generate a code assigned to their key via an on chain transaction',
},
{
number: (
<span className="text-2xl calt text-vega-clight-100 dark:text-vega-cdark-100">
2
</span>
),
step: 'Anyone with the referral link can apply it to their key(s) of choice via an on chain transaction',
},
{
number: (
<span className="text-2xl calt text-vega-clight-100 dark:text-vega-cdark-100">
3
</span>
),
step: 'Discounts are applied automatically during trading based on the key(s) used',
},
{
number: (
<span className="text-2xl calt text-vega-clight-100 dark:text-vega-cdark-100">
4
</span>
),
step: 'Referrers earn commission based on a percentage of the taker fees their referees pay',
},
{
number: (
<span className="text-2xl calt text-vega-clight-100 dark:text-vega-cdark-100">
5
</span>
),
step: 'The commission is taken from the infrastructure fee, maker fee, and liquidity provider fee, not from the referee',
},
]}
></Table>
);
@@ -0,0 +1,31 @@
import classNames from 'classnames';
import { AnimatedDudeWithWire } from './graphics/dude';
export const LandingBanner = () => {
return (
<div className={classNames('relative mb-20')}>
<div className="">
<div
aria-hidden
className="absolute top-64 right-[220px] md:right-[340px] max-sm:hidden"
>
<AnimatedDudeWithWire />
</div>
<div className="pt-32 sm:w-[50%]">
<h1 className="text-6xl font-alpha calt mb-10">
Earn commission & stake rewards
</h1>
<p className="text-lg mb-10">
Invite friends and earn commission in the form of Vega rewards from
the trading fees they pay. Stake those rewards to earn multipliers
on future rewards.
</p>
<p className="text-lg">
Any friends that join using the code will receive discounts off
trading fees.
</p>
</div>
</div>
</div>
);
};
@@ -0,0 +1,36 @@
import classNames from 'classnames';
import type { HTMLAttributes } from 'react';
import { SKY_BACKGROUND } from './constants';
import { Outlet } from 'react-router-dom';
export const Layout = ({
className,
children,
...props
}: HTMLAttributes<HTMLDivElement>) => {
return (
<div
className={classNames(
'max-w-[1440px]',
'mx-auto px-16 md:px-32 pb-32',
'relative z-0',
'h-full overflow-auto',
className
)}
{...props}
>
{children || <Outlet />}
</div>
);
};
export const LayoutWithSky = ({
className,
...props
}: HTMLAttributes<HTMLDivElement>) => {
return (
<div className={classNames('h-full', SKY_BACKGROUND)}>
<Layout className={className} {...props} />
</div>
);
};
@@ -0,0 +1,126 @@
import { Tile } from './tile';
import {
CopyWithTooltip,
Input,
VegaIcon,
VegaIconNames,
} from '@vegaprotocol/ui-toolkit';
import { Button, RainbowButton } from './buttons';
import { Tag } from './tag';
import { useVegaWallet, useVegaWalletDialogStore } from '@vegaprotocol/wallet';
import { useReferral } from './hooks/use-referral';
import { Link } from 'react-router-dom';
export const ReferralStatistics = () => {
const openWalletDialog = useVegaWalletDialogStore(
(store) => store.openVegaWalletDialog
);
const { pubKey } = useVegaWallet();
if (pubKey) {
return <ReferralStatisticsContainer pubKey={pubKey} />;
}
return (
<div className="text-center">
<RainbowButton variant="border" onClick={() => openWalletDialog()}>
Connect wallet
</RainbowButton>
</div>
);
};
const ReferralStatisticsContainer = ({ pubKey }: { pubKey: string }) => {
const { data } = useReferral(pubKey);
return (
<div className="grid grid-cols-1 grid-rows-1 gap-5">
<div className="grid grid-cols-3 grid-rows-1 gap-5">
<Tile>
<h3 className="mb-1 text-4xl text-center">3</h3>
<p className="mb-3 text-sm text-center text-vega-clight-100 dark:text-vega-cdark-100">
Your referral tier
</p>
<Tag className="mx-auto" color="purple">
10,000 until Tier 2
</Tag>
</Tile>
<Tile>
<h3 className="mb-1 text-4xl text-center">0.1%</h3>
<p className="mb-3 text-sm text-center text-vega-clight-100 dark:text-vega-cdark-100">
Your commission
</p>
<Tag className="mx-auto" color="purple">
10,000 until 0.5%x
</Tag>
</Tile>
{data?.code ? (
<Tile variant="rainbow">
<h3 className="mb-1 text-lg calt">Your referral code</h3>
<p className="mb-3 text-sm text-vega-clight-100 dark:text-vega-cdark-100">
Share this code with friends
</p>
<div className="flex gap-2">
<Input size={1} readOnly value={data.code} />
<CopyWithTooltip text={data.code}>
<Button
className="text-sm no-underline"
icon={<VegaIcon name={VegaIconNames.COPY} />}
>
<span>Copy</span>
</Button>
</CopyWithTooltip>
</div>
</Tile>
) : (
<Tile variant="rainbow">
<h3 className="mb-1 text-lg calt">
Create referral code to start earning rewards
</h3>
<p className="mb-3 text-sm text-vega-clight-100 dark:text-vega-cdark-100">
Invite friends and earn commission
</p>
<p>
<Link to="create-code" className="underline underline-offset-4">
Create a referral code
</Link>
</p>
</Tile>
)}
</div>
<div className="grid grid-cols-4 grid-rows-1 gap-5">
<Tile className="py-3">
<h3 className="mb-1 text-2xl text-center">10,000</h3>
<p className="text-sm text-center text-vega-clight-100 dark:text-vega-cdark-100">
Your total trading volume
</p>
</Tile>
{data?.code && (
<Tile className="py-3">
<h3 className="mb-1 text-2xl text-center">
{data.referees.length}
</h3>
<p className="text-sm text-center text-vega-clight-100 dark:text-vega-cdark-100">
{data.referees.length === 1
? 'Trader referred'
: 'Total traders referred'}
</p>
</Tile>
)}
<Tile className="py-3">
<h3 className="mb-1 text-2xl text-center">0.1%</h3>
<p className="text-sm text-center text-vega-clight-100 dark:text-vega-cdark-100">
Maximum trader discount
</p>
</Tile>
<Tile className="py-3">
<h3 className="mb-1 text-2xl text-center">10,000</h3>
<p className="text-sm text-center text-vega-clight-100 dark:text-vega-cdark-100">
Your commission (last 30d)
</p>
</Tile>
</div>
</div>
);
};
@@ -0,0 +1,151 @@
import {
TradingAnchorButton,
VegaIcon,
VegaIconNames,
} from '@vegaprotocol/ui-toolkit';
import classNames from 'classnames';
import { HowItWorksTable } from './how-it-works-table';
import { LandingBanner } from './landing-banner';
import { TiersContainer } from './tiers-table';
import { BORDER_COLOR, GRADIENT } from './constants';
import { RainbowTabLink } from './buttons';
import { Outlet } from 'react-router-dom';
import { Tag } from './tag';
import { Routes } from '../../pages/client-router';
export const Referrals = () => {
return (
<>
<LandingBanner />
<div>
<div className="flex justify-center">
<RainbowTabLink end to={Routes.REFERRALS}>
Your referrals
</RainbowTabLink>
<RainbowTabLink to={Routes.REFERRALS_CREATE_CODE}>
Your referrals
</RainbowTabLink>
<RainbowTabLink to={Routes.REFERRALS_APPLY_CODE}>
Apply a code
</RainbowTabLink>
</div>
<div className="py-16 border-t border-b border-vega-cdark-500">
<Outlet />
</div>
</div>
<div className="flex flex-row items-baseline justify-between mt-10 mb-5">
<h2 className="text-2xl">Referral tiers</h2>
<span className="text-base">
<span className="text-vega-clight-200 dark:text-vega-cdark-200">
Program ends:
</span>{' '}
16 epochs
</span>
</div>
<div className="mb-20">
<TiersContainer />
</div>
<div className="flex flex-row items-baseline justify-between mb-5">
<h2 className="text-2xl">Staking multipliers</h2>
</div>
<div className="flex flex-col mb-20 jjustify-items-stretch md:flex-row gap-5">
<div
className={classNames(
'overflow-hidden',
'border rounded-md w-full',
BORDER_COLOR
)}
>
<div aria-hidden>
{/* eslint-disable-next-line @next/next/no-img-element */}
<img
src="/1x.png"
alt="1x multiplier"
width={768}
height={400}
className="w-full"
/>
</div>
<div className={classNames('p-3', GRADIENT)}>
<h3 className="mb-3 text-xl">Tradestarter</h3>
<p className="text-base text-vega-clight-100 dark:text-vega-cdark-100">
Stake a minimum of 100 $VEGA tokens
</p>
<Tag color="green">Reward multiplier 1x</Tag>
</div>
</div>
<div
className={classNames(
'overflow-hidden',
'border rounded-md w-full',
BORDER_COLOR
)}
>
<div aria-hidden>
{/* eslint-disable-next-line @next/next/no-img-element */}
<img
src="/2x.png"
alt="2x multiplier"
width={768}
height={400}
className="w-full"
/>
</div>
<div className={classNames('p-3', GRADIENT)}>
<h3 className="mb-3 text-xl">Mid level degen</h3>
<p className="text-base text-vega-clight-100 dark:text-vega-cdark-100">
Stake a minimum of 1000 $VEGA tokens
</p>
<Tag color="blue">Reward multiplier 2x</Tag>
</div>
</div>
<div
className={classNames(
'overflow-hidden',
'border rounded-md w-full',
BORDER_COLOR
)}
>
<div aria-hidden>
{/* eslint-disable-next-line @next/next/no-img-element */}
<img
src="/3x.png"
alt="3x multiplier"
width={768}
height={400}
className="w-full"
/>
</div>
<div className={classNames('p-3', GRADIENT)}>
<h3 className="mb-3 text-xl">Reward hoarder</h3>
<p className="text-base text-vega-clight-100 dark:text-vega-cdark-100">
Stake a minimum of 10,000 $VEGA tokens
</p>
<Tag color="pink">Reward multiplier 3x</Tag>
</div>
</div>
</div>
<div className="mt-10 mb-5 text-center">
<h2 className="text-2xl">How it works</h2>
</div>
<div className="md:w-[60%] mx-auto">
<HowItWorksTable />
<div className="mt-5">
<TradingAnchorButton
className="mx-auto w-max"
href="https://docs.vega.xyz/"
target="_blank"
>
Read the terms <VegaIcon name={VegaIconNames.OPEN_EXTERNAL} />
</TradingAnchorButton>
</div>
</div>
</>
);
};
export default Referrals;
@@ -0,0 +1,116 @@
import { Tooltip, VegaIcon, VegaIconNames } from '@vegaprotocol/ui-toolkit';
import classNames from 'classnames';
import type { HTMLAttributes } from 'react';
import { BORDER_COLOR, GRADIENT } from './constants';
type TableColumnDefinition = {
displayName?: string;
name: string;
tooltip?: string;
className?: string;
};
type TableProps = {
columns: TableColumnDefinition[];
data: Record<TableColumnDefinition['name'] | 'className', React.ReactNode>[];
noHeader?: boolean;
noCollapse?: boolean;
};
const INNER_BORDER_STYLE = `border-b ${BORDER_COLOR}`;
export const Table = ({
columns,
data,
noHeader = false,
noCollapse = false,
className,
...props
}: TableProps & HTMLAttributes<HTMLTableElement>) => {
const header = (
<thead className={classNames({ 'max-md:hidden': !noCollapse })}>
<tr>
{columns.map(({ displayName, name, tooltip }) => (
<th
key={name}
col-id={name}
className={classNames(
'px-5 py-3 text-sm text-vega-clight-100 dark:text-vega-cdark-100',
INNER_BORDER_STYLE
)}
>
<span className="flex flex-row gap-2 items-center">
<span>{displayName}</span>
{tooltip ? (
<Tooltip description={tooltip}>
<button className="text-vega-clight-400 dark:text-vega-cdark-400 no-underline decoration-transparent w-[12px] h-[12px] inline-flex">
<VegaIcon size={12} name={VegaIconNames.INFO} />
</button>
</Tooltip>
) : null}
</span>
</th>
))}
</tr>
</thead>
);
return (
<table
className={classNames(
'w-full',
'border-separate border rounded-md border-spacing-0',
BORDER_COLOR,
GRADIENT,
className
)}
{...props}
>
{!noHeader && header}
<tbody>
{data.map((d, i) => (
<tr
key={i}
className={classNames(d['className'] as string, {
'max-md:flex flex-col w-full': !noCollapse,
})}
>
{columns.map(({ name, displayName, className }, j) => (
<td
className={classNames(
'px-5 py-3 text-base',
{
'max-md:flex max-md:flex-col max-md:justify-between':
!noCollapse,
},
INNER_BORDER_STYLE,
{
'border-none': i === data.length - 1 && noCollapse,
'md:border-none': i === data.length - 1,
'max-md:border-none':
i === data.length - 1 && j === columns.length - 1,
},
className
)}
key={`${i}-${name}`}
>
{/** display column name in mobile view */}
{!noCollapse &&
!noHeader &&
displayName &&
displayName.length > 0 && (
<span
aria-hidden
className="md:hidden font-mono text-xs px-0 text-vega-clight-100 dark:text-vega-cdark-100"
>
{displayName}
</span>
)}
<span>{d[name]}</span>
</td>
))}
</tr>
))}
</tbody>
</table>
);
};
@@ -0,0 +1,32 @@
import type { HTMLAttributes } from 'react';
import classNames from 'classnames';
type TagProps = {
color?: 'yellow' | 'green' | 'blue' | 'purple' | 'pink' | 'orange' | 'none';
};
export const Tag = ({
color = 'none',
children,
className,
...props
}: TagProps & HTMLAttributes<HTMLDivElement>) => (
<div
className={classNames(
'mt-3 w-max border rounded-[1rem] py-[0.125rem] px-2 text-xs',
{
'border-vega-yellow-500 text-vega-yellow-500': color === 'yellow',
'border-vega-green-500 text-vega-green-500': color === 'green',
'border-vega-blue-500 text-vega-blue-500': color === 'blue',
'border-vega-purple-500 text-vega-purple-500': color === 'purple',
'border-vega-pink-500 text-vega-pink-500': color === 'pink',
'border-vega-orange-500 text-vega-orange-500': color === 'orange',
'border-vega-clight-100 text-vega-clight-100 dark:border-vega-cdark-100 dark:text-vega-cdark-100':
color === 'none',
},
className
)}
{...props}
>
{children}
</div>
);
@@ -0,0 +1,39 @@
import { useReferralProgram } from './hooks/use-referral-program';
import { Table } from './table';
export const TiersContainer = () => {
const { benefitTiers } = useReferralProgram();
return <TiersTable data={benefitTiers} />;
};
const TiersTable = ({
data,
}: {
data: Array<{
tier: number;
commission: string;
discount: string;
volume: string;
}>;
}) => {
return (
<Table
columns={[
{ name: 'tier', displayName: 'Tier' },
{
name: 'commission',
displayName: 'Referrer commission',
tooltip: 'A percentage of commission earned by the referrer',
},
{ name: 'discount', displayName: 'Referrer trading discount' },
{ name: 'volume', displayName: 'Min. trading volume' },
]}
data={data.map((d) => ({
...d,
className:
'from-vega-pink-400 dark:from-vega-pink-600 to-20% bg-highlight',
}))}
/>
);
};
@@ -0,0 +1,39 @@
import classNames from 'classnames';
import type { HTMLAttributes } from 'react';
import { BORDER_COLOR, GRADIENT } from './constants';
type TileProps = {
variant?: 'rainbow' | 'default';
};
export const Tile = ({
variant = 'default',
className,
children,
}: TileProps & HTMLAttributes<HTMLDivElement>) => {
return (
<div
className={classNames(
{
'bg-rainbow p-[0.125rem]': variant === 'rainbow',
[`border-2 ${BORDER_COLOR} p-0`]: variant === 'default',
},
'rounded-lg overflow-hidden'
)}
>
<div
className={classNames(
{
'bg-white dark:bg-vega-cdark-900 text-black dark:text-white rounded-[0.35rem] overflow-hidden':
variant === 'rainbow',
},
'p-6',
GRADIENT,
className
)}
>
{children}
</div>
</div>
);
};
@@ -9,11 +9,5 @@ export const AnnouncementBanner = () => {
return null;
}
return (
<Banner
app="console"
configUrl={ANNOUNCEMENTS_CONFIG_URL}
background="url('/banner-bg.jpg')"
/>
);
return <Banner app="console" configUrl={ANNOUNCEMENTS_CONFIG_URL} />;
};
+1
View File
@@ -1 +1,2 @@
export * from './announcement-banner';
export * from './upgrade-banner';
@@ -0,0 +1,84 @@
import { useMemo, useState } from 'react';
import { gt, prerelease } from 'semver';
import {
ReleasesFeed,
useEnvironment,
useReleases,
Networks,
} from '@vegaprotocol/environment';
import { t } from '@vegaprotocol/i18n';
import {
CopyWithTooltip,
ExternalLink,
Intent,
NotificationBanner,
VegaIcon,
VegaIconNames,
} from '@vegaprotocol/ui-toolkit';
// v0.20.12-core-0.71.4 -> v0.20.12
// we need to strip the "core" suffix in order to determine whether a release
// is a pre-release (candidate); example: v.0.21.0-beta.1-core-0.71.4
const parseTagName = (tagName: string) => tagName.replace(/-core-[\d.]+$/i, '');
type UpgradeBannerProps = {
showVersionChange: boolean;
};
export const UpgradeBanner = ({ showVersionChange }: UpgradeBannerProps) => {
const [visible, setVisible] = useState(true);
const { data } = useReleases(ReleasesFeed.FrontEnd);
const { APP_VERSION, VEGA_ENV, VEGA_NETWORKS } = useEnvironment();
const CANONICAL_URL = VEGA_NETWORKS[VEGA_ENV] || 'https://console.vega.xyz';
const newest = useMemo(() => {
if (!APP_VERSION || !data) return undefined;
const newer = data.filter((r) => gt(r.tagName, APP_VERSION));
const valid =
// filter pre-releases on mainnet
VEGA_ENV === Networks.MAINNET
? newer?.filter((r) => !prerelease(parseTagName(r.tagName)))
: newer;
return valid.sort((a, b) => (gt(a.tagName, b.tagName) ? -1 : 1))[0];
}, [APP_VERSION, VEGA_ENV, data]);
if (!visible || !newest) {
return null;
}
return (
<NotificationBanner
intent={Intent.Warning}
onClose={() => {
setVisible(false);
}}
>
<div className="uppercase mb-1">
<ExternalLink href={CANONICAL_URL}>
{t('Upgrade to the latest version of Console')}
</ExternalLink>
</div>
<div data-testid="bookmark-message">
<a
className="underline"
href={newest.htmlUrl}
rel="noreferrer nofollow noopener"
target="_blank"
>
{t("View what's changed")}
</a>{' '}
{t(' or bookmark')}{' '}
<a className="underline" href={CANONICAL_URL}>
{t('console.vega.xyz')}
</a>{' '}
<CopyWithTooltip text={CANONICAL_URL}>
<button title={t('Copy %s', CANONICAL_URL)}>
<span className="sr-only">{t('Copy %s', CANONICAL_URL)}</span>
<VegaIcon size={14} name={VegaIconNames.COPY} />
</button>
</CopyWithTooltip>{' '}
{'to always see the latest version.'}
</div>
</NotificationBanner>
);
};
@@ -180,6 +180,11 @@ const NavbarMenu = ({ onClick }: { onClick: () => void }) => {
{t('Trading')}
</NavbarLink>
</NavbarItem>
<NavbarItem>
<NavbarLink to={Links[Routes.REFERRALS]()} onClick={onClick}>
{t('Referrals')}
</NavbarLink>
</NavbarItem>
<NavbarItem>
<NavbarLink to={Links[Routes.PORTFOLIO]()} onClick={onClick}>
{t('Portfolio')}
+2 -1
View File
@@ -38,7 +38,7 @@ import { AppLoader, DynamicLoader } from '../components/app-loader';
import { useDataProvider } from '@vegaprotocol/data-provider';
import { activeOrdersProvider } from '@vegaprotocol/orders';
import { useTelemetryApproval } from '../lib/hooks/use-telemetry-approval';
import { AnnouncementBanner } from '../components/banner';
import { AnnouncementBanner, UpgradeBanner } from '../components/banner';
import { Navbar } from '../components/navbar';
import classNames from 'classnames';
import {
@@ -115,6 +115,7 @@ function AppBody({ Component }: AppProps) {
/>
<ProtocolUpgradeInProgressNotification />
<ViewingBanner />
<UpgradeBanner showVersionChange={true} />
</div>
<div data-testid={`pathname-${location.pathname}`}>
<Component />
+17 -8
View File
@@ -4,24 +4,33 @@ export default function Document() {
return (
<>
<Head>
{/*
{/*
meta tags
- next advised against using _document for this, so they exist in our
- next advised against using _document for this, so they exist in our
- single page index.page.tsx
*/}
{/* preload fonts */}
{/* icons */}
<link
rel="icon"
type="image/x-icon"
href="https://static.vega.xyz/favicon.ico"
/>
<link
rel="apple-touch-icon"
content="https://static.vega.xyz/favicon.ico"
/>
{/* fonts */}
<link
rel="preload"
href="/AlphaLyrae-Medium.woff2"
href="https://static.vega.xyz/AlphaLyrae-Medium.woff2"
as="font"
type="font/woff2"
/>
{/* icons */}
<link rel="icon" type="image/x-icon" href="/favicon.ico" />
<link rel="apple-touch-icon" content="/favicon.ico" />
{/* styles */}
<link rel="stylesheet" href="https://static.vega.xyz/fonts.css" />
{/* eslint-disable-next-line @next/next/no-css-tags */}
<link rel="stylesheet" href="/preloader.css" media="all" />
+44 -1
View File
@@ -6,6 +6,10 @@ import { t } from '@vegaprotocol/i18n';
import { Loader, Splash } from '@vegaprotocol/ui-toolkit';
import trimEnd from 'lodash/trimEnd';
import { LayoutWithSidebar } from '../components/layouts';
import { LayoutWithSky } from '../client-pages/referrals/layout';
import { ReferralStatistics } from '../client-pages/referrals/referral-statistics';
import { ApplyCodeForm } from '../client-pages/referrals/apply-code-form';
import { CreateCodeContainer } from '../client-pages/referrals/create-code-form';
const LazyHome = dynamic(() => import('../client-pages/home'), {
ssr: false,
@@ -35,6 +39,13 @@ const LazyDeposit = dynamic(() => import('../client-pages/deposit'), {
ssr: false,
});
const LazyReferrals = dynamic(
() => import('../client-pages/referrals/referrals'),
{
ssr: false,
}
);
export enum Routes {
HOME = '/',
MARKET = '/markets/:marketId',
@@ -43,6 +54,10 @@ export enum Routes {
LIQUIDITY = '/liquidity/:marketId',
DISCLAIMER = '/disclaimer',
DEPOSIT = '/deposit',
REFERRALS = '/referrals',
REFERRALS_APPLY_CODE = '/referrals/apply-code',
REFERRALS_CREATE_CODE = '/referrals/create-code',
TEAMS = '/teams',
}
type ConsoleLinks = { [r in Routes]: (...args: string[]) => string };
@@ -57,6 +72,10 @@ export const Links: ConsoleLinks = {
trimEnd(Routes.LIQUIDITY.replace(':marketId', marketId)),
[Routes.DISCLAIMER]: () => Routes.DISCLAIMER,
[Routes.DEPOSIT]: () => Routes.DEPOSIT,
[Routes.REFERRALS]: () => Routes.REFERRALS,
[Routes.REFERRALS_APPLY_CODE]: () => Routes.REFERRALS_APPLY_CODE,
[Routes.REFERRALS_CREATE_CODE]: () => Routes.REFERRALS_CREATE_CODE,
[Routes.TEAMS]: () => Routes.TEAMS,
};
export const routerConfig: RouteObject[] = [
@@ -109,6 +128,30 @@ export const routerConfig: RouteObject[] = [
element: <LazyDisclaimer />,
},
{ path: Routes.DEPOSIT, element: <LazyDeposit /> },
// Referrals routing:
{
path: Routes.REFERRALS,
element: <LayoutWithSky />,
children: [
{
element: <LazyReferrals />,
children: [
{
index: true,
element: <ReferralStatistics />,
},
{
path: Routes.REFERRALS_APPLY_CODE,
element: <ApplyCodeForm />,
},
{
path: Routes.REFERRALS_CREATE_CODE,
element: <CreateCodeContainer />,
},
],
},
],
},
{
path: '*',
element: (
@@ -124,7 +167,7 @@ export const ClientRouter = () => {
return (
<Suspense
fallback={
<div className="w-full h-full flex justify-center items-center">
<div className="flex items-center justify-center w-full h-full">
<Loader />
</div>
}
+9 -3
View File
@@ -19,11 +19,17 @@ export default function Index() {
<meta name="og:url" content="https://console.vega.xyz/" />
<meta name="og:title" content="Vega Protocol - Console" />
<meta name="og:site_name" content="Vega Protocol - Console" />
<meta name="og:image" content="./favicon.ico" />
<meta name="twitter:card" content="./favicon.ico" />
<meta name="og:image" content="https://static.vega.xyz/favicon.ico" />
<meta
name="twitter:card"
content="https://static.vega.xyz/favicon.ico"
/>
<meta name="twitter:title" content="Vega Protocol - Console" />
<meta name="twitter:description" content="Vega Protocol - Console" />
<meta name="twitter:image" content="./favicon.ico" />
<meta
name="twitter:image"
content="https://static.vega.xyz/favicon.ico"
/>
<meta name="twitter:image:alt" content="VEGA logo" />
<meta name="twitter:site" content="@vegaprotocol" />
</Head>
-7
View File
@@ -1,13 +1,6 @@
@import 'ag-grid-community/styles/ag-grid.css';
@import 'ag-grid-community/styles/ag-theme-balham.css';
/** Load AlphaLyrae font */
@font-face {
font-family: AlphaLyrae;
src: url('/AlphaLyrae-Medium.woff2') format('woff2'),
url('/AlphaLyrae-Medium.woff') format('woff');
}
@tailwind base;
@tailwind components;
@tailwind utilities;
Binary file not shown.

After

Width:  |  Height:  |  Size: 614 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 572 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 574 KiB

Binary file not shown.
Binary file not shown.

Before

Width:  |  Height:  |  Size: 101 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 7.2 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 947 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 947 KiB

+31 -1
View File
@@ -13,7 +13,37 @@ module.exports = {
],
darkMode: 'class',
theme: {
extend: theme,
extend: {
...theme,
colors: {
transparent: 'transparent',
current: 'currentColor',
...theme.colors,
},
backgroundImage: {
...theme.backgroundImage,
rainbow:
'linear-gradient(103.47deg, #FF077F 1.68%, #8028FF 47.49%, #0075FF 100%)',
'rainbow-shifted':
'linear-gradient(103.47deg, #0075FF 1.68%, #8028FF 47.49%, #FF077F 100%)',
highlight:
'linear-gradient(170deg, var(--tw-gradient-from), transparent var(--tw-gradient-to-position))',
},
keyframes: {
...theme.keyframes,
shake: {
'0%': { transform: 'translateX(0)' },
'25%': { transform: 'translateX(5px)' },
'50%': { transform: 'translateX(-5px)' },
'75%': { transform: 'translateX(5px)' },
'100%': { transform: 'translateX(0)' },
},
},
animation: {
...theme.animation,
shake: 'shake 200ms linear',
},
},
},
plugins: [vegaCustomClasses],
};
+14
View File
@@ -0,0 +1,14 @@
#!/bin/sh
entrypoint="${1:-nginx}"
if [[ "$entrypoint" = "nginx" ]]; then
nginx -g 'daemon off;'
elif [[ "$entrypoint" = "ipfs" ]]; then
ipfs config profile apply server
ipfs config --json Addresses.Gateway '"/ip4/127.0.0.1/tcp/80"'
ipfs daemon
elif [[ "-c" ]]; then
shift
/bin/sh -c "$@"
fi
+2 -1
View File
@@ -20,7 +20,8 @@ RUN sh docker/docker-build.sh
FROM --platform=amd64 nginx:1.23-alpine@sha256:6318314189b40e73145a48060bff4783a116c34cc7241532d0d94198fb2c9629
# configuration of system
EXPOSE 80
COPY docker/run-ipfs.sh /run-ipfs.sh
COPY docker/entrypoint.sh /entrypoint.sh
ENTRYPOINT [ "/entrypoint.sh" ]
# Copy dist
COPY docker/nginx.conf /etc/nginx/conf.d/default.conf
RUN rm -rf /usr/share/nginx/html/*
+2 -1
View File
@@ -1,6 +1,7 @@
FROM --platform=amd64 nginx:1.23-alpine@sha256:6318314189b40e73145a48060bff4783a116c34cc7241532d0d94198fb2c9629
EXPOSE 80
COPY docker/run-ipfs.sh /run-ipfs.sh
COPY docker/entrypoint.sh /entrypoint.sh
ENTRYPOINT [ "/entrypoint.sh" ]
COPY docker/nginx.conf /etc/nginx/conf.d/default.conf
RUN rm -rf /usr/share/nginx/html/*
COPY ./dist-result/ /usr/share/nginx/html/
-4
View File
@@ -1,4 +0,0 @@
#!/bin/sh
ipfs config profile apply server
ipfs config --json Addresses.Gateway '"/ip4/127.0.0.1/tcp/80"'
ipfs daemon
+1 -1
View File
@@ -18,7 +18,7 @@ import {
VegaIconNames,
TooltipCellComponent,
} from '@vegaprotocol/ui-toolkit';
import { AgGrid } from '@vegaprotocol/datagrid';
import { AgGridLazy as AgGrid } from '@vegaprotocol/datagrid';
import type {
IGetRowsParams,
IRowNode,
+1 -1
View File
@@ -13,7 +13,7 @@ import type {
VegaICellRendererParams,
} from '@vegaprotocol/datagrid';
import { ProgressBarCell } from '@vegaprotocol/datagrid';
import { AgGrid, PriceCell } from '@vegaprotocol/datagrid';
import { AgGridLazy as AgGrid, PriceCell } from '@vegaprotocol/datagrid';
import type { ColDef } from 'ag-grid-community';
import { accountValuesComparator } from './accounts-table';
import { MarginHealthChart } from './margin-health-chart';
+3 -5
View File
@@ -14,7 +14,6 @@ import {
export type AnnouncementBannerProps = {
app: AppNameType;
configUrl: string;
background?: string;
};
// run only if below the allowed maximum delay ~24.8 days (https://developer.mozilla.org/en-US/docs/Web/API/setTimeout#maximum_delay_value)
@@ -37,7 +36,6 @@ const doesEndInTheFuture = (now: Date, data: Announcement) => {
export const AnnouncementBanner = ({
app,
configUrl,
background,
}: AnnouncementBannerProps) => {
const [isVisible, setVisible] = useState(false);
const { data, reload } = useAnnouncement(app, configUrl);
@@ -81,10 +79,10 @@ export const AnnouncementBanner = ({
}
return (
<Banner className="relative px-10" background={background}>
<Banner className="relative px-10">
<div
data-testid="app-announcement"
className="relative flex justify-center text-lg text-center text-white font-alpha gap-2"
className="relative font-alpha flex gap-2 justify-center text-center text-lg text-white"
>
<span>{data.text}</span>{' '}
{data.urlText && data.url && (
@@ -92,7 +90,7 @@ export const AnnouncementBanner = ({
)}
</div>
<button
className="absolute top-0 right-0 flex items-center justify-center w-10 h-full p-4 text-white"
className="absolute right-0 top-0 p-4 w-10 h-full flex items-center justify-center text-white"
data-testid="app-announcement-close"
onClick={() => {
setVisible(false);
+1 -1
View File
@@ -1,4 +1,4 @@
export * from './lib/ag-grid/ag-grid';
export * from './lib/ag-grid/ag-grid-lazy';
export * from './lib/column-definitions';
@@ -0,0 +1,17 @@
import { forwardRef, lazy } from 'react';
import type { AgGridReactProps, AgGridReact } from 'ag-grid-react';
type Props = AgGridReactProps & {
style?: React.CSSProperties;
gridRef?: React.Ref<AgGridReact>;
};
export const AgGridLazyInternal = lazy(() =>
import('./ag-grid-lazy-themed').then((module) => ({
default: module.AgGridThemed,
}))
);
export const AgGridLazy = forwardRef<AgGridReact, Props>((props, ref) => (
<AgGridLazyInternal {...props} gridRef={ref} />
));
-12
View File
@@ -1,12 +0,0 @@
import { forwardRef } from 'react';
import type { AgGridReactProps, AgGridReact } from 'ag-grid-react';
import { AgGridThemed } from './ag-grid-themed';
type Props = AgGridReactProps & {
style?: React.CSSProperties;
gridRef?: React.Ref<AgGridReact>;
};
export const AgGrid = forwardRef<AgGridReact, Props>((props, ref) => (
<AgGridThemed {...props} gridRef={ref} />
));
@@ -1,12 +1,13 @@
import { act, render, waitFor } from '@testing-library/react';
import { useDataGridEvents } from './use-datagrid-events';
import { AgGridThemed } from './ag-grid/ag-grid-themed';
import {
useDataGridEvents,
GRID_EVENT_DEBOUNCE_TIME,
} from './use-datagrid-events';
import { AgGridThemed } from './ag-grid/ag-grid-lazy-themed';
import type { MutableRefObject } from 'react';
import { useRef } from 'react';
import type { AgGridReact } from 'ag-grid-react';
const GRID_EVENT_DEBOUNCE_TIME = 500;
const gridProps = {
rowData: [{ id: 1 }],
columnDefs: [
@@ -37,16 +37,14 @@ export interface DealTicketFeeDetailsProps {
assetSymbol: string;
order: OrderSubmissionBody['orderSubmission'];
market: Market;
isMarketInAuction?: boolean;
}
export const DealTicketFeeDetails = ({
assetSymbol,
order,
market,
isMarketInAuction,
}: DealTicketFeeDetailsProps) => {
const feeEstimate = useEstimateFees(order, isMarketInAuction);
const feeEstimate = useEstimateFees(order);
const { settlementAsset: asset } =
market.tradableInstrument.instrument.product;
const { decimals: assetDecimals, quantum } = asset;
@@ -66,7 +64,7 @@ export const DealTicketFeeDetails = ({
<>
<span>
{t(
`An estimate of the most you would be expected to pay in fees, in the market's settlement asset ${assetSymbol}. Fees estimated are "taker" fees and will only be payable if the order trades aggressively. Rebate equal to the maker portion will be paid to the trader if the order trades passively.`
`An estimate of the most you would be expected to pay in fees, in the market's settlement asset ${assetSymbol}.`
)}
</span>
<FeesBreakdown
@@ -36,7 +36,7 @@ import {
formatValue,
} from '@vegaprotocol/utils';
import { activeOrdersProvider } from '@vegaprotocol/orders';
import { getDerivedPrice, isMarketInAuction } from '@vegaprotocol/markets';
import { getDerivedPrice } from '@vegaprotocol/markets';
import {
validateExpiration,
validateMarketState,
@@ -182,7 +182,6 @@ export const DealTicket = ({
const iceberg = watch('iceberg');
const peakSize = watch('peakSize');
const expiresAt = watch('expiresAt');
const postOnly = watch('postOnly');
useEffect(() => {
const size = storedFormValues?.[dealTicketType]?.size;
@@ -212,7 +211,6 @@ export const DealTicket = ({
size: rawSize,
timeInForce,
type,
postOnly,
},
market.id,
market.decimalPlaces,
@@ -221,7 +219,8 @@ export const DealTicket = ({
const price =
normalizedOrder &&
getDerivedPrice(normalizedOrder, marketPrice ?? undefined);
marketPrice &&
getDerivedPrice(normalizedOrder, marketPrice);
const notionalSize = getNotionalSize(
price,
@@ -475,7 +474,6 @@ export const DealTicket = ({
}
assetSymbol={assetSymbol}
market={market}
isMarketInAuction={isMarketInAuction(marketData.marketTradingMode)}
/>
</div>
<Controller
@@ -1,78 +0,0 @@
import { renderHook } from '@testing-library/react';
import { useEstimateFees } from './use-estimate-fees';
import { Side, OrderTimeInForce, OrderType } from '@vegaprotocol/types';
import type { EstimateFeesQuery } from './__generated__/EstimateOrder';
const data: EstimateFeesQuery = {
estimateFees: {
totalFeeAmount: '12',
fees: {
infrastructureFee: '2',
liquidityFee: '4',
makerFee: '6',
},
},
};
const mockUseEstimateFeesQuery = jest.fn((...args) => ({
data,
}));
jest.mock('./__generated__/EstimateOrder', () => ({
...jest.requireActual('@vegaprotocol/data-provider'),
useEstimateFeesQuery: jest.fn((...args) => mockUseEstimateFeesQuery(...args)),
}));
jest.mock('@vegaprotocol/wallet', () => ({
useVegaWallet: () => ({ pubKey: 'pubKey' }),
}));
describe('useEstimateFees', () => {
it('returns 0 as estimated values if order is postOnly', () => {
const { result } = renderHook(() =>
useEstimateFees({
marketId: 'marketId',
side: Side.SIDE_BUY,
size: '1',
price: '1',
timeInForce: OrderTimeInForce.TIME_IN_FORCE_FOK,
type: OrderType.TYPE_LIMIT,
postOnly: true,
})
);
expect(result.current).toEqual({
totalFeeAmount: '0',
fees: {
infrastructureFee: '0',
liquidityFee: '0',
makerFee: '0',
},
});
expect(mockUseEstimateFeesQuery.mock.lastCall?.[0].skip).toBeTruthy();
});
it('divide values by 2 if market is in auction', () => {
const { result } = renderHook(() =>
useEstimateFees(
{
marketId: 'marketId',
side: Side.SIDE_BUY,
size: '1',
price: '1',
timeInForce: OrderTimeInForce.TIME_IN_FORCE_FOK,
type: OrderType.TYPE_LIMIT,
},
true
)
);
expect(result.current).toEqual({
totalFeeAmount: '6',
fees: {
infrastructureFee: '1',
liquidityFee: '2',
makerFee: '3',
},
});
});
});
@@ -1,16 +1,13 @@
import { useVegaWallet } from '@vegaprotocol/wallet';
import type { OrderSubmissionBody } from '@vegaprotocol/wallet';
import type { EstimateFeesQuery } from './__generated__/EstimateOrder';
import { useEstimateFeesQuery } from './__generated__/EstimateOrder';
const divideByTwo = (n: string) => (BigInt(n) / BigInt(2)).toString();
export const useEstimateFees = (
order?: OrderSubmissionBody['orderSubmission'],
isMarketInAuction?: boolean
): EstimateFeesQuery['estimateFees'] | undefined => {
order?: OrderSubmissionBody['orderSubmission']
) => {
const { pubKey } = useVegaWallet();
const { data } = useEstimateFeesQuery({
variables: order && {
marketId: order.marketId,
@@ -22,28 +19,7 @@ export const useEstimateFees = (
type: order.type,
},
fetchPolicy: 'no-cache',
skip: !pubKey || !order?.size || !order?.price || order.postOnly,
skip: !pubKey || !order?.size || !order?.price,
});
if (order?.postOnly) {
return {
totalFeeAmount: '0',
fees: {
infrastructureFee: '0',
liquidityFee: '0',
makerFee: '0',
},
};
}
return isMarketInAuction && data?.estimateFees
? {
totalFeeAmount: divideByTwo(data.estimateFees.totalFeeAmount),
fees: {
infrastructureFee: divideByTwo(
data.estimateFees.fees.infrastructureFee
),
liquidityFee: divideByTwo(data.estimateFees.fees.liquidityFee),
makerFee: divideByTwo(data.estimateFees.fees.makerFee),
},
}
: data?.estimateFees;
return data?.estimateFees;
};
+1 -1
View File
@@ -6,7 +6,7 @@ import {
isNumeric,
} from '@vegaprotocol/utils';
import type { ColDef } from 'ag-grid-community';
import { AgGrid } from '@vegaprotocol/datagrid';
import { AgGridLazy as AgGrid } from '@vegaprotocol/datagrid';
import type {
VegaICellRendererParams,
VegaValueFormatterParams,
+1
View File
@@ -175,4 +175,5 @@ export const ExternalLinks = {
export const TokenStaticLinks = {
PROPOSAL_PAGE: ':tokenUrl/proposals/:proposalId',
UPDATE_PROPOSAL_PAGE: ':tokenUrl/proposals/propose/update-market',
ASSOCIATE: 'token/associate',
};
+2 -2
View File
@@ -15,7 +15,7 @@ import {
import { t } from '@vegaprotocol/i18n';
import * as Schema from '@vegaprotocol/types';
import {
AgGrid,
AgGridLazy as AgGrid,
positiveClassNames,
negativeClassNames,
MarketNameCell,
@@ -300,7 +300,7 @@ const FeesBreakdownTooltip = ({
return (
<div
data-testid="fee-breakdown-tooltip"
className="z-20 max-w-sm px-4 py-2 text-sm text-black border rounded bg-vega-light-100 dark:bg-vega-dark-100 border-vega-light-200 dark:border-vega-dark-200 break-word dark:text-white"
className="max-w-sm bg-vega-light-100 dark:bg-vega-dark-100 border border-vega-light-200 dark:border-vega-dark-200 px-4 py-2 z-20 rounded text-sm break-word text-black dark:text-white"
>
{role === MAKER && (
<>
+1 -1
View File
@@ -7,7 +7,7 @@ import {
} from '@vegaprotocol/utils';
import { t } from '@vegaprotocol/i18n';
import type { TypedDataAgGrid } from '@vegaprotocol/datagrid';
import { AgGrid } from '@vegaprotocol/datagrid';
import { AgGridLazy as AgGrid } from '@vegaprotocol/datagrid';
import { TooltipCellComponent } from '@vegaprotocol/ui-toolkit';
import type {
ColDef,
@@ -60,7 +60,6 @@ export const FeesBreakdown = ({
.plus(fees.infrastructureFee)
.plus(fees.liquidityFee)
.toString();
if (totalFees === '0') return null;
const formatValue = (value: string | number | null | undefined): string => {
return value && !isNaN(Number(value))
? addDecimalsFormatNumber(value, decimals)
+1 -1
View File
@@ -33,7 +33,7 @@ export const getDerivedPrice = (
type: Schema.OrderType;
price?: string | undefined;
},
marketPrice?: string
marketPrice: string
) => {
// If order type is market we should use either the mark price
// or the uncrossing price. If order type is limit use the price
+6 -2
View File
@@ -96,11 +96,15 @@ export const calcTradedFactor = (m: MarketMaybeWithDataAndCandles) => {
const volume = Number(calcCandleVolume(m.candles || []) || 0);
const price = m.data?.markPrice ? Number(m.data.markPrice) : 0;
const quantum = Number(
m.tradableInstrument.instrument.product.settlementAsset.quantum
m.tradableInstrument.instrument.product.settlementAsset?.quantum
);
const decimals = Number(
m.tradableInstrument.instrument.product.settlementAsset.decimals
m.tradableInstrument.instrument.product.settlementAsset?.decimals
);
// `settlementAsset` can be undefined hence there's no quantum or decimals
if (isNaN(quantum) || isNaN(decimals)) return 0;
const fp = toBigNum(price, decimals);
const fq = toBigNum(quantum, decimals);
const factor = fq.multipliedBy(fp).multipliedBy(volume);
@@ -17,7 +17,7 @@ import {
import type { ForwardedRef } from 'react';
import { memo, forwardRef, useMemo } from 'react';
import {
AgGrid,
AgGridLazy as AgGrid,
SetFilter,
DateRangeFilter,
negativeClassNames,
@@ -18,7 +18,7 @@ import {
} from '@vegaprotocol/ui-toolkit';
import { memo, useMemo } from 'react';
import {
AgGrid,
AgGridLazy as AgGrid,
SetFilter,
DateRangeFilter,
negativeClassNames,
+1 -1
View File
@@ -8,7 +8,7 @@ import type {
VegaICellRendererParams,
} from '@vegaprotocol/datagrid';
import {
AgGrid,
AgGridLazy as AgGrid,
COL_DEFS,
PriceFlashCell,
signedNumberCssClassRules,
@@ -100,8 +100,7 @@ describe('ProposalsList', () => {
const container = within(
document.querySelector(rowContainerSelector) as HTMLElement
);
expect(await container.findAllByRole('row')).toHaveLength(
expect(container.getAllByRole('row')).toHaveLength(
// @ts-ignore data is mocked
mock?.result?.data.proposalsConnection.edges.length
);
@@ -1,5 +1,5 @@
import type { FC } from 'react';
import { AgGrid } from '@vegaprotocol/datagrid';
import { AgGridLazy as AgGrid } from '@vegaprotocol/datagrid';
import { t } from '@vegaprotocol/i18n';
import * as Types from '@vegaprotocol/types';
import { removePaginationWrapper } from '@vegaprotocol/utils';
+1 -1
View File
@@ -4,7 +4,7 @@ import type {
VegaICellRendererParams,
VegaValueFormatterParams,
} from '@vegaprotocol/datagrid';
import { AgGrid, NumericCell } from '@vegaprotocol/datagrid';
import { AgGridLazy as AgGrid, NumericCell } from '@vegaprotocol/datagrid';
import {
addDecimal,
addDecimalsFormatNumber,
@@ -4,27 +4,14 @@ import type { ReactNode } from 'react';
export interface BannerProps {
children?: ReactNode;
className?: string;
background?: string;
}
export const AnnouncementBanner = ({
className,
children,
background = 'url("https://static.vega.xyz/assets/img/banner-bg.jpg")',
}: BannerProps) => {
const bannerClasses = classnames('p-4', className);
return (
<div
className={bannerClasses}
style={{
background,
backgroundRepeat: 'no-repeat',
backgroundPosition: 'center',
backgroundSize: 'cover',
}}
>
{children}
</div>
export const AnnouncementBanner = ({ className, children }: BannerProps) => {
const bannerClasses = classnames(
"bg-[url('https://static.vega.xyz/assets/img/banner-bg.jpg')] bg-cover bg-center bg-no-repeat",
'p-4',
className
);
return <div className={bannerClasses}>{children}</div>;
};
@@ -155,7 +155,8 @@ const ConnectDialogContainer = ({
const isDesktopWalletRunning = useIsWalletServiceRunning(
walletUrl,
connectors['jsonRpc']
connectors['jsonRpc'],
appChainId
);
const snapStatus = useSnapStatus(
+21 -1
View File
@@ -388,6 +388,24 @@ export interface TransferBody {
transfer: Transfer;
}
export type ApplyReferralCode = {
applyReferralCode: {
id: string;
};
};
export type CreateReferralSet = {
createReferralSet: {
isTeam: boolean;
team?: {
name: string;
teamUrl?: string;
avatarUrl?: string;
closed: boolean;
};
};
};
export type Transaction =
| StopOrdersSubmissionBody
| StopOrdersCancellationBody
@@ -401,7 +419,9 @@ export type Transaction =
| ProposalSubmissionBody
| BatchMarketInstructionSubmissionBody
| TransferBody
| LiquidityProvisionSubmission;
| LiquidityProvisionSubmission
| ApplyReferralCode
| CreateReferralSet;
export const isWithdrawTransaction = (
transaction: Transaction
@@ -1,41 +0,0 @@
import { renderHook, waitFor } from '@testing-library/react';
import { JsonRpcConnector } from './connectors';
import { useIsWalletServiceRunning } from './use-is-wallet-service-running';
describe('useIsWalletServiceRunning', () => {
it('returns true if wallet is running', async () => {
const url = 'https://foo.bar.com';
const connector = new JsonRpcConnector();
const spyOnCheckCompat = jest
.spyOn(connector, 'checkCompat')
.mockResolvedValue(true);
const { result } = renderHook(() =>
useIsWalletServiceRunning(url, connector)
);
expect(result.current).toBe(null);
await waitFor(() => {
expect(spyOnCheckCompat).toHaveBeenCalled();
expect(result.current).toBe(true);
});
});
it('returns false if wallet is not running', async () => {
const url = 'https://foo.bar.com';
const connector = new JsonRpcConnector();
const spyOnCheckCompat = jest
.spyOn(connector, 'checkCompat')
.mockRejectedValue(false);
const { result } = renderHook(() =>
useIsWalletServiceRunning(url, connector)
);
expect(result.current).toBe(null);
await waitFor(() => {
expect(spyOnCheckCompat).toHaveBeenCalled();
expect(result.current).toBe(false);
});
});
});
@@ -1,39 +0,0 @@
import { useEffect, useState } from 'react';
import type { JsonRpcConnector } from './connectors';
export const useIsWalletServiceRunning = (
url: string,
connector: JsonRpcConnector | undefined
) => {
const [isRunning, setIsRunning] = useState<boolean | null>(null);
useEffect(() => {
if (!connector) return;
if (url && url !== connector.url) {
connector.url = url;
}
const check = async () => {
try {
// we are not checking wallet compatibility here, only that the wallet is running
await connector.checkCompat();
setIsRunning(true);
} catch {
setIsRunning(false);
}
};
// check immediately
check();
// check every second for quick feedback to the user
const interval = setInterval(check, 1000);
return () => {
clearInterval(interval);
};
}, [connector, url]);
return isRunning;
};
@@ -0,0 +1,47 @@
import { useCallback, useEffect, useState } from 'react';
import type { JsonRpcConnector } from './connectors';
import { ClientErrors } from './connectors';
export const useIsWalletServiceRunning = (
url: string,
connector: JsonRpcConnector | undefined,
appChainId: string
) => {
const [run, setRun] = useState<boolean | null>(null);
const checkState = useCallback(async () => {
if (!connector) return false;
if (url && url !== connector.url) {
connector.url = url;
}
try {
await connector.checkCompat();
const chainIdResult = await connector.getChainId();
if (chainIdResult.chainID !== appChainId) {
throw ClientErrors.WRONG_NETWORK;
}
} catch (e) {
return false;
}
return true;
}, [connector, url, appChainId]);
useEffect(() => {
if (!connector) return;
let interval: NodeJS.Timeout;
checkState().then((value) => {
setRun(value);
interval = setInterval(async () => {
setRun(await checkState());
}, 1000 * 10);
});
return () => {
clearInterval(interval);
};
}, [checkState, connector]);
return run;
};
+1 -1
View File
@@ -20,7 +20,7 @@ import type {
VegaICellRendererParams,
VegaValueFormatterParams,
} from '@vegaprotocol/datagrid';
import { AgGrid } from '@vegaprotocol/datagrid';
import { AgGridLazy as AgGrid } from '@vegaprotocol/datagrid';
import { EtherscanLink } from '@vegaprotocol/environment';
import type { WithdrawalFieldsFragment } from './__generated__/Withdrawal';
import {
+2 -1
View File
@@ -87,6 +87,7 @@
"recharts": "^2.1.2",
"recursive-key-filter": "^1.0.2",
"regenerator-runtime": "0.13.7",
"semver": "^7.5.1",
"toml": "^3.0.0",
"tslib": "^2.3.0",
"uuid": "^8.3.2",
@@ -211,7 +212,7 @@
"style-loader": "^3.3.0",
"stylus": "^0.55.0",
"stylus-loader": "^7.1.0",
"tailwindcss": "3.2.7",
"tailwindcss": "3.3.3",
"ts-jest": "29.1.0",
"ts-node": "10.9.1",
"tslib": "^2.3.0",
+166 -63
View File
@@ -7,6 +7,11 @@
resolved "https://registry.yarnpkg.com/@adobe/css-tools/-/css-tools-4.0.1.tgz#b38b444ad3aa5fedbb15f2f746dcd934226a12dd"
integrity sha512-+u76oB43nOHrF4DDWRLWDCtci7f3QJoEBigemIdIeTi1ODqjx6Tad9NCVnPRwewWlKkVab5PlK8DCtPTyX7S8g==
"@alloc/quick-lru@^5.2.0":
version "5.2.0"
resolved "https://registry.yarnpkg.com/@alloc/quick-lru/-/quick-lru-5.2.0.tgz#7bf68b20c0a350f936915fcae06f58e32007ce30"
integrity sha512-UrcABB+4bUrFABwbluTIBErXwvbsU/V7TZWfmbgJfbkwiBuziS9gxdODUyuiecfdGQ85jglMW6juS3+z5TsKLw==
"@ampproject/remapping@^2.1.0", "@ampproject/remapping@^2.2.0":
version "2.2.0"
resolved "https://registry.yarnpkg.com/@ampproject/remapping/-/remapping-2.2.0.tgz#56c133824780de3174aed5ab6834f3026790154d"
@@ -9629,16 +9634,7 @@ acorn-jsx@^5.3.1, acorn-jsx@^5.3.2:
resolved "https://registry.yarnpkg.com/acorn-jsx/-/acorn-jsx-5.3.2.tgz#7ed5bb55908b3b2f1bc55c6af1653bada7f07937"
integrity sha512-rq9s+JNhf0IChjtDXxllJ7g41oZk5SlXtp0LHwyA5cejwn7vKmKp4pPri6YEePv2PU65sAsegbXtIinmDFDXgQ==
acorn-node@^1.8.2:
version "1.8.2"
resolved "https://registry.yarnpkg.com/acorn-node/-/acorn-node-1.8.2.tgz#114c95d64539e53dede23de8b9d96df7c7ae2af8"
integrity sha512-8mt+fslDufLYntIoPAaIMUe/lrbrehIiwmR3t2k9LljIzoigEPF27eLk2hy8zSGzmR/ogr7zbRKINMo1u0yh5A==
dependencies:
acorn "^7.0.0"
acorn-walk "^7.0.0"
xtend "^4.0.2"
acorn-walk@^7.0.0, acorn-walk@^7.2.0:
acorn-walk@^7.2.0:
version "7.2.0"
resolved "https://registry.yarnpkg.com/acorn-walk/-/acorn-walk-7.2.0.tgz#0de889a601203909b0fbe07b8938dc21d2e967bc"
integrity sha512-OPdCF6GsMIP+Az+aWfAAOEt2/+iVDKE7oy6lJ098aoe59oAmK76qV6Gw60SbZ8jHuG2wH058GF4pLFbYamYrVA==
@@ -9648,7 +9644,7 @@ acorn-walk@^8.0.2, acorn-walk@^8.1.1, acorn-walk@^8.2.0:
resolved "https://registry.yarnpkg.com/acorn-walk/-/acorn-walk-8.2.0.tgz#741210f2e2426454508853a2f44d0ab83b7f69c1"
integrity sha512-k+iyHEuPgSw6SbuDpGQM+06HQUa04DZ3o+F6CSzXMvvI5KMvnaEqXe+YVe555R9nn6GPt404fos4wcgpw12SDA==
acorn@^7.0.0, acorn@^7.4.1:
acorn@^7.4.1:
version "7.4.1"
resolved "https://registry.yarnpkg.com/acorn/-/acorn-7.4.1.tgz#feaed255973d2e77555b83dbc08851a6c63520fa"
integrity sha512-nQyp0o1/mNdbTO1PO6kHkwSrmgZ0MT/jCCpNiwbUjGoRN4dlBhqJtoQuCnEOKzgTVwg0ZWiCoQy6SxMebQVh8A==
@@ -9899,6 +9895,11 @@ any-observable@^0.3.0:
resolved "https://registry.yarnpkg.com/any-observable/-/any-observable-0.3.0.tgz#af933475e5806a67d0d7df090dd5e8bef65d119b"
integrity sha512-/FQM1EDkTsf63Ub2C6O7GuYFDsSXUwsaZDurV0np41ocwq0jthUAYCmhBX9f+KwlaCgIuWyr/4WlUQUBfKfZog==
any-promise@^1.0.0:
version "1.3.0"
resolved "https://registry.yarnpkg.com/any-promise/-/any-promise-1.3.0.tgz#abc6afeedcea52e809cdc0376aed3ce39635d17f"
integrity sha512-7UvmKalWRt1wgjL1RrGxoSJW/0QZFIegpeGvZG9kjp8vrRu55XTHbwnqq2GpXm9uLbcuhxm3IqX9OB4MZR1b2A==
anymatch@^3.0.3, anymatch@~3.1.2:
version "3.1.2"
resolved "https://registry.yarnpkg.com/anymatch/-/anymatch-3.1.2.tgz#c0557c096af32f106198f4f4e2a383537e378716"
@@ -12972,11 +12973,6 @@ define-properties@^1.2.0:
has-property-descriptors "^1.0.0"
object-keys "^1.1.1"
defined@^1.0.0:
version "1.0.0"
resolved "https://registry.yarnpkg.com/defined/-/defined-1.0.0.tgz#c98d9bcef75674188e110969151199e39b1fa693"
integrity sha512-Y2caI5+ZwS5c3RiNDJ6u53VhQHv+hHKwhkI1iHvceKUHw9Df6EK2zRLfjejRgMuCuxK7PfSWIMwWecceVvThjQ==
defu@^6.1.2:
version "6.1.2"
resolved "https://registry.yarnpkg.com/defu/-/defu-6.1.2.tgz#1217cba167410a1765ba93893c6dbac9ed9d9e5c"
@@ -13088,15 +13084,6 @@ detect-port@^1.3.0, detect-port@^1.5.1:
address "^1.0.1"
debug "4"
detective@^5.2.1:
version "5.2.1"
resolved "https://registry.yarnpkg.com/detective/-/detective-5.2.1.tgz#6af01eeda11015acb0e73f933242b70f24f91034"
integrity sha512-v9XE1zRnz1wRtgurGu0Bs8uHKFSTdteYZNbIPFVhUZ39L/S79ppMpdmVOZAnoz1jfEFodc48n6MX483Xo3t1yw==
dependencies:
acorn-node "^1.8.2"
defined "^1.0.0"
minimist "^1.2.6"
didyoumean@^1.2.2:
version "1.2.2"
resolved "https://registry.yarnpkg.com/didyoumean/-/didyoumean-1.2.2.tgz#989346ffe9e839b4555ecf5666edea0d3e8ad037"
@@ -15330,6 +15317,18 @@ glob@7.1.4:
once "^1.3.0"
path-is-absolute "^1.0.0"
glob@7.1.6:
version "7.1.6"
resolved "https://registry.yarnpkg.com/glob/-/glob-7.1.6.tgz#141f33b81a7c2492e125594307480c46679278a6"
integrity sha512-LwaxwyZ72Lk7vZINtNNrywX0ZuLyStrdDtabefZKAY5ZGJhVtgdznluResxNmPitE0SAO+O26sWTHeKSI2wMBA==
dependencies:
fs.realpath "^1.0.0"
inflight "^1.0.4"
inherits "2"
minimatch "^3.0.4"
once "^1.3.0"
path-is-absolute "^1.0.0"
glob@7.1.7, glob@~7.1.1:
version "7.1.7"
resolved "https://registry.yarnpkg.com/glob/-/glob-7.1.7.tgz#3b193e9233f01d42d0b3f78294bbeeb418f94a90"
@@ -16401,6 +16400,13 @@ is-core-module@^2.11.0:
dependencies:
has "^1.0.3"
is-core-module@^2.13.0:
version "2.13.0"
resolved "https://registry.yarnpkg.com/is-core-module/-/is-core-module-2.13.0.tgz#bb52aa6e2cbd49a30c2ba68c42bf3435ba6072db"
integrity sha512-Z7dk6Qo8pOCp3l4tsX2C5ZVas4V+UxwQodwZhLopL91TX8UyyHEXafPcyoeeWuLrwzHcr3igO78wNLwHJHsMCQ==
dependencies:
has "^1.0.3"
is-core-module@^2.5.0, is-core-module@^2.8.1:
version "2.10.0"
resolved "https://registry.yarnpkg.com/is-core-module/-/is-core-module-2.10.0.tgz#9012ede0a91c69587e647514e1d5277019e728ed"
@@ -17443,6 +17449,11 @@ jest@29.4.3:
import-local "^3.0.2"
jest-cli "^29.4.3"
jiti@^1.18.2:
version "1.20.0"
resolved "https://registry.yarnpkg.com/jiti/-/jiti-1.20.0.tgz#2d823b5852ee8963585c8dd8b7992ffc1ae83b42"
integrity sha512-3TV69ZbrvV6U5DfQimop50jE9Dl6J8O1ja1dvBbMba/sZ3YBEQqJ2VZRoQPVnhlzjNtU1vaXRZVrVjU4qtm8yA==
js-sha3@0.8.0, js-sha3@^0.8.0:
version "0.8.0"
resolved "https://registry.yarnpkg.com/js-sha3/-/js-sha3-0.8.0.tgz#b9b7a5da73afad7dedd0f8c463954cbde6818840"
@@ -17888,7 +17899,7 @@ lilconfig@^2.0.3, lilconfig@^2.1.0:
resolved "https://registry.yarnpkg.com/lilconfig/-/lilconfig-2.1.0.tgz#78e23ac89ebb7e1bfbf25b18043de756548e7f52"
integrity sha512-utWOt/GHzuUxnLKxB6dk81RoOeoNeHgbrXiuGk4yyF5qlRz+iIVWu56E2fqGHFrXz0QNUhLB/8nKqvRH66JKGQ==
lilconfig@^2.0.5, lilconfig@^2.0.6:
lilconfig@^2.0.5:
version "2.0.6"
resolved "https://registry.yarnpkg.com/lilconfig/-/lilconfig-2.0.6.tgz#32a384558bd58af3d4c6e077dd1ad1d397bc69d4"
integrity sha512-9JROoBW7pobfsx+Sq2JsASvCo6Pfo6WWoUW79HuB1BCoBXD4PLWJPqDF6fNj67pqBYTbAHkE57M1kS/+L1neOg==
@@ -19278,6 +19289,15 @@ mute-stream@0.0.8:
resolved "https://registry.yarnpkg.com/mute-stream/-/mute-stream-0.0.8.tgz#1630c42b2251ff81e2a283de96a5497ea92e5e0d"
integrity sha512-nnbWWOkoWyUsTjKrhgD0dcz22mdkSnpYqbEjIm2nhwhuxlSkpywJmBo8h0ZqJdkp73mb90SssHkN4rsRaBAfAA==
mz@^2.7.0:
version "2.7.0"
resolved "https://registry.yarnpkg.com/mz/-/mz-2.7.0.tgz#95008057a56cafadc2bc63dde7f9ff6955948e32"
integrity sha512-z81GNO7nnYMEhrGh9LeymoE4+Yr0Wn5McHIZMK5cfQCl+NDX08sCZgUc9/6MHni9IWuFLm1Z3HTCXu2z9fN62Q==
dependencies:
any-promise "^1.0.0"
object-assign "^4.0.1"
thenify-all "^1.0.0"
nanoid@3.3.3:
version "3.3.3"
resolved "https://registry.yarnpkg.com/nanoid/-/nanoid-3.3.3.tgz#fd8e8b7aa761fe807dba2d1b98fb7241bb724a25"
@@ -19630,7 +19650,7 @@ nx@16.4.0:
"@nx/nx-win32-arm64-msvc" "16.4.0"
"@nx/nx-win32-x64-msvc" "16.4.0"
object-assign@^4.1.0, object-assign@^4.1.1:
object-assign@^4.0.1, object-assign@^4.1.0, object-assign@^4.1.1:
version "4.1.1"
resolved "https://registry.yarnpkg.com/object-assign/-/object-assign-4.1.1.tgz#2109adc7965887cfc05cbbd442cac8bfbb360863"
integrity sha512-rJgTQnkUnH1sFw8yT6VSU3zD3sWmu6sZhIseY8VX+GRu3P6F7Fu+JNDoXfklElbLJSnc3FUQHVe4cU5hj+BcUg==
@@ -20370,6 +20390,11 @@ pino@7.11.0:
sonic-boom "^2.2.1"
thread-stream "^0.15.1"
pirates@^4.0.1:
version "4.0.6"
resolved "https://registry.yarnpkg.com/pirates/-/pirates-4.0.6.tgz#3018ae32ecfcff6c29ba2267cbf21166ac1f36b9"
integrity sha512-saLsH7WeYYPiD25LDuLRRY/i+6HaPYr6G1OUlN39otzkSTxKnubR9RTxS3/Kk50s1g2JTgFwWQDQyplC5/SHZg==
pirates@^4.0.4, pirates@^4.0.5:
version "4.0.5"
resolved "https://registry.yarnpkg.com/pirates/-/pirates-4.0.5.tgz#feec352ea5c3268fb23a37c702ab1699f35a5f3b"
@@ -20519,7 +20544,16 @@ postcss-discard-overridden@^6.0.0:
resolved "https://registry.yarnpkg.com/postcss-discard-overridden/-/postcss-discard-overridden-6.0.0.tgz#49c5262db14e975e349692d9024442de7cd8e234"
integrity sha512-4VELwssYXDFigPYAZ8vL4yX4mUepF/oCBeeIT4OXsJPYOtvJumyz9WflmJWTfDwCUcpDR+z0zvCWBXgTx35SVw==
postcss-import@^14.1.0, postcss-import@~14.1.0:
postcss-import@^15.1.0:
version "15.1.0"
resolved "https://registry.yarnpkg.com/postcss-import/-/postcss-import-15.1.0.tgz#41c64ed8cc0e23735a9698b3249ffdbf704adc70"
integrity sha512-hpr+J05B2FVYUAXHeK1YyI267J/dDDhMU6B6civm8hSY1jYJnBXxzKDKDswzJmtLHryrjhnDjqqp/49t8FALew==
dependencies:
postcss-value-parser "^4.0.0"
read-cache "^1.0.0"
resolve "^1.1.7"
postcss-import@~14.1.0:
version "14.1.0"
resolved "https://registry.yarnpkg.com/postcss-import/-/postcss-import-14.1.0.tgz#a7333ffe32f0b8795303ee9e40215dac922781f0"
integrity sha512-flwI+Vgm4SElObFVPpTIT7SU7R3qk2L7PyduMcokiaVKuWv9d/U+Gm/QAd8NDLuykTWTkcrjOeD2Pp1rMeBTGw==
@@ -20528,14 +20562,14 @@ postcss-import@^14.1.0, postcss-import@~14.1.0:
read-cache "^1.0.0"
resolve "^1.1.7"
postcss-js@^4.0.0:
version "4.0.0"
resolved "https://registry.yarnpkg.com/postcss-js/-/postcss-js-4.0.0.tgz#31db79889531b80dc7bc9b0ad283e418dce0ac00"
integrity sha512-77QESFBwgX4irogGVPgQ5s07vLvFqWr228qZY+w6lW599cRlK/HmnlivnnVUxkjHnCu4J16PDMHcH+e+2HbvTQ==
postcss-js@^4.0.1:
version "4.0.1"
resolved "https://registry.yarnpkg.com/postcss-js/-/postcss-js-4.0.1.tgz#61598186f3703bab052f1c4f7d805f3991bee9d2"
integrity sha512-dDLF8pEO191hJMtlHFPRa8xsizHaM82MLfNkUHdUtVEV3tgTp5oj+8qbEqYM57SLfc74KSbw//4SeJma2LRVIw==
dependencies:
camelcase-css "^2.0.1"
postcss-load-config@^3.0.0, postcss-load-config@^3.1.4:
postcss-load-config@^3.0.0:
version "3.1.4"
resolved "https://registry.yarnpkg.com/postcss-load-config/-/postcss-load-config-3.1.4.tgz#1ab2571faf84bb078877e1d07905eabe9ebda855"
integrity sha512-6DiM4E7v4coTE4uzA8U//WhtPwyhiim3eyjEMFCnUpzbrkK9wJHgKDT2mR+HbtSrd/NubVaYTOpSpjUl8NQeRg==
@@ -20543,6 +20577,14 @@ postcss-load-config@^3.0.0, postcss-load-config@^3.1.4:
lilconfig "^2.0.5"
yaml "^1.10.2"
postcss-load-config@^4.0.1:
version "4.0.1"
resolved "https://registry.yarnpkg.com/postcss-load-config/-/postcss-load-config-4.0.1.tgz#152383f481c2758274404e4962743191d73875bd"
integrity sha512-vEJIc8RdiBRu3oRAI0ymerOn+7rPuMvRXslTvZUKZonDHFIczxztIyJ1urxM1x9JXEikvpWWTUUqal5j/8QgvA==
dependencies:
lilconfig "^2.0.5"
yaml "^2.1.1"
postcss-loader@^6.1.1:
version "6.2.1"
resolved "https://registry.yarnpkg.com/postcss-loader/-/postcss-loader-6.2.1.tgz#0895f7346b1702103d30fdc66e4d494a93c008ef"
@@ -20703,12 +20745,12 @@ postcss-modules@^4.0.0:
postcss-modules-values "^4.0.0"
string-hash "^1.1.1"
postcss-nested@6.0.0:
version "6.0.0"
resolved "https://registry.yarnpkg.com/postcss-nested/-/postcss-nested-6.0.0.tgz#1572f1984736578f360cffc7eb7dca69e30d1735"
integrity sha512-0DkamqrPcmkBDsLn+vQDIrtkSbNkv5AD/M322ySo9kqFkCIYklym2xEmWkwo+Y3/qZo34tzEPNUw4y7yMCdv5w==
postcss-nested@^6.0.1:
version "6.0.1"
resolved "https://registry.yarnpkg.com/postcss-nested/-/postcss-nested-6.0.1.tgz#f83dc9846ca16d2f4fa864f16e9d9f7d0961662c"
integrity sha512-mEp4xPMi5bSWiMbsgoPfcP74lsWLHkQbZc3sY+jWYd65CUwXrUaTp0fmNpa01ZcETKlIgUdFN/MpS2xZtqL9dQ==
dependencies:
postcss-selector-parser "^6.0.10"
postcss-selector-parser "^6.0.11"
postcss-normalize-charset@^5.1.0:
version "5.1.0"
@@ -20881,14 +20923,6 @@ postcss-reduce-transforms@^6.0.0:
dependencies:
postcss-value-parser "^4.2.0"
postcss-selector-parser@^6.0.10, postcss-selector-parser@^6.0.2, postcss-selector-parser@^6.0.4:
version "6.0.10"
resolved "https://registry.yarnpkg.com/postcss-selector-parser/-/postcss-selector-parser-6.0.10.tgz#79b61e2c0d1bfc2602d549e11d0876256f8df88d"
integrity sha512-IQ7TZdoaqbT+LCpShg46jnZVlhWD2w6iQYAcYXfHARZ7X1t/UGhhceQDs5X0cGqKvYlHNOuv7Oa1xmb0oQuA3w==
dependencies:
cssesc "^3.0.0"
util-deprecate "^1.0.2"
postcss-selector-parser@^6.0.11, postcss-selector-parser@^6.0.5, postcss-selector-parser@^6.0.9:
version "6.0.13"
resolved "https://registry.yarnpkg.com/postcss-selector-parser/-/postcss-selector-parser-6.0.13.tgz#d05d8d76b1e8e173257ef9d60b706a8e5e99bf1b"
@@ -20897,6 +20931,14 @@ postcss-selector-parser@^6.0.11, postcss-selector-parser@^6.0.5, postcss-selecto
cssesc "^3.0.0"
util-deprecate "^1.0.2"
postcss-selector-parser@^6.0.2, postcss-selector-parser@^6.0.4:
version "6.0.10"
resolved "https://registry.yarnpkg.com/postcss-selector-parser/-/postcss-selector-parser-6.0.10.tgz#79b61e2c0d1bfc2602d549e11d0876256f8df88d"
integrity sha512-IQ7TZdoaqbT+LCpShg46jnZVlhWD2w6iQYAcYXfHARZ7X1t/UGhhceQDs5X0cGqKvYlHNOuv7Oa1xmb0oQuA3w==
dependencies:
cssesc "^3.0.0"
util-deprecate "^1.0.2"
postcss-svgo@^5.1.0:
version "5.1.0"
resolved "https://registry.yarnpkg.com/postcss-svgo/-/postcss-svgo-5.1.0.tgz#0a317400ced789f233a28826e77523f15857d80d"
@@ -20955,7 +20997,7 @@ postcss@8.4.21:
picocolors "^1.0.0"
source-map-js "^1.0.2"
postcss@^8.0.9, postcss@^8.4.14, postcss@^8.4.21, postcss@^8.4.24:
postcss@^8.4.14, postcss@^8.4.21, postcss@^8.4.24:
version "8.4.24"
resolved "https://registry.yarnpkg.com/postcss/-/postcss-8.4.24.tgz#f714dba9b2284be3cc07dbd2fc57ee4dc972d2df"
integrity sha512-M0RzbcI0sO/XJNucsGjvWU9ERWxb/ytp1w6dKtxTKgixdtQDq4rmx/g8W1hnaheq9jgwL/oyEdH5Bc4WwJKMqg==
@@ -20964,6 +21006,15 @@ postcss@^8.0.9, postcss@^8.4.14, postcss@^8.4.21, postcss@^8.4.24:
picocolors "^1.0.0"
source-map-js "^1.0.2"
postcss@^8.4.23:
version "8.4.29"
resolved "https://registry.yarnpkg.com/postcss/-/postcss-8.4.29.tgz#33bc121cf3b3688d4ddef50be869b2a54185a1dd"
integrity sha512-cbI+jaqIeu/VGqXEarWkRCCffhjgXc0qjBtXpqJhTBohMUjUQnbBr0xqX3vEKudc4iviTewcJo5ajcec5+wdJw==
dependencies:
nanoid "^3.3.6"
picocolors "^1.0.0"
source-map-js "^1.0.2"
preact@10.4.1:
version "10.4.1"
resolved "https://registry.yarnpkg.com/preact/-/preact-10.4.1.tgz#9b3ba020547673a231c6cf16f0fbaef0e8863431"
@@ -22133,6 +22184,15 @@ resolve@^1.17.0:
path-parse "^1.0.7"
supports-preserve-symlinks-flag "^1.0.0"
resolve@^1.22.2:
version "1.22.6"
resolved "https://registry.yarnpkg.com/resolve/-/resolve-1.22.6.tgz#dd209739eca3aef739c626fea1b4f3c506195362"
integrity sha512-njhxM7mV12JfufShqGy3Rz8j11RPdLy4xi15UurGJeoHLfJpVXKdh3ueuOqbYUcDZnffr6X739JBo5LzyahEsw==
dependencies:
is-core-module "^2.13.0"
path-parse "^1.0.7"
supports-preserve-symlinks-flag "^1.0.0"
resolve@^2.0.0-next.3, resolve@^2.0.0-next.4:
version "2.0.0-next.4"
resolved "https://registry.yarnpkg.com/resolve/-/resolve-2.0.0-next.4.tgz#3d37a113d6429f496ec4752d2a2e58efb1fd4660"
@@ -22591,6 +22651,13 @@ semver@^6.0.0, semver@^6.1.1, semver@^6.1.2, semver@^6.2.0, semver@^6.3.0:
resolved "https://registry.yarnpkg.com/semver/-/semver-6.3.0.tgz#ee0a64c8af5e8ceea67687b133761e1becbd1d3d"
integrity sha512-b39TBaTSfV6yBrapU89p5fKekE2m/NwnDocOVruQFS1/veMgdzuPcnOM34M6CwxW8jH/lxEa5rBoDeUwu5HHTw==
semver@^7.5.1:
version "7.5.1"
resolved "https://registry.yarnpkg.com/semver/-/semver-7.5.1.tgz#c90c4d631cf74720e46b21c1d37ea07edfab91ec"
integrity sha512-Wvss5ivl8TMRZXXESstBA4uR5iXgEN/VC5/sOcuXdVLzcdkz4HWetIoRfG5gb5X+ij/G9rw9YoGn3QoQ8OCSpw==
dependencies:
lru-cache "^6.0.0"
semver@^7.5.3:
version "7.5.4"
resolved "https://registry.yarnpkg.com/semver/-/semver-7.5.4.tgz#483986ec4ed38e1c6c48c34894a9182dbff68a6e"
@@ -23468,6 +23535,19 @@ stylus@^0.59.0:
sax "~1.2.4"
source-map "^0.7.3"
sucrase@^3.32.0:
version "3.34.0"
resolved "https://registry.yarnpkg.com/sucrase/-/sucrase-3.34.0.tgz#1e0e2d8fcf07f8b9c3569067d92fbd8690fb576f"
integrity sha512-70/LQEZ07TEcxiU2dz51FKaE6hCTWC6vr7FOk3Gr0U60C3shtAN+H+BFr9XlYe5xqf3RA8nrc+VIwzCfnxuXJw==
dependencies:
"@jridgewell/gen-mapping" "^0.3.2"
commander "^4.0.0"
glob "7.1.6"
lines-and-columns "^1.1.6"
mz "^2.7.0"
pirates "^4.0.1"
ts-interface-checker "^0.1.9"
superstruct@^0.14.2:
version "0.14.2"
resolved "https://registry.yarnpkg.com/superstruct/-/superstruct-0.14.2.tgz#0dbcdf3d83676588828f1cf5ed35cda02f59025b"
@@ -23593,34 +23673,33 @@ table@6.8.0:
string-width "^4.2.3"
strip-ansi "^6.0.1"
tailwindcss@3.2.7:
version "3.2.7"
resolved "https://registry.yarnpkg.com/tailwindcss/-/tailwindcss-3.2.7.tgz#5936dd08c250b05180f0944500c01dce19188c07"
integrity sha512-B6DLqJzc21x7wntlH/GsZwEXTBttVSl1FtCzC8WP4oBc/NKef7kaax5jeihkkCEWc831/5NDJ9gRNDK6NEioQQ==
tailwindcss@3.3.3:
version "3.3.3"
resolved "https://registry.yarnpkg.com/tailwindcss/-/tailwindcss-3.3.3.tgz#90da807393a2859189e48e9e7000e6880a736daf"
integrity sha512-A0KgSkef7eE4Mf+nKJ83i75TMyq8HqY3qmFIJSWy8bNt0v1lG7jUcpGpoTFxAwYcWOphcTBLPPJg+bDfhDf52w==
dependencies:
"@alloc/quick-lru" "^5.2.0"
arg "^5.0.2"
chokidar "^3.5.3"
color-name "^1.1.4"
detective "^5.2.1"
didyoumean "^1.2.2"
dlv "^1.1.3"
fast-glob "^3.2.12"
glob-parent "^6.0.2"
is-glob "^4.0.3"
lilconfig "^2.0.6"
jiti "^1.18.2"
lilconfig "^2.1.0"
micromatch "^4.0.5"
normalize-path "^3.0.0"
object-hash "^3.0.0"
picocolors "^1.0.0"
postcss "^8.0.9"
postcss-import "^14.1.0"
postcss-js "^4.0.0"
postcss-load-config "^3.1.4"
postcss-nested "6.0.0"
postcss "^8.4.23"
postcss-import "^15.1.0"
postcss-js "^4.0.1"
postcss-load-config "^4.0.1"
postcss-nested "^6.0.1"
postcss-selector-parser "^6.0.11"
postcss-value-parser "^4.2.0"
quick-lru "^5.1.1"
resolve "^1.22.1"
resolve "^1.22.2"
sucrase "^3.32.0"
tapable@^2.0.0, tapable@^2.1.1, tapable@^2.2.0, tapable@^2.2.1:
version "2.2.1"
@@ -23783,6 +23862,20 @@ text-table@^0.2.0:
resolved "https://registry.yarnpkg.com/text-table/-/text-table-0.2.0.tgz#7f5ee823ae805207c00af2df4a84ec3fcfa570b4"
integrity sha512-N+8UisAXDGk8PFXP4HAzVR9nbfmVJ3zYLAWiTIoqC5v5isinhr+r5uaO8+7r3BMfuNIufIsA7RdpVgacC2cSpw==
thenify-all@^1.0.0:
version "1.6.0"
resolved "https://registry.yarnpkg.com/thenify-all/-/thenify-all-1.6.0.tgz#1a1918d402d8fc3f98fbf234db0bcc8cc10e9726"
integrity sha512-RNxQH/qI8/t3thXJDwcstUO4zeqo64+Uy/+sNVRBx4Xn2OX+OZ9oP+iJnNFqplFra2ZUVeKCSa2oVWi3T4uVmA==
dependencies:
thenify ">= 3.1.0 < 4"
"thenify@>= 3.1.0 < 4":
version "3.3.1"
resolved "https://registry.yarnpkg.com/thenify/-/thenify-3.3.1.tgz#8932e686a4066038a016dd9e2ca46add9838a95f"
integrity sha512-RVZSIV5IG10Hk3enotrhvz0T9em6cyHBLkH/YAZuKqd8hRkKhSfCGIcP2KUY0EPxndzANBmNllzWPwak+bheSw==
dependencies:
any-promise "^1.0.0"
thread-stream@^0.15.1:
version "0.15.2"
resolved "https://registry.yarnpkg.com/thread-stream/-/thread-stream-0.15.2.tgz#fb95ad87d2f1e28f07116eb23d85aba3bc0425f4"
@@ -23947,6 +24040,11 @@ ts-dedent@^2.0.0, ts-dedent@^2.2.0:
resolved "https://registry.yarnpkg.com/ts-dedent/-/ts-dedent-2.2.0.tgz#39e4bd297cd036292ae2394eb3412be63f563bb5"
integrity sha512-q5W7tVM71e2xjHZTlgfTDoPF/SmqKG5hddq9SzR49CH2hayqRKJtQ4mtRlSxKaJlR/+9rEM+mnBHf7I2/BQcpQ==
ts-interface-checker@^0.1.9:
version "0.1.13"
resolved "https://registry.yarnpkg.com/ts-interface-checker/-/ts-interface-checker-0.1.13.tgz#784fd3d679722bc103b1b4b8030bcddb5db2a699"
integrity sha512-Y/arvbn+rrz3JCKl9C4kVNfTfSm2/mEp5FSz5EsZSANGPSlQrpRI5M4PKF+mJnE52jOO90PnPSc3Ur3bTQw0gA==
ts-invariant@^0.10.3:
version "0.10.3"
resolved "https://registry.yarnpkg.com/ts-invariant/-/ts-invariant-0.10.3.tgz#3e048ff96e91459ffca01304dbc7f61c1f642f6c"
@@ -25165,7 +25263,7 @@ xmlchars@^2.2.0:
resolved "https://registry.yarnpkg.com/xmlchars/-/xmlchars-2.2.0.tgz#060fe1bcb7f9c76fe2a17db86a9bc3ab894210cb"
integrity sha512-JZnDKK8B0RCDw84FNdDAIpZK+JuJw+s7Lz8nksI7SIuU3UXJJslUthsi+uWBUYOwPFwW7W7PRLRfUKpxjtjFCw==
xtend@^4.0.0, xtend@^4.0.1, xtend@^4.0.2, xtend@~4.0.1:
xtend@^4.0.0, xtend@^4.0.1, xtend@~4.0.1:
version "4.0.2"
resolved "https://registry.yarnpkg.com/xtend/-/xtend-4.0.2.tgz#bb72779f5fa465186b1f438f674fa347fdb5db54"
integrity sha512-LKYU1iAXJXUgAXn9URjiu+MWhyUXHsvfp7mcuYm9dSUKK0/CjtrUwFAxD82/mCWbtLsGjFIad0wIsod4zrTAEQ==
@@ -25205,6 +25303,11 @@ yaml@^1.10.0, yaml@^1.10.2, yaml@^1.7.2:
resolved "https://registry.yarnpkg.com/yaml/-/yaml-1.10.2.tgz#2301c5ffbf12b467de8da2333a459e29e7920e4b"
integrity sha512-r3vXyErRCYJ7wg28yvBY5VSoAF8ZvlcW9/BwUzEtUsjvX/DKs24dIkuwjtuprwJJHsbyUbLApepYTR1BN4uHrg==
yaml@^2.1.1:
version "2.3.2"
resolved "https://registry.yarnpkg.com/yaml/-/yaml-2.3.2.tgz#f522db4313c671a0ca963a75670f1c12ea909144"
integrity sha512-N/lyzTPaJasoDmfV7YTrYCI0G/3ivm/9wdG0aHuheKowWQwGTsK0Eoiw6utmzAnI6pkJa0DUVygvp3spqqEKXg==
yargs-parser@20.2.4:
version "20.2.4"
resolved "https://registry.yarnpkg.com/yargs-parser/-/yargs-parser-20.2.4.tgz#b42890f14566796f85ae8e3a25290d205f154a54"