diff --git a/apps/web/src/api/index.ts b/apps/web/src/api/index.ts index 0037074acaa5ace26d4ca2cb06330e1440f8bd54..668842d0935d51f2a726531ee20d6b47edd30971 100644 --- a/apps/web/src/api/index.ts +++ b/apps/web/src/api/index.ts @@ -59,26 +59,31 @@ class GatewayApi { Object.assign(init.headers, { Authorization: `Bearer ${await config.getToken()}` }) - const gatewayResponse = await fetch(url, init); - const gatewayData = await gatewayResponse.json(); - const eventId = gatewayData.id; - return new Promise((resolve, reject) => { + const conf: any = {}; + let eventId = "-1"; + conf.fn = (message: GatewayMessageType) => { + if (message.id === eventId) { + // unsubscribe from events + this.events.off('message', conf.fn); + clearTimeout(conf.timeoutId); + conf.resolve({ status: 200, json: () => message.data }); + } + }; + this.events.on('message', conf.fn); + + return new Promise(async (resolve, reject) => { const TIMEOUT = 5 * 60 * 1000; // 5 minutes; - const conf: any = {}; - conf.fn = (message: GatewayMessageType) => { - if (message.id === eventId) { - // unsubscribe from events - this.events.off('message', conf.fn); - clearTimeout(conf.timeoutId); - resolve({ status: 200, json: () => message.data }); - } - }; - this.events.on('message', conf.fn); + + conf.resolve = resolve; conf.timeoutId = setTimeout(() => { this.events.off('message', conf.fn); reject(new Error('Timeout exception')); }, TIMEOUT); + + const gatewayResponse = await fetch(url, init); + const gatewayData = await gatewayResponse.json(); + eventId = gatewayData.id; }); }; diff --git a/apps/web/src/components/CredentialItem/index.tsx b/apps/web/src/components/CredentialItem/index.tsx new file mode 100644 index 0000000000000000000000000000000000000000..f8a9260a10d8d9bed07a296ae64147d6735e1175 --- /dev/null +++ b/apps/web/src/components/CredentialItem/index.tsx @@ -0,0 +1,27 @@ +import React from "react"; +import { observer } from "mobx-react"; +import s from './styles.module.scss'; +import {Button} from "antd"; + +interface CredentialItemProps { + item: any; + onOpen?: () => void; + onRemove?: () => void; +} + +const CredentialItem = observer(({ item, onOpen, onRemove }: CredentialItemProps) => { + return ( + <div className={s.item}> + <pre> + {JSON.stringify(item, null, 2)} + </pre> + <div> + {onOpen && <Button onClick={onOpen}>Go to credential</Button>} + {onRemove && <Button onClick={onRemove}>Remove</Button>} + </div> + </div> + ); +}); + +export default CredentialItem; + diff --git a/apps/web/src/components/CredentialItem/styles.module.scss b/apps/web/src/components/CredentialItem/styles.module.scss new file mode 100644 index 0000000000000000000000000000000000000000..388a3ad936f635eedb6d5599eb017c9fdea78295 --- /dev/null +++ b/apps/web/src/components/CredentialItem/styles.module.scss @@ -0,0 +1,38 @@ +.item { + display: flex; + flex-direction: column; + + margin: 4px 0; + border: 1px solid rgba(0, 0, 0, 0.1); + border-radius: 8px; + background: rgba(255, 255, 255, 0.12); + padding: 8px; + cursor: pointer; +} + +.row { + display: flex; + margin-bottom: 4px; +} + + +.pair { + display: flex; +} + +.pair + .pair { + margin-left: 16px; +} + +.label { + display: flex; + align-items: flex-end; + font-size: 13px; + color: gray; +} +.value { + display: flex; + align-items: flex-end; + font-size: 14px; + margin-left: 4px; +} diff --git a/apps/web/src/components/SchemaItem/index.tsx b/apps/web/src/components/SchemaItem/index.tsx index 60500df32dcb5903184460ff959fb7f4ed5cbd8b..1633ebef5b4690169522957da3f7e012c1091d7e 100644 --- a/apps/web/src/components/SchemaItem/index.tsx +++ b/apps/web/src/components/SchemaItem/index.tsx @@ -12,11 +12,12 @@ const SchemaItem = observer(({ item, onClick }: SchemaItemProps) => { const query = encodeURIComponent(item.id); return ( <div className={s.item} onClick={onClick}> - <a href={`http://ledger.didgram.pro/browse/domain?page=1&query=${query}&txn_type=`} target="_blank"> + <a href={`http://test.bcovrin.vonx.io/browse/domain?page=1&query=${query}&txn_type=`} target="_blank"> <Button> See {item.id} on ledger </Button> </a> + <b>schemaId: {item.schemaId}</b> <pre> {JSON.stringify(item, null, 2)} </pre> diff --git a/apps/web/src/components/Sidebar/index.tsx b/apps/web/src/components/Sidebar/index.tsx index 3072db958118cbfbbb3990af6f33088a876f8c6d..edc4f3d1288fc671cfcc534483753bdc2f9bc444 100644 --- a/apps/web/src/components/Sidebar/index.tsx +++ b/apps/web/src/components/Sidebar/index.tsx @@ -15,13 +15,12 @@ const Sidebar = () => { <Link className={s.link} to={config.connection_list.getLink()}> Connection List </Link> - - {/* <Link className={s.link} to={config.cred_def_list.getLink()}>*/} - {/* <SidebarMenuItem name="Credential Definition List" />*/} - {/* </Link>*/} - {/* <Link className={s.link} to={config.credential_list.getLink()}>*/} - {/* <SidebarMenuItem name="Credential List" />*/} - {/* </Link>*/} + <Link className={s.link} to={config.cred_def_list.getLink()}> + Credential Definition List + </Link> + <Link className={s.link} to={config.credential_list.getLink()}> + Credential List + </Link> {/* <Link className={s.link} to={config.cred_ex_v10_list.getLink()}>*/} {/* <SidebarMenuItem name="Issued Credentials v1.0" />*/} {/* </Link>*/} diff --git a/apps/web/src/routes/config.ts b/apps/web/src/routes/config.ts index e1e68551cb4957081749101f10ca29bc87e679df..2cae401e8c122000e9ed0cc42c8e75507b8a3bd0 100644 --- a/apps/web/src/routes/config.ts +++ b/apps/web/src/routes/config.ts @@ -1,11 +1,10 @@ import ConnectionListPage from "./pages/ConnectionListPage"; import ConnectionCreatePage from "./pages/ConnectionCreatePage"; -import ConnectionViewPage from "./pages/EmptyPage"; +import ConnectionViewPage from "./pages/ConnectionViewPage"; // import ConnectionOfferCredentialPage from "./pages/EmptyPage"; import SchemaListPage from "./pages/SchemaListPage"; -// import CredentialDefListPage from "./pages/CredentialDefListPage"; -// import CredentialListPage from "./pages/CredentialListPage"; -// import CredentialViewPage from "./pages/CredentialViewPage"; +import CredentialDefListPage from "./pages/CredentialDefListPage"; +import CredentialListPage from "./pages/CredentialListPage"; // // import CredentialIssuesV10Page from "./pages/CredentialIssuesV10Page"; // import CredentialIssuesV10ViewPage from "./pages/CredentialIssuesV10ViewPage"; @@ -42,18 +41,19 @@ const routes = { route: "/schema/list", getLink: () => "/schema/list", }, - // - // cred_def_list: { - // Component: CredentialDefListPage, - // route: "/cred-def/list", - // getLink: () => "/cred-def/list", - // }, - // - // credential_list: { - // Component: CredentialListPage, - // route: "/credential/list", - // getLink: () => "/credential/list", - // }, + + cred_def_list: { + Component: CredentialDefListPage, + route: "/cred-def/list", + getLink: () => "/cred-def/list", + }, + + credential_list: { + Component: CredentialListPage, + route: "/credential/list", + getLink: () => "/credential/list", + }, + // credential_view: { // Component: CredentialViewPage, // route: "/credential/view/:id", diff --git a/apps/web/src/routes/pages/ConnectionListPage/index.tsx b/apps/web/src/routes/pages/ConnectionListPage/index.tsx index e3df85de524ec991a531edcd0904d407c884c4cc..4263685534407b682a79f637175d7e418fcbe114 100644 --- a/apps/web/src/routes/pages/ConnectionListPage/index.tsx +++ b/apps/web/src/routes/pages/ConnectionListPage/index.tsx @@ -36,9 +36,9 @@ const ConnectionListPage = observer(() => { <div> {store.list.map(p => ( <ConnectionItem - key={p.connection_id} + key={p.id} item={p} - onOpen={() => navigate(config.connection_view.getLink(p.connection_id))} + onOpen={() => navigate(config.connection_view.getLink(p.id))} /> ))} {store.loading && ( @@ -49,7 +49,7 @@ const ConnectionListPage = observer(() => { {receiveInvDialogOpened && ( <ReceiveInvitationDialog onInvitationReceived={(connRecord) => { - navigate(config.connection_view.getLink(connRecord.connection_id)); + navigate(config.connection_view.getLink(connRecord.connectionId)); setReceiveInvDialogOpened(false); }} onClose={() => { setReceiveInvDialogOpened(false) }} diff --git a/apps/web/src/routes/pages/ConnectionViewPage/ConnectionViewPageStore.ts b/apps/web/src/routes/pages/ConnectionViewPage/ConnectionViewPageStore.ts new file mode 100644 index 0000000000000000000000000000000000000000..fa3318a9f97746b1ffe586da266c009ab97a8be2 --- /dev/null +++ b/apps/web/src/routes/pages/ConnectionViewPage/ConnectionViewPageStore.ts @@ -0,0 +1,30 @@ +import {makeAutoObservable, runInAction} from "mobx"; +import api from "../../../api"; +import {toast} from "react-toastify"; + +class ConnectionViewPageStore { + public loading = false; + public connection: any | null = null; + constructor() { + makeAutoObservable(this); + } + + public async load(id: string) { + runInAction(() => this.loading = true); + try { + const record = await api.connections.connectionControllerGetById(id); + this.connection = record; + runInAction(() => { + this.loading = false; + }); + } catch (e: any) { + toast(e.message); + console.error(e); + runInAction(() => this.loading = false); + } + } +} + +export type { ConnectionViewPageStore }; + +export default ConnectionViewPageStore; diff --git a/apps/web/src/routes/pages/ConnectionViewPage/index.tsx b/apps/web/src/routes/pages/ConnectionViewPage/index.tsx new file mode 100644 index 0000000000000000000000000000000000000000..5f71cc9065eee563a1f6f57354e4396eb4066da0 --- /dev/null +++ b/apps/web/src/routes/pages/ConnectionViewPage/index.tsx @@ -0,0 +1,56 @@ +import React, {useEffect, useState} from "react"; +import { observer } from "mobx-react"; +import {useNavigate, useParams} from "react-router-dom"; +import ConnectionViewPageStore from './ConnectionViewPageStore'; +import ConnectionItem from "../../../components/ConnectionItem"; +import {Button, Spin} from "antd"; +import config from "../../config"; +// import MessagesDialog from "./MessagesDialog"; +// import OfferCredentialDialog from "./OfferCredentialDialog"; + +const ConnectionViewPage = observer(() => { + const { id } = useParams() as any; + const [store] = useState(() => new ConnectionViewPageStore()); + const [messagingOpen, setMessagingOpen] = useState(false); + const [offerCredOpen, setOfferCredOpen] = useState(false); + const navigate = useNavigate(); + + useEffect(() => { + store.load(id); + }, [id]) + return ( + <div> + <h2> + Connection + </h2> + {store.loading && ( + <Spin /> + )} + {!store.loading && ( + <> + {store.connection && ( + <div> + <ConnectionItem item={store.connection} /> + </div> + )} + </> + )} + <h2> + Actions + </h2> + <div> + {/*<Button onClick={() => setOfferCredOpen(true)}>*/} + {/* Offer credential*/} + {/*</Button>*/} + + {/*<Button onClick={() => setMessagingOpen(true)}>*/} + {/* Start messaging*/} + {/*</Button>*/} + </div> + {/*{offerCredOpen && (<OfferCredentialDialog connectionId={id} onClose={() => { setOfferCredOpen(false); }} />)}*/} + {/*{messagingOpen && (<MessagesDialog connectionId={id} onClose={() => { setMessagingOpen(false); }} />)}*/} + </div> + ); +}); + +export default ConnectionViewPage; diff --git a/apps/web/src/routes/pages/ConnectionViewPage/styles.module.scss b/apps/web/src/routes/pages/ConnectionViewPage/styles.module.scss new file mode 100644 index 0000000000000000000000000000000000000000..e69de29bb2d1d6434b8b29ae775ad8c2e48c5391 diff --git a/apps/web/src/routes/pages/CredentialDefListPage/NewCredentialDefDialog/index.tsx b/apps/web/src/routes/pages/CredentialDefListPage/NewCredentialDefDialog/index.tsx new file mode 100644 index 0000000000000000000000000000000000000000..7f14bddcccc95278d53a9fb33912068068fb9cf2 --- /dev/null +++ b/apps/web/src/routes/pages/CredentialDefListPage/NewCredentialDefDialog/index.tsx @@ -0,0 +1,102 @@ +import React, { useEffect, useState } from "react"; +import { observer } from "mobx-react"; +import {Form, Input, Button, Modal} from 'antd'; +import api from "../../../../api"; +import {toast} from "react-toastify"; +import s from "./styles.module.scss"; + + +export interface NewCredentialDefDialogProps { + onCredDefCreated: (credDef: any) => void; + onClose: () => void; +} + +const NewCredentialDefDialog = observer( + ({ onClose, onCredDefCreated }: NewCredentialDefDialogProps) => { + const [open, setOpen] = useState(false); + useEffect(() => { + setOpen(true); + }, []); + const [loading, setLoading] = useState(false); + + const onFinish = async ({ schemaId, tag }: any) => { + try { + setLoading(true); + + const schema = await api.schema.attestationControllerGetSchemaById({ schemaId }); + if (!schema) { + throw new Error('Schema with specified id not found'); + } + + const credDef = await api.credentials.attestationControllerCreateCredentialDefinition({ + schemaId, + tag, + }); + console.log(credDef); + toast(`New credential definition created`); + onCredDefCreated(credDef); + } catch (e: any) { + console.error(e); + toast(e.message); + } + setLoading(false); + }; + + return ( + <Modal + style={{ top: 60 }} + open={open} + title="Add new credetial definition" + onCancel={onClose} + footer={[]} + > + <div className={s.body}> + <Form + layout="vertical" + labelCol={{ span: 8 }} + wrapperCol={{ span: 16 }} + style={{ maxWidth: 600 }} + initialValues={{ + schemaId: '', + tag: '' + }} + onFinish={onFinish} + autoComplete="off" + > + <Form.Item + label="Schema Id" + name="schemaId" + rules={[{ required: true, message: 'Please enter schemaId!' }]} + > + <Input /> + </Form.Item> + <Form.Item + label="Tag" + name="tag" + rules={[{ required: true, message: 'Please enter tag!' }]} + > + <Input /> + </Form.Item> + + <div className={s.footerActions}> + <Button type="primary" htmlType="submit" disabled={loading}> + Create + </Button> + <Button + className={s.cancelButton} + onClick={onClose} + disabled={loading} + > + Close + </Button> + </div> + </Form> + + </div> + </Modal> + ); + } +); + +export default NewCredentialDefDialog; + diff --git a/apps/web/src/routes/pages/CredentialDefListPage/NewCredentialDefDialog/styles.module.scss b/apps/web/src/routes/pages/CredentialDefListPage/NewCredentialDefDialog/styles.module.scss new file mode 100644 index 0000000000000000000000000000000000000000..5cc463b56978bf734f1fb44f64ead3f85dccfaa8 --- /dev/null +++ b/apps/web/src/routes/pages/CredentialDefListPage/NewCredentialDefDialog/styles.module.scss @@ -0,0 +1,4 @@ +.footerActions { + display: flex; + justify-content: space-around; +} diff --git a/apps/web/src/routes/pages/CredentialDefListPage/index.tsx b/apps/web/src/routes/pages/CredentialDefListPage/index.tsx new file mode 100644 index 0000000000000000000000000000000000000000..d581133405669f68e2db75614cc46224253e578d --- /dev/null +++ b/apps/web/src/routes/pages/CredentialDefListPage/index.tsx @@ -0,0 +1,48 @@ +import React, { useState} from "react"; +import { observer } from "mobx-react"; +import {Button} from 'antd'; +import NewCredentialDefDialog from "./NewCredentialDefDialog"; + +const ConnectionListPage = observer(() => { + const [newCredDefDialog, setNewCredDefDialog] = useState(false); + const [newCredDef, setNewCredDef] = useState(''); + + return ( + <div> + <h2> + Actions + </h2> + <div> + <Button onClick={() => setNewCredDefDialog(true)}> + Create new credential definition + </Button> + </div> + {newCredDef && ( + <div> + New credential definition: + <pre> + {newCredDef} + </pre> + </div> + )} + <h2>Credential definition List</h2> + <div> + Not available + </div> + {newCredDefDialog && ( + <NewCredentialDefDialog + onCredDefCreated={(credDef) => { + setNewCredDefDialog(false) + try { + setNewCredDef(JSON.stringify(credDef, null, 2)) + } catch (e: any){} + }} + onClose={() => setNewCredDefDialog(false)} + /> + )} + </div> + ); +}); + +export default ConnectionListPage; + diff --git a/apps/web/src/routes/pages/CredentialDefListPage/styles.module.scss b/apps/web/src/routes/pages/CredentialDefListPage/styles.module.scss new file mode 100644 index 0000000000000000000000000000000000000000..e69de29bb2d1d6434b8b29ae775ad8c2e48c5391 diff --git a/apps/web/src/routes/pages/CredentialListPage/CredentialListPageStore.ts b/apps/web/src/routes/pages/CredentialListPage/CredentialListPageStore.ts new file mode 100644 index 0000000000000000000000000000000000000000..bed97417dcfb0cec1f5ba3a512131b8ecd271fb9 --- /dev/null +++ b/apps/web/src/routes/pages/CredentialListPage/CredentialListPageStore.ts @@ -0,0 +1,31 @@ +import {makeAutoObservable, runInAction} from "mobx"; +import api from "../../../api"; +import {toast} from "react-toastify"; + + +class CredentialListPageStore { + public loading = false; + public items: any[] = []; + constructor() { + makeAutoObservable(this); + } + + public async loadAll() { + runInAction(() => this.loading = true); + try { + const items = await api.credentials.attestationControllerCredentials() as any; + runInAction(() => { + this.items = items; + this.loading = false; + }); + } catch (e: any) { + toast(e.message); + console.error(e); + runInAction(() => this.loading = false); + } + } +} + +export type { CredentialListPageStore }; + +export default CredentialListPageStore; diff --git a/apps/web/src/routes/pages/CredentialListPage/index.tsx b/apps/web/src/routes/pages/CredentialListPage/index.tsx new file mode 100644 index 0000000000000000000000000000000000000000..60121e5d556fdedeef02af40246ef86336f4c09c --- /dev/null +++ b/apps/web/src/routes/pages/CredentialListPage/index.tsx @@ -0,0 +1,36 @@ +import React, {useEffect, useState} from "react"; +import {observer} from "mobx-react"; +import {Button, Spin} from 'antd'; +import CredentialItem from "../../../components/CredentialItem"; +import CredentialListPageStore from './CredentialListPageStore'; + +const CredentialListPage = observer(() => { + const [store] = useState(() => new CredentialListPageStore()); + + useEffect(() => { + store.loadAll() + }, []) + return ( + <div> + <h2>Credential List</h2> + <div> + {store.items.map(p => ( + <CredentialItem + key={p.referent} + item={p} + /> + ))} + {!store.loading && !store.items.length && ( + <div> + No items + </div> + )} + {store.loading && ( + <Spin /> + )} + </div> + </div> + ); +}); + +export default CredentialListPage; diff --git a/apps/web/src/routes/pages/CredentialListPage/styles.module.scss b/apps/web/src/routes/pages/CredentialListPage/styles.module.scss new file mode 100644 index 0000000000000000000000000000000000000000..e69de29bb2d1d6434b8b29ae775ad8c2e48c5391