From 3df3f0c995c3bdcb0d4543249f0d03880f7ea0f3 Mon Sep 17 00:00:00 2001 From: cyrbuzz Date: Wed, 17 Jan 2024 10:54:19 +0800 Subject: [PATCH 1/3] feat: play ground --- src/components/Header/Header.tsx | 13 +-- .../IndexerDetails/IndexerDetails.module.less | 2 + .../IndexerDetails/IndexerDetails.tsx | 6 +- src/components/IndexerDetails/Row.tsx | 37 ++++++-- .../ProjectHeader/ProjectHeader.tsx | 5 +- .../RpcPlayground/RpcPlayground.tsx | 88 +++++++++++++++++++ .../RpcPlayground/index.module.less | 37 ++++++++ src/hooks/useProjectList.module.less | 2 +- src/hooks/useProjectList.tsx | 21 ++++- src/i18n/en/explorer.ts | 2 +- src/images/rpcPlayground.tsx | 60 +++++++++++++ .../delegator/AllIndexers/AllIndexers.tsx | 11 ++- src/pages/explorer/Home/Home.module.css | 2 +- src/pages/explorer/Home/Home.tsx | 19 ---- src/pages/explorer/Project/Project.tsx | 7 +- src/pages/studio/Home/Home.tsx | 15 +--- 16 files changed, 264 insertions(+), 63 deletions(-) create mode 100644 src/components/RpcPlayground/RpcPlayground.tsx create mode 100644 src/components/RpcPlayground/index.module.less create mode 100644 src/images/rpcPlayground.tsx diff --git a/src/components/Header/Header.tsx b/src/components/Header/Header.tsx index 5cdf84d93..2c93dc564 100644 --- a/src/components/Header/Header.tsx +++ b/src/components/Header/Header.tsx @@ -37,17 +37,6 @@ export interface AppNavigation { export const Header: React.FC = () => { const { address: account } = useAccount(); const navigate = useNavigate(); - const calEntryLinks = React.useMemo(() => { - return entryLinks.map((entry) => { - if (entry.key === 'explorer') { - return { - ...entry, - dropdown: undefined, - }; - } - return entry; - }); - }, []); return (
@@ -55,7 +44,7 @@ export const Header: React.FC = () => { navigate={(link) => { navigate(link); }} - appNavigation={calEntryLinks} + appNavigation={entryLinks} dropdownLinks={{ label: 'Kepler', links: externalAppLinks }} rightElement={ <> diff --git a/src/components/IndexerDetails/IndexerDetails.module.less b/src/components/IndexerDetails/IndexerDetails.module.less index e9d9f73a9..a4fd443c1 100644 --- a/src/components/IndexerDetails/IndexerDetails.module.less +++ b/src/components/IndexerDetails/IndexerDetails.module.less @@ -170,6 +170,8 @@ color: #fff; font-size: 18px; margin-bottom: 8px; + display: flex; + align-items: center; } &LimitInfo { diff --git a/src/components/IndexerDetails/IndexerDetails.tsx b/src/components/IndexerDetails/IndexerDetails.tsx index 87ce54705..5fc6683af 100644 --- a/src/components/IndexerDetails/IndexerDetails.tsx +++ b/src/components/IndexerDetails/IndexerDetails.tsx @@ -4,6 +4,7 @@ import * as React from 'react'; import { useTranslation } from 'react-i18next'; import { EmptyList } from '@components/EmptyList'; +import { ProjectDetailsQuery } from '@hooks/useProjectFromQuery'; import { Spinner, TableTitle, Typography } from '@subql/components'; import { ServiceStatus } from '@subql/network-query'; import { renderAsync, useGetDeploymentIndexersLazyQuery, useGetIndexerDeploymentLazyQuery } from '@subql/react-hooks'; @@ -16,6 +17,7 @@ import Row from './Row'; type Props = { deploymentId: string | undefined; + project: ProjectDetailsQuery; }; const NoIndexers: React.FC = () => { @@ -31,7 +33,7 @@ const NoIndexers: React.FC = () => { ); }; -const IndexerDetails: React.FC = ({ deploymentId }) => { +const IndexerDetails: React.FC = ({ deploymentId, project }) => { const { t } = useTranslation(); const [loadIndexersLazy, asyncIndexers] = useGetDeploymentIndexersLazyQuery(); @@ -156,7 +158,7 @@ const IndexerDetails: React.FC = ({ deploymentId }) => { .filter(notEmpty) .sort((indexer) => (indexer.status === ServiceStatus.READY ? -1 : 1)) .map((indexer) => ( - + ))}
diff --git a/src/components/IndexerDetails/Row.tsx b/src/components/IndexerDetails/Row.tsx index 1ed089111..c3cbf24ff 100644 --- a/src/components/IndexerDetails/Row.tsx +++ b/src/components/IndexerDetails/Row.tsx @@ -6,6 +6,7 @@ import { useTranslation } from 'react-i18next'; import { BsChevronDown, BsChevronUp, BsInfoSquare } from 'react-icons/bs'; import { useNavigate } from 'react-router'; import { LazyQueryResult } from '@apollo/client'; +import RpcPlayground from '@components/RpcPlayground/RpcPlayground'; import { WalletRoute } from '@components/WalletRoute'; import { useIsLogin } from '@hooks/useIsLogin'; import { useRequestServiceAgreementToken } from '@hooks/useRequestServiceAgreementToken'; @@ -14,6 +15,7 @@ import { GraphiQL } from '@subql/components/dist/common/GraphiQL'; import { GetDeploymentIndexersQuery, PlansNodeFieldsFragment as Plan, + ProjectType, ServiceStatus as DeploymentStatus, } from '@subql/network-query'; import { useGetDeploymentPlansLazyQuery } from '@subql/react-hooks'; @@ -25,6 +27,7 @@ import clsx from 'clsx'; import { t } from 'i18next'; import { useAccount } from 'wagmi'; +import RpcPlaygroundIcon from 'src/images/rpcPlayground'; import { useWeb3Store } from 'src/stores'; import { useProjectStore } from 'src/stores/project'; @@ -77,7 +80,8 @@ export interface QueryLimit { const ConnectedRow: React.FC<{ indexer: ExcludeNull['nodes'][number]>; deploymentId?: string; -}> = ({ indexer, deploymentId }) => { + type: ProjectType; +}> = ({ indexer, deploymentId, type }) => { const { t } = useTranslation(); const { address: account } = useAccount(); const navigate = useNavigate(); @@ -175,7 +179,6 @@ const ConnectedRow: React.FC<{ ), }, - { width: '13%', render: () => { @@ -211,7 +214,16 @@ const ConnectedRow: React.FC<{ setShowReqTokenConfirmModal(true); }} > - + {type === ProjectType.SUBQUERY ? ( + + ) : ( + + )} Playground ); @@ -332,7 +344,13 @@ const ConnectedRow: React.FC<{ > {t('explorer.flexPlans.requestToken')}} + element={ + + {t('explorer.flexPlans.requestToken', { + type: type === ProjectType.RPC ? 'JSON' : 'Graphql', + })} + + } > @@ -344,7 +362,11 @@ const ConnectedRow: React.FC<{ >
- + {type === ProjectType.SUBQUERY ? ( + + ) : ( + + )} {t('myFlexPlans.playground')} @@ -361,7 +383,10 @@ const ConnectedRow: React.FC<{
- {queryUrl && trailToken && } + {type === ProjectType.SUBQUERY && queryUrl && trailToken && ( + + )} + {type === ProjectType.RPC && } ); diff --git a/src/components/ProjectHeader/ProjectHeader.tsx b/src/components/ProjectHeader/ProjectHeader.tsx index 25cc62b09..f6ee18363 100644 --- a/src/components/ProjectHeader/ProjectHeader.tsx +++ b/src/components/ProjectHeader/ProjectHeader.tsx @@ -71,9 +71,10 @@ const ProjectHeader: React.FC = ({ {isUnsafeDeployment && } - + */}
diff --git a/src/components/RpcPlayground/RpcPlayground.tsx b/src/components/RpcPlayground/RpcPlayground.tsx new file mode 100644 index 000000000..c07eebfba --- /dev/null +++ b/src/components/RpcPlayground/RpcPlayground.tsx @@ -0,0 +1,88 @@ +// Copyright 2020-2022 SubQuery Pte Ltd authors & contributors +// SPDX-License-Identifier: Apache-2.0 + +import React, { FC, useMemo, useState } from 'react'; +import { Typography } from '@subql/components'; +import { getAuthReqHeader } from '@utils'; +import { Button, Input } from 'antd'; +import { fetchJson } from 'ethers/lib/utils'; + +import styles from './index.module.less'; + +interface IProps { + url?: string; + trailToken: string; +} + +const RpcPlayground: FC = ({ url, trailToken }) => { + const [val, setVal] = useState(''); + const [loading, setLoading] = useState(false); + const enteredRows = useMemo(() => { + return val.split('\n').length; + }, [val]); + + const [responseData, setResponseData] = useState(''); + + const fetchRpc = async () => { + if (!url) return; + try { + setLoading(true); + const res = await fetchJson( + { + url, + headers: { + ...getAuthReqHeader(trailToken), + }, + }, + val, + ); + + setResponseData(JSON.stringify(res)); + } catch (e: any) { + setResponseData(`${e.toString()}`); + } finally { + setLoading(false); + } + }; + + return ( +
+
+ + Request + + { + setVal(e.target.value); + }} + placeholder="JSON RPC playground is a simple tool to help you test queries, click to enter you requests." + > + + +
+ +
+ + Response + +
+ {responseData} +
+
+
+ ); +}; +export default RpcPlayground; diff --git a/src/components/RpcPlayground/index.module.less b/src/components/RpcPlayground/index.module.less new file mode 100644 index 000000000..28001d4ab --- /dev/null +++ b/src/components/RpcPlayground/index.module.less @@ -0,0 +1,37 @@ +.rpcPlayground { + display: flex; + padding: 16px; + border-radius: 0px 0px 16px 16px; + background: #2A3546; + + &Editor { + flex: 1; + padding: 16px; + background: #1F2A3B; + border-radius: 10px; + position: relative; + + :global { + .ant-input { + background: #1F2A3B; + border: 1px solid #1F2A3B; + color: var(--sq-gray500); + padding: 0; + font-family: var(--sq-font-family); + &:focus { + box-shadow: none; + } + + &::placeholder { + color: var(--sq-gary500); + } + } + } + } + + &Response { + flex: 1; + padding: 16px; + + } +} \ No newline at end of file diff --git a/src/hooks/useProjectList.module.less b/src/hooks/useProjectList.module.less index 4c8539db9..0e4b7c119 100644 --- a/src/hooks/useProjectList.module.less +++ b/src/hooks/useProjectList.module.less @@ -8,7 +8,7 @@ .typeFilter { display: flex; - justify-content: center; + // justify-content: center; margin-bottom: 40px; :global { diff --git a/src/hooks/useProjectList.tsx b/src/hooks/useProjectList.tsx index 4accda27b..c1e38b08d 100644 --- a/src/hooks/useProjectList.tsx +++ b/src/hooks/useProjectList.tsx @@ -5,12 +5,13 @@ import React, { useMemo, useState } from 'react'; import SearchOutlined from '@ant-design/icons/SearchOutlined'; import { ProjectCard } from '@components'; import { useProjectMetadata } from '@containers'; +import { PublishNewProjectModal } from '@pages/studio/Home/Home'; import { SubqlCheckbox } from '@subql/components'; import { ProjectFieldsFragment, ProjectsOrderBy, ProjectType } from '@subql/network-query'; import { useAsyncMemo, useGetProjectLazyQuery, useGetProjectsLazyQuery } from '@subql/react-hooks'; import { categoriesOptions, notEmpty, rpcCategoriesOptions } from '@utils'; import { useInfiniteScroll, useMount } from 'ahooks'; -import { Input, Radio, Skeleton, Typography } from 'antd'; +import { Button, Input, Radio, Skeleton, Typography } from 'antd'; import { useGetDeploymentManifest } from './useGetDeploymentManifest'; import { useLocalProjects } from './useLocalProjects'; @@ -63,6 +64,7 @@ export const useProjectList = (props: UseProjectListProps = {}) => { // assum there at lease have 11 projects const [total, setTotal] = React.useState(10); const [inSearchMode, setInSearchMode] = React.useState(false); + const [showPublishModal, setShowPublishModal] = React.useState(false); const { getProjectBySearch } = useLocalProjects(); @@ -236,6 +238,23 @@ export const useProjectList = (props: UseProjectListProps = {}) => { buttonStyle="solid" size="large" /> + + + { + setShowPublishModal(val); + }} + >
diff --git a/src/i18n/en/explorer.ts b/src/i18n/en/explorer.ts index c8110ca03..da83cb1ba 100644 --- a/src/i18n/en/explorer.ts +++ b/src/i18n/en/explorer.ts @@ -105,7 +105,7 @@ const translation = { indexer: 'indexer', validityPeriod: 'Validity Period', non: 'There are no flex plans for this project yet.', - requestToken: 'To start testing your queries in the GraphQL playground, simply request a trial token.', + requestToken: 'To start testing your queries in the {{type}} playground, simply request a trial token.', remainLimit: 'Remain requests limit: {{limit}}', expireTime: 'Token expires in {{time}}', }, diff --git a/src/images/rpcPlayground.tsx b/src/images/rpcPlayground.tsx new file mode 100644 index 000000000..934c63f29 --- /dev/null +++ b/src/images/rpcPlayground.tsx @@ -0,0 +1,60 @@ +// Copyright 2020-2022 SubQuery Pte Ltd authors & contributors +// SPDX-License-Identifier: Apache-2.0 + +import { FC } from 'react'; + +export type Icon = { + color?: string; + width?: number; + height?: number; + style?: React.CSSProperties; +}; + +const RpcPlaygroundIcon: FC = ({ color = '#fff', width = 18, height = 18, style }) => { + return ( + + + + + + + + + + ); +}; + +export default RpcPlaygroundIcon; diff --git a/src/pages/delegator/AllIndexers/AllIndexers.tsx b/src/pages/delegator/AllIndexers/AllIndexers.tsx index b3ebf1cb5..50f1cac06 100644 --- a/src/pages/delegator/AllIndexers/AllIndexers.tsx +++ b/src/pages/delegator/AllIndexers/AllIndexers.tsx @@ -4,11 +4,12 @@ import * as React from 'react'; import { useTranslation } from 'react-i18next'; import { EmptyList, WalletRoute } from '@components'; +import RpcError from '@components/RpcError'; import { useEra } from '@hooks'; import { Spinner, Typography } from '@subql/components'; import { useGetIndexersQuery } from '@subql/react-hooks'; -import { getUseQueryFetchMore, mapAsync, mergeAsync, notEmpty, renderAsync } from '../../../utils'; +import { getUseQueryFetchMore, isRPCError, mapAsync, mergeAsync, notEmpty, renderAsync } from '../../../utils'; import { IndexerList } from './IndexerList/IndexerList'; export const AllIndexers: React.FC = () => { @@ -35,7 +36,13 @@ export const AllIndexers: React.FC = () => { ), { loading: () => , - error: (error) => {`Error: Failed to get Indexers: ${error.message}`}, + error: (error) => { + if (isRPCError(error)) { + return ; + } + + return {`Error: Failed to get Indexers: ${error.message}`}; + }, data: (data) => { if (!data || data?.totalCount === 0) { ; diff --git a/src/pages/explorer/Home/Home.module.css b/src/pages/explorer/Home/Home.module.css index bf9b29275..18448c381 100644 --- a/src/pages/explorer/Home/Home.module.css +++ b/src/pages/explorer/Home/Home.module.css @@ -7,7 +7,7 @@ .header { display: flex; flex-direction: column; - margin-bottom: 72px; + margin-bottom: 18px; } .headerTitle { diff --git a/src/pages/explorer/Home/Home.tsx b/src/pages/explorer/Home/Home.tsx index 0325152bb..b1a6b700c 100644 --- a/src/pages/explorer/Home/Home.tsx +++ b/src/pages/explorer/Home/Home.tsx @@ -5,9 +5,7 @@ import * as React from 'react'; import { useTranslation } from 'react-i18next'; import { useNavigate } from 'react-router'; import { useProjectList } from '@hooks/useProjectList'; -import { PublishNewProjectModal } from '@pages/studio/Home/Home'; import { Typography } from '@subql/components'; -import { Button } from 'antd'; import { ROUTES } from '../../../utils'; import styles from './Home.module.css'; @@ -17,7 +15,6 @@ const { PROJECT_NAV } = ROUTES; // TODO move to components export const Header: React.FC = () => { const { t } = useTranslation(); - const [showPublishModal, setShowPublishModal] = React.useState(false); return (
@@ -26,23 +23,7 @@ export const Header: React.FC = () => { {t('explorer.home.headerDesc')} -
- { - setShowPublishModal(val); - }} - >
); }; diff --git a/src/pages/explorer/Project/Project.tsx b/src/pages/explorer/Project/Project.tsx index 56d96d521..bcc507515 100644 --- a/src/pages/explorer/Project/Project.tsx +++ b/src/pages/explorer/Project/Project.tsx @@ -45,7 +45,7 @@ const ProjectInner: React.FC = () => { { link: `${OVERVIEW}${location.search}`, label: t('explorer.project.tab1') }, { link: `${INDEXERS}${location.search}`, - label: asyncProject.data?.type === ProjectType.RPC ? 'RPC Endpoints' : t('explorer.project.tab2'), + label: asyncProject.data?.type === ProjectType.RPC ? 'RPC Providers' : t('explorer.project.tab2'), }, { link: `${SERVICE_AGREEMENTS}${location.search}`, label: t('explorer.project.tab3') }, ]; @@ -158,7 +158,10 @@ const ProjectInner: React.FC = () => { /> } /> - } /> + } + /> { }, }); - const [showCreateModal, setShowCreateModal] = React.useState(false); - const enableCreateModal = () => setShowCreateModal(true); - return (
My Projects -
- { - setShowCreateModal(val); - }} - /> - {listsWithSearch}
); From 6d2e3191ac4143d0fad7ecea37daa2bb92f2329f Mon Sep 17 00:00:00 2001 From: cyrbuzz Date: Wed, 17 Jan 2024 15:15:37 +0800 Subject: [PATCH 2/3] feat: playground --- .../RpcPlayground/RpcPlayground.tsx | 50 +++++++++++-------- .../RpcPlayground/index.module.less | 9 ++++ src/components/Spinner/Spinner.tsx | 7 +-- .../consumer/Playground/AuthPlayground.tsx | 12 ++++- .../consumer/Playground/FlexPlayground.tsx | 3 +- .../consumer/Playground/SAPlayground.tsx | 8 ++- .../ServiceAgreementsTable.tsx | 1 + vite.config.js | 3 ++ 8 files changed, 65 insertions(+), 28 deletions(-) diff --git a/src/components/RpcPlayground/RpcPlayground.tsx b/src/components/RpcPlayground/RpcPlayground.tsx index c07eebfba..ac353c132 100644 --- a/src/components/RpcPlayground/RpcPlayground.tsx +++ b/src/components/RpcPlayground/RpcPlayground.tsx @@ -38,6 +38,7 @@ const RpcPlayground: FC = ({ url, trailToken }) => { ); setResponseData(JSON.stringify(res)); + // eslint-disable-next-line @typescript-eslint/no-explicit-any } catch (e: any) { setResponseData(`${e.toString()}`); } finally { @@ -51,27 +52,36 @@ const RpcPlayground: FC = ({ url, trailToken }) => { Request - { - setVal(e.target.value); - }} - placeholder="JSON RPC playground is a simple tool to help you test queries, click to enter you requests." - > +
+
+ {new Array(enteredRows).fill(0).map((_, index) => ( + {index + 1} + ))} +
+ { + setVal(e.target.value); + }} + style={{ resize: 'none' }} + placeholder="JSON RPC playground is a simple tool to help you test queries, click to enter you requests." + > +
- +
+ +
diff --git a/src/components/RpcPlayground/index.module.less b/src/components/RpcPlayground/index.module.less index 28001d4ab..9864645b0 100644 --- a/src/components/RpcPlayground/index.module.less +++ b/src/components/RpcPlayground/index.module.less @@ -11,6 +11,15 @@ border-radius: 10px; position: relative; + .rows { + display: flex; + flex-direction: column; + color: var(--sq-gray600); + font-size: 14px; + line-height: 1.5714285714285714; + padding-top: 2px; + } + :global { .ant-input { background: #1F2A3B; diff --git a/src/components/Spinner/Spinner.tsx b/src/components/Spinner/Spinner.tsx index 5822335d0..466f3427e 100644 --- a/src/components/Spinner/Spinner.tsx +++ b/src/components/Spinner/Spinner.tsx @@ -2,8 +2,7 @@ // SPDX-License-Identifier: Apache-2.0 import * as React from 'react'; -import LoadingOutlined from '@ant-design/icons/LoadingOutlined'; -import { Spin } from 'antd'; +import { Spinner as SubqlSpinner } from '@subql/components'; import styles from './Spinner.module.css'; @@ -12,11 +11,9 @@ type Props = { }; const Spinner: React.FC = ({ size }) => { - const antIcon = ; - return (
- +
); }; diff --git a/src/pages/consumer/Playground/AuthPlayground.tsx b/src/pages/consumer/Playground/AuthPlayground.tsx index 285d8deb7..e80a78a8a 100644 --- a/src/pages/consumer/Playground/AuthPlayground.tsx +++ b/src/pages/consumer/Playground/AuthPlayground.tsx @@ -4,7 +4,9 @@ import * as React from 'react'; import { useTranslation } from 'react-i18next'; import { BreadcrumbNav } from '@components'; +import RpcPlayground from '@components/RpcPlayground/RpcPlayground'; import { Spinner } from '@subql/components'; +import { ProjectType } from '@subql/network-query'; import { Table } from 'antd'; import { ColumnsType } from 'antd/lib/table'; @@ -33,6 +35,7 @@ interface AuthPlaygroundProps { columns?: ColumnsType; dataSource?: any[]; rowKey?: string; + type: ProjectType; loading?: boolean; requireAuth: boolean; @@ -53,6 +56,7 @@ export const AuthPlayground: React.FC = ({ columns, dataSource, rowKey, + type, loading, requireAuth, @@ -82,7 +86,13 @@ export const AuthPlayground: React.FC = ({
{loading && } {requireAuth && } - {playgroundVisible && } + {type === ProjectType.SUBQUERY && playgroundVisible && } + {type === ProjectType.RPC && playgroundVisible && ( + + )}
); diff --git a/src/pages/consumer/Playground/FlexPlayground.tsx b/src/pages/consumer/Playground/FlexPlayground.tsx index 5290885a6..c86ddf815 100644 --- a/src/pages/consumer/Playground/FlexPlayground.tsx +++ b/src/pages/consumer/Playground/FlexPlayground.tsx @@ -7,7 +7,7 @@ import { useNavigate, useParams } from 'react-router'; import { NotificationType, openNotification } from '@components/Notification'; import { FetcherParams } from '@graphiql/toolkit'; import { Spinner, TableTitle } from '@subql/components'; -import { StateChannelFieldsFragment as ConsumerFlexPlan } from '@subql/network-query'; +import { ProjectType, StateChannelFieldsFragment as ConsumerFlexPlan } from '@subql/network-query'; import { renderAsync, useGetConsumerFlexPlanQuery } from '@subql/react-hooks'; import { TableProps } from 'antd'; import { BigNumber } from 'ethers'; @@ -202,6 +202,7 @@ export const FlexPlayground: React.FC = () => { headerText={t('plans.category.myFlexPlans')} deploymentId={flexPlan?.deployment?.id ?? ''} projectMetadata={flexPlan?.deployment?.project?.metadata} + type={flexPlan.deployment?.project?.type || ProjectType.SUBQUERY} columns={columns} dataSource={[flexPlan]} rowKey={'id'} diff --git a/src/pages/consumer/Playground/SAPlayground.tsx b/src/pages/consumer/Playground/SAPlayground.tsx index 1949741cb..16d219c00 100644 --- a/src/pages/consumer/Playground/SAPlayground.tsx +++ b/src/pages/consumer/Playground/SAPlayground.tsx @@ -7,7 +7,7 @@ import { useLocation, useNavigate } from 'react-router'; import { NotificationType, openNotification } from '@components/Notification'; import { FetcherParams } from '@graphiql/toolkit'; import { useIndexerMetadata } from '@hooks'; -import { ServiceAgreementFieldsFragment as ServiceAgreement } from '@subql/network-query'; +import { ProjectType, ServiceAgreementFieldsFragment as ServiceAgreement } from '@subql/network-query'; import { TableProps } from 'antd'; import i18next from 'i18next'; import moment from 'moment'; @@ -166,12 +166,18 @@ export const SAPlayground: React.FC = () => { const requireAuth = queryable === false && !isCheckingAuth; const showPlayground = !!(queryable && queryUrl && !isCheckingAuth); + if (!serviceAgreement) { + navigate('/consumer/service-agreements'); + return
; + } + console.warn(serviceAgreement); return ( = ({ {t('serviceAgreements.playground.comingSoon')} ); + return (