From f730dd7e87689edbbc276b1cf170cbc17e580a3c Mon Sep 17 00:00:00 2001 From: Mukul Kolpe Date: Thu, 12 Sep 2024 10:33:38 +0530 Subject: [PATCH] refactor: redesigned projects built with MACI page (#1813) --- .../src/components/ActionCard/index.tsx | 30 +++ .../components/ActionCard/styles.module.css | 96 +++++++++ .../src/components/ProjectCard/index.tsx | 74 +++++++ .../components/ProjectCard/styles.module.css | 77 +++++++ .../src/components/ProjectList/index.tsx | 197 ++++++++++++++++++ .../components/ProjectList/styles.module.css | 180 ++++++++++++++++ apps/website/src/content/projects.json | 159 ++++++++++++++ apps/website/src/icons/IconDiscord.tsx | 16 ++ apps/website/src/icons/IconGithub.tsx | 16 ++ apps/website/src/icons/IconWebsite.tsx | 16 ++ apps/website/src/pages/index.module.css | 70 +++++++ apps/website/src/pages/projects.md | 50 ----- apps/website/src/pages/projects.tsx | 41 ++++ apps/website/src/utils/getProjectsByFilter.ts | 40 ++++ 14 files changed, 1012 insertions(+), 50 deletions(-) create mode 100644 apps/website/src/components/ActionCard/index.tsx create mode 100644 apps/website/src/components/ActionCard/styles.module.css create mode 100644 apps/website/src/components/ProjectCard/index.tsx create mode 100644 apps/website/src/components/ProjectCard/styles.module.css create mode 100644 apps/website/src/components/ProjectList/index.tsx create mode 100644 apps/website/src/components/ProjectList/styles.module.css create mode 100644 apps/website/src/content/projects.json create mode 100644 apps/website/src/icons/IconDiscord.tsx create mode 100644 apps/website/src/icons/IconGithub.tsx create mode 100644 apps/website/src/icons/IconWebsite.tsx delete mode 100644 apps/website/src/pages/projects.md create mode 100644 apps/website/src/pages/projects.tsx create mode 100644 apps/website/src/utils/getProjectsByFilter.ts diff --git a/apps/website/src/components/ActionCard/index.tsx b/apps/website/src/components/ActionCard/index.tsx new file mode 100644 index 0000000000..8f4bd30f72 --- /dev/null +++ b/apps/website/src/components/ActionCard/index.tsx @@ -0,0 +1,30 @@ +import styles from "./styles.module.css"; + +interface ActionCardProps { + title: string; + description: string; + buttonText: string; + buttonUrl: string; +} + +const ActionCard: React.FC = ({ buttonText, buttonUrl, description, title }: ActionCardProps) => ( +
+
+
+
+

{title}

+ +

{description}

+
+ + +
+
+
+); + +export default ActionCard; diff --git a/apps/website/src/components/ActionCard/styles.module.css b/apps/website/src/components/ActionCard/styles.module.css new file mode 100644 index 0000000000..b91840698d --- /dev/null +++ b/apps/website/src/components/ActionCard/styles.module.css @@ -0,0 +1,96 @@ +.actionCard { + background-color: #1a202c; /* darkBlue */ + color: white; + border-radius: 24px; + width: 100%; + padding: 64px 32px; +} + +.actionCardBody { + padding: 0; +} + +.actionCardContent { + display: flex; + flex-direction: column; + justify-content: space-between; + align-items: center; + gap: 2rem; +} + +.actionCardText { + width: 100%; +} + +.actionCardTitle { + font-size: 30px; + line-height: 44px; + font-weight: normal; + color: white; +} + +.actionCardDescription { + margin-top: 1rem; + font-size: 16px; + line-height: 25px; + font-weight: normal; + color: #a0aec0; /* text.400 */ +} + +.actionCardButtonContainer { + width: 100%; +} + +.actionCardButton { + display: inline-block; + padding: 0.5rem 1rem; + background-color: #3182ce; /* primary */ + color: white; + text-decoration: none; + border-radius: 0.25rem; + font-size: 1rem; + line-height: 1.5; + text-align: center; +} + +@media (min-width: 768px) { + .actionCardContent { + flex-direction: row; + gap: 0; + } + + .actionCardText { + width: 522px; + } + + .actionCardTitle { + font-size: 40px; + } + + .actionCardDescription { + font-size: 20px; + line-height: 32px; + } + + .actionCardButtonContainer { + width: auto; + } + + .actionCardButton { + padding: 0.75rem 1.5rem; + font-size: 1.125rem; + border-radius: 30px; + } +} + +@media (min-width: 992px) { + .actionCard { + padding: 41px 80px; + } +} + +@media (min-width: 1280px) { + .actionCard { + width: 1110px; + } +} diff --git a/apps/website/src/components/ProjectCard/index.tsx b/apps/website/src/components/ProjectCard/index.tsx new file mode 100644 index 0000000000..dfd4c4259d --- /dev/null +++ b/apps/website/src/components/ProjectCard/index.tsx @@ -0,0 +1,74 @@ +import { useColorMode } from "@docusaurus/theme-common"; + +import IconDiscord from "../../icons/IconDiscord"; +import IconGithub from "../../icons/IconGithub"; +import IconWebsite from "../../icons/IconWebsite"; + +import styles from "./styles.module.css"; + +interface ProjectLinks { + website?: string; + github?: string; + discord?: string; +} + +interface ProjectCardProps { + name: string; + description: string; + hackathon?: string; + status?: string; + links: ProjectLinks; +} + +const ProjectCard: React.FC = ({ + description, + hackathon = "", + links, + name, + status = "", +}: ProjectCardProps) => { + const categories = hackathon ? [hackathon] : [status]; + const { colorMode } = useColorMode(); + + return ( +
+
+ {categories.map((category) => ( + + {category} + + ))} +
+ +
+

{name}

+ +

{description}

+
+ + {(links.website || links.github || links.discord) && ( +
+ {links.github && ( + + + + )} + + {links.website && ( + + + + )} + + {links.discord && ( + + + + )} +
+ )} +
+ ); +}; + +export default ProjectCard; diff --git a/apps/website/src/components/ProjectCard/styles.module.css b/apps/website/src/components/ProjectCard/styles.module.css new file mode 100644 index 0000000000..5e618f41fa --- /dev/null +++ b/apps/website/src/components/ProjectCard/styles.module.css @@ -0,0 +1,77 @@ +.card { + border-radius: 18px; + padding: 34px; + width: 100%; + height: 100%; + display: flex; + flex-direction: column; + transition: border-color 0.3s ease; +} + +.card.light { + background-color: var(--ifm-color-gray-100); + border: 1px solid var(--ifm-color-gray-300); + color: var(--ifm-color-gray-900); +} + +.card.dark { + background-color: var(--ifm-color-gray-900); + border: 1px solid var(--ifm-color-gray-800); + color: var(--ifm-color-gray-100); +} + +.card:hover { + border-color: var(--ifm-color-primary); +} + +.cardTags { + display: flex; + flex-wrap: wrap; + gap: 8px; + margin-bottom: 2rem; +} + +.tag { + border: 1px solid var(--ifm-color-gray-600); + border-radius: 9999px; + padding: 0.25rem 0.75rem; + font-size: 0.875rem; +} + +.cardBody { + flex-grow: 1; +} + +.cardTitle { + font-size: 24px; + line-height: 33px; + margin-bottom: 1rem; +} + +.cardDescription { + font-size: 14px; + line-height: 22.4px; +} + +.cardFooter { + display: flex; + gap: 1rem; + padding-top: 1rem; +} + +.cardFooter a { + color: inherit; + text-decoration: none; +} + +.cardFooter svg { + width: 24px; + height: 24px; +} + +@media (max-width: 768px) { + .cardFooter svg { + width: 16px; + height: 16px; + } +} diff --git a/apps/website/src/components/ProjectList/index.tsx b/apps/website/src/components/ProjectList/index.tsx new file mode 100644 index 0000000000..562714200c --- /dev/null +++ b/apps/website/src/components/ProjectList/index.tsx @@ -0,0 +1,197 @@ +import { useColorMode } from "@docusaurus/theme-common"; +import React, { useState, useEffect, useCallback } from "react"; + +import projects from "../../content/projects.json"; +import { getProjectsByFilter, getUniqueHackathons, getUniqueStatuses } from "../../utils/getProjectsByFilter"; +import ActionCard from "../ActionCard"; +import ProjectCard from "../ProjectCard"; + +import styles from "./styles.module.css"; + +interface Project { + name: string; + description: string; + hackathon: string | null; + status: string; + links: { + website?: string; + github?: string; + discord?: string; + }; +} + +const typedProjects = projects as unknown as Project[]; + +const sortedProjects = typedProjects.slice().sort((a, b) => a.name.localeCompare(b.name)); + +function chunkArray(array: Project[]): Project[][] { + const result = []; + for (let i = 0; i < array.length; i += 9) { + const chunk = array.slice(i, i + 9); + result.push(chunk); + } + return result.length === 0 ? [[]] : result; +} + +const typedGetProjectsByFilter = getProjectsByFilter as ( + projects: Project[], + filters: { hackathon: string; status: string }, +) => Project[]; +const typedGetUniqueHackathons = getUniqueHackathons as (projects: Project[]) => string[]; +const typedGetUniqueStatuses = getUniqueStatuses as (projects: Project[]) => string[]; + +const ProjectList: React.FC = () => { + const [filteredProjects, setFilteredProjects] = useState(chunkArray(sortedProjects)); + const [selectedHackathon, setSelectedHackathon] = useState(""); + const [selectedStatus, setSelectedStatus] = useState(""); + const [currentPage, setCurrentPage] = useState(0); + const { colorMode } = useColorMode(); + + const filterProjects = useCallback(() => { + const filtered = typedGetProjectsByFilter(sortedProjects, { + hackathon: selectedHackathon, + status: selectedStatus, + }); + setFilteredProjects(chunkArray(filtered)); + setCurrentPage(0); + }, [selectedHackathon, selectedStatus]); + + useEffect(() => { + filterProjects(); + }, [filterProjects]); + + const hackathons = typedGetUniqueHackathons(sortedProjects); + const statuses = typedGetUniqueStatuses(sortedProjects); + + return ( +
+
+
+

Status

+ +
+ + + {statuses.map((status) => ( + + ))} +
+
+ +
+

Hackathon

+ +
+ + + {hackathons.map((hackathon) => ( + + ))} +
+
+
+ +
+ {filteredProjects[currentPage]?.length > 0 ? ( + filteredProjects[currentPage].map((project) => ( + + )) + ) : ( +
+

+ No results found. +

+ +

No projects matching these filters. Try changing your search.

+
+ )} +
+ + {filteredProjects.length > 1 && ( +
+ { + setCurrentPage((prev) => Math.max(0, prev - 1)); + }} + > + ← + + + {filteredProjects.map((_, index) => ( + { + setCurrentPage(index); + }} + > + {index + 1} + + ))} + + { + setCurrentPage((prev) => Math.min(filteredProjects.length - 1, prev + 1)); + }} + > + → + +
+ )} + +
+ +
+
+ ); +}; + +export default ProjectList; diff --git a/apps/website/src/components/ProjectList/styles.module.css b/apps/website/src/components/ProjectList/styles.module.css new file mode 100644 index 0000000000..e758a0abf6 --- /dev/null +++ b/apps/website/src/components/ProjectList/styles.module.css @@ -0,0 +1,180 @@ +.projectList { + width: 100%; +} + +.projectList.light { + color: var(--ifm-color-gray-800); +} + +.projectList.dark { + color: var(--ifm-color-gray-300); +} + +.filters { + display: flex; + flex-direction: column; + gap: 1.5rem; + margin-bottom: 2rem; +} + +.filterGroup h3 { + font-size: 20px; + margin-bottom: 1rem; +} + +.filterOptions { + display: flex; + flex-wrap: wrap; + gap: 0.75rem; +} + +.filterOptions button { + padding: 0.5rem 1rem; + border: 1px solid var(--ifm-color-gray-600); + border-radius: 9999px; + background-color: transparent; + color: inherit; + cursor: pointer; + transition: all 0.3s ease; +} + +.filterOptions button:hover { + background-color: var(--ifm-color-gray-200); + color: var(--ifm-color-gray-900); +} + +.light .filterOptions button.active { + background-color: var(--ifm-color-primary); + border-color: var(--ifm-color-primary); + color: white; +} + +.dark .filterOptions button.active { + background-color: var(--ifm-color-primary-dark); + border-color: var(--ifm-color-primary-dark); + color: white; +} + +.projectsGrid { + display: grid; + grid-template-columns: repeat(3, 1fr); + gap: 1.5rem; + margin-bottom: 2rem; +} + +.noResults { + grid-column: 1 / -1; + text-align: center; + padding: 2rem; +} + +.pagination { + display: flex; + justify-content: center; + align-items: center; + margin-top: 2rem; + font-size: 1rem; +} + +.paginationNumber { + margin: 0 0.5rem; + cursor: pointer; + color: inherit; + transition: color 0.3s ease; +} + +.paginationNumber:hover { + color: var(--ifm-color-primary); +} + +.paginationNumber.active { + color: var(--ifm-color-primary); + font-weight: bold; +} + +.paginationArrow { + cursor: pointer; + margin: 0 1rem; + font-size: 1.4rem; + font-weight: bolder; + color: var(--ifm-color-primary); + transition: opacity 0.3s ease; +} + +.paginationArrow:hover { + opacity: 0.7; +} + +.paginationArrow.disabled { + opacity: 0.3; + cursor: not-allowed; +} + +.actionCardContainer { + display: flex; + justify-content: center; + margin-top: 128px; + margin-bottom: 128px; + width: 100%; +} + +.actionCard { + background-color: #f8f9fa; + border-radius: 8px; + padding: 2rem; + text-align: center; + box-shadow: 0 4px 6px rgba(0, 0, 0, 0.1); +} + +.actionCard h2 { + font-size: 24px; + margin-bottom: 1rem; + color: #333; +} + +.actionCard p { + font-size: 16px; + margin-bottom: 1.5rem; + color: #666; +} + +.actionButton { + display: inline-block; + background-color: #007bff; + color: white; + padding: 0.75rem 1.5rem; + border-radius: 9999px; + text-decoration: none; + font-weight: bold; + transition: background-color 0.3s ease; +} + +.actionButton:hover { + background-color: #0056b3; +} + +@media (max-width: 768px) { + .actionCard { + padding: 1.5rem; + } + + .actionCard h2 { + font-size: 20px; + } + + .actionCard p { + font-size: 14px; + } +} + +@media (max-width: 1024px) { + .projectsGrid { + grid-template-columns: repeat(2, 1fr); + } +} + +@media (max-width: 768px) { + .projectsGrid { + grid-template-columns: 1fr; + } +} diff --git a/apps/website/src/content/projects.json b/apps/website/src/content/projects.json new file mode 100644 index 0000000000..5e57d17e66 --- /dev/null +++ b/apps/website/src/content/projects.json @@ -0,0 +1,159 @@ +[ + { + "name": "clr.fund", + "description": "a protocol for efficiently allocating funds to public goods that benefit the Ethereum Network.", + "hackathon": null, + "status": "Production", + "links": { + "website": "https://clr.fund/", + "github": "https://github.com/clrfund/monorepo/", + "discord": "https://discord.com/invite/ZnsYPV6dCv" + } + }, + { + "name": "MACI_QF in Allo", + "description": "a MACI Quadratic Funding strategy to be used within Gitcoin's Allo Protocol.", + "hackathon": null, + "status": "Production", + "links": { + "github": "https://github.com/gitcoinco/MACI_QF" + } + }, + { + "name": "MACI Platform", + "description": "a platform that allows to run different types of vote and funding rounds using MACI.", + "hackathon": null, + "status": "Production", + "links": { + "github": "https://github.com/privacy-scaling-explorations/maci-platform", + "website": "https://maci-platform.vercel.app/" + } + }, + { + "name": "MACI Starter Kit", + "description": "a starter kit for quickly prototyping applications with MACI.", + "hackathon": null, + "status": "Utility", + "links": { + "github": "https://github.com/yashgo0018/maci-wrapper" + } + }, + { + "name": "Voto", + "description": "Voto is an on-chain censorship-resistant anonymous voting solution built on decentralised identities.", + "hackathon": "ETHBerlin 2024", + "status": "Hackathon", + "links": { + "website": "https://projects.ethberlin.org/submissions/334", + "github": "https://github.com/Vote-tech/voto-tech" + } + }, + { + "name": "0xFeedback", + "description": "A privacy preserving B2C Survey Platform.", + "hackathon": "ETHDam 2024", + "status": "Hackathon", + "links": { + "website": "https://taikai.network/cryptocanal/hackathons/ethdam2024/projects/cluyuw0j200y3yz01yjjd4rap/idea" + } + }, + { + "name": "StealthAlloc", + "description": "MACI as an Allo protocol Quadratic Funding Strategy for collusion-resistant, privacy-protecting allocations.", + "hackathon": "ETHDam 2024", + "status": "Hackathon", + "links": { + "website": "https://taikai.network/cryptocanal/hackathons/ethdam2024/projects/cluxse8cz00pjz3010wbq3thf/idea", + "github": "https://github.com/tse-lao/ethdam24" + } + }, + { + "name": "NEVO", + "description": "MACI's anti-collusion voting system with Near Protocol's Cross-Chain Signatures.", + "hackathon": "ETHDam 2024", + "status": "Hackathon", + "links": { + "website": "https://taikai.network/cryptocanal/hackathons/ethdam2024/projects/cluz9746q01c2z301tupriiuq/idea", + "github": "https://github.com/atahanyild/NEVO" + } + }, + { + "name": "EtherTale", + "description": "a decentralized, interactive fiction platform powered by AI.", + "hackathon": "ETHTaipei 2024", + "status": "Hackathon", + "links": { + "website": "https://taikai.network/ethtaipei/hackathons/hackathon-2024/projects/clu501yr50l1ey501e65jj8nr", + "github": "https://github.com/saurabhchalke/EtherTale" + } + }, + { + "name": "Demokratia", + "description": "Offers infrastructure to create and seamlessly integrate personalised AI agents for DAO voting.", + "hackathon": "ETHGlobal London 2024", + "status": "Hackathon", + "links": { + "website": "https://ethglobal.com/showcase/demokratia-c7bab", + "github": "https://github.com/ConfidentiDemokratia" + } + }, + { + "name": "SkaffoldMACI+ZkOSIOS", + "description": "User-friendly voting system UI (powered by Skaffold) for sports fans using MACI for enhanced security and privacy.", + "hackathon": "ETHGlobal London 2024", + "status": "Hackathon", + "links": { + "website": "https://ethglobal.com/showcase/skaffoldmaci-zkosios-2no6q", + "github": "https://github.com/GaetanoMondelli/ETH-GLOBAL-LONDON" + } + }, + { + "name": "Vota Protocol", + "description": "a privacy-preserved fair and trustless election Voting System on Blockchain programmable for global constituencies.", + "hackathon": "ETHGlobal London 2024", + "status": "Hackathon", + "links": { + "website": "https://ethglobal.com/showcase/vota-protocol-qxf3z", + "github": "https://github.com/Vota-Protocol" + } + }, + { + "name": "Votelik Pollerin", + "description": "Sybil- and bribery-resistant polling on Farcaster, utilizing World ID and MACI.", + "hackathon": "ETHGlobal London 2024", + "status": "Hackathon", + "links": { + "website": "https://ethglobal.com/showcase/votelik-pollerin-u0dcs", + "github": "https://github.com/pauldev20/farcaster-frames-polls" + } + }, + { + "name": "Anon Vote", + "description": "an on-chain anonymous voting solution with guaranteed proof of personhood.", + "hackathon": "ETHGlobal London 2024", + "status": "Hackathon", + "links": { + "website": "https://ethglobal.com/showcase/anonvote-uc7w0", + "github": "https://github.com/gasperpre/veripoll" + } + }, + { + "name": "MACI Subgraph", + "description": "a subgraph to index data from MACI protocol to serve as data layer for frontend integration", + "hackathon": "ETHGlobal Circuit Breaker 2024", + "status": "Hackathon", + "links": { + "website": "https://ethglobal.com/showcase/maci-subgraph-89h7c" + } + }, + { + "name": "Quadratic Dollar Homepage", + "description": "The Quadratic Dollar Homepage is a spin on the Million Dollar Homepage.", + "hackathon": null, + "status": "Unmaintained", + "links": { + "github": "https://github.com/privacy-scaling-explorations/qdh", + "website": "https://quadratic.page/" + } + } +] diff --git a/apps/website/src/icons/IconDiscord.tsx b/apps/website/src/icons/IconDiscord.tsx new file mode 100644 index 0000000000..c823855fe9 --- /dev/null +++ b/apps/website/src/icons/IconDiscord.tsx @@ -0,0 +1,16 @@ +import React from "react"; + +interface IconDiscordProps extends React.SVGProps { + size?: number; +} + +const IconDiscord: React.FC = ({ size = 24, ...props }) => ( + + + +); + +export default IconDiscord; diff --git a/apps/website/src/icons/IconGithub.tsx b/apps/website/src/icons/IconGithub.tsx new file mode 100644 index 0000000000..208ec7c1ad --- /dev/null +++ b/apps/website/src/icons/IconGithub.tsx @@ -0,0 +1,16 @@ +import React from "react"; + +interface IconGithubProps extends React.SVGProps { + size?: number; +} + +const IconGithub: React.FC = ({ size = 22, ...props }) => ( + + + +); + +export default IconGithub; diff --git a/apps/website/src/icons/IconWebsite.tsx b/apps/website/src/icons/IconWebsite.tsx new file mode 100644 index 0000000000..2f3bdf0659 --- /dev/null +++ b/apps/website/src/icons/IconWebsite.tsx @@ -0,0 +1,16 @@ +import React from "react"; + +interface IconWebsiteProps extends React.SVGProps { + size?: number; +} + +const IconWebsite: React.FC = ({ size = 18, ...props }) => ( + + + +); + +export default IconWebsite; diff --git a/apps/website/src/pages/index.module.css b/apps/website/src/pages/index.module.css index 037695e49a..79c945faac 100644 --- a/apps/website/src/pages/index.module.css +++ b/apps/website/src/pages/index.module.css @@ -60,6 +60,76 @@ margin-bottom: 1rem; } +.projectsPage { + width: 100%; +} + +.heroSection { + padding-top: 170px; + padding-bottom: 56px; + width: 100%; + position: relative; + display: flex; + justify-content: flex-end; + align-items: flex-start; +} + +.backgroundImage { + position: absolute; + top: 0; + left: 50%; + transform: translateX(-50%); + width: 100vw; + height: 100%; + overflow: hidden; + z-index: -1; +} + +.backgroundImage img { + width: 100%; + height: 100%; + object-fit: cover; +} + +.heroContent { + padding-bottom: 64px; +} + +.heroContent h1 { + font-size: 72px; + margin-bottom: 16px; +} + +.heroContent p { + font-size: 20px; +} + +.actionSection { + margin-top: 128px; + margin-bottom: 128px; +} + +/* Responsive styles */ +@media (max-width: 768px) { + .heroContent h1 { + font-size: 46px; + } + + .heroContent p { + font-size: 18px; + } +} + +@media (max-width: 480px) { + .heroContent h1 { + font-size: 40px; + } + + .heroContent p { + font-size: 16px; + } +} + @media screen and (max-width: 996px) { .heroBanner { padding: 1.5rem; diff --git a/apps/website/src/pages/projects.md b/apps/website/src/pages/projects.md deleted file mode 100644 index bba65ab9fd..0000000000 --- a/apps/website/src/pages/projects.md +++ /dev/null @@ -1,50 +0,0 @@ ---- -title: Projects built with MACI -description: A list of projects built with the MACI protocol ---- - -# Projects built with MACI - -Please find below a list of all projects built with MACI. If you have a project that is not listed, please reach out to our team in the [PSE Discord](https://discord.com/invite/sF5CT5rzrR) (`#maci` channel), or [create an issue on our GitHub](https://github.com/privacy-scaling-explorations/maci/issues). - -## Production - -- [clr.fund](https://clr.fund): a protocol for efficiently allocating funds to public goods that benefit the Ethereum Network -- [MACI_QF in Allo](https://github.com/gitcoinco/MACI_QF): a MACI Quadratic Funding strategy to be used within Gitcoin's Allo Protocol -- [MACI-RPGF](https://github.com/privacy-scaling-explorations/maci-rpgf): a fork from Easy-RPGF that enables any community to easily run an RPGF round using MACI - -## Utilities - -- [MACI Starter Kit](https://github.com/yashgo0018/maci-wrapper): a starter kit for quickly prototyping applications with MACI - -## Hackathons - -### ETHBerlin 2024 - -- [Voto](https://projects.ethberlin.org/submissions/334) Voto is an on-chain censorship-resistant anonymous voting solution built on decentralised identities. - -### ETHDam 2024 - -- [0xFeedback](https://taikai.network/cryptocanal/hackathons/ethdam2024/projects/cluyuw0j200y3yz01yjjd4rap/idea) A privacy preserving B2C Survey Platform -- [StealthAlloc](https://taikai.network/cryptocanal/hackathons/ethdam2024/projects/cluxse8cz00pjz3010wbq3thf/idea) MACI as an Allo protocol Quadratic Funding Strategy for collusion-resistant, privacy-protecting allocations -- [NEVO](https://taikai.network/cryptocanal/hackathons/ethdam2024/projects/cluz9746q01c2z301tupriiuq/idea) MACI's anti-collusion voting system with Near Protocol's Cross-Chain Signatures - -### ETHTaipei 2024 - -- [EtherTale](https://taikai.network/ethtaipei/hackathons/hackathon-2024/projects/clu501yr50l1ey501e65jj8nr) - -### ETHGlobal London 2024 - -- [Demokratia](https://ethglobal.com/showcase/demokratia-c7bab) -- [SkaffoldMACI+ZkOSIOS](https://ethglobal.com/showcase/skaffoldmaci-zkosios-2no6q) -- [Vota Protocol](https://ethglobal.com/showcase/vota-protocol-qxf3z) -- [Votelik Pollerin](https://ethglobal.com/showcase/votelik-pollerin-u0dcs) -- [Anon Vote](https://ethglobal.com/showcase/anonvote-uc7w0) - -### ETHGlobal Circuit Breaker 2024 - -- [MACI Subgraph](https://ethglobal.com/showcase/maci-subgraph-89h7c) - -## Unmaintained - -- [Quadratic Dollar Homepage](https://github.com/privacy-scaling-explorations/qdh) diff --git a/apps/website/src/pages/projects.tsx b/apps/website/src/pages/projects.tsx new file mode 100644 index 0000000000..9e1b7ca3d9 --- /dev/null +++ b/apps/website/src/pages/projects.tsx @@ -0,0 +1,41 @@ +import Layout from "@theme/Layout"; +import clsx from "clsx"; + +import ProjectsList from "../components/ProjectList"; + +import styles from "./index.module.css"; + +interface ProjectspageHeaderProps { + tagline: string; + title: string; +} + +const ProjectspageHeader = ({ tagline, title }: ProjectspageHeaderProps) => ( +
+
{title}
+ +
+ {tagline} + + . +
+
+); + +const Projects: React.FC = () => ( + +
+ +
+ +
+
+
+ +
+
+
+
+); + +export default Projects; diff --git a/apps/website/src/utils/getProjectsByFilter.ts b/apps/website/src/utils/getProjectsByFilter.ts new file mode 100644 index 0000000000..9ba1fedfbd --- /dev/null +++ b/apps/website/src/utils/getProjectsByFilter.ts @@ -0,0 +1,40 @@ +import type Projects from "../content/projects.json"; + +/** + * Filters projects based on hackathon and status criteria. + * @param projects An array of objects where each object represents a project. + * @param filter An object containing optional hackathon and status filter criteria. + * @returns An array of projects that match the given filter criteria. + */ +export function getProjectsByFilter( + projects: typeof Projects, + filter: { hackathon?: string; status?: string }, +): typeof Projects { + return projects.filter((project) => { + const hackathonMatch = !filter.hackathon || project.hackathon === filter.hackathon; + const statusMatch = !filter.status || project.status === filter.status; + return hackathonMatch && statusMatch; + }); +} + +/** + * Extracts unique hackathons from the projects list. + * @param projects An array of objects where each object represents a project. + * @returns An array of strings, where each string is a unique hackathon from across all projects. + */ +export function getUniqueHackathons(projects: typeof Projects): string[] { + const hackathons = projects + .map((project) => project.hackathon) + .filter((hackathon): hackathon is string => hackathon !== null && hackathon !== ""); + return Array.from(new Set(hackathons)); +} + +/** + * Extracts unique statuses from the projects list. + * @param projects An array of objects where each object represents a project. + * @returns An array of strings, where each string is a unique status from across all projects. + */ +export function getUniqueStatuses(projects: typeof Projects): string[] { + const statuses = projects.map((project) => project.status); + return Array.from(new Set(statuses)); +}