From 630ed623d3ebdab42d323209bbea557173a895c6 Mon Sep 17 00:00:00 2001 From: vplasencia Date: Mon, 6 Nov 2023 18:51:10 +0100 Subject: [PATCH] feat(website): create the learn page --- apps/website/package.json | 1 + apps/website/src/app/learn/page.tsx | 293 +++++++++++++++++ apps/website/src/components/ActionCard.tsx | 6 +- apps/website/src/components/ArticleCard.tsx | 47 +-- .../src/components/ArticlesCarousel.tsx | 65 ++++ apps/website/src/components/CodeBlock.tsx | 51 +++ apps/website/src/components/InfoCard.tsx | 37 +++ apps/website/src/components/SectionBlock.tsx | 67 ++++ apps/website/src/components/VideoCard.tsx | 42 +-- .../website/src/components/VideosCarousel.tsx | 65 ++++ apps/website/src/data/articles.json | 37 +++ apps/website/src/data/videos.json | 98 ++++++ apps/website/src/icons/IconBadge.tsx | 15 + apps/website/src/icons/IconCheck.tsx | 15 + apps/website/src/icons/IconEye.tsx | 6 +- apps/website/src/icons/IconFlag.tsx | 15 + apps/website/src/styles/colors.ts | 2 + yarn.lock | 306 +++++++++++++++++- 18 files changed, 1115 insertions(+), 53 deletions(-) create mode 100644 apps/website/src/app/learn/page.tsx create mode 100644 apps/website/src/components/ArticlesCarousel.tsx create mode 100644 apps/website/src/components/CodeBlock.tsx create mode 100644 apps/website/src/components/InfoCard.tsx create mode 100644 apps/website/src/components/SectionBlock.tsx create mode 100644 apps/website/src/components/VideosCarousel.tsx create mode 100644 apps/website/src/data/articles.json create mode 100644 apps/website/src/data/videos.json create mode 100644 apps/website/src/icons/IconBadge.tsx create mode 100644 apps/website/src/icons/IconCheck.tsx create mode 100644 apps/website/src/icons/IconFlag.tsx diff --git a/apps/website/package.json b/apps/website/package.json index 4bd63b3a8..4f0f70723 100644 --- a/apps/website/package.json +++ b/apps/website/package.json @@ -18,6 +18,7 @@ "framer-motion": "^10.16.4", "next": "13.5.5", "react": "^18", + "react-code-blocks": "^0.1.4", "react-dom": "^18" }, "devDependencies": { diff --git a/apps/website/src/app/learn/page.tsx b/apps/website/src/app/learn/page.tsx new file mode 100644 index 000000000..a150ce627 --- /dev/null +++ b/apps/website/src/app/learn/page.tsx @@ -0,0 +1,293 @@ +import { + Flex, + Link, + Text, + VStack, + Tabs, + TabList, + TabPanels, + Tab, + TabPanel, + TabIndicator, + Divider +} from "@chakra-ui/react" +import Image from "next/image" +import InfoCard, { InfoBlock } from "../../components/InfoCard" +import SectionBlock, { SectionBlockProps } from "../../components/SectionBlock" +import MediaCarousel from "../../components/VideosCarousel" +import IconEyelash from "@/icons/IconEyelash" +import IconEye from "@/icons/IconEye" +import IconUser from "@/icons/IconUser" +import IconTree from "@/icons/IconTree" +import IconManageUsers from "@/icons/IconManageUsers" +import IconGroup from "@/icons/IconGroup" +import IconBadge from "@/icons/IconBadge" +import IconCheck from "@/icons/IconCheck" +import IconFlag from "@/icons/IconFlag" +import ArticlesCarousel from "@/components/ArticlesCarousel" + +export default function Learn() { + const infoCardTexts: InfoBlock[][] = [ + [ + { + title: "Accessibility", + body: "To reach a very large group of developers, a protocol needs to be extremely user-friendly, understandable and simple." + }, + { + title: "Composability", + body: "Achieve an excellent balance between simplicity and functionality through modularity, autonomy, and interoperability. " + }, + { + title: "Efficiency", + body: "A protocol must not only work, it must also be extremely efficient if the goal is to support privacy by default for everyone." + } + ], + [ + { + title: "Developer experience", + body: "Enabling developers to focus on innovation by simplifying complexities while supporting diverse use cases." + }, + { + title: "Education", + body: "Empowering individuals with knowledge, resources, and support, ensuring they're equipped to innovate and solve challenges." + }, + { + title: "Community", + body: "Fostering spaces where collaboration thrives, ideas flourish, and diverse voices are celebrated." + } + ], + [ + { + title: "Completeness", + body: "If the statement is true, an honest verifier will be convinced of this fact by an honest prover every time." + }, + { + title: "Soundness", + body: "If the statement is false, no cheating prover can convince an honest verifier that is true, except with some small probability." + }, + { + title: "Zero-knowledge", + body: "If the statement is true, no verifier learns anything other than the fact that the statement is true." + } + ] + ] + + const sectionBlockTexts: SectionBlockProps[] = [ + { + title: "Semaphore identities", + description: + "Given to all Semaphore group members, it is comprised of three parts - identity commitment, trapdoor, and nullifier.", + linkText: "Create Semaphore identities", + linkUrl: "https://semaphore.pse.dev/docs/guides/identities", + codeText: `import { Identity } from "@semaphore-protocol/identity" + +const identity = new Identity() + +const trapdoor = identity.getTrapdoor() +const nullifier = identity.getNullifier() +const commitment = identity.generateCommitment()`, + itemList: [ + { + icon: , + heading: "Private values", + body: "Trapdoor and nullifier values are the private values of the Semaphore identity. To avoid fraud, the owner must keep both values secret." + }, + { + icon: , + heading: "Private values", + body: "Trapdoor and nullifier values are the private values of the Semaphore identity. To avoid fraud, the owner must keep both values secret." + }, + { + icon: , + heading: "Generate identities", + body: "Semaphore identities can be generated deterministically or randomly. Deterministic identities can be generated from the hash of a secret message." + } + ] + }, + { + title: "Semaphore groups", + description: + "Semaphore groups are binary incremental Merkle trees that store the public identity commitment of each member.", + linkText: "Create Semaphore groups", + linkUrl: "https://semaphore.pse.dev/docs/guides/groups", + codeText: `import { Group } from "@semaphore-protocol/group" + +const group = new Group() + +group.addMember(commitment)`, + itemList: [ + { + icon: , + heading: "Merkle trees", + body: "Each leaf contains an identity commitment for a user. The identity commitment proves that the user is a group member without revealing the private identity of the user." + }, + { + icon: , + heading: "Types of groups", + body: "Groups can be created and managed in a decentralized fashion with Semaphore contracts or off-chain with our JavaScript libraries." + }, + { + icon: , + heading: "Group management", + body: "Users can join and leave groups by themselves, or an admin can add and remove them. Admins can be centralized authorities, Ethereum accounts, multi-sig wallets or smart contracts." + } + ] + }, + { + title: "Semaphore proofs", + description: + "Semaphore group members can anonymously prove that they are part of a group and that they are generating their own proofs and signals.", + linkText: "Generate Semaphore proofs", + linkUrl: "https://semaphore.pse.dev/docs/guides/proofs", + codeText: `import { generateProof, verifyProof } from "@semaphore-protocol/proof" + +const externalNullifier = BigInt(1) +const signal = "Hello world" + +const fullProof = await generateProof(identity, group, externalNullifier, signal, { + zkeyFilePath: "./semaphore.zkey", + wasmFilePath: "./semaphore.wasm" +}) + +const verificationKey = JSON.parse(fs.readFileSync("./semaphore.json", "utf-8")) + +await verifyProof(verificationKey, fullProof)`, + itemList: [ + { + icon: , + heading: "Membership", + body: "Only users who are part of a group can generate a valid proof for that group." + }, + { + icon: , + heading: "Signals", + body: "Group users can anonymously broadcast signals such as votes or endorsements without revealing their original identity." + }, + { + icon: , + heading: "Verifiers", + body: "Semaphore proofs can be verified with our contracts or off-chain with our JavaScript libraries." + } + ] + } + ] + + const renderTabBlockSemaphore = () => ( + + + + Semaphore: Anonymous interactions + + + Using zero knowledge, Semaphore allows users to prove their membership of a group and send signals + such as votes or endorsements without revealing their original identity. The goal is to make + Semaphore a standard for anonymous signaling and group membership proving. + + + + + + + Principles + + + + + + Main focus + + + + + + + ) + + const renderTabBlockZeroKnowledge = () => ( + + + + Zero Knowledge: new cryptography + + + Zero-knowledge is a new field in cryptography that allows developers to build apps that allow users + to share information with each other without revealing their identities or the contents of the + information being shared. + + + + Learn more + + + + + + + Characteristics + + + + + + ) + + return ( + + + Shadow of a person + + + + About Semaphore + + + About Zero Knowledge + + + + + {renderTabBlockSemaphore()} + {renderTabBlockZeroKnowledge()} + + + + + {sectionBlockTexts.map((sectionBlockText, i) => ( + + + {i !== sectionBlockTexts.length - 1 && } + + ))} + + + + + + + + + + + ) +} diff --git a/apps/website/src/components/ActionCard.tsx b/apps/website/src/components/ActionCard.tsx index cd899c4e5..7cb220f4d 100644 --- a/apps/website/src/components/ActionCard.tsx +++ b/apps/website/src/components/ActionCard.tsx @@ -9,8 +9,8 @@ export type ActionCardProps = { export default function ActionCard({ title, description, buttonText }: ActionCardProps) { return ( - + {title} - - - {title} - - - - - {`${minRead} min read`} - - - + + + + + {title} + + + + + {`${minRead} min read`} + + + + ) } diff --git a/apps/website/src/components/ArticlesCarousel.tsx b/apps/website/src/components/ArticlesCarousel.tsx new file mode 100644 index 000000000..acddc278b --- /dev/null +++ b/apps/website/src/components/ArticlesCarousel.tsx @@ -0,0 +1,65 @@ +"use client" + +import { HStack, IconButton, Stack, useBreakpointValue } from "@chakra-ui/react" +import { useCallback, useEffect, useState } from "react" +import allArticles from "../data/articles.json" +import IconArrowLeft from "../icons/IconArrowLeft" +import IconArrowRight from "../icons/IconArrowRight" +import { circularSlice } from "../utils/circularSlice" +import ArticleCard from "./ArticleCard" + +export default function ArticlesCarousel() { + const variant = useBreakpointValue( + { + base: 3, + sm: 3, + md: 2, + lg: 4 + }, + { + fallback: "md" + } + ) + + const [articles, setArticles] = useState() + const [index, setIndex] = useState(0) + + useEffect(() => { + setArticles(circularSlice(allArticles, index, variant!)) + }, [index, variant]) + + const nextProject = useCallback(() => { + setIndex((i) => (i + 1) % allArticles.length) + }, [index]) + + const previousProject = useCallback(() => { + setIndex((i) => (i === 0 ? allArticles.length - 1 : i - 1)) + }, [index]) + + return ( + <> + + + } + /> + } + /> + + + + {articles && + articles.map((article, i) => ( + + ))} + + + ) +} diff --git a/apps/website/src/components/CodeBlock.tsx b/apps/website/src/components/CodeBlock.tsx new file mode 100644 index 000000000..bf4e71c1a --- /dev/null +++ b/apps/website/src/components/CodeBlock.tsx @@ -0,0 +1,51 @@ +"use client" + +import { Flex, useClipboard, Button } from "@chakra-ui/react" +import { CodeBlock, ocean } from "react-code-blocks" + +export type CodekBlockProps = { + text: string +} + +export default function CodekBlock({ text }: CodekBlockProps) { + const { onCopy, hasCopied } = useClipboard(text) + + const copyBlockProps = { + text, + language: "ts", + showLineNumbers: false, + wrapLines: true, + theme: ocean, + customStyle: { background: "transparent", overflow: "auto", paddingRight: "70px" } + } + return ( + + + + + ) +} diff --git a/apps/website/src/components/InfoCard.tsx b/apps/website/src/components/InfoCard.tsx new file mode 100644 index 000000000..c955ed919 --- /dev/null +++ b/apps/website/src/components/InfoCard.tsx @@ -0,0 +1,37 @@ +import { Heading, Text, Card, CardBody, VStack } from "@chakra-ui/react" + +export type InfoBlock = { + title: string + body: string +} + +export type InfoCardProps = { + texts: InfoBlock[] +} + +export default function InfoCard({ texts }: InfoCardProps) { + return ( + + + + {texts.map((text, i) => ( + + {text.title} + {text.body} + + ))} + + + + ) +} diff --git a/apps/website/src/components/SectionBlock.tsx b/apps/website/src/components/SectionBlock.tsx new file mode 100644 index 000000000..a6c8d24ee --- /dev/null +++ b/apps/website/src/components/SectionBlock.tsx @@ -0,0 +1,67 @@ +"use client" + +import { Text, VStack, Flex, Link } from "@chakra-ui/react" +import IconArrowRight from "@/icons/IconArrowRight" +import CodekBlock from "./CodeBlock" + +export type SectionItem = { + icon: any + heading: string + body: string +} + +export type SectionBlockProps = { + title: string + description: string + linkText: string + linkUrl: string + codeText: string + itemList: SectionItem[] +} + +export default function SectionBlock({ title, description, linkText, linkUrl, codeText, itemList }: SectionBlockProps) { + return ( + + + + {title} + + {description} + + + + {linkText} + + + + + + + + {itemList.map((item, i) => ( + + {item.icon} + {item.heading} + + {item.body} + + + ))} + + + + ) +} diff --git a/apps/website/src/components/VideoCard.tsx b/apps/website/src/components/VideoCard.tsx index 802ffac7e..bc8c36002 100644 --- a/apps/website/src/components/VideoCard.tsx +++ b/apps/website/src/components/VideoCard.tsx @@ -1,4 +1,4 @@ -import { Heading, Card, CardBody, AspectRatio, HStack } from "@chakra-ui/react" +import { Heading, Card, CardBody, AspectRatio, HStack, Link } from "@chakra-ui/react" export type VideoCardProps = { embeddedVideoUrl: string @@ -7,24 +7,26 @@ export type VideoCardProps = { export default function VideoCard({ embeddedVideoUrl, title }: VideoCardProps) { return ( - - - -