From 8045ae83384fc3d265ef936c316484eb9fc6b26b Mon Sep 17 00:00:00 2001 From: cyrbuzz Date: Tue, 12 Dec 2023 16:24:00 +0800 Subject: [PATCH 01/11] feat: pub --- src/App.tsx | 10 +- src/components/Header/Header.module.less | 162 +------------ src/components/Header/Header.tsx | 221 ++++-------------- .../NewDeployment/NewDeployment.module.css | 16 -- .../NewDeployment/NewDeployment.tsx | 54 ----- src/components/NewDeployment/index.ts | 4 - .../NewProject/NewProject.module.css | 37 --- src/components/NewProject/NewProject.tsx | 53 ----- src/components/NewProject/index.ts | 4 - src/components/index.ts | 4 - src/hooks/useProjectList.module.less | 73 ++++++ src/hooks/useProjectList.tsx | 214 +++++++++++++++++ src/hooks/useStudio.ts | 3 + src/i18n/en/explorer.ts | 10 +- src/pages/explorer/Home/Home.module.css | 73 ------ src/pages/explorer/Home/Home.tsx | 181 +------------- src/pages/studio/Create/Create.module.css | 20 +- src/pages/studio/Create/Create.tsx | 181 +++++++++++--- src/pages/studio/Home/Home.tsx | 184 +++++---------- src/pages/studio/Project/Project.tsx | 15 +- src/utils/links.ts | 4 + 21 files changed, 588 insertions(+), 935 deletions(-) delete mode 100644 src/components/NewDeployment/NewDeployment.module.css delete mode 100644 src/components/NewDeployment/NewDeployment.tsx delete mode 100644 src/components/NewDeployment/index.ts delete mode 100644 src/components/NewProject/NewProject.module.css delete mode 100644 src/components/NewProject/NewProject.tsx delete mode 100644 src/components/NewProject/index.ts create mode 100644 src/hooks/useProjectList.module.less create mode 100644 src/hooks/useProjectList.tsx diff --git a/src/App.tsx b/src/App.tsx index 6c9a36b91..6aa54b201 100644 --- a/src/App.tsx +++ b/src/App.tsx @@ -1,11 +1,9 @@ // Copyright 2020-2022 SubQuery Pte Ltd authors & contributors // SPDX-License-Identifier: Apache-2.0 -import React, { PropsWithChildren, useMemo } from 'react'; +import React, { PropsWithChildren } from 'react'; import { BrowserRouter } from 'react-router-dom'; import { AppInitProvider } from '@containers/AppInitialProvider'; -import { useStudioEnabled } from '@hooks'; -import { entryLinks, externalAppLinks, studioLink } from '@utils/links'; import { RainbowProvider } from './config/rainbowConf'; import { ChainStatus, Header } from './components'; @@ -40,15 +38,11 @@ const Providers: React.FC = ({ children }) => { }; const RenderRouter: React.FC = () => { - const studioEnabled = useStudioEnabled(); - const calEntryLinks = useMemo(() => (studioEnabled ? [...entryLinks, studioLink] : [...entryLinks]), [studioEnabled]); - return (
- {/* TODO: replace with component from ui library */} -
+
diff --git a/src/components/Header/Header.module.less b/src/components/Header/Header.module.less index 15b5df467..acb46abc4 100644 --- a/src/components/Header/Header.module.less +++ b/src/components/Header/Header.module.less @@ -1,159 +1,9 @@ .header { - width: 100%; - display: flex; - justify-content: space-between; - box-shadow: inset 0px -1px 0px rgba(26, 32, 44, 0.06); - padding: 1rem 1.25rem; - - .expandIcon { - display: none; - font-size: 40px; - } - - &Inner { - width: 100%; - } - - &Logo { - display: flex; - } - - &Right { - display: flex; - width: 100%; - align-items: center; - transition: all 0.2s linear; - - } - - .leftHeader { - display: inline-flex; - } -} - -.justifyBetween { - justify-content: space-between; -} - -.flexCenter { - display: flex; - align-items: center; -} - -.leftElement { - margin-left: 2rem; - position: relative; -} - -.appDropdown { - margin: 1rem; -} - -.menuOverlay { - padding: 1rem !important; -} - -.dropMenu { - width: 300px !important; -} - -.navLink { - text-decoration: none; - margin: 0 1vw; - display: flex; - color: var(--gray700); - width: fit-content; -} - -.navLink:hover { - text-decoration: underline; - color: var(--sq-primary-blue); -} - -.navLinkCurrent { - font-weight: 500; - color: var(--sq-info); -} - -@media screen and (max-width: 600px) { - .header { - flex-direction: column; - .expandIcon { - display: inline-block; - } - - .middleHeader { - width: 100%; - } - - &Logo { - width: 100%; - } - - &Inner { - flex-direction: column; - width: 100vw; - padding: 0 20px; - .middleHeader { - flex-direction: column; - } - - :global { - #leftHeader { - margin-left: 0; - } - } - - .appDropdown { - width: 100%; - margin: 0; - padding: 14px 0; - border-bottom: 1px solid var(--sq-gray200); - :global { - .ant-space.ant-dropdown-trigger { - margin: 0 1vw; - } - } - } - } - - &Right { - flex-direction: column; - } - - &Item { - width: 100%; - padding: 14px 0; - border-top: 1px solid var(--sq-gray200); - border-bottom: 1px solid var(--sq-gray200); - - & + .headerItem { - margin-top: -1px; - } - } - - .hideOnMobile { - height: 0; - overflow: hidden; - } - - .showExpand { - height: 570px; - } - - .right { - width: 100%; - margin-bottom: 8px; - - &>a { - width: 100%; - } - } - - .leftHeader { - border-top: 1px solid var(--sq-gray200); - padding: 14px 0; - width: 100%; + :global { + .subql-header { + padding: 0 24px; + display: flex; + align-items: center; } } -} +} \ No newline at end of file diff --git a/src/components/Header/Header.tsx b/src/components/Header/Header.tsx index 7c293e817..e0d174127 100644 --- a/src/components/Header/Header.tsx +++ b/src/components/Header/Header.tsx @@ -2,12 +2,12 @@ // SPDX-License-Identifier: Apache-2.0 import * as React from 'react'; -import { BsList, BsX } from 'react-icons/bs'; -import { NavLink, useNavigate } from 'react-router-dom'; +import { useNavigate } from 'react-router-dom'; import { AccountActions } from '@components/AccountActions'; +import { useStudioEnabled } from '@hooks'; import { ConnectButton } from '@rainbow-me/rainbowkit'; -import { Button, Dropdown, MenuWithDesc, Typography } from '@subql/components'; -import { Divider } from 'antd'; +import { Button, Header as SubqlHeader } from '@subql/components'; +import { entryLinks, externalAppLinks } from '@utils/links'; import clsx from 'clsx'; import { useAccount } from 'wagmi'; @@ -35,177 +35,58 @@ export interface AppNavigation { dropdown?: AppLink[]; } -const isExternalLink = (to: string) => to.startsWith('https') || to.startsWith('http'); - -const renderLink = (to: string, label: string) => { - if (!isExternalLink(to)) { - return ( - - clsx(styles.navLink, isActive && styles.navLinkCurrent)}> - {label} - - - ); - } +export const Header: React.FC = () => { + const { address: account } = useAccount(); + const navigate = useNavigate(); + const studioEnabled = useStudioEnabled(); + const calEntryLinks = React.useMemo(() => { + if (!studioEnabled) { + return entryLinks.map((entry) => { + if (entry.key === 'explorer') { + return { + ...entry, + dropdown: undefined, + }; + } + return entry; + }); + } + return entryLinks; + }, [studioEnabled]); return ( - + ); + }} + + )}
- ); + } - return ( -
- {renderLink(nav.link ?? '/', nav.label)} -
- ); - })} -
- ); - - return ( - <> - <>{middleElement} - <>{sortedAppNavigation} - - ); -}; - -export interface HeaderProps { - logoLink?: string; - dropdownLinks?: DropdownLink; - appNavigation?: AppNavigation[]; - leftElement?: React.ReactElement; - middleElement?: React.ReactElement; - rightElement?: React.ReactElement; - className?: string; -} - -export const Header: React.FC> = ({ - logoLink, - dropdownLinks, - appNavigation, - leftElement, - middleElement, - rightElement, - className, -}) => { - const { address: account } = useAccount(); - const [showExpand, setShowExpand] = React.useState(false); - - return ( -
-
-
- - SubQuery Logo - - - -
{ - setShowExpand(!showExpand); - }} - > - {showExpand ? : } -
-
- -
- - - -
- {account ? ( - - ) : ( - - {({ openConnectModal }) => { - return ( - - ); - }} - - )} -
-
-
+ >
); }; diff --git a/src/components/NewDeployment/NewDeployment.module.css b/src/components/NewDeployment/NewDeployment.module.css deleted file mode 100644 index ebe11b63d..000000000 --- a/src/components/NewDeployment/NewDeployment.module.css +++ /dev/null @@ -1,16 +0,0 @@ -.form { - display: flex; - flex-direction: column; - align-items: flex-start; - width: 100%; -} - -.submit { - width: 95%; - margin-top: 24px; -} - -.error { - color: var(--sq-error); - padding-top: 8px; -} diff --git a/src/components/NewDeployment/NewDeployment.tsx b/src/components/NewDeployment/NewDeployment.tsx deleted file mode 100644 index 80e4a8001..000000000 --- a/src/components/NewDeployment/NewDeployment.tsx +++ /dev/null @@ -1,54 +0,0 @@ -// Copyright 2020-2022 SubQuery Pte Ltd authors & contributors -// SPDX-License-Identifier: Apache-2.0 - -import * as React from 'react'; -import { useTranslation } from 'react-i18next'; -import { Button } from '@subql/components'; -import { Form, Formik } from 'formik'; - -import { NewDeployment as NewDeploymentParams, newDeploymentSchema } from '../../models'; -import { ModalContainer } from '../NewProject/NewProject'; -import { FTextInput } from '..'; -import styles from './NewDeployment.module.css'; - -type Props = { - onSubmit: (deployment: NewDeploymentParams) => Promise | void; - onClose?: () => void; -}; - -const NewDeployment: React.FC = (props) => { - const { t } = useTranslation(); - - return ( - - - {({ isSubmitting, submitForm }) => ( -
-
- - - -
-
, + ]} + > + ), + okButtonProps: { + style: { display: 'none' }, + }, + }); } catch (e) { if (isEthError(e) && e.code === 4001) { setSubmitError(t('errors.transactionRejected')); @@ -60,45 +95,117 @@ const Create: React.FC = () => { image: undefined, version: '1.0.0', versionDescription: '', - deploymentId: '', + deploymentId: query.get('deploymentId') ?? '', }} validationSchema={projectMetadataSchema.shape({}).concat(newDeploymentSchema.shape({}))} onSubmit={handleSubmit} > - {({ setFieldValue, values, isSubmitting, submitForm }) => ( -
-
-
-
- setFieldValue('image', value)} - placeholder="/static/default.project.png" - /> -

{values.name}

-
-
- +
- -
-
- - - - -

Deployment Details

- - - - {submitError && {submitError}} +
+
+ + Project Details + + {t('studio.create.description')} + + + {({ + field, + form, + }: { + field: { name: string; value: string }; + // eslint-disable-next-line @typescript-eslint/no-explicit-any + form: { setFieldValue: (field: string, val: any) => void }; + }) => { + return ( + { + form.setFieldValue(field.name, e); + }} + inputProps={{ + placeholder: `You can provide a detailed description of your SubQuery project. + +# hello world +- you can use lists +- *Italics* +- *bold* +- [Links](http://subquery.network) + `, + }} + /> + ); + }} + + + +

Deployment Details

+ + + {t('studio.create.versionDesc')} + + {({ + field, + form, + }: { + field: { name: string; value: string }; + // eslint-disable-next-line @typescript-eslint/no-explicit-any + form: { setFieldValue: (field: string, val: any) => void }; + }) => { + return ( + { + form.setFieldValue(field.name, e); + }} + inputProps={{ + placeholder: `You can provide a detailed description of your SubQuery project. + +# hello world +- you can use lists +- *Italics* +- *bold* +- [Links](http://subquery.network) + `, + }} + /> + ); + }} + + {submitError && {submitError}} +
- -
- - )} + + ); + }}
); diff --git a/src/pages/studio/Home/Home.tsx b/src/pages/studio/Home/Home.tsx index 4ba8a2a3d..e4497f19d 100644 --- a/src/pages/studio/Home/Home.tsx +++ b/src/pages/studio/Home/Home.tsx @@ -2,150 +2,88 @@ // SPDX-License-Identifier: Apache-2.0 import * as React from 'react'; -import Modal from 'react-modal'; import { useNavigate } from 'react-router'; -import { Button } from '@subql/components'; -import { ProjectFieldsFragment } from '@subql/network-query'; -import { useGetProjectsLazyQuery } from '@subql/react-hooks'; -import { useInfiniteScroll } from 'ahooks'; +import { useProjectList } from '@hooks/useProjectList'; +import { Typography } from '@subql/components'; +import { Button, Form, Input, Modal } from 'antd'; +import { useForm } from 'antd/es/form/Form'; -import { CreateInstructions, NewProject, ProjectCard, Spinner } from '../../../components'; import { useWeb3 } from '../../../containers'; -import { useProject } from '../../../hooks'; -import { modalStyles, notEmpty, renderAsync } from '../../../utils'; import { ROUTES } from '../../../utils'; -import { Header } from '../../explorer/Home/Home'; -import styles from './Home.module.css'; -const { STUDIO_CREATE_NAV, STUDIO_PROJECT_NAV } = ROUTES; - -const Project: React.FC<{ - projectId: string; - account: string; - onClick?: () => void; - projectDetails: ProjectFieldsFragment; -}> = ({ projectId, account, onClick, projectDetails }) => { - const asyncProject = useProject(projectId); - return ( -
- {renderAsync(asyncProject, { - error: (e) => { - console.log('ERROR loading project', e); - return {`Failed to load project: ${e.message}`}; - }, - loading: () => { - return ( - - ); - // return {`Loading project id: ${projectId}`} - }, - data: (project) => { - if (!project) return null; - return ( - - ); - }, - })} -
- ); -}; +const { STUDIO_CREATE_NAV, STUDIO_PROJECT_NAV } = ROUTES; const Home: React.FC = () => { const { account } = useWeb3(); const navigate = useNavigate(); - const [showCreateModal, setShowCreateModal] = React.useState(false); - const [getProjects, asyncProjects] = useGetProjectsLazyQuery({ - variables: { offset: 0 }, + const { listsWithSearch } = useProjectList({ + // @ts-ignore + account: '0x70d0AFeE4A6A314d71046DA9B4BbcFB8Fd1722Ce', + onProjectClick: (projectId) => { + navigate(`${STUDIO_PROJECT_NAV}/${projectId}`); + }, }); - const [projects, setProjects] = React.useState([]); - - const loadMore = async () => { - const res = await getProjects({ - variables: { - offset: projects.length, - }, - }); - - if (res.data?.projects?.nodes) { - setProjects([...projects, ...res.data.projects?.nodes.filter(notEmpty)]); - } - - return { - list: [], - isNoMore: !res.error && !res.data?.projects?.nodes.length, - }; - }; - - const handleCreateProject = (name: string) => { - navigate(`${STUDIO_CREATE_NAV}?name=${encodeURI(name)}`); + const [form] = useForm(); + const [showCreateModal, setShowCreateModal] = React.useState(false); + const handleCreateProject = () => { + navigate( + `${STUDIO_CREATE_NAV}?name=${encodeURI(form.getFieldValue('projectName'))}&deploymentId=${form.getFieldValue( + 'deploymentId', + )}`, + ); }; - const enableCreateModal = () => setShowCreateModal(true); - useInfiniteScroll(() => loadMore(), { - target: document, - isNoMore: (d) => !!d?.isNoMore, - threshold: 300, - }); - return ( -
-
-
-
-
+
+
+ My Projects +
setShowCreateModal(false)} - closeTimeoutMS={200} + title="Publish New Project to the Decentralised Network" + width={572} + open={showCreateModal} + onCancel={() => setShowCreateModal(false)} + onOk={async () => { + await form.validateFields(); + + handleCreateProject(); + }} + okText={'Next'} + okButtonProps={{ + shape: 'round', + size: 'large', + }} + cancelButtonProps={{ + style: { display: 'none' }, + }} > - setShowCreateModal(false)} /> +
+ + Please enter the deployment ID of your SubQuery project below. To get the deployment ID, run subql publish + from your project code to publish it to IPFS. Learn how to publish a SubQuery project here. + + +
+ + + +
+ + + + Project names should be unique, you can edit this later. +
+
+
- {renderAsync(asyncProjects, { - loading: () => , - error: (error) =>

{`Failed to load projects: ${error.message}`}

, - data: (_projects) => { - if (!projects?.length) { - return ; - } - - const renderProjects = projects.filter(({ id, owner }) => account && id && owner === account); - - return ( -
- {renderProjects.map((proj) => ( - navigate(`${STUDIO_PROJECT_NAV}/${proj.id}`)} - account={account ?? ''} - projectDetails={proj} - /> - ))} -
- ); - }, - })} + {listsWithSearch}
); }; diff --git a/src/pages/studio/Project/Project.tsx b/src/pages/studio/Project/Project.tsx index 7f32d6914..2c5289eac 100644 --- a/src/pages/studio/Project/Project.tsx +++ b/src/pages/studio/Project/Project.tsx @@ -4,12 +4,12 @@ import * as React from 'react'; import { useTranslation } from 'react-i18next'; import Modal from 'react-modal'; -import { Navigate, Route, useParams } from 'react-router'; +import { useNavigate, useParams } from 'react-router'; import { Button } from '@subql/components'; import { Typography } from 'antd'; import clsx from 'clsx'; -import { NewDeployment, ProjectDetail, ProjectEdit, ProjectHeader, Spinner } from '../../../components'; +import { ProjectDetail, ProjectEdit, ProjectHeader, Spinner } from '../../../components'; import { useWeb3 } from '../../../containers'; import { useCreateDeployment, useProject, useUpdateProjectMetadata } from '../../../hooks'; import { FormProjectMetadata, NewDeployment as NewDeploymentParams } from '../../../models'; @@ -25,7 +25,7 @@ const Project: React.FC = () => { const { account } = useWeb3(); const asyncProject = useProject(id ?? ''); const { t } = useTranslation(); - + const navigate = useNavigate(); const [tab, setTab] = React.useState(DETAILS); const [deploymentModal, setDeploymentModal] = React.useState(false); const [editing, setEditing] = React.useState(false); @@ -57,9 +57,10 @@ const Project: React.FC = () => { return Project doesn't exist; } - if (project.owner !== account) { - return } />; - } + // @ts-expect-error + // if (project.owner !== account) { + // navigate('/studio'); + // } return (
@@ -69,7 +70,7 @@ const Project: React.FC = () => { onRequestClose={() => setDeploymentModal(false)} closeTimeoutMS={200} > - setDeploymentModal(false)} /> + {/* setDeploymentModal(false)} /> */}
diff --git a/src/utils/links.ts b/src/utils/links.ts index a9ced8e03..bf99fa615 100644 --- a/src/utils/links.ts +++ b/src/utils/links.ts @@ -29,7 +29,11 @@ export const entryLinks = [ }, { link: ROUTES.EXPLORER, + key: 'explorer', label: t('header.explorer'), + dropdown: [ + studioLink + ] }, { link: ROUTES.INDEXER, From f6cb2eaad03cd432656fa11bfe01b8a2f877eb2d Mon Sep 17 00:00:00 2001 From: cyrbuzz Date: Tue, 12 Dec 2023 18:09:17 +0800 Subject: [PATCH 02/11] feat: new ui --- src/components/Markdown/index.module.less | 19 -- src/components/Markdown/index.tsx | 16 -- .../ProjectDeployments/ProjectDeployments.tsx | 92 ++++----- .../ProjectDetail/ProjectDetail.tsx | 5 +- .../ProjectOverview/ProjectOverview.tsx | 9 +- src/hooks/useProject.tsx | 2 +- src/pages/studio/Create/Create.tsx | 20 -- src/pages/studio/Project/Deployments.tsx | 2 +- src/pages/studio/Project/Project.module.css | 10 +- src/pages/studio/Project/Project.tsx | 190 +++++++++++------- 10 files changed, 175 insertions(+), 190 deletions(-) delete mode 100644 src/components/Markdown/index.module.less delete mode 100644 src/components/Markdown/index.tsx diff --git a/src/components/Markdown/index.module.less b/src/components/Markdown/index.module.less deleted file mode 100644 index 2d74c86fc..000000000 --- a/src/components/Markdown/index.module.less +++ /dev/null @@ -1,19 +0,0 @@ -.markdown { - color: var(--sq-gray700); - :global { - p { - margin: 1em 0; - } - ul { - margin-left: 22px; - } - li { - margin: 0.5em 0; - } - - img { - max-width: 100%; - margin: 1em 0; - } - } -} \ No newline at end of file diff --git a/src/components/Markdown/index.tsx b/src/components/Markdown/index.tsx deleted file mode 100644 index 6f0355c26..000000000 --- a/src/components/Markdown/index.tsx +++ /dev/null @@ -1,16 +0,0 @@ -// Copyright 2020-2022 SubQuery Pte Ltd authors & contributors -// SPDX-License-Identifier: Apache-2.0 - -import React, { FC } from 'react'; -import MarkdownCompiler, { Options } from 'react-markdown'; - -import styles from './index.module.less'; - -const Markdown: FC = (props) => { - return ( -
- -
- ); -}; -export default Markdown; diff --git a/src/components/ProjectDeployments/ProjectDeployments.tsx b/src/components/ProjectDeployments/ProjectDeployments.tsx index 584aae78e..de25fb6f6 100644 --- a/src/components/ProjectDeployments/ProjectDeployments.tsx +++ b/src/components/ProjectDeployments/ProjectDeployments.tsx @@ -3,66 +3,66 @@ import * as React from 'react'; import { useTranslation } from 'react-i18next'; -import Markdown from '@components/Markdown'; -import moment from 'moment'; +import { Markdown } from '@subql/components'; +import dayjs from 'dayjs'; +import utc from 'dayjs/plugin/utc'; import { NewDeployment } from '../../models'; import { Table, TableBody, TableCell, TableHead, TableRow } from '../Table'; import { Copy } from '..'; import styles from './ProjectDeployments.module.less'; +dayjs.extend(utc); + type Deployment = NewDeployment & { createdAt?: Date }; type Props = { deployments: Deployment[]; + projectId: string; }; -const Row: React.FC<{ deployment: Deployment }> = ({ deployment }) => { - const createdAt = React.useMemo( - () => (deployment.createdAt ? moment(deployment.createdAt).utc(true).fromNow() : 'N/A'), - [deployment], - ); - return ( - - -

{deployment.version}

-
- -
-

{deployment.deploymentId}

- -
-
- -
- {deployment.description} -
-
- -

{createdAt}

-
-
- ); -}; - -const ProjectDeployments: React.FC = ({ deployments }) => { +const ProjectDeployments: React.FC = ({ deployments, projectId }) => { const { t } = useTranslation(); + return ( - - - - {t('deployments.header1')} - {t('deployments.header2')} - {t('deployments.header3')} - {t('deployments.header4')} - - - - {deployments.map((indexer, index) => ( - - ))} - -
+ <> + + + + {t('deployments.header1')} + {t('deployments.header2')} + {t('deployments.header3')} + {t('deployments.header4')} + {/* {t('general.action')} */} + + + + {deployments.map((deployment, index) => ( + + +

{deployment.version}

+
+ +
+

{deployment.deploymentId}

+ +
+
+ +
+ {deployment.description} +
+
+ +

+ {deployment.createdAt ? dayjs(deployment.createdAt).utc(true).fromNow() : 'N/A'} +

+
+
+ ))} +
+
+ ); }; diff --git a/src/components/ProjectDetail/ProjectDetail.tsx b/src/components/ProjectDetail/ProjectDetail.tsx index 4b83225e2..9ebc44cd7 100644 --- a/src/components/ProjectDetail/ProjectDetail.tsx +++ b/src/components/ProjectDetail/ProjectDetail.tsx @@ -3,8 +3,7 @@ import * as React from 'react'; import { useTranslation } from 'react-i18next'; -import Markdown from '@components/Markdown'; -import { Button } from '@subql/components'; +import { Button, Markdown } from '@subql/components'; import { ProjectMetadata } from '../../models'; import Detail from '../Detail'; @@ -21,7 +20,7 @@ const ProjectDetail: React.FC = ({ metadata, onEdit }) => {
- {metadata.description} + {metadata.description}
diff --git a/src/components/ProjectOverview/ProjectOverview.tsx b/src/components/ProjectOverview/ProjectOverview.tsx index dc50d524a..83063f435 100644 --- a/src/components/ProjectOverview/ProjectOverview.tsx +++ b/src/components/ProjectOverview/ProjectOverview.tsx @@ -4,12 +4,11 @@ import * as React from 'react'; import { useTranslation } from 'react-i18next'; import { BsGithub, BsGlobe } from 'react-icons/bs'; -import Markdown from '@components/Markdown'; import NewCard from '@components/NewCard'; import { useRouteQuery } from '@hooks'; import { ProjectDetailsQuery } from '@hooks/useProjectFromQuery'; import { BalanceLayout } from '@pages/dashboard'; -import { Typography } from '@subql/components'; +import { Markdown, Typography } from '@subql/components'; import { formatSQT, useGetOfferCountByDeploymentIdLazyQuery } from '@subql/react-hooks'; import { ProjectMetadata } from '../../models'; @@ -21,7 +20,7 @@ type Props = { deploymentDescription?: string; }; -const ExternalLink: React.FC<{ link?: string; icon: 'globe' | 'github' }> = ({ link, icon }) => { +export const ExternalLink: React.FC<{ link?: string; icon: 'globe' | 'github' }> = ({ link, icon }) => { return (
{icon === 'github' ? ( @@ -63,7 +62,7 @@ const ProjectOverview: React.FC = ({ project, metadata, deploymentDescrip
- {metadata.description || 'N/A'} + {metadata.description || 'N/A'}
@@ -75,7 +74,7 @@ const ProjectOverview: React.FC = ({ project, metadata, deploymentDescrip {t('projectOverview.deploymentDescription')}
- {deploymentDescription || 'N/A'} + {deploymentDescription || 'N/A'}
diff --git a/src/hooks/useProject.tsx b/src/hooks/useProject.tsx index 196ee1ead..cd4debf2e 100644 --- a/src/hooks/useProject.tsx +++ b/src/hooks/useProject.tsx @@ -58,7 +58,7 @@ export function useProject(id: string): AsyncData { } const metadata = await getMetadataFromCid(query.metadata); - + console.warn(query); return { id, owner: query.owner, diff --git a/src/pages/studio/Create/Create.tsx b/src/pages/studio/Create/Create.tsx index bb1025733..584369810 100644 --- a/src/pages/studio/Create/Create.tsx +++ b/src/pages/studio/Create/Create.tsx @@ -151,16 +151,6 @@ const Create: React.FC = () => { onChange={(e) => { form.setFieldValue(field.name, e); }} - inputProps={{ - placeholder: `You can provide a detailed description of your SubQuery project. - -# hello world -- you can use lists -- *Italics* -- *bold* -- [Links](http://subquery.network) - `, - }} /> ); }} @@ -186,16 +176,6 @@ const Create: React.FC = () => { onChange={(e) => { form.setFieldValue(field.name, e); }} - inputProps={{ - placeholder: `You can provide a detailed description of your SubQuery project. - -# hello world -- you can use lists -- *Italics* -- *bold* -- [Links](http://subquery.network) - `, - }} /> ); }} diff --git a/src/pages/studio/Project/Deployments.tsx b/src/pages/studio/Project/Deployments.tsx index d2a85fb31..110310d6c 100644 --- a/src/pages/studio/Project/Deployments.tsx +++ b/src/pages/studio/Project/Deployments.tsx @@ -75,7 +75,7 @@ const DeploymentsTab: React.FC = ({ projectId, currentDeployment }) => { return
There has no deployments for this project
; } - return ; + return ; }, }); }; diff --git a/src/pages/studio/Project/Project.module.css b/src/pages/studio/Project/Project.module.css index a4b896d51..51324d062 100644 --- a/src/pages/studio/Project/Project.module.css +++ b/src/pages/studio/Project/Project.module.css @@ -9,13 +9,11 @@ margin-top: 32px; } -.upper { - padding-top: 80px; - background: var(--sq-background-gradient); -} - .content { - padding-bottom: 80px; + padding: 0 80px 80px 80px; + display: flex; + flex-direction: column; + gap: 24px; } .tabContainer { diff --git a/src/pages/studio/Project/Project.tsx b/src/pages/studio/Project/Project.tsx index 2c5289eac..0095abf21 100644 --- a/src/pages/studio/Project/Project.tsx +++ b/src/pages/studio/Project/Project.tsx @@ -2,52 +2,36 @@ // SPDX-License-Identifier: Apache-2.0 import * as React from 'react'; -import { useTranslation } from 'react-i18next'; -import Modal from 'react-modal'; import { useNavigate, useParams } from 'react-router'; -import { Button } from '@subql/components'; -import { Typography } from 'antd'; +import { ExternalLink } from '@components/ProjectOverview/ProjectOverview'; +import { Markdown, Typography } from '@subql/components'; +import { Breadcrumb, Form, Input, Modal } from 'antd'; +import { useForm } from 'antd/es/form/Form'; import clsx from 'clsx'; -import { ProjectDetail, ProjectEdit, ProjectHeader, Spinner } from '../../../components'; +import { IPFSImage, Spinner } from '../../../components'; import { useWeb3 } from '../../../containers'; -import { useCreateDeployment, useProject, useUpdateProjectMetadata } from '../../../hooks'; -import { FormProjectMetadata, NewDeployment as NewDeploymentParams } from '../../../models'; -import { modalStyles, renderAsync } from '../../../utils'; -import { ROUTES } from '../../../utils'; +import { useCreateDeployment, useProject } from '../../../hooks'; +import { renderAsync } from '../../../utils'; import DeploymentsTab from './Deployments'; import styles from './Project.module.css'; -const { DETAILS, DEPLOYMENTS } = ROUTES; - const Project: React.FC = () => { const { id } = useParams(); const { account } = useWeb3(); const asyncProject = useProject(id ?? ''); - const { t } = useTranslation(); const navigate = useNavigate(); - const [tab, setTab] = React.useState(DETAILS); + const [form] = useForm(); const [deploymentModal, setDeploymentModal] = React.useState(false); - const [editing, setEditing] = React.useState(false); const createDeployment = useCreateDeployment(id ?? ''); - const updateMetadata = useUpdateProjectMetadata(id ?? ''); - const handleSubmitCreate = async (details: NewDeploymentParams) => { - await createDeployment(details); + const handleSubmitCreate = async () => { + await form.validateFields(); + await createDeployment(form.getFieldsValue()); + form.resetFields(); setDeploymentModal(false); }; - const handleNewDeployment = () => setDeploymentModal(true); - const handleEditMetadata = () => setEditing(true); - const handleSubmitEdit = async (metadata: FormProjectMetadata) => { - await updateMetadata(metadata); - - // TODO call this once tx submitted, but not confirmed - setEditing(false); - }; - - const tabStyle = (curTab: typeof DETAILS | typeof DEPLOYMENTS) => (curTab === tab ? undefined : 'secondary'); - return renderAsync(asyncProject, { loading: () => , error: (error: Error) => {`Failed to load project: ${error.message}`}, @@ -65,57 +49,117 @@ const Project: React.FC = () => { return (
setDeploymentModal(false)} - closeTimeoutMS={200} + open={deploymentModal} + onCancel={() => setDeploymentModal(false)} + title="Add New Deployment Version" + width={572} + cancelButtonProps={{ + style: { + display: 'none', + }, + }} + okText="Add" + okButtonProps={{ + shape: 'round', + size: 'large', + }} + onOk={() => { + handleSubmitCreate(); + }} > - {/* setDeploymentModal(false)} /> */} +
+
+ + + + + + + + { + form.setFieldValue('deploymentDesc', e); + }} + > + +
+
-
-
- {/* ignore it for now. studio would refactor later. */} - {/* eslint-disable-next-line @typescript-eslint/ban-ts-comment */} - {/* @ts-ignore */} - -
- setTab(DETAILS)} className={`${styles.tab} `} type={tabStyle(DETAILS)}> - {'Details'} - - setTab(DEPLOYMENTS)} - className={styles.tab} - type={tabStyle(DEPLOYMENTS)} - > - {'Deployments'} - +
+ + SubQuery Projects + + ), + onClick: () => { + navigate('/studio'); + }, + }, + { + key: 'current', + title: project.metadata.name, + }, + ]} + > + +
+ {' '} +
+
+ + {project.metadata.name} + +
+ + + Project Detail + + +
+ {project.metadata.description} +
+ + + + +
+
- {tab === DETAILS && ( - <> - {editing ? ( - setEditing(false)} /> - ) : ( - - )} - - )} - {tab === DEPLOYMENTS && ( -
- -
- )} +
+ + Deployment Details + + + { + setDeploymentModal(true); + }} + > + Deploy New Version + +
+
); From 27aff20c04fc33028ef2df4103391d9bc2f09b40 Mon Sep 17 00:00:00 2001 From: cyrbuzz Date: Wed, 13 Dec 2023 10:37:47 +0800 Subject: [PATCH 03/11] feat: new studio --- src/hooks/useProject.tsx | 4 +- src/hooks/useStudio.ts | 4 -- src/pages/studio/Create/Create.tsx | 102 ++++++++++++++++++--------- src/pages/studio/Project/Project.tsx | 20 ++++-- 4 files changed, 87 insertions(+), 43 deletions(-) diff --git a/src/hooks/useProject.tsx b/src/hooks/useProject.tsx index cd4debf2e..fcdb54684 100644 --- a/src/hooks/useProject.tsx +++ b/src/hooks/useProject.tsx @@ -20,7 +20,7 @@ export function useProject(id: string): AsyncData { const [cacheBreak, setCacheBreak] = useState(0); const sub = useCallback(async () => { - if (!contracts) { + if (!contracts || !id) { return () => undefined; } @@ -58,7 +58,7 @@ export function useProject(id: string): AsyncData { } const metadata = await getMetadataFromCid(query.metadata); - console.warn(query); + return { id, owner: query.owner, diff --git a/src/hooks/useStudio.ts b/src/hooks/useStudio.ts index e1cd22034..9185d30da 100644 --- a/src/hooks/useStudio.ts +++ b/src/hooks/useStudio.ts @@ -12,10 +12,6 @@ export function useStudioEnabled() { const [studioEnabled, setStudioEnabled] = useState(false); const checkStudioEnabled = useCallback(async () => { - // @ts-ignore - setStudioEnabled(true); - return - console.log('account:', account); if (!contracts || !account) return; const studioEnabled = await contracts.queryRegistry.creatorWhitelist(account); setStudioEnabled(studioEnabled); diff --git a/src/pages/studio/Create/Create.tsx b/src/pages/studio/Create/Create.tsx index 584369810..cfff53e51 100644 --- a/src/pages/studio/Create/Create.tsx +++ b/src/pages/studio/Create/Create.tsx @@ -6,13 +6,13 @@ import { useTranslation } from 'react-i18next'; import { useNavigate } from 'react-router'; import { CloseOutlined } from '@ant-design/icons'; import { BigNumber } from '@ethersproject/bignumber'; -import { Markdown, Typography } from '@subql/components'; +import { Markdown, Spinner, Typography } from '@subql/components'; import { Button, Modal, Result } from 'antd'; import clsx from 'clsx'; import { Field, Form, Formik } from 'formik'; import { FTextInput, ImageInput } from '../../../components'; -import { useCreateProject, useRouteQuery } from '../../../hooks'; +import { useCreateProject, useProject, useRouteQuery, useUpdateProjectMetadata } from '../../../hooks'; import { FormCreateProjectMetadata, newDeploymentSchema, projectMetadataSchema } from '../../../models'; import { isEthError, parseError } from '../../../utils'; import { ROUTES } from '../../../utils'; @@ -24,19 +24,38 @@ const Create: React.FC = () => { const { t } = useTranslation(); const query = useRouteQuery(); + const asyncProject = useProject(query.get('id') ?? ''); + + const isEdit = React.useMemo(() => query.get('id'), [query]); const navigate = useNavigate(); const createProject = useCreateProject(); + const updateMetadata = useUpdateProjectMetadata(query.get('id') ?? ''); const [submitError, setSubmitError] = React.useState(); const handleSubmit = React.useCallback( async (project: FormCreateProjectMetadata & { versionDescription: string }) => { try { - // Form can give us a File type that doesn't match the schema - const queryId = await createProject(project); + let resultId = query.get('id'); + if (isEdit) { + const payload = { + name: project.name, + description: project.description, + websiteUrl: project.websiteUrl, + codeUrl: project.codeUrl, + image: project.image, + version: project.version, + versionDescription: project.versionDescription, + }; + return; + await updateMetadata(payload); + } else { + // Form can give us a File type that doesn't match the schema + const queryId = await createProject(project); - const idHex = BigNumber.from(queryId).toHexString(); + resultId = BigNumber.from(queryId).toHexString(); + } const { destroy } = Modal.info({ width: 572, @@ -61,7 +80,7 @@ const Create: React.FC = () => { shape="round" size="large" onClick={() => { - navigate(`${STUDIO_PROJECT_NAV}/${idHex}`); + navigate(`${STUDIO_PROJECT_NAV}/${resultId}`); }} > View project in Explorer @@ -81,9 +100,16 @@ const Create: React.FC = () => { setSubmitError(parseError(e)); } }, - [navigate, createProject, t], + [navigate, createProject, t, isEdit], ); + if (isEdit && !asyncProject.data) + return ( +
+ +
+ ); + return (
{ version: '1.0.0', versionDescription: '', deploymentId: query.get('deploymentId') ?? '', + ...(isEdit ? asyncProject.data?.metadata : {}), }} - validationSchema={projectMetadataSchema.shape({}).concat(newDeploymentSchema.shape({}))} + validationSchema={ + isEdit + ? projectMetadataSchema.shape({}) + : projectMetadataSchema.shape({}).concat(newDeploymentSchema.shape({})) + } onSubmit={handleSubmit} > {({ setFieldValue, values, isSubmitting, submitForm, errors }) => { @@ -124,7 +155,7 @@ const Create: React.FC = () => { shape="round" size="large" > - Publish + {isEdit ? 'Save Changes' : 'Publish'}
@@ -157,29 +188,36 @@ const Create: React.FC = () => { -

Deployment Details

- - - {t('studio.create.versionDesc')} - - {({ - field, - form, - }: { - field: { name: string; value: string }; - // eslint-disable-next-line @typescript-eslint/no-explicit-any - form: { setFieldValue: (field: string, val: any) => void }; - }) => { - return ( - { - form.setFieldValue(field.name, e); - }} - /> - ); - }} - + {isEdit ? ( + '' + ) : ( + <> +

Deployment Details

+ + + {t('studio.create.versionDesc')} + + {({ + field, + form, + }: { + field: { name: string; value: string }; + // eslint-disable-next-line @typescript-eslint/no-explicit-any + form: { setFieldValue: (field: string, val: any) => void }; + }) => { + return ( + { + form.setFieldValue(field.name, e); + }} + /> + ); + }} + + + )} + {submitError && {submitError}}
diff --git a/src/pages/studio/Project/Project.tsx b/src/pages/studio/Project/Project.tsx index 0095abf21..1660f2d55 100644 --- a/src/pages/studio/Project/Project.tsx +++ b/src/pages/studio/Project/Project.tsx @@ -5,7 +5,7 @@ import * as React from 'react'; import { useNavigate, useParams } from 'react-router'; import { ExternalLink } from '@components/ProjectOverview/ProjectOverview'; import { Markdown, Typography } from '@subql/components'; -import { Breadcrumb, Form, Input, Modal } from 'antd'; +import { Breadcrumb, Button, Form, Input, Modal } from 'antd'; import { useForm } from 'antd/es/form/Form'; import clsx from 'clsx'; @@ -41,10 +41,9 @@ const Project: React.FC = () => { return Project doesn't exist; } - // @ts-expect-error - // if (project.owner !== account) { - // navigate('/studio'); - // } + if (project.owner !== account) { + navigate('/studio'); + } return (
@@ -120,6 +119,17 @@ const Project: React.FC = () => { {project.metadata.name} + +
From ea8903b6ce633ab1b5dc77018d4243baffbd37d4 Mon Sep 17 00:00:00 2001 From: cyrbuzz Date: Wed, 13 Dec 2023 10:42:09 +0800 Subject: [PATCH 04/11] chore: clear --- src/utils/limitation.ts | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/src/utils/limitation.ts b/src/utils/limitation.ts index be9834004..5b3640c0d 100644 --- a/src/utils/limitation.ts +++ b/src/utils/limitation.ts @@ -23,13 +23,11 @@ export const makeCacheKey = ( type?: string; // maybe need to make a const varible. gerneral/sqt/flexplan such as. } = {}, ) => { - // const prefix = options.prefix ?? import.meta.env.MODE - // const type = options.type ?? 'gerneral' - // const suffix = options const { prefix = import.meta.env.MODE, type = 'gerneral', suffix = '' } = options; return `${prefix}-${type}-${key}-${suffix}`; }; +// eslint-disable-next-line @typescript-eslint/no-explicit-any export const cachedResult: Map = new Map(); export enum limitContractCacheEnum { From 34598e43f1daad9cb4bae9cd591279c3506cef7a Mon Sep 17 00:00:00 2001 From: cyrbuzz Date: Wed, 13 Dec 2023 11:27:06 +0800 Subject: [PATCH 05/11] chore: update library --- package.json | 2 +- yarn.lock | 8 ++++---- 2 files changed, 5 insertions(+), 5 deletions(-) diff --git a/package.json b/package.json index d830d76df..9fea49af7 100644 --- a/package.json +++ b/package.json @@ -20,7 +20,7 @@ "@rainbow-me/rainbowkit": "^1.2.0", "@sentry/react": "^7.57.0", "@subql/apollo-links": "^1.2.3", - "@subql/components": "1.0.3-20", + "@subql/components": "1.0.3-21", "@subql/contract-sdk": "0.16.2", "@subql/network-clients": "^0.3.17", "@subql/network-config": "0.3.17-2", diff --git a/yarn.lock b/yarn.lock index ef6c9ee18..c9b1c59dc 100644 --- a/yarn.lock +++ b/yarn.lock @@ -4629,10 +4629,10 @@ jwt-decode "^3.1.2" lru-cache "^10.0.1" -"@subql/components@1.0.3-20": - version "1.0.3-20" - resolved "https://registry.npmjs.org/@subql/components/-/components-1.0.3-20.tgz#c7b7b8b22c68a7d0eb1c5ac6eb22af61c6c43c3b" - integrity sha512-2nmfjbnUXRc634ctnNqzYSJBhLrOndI8BoOUZxurihYOZ1qEfDsMeNCIptFnU4aQDUlSd0GyEctlqvsSkniEtg== +"@subql/components@1.0.3-21": + version "1.0.3-21" + resolved "https://registry.npmjs.org/@subql/components/-/components-1.0.3-21.tgz#af1c55bc6dc0f78626e361ea5d024f670249235d" + integrity sha512-B5f6h6CvzL1tQ1s/s034q9IV7IRL/1DJis+rMloHbQBrwCz4NgE5x1oV18iP9Yik9f2t2gXzWCmmlOng/DvYcQ== dependencies: "@graphiql/plugin-explorer" "^0.3.4" "@graphiql/toolkit" "^0.9.1" From 79749eb0b1cfe8bb56034c5ed8f41000daf15e49 Mon Sep 17 00:00:00 2001 From: cyrbuzz Date: Wed, 13 Dec 2023 11:48:34 +0800 Subject: [PATCH 06/11] chore: clear useless --- .../ProjectDetail/ProjectDetail.module.css | 35 -------- .../ProjectDetail/ProjectDetail.tsx | 45 ---------- src/components/ProjectDetail/index.ts | 4 - .../ProjectEdit/ProjectEdit.module.css | 29 ------ src/components/ProjectEdit/ProjectEdit.tsx | 88 ------------------- src/components/ProjectEdit/index.ts | 4 - src/components/index.ts | 4 - src/hooks/useProjectList.tsx | 25 +++--- src/hooks/useStudio.ts | 2 + src/pages/studio/Home/Home.tsx | 3 +- 10 files changed, 15 insertions(+), 224 deletions(-) delete mode 100644 src/components/ProjectDetail/ProjectDetail.module.css delete mode 100644 src/components/ProjectDetail/ProjectDetail.tsx delete mode 100644 src/components/ProjectDetail/index.ts delete mode 100644 src/components/ProjectEdit/ProjectEdit.module.css delete mode 100644 src/components/ProjectEdit/ProjectEdit.tsx delete mode 100644 src/components/ProjectEdit/index.ts diff --git a/src/components/ProjectDetail/ProjectDetail.module.css b/src/components/ProjectDetail/ProjectDetail.module.css deleted file mode 100644 index a7d9ffa42..000000000 --- a/src/components/ProjectDetail/ProjectDetail.module.css +++ /dev/null @@ -1,35 +0,0 @@ -.container { - display: flex; - flex-direction: column; - /*background-color: white; - border: 1px solid rgba(0, 0, 0, 0.12); - border-radius: 10px;*/ - padding-top: 50px; - /*margin: 32px;*/ - /*flex-wrap: wrap;*/ - max-width: 1280px; -} - -.column { - display: flex; - flex-grow: 1; - flex-direction: column; - align-items: flex-start; -} - -.left { - display: flex; - flex-direction: row; - justify-content: space-around; - align-self: normal; -} - -.editContainer { - display: flex; - justify-content: flex-start; -} - -.edit { - min-width: 70px; - margin-top: 50px; -} diff --git a/src/components/ProjectDetail/ProjectDetail.tsx b/src/components/ProjectDetail/ProjectDetail.tsx deleted file mode 100644 index 9ebc44cd7..000000000 --- a/src/components/ProjectDetail/ProjectDetail.tsx +++ /dev/null @@ -1,45 +0,0 @@ -// Copyright 2020-2022 SubQuery Pte Ltd authors & contributors -// SPDX-License-Identifier: Apache-2.0 - -import * as React from 'react'; -import { useTranslation } from 'react-i18next'; -import { Button, Markdown } from '@subql/components'; - -import { ProjectMetadata } from '../../models'; -import Detail from '../Detail'; -import styles from './ProjectDetail.module.css'; - -type Props = { - metadata: ProjectMetadata; - onEdit?: () => void; -}; - -const ProjectDetail: React.FC = ({ metadata, onEdit }) => { - const { t } = useTranslation(); - return ( -
-
- - {metadata.description} - -
-
- -
-
- -
-
-
-
- {onEdit &&
-
- ); -}; - -export default ProjectDetail; diff --git a/src/components/ProjectDetail/index.ts b/src/components/ProjectDetail/index.ts deleted file mode 100644 index d80eed17b..000000000 --- a/src/components/ProjectDetail/index.ts +++ /dev/null @@ -1,4 +0,0 @@ -// Copyright 2020-2022 SubQuery Pte Ltd authors & contributors -// SPDX-License-Identifier: Apache-2.0 - -export { default } from './ProjectDetail'; diff --git a/src/components/ProjectEdit/ProjectEdit.module.css b/src/components/ProjectEdit/ProjectEdit.module.css deleted file mode 100644 index 54e8cdc77..000000000 --- a/src/components/ProjectEdit/ProjectEdit.module.css +++ /dev/null @@ -1,29 +0,0 @@ -.form { - padding-top: 50px; - display: flex; - flex-direction: row; - align-items: space-between; -} - -.fields { - flex-grow: 100; - padding-bottom: 32px; -} - -.image { - padding-left: 48px; -} - -.submit { - min-width: 100px; -} - -.cancel { - margin-left: 12px; - min-width: 100px; -} - -.error { - color: var(--sq-error); - padding-top: 8px; -} diff --git a/src/components/ProjectEdit/ProjectEdit.tsx b/src/components/ProjectEdit/ProjectEdit.tsx deleted file mode 100644 index 7a3029d9f..000000000 --- a/src/components/ProjectEdit/ProjectEdit.tsx +++ /dev/null @@ -1,88 +0,0 @@ -// Copyright 2020-2022 SubQuery Pte Ltd authors & contributors -// SPDX-License-Identifier: Apache-2.0 - -import * as React from 'react'; -import { useTranslation } from 'react-i18next'; -import { Button, Typography } from '@subql/components'; -import { Form, Formik } from 'formik'; - -import { FormProjectMetadata, projectMetadataSchema, ProjectWithMetadata } from '../../models'; -import { isEthError, parseError } from '../../utils'; -import ImageInput from '../ImageInput'; -import { FTextInput } from '..'; -import styles from './ProjectEdit.module.css'; - -type Props = { - project: Required; - onSubmit: (metadata: FormProjectMetadata) => void | Promise; - onCancel?: () => void; -}; - -const ProjectEdit: React.FC = (props) => { - const { t } = useTranslation(); - const [submitError, setSubmitError] = React.useState(); - - const handleSubmit = async (metadata: FormProjectMetadata) => { - try { - await props.onSubmit(metadata); - } catch (e) { - if (isEthError(e) && e.code === 4001) { - setSubmitError(t('errors.transactionRejected')); - return; - } - setSubmitError(parseError(e)); - } - }; - - return ( -
- - {({ isSubmitting, submitForm, setFieldValue, values }) => ( -
-
-
- - - - - {submitError && {submitError}} -
-
- setFieldValue('image', value)} - placeholder="/static/default.project.png" - /> -
-
-
- {props.onCancel && ( -
-
- )} -
-
- ); -}; - -export default ProjectEdit; diff --git a/src/components/ProjectEdit/index.ts b/src/components/ProjectEdit/index.ts deleted file mode 100644 index d5f6e92a8..000000000 --- a/src/components/ProjectEdit/index.ts +++ /dev/null @@ -1,4 +0,0 @@ -// Copyright 2020-2022 SubQuery Pte Ltd authors & contributors -// SPDX-License-Identifier: Apache-2.0 - -export { default } from './ProjectEdit'; diff --git a/src/components/index.ts b/src/components/index.ts index 2f08842e4..3aebda5df 100644 --- a/src/components/index.ts +++ b/src/components/index.ts @@ -10,8 +10,6 @@ import IndexerProgress from './IndexerProgress'; import IPFSImage from './IPFSImage'; import ProjectCard from './ProjectCard'; import ProjectDeployments from './ProjectDeployments'; -import ProjectDetail from './ProjectDetail'; -import ProjectEdit from './ProjectEdit'; import ProjectHeader from './ProjectHeader'; import ProjectOverview from './ProjectOverview'; import Spinner from './Spinner'; @@ -53,8 +51,6 @@ export { IPFSImage, ProjectHeader, ProjectCard, - ProjectDetail, - ProjectEdit, ProjectDeployments, ProjectOverview, IndexerProgress, diff --git a/src/hooks/useProjectList.tsx b/src/hooks/useProjectList.tsx index 6912e71f8..43eade28e 100644 --- a/src/hooks/useProjectList.tsx +++ b/src/hooks/useProjectList.tsx @@ -108,7 +108,7 @@ export const useProjectList = (props: UseProjectListProps = {}) => { // filter once or twice is the same. const nonEmptyProjects = res.data.projects?.nodes.filter(notEmpty).filter(notEmpty); const mergered = options?.refresh ? [...nonEmptyProjects] : [...fetchedProejcts.current, ...nonEmptyProjects]; - setProjects(mergered); + setProjects(mergered.filter((proj) => (account ? account.toLowerCase() === proj.owner.toLowerCase() : true))); fetchedProejcts.current = mergered; updatedLength = mergered.length; setTotal(res.data?.projects?.totalCount); @@ -172,18 +172,17 @@ export const useProjectList = (props: UseProjectListProps = {}) => {
{topProjectItem} {projects?.length - ? projects - .filter((proj) => (account ? account.toLowerCase() === proj.owner.toLowerCase() : true)) - .map((project) => ( - { - onProjectClick?.(project.id); - }} - /> - )) - : ''} + ? projects.map((project) => ( + { + onProjectClick?.(project.id); + }} + /> + )) + : // TODO: update UI + 'No Projects'} {loading && new Array(projects.length + 10 <= total ? 10 : total - projects.length).fill(0).map((_, i) => { return ; diff --git a/src/hooks/useStudio.ts b/src/hooks/useStudio.ts index 9185d30da..df8f3a631 100644 --- a/src/hooks/useStudio.ts +++ b/src/hooks/useStudio.ts @@ -12,6 +12,8 @@ export function useStudioEnabled() { const [studioEnabled, setStudioEnabled] = useState(false); const checkStudioEnabled = useCallback(async () => { + setStudioEnabled(true); + return true; if (!contracts || !account) return; const studioEnabled = await contracts.queryRegistry.creatorWhitelist(account); setStudioEnabled(studioEnabled); diff --git a/src/pages/studio/Home/Home.tsx b/src/pages/studio/Home/Home.tsx index e4497f19d..136fa80cc 100644 --- a/src/pages/studio/Home/Home.tsx +++ b/src/pages/studio/Home/Home.tsx @@ -17,8 +17,7 @@ const Home: React.FC = () => { const { account } = useWeb3(); const navigate = useNavigate(); const { listsWithSearch } = useProjectList({ - // @ts-ignore - account: '0x70d0AFeE4A6A314d71046DA9B4BbcFB8Fd1722Ce', + account, onProjectClick: (projectId) => { navigate(`${STUDIO_PROJECT_NAV}/${projectId}`); }, From aab571b3d80a7cabb661f3671b421fbd0f55b3f8 Mon Sep 17 00:00:00 2001 From: cyrbuzz Date: Wed, 13 Dec 2023 11:49:45 +0800 Subject: [PATCH 07/11] chore: clear useless --- src/hooks/useStudio.ts | 2 -- 1 file changed, 2 deletions(-) diff --git a/src/hooks/useStudio.ts b/src/hooks/useStudio.ts index df8f3a631..9185d30da 100644 --- a/src/hooks/useStudio.ts +++ b/src/hooks/useStudio.ts @@ -12,8 +12,6 @@ export function useStudioEnabled() { const [studioEnabled, setStudioEnabled] = useState(false); const checkStudioEnabled = useCallback(async () => { - setStudioEnabled(true); - return true; if (!contracts || !account) return; const studioEnabled = await contracts.queryRegistry.creatorWhitelist(account); setStudioEnabled(studioEnabled); From 0b061eab3c0d281886968cc89e1a69c097a9b929 Mon Sep 17 00:00:00 2001 From: cyrbuzz Date: Wed, 13 Dec 2023 13:38:23 +0800 Subject: [PATCH 08/11] chore: fix loading --- src/hooks/useProjectList.tsx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/hooks/useProjectList.tsx b/src/hooks/useProjectList.tsx index 43eade28e..30888a078 100644 --- a/src/hooks/useProjectList.tsx +++ b/src/hooks/useProjectList.tsx @@ -182,7 +182,7 @@ export const useProjectList = (props: UseProjectListProps = {}) => { /> )) : // TODO: update UI - 'No Projects'} + ''} {loading && new Array(projects.length + 10 <= total ? 10 : total - projects.length).fill(0).map((_, i) => { return ; From b68cfdfc3ebaaf72a394fd6363abad40b712bd09 Mon Sep 17 00:00:00 2001 From: cyrbuzz Date: Wed, 13 Dec 2023 13:42:10 +0800 Subject: [PATCH 09/11] fix: loading-status --- src/hooks/useProjectList.tsx | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/src/hooks/useProjectList.tsx b/src/hooks/useProjectList.tsx index 30888a078..109f23f5a 100644 --- a/src/hooks/useProjectList.tsx +++ b/src/hooks/useProjectList.tsx @@ -182,7 +182,9 @@ export const useProjectList = (props: UseProjectListProps = {}) => { /> )) : // TODO: update UI - ''} + loading + ? '' + : 'No projects'} {loading && new Array(projects.length + 10 <= total ? 10 : total - projects.length).fill(0).map((_, i) => { return ; From a9aaf8753efb9c407832f348e19cd06eb6df62cc Mon Sep 17 00:00:00 2001 From: cyrbuzz Date: Wed, 13 Dec 2023 17:23:23 +0800 Subject: [PATCH 10/11] feat: category --- public/sw.js | 2 +- src/containers/ProjectMetadata.ts | 1 + src/containers/ProjectRegistry.ts | 6 +- src/hooks/useCreateProject.tsx | 2 +- src/models.tsx | 1 + src/pages/studio/Create/Create.module.css | 46 --------- src/pages/studio/Create/Create.module.less | 105 +++++++++++++++++++++ src/pages/studio/Create/Create.tsx | 83 +++++++++++++++- src/pages/studio/Project/Project.tsx | 29 +++++- 9 files changed, 219 insertions(+), 56 deletions(-) delete mode 100644 src/pages/studio/Create/Create.module.css create mode 100644 src/pages/studio/Create/Create.module.less diff --git a/public/sw.js b/public/sw.js index afac9fa11..0c4b24ca7 100644 --- a/public/sw.js +++ b/public/sw.js @@ -11,7 +11,7 @@ workbox.setConfig({ workbox.routing.registerRoute( // \/{0,1} will also match dev.thechaindata.comxxxyyy, but this is would not a valid suffix, so it can be use. // for match dev.thechaindata.com & dev.thechaindata.com/ - /((localhost:3006)|((dev|kepler)\.thechaindata\.com)|(kepler\.subquery\.network))\/{0,1}(?=((dashboard)|(explorer)|(profile)|(indexer)|(delegator)|(consumer)|(swap)|(studio))|\?|$).*/g, + /(((dev|kepler)\.thechaindata\.com)|(kepler\.subquery\.network))\/{0,1}(?=((dashboard)|(explorer)|(profile)|(indexer)|(delegator)|(consumer)|(swap)|(studio))|\?|$).*/g, // https://developers.google.com/web/tools/workbox/reference-docs/latest/workbox.strategies new workbox.strategies.NetworkFirst({ cacheName: 'workbox:html', diff --git a/src/containers/ProjectMetadata.ts b/src/containers/ProjectMetadata.ts index 01fbd86c2..e3f1ef61c 100644 --- a/src/containers/ProjectMetadata.ts +++ b/src/containers/ProjectMetadata.ts @@ -16,6 +16,7 @@ function useProjectMetadataImpl() { const getMetadataFromCid = async (cid: string): Promise => { const result = await catSingle(cid); + const rawMeta = JSON.parse(Buffer.from(result).toString('utf8')); return projectMetadataSchema.validate({ type: 'SUBQUERY', ...rawMeta }); }; diff --git a/src/containers/ProjectRegistry.ts b/src/containers/ProjectRegistry.ts index 0e3fb84cb..2905475ec 100644 --- a/src/containers/ProjectRegistry.ts +++ b/src/containers/ProjectRegistry.ts @@ -122,7 +122,11 @@ function useProjectRegistryImpl(logger: Logger) { projectRegistry.tokenURI(id), ]); - const deploymentInfo = await projectRegistry.deploymentInfos(cidToBytes32(project.latestDeploymentId)); + const deploymentInfo = await projectRegistry.deploymentInfos( + project.latestDeploymentId.startsWith('Qm') + ? cidToBytes32(project.latestDeploymentId) + : project.latestDeploymentId, + ); projectCache.current[BigNumber.from(id).toString()] = { owner, diff --git a/src/hooks/useCreateProject.tsx b/src/hooks/useCreateProject.tsx index c8f92ea8a..ec3d50ef7 100644 --- a/src/hooks/useCreateProject.tsx +++ b/src/hooks/useCreateProject.tsx @@ -43,7 +43,7 @@ export function useCreateProject(): (params: P) => Promise { throw new Error('No successful events'); } - return event.args?.['queryId']; + return event.args?.['tokenId']; }, [ipfs, uploadMetadata, registerProject, uploadVersionMetadata], ); diff --git a/src/models.tsx b/src/models.tsx index 95a67d497..200200dcb 100644 --- a/src/models.tsx +++ b/src/models.tsx @@ -18,6 +18,7 @@ export const projectMetadataSchema = yup.object({ websiteUrl: yup.string().optional().url(), codeUrl: yup.string().optional().url(), versionDescription: yup.string().default('').optional(), + categories: yup.array().max(2), }); export type ProjectMetadata = yup.Asserts; diff --git a/src/pages/studio/Create/Create.module.css b/src/pages/studio/Create/Create.module.css deleted file mode 100644 index eecc65ee1..000000000 --- a/src/pages/studio/Create/Create.module.css +++ /dev/null @@ -1,46 +0,0 @@ -.form { - display: flex; - flex-direction: row; - align-items: flex-start; - padding: 0 80px 80px 80px; -} - -.deployment { - font-family: Futura; - font-weight: 500; - font-size: 18px; - line-height: 28px; - color: var(--gray900); - margin-top: 8px; -} - -.header { - display: flex; - flex-direction: row; - justify-content: space-between; - padding: 64px 80px 40px 80px; -} - -.name { - font-family: Futura; - font-weight: 500; - font-size: 32px; - line-height: 48px; - - color: var(--gray900); - padding-left: 22px; -} - -.details { - display: flex; - flex-direction: row; - gap: 22px; -} - -.fields { - display: flex; - flex-direction: column; - gap: 16px; - max-width: 845px; - flex: 1; -} diff --git a/src/pages/studio/Create/Create.module.less b/src/pages/studio/Create/Create.module.less new file mode 100644 index 000000000..2e860d200 --- /dev/null +++ b/src/pages/studio/Create/Create.module.less @@ -0,0 +1,105 @@ +.form { + display: flex; + flex-direction: row; + align-items: flex-start; + padding: 0 80px 80px 80px; +} + +.deployment { + font-family: Futura; + font-weight: 500; + font-size: 18px; + line-height: 28px; + color: var(--gray900); + margin-top: 8px; +} + +.header { + display: flex; + flex-direction: row; + justify-content: space-between; + padding: 64px 80px 40px 80px; +} + +.name { + font-family: Futura; + font-weight: 500; + font-size: 32px; + line-height: 48px; + + color: var(--gray900); + padding-left: 22px; +} + +.details { + display: flex; + flex-direction: row; + gap: 22px; +} + +.fields { + display: flex; + flex-direction: column; + gap: 16px; + max-width: 845px; + flex: 1; +} + +.successModal { + :global { + .ant-modal-confirm-body { + flex-direction: column; + } + + .ant-modal-confirm-paragraph.ant-modal-confirm-paragraph { + max-width: 100%; + } + } +} + +.checkbox { + :global { + .ant-checkbox-group { + .ant-checkbox-wrapper { + background: var(--sq-gray200); + color: var(--sq-gray500); + padding: 5px 16px; + border-radius: 100px; + font-family: var(--sq-font-family); + .ant-checkbox { + &-inner { + width: 0px; + height: 0px; + border: none; + &::after { + width: 0px; + height: 0px; + } + } + } + } + + .ant-checkbox-wrapper.ant-checkbox-wrapper-checked { + color: var(--sq-blue600); + background: rgba(67, 136, 221, 0.10); + + .ant-checkbox { + display: inline-flex; + + &-inner { + // override the antd style. a lazy way. + background: transparent!important; + border: none; + width: 16px; + height: 16px; + &::after { + border-color: var(--sq-blue600); + width: 6px; + height: 9px; + } + } + } + } + } + } +} diff --git a/src/pages/studio/Create/Create.tsx b/src/pages/studio/Create/Create.tsx index b77923e79..e9d195284 100644 --- a/src/pages/studio/Create/Create.tsx +++ b/src/pages/studio/Create/Create.tsx @@ -7,18 +7,41 @@ import { useNavigate } from 'react-router'; import { CloseOutlined } from '@ant-design/icons'; import { BigNumber } from '@ethersproject/bignumber'; import { Markdown, Spinner, Typography } from '@subql/components'; -import { Button, Modal, Result } from 'antd'; +import { Button, Checkbox, Modal, Radio, Result } from 'antd'; import clsx from 'clsx'; -import { Field, Form, Formik } from 'formik'; +import { Field, FieldArray, Form, Formik } from 'formik'; import { FTextInput, ImageInput } from '../../../components'; import { useCreateProject, useProject, useRouteQuery, useUpdateProjectMetadata } from '../../../hooks'; import { FormCreateProjectMetadata, newDeploymentSchema, projectMetadataSchema } from '../../../models'; import { isEthError, parseError, ROUTES } from '../../../utils'; -import styles from './Create.module.css'; +import styles from './Create.module.less'; const { STUDIO_PROJECT_NAV } = ROUTES; +const categoriesOptions = [ + { + label: 'Dictionary', + value: 'Dictionary', + }, + { + label: 'DeFi', + value: 'DeFi', + }, + { + label: 'Oracle', + value: 'Oracle', + }, + { + label: 'Wallet', + value: 'Wallet', + }, + { + label: 'NFT', + value: 'NFT', + }, +]; + const Create: React.FC = () => { const { t } = useTranslation(); @@ -47,6 +70,7 @@ const Create: React.FC = () => { version: project.version, versionDescription: project.versionDescription, type: project.type, + categories: project.categories, }; await updateMetadata(payload); } else { @@ -56,7 +80,8 @@ const Create: React.FC = () => { resultId = BigNumber.from(queryId).toHexString(); } - const { destroy } = Modal.info({ + const { destroy } = Modal.success({ + className: styles.successModal, width: 572, icon: (
@@ -80,6 +105,7 @@ const Create: React.FC = () => { size="large" onClick={() => { navigate(`${STUDIO_PROJECT_NAV}/${resultId}`); + destroy(); }} > View project in Explorer @@ -122,6 +148,7 @@ const Create: React.FC = () => { version: '1.0.0', versionDescription: '', deploymentId: query.get('deploymentId') ?? '', + categories: [], ...(isEdit ? asyncProject.data?.metadata : {}), }} validationSchema={ @@ -186,6 +213,54 @@ const Create: React.FC = () => { ); }} + + Categories + + Select up to 2 + + + + { + return ( +
+ { + arrayHelper.form.setFieldValue('categories', e); + }} + > +
+ ); + }} + >
+ + Project Type + + {({ + field, + form, + }: { + field: { name: string; value: string }; + // eslint-disable-next-line @typescript-eslint/no-explicit-any + form: { setFieldValue: (field: string, val: any) => void }; + }) => { + return ( + { + form.setFieldValue(field.name, val.target.value); + }} + disabled={isEdit ? true : false} + > + SubQuery + RPC + + ); + }} + {isEdit ? ( diff --git a/src/pages/studio/Project/Project.tsx b/src/pages/studio/Project/Project.tsx index 1660f2d55..2b9482d76 100644 --- a/src/pages/studio/Project/Project.tsx +++ b/src/pages/studio/Project/Project.tsx @@ -12,7 +12,7 @@ import clsx from 'clsx'; import { IPFSImage, Spinner } from '../../../components'; import { useWeb3 } from '../../../containers'; import { useCreateDeployment, useProject } from '../../../hooks'; -import { renderAsync } from '../../../utils'; +import { parseError, renderAsync } from '../../../utils'; import DeploymentsTab from './Deployments'; import styles from './Project.module.css'; @@ -34,7 +34,9 @@ const Project: React.FC = () => { return renderAsync(asyncProject, { loading: () => , - error: (error: Error) => {`Failed to load project: ${error.message}`}, + error: (error: Error) => { + return {`Failed to load project: ${parseError(error)}`}; + }, data: (project) => { if (!project) { // Should never happen @@ -134,7 +136,7 @@ const Project: React.FC = () => {
- + Project Detail @@ -142,6 +144,27 @@ const Project: React.FC = () => { {project.metadata.description}
+ + Categories + + +
+ {project.metadata.categories?.map((category) => { + return ( +
+ {category} +
+ ); + })} +
+ From 60f49ea902171f74936cc8889871dcfe8332bf6c Mon Sep 17 00:00:00 2001 From: cyrbuzz Date: Wed, 13 Dec 2023 18:08:57 +0800 Subject: [PATCH 11/11] feat: deployments --- .../ProjectDeployments/ProjectDeployments.tsx | 84 ++++++++++++++++++- src/containers/ProjectRegistry.ts | 3 +- src/hooks/useCreateDeployment.tsx | 13 ++- src/pages/studio/Project/Deployments.tsx | 38 +++++++-- src/pages/studio/Project/Project.tsx | 32 +++++-- 5 files changed, 146 insertions(+), 24 deletions(-) diff --git a/src/components/ProjectDeployments/ProjectDeployments.tsx b/src/components/ProjectDeployments/ProjectDeployments.tsx index de25fb6f6..bf5a601a5 100644 --- a/src/components/ProjectDeployments/ProjectDeployments.tsx +++ b/src/components/ProjectDeployments/ProjectDeployments.tsx @@ -3,7 +3,10 @@ import * as React from 'react'; import { useTranslation } from 'react-i18next'; -import { Markdown } from '@subql/components'; +import { useCreateDeployment } from '@hooks'; +import { Markdown, Typography } from '@subql/components'; +import { Form, Modal, Radio } from 'antd'; +import { useForm } from 'antd/es/form/Form'; import dayjs from 'dayjs'; import utc from 'dayjs/plugin/utc'; @@ -19,29 +22,90 @@ type Deployment = NewDeployment & { createdAt?: Date }; type Props = { deployments: Deployment[]; projectId: string; + onRefresh: () => Promise; }; -const ProjectDeployments: React.FC = ({ deployments, projectId }) => { +const ProjectDeployments: React.FC = ({ deployments, projectId, onRefresh }) => { const { t } = useTranslation(); + const updateDeployment = useCreateDeployment(projectId); + const [deploymentModal, setDeploymentModal] = React.useState(false); + const [form] = useForm(); + const [currentDeployment, setCurrentDeployment] = React.useState(); + const [addDeploymentsLoading, setAddDeploymentsLoading] = React.useState(false); + + const handleSubmitUpdate = async () => { + try { + setAddDeploymentsLoading(true); + await form.validateFields(); + await updateDeployment({ + ...currentDeployment, + ...form.getFieldsValue(), + }); + await onRefresh(); + form.resetFields(); + setDeploymentModal(false); + } finally { + setAddDeploymentsLoading(false); + } + }; return ( <> + setDeploymentModal(false)} + title="Edit Deployment" + width={572} + cancelButtonProps={{ + style: { + display: 'none', + }, + }} + okText="Update" + okButtonProps={{ + shape: 'round', + size: 'large', + loading: addDeploymentsLoading, + }} + onOk={() => { + handleSubmitUpdate(); + }} + > +
+
+ + { + form.setFieldValue('description', e); + }} + > + +
+
+
{t('deployments.header1')} + RECOMMENDED {t('deployments.header2')} {t('deployments.header3')} {t('deployments.header4')} - {/* {t('general.action')} */} + {t('general.action')} {deployments.map((deployment, index) => ( - +

{deployment.version}

+ +

+ RECOMMENDED +

+

{deployment.deploymentId}

@@ -58,6 +122,18 @@ const ProjectDeployments: React.FC = ({ deployments, projectId }) => { {deployment.createdAt ? dayjs(deployment.createdAt).utc(true).fromNow() : 'N/A'}

+ + { + form.setFieldValue('description', deployment.description || ''); + setCurrentDeployment(deployment); + setDeploymentModal(true); + }} + > + Edit + + ))} diff --git a/src/containers/ProjectRegistry.ts b/src/containers/ProjectRegistry.ts index 2905475ec..0fccdaf86 100644 --- a/src/containers/ProjectRegistry.ts +++ b/src/containers/ProjectRegistry.ts @@ -80,6 +80,7 @@ function useProjectRegistryImpl(logger: Logger) { id: BigNumberish, deploymentId: string, version: string, + recommended?: boolean, ): Promise => { if (!contracts) { throw new Error('ProjectRegistry contract not available'); @@ -90,7 +91,7 @@ function useProjectRegistryImpl(logger: Logger) { id, cidToBytes32(deploymentId), cidToBytes32(version), - true, + !!recommended, ); tx.wait().then((receipt) => { diff --git a/src/hooks/useCreateDeployment.tsx b/src/hooks/useCreateDeployment.tsx index 8c4949861..2bc44c1f1 100644 --- a/src/hooks/useCreateDeployment.tsx +++ b/src/hooks/useCreateDeployment.tsx @@ -6,11 +6,13 @@ import { ContractReceipt } from '@ethersproject/contracts'; import { useProjectMetadata, useProjectRegistry } from '../containers'; import { NewDeployment } from '../models'; -export function useCreateDeployment(projectId: string): (deploymentDetails: NewDeployment) => Promise { +export function useCreateDeployment( + projectId: string, +): (deploymentDetails: NewDeployment & { recommended: boolean }) => Promise { const projectRegistry = useProjectRegistry(); const { uploadVersionMetadata } = useProjectMetadata(); - const createDeployment = async (deploymentDetails: NewDeployment) => { + const createDeployment = async (deploymentDetails: NewDeployment & { recommended: boolean }) => { const versionCid = await uploadVersionMetadata({ version: deploymentDetails.version, description: deploymentDetails.description, @@ -18,7 +20,12 @@ export function useCreateDeployment(projectId: string): (deploymentDetails: NewD console.log('Uploaded version details', versionCid); - const tx = await projectRegistry.updateDeployment(projectId, deploymentDetails.deploymentId, versionCid); + const tx = await projectRegistry.updateDeployment( + projectId, + deploymentDetails.deploymentId, + versionCid, + deploymentDetails.recommended, + ); return await tx.wait(1); }; diff --git a/src/pages/studio/Project/Deployments.tsx b/src/pages/studio/Project/Deployments.tsx index e44e85143..3c2b648ee 100644 --- a/src/pages/studio/Project/Deployments.tsx +++ b/src/pages/studio/Project/Deployments.tsx @@ -1,7 +1,8 @@ // Copyright 2020-2022 SubQuery Pte Ltd authors & contributors // SPDX-License-Identifier: Apache-2.0 -import { useGetProjectDeploymentsQuery } from '@subql/react-hooks'; +import { forwardRef, useImperativeHandle } from 'react'; +import { useGetProjectDeploymentsLazyQuery } from '@subql/react-hooks'; import { uniqBy } from 'ramda'; import { ProjectDeployments, Spinner } from '../../../components'; @@ -18,8 +19,12 @@ type Props = { }; }; -const DeploymentsTab: React.FC = ({ projectId, currentDeployment }) => { - const query = useGetProjectDeploymentsQuery({ +export interface DeploymendRef { + refresh: () => void; +} + +const DeploymentsTab = forwardRef(({ projectId, currentDeployment }, ref) => { + const [getProjDeployments] = useGetProjectDeploymentsLazyQuery({ variables: { projectId, }, @@ -27,7 +32,10 @@ const DeploymentsTab: React.FC = ({ projectId, currentDeployment }) => { const { catSingle } = useIPFS(); const asyncDeployments = useAsyncMemo(async () => { - let projectDeployments = query.data?.project?.deployments.nodes.filter(notEmpty); + const res = await getProjDeployments({ + fetchPolicy: 'network-only', + }); + let projectDeployments = res.data?.project?.deployments.nodes.filter(notEmpty); if (!projectDeployments) { return []; } @@ -61,10 +69,18 @@ const DeploymentsTab: React.FC = ({ projectId, currentDeployment }) => { createdAt: deployment.createdTimestamp, version: result.version, description: result.description, + // TODO: backend support + recommended: true, }; }), ); - }, [query, currentDeployment]); + }, [currentDeployment]); + + useImperativeHandle(ref, () => ({ + refresh: () => { + return asyncDeployments.refetch(); + }, + })); return renderAsync(asyncDeployments, { loading: () => , @@ -75,9 +91,17 @@ const DeploymentsTab: React.FC = ({ projectId, currentDeployment }) => { return
There has no deployments for this project
; } - return ; + return ( + { + await asyncDeployments.refetch(); + }} + /> + ); }, }); -}; +}); export default DeploymentsTab; diff --git a/src/pages/studio/Project/Project.tsx b/src/pages/studio/Project/Project.tsx index 2b9482d76..26f5cad06 100644 --- a/src/pages/studio/Project/Project.tsx +++ b/src/pages/studio/Project/Project.tsx @@ -5,7 +5,7 @@ import * as React from 'react'; import { useNavigate, useParams } from 'react-router'; import { ExternalLink } from '@components/ProjectOverview/ProjectOverview'; import { Markdown, Typography } from '@subql/components'; -import { Breadcrumb, Button, Form, Input, Modal } from 'antd'; +import { Breadcrumb, Button, Checkbox, Form, Input, Modal } from 'antd'; import { useForm } from 'antd/es/form/Form'; import clsx from 'clsx'; @@ -13,7 +13,7 @@ import { IPFSImage, Spinner } from '../../../components'; import { useWeb3 } from '../../../containers'; import { useCreateDeployment, useProject } from '../../../hooks'; import { parseError, renderAsync } from '../../../utils'; -import DeploymentsTab from './Deployments'; +import DeploymentsTab, { DeploymendRef } from './Deployments'; import styles from './Project.module.css'; const Project: React.FC = () => { @@ -24,12 +24,20 @@ const Project: React.FC = () => { const [form] = useForm(); const [deploymentModal, setDeploymentModal] = React.useState(false); const createDeployment = useCreateDeployment(id ?? ''); + const deploymentsRef = React.useRef(null); + const [addDeploymentsLoading, setAddDeploymentsLoading] = React.useState(false); const handleSubmitCreate = async () => { - await form.validateFields(); - await createDeployment(form.getFieldsValue()); - form.resetFields(); - setDeploymentModal(false); + try { + setAddDeploymentsLoading(true); + await form.validateFields(); + await createDeployment(form.getFieldsValue()); + await deploymentsRef.current?.refresh(); + form.resetFields(); + setDeploymentModal(false); + } finally { + setAddDeploymentsLoading(false); + } }; return renderAsync(asyncProject, { @@ -63,6 +71,7 @@ const Project: React.FC = () => { okButtonProps={{ shape: 'round', size: 'large', + loading: addDeploymentsLoading, }} onOk={() => { handleSubmitCreate(); @@ -76,11 +85,14 @@ const Project: React.FC = () => { - + + Set as recommended version + + { - form.setFieldValue('deploymentDesc', e); + form.setFieldValue('description', e); }} > @@ -152,6 +164,7 @@ const Project: React.FC = () => { {project.metadata.categories?.map((category) => { return (
{