diff --git a/.gitignore b/.gitignore index 790b039b9..7770ecbd2 100644 --- a/.gitignore +++ b/.gitignore @@ -37,7 +37,7 @@ builds/ builds.zip development/ts-migration-dashboard/build development/ts-migration-dashboard/intermediate - +packages/demo/public/docs test-artifacts test-builds build-artifacts @@ -67,3 +67,4 @@ playwright/.cache/ playwright-downloads/ user-data/ tmp/ +.nx diff --git a/packages/app/src/background/services/connection/ConnectionService.ts b/packages/app/src/background/services/connection/ConnectionService.ts index c5e59de4a..4a09a26f4 100644 --- a/packages/app/src/background/services/connection/ConnectionService.ts +++ b/packages/app/src/background/services/connection/ConnectionService.ts @@ -177,7 +177,7 @@ export default class ConnectionService extends BaseService implements IBackupabl { urlOrigin }: IZkMetadata, ): Promise => { const appOrigin = url || urlOrigin; - const connectedIdentity = this.getConnectedIdentity(appOrigin!); + const connectedIdentity = this.getConnectedIdentity(appOrigin); if (!connectedIdentity) { throw new Error("CryptKeeper: No connected identity found"); diff --git a/packages/app/src/ui/pages/GroupMerkleProof/GroupMerkleProof.tsx b/packages/app/src/ui/pages/GroupMerkleProof/GroupMerkleProof.tsx index 7606c00e0..876caf2eb 100644 --- a/packages/app/src/ui/pages/GroupMerkleProof/GroupMerkleProof.tsx +++ b/packages/app/src/ui/pages/GroupMerkleProof/GroupMerkleProof.tsx @@ -120,7 +120,7 @@ const GroupMerkleProof = (): JSX.Element => { variant="h6" onClick={onGoToHost} > - {connection!.urlOrigin} + {connection.urlOrigin} diff --git a/packages/app/src/ui/pages/GroupMerkleProof/useGroupMerkleProof.ts b/packages/app/src/ui/pages/GroupMerkleProof/useGroupMerkleProof.ts index cce6e3be5..11f39de55 100644 --- a/packages/app/src/ui/pages/GroupMerkleProof/useGroupMerkleProof.ts +++ b/packages/app/src/ui/pages/GroupMerkleProof/useGroupMerkleProof.ts @@ -91,7 +91,7 @@ export const useGroupMerkleProof = (): IUseGroupMerkleProofData => { }, [groupId, connection?.urlOrigin, dispatch, navigate]); const onGoToHost = useCallback(() => { - redirectToNewTab(connection!.urlOrigin); + redirectToNewTab(connection.urlOrigin); }, [connection?.urlOrigin]); const onGoToGroup = useCallback(() => { @@ -100,7 +100,7 @@ export const useGroupMerkleProof = (): IUseGroupMerkleProofData => { const onGenerateMerkleProof = useCallback(() => { setSubmitting(true); - dispatch(generateGroupMerkleProof({ groupId: groupId! }, connection!.urlOrigin)) + dispatch(generateGroupMerkleProof({ groupId: groupId! }, connection.urlOrigin)) .then(() => dispatch(closePopup())) .then(() => { navigate(Paths.HOME); diff --git a/packages/app/src/ui/pages/JoinGroup/JoinGroup.tsx b/packages/app/src/ui/pages/JoinGroup/JoinGroup.tsx index 8fdfe7d6b..46b96bbc1 100644 --- a/packages/app/src/ui/pages/JoinGroup/JoinGroup.tsx +++ b/packages/app/src/ui/pages/JoinGroup/JoinGroup.tsx @@ -119,7 +119,7 @@ const JoinGroup = (): JSX.Element => { variant="h6" onClick={onGoToHost} > - {connection!.urlOrigin} + {connection.urlOrigin} diff --git a/packages/app/src/ui/pages/JoinGroup/useJoinGroup.ts b/packages/app/src/ui/pages/JoinGroup/useJoinGroup.ts index 2c7ce21b3..7d3dc0240 100644 --- a/packages/app/src/ui/pages/JoinGroup/useJoinGroup.ts +++ b/packages/app/src/ui/pages/JoinGroup/useJoinGroup.ts @@ -94,7 +94,7 @@ export const useJoinGroup = (): IUseJoinGroupData => { }, [groupId, connection?.urlOrigin, dispatch, navigate]); const onGoToHost = useCallback(() => { - redirectToNewTab(connection!.urlOrigin); + redirectToNewTab(connection.urlOrigin); }, [connection?.urlOrigin]); const onGoToGroup = useCallback(() => { @@ -103,7 +103,7 @@ export const useJoinGroup = (): IUseJoinGroupData => { const onJoin = useCallback(() => { setSubmitting(true); - dispatch(joinGroup({ groupId: groupId!, apiKey, inviteCode }, connection!.urlOrigin)) + dispatch(joinGroup({ groupId: groupId!, apiKey, inviteCode }, connection.urlOrigin)) .then(() => dispatch(closePopup())) .then(() => { navigate(Paths.HOME); diff --git a/packages/app/src/ui/pages/RevealIdentityCommitment/useRevealIdentityCommitment.ts b/packages/app/src/ui/pages/RevealIdentityCommitment/useRevealIdentityCommitment.ts index 28e62d337..ecdd65685 100644 --- a/packages/app/src/ui/pages/RevealIdentityCommitment/useRevealIdentityCommitment.ts +++ b/packages/app/src/ui/pages/RevealIdentityCommitment/useRevealIdentityCommitment.ts @@ -51,11 +51,11 @@ export const useRevealIdentityCommitment = (): IUseRevealIdentityCommitmentData }, [connection?.urlOrigin, dispatch, navigate]); const onGoToHost = useCallback(() => { - redirectToNewTab(connection!.urlOrigin); + redirectToNewTab(connection.urlOrigin); }, [connection?.urlOrigin]); const onReveal = useCallback(() => { - dispatch(revealConnectedIdentityCommitment(connection!.urlOrigin)) + dispatch(revealConnectedIdentityCommitment(connection.urlOrigin)) .then(() => dispatch(closePopup())) .then(() => { navigate(Paths.HOME); diff --git a/packages/demo/.eslintignore b/packages/demo/.eslintignore index c9168307d..335fbf87f 100644 --- a/packages/demo/.eslintignore +++ b/packages/demo/.eslintignore @@ -1,3 +1,3 @@ dist/ node_modules/ -public/docs +public/docs/ diff --git a/packages/demo/package.json b/packages/demo/package.json index f0b0deec1..9b4158c12 100644 --- a/packages/demo/package.json +++ b/packages/demo/package.json @@ -3,8 +3,8 @@ "private": true, "version": "0.4.3", "scripts": { - "start": "parcel serve --no-cache public/index.html", - "build": "parcel build public/index.html", + "start": "webpack serve --config webpack.dev.js --mode development", + "build": "webpack --config webpack.prod.js --mode=production", "build:e2e": "pnpm run build", "dev": "pnpm run build", "lint": "eslint . --ext .js,.jsx,.ts,.tsx", @@ -18,30 +18,54 @@ "@cryptkeeperzk/providers": "workspace:^", "@cryptkeeperzk/semaphore-identity": "^3.10.3", "@cryptkeeperzk/types": "workspace:^", + "@fortawesome/free-brands-svg-icons": "^6.4.2", + "@fortawesome/react-fontawesome": "^0.2.0", + "@mui/base": "5.0.0-beta.20", "@mui/icons-material": "^5.14.14", - "@mui/material": "^5.14.17", - "@mui/styles": "^5.14.17", - "bigint-conversion": "^2.4.3", + "@mui/lab": "5.0.0-alpha.149", + "@mui/material": "^5.14.13", + "@mui/styles": "^5.14.14", + "@types/react-syntax-highlighter": "^15.5.9", + "bigint-conversion": "^2.4.2", "classnames": "^2.3.2", "dotenv": "^16.3.1", - "ethers": "^6.8.1", - "prism-react-renderer": "^2.2.0", + "ethers": "^6.8.0", + "prism-react-renderer": "^2.1.0", "react": "^18.2.0", + "react-code-blocks": "^0.1.4", + "react-collapse": "^5.1.1", "react-dom": "^18.2.0", "react-live": "^4.1.5", - "react-toastify": "^9.1.3" + "react-markdown": "^9.0.0", + "react-router-dom": "^6.17.0", + "react-scripts": "^5.0.1", + "react-syntax-highlighter": "^15.5.0", + "react-toastify": "^9.1.3", + "rehype-autolink-headings": "^7.0.0", + "rehype-slug": "^6.0.0", + "ts-loader": "^9.5.0" }, "devDependencies": { "@cryptkeeperzk/eslint-config-react": "workspace:^", - "@types/node": "^20.9.0", - "@types/react": "^18.2.37", - "@types/react-dom": "^18.2.15", + "@types/node": "^20.8.8", + "@types/react": "^18.2.32", + "@types/react-collapse": "^5.0.3", + "@types/react-dom": "^18.2.14", "assert": "^2.1.0", - "eslint": "^8.53.0", - "lint-staged": "^15.1.0", - "parcel": "^2.10.2", - "prettier": "^3.1.0", + "copy-webpack-plugin": "^11.0.0", + "css-loader": "^6.8.1", + "eslint": "^8.52.0", + "glob": "^10.3.10", + "html-webpack-plugin": "^5.5.3", + "lint-staged": "^15.0.2", + "path-browserify": "^1.0.1", + "prettier": "^3.0.3", "process": "^0.11.10", - "typescript": "^5.2.2" + "style-loader": "^3.3.3", + "typescript": "^5.2.2", + "webpack": "^5.89.0", + "webpack-cli": "^5.1.4", + "webpack-dev-server": "^4.15.1", + "webpack-merge": "^5.10.0" } } diff --git a/packages/demo/public/index.html b/packages/demo/public/index.html index 74cb6d679..324acd2f8 100644 --- a/packages/demo/public/index.html +++ b/packages/demo/public/index.html @@ -9,6 +9,6 @@
- + diff --git a/packages/demo/public/screenshots/approve.png b/packages/demo/public/screenshots/approve.png new file mode 100644 index 000000000..86887f4e4 Binary files /dev/null and b/packages/demo/public/screenshots/approve.png differ diff --git a/packages/demo/public/screenshots/bandada-generate.png b/packages/demo/public/screenshots/bandada-generate.png new file mode 100644 index 000000000..c42577afa Binary files /dev/null and b/packages/demo/public/screenshots/bandada-generate.png differ diff --git a/packages/demo/public/screenshots/bandada-join.png b/packages/demo/public/screenshots/bandada-join.png new file mode 100644 index 000000000..38c664957 Binary files /dev/null and b/packages/demo/public/screenshots/bandada-join.png differ diff --git a/packages/demo/public/screenshots/check-connect.png b/packages/demo/public/screenshots/check-connect.png new file mode 100644 index 000000000..c40a08832 Binary files /dev/null and b/packages/demo/public/screenshots/check-connect.png differ diff --git a/packages/demo/public/screenshots/connect.png b/packages/demo/public/screenshots/connect.png new file mode 100644 index 000000000..0b30e81b7 Binary files /dev/null and b/packages/demo/public/screenshots/connect.png differ diff --git a/packages/demo/public/screenshots/create.png b/packages/demo/public/screenshots/create.png new file mode 100644 index 000000000..ac33fdd41 Binary files /dev/null and b/packages/demo/public/screenshots/create.png differ diff --git a/packages/demo/public/screenshots/home-large.jpg b/packages/demo/public/screenshots/home-large.jpg new file mode 100644 index 000000000..57d26a3f8 Binary files /dev/null and b/packages/demo/public/screenshots/home-large.jpg differ diff --git a/packages/demo/public/screenshots/import.png b/packages/demo/public/screenshots/import.png new file mode 100644 index 000000000..717309fc1 Binary files /dev/null and b/packages/demo/public/screenshots/import.png differ diff --git a/packages/demo/public/screenshots/lock.png b/packages/demo/public/screenshots/lock.png new file mode 100644 index 000000000..74d15aa12 Binary files /dev/null and b/packages/demo/public/screenshots/lock.png differ diff --git a/packages/demo/public/screenshots/reveal.png b/packages/demo/public/screenshots/reveal.png new file mode 100644 index 000000000..e6c066736 Binary files /dev/null and b/packages/demo/public/screenshots/reveal.png differ diff --git a/packages/demo/public/screenshots/rln-1.png b/packages/demo/public/screenshots/rln-1.png new file mode 100644 index 000000000..03674a387 Binary files /dev/null and b/packages/demo/public/screenshots/rln-1.png differ diff --git a/packages/demo/public/screenshots/rln-2.png b/packages/demo/public/screenshots/rln-2.png new file mode 100644 index 000000000..3542fee8a Binary files /dev/null and b/packages/demo/public/screenshots/rln-2.png differ diff --git a/packages/demo/public/screenshots/semaphore.png b/packages/demo/public/screenshots/semaphore.png new file mode 100644 index 000000000..9fc32a4b1 Binary files /dev/null and b/packages/demo/public/screenshots/semaphore.png differ diff --git a/packages/demo/src/components/ActionBox/ActionBox.tsx b/packages/demo/src/components/ActionBox/ActionBox.tsx index 5b4ed3581..7ca123946 100644 --- a/packages/demo/src/components/ActionBox/ActionBox.tsx +++ b/packages/demo/src/components/ActionBox/ActionBox.tsx @@ -1,25 +1,33 @@ +import PlayCircleFilledIcon from "@mui/icons-material/PlayCircleFilled"; import Box from "@mui/material/Box"; import Button from "@mui/material/Button"; +import Typography from "@mui/material/Typography"; import { useTheme } from "@mui/styles"; import { themes } from "prism-react-renderer"; +import { ReactNode } from "react"; import { LiveProvider, LiveEditor } from "react-live"; import { useGlobalStyles } from "@src/styles"; +import { sharedStyles } from "@src/styles/useGlobalStyles"; type actionFnRequiredOption = (option: T) => U; type actionFnOptionalOption = (option?: T) => U; -interface IActionBox { +interface IActionBox { + children?: ReactNode; title: string; - code: string; - option?: T; + description?: string; + option: T; + code?: string; testId?: string; onClick: actionFnOptionalOption | actionFnRequiredOption; } export const ActionBox = ({ + children, title, - option = undefined, + description, + option, code, testId = "", onClick, @@ -28,23 +36,48 @@ export const ActionBox = ({ const classes = useGlobalStyles(theme); return ( - - - - + + + + + Demo Action + + + + + {description} + - - - - + {children} + + + + + + {code && ( + + + + + + )} ); }; diff --git a/packages/demo/src/components/Bandada/Bandada.tsx b/packages/demo/src/components/Bandada/Bandada.tsx deleted file mode 100644 index d37685044..000000000 --- a/packages/demo/src/components/Bandada/Bandada.tsx +++ /dev/null @@ -1,36 +0,0 @@ -import Box from "@mui/material/Box"; -import Typography from "@mui/material/Typography"; - -import { useCodeExample } from "@src/hooks/useCodeExample"; -import { useGlobalStyles } from "@src/styles"; - -import ActionBox from "../ActionBox"; - -interface IBandadaProps { - joinGroup: () => Promise; - generateGroupMerkleProof: () => Promise; -} - -export const Bandada = ({ joinGroup, generateGroupMerkleProof }: IBandadaProps): JSX.Element => { - const classes = useGlobalStyles(); - - const { code: joinGroupCode } = useCodeExample("joinGroup.ts"); - const { code: generateGroupMerkleProofCode } = useCodeExample("generateGroupMerkleProof.ts"); - - return ( - - Integration with Bandada service - - - - - - ); -}; diff --git a/packages/demo/src/components/Bandada/index.ts b/packages/demo/src/components/Bandada/index.ts deleted file mode 100644 index ed27c5ed0..000000000 --- a/packages/demo/src/components/Bandada/index.ts +++ /dev/null @@ -1,3 +0,0 @@ -import { Bandada } from "./Bandada"; - -export default Bandada; diff --git a/packages/demo/src/components/Connect/Connect.tsx b/packages/demo/src/components/Connect/Connect.tsx deleted file mode 100644 index 29194de2d..000000000 --- a/packages/demo/src/components/Connect/Connect.tsx +++ /dev/null @@ -1,30 +0,0 @@ -import Box from "@mui/material/Box"; -import Typography from "@mui/material/Typography"; - -import { useCodeExample } from "@src/hooks/useCodeExample"; -import { useGlobalStyles } from "@src/styles"; - -import ActionBox from "../ActionBox"; - -interface IConnectProps { - title: string; - isChangeIdentity: boolean; - connect: (isChangeIdentity: boolean) => void; -} - -export const Connect = ({ title, isChangeIdentity = false, connect }: IConnectProps): JSX.Element => { - const classes = useGlobalStyles(); - - const { code } = useCodeExample("connect.ts"); - - return ( - - {title} - - code={code} option={isChangeIdentity} title="Connect Identity" onClick={connect} /> - - ); -}; diff --git a/packages/demo/src/components/Connect/index.ts b/packages/demo/src/components/Connect/index.ts deleted file mode 100644 index d7b0403f0..000000000 --- a/packages/demo/src/components/Connect/index.ts +++ /dev/null @@ -1,3 +0,0 @@ -import { Connect } from "./Connect"; - -export default Connect; diff --git a/packages/demo/src/components/ConnectedIdentity/ConnectedIdentity.tsx b/packages/demo/src/components/ConnectedIdentity/ConnectedIdentity.tsx deleted file mode 100644 index 0a79bf8f5..000000000 --- a/packages/demo/src/components/ConnectedIdentity/ConnectedIdentity.tsx +++ /dev/null @@ -1,61 +0,0 @@ -import AccountCircleIcon from "@mui/icons-material/AccountCircle"; -import Box from "@mui/material/Box"; -import Typography from "@mui/material/Typography"; - -import { useGlobalStyles } from "@src/styles"; - -interface IConnectedIdentityProps { - commitment?: string; - name?: string; -} - -export const ConnectedIdentity = ({ commitment = "", name = "" }: IConnectedIdentityProps): JSX.Element => { - const classes = useGlobalStyles(); - - return ( - - - - - - CryptKeeper Connected Identity - - - - - - - Name: - - {name} - - - - {commitment ? ( - - Commitment: - - {commitment} - - ) : ( - - You need to ask to reveal identity Commitment - - )} - - - ); -}; diff --git a/packages/demo/src/components/ConnectedIdentity/index.ts b/packages/demo/src/components/ConnectedIdentity/index.ts deleted file mode 100644 index f5e174303..000000000 --- a/packages/demo/src/components/ConnectedIdentity/index.ts +++ /dev/null @@ -1,3 +0,0 @@ -import { ConnectedIdentity } from "./ConnectedIdentity"; - -export default ConnectedIdentity; diff --git a/packages/demo/src/components/DisplayCode/DisplayCode.tsx b/packages/demo/src/components/DisplayCode/DisplayCode.tsx new file mode 100644 index 000000000..571337b04 --- /dev/null +++ b/packages/demo/src/components/DisplayCode/DisplayCode.tsx @@ -0,0 +1,23 @@ +import Box from "@mui/material/Box"; +import { CopyBlock, irBlack } from "react-code-blocks"; + +import { useGlobalStyles } from "@src/styles"; + +export interface IDisplayCodeProps { + isShowCode: boolean; + code: string; +} + +export const DisplayCode = ({ isShowCode, code }: IDisplayCodeProps): JSX.Element => { + const classes = useGlobalStyles(); + + return ( + + {isShowCode && ( + // eslint-disable-next-line @typescript-eslint/ban-ts-comment + // @ts-expect-error + + )} + + ); +}; diff --git a/packages/demo/src/components/DisplayCode/index.ts b/packages/demo/src/components/DisplayCode/index.ts new file mode 100644 index 000000000..a291f22e8 --- /dev/null +++ b/packages/demo/src/components/DisplayCode/index.ts @@ -0,0 +1,5 @@ +import { DisplayCode } from "./DisplayCode"; + +export type { IDisplayCodeProps } from "./DisplayCode"; + +export default DisplayCode; diff --git a/packages/demo/src/components/DrawerList/DrawerList.tsx b/packages/demo/src/components/DrawerList/DrawerList.tsx new file mode 100644 index 000000000..170ff1687 --- /dev/null +++ b/packages/demo/src/components/DrawerList/DrawerList.tsx @@ -0,0 +1,32 @@ +import { createSvgIcon } from "@mui/material"; +import Box from "@mui/material/Box"; +import Toolbar from "@mui/material/Toolbar"; +import useTheme from "@mui/styles/useTheme"; + +import ListBox from "@src/components/ListBox"; +import { DEMO, GETTING_STARTED, REFERENCES } from "@src/constants/lists"; +import LogoSvg from "@src/static/icons/logo.svg"; +import { useGlobalStyles } from "@src/styles/useGlobalStyles"; + +import { Icon } from "../Icon/Icon"; + +const CKIcon = createSvgIcon(LogoSvg, "CKIcon"); + +export const DrawerList = (): JSX.Element => { + const theme = useTheme(); + const classes = useGlobalStyles(theme); + + return ( + + + + + + + + + + + + ); +}; diff --git a/packages/demo/src/components/DrawerList/index.ts b/packages/demo/src/components/DrawerList/index.ts new file mode 100644 index 000000000..2ddcbafad --- /dev/null +++ b/packages/demo/src/components/DrawerList/index.ts @@ -0,0 +1,3 @@ +import { DrawerList } from "./DrawerList"; + +export default DrawerList; diff --git a/packages/demo/src/components/Footer/Footer.tsx b/packages/demo/src/components/Footer/Footer.tsx new file mode 100644 index 000000000..de7e01fd5 --- /dev/null +++ b/packages/demo/src/components/Footer/Footer.tsx @@ -0,0 +1,88 @@ +import { faDiscord } from "@fortawesome/free-brands-svg-icons"; +import { FontAwesomeIcon } from "@fortawesome/react-fontawesome"; +import { ArrowBackIosNewOutlined, ArrowForwardIosOutlined, Twitter } from "@mui/icons-material"; +import GitHubIcon from "@mui/icons-material/GitHub"; +import { Box, Button, IconButton, useTheme } from "@mui/material"; +import { useLocation, useNavigate } from "react-router-dom"; + +import { Paths } from "@src/constants"; +import { useGlobalStyles } from "@src/styles/useGlobalStyles"; + +const pages = [ + // GettingStarted + { path: Paths.OVERVIEW, label: "Overview" }, + { path: Paths.CONTRIBUTING, label: "Contributing" }, + + // DEMO + { path: Paths.CONNECT, label: "Connect to CryptKeeper" }, + { path: Paths.GET_IDENTITY_METADATA, label: "Connected Identity Metadata" }, + { path: Paths.IMPORT_IDENTITY, label: "Import Identity" }, + { path: Paths.REVEAL_IDENTITY_COMMITMENT, label: "Reveal Identity Commitment" }, + { path: Paths.SEMAPHORE, label: "Semaphore" }, + { path: Paths.RLN, label: "Rate-Limiting Nullifier" }, + { path: Paths.BANDADA, label: "Bandada" }, + + // References + { path: Paths.TERMS, label: "Terms" }, + { path: Paths.FAQ, label: "FAQ" }, + { path: Paths.RESOURCES, label: "Resources" }, + { path: Paths.PRIVACY_POLICY, label: "Privacy Policy" }, +]; + +export const Footer = (): JSX.Element => { + const theme = useTheme(); + const classes = useGlobalStyles(theme); + + const location = useLocation(); + const navigate = useNavigate(); + + const currentPageIndex = pages.findIndex((page) => page.path === location.pathname); + const previousPage = pages[currentPageIndex - 1] || pages[pages.length - 1]; + const nextPage = pages[currentPageIndex + 1] || pages[0]; + return ( + + {/* Layer One */} + + + + + {/* Layer Two */} + + + + + + + {/* Layer Three */} + + + + + + + + + + + + + + + ); +}; diff --git a/packages/demo/src/components/Footer/index.ts b/packages/demo/src/components/Footer/index.ts new file mode 100644 index 000000000..e69de29bb diff --git a/packages/demo/src/components/GetCommitment/GetCommitment.tsx b/packages/demo/src/components/GetCommitment/GetCommitment.tsx deleted file mode 100644 index 0baf5fb77..000000000 --- a/packages/demo/src/components/GetCommitment/GetCommitment.tsx +++ /dev/null @@ -1,32 +0,0 @@ -import Box from "@mui/material/Box"; -import Typography from "@mui/material/Typography"; - -import { useCodeExample } from "@src/hooks/useCodeExample"; -import { useGlobalStyles } from "@src/styles"; - -import ActionBox from "../ActionBox"; - -interface IGetCommitmentProps { - revealConnectedIdentityCommitment: () => Promise; -} - -export const GetCommitment = ({ revealConnectedIdentityCommitment }: IGetCommitmentProps): JSX.Element => { - const classes = useGlobalStyles(); - const { code } = useCodeExample("revealIdentityCommitment.ts"); - - return ( - - Reveal connected identity Commitment - - - - ); -}; diff --git a/packages/demo/src/components/GetCommitment/index.ts b/packages/demo/src/components/GetCommitment/index.ts deleted file mode 100644 index 48b2b3d4c..000000000 --- a/packages/demo/src/components/GetCommitment/index.ts +++ /dev/null @@ -1,3 +0,0 @@ -import { GetCommitment } from "./GetCommitment"; - -export default GetCommitment; diff --git a/packages/demo/src/components/GetMetadata/GetMetadata.tsx b/packages/demo/src/components/GetMetadata/GetMetadata.tsx deleted file mode 100644 index 6325cd76e..000000000 --- a/packages/demo/src/components/GetMetadata/GetMetadata.tsx +++ /dev/null @@ -1,26 +0,0 @@ -import Box from "@mui/material/Box"; -import Typography from "@mui/material/Typography"; - -import ActionBox from "@src/components/ActionBox"; -import { useCodeExample } from "@src/hooks/useCodeExample"; -import { useGlobalStyles } from "@src/styles"; - -interface IGetMetadataProps { - getConnectedIdentityMetadata: () => void; -} - -export const GetMetadata = ({ getConnectedIdentityMetadata }: IGetMetadataProps): JSX.Element => { - const classes = useGlobalStyles(); - const { code } = useCodeExample("getConnectedIdentityMetadata.ts"); - - return ( - - Get Connected Identity Metadata - - - - ); -}; diff --git a/packages/demo/src/components/GetMetadata/index.ts b/packages/demo/src/components/GetMetadata/index.ts deleted file mode 100644 index f4440abfc..000000000 --- a/packages/demo/src/components/GetMetadata/index.ts +++ /dev/null @@ -1,3 +0,0 @@ -import { GetMetadata } from "./GetMetadata"; - -export default GetMetadata; diff --git a/packages/demo/src/components/Header/Header.tsx b/packages/demo/src/components/Header/Header.tsx new file mode 100644 index 000000000..a92a857ce --- /dev/null +++ b/packages/demo/src/components/Header/Header.tsx @@ -0,0 +1,70 @@ +import MenuIcon from "@mui/icons-material/Menu"; +import AppBar from "@mui/material/AppBar"; +import Box from "@mui/material/Box"; +import IconButton from "@mui/material/IconButton"; +import Toolbar from "@mui/material/Toolbar"; +import { useTheme } from "@mui/styles"; +import { useState } from "react"; +import { ToastContainer } from "react-toastify"; + +import { useGlobalStyles } from "@src/styles"; +import { sharedStyles } from "@src/styles/useGlobalStyles"; + +import { LeftSideBar } from "../LeftSideBar/LeftSideBar"; +import RightSideBar from "../RightSideBar"; + +interface IHeader { + type: "h1" | "h2"; // Add more header levels as needed + text: string; + id?: string; +} + +export const Header = (): JSX.Element => { + const theme = useTheme(); + const classes = useGlobalStyles(theme); + const [isMobileOpen, setIsMobileOpen] = useState(false); + + const handleDrawerToggle = () => { + setIsMobileOpen(!isMobileOpen); + }; + + return ( + + + + + + + + + + + {/* LeftSideBar */} + + + + + {/* RightSideBar */} + + + + + ); +}; diff --git a/packages/demo/src/components/Header/index.ts b/packages/demo/src/components/Header/index.ts new file mode 100644 index 000000000..c7bb5b34b --- /dev/null +++ b/packages/demo/src/components/Header/index.ts @@ -0,0 +1,3 @@ +import { Header } from "./Header"; + +export default Header; diff --git a/packages/demo/src/components/Icon/Icon.tsx b/packages/demo/src/components/Icon/Icon.tsx new file mode 100644 index 000000000..15de602bb --- /dev/null +++ b/packages/demo/src/components/Icon/Icon.tsx @@ -0,0 +1,47 @@ +import Box from "@mui/material/Box"; +import { useTheme } from "@mui/styles"; +import { forwardRef, MouseEventHandler, Ref } from "react"; + +import { useGlobalStyles } from "@src/styles"; + +export interface IconProps { + url?: string; + fontAwesome?: string; + className?: string; + size?: number; + disabled?: boolean; + onClick?: MouseEventHandler; +} + +const IconUI = ( + { + url = "", + size = 0.75, + className = "", + disabled = false, + fontAwesome = "", + onClick = undefined, + ...rest + }: IconProps, + ref: Ref, +): JSX.Element => { + const theme = useTheme(); + const classes = useGlobalStyles(theme); + + return ( + + ); +}; + +export const Icon = forwardRef(IconUI); diff --git a/packages/demo/src/components/Icon/index.ts b/packages/demo/src/components/Icon/index.ts new file mode 100644 index 000000000..867fce3e5 --- /dev/null +++ b/packages/demo/src/components/Icon/index.ts @@ -0,0 +1,3 @@ +import { Icon } from "./Icon"; + +export default Icon; diff --git a/packages/demo/src/components/ImportIdentity/ImportIdentity.tsx b/packages/demo/src/components/ImportIdentity/ImportIdentity.tsx deleted file mode 100644 index d5299e749..000000000 --- a/packages/demo/src/components/ImportIdentity/ImportIdentity.tsx +++ /dev/null @@ -1,38 +0,0 @@ -import Box from "@mui/material/Box"; -import TextField from "@mui/material/TextField"; -import Typography from "@mui/material/Typography"; - -import { useCodeExample } from "@src/hooks/useCodeExample"; -import { useGlobalStyles } from "@src/styles"; - -import ActionBox from "../ActionBox"; - -interface IImportIdentityProps { - importIdentity: () => Promise; -} - -export const ImportIdentity = ({ importIdentity }: IImportIdentityProps): JSX.Element => { - const classes = useGlobalStyles(); - - const { code } = useCodeExample("importIdentity.ts"); - - return ( - - Import identity - - - Import identity with trapdoor and nullifier - - - - - - - - - ); -}; diff --git a/packages/demo/src/components/ImportIdentity/index.ts b/packages/demo/src/components/ImportIdentity/index.ts deleted file mode 100644 index e0996eeb2..000000000 --- a/packages/demo/src/components/ImportIdentity/index.ts +++ /dev/null @@ -1,3 +0,0 @@ -import { ImportIdentity } from "./ImportIdentity"; - -export default ImportIdentity; diff --git a/packages/demo/src/components/LeftSideBar/LeftSideBar.tsx b/packages/demo/src/components/LeftSideBar/LeftSideBar.tsx new file mode 100644 index 000000000..e3a3d664e --- /dev/null +++ b/packages/demo/src/components/LeftSideBar/LeftSideBar.tsx @@ -0,0 +1,62 @@ +import Box from "@mui/material/Box"; +import Divider from "@mui/material/Divider"; +import Drawer from "@mui/material/Drawer"; +import { useTheme } from "@mui/styles"; + +import { useGlobalStyles } from "@src/styles"; +import { sharedStyles } from "@src/styles/useGlobalStyles"; + +import { DrawerList } from "../DrawerList/DrawerList"; + +interface ILeftSideBar { + isMobileOpen: boolean; + handleDrawerToggle: () => void; +} + +export const LeftSideBar = ({ isMobileOpen, handleDrawerToggle }: ILeftSideBar): JSX.Element => { + const theme = useTheme(); + const classes = useGlobalStyles(theme); + + const container = window.document.body; + + return ( + + + + + + + + + + + + ); +}; diff --git a/packages/demo/src/components/LeftSideBar/index.ts b/packages/demo/src/components/LeftSideBar/index.ts new file mode 100644 index 000000000..e69de29bb diff --git a/packages/demo/src/components/ListBox/ListBox.tsx b/packages/demo/src/components/ListBox/ListBox.tsx new file mode 100644 index 000000000..41a8888ac --- /dev/null +++ b/packages/demo/src/components/ListBox/ListBox.tsx @@ -0,0 +1,90 @@ +import { KeyboardArrowRight } from "@mui/icons-material"; +import Box from "@mui/material/Box"; +import ListItemButton from "@mui/material/ListItemButton"; +import ListItemText from "@mui/material/ListItemText"; +import { useTheme } from "@mui/styles"; +import { useCallback, useState, type MouseEvent as ReactMouseEvent } from "react"; +import { Collapse } from "react-collapse"; +import { useNavigate } from "react-router-dom"; + +import { type IListComponents } from "@src/constants"; +import { useGlobalStyles } from "@src/styles"; + +interface IListBoxProps { + listComponents: IListComponents; +} + +export const ListBox = ({ listComponents }: IListBoxProps): JSX.Element => { + const theme = useTheme(); + const classes = useGlobalStyles(theme); + + const navigate = useNavigate(); + + const [isShowList, setIsShowList] = useState(true); + + const handleShowList = useCallback( + (event: ReactMouseEvent) => { + event.stopPropagation(); + setIsShowList((isShow) => !isShow); + }, + [setIsShowList], + ); + + const goToPage = useCallback( + (path?: string) => { + if (path) { + navigate(path); + } + }, + [navigate], + ); + + return ( + + + + + + + + + {listComponents.subHeader?.map((subHeader) => ( + + + + + + + + {subHeader.items.map((item) => ( + + { + goToPage(item.path); + }} + > + + + + ))} + + ))} + + {listComponents.items?.map((item) => ( + + { + goToPage(item.path); + }} + > + + + + ))} + + + ); +}; diff --git a/packages/demo/src/components/ListBox/index.ts b/packages/demo/src/components/ListBox/index.ts new file mode 100644 index 000000000..041aed767 --- /dev/null +++ b/packages/demo/src/components/ListBox/index.ts @@ -0,0 +1,3 @@ +import { ListBox } from "./ListBox"; + +export default ListBox; diff --git a/packages/demo/src/components/MarkdownBox/MarkdownBox.tsx b/packages/demo/src/components/MarkdownBox/MarkdownBox.tsx new file mode 100644 index 000000000..ea624e6f7 --- /dev/null +++ b/packages/demo/src/components/MarkdownBox/MarkdownBox.tsx @@ -0,0 +1,77 @@ +import Box from "@mui/material/Box"; +import { useTheme } from "@mui/styles"; +import Markdown from "react-markdown"; +import rehypeAutolinkHeadings from "rehype-autolink-headings"; +import rehypeSlug from "rehype-slug"; + +import { useGlobalStyles } from "@src/styles"; + +import MarkdownCodeBox from "../MarkdownCodeBox"; +import MarkdownHeader from "../MarkdownHeader"; +import MarkdownImg from "../MarkdownImg"; + +interface IMarkdownBox { + doc: string; +} + +// Helper function to slugify a string (convert "Some Text" to "some-text") +function slugify(text: string): string { + return text + .toLowerCase() + .trim() + .replace(/[^a-z0-9\s-]/g, "") + .replace(/[\s-]+/g, "-"); +} + +export const MarkdownBox = ({ doc }: IMarkdownBox): JSX.Element => { + const theme = useTheme(); + const classes = useGlobalStyles(theme); + + return ( + + {children}; + } + + const match = /language-(\w+)/.exec(className || ""); + const language = match ? match[1] : undefined; + const value = String(children).replace(/\n$/, ""); + return language && ; + }, + h1({ children = "", id, ref, ...rest }) { + const finalId = id || slugify(String(children)); + return ; + }, + h2({ children = "", id, ref, ...rest }) { + const finalId = id || slugify(String(children)); + return ; + }, + img({ src, alt }) { + return ; + }, + }} + rehypePlugins={[ + rehypeSlug, + [ + rehypeAutolinkHeadings, + { + behavior: "wrap", + properties: { + style: { + textDecoration: "none", + }, + }, + }, + ], + ]} + > + {doc} + + + ); +}; diff --git a/packages/demo/src/components/MarkdownBox/index.ts b/packages/demo/src/components/MarkdownBox/index.ts new file mode 100644 index 000000000..24756ecb2 --- /dev/null +++ b/packages/demo/src/components/MarkdownBox/index.ts @@ -0,0 +1,3 @@ +import { MarkdownBox } from "./MarkdownBox"; + +export default MarkdownBox; diff --git a/packages/demo/src/components/MarkdownCodeBox/MarkdownCodeBox.tsx b/packages/demo/src/components/MarkdownCodeBox/MarkdownCodeBox.tsx new file mode 100644 index 000000000..4f6ed8a89 --- /dev/null +++ b/packages/demo/src/components/MarkdownCodeBox/MarkdownCodeBox.tsx @@ -0,0 +1,66 @@ +import CheckIcon from "@mui/icons-material/Check"; +import FileCopyIcon from "@mui/icons-material/FileCopy"; +import { Box } from "@mui/material"; +import { useTheme } from "@mui/styles"; +import { FC, HTMLAttributes, memo, useState } from "react"; +import { Prism as SyntaxHighlighter } from "react-syntax-highlighter"; +import { vscDarkPlus } from "react-syntax-highlighter/dist/esm/styles/prism"; + +import { useGlobalStyles } from "@src/styles"; + +type MarkdownCodeBox = { + language: string; + value: string; +} & HTMLAttributes; + +function copyToClipboard(text: string) { + const textarea = document.createElement("textarea"); + textarea.innerText = text; + document.body.appendChild(textarea); + textarea.select(); + document.execCommand("copy"); + textarea.remove(); +} + +export const MarkdownCodeBox: FC = memo(({ language, value }) => { + const theme = useTheme(); + const classes = useGlobalStyles(theme); + + const [isCopied, setIsCopied] = useState(false); + + const handleCopyClick = () => { + copyToClipboard(value); + setIsCopied(true); + + setTimeout(() => { + setIsCopied(false); + }, 2000); // reset after 2 seconds + }; + + return ( + + + {value} + + + + {isCopied ? ( + <> + + Copied! + + + + + + + + ) : ( + + + + )} + + + ); +}); diff --git a/packages/demo/src/components/MarkdownCodeBox/index.ts b/packages/demo/src/components/MarkdownCodeBox/index.ts new file mode 100644 index 000000000..fd8b8f64c --- /dev/null +++ b/packages/demo/src/components/MarkdownCodeBox/index.ts @@ -0,0 +1,3 @@ +import { MarkdownCodeBox } from "./MarkdownCodeBox"; + +export default MarkdownCodeBox; diff --git a/packages/demo/src/components/MarkdownHeader/MarkdownHeader.tsx b/packages/demo/src/components/MarkdownHeader/MarkdownHeader.tsx new file mode 100644 index 000000000..97ccee460 --- /dev/null +++ b/packages/demo/src/components/MarkdownHeader/MarkdownHeader.tsx @@ -0,0 +1,59 @@ +import Typography from "@mui/material/Typography"; +import { useTheme } from "@mui/styles"; +import { Children, FC, ReactNode, isValidElement, memo, useEffect } from "react"; + +import { useMarkdownHeaders } from "@src/context/MarkdownHeadersProvider"; +import { useGlobalStyles } from "@src/styles"; +import { handleHeadingClick } from "@src/utils"; + +interface IHeader { + type: "h1" | "h2" | "h3" | "h4" | "h5" | "h6"; + text: string; + id: string; +} + +interface HeaderProps { + children: React.ReactNode; + id: string; + variant: "h1" | "h2" | "h3" | "h4" | "h5" | "h6"; +} + +export const MarkdownHeader: FC = memo(({ children, id, variant, ...rest }) => { + const { addHeader } = useMarkdownHeaders(); + const theme = useTheme(); + const classes = useGlobalStyles(theme); + + const getTextFromChildren = (child: ReactNode): string => { + if (typeof child === "string") { + return child; + } + + if (isValidElement(child) && child.props.children) { + return Children.toArray(child.props.children).map(getTextFromChildren).join(""); + } + + return ""; + }; + + useEffect(() => { + const textContent = getTextFromChildren(children); + + const header: IHeader = { type: variant, text: textContent, id }; + + addHeader(header); + }, [children, id, variant]); + + return ( + { + handleHeadingClick(event, id); + }} + {...rest} + > + {children} + + ); +}); diff --git a/packages/demo/src/components/MarkdownHeader/index.ts b/packages/demo/src/components/MarkdownHeader/index.ts new file mode 100644 index 000000000..c5b43b184 --- /dev/null +++ b/packages/demo/src/components/MarkdownHeader/index.ts @@ -0,0 +1,3 @@ +import { MarkdownHeader } from "./MarkdownHeader"; + +export default MarkdownHeader; diff --git a/packages/demo/src/components/MarkdownImg/MarkdownImg.tsx b/packages/demo/src/components/MarkdownImg/MarkdownImg.tsx new file mode 100644 index 000000000..9e889a97f --- /dev/null +++ b/packages/demo/src/components/MarkdownImg/MarkdownImg.tsx @@ -0,0 +1,31 @@ +import { ArrowDropDown, ArrowRight } from "@mui/icons-material"; +import Box from "@mui/material/Box"; +import Button from "@mui/material/Button"; +import { useTheme } from "@mui/styles"; +import { useState } from "react"; + +import { useGlobalStyles } from "@src/styles"; + +export const MarkdownImg = ({ src, alt }: { src?: string; alt?: string }): JSX.Element => { + const [isExpanded, setIsExpanded] = useState(false); + + const theme = useTheme(); + const classes = useGlobalStyles(theme); + + return ( + + + + {isExpanded && {alt}} + + ); +}; diff --git a/packages/demo/src/components/MarkdownImg/index.ts b/packages/demo/src/components/MarkdownImg/index.ts new file mode 100644 index 000000000..ab916d5f7 --- /dev/null +++ b/packages/demo/src/components/MarkdownImg/index.ts @@ -0,0 +1,3 @@ +import { MarkdownImg } from "./MarkdownImg"; + +export default MarkdownImg; diff --git a/packages/demo/src/components/RateLimitingNullifier/RateLimitingNullifier.tsx b/packages/demo/src/components/RateLimitingNullifier/RateLimitingNullifier.tsx deleted file mode 100644 index 13da9a3f8..000000000 --- a/packages/demo/src/components/RateLimitingNullifier/RateLimitingNullifier.tsx +++ /dev/null @@ -1,40 +0,0 @@ -import Box from "@mui/material/Box"; -import Typography from "@mui/material/Typography"; - -import { useCodeExample } from "@src/hooks/useCodeExample"; -import { useGlobalStyles } from "@src/styles"; -import { MerkleProofType } from "@src/types"; - -import ActionBox from "../ActionBox"; - -interface IRateLimitingNullifierProps { - genRLNProof: (proofType: MerkleProofType) => void; -} - -export const RateLimitingNullifier = ({ genRLNProof }: IRateLimitingNullifierProps): JSX.Element => { - const classes = useGlobalStyles(); - const { code } = useCodeExample("rateLimitingNullifier.ts"); - - return ( - - Integration with Rate-Limiting Nullifier (RLN) - - - code={code} - option={MerkleProofType.STORAGE_ADDRESS} - title="Generate proof from Merkle proof storage address" - onClick={genRLNProof} - /> - - - code={code} - option={MerkleProofType.ARTIFACTS} - title="Generate proof from Merkle proof artifacts" - onClick={genRLNProof} - /> - - ); -}; diff --git a/packages/demo/src/components/RateLimitingNullifier/index.ts b/packages/demo/src/components/RateLimitingNullifier/index.ts deleted file mode 100644 index 0e2f90761..000000000 --- a/packages/demo/src/components/RateLimitingNullifier/index.ts +++ /dev/null @@ -1,3 +0,0 @@ -import { RateLimitingNullifier } from "./RateLimitingNullifier"; - -export default RateLimitingNullifier; diff --git a/packages/demo/src/components/RightSideBar/RightSideBar.tsx b/packages/demo/src/components/RightSideBar/RightSideBar.tsx new file mode 100644 index 000000000..9003afbec --- /dev/null +++ b/packages/demo/src/components/RightSideBar/RightSideBar.tsx @@ -0,0 +1,80 @@ +import Box from "@mui/material/Box"; +import Drawer from "@mui/material/Drawer"; +import List from "@mui/material/List"; +import ListItem from "@mui/material/ListItem"; +import ListItemText from "@mui/material/ListItemText"; +import Typography from "@mui/material/Typography"; +import { useTheme } from "@mui/styles"; +import { useEffect, useState } from "react"; + +import { useMarkdownHeaders } from "@src/context/MarkdownHeadersProvider"; +import { useGlobalStyles } from "@src/styles"; +import { handleHeadingClick } from "@src/utils"; + +import ConnectedIdentity from "../ConnectedIdentity"; + +export const RightSideBar = (): JSX.Element => { + const theme = useTheme(); + const classes = useGlobalStyles(theme); + + const [activeHeader, setActiveHeader] = useState(null); + + const { headers } = useMarkdownHeaders(); + + useEffect(() => { + const handleScroll = () => { + let newActiveHeader = null; + + headers.forEach((header) => { + const element = document.getElementById(header.id); + if (element) { + const rect = element.getBoundingClientRect(); + if (rect.top <= 10 && rect.top >= -10) { + newActiveHeader = header.id; + } + } + }); + + setActiveHeader(newActiveHeader); + }; + + document.addEventListener("scroll", handleScroll); + + return () => { + document.removeEventListener("scroll", handleScroll); + }; + }, [headers]); + + return ( + + + + {headers.length > 0 && ( + + + + Contents + + + + {headers.map((header, index) => ( + { + handleHeadingClick(event, header.id); + }} + > + + + ))} + + )} + + ); +}; diff --git a/packages/demo/src/components/RightSideBar/index.ts b/packages/demo/src/components/RightSideBar/index.ts new file mode 100644 index 000000000..5ac917c38 --- /dev/null +++ b/packages/demo/src/components/RightSideBar/index.ts @@ -0,0 +1,3 @@ +import { RightSideBar } from "./RightSideBar"; + +export default RightSideBar; diff --git a/packages/demo/src/components/Semaphore/Semaphore.tsx b/packages/demo/src/components/Semaphore/Semaphore.tsx deleted file mode 100644 index 257253b1a..000000000 --- a/packages/demo/src/components/Semaphore/Semaphore.tsx +++ /dev/null @@ -1,41 +0,0 @@ -import Box from "@mui/material/Box"; -import Typography from "@mui/material/Typography"; - -import { useCodeExample } from "@src/hooks/useCodeExample"; -import { useGlobalStyles } from "@src/styles"; -import { MerkleProofType } from "@src/types"; - -import ActionBox from "../ActionBox"; - -interface ISemaphoreProps { - genSemaphoreProof: (proofType: MerkleProofType) => void; -} - -export const Semaphore = ({ genSemaphoreProof }: ISemaphoreProps): JSX.Element => { - const classes = useGlobalStyles(); - - const { code } = useCodeExample("semaphore.ts"); - - return ( - - Integration with Semaphore - - - code={code} - option={MerkleProofType.STORAGE_ADDRESS} - title="Generate proof from Merkle proof storage address" - onClick={genSemaphoreProof} - /> - - - code={code} - option={MerkleProofType.ARTIFACTS} - title="Generate proof from Merkle proof artifacts" - onClick={genSemaphoreProof} - /> - - ); -}; diff --git a/packages/demo/src/components/Semaphore/index.ts b/packages/demo/src/components/Semaphore/index.ts deleted file mode 100644 index 9b7624a89..000000000 --- a/packages/demo/src/components/Semaphore/index.ts +++ /dev/null @@ -1,3 +0,0 @@ -import { Semaphore } from "./Semaphore"; - -export default Semaphore; diff --git a/packages/demo/src/components/VerifiableCredentials/VerifiableCredentials.tsx b/packages/demo/src/components/VerifiableCredentials/VerifiableCredentials.tsx deleted file mode 100644 index 39f611e09..000000000 --- a/packages/demo/src/components/VerifiableCredentials/VerifiableCredentials.tsx +++ /dev/null @@ -1,55 +0,0 @@ -import Box from "@mui/material/Box"; -import Typography from "@mui/material/Typography"; - -import { useCodeExample } from "@src/hooks/useCodeExample"; -import { useGlobalStyles } from "@src/styles"; - -import ActionBox from "../ActionBox"; - -interface IVerifiableCredentialsProps { - addVerifiableCredentialRequest: (credentialType: string) => Promise; - generateVerifiablePresentationRequest: () => Promise; -} - -export const VerifiableCredentials = ({ - addVerifiableCredentialRequest, - generateVerifiablePresentationRequest, -}: IVerifiableCredentialsProps): JSX.Element => { - const classes = useGlobalStyles(); - - const { code: addVC } = useCodeExample("addVC.ts"); - const { code: generateVP } = useCodeExample("generateVP.ts"); - - return ( - - Accepting Verifiable Credentials - - - code={addVC} - option="UniversityDegreeCredential" - testId="add-verifiable-credential" - title="Add a University Degree Verifiable Credential" - onClick={addVerifiableCredentialRequest} - /> - - - code={generateVP} - option="DriversLicenseCredential" - testId="add-verifiable-credential" - title="Add a Drivers License Verifiable Credential" - onClick={addVerifiableCredentialRequest} - /> - - - - ); -}; diff --git a/packages/demo/src/components/VerifiableCredentials/index.ts b/packages/demo/src/components/VerifiableCredentials/index.ts deleted file mode 100644 index 9204505b9..000000000 --- a/packages/demo/src/components/VerifiableCredentials/index.ts +++ /dev/null @@ -1,3 +0,0 @@ -import { VerifiableCredentials } from "./VerifiableCredentials"; - -export default VerifiableCredentials; diff --git a/packages/demo/src/constants/index.ts b/packages/demo/src/constants/index.ts index 0884b1457..52beac659 100644 --- a/packages/demo/src/constants/index.ts +++ b/packages/demo/src/constants/index.ts @@ -1,2 +1,5 @@ -export const SERVER_URL = process.env.MERKLE_MOCK_SERVER; +export const SERVER_URL = "http://localhost:8090"; export const MERKLE_STORAGE_URL = `${SERVER_URL}/merkleProof`; + +export { Paths } from "./paths"; +export { type IListComponents, DEMO, GETTING_STARTED, REFERENCES } from "./lists"; diff --git a/packages/demo/src/constants/lists.ts b/packages/demo/src/constants/lists.ts new file mode 100644 index 000000000..0cf69ee70 --- /dev/null +++ b/packages/demo/src/constants/lists.ts @@ -0,0 +1,70 @@ +import { Paths } from "./paths"; + +interface IItemComponent { + title: string; + path?: string; +} + +interface ISubHeaderComponent { + title?: string; + items: IItemComponent[]; +} + +export interface IListComponents { + header: IItemComponent; + subHeader?: ISubHeaderComponent[]; + items?: IItemComponent[]; +} + +export const DEMO: IListComponents = { + header: { + title: "Demo", + }, + subHeader: [ + { + title: "CONNECTION", + items: [{ title: "Connect to CryptKeeper", path: Paths.CONNECT }], + }, + { + title: "IDENTITY MANAGEMENT", + items: [ + { title: "Connected Identity Metadata", path: Paths.GET_IDENTITY_METADATA }, + { title: "Import Identity", path: Paths.IMPORT_IDENTITY }, + { title: "Reveal Identity Commitment", path: Paths.REVEAL_IDENTITY_COMMITMENT }, + ], + }, + { + title: "ZERO-KNOWLEDGE PROOFS MANAGEMENT", + items: [ + { title: "Semaphore", path: Paths.SEMAPHORE }, + { title: "Rate-Limiting Nullifier", path: Paths.RLN }, + ], + }, + { + title: "External System", + items: [{ title: "Bandada", path: Paths.BANDADA }], + }, + ], +}; + +export const GETTING_STARTED: IListComponents = { + header: { + title: "Getting Started", + }, + items: [ + { title: "Overview", path: Paths.OVERVIEW }, + { title: "Contributing", path: Paths.CONTRIBUTING }, + ], +}; + +export const REFERENCES: IListComponents = { + header: { + title: "References", + }, + items: [ + { title: "Terms", path: Paths.TERMS }, + { title: "FAQ", path: Paths.FAQ }, + { title: "Resources", path: Paths.RESOURCES }, + { title: "Privacy Policy", path: Paths.PRIVACY_POLICY }, + ], +}; diff --git a/packages/demo/src/constants/paths.ts b/packages/demo/src/constants/paths.ts new file mode 100644 index 000000000..b34e19139 --- /dev/null +++ b/packages/demo/src/constants/paths.ts @@ -0,0 +1,17 @@ +export enum Paths { + HOME = "/", + OVERVIEW = "/overview", + CONTRIBUTING = "/contributing", + FAQ = "/faq", + TERMS = "/terms", + RESOURCES = "/resources", + PRIVACY_POLICY = "/privacy-policy", + CONNECT = "/connect", + GET_IDENTITY_METADATA = "/get-connected-metadata", + IMPORT_IDENTITY = "/import-identity", + REVEAL_IDENTITY_COMMITMENT = "/reveal-identity-commitment", + SEMAPHORE = "/semaphore", + RLN = "/rate-limiting-nullifier", + BANDADA = "/bandada", + VERIFIABLE_CREDENTIALS = "/verifiable-credentials", +} diff --git a/packages/demo/src/context/CryptKeeperClientProvider.tsx b/packages/demo/src/context/CryptKeeperClientProvider.tsx new file mode 100644 index 000000000..294a9d45c --- /dev/null +++ b/packages/demo/src/context/CryptKeeperClientProvider.tsx @@ -0,0 +1,164 @@ +/* eslint-disable @typescript-eslint/no-empty-function */ +import { + initializeCryptKeeper, + type ICryptKeeperInjectedProvider, + EventName, + RPCExternalAction, +} from "@cryptkeeperzk/providers"; +import { + createContext, + useContext, + useState, + useEffect, + type PropsWithChildren, + type Dispatch, + type SetStateAction, + useCallback, + useMemo, +} from "react"; +import { toast } from "react-toastify"; + +import type { ConnectedIdentityMetadata } from "@cryptkeeperzk/types"; + +interface IClientContext { + client: ICryptKeeperInjectedProvider | undefined; + isConnected: boolean; + connectedCommitment: string; + connectedIdentityMetadata?: ConnectedIdentityMetadata; + setConnectedIdentityCommitment: Dispatch>; + setConnectedIdentityMetadata: Dispatch>; + setClient: Dispatch>; + getConnectedIdentityMetadata: () => Promise; +} + +const ClientContext = createContext({ + client: undefined, + isConnected: false, + connectedCommitment: "", + connectedIdentityMetadata: undefined, + setConnectedIdentityCommitment: () => {}, + setConnectedIdentityMetadata: () => {}, + setClient: () => {}, + getConnectedIdentityMetadata: async () => {}, +}); + +export const CryptKeeperClientProvider = ({ children }: PropsWithChildren): JSX.Element => { + const [isConnected, setIsConnected] = useState(false); + const [client, setClient] = useState(); + const [connectedCommitment, setConnectedIdentityCommitment] = useState(""); + const [connectedIdentityMetadata, setConnectedIdentityMetadata] = useState(); + + const getConnectedIdentityMetadata = useCallback(async () => { + await client + ?.request({ + method: RPCExternalAction.GET_CONNECTED_IDENTITY_DATA, + }) + .then((connectedIdentity) => { + if (connectedIdentity) { + setConnectedIdentityMetadata(connectedIdentity as ConnectedIdentityMetadata); + setIsConnected(true); + toast(`Getting Identity Metadata Successfully!`, { type: "success" }); + } + }); + }, [client, setConnectedIdentityMetadata, setIsConnected]); + + const contextValues = useMemo( + () => ({ + client, + isConnected, + connectedCommitment, + connectedIdentityMetadata, + setConnectedIdentityCommitment, + setConnectedIdentityMetadata, + setClient, + getConnectedIdentityMetadata, + }), + [ + client, + isConnected, + connectedCommitment, + connectedIdentityMetadata, + setConnectedIdentityCommitment, + setConnectedIdentityMetadata, + setClient, + getConnectedIdentityMetadata, + ], + ); + + const onLogin = useCallback(() => { + getConnectedIdentityMetadata(); + }, [getConnectedIdentityMetadata]); + + const onApproval = useCallback( + (payload: unknown) => { + const { isApproved } = payload as { isApproved: boolean }; + + if (isApproved) { + getConnectedIdentityMetadata(); + } else { + setConnectedIdentityMetadata(undefined); + } + }, + [setConnectedIdentityMetadata, getConnectedIdentityMetadata], + ); + + const onIdentityChanged = useCallback( + (payload: unknown) => { + const metadata = payload as ConnectedIdentityMetadata; + setConnectedIdentityMetadata(metadata); + + toast(`Identity has changed! ${metadata.name}`, { + type: "success", + }); + }, + [setConnectedIdentityMetadata], + ); + + const onLogout = useCallback(() => { + setConnectedIdentityMetadata(undefined); + setIsConnected(false); + }, [setConnectedIdentityMetadata, setIsConnected]); + + const onReject = useCallback(() => { + toast(`User rejected request`, { type: "error" }); + }, []); + + const onCreateIdentity = useCallback((payload: unknown) => { + toast(`Identity has been created ${JSON.stringify(payload)}`, { type: "success" }); + }, []); + + // Initialize Injected CryptKeeper Provider Client + useEffect(() => { + const cryptkeeperInjectedProvider = initializeCryptKeeper(); + + if (cryptkeeperInjectedProvider) { + setClient(cryptkeeperInjectedProvider); + } else { + toast(`CryptKeeper is not installed in the browser`, { type: "error" }); + } + }, [setClient]); + + // Listen to Injected CryptKeeper Provider Client Events + useEffect(() => { + if (!client) { + return undefined; + } + + client.on(EventName.LOGIN, onLogin); + client.on(EventName.LOGOUT, onLogout); + client.on(EventName.APPROVAL, onApproval); + client.on(EventName.USER_REJECT, onReject); + client.on(EventName.CREATE_IDENTITY, onCreateIdentity); + + getConnectedIdentityMetadata(); + + return () => { + client.cleanListeners(); + }; + }, [client, onLogout, onIdentityChanged, onReject, onCreateIdentity, onApproval]); + + return {children}; +}; + +export const useCryptKeeperClient = (): IClientContext => useContext(ClientContext); +/* eslint-enable @typescript-eslint/no-empty-function */ diff --git a/packages/demo/src/context/MarkdownHeadersProvider.tsx b/packages/demo/src/context/MarkdownHeadersProvider.tsx new file mode 100644 index 000000000..fd1d490b5 --- /dev/null +++ b/packages/demo/src/context/MarkdownHeadersProvider.tsx @@ -0,0 +1,39 @@ +import { createContext, type PropsWithChildren, useContext, useState, useEffect } from "react"; +import { useLocation } from "react-router-dom"; + +interface IHeaderContext { + type: "h1" | "h2" | "h3" | "h4" | "h5" | "h6"; + text: string; + id: string; +} + +interface IMarkdownHeaderContext { + headers: IHeaderContext[]; + addHeader: (header: IHeaderContext) => void; +} + +// Define the context with the updated type and default values +const MarkdownHeaderContext = createContext({ + headers: [], + addHeader: () => {}, +}); + +export const MarkdownHeaderProvider = ({ children }: PropsWithChildren): JSX.Element => { + const [headers, setHeaders] = useState([]); + const location = useLocation(); + + // Listen to changes in the location + useEffect(() => { + setHeaders([]); + }, [location]); + + const addHeader = (header: IHeaderContext) => { + if (!headers.some((h) => h.id === header.id)) { + setHeaders((prevHeaders) => [...prevHeaders, header]); + } + }; + + return {children}; +}; + +export const useMarkdownHeaders = (): IMarkdownHeaderContext => useContext(MarkdownHeaderContext); // <-- Return type is an array diff --git a/packages/demo/src/context/index.ts b/packages/demo/src/context/index.ts new file mode 100644 index 000000000..10c992933 --- /dev/null +++ b/packages/demo/src/context/index.ts @@ -0,0 +1,2 @@ +export { CryptKeeperClientProvider } from "./CryptKeeperClientProvider"; +export { MarkdownHeaderProvider } from "./MarkdownHeadersProvider"; diff --git a/packages/demo/src/custom.d.ts b/packages/demo/src/custom.d.ts index b14005e1f..5995bab73 100644 --- a/packages/demo/src/custom.d.ts +++ b/packages/demo/src/custom.d.ts @@ -8,6 +8,11 @@ declare module "*.png" { export default content; } +declare module "*.jpg" { + const content: string; + export default content; +} + declare module "*.gif" { const content: string; export default content; diff --git a/packages/demo/src/hooks/index.ts b/packages/demo/src/hooks/index.ts new file mode 100644 index 000000000..5913e8d23 --- /dev/null +++ b/packages/demo/src/hooks/index.ts @@ -0,0 +1 @@ +export { useFileReader } from "./useFileReader"; diff --git a/packages/demo/src/hooks/useFileReader.ts b/packages/demo/src/hooks/useFileReader.ts new file mode 100644 index 000000000..4e4604712 --- /dev/null +++ b/packages/demo/src/hooks/useFileReader.ts @@ -0,0 +1,24 @@ +import { useState, useCallback, useEffect } from "react"; + +import { loadFile } from "@src/utils"; + +interface IUseCodeExampleData { + fileContent: string; +} + +export const useFileReader = (file: string): IUseCodeExampleData => { + const [fileContent, setFileContent] = useState(""); + + const getFileContent = useCallback(async () => { + const codeExampleResponse = await loadFile(`./docs/${file}`); + setFileContent(codeExampleResponse); + }, [setFileContent]); + + useEffect(() => { + getFileContent(); + }, [getFileContent]); + + return { + fileContent, + }; +}; diff --git a/packages/demo/src/index.tsx b/packages/demo/src/index.tsx index 03d1116fe..d2649e700 100644 --- a/packages/demo/src/index.tsx +++ b/packages/demo/src/index.tsx @@ -1,23 +1,31 @@ import CssBaseline from "@mui/material/CssBaseline"; import { ThemeProvider } from "@mui/material/styles"; -import dotenv from "dotenv"; +import { Suspense } from "react"; import { createRoot } from "react-dom/client"; +import { BrowserRouter } from "react-router-dom"; import "react-toastify/dist/ReactToastify.css"; -import path from "path"; - +import { CryptKeeperClientProvider } from "@src/context/CryptKeeperClientProvider"; import Main from "@src/pages/Main"; import { theme } from "@src/styles"; -dotenv.config({ path: path.resolve(__dirname, "../..", ".env"), override: true }); +import { MarkdownHeaderProvider } from "./context"; const container = document.getElementById("root"); const root = createRoot(container!); root.render( - - + + + + + + -
- , +
+ + + + + , ); diff --git a/packages/demo/src/pages/Bandada/Bandada.tsx b/packages/demo/src/pages/Bandada/Bandada.tsx new file mode 100644 index 000000000..9977ad1f3 --- /dev/null +++ b/packages/demo/src/pages/Bandada/Bandada.tsx @@ -0,0 +1,66 @@ +import Box from "@mui/material/Box"; +import Container from "@mui/material/Container"; + +import ActionBox from "@src/components/ActionBox"; +import DisplayProof from "@src/components/DisplayProof"; +import { Footer } from "@src/components/Footer/Footer"; +import Header from "@src/components/Header"; +import MarkdownBox from "@src/components/MarkdownBox"; +import { useFileReader } from "@src/hooks"; +import { useGlobalStyles } from "@src/styles"; +import { injectMarkdownCode } from "@src/utils"; + +import { useBandada } from "./useBandada"; + +export const Bandada = (): JSX.Element => { + const classes = useGlobalStyles(); + const { proof, joinGroup, generateGroupMerkleProof } = useBandada(); + + const { fileContent: joinGroupCode } = useFileReader("joinGroup.ts"); + const { fileContent: generateGroupMerkleProofCode } = useFileReader("generateGroupMerkleProof.ts"); + + const { fileContent: doc } = useFileReader("bandada.md"); + + const codeSnippets: Record = { + 1: useFileReader("join.ts").fileContent, + 2: useFileReader("joinEvent.ts").fileContent, + 3: useFileReader("generate.ts").fileContent, + 4: useFileReader("generateEvent.ts").fileContent, + }; + + const injectedMarkdown = injectMarkdownCode(doc, codeSnippets); + + return ( + + {/* Header */} +
+ + {/* Center Content */} + + + + + + + description="Generate Group Merkle Proof" + option={null} + title="Generate" + onClick={generateGroupMerkleProof} + /> + + description="Join test group" option={null} title="Join" onClick={joinGroup} /> + + + +