Commit d7252d4c authored by tom's avatar tom

Merge branch 'main' of github.com:blockscout/frontend into address-metamask

parents d04e2d33 6f6e325f
......@@ -43,6 +43,7 @@ NEXT_PUBLIC_AD_ADBUTLER_ON=__PLACEHOLDER_FORNEXT_PUBLIC_AD_ADBUTLER_ON__
# api config
NEXT_PUBLIC_API_HOST=__PLACEHOLDER_FOR_NEXT_PUBLIC_API_HOST__
NEXT_PUBLIC_API_BASE_PATH=__PLACEHOLDER_FOR_NEXT_PUBLIC_API_BASE_PATH__
NEXT_PUBLIC_STATS_API_HOST=__PLACEHOLDER_FOR_NEXT_PUBLIC_STATS_API_HOST__
# external services config
NEXT_PUBLIC_SENTRY_DSN=__PLACEHOLDER_FOR_NEXT_PUBLIC_SENTRY_DSN__
......
......@@ -94,6 +94,7 @@ The app instance could be customized by passing following variables to NodeJS en
| --- | --- | --- | --- |
| NEXT_PUBLIC_API_HOST | `string` *(optional)* | By default the API endpoint base URL will be set as `https://blockscout.com`. If it is not the case, pass the API host in this variable | `my-host.com` |
| NEXT_PUBLIC_API_BASE_PATH | `string` *(optional)* | Base path for API endpoint url | `/poa/core` |
| NEXT_PUBLIC_STATS_API_HOST | `string` *(optional)* | Pass the Stats API host in this variable | `https://my-host.com` |
### Featured network configuration properties
......
......@@ -94,6 +94,9 @@ const config = Object.freeze({
socket: apiHost ? `wss://${ apiHost }` : 'wss://blockscout.com',
basePath: stripTrailingSlash(getEnvValue(process.env.NEXT_PUBLIC_API_BASE_PATH) || ''),
},
statsApi: {
endpoint: getEnvValue(process.env.NEXT_PUBLIC_STATS_API_HOST),
},
homepage: {
charts: parseEnvJson<Array<ChainIndicatorId>>(getEnvValue(process.env.NEXT_PUBLIC_HOMEPAGE_CHARTS)) || [],
plateGradient: getEnvValue(process.env.NEXT_PUBLIC_HOMEPAGE_PLATE_GRADIENT) ||
......
......@@ -12,3 +12,4 @@ NEXT_PUBLIC_FOOTER_TWITTER_LINK=https://www.twitter.com/blockscoutcom
# api config
NEXT_PUBLIC_API_HOST=blockscout.com
NEXT_PUBLIC_STATS_API_HOST=https://stats-test.aws-k8s.blockscout.com
......@@ -20,3 +20,4 @@ NEXT_PUBLIC_MARKETPLACE_APP_LIST=[{'author': 'Blockscout','id':'token-approval-t
# api config
NEXT_PUBLIC_API_HOST=blockscout-main.test.aws-k8s.blockscout.com
NEXT_PUBLIC_API_BASE_PATH=/
NEXT_PUBLIC_STATS_API_HOST=https://stats-test.aws-k8s.blockscout.com
......@@ -10,3 +10,4 @@ NEXT_PUBLIC_FOOTER_TWITTER_LINK=https://www.twitter.com/blockscoutcom
# api config
NEXT_PUBLIC_API_HOST=blockscout.com
NEXT_PUBLIC_STATS_API_HOST=https://stats-test.aws-k8s.blockscout.com
......@@ -5,6 +5,7 @@ NEXT_PUBLIC_FEATURED_NETWORKS=[{'title':'Gnosis Chain','url':'https://blockscout
NEXT_PUBLIC_NETWORK_EXPLORERS=[{'title':'Anyblock','baseUrl':'https://explorer.anyblock.tools','paths':{'tx':'/ethereum/poa/core/transaction','address':'/ethereum/poa/core/address'}}]
NEXT_PUBLIC_HOMEPAGE_CHARTS=['daily_txs','coin_price','market_cup']
NEXT_PUBLIC_HOMEPAGE_PLATE_GRADIENT=radial-gradient(at 12% 37%, hsla(324,73%,67%,1) 0px, transparent 50%), radial-gradient(at 62% 14%, hsla(256,87%,73%,1) 0px, transparent 50%), radial-gradient(at 84% 80%, hsla(128,75%,73%,1) 0px, transparent 50%), radial-gradient(at 57% 46%, hsla(285,63%,72%,1) 0px, transparent 50%), radial-gradient(at 37% 30%, hsla(174,70%,61%,1) 0px, transparent 50%), radial-gradient(at 15% 86%, hsla(350,65%,70%,1) 0px, transparent 50%), radial-gradient(at 67% 57%, hsla(14,95%,76%,1) 0px, transparent 50%)
#NEXT_PUBLIC_NETWORK_LOGO=https://placekitten.com/300/60
#NEXT_PUBLIC_NETWORK_SMALL_LOGO=https://placekitten.com/300/300
# network config
......
......@@ -14,3 +14,4 @@ NEXT_PUBLIC_HOMEPAGE_SHOW_GAS_TRACKER=true
# api config
NEXT_PUBLIC_API_HOST=blockscout.com
NEXT_PUBLIC_STATS_API_HOST=https://stats-test.aws-k8s.blockscout.com
......@@ -455,6 +455,7 @@ frontend:
- "/blocks"
- "/block"
- "/address"
- "/stats"
resources:
limits:
memory:
......@@ -518,6 +519,8 @@ frontend:
_default: unknown
NEXT_PUBLIC_API_HOST:
_default: blockscout.com/eth/goerli
NEXT_PUBLIC_STATS_API_HOST:
_default: https://stats-test.aws-k8s.blockscout.com/
NEXT_PUBLIC_APP_HOST:
_default: blockscout.com/eth/goerli
NEXT_PUBLIC_LOGOUT_URL:
......
......@@ -318,6 +318,7 @@ frontend:
- "/block"
- "/login"
- "/address"
- "/stats"
resources:
limits:
memory:
......@@ -373,6 +374,8 @@ frontend:
NEXT_PUBLIC_API_HOST:
_default: blockscout.com
review: blockscout-main.test.aws-k8s.blockscout.com
NEXT_PUBLIC_STATS_API_HOST:
_default: https://stats-test.aws-k8s.blockscout.com/
NEXT_PUBLIC_AUTH_URL:
_default: https://blockscout-main.test.aws-k8s.blockscout.com
NEXT_PUBLIC_API_BASE_PATH:
......
<svg viewBox="0 0 30 30" xmlns="http://www.w3.org/2000/svg">
<path fill-rule="evenodd" clip-rule="evenodd" d="M13.822 15.052h-6.9v2.731h3.19l-.01 3.433c-.123.132-.269.24-.43.319-.21.108-.43.197-.656.267a5.653 5.653 0 0 1-1.656.247 3.012 3.012 0 0 1-2.715-1.424 7.746 7.746 0 0 1-.9-4.064v-2.6a9.44 9.44 0 0 1 .265-2.36 5.76 5.76 0 0 1 .715-1.71c.267-.427.63-.785 1.06-1.047.4-.237.858-.36 1.323-.357a2.922 2.922 0 0 1 2.191.735c.526.615.84 1.383.895 2.191h3.627a8.472 8.472 0 0 0-.615-2.458 5.315 5.315 0 0 0-1.304-1.857 5.626 5.626 0 0 0-2.047-1.164 9.016 9.016 0 0 0-2.84-.4 6.71 6.71 0 0 0-2.774.572A6.354 6.354 0 0 0 2.016 7.77a7.927 7.927 0 0 0-1.483 2.659 10.954 10.954 0 0 0-.532 3.557v2.575a11.475 11.475 0 0 0 .51 3.557c.3.97.793 1.871 1.45 2.646a6.251 6.251 0 0 0 2.264 1.65 7.406 7.406 0 0 0 2.966.573c.765.005 1.529-.07 2.278-.221a10.497 10.497 0 0 0 1.914-.58 7.636 7.636 0 0 0 1.477-.8c.362-.247.691-.54.98-.87l-.018-7.464Zm2.66 2.783c-.01.97.143 1.935.451 2.855.281.837.73 1.61 1.317 2.269a5.985 5.985 0 0 0 2.133 1.5 7.278 7.278 0 0 0 2.88.54 7.194 7.194 0 0 0 2.86-.54 5.935 5.935 0 0 0 2.118-1.5 6.602 6.602 0 0 0 1.311-2.27c.308-.92.46-1.884.45-2.854v-.274a8.673 8.673 0 0 0-.45-2.84 6.532 6.532 0 0 0-1.317-2.27 6.078 6.078 0 0 0-2.125-1.508 7.812 7.812 0 0 0-5.74 0 6.082 6.082 0 0 0-2.12 1.508 6.516 6.516 0 0 0-1.317 2.27 8.647 8.647 0 0 0-.45 2.84v.274Zm3.681-.273a7.394 7.394 0 0 1 .174-1.625c.1-.478.284-.936.541-1.352a2.633 2.633 0 0 1 2.357-1.26c.495-.016.984.1 1.418.337.387.224.712.542.947.923.253.417.435.874.535 1.352a7.4 7.4 0 0 1 .173 1.625v.273a7.613 7.613 0 0 1-.172 1.659c-.1.478-.281.935-.537 1.352a2.622 2.622 0 0 1-2.337 1.255 2.818 2.818 0 0 1-1.424-.338 2.759 2.759 0 0 1-.96-.917 4.143 4.143 0 0 1-.541-1.352 7.6 7.6 0 0 1-.174-1.659v-.274.001Zm-1.747-10.3c.086.196.212.37.37.514.167.147.36.262.57.338.472.164.985.164 1.456 0 .21-.076.404-.19.57-.338.16-.143.285-.318.37-.514a1.594 1.594 0 0 0 0-1.274 1.562 1.562 0 0 0-.37-.52 1.7 1.7 0 0 0-.57-.344 2.206 2.206 0 0 0-1.456 0 1.7 1.7 0 0 0-.57.345 1.562 1.562 0 0 0-.502 1.157c0 .219.045.436.133.637v-.001Zm6.357.013c.086.197.212.374.37.52a1.7 1.7 0 0 0 .57.345c.47.165.984.165 1.456 0a1.7 1.7 0 0 0 .57-.345c.157-.146.283-.323.37-.52.089-.205.134-.427.132-.65a1.561 1.561 0 0 0-.503-1.151 1.761 1.761 0 0 0-.57-.338 2.206 2.206 0 0 0-1.456 0c-.21.075-.403.19-.57.338a1.546 1.546 0 0 0-.503 1.151c-.002.224.043.446.134.65Z" fill="currentColor"/>
<path fill-rule="evenodd" clip-rule="evenodd" d="M13.822 15.052h-6.9v2.731h3.19l-.01 3.433c-.123.132-.269.24-.43.319-.21.108-.43.197-.656.267a5.653 5.653 0 0 1-1.656.247 3.012 3.012 0 0 1-2.715-1.424 7.746 7.746 0 0 1-.9-4.064v-2.6a9.44 9.44 0 0 1 .265-2.36 5.76 5.76 0 0 1 .715-1.71c.267-.427.63-.785 1.06-1.047.4-.237.858-.36 1.323-.357a2.922 2.922 0 0 1 2.191.735c.526.615.84 1.383.895 2.191h3.627a8.472 8.472 0 0 0-.615-2.458 5.315 5.315 0 0 0-1.304-1.857 5.626 5.626 0 0 0-2.047-1.164 9.016 9.016 0 0 0-2.84-.4 6.71 6.71 0 0 0-2.774.572A6.354 6.354 0 0 0 2.016 7.77a7.927 7.927 0 0 0-1.483 2.659 10.954 10.954 0 0 0-.532 3.557v2.575a11.475 11.475 0 0 0 .51 3.557c.3.97.793 1.871 1.45 2.646a6.251 6.251 0 0 0 2.264 1.65 7.406 7.406 0 0 0 2.966.573c.765.005 1.529-.07 2.278-.221a10.497 10.497 0 0 0 1.914-.58 7.636 7.636 0 0 0 1.477-.8 5.2 5.2 0 0 0 .98-.87l-.018-7.464Zm2.66 2.783c-.01.97.143 1.935.451 2.855.281.837.73 1.61 1.317 2.269a5.985 5.985 0 0 0 2.133 1.5 7.278 7.278 0 0 0 2.88.54 7.194 7.194 0 0 0 2.86-.54 5.935 5.935 0 0 0 2.118-1.5 6.602 6.602 0 0 0 1.311-2.27c.308-.92.46-1.884.45-2.854v-.274a8.673 8.673 0 0 0-.45-2.84 6.532 6.532 0 0 0-1.317-2.27 6.078 6.078 0 0 0-2.125-1.508 7.812 7.812 0 0 0-5.74 0 6.082 6.082 0 0 0-2.12 1.508 6.516 6.516 0 0 0-1.317 2.27 8.647 8.647 0 0 0-.45 2.84v.274Zm3.681-.273a7.394 7.394 0 0 1 .174-1.625c.1-.478.284-.936.541-1.352a2.633 2.633 0 0 1 2.357-1.26c.495-.016.984.1 1.418.337.387.224.712.542.947.923.253.417.435.874.535 1.352a7.4 7.4 0 0 1 .173 1.625v.273a7.613 7.613 0 0 1-.172 1.659c-.1.478-.281.935-.537 1.352a2.622 2.622 0 0 1-2.337 1.255 2.818 2.818 0 0 1-1.424-.338 2.759 2.759 0 0 1-.96-.917 4.143 4.143 0 0 1-.541-1.352 7.6 7.6 0 0 1-.174-1.659v-.274.001Zm-1.747-10.3c.086.196.212.37.37.514.167.147.36.262.57.338.472.164.985.164 1.456 0 .21-.076.404-.19.57-.338.16-.143.285-.318.37-.514a1.594 1.594 0 0 0 0-1.274 1.562 1.562 0 0 0-.37-.52 1.7 1.7 0 0 0-.57-.344 2.206 2.206 0 0 0-1.456 0 1.7 1.7 0 0 0-.57.345 1.562 1.562 0 0 0-.502 1.157c0 .219.045.436.133.637v-.001Zm6.357.013c.086.197.212.374.37.52a1.7 1.7 0 0 0 .57.345c.47.165.984.165 1.456 0a1.7 1.7 0 0 0 .57-.345c.157-.146.283-.323.37-.52.089-.205.134-.427.132-.65a1.561 1.561 0 0 0-.503-1.151 1.761 1.761 0 0 0-.57-.338 2.206 2.206 0 0 0-1.456 0c-.21.075-.403.19-.57.338a1.546 1.546 0 0 0-.503 1.151c-.002.224.043.446.134.65Z" fill="currentColor"/>
</svg>
<svg viewBox="0 0 20 20" fill="none" xmlns="http://www.w3.org/2000/svg">
<path d="M6.485 7.012h1.446a.918.918 0 1 1 0 1.84H4.253a.919.919 0 0 1-.92-.92V4.254a.919.919 0 1 1 1.84 0v1.47l.505-.505a6.436 6.436 0 0 1 9.103 0 6.436 6.436 0 0 1 0 9.103 6.436 6.436 0 0 1-9.103 0 .92.92 0 0 1 1.302-1.301 4.597 4.597 0 1 0 0-6.503l-.495.494Z" fill="currentColor"/>
</svg>
<svg viewBox="0 0 30 30" fill="none" xmlns="http://www.w3.org/2000/svg">
<path fill-rule="evenodd" clip-rule="evenodd" d="M11.615 5.192c0-.934.758-1.692 1.693-1.692h2.538c.935 0 1.692.758 1.692 1.692v18.616c0 .934-.757 1.692-1.692 1.692h-2.538a1.692 1.692 0 0 1-1.693-1.692V5.192Zm4.231.339a.339.339 0 0 0-.338-.339h-1.862a.339.339 0 0 0-.338.339v17.938c0 .187.151.339.338.339h1.862a.339.339 0 0 0 .338-.339V5.531ZM4 19.577c0-.935.758-1.692 1.692-1.692h2.539c.934 0 1.692.757 1.692 1.692v4.23c0 .935-.758 1.693-1.692 1.693H5.692A1.692 1.692 0 0 1 4 23.808v-4.231Zm4.23.338a.339.339 0 0 0-.338-.338H6.031a.339.339 0 0 0-.339.338v3.554a.34.34 0 0 0 .339.339h1.861a.338.338 0 0 0 .339-.339v-3.554Zm12.693-9.646c-.934 0-1.692.758-1.692 1.693v11.846c0 .934.757 1.692 1.692 1.692h2.539c.934 0 1.692-.758 1.692-1.692V11.962c0-.935-.758-1.693-1.692-1.693h-2.539Zm2.2 1.693c.187 0 .339.151.339.338v11.17a.338.338 0 0 1-.339.338h-1.861a.338.338 0 0 1-.339-.339V12.3c0-.187.152-.338.339-.338h1.861Z" fill="currentColor"/>
</svg>
......@@ -8,14 +8,17 @@ import * as cookies from 'lib/cookies';
// first arg can be only a string
// FIXME migrate to RequestInfo later if needed
export default function fetchFactory(_req: NextApiRequest) {
export default function fetchFactory(
_req: NextApiRequest,
apiEndpoint: string = appConfig.api.endpoint,
) {
return function fetch(path: string, init?: RequestInit): Promise<Response> {
const headers = {
accept: 'application/json',
'content-type': 'application/json',
cookie: `${ cookies.NAMES.API_TOKEN }=${ _req.cookies[cookies.NAMES.API_TOKEN] }`,
};
const url = new URL(path, appConfig.api.endpoint);
const url = new URL(path, apiEndpoint);
httpLogger.logger.info({
message: 'Trying to call API',
......
......@@ -6,7 +6,7 @@ import { httpLogger } from 'lib/api/logger';
type Methods = 'GET' | 'POST' | 'PUT' | 'DELETE';
export default function createHandler(getUrl: (_req: NextApiRequest) => string, allowedMethods: Array<Methods>) {
export default function createHandler(getUrl: (_req: NextApiRequest) => string, allowedMethods: Array<Methods>, apiEndpoint?: string) {
const handler = async(_req: NextApiRequest, res: NextApiResponse) => {
httpLogger(_req, res);
......@@ -18,8 +18,8 @@ export default function createHandler(getUrl: (_req: NextApiRequest) => string,
const isBodyDisallowed = _req.method === 'GET' || _req.method === 'HEAD';
const url = getUrlWithNetwork(_req, `/api${ getUrl(_req) }`);
const fetch = fetchFactory(_req);
const url = apiEndpoint ? `/api${ getUrl(_req) }` : getUrlWithNetwork(_req, `/api${ getUrl(_req) }`);
const fetch = fetchFactory(_req, apiEndpoint);
const response = await fetch(url, {
method: _req.method,
body: isBodyDisallowed ? undefined : _req.body,
......
import BigNumber from 'bignumber.js';
interface Params {
value: string;
exchangeRate?: string | null;
accuracy?: number;
accuracyUsd?: number;
decimals?: string | null;
}
export default function getCurrencyValue({ value, accuracy, accuracyUsd, decimals, exchangeRate }: Params) {
const valueCurr = BigNumber(value).div(BigNumber(10 ** Number(decimals || '18')));
const valueResult = accuracy ? valueCurr.dp(accuracy).toFormat() : valueCurr.toFormat();
let usdResult: string | undefined;
if (exchangeRate) {
const exchangeRateBn = new BigNumber(exchangeRate);
const usdBn = valueCurr.times(exchangeRateBn);
if (accuracyUsd && !usdBn.isEqualTo(0)) {
const usdBnDp = usdBn.dp(accuracyUsd);
usdResult = usdBnDp.isEqualTo(0) ? usdBn.precision(accuracyUsd).toFormat() : usdBnDp.toFormat();
} else {
usdResult = usdBn.toFormat();
}
}
return { valueStr: valueResult, usd: usdResult };
}
import { useRouter } from 'next/router';
import link from 'lib/link/link';
export default function useLoginUrl() {
const router = useRouter();
return link('auth', {}, { path: router.asPath });
}
......@@ -9,6 +9,7 @@ import blocksIcon from 'icons/block.svg';
import privateTagIcon from 'icons/privattags.svg';
import profileIcon from 'icons/profile.svg';
import publicTagIcon from 'icons/publictags.svg';
import statsIcon from 'icons/stats.svg';
import tokensIcon from 'icons/token.svg';
import transactionsIcon from 'icons/transactions.svg';
import watchlistIcon from 'icons/watchlist.svg';
......@@ -28,6 +29,7 @@ export default function useNavItems() {
{ text: 'Tokens', url: link('tokens'), icon: tokensIcon, isActive: currentRoute === 'tokens', isNewUi: false },
isMarketplaceFilled ?
{ text: 'Apps', url: link('apps'), icon: appsIcon, isActive: currentRoute === 'apps', isNewUi: true } : null,
{ text: 'Charts & stats', url: link('stats'), icon: statsIcon, isActive: currentRoute === 'stats', isNewUi: true },
// there should be custom site sections like Stats, Faucet, More, etc but never an 'other'
// examples https://explorer-edgenet.polygon.technology/ and https://explorer.celo.org/
// at this stage custom menu items is under development, we will implement it later
......
import React from 'react';
// prevent set focus on button when closing modal
export default function usePreventFocusAfterModalClosing() {
return React.useCallback((e: React.SyntheticEvent) => e.stopPropagation(), []);
}
......@@ -3,9 +3,9 @@ import { useQuery, useQueryClient } from '@tanstack/react-query';
import { pick, omit } from 'lodash';
import { useRouter } from 'next/router';
import React, { useCallback } from 'react';
import { animateScroll } from 'react-scroll';
import { animateScroll, scroller } from 'react-scroll';
import { PAGINATION_FIELDS } from 'types/api/pagination';
import { PAGINATION_FIELDS, PAGINATION_FILTERS_FIELDS } from 'types/api/pagination';
import type { PaginationParams, PaginatedResponse, PaginatedQueryKeys, PaginationFilters } from 'types/api/pagination';
import useFetch from 'lib/hooks/useFetch';
......@@ -16,9 +16,17 @@ interface Params<QueryName extends PaginatedQueryKeys> {
queryIds?: Array<string>;
filters?: PaginationFilters<QueryName>;
options?: Omit<UseQueryOptions<unknown, unknown, PaginatedResponse<QueryName>>, 'queryKey' | 'queryFn'>;
scroll?: { elem: string; offset: number };
}
export default function useQueryWithPages<QueryName extends PaginatedQueryKeys>({ queryName, filters, options, apiPath, queryIds }: Params<QueryName>) {
export default function useQueryWithPages<QueryName extends PaginatedQueryKeys>({
queryName,
filters,
options,
apiPath,
queryIds,
scroll,
}: Params<QueryName>) {
const paginationFields = PAGINATION_FIELDS[queryName];
const queryClient = useQueryClient();
const router = useRouter();
......@@ -31,6 +39,10 @@ export default function useQueryWithPages<QueryName extends PaginatedQueryKeys>(
const queryKey = [ queryName, ...(queryIds || []), { page, filters } ];
const scrollToTop = useCallback(() => {
scroll ? scroller.scrollTo(scroll.elem, { offset: scroll.offset }) : animateScroll.scrollToTop({ duration: 0 });
}, [ scroll ]);
const queryResult = useQuery<unknown, unknown, PaginatedResponse<QueryName>>(
queryKey,
async() => {
......@@ -65,10 +77,10 @@ export default function useQueryWithPages<QueryName extends PaginatedQueryKeys>(
router.push({ pathname: router.pathname, query: nextPageQuery }, undefined, { shallow: true })
.then(() => {
animateScroll.scrollToTop({ duration: 0 });
scrollToTop();
setPage(prev => prev + 1);
});
}, [ data?.next_page_params, page, pageParams.length, router ]);
}, [ data?.next_page_params, page, pageParams.length, router, scrollToTop ]);
const onPrevPageClick = useCallback(() => {
// returning to the first page
......@@ -85,21 +97,44 @@ export default function useQueryWithPages<QueryName extends PaginatedQueryKeys>(
router.query = nextPageQuery;
router.push({ pathname: router.pathname, query: nextPageQuery }, undefined, { shallow: true })
.then(() => {
animateScroll.scrollToTop({ duration: 0 });
scrollToTop();
setPage(prev => prev - 1);
page === 2 && queryClient.clear();
});
}, [ router, page, paginationFields, pageParams, queryClient ]);
}, [ router, page, paginationFields, pageParams, queryClient, scrollToTop ]);
const resetPage = useCallback(() => {
queryClient.clear();
router.push({ pathname: router.pathname, query: omit(router.query, paginationFields, 'page') }, undefined, { shallow: true }).then(() => {
animateScroll.scrollToTop({ duration: 0 });
queryClient.removeQueries({ queryKey: [ queryName ] });
scrollToTop();
setPage(1);
setPageParams([ ]);
canGoBackwards.current = true;
});
}, [ queryClient, router, paginationFields ]);
}, [ queryClient, queryName, router, paginationFields, scrollToTop ]);
const onFilterChange = useCallback((newFilters: PaginationFilters<QueryName> | undefined) => {
const newQuery = omit(router.query, PAGINATION_FIELDS[queryName], 'page', PAGINATION_FILTERS_FIELDS[queryName]);
if (newFilters) {
Object.entries(newFilters).forEach(([ key, value ]) => {
if (value) {
newQuery[key] = Array.isArray(value) ? value.join(',') : (value || '');
}
});
}
router.push(
{
pathname: router.pathname,
query: newQuery,
},
undefined,
{ shallow: true },
).then(() => {
setPage(1);
setPageParams([ ]);
scrollToTop();
});
}, [ queryName, router, scrollToTop, setPageParams, setPage ]);
const hasPaginationParams = Object.keys(currPageParams).length > 0;
const nextPageParams = data?.next_page_params;
......@@ -129,5 +164,5 @@ export default function useQueryWithPages<QueryName extends PaginatedQueryKeys>(
}, 0);
}, []);
return { ...queryResult, pagination };
return { ...queryResult, pagination, onFilterChange };
}
......@@ -5,7 +5,7 @@ import React from 'react';
import { QueryKeys } from 'types/client/accountQueries';
import * as cookies from 'lib/cookies';
import link from 'lib/link/link';
import useLoginUrl from 'lib/hooks/useLoginUrl';
export interface ErrorType {
error?: {
......@@ -19,6 +19,7 @@ export default function useRedirectForInvalidAuthToken() {
const state = queryClient.getQueryState<unknown, ErrorType>([ QueryKeys.profile ]);
const errorStatus = state?.error?.error?.status;
const loginUrl = useLoginUrl();
React.useEffect(() => {
if (errorStatus === 401) {
......@@ -26,9 +27,8 @@ export default function useRedirectForInvalidAuthToken() {
if (apiToken) {
Sentry.captureException(new Error('Invalid api token'), { tags: { source: 'fetch' } });
const authURL = link('auth');
window.location.assign(authURL);
window.location.assign(loginUrl);
}
}
}, [ errorStatus ]);
}, [ errorStatus, loginUrl ]);
}
......@@ -4,13 +4,18 @@ import React from 'react';
import type { RouteName } from 'lib/link/routes';
import { ROUTES } from 'lib/link/routes';
const PATH_PARAM_REGEXP = /\/:(\w+)/g;
export default function useCurrentRoute() {
const { route: nextRoute } = useRouter();
return React.useCallback((): RouteName => {
for (const routeName in ROUTES) {
const route = ROUTES[routeName as RouteName];
if (route.pattern === nextRoute) {
const formattedRoute = route.pattern.replace(PATH_PARAM_REGEXP, (_, paramName: string) => {
return `/[${ paramName }]`;
});
if (formattedRoute === nextRoute) {
return routeName as RouteName;
}
}
......
......@@ -7,6 +7,10 @@ SocketMessage.BlocksIndexStatus |
SocketMessage.TxStatusUpdate |
SocketMessage.NewTx |
SocketMessage.NewPendingTx |
SocketMessage.AddressBalance |
SocketMessage.AddressCurrentCoinBalance |
SocketMessage.AddressTokenBalance |
SocketMessage.AddressCoinBalance |
SocketMessage.Unknown;
interface SocketMessageParamsGeneric<Event extends string | undefined, Payload extends object | unknown> {
......@@ -15,6 +19,16 @@ interface SocketMessageParamsGeneric<Event extends string | undefined, Payload e
handler: (payload: Payload) => void;
}
export interface AddressCoinBalancePayload {
coin_balance: {
block_number: number;
block_timestamp?: string;
delta?: string;
transaction_hash?: string | null;
value?: string;
};
}
// eslint-disable-next-line @typescript-eslint/no-namespace
export namespace SocketMessage {
export type NewBlock = SocketMessageParamsGeneric<'new_block', NewBlockSocketResponse>;
......@@ -22,5 +36,10 @@ export namespace SocketMessage {
export type TxStatusUpdate = SocketMessageParamsGeneric<'collated', NewBlockSocketResponse>;
export type NewTx = SocketMessageParamsGeneric<'transaction', { transaction: number }>;
export type NewPendingTx = SocketMessageParamsGeneric<'pending_transaction', { pending_transaction: number }>;
export type AddressBalance = SocketMessageParamsGeneric<'balance', { balance: string; block_number: number; exchange_rate: string }>;
export type AddressCurrentCoinBalance =
SocketMessageParamsGeneric<'current_coin_balance', { coin_balance: string; block_number: number; exchange_rate: string }>;
export type AddressTokenBalance = SocketMessageParamsGeneric<'token_balance', { block_number: number }>;
export type AddressCoinBalance = SocketMessageParamsGeneric<'coin_balance', AddressCoinBalancePayload>;
export type Unknown = SocketMessageParamsGeneric<undefined, unknown>;
}
......@@ -21,7 +21,7 @@ export function middleware(req: NextRequest) {
const apiToken = req.cookies.get(NAMES.API_TOKEN);
if ((isAccountRoute || isProfileRoute) && !apiToken && appConfig.isAccountSupported) {
const authUrl = link('auth');
const authUrl = link('auth', undefined, { path: req.nextUrl.pathname });
return NextResponse.redirect(authUrl);
}
......
import type { AddressTokenBalance } from 'types/api/address';
export const erc20a: AddressTokenBalance = {
token: {
address: '0xb2a90505dc6680a7a695f7975d0d32EeF610f456',
decimals: '18',
exchange_rate: null,
holders: '23',
name: 'hyfi.token',
symbol: 'HyFi',
total_supply: '369000000000000000000000000',
type: 'ERC-20',
},
token_id: null,
value: '1169320000000000000000000',
};
export const erc20b: AddressTokenBalance = {
token: {
address: '0xc1116c98ba622a6218433fF90a2E40DEa482d7A7',
decimals: '6',
exchange_rate: '0.982',
holders: '17',
name: 'USD Coin',
symbol: 'USDC',
total_supply: '900000000000000000000000000',
type: 'ERC-20',
},
token_id: null,
value: '872500000000',
};
export const erc20c: AddressTokenBalance = {
token: {
address: '0xc1116c98ba622a6218433fF90a2E40DEa482d7A7',
decimals: '18',
exchange_rate: '1328.89',
holders: '17',
name: 'Ethereum',
symbol: 'ETH',
total_supply: '1000000000000000000000000',
type: 'ERC-20',
},
token_id: null,
value: '9852000000000000000000',
};
export const erc20d: AddressTokenBalance = {
token: {
address: '0xCc7bb2D219A0FC08033E130629C2B854b7bA9195',
decimals: '18',
exchange_rate: null,
holders: '102625',
name: 'Zeta',
symbol: 'ZETA',
total_supply: '2100000000000000000000000000',
type: 'ERC-20',
},
token_id: null,
value: '39000000000000000000',
};
export const erc721a: AddressTokenBalance = {
token: {
address: '0xDe7cAc71E072FCBd4453E5FB3558C2684d1F88A0',
decimals: null,
exchange_rate: null,
holders: '7',
name: 'HyFi Athena',
symbol: 'HYFI_ATHENA',
total_supply: '105',
type: 'ERC-721',
},
token_id: null,
value: '51',
};
export const erc721b: AddressTokenBalance = {
token: {
address: '0xA8d5C7beEA8C9bB57f5fBa35fB638BF45550b11F',
decimals: null,
exchange_rate: null,
holders: '2',
name: 'World Of Women Galaxy',
symbol: 'WOWG',
total_supply: null,
type: 'ERC-721',
},
token_id: null,
value: '1',
};
export const erc721c: AddressTokenBalance = {
token: {
address: '0x47646F1d7dc4Dd2Db5a41D092e2Cf966e27A4992',
decimals: null,
exchange_rate: null,
holders: '12',
name: 'Puma',
symbol: 'PUMA',
total_supply: null,
type: 'ERC-721',
},
token_id: null,
value: '5',
};
export const erc1155a: AddressTokenBalance = {
token: {
address: '0x4b333DEd10c7ca855EA2C8D4D90A0a8b73788c8e',
decimals: null,
exchange_rate: null,
holders: '22',
name: 'HyFi Membership',
symbol: 'HYFI_MEMBERSHIP',
total_supply: '482',
type: 'ERC-1155',
},
token_id: '42',
value: '24',
};
export const erc1155b: AddressTokenBalance = {
token: {
address: '0xf4b71b179132ad457f6bcae2a55efa9e4b26eefc',
decimals: null,
exchange_rate: null,
holders: '100',
name: 'WinkyVerse Collections',
symbol: 'WVC',
total_supply: '4943',
type: 'ERC-1155',
},
token_id: '100010000000001',
value: '11',
};
export const erc1155withoutName: AddressTokenBalance = {
token: {
address: '0x4b333DEd10c7ca855EA2C8D4D90A0a8b73788c8e',
decimals: null,
exchange_rate: null,
holders: '22',
name: null,
symbol: null,
total_supply: '482',
type: 'ERC-1155',
},
token_id: '64532245',
value: '42',
};
export const baseList = [
erc20a,
erc20b,
erc20c,
erc721a,
erc721b,
erc721c,
erc1155withoutName,
erc1155a,
erc1155b,
];
......@@ -29,6 +29,7 @@ export const erc20: TokenTransfer = {
name: 'ARIANEE',
symbol: 'ARIA',
type: 'ERC-20',
total_supply: '0',
},
total: {
decimals: '18',
......@@ -67,6 +68,7 @@ export const erc721: TokenTransfer = {
name: 'Arianee Smart-Asset',
symbol: 'AriaSA',
type: 'ERC-721',
total_supply: '0',
},
total: {
token_id: '875879856',
......@@ -104,6 +106,7 @@ export const erc1155: TokenTransfer = {
name: null,
symbol: null,
type: 'ERC-1155',
total_supply: '0',
},
total: {
token_id: '123',
......
import type { NextApiRequest } from 'next';
import getSearchParams from 'lib/api/getSearchParams';
import handler from 'lib/api/handler';
const getUrl = (req: NextApiRequest) => {
const searchParamsStr = getSearchParams(req);
return `/v2/addresses/${ req.query.id }/token-transfers${ searchParamsStr ? '?' + searchParamsStr : '' }`;
};
const requestHandler = handler(getUrl, [ 'GET' ]);
export default requestHandler;
import type { NextApiRequest } from 'next';
import getSearchParams from 'lib/api/getSearchParams';
import handler from 'lib/api/handler';
const getUrl = (req: NextApiRequest) => {
const searchParamsStr = getSearchParams(req);
return `/v2/addresses/${ req.query.id }/transactions${ searchParamsStr ? '?' + searchParamsStr : '' }`;
};
const requestHandler = handler(getUrl, [ 'GET' ]);
export default requestHandler;
import type { NextApiRequest } from 'next';
import appConfig from 'configs/app/config';
import handler from 'lib/api/handler';
const getUrl = (req: NextApiRequest) => {
const { name, from, to } = req.query;
return `/v1/charts/line?name=${ name }${ from ? `&from=${ from }&to=${ to }` : '' }`;
};
const requestHandler = handler(getUrl, [ 'GET' ], appConfig.statsApi.endpoint);
export default requestHandler;
import appConfig from 'configs/app/config';
import handler from 'lib/api/handler';
const getUrl = () => '/v1/counters';
const requestHandler = handler(getUrl, [ 'GET' ], appConfig.statsApi.endpoint);
export default requestHandler;
......@@ -2,6 +2,7 @@ import type { TestFixture, Page } from '@playwright/test';
import type { WebSocket } from 'ws';
import { WebSocketServer } from 'ws';
import type { AddressCoinBalancePayload } from 'lib/socket/types';
import type { NewBlockSocketResponse } from 'types/api/block';
type ReturnType = () => Promise<WebSocket>;
......@@ -53,6 +54,8 @@ export const joinChannel = async(socket: WebSocket, channelName: string) => {
});
};
export function sendMessage(socket: WebSocket, channel: Channel, msg: 'coin_balance', payload: AddressCoinBalancePayload): void;
export function sendMessage(socket: WebSocket, channel: Channel, msg: 'token_balance', payload: { block_number: number }): void;
export function sendMessage(socket: WebSocket, channel: Channel, msg: 'transaction', payload: { transaction: number }): void;
export function sendMessage(socket: WebSocket, channel: Channel, msg: 'pending_transaction', payload: { pending_transaction: number }): void;
export function sendMessage(socket: WebSocket, channel: Channel, msg: 'new_block', payload: NewBlockSocketResponse): void;
......
......@@ -51,15 +51,15 @@ const variantOutline = defineStyle((props) => {
const activeBg = isGrayTheme ? mode('blue.50', 'gray.600')(props) : mode(`${ c }.50`, 'gray.600')(props);
const activeColor = (() => {
if (c === 'gray') {
return mode('blue.400', 'gray.50')(props);
return mode('blue.600', 'gray.50')(props);
}
if (c === 'gray-dark') {
return mode('blue.700', 'gray.50')(props);
return mode('blue.600', 'gray.50')(props);
}
if (c === 'blue') {
return mode('blue.400', 'gray.50')(props);
return mode('blue.600', 'gray.50')(props);
}
return 'blue.400';
return 'blue.600';
})();
return {
......@@ -77,11 +77,17 @@ const variantOutline = defineStyle((props) => {
bg: props.isActive ? activeBg : 'transparent',
borderColor: props.isActive ? activeBg : 'blue.400',
color: props.isActive ? activeColor : 'blue.400',
p: {
color: 'blue.400',
},
},
_disabled: {
color,
borderColor,
},
p: {
color: 'blue.400',
},
},
_disabled: {
opacity: 0.2,
......@@ -94,6 +100,9 @@ const variantOutline = defineStyle((props) => {
color,
borderColor,
},
p: {
color: activeColor,
},
},
};
});
......
import { formAnatomy as parts } from '@chakra-ui/anatomy';
import { createMultiStyleConfigHelpers } from '@chakra-ui/styled-system';
import type { StyleFunctionProps } from '@chakra-ui/theme-tools';
import { getColor, mode } from '@chakra-ui/theme-tools';
import { getColor } from '@chakra-ui/theme-tools';
import getDefaultFormColors from '../utils/getDefaultFormColors';
import FormLabel from './FormLabel';
......@@ -13,7 +13,7 @@ const { definePartsStyle, defineMultiStyleConfig } =
function getFloatingVariantStylesForSize(size: 'md' | 'lg', props: StyleFunctionProps) {
const { theme } = props;
const { focusColor: fc, errorColor: ec } = getDefaultFormColors(props);
const { focusPlaceholderColor, errorColor } = getDefaultFormColors(props);
const activeLabelStyles = {
...FormLabel.variants?.floating?.(props)._focusWithin,
......@@ -62,7 +62,7 @@ function getFloatingVariantStylesForSize(size: 'md' | 'lg', props: StyleFunction
label: FormLabel.sizes?.[size](props) || {},
'input:not(:placeholder-shown) + label, textarea:not(:placeholder-shown) + label': activeLabelStyles,
'input[aria-invalid=true] + label, textarea[aria-invalid=true] + label': {
color: getColor(theme, ec),
color: getColor(theme, errorColor),
},
// input styles
......@@ -78,20 +78,20 @@ function getFloatingVariantStylesForSize(size: 'md' | 'lg', props: StyleFunction
// indicator styles
'input:not(:placeholder-shown) + label .chakra-form__required-indicator, textarea:not(:placeholder-shown) + label .chakra-form__required-indicator': {
color: getColor(theme, fc),
color: getColor(theme, focusPlaceholderColor),
},
'input[aria-invalid=true] + label .chakra-form__required-indicator, textarea[aria-invalid=true] + label .chakra-form__required-indicator': {
color: getColor(theme, ec),
color: getColor(theme, errorColor),
},
},
};
}
const baseStyle = definePartsStyle((props) => {
const baseStyle = definePartsStyle(() => {
return {
requiredIndicator: {
marginStart: 0,
color: mode('gray.500', 'whiteAlpha.400')(props),
color: 'gray.500',
},
};
});
......
......@@ -19,7 +19,7 @@ const baseStyle = defineStyle({
const variantFloating = defineStyle((props) => {
const { theme, backgroundColor } = props;
const { focusColor: fc } = getDefaultFormColors(props);
const { focusPlaceholderColor } = getDefaultFormColors(props);
const bc = backgroundColor || mode('white', 'black')(props);
return {
......@@ -40,7 +40,7 @@ const variantFloating = defineStyle((props) => {
textOverflow: 'ellipsis',
_focusWithin: {
backgroundColor: bc,
color: getColor(theme, fc),
color: getColor(theme, focusPlaceholderColor),
fontSize: 'xs',
lineHeight: '16px',
borderTopRightRadius: 'none',
......
......@@ -26,6 +26,10 @@ const baseStyleContent = defineStyle((props) => {
bg: $popperBg.reference,
[$arrowBg.variable]: $popperBg.reference,
[$arrowShadowColor.variable]: `colors.${ shadowColor }`,
_dark: {
[$popperBg.variable]: `colors.gray.900`,
[$arrowShadowColor.variable]: `colors.whiteAlpha.300`,
},
width: 'xs',
border: 'none',
borderColor: 'inherit',
......
......@@ -4,7 +4,8 @@ import { mode } from '@chakra-ui/theme-tools';
export default function getDefaultFormColors(props: StyleFunctionProps) {
const { focusBorderColor: fc, errorBorderColor: ec, filledBorderColor: flc } = props;
return {
focusColor: fc || mode('brand.700', 'brand.300')(props),
focusBorderColor: fc || mode('blue.500', 'blue.300')(props),
focusPlaceholderColor: fc || 'gray.500',
errorColor: ec || mode('red.400', 'red.300')(props),
filledColor: flc || mode('gray.300', 'gray.600')(props),
};
......
......@@ -6,7 +6,7 @@ import getDefaultTransitionProps from './getDefaultTransitionProps';
export default function getOutlinedFieldStyles(props: StyleFunctionProps) {
const { theme, borderColor } = props;
const { focusColor: fc, errorColor: ec } = getDefaultFormColors(props);
const { focusBorderColor, errorColor } = getDefaultFormColors(props);
const transitionProps = getDefaultTransitionProps();
return {
......@@ -32,12 +32,12 @@ export default function getOutlinedFieldStyles(props: StyleFunctionProps) {
},
},
_invalid: {
borderColor: getColor(theme, ec),
borderColor: getColor(theme, errorColor),
boxShadow: `none`,
},
_focusVisible: {
zIndex: 1,
borderColor: getColor(theme, fc),
borderColor: getColor(theme, focusBorderColor),
boxShadow: 'md',
},
_placeholder: {
......
import type { AddressParam } from './addressParams';
export interface AddressTag {
address_hash: string;
name: string;
......@@ -63,6 +64,7 @@ export interface WatchlistAddress {
notification_settings: NotificationSettings;
notification_methods: NotificationMethods;
id: string;
address?: AddressParam;
}
export interface WatchlistAddressNew {
......
import type { Transaction } from 'types/api/transaction';
import type { AddressTag, WatchlistName } from './addressParams';
import type { TokenInfo } from './tokenInfo';
import type { TokenInfo, TokenType } from './tokenInfo';
import type { TokenTransfer, TokenTransferPagination } from './tokenTransfer';
export interface Address {
block_number_balance_updated_at: number | null;
......@@ -20,10 +23,10 @@ export interface Address {
}
export interface AddressCounters {
transaction_count: string;
token_transfer_count: string;
transactions_count: string;
token_transfers_count: string;
gas_usage_count: string;
validation_count: string | null;
validations_count: string | null;
}
export interface AddressTokenBalance {
......@@ -31,3 +34,28 @@ export interface AddressTokenBalance {
token_id: string | null;
value: string;
}
export interface AddressTransactionsResponse {
items: Array<Transaction>;
next_page_params: {
block_number: number;
index: number;
items_count: number;
} | null;
}
type AddressFromToFilter = 'from' | 'to' | undefined;
export type AddressTxsFilters = {
filter: AddressFromToFilter;
}
export interface AddressTokenTransferResponse {
items: Array<TokenTransfer>;
next_page_params: TokenTransferPagination | null;
}
export type AddressTokenTransferFilters = {
filter: AddressFromToFilter;
type: TokenType;
}
import type { AddressTransactionsResponse, AddressTokenTransferResponse, AddressTxsFilters, AddressTokenTransferFilters } from 'types/api/address';
import type { BlocksResponse, BlockTransactionsResponse, BlockFilters } from 'types/api/block';
import type { InternalTransactionsResponse } from 'types/api/internalTransaction';
import type { LogsResponse } from 'types/api/log';
......@@ -8,6 +9,8 @@ import { QueryKeys } from 'types/client/queries';
import type { KeysOfObjectOrNull } from 'types/utils/KeysOfObjectOrNull';
export type PaginatedQueryKeys =
QueryKeys.addressTxs |
QueryKeys.addressTokenTransfers |
QueryKeys.blocks |
QueryKeys.blocksReorgs |
QueryKeys.blocksUncles |
......@@ -19,21 +22,25 @@ export type PaginatedQueryKeys =
QueryKeys.txTokenTransfers;
export type PaginatedResponse<Q extends PaginatedQueryKeys> =
Q extends (QueryKeys.blocks | QueryKeys.blocksReorgs | QueryKeys.blocksUncles) ? BlocksResponse :
Q extends QueryKeys.blockTxs ? BlockTransactionsResponse :
Q extends QueryKeys.txsValidate ? TransactionsResponseValidated :
Q extends QueryKeys.txsPending ? TransactionsResponsePending :
Q extends QueryKeys.txInternals ? InternalTransactionsResponse :
Q extends QueryKeys.txLogs ? LogsResponse :
Q extends QueryKeys.txTokenTransfers ? TokenTransferResponse :
never
Q extends QueryKeys.addressTxs ? AddressTransactionsResponse :
Q extends QueryKeys.addressTokenTransfers ? AddressTokenTransferResponse :
Q extends (QueryKeys.blocks | QueryKeys.blocksReorgs | QueryKeys.blocksUncles) ? BlocksResponse :
Q extends QueryKeys.blockTxs ? BlockTransactionsResponse :
Q extends QueryKeys.txsValidate ? TransactionsResponseValidated :
Q extends QueryKeys.txsPending ? TransactionsResponsePending :
Q extends QueryKeys.txInternals ? InternalTransactionsResponse :
Q extends QueryKeys.txLogs ? LogsResponse :
Q extends QueryKeys.txTokenTransfers ? TokenTransferResponse :
never
export type PaginationFilters<Q extends PaginatedQueryKeys> =
Q extends QueryKeys.blocks ? BlockFilters :
Q extends QueryKeys.txsValidate ? TTxsFilters :
Q extends QueryKeys.txsPending ? TTxsFilters :
Q extends QueryKeys.txTokenTransfers ? TokenTransferFilters :
never
Q extends QueryKeys.addressTxs ? AddressTxsFilters :
Q extends QueryKeys.addressTokenTransfers ? AddressTokenTransferFilters :
Q extends QueryKeys.blocks ? BlockFilters :
Q extends QueryKeys.txsValidate ? TTxsFilters :
Q extends QueryKeys.txsPending ? TTxsFilters :
Q extends QueryKeys.txTokenTransfers ? TokenTransferFilters :
never
export type PaginationParams<Q extends PaginatedQueryKeys> = PaginatedResponse<Q>['next_page_params'];
......@@ -42,6 +49,8 @@ type PaginationFields = {
}
export const PAGINATION_FIELDS: PaginationFields = {
[QueryKeys.addressTxs]: [ 'block_number', 'items_count', 'index' ],
[QueryKeys.addressTokenTransfers]: [ 'block_number', 'items_count', 'index', 'transaction_hash' ],
[QueryKeys.blocks]: [ 'block_number', 'items_count' ],
[QueryKeys.blocksReorgs]: [ 'block_number', 'items_count' ],
[QueryKeys.blocksUncles]: [ 'block_number', 'items_count' ],
......@@ -52,3 +61,21 @@ export const PAGINATION_FIELDS: PaginationFields = {
[QueryKeys.txTokenTransfers]: [ 'block_number', 'items_count', 'transaction_hash', 'index' ],
[QueryKeys.txLogs]: [ 'items_count', 'transaction_hash', 'index' ],
};
type PaginationFiltersFields = {
[K in PaginatedQueryKeys]: Array<KeysOfObjectOrNull<PaginationFilters<K>>>
}
export const PAGINATION_FILTERS_FIELDS: PaginationFiltersFields = {
[QueryKeys.addressTxs]: [ 'filter' ],
[QueryKeys.addressTokenTransfers]: [ 'filter', 'type' ],
[QueryKeys.blocks]: [ 'type' ],
[QueryKeys.txsValidate]: [ 'filter', 'type', 'method' ],
[QueryKeys.txsPending]: [ 'filter', 'type', 'method' ],
[QueryKeys.txTokenTransfers]: [ 'type' ],
[QueryKeys.blocksReorgs]: [],
[QueryKeys.blocksUncles]: [],
[QueryKeys.blockTxs]: [],
[QueryKeys.txInternals]: [],
[QueryKeys.txLogs]: [],
};
......@@ -20,5 +20,12 @@ export type GasPrices = {
}
export type Stats = {
total_blocks: string;
totalBlocksAllTime: string;
}
export type Charts = {
'chart': Array<{
date: string;
value: string;
}>;
}
......@@ -8,6 +8,7 @@ export interface TokenInfo {
decimals: string | null;
holders: string | null;
exchange_rate: string | null;
total_supply: string | null;
}
export type TokenInfoGeneric<Type extends TokenType> = Omit<TokenInfo, 'type'> & { type: Type };
......@@ -38,14 +38,16 @@ interface TokenTransferBase {
to: AddressParam;
}
export type TokenTransferPagination = {
block_number: number;
index: number;
items_count: number;
transaction_hash: string;
}
export interface TokenTransferResponse {
items: Array<TokenTransfer>;
next_page_params: {
block_number: number;
index: number;
items_count: number;
transaction_hash: string;
} | null;
next_page_params: TokenTransferPagination | null;
}
export interface TokenTransferFilters {
......
import type { AddressParam } from './addressParams';
export type TransactionReward = {
types: Array<string>;
emission_reward: string;
block_hash: string;
from: AddressParam;
to: AddressParam;
}
......@@ -5,6 +5,7 @@ export enum QueryKeys {
txsPending = 'txs-pending',
homeStats='homeStats',
stats='stats',
charts='stats',
tx = 'tx',
txInternals = 'tx-internals',
txLogs = 'tx-logs',
......@@ -23,4 +24,6 @@ export enum QueryKeys {
address='address',
addressCounters='address-counters',
addressTokenBalances='address-token-balances',
addressTxs='addressTxs',
addressTokenTransfers='addressTokenTransfers',
}
......@@ -23,10 +23,4 @@ export type StatsChart = {
id: string;
title: string;
description: string;
apiMethodURL: string;
}
export interface ModalChart {
id: string;
title: string;
}
import { Box, Flex, Text, Icon, Button, Grid, Select } from '@chakra-ui/react';
import { Box, Flex, Text, Icon, Grid, Link } from '@chakra-ui/react';
import type { UseQueryResult } from '@tanstack/react-query';
import { useQuery } from '@tanstack/react-query';
import BigNumber from 'bignumber.js';
......@@ -9,18 +9,25 @@ import type { Address as TAddress, AddressCounters, AddressTokenBalance } from '
import { QueryKeys } from 'types/client/queries';
import appConfig from 'configs/app/config';
import qrCodeIcon from 'icons/qr_code.svg';
import starOutlineIcon from 'icons/star_outline.svg';
import walletIcon from 'icons/wallet.svg';
import blockIcon from 'icons/block.svg';
import useFetch from 'lib/hooks/useFetch';
import useIsMobile from 'lib/hooks/useIsMobile';
import link from 'lib/link/link';
import AddressIcon from 'ui/shared/address/AddressIcon';
import AddressLink from 'ui/shared/address/AddressLink';
import CopyToClipboard from 'ui/shared/CopyToClipboard';
import DataFetchAlert from 'ui/shared/DataFetchAlert';
import DetailsInfoItem from 'ui/shared/DetailsInfoItem';
import ExternalLink from 'ui/shared/ExternalLink';
import HashStringShorten from 'ui/shared/HashStringShorten';
import AddressAddToMetaMask from './details/AddressAddToMetaMask';
import AddressBalance from './details/AddressBalance';
import AddressDetailsSkeleton from './details/AddressDetailsSkeleton';
import AddressFavoriteButton from './details/AddressFavoriteButton';
import AddressNameInfo from './details/AddressNameInfo';
import AddressQrCode from './details/AddressQrCode';
import TokenSelect from './tokenSelect/TokenSelect';
interface Props {
addressQuery: UseQueryResult<TAddress>;
......@@ -48,14 +55,15 @@ const AddressDetails = ({ addressQuery }: Props) => {
);
if (countersQuery.isLoading || addressQuery.isLoading || tokenBalancesQuery.isLoading) {
return <Box>loading</Box>;
return <AddressDetailsSkeleton/>;
}
if (countersQuery.isError || addressQuery.isError || tokenBalancesQuery.isError) {
return <Box>error</Box>;
return <DataFetchAlert/>;
}
const explorers = appConfig.network.explorers.filter(({ paths }) => paths.address);
const validationsCount = Number(countersQuery.data.validations_count);
return (
<Box>
......@@ -66,16 +74,12 @@ const AddressDetails = ({ addressQuery }: Props) => {
</Text>
<CopyToClipboard text={ addressQuery.data.hash }/>
{ addressQuery.data.is_contract && addressQuery.data.token && <AddressAddToMetaMask ml={ 2 } token={ addressQuery.data.token }/> }
<Button variant="outline" size="sm" ml={ 3 }>
<Icon as={ starOutlineIcon } boxSize={ 5 }/>
</Button>
<Button variant="outline" size="sm" ml={ 2 }>
<Icon as={ qrCodeIcon } boxSize={ 5 }/>
</Button>
<AddressFavoriteButton hash={ addressQuery.data.hash } isAdded={ Boolean(addressQuery.data.watchlist_names?.length) } ml={ 3 }/>
<AddressQrCode hash={ addressQuery.data.hash } ml={ 2 }/>
</Flex>
{ explorers.length > 0 && (
<Flex mt={ 8 } columnGap={ 4 } flexWrap="wrap">
<Text>Verify with other explorers</Text>
<Text fontSize="sm">Verify with other explorers</Text>
{ explorers.map((explorer) => {
const url = new URL(explorer.paths.tx + '/' + router.query.id, explorer.baseUrl);
return <ExternalLink key={ explorer.baseUrl } title={ explorer.title } href={ url.toString() }/>;
......@@ -85,46 +89,40 @@ const AddressDetails = ({ addressQuery }: Props) => {
<Grid
mt={ 8 }
columnGap={ 8 }
rowGap={{ base: 3, lg: 3 }}
rowGap={{ base: 1, lg: 3 }}
templateColumns={{ base: 'minmax(0, 1fr)', lg: 'auto minmax(0, 1fr)' }} overflow="hidden"
>
<AddressNameInfo data={ addressQuery.data }/>
{ addressQuery.data.is_contract && addressQuery.data.creation_tx_hash && addressQuery.data.creator_address_hash && (
<DetailsInfoItem
title="Creator"
hint="Transaction and address of creation."
>
<AddressLink hash={ addressQuery.data.creator_address_hash } truncation="constant"/>
<Text whiteSpace="pre"> at </Text>
<AddressLink hash={ addressQuery.data.creation_tx_hash } truncation="constant"/>
</DetailsInfoItem>
) }
<AddressBalance data={ addressQuery.data }/>
<DetailsInfoItem
title="Tokens"
hint="All tokens in the account and total value."
alignSelf="center"
py={ 0 }
>
{ tokenBalancesQuery.data.length > 0 ? (
<>
{ /* TODO will be fixed later when we implement select with custom menu */ }
<Select
size="sm"
borderRadius="base"
focusBorderColor="none"
display="inline-block"
w="auto"
>
{ tokenBalancesQuery.data.map((token) =>
<option key={ token.token.address } value={ token.token.address }>{ token.token.symbol }</option>) }
</Select>
<Button variant="outline" size="sm" ml={ 3 }>
<Icon as={ walletIcon } boxSize={ 5 }/>
</Button>
</>
) : (
'-'
) }
<TokenSelect/>
</DetailsInfoItem>
<DetailsInfoItem
title="Transactions"
hint="Number of transactions related to this address."
>
{ Number(countersQuery.data.transaction_count).toLocaleString() }
{ Number(countersQuery.data.transactions_count).toLocaleString() }
</DetailsInfoItem>
<DetailsInfoItem
title="Transfers"
hint="Number of transfers to/from this address."
>
{ Number(countersQuery.data.token_transfer_count).toLocaleString() }
{ Number(countersQuery.data.token_transfers_count).toLocaleString() }
</DetailsInfoItem>
<DetailsInfoItem
title="Gas used"
......@@ -132,12 +130,29 @@ const AddressDetails = ({ addressQuery }: Props) => {
>
{ BigNumber(countersQuery.data.gas_usage_count).toFormat() }
</DetailsInfoItem>
{ countersQuery.data.validation_count && (
{ !Object.is(validationsCount, NaN) && validationsCount > 0 && (
<DetailsInfoItem
title="Blocks validated"
hint="Number of blocks validated by this validator."
>
{ Number(countersQuery.data.validation_count).toLocaleString() }
{ validationsCount.toLocaleString() }
</DetailsInfoItem>
) }
{ addressQuery.data.block_number_balance_updated_at && (
<DetailsInfoItem
title="Last balance update"
hint="Block number in which the address was updated."
alignSelf="center"
py={{ base: '2px', lg: 1 }}
>
<Link
href={ link('block', { id: String(addressQuery.data.block_number_balance_updated_at) }) }
display="flex"
alignItems="center"
>
<Icon as={ blockIcon } boxSize={ 6 } mr={ 2 }/>
{ addressQuery.data.block_number_balance_updated_at }
</Link>
</DetailsInfoItem>
) }
</Grid>
......
import { Box } from '@chakra-ui/react';
import { test, expect } from '@playwright/experimental-ct-react';
import React from 'react';
import { base as txMock } from 'mocks/txs/tx';
import TestApp from 'playwright/TestApp';
import AddressTxs from './AddressTxs';
const API_URL = '/node-api/addresses/0xd789a607CEac2f0E14867de4EB15b15C9FFB5859/transactions';
const hooksConfig = {
router: {
query: { id: '0xd789a607CEac2f0E14867de4EB15b15C9FFB5859' },
},
};
test('address txs +@mobile +@desktop-xl', async({ mount, page }) => {
await page.route(API_URL, (route) => route.fulfill({
status: 200,
body: JSON.stringify({ items: [ txMock, txMock ], next_page_params: { block: 1 } }),
}));
const component = await mount(
<TestApp>
<Box h={{ base: '134px', lg: 6 }}/>
<AddressTxs/>
</TestApp>,
{ hooksConfig },
);
await page.waitForResponse(API_URL),
await expect(component).toHaveScreenshot();
});
import { useRouter } from 'next/router';
import React from 'react';
import { Element } from 'react-scroll';
import { QueryKeys } from 'types/client/queries';
import useIsMobile from 'lib/hooks/useIsMobile';
import useQueryWithPages from 'lib/hooks/useQueryWithPages';
import ActionBar from 'ui/shared/ActionBar';
import Pagination from 'ui/shared/Pagination';
import TxsContent from 'ui/txs/TxsContent';
import AddressTxsFilter from './AddressTxsFilter';
const FILTER_VALUES = [ 'from', 'to' ] as const;
type FilterType = typeof FILTER_VALUES[number];
const getFilterValue = (val: string | Array<string> | undefined): FilterType | undefined => {
if (typeof val === 'string' && FILTER_VALUES.includes(val as FilterType)) {
return val as FilterType;
}
};
const SCROLL_ELEM = 'address-txs';
const SCROLL_OFFSET = -100;
const AddressTxs = () => {
const router = useRouter();
const isMobile = useIsMobile();
const [ filterValue, setFilterValue ] = React.useState<'from' | 'to' | undefined>(getFilterValue(router.query.filter));
const addressTxsQuery = useQueryWithPages({
apiPath: `/node-api/addresses/${ router.query.id }/transactions`,
queryName: QueryKeys.addressTxs,
filters: { filter: filterValue },
scroll: { elem: SCROLL_ELEM, offset: SCROLL_OFFSET },
});
const handleFilterChange = React.useCallback((val: string | Array<string>) => {
const newVal = getFilterValue(val);
setFilterValue(newVal);
addressTxsQuery.onFilterChange({ filter: newVal });
}, [ addressTxsQuery ]);
const isPaginatorHidden =
!addressTxsQuery.isLoading &&
!addressTxsQuery.isError &&
addressTxsQuery.pagination.page === 1 &&
!addressTxsQuery.pagination.hasNextPage;
const filter = (
<AddressTxsFilter
defaultFilter={ filterValue }
onFilterChange={ handleFilterChange }
isActive={ Boolean(filterValue) }
/>
);
return (
<Element name={ SCROLL_ELEM }>
{ !isMobile && (
<ActionBar mt={ -6 }>
{ filter }
{ !isPaginatorHidden && <Pagination { ...addressTxsQuery.pagination }/> }
</ActionBar>
) }
<TxsContent
filter={ filter }
query={ addressTxsQuery }
showSocketInfo={ false }
currentAddress={ typeof router.query.id === 'string' ? router.query.id : undefined }
/>
</Element>
);
};
export default AddressTxs;
import {
Menu,
MenuButton,
MenuList,
MenuOptionGroup,
MenuItemOption,
useDisclosure,
} from '@chakra-ui/react';
import React from 'react';
import FilterButton from 'ui/shared/FilterButton';
interface Props {
isActive: boolean;
defaultFilter: 'from' | 'to' | undefined;
onFilterChange: (nextValue: string | Array<string>) => void;
}
const AddressTxsFilter = ({ onFilterChange, defaultFilter, isActive }: Props) => {
const { isOpen, onToggle } = useDisclosure();
return (
<Menu>
<MenuButton>
<FilterButton
isActive={ isOpen || isActive }
onClick={ onToggle }
/>
</MenuButton>
<MenuList zIndex={ 2 }>
<MenuOptionGroup defaultValue={ defaultFilter || 'all' } title="Address" type="radio" onChange={ onFilterChange }>
<MenuItemOption value="all">All</MenuItemOption>
<MenuItemOption value="from">From</MenuItemOption>
<MenuItemOption value="to">To</MenuItemOption>
</MenuOptionGroup>
</MenuList>
</Menu>
);
};
export default React.memo(AddressTxsFilter);
import { useQueryClient } from '@tanstack/react-query';
import React from 'react';
import type { SocketMessage } from 'lib/socket/types';
import type { Address } from 'types/api/address';
import { QueryKeys } from 'types/client/queries';
import appConfig from 'configs/app/config';
import useSocketChannel from 'lib/socket/useSocketChannel';
import useSocketMessage from 'lib/socket/useSocketMessage';
import CurrencyValue from 'ui/shared/CurrencyValue';
import DetailsInfoItem from 'ui/shared/DetailsInfoItem';
import TokenLogo from 'ui/shared/TokenLogo';
interface Props {
data: Address;
}
const AddressBalance = ({ data }: Props) => {
const [ lastBlockNumber, setLastBlockNumber ] = React.useState<number>(data.block_number_balance_updated_at || 0);
const queryClient = useQueryClient();
const updateData = React.useCallback((balance: string, exchangeRate: string, blockNumber: number) => {
if (blockNumber < lastBlockNumber) {
return;
}
setLastBlockNumber(blockNumber);
queryClient.setQueryData([ QueryKeys.address, data.hash ], (prevData: Address | undefined) => {
if (!prevData) {
return;
}
return {
...prevData,
coin_balance: balance,
exchange_rate: exchangeRate,
block_number_balance_updated_at: blockNumber,
};
});
}, [ data.hash, lastBlockNumber, queryClient ]);
const handleNewBalanceMessage: SocketMessage.AddressBalance['handler'] = React.useCallback((payload) => {
updateData(payload.balance, payload.exchange_rate, payload.block_number);
}, [ updateData ]);
const handleNewCoinBalanceMessage: SocketMessage.AddressCurrentCoinBalance['handler'] = React.useCallback((payload) => {
updateData(payload.coin_balance, payload.exchange_rate, payload.block_number);
}, [ updateData ]);
const channel = useSocketChannel({
topic: `addresses:${ data.hash.toLowerCase() }`,
isDisabled: !data.coin_balance,
});
useSocketMessage({
channel,
event: 'balance',
handler: handleNewBalanceMessage,
});
useSocketMessage({
channel,
event: 'current_coin_balance',
handler: handleNewCoinBalanceMessage,
});
if (!data.coin_balance) {
return null;
}
return (
<DetailsInfoItem
title="Balance"
hint={ `Address balance in ${ appConfig.network.currency.symbol }. Doesn't include ERC20, ERC721 and ERC1155 tokens.` }
>
<TokenLogo
hash={ appConfig.network.currency.address }
name={ appConfig.network.currency.name }
boxSize={ 5 }
mr={ 2 }
fontSize="sm"
/>
<CurrencyValue
value={ data.coin_balance }
exchangeRate={ data.exchange_rate }
decimals={ String(appConfig.network.currency.decimals) }
currency={ appConfig.network.currency.symbol }
accuracyUsd={ 2 }
accuracy={ 8 }
/>
</DetailsInfoItem>
);
};
export default React.memo(AddressBalance);
import { Box, Flex, Grid, Skeleton, SkeletonCircle } from '@chakra-ui/react';
import React from 'react';
import DetailsSkeletonRow from 'ui/shared/skeletons/DetailsSkeletonRow';
const AddressDetailsSkeleton = () => {
return (
<Box>
<Flex align="center">
<SkeletonCircle boxSize={ 6 } flexShrink={ 0 }/>
<Skeleton h={ 6 } w={{ base: '100px', lg: '420px' }} ml={ 2 }/>
<Skeleton h={ 6 } w="24px" ml={ 2 } flexShrink={ 0 }/>
<Skeleton h={ 8 } w="48px" ml={ 3 } flexShrink={ 0 }/>
<Skeleton h={ 8 } w="48px" ml={ 3 } flexShrink={ 0 }/>
</Flex>
<Flex align="center" columnGap={ 4 } mt={ 8 }>
<Skeleton h={ 6 } w="200px"/>
<Skeleton h={ 6 } w="80px"/>
</Flex>
<Grid columnGap={ 8 } rowGap={{ base: 5, lg: 7 }} templateColumns={{ base: '1fr', lg: '150px 1fr' }} maxW="1000px" mt={ 8 }>
<DetailsSkeletonRow w="30%"/>
<DetailsSkeletonRow w="30%"/>
<DetailsSkeletonRow w="10%"/>
<DetailsSkeletonRow w="10%"/>
<DetailsSkeletonRow w="20%"/>
<DetailsSkeletonRow w="20%"/>
</Grid>
</Box>
);
};
export default AddressDetailsSkeleton;
import { Icon, chakra, Tooltip, IconButton, useDisclosure } from '@chakra-ui/react';
import { useQuery, useQueryClient } from '@tanstack/react-query';
import { useRouter } from 'next/router';
import React from 'react';
import type { UserInfo } from 'types/api/account';
import type { TWatchlist } from 'types/client/account';
import { QueryKeys as AccountQueryKeys } from 'types/client/accountQueries';
import { QueryKeys } from 'types/client/queries';
import starFilledIcon from 'icons/star_filled.svg';
import starOutlineIcon from 'icons/star_outline.svg';
import useFetch from 'lib/hooks/useFetch';
import useLoginUrl from 'lib/hooks/useLoginUrl';
import usePreventFocusAfterModalClosing from 'lib/hooks/usePreventFocusAfterModalClosing';
import WatchlistAddModal from 'ui/watchlist/AddressModal/AddressModal';
import DeleteAddressModal from 'ui/watchlist/DeleteAddressModal';
interface Props {
className?: string;
hash: string;
isAdded: boolean;
}
const AddressFavoriteButton = ({ className, hash, isAdded }: Props) => {
const addModalProps = useDisclosure();
const deleteModalProps = useDisclosure();
const queryClient = useQueryClient();
const router = useRouter();
const fetch = useFetch();
const profileData = queryClient.getQueryData<UserInfo>([ AccountQueryKeys.profile ]);
const isAuth = Boolean(profileData);
const loginUrl = useLoginUrl();
const watchListQuery = useQuery<unknown, unknown, TWatchlist>(
[ AccountQueryKeys.watchlist ],
async() => fetch('/node-api/account/watchlist'),
{
enabled: isAdded,
},
);
const handleClick = React.useCallback(() => {
if (!isAuth) {
window.location.assign(loginUrl);
return;
}
isAdded ? deleteModalProps.onOpen() : addModalProps.onOpen();
}, [ addModalProps, deleteModalProps, isAdded, isAuth, loginUrl ]);
const handleAddOrDeleteSuccess = React.useCallback(async() => {
await queryClient.refetchQueries({ queryKey: [ QueryKeys.address, router.query.id ] });
addModalProps.onClose();
}, [ addModalProps, queryClient, router.query.id ]);
const handleAddModalClose = React.useCallback(() => {
addModalProps.onClose();
}, [ addModalProps ]);
const handleDeleteModalClose = React.useCallback(() => {
deleteModalProps.onClose();
}, [ deleteModalProps ]);
const formData = React.useMemo(() => {
return {
address_hash: hash,
// FIXME temporary solution
// there is no endpoint in api what can return watchlist address info by its hash
// so we look up in the whole watchlist and hope we can find a necessary item
id: watchListQuery.data?.find((address) => address.address?.hash === hash)?.id || '',
};
}, [ hash, watchListQuery.data ]);
return (
<>
<Tooltip label={ `${ isAdded ? 'Remove address from Watch list' : 'Add address to Watch list' }` }>
<IconButton
isActive={ isAdded }
className={ className }
aria-label="edit"
variant="outline"
size="sm"
pl="6px"
pr="6px"
onClick={ handleClick }
icon={ <Icon as={ isAdded ? starFilledIcon : starOutlineIcon } boxSize={ 5 }/> }
onFocusCapture={ usePreventFocusAfterModalClosing() }
/>
</Tooltip>
<WatchlistAddModal
{ ...addModalProps }
isAdd
onClose={ handleAddModalClose }
onSuccess={ handleAddOrDeleteSuccess }
data={ formData }
/>
<DeleteAddressModal
{ ...deleteModalProps }
onClose={ handleDeleteModalClose }
data={ formData }
onSuccess={ handleAddOrDeleteSuccess }
/>
</>
);
};
export default chakra(AddressFavoriteButton);
import { Link } from '@chakra-ui/react';
import React from 'react';
import type { Address } from 'types/api/address';
import link from 'lib/link/link';
import DetailsInfoItem from 'ui/shared/DetailsInfoItem';
interface Props {
data: Address;
}
const AddressNameInfo = ({ data }: Props) => {
if (data.token) {
return (
<DetailsInfoItem
title="Token name"
hint="Token name and symbol"
>
<Link href={ link('token_index', { hash: data.token.address }) }>
{ data.token.name } ({ data.token.symbol })
</Link>
</DetailsInfoItem>
);
}
if (data.is_contract && data.name) {
return (
<DetailsInfoItem
title="Contract name"
hint="The name found in the source code of the Contract"
>
{ data.name }
</DetailsInfoItem>
);
}
if (data.name) {
return (
<DetailsInfoItem
title="Validator name"
hint="The name of the validator"
>
{ data.name }
</DetailsInfoItem>
);
}
return null;
};
export default React.memo(AddressNameInfo);
import { test, expect } from '@playwright/experimental-ct-react';
import React from 'react';
import TestApp from 'playwright/TestApp';
import AddressQrCode from './AddressQrCode';
test('default view +@mobile +@dark-mode', async({ mount, page }) => {
await mount(
<TestApp>
<AddressQrCode hash="0x363574E6C5C71c343d7348093D84320c76d5Dd29"/>
</TestApp>,
);
await page.getByRole('button', { name: /qr code/i }).click();
await expect(page).toHaveScreenshot();
});
import { chakra, Alert, Icon, Modal, ModalBody, ModalContent, ModalCloseButton, ModalOverlay, Box, useDisclosure, Tooltip, IconButton } from '@chakra-ui/react';
import * as Sentry from '@sentry/react';
import QRCode from 'qrcode';
import React from 'react';
import qrCodeIcon from 'icons/qr_code.svg';
import useIsMobile from 'lib/hooks/useIsMobile';
const SVG_OPTIONS = {
margin: 0,
};
interface Props {
className?: string;
hash: string;
}
const AddressQrCode = ({ hash, className }: Props) => {
const { isOpen, onOpen, onClose } = useDisclosure();
const isMobile = useIsMobile();
const [ qr, setQr ] = React.useState('');
const [ error, setError ] = React.useState('');
React.useEffect(() => {
if (isOpen) {
QRCode.toString(hash, SVG_OPTIONS, (error: Error | null | undefined, svg: string) => {
if (error) {
setError('We were unable to generate QR code.');
Sentry.captureException(error, { tags: { source: 'QR code' } });
return;
}
setError('');
setQr(svg);
});
}
}, [ hash, isOpen, onClose ]);
return (
<>
<Tooltip label="Click to view QR code">
<IconButton
className={ className }
aria-label="Show QR code"
variant="outline"
size="sm"
pl="6px"
pr="6px"
onClick={ onOpen }
icon={ <Icon as={ qrCodeIcon } boxSize={ 5 }/> }
/>
</Tooltip>
<Modal isOpen={ isOpen } onClose={ onClose } size={{ base: 'full', lg: 'sm' }}>
<ModalOverlay/>
<ModalContent bgColor={ error ? undefined : 'white' }>
{ isMobile && <ModalCloseButton/> }
<ModalBody mb={ 0 }>
{ error ? <Alert status="warning">{ error }</Alert> : <Box dangerouslySetInnerHTML={{ __html: qr }}/> }
</ModalBody>
</ModalContent>
</Modal>
</>
);
};
export default React.memo(chakra(AddressQrCode));
import { useQuery } from '@tanstack/react-query';
import { useRouter } from 'next/router';
import React from 'react';
import { QueryKeys } from 'types/client/queries';
import useFetch from 'lib/hooks/useFetch';
const MockAddressPage = ({ children }: { children: JSX.Element }): JSX.Element => {
const router = useRouter();
const fetch = useFetch();
const { data } = useQuery(
[ QueryKeys.address, router.query.id ],
async() => await fetch(`/node-api/addresses/${ router.query.id }`),
{
enabled: Boolean(router.query.id),
},
);
if (!data) {
return <div/>;
}
return children;
};
export default MockAddressPage;
import { Flex } from '@chakra-ui/react';
import { test as base, expect, devices } from '@playwright/experimental-ct-react';
import React from 'react';
import * as tokenBalanceMock from 'mocks/address/tokenBalance';
import * as socketServer from 'playwright/fixtures/socketServer';
import TestApp from 'playwright/TestApp';
import MockAddressPage from 'ui/address/testUtils/MockAddressPage';
import TokenSelect from './TokenSelect';
const ASSET_URL = 'https://raw.githubusercontent.com/trustwallet/assets/master/blockchains/poa/assets/0xb2a90505dc6680a7a695f7975d0d32EeF610f456/logo.png';
const TOKENS_API_URL = '/node-api/addresses/1/token-balances';
const ADDRESS_API_URL = '/node-api/addresses/1';
const hooksConfig = {
router: {
query: { id: '1' },
},
};
const CLIPPING_AREA = { x: 0, y: 0, width: 360, height: 500 };
const test = base.extend({
page: async({ page }, use) => {
await page.route(ASSET_URL, (route) => {
return route.fulfill({
status: 200,
path: './playwright/image_s.jpg',
});
});
await page.route(ADDRESS_API_URL, (route) => route.fulfill({
status: 200,
body: JSON.stringify({ hash: '1' }),
}), { times: 1 });
await page.route(TOKENS_API_URL, async(route) => route.fulfill({
status: 200,
body: JSON.stringify(tokenBalanceMock.baseList),
}), { times: 1 });
use(page);
},
});
test('base view +@dark-mode', async({ mount, page }) => {
await mount(
<TestApp>
<MockAddressPage>
<Flex>
<TokenSelect/>
</Flex>
</MockAddressPage>
</TestApp>,
{ hooksConfig },
);
await page.getByRole('button', { name: /select/i }).click();
await page.getByText('USD Coin').hover();
await expect(page).toHaveScreenshot({ clip: CLIPPING_AREA });
await page.mouse.move(100, 200);
await page.mouse.wheel(0, 1000);
await expect(page).toHaveScreenshot({ clip: CLIPPING_AREA });
});
test.describe('mobile', () => {
test.use({ viewport: devices['iPhone 13 Pro'].viewport });
test('base view', async({ mount, page }) => {
await mount(
<TestApp>
<MockAddressPage>
<Flex>
<TokenSelect/>
</Flex>
</MockAddressPage>
</TestApp>,
{ hooksConfig },
);
await page.getByRole('button', { name: /select/i }).click();
await page.getByText('USD Coin').hover();
await expect(page).toHaveScreenshot();
});
});
test('sort', async({ mount, page }) => {
await mount(
<TestApp>
<MockAddressPage>
<Flex>
<TokenSelect/>
</Flex>
</MockAddressPage>
</TestApp>,
{ hooksConfig },
);
await page.getByRole('button', { name: /select/i }).click();
await page.locator('a[aria-label="Sort ERC-20 tokens"]').click();
await expect(page).toHaveScreenshot({ clip: CLIPPING_AREA });
await page.mouse.move(100, 200);
await page.mouse.wheel(0, 1000);
await page.locator('a[aria-label="Sort ERC-1155 tokens"]').click();
await expect(page).toHaveScreenshot({ clip: CLIPPING_AREA });
});
test('filter', async({ mount, page }) => {
await mount(
<TestApp>
<MockAddressPage>
<Flex>
<TokenSelect/>
</Flex>
</MockAddressPage>
</TestApp>,
{ hooksConfig },
);
await page.getByRole('button', { name: /select/i }).click();
await page.getByPlaceholder('Search by token name').type('c');
await expect(page).toHaveScreenshot({ clip: CLIPPING_AREA });
});
test.describe('socket', () => {
const testWithSocket = test.extend<socketServer.SocketServerFixture>({
createSocket: socketServer.createSocket,
});
testWithSocket.describe.configure({ mode: 'serial' });
testWithSocket('new item after token balance update', async({ page, mount, createSocket }) => {
await mount(
<TestApp withSocket>
<MockAddressPage>
<Flex>
<TokenSelect/>
</Flex>
</MockAddressPage>
</TestApp>,
{ hooksConfig },
);
await page.route(TOKENS_API_URL, (route) => route.fulfill({
status: 200,
body: JSON.stringify([
...tokenBalanceMock.baseList,
tokenBalanceMock.erc20d,
]),
}));
const socket = await createSocket();
const channel = await socketServer.joinChannel(socket, 'addresses:1');
socketServer.sendMessage(socket, channel, 'token_balance', {
block_number: 1,
});
const button = page.getByRole('button', { name: /select/i });
const text = await button.innerText();
expect(text).toContain('10');
});
testWithSocket('new item after coin balance update', async({ page, mount, createSocket }) => {
await mount(
<TestApp withSocket>
<MockAddressPage>
<Flex>
<TokenSelect/>
</Flex>
</MockAddressPage>
</TestApp>,
{ hooksConfig },
);
await page.route(TOKENS_API_URL, (route) => route.fulfill({
status: 200,
body: JSON.stringify([
...tokenBalanceMock.baseList,
tokenBalanceMock.erc20d,
]),
}));
const socket = await createSocket();
const channel = await socketServer.joinChannel(socket, 'addresses:1');
socketServer.sendMessage(socket, channel, 'coin_balance', {
coin_balance: {
block_number: 1,
},
});
const button = page.getByRole('button', { name: /select/i });
const text = await button.innerText();
expect(text).toContain('10');
});
});
import { Box, Flex, Icon, IconButton, Skeleton, Tooltip } from '@chakra-ui/react';
import { useQuery, useQueryClient, useIsFetching } from '@tanstack/react-query';
import { useRouter } from 'next/router';
import React from 'react';
import type { SocketMessage } from 'lib/socket/types';
import type { Address, AddressTokenBalance } from 'types/api/address';
import { QueryKeys } from 'types/client/queries';
import walletIcon from 'icons/wallet.svg';
import useFetch from 'lib/hooks/useFetch';
import useIsMobile from 'lib/hooks/useIsMobile';
import useSocketChannel from 'lib/socket/useSocketChannel';
import useSocketMessage from 'lib/socket/useSocketMessage';
import TokenSelectDesktop from './TokenSelectDesktop';
import TokenSelectMobile from './TokenSelectMobile';
const TokenSelect = () => {
const router = useRouter();
const fetch = useFetch();
const isMobile = useIsMobile();
const queryClient = useQueryClient();
const [ blockNumber, setBlockNumber ] = React.useState<number>();
const addressQueryData = queryClient.getQueryData<Address>([ QueryKeys.address, router.query.id ]);
const { data, isError, isLoading, refetch } = useQuery<unknown, unknown, Array<AddressTokenBalance>>(
[ QueryKeys.addressTokenBalances, addressQueryData?.hash ],
async() => await fetch(`/node-api/addresses/${ addressQueryData?.hash }/token-balances`),
{
enabled: Boolean(addressQueryData),
},
);
const balancesIsFetching = useIsFetching({ queryKey: [ QueryKeys.addressTokenBalances, addressQueryData?.hash ] });
const handleTokenBalanceMessage: SocketMessage.AddressTokenBalance['handler'] = React.useCallback((payload) => {
if (payload.block_number !== blockNumber) {
refetch();
setBlockNumber(payload.block_number);
}
}, [ blockNumber, refetch ]);
const handleCoinBalanceMessage: SocketMessage.AddressCoinBalance['handler'] = React.useCallback((payload) => {
if (payload.coin_balance.block_number !== blockNumber) {
refetch();
setBlockNumber(payload.coin_balance.block_number);
}
}, [ blockNumber, refetch ]);
const channel = useSocketChannel({
topic: `addresses:${ addressQueryData?.hash.toLowerCase() }`,
isDisabled: !addressQueryData,
});
useSocketMessage({
channel,
event: 'coin_balance',
handler: handleCoinBalanceMessage,
});
useSocketMessage({
channel,
event: 'token_balance',
handler: handleTokenBalanceMessage,
});
if (isLoading) {
return <Skeleton h={ 8 } w="160px"/>;
}
if (isError || data.length === 0) {
return <Box py="6px">0</Box>;
}
return (
<Flex columnGap={ 3 } mt={{ base: '6px', lg: 0 }}>
{ isMobile ?
<TokenSelectMobile data={ data } isLoading={ balancesIsFetching === 1 }/> :
<TokenSelectDesktop data={ data } isLoading={ balancesIsFetching === 1 }/>
}
<Tooltip label="Show all tokens">
<IconButton
aria-label="Show all tokens"
variant="outline"
size="sm"
pl="6px"
pr="6px"
icon={ <Icon as={ walletIcon } boxSize={ 5 }/> }
/>
</Tooltip>
</Flex>
);
};
export default React.memo(TokenSelect);
import { Box, Button, Icon, Skeleton, Text, useColorModeValue } from '@chakra-ui/react';
import BigNumber from 'bignumber.js';
import React from 'react';
import arrowIcon from 'icons/arrows/east-mini.svg';
import tokensIcon from 'icons/tokens.svg';
import { ZERO } from 'lib/consts';
import type { EnhancedData } from './utils';
interface Props {
isOpen: boolean;
isLoading: boolean;
onClick: () => void;
data: Array<EnhancedData>;
}
const TokenSelectButton = ({ isOpen, isLoading, onClick, data }: Props, ref: React.ForwardedRef<HTMLButtonElement>) => {
const totalBn = data.reduce((result, item) => !item.usd ? result : result.plus(BigNumber(item.usd)), ZERO);
const skeletonBgColor = useColorModeValue('white', 'black');
const handleClick = React.useCallback(() => {
if (isLoading && !isOpen) {
return;
}
onClick();
}, [ isLoading, isOpen, onClick ]);
return (
<Box position="relative">
<Button
ref={ ref }
size="sm"
variant="outline"
colorScheme="gray"
onClick={ handleClick }
aria-label="Token select"
>
<Icon as={ tokensIcon } boxSize={ 4 } mr={ 2 }/>
<Text fontWeight={ 600 }>{ data.length }</Text>
<Text whiteSpace="pre" variant="secondary" fontWeight={ 400 }> (${ totalBn.toFormat(2) })</Text>
<Icon as={ arrowIcon } transform={ isOpen ? 'rotate(90deg)' : 'rotate(-90deg)' } transitionDuration="faster" boxSize={ 5 } ml={ 3 }/>
</Button>
{ isLoading && !isOpen && <Skeleton h="100%" w="100%" position="absolute" top={ 0 } left={ 0 } bgColor={ skeletonBgColor }/> }
</Box>
);
};
export default React.forwardRef(TokenSelectButton);
import { useColorModeValue, Popover, PopoverTrigger, PopoverContent, PopoverBody, useDisclosure } from '@chakra-ui/react';
import React from 'react';
import type { AddressTokenBalance } from 'types/api/address';
import TokenSelectButton from './TokenSelectButton';
import TokenSelectMenu from './TokenSelectMenu';
import useTokenSelect from './useTokenSelect';
interface Props {
data: Array<AddressTokenBalance>;
isLoading: boolean;
}
const TokenSelectDesktop = ({ data, isLoading }: Props) => {
const { isOpen, onToggle, onClose } = useDisclosure();
const bgColor = useColorModeValue('white', 'gray.900');
const result = useTokenSelect(data);
return (
<Popover isOpen={ isOpen } onClose={ onClose } placement="bottom-start" isLazy>
<PopoverTrigger>
<TokenSelectButton isOpen={ isOpen } onClick={ onToggle } data={ result.modifiedData } isLoading={ isLoading }/>
</PopoverTrigger>
<PopoverContent w="355px" maxH="450px" overflowY="scroll">
<PopoverBody px={ 4 } py={ 6 } bgColor={ bgColor } boxShadow="2xl" >
<TokenSelectMenu { ...result }/>
</PopoverBody>
</PopoverContent>
</Popover>
);
};
export default React.memo(TokenSelectDesktop);
import { Flex, Text, useColorModeValue } from '@chakra-ui/react';
import BigNumber from 'bignumber.js';
import React from 'react';
import link from 'lib/link/link';
import HashStringShorten from 'ui/shared/HashStringShorten';
import TokenLogo from 'ui/shared/TokenLogo';
import type { EnhancedData } from './utils';
interface Props {
data: EnhancedData;
}
const TokenSelectItem = ({ data }: Props) => {
const secondRow = (() => {
switch (data.token.type) {
case 'ERC-20': {
const tokenDecimals = Number(data.token.decimals) || 18;
return (
<>
<Text >{ BigNumber(data.value).dividedBy(10 ** tokenDecimals).toFormat(2) } { data.token.symbol }</Text>
{ data.token.exchange_rate && <Text >@{ data.token.exchange_rate }</Text> }
</>
);
}
case 'ERC-721': {
return <Text >{ BigNumber(data.value).toFormat() } { data.token.symbol }</Text>;
}
case 'ERC-1155': {
return (
<>
<Text >#{ data.token_id || 0 }</Text>
<Text >{ BigNumber(data.value).toFormat() }</Text>
</>
);
}
}
})();
// TODO add filter param when token page is ready
const url = link('token_index', { hash: data.token.address });
return (
<Flex
px={ 1 }
py="10px"
display="flex"
flexDir="column"
rowGap={ 2 }
borderColor={ useColorModeValue('blackAlpha.200', 'whiteAlpha.200') }
borderBottomWidth="1px"
_hover={{
bgColor: useColorModeValue('blue.50', 'gray.800'),
}}
fontSize="sm"
cursor="pointer"
as="a"
href={ url }
>
<Flex alignItems="center" w="100%">
<TokenLogo hash={ data.token.address } name={ data.token.name } boxSize={ 6 }/>
<Text fontWeight={ 700 } ml={ 2 }>{ data.token.name || <HashStringShorten hash={ data.token.address }/> }</Text>
{ data.usd && <Text fontWeight={ 700 } ml="auto">${ data.usd.toFormat(2) }</Text> }
</Flex>
<Flex alignItems="center" justifyContent="space-between" w="100%">
{ secondRow }
</Flex>
</Flex>
);
};
export default React.memo(TokenSelectItem);
import { Icon, Text, Box, Input, InputGroup, InputLeftElement, useColorModeValue, Flex, Link } from '@chakra-ui/react';
import type { Dictionary } from 'lodash';
import type { ChangeEvent } from 'react';
import React from 'react';
import type { TokenType } from 'types/api/tokenInfo';
import arrowIcon from 'icons/arrows/east.svg';
import searchIcon from 'icons/search.svg';
import TokenSelectItem from './TokenSelectItem';
import type { Sort, EnhancedData } from './utils';
import { sortTokenGroups, sortingFns } from './utils';
interface Props {
searchTerm: string;
erc20sort: Sort;
erc1155sort: Sort;
modifiedData: Array<EnhancedData>;
groupedData: Dictionary<Array<EnhancedData>>;
onInputChange: (event: ChangeEvent<HTMLInputElement>) => void;
onSortClick: (event: React.SyntheticEvent) => void;
}
const TokenSelectMenu = ({ erc20sort, erc1155sort, modifiedData, groupedData, onInputChange, onSortClick, searchTerm }: Props) => {
const searchIconColor = useColorModeValue('blackAlpha.600', 'whiteAlpha.600');
const inputBorderColor = useColorModeValue('blackAlpha.100', 'whiteAlpha.200');
return (
<>
<InputGroup size="xs" mb={ 5 }>
<InputLeftElement >
<Icon as={ searchIcon } boxSize={ 4 } color={ searchIconColor }/>
</InputLeftElement>
<Input
paddingInlineStart="38px"
placeholder="Search by token name"
ml="1px"
onChange={ onInputChange }
borderColor={ inputBorderColor }
/>
</InputGroup>
<Flex flexDir="column" rowGap={ 6 }>
{ Object.entries(groupedData).sort(sortTokenGroups).map(([ tokenType, tokenInfo ]) => {
const type = tokenType as TokenType;
const arrowTransform = (type === 'ERC-1155' && erc1155sort === 'desc') || (type === 'ERC-20' && erc20sort === 'desc') ?
'rotate(90deg)' :
'rotate(-90deg)';
const sortDirection: Sort = (() => {
switch (type) {
case 'ERC-1155':
return erc1155sort;
case 'ERC-20':
return erc20sort;
default:
return 'desc';
}
})();
const hasSort = type === 'ERC-1155' || (type === 'ERC-20' && tokenInfo.some(({ usd }) => usd));
return (
<Box key={ type }>
<Flex justifyContent="space-between">
<Text mb={ 3 } color="gray.500" fontWeight={ 600 } fontSize="sm">{ type } tokens ({ tokenInfo.length })</Text>
{ hasSort && (
<Link data-type={ type } onClick={ onSortClick } aria-label={ `Sort ${ type } tokens` }>
<Icon as={ arrowIcon } boxSize={ 5 } transform={ arrowTransform } transitionDuration="faster"/>
</Link>
) }
</Flex>
{ tokenInfo.sort(sortingFns[type](sortDirection)).map((data) => <TokenSelectItem key={ data.token.address + data.token_id } data={ data }/>) }
</Box>
);
}) }
</Flex>
{ modifiedData.length === 0 && searchTerm && <Text fontSize="sm">Could not find any matches.</Text> }
</>
);
};
export default React.memo(TokenSelectMenu);
import { useDisclosure, Modal, ModalContent, ModalCloseButton } from '@chakra-ui/react';
import React from 'react';
import type { AddressTokenBalance } from 'types/api/address';
import TokenSelectButton from './TokenSelectButton';
import TokenSelectMenu from './TokenSelectMenu';
import useTokenSelect from './useTokenSelect';
interface Props {
data: Array<AddressTokenBalance>;
isLoading: boolean;
}
const TokenSelectMobile = ({ data, isLoading }: Props) => {
const { isOpen, onToggle, onClose } = useDisclosure();
const result = useTokenSelect(data);
return (
<>
<TokenSelectButton isOpen={ isOpen } onClick={ onToggle } data={ result.modifiedData } isLoading={ isLoading }/>
<Modal isOpen={ isOpen } onClose={ onClose } size="full">
<ModalContent>
<ModalCloseButton/>
<TokenSelectMenu { ...result }/>
</ModalContent>
</Modal>
</>
);
};
export default React.memo(TokenSelectMobile);
import _groupBy from 'lodash/groupBy';
import type { ChangeEvent } from 'react';
import React from 'react';
import type { AddressTokenBalance } from 'types/api/address';
import type { Sort } from './utils';
import { calculateUsdValue, filterTokens } from './utils';
export default function useData(data: Array<AddressTokenBalance>) {
const [ searchTerm, setSearchTerm ] = React.useState('');
const [ erc1155sort, setErc1155Sort ] = React.useState<Sort>('desc');
const [ erc20sort, setErc20Sort ] = React.useState<Sort>('desc');
const onInputChange = React.useCallback((event: ChangeEvent<HTMLInputElement>) => {
setSearchTerm(event.target.value);
}, []);
const onSortClick = React.useCallback((event: React.SyntheticEvent) => {
const tokenType = (event.currentTarget as HTMLAnchorElement).getAttribute('data-type');
if (tokenType === 'ERC-1155') {
setErc1155Sort((prevValue) => prevValue === 'desc' ? 'asc' : 'desc');
}
if (tokenType === 'ERC-20') {
setErc20Sort((prevValue) => prevValue === 'desc' ? 'asc' : 'desc');
}
}, []);
const modifiedData = React.useMemo(() => {
return data.filter(filterTokens(searchTerm.toLowerCase())).map(calculateUsdValue);
}, [ data, searchTerm ]);
const groupedData = React.useMemo(() => {
return _groupBy(modifiedData, 'token.type');
}, [ modifiedData ]);
return {
searchTerm,
erc20sort,
erc1155sort,
onInputChange,
onSortClick,
modifiedData,
groupedData,
};
}
import BigNumber from 'bignumber.js';
import type { AddressTokenBalance } from 'types/api/address';
import type { TokenType } from 'types/api/tokenInfo';
export type EnhancedData = AddressTokenBalance & {
usd?: BigNumber ;
}
export type Sort = 'desc' | 'asc';
const TOKEN_GROUPS_ORDER: Array<TokenType> = [ 'ERC-20', 'ERC-721', 'ERC-1155' ];
type TokenGroup = [string, Array<AddressTokenBalance>];
export const sortTokenGroups = (groupA: TokenGroup, groupB: TokenGroup) => {
return TOKEN_GROUPS_ORDER.indexOf(groupA[0] as TokenType) > TOKEN_GROUPS_ORDER.indexOf(groupB[0] as TokenType) ? 1 : -1;
};
const sortErc1155Tokens = (sort: Sort) => (dataA: AddressTokenBalance, dataB: AddressTokenBalance) => {
if (dataA.value === dataB.value) {
return 0;
}
if (sort === 'desc') {
return Number(dataA.value) > Number(dataB.value) ? -1 : 1;
}
return Number(dataA.value) > Number(dataB.value) ? 1 : -1;
};
const sortErc20Tokens = (sort: Sort) => (dataA: EnhancedData, dataB: EnhancedData) => {
if (!dataA.usd && !dataB.usd) {
return 0;
}
// keep tokens without usd value in the end of the group
if (!dataB.usd) {
return -1;
}
if (!dataA.usd) {
return 0;
}
if (sort === 'desc') {
return dataA.usd.gt(dataB.usd) ? -1 : 1;
}
return dataA.usd.gt(dataB.usd) ? 1 : -1;
};
const sortErc721Tokens = () => () => 0;
export const sortingFns = {
'ERC-20': sortErc20Tokens,
'ERC-721': sortErc721Tokens,
'ERC-1155': sortErc1155Tokens,
};
export const filterTokens = (searchTerm: string) => ({ token }: AddressTokenBalance) => {
if (!token.name) {
return !searchTerm ? true : token.address.toLowerCase().includes(searchTerm);
}
return token.name?.toLowerCase().includes(searchTerm);
};
export const calculateUsdValue = (data: AddressTokenBalance): EnhancedData => {
if (data.token.type !== 'ERC-20') {
return data;
}
const exchangeRate = data.token.exchange_rate;
if (!exchangeRate) {
return data;
}
const decimals = Number(data.token.decimals || '18');
return {
...data,
usd: BigNumber(data.value).div(BigNumber(10 ** decimals)).multipliedBy(BigNumber(exchangeRate)),
};
};
......@@ -112,9 +112,7 @@ const ApiKeyForm: React.FC<Props> = ({ data, onClose, setAlertVisible }) => {
isInvalid={ Boolean(errors.name) }
maxLength={ NAME_MAX_LENGTH }
/>
<FormLabel>
<InputPlaceholder text="Application name for API key (e.g Web3 project)" error={ errors.name }/>
</FormLabel>
<InputPlaceholder text="Application name for API key (e.g Web3 project)" error={ errors.name }/>
</FormControl>
);
}, [ errors, formBackgroundColor ]);
......
import { Grid, GridItem, Skeleton, SkeletonCircle } from '@chakra-ui/react';
import { Grid, GridItem, Skeleton } from '@chakra-ui/react';
import React from 'react';
const SkeletonRow = ({ w = '100%' }: { w?: string }) => (
<>
<GridItem display="flex" columnGap={ 2 } w={{ base: '50%', lg: 'auto' }} _notFirst={{ mt: { base: 3, lg: 0 } }}>
<SkeletonCircle h={ 5 } w={ 5 }/>
<Skeleton flexGrow={ 1 } h={ 5 } borderRadius="full"/>
</GridItem>
<GridItem pl={{ base: 7, lg: 0 }}>
<Skeleton h={ 5 } borderRadius="full" w={{ base: '100%', lg: w }}/>
</GridItem>
</>
);
import DetailsSkeletonRow from 'ui/shared/skeletons/DetailsSkeletonRow';
const BlockDetailsSkeleton = () => {
const sectionGap = <GridItem colSpan={{ base: undefined, lg: 2 }} mt={{ base: 1, lg: 4 }}/>;
return (
<Grid columnGap={ 8 } rowGap={{ base: 5, lg: 7 }} templateColumns={{ base: '1fr', lg: '210px 1fr' }} maxW="1000px">
<SkeletonRow w="25%"/>
<SkeletonRow w="25%"/>
<SkeletonRow w="65%"/>
<SkeletonRow w="25%"/>
<SkeletonRow/>
<SkeletonRow/>
<DetailsSkeletonRow w="25%"/>
<DetailsSkeletonRow w="25%"/>
<DetailsSkeletonRow w="65%"/>
<DetailsSkeletonRow w="25%"/>
<DetailsSkeletonRow/>
<DetailsSkeletonRow/>
{ sectionGap }
<SkeletonRow w="50%"/>
<SkeletonRow w="25%"/>
<SkeletonRow w="50%"/>
<SkeletonRow w="50%"/>
<SkeletonRow w="50%"/>
<DetailsSkeletonRow w="50%"/>
<DetailsSkeletonRow w="25%"/>
<DetailsSkeletonRow w="50%"/>
<DetailsSkeletonRow w="50%"/>
<DetailsSkeletonRow w="50%"/>
{ sectionGap }
<GridItem colSpan={{ base: undefined, lg: 2 }}>
<Skeleton h={ 5 } borderRadius="full" w="100px"/>
......
......@@ -82,10 +82,10 @@ const BlocksContent = ({ type, query }: Props) => {
if (query.isLoading) {
return (
<>
<Show below="lg" key="skeleton-mobile">
<Show below="lg" key="skeleton-mobile" ssr={ false }>
<BlocksSkeletonMobile/>
</Show>
<Hide below="lg" key="skeleton-desktop">
<Hide below="lg" key="skeleton-desktop" ssr={ false }>
<SkeletonTable columns={ [ '125px', '120px', '21%', '64px', '35%', '22%', '22%' ] }/>
</Hide>
</>
......@@ -103,8 +103,8 @@ const BlocksContent = ({ type, query }: Props) => {
return (
<>
{ socketAlert && <Alert status="warning" mb={ 6 } as="a" href={ window.document.location.href }>{ socketAlert }</Alert> }
<Show below="lg" key="content-mobile"><BlocksList data={ query.data.items }/></Show>
<Hide below="lg" key="content-desktop"><BlocksTable data={ query.data.items } top={ 80 } page={ 1 }/></Hide>
<Show below="lg" key="content-mobile" ssr={ false }><BlocksList data={ query.data.items }/></Show>
<Hide below="lg" key="content-desktop" ssr={ false }><BlocksTable data={ query.data.items } top={ 80 } page={ 1 }/></Hide>
</>
);
......@@ -115,7 +115,7 @@ const BlocksContent = ({ type, query }: Props) => {
return (
<>
{ isMobile && !isPaginatorHidden && (
<ActionBar>
<ActionBar mt={ -6 }>
<Pagination ml="auto" { ...query.pagination }/>
</ActionBar>
) }
......
......@@ -21,7 +21,7 @@ const BlocksTabSlot = ({ pagination }: Props) => {
const statsQuery = useQuery<unknown, unknown, HomeStats>(
[ QueryKeys.homeStats ],
() => fetch('/node-api/stats'),
() => fetch('/node-api/home-stats'),
);
if (isMobile) {
......
......@@ -8,7 +8,7 @@ import TestApp from 'playwright/TestApp';
import LatestBlocks from './LatestBlocks';
const STATS_API_URL = '/node-api/stats';
const STATS_API_URL = '/node-api/home-stats';
const BLOCKS_API_URL = '/node-api/index/blocks';
export const test = base.extend<socketServer.SocketServerFixture>({
......
......@@ -33,7 +33,7 @@ const LatestBlocks = () => {
const queryClient = useQueryClient();
const statsQueryResult = useQuery<unknown, unknown, HomeStats>(
[ QueryKeys.homeStats ],
() => fetch('/node-api/stats'),
() => fetch('/node-api/home-stats'),
);
const handleNewBlockMessage: SocketMessage.NewBlock['handler'] = React.useCallback((payload) => {
......
......@@ -14,7 +14,7 @@ export const test = base.extend<socketServer.SocketServerFixture>({
});
test('default view +@mobile +@dark-mode', async({ mount, page }) => {
await page.route('/node-api/stats', (route) => route.fulfill({
await page.route('/node-api/home-stats', (route) => route.fulfill({
status: 200,
body: JSON.stringify(statsMock.base),
}));
......@@ -47,7 +47,7 @@ test.describe('socket', () => {
};
test('new item', async({ mount, page, createSocket }) => {
await page.route('/node-api/stats', (route) => route.fulfill({
await page.route('/node-api/home-stats', (route) => route.fulfill({
status: 200,
body: JSON.stringify(statsMock.base),
}));
......
......@@ -7,7 +7,7 @@ import TestApp from 'playwright/TestApp';
import Stats from './Stats';
const API_URL = '/node-api/stats';
const API_URL = '/node-api/home-stats';
test('all items +@mobile +@dark-mode +@desktop-xl', async({ mount, page }) => {
await page.route(API_URL, (route) => route.fulfill({
......
......@@ -29,7 +29,7 @@ const Stats = () => {
const { data, isLoading, isError } = useQuery<unknown, unknown, HomeStats>(
[ QueryKeys.homeStats ],
async() => await fetch(`/node-api/stats`),
async() => await fetch(`/node-api/home-stats`),
);
if (isError) {
......
......@@ -7,8 +7,8 @@ import TestApp from 'playwright/TestApp';
import ChainIndicators from './ChainIndicators';
const STATS_API_URL = '/node-api/stats';
const TX_CHART_API_URL = '/node-api/stats/charts/transactions';
const STATS_API_URL = '/node-api/home-stats';
const TX_CHART_API_URL = '/node-api/home-stats/charts/transactions';
test('daily txs chart +@mobile +@dark-mode +@dark-mode-mobile', async({ mount, page }) => {
await page.route(STATS_API_URL, (route) => route.fulfill({
......
......@@ -37,7 +37,7 @@ const ChainIndicators = () => {
const fetch = useFetch();
const statsQueryResult = useQuery<unknown, unknown, HomeStats>(
[ QueryKeys.homeStats ],
() => fetch('/node-api/stats'),
() => fetch('/node-api/home-stats'),
);
const bgColorDesktop = useColorModeValue('white', 'gray.900');
......
......@@ -19,7 +19,7 @@ const dailyTxsIndicator: TChainIndicator<QueryKeys.chartsTxs> = {
hint: `The total daily number of transactions on the blockchain for the last month.`,
api: {
queryName: QueryKeys.chartsTxs,
path: '/node-api/stats/charts/transactions',
path: '/node-api/home-stats/charts/transactions',
dataFn: (response) => ([ {
items: response.chart_data
.map((item) => ({ date: new Date(item.date), value: item.tx_count }))
......@@ -38,7 +38,7 @@ const coinPriceIndicator: TChainIndicator<QueryKeys.chartsMarket> = {
hint: `${ appConfig.network.currency.symbol } token daily price in USD.`,
api: {
queryName: QueryKeys.chartsMarket,
path: '/node-api/stats/charts/market',
path: '/node-api/home-stats/charts/market',
dataFn: (response) => ([ {
items: response.chart_data
.map((item) => ({ date: new Date(item.date), value: Number(item.closing_price) }))
......@@ -58,7 +58,7 @@ const marketPriceIndicator: TChainIndicator<QueryKeys.chartsMarket> = {
hint: 'The total market value of a cryptocurrency\'s circulating supply. It is analogous to the free-float capitalization in the stock market. Market Cap = Current Price x Circulating Supply.',
api: {
queryName: QueryKeys.chartsMarket,
path: '/node-api/stats/charts/market',
path: '/node-api/home-stats/charts/market',
dataFn: (response) => ([ {
items: response.chart_data
.map((item) => ({ date: new Date(item.date), value: Number(item.closing_price) * Number(response.available_supply) }))
......
......@@ -9,6 +9,7 @@ import type { RoutedTab } from 'ui/shared/RoutedTabs/types';
import useFetch from 'lib/hooks/useFetch';
import AddressDetails from 'ui/address/AddressDetails';
import AddressTxs from 'ui/address/AddressTxs';
import Page from 'ui/shared/Page/Page';
import PageTitle from 'ui/shared/Page/PageTitle';
import RoutedTabs from 'ui/shared/RoutedTabs/RoutedTabs';
......@@ -32,7 +33,7 @@ const AddressPageContent = () => {
].map((tag) => <Tag key={ tag.label }>{ tag.display_name }</Tag>);
const tabs: Array<RoutedTab> = [
{ id: 'txs', title: 'Transactions', component: null },
{ id: 'txs', title: 'Transactions', component: <AddressTxs/> },
{ id: 'token_transfers', title: 'Token transfers', component: null },
{ id: 'tokens', title: 'Tokens', component: null },
{ id: 'internal_txn', title: 'Internal txn', component: null },
......
......@@ -9,7 +9,7 @@ import TestApp from 'playwright/TestApp';
import Blocks from './Blocks';
const BLOCKS_API_URL = '/node-api/blocks?type=block';
const STATS_API_URL = '/node-api/stats';
const STATS_API_URL = '/node-api/home-stats';
const hooksConfig = {
router: {
query: { tab: 'blocks' },
......@@ -17,7 +17,7 @@ const hooksConfig = {
},
};
export const test = base.extend<socketServer.SocketServerFixture>({
const test = base.extend<socketServer.SocketServerFixture>({
createSocket: socketServer.createSocket,
});
......
......@@ -11,7 +11,7 @@ import TestApp from 'playwright/TestApp';
import Home from './Home';
test('default view -@default +@desktop-xl +@mobile +@dark-mode', async({ mount, page }) => {
await page.route('/node-api/stats', (route) => route.fulfill({
await page.route('/node-api/home-stats', (route) => route.fulfill({
status: 200,
body: JSON.stringify(statsMock.base),
}));
......@@ -30,7 +30,7 @@ test('default view -@default +@desktop-xl +@mobile +@dark-mode', async({ mount,
txMock.withTokenTransfer,
]),
}));
await page.route('/node-api/stats/charts/transactions', (route) => route.fulfill({
await page.route('/node-api/home-stats/charts/transactions', (route) => route.fulfill({
status: 200,
body: JSON.stringify(dailyTxsMock.base),
}));
......
......@@ -18,9 +18,6 @@ const Stats = () => {
handleIntervalChange,
debounceFilterCharts,
displayedCharts,
showChartFullscreen,
clearFullscreenChart,
fullscreenChart,
} = useStats();
return (
......@@ -43,9 +40,7 @@ const Stats = () => {
<ChartsWidgetsList
charts={ displayedCharts }
onChartFullscreenClick={ showChartFullscreen }
fullscreenChart={ fullscreenChart }
onModalClose={ clearFullscreenChart }
interval={ interval }
/>
</Page>
);
......
import { Box, Button, Skeleton, useDisclosure } from '@chakra-ui/react';
import { useQuery } from '@tanstack/react-query';
import { useQuery, useQueryClient } from '@tanstack/react-query';
import React, { useCallback, useState } from 'react';
import type { TWatchlist, TWatchlistItem } from 'types/client/account';
......@@ -22,6 +22,7 @@ import WatchlistTable from 'ui/watchlist/WatchlistTable/WatchlistTable';
const WatchList: React.FC = () => {
const { data, isLoading, isError } =
useQuery<unknown, unknown, TWatchlist>([ QueryKeys.watchlist ], async() => fetch('/node-api/account/watchlist/get-with-tokens'));
const queryClient = useQueryClient();
const addressModalProps = useDisclosure();
const deleteModalProps = useDisclosure();
......@@ -42,6 +43,12 @@ const WatchList: React.FC = () => {
addressModalProps.onClose();
}, [ addressModalProps ]);
const onAddOrEditSuccess = useCallback(async() => {
await queryClient.refetchQueries([ QueryKeys.watchlist ]);
setAddressModalData(undefined);
addressModalProps.onClose();
}, [ addressModalProps, queryClient ]);
const onDeleteClick = useCallback((data: TWatchlistItem) => {
setDeleteModalData(data);
deleteModalProps.onOpen();
......@@ -52,6 +59,12 @@ const WatchList: React.FC = () => {
deleteModalProps.onClose();
}, [ deleteModalProps ]);
const onDeleteSuccess = useCallback(async() => {
queryClient.setQueryData([ QueryKeys.watchlist ], (prevData: TWatchlist | undefined) => {
return prevData?.filter((item) => item.id !== deleteModalData?.id);
});
}, [ deleteModalData?.id, queryClient ]);
const description = (
<AccountPageDescription>
An email notification can be sent to you when an address on your watch list sends or receives any transactions.
......@@ -107,8 +120,21 @@ const WatchList: React.FC = () => {
Add address
</Button>
</Box>
<AddressModal { ...addressModalProps } onClose={ onAddressModalClose } data={ addressModalData }/>
{ deleteModalData && <DeleteAddressModal { ...deleteModalProps } onClose={ onDeleteModalClose } data={ deleteModalData }/> }
<AddressModal
{ ...addressModalProps }
onClose={ onAddressModalClose }
onSuccess={ onAddOrEditSuccess }
data={ addressModalData }
isAdd={ !addressModalData }
/>
{ deleteModalData && (
<DeleteAddressModal
{ ...deleteModalProps }
onClose={ onDeleteModalClose }
onSuccess={ onDeleteSuccess }
data={ deleteModalData }
/>
) }
</>
);
}
......
import type { InputProps } from '@chakra-ui/react';
import { FormControl, FormLabel, Textarea } from '@chakra-ui/react';
import { FormControl, Textarea } from '@chakra-ui/react';
import React, { useCallback } from 'react';
import type { ControllerRenderProps, Control, FieldError } from 'react-hook-form';
import { Controller } from 'react-hook-form';
......@@ -24,9 +24,7 @@ export default function PublicTagFormComment({ control, error, size }: Props) {
{ ...field }
isInvalid={ Boolean(error) }
/>
<FormLabel>
<InputPlaceholder text="Specify the reason for adding tags and color preference(s)" error={ error }/>
</FormLabel>
<InputPlaceholder text="Specify the reason for adding tags and color preference(s)" error={ error }/>
</FormControl>
);
}, [ error, size ]);
......
import { Box, Text, chakra } from '@chakra-ui/react';
import BigNumber from 'bignumber.js';
import React from 'react';
import getCurrencyValue from 'lib/getCurrencyValue';
interface Props {
value: string;
currency?: string;
......@@ -20,32 +21,14 @@ const CurrencyValue = ({ value, currency = '', decimals, exchangeRate, className
</Box>
);
}
const valueCurr = BigNumber(value).div(BigNumber(10 ** Number(decimals || '18')));
const valueResult = accuracy ? valueCurr.dp(accuracy).toFormat() : valueCurr.toFormat();
let usdContent;
if (exchangeRate !== undefined && exchangeRate !== null) {
const exchangeRateBn = new BigNumber(exchangeRate);
const usdBn = valueCurr.times(exchangeRateBn);
let usdResult: string;
if (accuracyUsd && !usdBn.isEqualTo(0)) {
const usdBnDp = usdBn.dp(accuracyUsd);
usdResult = usdBnDp.isEqualTo(0) ? usdBn.precision(accuracyUsd).toFormat() : usdBnDp.toFormat();
} else {
usdResult = usdBn.toFormat();
}
usdContent = (
<Text as="span" variant="secondary" fontWeight={ 400 }>(${ usdResult })</Text>
);
}
const { valueStr: valueResult, usd: usdResult } = getCurrencyValue({ value, accuracy, accuracyUsd, exchangeRate, decimals });
return (
<Box as="span" className={ className } display="inline-flex" rowGap={ 3 } columnGap={ 1 }>
<Text display="inline-block">
{ valueResult }{ currency ? ` ${ currency }` : '' }
</Text>
{ usdContent }
{ usdResult && <Text as="span" variant="secondary" fontWeight={ 400 }>(${ usdResult })</Text> }
</Box>
);
};
......
......@@ -2,16 +2,28 @@ import { Tag, chakra } from '@chakra-ui/react';
import React from 'react';
interface Props {
baseAddress: string;
addressFrom: string;
isIn: boolean;
isOut: boolean;
className?: string;
}
const InOutTag = ({ baseAddress, addressFrom, className }: Props) => {
const isOut = addressFrom === baseAddress;
const InOutTag = ({ isIn, isOut, className }: Props) => {
if (!isIn && !isOut) {
return null;
}
const colorScheme = isOut ? 'orange' : 'green';
return <Tag className={ className } colorScheme={ colorScheme }>{ isOut ? 'OUT' : 'IN' }</Tag>;
return (
<Tag
className={ className }
colorScheme={ colorScheme }
display="flex"
justifyContent="center"
>
{ isOut ? 'OUT' : 'IN' }
</Tag>
);
};
export default React.memo(chakra(InOutTag));
......@@ -96,7 +96,8 @@ const RoutedTabs = ({ tabs, tabListProps, rightSlot, stickyEnabled }: Props) =>
}}
bgColor={ listBgColor }
transitionProperty="top,box-shadow,background-color,color"
transitionDuration="slow"
transitionDuration="normal"
transitionTimingFunction="ease"
{
...(stickyEnabled ? {
position: 'sticky',
......
import { Tooltip, IconButton, Icon, HStack } from '@chakra-ui/react';
import React, { useCallback } from 'react';
import React from 'react';
import DeleteIcon from 'icons/delete.svg';
import EditIcon from 'icons/edit.svg';
import usePreventFocusAfterModalClosing from 'lib/hooks/usePreventFocusAfterModalClosing';
type Props = {
onEditClick: () => void;
......@@ -10,8 +11,7 @@ type Props = {
}
const TableItemActionButtons = ({ onEditClick, onDeleteClick }: Props) => {
// prevent set focus on button when closing modal
const onFocusCapture = useCallback((e: React.SyntheticEvent) => e.stopPropagation(), []);
const onFocusCapture = usePreventFocusAfterModalClosing();
return (
<HStack spacing={ 6 } alignSelf="flex-end">
......
......@@ -21,13 +21,13 @@ const EmptyElement = ({ className, letter }: { className?: string; letter: strin
};
interface Props {
hash: string;
hash?: string;
name?: string | null;
className?: string;
}
const TokenLogo = ({ hash, name, className }: Props) => {
const logoSrc = appConfig.network.assetsPathname ? [
const logoSrc = appConfig.network.assetsPathname && hash ? [
'https://raw.githubusercontent.com/trustwallet/assets/master/blockchains/',
appConfig.network.assetsPathname,
'/assets/',
......@@ -37,6 +37,7 @@ const TokenLogo = ({ hash, name, className }: Props) => {
return (
<Image
borderRadius="base"
className={ className }
src={ logoSrc }
alt={ `${ name || 'token' } logo` }
......
......@@ -53,7 +53,7 @@ const TokenTransferListItem = ({ token, total, tx_hash: txHash, from, to, baseAd
<AddressLink ml={ 2 } fontWeight="500" hash={ from.hash }/>
</Address>
{ baseAddress ?
<InOutTag baseAddress={ baseAddress } addressFrom={ from.hash } w="50px" textAlign="center"/> :
<InOutTag isIn={ baseAddress === to.hash } isOut={ baseAddress === from.hash } w="50px" textAlign="center"/> :
<Icon as={ eastArrowIcon } boxSize={ 6 } color="gray.500"/>
}
<Address width={ addressWidth }>
......
......@@ -59,7 +59,7 @@ const TokenTransferTableItem = ({ token, total, tx_hash: txHash, from, to, baseA
</Td>
{ baseAddress && (
<Td px={ 0 }>
<InOutTag baseAddress={ baseAddress } addressFrom={ from.hash } w="50px" textAlign="center" mt="3px"/>
<InOutTag isIn={ baseAddress === to.hash } isOut={ baseAddress === from.hash } w="50px" textAlign="center" mt="3px"/>
</Td>
) }
<Td>
......
......@@ -18,10 +18,9 @@ const ChartSelectionX = ({ anchorEl, height, scale, data, onSelect }: Props) =>
const borderColor = useToken('colors', 'blue.200');
const ref = React.useRef(null);
const isPressed = React.useRef(false);
const isActive = React.useRef(false);
const startX = React.useRef<number>();
const endX = React.useRef<number>();
const startIndex = React.useRef<number>(0);
const getIndexByX = React.useCallback((x: number) => {
const xDate = scale.invert(x);
......@@ -51,20 +50,33 @@ const ChartSelectionX = ({ anchorEl, height, scale, data, onSelect }: Props) =>
.attr('width', Math.abs(diffX));
}, []);
const handelMouseUp = React.useCallback(() => {
isPressed.current = false;
const handleSelect = React.useCallback((x0: number, x1: number) => {
const index0 = getIndexByX(x0);
const index1 = getIndexByX(x1);
if (Math.abs(index0 - index1) > SELECTION_THRESHOLD) {
onSelect([ Math.min(index0, index1), Math.max(index0, index1) ]);
}
}, [ getIndexByX, onSelect ]);
const cleanUp = React.useCallback(() => {
isActive.current = false;
startX.current = undefined;
endX.current = undefined;
d3.select(ref.current).attr('opacity', 0);
}, [ ]);
if (!endX.current) {
const handelMouseUp = React.useCallback(() => {
if (!isActive.current) {
return;
}
const index = getIndexByX(endX.current);
if (Math.abs(index - startIndex.current) > SELECTION_THRESHOLD) {
onSelect([ Math.min(index, startIndex.current), Math.max(index, startIndex.current) ]);
if (startX.current && endX.current) {
handleSelect(startX.current, endX.current);
}
}, [ getIndexByX, onSelect ]);
cleanUp();
}, [ cleanUp, handleSelect ]);
React.useEffect(() => {
if (!anchorEl) {
......@@ -76,20 +88,34 @@ const ChartSelectionX = ({ anchorEl, height, scale, data, onSelect }: Props) =>
anchorD3
.on('mousedown.selectionX', (event: MouseEvent) => {
const [ x ] = d3.pointer(event, anchorEl);
isPressed.current = true;
isActive.current = true;
startX.current = x;
const index = getIndexByX(x);
startIndex.current = index;
})
.on('mouseup.selectionX', handelMouseUp)
.on('mousemove.selectionX', (event: MouseEvent) => {
if (isPressed.current) {
if (isActive.current) {
const [ x ] = d3.pointer(event, anchorEl);
startX.current && drawSelection(startX.current, x);
endX.current = x;
}
});
})
.on('mouseup.selectionX', handelMouseUp)
.on('touchstart.selectionX', (event: TouchEvent) => {
const pointers = d3.pointers(event, anchorEl);
isActive.current = pointers.length === 2;
})
.on('touchmove.selectionX', (event: TouchEvent) => {
if (isActive.current) {
const pointers = d3.pointers(event, anchorEl);
if (pointers.length === 2 && Math.abs(pointers[0][0] - pointers[1][0]) > 5) {
drawSelection(pointers[0][0], pointers[1][0]);
startX.current = pointers[0][0];
endX.current = pointers[1][0];
}
}
})
.on('touchend.selectionX', handelMouseUp);
d3.select('body').on('mouseup.selectionX', function(event) {
const isOutside = startX.current !== undefined && event.target !== anchorD3.node();
......@@ -99,10 +125,10 @@ const ChartSelectionX = ({ anchorEl, height, scale, data, onSelect }: Props) =>
});
return () => {
anchorD3.on('mousedown.selectionX mouseup.selectionX mousemove.selectionX', null);
d3.select('body').on('mouseup.selectionX', null);
anchorD3.on('.selectionX', null);
d3.select('body').on('.selectionX', null);
};
}, [ anchorEl, drawSelection, getIndexByX, handelMouseUp ]);
}, [ anchorEl, cleanUp, drawSelection, getIndexByX, handelMouseUp, handleSelect ]);
return (
<g className="ChartSelectionX" ref={ ref } opacity={ 0 }>
......
......@@ -32,6 +32,8 @@ const ChartTooltip = ({ chartId, xScale, yScale, width, height, data, anchorEl,
const bgColor = useToken('colors', 'blackAlpha.900');
const ref = React.useRef(null);
const trackerId = React.useRef<number>();
const isVisible = React.useRef(false);
const drawLine = React.useCallback(
(x: number) => {
......@@ -129,67 +131,78 @@ const ChartTooltip = ({ chartId, xScale, yScale, width, height, data, anchorEl,
}, [ drawPoints, drawLine, drawContent ]);
const showContent = React.useCallback(() => {
d3.select(ref.current).attr('opacity', 1);
d3.select(ref.current)
.selectAll('.ChartTooltip__point')
.attr('opacity', 1);
if (!isVisible.current) {
d3.select(ref.current).attr('opacity', 1);
d3.select(ref.current)
.selectAll('.ChartTooltip__point')
.attr('opacity', 1);
isVisible.current = true;
}
}, []);
const hideContent = React.useCallback(() => {
d3.select(ref.current).attr('opacity', 0);
isVisible.current = false;
}, []);
const createPointerTracker = React.useCallback((event: PointerEvent, isSubsequentCall?: boolean) => {
let isShown = false;
let isPressed = event.pointerType === 'mouse' && event.type === 'pointerdown' && !isSubsequentCall;
if (isPressed) {
hideContent();
}
trackPointer(event, {
return trackPointer(event, {
move: (pointer) => {
if (!pointer.point || isPressed) {
return;
}
draw(pointer);
if (!isShown) {
showContent();
isShown = true;
}
showContent();
},
out: () => {
hideContent();
isShown = false;
trackerId.current = undefined;
},
end: (tracker) => {
end: () => {
hideContent();
const isOutside = tracker.sourceEvent?.offsetX && width && (tracker.sourceEvent.offsetX > width || tracker.sourceEvent.offsetX < 0);
if (!isOutside && isPressed) {
window.setTimeout(() => {
createPointerTracker(event, true);
}, 0);
}
isShown = false;
trackerId.current = undefined;
isPressed = false;
},
});
}, [ draw, hideContent, showContent, width ]);
}, [ draw, hideContent, showContent ]);
React.useEffect(() => {
const anchorD3 = d3.select(anchorEl);
let isMultiTouch = false; // disabling creation of new tracker in multi touch mode
anchorD3
.on('touchmove.tooltip', (event: PointerEvent) => event.preventDefault()) // prevent scrolling
.on('touchmove.tooltip', (event: TouchEvent) => event.preventDefault()) // prevent scrolling
.on(`touchstart.tooltip`, (event: TouchEvent) => {
isMultiTouch = event.touches.length > 1;
})
.on(`touchend.tooltip`, (event: TouchEvent) => {
if (isMultiTouch && event.touches.length === 0) {
isMultiTouch = false;
}
})
.on('pointerenter.tooltip pointerdown.tooltip', (event: PointerEvent) => {
createPointerTracker(event);
if (!isMultiTouch) {
trackerId.current = createPointerTracker(event);
}
})
.on('pointermove.tooltip', (event: PointerEvent) => {
if (event.pointerType === 'mouse' && !isMultiTouch && trackerId.current === undefined) {
trackerId.current = createPointerTracker(event);
}
});
return () => {
anchorD3.on('touchmove.tooltip pointerenter.tooltip pointerdown.tooltip', null);
trackerId.current && anchorD3.on(
[ 'pointerup', 'pointercancel', 'lostpointercapture', 'pointermove', 'pointerout' ].map((event) => `${ event }.${ trackerId.current }`).join(' '),
null,
);
};
}, [ anchorEl, createPointerTracker, draw, hideContent, showContent ]);
......
......@@ -14,7 +14,7 @@ export interface PointerOptions {
end?: (tracker: Pointer) => void;
}
export function trackPointer(event: PointerEvent, { start, move, out, end }: PointerOptions) {
export function trackPointer(event: PointerEvent, { start, move, out, end }: PointerOptions): number {
const tracker: Pointer = {
id: event.pointerId,
point: null,
......@@ -26,16 +26,27 @@ export function trackPointer(event: PointerEvent, { start, move, out, end }: Poi
tracker.point = d3.pointer(event, target);
target.setPointerCapture(id);
const untrack = (sourceEvent: PointerEvent) => {
tracker.sourceEvent = sourceEvent;
d3.select(target).on(`.${ id }`, null);
target.releasePointerCapture(id);
end?.(tracker);
};
d3.select(target)
.on(`touchstart.${ id }`, (sourceEvent: PointerEvent) => {
const target = sourceEvent.target as Element;
const touches = d3.pointers(sourceEvent, target);
// disable current tracker when entering multi touch mode
touches.length > 1 && untrack(sourceEvent);
})
.on(`pointerup.${ id } pointercancel.${ id } lostpointercapture.${ id }`, (sourceEvent: PointerEvent) => {
if (sourceEvent.pointerId !== id) {
return;
}
tracker.sourceEvent = sourceEvent;
d3.select(target).on(`.${ id }`, null);
target.releasePointerCapture(id);
end?.(tracker);
untrack(sourceEvent);
})
.on(`pointermove.${ id }`, (sourceEvent) => {
if (sourceEvent.pointerId !== id) {
......@@ -57,5 +68,5 @@ export function trackPointer(event: PointerEvent, { start, move, out, end }: Poi
start?.(tracker);
return [ 'pointerup', 'pointercancel', 'lostpointercapture', 'pointermove', 'pointerout' ].map((event) => `${ event }.${ id }`);
return id;
}
import { GridItem, Skeleton, SkeletonCircle } from '@chakra-ui/react';
import React from 'react';
const DetailsSkeletonRow = ({ w = '100%' }: { w?: string }) => {
return (
<>
<GridItem display="flex" columnGap={ 2 } w={{ base: '50%', lg: 'auto' }} _notFirst={{ mt: { base: 3, lg: 0 } }}>
<SkeletonCircle h={ 5 } w={ 5 }/>
<Skeleton flexGrow={ 1 } h={ 5 } borderRadius="full"/>
</GridItem>
<GridItem pl={{ base: 7, lg: 0 }}>
<Skeleton h={ 5 } borderRadius="full" w={{ base: '100%', lg: w }}/>
</GridItem>
</>
);
};
export default DetailsSkeletonRow;
......@@ -69,7 +69,9 @@ const NavigationDesktop = () => {
w="100%"
px={{ lg: isExpanded ? 3 : '15px', xl: isCollapsed ? '15px' : 3 }}
h={ 10 }
{ ...getDefaultTransitionProps({ transitionProperty: 'padding' }) }
transitionProperty="padding"
transitionDuration="normal"
transitionTimingFunction="ease"
>
<NetworkLogo isCollapsed={ isCollapsed }/>
<NetworkMenu isCollapsed={ isCollapsed }/>
......
import { Icon, Box, Image, useColorModeValue, useBreakpointValue } from '@chakra-ui/react';
import { Icon, Box, Image, useColorModeValue } from '@chakra-ui/react';
import React from 'react';
import appConfig from 'configs/app/config';
......@@ -6,7 +6,6 @@ import smallLogoPlaceholder from 'icons/networks/icons/placeholder.svg';
import logoPlaceholder from 'icons/networks/logos/blockscout.svg';
import link from 'lib/link/link';
import ASSETS from 'lib/networks/networkAssets';
import getDefaultTransitionProps from 'theme/utils/getDefaultTransitionProps';
interface Props {
isCollapsed?: boolean;
......@@ -16,59 +15,71 @@ interface Props {
const NetworkLogo = ({ isCollapsed, onClick }: Props) => {
const logoColor = useColorModeValue('blue.600', 'white');
const href = link('network_index');
const [ isLogoError, setLogoError ] = React.useState(false);
const [ isSmallLogoError, setSmallLogoError ] = React.useState(false);
const style = useColorModeValue({}, { filter: 'brightness(0) invert(1)' });
const isLg = useBreakpointValue({ base: false, lg: true, xl: false }, { ssr: true });
const logoEl = (() => {
const showSmallLogo = isCollapsed || (isCollapsed !== false && isLg);
if (showSmallLogo) {
if (appConfig.network.smallLogo) {
return (
<Image
w="auto"
h="100%"
src={ appConfig.network.smallLogo }
alt={ `${ appConfig.network.name } network logo` }
/>
);
}
const handleSmallLogoError = React.useCallback(() => {
setSmallLogoError(true);
}, []);
const smallLogo = appConfig.network.type ? ASSETS[appConfig.network.type]?.smallLogo || ASSETS[appConfig.network.type]?.icon : undefined;
return (
<Icon
as={ smallLogo || smallLogoPlaceholder }
width="auto"
height="100%"
color={ smallLogo ? undefined : logoColor }
{ ...getDefaultTransitionProps() }
style={ style }
/>
);
}
const handleLogoError = React.useCallback(() => {
setLogoError(true);
}, []);
if (appConfig.network.logo) {
return (
<Image
w="auto"
h="100%"
src={ appConfig.network.logo }
alt={ `${ appConfig.network.name } network logo` }
/>
);
}
const logoEl = (() => {
const fallbackLogoSrc = appConfig.network.type ? ASSETS[appConfig.network.type]?.logo : undefined;
const fallbackSmallLogoSrc = appConfig.network.type ? ASSETS[appConfig.network.type]?.smallLogo || ASSETS[appConfig.network.type]?.icon : undefined;
const logo = appConfig.network.type ? ASSETS[appConfig.network.type]?.logo : undefined;
return (
const logo = appConfig.network.logo;
const smallLogo = appConfig.network.smallLogo;
const fallbackLogo = (
<Icon
as={ logo || logoPlaceholder }
as={ fallbackLogoSrc || logoPlaceholder }
width="auto"
height="100%"
color={ logo ? undefined : logoColor }
{ ...getDefaultTransitionProps() }
color={ fallbackLogoSrc ? undefined : logoColor }
display={{ base: 'block', lg: isCollapsed === false ? 'block' : 'none', xl: isCollapsed ? 'none' : 'block' }}
style={ style }
/>
);
const fallbackSmallLogo = (
<Icon
as={ fallbackSmallLogoSrc || smallLogoPlaceholder }
width="auto"
height="100%"
color={ fallbackSmallLogoSrc ? undefined : logoColor }
display={{ base: 'none', lg: isCollapsed === false ? 'none' : 'block', xl: isCollapsed ? 'block' : 'none' }}
style={ style }
/>
);
return (
<>
{ /* big logo */ }
<Image
w="auto"
h="100%"
src={ logo }
display={{ base: 'block', lg: isCollapsed === false ? 'block' : 'none', xl: isCollapsed ? 'none' : 'block' }}
alt={ `${ appConfig.network.name } network logo` }
fallback={ isLogoError || !logo ? fallbackSmallLogo : undefined }
onError={ handleLogoError }
/>
{ /* small logo */ }
<Image
w="auto"
h="100%"
src={ smallLogo }
display={{ base: 'none', lg: isCollapsed === false ? 'none' : 'block', xl: isCollapsed ? 'block' : 'none' }}
alt={ `${ appConfig.network.name } network logo` }
fallback={ isSmallLogoError || !smallLogo ? fallbackLogo : undefined }
onError={ handleSmallLogoError }
/>
</>
);
})();
return (
......@@ -82,7 +93,6 @@ const NetworkLogo = ({ isCollapsed, onClick }: Props) => {
overflow="hidden"
onClick={ onClick }
flexShrink={ 0 }
{ ...getDefaultTransitionProps({ transitionProperty: 'width' }) }
aria-label="Link to main page"
>
{ logoEl }
......
......@@ -2,13 +2,13 @@ import { Popover, PopoverContent, PopoverBody, PopoverTrigger, Button } from '@c
import React from 'react';
import useFetchProfileInfo from 'lib/hooks/useFetchProfileInfo';
import link from 'lib/link/link';
import useLoginUrl from 'lib/hooks/useLoginUrl';
import UserAvatar from 'ui/shared/UserAvatar';
import ProfileMenuContent from 'ui/snippets/profileMenu/ProfileMenuContent';
const ProfileMenuDesktop = () => {
const { data, isFetched } = useFetchProfileInfo();
const loginUrl = link('auth');
const loginUrl = useLoginUrl();
return (
<Popover openDelay={ 300 } placement="bottom-end" gutter={ 10 } isLazy>
......
......@@ -2,7 +2,7 @@ import { Flex, Box, Drawer, DrawerOverlay, DrawerContent, DrawerBody, useDisclos
import React from 'react';
import useFetchProfileInfo from 'lib/hooks/useFetchProfileInfo';
import link from 'lib/link/link';
import useLoginUrl from 'lib/hooks/useLoginUrl';
import UserAvatar from 'ui/shared/UserAvatar';
import ColorModeToggler from 'ui/snippets/header/ColorModeToggler';
import ProfileMenuContent from 'ui/snippets/profileMenu/ProfileMenuContent';
......@@ -11,7 +11,7 @@ const ProfileMenuMobile = () => {
const { isOpen, onOpen, onClose } = useDisclosure();
const { data, isFetched } = useFetchProfileInfo();
const loginUrl = link('auth');
const loginUrl = useLoginUrl();
return (
<>
......
import { Box, Button, Grid, Heading, Icon, IconButton, Menu, MenuButton, MenuItem, MenuList, Text, useColorModeValue, VisuallyHidden } from '@chakra-ui/react';
import React, { useCallback } from 'react';
import { Box, Grid, Heading, Icon, IconButton, Menu, MenuButton, MenuItem, MenuList, Text, useColorModeValue, VisuallyHidden } from '@chakra-ui/react';
import { useQuery } from '@tanstack/react-query';
import React, { useCallback, useState } from 'react';
import type { ModalChart } from 'types/client/stats';
import type { Charts } from 'types/api/stats';
import { QueryKeys } from 'types/client/queries';
import type { StatsIntervalIds } from 'types/client/stats';
import dotsIcon from 'icons/vertical-dots.svg';
import repeatArrow from 'icons/repeat_arrow.svg';
import dotsIcon from 'icons/vertical_dots.svg';
import useFetch from 'lib/hooks/useFetch';
import ChartWidgetGraph from './ChartWidgetGraph';
import { demoChartsData } from './constants/demo-charts-data';
import ChartWidgetSkeleton from './ChartWidgetSkeleton';
import { STATS_INTERVALS } from './constants';
import FullscreenChartModal from './FullscreenChartModal';
type Props = {
id: string;
onFullscreenClick: (chart: ModalChart) => void;
apiMethodURL: string;
title: string;
description: string;
interval: StatsIntervalIds;
}
const ChartWidget = ({ id, title, description, onFullscreenClick }: Props) => {
function formatDate(date: Date) {
return date.toISOString().substring(0, 10);
}
const ChartWidget = ({ id, title, description, interval }: Props) => {
const fetch = useFetch();
const selectedInterval = STATS_INTERVALS[interval];
const [ isFullscreen, setIsFullscreen ] = useState(false);
const [ isZoomResetInitial, setIsZoomResetInitial ] = React.useState(true);
const endDate = selectedInterval.start ? formatDate(new Date()) : undefined;
const startDate = selectedInterval.start ? formatDate(selectedInterval.start) : undefined;
const menuButtonColor = useColorModeValue('black', 'white');
const borderColor = useColorModeValue('gray.200', 'gray.600');
const url = `/node-api/stats/charts?name=${ id }${ startDate ? `&from=${ startDate }&to=${ endDate }` : '' }`;
const { data, isLoading } = useQuery<unknown, unknown, Charts>(
[ QueryKeys.charts, id, startDate ],
async() => await fetch(url),
);
const handleZoom = useCallback(() => {
setIsZoomResetInitial(false);
}, []);
......@@ -27,81 +55,118 @@ const ChartWidget = ({ id, title, description, onFullscreenClick }: Props) => {
setIsZoomResetInitial(true);
}, []);
const handleFullscreenClick = useCallback(() => {
onFullscreenClick({ id, title });
}, [ id, title, onFullscreenClick ]);
return (
<Box
padding={{ base: 3, md: 4 }}
borderRadius="md"
border="1px"
borderColor={ useColorModeValue('gray.200', 'gray.600') }
>
<Grid
gridTemplateColumns="auto auto 36px"
gridColumnGap={ 4 }
>
<Heading
mb={ 1 }
size={{ base: 'xs', md: 'sm' }}
>
{ title }
</Heading>
<Text
mb={ 1 }
gridColumn={ 1 }
as="p"
variant="secondary"
fontSize="xs"
const showChartFullscreen = useCallback(() => {
setIsFullscreen(true);
if (!document.fullscreenElement && document.documentElement.requestFullscreen) {
document.documentElement.requestFullscreen();
}
}, []);
const clearFullscreenChart = useCallback(() => {
setIsFullscreen(false);
if (document.fullscreenElement) {
document.exitFullscreen();
}
}, []);
if (isLoading) {
return <ChartWidgetSkeleton/>;
}
if (data) {
const items = data.chart
.map((item) => {
return { date: new Date(item.date), value: Number(item.value) };
});
return (
<>
<Box
padding={{ base: 3, md: 4 }}
borderRadius="md"
border="1px"
borderColor={ borderColor }
>
{ description }
</Text>
{ !isZoomResetInitial && (
<Button
gridColumn={ 2 }
justifySelf="end"
alignSelf="top"
gridRow="1/3"
size="sm"
variant="outline"
onClick={ handleZoomResetClick }
>
Reset zoom
</Button>
) }
<Menu>
<MenuButton
gridColumn={ 3 }
gridRow="1/3"
justifySelf="end"
w="36px"
h="32px"
icon={ <Icon as={ dotsIcon } w={ 4 } h={ 4 } color={ useColorModeValue('black', 'white') }/> }
colorScheme="transparent"
as={ IconButton }
<Grid
gridTemplateColumns="auto auto 36px"
gridColumnGap={ 2 }
>
<VisuallyHidden>
Open chart options menu
</VisuallyHidden>
</MenuButton>
<MenuList>
<MenuItem onClick={ handleFullscreenClick }>View fullscreen</MenuItem>
</MenuList>
</Menu>
</Grid>
<ChartWidgetGraph
items={ demoChartsData }
onZoom={ handleZoom }
isZoomResetInitial={ isZoomResetInitial }
title={ title }
/>
</Box>
);
<Heading
mb={ 1 }
size={{ base: 'xs', md: 'sm' }}
>
{ title }
</Heading>
<Text
mb={ 1 }
gridColumn={ 1 }
as="p"
variant="secondary"
fontSize="xs"
>
{ description }
</Text>
<IconButton
hidden={ isZoomResetInitial }
aria-label="Reset zoom"
title="Reset zoom"
colorScheme="blue"
w={ 9 }
h={ 8 }
gridColumn={ 2 }
justifySelf="end"
alignSelf="top"
gridRow="1/3"
size="sm"
variant="ghost"
onClick={ handleZoomResetClick }
icon={ <Icon as={ repeatArrow } w={ 4 } h={ 4 } color="blue.700"/> }
/>
<Menu>
<MenuButton
gridColumn={ 3 }
gridRow="1/3"
justifySelf="end"
w="36px"
h="32px"
icon={ <Icon as={ dotsIcon } w={ 4 } h={ 4 } color={ menuButtonColor }/> }
colorScheme="transparent"
as={ IconButton }
>
<VisuallyHidden>
Open chart options menu
</VisuallyHidden>
</MenuButton>
<MenuList>
<MenuItem onClick={ showChartFullscreen }>View fullscreen</MenuItem>
</MenuList>
</Menu>
</Grid>
<ChartWidgetGraph
items={ items }
onZoom={ handleZoom }
isZoomResetInitial={ isZoomResetInitial }
title={ title }
/>
</Box>
<FullscreenChartModal
isOpen={ isFullscreen }
items={ items }
title={ title }
onClose={ clearFullscreenChart }
/>
</>
);
}
return null;
};
export default ChartWidget;
......@@ -3,6 +3,7 @@ import React, { useEffect, useMemo } from 'react';
import type { TimeChartItem } from 'ui/shared/chart/types';
import useIsMobile from 'lib/hooks/useIsMobile';
import ChartArea from 'ui/shared/chart/ChartArea';
import ChartAxis from 'ui/shared/chart/ChartAxis';
import ChartGridLine from 'ui/shared/chart/ChartGridLine';
......@@ -23,12 +24,13 @@ interface Props {
const CHART_MARGIN = { bottom: 20, left: 52, right: 30, top: 10 };
const ChartWidgetGraph = ({ items, onZoom, isZoomResetInitial, title }: Props) => {
const isMobile = useIsMobile();
const ref = React.useRef<SVGSVGElement>(null);
const [ range, setRange ] = React.useState<[ number, number ]>([ 0, Infinity ]);
const { width, height, innerWidth, innerHeight } = useChartSize(ref.current, CHART_MARGIN);
const overlayRef = React.useRef<SVGRectElement>(null);
const color = useToken('colors', 'blue.200');
const overlayRef = React.useRef<SVGRectElement>(null);
const chartId = useMemo(() => `chart-${ crypto.randomUUID() }`, []);
const [ range, setRange ] = React.useState<[ number, number ]>([ 0, Infinity ]);
const { innerWidth, innerHeight } = useChartSize(ref.current, CHART_MARGIN);
const displayedData = useMemo(() => items.slice(range[0], range[1]).map((d) =>
({ ...d, date: new Date(d.date) })), [ items, range ]);
......@@ -52,9 +54,9 @@ const ChartWidgetGraph = ({ items, onZoom, isZoomResetInitial, title }: Props) =
}, [ isZoomResetInitial ]);
return (
<svg width={ width || '100%' } height={ height || '100%' } ref={ ref } cursor="pointer" id={ chartId }>
<svg width="100%" height="100%" ref={ ref } cursor="pointer" id={ chartId }>
<g transform={ `translate(${ CHART_MARGIN?.left || 0 },${ CHART_MARGIN?.top || 0 })` } opacity={ width ? 1 : 0 }>
<g transform={ `translate(${ CHART_MARGIN?.left || 0 },${ CHART_MARGIN?.top || 0 })` } opacity={ 1 }>
<ChartGridLine
type="horizontal"
scale={ yScale }
......@@ -93,7 +95,7 @@ const ChartWidgetGraph = ({ items, onZoom, isZoomResetInitial, title }: Props) =
type="bottom"
scale={ xScale }
transform={ `translate(0, ${ innerHeight })` }
ticks={ 5 }
ticks={ isMobile ? 1 : 3 }
anchorEl={ overlayRef.current }
disableAnimation
/>
......
import { Box, Skeleton } from '@chakra-ui/react';
import React from 'react';
const ChartWidgetSkeleton = () => {
return (
<Box
height="235px"
paddingY={{ base: 3, md: 4 }}
>
<Skeleton w="75%" h="24px" mb={ 1 }/>
<Skeleton w="50%" h="18px" mb={ 5 }/>
<Skeleton w="100%" h="150px"/>
</Box>
);
};
export default ChartWidgetSkeleton;
import { Box, Grid, GridItem, Heading, List, ListItem } from '@chakra-ui/react';
import React from 'react';
import type { ModalChart, StatsSection } from 'types/client/stats';
import type { StatsIntervalIds, StatsSection } from 'types/client/stats';
import { apos } from 'lib/html-entities';
import EmptySearchResult from '../apps/EmptySearchResult';
import ChartWidget from './ChartWidget';
import FullscreenChartModal from './FullscreenChartModal';
type Props = {
charts: Array<StatsSection>;
onChartFullscreenClick: (chart: ModalChart) => void;
fullscreenChart: ModalChart | null;
onModalClose: () => void;
interval: StatsIntervalIds;
}
const ChartsWidgetsList = ({ charts, onChartFullscreenClick, fullscreenChart, onModalClose }: Props) => {
const ChartsWidgetsList = ({ charts, interval }: Props) => {
const isAnyChartDisplayed = charts.some((section) => section.charts.some(chart => chart.visible));
return (
......@@ -42,7 +39,7 @@ const ChartsWidgetsList = ({ charts, onChartFullscreenClick, fullscreenChart, on
<Grid
templateColumns={{
sm: 'repeat(2, 1fr)',
lg: 'repeat(2, 1fr)',
}}
gap={ 4 }
>
......@@ -53,10 +50,9 @@ const ChartsWidgetsList = ({ charts, onChartFullscreenClick, fullscreenChart, on
>
<ChartWidget
id={ chart.id }
onFullscreenClick={ onChartFullscreenClick }
apiMethodURL={ chart.apiMethodURL }
title={ chart.title }
description={ chart.description }
interval={ interval }
/>
</GridItem>
)) }
......@@ -68,14 +64,6 @@ const ChartsWidgetsList = ({ charts, onChartFullscreenClick, fullscreenChart, on
) : (
<EmptySearchResult text={ `Couldn${ apos }t find a chart that matches your filter query.` }/>
) }
{ fullscreenChart && (
<FullscreenChartModal
id={ fullscreenChart.id }
title={ fullscreenChart.title }
onClose={ onModalClose }
/>
) }
</Box>
);
};
......
import { Button, Flex, Heading, Modal, ModalBody, ModalCloseButton, ModalContent, ModalHeader, ModalOverlay } from '@chakra-ui/react';
import React, { useCallback } from 'react';
import type { TimeChartItem } from '../shared/chart/types';
import ChartWidgetGraph from './ChartWidgetGraph';
import { demoChartsData } from './constants/demo-charts-data';
type Props = {
id: string;
isOpen: boolean;
title: string;
items: Array<TimeChartItem>;
onClose: () => void;
}
const FullscreenChartModal = ({
id,
isOpen,
title,
items,
onClose,
}: Props) => {
const [ isZoomResetInitial, setIsZoomResetInitial ] = React.useState(true);
......@@ -27,7 +30,7 @@ const FullscreenChartModal = ({
return (
<Modal
isOpen={ Boolean(id) }
isOpen={ isOpen }
onClose={ onClose }
size="full"
isCentered
......@@ -71,13 +74,13 @@ const FullscreenChartModal = ({
<ModalCloseButton/>
<ModalBody
h="100%"
h="75%"
>
<ChartWidgetGraph
items={ demoChartsData }
items={ items }
onZoom={ handleZoom }
isZoomResetInitial={ isZoomResetInitial }
title="test"
title={ title }
/>
</ModalBody>
</ModalContent>
......
......@@ -17,8 +17,7 @@ const NumberWidgetsList = () => {
const { data, isLoading } = useQuery<unknown, unknown, Stats>(
[ QueryKeys.stats ],
// TODO: Just temporary. Remove this when the API is ready.
async() => await fetch(`/node-api/stats`),
async() => await fetch(`/node-api/stats/counters`),
);
return (
......@@ -30,8 +29,8 @@ const NumberWidgetsList = () => {
.map((e, i) => <NumberWidgetSkeleton key={ i }/>) :
(
<NumberWidget
label="Total blocks"
value={ Number(data?.total_blocks).toLocaleString() }
label="Total blocks all time"
value={ Number(data?.totalBlocksAllTime).toLocaleString() }
/>
) }
</Grid>
......
import { Box, Button, Icon, Menu, MenuButton, MenuItemOption, MenuList, MenuOptionGroup } from '@chakra-ui/react';
import { Box, Button, Icon, Menu, MenuButton, MenuItemOption, MenuList, MenuOptionGroup, Text } from '@chakra-ui/react';
import React, { useCallback } from 'react';
import eastMiniArrowIcon from 'icons/arrows/east-mini.svg';
......@@ -32,8 +32,14 @@ export function StatsDropdownMenu<T extends string>({ items, selectedId, onSelec
display="flex"
alignItems="center"
>
{ selectedCategory?.title }
<Icon transform="rotate(-90deg)" ml={{ base: 'auto', sm: 1 }} as={ eastMiniArrowIcon } w={ 5 } h={ 5 }/>
<Text
whiteSpace="nowrap"
overflow="hidden"
textOverflow="ellipsis"
>
{ selectedCategory?.title }
</Text>
<Icon transform="rotate(-90deg)" ml="auto" as={ eastMiniArrowIcon } w={ 5 } h={ 5 }/>
</Box>
</MenuButton>
......
......@@ -21,7 +21,7 @@ const sectionsList = Object.keys(STATS_SECTIONS)
const intervalList = Object.keys(STATS_INTERVALS).map((id: string) => ({
id: id,
title: STATS_INTERVALS[id as StatsIntervalIds],
title: STATS_INTERVALS[id as StatsIntervalIds].title,
})) as Array<StatsInterval>;
type Props = {
......@@ -48,7 +48,7 @@ const StatsFilters = ({
"section interval"`,
lg: `"input section interval"`,
}}
gridTemplateColumns={{ lg: '1fr auto auto' }}
gridTemplateColumns={{ base: 'repeat(2, minmax(0, 1fr))', lg: '1fr auto auto' }}
>
<GridItem
w="100%"
......
......@@ -9,31 +9,7 @@ export const statsChartsScheme: Array<StatsSection> = [
id: 'new-blocks',
title: 'New blocks',
description: 'New blocks number per day',
apiMethodURL: '/node-api/stats/charts/transactions',
},
{
id: 'average-block-size',
title: 'Average block size',
description: 'Average size of blocks in bytes per day',
apiMethodURL: '/node-api/stats/charts/transactions',
},
],
},
{
id: 'transactions',
title: 'Transactions',
charts: [
{
id: 'transaction-fees',
title: 'Transaction fees',
description: 'Amount of tokens paid as fees per day',
apiMethodURL: '/node-api/stats/charts/transactions',
},
{
id: 'native-coin-holders-growth',
title: 'Native coin holders growth',
description: 'Total token holders number per day',
apiMethodURL: '/node-api/stats/charts/transactions',
visible: true,
},
],
},
......
import type { TimeChartItem } from 'ui/shared/chart/types';
export const demoChartsData: Array<TimeChartItem> = [ { date: new Date('2022-10-17T00:00:00.000Z'), value: 432670 }, {
date: new Date('2022-10-18T00:00:00.000Z'),
value: 370100,
}, { date: new Date('2022-10-19T00:00:00.000Z'), value: 283234 }, { date: new Date('2022-10-20T00:00:00.000Z'), value: 420910 }, {
date: new Date('2022-10-21T00:00:00.000Z'),
value: 411988,
}, { date: new Date('2022-10-22T00:00:00.000Z'), value: 356269 }, { date: new Date('2022-10-23T00:00:00.000Z'), value: 389747 }, {
date: new Date('2022-10-24T00:00:00.000Z'),
value: 387130,
}, { date: new Date('2022-10-25T00:00:00.000Z'), value: 428785 }, { date: new Date('2022-10-26T00:00:00.000Z'), value: 63809 }, {
date: new Date('2022-10-27T00:00:00.000Z'),
value: 50518,
}, { date: new Date('2022-10-28T00:00:00.000Z'), value: 39087 }, { date: new Date('2022-10-29T00:00:00.000Z'), value: 36789 }, {
date: new Date('2022-10-30T00:00:00.000Z'),
value: 48569,
}, { date: new Date('2022-10-31T00:00:00.000Z'), value: 62519 }, { date: new Date('2022-11-01T00:00:00.000Z'), value: 152059 }, {
date: new Date('2022-11-02T00:00:00.000Z'),
value: 63743,
}, { date: new Date('2022-11-03T00:00:00.000Z'), value: 83667 }, { date: new Date('2022-11-04T00:00:00.000Z'), value: 91725 }, {
date: new Date('2022-11-05T00:00:00.000Z'),
value: 82897,
}, { date: new Date('2022-11-06T00:00:00.000Z'), value: 62477 }, { date: new Date('2022-11-07T00:00:00.000Z'), value: 58131 }, {
date: new Date('2022-11-08T00:00:00.000Z'),
value: 74197,
}, { date: new Date('2022-11-09T00:00:00.000Z'), value: 43691 }, { date: new Date('2022-11-10T00:00:00.000Z'), value: 92887 }, {
date: new Date('2022-11-11T00:00:00.000Z'),
value: 79493,
}, { date: new Date('2022-11-12T00:00:00.000Z'), value: 86764 }, { date: new Date('2022-11-13T00:00:00.000Z'), value: 22338 }, {
date: new Date('2022-11-14T00:00:00.000Z'),
value: 62266,
}, { date: new Date('2022-11-15T00:00:00.000Z'), value: 84084 }, { date: new Date('2022-11-16T00:00:00.000Z'), value: 75898 } ];
......@@ -8,10 +8,30 @@ export const STATS_SECTIONS: { [key in StatsSectionIds]?: string } = {
gas: 'Gas',
};
export const STATS_INTERVALS: { [key in StatsIntervalIds]: string } = {
all: 'All time',
oneMonth: '1 month',
threeMonths: '3 months',
sixMonths: '6 months',
oneYear: '1 year',
export const STATS_INTERVALS: { [key in StatsIntervalIds]: { title: string; start?: Date } } = {
all: {
title: 'All time',
},
oneMonth: {
title: '1 month',
start: getStartDateInPast(1),
},
threeMonths: {
title: '3 months',
start: getStartDateInPast(3),
},
sixMonths: {
title: '6 months',
start: getStartDateInPast(6),
},
oneYear: {
title: '1 year',
start: getStartDateInPast(12),
},
};
function getStartDateInPast(months: number): Date {
const date = new Date();
date.setMonth(date.getMonth() - months);
return date;
}
import debounce from 'lodash/debounce';
import React, { useCallback, useEffect, useState } from 'react';
import type { ModalChart, StatsChart, StatsIntervalIds, StatsSection, StatsSectionIds } from 'types/client/stats';
import type { StatsChart, StatsIntervalIds, StatsSection, StatsSectionIds } from 'types/client/stats';
import { statsChartsScheme } from './constants/charts-scheme';
......@@ -16,8 +16,7 @@ function isChartNameMatches(q: string, chart: StatsChart) {
export default function useStats() {
const [ displayedCharts, setDisplayedCharts ] = useState<Array<StatsSection>>(statsChartsScheme);
const [ section, setSection ] = useState<StatsSectionIds>('all');
const [ interval, setInterval ] = useState<StatsIntervalIds>('all');
const [ fullscreenChart, setFullscreenChart ] = useState<ModalChart | null>(null);
const [ interval, setInterval ] = useState<StatsIntervalIds>('oneMonth');
const [ filterQuery, setFilterQuery ] = useState('');
// eslint-disable-next-line react-hooks/exhaustive-deps
......@@ -48,22 +47,6 @@ export default function useStats() {
setInterval(newInterval);
}, []);
const showChartFullscreen = useCallback((chart: ModalChart) => {
setFullscreenChart(chart);
if (!document.fullscreenElement) {
document.documentElement.requestFullscreen();
}
}, []);
const clearFullscreenChart = useCallback(() => {
setFullscreenChart(null);
if (document.fullscreenElement) {
document.exitFullscreen();
}
}, []);
useEffect(() => {
filterCharts(filterQuery, section);
}, [ filterQuery, section, filterCharts ]);
......@@ -75,9 +58,6 @@ export default function useStats() {
handleIntervalChange,
debounceFilterCharts,
displayedCharts,
showChartFullscreen,
clearFullscreenChart,
fullscreenChart,
}), [
section,
handleSectionChange,
......@@ -85,8 +65,5 @@ export default function useStats() {
handleIntervalChange,
debounceFilterCharts,
displayedCharts,
showChartFullscreen,
clearFullscreenChart,
fullscreenChart,
]);
}
......@@ -26,7 +26,7 @@ test('between addresses +@mobile +@dark-mode', async({ mount, page }) => {
{ hooksConfig },
);
await page.waitForResponse(API_URL),
await page.waitForResponse(API_URL);
await page.getByText('View details').click();
await expect(component).toHaveScreenshot();
......
import { Grid, GridItem, Skeleton, SkeletonCircle } from '@chakra-ui/react';
import { Grid, GridItem, Skeleton } from '@chakra-ui/react';
import React from 'react';
const SkeletonRow = ({ w = '100%' }: { w?: string }) => (
<>
<GridItem display="flex" columnGap={ 2 } w={{ base: '50%', lg: 'auto' }} _notFirst={{ mt: { base: 3, lg: 0 } }}>
<SkeletonCircle h={ 5 } w={ 5 }/>
<Skeleton flexGrow={ 1 } h={ 5 } borderRadius="full"/>
</GridItem>
<GridItem pl={{ base: 7, lg: 0 }}>
<Skeleton h={ 5 } borderRadius="full" w={{ base: '100%', lg: w }}/>
</GridItem>
</>
);
import DetailsSkeletonRow from 'ui/shared/skeletons/DetailsSkeletonRow';
const TxDetailsSkeleton = () => {
const sectionGap = <GridItem colSpan={{ base: undefined, lg: 2 }} mt={{ base: 1, lg: 4 }}/>;
return (
<Grid columnGap={ 8 } rowGap={{ base: 5, lg: 7 }} templateColumns={{ base: '1fr', lg: '210px 1fr' }} maxW="1000px">
<SkeletonRow/>
<SkeletonRow w="20%"/>
<SkeletonRow w="50%"/>
<SkeletonRow/>
<SkeletonRow w="70%"/>
<SkeletonRow w="70%"/>
<DetailsSkeletonRow/>
<DetailsSkeletonRow w="20%"/>
<DetailsSkeletonRow w="50%"/>
<DetailsSkeletonRow/>
<DetailsSkeletonRow w="70%"/>
<DetailsSkeletonRow w="70%"/>
{ sectionGap }
<SkeletonRow w="40%"/>
<SkeletonRow w="40%"/>
<SkeletonRow w="40%"/>
<SkeletonRow w="40%"/>
<SkeletonRow w="40%"/>
<DetailsSkeletonRow w="40%"/>
<DetailsSkeletonRow w="40%"/>
<DetailsSkeletonRow w="40%"/>
<DetailsSkeletonRow w="40%"/>
<DetailsSkeletonRow w="40%"/>
{ sectionGap }
<GridItem colSpan={{ base: undefined, lg: 2 }}>
<Skeleton h={ 5 } borderRadius="full" w="100px"/>
......
......@@ -24,9 +24,11 @@ type Props = {
query: QueryResult;
showBlockInfo?: boolean;
showSocketInfo?: boolean;
currentAddress?: string;
filter?: React.ReactNode;
}
const TxsContent = ({ query, showBlockInfo = true, showSocketInfo = true }: Props) => {
const TxsContent = ({ filter, query, showBlockInfo = true, showSocketInfo = true, currentAddress }: Props) => {
const { data, isLoading, isError, setSortByField, setSortByValue, sorting } = useTxsSort(query);
const isPaginatorHidden = !isLoading && !isError && query.pagination.page === 1 && !query.pagination.hasNextPage;
const isMobile = useIsMobile();
......@@ -60,7 +62,7 @@ const TxsContent = ({ query, showBlockInfo = true, showSocketInfo = true }: Prop
{ ({ content }) => <Box>{ content }</Box> }
</TxsNewItemNotice>
) }
{ txs.map(tx => <TxsListItem tx={ tx } key={ tx.hash } showBlockInfo={ showBlockInfo }/>) }
{ txs.map(tx => <TxsListItem tx={ tx } key={ tx.hash } showBlockInfo={ showBlockInfo } currentAddress={ currentAddress }/>) }
</Box>
</Show>
<Hide below="lg" ssr={ false }>
......@@ -71,6 +73,7 @@ const TxsContent = ({ query, showBlockInfo = true, showSocketInfo = true }: Prop
showBlockInfo={ showBlockInfo }
showSocketInfo={ showSocketInfo }
top={ isPaginatorHidden ? 0 : 80 }
currentAddress={ currentAddress }
/>
</Hide>
</>
......@@ -86,6 +89,7 @@ const TxsContent = ({ query, showBlockInfo = true, showSocketInfo = true }: Prop
setSorting={ setSortByValue }
paginationProps={ query.pagination }
showPagination={ !isPaginatorHidden }
filterComponent={ filter }
/>
) }
{ content }
......
......@@ -17,18 +17,14 @@ type Props = {
paginationProps: PaginationProps;
className?: string;
showPagination?: boolean;
filterComponent?: React.ReactNode;
}
const TxsHeaderMobile = ({ sorting, setSorting, paginationProps, className, showPagination = true }: Props) => {
const TxsHeaderMobile = ({ filterComponent, sorting, setSorting, paginationProps, className, showPagination = true }: Props) => {
return (
<ActionBar className={ className }>
<HStack>
{ /* api is not implemented */ }
{ /* <TxsFilters
filters={ filters }
onFiltersChange={ setFilters }
appliedFiltersNum={ 0 }
/> */ }
{ filterComponent }
<TxsSorting
isActive={ Boolean(sorting) }
setSorting={ setSorting }
......
......@@ -24,17 +24,30 @@ import AdditionalInfoButton from 'ui/shared/AdditionalInfoButton';
import Address from 'ui/shared/address/Address';
import AddressIcon from 'ui/shared/address/AddressIcon';
import AddressLink from 'ui/shared/address/AddressLink';
import InOutTag from 'ui/shared/InOutTag';
import TxStatus from 'ui/shared/TxStatus';
import TxAdditionalInfo from 'ui/txs/TxAdditionalInfo';
import TxType from 'ui/txs/TxType';
const TxsListItem = ({ tx, showBlockInfo }: {tx: Transaction; showBlockInfo: boolean}) => {
type Props = {
tx: Transaction;
showBlockInfo: boolean;
currentAddress?: string;
}
const TAG_WIDTH = 48;
const ARROW_WIDTH = 24;
const TxsListItem = ({ tx, showBlockInfo, currentAddress }: Props) => {
const { isOpen, onOpen, onClose } = useDisclosure();
const iconColor = useColorModeValue('blue.600', 'blue.300');
const borderColor = useColorModeValue('blackAlpha.200', 'whiteAlpha.200');
const dataTo = tx.to ? tx.to : tx.created_contract;
const isOut = Boolean(currentAddress && currentAddress === tx.from.hash);
const isIn = Boolean(currentAddress && currentAddress === tx.to?.hash);
return (
<>
<Box width="100%" borderBottom="1px solid" borderColor={ borderColor } _first={{ borderTop: '1px solid', borderColor }}>
......@@ -83,7 +96,7 @@ const TxsListItem = ({ tx, showBlockInfo }: {tx: Transaction; showBlockInfo: boo
</Box>
) }
<Flex alignItems="center" height={ 6 } mt={ 6 }>
<Address width="calc((100%-40px)/2)">
<Address width={ `calc((100%-${ currentAddress ? TAG_WIDTH : ARROW_WIDTH + 8 }px)/2)` }>
<AddressIcon hash={ tx.from.hash }/>
<AddressLink
hash={ tx.from.hash }
......@@ -92,12 +105,15 @@ const TxsListItem = ({ tx, showBlockInfo }: {tx: Transaction; showBlockInfo: boo
ml={ 2 }
/>
</Address>
<Icon
as={ rightArrowIcon }
boxSize={ 6 }
mx={ 2 }
color="gray.500"
/>
{ (isIn || isOut) ?
<InOutTag isIn={ isIn } isOut={ isOut } width="48px" mr={ 2 }/> : (
<Icon
as={ rightArrowIcon }
boxSize={ 6 }
mx={ 2 }
color="gray.500"
/>
) }
<Address width="calc((100%-40px)/2)">
<AddressIcon hash={ dataTo.hash }/>
<AddressLink
......
......@@ -18,9 +18,10 @@ type Props = {
top: number;
showBlockInfo: boolean;
showSocketInfo: boolean;
currentAddress?: string;
}
const TxsTable = ({ txs, sort, sorting, top, showBlockInfo, showSocketInfo }: Props) => {
const TxsTable = ({ txs, sort, sorting, top, showBlockInfo, showSocketInfo, currentAddress }: Props) => {
return (
<Table variant="simple" minWidth="950px" size="xs">
<TheadSticky top={ top }>
......@@ -31,7 +32,7 @@ const TxsTable = ({ txs, sort, sorting, top, showBlockInfo, showSocketInfo }: Pr
<Th width="15%">Method</Th>
{ showBlockInfo && <Th width="11%">Block</Th> }
<Th width={{ xl: '128px', base: '66px' }}>From</Th>
<Th width={{ xl: '36px', base: '0' }}></Th>
<Th width={{ xl: currentAddress ? '48px' : '36px', base: '0' }}></Th>
<Th width={{ xl: '128px', base: '66px' }}>To</Th>
<Th width="18%" isNumeric>
<Link onClick={ sort('val') } display="flex" justifyContent="end">
......@@ -60,6 +61,7 @@ const TxsTable = ({ txs, sort, sorting, top, showBlockInfo, showSocketInfo }: Pr
key={ item.hash }
tx={ item }
showBlockInfo={ showBlockInfo }
currentAddress={ currentAddress }
/>
)) }
</Tbody>
......
......@@ -28,13 +28,23 @@ import Address from 'ui/shared/address/Address';
import AddressIcon from 'ui/shared/address/AddressIcon';
import AddressLink from 'ui/shared/address/AddressLink';
import CurrencyValue from 'ui/shared/CurrencyValue';
import InOutTag from 'ui/shared/InOutTag';
import TruncatedTextTooltip from 'ui/shared/TruncatedTextTooltip';
import TxStatus from 'ui/shared/TxStatus';
import TxAdditionalInfo from 'ui/txs/TxAdditionalInfo';
import TxType from './TxType';
const TxsTableItem = ({ tx, showBlockInfo }: {tx: Transaction; showBlockInfo: boolean }) => {
type Props = {
tx: Transaction;
showBlockInfo: boolean;
currentAddress?: string;
}
const TxsTableItem = ({ tx, showBlockInfo, currentAddress }: Props) => {
const isOut = Boolean(currentAddress && currentAddress === tx.from.hash);
const isIn = Boolean(currentAddress && currentAddress === tx.to?.hash);
const addressFrom = (
<Address>
<Tooltip label={ tx.from.implementation_name }>
......@@ -110,8 +120,11 @@ const TxsTableItem = ({ tx, showBlockInfo }: {tx: Transaction; showBlockInfo: bo
<Td>
{ addressFrom }
</Td>
<Td>
<Icon as={ rightArrowIcon } boxSize={ 6 } mr={ 2 } color="gray.500"/>
<Td px={ 0 }>
{ (isIn || isOut) ?
<InOutTag isIn={ isIn } isOut={ isOut } width="48px" mr={ 2 }/> :
<Icon as={ rightArrowIcon } boxSize={ 6 } mx="6px" color="gray.500"/>
}
</Td>
<Td>
{ addressTo }
......@@ -121,14 +134,17 @@ const TxsTableItem = ({ tx, showBlockInfo }: {tx: Transaction; showBlockInfo: bo
<Td colSpan={ 3 }>
<Box>
{ addressFrom }
<Icon
as={ rightArrowIcon }
boxSize={ 6 }
mt={ 2 }
mb={ 1 }
color="gray.500"
transform="rotate(90deg)"
/>
{ (isIn || isOut) ?
<InOutTag isIn={ isIn } isOut={ isOut } width="48px" my={ 2 }/> : (
<Icon
as={ rightArrowIcon }
boxSize={ 6 }
mt={ 2 }
mb={ 1 }
color="gray.500"
transform="rotate(90deg)"
/>
) }
{ addressTo }
</Box>
</Td>
......
......@@ -4,14 +4,13 @@ import {
Text,
useColorModeValue,
} from '@chakra-ui/react';
import { useMutation, useQueryClient } from '@tanstack/react-query';
import { useMutation } from '@tanstack/react-query';
import React, { useCallback, useState } from 'react';
import type { SubmitHandler, ControllerRenderProps } from 'react-hook-form';
import { useForm, Controller } from 'react-hook-form';
import type { WatchlistErrors } from 'types/api/account';
import type { TWatchlistItem } from 'types/client/account';
import { QueryKeys } from 'types/client/accountQueries';
import getErrorMessage from 'lib/getErrorMessage';
import type { ErrorType } from 'lib/hooks/useFetch';
......@@ -29,9 +28,10 @@ const NOTIFICATIONS = [ 'native', 'ERC-20', 'ERC-721' ] as const;
const TAG_MAX_LENGTH = 35;
type Props = {
data?: TWatchlistItem;
onClose: () => void;
data?: Partial<TWatchlistItem>;
onSuccess: () => Promise<void>;
setAlertVisible: (isAlertVisible: boolean) => void;
isAdd: boolean;
}
type Inputs = {
......@@ -62,12 +62,12 @@ type Checkboxes = 'notification' |
'notification_settings.ERC-721.outcoming' |
'notification_settings.ERC-721.incoming';
const AddressForm: React.FC<Props> = ({ data, onClose, setAlertVisible }) => {
const AddressForm: React.FC<Props> = ({ data, onSuccess, setAlertVisible, isAdd }) => {
const [ pending, setPending ] = useState(false);
const formBackgroundColor = useColorModeValue('white', 'gray.900');
let notificationsDefault = {} as Inputs['notification_settings'];
if (!data) {
if (!data?.notification_settings) {
NOTIFICATIONS.forEach(n => notificationsDefault[n] = { incoming: true, outcoming: true });
} else {
notificationsDefault = data.notification_settings;
......@@ -77,13 +77,12 @@ const AddressForm: React.FC<Props> = ({ data, onClose, setAlertVisible }) => {
defaultValues: {
address: data?.address_hash || '',
tag: data?.name || '',
notification: data ? data.notification_methods.email : true,
notification: data?.notification_methods ? data.notification_methods.email : true,
notification_settings: notificationsDefault,
},
mode: 'onTouched',
});
const queryClient = useQueryClient();
const fetch = useFetch();
function updateWatchlist(formData: Inputs) {
......@@ -95,7 +94,7 @@ const AddressForm: React.FC<Props> = ({ data, onClose, setAlertVisible }) => {
email: formData.notification,
},
};
if (data) {
if (!isAdd && data) {
// edit address
return fetch<TWatchlistItem, WatchlistErrors>(`/node-api/account/watchlist/${ data.id }`, { method: 'PUT', body });
......@@ -106,11 +105,9 @@ const AddressForm: React.FC<Props> = ({ data, onClose, setAlertVisible }) => {
}
const { mutate } = useMutation(updateWatchlist, {
onSuccess: () => {
queryClient.refetchQueries([ QueryKeys.watchlist ]).then(() => {
onClose();
setPending(false);
});
onSuccess: async() => {
await onSuccess();
setPending(false);
},
onError: (e: ErrorType<WatchlistErrors>) => {
setPending(false);
......@@ -193,7 +190,7 @@ const AddressForm: React.FC<Props> = ({ data, onClose, setAlertVisible }) => {
isLoading={ pending }
disabled={ !isValid || !isDirty }
>
{ data ? 'Save changes' : 'Add address' }
{ !isAdd ? 'Save changes' : 'Add address' }
</Button>
</Box>
</form>
......
......@@ -7,20 +7,22 @@ import FormModal from 'ui/shared/FormModal';
import AddressForm from './AddressForm';
type Props = {
isAdd: boolean;
isOpen: boolean;
onClose: () => void;
data?: TWatchlistItem;
onSuccess: () => Promise<void>;
data?: Partial<TWatchlistItem>;
}
const AddressModal: React.FC<Props> = ({ isOpen, onClose, data }) => {
const title = data ? 'Edit watch list address' : 'New address to watch list';
const text = !data ? 'An email notification can be sent to you when an address on your watch list sends or receives any transactions.' : '';
const AddressModal: React.FC<Props> = ({ isOpen, onClose, onSuccess, data, isAdd }) => {
const title = !isAdd ? 'Edit watch list address' : 'New address to watch list';
const text = isAdd ? 'An email notification can be sent to you when an address on your watch list sends or receives any transactions.' : '';
const [ isAlertVisible, setAlertVisible ] = useState(false);
const renderForm = useCallback(() => {
return <AddressForm data={ data } onClose={ onClose } setAlertVisible={ setAlertVisible }/>;
}, [ data, onClose ]);
return <AddressForm data={ data } onSuccess={ onSuccess } setAlertVisible={ setAlertVisible } isAdd={ isAdd }/>;
}, [ data, isAdd, onSuccess ]);
return (
<FormModal<TWatchlistItem>
......
import { Text } from '@chakra-ui/react';
import { useQueryClient } from '@tanstack/react-query';
import React, { useCallback } from 'react';
import type { TWatchlistItem, TWatchlist } from 'types/client/account';
import { QueryKeys } from 'types/client/accountQueries';
import type { TWatchlistItem } from 'types/client/account';
import useFetch from 'lib/hooks/useFetch';
import useIsMobile from 'lib/hooks/useIsMobile';
......@@ -12,11 +10,11 @@ import DeleteModal from 'ui/shared/DeleteModal';
type Props = {
isOpen: boolean;
onClose: () => void;
data: TWatchlistItem;
onSuccess: () => Promise<void>;
data: Pick<TWatchlistItem, 'address_hash' | 'id'>;
}
const DeleteAddressModal: React.FC<Props> = ({ isOpen, onClose, data }) => {
const queryClient = useQueryClient();
const DeleteAddressModal: React.FC<Props> = ({ isOpen, onClose, onSuccess, data }) => {
const isMobile = useIsMobile();
const fetch = useFetch();
......@@ -24,12 +22,6 @@ const DeleteAddressModal: React.FC<Props> = ({ isOpen, onClose, data }) => {
return fetch(`/node-api/account/watchlist/${ data?.id }`, { method: 'DELETE' });
}, [ data?.id, fetch ]);
const onSuccess = useCallback(async() => {
queryClient.setQueryData([ QueryKeys.watchlist ], (prevData: TWatchlist | undefined) => {
return prevData?.filter((item) => item.id !== data.id);
});
}, [ data, queryClient ]);
const address = data?.address_hash;
const renderModalContent = useCallback(() => {
......
......@@ -1076,734 +1076,751 @@
resolved "https://registry.yarnpkg.com/@bcoe/v8-coverage/-/v8-coverage-0.2.3.tgz#75a2e8b51cb758a7553d6804a5932d7aace75c39"
integrity sha512-0hYQ8SB4Db5zvZB4axdMHGwEaQjkZzFjQiN9LVYvIFB2nSUHW9tYpxWriPrWDASIxiaXax83REcLxuSdnGPZtw==
"@chakra-ui/accordion@2.0.12":
version "2.0.12"
resolved "https://registry.yarnpkg.com/@chakra-ui/accordion/-/accordion-2.0.12.tgz#dd260fbecb639748314f440c89052ed45006c585"
integrity sha512-O3qq8mILo1QODjCGr2xwxC5LNFakBoMzTjEgpvpIMynxWc/1RKfGuFLis3IDfpHIicXmBTK6sNiZXewmna88CQ==
dependencies:
"@chakra-ui/descendant" "3.0.9"
"@chakra-ui/icon" "3.0.9"
"@chakra-ui/react-context" "2.0.3"
"@chakra-ui/react-use-controllable-state" "2.0.3"
"@chakra-ui/react-use-merge-refs" "2.0.3"
"@chakra-ui/transition" "2.0.9"
"@chakra-ui/alert@2.0.9":
version "2.0.9"
resolved "https://registry.yarnpkg.com/@chakra-ui/alert/-/alert-2.0.9.tgz#25e88c105e095def374c9fe1e3c8a3f6fab08f6c"
integrity sha512-hFRIh6ZzQJ0sAESRym15mW/mcZE/yu4z6lFtdToBhpfSlhZLuE7gDdOTxqGkg417hY//48NiNXOCoQ2dUUuHKw==
"@chakra-ui/accordion@2.1.4":
version "2.1.4"
resolved "https://registry.yarnpkg.com/@chakra-ui/accordion/-/accordion-2.1.4.tgz#a3eca38f8e52d5a5f4b9528fb9d269dcdcb035ac"
integrity sha512-PQFW6kr+Bdru0DjKA8akC4BAz1VAJisLgo4TsJwjPO2gTS0zr99C+3bBs9uoDnjSJAf18/Q5zdXv11adA8n2XA==
dependencies:
"@chakra-ui/descendant" "3.0.11"
"@chakra-ui/icon" "3.0.13"
"@chakra-ui/react-context" "2.0.5"
"@chakra-ui/react-use-controllable-state" "2.0.6"
"@chakra-ui/react-use-merge-refs" "2.0.5"
"@chakra-ui/transition" "2.0.12"
"@chakra-ui/alert@2.0.13":
version "2.0.13"
resolved "https://registry.yarnpkg.com/@chakra-ui/alert/-/alert-2.0.13.tgz#11d48346e501988074affe12a448add1a6060296"
integrity sha512-7LqPv6EUBte4XM/Q2qBFIT5o4BC0dSlni9BHOH2BgAc5B1NF+pBAMDTUH7JNBiN7RHTV7EHAIWDziiX/NK28+Q==
dependencies:
"@chakra-ui/icon" "3.0.9"
"@chakra-ui/react-context" "2.0.3"
"@chakra-ui/spinner" "2.0.9"
"@chakra-ui/anatomy@2.0.6":
version "2.0.6"
resolved "https://registry.yarnpkg.com/@chakra-ui/anatomy/-/anatomy-2.0.6.tgz#83164841d27eaa271ffa747534519bcd323c312f"
integrity sha512-Vgop2FFdhVtX7BydjZdJWZAWy+DdXBU1IMaBppz6COaH+/7OXxoI2ec2bs17ehJyBO0M+ud3OLj5UCFQ79YsoQ==
"@chakra-ui/icon" "3.0.13"
"@chakra-ui/react-context" "2.0.5"
"@chakra-ui/spinner" "2.0.11"
"@chakra-ui/anatomy@2.0.7":
version "2.0.7"
resolved "https://registry.yarnpkg.com/@chakra-ui/anatomy/-/anatomy-2.0.7.tgz#33e60c7c4d6e5f949f6f8308249dc571f84ead1e"
integrity sha512-vzcB2gcsGCxhrKbldQQV6LnBPys4eSSsH2UA2mLsT+J3WlXw0aodZw0eE/nH7yLxe4zaQ4Gnc0KjkFW4EWNKSg==
"@chakra-ui/avatar@2.1.0":
"@chakra-ui/anatomy@2.1.0":
version "2.1.0"
resolved "https://registry.yarnpkg.com/@chakra-ui/avatar/-/avatar-2.1.0.tgz#009b4e126c58ef0183618cfbfb29f8e7e3357ee9"
integrity sha512-SRQeH6NNvIBgUc4OsO14ypvcn8I66ndw7r4piIkm+R2zqbYnrzpp1d2zNPNHkChc4xQY71/GenenYO5Fhsi2DA==
resolved "https://registry.yarnpkg.com/@chakra-ui/anatomy/-/anatomy-2.1.0.tgz#8aeb9b753f0412f262743adf68519dfa85120b3e"
integrity sha512-E3jMPGqKuGTbt7mKtc8g/MOOenw2c4wqRC1vOypyFgmC8wsewdY+DJJNENF3atXAK7p5VMBKQfZ7ipNlHnDAwA==
"@chakra-ui/avatar@2.2.1":
version "2.2.1"
resolved "https://registry.yarnpkg.com/@chakra-ui/avatar/-/avatar-2.2.1.tgz#3946d8c3b1d49dc425aa80f22d2f53661395e394"
integrity sha512-sgiogfLM8vas8QJTt7AJI4XxNXYdViCWj+xYJwyOwUN93dWKImqqx3O2ihCXoXTIqQWg1rcEgoJ5CxCg6rQaQQ==
dependencies:
"@chakra-ui/image" "2.0.10"
"@chakra-ui/react-children-utils" "2.0.1"
"@chakra-ui/react-context" "2.0.3"
"@chakra-ui/image" "2.0.12"
"@chakra-ui/react-children-utils" "2.0.4"
"@chakra-ui/react-context" "2.0.5"
"@chakra-ui/breadcrumb@2.0.9":
version "2.0.9"
resolved "https://registry.yarnpkg.com/@chakra-ui/breadcrumb/-/breadcrumb-2.0.9.tgz#357e4e2a50cdad87c0b3b59656aafa85671e6142"
integrity sha512-cc3WbxrJNRUph4v45qCdcIKJI0xECeV9VikQNIactBB+iexN4d+5P66xZABAkD8wWGmyH5KuSZcd9sFYNmC13w==
"@chakra-ui/breadcrumb@2.1.1":
version "2.1.1"
resolved "https://registry.yarnpkg.com/@chakra-ui/breadcrumb/-/breadcrumb-2.1.1.tgz#e8a682a4909cf8ee5771f7b287524df2be383b8a"
integrity sha512-OSa+F9qJ1xmF0zVxC1GU46OWbbhGf0kurHioSB729d+tRw/OMzmqrrfCJ7KVUUN8NEnTZXT5FIgokMvHGEt+Hg==
dependencies:
"@chakra-ui/react-children-utils" "2.0.1"
"@chakra-ui/react-context" "2.0.3"
"@chakra-ui/react-children-utils" "2.0.4"
"@chakra-ui/react-context" "2.0.5"
"@chakra-ui/breakpoint-utils@2.0.3":
version "2.0.3"
resolved "https://registry.yarnpkg.com/@chakra-ui/breakpoint-utils/-/breakpoint-utils-2.0.3.tgz#af7f7603f31a7d8d0166307a47e88cf5902401b4"
integrity sha512-smi41ZtaiPw4mXaCgicyAh5M45Drt20wypThP+qQUT2CQ51UFZhYlItRA2lCXKQ9QB83POcHPC/oAwIsNOAfTg==
"@chakra-ui/breakpoint-utils@2.0.5":
version "2.0.5"
resolved "https://registry.yarnpkg.com/@chakra-ui/breakpoint-utils/-/breakpoint-utils-2.0.5.tgz#55b571038b66e9f6d41633c102ea904c679dac5c"
integrity sha512-8uhrckMwoR/powlAhxiFZPM0s8vn0B2yEyEaRcwpy5NmRAJSTEotC2WkSyQl/Cjysx9scredumB5g+fBX7IqGQ==
"@chakra-ui/button@2.0.9":
version "2.0.9"
resolved "https://registry.yarnpkg.com/@chakra-ui/button/-/button-2.0.9.tgz#f005f98bb5f1d5673a244957e6b3e2396acdf395"
integrity sha512-4BuDBiBlChHW1rQ9iod9MKs87AY3IyvZQwjV3DZTU4IG0KcDDfLQf++jj4dkg9Ttu+pIWhwF42pzA40JxW1oNg==
dependencies:
"@chakra-ui/react-context" "2.0.3"
"@chakra-ui/react-use-merge-refs" "2.0.3"
"@chakra-ui/spinner" "2.0.9"
"@chakra-ui/checkbox@2.1.8":
version "2.1.8"
resolved "https://registry.yarnpkg.com/@chakra-ui/checkbox/-/checkbox-2.1.8.tgz#d04a9a65494cf22e8bcfafa161bb7185d92dc13b"
integrity sha512-HhRs3nwTFoIE/UpX4N2AZxxW39Xm/Vw01HjwP/59X60kdKs3RBXlm52cODkfUDfveyT9o5ezLhU/jRf0qA909Q==
dependencies:
"@chakra-ui/form-control" "2.0.9"
"@chakra-ui/react-context" "2.0.3"
"@chakra-ui/react-types" "2.0.3"
"@chakra-ui/react-use-callback-ref" "2.0.3"
"@chakra-ui/react-use-controllable-state" "2.0.3"
"@chakra-ui/react-use-merge-refs" "2.0.3"
"@chakra-ui/react-use-safe-layout-effect" "2.0.1"
"@chakra-ui/react-use-update-effect" "2.0.3"
"@chakra-ui/visually-hidden" "2.0.9"
"@chakra-ui/button@2.0.13":
version "2.0.13"
resolved "https://registry.yarnpkg.com/@chakra-ui/button/-/button-2.0.13.tgz#5db6aa3425a6bebc2102cd9f58e434d5508dd999"
integrity sha512-T9W/zHpHZVcbx/BMg0JIXCgRycut/eYoTYee/E+eBxyPCH45n308AsYU2bZ8TgZxUwbYNRgMp4qRL/KHUQDv5g==
dependencies:
"@chakra-ui/react-context" "2.0.5"
"@chakra-ui/react-use-merge-refs" "2.0.5"
"@chakra-ui/spinner" "2.0.11"
"@chakra-ui/card@2.1.2":
version "2.1.2"
resolved "https://registry.yarnpkg.com/@chakra-ui/card/-/card-2.1.2.tgz#6a8b58d365557fd33da8e257e207fd8ba3640919"
integrity sha512-zLfrLe7NP14xWgzS+vVz07yapZNOTm3W0Gh9RCwwoQz7l4FNFNjGPmbktQGGp3fLqDUk9n9ugdFCNmxEcQ8wXA==
"@chakra-ui/checkbox@2.2.5":
version "2.2.5"
resolved "https://registry.yarnpkg.com/@chakra-ui/checkbox/-/checkbox-2.2.5.tgz#ce1c409647d11bf947ff0316bc397bc7cf25316c"
integrity sha512-7fNH+Q2nB2uMSnYAPtYxnuwZ1MOJqblZHa/ScfZ/fjiPDyEae1m068ZP/l9yJ5zlawYMTkp83m/JVcu5QFYurA==
dependencies:
"@chakra-ui/form-control" "2.0.13"
"@chakra-ui/react-context" "2.0.5"
"@chakra-ui/react-types" "2.0.5"
"@chakra-ui/react-use-callback-ref" "2.0.5"
"@chakra-ui/react-use-controllable-state" "2.0.6"
"@chakra-ui/react-use-merge-refs" "2.0.5"
"@chakra-ui/react-use-safe-layout-effect" "2.0.3"
"@chakra-ui/react-use-update-effect" "2.0.5"
"@chakra-ui/visually-hidden" "2.0.13"
"@zag-js/focus-visible" "0.1.0"
"@chakra-ui/clickable@2.0.9":
version "2.0.9"
resolved "https://registry.yarnpkg.com/@chakra-ui/clickable/-/clickable-2.0.9.tgz#c06486d36f4a4cb517ea75176e05021dfde117cd"
integrity sha512-tGXYM6M6I954fif98QkNu5M76oBZmksCTj2mILOan9/BSimpFpu06aPGX3ZIkNsz300nIObn0FdtMvKpIEQueA==
"@chakra-ui/clickable@2.0.11":
version "2.0.11"
resolved "https://registry.yarnpkg.com/@chakra-ui/clickable/-/clickable-2.0.11.tgz#d0afcdb40ed1b1ceeabb4ac3e9f2f51fd3cbdac7"
integrity sha512-5Y2dl5cxNgOxHbjxyxsL6Vdze4wUUvwsMCCW3kXwgz2OUI2y5UsBZNcvhNJx3RchJEd0fylMKiKoKmnZMHN2aw==
dependencies:
"@chakra-ui/react-use-merge-refs" "2.0.3"
"@chakra-ui/react-use-merge-refs" "2.0.5"
"@chakra-ui/close-button@2.0.9":
version "2.0.9"
resolved "https://registry.yarnpkg.com/@chakra-ui/close-button/-/close-button-2.0.9.tgz#dab2d66c7a240c4d3d150e370980709336a4a266"
integrity sha512-0RI/zLR+/mycGbYCCwDAc9hAVG7IIVmdikmo1ET7+rYip4TN94aWR0hA4dYtWqqghG1oW/pYQ9Yja6fEY90V5w==
"@chakra-ui/close-button@2.0.13":
version "2.0.13"
resolved "https://registry.yarnpkg.com/@chakra-ui/close-button/-/close-button-2.0.13.tgz#c549d682c66f3e08b1f37e98a83ebe0421846496"
integrity sha512-ZI/3p84FPlW0xoDCZYqsnIvR6bTc2d/TlhwyTHsDDxq9ZOWp9c2JicVn6WTdWGdshk8itnZZdG50IcnizGnimA==
dependencies:
"@chakra-ui/icon" "3.0.9"
"@chakra-ui/icon" "3.0.13"
"@chakra-ui/color-mode@2.1.7":
version "2.1.7"
resolved "https://registry.yarnpkg.com/@chakra-ui/color-mode/-/color-mode-2.1.7.tgz#91c02e82e551c5448081e4934efeddb10bb732c5"
integrity sha512-GAoKJzVRQeuEfCa2i0BZdMwxuOoaGknU3+5wgvLuaSpwlov4OyqpjKMRdSdpjr4IFiqqHK47dsr3H4LQsbO+9w==
"@chakra-ui/color-mode@2.1.10":
version "2.1.10"
resolved "https://registry.yarnpkg.com/@chakra-ui/color-mode/-/color-mode-2.1.10.tgz#8d446550af80cf01a2ccd7470861cb0180112049"
integrity sha512-aUPouOUPn7IPm1v00/9AIkRuNrkCwJlbjVL1kJzLzxijYjbHvEHPxntITt+JWjtXPT8xdOq6mexLYCOGA67JwQ==
dependencies:
"@chakra-ui/react-use-safe-layout-effect" "2.0.1"
"@chakra-ui/react-use-safe-layout-effect" "2.0.3"
"@chakra-ui/control-box@2.0.9":
version "2.0.9"
resolved "https://registry.yarnpkg.com/@chakra-ui/control-box/-/control-box-2.0.9.tgz#b3cd98ceb1ce683c00445ab7469e65ba3d90c3cc"
integrity sha512-/viS9OBah1wCLNZbgfwkoQOnVRUYgp8Gypjqk9QNQwnNdFUTEgWc1RWN+1RYO85esJzHLkA2hZFIrYu1TZeZ6g==
"@chakra-ui/control-box@2.0.11":
version "2.0.11"
resolved "https://registry.yarnpkg.com/@chakra-ui/control-box/-/control-box-2.0.11.tgz#b2deec368fc83f6675964785f823e4c0c1f5d4ac"
integrity sha512-UJb4vqq+/FPuwTCuaPeHa2lwtk6u7eFvLuwDCST2e/sBWGJC1R+1/Il5pHccnWs09FWxyZ9v/Oxkg/CG3jZR4Q==
"@chakra-ui/counter@2.0.9":
version "2.0.9"
resolved "https://registry.yarnpkg.com/@chakra-ui/counter/-/counter-2.0.9.tgz#b1b7c74c4e5d1ac506d699d93da57d535370a702"
integrity sha512-LuqtpyxCOZM19gAmV0vtVeaFd9ccPmEjoGJQ0NoO8CFheltgLC/7m/8YpDbgWiG4+BAkTUfIG+5nLg5hwvvQxw==
"@chakra-ui/counter@2.0.11":
version "2.0.11"
resolved "https://registry.yarnpkg.com/@chakra-ui/counter/-/counter-2.0.11.tgz#b49aa76423e5f4a4a8e717750c190fa5050a3dca"
integrity sha512-1YRt/jom+m3iWw9J9trcM6rAHDvD4lwThiO9raxUK7BRsYUhnPZvsMpcXU1Moax218C4rRpbI9KfPLaig0m1xQ==
dependencies:
"@chakra-ui/number-utils" "2.0.3"
"@chakra-ui/react-use-callback-ref" "2.0.3"
"@chakra-ui/number-utils" "2.0.5"
"@chakra-ui/react-use-callback-ref" "2.0.5"
"@chakra-ui/css-reset@2.0.7":
version "2.0.7"
resolved "https://registry.yarnpkg.com/@chakra-ui/css-reset/-/css-reset-2.0.7.tgz#28f4284c36230e30892dc3f2de2464aaacc4f623"
integrity sha512-ztGdFQ6U1hX2k6a3HZ8D3A/dZWVxlGe2F5mvUrRU554mFWBYmsq0ydZ7UBEPlykv9NoCz4nN8VCkIxcKJ3p29Q==
"@chakra-ui/css-reset@2.0.10":
version "2.0.10"
resolved "https://registry.yarnpkg.com/@chakra-ui/css-reset/-/css-reset-2.0.10.tgz#cb6cd97ee38f8069789f08c31a828bf3a7e339ea"
integrity sha512-FwHOfw2P4ckbpSahDZef2KoxcvHPUg09jlicWdp24/MjdsOO5PAB/apm2UBvQflY4WAJyOqYaOdnXFlR6nF4cQ==
"@chakra-ui/descendant@3.0.9":
version "3.0.9"
resolved "https://registry.yarnpkg.com/@chakra-ui/descendant/-/descendant-3.0.9.tgz#6574a1ce00067c49a070c5b005f8f1ca399006ea"
integrity sha512-30E5yMWvxgBx43PoI/67r9h9OhbpDfLb/MLOCjtEwebSbD0V5+fmnmCoUELScQbhozQVjA9t195X6UP0VQWj8w==
"@chakra-ui/descendant@3.0.11":
version "3.0.11"
resolved "https://registry.yarnpkg.com/@chakra-ui/descendant/-/descendant-3.0.11.tgz#cb8bca7b6e8915afc58cdb1444530a2d1b03efd3"
integrity sha512-sNLI6NS6uUgrvYS6Imhoc1YlI6bck6pfxMBJcnXVSfdIjD6XjCmeY2YgzrtDS+o+J8bB3YJeIAG/vsVy5USE5Q==
dependencies:
"@chakra-ui/react-context" "2.0.3"
"@chakra-ui/react-use-merge-refs" "2.0.3"
"@chakra-ui/react-context" "2.0.5"
"@chakra-ui/react-use-merge-refs" "2.0.5"
"@chakra-ui/dom-utils@2.0.1":
version "2.0.1"
resolved "https://registry.yarnpkg.com/@chakra-ui/dom-utils/-/dom-utils-2.0.1.tgz#3061819ac365f5947423d63a5fcc26a281bbb5c2"
integrity sha512-sbob9AHQq1+KIQ3XKslafislwtC8pYcpwM0S1SLzgyZumHRwhDimKwdi4MtRQfOCenub0E3diRjp4RpGRL0JuQ==
"@chakra-ui/editable@2.0.9":
version "2.0.9"
resolved "https://registry.yarnpkg.com/@chakra-ui/editable/-/editable-2.0.9.tgz#a31d2e1c176c0817574e98a8567314c1a74444ab"
integrity sha512-s5F3UMR09s6ga3eVhw0UBMGmegtxg6jCp29VLqaEwP5BuWIEOjcJz358gTlnFr3dhvb31e3rcr+B1XiYv4wxqg==
dependencies:
"@chakra-ui/react-context" "2.0.3"
"@chakra-ui/react-types" "2.0.3"
"@chakra-ui/react-use-controllable-state" "2.0.3"
"@chakra-ui/react-use-focus-on-pointer-down" "2.0.1"
"@chakra-ui/react-use-merge-refs" "2.0.3"
"@chakra-ui/react-use-safe-layout-effect" "2.0.1"
"@chakra-ui/react-use-update-effect" "2.0.3"
"@chakra-ui/shared-utils" "2.0.1"
"@chakra-ui/event-utils@2.0.4":
"@chakra-ui/dom-utils@2.0.4":
version "2.0.4"
resolved "https://registry.yarnpkg.com/@chakra-ui/event-utils/-/event-utils-2.0.4.tgz#eeb3eb4f37c3828955dbbc182ea43a8a3238a599"
integrity sha512-J2YgAM5Dw9hMkwfMsWhsiAG848GfTMxNclUIUcgV9RQhLEs0eTFhelzNiKVOMA3vBxlT6lOARuRun/ESiFZgGg==
resolved "https://registry.yarnpkg.com/@chakra-ui/dom-utils/-/dom-utils-2.0.4.tgz#367fffecbd287e16836e093d4030dc6e3785d402"
integrity sha512-P936+WKinz5fgHzfwiUQjE/t7NC8bU89Tceim4tbn8CIm/9b+CsHX64eNw4vyJqRwt78TXQK7aGBIbS18R0q5Q==
"@chakra-ui/focus-lock@2.0.10":
version "2.0.10"
resolved "https://registry.yarnpkg.com/@chakra-ui/focus-lock/-/focus-lock-2.0.10.tgz#8f17d212786bd2977afc07f72b520aeae30b9434"
integrity sha512-LeRZYzwfJp0eq84oO8e1pC2qC8v8fJw/P4nYDrCDjuJU753DV6nVjp5MKMRqbkp+6IAElPc+ojy/sp2a9GCocw==
"@chakra-ui/editable@2.0.16":
version "2.0.16"
resolved "https://registry.yarnpkg.com/@chakra-ui/editable/-/editable-2.0.16.tgz#8a45ff26d77f06841ea986484d71ede312b4c22e"
integrity sha512-kIFPufzIlViNv7qi2PxxWWBvjLb+3IP5hUGmqOA9qcYz5TAdqblQqDClm0iajlIDNUFWnS4h056o8jKsQ42a5A==
dependencies:
"@chakra-ui/react-context" "2.0.5"
"@chakra-ui/react-types" "2.0.5"
"@chakra-ui/react-use-callback-ref" "2.0.5"
"@chakra-ui/react-use-controllable-state" "2.0.6"
"@chakra-ui/react-use-focus-on-pointer-down" "2.0.4"
"@chakra-ui/react-use-merge-refs" "2.0.5"
"@chakra-ui/react-use-safe-layout-effect" "2.0.3"
"@chakra-ui/react-use-update-effect" "2.0.5"
"@chakra-ui/shared-utils" "2.0.3"
"@chakra-ui/event-utils@2.0.6":
version "2.0.6"
resolved "https://registry.yarnpkg.com/@chakra-ui/event-utils/-/event-utils-2.0.6.tgz#5e04d68ea070ef52ce212c2a99be9afcc015cfaf"
integrity sha512-ZIoqUbgJ5TcCbZRchMv4n7rOl1JL04doMebED88LO5mux36iVP9er/nnOY4Oke1bANKKURMrQf5VTT9hoYeA7A==
"@chakra-ui/focus-lock@2.0.13":
version "2.0.13"
resolved "https://registry.yarnpkg.com/@chakra-ui/focus-lock/-/focus-lock-2.0.13.tgz#19d6ca35555965a9aaa241b991a67bbc875ee53d"
integrity sha512-AVSJt+3Ukia/m9TCZZgyWvTY7pw88jArivWVJ2gySGYYIs6z/FJMnlwbCVldV2afS0g3cYaii7aARb/WrlG34Q==
dependencies:
"@chakra-ui/dom-utils" "2.0.1"
"@chakra-ui/dom-utils" "2.0.4"
react-focus-lock "^2.9.1"
"@chakra-ui/form-control@2.0.9":
version "2.0.9"
resolved "https://registry.yarnpkg.com/@chakra-ui/form-control/-/form-control-2.0.9.tgz#10678857e6586e7d1be0a34c8d045c6484c1180b"
integrity sha512-P8Tr45z/XSAa1m6uAma0eKf1h7Ltg2sLj2jK5YhaXJER9VUUY18iGe96D4JrAXlgEWDhTyWMb63nB+eYO1tKtw==
"@chakra-ui/form-control@2.0.13":
version "2.0.13"
resolved "https://registry.yarnpkg.com/@chakra-ui/form-control/-/form-control-2.0.13.tgz#51831f981a2e937b0258b4fd2dd4ceacda03c01a"
integrity sha512-J964JlgrxP+LP3kYmLk1ttbl73u6ghT+JQDjEjkEUc8lSS9Iv4u9XkRDQHuz2t2y0KHjQdH12PUfUfBqcITbYw==
dependencies:
"@chakra-ui/icon" "3.0.9"
"@chakra-ui/react-context" "2.0.3"
"@chakra-ui/react-types" "2.0.3"
"@chakra-ui/react-use-merge-refs" "2.0.3"
"@chakra-ui/icon" "3.0.13"
"@chakra-ui/react-context" "2.0.5"
"@chakra-ui/react-types" "2.0.5"
"@chakra-ui/react-use-merge-refs" "2.0.5"
"@chakra-ui/hooks@2.0.9":
version "2.0.9"
resolved "https://registry.yarnpkg.com/@chakra-ui/hooks/-/hooks-2.0.9.tgz#1f6d6157968a60dd9678112763d8f5fd3bdeffad"
integrity sha512-0JRgEPtsBaXr9nQW1xEKlWGA7WwFbLNqac7fQXp9zQvoHOWTfNJkK/NJaVBvyFPgfTLxy37WKHooVSwNG/Lwmg==
"@chakra-ui/hooks@2.1.2":
version "2.1.2"
resolved "https://registry.yarnpkg.com/@chakra-ui/hooks/-/hooks-2.1.2.tgz#1e413f6624e97b854569e8a19846c9162a4ec153"
integrity sha512-/vDBOqqnho9q++lay0ZcvnH8VuE0wT2OkZj+qDwFwjiHAtGPVxHCSpu9KC8BIHME5TlWjyO6riVyUCb2e2ip6w==
dependencies:
"@chakra-ui/react-utils" "2.0.6"
"@chakra-ui/utils" "2.0.9"
"@chakra-ui/react-utils" "2.0.9"
"@chakra-ui/utils" "2.0.12"
compute-scroll-into-view "1.0.14"
copy-to-clipboard "3.3.1"
"@chakra-ui/icon@3.0.9":
version "3.0.9"
resolved "https://registry.yarnpkg.com/@chakra-ui/icon/-/icon-3.0.9.tgz#ba127d9eefd727f62e9bce07a23eca39ae506744"
integrity sha512-P2Pwm/za6m1W1oqL2kGHH6XrrymsBjqYAFwOW2lB5Q6mI1e+RYe/iMxDoPSLHMYhqdfH7vyib/ffE3Vv3a5oTA==
"@chakra-ui/icon@3.0.13":
version "3.0.13"
resolved "https://registry.yarnpkg.com/@chakra-ui/icon/-/icon-3.0.13.tgz#1782f8bd81946eabb39d4dde9eccebba1e5571ba"
integrity sha512-RaDLC4psd8qyInY2RX4AlYRfpLBNw3VsMih17BFf8EESVhBXNJcYy7Q9eMV/K4NvZfZT42vuVqGVNFmkG89lBQ==
dependencies:
"@chakra-ui/shared-utils" "2.0.1"
"@chakra-ui/shared-utils" "2.0.3"
"@chakra-ui/image@2.0.10":
version "2.0.10"
resolved "https://registry.yarnpkg.com/@chakra-ui/image/-/image-2.0.10.tgz#712c0e1c579d959225bd8316d8d8f66cbeb95bb8"
integrity sha512-Atc1bdog4V5xv7IbpF2F2UkKWfgG/TD74cIac09JuSpQcYyh7lrJ7iVvhTkeP+LDdCs+QCD7SnTUM4Y0ZlaHbA==
"@chakra-ui/image@2.0.12":
version "2.0.12"
resolved "https://registry.yarnpkg.com/@chakra-ui/image/-/image-2.0.12.tgz#e90b1d2a5f87fff90b1ef86ca75bfe6b44ac545d"
integrity sha512-uclFhs0+wq2qujGu8Wk4eEWITA3iZZQTitGiFSEkO9Ws5VUH+Gqtn3mUilH0orubrI5srJsXAmjVTuVwge1KJQ==
dependencies:
"@chakra-ui/react-use-safe-layout-effect" "2.0.1"
"@chakra-ui/react-use-safe-layout-effect" "2.0.3"
"@chakra-ui/input@2.0.9":
version "2.0.9"
resolved "https://registry.yarnpkg.com/@chakra-ui/input/-/input-2.0.9.tgz#905b82ed647a20080a25a6a7e6740e3bb65586c1"
integrity sha512-6MKydxTyF7JV7PtQHircQ5HBTd6Ik9Vn7p8fCLeAieT0TK8UQTxMWZVPminS7TRWMutrq8W99DcQOBlMz0cKrw==
dependencies:
"@chakra-ui/form-control" "2.0.9"
"@chakra-ui/object-utils" "2.0.3"
"@chakra-ui/react-children-utils" "2.0.1"
"@chakra-ui/react-context" "2.0.3"
"@chakra-ui/shared-utils" "2.0.1"
"@chakra-ui/layout@2.1.6":
version "2.1.6"
resolved "https://registry.yarnpkg.com/@chakra-ui/layout/-/layout-2.1.6.tgz#3dfdd8b3f08d9ff34fc923d44ebe4bc86291b889"
integrity sha512-QDNaVu44UI46c+YlSF1KrzJkiwua0UtRXNTnR3jBE1uzcuqRow7xgr3E60dLphY2cPFqAljfQZUNlP3sgvCLww==
dependencies:
"@chakra-ui/breakpoint-utils" "2.0.3"
"@chakra-ui/icon" "3.0.9"
"@chakra-ui/object-utils" "2.0.3"
"@chakra-ui/react-children-utils" "2.0.1"
"@chakra-ui/react-context" "2.0.3"
"@chakra-ui/shared-utils" "2.0.1"
"@chakra-ui/lazy-utils@2.0.1":
version "2.0.1"
resolved "https://registry.yarnpkg.com/@chakra-ui/lazy-utils/-/lazy-utils-2.0.1.tgz#6814836552028fa0823563ce3d39d22bccb203e1"
integrity sha512-986YjYq+hEzHDLZiqYlYbdqfiKdC3h2g896Eoe5K2UXtAVxqZI3UOnMH781X6N1R7rGJWquskzG681qFigW/BA==
"@chakra-ui/input@2.0.14":
version "2.0.14"
resolved "https://registry.yarnpkg.com/@chakra-ui/input/-/input-2.0.14.tgz#eec3d04834ab1ac7dab344e9bf14d779c4f4da31"
integrity sha512-CkSrUJeKWogOSt2pUf2vVv5s0bUVcAi4/XGj1JVCCfyIX6a6h1m8R69MShTPxPiQ0Mdebq5ATrW/aZQQXZzRGQ==
dependencies:
"@chakra-ui/form-control" "2.0.13"
"@chakra-ui/object-utils" "2.0.5"
"@chakra-ui/react-children-utils" "2.0.4"
"@chakra-ui/react-context" "2.0.5"
"@chakra-ui/shared-utils" "2.0.3"
"@chakra-ui/layout@2.1.11":
version "2.1.11"
resolved "https://registry.yarnpkg.com/@chakra-ui/layout/-/layout-2.1.11.tgz#6b0005dd897a901f2fded99c19fe47f60db80cb3"
integrity sha512-UP19V8EeI/DEODbWrZlqC0sg248bpFaWpMiM/+g9Bsxs9aof3yexpMD/7gb0yrfbIrkdvSBrcQeqxXGzbfoopw==
dependencies:
"@chakra-ui/breakpoint-utils" "2.0.5"
"@chakra-ui/icon" "3.0.13"
"@chakra-ui/object-utils" "2.0.5"
"@chakra-ui/react-children-utils" "2.0.4"
"@chakra-ui/react-context" "2.0.5"
"@chakra-ui/shared-utils" "2.0.3"
"@chakra-ui/lazy-utils@2.0.3":
version "2.0.3"
resolved "https://registry.yarnpkg.com/@chakra-ui/lazy-utils/-/lazy-utils-2.0.3.tgz#5ba459a2541ad0c98cd98b20a8054664c129e9b4"
integrity sha512-SQ5I5rJrcHpVUcEftHLOh8UyeY+06R8Gv3k2RjcpvM6mb2Gktlz/4xl2GcUh3LWydgGQDW/7Rse5rQhKWgzmcg==
"@chakra-ui/live-region@2.0.9":
version "2.0.9"
resolved "https://registry.yarnpkg.com/@chakra-ui/live-region/-/live-region-2.0.9.tgz#f26cf1b96df51515cd3a0897f9516f8b5f6bbfec"
integrity sha512-ilbo/C5wcUoSHDU5owFPQP3KsabPYGzDEbwV+Z76BlyNdFN2PD0j13RGEH+sBNNZ3HzLyyuuc1YmkVcJi7ycQg==
"@chakra-ui/live-region@2.0.11":
version "2.0.11"
resolved "https://registry.yarnpkg.com/@chakra-ui/live-region/-/live-region-2.0.11.tgz#1008c5b629aa4120e5158be53f13d8d34bc2d71a"
integrity sha512-ltObaKQekP75GCCbN+vt1/mGABSCaRdQELmotHTBc5AioA3iyCDHH69ev+frzEwLvKFqo+RomAdAAgqBIMJ02Q==
"@chakra-ui/media-query@3.2.5":
version "3.2.5"
resolved "https://registry.yarnpkg.com/@chakra-ui/media-query/-/media-query-3.2.5.tgz#c0b9dc4bc6245d9abddcbe17693e40bf5dfe34f8"
integrity sha512-V+Dngi/r7u/uj7JhsZerM1RI597Oo4wED2ojNfclnnEVb/IoqktiuFy6RQgbo3HmE7M/E5B1i4yYzt7tQJhXlg==
"@chakra-ui/media-query@3.2.8":
version "3.2.8"
resolved "https://registry.yarnpkg.com/@chakra-ui/media-query/-/media-query-3.2.8.tgz#7d5feccb7ac52891426c060dd2ed1df37420956d"
integrity sha512-djmEg/eJ5Qrjn7SArTqjsvlwF6mNeMuiawrTwnU+0EKq9Pq/wVSb7VaIhxdQYJLA/DbRhE/KPMogw1LNVKa4Rw==
dependencies:
"@chakra-ui/breakpoint-utils" "2.0.3"
"@chakra-ui/react-env" "2.0.9"
"@chakra-ui/breakpoint-utils" "2.0.5"
"@chakra-ui/react-env" "2.0.11"
"@chakra-ui/menu@2.0.13":
version "2.0.13"
resolved "https://registry.yarnpkg.com/@chakra-ui/menu/-/menu-2.0.13.tgz#3ac5f448efc894045769c606ebe1376051556be9"
integrity sha512-XZYoq9k/txAELUgn5OokyxfXEpVZwBueVYXiT9ji0XvMuzXVxeHd40klJEkiJUctNsOahZf10t5yxlT4B00pwA==
dependencies:
"@chakra-ui/clickable" "2.0.9"
"@chakra-ui/descendant" "3.0.9"
"@chakra-ui/lazy-utils" "2.0.1"
"@chakra-ui/popper" "3.0.7"
"@chakra-ui/react-children-utils" "2.0.1"
"@chakra-ui/react-context" "2.0.3"
"@chakra-ui/react-use-animation-state" "2.0.3"
"@chakra-ui/react-use-controllable-state" "2.0.3"
"@chakra-ui/react-use-disclosure" "2.0.3"
"@chakra-ui/react-use-focus-effect" "2.0.3"
"@chakra-ui/react-use-merge-refs" "2.0.3"
"@chakra-ui/react-use-outside-click" "2.0.3"
"@chakra-ui/react-use-update-effect" "2.0.3"
"@chakra-ui/transition" "2.0.9"
"@chakra-ui/modal@2.1.7":
version "2.1.7"
resolved "https://registry.yarnpkg.com/@chakra-ui/modal/-/modal-2.1.7.tgz#dba55bddd407689f4c2bba886b03a5355578a20d"
integrity sha512-A+CbvhQYpmLH3SrqJ1wJysUCGm0mNoSDxRjP4wX98j56nMTDAsMYlzttpuLmKaSzvbJ7uEQDLtQV8lZjB0gUuw==
dependencies:
"@chakra-ui/close-button" "2.0.9"
"@chakra-ui/focus-lock" "2.0.10"
"@chakra-ui/portal" "2.0.9"
"@chakra-ui/react-context" "2.0.3"
"@chakra-ui/react-types" "2.0.3"
"@chakra-ui/react-use-merge-refs" "2.0.3"
"@chakra-ui/transition" "2.0.9"
"@chakra-ui/menu@2.1.5":
version "2.1.5"
resolved "https://registry.yarnpkg.com/@chakra-ui/menu/-/menu-2.1.5.tgz#73b6db93d6dec9d04ab7c2630a7984e3180fd769"
integrity sha512-2UusrQtxHcqcO9n/0YobNN3RJC8yAZU6oJbRPuvsQ9IL89scEWCTIxXEYrnIjeh/5zikcSEDGo9zM9Udg/XcsA==
dependencies:
"@chakra-ui/clickable" "2.0.11"
"@chakra-ui/descendant" "3.0.11"
"@chakra-ui/lazy-utils" "2.0.3"
"@chakra-ui/popper" "3.0.10"
"@chakra-ui/react-children-utils" "2.0.4"
"@chakra-ui/react-context" "2.0.5"
"@chakra-ui/react-use-animation-state" "2.0.6"
"@chakra-ui/react-use-controllable-state" "2.0.6"
"@chakra-ui/react-use-disclosure" "2.0.6"
"@chakra-ui/react-use-focus-effect" "2.0.7"
"@chakra-ui/react-use-merge-refs" "2.0.5"
"@chakra-ui/react-use-outside-click" "2.0.5"
"@chakra-ui/react-use-update-effect" "2.0.5"
"@chakra-ui/transition" "2.0.12"
"@chakra-ui/modal@2.2.5":
version "2.2.5"
resolved "https://registry.yarnpkg.com/@chakra-ui/modal/-/modal-2.2.5.tgz#adcbf06c29d853b5fa17fec3a91b8096eecffe6b"
integrity sha512-QIoN89bT5wnR71wxZFHt7vsS65yF9WCfIwDtFk8ifxJORPi/UkLMwBpjTV2Jfsxd22W6Oo2VOpRR0a5WFeK+jA==
dependencies:
"@chakra-ui/close-button" "2.0.13"
"@chakra-ui/focus-lock" "2.0.13"
"@chakra-ui/portal" "2.0.12"
"@chakra-ui/react-context" "2.0.5"
"@chakra-ui/react-types" "2.0.5"
"@chakra-ui/react-use-merge-refs" "2.0.5"
"@chakra-ui/transition" "2.0.12"
aria-hidden "^1.1.1"
react-remove-scroll "^2.5.4"
"@chakra-ui/number-input@2.0.9":
version "2.0.9"
resolved "https://registry.yarnpkg.com/@chakra-ui/number-input/-/number-input-2.0.9.tgz#c5ebfa0311f7586fb4c1e5f4284355b1d1f04383"
integrity sha512-RsDzoNvSBZMgyXjN543AtQ2v99U1p/0xnGWZy4NCkgCDWMBn3kIXqSzQq5CB9Ot0MD8nnKF5VYdVdXWguXExEQ==
dependencies:
"@chakra-ui/counter" "2.0.9"
"@chakra-ui/form-control" "2.0.9"
"@chakra-ui/icon" "3.0.9"
"@chakra-ui/react-context" "2.0.3"
"@chakra-ui/react-types" "2.0.3"
"@chakra-ui/react-use-callback-ref" "2.0.3"
"@chakra-ui/react-use-event-listener" "2.0.3"
"@chakra-ui/react-use-interval" "2.0.1"
"@chakra-ui/react-use-merge-refs" "2.0.3"
"@chakra-ui/react-use-safe-layout-effect" "2.0.1"
"@chakra-ui/react-use-update-effect" "2.0.3"
"@chakra-ui/number-utils@2.0.3":
version "2.0.3"
resolved "https://registry.yarnpkg.com/@chakra-ui/number-utils/-/number-utils-2.0.3.tgz#2cf1190647ac5a17c90baaf8176226a98eb3bfff"
integrity sha512-oN03kYAUCCp/FNtpLr5mh+cvd/sRTzZWTBoFydmxc955psXq/X950gzs6o5kzoeFCpgXaxMmHAXQm3ReEK2NsQ==
"@chakra-ui/object-utils@2.0.3":
version "2.0.3"
resolved "https://registry.yarnpkg.com/@chakra-ui/object-utils/-/object-utils-2.0.3.tgz#0bc8d1c7c452fe1ce8fcda439336e0392e867d7e"
integrity sha512-36prckrqTynVD/JTzyCr8OCWVOrMs/awZo3djVbIiNxRIcJ5iEwUVy26h3MWN4ENSopipBtxNfAwPNTLU5Si/g==
"@chakra-ui/number-input@2.0.14":
version "2.0.14"
resolved "https://registry.yarnpkg.com/@chakra-ui/number-input/-/number-input-2.0.14.tgz#e6336228b9210f9543fe440bfc6478537810d59c"
integrity sha512-IARUAbP4pn1gP5fY2dK4wtbR3ONjzHgTjH4Zj3ErZvdu/yTURLaZmlb6UGHwgqjWLyioactZ/+n4Njj5CRjs8w==
dependencies:
"@chakra-ui/counter" "2.0.11"
"@chakra-ui/form-control" "2.0.13"
"@chakra-ui/icon" "3.0.13"
"@chakra-ui/react-context" "2.0.5"
"@chakra-ui/react-types" "2.0.5"
"@chakra-ui/react-use-callback-ref" "2.0.5"
"@chakra-ui/react-use-event-listener" "2.0.5"
"@chakra-ui/react-use-interval" "2.0.3"
"@chakra-ui/react-use-merge-refs" "2.0.5"
"@chakra-ui/react-use-safe-layout-effect" "2.0.3"
"@chakra-ui/react-use-update-effect" "2.0.5"
"@chakra-ui/number-utils@2.0.5":
version "2.0.5"
resolved "https://registry.yarnpkg.com/@chakra-ui/number-utils/-/number-utils-2.0.5.tgz#c7595fc919fca7c43fe172bfd6c5197c653ee572"
integrity sha512-Thhohnlqze0i5HBJO9xkfOPq1rv3ji/hNPf2xh1fh4hxrNzdm3HCkz0c6lyRQwGuVoeltEHysYZLH/uWLFTCSQ==
"@chakra-ui/pin-input@2.0.12":
version "2.0.12"
resolved "https://registry.yarnpkg.com/@chakra-ui/pin-input/-/pin-input-2.0.12.tgz#1ff927fdc61433a7b9b4421ceafad5674299e91a"
integrity sha512-gaMRp5AFW+qAJCUj93V1WluuYBBZ/5A3Wy5q796g8Auvw7vufgkVtl6EBznwvtynZN8gJwbRFpMtJxQyXCkUiw==
dependencies:
"@chakra-ui/descendant" "3.0.9"
"@chakra-ui/react-children-utils" "2.0.1"
"@chakra-ui/react-context" "2.0.3"
"@chakra-ui/react-use-controllable-state" "2.0.3"
"@chakra-ui/react-use-merge-refs" "2.0.3"
"@chakra-ui/object-utils@2.0.5":
version "2.0.5"
resolved "https://registry.yarnpkg.com/@chakra-ui/object-utils/-/object-utils-2.0.5.tgz#231602066ddb96ae91dcc7da243b97ad46972398"
integrity sha512-/rIMoYI3c2uLtFIrnTFOPRAI8StUuu335WszqKM0KAW1lwG9H6uSbxqlpZT1Pxi/VQqZKfheGiMQOx5lfTmM/A==
"@chakra-ui/popover@2.0.9":
version "2.0.9"
resolved "https://registry.yarnpkg.com/@chakra-ui/popover/-/popover-2.0.9.tgz#7f2df4cbbc3eee7440c311750e18ba00150f973f"
integrity sha512-+7tH4RVuheFQOyAZ5KT9x+qsLvz7rGuKaHtb0427+5bhUzLaSAghtr/afzOKHDwUVBwF2tTUNanR23ipW1fXDg==
dependencies:
"@chakra-ui/close-button" "2.0.9"
"@chakra-ui/hooks" "2.0.9"
"@chakra-ui/lazy-utils" "2.0.1"
"@chakra-ui/popper" "3.0.7"
"@chakra-ui/react-context" "2.0.3"
"@chakra-ui/react-types" "2.0.3"
"@chakra-ui/react-use-disclosure" "2.0.3"
"@chakra-ui/react-use-merge-refs" "2.0.3"
"@chakra-ui/popper@3.0.7":
version "3.0.7"
resolved "https://registry.yarnpkg.com/@chakra-ui/popper/-/popper-3.0.7.tgz#af3428bf5d64ad9372210a70181f69a9d79eefb2"
integrity sha512-xLYhuNsk1gOjymtek1ZdZlG21hmg2a7Iu2KsD9Hi7+aUxc2K5/XxX+/vyjjz8u4s0gmj83pTqnauQRynb/TCXA==
dependencies:
"@chakra-ui/react-types" "2.0.3"
"@chakra-ui/react-use-merge-refs" "2.0.3"
"@chakra-ui/pin-input@2.0.16":
version "2.0.16"
resolved "https://registry.yarnpkg.com/@chakra-ui/pin-input/-/pin-input-2.0.16.tgz#d31a6e2bce85aa2d1351ccb4cd9bf7a5134d3fb9"
integrity sha512-51cioNYpBSgi9/jq6CrzoDvo8fpMwFXu3SaFRbKO47s9Dz/OAW0MpjyabTfSpwOv0xKZE+ayrYGJopCzZSWXPg==
dependencies:
"@chakra-ui/descendant" "3.0.11"
"@chakra-ui/react-children-utils" "2.0.4"
"@chakra-ui/react-context" "2.0.5"
"@chakra-ui/react-use-controllable-state" "2.0.6"
"@chakra-ui/react-use-merge-refs" "2.0.5"
"@chakra-ui/popover@2.1.4":
version "2.1.4"
resolved "https://registry.yarnpkg.com/@chakra-ui/popover/-/popover-2.1.4.tgz#c44df775875faabe09ef13ce32150a2631c768dd"
integrity sha512-NXVtyMxYzDKzzQph/+GFRSM3tEj3gNvlCX/xGRsCOt9I446zJ1InCd/boXQKAc813coEN9McSOjNWgo+NCBD+Q==
dependencies:
"@chakra-ui/close-button" "2.0.13"
"@chakra-ui/lazy-utils" "2.0.3"
"@chakra-ui/popper" "3.0.10"
"@chakra-ui/react-context" "2.0.5"
"@chakra-ui/react-types" "2.0.5"
"@chakra-ui/react-use-animation-state" "2.0.6"
"@chakra-ui/react-use-disclosure" "2.0.6"
"@chakra-ui/react-use-focus-effect" "2.0.7"
"@chakra-ui/react-use-focus-on-pointer-down" "2.0.4"
"@chakra-ui/react-use-merge-refs" "2.0.5"
"@chakra-ui/popper@3.0.10":
version "3.0.10"
resolved "https://registry.yarnpkg.com/@chakra-ui/popper/-/popper-3.0.10.tgz#5d382c36359615e349e679445eb58c139dbb4d4f"
integrity sha512-6LacbBGX0piHWY/DYxOGCTTFAoRGRHpGIRzTgfNy8jxw4f+rukaVudd4Pc2fwjCTdobJKM8nGNYIYNv9/Dmq9Q==
dependencies:
"@chakra-ui/react-types" "2.0.5"
"@chakra-ui/react-use-merge-refs" "2.0.5"
"@popperjs/core" "^2.9.3"
"@chakra-ui/portal@2.0.9":
version "2.0.9"
resolved "https://registry.yarnpkg.com/@chakra-ui/portal/-/portal-2.0.9.tgz#b427c383a9d602c5b52b21312b4b1c0ffecaf583"
integrity sha512-9e9S0MLbkpofPGlyYA12jNYSdndugy6ylPi5pC9nr3/VqG2Kn+8VcBChAeXW8K4ms7WFc74rNX1pBY/UVwr4qg==
"@chakra-ui/portal@2.0.12":
version "2.0.12"
resolved "https://registry.yarnpkg.com/@chakra-ui/portal/-/portal-2.0.12.tgz#bba0dac00f5610efbf2f15b0e5e6e984135c82c2"
integrity sha512-8D/1fFUdbJtzyGL5sCBIb4oyTnPG2v6rx/L/qbG43FcXDrongmzLj0+tJ//PbJr+5hxjXAWFUjpPvyx10pTN6Q==
dependencies:
"@chakra-ui/react-context" "2.0.3"
"@chakra-ui/react-use-safe-layout-effect" "2.0.1"
"@chakra-ui/react-context" "2.0.5"
"@chakra-ui/react-use-safe-layout-effect" "2.0.3"
"@chakra-ui/progress@2.0.10":
version "2.0.10"
resolved "https://registry.yarnpkg.com/@chakra-ui/progress/-/progress-2.0.10.tgz#9191ae9061ef08066d37c5cb8341fedc10214a29"
integrity sha512-my0Pi3NG1PYhlvCav4fybg3gL5HBNe+7lO4PVdri4QHEyfJlrDeBWID+1GgqlpUWdTj3sOf7ysku+FEgkeOeSA==
"@chakra-ui/progress@2.1.2":
version "2.1.2"
resolved "https://registry.yarnpkg.com/@chakra-ui/progress/-/progress-2.1.2.tgz#dc3dcae7ddc94d18c3d7b64c2a84a83055038f4f"
integrity sha512-ofhMWTqCxnm1NiP/zH4SV7EvOLogfX15MSMTNfGqZv6t8eSSeTn6oRRzsTSllJfSqDey7oZNCRbP7vDhvx9HtQ==
dependencies:
"@chakra-ui/react-context" "2.0.3"
"@chakra-ui/react-context" "2.0.5"
"@chakra-ui/provider@2.0.16":
version "2.0.16"
resolved "https://registry.yarnpkg.com/@chakra-ui/provider/-/provider-2.0.16.tgz#a4afdb4c8f2050beb5d2b61db8971a145481c6f2"
integrity sha512-4t/PmjJ7WXPPaPfoYgw8F1/rVtorZuvknugHfOZcOtAPGQmOPotSv28qjKpu/mCvc1GMGV0swMsvCeInYz7g0w==
"@chakra-ui/provider@2.0.25":
version "2.0.25"
resolved "https://registry.yarnpkg.com/@chakra-ui/provider/-/provider-2.0.25.tgz#6f27abcb8744a7d3c4ebd09409a9e00e0f90c566"
integrity sha512-9BQm9aqiUK/5xeYECpD9dfULd/BfxV4nncFGQNXFqZr0Nx54BALndeKdtId3j+36j9MwcKqjHgKyEpv4s+4vkQ==
dependencies:
"@chakra-ui/css-reset" "2.0.7"
"@chakra-ui/portal" "2.0.9"
"@chakra-ui/react-env" "2.0.9"
"@chakra-ui/system" "2.2.9"
"@chakra-ui/utils" "2.0.9"
"@chakra-ui/css-reset" "2.0.10"
"@chakra-ui/portal" "2.0.12"
"@chakra-ui/react-env" "2.0.11"
"@chakra-ui/system" "2.3.4"
"@chakra-ui/utils" "2.0.12"
"@chakra-ui/radio@2.0.10":
version "2.0.10"
resolved "https://registry.yarnpkg.com/@chakra-ui/radio/-/radio-2.0.10.tgz#82eb02313efafc460da3030d011bfd434b1ecabc"
integrity sha512-LhAWsY22cmb+M/iyhFgkzf2+V9TJmAC77Cd+GbP3M3sxDSEUDtq08KOc3JjoYc3GzeZml3JL1yssbxh+liY3xA==
"@chakra-ui/radio@2.0.14":
version "2.0.14"
resolved "https://registry.yarnpkg.com/@chakra-ui/radio/-/radio-2.0.14.tgz#f214f728235782a2ac49c0eb507f151612e31b2e"
integrity sha512-e/hY1g92Xdu5d5A27NFfa1+ccE2q/A5H7sc/M7p0fId6KO33Dst25Hy+HThtqnYN0Y3Om58fiXEKo5SsdtvSfA==
dependencies:
"@chakra-ui/form-control" "2.0.9"
"@chakra-ui/react-context" "2.0.3"
"@chakra-ui/react-types" "2.0.3"
"@chakra-ui/react-use-merge-refs" "2.0.3"
"@chakra-ui/form-control" "2.0.13"
"@chakra-ui/react-context" "2.0.5"
"@chakra-ui/react-types" "2.0.5"
"@chakra-ui/react-use-merge-refs" "2.0.5"
"@zag-js/focus-visible" "0.1.0"
"@chakra-ui/react-children-utils@2.0.1":
version "2.0.1"
resolved "https://registry.yarnpkg.com/@chakra-ui/react-children-utils/-/react-children-utils-2.0.1.tgz#321ac05362ade1495a34ea74052d3c7da3d9e923"
integrity sha512-sEgpuh/vWSt2+W0F49EGYXXUyjmg0lbosjVg6qUKHv9sAyx5tbrOrZ6df/TaMUSAe9m3AUOMGqUIPLpxno0DjA==
"@chakra-ui/react-children-utils@2.0.4":
version "2.0.4"
resolved "https://registry.yarnpkg.com/@chakra-ui/react-children-utils/-/react-children-utils-2.0.4.tgz#6e4284297a8a9b4e6f5f955b099bb6c2c6bbf8b9"
integrity sha512-qsKUEfK/AhDbMexWo5JhmdlkxLg5WEw2dFh4XorvU1/dTYsRfP6cjFfO8zE+X3F0ZFNsgKz6rbN5oU349GLEFw==
"@chakra-ui/react-context@2.0.3":
version "2.0.3"
resolved "https://registry.yarnpkg.com/@chakra-ui/react-context/-/react-context-2.0.3.tgz#e988be62f5f5fe29d6a8496c79cbf934f840fa5a"
integrity sha512-KmPq6sb1y05WsOUqXZtBBC4LsNKZIFrp2thTsLBwcuH7lkXZwPMHmJGKa9K980P+SWEgfH2s2PY2z+QrIuqWGg==
"@chakra-ui/react-context@2.0.5":
version "2.0.5"
resolved "https://registry.yarnpkg.com/@chakra-ui/react-context/-/react-context-2.0.5.tgz#c434013ecc46c780539791d756dafdfc7c64320e"
integrity sha512-WYS0VBl5Q3/kNShQ26BP+Q0OGMeTQWco3hSiJWvO2wYLY7N1BLq6dKs8vyKHZfpwKh2YL2bQeAObi+vSkXp6tQ==
"@chakra-ui/react-env@2.0.9":
version "2.0.9"
resolved "https://registry.yarnpkg.com/@chakra-ui/react-env/-/react-env-2.0.9.tgz#d51efc31d77197a3526e2c4b2f2fde557396bb3c"
integrity sha512-4AJHNUGBR19hzVyOILYpZZgq8jGrpEcbhvR++CppbvPH7vfPZpoz6L/cBtHxS07YwDtUeBL8yCNiLlTxctV//Q==
"@chakra-ui/react-env@2.0.11":
version "2.0.11"
resolved "https://registry.yarnpkg.com/@chakra-ui/react-env/-/react-env-2.0.11.tgz#d9d65fb695de7aff15e1d0e97d57bb7bedce5fa2"
integrity sha512-rPwUHReSWh7rbCw0HePa8Pvc+Q82fUFvVjHTIbXKnE6d+01cCE7j4f1NLeRD9pStKPI6sIZm9xTGvOCzl8F8iw==
"@chakra-ui/react-types@2.0.3":
version "2.0.3"
resolved "https://registry.yarnpkg.com/@chakra-ui/react-types/-/react-types-2.0.3.tgz#dc454c4703b4de585e6461fd607304ede06fe595"
integrity sha512-1mJYOQldFTALE0Wr3j6tk/MYvgQIp6CKkJulNzZrI8QN+ox/bJOh8OVP4vhwqvfigdLTui0g0k8M9h+j2ub/Mw==
"@chakra-ui/react-types@2.0.5":
version "2.0.5"
resolved "https://registry.yarnpkg.com/@chakra-ui/react-types/-/react-types-2.0.5.tgz#1e4c99ef0e59b5fe9263d0e186cd66afdfb6c87b"
integrity sha512-GApp+R/VjS1UV5ms5irrij5LOIgUM0dqSVHagyEFEz88LRKkqMD9RuO577ZsVd4Gn0ULsacVJCUA0HtNUBJNzA==
"@chakra-ui/react-use-animation-state@2.0.3":
version "2.0.3"
resolved "https://registry.yarnpkg.com/@chakra-ui/react-use-animation-state/-/react-use-animation-state-2.0.3.tgz#c10e575de76e907a84358595884a819039b24200"
integrity sha512-sjGgzMMmxurwKDSFhDLpLNn3SWUERI5iAZOOa0pYnyOLGVXMowgIjK6jpZxre1vc3A+unjJk5P4qeiyY+C4uwQ==
"@chakra-ui/react-use-animation-state@2.0.6":
version "2.0.6"
resolved "https://registry.yarnpkg.com/@chakra-ui/react-use-animation-state/-/react-use-animation-state-2.0.6.tgz#2a324d3c67015a542ed589f899672d681889e1e7"
integrity sha512-M2kUzZkSBgDpfvnffh3kTsMIM3Dvn+CTMqy9zfY97NL4P3LAWL1MuFtKdlKfQ8hs/QpwS/ew8CTmCtaywn4sKg==
dependencies:
"@chakra-ui/dom-utils" "2.0.1"
"@chakra-ui/react-use-event-listener" "2.0.3"
"@chakra-ui/dom-utils" "2.0.4"
"@chakra-ui/react-use-event-listener" "2.0.5"
"@chakra-ui/react-use-callback-ref@2.0.3":
version "2.0.3"
resolved "https://registry.yarnpkg.com/@chakra-ui/react-use-callback-ref/-/react-use-callback-ref-2.0.3.tgz#532f993ae0dda27b2638d41e98f42c83751cd3b6"
integrity sha512-kdYlhgnQKWWLNwl3WSv/Oq3+mlnu2p3y4Xc1AqKVHVcBOdQE9lpW3d7ZaOoK2aIXXWq1rocscOiXBUtM0Vqd2A==
"@chakra-ui/react-use-callback-ref@2.0.5":
version "2.0.5"
resolved "https://registry.yarnpkg.com/@chakra-ui/react-use-callback-ref/-/react-use-callback-ref-2.0.5.tgz#862430dfbab8e1f0b8e04476e5e25469bd044ec9"
integrity sha512-vKnXleD2PzB0nGabY35fRtklMid4z7cecbMG0fkasNNsgWmrQcXJOuEKUUVCynL6FBU6gBnpKFi5Aqj6x+K4tw==
"@chakra-ui/react-use-controllable-state@2.0.3":
version "2.0.3"
resolved "https://registry.yarnpkg.com/@chakra-ui/react-use-controllable-state/-/react-use-controllable-state-2.0.3.tgz#7aa3f9c038513763332f6754e69ece90aed55a9c"
integrity sha512-su8efwCWWnY2LQUU6PEnYwSGJX8kvPSO2KyUKuymx8q3fNWuyhzAZriG/TbeeCxESLp70+wuniUlSGRa4vxylQ==
"@chakra-ui/react-use-controllable-state@2.0.6":
version "2.0.6"
resolved "https://registry.yarnpkg.com/@chakra-ui/react-use-controllable-state/-/react-use-controllable-state-2.0.6.tgz#ec62aff9b9c00324a0a4c9a4523824a9ad5ef9aa"
integrity sha512-7WuKrhQkpSRoiI5PKBvuIsO46IIP0wsRQgXtStSaIXv+FIvIJl9cxQXTbmZ5q1Ds641QdAUKx4+6v0K/zoZEHg==
dependencies:
"@chakra-ui/react-use-callback-ref" "2.0.3"
"@chakra-ui/react-use-callback-ref" "2.0.5"
"@chakra-ui/react-use-disclosure@2.0.3":
version "2.0.3"
resolved "https://registry.yarnpkg.com/@chakra-ui/react-use-disclosure/-/react-use-disclosure-2.0.3.tgz#c27bfc7e3af0728423b9e2def1e1665d0ba941bb"
integrity sha512-3IdrzvQZcgjqSx5wTVffInOyhMU+d3ZlIE26JmqejMyN/B+qAs932iKfm0A1mTMPTz38ZnNtuaKazmzyfR1ePg==
"@chakra-ui/react-use-disclosure@2.0.6":
version "2.0.6"
resolved "https://registry.yarnpkg.com/@chakra-ui/react-use-disclosure/-/react-use-disclosure-2.0.6.tgz#db707ee119db829e9b21ff1c05e867938f1e27ba"
integrity sha512-4UPePL+OcCY37KZ585iLjg8i6J0sjpLm7iZG3PUwmb97oKHVHq6DpmWIM0VfSjcT6AbSqyGcd5BXZQBgwt8HWQ==
dependencies:
"@chakra-ui/react-use-callback-ref" "2.0.3"
"@chakra-ui/react-use-callback-ref" "2.0.5"
"@chakra-ui/react-use-event-listener@2.0.3":
version "2.0.3"
resolved "https://registry.yarnpkg.com/@chakra-ui/react-use-event-listener/-/react-use-event-listener-2.0.3.tgz#11b5409c4442888e7981d5288c9e781acdacd685"
integrity sha512-m3ZdJjo3QQ1HcQGnehlBTgHaCVewz5fwIRTXVzbZTraVJr4k589Zf87eagW57tT4dyv656lSmdhaFGZ8p5Snww==
"@chakra-ui/react-use-event-listener@2.0.5":
version "2.0.5"
resolved "https://registry.yarnpkg.com/@chakra-ui/react-use-event-listener/-/react-use-event-listener-2.0.5.tgz#949aa99878b25b23709452d3c80a1570c99747cc"
integrity sha512-etLBphMigxy/cm7Yg22y29gQ8u/K3PniR5ADZX7WVX61Cgsa8ciCqjTE9sTtlJQWAQySbWxt9+mjlT5zaf+6Zw==
dependencies:
"@chakra-ui/react-use-callback-ref" "2.0.3"
"@chakra-ui/react-use-callback-ref" "2.0.5"
"@chakra-ui/react-use-focus-effect@2.0.3":
version "2.0.3"
resolved "https://registry.yarnpkg.com/@chakra-ui/react-use-focus-effect/-/react-use-focus-effect-2.0.3.tgz#3e221a5e9b06a7b832a6fca1f883e6335fd69f19"
integrity sha512-N0rho7P+rH5cn13dbS8GUOye+6RYXAmXhmlS+WW/3lWidGH3HAbMoOVf56UiuSnE1+2or8/U7qRshUryj2H1nA==
"@chakra-ui/react-use-focus-effect@2.0.7":
version "2.0.7"
resolved "https://registry.yarnpkg.com/@chakra-ui/react-use-focus-effect/-/react-use-focus-effect-2.0.7.tgz#bd03290cac32e0de6a71ce987f939a5e697bca04"
integrity sha512-wI8OUNwfbkusajLac8QtjfSyNmsNu1D5pANmnSHIntHhui6Jwv75Pxx7RgmBEnfBEpleBndhR9E75iCjPLhZ/A==
dependencies:
"@chakra-ui/dom-utils" "2.0.1"
"@chakra-ui/react-use-event-listener" "2.0.3"
"@chakra-ui/react-use-update-effect" "2.0.3"
"@chakra-ui/dom-utils" "2.0.4"
"@chakra-ui/react-use-event-listener" "2.0.5"
"@chakra-ui/react-use-safe-layout-effect" "2.0.3"
"@chakra-ui/react-use-update-effect" "2.0.5"
"@chakra-ui/react-use-focus-on-pointer-down@2.0.1":
version "2.0.1"
resolved "https://registry.yarnpkg.com/@chakra-ui/react-use-focus-on-pointer-down/-/react-use-focus-on-pointer-down-2.0.1.tgz#be0668ff844dec8bbfe978d6eaff50534f290c48"
integrity sha512-f0qL2iWvajUo+0jwDZyJpUMJ6J6BH3WjDZE2Rp6cns4pgI6uYuv2gj+FqQ5jnoYdXkeER6lBI56a+aIW/1RYiA==
"@chakra-ui/react-use-focus-on-pointer-down@2.0.4":
version "2.0.4"
resolved "https://registry.yarnpkg.com/@chakra-ui/react-use-focus-on-pointer-down/-/react-use-focus-on-pointer-down-2.0.4.tgz#aeba543c451ac1b0138093234e71d334044daf84"
integrity sha512-L3YKouIi77QbXH9mSLGEFzJbJDhyrPlcRcuu+TSC7mYaK9E+3Ap+RVSAVxj+CfQz7hCWpikPecKDuspIPWlyuA==
dependencies:
"@chakra-ui/react-use-event-listener" "2.0.3"
"@chakra-ui/react-use-event-listener" "2.0.5"
"@chakra-ui/react-use-interval@2.0.1":
version "2.0.1"
resolved "https://registry.yarnpkg.com/@chakra-ui/react-use-interval/-/react-use-interval-2.0.1.tgz#a8f5dbf83607f5dc53022aa2a766fdcb09d8a081"
integrity sha512-6ZLzKA7Ga894UZcXO3bbGYThlhviiau1oxZ1UcJG5pUXNM9Up7O/4Joq31sL+KcpteCN45vd1etomilsv/blxw==
"@chakra-ui/react-use-interval@2.0.3":
version "2.0.3"
resolved "https://registry.yarnpkg.com/@chakra-ui/react-use-interval/-/react-use-interval-2.0.3.tgz#d5c7bce117fb25edb54e3e2c666e900618bb5bb2"
integrity sha512-Orbij5c5QkL4NuFyU4mfY/nyRckNBgoGe9ic8574VVNJIXfassevZk0WB+lvqBn5XZeLf2Tj+OGJrg4j4H9wzw==
dependencies:
"@chakra-ui/react-use-callback-ref" "2.0.3"
"@chakra-ui/react-use-callback-ref" "2.0.5"
"@chakra-ui/react-use-merge-refs@2.0.3":
"@chakra-ui/react-use-latest-ref@2.0.3":
version "2.0.3"
resolved "https://registry.yarnpkg.com/@chakra-ui/react-use-merge-refs/-/react-use-merge-refs-2.0.3.tgz#cd8dac79c62dd45daaf4acc4507721d23dc5dc51"
integrity sha512-n35BmVbasy5Esa6qxznWmiV3NaRxGpqMpZH0n+X7aXt8VkGAJzRpAVjUmKCLNYyCLpqsQceCmAEK8a5SR6vxqw==
resolved "https://registry.yarnpkg.com/@chakra-ui/react-use-latest-ref/-/react-use-latest-ref-2.0.3.tgz#27cf703858e65ecb5a0eef26215c794ad2a5353d"
integrity sha512-exNSQD4rPclDSmNwtcChUCJ4NuC2UJ4amyNGBqwSjyaK5jNHk2kkM7rZ6I0I8ul+26lvrXlSuhyv6c2PFwbFQQ==
"@chakra-ui/react-use-outside-click@2.0.3":
version "2.0.3"
resolved "https://registry.yarnpkg.com/@chakra-ui/react-use-outside-click/-/react-use-outside-click-2.0.3.tgz#d0136d1c2fb45d86361224e98e3a50648bf9b85f"
integrity sha512-r5OohM8lOuZTz6e3vVHvfm/3sEkd06nUPBNU+r3rWh1I7bR9z5Gia/BOQD6GE4jUTanDkHcH76Pf9qJ45kpibQ==
"@chakra-ui/react-use-merge-refs@2.0.5":
version "2.0.5"
resolved "https://registry.yarnpkg.com/@chakra-ui/react-use-merge-refs/-/react-use-merge-refs-2.0.5.tgz#13181e1a43219c6a04a01f84de0188df042ee92e"
integrity sha512-uc+MozBZ8asaUpO8SWcK6D4svRPACN63jv5uosUkXJR+05jQJkUofkfQbf2HeGVbrWCr0XZsftLIm4Mt/QMoVw==
"@chakra-ui/react-use-outside-click@2.0.5":
version "2.0.5"
resolved "https://registry.yarnpkg.com/@chakra-ui/react-use-outside-click/-/react-use-outside-click-2.0.5.tgz#6a9896d2c2d35f3c301c3bb62bed1bf5290d1e60"
integrity sha512-WmtXUeVaMtxP9aUGGG+GQaDeUn/Bvf8TI3EU5mE1+TtqLHxyA9wtvQurynrogvpilLaBADwn/JeBeqs2wHpvqA==
dependencies:
"@chakra-ui/react-use-callback-ref" "2.0.3"
"@chakra-ui/react-use-callback-ref" "2.0.5"
"@chakra-ui/react-use-pan-event@2.0.4":
version "2.0.4"
resolved "https://registry.yarnpkg.com/@chakra-ui/react-use-pan-event/-/react-use-pan-event-2.0.4.tgz#bfc2c1a2a44b2996951a729182566f02c7dc05e4"
integrity sha512-lcEjngfCgIjE5qZeJiaDx+aJzZPLjbjUmbWumi8pIgWOnDL8Ffjh7AMKW4CddP5OgcRnDDb+7aqJbb55wraboA==
"@chakra-ui/react-use-pan-event@2.0.6":
version "2.0.6"
resolved "https://registry.yarnpkg.com/@chakra-ui/react-use-pan-event/-/react-use-pan-event-2.0.6.tgz#e489d61672e6f473b7fd362d816e2e27ed3b2af6"
integrity sha512-Vtgl3c+Mj4hdehFRFIgruQVXctwnG1590Ein1FiU8sVnlqO6bpug6Z+B14xBa+F+X0aK+DxnhkJFyWI93Pks2g==
dependencies:
"@chakra-ui/event-utils" "2.0.4"
"@chakra-ui/event-utils" "2.0.6"
"@chakra-ui/react-use-latest-ref" "2.0.3"
framesync "5.3.0"
"@chakra-ui/react-use-previous@2.0.1":
version "2.0.1"
resolved "https://registry.yarnpkg.com/@chakra-ui/react-use-previous/-/react-use-previous-2.0.1.tgz#e19f6b363271f62c36c9f3bd91dc60caa4c4e340"
integrity sha512-ROi+/puVd8D1QaxBSOcGlJNqV2x02ppSgmXzZZJhM8ryFLZjY9ojV3HhamB2IJ/7SIb1rMSSV1GPedFw7YMCwA==
"@chakra-ui/react-use-safe-layout-effect@2.0.1":
version "2.0.1"
resolved "https://registry.yarnpkg.com/@chakra-ui/react-use-safe-layout-effect/-/react-use-safe-layout-effect-2.0.1.tgz#76f8882abaf17078c3b6eb93e1bb26f8c319f3f7"
integrity sha512-H+ZOjkPqv3KBPEoP68JKpQBNdLOI0mwzEiTT397UdvBVCCJ+1/ijWVUT+Ub/pYic60O6xUghy5ORaWqJHhnKDA==
"@chakra-ui/react-use-previous@2.0.3":
version "2.0.3"
resolved "https://registry.yarnpkg.com/@chakra-ui/react-use-previous/-/react-use-previous-2.0.3.tgz#9da3d53fd75f1c3da902bd6af71dcb1a7be37f31"
integrity sha512-A2ODOa0rm2HM4aqXfxxI0zPLcn5Q7iBEjRyfIQhb+EH+d2OFuj3L2slVoIpp6e/km3Xzv2d+u/WbjgTzdQ3d0w==
"@chakra-ui/react-use-size@2.0.3":
"@chakra-ui/react-use-safe-layout-effect@2.0.3":
version "2.0.3"
resolved "https://registry.yarnpkg.com/@chakra-ui/react-use-size/-/react-use-size-2.0.3.tgz#ae3bd683eb87a40208cf0dd467a5dafb68d87b3e"
integrity sha512-hr4hKepPUmM2paXseSZiOTK2y+ZqnSzYNusDEB01f+cDerFjdN1jSfNJKXpiKF0+hNESXfOPQb3Zt0eDusRdoA==
dependencies:
"@zag-js/element-size" "0.1.0"
resolved "https://registry.yarnpkg.com/@chakra-ui/react-use-safe-layout-effect/-/react-use-safe-layout-effect-2.0.3.tgz#bf63ac8c94460aa1b20b6b601a0ea873556ffb1b"
integrity sha512-dlTvQURzmdfyBbNdydgO4Wy2/HV8aJN8LszTtyb5vRZsyaslDM/ftcxo8E8QjHwRLD/V1Epb/A8731QfimfVaQ==
"@chakra-ui/react-use-timeout@2.0.1":
version "2.0.1"
resolved "https://registry.yarnpkg.com/@chakra-ui/react-use-timeout/-/react-use-timeout-2.0.1.tgz#acacadfb7c1443aacf634ddce710b1cd7cf3b6ec"
integrity sha512-zXh9RH+GciKr8hvaOADHOoHP72B7UZUEymA8CWCV4WEs/9s/PfQJH7X1bwvaj43CcOmfVQg4oODWqCYQM1lSsg==
"@chakra-ui/react-use-size@2.0.5":
version "2.0.5"
resolved "https://registry.yarnpkg.com/@chakra-ui/react-use-size/-/react-use-size-2.0.5.tgz#4bbffb64f97dcfe1d7edeb0f03bb1d5fbc48df64"
integrity sha512-4arAApdiXk5uv5ZeFKltEUCs5h3yD9dp6gTIaXbAdq+/ENK3jMWTwlqzNbJtCyhwoOFrblLSdBrssBMIsNQfZQ==
dependencies:
"@chakra-ui/react-use-callback-ref" "2.0.3"
"@zag-js/element-size" "0.1.0"
"@chakra-ui/react-use-update-effect@2.0.3":
"@chakra-ui/react-use-timeout@2.0.3":
version "2.0.3"
resolved "https://registry.yarnpkg.com/@chakra-ui/react-use-update-effect/-/react-use-update-effect-2.0.3.tgz#5b0128fe1325b5b1413690db6bc8dd0712d01e29"
integrity sha512-8hkP1o/UUUA49w/R+XyAlPiCjxXTCWCNsHWUOEhAitjJfoCNUjgaNKOD52hT07kc5ACJEcJQHA5327LnwtiIlg==
"@chakra-ui/react-utils@2.0.6":
version "2.0.6"
resolved "https://registry.yarnpkg.com/@chakra-ui/react-utils/-/react-utils-2.0.6.tgz#bb471ce2bff724b99563685962145a2cc56bf61d"
integrity sha512-ZL0FPaolovXOxMzYRSLHgBYtvxIkA/c5GTSYpXL8DcC+TBLZnAmQ8BPTS2b6xys6xvwdQjkZRUeQ0cBNFaryJg==
resolved "https://registry.yarnpkg.com/@chakra-ui/react-use-timeout/-/react-use-timeout-2.0.3.tgz#16ca397dbca55a64811575884cb81a348d86d4e2"
integrity sha512-rBBUkZSQq3nJQ8fuMkgZNY2Sgg4vKiKNp05GxAwlT7TitOfVZyoTriqQpqz296bWlmkICTZxlqCWfE5fWpsTsg==
dependencies:
"@chakra-ui/utils" "2.0.9"
"@chakra-ui/react-use-callback-ref" "2.0.5"
"@chakra-ui/react@2.3.1":
version "2.3.1"
resolved "https://registry.yarnpkg.com/@chakra-ui/react/-/react-2.3.1.tgz#da7e393f48e64d3ae64b6e2370d15729259ebd3a"
integrity sha512-ROaIyQqpN5AXhhxDGkrP37QmnPu/ZJnT1nLdzUxHZE1sO7ujBbir3G+zHlj/Hg2DbYClkB3yfAhA08imeiJh8g==
dependencies:
"@chakra-ui/accordion" "2.0.12"
"@chakra-ui/alert" "2.0.9"
"@chakra-ui/avatar" "2.1.0"
"@chakra-ui/breadcrumb" "2.0.9"
"@chakra-ui/button" "2.0.9"
"@chakra-ui/checkbox" "2.1.8"
"@chakra-ui/close-button" "2.0.9"
"@chakra-ui/control-box" "2.0.9"
"@chakra-ui/counter" "2.0.9"
"@chakra-ui/css-reset" "2.0.7"
"@chakra-ui/editable" "2.0.9"
"@chakra-ui/form-control" "2.0.9"
"@chakra-ui/hooks" "2.0.9"
"@chakra-ui/icon" "3.0.9"
"@chakra-ui/image" "2.0.10"
"@chakra-ui/input" "2.0.9"
"@chakra-ui/layout" "2.1.6"
"@chakra-ui/live-region" "2.0.9"
"@chakra-ui/media-query" "3.2.5"
"@chakra-ui/menu" "2.0.13"
"@chakra-ui/modal" "2.1.7"
"@chakra-ui/number-input" "2.0.9"
"@chakra-ui/pin-input" "2.0.12"
"@chakra-ui/popover" "2.0.9"
"@chakra-ui/popper" "3.0.7"
"@chakra-ui/portal" "2.0.9"
"@chakra-ui/progress" "2.0.10"
"@chakra-ui/provider" "2.0.16"
"@chakra-ui/radio" "2.0.10"
"@chakra-ui/react-env" "2.0.9"
"@chakra-ui/select" "2.0.10"
"@chakra-ui/skeleton" "2.0.15"
"@chakra-ui/slider" "2.0.10"
"@chakra-ui/spinner" "2.0.9"
"@chakra-ui/stat" "2.0.9"
"@chakra-ui/switch" "2.0.11"
"@chakra-ui/system" "2.2.9"
"@chakra-ui/table" "2.0.9"
"@chakra-ui/tabs" "2.1.1"
"@chakra-ui/tag" "2.0.9"
"@chakra-ui/textarea" "2.0.10"
"@chakra-ui/theme" "2.1.10"
"@chakra-ui/toast" "3.0.9"
"@chakra-ui/tooltip" "2.0.10"
"@chakra-ui/transition" "2.0.9"
"@chakra-ui/utils" "2.0.9"
"@chakra-ui/visually-hidden" "2.0.9"
"@chakra-ui/select@2.0.10":
version "2.0.10"
resolved "https://registry.yarnpkg.com/@chakra-ui/select/-/select-2.0.10.tgz#827028484769a32205f99baed3098a115da292b3"
integrity sha512-7AslBWwI/JyczjMMGtPuN34M/C38koVd+N/pb6swHoIP9TRkkdvDlonIakcmtO1oLEzlNIFKmt4FQ7bUp9ea5Q==
"@chakra-ui/react-use-update-effect@2.0.5":
version "2.0.5"
resolved "https://registry.yarnpkg.com/@chakra-ui/react-use-update-effect/-/react-use-update-effect-2.0.5.tgz#aede8f13f2b3de254b4ffa3b8cec1b70bd2876c5"
integrity sha512-y9tCMr1yuDl8ATYdh64Gv8kge5xE1DMykqPDZw++OoBsTaWr3rx40wblA8NIWuSyJe5ErtKP2OeglvJkYhryJQ==
"@chakra-ui/react-utils@2.0.9":
version "2.0.9"
resolved "https://registry.yarnpkg.com/@chakra-ui/react-utils/-/react-utils-2.0.9.tgz#5cdf0bc8dee57c15f15ace04fbba574ec8aa6ecc"
integrity sha512-nlwPBVlQmcl1PiLzZWyrT3FSnt3vKSkBMzQ0EF4SJWA/nOIqTvmffb5DCzCqPzgQaE/Da1Xgus+JufFGM8GLCQ==
dependencies:
"@chakra-ui/utils" "2.0.12"
"@chakra-ui/react@2.4.3":
version "2.4.3"
resolved "https://registry.yarnpkg.com/@chakra-ui/react/-/react-2.4.3.tgz#c86b92b38ebaa482f22e4515208da429cd4c94bf"
integrity sha512-OzeN8lb/yoJZMuLYgnX00ZcrvMwgMomIHvq3lD7v0jtfdzAQKEySgu7/CmHOsvWnDB7Qy9LMlSb8PLUII1aDow==
dependencies:
"@chakra-ui/accordion" "2.1.4"
"@chakra-ui/alert" "2.0.13"
"@chakra-ui/avatar" "2.2.1"
"@chakra-ui/breadcrumb" "2.1.1"
"@chakra-ui/button" "2.0.13"
"@chakra-ui/card" "2.1.2"
"@chakra-ui/checkbox" "2.2.5"
"@chakra-ui/close-button" "2.0.13"
"@chakra-ui/control-box" "2.0.11"
"@chakra-ui/counter" "2.0.11"
"@chakra-ui/css-reset" "2.0.10"
"@chakra-ui/editable" "2.0.16"
"@chakra-ui/form-control" "2.0.13"
"@chakra-ui/hooks" "2.1.2"
"@chakra-ui/icon" "3.0.13"
"@chakra-ui/image" "2.0.12"
"@chakra-ui/input" "2.0.14"
"@chakra-ui/layout" "2.1.11"
"@chakra-ui/live-region" "2.0.11"
"@chakra-ui/media-query" "3.2.8"
"@chakra-ui/menu" "2.1.5"
"@chakra-ui/modal" "2.2.5"
"@chakra-ui/number-input" "2.0.14"
"@chakra-ui/pin-input" "2.0.16"
"@chakra-ui/popover" "2.1.4"
"@chakra-ui/popper" "3.0.10"
"@chakra-ui/portal" "2.0.12"
"@chakra-ui/progress" "2.1.2"
"@chakra-ui/provider" "2.0.25"
"@chakra-ui/radio" "2.0.14"
"@chakra-ui/react-env" "2.0.11"
"@chakra-ui/select" "2.0.14"
"@chakra-ui/skeleton" "2.0.19"
"@chakra-ui/slider" "2.0.15"
"@chakra-ui/spinner" "2.0.11"
"@chakra-ui/stat" "2.0.13"
"@chakra-ui/styled-system" "2.4.0"
"@chakra-ui/switch" "2.0.17"
"@chakra-ui/system" "2.3.4"
"@chakra-ui/table" "2.0.12"
"@chakra-ui/tabs" "2.1.5"
"@chakra-ui/tag" "2.0.13"
"@chakra-ui/textarea" "2.0.14"
"@chakra-ui/theme" "2.2.2"
"@chakra-ui/theme-utils" "2.0.5"
"@chakra-ui/toast" "4.0.5"
"@chakra-ui/tooltip" "2.2.3"
"@chakra-ui/transition" "2.0.12"
"@chakra-ui/utils" "2.0.12"
"@chakra-ui/visually-hidden" "2.0.13"
"@chakra-ui/select@2.0.14":
version "2.0.14"
resolved "https://registry.yarnpkg.com/@chakra-ui/select/-/select-2.0.14.tgz#b2230702e31d2b9b4cc7848b18ba7ae8e4c89bdb"
integrity sha512-fvVGxAtLaIXGOMicrzSa6imMw5h26S1ar3xyNmXgR40dbpTPHmtQJkbHBf9FwwQXgSgKWgBzsztw5iDHCpPVzA==
dependencies:
"@chakra-ui/form-control" "2.0.9"
"@chakra-ui/form-control" "2.0.13"
"@chakra-ui/shared-utils@2.0.1":
version "2.0.1"
resolved "https://registry.yarnpkg.com/@chakra-ui/shared-utils/-/shared-utils-2.0.1.tgz#41e314e42c96039e8ffb265e73145cf755813ab4"
integrity sha512-NXDBl/u4wrSNp0ON5R3r3evkRurrAz2yuO7neooaG+O5HEenVouGqm4CsXd6lUAPmjwiGzA0LQFNCt0Hj92dXg==
"@chakra-ui/shared-utils@2.0.3":
version "2.0.3"
resolved "https://registry.yarnpkg.com/@chakra-ui/shared-utils/-/shared-utils-2.0.3.tgz#97cbc11282e381ebd9f581c603088f9d60ead451"
integrity sha512-pCU+SUGdXzjAuUiUT8mriekL3tJVfNdwSTIaNeip7k/SWDzivrKGMwAFBxd3XVTDevtVusndkO4GJuQ3yILzDg==
"@chakra-ui/skeleton@2.0.15":
version "2.0.15"
resolved "https://registry.yarnpkg.com/@chakra-ui/skeleton/-/skeleton-2.0.15.tgz#fd41383bf84319e47c6ea1f4f3138f5f5e0dabca"
integrity sha512-QVMkXwrH9jLfim8uJTZcjHeGjzoquNcHGXD5wapd7eDqp9BygvmMXAHBxFm8eEJLHuvIqLX94P6DLeiieYwX7Q==
"@chakra-ui/skeleton@2.0.19":
version "2.0.19"
resolved "https://registry.yarnpkg.com/@chakra-ui/skeleton/-/skeleton-2.0.19.tgz#c2f8619edf06d8411aeb8fac8748134bfba7ea35"
integrity sha512-i/U70wB9rX3DdO9i50kQLfPVdLPw8oIZlKynq15DkXEch3uXzLMY8ZzcEQW99B/PnynC1gni0+P9jMfeX7Wi+Q==
dependencies:
"@chakra-ui/media-query" "3.2.5"
"@chakra-ui/react-use-previous" "2.0.1"
"@chakra-ui/media-query" "3.2.8"
"@chakra-ui/react-use-previous" "2.0.3"
"@chakra-ui/slider@2.0.10":
version "2.0.10"
resolved "https://registry.yarnpkg.com/@chakra-ui/slider/-/slider-2.0.10.tgz#ffd7376d4a7fd9aa02e72b148e3ad4739c880498"
integrity sha512-F0RGl2ruADbXO/GnoBUiTEl+przxhZo2e0tfw9VTtS+RsJZ22uHrTNVvVJHNmjK7/E3++kBfaLCacoJFz/io+g==
dependencies:
"@chakra-ui/number-utils" "2.0.3"
"@chakra-ui/react-context" "2.0.3"
"@chakra-ui/react-types" "2.0.3"
"@chakra-ui/react-use-callback-ref" "2.0.3"
"@chakra-ui/react-use-controllable-state" "2.0.3"
"@chakra-ui/react-use-merge-refs" "2.0.3"
"@chakra-ui/react-use-pan-event" "2.0.4"
"@chakra-ui/react-use-size" "2.0.3"
"@chakra-ui/react-use-update-effect" "2.0.3"
"@chakra-ui/spinner@2.0.9":
version "2.0.9"
resolved "https://registry.yarnpkg.com/@chakra-ui/spinner/-/spinner-2.0.9.tgz#1d8544cc136699a590c3f5c518ae2c14abb459cf"
integrity sha512-9ALl51fiVWptDu2J2xcv0TSfGf4buumpHrEXHvV2Qy+HZ6rYnUmSThBSb/VgoQS+rASG8bAbLUPlQTQ+v9ibFg==
"@chakra-ui/slider@2.0.15":
version "2.0.15"
resolved "https://registry.yarnpkg.com/@chakra-ui/slider/-/slider-2.0.15.tgz#e574020d3240490b12204a6b1db4f40101567f87"
integrity sha512-1PwTBgaPKe8L1aOryGd1i52snqQY615Jd7d1Jyjzr/7s3uvLnpCmTE7bazu/cJU0h1qSpluMv++A+d+fjICdmA==
dependencies:
"@chakra-ui/number-utils" "2.0.5"
"@chakra-ui/react-context" "2.0.5"
"@chakra-ui/react-types" "2.0.5"
"@chakra-ui/react-use-callback-ref" "2.0.5"
"@chakra-ui/react-use-controllable-state" "2.0.6"
"@chakra-ui/react-use-latest-ref" "2.0.3"
"@chakra-ui/react-use-merge-refs" "2.0.5"
"@chakra-ui/react-use-pan-event" "2.0.6"
"@chakra-ui/react-use-size" "2.0.5"
"@chakra-ui/react-use-update-effect" "2.0.5"
"@chakra-ui/spinner@2.0.11":
version "2.0.11"
resolved "https://registry.yarnpkg.com/@chakra-ui/spinner/-/spinner-2.0.11.tgz#a5dd76b6cb0f3524d9b90b73fa4acfb6adc69f33"
integrity sha512-piO2ghWdJzQy/+89mDza7xLhPnW7pA+ADNbgCb1vmriInWedS41IBKe+pSPz4IidjCbFu7xwKE0AerFIbrocCA==
"@chakra-ui/stat@2.0.9":
version "2.0.9"
resolved "https://registry.yarnpkg.com/@chakra-ui/stat/-/stat-2.0.9.tgz#cecf35a4392a88227c3b85e80a45f0f5ac5f298d"
integrity sha512-C9cytqegWSGJ/hh3/qwsgGlerXLYHrU0iQcJQ+pKSRFJhshXsv3go5IR6kVL72Yf2s4Gs5c3GsMZrLM22ePpDg==
"@chakra-ui/stat@2.0.13":
version "2.0.13"
resolved "https://registry.yarnpkg.com/@chakra-ui/stat/-/stat-2.0.13.tgz#1805817ab54f9d9b663b465fcb255285d22d0152"
integrity sha512-6XeuE/7w0BjyCHSxMbsf6/rNOOs8BSit1NS7g7+Jd/40Pc/SKlNWLd3kxXPid4eT3RwyNIdMPtm30OActr9nqQ==
dependencies:
"@chakra-ui/icon" "3.0.9"
"@chakra-ui/react-context" "2.0.3"
"@chakra-ui/icon" "3.0.13"
"@chakra-ui/react-context" "2.0.5"
"@chakra-ui/styled-system@2.3.1":
version "2.3.1"
resolved "https://registry.yarnpkg.com/@chakra-ui/styled-system/-/styled-system-2.3.1.tgz#abf7c4e1638aaa9d92e7cf9acde17785703d166e"
integrity sha512-jyR9s2yk5TEyq4HUfjrgUeaOzd9ZTZrbjK96UjtiTCZGO/q4j2RXtYvfheUjUyW1UnzI2A1ffHOJca8tBMDjpA==
"@chakra-ui/styled-system@2.4.0":
version "2.4.0"
resolved "https://registry.yarnpkg.com/@chakra-ui/styled-system/-/styled-system-2.4.0.tgz#4b50079606331e4e8fda7ea59da9db51b446d40c"
integrity sha512-G4HpbFERq4C1cBwKNDNkpCiliOICLXjYwKI/e/6hxNY+GlPxt8BCzz3uhd3vmEoG2vRM4qjidlVjphhWsf6vRQ==
dependencies:
csstype "^3.0.11"
lodash.mergewith "4.6.2"
"@chakra-ui/switch@2.0.11":
version "2.0.11"
resolved "https://registry.yarnpkg.com/@chakra-ui/switch/-/switch-2.0.11.tgz#57117417d1bb072f506c8c30e1a961ee7f78496b"
integrity sha512-gY8OGBnoPosZpq7dDNVf432t67pTc/cz5VkGhbtER7bbjXSoXe0DAiAYL+HT2kD7mbTJQzzHK/y0St0WimR1Mw==
"@chakra-ui/switch@2.0.17":
version "2.0.17"
resolved "https://registry.yarnpkg.com/@chakra-ui/switch/-/switch-2.0.17.tgz#1d6904b6cde2469212bbd8311b749b96c653a9a3"
integrity sha512-BQabfC6qYi5xBJvEFPzKq0yl6fTtTNNEHTid5r7h0PWcCnAiHwQJTpQRpxp+AjK569LMLtTXReTZvNBrzEwOrA==
dependencies:
"@chakra-ui/checkbox" "2.1.8"
"@chakra-ui/checkbox" "2.2.5"
"@chakra-ui/system@2.2.9":
version "2.2.9"
resolved "https://registry.yarnpkg.com/@chakra-ui/system/-/system-2.2.9.tgz#d6e7dfb9a954b8ab03c28e14c69aad56a9fcffbd"
integrity sha512-SyTeIGm+goyYK8vqX4dU6oeLhxUAeGI3Cl+mxA+aiKIX01YTALhTWhpbrsuMYBevV+l9EGK12egPUQE+Mo3WlQ==
"@chakra-ui/system@2.3.4":
version "2.3.4"
resolved "https://registry.yarnpkg.com/@chakra-ui/system/-/system-2.3.4.tgz#425bf7eebf61bd92aa68f60a6b62c380274fbe4e"
integrity sha512-/2m8hFfFzOMO2OlwHxTWqINOBJMjxWwU5V/AcB7C0qS51Dcj9c7kupilM6QdqiOLLdMS7mIVRSYr8jn8gMw9fA==
dependencies:
"@chakra-ui/color-mode" "2.1.7"
"@chakra-ui/react-utils" "2.0.6"
"@chakra-ui/styled-system" "2.3.1"
"@chakra-ui/utils" "2.0.9"
"@chakra-ui/color-mode" "2.1.10"
"@chakra-ui/react-utils" "2.0.9"
"@chakra-ui/styled-system" "2.4.0"
"@chakra-ui/theme-utils" "2.0.5"
"@chakra-ui/utils" "2.0.12"
react-fast-compare "3.2.0"
"@chakra-ui/table@2.0.9":
version "2.0.9"
resolved "https://registry.yarnpkg.com/@chakra-ui/table/-/table-2.0.9.tgz#2ddb0202e8146e517bf602e62195d13fee8f1b0a"
integrity sha512-XRz6+x4dMeQX3xyViyG2H/P1STI/2vwvgU2cjzzwS+5fZ2JdGaTgYzBb+IZoH9agEq1Ma3rlKMUPDrRCFb7kLQ==
dependencies:
"@chakra-ui/react-context" "2.0.3"
"@chakra-ui/tabs@2.1.1":
version "2.1.1"
resolved "https://registry.yarnpkg.com/@chakra-ui/tabs/-/tabs-2.1.1.tgz#0fb540782c2e4122b63a203fc1f04eff850f2c0e"
integrity sha512-xA+vwqpAHb0nBLrkiO5Lea2UDGROyAIBqsyp/8XXXEr6eKxtNe1I6WJPbDQy0aazB2ToAA0R6fT34HjLaXP8MQ==
dependencies:
"@chakra-ui/clickable" "2.0.9"
"@chakra-ui/descendant" "3.0.9"
"@chakra-ui/lazy-utils" "2.0.1"
"@chakra-ui/react-children-utils" "2.0.1"
"@chakra-ui/react-context" "2.0.3"
"@chakra-ui/react-use-controllable-state" "2.0.3"
"@chakra-ui/react-use-merge-refs" "2.0.3"
"@chakra-ui/react-use-safe-layout-effect" "2.0.1"
"@chakra-ui/tag@2.0.9":
version "2.0.9"
resolved "https://registry.yarnpkg.com/@chakra-ui/tag/-/tag-2.0.9.tgz#bf8530aa766bd6b9196d374ff75b0b1ce62cd0d3"
integrity sha512-NKARwhsZ04t2vkrdRhNcakEiVtg1q44yUUsDw2Jwdu4idAWQupZGGochQI2Ac4T2MI1b66zQUkaGnm3l1mhTtg==
"@chakra-ui/table@2.0.12":
version "2.0.12"
resolved "https://registry.yarnpkg.com/@chakra-ui/table/-/table-2.0.12.tgz#387653cf660318b13086b6497aca2b671deb055a"
integrity sha512-TSxzpfrOoB+9LTdNTMnaQC6OTsp36TlCRxJ1+1nAiCmlk+m+FiNzTQsmBalDDhc29rm+6AdRsxSPsjGWB8YVwg==
dependencies:
"@chakra-ui/icon" "3.0.9"
"@chakra-ui/react-context" "2.0.3"
"@chakra-ui/react-context" "2.0.5"
"@chakra-ui/textarea@2.0.10":
version "2.0.10"
resolved "https://registry.yarnpkg.com/@chakra-ui/textarea/-/textarea-2.0.10.tgz#dbbc8df8adddb488d0ee97164917e7be33d6b247"
integrity sha512-HSo0EPsY8XKGA+Af6jTob1oe1T6NKZwgjLmX0binK3MMM9pDTXsUTw8GD0g971lxw9oktVMLK/O9QVAgVAm5mw==
"@chakra-ui/tabs@2.1.5":
version "2.1.5"
resolved "https://registry.yarnpkg.com/@chakra-ui/tabs/-/tabs-2.1.5.tgz#827b0e71eb173c09c31dcbbe05fc1146f4267229"
integrity sha512-XmnKDclAJe0FoW4tdC8AlnZpPN5fcj92l4r2sqiL9WyYVEM71hDxZueETIph/GTtfMelG7Z8e5vBHP4rh1RT5g==
dependencies:
"@chakra-ui/clickable" "2.0.11"
"@chakra-ui/descendant" "3.0.11"
"@chakra-ui/lazy-utils" "2.0.3"
"@chakra-ui/react-children-utils" "2.0.4"
"@chakra-ui/react-context" "2.0.5"
"@chakra-ui/react-use-controllable-state" "2.0.6"
"@chakra-ui/react-use-merge-refs" "2.0.5"
"@chakra-ui/react-use-safe-layout-effect" "2.0.3"
"@chakra-ui/tag@2.0.13":
version "2.0.13"
resolved "https://registry.yarnpkg.com/@chakra-ui/tag/-/tag-2.0.13.tgz#ad7349bfcdd5642d3894fadb43728acc0f061101"
integrity sha512-W1urf+tvGMt6J3cc31HudybYSl+B5jYUP5DJxzXM9p+n3JrvXWAo4D6LmpLBHY5zT2mNne14JF1rVeRcG4Rtdg==
dependencies:
"@chakra-ui/form-control" "2.0.9"
"@chakra-ui/icon" "3.0.13"
"@chakra-ui/react-context" "2.0.5"
"@chakra-ui/theme-tools@2.0.11":
version "2.0.11"
resolved "https://registry.yarnpkg.com/@chakra-ui/theme-tools/-/theme-tools-2.0.11.tgz#de97b422799627b5a514ae424ca08c1d348bc2a5"
integrity sha512-0Juf98bAyOgnBeQ39nMKWqRsOxZDw75BbAB8o0oVyjhYVS1wJh7tFX1ZRV8N/+AN6fuRXEznZPpyUh3J+ZTiRg==
"@chakra-ui/textarea@2.0.14":
version "2.0.14"
resolved "https://registry.yarnpkg.com/@chakra-ui/textarea/-/textarea-2.0.14.tgz#a79a3fdd850a3303e6ebb68d64b7c334de03da4d"
integrity sha512-r8hF1rCi+GseLtY/IGeVWXFN0Uve2b820UQumRj4qxj7PsPqw1hFg7Cecbbb9zwF38K/m+D3IdwFeJzI1MtgRA==
dependencies:
"@chakra-ui/anatomy" "2.0.6"
"@ctrl/tinycolor" "^3.4.0"
"@chakra-ui/form-control" "2.0.13"
"@chakra-ui/theme-tools@^2.0.2":
version "2.0.12"
resolved "https://registry.yarnpkg.com/@chakra-ui/theme-tools/-/theme-tools-2.0.12.tgz#b29d9fb626d35e3b00f532c64f95ea261d8f6997"
integrity sha512-mnMlKSmXkCjHUJsKWmJbgBTGF2vnLaMLv1ihkBn5eQcCubMQrBLTiMAEFl5pZdzuHItU6QdnLGA10smcXbNl0g==
"@chakra-ui/theme-tools@2.0.14", "@chakra-ui/theme-tools@^2.0.14":
version "2.0.14"
resolved "https://registry.yarnpkg.com/@chakra-ui/theme-tools/-/theme-tools-2.0.14.tgz#6c523284ab384ca57a3aef1fcfa7c32ed357fbde"
integrity sha512-lVcDmq5pyU0QbsIFKjt/iVUFDap7di2QHvPvGChA1YSjtg1PtuUi+BxEXWzp3Nfgw/N4rMvlBs+S0ynJypdwbg==
dependencies:
"@chakra-ui/anatomy" "2.0.7"
"@ctrl/tinycolor" "^3.4.0"
"@chakra-ui/anatomy" "2.1.0"
color2k "^2.0.0"
"@chakra-ui/theme@2.1.10":
version "2.1.10"
resolved "https://registry.yarnpkg.com/@chakra-ui/theme/-/theme-2.1.10.tgz#3bd3e212e50f902d65165ffe408d63c1554b6cf2"
integrity sha512-7V1ReVD2B29amMO9LdRrNsyzAacXzC4Xb7P/0RDDSPWo0rZxPPrPtfhiuPi+bYqk6NN9tJ8dApVhSY+hgIH//A==
"@chakra-ui/theme-utils@2.0.5":
version "2.0.5"
resolved "https://registry.yarnpkg.com/@chakra-ui/theme-utils/-/theme-utils-2.0.5.tgz#ad1e53fc7f71326d15b9b01a157c7e2a029f3dda"
integrity sha512-QQowSM8fvQlTmT0w9wtqUlWOB4i+9eA7P4XRm4bfhBMZ7XpK4ctV95sPeGqaXVccsz5m0q1AuGWa+j6eMCbrrg==
dependencies:
"@chakra-ui/anatomy" "2.0.6"
"@chakra-ui/theme-tools" "2.0.11"
"@chakra-ui/styled-system" "2.4.0"
"@chakra-ui/theme" "2.2.2"
lodash.mergewith "4.6.2"
"@chakra-ui/toast@3.0.9":
version "3.0.9"
resolved "https://registry.yarnpkg.com/@chakra-ui/toast/-/toast-3.0.9.tgz#25d00bb2d205047062c9c111600cef5e858c1003"
integrity sha512-FbGiza882r5cseN3MZ6uCjGRgtoE8ZHMckQfkmM9Pbhh80I7WMWkWBlr7TjQo6q+EcZ+/4YRHrAw/TRp78v5uw==
"@chakra-ui/theme@2.2.2":
version "2.2.2"
resolved "https://registry.yarnpkg.com/@chakra-ui/theme/-/theme-2.2.2.tgz#5ea69adde78ee6ea59f9dce674947ed8be2ebc62"
integrity sha512-7DlOQiXmnaqYyqXwqmfFSCWGkUonuqmNC5mmUCwxI435KgHNCaE2bIm6DI7N2NcIcuVcfc8Vn0UqrDoGU3zJBg==
dependencies:
"@chakra-ui/alert" "2.0.9"
"@chakra-ui/close-button" "2.0.9"
"@chakra-ui/portal" "2.0.9"
"@chakra-ui/react-use-timeout" "2.0.1"
"@chakra-ui/react-use-update-effect" "2.0.3"
"@chakra-ui/theme" "2.1.10"
"@chakra-ui/anatomy" "2.1.0"
"@chakra-ui/theme-tools" "2.0.14"
"@chakra-ui/tooltip@2.0.10":
version "2.0.10"
resolved "https://registry.yarnpkg.com/@chakra-ui/tooltip/-/tooltip-2.0.10.tgz#2166753f9f246dd217d3170fd85f95a86392d9b6"
integrity sha512-pBILBdZoux2K3EW9V6JuyZYUWz2/Y7oYCVO6AwNOesiEBGAONyzoDwFV728EzPEHe9e+YBcKOSZ9tEpDdrzHMA==
"@chakra-ui/toast@4.0.5":
version "4.0.5"
resolved "https://registry.yarnpkg.com/@chakra-ui/toast/-/toast-4.0.5.tgz#b501d7160ab67d9bcfa4df5c8b763d4aa789731e"
integrity sha512-avJKRiZZACi6v04cUc2uRcmta3gAes67HGkQhq5sHG/VV2cH9wXV2NMT3DolT8WiU9j7g8lWKxWe7bqFFrX+wA==
dependencies:
"@chakra-ui/alert" "2.0.13"
"@chakra-ui/close-button" "2.0.13"
"@chakra-ui/portal" "2.0.12"
"@chakra-ui/react-use-timeout" "2.0.3"
"@chakra-ui/react-use-update-effect" "2.0.5"
"@chakra-ui/styled-system" "2.4.0"
"@chakra-ui/theme" "2.2.2"
"@chakra-ui/tooltip@2.2.3":
version "2.2.3"
resolved "https://registry.yarnpkg.com/@chakra-ui/tooltip/-/tooltip-2.2.3.tgz#92c9ed224e4c16839310acd0759c0017caec3134"
integrity sha512-yOne9ofFYfW2XHsbCEPWgLUTnHKm5z21f/cPjwEqtmvCS7aTCOLFiwz2ckRS8yJbIAy+mw0UG6jQsblYKgXj4A==
dependencies:
"@chakra-ui/popper" "3.0.7"
"@chakra-ui/portal" "2.0.9"
"@chakra-ui/react-types" "2.0.3"
"@chakra-ui/react-use-disclosure" "2.0.3"
"@chakra-ui/react-use-event-listener" "2.0.3"
"@chakra-ui/react-use-merge-refs" "2.0.3"
"@chakra-ui/popper" "3.0.10"
"@chakra-ui/portal" "2.0.12"
"@chakra-ui/react-types" "2.0.5"
"@chakra-ui/react-use-disclosure" "2.0.6"
"@chakra-ui/react-use-event-listener" "2.0.5"
"@chakra-ui/react-use-merge-refs" "2.0.5"
"@chakra-ui/transition@2.0.9":
version "2.0.9"
resolved "https://registry.yarnpkg.com/@chakra-ui/transition/-/transition-2.0.9.tgz#1967fd77f44b57681a9efe4e87561c82420cd2a2"
integrity sha512-cVfKdZl128AEj0LDS8M9dzXao4wmTVj3gRJBnm91Qcg243Pm8OlgIBNbHEwsq/Fps+PsN431BtEGfL4w79wQEA==
"@chakra-ui/transition@2.0.12":
version "2.0.12"
resolved "https://registry.yarnpkg.com/@chakra-ui/transition/-/transition-2.0.12.tgz#876c6ed24e442a720a8570490a93cb1f87008700"
integrity sha512-ff6eU+m08ccYfCkk0hKfY/XlmGxCrfbBgsKgV4mirZ4SKUL1GVye8CYuHwWQlBJo+8s0yIpsTNxAuX4n/cW9/w==
"@chakra-ui/utils@2.0.9":
version "2.0.9"
resolved "https://registry.yarnpkg.com/@chakra-ui/utils/-/utils-2.0.9.tgz#1af3882b31fb46e0a411998d8e3607656f8d5043"
integrity sha512-7ct5562Jw6pZdtj63XfUkEUXXsCCVqdqIXyLtQ9VgOKtRQWwDxzc8uPI5Zjdw9AleEITZFUH8TNKWn75nm54kQ==
"@chakra-ui/utils@2.0.12":
version "2.0.12"
resolved "https://registry.yarnpkg.com/@chakra-ui/utils/-/utils-2.0.12.tgz#5ab8a4529fca68d9f8c6722004f6a5129b0b75e9"
integrity sha512-1Z1MgsrfMQhNejSdrPJk8v5J4gCefHo+1wBmPPHTz5bGEbAAbZ13aXAfXy8w0eFy0Nvnawn0EHW7Oynp/MdH+Q==
dependencies:
"@types/lodash.mergewith" "4.6.6"
css-box-model "1.2.1"
framesync "5.3.0"
lodash.mergewith "4.6.2"
"@chakra-ui/visually-hidden@2.0.9":
version "2.0.9"
resolved "https://registry.yarnpkg.com/@chakra-ui/visually-hidden/-/visually-hidden-2.0.9.tgz#b43a3dd0bc1108954ad0eeb50d0261887ab5e31c"
integrity sha512-PkNxrRGp9H3bdqEaoo8XGt/AL9UuGRTom0/9XJa+G/Dj8Cy1sDuamOWk3pN/ZQs46RokfK9Uh5LqPY5dwSDweg==
"@chakra-ui/visually-hidden@2.0.13":
version "2.0.13"
resolved "https://registry.yarnpkg.com/@chakra-ui/visually-hidden/-/visually-hidden-2.0.13.tgz#6553467d93f206d17716bcbe6e895a84eef87472"
integrity sha512-sDEeeEjLfID333EC46NdCbhK2HyMXlpl5HzcJjuwWIpyVz4E1gKQ9hlwpq6grijvmzeSywQ5D3tTwUrvZck4KQ==
"@cspotcode/source-map-support@^0.8.0":
version "0.8.1"
......@@ -1812,44 +1829,39 @@
dependencies:
"@jridgewell/trace-mapping" "0.3.9"
"@ctrl/tinycolor@^3.4.0":
version "3.4.1"
resolved "https://registry.yarnpkg.com/@ctrl/tinycolor/-/tinycolor-3.4.1.tgz#75b4c27948c81e88ccd3a8902047bcd797f38d32"
integrity sha512-ej5oVy6lykXsvieQtqZxCOaLT+xD4+QNarq78cIYISHmZXshCvROLudpQN3lfL8G0NL7plMSSK+zlyvCaIJ4Iw==
"@cush/relative@^1.0.0":
version "1.0.0"
resolved "https://registry.yarnpkg.com/@cush/relative/-/relative-1.0.0.tgz#8cd1769bf9bde3bb27dac356b1bc94af40f6cc16"
integrity sha512-RpfLEtTlyIxeNPGKcokS+p3BZII/Q3bYxryFRglh5H3A3T8q9fsLYm72VYAMEOOIBLEa8o93kFLiBDUWKrwXZA==
"@emotion/babel-plugin@^11.10.0":
version "11.10.2"
resolved "https://registry.yarnpkg.com/@emotion/babel-plugin/-/babel-plugin-11.10.2.tgz#879db80ba622b3f6076917a1e6f648b1c7d008c7"
integrity sha512-xNQ57njWTFVfPAc3cjfuaPdsgLp5QOSuRsj9MA6ndEhH/AzuZM86qIQzt6rq+aGBwj3n5/TkLmU5lhAfdRmogA==
"@emotion/babel-plugin@^11.10.5":
version "11.10.5"
resolved "https://registry.yarnpkg.com/@emotion/babel-plugin/-/babel-plugin-11.10.5.tgz#65fa6e1790ddc9e23cc22658a4c5dea423c55c3c"
integrity sha512-xE7/hyLHJac7D2Ve9dKroBBZqBT7WuPQmWcq7HSGb84sUuP4mlOWoB8dvVfD9yk5DHkU1m6RW7xSoDtnQHNQeA==
dependencies:
"@babel/helper-module-imports" "^7.16.7"
"@babel/plugin-syntax-jsx" "^7.17.12"
"@babel/runtime" "^7.18.3"
"@emotion/hash" "^0.9.0"
"@emotion/memoize" "^0.8.0"
"@emotion/serialize" "^1.1.0"
"@emotion/serialize" "^1.1.1"
babel-plugin-macros "^3.1.0"
convert-source-map "^1.5.0"
escape-string-regexp "^4.0.0"
find-root "^1.1.0"
source-map "^0.5.7"
stylis "4.0.13"
stylis "4.1.3"
"@emotion/cache@^11.10.0":
version "11.10.3"
resolved "https://registry.yarnpkg.com/@emotion/cache/-/cache-11.10.3.tgz#c4f67904fad10c945fea5165c3a5a0583c164b87"
integrity sha512-Psmp/7ovAa8appWh3g51goxu/z3iVms7JXOreq136D8Bbn6dYraPnmL6mdM8GThEx9vwSn92Fz+mGSjBzN8UPQ==
"@emotion/cache@^11.10.5":
version "11.10.5"
resolved "https://registry.yarnpkg.com/@emotion/cache/-/cache-11.10.5.tgz#c142da9351f94e47527ed458f7bbbbe40bb13c12"
integrity sha512-dGYHWyzTdmK+f2+EnIGBpkz1lKc4Zbj2KHd4cX3Wi8/OWr5pKslNjc3yABKH4adRGCvSX4VDC0i04mrrq0aiRA==
dependencies:
"@emotion/memoize" "^0.8.0"
"@emotion/sheet" "^1.2.0"
"@emotion/sheet" "^1.2.1"
"@emotion/utils" "^1.2.0"
"@emotion/weak-memoize" "^0.3.0"
stylis "4.0.13"
stylis "4.1.3"
"@emotion/hash@^0.9.0":
version "0.9.0"
......@@ -1880,24 +1892,24 @@
resolved "https://registry.yarnpkg.com/@emotion/memoize/-/memoize-0.8.0.tgz#f580f9beb67176fa57aae70b08ed510e1b18980f"
integrity sha512-G/YwXTkv7Den9mXDO7AhLWkE3q+I92B+VqAE+dYG4NGPaHZGvt3G8Q0p9vmE+sq7rTGphUbAvmQ9YpbfMQGGlA==
"@emotion/react@^11":
version "11.10.4"
resolved "https://registry.yarnpkg.com/@emotion/react/-/react-11.10.4.tgz#9dc6bccbda5d70ff68fdb204746c0e8b13a79199"
integrity sha512-j0AkMpr6BL8gldJZ6XQsQ8DnS9TxEQu1R+OGmDZiWjBAJtCcbt0tS3I/YffoqHXxH6MjgI7KdMbYKw3MEiU9eA==
"@emotion/react@^11.10.4":
version "11.10.5"
resolved "https://registry.yarnpkg.com/@emotion/react/-/react-11.10.5.tgz#95fff612a5de1efa9c0d535384d3cfa115fe175d"
integrity sha512-TZs6235tCJ/7iF6/rvTaOH4oxQg2gMAcdHemjwLKIjKz4rRuYe1HJ2TQJKnAcRAfOUDdU8XoDadCe1rl72iv8A==
dependencies:
"@babel/runtime" "^7.18.3"
"@emotion/babel-plugin" "^11.10.0"
"@emotion/cache" "^11.10.0"
"@emotion/serialize" "^1.1.0"
"@emotion/babel-plugin" "^11.10.5"
"@emotion/cache" "^11.10.5"
"@emotion/serialize" "^1.1.1"
"@emotion/use-insertion-effect-with-fallbacks" "^1.0.0"
"@emotion/utils" "^1.2.0"
"@emotion/weak-memoize" "^0.3.0"
hoist-non-react-statics "^3.3.1"
"@emotion/serialize@^1.1.0":
version "1.1.0"
resolved "https://registry.yarnpkg.com/@emotion/serialize/-/serialize-1.1.0.tgz#b1f97b1011b09346a40e9796c37a3397b4ea8ea8"
integrity sha512-F1ZZZW51T/fx+wKbVlwsfchr5q97iW8brAnXmsskz4d0hVB4O3M/SiA3SaeH06x02lSNzkkQv+n3AX3kCXKSFA==
"@emotion/serialize@^1.1.1":
version "1.1.1"
resolved "https://registry.yarnpkg.com/@emotion/serialize/-/serialize-1.1.1.tgz#0595701b1902feded8a96d293b26be3f5c1a5cf0"
integrity sha512-Zl/0LFggN7+L1liljxXdsVSVlg6E/Z/olVWpfxUTxOAmi8NU7YoeWeLfi1RmnB2TATHoaWwIBRoL+FvAJiTUQA==
dependencies:
"@emotion/hash" "^0.9.0"
"@emotion/memoize" "^0.8.0"
......@@ -1905,20 +1917,20 @@
"@emotion/utils" "^1.2.0"
csstype "^3.0.2"
"@emotion/sheet@^1.2.0":
version "1.2.0"
resolved "https://registry.yarnpkg.com/@emotion/sheet/-/sheet-1.2.0.tgz#771b1987855839e214fc1741bde43089397f7be5"
integrity sha512-OiTkRgpxescko+M51tZsMq7Puu/KP55wMT8BgpcXVG2hqXc0Vo0mfymJ/Uj24Hp0i083ji/o0aLddh08UEjq8w==
"@emotion/sheet@^1.2.1":
version "1.2.1"
resolved "https://registry.yarnpkg.com/@emotion/sheet/-/sheet-1.2.1.tgz#0767e0305230e894897cadb6c8df2c51e61a6c2c"
integrity sha512-zxRBwl93sHMsOj4zs+OslQKg/uhF38MB+OMKoCrVuS0nyTkqnau+BM3WGEoOptg9Oz45T/aIGs1qbVAsEFo3nA==
"@emotion/styled@^11":
version "11.10.4"
resolved "https://registry.yarnpkg.com/@emotion/styled/-/styled-11.10.4.tgz#e93f84a4d54003c2acbde178c3f97b421fce1cd4"
integrity sha512-pRl4R8Ez3UXvOPfc2bzIoV8u9P97UedgHS4FPX594ntwEuAMA114wlaHvOK24HB48uqfXiGlYIZYCxVJ1R1ttQ==
"@emotion/styled@^11.10.4":
version "11.10.5"
resolved "https://registry.yarnpkg.com/@emotion/styled/-/styled-11.10.5.tgz#1fe7bf941b0909802cb826457e362444e7e96a79"
integrity sha512-8EP6dD7dMkdku2foLoruPCNkRevzdcBaY6q0l0OsbyJK+x8D9HWjX27ARiSIKNF634hY9Zdoedh8bJCiva8yZw==
dependencies:
"@babel/runtime" "^7.18.3"
"@emotion/babel-plugin" "^11.10.0"
"@emotion/babel-plugin" "^11.10.5"
"@emotion/is-prop-valid" "^1.2.0"
"@emotion/serialize" "^1.1.0"
"@emotion/serialize" "^1.1.1"
"@emotion/use-insertion-effect-with-fallbacks" "^1.0.0"
"@emotion/utils" "^1.2.0"
......@@ -2837,6 +2849,16 @@
"@sentry/utils" "7.15.0"
tslib "^1.9.3"
"@sentry/browser@7.24.2":
version "7.24.2"
resolved "https://registry.yarnpkg.com/@sentry/browser/-/browser-7.24.2.tgz#1e514448cd07ff7da78d02797149ecc1922ffcc2"
integrity sha512-X6NbQT0Dp+h54j73TPLgWf3yyLyTZGJI5WQSGEsNIroqhVzD3UF8M+E+3roYpSJDDyYdfuM+WBme+MYkmeqHIw==
dependencies:
"@sentry/core" "7.24.2"
"@sentry/types" "7.24.2"
"@sentry/utils" "7.24.2"
tslib "^1.9.3"
"@sentry/cli@^1.74.4":
version "1.74.5"
resolved "https://registry.yarnpkg.com/@sentry/cli/-/cli-1.74.5.tgz#4a5c622913087c9ab6f82994da9a7526423779b8"
......@@ -2859,6 +2881,15 @@
"@sentry/utils" "7.15.0"
tslib "^1.9.3"
"@sentry/core@7.24.2":
version "7.24.2"
resolved "https://registry.yarnpkg.com/@sentry/core/-/core-7.24.2.tgz#d3b69cc9c5703a4b35be1d648379804b099894e6"
integrity sha512-CDfrVvr3PQ0qImJv7/6yN/5hxhwxy1HicxTL9K5RwSDoXqgK3kUGv/WmTvPNIVB2RQKodLwzS2T52NFRxRoqNw==
dependencies:
"@sentry/types" "7.24.2"
"@sentry/utils" "7.24.2"
tslib "^1.9.3"
"@sentry/integrations@7.15.0":
version "7.15.0"
resolved "https://registry.yarnpkg.com/@sentry/integrations/-/integrations-7.15.0.tgz#c2af3a2d2c0667216d76d829f24c5125b110e6c8"
......@@ -2900,7 +2931,7 @@
lru_map "^0.3.3"
tslib "^1.9.3"
"@sentry/react@7.15.0", "@sentry/react@^7.13.0":
"@sentry/react@7.15.0":
version "7.15.0"
resolved "https://registry.yarnpkg.com/@sentry/react/-/react-7.15.0.tgz#441ed851ca64afeef10abcb00302e0c95846404e"
integrity sha512-a+5+Og93YPtWSCmOFYa/qzrbvfgIZXShJk1bsIaEI0KdltTOVJBdwvLQc8OiIOBe/CMDVCmK1t2DqiWfOWj41w==
......@@ -2911,7 +2942,18 @@
hoist-non-react-statics "^3.3.2"
tslib "^1.9.3"
"@sentry/tracing@7.15.0", "@sentry/tracing@^7.13.0":
"@sentry/react@^7.24.0":
version "7.24.2"
resolved "https://registry.yarnpkg.com/@sentry/react/-/react-7.24.2.tgz#cc471cd75727c518f8942d8cf9a7777752fcf4b5"
integrity sha512-NK4/SDIWyQVYdi/EPfHfp7d0+flGNHbBuqV/GG/+CLSekUCuACsczSEWgMSyEad4ptbF9850yt5WN15oL5vAXg==
dependencies:
"@sentry/browser" "7.24.2"
"@sentry/types" "7.24.2"
"@sentry/utils" "7.24.2"
hoist-non-react-statics "^3.3.2"
tslib "^1.9.3"
"@sentry/tracing@7.15.0":
version "7.15.0"
resolved "https://registry.yarnpkg.com/@sentry/tracing/-/tracing-7.15.0.tgz#ea516957b2ed39f389c21132f433b6470d54b465"
integrity sha512-c0Y3+z6EWsc+EJsfBcRtc58ugkWYa6+6KTu3ceMkx2ZgZTCmRUuzAb7yodMt/gwezBsxzq706fnQivx1lQgzlQ==
......@@ -2921,11 +2963,26 @@
"@sentry/utils" "7.15.0"
tslib "^1.9.3"
"@sentry/tracing@^7.24.0":
version "7.24.2"
resolved "https://registry.yarnpkg.com/@sentry/tracing/-/tracing-7.24.2.tgz#d9f0cf7d3055283a50fd38b14e0891b729c5d107"
integrity sha512-rK1HUeCLM27DGGah1+5DN0C9Y4g9dnyMU5rdrRxGQGqxIJiwzHYwJI9xoNoAVMmt8jqFliDEpYvh2jsW8593IA==
dependencies:
"@sentry/core" "7.24.2"
"@sentry/types" "7.24.2"
"@sentry/utils" "7.24.2"
tslib "^1.9.3"
"@sentry/types@7.15.0":
version "7.15.0"
resolved "https://registry.yarnpkg.com/@sentry/types/-/types-7.15.0.tgz#50c57c924993d4dd16b43172d310c66384d17463"
integrity sha512-MN9haDRh9ZOsTotoDTHu2BT3sT8Vs1F0alhizUpDyjN2YgBCqR6JV+AbAE1XNHwS2+5zbppch1PwJUVeE58URQ==
"@sentry/types@7.24.2":
version "7.24.2"
resolved "https://registry.yarnpkg.com/@sentry/types/-/types-7.24.2.tgz#2ef728db8eea14de8ba916896837d0cbeb3d28da"
integrity sha512-x2LEnKBPzUVzTGspvB0CjZmt1dWeJsLVHGeDKPUMUm004nIscFCxJsmYefqaJQdaIUMqDit5ApwcmKchuK6VKQ==
"@sentry/utils@7.15.0":
version "7.15.0"
resolved "https://registry.yarnpkg.com/@sentry/utils/-/utils-7.15.0.tgz#cda642a353a58fd6631979c1e5986788e6db6c43"
......@@ -2934,6 +2991,14 @@
"@sentry/types" "7.15.0"
tslib "^1.9.3"
"@sentry/utils@7.24.2":
version "7.24.2"
resolved "https://registry.yarnpkg.com/@sentry/utils/-/utils-7.24.2.tgz#7120a8d36bd1d05043c902a0f22fbc2012fe2116"
integrity sha512-VuuYEF39v43Qk6YZMid8Em/N0HqCsS5ItuTSvunMtBai2dzDAIkJ2LqemF95wWFAXrzpLy4Nx3QyGVHayMn31A==
dependencies:
"@sentry/types" "7.24.2"
tslib "^1.9.3"
"@sentry/webpack-plugin@1.19.0":
version "1.19.0"
resolved "https://registry.yarnpkg.com/@sentry/webpack-plugin/-/webpack-plugin-1.19.0.tgz#2b134318f1552ba7f3e3f9c83c71a202095f7a44"
......@@ -3533,6 +3598,13 @@
resolved "https://registry.yarnpkg.com/@types/prop-types/-/prop-types-15.7.5.tgz#5f19d2b85a98e9558036f6a3cacc8819420f05cf"
integrity sha512-JCB8C6SnDoQf0cNycqd/35A7MjcnK+ZTqE7judS6o7utxUCg6imJg3QK2qzHKszlTjcj2cn+NwMB2i96ubpj7w==
"@types/qrcode@^1.5.0":
version "1.5.0"
resolved "https://registry.yarnpkg.com/@types/qrcode/-/qrcode-1.5.0.tgz#6a98fe9a9a7b2a9a3167b6dde17eff999eabe40b"
integrity sha512-x5ilHXRxUPIMfjtM+1vf/GPTRWZ81nqscursm5gMznJeK9M0YnZ1c3bEvRLQ0zSSgedLx1J6MGL231ObQGGhaA==
dependencies:
"@types/node" "*"
"@types/react-dom@18.0.5":
version "18.0.5"
resolved "https://registry.yarnpkg.com/@types/react-dom/-/react-dom-18.0.5.tgz#330b2d472c22f796e5531446939eacef8378444a"
......@@ -4220,7 +4292,7 @@ callsites@^3.0.0:
resolved "https://registry.yarnpkg.com/callsites/-/callsites-3.1.0.tgz#b3630abd8943432f54b3f0519238e33cd7df2f73"
integrity sha512-P8BjAsXvZS+VIDUI11hHCQEv74YT67YUi5JJFNWIqL235sBmjX4+qx9Muvls5ivyNENctx46xQLQ3aTuE7ssaQ==
camelcase@^5.3.1:
camelcase@^5.0.0, camelcase@^5.3.1:
version "5.3.1"
resolved "https://registry.yarnpkg.com/camelcase/-/camelcase-5.3.1.tgz#e3c9b31569e106811df242f715725a1f4c494320"
integrity sha512-L28STB170nwWS63UjtlEOE3dldQApaJXZkOI1uMFfzf3rRuPegHaHesyee+YxQ+W6SvRDQV6UrdOdRiR153wJg==
......@@ -4303,6 +4375,15 @@ cli-truncate@^3.1.0:
slice-ansi "^5.0.0"
string-width "^5.0.0"
cliui@^6.0.0:
version "6.0.0"
resolved "https://registry.yarnpkg.com/cliui/-/cliui-6.0.0.tgz#511d702c0c4e41ca156d7d0e96021f23e13225b1"
integrity sha512-t6wbgtoCXvAzst7QgXxJYqPt0usEfbgQdftEPbLL/cvv6HPE5VgvqCuAIDR0NgU52ds6rFwqrgakNLrHEjCbrQ==
dependencies:
string-width "^4.2.0"
strip-ansi "^6.0.0"
wrap-ansi "^6.2.0"
cliui@^8.0.1:
version "8.0.1"
resolved "https://registry.yarnpkg.com/cliui/-/cliui-8.0.1.tgz#0c04b075db02cbfe60dc8e6cf2f5486b1a3608aa"
......@@ -4351,6 +4432,11 @@ color-name@~1.1.4:
resolved "https://registry.yarnpkg.com/color-name/-/color-name-1.1.4.tgz#c2a09a87acbde69543de6f63fa3995c826c536a2"
integrity sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==
color2k@^2.0.0:
version "2.0.0"
resolved "https://registry.yarnpkg.com/color2k/-/color2k-2.0.0.tgz#86992c82e248c29f524023ed0822bc152c4fa670"
integrity sha512-DWX9eXOC4fbJNiuvdH4QSHvvfLWyFo9TuFp7V9OzdsbPAdrWAuYc8qvFP2bIQ/LKh4LrAVnJ6vhiQYPvAHdtTg==
colorette@^2.0.16, colorette@^2.0.17, colorette@^2.0.7:
version "2.0.19"
resolved "https://registry.yarnpkg.com/colorette/-/colorette-2.0.19.tgz#cdf044f47ad41a0f4b56b3a0d5b4e6e1a2d5a798"
......@@ -4823,6 +4909,11 @@ debug@^3.2.7:
dependencies:
ms "^2.1.1"
decamelize@^1.2.0:
version "1.2.0"
resolved "https://registry.yarnpkg.com/decamelize/-/decamelize-1.2.0.tgz#f6534d15148269b20352e7bee26f501f9a191290"
integrity sha512-z2S+W9X73hAUUki+N+9Za2lBlun89zigOyGrsax+KUQ6wKW4ZoWpEYBkGhQjwAjjDCkWxhY0VKEhk8wzY7F5cA==
decimal.js@^10.4.1:
version "10.4.2"
resolved "https://registry.yarnpkg.com/decimal.js/-/decimal.js-10.4.2.tgz#0341651d1d997d86065a2ce3a441fbd0d8e8b98e"
......@@ -4914,6 +5005,11 @@ diff@^4.0.1:
resolved "https://registry.yarnpkg.com/diff/-/diff-4.0.2.tgz#60f3aecb89d5fae520c11aa19efc2bb982aade7d"
integrity sha512-58lmxKSA4BNyLz+HHMUzlOEpg09FV+ev6ZMe3vJihgdxzgcwZ8VoEEPmALCZG9LmqfVoNMMKpttIYTVG6uDY7A==
dijkstrajs@^1.0.1:
version "1.0.2"
resolved "https://registry.yarnpkg.com/dijkstrajs/-/dijkstrajs-1.0.2.tgz#2e48c0d3b825462afe75ab4ad5e829c8ece36257"
integrity sha512-QV6PMaHTCNmKSeP6QoXhVTw9snc9VD8MulTT0Bd99Pacp4SS1cjcrYPgBPmibqKVtMJJfqC6XvOXgPMEEPH/fg==
dir-glob@^3.0.1:
version "3.0.1"
resolved "https://registry.yarnpkg.com/dir-glob/-/dir-glob-3.0.1.tgz#56dbf73d992a4a93ba1584f4534063fd2e41717f"
......@@ -5035,6 +5131,11 @@ emoji-regex@^9.2.2:
resolved "https://registry.yarnpkg.com/emoji-regex/-/emoji-regex-9.2.2.tgz#840c8803b0d8047f4ff0cf963176b32d4ef3ed72"
integrity sha512-L18DaJsXSUk2+42pv8mLs5jJT2hqFkFE4j21wOmgbUqsZ2hL72NsUU785g9RXgo3s0ZNgVl42TiHp3ZtOv/Vyg==
encode-utf8@^1.0.3:
version "1.0.3"
resolved "https://registry.yarnpkg.com/encode-utf8/-/encode-utf8-1.0.3.tgz#f30fdd31da07fb596f281beb2f6b027851994cda"
integrity sha512-ucAnuBEhUK4boH2HjVYG5Q2mQyPorvv0u/ocS+zhdw0S8AlHYY+GOFhP1Gio5z4icpP2ivFSvhtFjQi8+T9ppw==
end-of-stream@^1.1.0, end-of-stream@^1.4.4:
version "1.4.4"
resolved "https://registry.yarnpkg.com/end-of-stream/-/end-of-stream-1.4.4.tgz#5ae64a5f45057baf3626ec14da0ca5e4b2431eb0"
......@@ -5809,7 +5910,7 @@ formdata-polyfill@^4.0.10:
dependencies:
fetch-blob "^3.1.2"
framer-motion@^6:
framer-motion@^6.5.1:
version "6.5.1"
resolved "https://registry.yarnpkg.com/framer-motion/-/framer-motion-6.5.1.tgz#802448a16a6eb764124bf36d8cbdfa6dd6b931a7"
integrity sha512-o1BGqqposwi7cgDrtg0dNONhkmPsUFDaLcKXigzuTFC5x58mE8iyTazxSudFzmT6MEyJKfjjU8ItoMe3W+3fiw==
......@@ -5886,7 +5987,7 @@ gensync@^1.0.0-beta.2:
resolved "https://registry.yarnpkg.com/gensync/-/gensync-1.0.0-beta.2.tgz#32a6ee76c3d7f52d46b2b1ae5d93fea8580a25e0"
integrity sha512-3hN7NaskYvMDLQY55gnW3NQ+mesEAepTqlg+VEbj7zzqEMBVNhzcGYYeqFo/TlYz6eQiFcp1HcsCZO+nGgS8zg==
get-caller-file@^2.0.5:
get-caller-file@^2.0.1, get-caller-file@^2.0.5:
version "2.0.5"
resolved "https://registry.yarnpkg.com/get-caller-file/-/get-caller-file-2.0.5.tgz#4f94412a82db32f36e3b0b9741f8a97feb031f7e"
integrity sha512-DyFP3BM/3YHTQOCUL/w0OZHR0lpKeGrxotcHWcqNEdnltqFwXVfhEBQ94eIo34AfQpo0rGki4cyIiftY06h2Fg==
......@@ -7746,6 +7847,11 @@ playwright@^1.28.0:
dependencies:
playwright-core "1.28.0"
pngjs@^5.0.0:
version "5.0.0"
resolved "https://registry.yarnpkg.com/pngjs/-/pngjs-5.0.0.tgz#e79dd2b215767fd9c04561c01236df960bce7fbb"
integrity sha512-40QW5YalBNfQo5yRYmiw7Yz6TKKVr3h6970B2YE+3fQpsWcrbj1PzJgxeJ19DRQjhMbKPIuMY8rFaXc8moolVw==
popmotion@11.0.3:
version "11.0.3"
resolved "https://registry.yarnpkg.com/popmotion/-/popmotion-11.0.3.tgz#565c5f6590bbcddab7a33a074bb2ba97e24b0cc9"
......@@ -7867,6 +7973,16 @@ punycode@^2.1.0, punycode@^2.1.1:
resolved "https://registry.yarnpkg.com/punycode/-/punycode-2.1.1.tgz#b58b010ac40c22c5657616c8d2c2c02c7bf479ec"
integrity sha512-XRsRjdf+j5ml+y/6GKHPZbrF/8p2Yga0JPtdqTIY2Xe5ohJPD9saDJJLPvp9+NSBprVvevdXZybnj2cv8OEd0A==
qrcode@^1.5.1:
version "1.5.1"
resolved "https://registry.yarnpkg.com/qrcode/-/qrcode-1.5.1.tgz#0103f97317409f7bc91772ef30793a54cd59f0cb"
integrity sha512-nS8NJ1Z3md8uTjKtP+SGGhfqmTCs5flU/xR623oI0JX+Wepz9R8UrRVCTBTJm3qGw3rH6jJ6MUHjkDx15cxSSg==
dependencies:
dijkstrajs "^1.0.1"
encode-utf8 "^1.0.3"
pngjs "^5.0.0"
yargs "^15.3.1"
querystringify@^2.1.1:
version "2.2.0"
resolved "https://registry.yarnpkg.com/querystringify/-/querystringify-2.2.0.tgz#3345941b4153cb9d082d8eee4cda2016a9aef7f6"
......@@ -7889,13 +8005,13 @@ react-clientside-effect@^1.2.6:
dependencies:
"@babel/runtime" "^7.12.13"
react-dom@18.1.0:
version "18.1.0"
resolved "https://registry.yarnpkg.com/react-dom/-/react-dom-18.1.0.tgz#7f6dd84b706408adde05e1df575b3a024d7e8a2f"
integrity sha512-fU1Txz7Budmvamp7bshe4Zi32d0ll7ect+ccxNu9FlObT605GOEB8BfO4tmRJ39R5Zj831VCpvQ05QPBW5yb+w==
react-dom@18.2.0:
version "18.2.0"
resolved "https://registry.yarnpkg.com/react-dom/-/react-dom-18.2.0.tgz#22aaf38708db2674ed9ada224ca4aa708d821e3d"
integrity sha512-6IMTriUmvsjHUjNtEDudZfuDQUoWXVxKHhlEGSk81n4YFS+r/Kl99wXiwlVXtPBtJenozv2P+hxDsw9eA7Xo6g==
dependencies:
loose-envify "^1.1.0"
scheduler "^0.22.0"
scheduler "^0.23.0"
react-fast-compare@3.2.0:
version "3.2.0"
......@@ -7995,10 +8111,10 @@ react@17.0.2:
loose-envify "^1.1.0"
object-assign "^4.1.1"
react@18.1.0:
version "18.1.0"
resolved "https://registry.yarnpkg.com/react/-/react-18.1.0.tgz#6f8620382decb17fdc5cc223a115e2adbf104890"
integrity sha512-4oL8ivCz5ZEPyclFQXaNksK3adutVS8l2xzZU0cqEFrE9Sb7fC0EFK5uEk74wIreL1DERyjvsU915j1pcT2uEQ==
react@18.2.0:
version "18.2.0"
resolved "https://registry.yarnpkg.com/react/-/react-18.2.0.tgz#555bd98592883255fa00de14f1151a917b5d77d5"
integrity sha512-/3IjMdb2L9QbBdWiW5e3P2/npwMBaU9mHCSCUzNln0ZCYbcfTsGbTJrU/kGemdH2IWmB2ioZ+zkxtmq6g09fGQ==
dependencies:
loose-envify "^1.1.0"
......@@ -8150,6 +8266,11 @@ require-directory@^2.1.1:
resolved "https://registry.yarnpkg.com/require-directory/-/require-directory-2.1.1.tgz#8c64ad5fd30dab1c976e2344ffe7f792a6a6df42"
integrity sha512-fGxEI7+wsG9xrvdjsrlmL22OMTTiHRwAMroiEeMgq8gzoLC/PQr7RsRDSTLUg/bZAZtF+TVIkHc6/4RIKrui+Q==
require-main-filename@^2.0.0:
version "2.0.0"
resolved "https://registry.yarnpkg.com/require-main-filename/-/require-main-filename-2.0.0.tgz#d0b329ecc7cc0f61649f62215be69af54aa8989b"
integrity sha512-NKN5kMDylKuldxYLSUfrbo5Tuzh4hd+2E8NPPX02mZtn1VuREQToYe/ZdlJy+J3uCpfaiGF05e7B8W0iXbQHmg==
requires-port@^1.0.0:
version "1.0.0"
resolved "https://registry.yarnpkg.com/requires-port/-/requires-port-1.0.0.tgz#925d2601d39ac485e091cf0da5c6e694dc3dcaff"
......@@ -8294,10 +8415,10 @@ saxes@^6.0.0:
dependencies:
xmlchars "^2.2.0"
scheduler@^0.22.0:
version "0.22.0"
resolved "https://registry.yarnpkg.com/scheduler/-/scheduler-0.22.0.tgz#83a5d63594edf074add9a7198b1bae76c3db01b8"
integrity sha512-6QAm1BgQI88NPYymgGQLCZgvep4FyePDWFpXVK+zNSUgHwlqpJy8VEh8Et0KxTACS4VWwMousBElAZOH9nkkoQ==
scheduler@^0.23.0:
version "0.23.0"
resolved "https://registry.yarnpkg.com/scheduler/-/scheduler-0.23.0.tgz#ba8041afc3d30eb206a487b6b384002e4e61fdfe"
integrity sha512-CtuThmgHNg7zIZWAXi3AsyIzA3n4xx7aNyjwC2VJldO2LMVDhFK+63xGqq6CsJH4rTAt6/M+N4GhZiDYPx9eUw==
dependencies:
loose-envify "^1.1.0"
......@@ -8332,7 +8453,7 @@ semver@^6.0.0, semver@^6.1.1, semver@^6.1.2, semver@^6.3.0:
resolved "https://registry.yarnpkg.com/semver/-/semver-6.3.0.tgz#ee0a64c8af5e8ceea67687b133761e1becbd1d3d"
integrity sha512-b39TBaTSfV6yBrapU89p5fKekE2m/NwnDocOVruQFS1/veMgdzuPcnOM34M6CwxW8jH/lxEa5rBoDeUwu5HHTw==
set-blocking@~2.0.0:
set-blocking@^2.0.0, set-blocking@~2.0.0:
version "2.0.0"
resolved "https://registry.yarnpkg.com/set-blocking/-/set-blocking-2.0.0.tgz#045f9782d011ae9a6803ddd382b24392b3d890f7"
integrity sha512-KiKBS8AnWGEyLzofFfmvKwpdPzqiy16LvQfK3yv/fVH7Bj13/wl3JSR1J+rfgRE9q7xUJK4qvgS8raSOeLUehw==
......@@ -8601,10 +8722,10 @@ styled-jsx@5.0.4:
resolved "https://registry.yarnpkg.com/styled-jsx/-/styled-jsx-5.0.4.tgz#5b1bd0b9ab44caae3dd1361295559706e044aa53"
integrity sha512-sDFWLbg4zR+UkNzfk5lPilyIgtpddfxXEULxhujorr5jtePTUqiPDc5BC0v1NRqTr/WaFBGQQUoYToGlF4B2KQ==
stylis@4.0.13:
version "4.0.13"
resolved "https://registry.yarnpkg.com/stylis/-/stylis-4.0.13.tgz#f5db332e376d13cc84ecfe5dace9a2a51d954c91"
integrity sha512-xGPXiFVl4YED9Jh7Euv2V220mriG9u4B2TA6Ybjc1catrstKD2PpIdU3U0RKpkVBC2EhmL/F0sPCr9vrFTNRag==
stylis@4.1.3:
version "4.1.3"
resolved "https://registry.yarnpkg.com/stylis/-/stylis-4.1.3.tgz#fd2fbe79f5fed17c55269e16ed8da14c84d069f7"
integrity sha512-GP6WDNWf+o403jrEp9c5jibKavrtLW+/qYGhFxFrG8maXhwTBI7gLLhiBb0o7uFccWN+EOS9aMO6cGHWAO07OA==
sucrase@^3.20.0:
version "3.28.0"
......@@ -9123,6 +9244,11 @@ which-collection@^1.0.1:
is-weakmap "^2.0.1"
is-weakset "^2.0.1"
which-module@^2.0.0:
version "2.0.0"
resolved "https://registry.yarnpkg.com/which-module/-/which-module-2.0.0.tgz#d9ef07dce77b9902b8a3a8fa4b31c3e3f7e6e87a"
integrity sha512-B+enWhmw6cjfVC7kS8Pj9pCrKSc5txArRyaYGe088shv/FGWH+0Rjx/xPgtsWfsUtS27FkP697E4DDhgrgoc0Q==
which-typed-array@^1.1.8:
version "1.1.9"
resolved "https://registry.yarnpkg.com/which-typed-array/-/which-typed-array-1.1.9.tgz#307cf898025848cf995e795e8423c7f337efbde6"
......@@ -9205,6 +9331,11 @@ xmlchars@^2.2.0:
resolved "https://registry.yarnpkg.com/xmlchars/-/xmlchars-2.2.0.tgz#060fe1bcb7f9c76fe2a17db86a9bc3ab894210cb"
integrity sha512-JZnDKK8B0RCDw84FNdDAIpZK+JuJw+s7Lz8nksI7SIuU3UXJJslUthsi+uWBUYOwPFwW7W7PRLRfUKpxjtjFCw==
y18n@^4.0.0:
version "4.0.3"
resolved "https://registry.yarnpkg.com/y18n/-/y18n-4.0.3.tgz#b5f259c82cd6e336921efd7bfd8bf560de9eeedf"
integrity sha512-JKhqTOwSrqNA1NY5lSztJ1GrBiUodLMmIZuLiDaMRJ+itFd+ABVE8XBjOvIWL+rSqNDC74LCSFmlb/U4UZ4hJQ==
y18n@^5.0.5:
version "5.0.8"
resolved "https://registry.yarnpkg.com/y18n/-/y18n-5.0.8.tgz#7f4934d0f7ca8c56f95314939ddcd2dd91ce1d55"
......@@ -9225,11 +9356,36 @@ yaml@^2.1.1:
resolved "https://registry.yarnpkg.com/yaml/-/yaml-2.1.3.tgz#9b3a4c8aff9821b696275c79a8bee8399d945207"
integrity sha512-AacA8nRULjKMX2DvWvOAdBZMOfQlypSFkjcOcu9FalllIDJ1kvlREzcdIZmidQUqqeMv7jorHjq2HlLv/+c2lg==
yargs-parser@^18.1.2:
version "18.1.3"
resolved "https://registry.yarnpkg.com/yargs-parser/-/yargs-parser-18.1.3.tgz#be68c4975c6b2abf469236b0c870362fab09a7b0"
integrity sha512-o50j0JeToy/4K6OZcaQmW6lyXXKhq7csREXcDwk2omFPJEwUNOVtJKvmDr9EI1fAJZUyZcRF7kxGBWmRXudrCQ==
dependencies:
camelcase "^5.0.0"
decamelize "^1.2.0"
yargs-parser@^21.0.1, yargs-parser@^21.1.1:
version "21.1.1"
resolved "https://registry.yarnpkg.com/yargs-parser/-/yargs-parser-21.1.1.tgz#9096bceebf990d21bb31fa9516e0ede294a77d35"
integrity sha512-tVpsJW7DdjecAiFpbIB1e3qxIQsE6NoPc5/eTdrbbIC4h0LVsWhnoa3g+m2HclBIujHzsxZ4VJVA+GUuc2/LBw==
yargs@^15.3.1:
version "15.4.1"
resolved "https://registry.yarnpkg.com/yargs/-/yargs-15.4.1.tgz#0d87a16de01aee9d8bec2bfbf74f67851730f4f8"
integrity sha512-aePbxDmcYW++PaqBsJ+HYUFwCdv4LVvdnhBy78E57PIor8/OVvhMrADFFEDh8DHDFRv/O9i3lPhsENjO7QX0+A==
dependencies:
cliui "^6.0.0"
decamelize "^1.2.0"
find-up "^4.1.0"
get-caller-file "^2.0.1"
require-directory "^2.1.1"
require-main-filename "^2.0.0"
set-blocking "^2.0.0"
string-width "^4.2.0"
which-module "^2.0.0"
y18n "^4.0.0"
yargs-parser "^18.1.2"
yargs@^17.3.1:
version "17.6.2"
resolved "https://registry.yarnpkg.com/yargs/-/yargs-17.6.2.tgz#2e23f2944e976339a1ee00f18c77fedee8332541"
......
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment