From c4fbe0bf549204ea6557cb9f1fe59d626e67bc6a Mon Sep 17 00:00:00 2001 From: shixuewen Date: Fri, 28 Jul 2023 11:56:22 +0800 Subject: [PATCH 1/2] feat: dapp dashboard --- packages/client/dashboard/src/api/index.ts | 17 ++ .../src/components/dapp-home/Dashboard.tsx | 152 ++++++++++++++++++ .../components/dapp-home/imgs/composites.svg | 9 ++ .../src/components/dapp-home/imgs/models.svg | 9 ++ .../src/components/dapp-home/imgs/streams.svg | 9 ++ .../dashboard/src/container/DappHome.tsx | 3 + 6 files changed, 199 insertions(+) create mode 100644 packages/client/dashboard/src/components/dapp-home/Dashboard.tsx create mode 100644 packages/client/dashboard/src/components/dapp-home/imgs/composites.svg create mode 100644 packages/client/dashboard/src/components/dapp-home/imgs/models.svg create mode 100644 packages/client/dashboard/src/components/dapp-home/imgs/streams.svg diff --git a/packages/client/dashboard/src/api/index.ts b/packages/client/dashboard/src/api/index.ts index 8ec5ba96..0bd7690b 100644 --- a/packages/client/dashboard/src/api/index.ts +++ b/packages/client/dashboard/src/api/index.ts @@ -296,3 +296,20 @@ export function startIndexModel({ }, }) } + +export function getStreamsCountWithModels({ + network, + modelStreamIds, +}: { + network: Network + modelStreamIds: string +}): AxiosPromise> { + let host = APP_API_URL + let net = network === Network.MAINNET ? Network.MAINNET : Network.TESTNET + return axios({ + url: + host + + `/${net.toUpperCase()}/streams/count?modelStreamIds=${modelStreamIds}`, + method: 'GET', + }) +} diff --git a/packages/client/dashboard/src/components/dapp-home/Dashboard.tsx b/packages/client/dashboard/src/components/dapp-home/Dashboard.tsx new file mode 100644 index 00000000..da6cc0c4 --- /dev/null +++ b/packages/client/dashboard/src/components/dapp-home/Dashboard.tsx @@ -0,0 +1,152 @@ +import styled from 'styled-components' +import modelsIconUrl from './imgs/models.svg' +import compositesIconUrl from './imgs/composites.svg' +import streamsIconUrl from './imgs/streams.svg' +import { useEffect, useState, useMemo } from 'react' +import useSelectedDapp from '../../hooks/useSelectedDapp' +import { getDappComposites, getStreamsCountWithModels } from '../../api' +import { useSession } from '@us3r-network/auth-with-rainbowkit' +import { Network } from '../Selector/EnumSelect' + +export default function Dashboard() { + const session = useSession() + const { selectedDapp } = useSelectedDapp() + const models = useMemo(() => selectedDapp?.models || [], [selectedDapp]) + const network = useMemo( + () => selectedDapp?.network as Network, + [selectedDapp] + ) + const [compositesCount, setCompositesCount] = useState(0) + const [streamsCount, setStreamsCount] = useState(0) + + useEffect(() => { + if (!session || !selectedDapp) { + setCompositesCount(0) + return + } + ;(async () => { + try { + const resp = await getDappComposites({ + dapp: selectedDapp, + didSession: session.serialize(), + }) + if (resp.data.code !== 0) throw new Error(resp.data.msg) + setCompositesCount(resp.data?.data?.length || 0) + } catch (error) { + console.error(error) + setCompositesCount(0) + } + })() + }, [selectedDapp, session]) + + useEffect(() => { + if (!network || !models.length) { + setStreamsCount(0) + return + } + ;(async () => { + try { + const resp = await getStreamsCountWithModels({ + network: network, + modelStreamIds: models.join(','), + }) + if (resp.data.code !== 0) throw new Error(resp.data.msg) + setStreamsCount(resp.data?.data || 0) + } catch (error) { + console.error(error) + setStreamsCount(0) + } + })() + }, [network, models]) + + return ( + + + + + + + + ) +} + +const DashboardWrap = styled.div` + display: flex; + flex-direction: column; + gap: 20px; +` +const TotalCards = styled.div` + display: flex; + gap: 20px; +` + +function TotalCard({ + name, + number, + iconUrl, +}: { + name: string + number: number + iconUrl: string +}) { + return ( + + {name} + {number.toLocaleString()} + + + ) +} +const TotalCardWrap = styled.div` + display: flex; + padding: 20px; + box-sizing: border-box; + flex-direction: column; + justify-content: center; + align-items: flex-start; + gap: 20px; + flex: 1; + height: 145px; + + border-radius: 20px; + background: linear-gradient(133deg, #233754 0%, #324765 100%); + + position: relative; + overflow: hidden; +` +const TotalCardName = styled.span` + color: var(--ffffff, #fff); + + font-size: 24px; + font-style: italic; + font-weight: 700; + line-height: normal; + + z-index: 2; +` +const TotalCardNumber = styled.span` + color: #fff; + font-size: 48px; + font-style: normal; + font-weight: 500; + line-height: normal; + z-index: 2; +` +const TotalCardIcon = styled.img` + position: absolute; + right: 0px; + top: 15px; + z-index: 1; +` diff --git a/packages/client/dashboard/src/components/dapp-home/imgs/composites.svg b/packages/client/dashboard/src/components/dapp-home/imgs/composites.svg new file mode 100644 index 00000000..c0e21fc2 --- /dev/null +++ b/packages/client/dashboard/src/components/dapp-home/imgs/composites.svg @@ -0,0 +1,9 @@ + + + + + + + + + \ No newline at end of file diff --git a/packages/client/dashboard/src/components/dapp-home/imgs/models.svg b/packages/client/dashboard/src/components/dapp-home/imgs/models.svg new file mode 100644 index 00000000..28d80e47 --- /dev/null +++ b/packages/client/dashboard/src/components/dapp-home/imgs/models.svg @@ -0,0 +1,9 @@ + + + + + + + + + \ No newline at end of file diff --git a/packages/client/dashboard/src/components/dapp-home/imgs/streams.svg b/packages/client/dashboard/src/components/dapp-home/imgs/streams.svg new file mode 100644 index 00000000..abecc3a9 --- /dev/null +++ b/packages/client/dashboard/src/components/dapp-home/imgs/streams.svg @@ -0,0 +1,9 @@ + + + + + + + + + \ No newline at end of file diff --git a/packages/client/dashboard/src/container/DappHome.tsx b/packages/client/dashboard/src/container/DappHome.tsx index 2ae74cd4..b04302b4 100644 --- a/packages/client/dashboard/src/container/DappHome.tsx +++ b/packages/client/dashboard/src/container/DappHome.tsx @@ -10,6 +10,7 @@ import CheckCircleIcon from '../components/Icons/CheckCircleIcon' import { useAppCtx } from '../context/AppCtx' import DisabledIcon from '../components/Icons/DisabledIcon' import Header from '../components/dapp-home/Header' +import Dashboard from '../components/dapp-home/Dashboard' export default function DappHome() { const { guideSteps } = useAppCtx() @@ -54,6 +55,8 @@ export default function DappHome() { return (
+ {!!selectedDapp?.models?.length && } + Date: Thu, 21 Sep 2023 10:58:59 +0800 Subject: [PATCH 2/2] feat: read with anyone --- packages/client/dashboard/src/App.tsx | 8 +- packages/client/dashboard/src/api/index.ts | 12 +- .../src/components/DappSocialEditor.tsx | 38 +- .../src/components/DappTitleEditor.tsx | 35 +- .../dashboard/src/components/ModelList.tsx | 37 +- .../src/components/dapp-home/Dashboard.tsx | 21 +- .../src/components/dapp-home/Header.tsx | 13 +- .../dashboard/src/container/DappHome.tsx | 463 +++++++++--------- .../dashboard/src/container/DappInfo.tsx | 100 ++-- .../client/dashboard/src/context/AppCtx.tsx | 29 +- .../client/dashboard/src/hooks/useIsOwner.ts | 13 + 11 files changed, 435 insertions(+), 334 deletions(-) create mode 100644 packages/client/dashboard/src/hooks/useIsOwner.ts diff --git a/packages/client/dashboard/src/App.tsx b/packages/client/dashboard/src/App.tsx index d803c9dd..66598632 100644 --- a/packages/client/dashboard/src/App.tsx +++ b/packages/client/dashboard/src/App.tsx @@ -23,7 +23,7 @@ import DappModelPlayground from './container/DappModelPlayground' import DappDataStatistic from './container/DappDataStatistic' import Components from './container/Components' -import { useState } from 'react' +import { useEffect, useState } from 'react' import ModelList from './components/ModelList' import { DappComposite, ModelStream } from './types' import { ToastContainer } from 'react-toastify' @@ -96,9 +96,13 @@ function Layout() { } function DappLayout() { - const { loadingDApps } = useAppCtx() + const { loadingDApps, setCurrAppId } = useAppCtx() const { appId } = useParams() + useEffect(() => { + if (appId) setCurrAppId(appId) + }, [appId, setCurrAppId]) + if (!appId || loadingDApps) { return (
diff --git a/packages/client/dashboard/src/api/index.ts b/packages/client/dashboard/src/api/index.ts index 0bd7690b..98cfaa6d 100644 --- a/packages/client/dashboard/src/api/index.ts +++ b/packages/client/dashboard/src/api/index.ts @@ -37,6 +37,14 @@ export function uploadImage({ file }: { file: File }) { }) } +export function getDapp(appId: string) { + let host = APP_API_URL + return axios({ + url: host + `/dapps/${appId}`, + method: 'GET', + }) +} + export function createDapp( dapp: ClientDApp, didSession: string @@ -206,7 +214,7 @@ export function getDappComposites({ didSession, }: { dapp: ClientDApp - didSession: string + didSession?: string }): AxiosPromise> { let host = APP_API_URL @@ -214,7 +222,7 @@ export function getDappComposites({ url: host + `/dapps/${id}/composites`, method: 'GET', headers: { - 'did-session': didSession, + 'did-session': didSession || '', }, }) } diff --git a/packages/client/dashboard/src/components/DappSocialEditor.tsx b/packages/client/dashboard/src/components/DappSocialEditor.tsx index b3b2c429..d00bd05a 100644 --- a/packages/client/dashboard/src/components/DappSocialEditor.tsx +++ b/packages/client/dashboard/src/components/DappSocialEditor.tsx @@ -27,30 +27,34 @@ const IconMap: { [key: string]: () => JSX.Element } = { export default function DappSocialEditor({ selectedDapp, + isOwner, }: { selectedDapp: ClientDApp + isOwner: boolean }) { return (

Social Links

- - - - - - {({ close }) => ( - - )} - - - - + {isOwner && ( + + + + + + {({ close }) => ( + + )} + + + + + )}
{selectedDapp.socialLinks?.map((item) => { diff --git a/packages/client/dashboard/src/components/DappTitleEditor.tsx b/packages/client/dashboard/src/components/DappTitleEditor.tsx index 3a67ca6d..f4e0648a 100644 --- a/packages/client/dashboard/src/components/DappTitleEditor.tsx +++ b/packages/client/dashboard/src/components/DappTitleEditor.tsx @@ -13,8 +13,10 @@ import { Network } from './Selector/EnumSelect' export default function DappTitleEditor({ selectedDapp, + isOwner, }: { selectedDapp: ClientDApp + isOwner: boolean }) { return ( @@ -23,20 +25,25 @@ export default function DappTitleEditor({ {selectedDapp.network || Network.TESTNET} {selectedDapp.stage || 'Under Development'}
- - - - - - {({ close }) => ( - - )} - - - - + {isOwner && ( + + + + + + {({ close }) => ( + + )} + + + + + )} ) } diff --git a/packages/client/dashboard/src/components/ModelList.tsx b/packages/client/dashboard/src/components/ModelList.tsx index e808d942..31defd6f 100644 --- a/packages/client/dashboard/src/components/ModelList.tsx +++ b/packages/client/dashboard/src/components/ModelList.tsx @@ -7,7 +7,7 @@ import { Modal, ModalOverlay } from 'react-aria-components' import PlusIcon from './Icons/PlusIcon' import { useLocation, useNavigate, useSearchParams } from 'react-router-dom' import useSelectedDapp from '../hooks/useSelectedDapp' -import { useCallback, useEffect, useRef, useState } from 'react' +import { useCallback, useEffect, useMemo, useRef, useState } from 'react' import { DappComposite, ModelStream } from '../types' import { getStarModels, getDappComposites, deleteDappComposites } from '../api' import { Network } from './Selector/EnumSelect' @@ -22,6 +22,7 @@ import CreateCompositeModal from './CreateCompositeModal' import MergeIcon from './Icons/MergeIcon' import { shortPubKey } from '../utils/shortPubKey' import CopyTint from './CopyTint' +import useIsOwner from '../hooks/useIsOwner' export default function ModelList({ editable, @@ -38,7 +39,7 @@ export default function ModelList({ editable?: boolean }) { const session = useSession() - const { loadDapps } = useAppCtx() + const { loadDapps, currDapp } = useAppCtx() const { appId, selectedDapp } = useSelectedDapp() const navigate = useNavigate() const [loading, setLoading] = useState(false) @@ -46,16 +47,22 @@ export default function ModelList({ const [composites, setComposites] = useState([]) const location = useLocation() + const dapp = useMemo(() => { + return selectedDapp || currDapp + }, [selectedDapp, currDapp]) + + const { isOwner } = useIsOwner() + const loadModelsInfo = useCallback(async () => { - if (selectedDapp?.models?.length === 0 || !selectedDapp) { + if (dapp?.models?.length === 0 || !dapp) { setDappModels([]) return } try { const resp = await getStarModels({ - network: selectedDapp.network as Network, - ids: selectedDapp.models || [], + network: dapp.network as Network, + ids: dapp.models || [], }) const list = resp.data.data @@ -67,22 +74,20 @@ export default function ModelList({ console.error(error) } // eslint-disable-next-line react-hooks/exhaustive-deps - }, [selectedDapp]) + }, [dapp]) const loadDappComposites = useCallback(async () => { - if (!session) return - if (!selectedDapp) return + if (!dapp) return try { const resp = await getDappComposites({ - dapp: selectedDapp, - didSession: session.serialize(), + dapp, }) if (resp.data.code !== 0) throw new Error(resp.data.msg) setComposites(resp.data.data) } catch (error) { console.error(error) } - }, [selectedDapp, session]) + }, [dapp]) const removeModelFromDapp = useCallback( async (modelId: string) => { @@ -168,7 +173,7 @@ export default function ModelList({

Models

- {editable && ( + {editable && isOwner && ( - - -
- -
- - Submit your model schema to create new model - - https://composedb.js.org/docs/0.4.x/guides/composedb-client - -
-
- + {session?.id && isOwner && ( + <> + + setOpenSteps({ ...openSteps, s1: isOpen }) + } + > +
+ +
+ 1a. Create your own model or composite + + + +
+
+ +
+ + Submit your model schema to create new model + + https://composedb.js.org/docs/0.4.x/guides/composedb-client + +
+
+
-
- -
- - 1b. Explore the existing models, and add the model which suits - your Dapp. -
- When in doubt, you can mark it to your favorite models first and - choose from the list in Model Editor. -
- - - -
-
- -
- - - Click the "Add" button. It will be added to your model list, or - add it to "My Favorite Model" first. - -
-
-
-
+
+ +
+ + 1b. Explore the existing models, and add the model which suits + your Dapp. +
+ When in doubt, you can mark it to your favorite models first + and choose from the list in Model Editor. +
+ + + +
+
+ +
+ + + Click the "Add" button. It will be added to your model list, + or add it to "My Favorite Model" first. + +
+
+
+ - setOpenSteps({ ...openSteps, s2: isOpen })} - > -
- -
- - 2a. Download runtime definitions of models or composites - - - - -
-
- -
- - - Click the "Download" button. -
- Please refer to the website for coding guides: -
- - https://composedb.js.org/docs/0.4.x/guides/composedb-client - -
-
-
+ + setOpenSteps({ ...openSteps, s2: isOpen }) + } + > +
+ +
+ + 2a. Download runtime definitions of models or composites + + + + +
+
+ +
+ + + Click the "Download" button. +
+ Please refer to the website for coding guides: +
+ + https://composedb.js.org/docs/0.4.x/guides/composedb-client + +
+
+
-
- -
- 2b. Coding and testing query and mutation - - - -
-
- -
-
+
+ +
+ 2b. Coding and testing query and mutation + + + +
+
+ +
+
-
- -
- 2c. Viewing and adding data to the model - - - -
-
- -
- - Fill in the corresponding form to add data -
-
-
+
+ +
+ 2c. Viewing and adding data to the model + + + +
+
+ +
+ + Fill in the corresponding form to add data +
+
+
-
- -
- 2d. Download the SDK of the model - - - -
-
- -
-
-
+
+ +
+ 2d. Download the SDK of the model + + + +
+
+ +
+
+
- setOpenSteps({ ...openSteps, s3: isOpen })} - > -
- -
- - We also provide two components (profile and link) to help - developers quickly and easily build decentralised user systems and - social systems - - - - -
-
- -
-
-
+ + setOpenSteps({ ...openSteps, s3: isOpen }) + } + > +
+ +
+ + We also provide two components (profile and link) to help + developers quickly and easily build decentralised user systems + and social systems + + + + +
+
+ +
+
+
- setOpenSteps({ ...openSteps, s4: isOpen })} - > -
- -
- 4a. Release the Dapp's data model to mainnet -
Coming Soon
-
-
+ + setOpenSteps({ ...openSteps, s4: isOpen }) + } + > +
+ +
+ 4a. Release the Dapp's data model to mainnet +
Coming Soon
+
+
- -
- - 4b. Improve more information about Dapp and publish to U3 - -
Coming Soon
-
-
-
+ +
+ + 4b. Improve more information about Dapp and publish to U3 + +
Coming Soon
+
+
+
- setOpenSteps({ ...openSteps, s5: isOpen })} - > -
- -
- 5a. View detailed visual data analysis -
Coming Soon
-
-
+ + setOpenSteps({ ...openSteps, s5: isOpen }) + } + > +
+ +
+ 5a. View detailed visual data analysis +
Coming Soon
+
+
- -
- 5b. View independent stream -
Coming Soon
-
-
-
+ +
+ 5b. View independent stream +
Coming Soon
+
+
+
+ + )} ) } diff --git a/packages/client/dashboard/src/container/DappInfo.tsx b/packages/client/dashboard/src/container/DappInfo.tsx index 3e6c7cc7..4a3133b7 100644 --- a/packages/client/dashboard/src/container/DappInfo.tsx +++ b/packages/client/dashboard/src/container/DappInfo.tsx @@ -5,18 +5,21 @@ import useSelectedDapp from '../hooks/useSelectedDapp' import DappTitleEditor from '../components/DappTitleEditor' import DappSocialEditor from '../components/DappSocialEditor' -import { useCallback, useState } from 'react' +import { useCallback, useMemo, useState } from 'react' import CopyIcon from '../components/Icons/CopyIcon' import DelConfirmModal from '../components/DelDappConfirmModal' import { useNavigate } from 'react-router-dom' import { createImageFromInitials } from '../utils/createImage' import { getRandomColor } from '../utils/randomColor' +import { useAppCtx } from '../context/AppCtx' +import useIsOwner from '../hooks/useIsOwner' export default function DappInfo() { const { selectedDapp } = useSelectedDapp() const navigate = useNavigate() const [showCopyTint, setShowCopyTint] = useState(false) + const { currDapp } = useAppCtx() const copyAppId = useCallback(async (appId: string) => { try { @@ -30,7 +33,13 @@ export default function DappInfo() { } }, []) - if (!selectedDapp) { + const { isOwner } = useIsOwner() + + const dapp = useMemo(() => { + return selectedDapp || currDapp + }, [selectedDapp, currDapp]) + + if (!dapp) { return null } @@ -39,19 +48,19 @@ export default function DappInfo() {
icon
- +
- APP ID: {selectedDapp.id} + APP ID: {dapp.id}
-

{selectedDapp.description}

+

{dapp.description}

- -
-
-

Release to Mainnet

-

Create the same Dapp at Mainnet.

-
- + + + {isOwner && ( +
+
+

Release to Mainnet

+

Create the same Dapp at Mainnet.

+
+ +
-
-
-

Sync to U3

-

Sync the information of the Dapp to U3.xyz.

-
- +
+

Sync to U3

+

Sync the information of the Dapp to U3.xyz.

+
+ +
-
-
-

Delete

-

Delete the Dapp will also remove your data.

-
- - - - - {({ close }) => ( - { - close() - if (!del) return - setTimeout(() => { - navigate('/') - }, 1) - }} - /> - )} - - - +
+

Delete

+

Delete the Dapp will also remove your data.

+
+ + + + + {({ close }) => ( + { + close() + if (!del) return + setTimeout(() => { + navigate('/') + }, 1) + }} + /> + )} + + + +
-
+ )}
) diff --git a/packages/client/dashboard/src/context/AppCtx.tsx b/packages/client/dashboard/src/context/AppCtx.tsx index 0a434b8c..8f8c2af6 100644 --- a/packages/client/dashboard/src/context/AppCtx.tsx +++ b/packages/client/dashboard/src/context/AppCtx.tsx @@ -8,7 +8,7 @@ import React, { import { useSession } from '@us3r-network/auth-with-rainbowkit' import { ClientDApp } from '../types' -import { getDappWithDid } from '../api' +import { getDapp, getDappWithDid } from '../api' import { useGuideStepsState } from '../hooks/useGuideSteps' export type PersonalCollection = { @@ -19,6 +19,9 @@ export type PersonalCollection = { export interface AppContextData { loadingDApps: boolean dapps: ClientDApp[] + currDapp: ClientDApp | undefined + currAppId: string + setCurrAppId: React.Dispatch> loadDapps: () => Promise guideSteps: ReturnType } @@ -30,8 +33,11 @@ export default function AppProvider({ }: { children: React.ReactNode }) { + const [currDapp, setCurrDapp] = useState() const [dapps, setDapps] = useState([]) const [loadingDApps, setLoadingDApps] = useState(false) + const [loadingDApp, setLoadingDApp] = useState(false) + const [currAppId, setCurrAppId] = useState('') const session = useSession() @@ -47,6 +53,22 @@ export default function AppProvider({ setDapps(resp.data.data) }, [session]) + const loadCurrDapp = useCallback(async () => { + setCurrDapp(undefined) + if (!currAppId) return + const resp = await getDapp(currAppId) + setCurrDapp(resp.data.data) + }, [currAppId]) + + useEffect(() => { + setLoadingDApp(true) + loadCurrDapp() + .catch(console.error) + .finally(() => { + setLoadingDApp(false) + }) + }, [loadCurrDapp]) + useEffect(() => { setLoadingDApps(true) loadDapps() @@ -68,8 +90,11 @@ export default function AppProvider({ diff --git a/packages/client/dashboard/src/hooks/useIsOwner.ts b/packages/client/dashboard/src/hooks/useIsOwner.ts new file mode 100644 index 00000000..92862b5e --- /dev/null +++ b/packages/client/dashboard/src/hooks/useIsOwner.ts @@ -0,0 +1,13 @@ +import { useSession } from '@us3r-network/auth-with-rainbowkit' +import { useMemo } from 'react' +import { useAppCtx } from '../context/AppCtx' + +export default function useIsOwner() { + const session = useSession() + const { dapps, currAppId } = useAppCtx() + const isOwner = useMemo(() => { + if (!session?.id) return false + return dapps.some((dapp) => `${dapp.id}` === currAppId) + }, [dapps, session?.id, currAppId]) + return { isOwner } +}