mirror of
https://github.com/tlsnotary/website.git
synced 2026-01-07 21:24:15 -05:00
copy projects page from maci project
This commit is contained in:
@@ -100,8 +100,11 @@ const config: Config = {
|
||||
position: 'left',
|
||||
label: 'Documentation',
|
||||
},
|
||||
|
||||
{ to: '/docs/faq', label: 'FAQ', position: 'left' },
|
||||
|
||||
{ to: '/projects', label: 'Projects', position: 'left' },
|
||||
|
||||
{ href: 'https://tlsnotary.github.io/tlsn/tlsn_prover/', label: 'API', position: 'left' },
|
||||
{ to: '/blog', label: 'Blog', position: 'left' },
|
||||
{
|
||||
|
||||
30
src/components/ActionCard/index.tsx
Normal file
30
src/components/ActionCard/index.tsx
Normal file
@@ -0,0 +1,30 @@
|
||||
import styles from "./styles.module.css";
|
||||
|
||||
interface ActionCardProps {
|
||||
title: string;
|
||||
description: string;
|
||||
buttonText: string;
|
||||
buttonUrl: string;
|
||||
}
|
||||
|
||||
const ActionCard: React.FC<ActionCardProps> = ({ buttonText, buttonUrl, description, title }: ActionCardProps) => (
|
||||
<div className={styles.actionCard}>
|
||||
<div className={styles.actionCardBody}>
|
||||
<div className={styles.actionCardContent}>
|
||||
<div className={styles.actionCardText}>
|
||||
<h2 className={styles.actionCardTitle}>{title}</h2>
|
||||
|
||||
<p className={styles.actionCardDescription}>{description}</p>
|
||||
</div>
|
||||
|
||||
<div className={styles.actionCardButtonContainer}>
|
||||
<a className={styles.actionCardButton} href={buttonUrl} rel="noopener noreferrer" target="_blank">
|
||||
{buttonText}
|
||||
</a>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
|
||||
export default ActionCard;
|
||||
96
src/components/ActionCard/styles.module.css
Normal file
96
src/components/ActionCard/styles.module.css
Normal file
@@ -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;
|
||||
}
|
||||
}
|
||||
74
src/components/ProjectCard/index.tsx
Normal file
74
src/components/ProjectCard/index.tsx
Normal file
@@ -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<ProjectCardProps> = ({
|
||||
description,
|
||||
hackathon = "",
|
||||
links,
|
||||
name,
|
||||
status = "",
|
||||
}: ProjectCardProps) => {
|
||||
const categories = hackathon ? [hackathon] : [status];
|
||||
const { colorMode } = useColorMode();
|
||||
|
||||
return (
|
||||
<div className={`${styles.card} ${styles[colorMode]}`}>
|
||||
<div className={styles.cardTags}>
|
||||
{categories.map((category) => (
|
||||
<span key={category} className={styles.tag}>
|
||||
{category}
|
||||
</span>
|
||||
))}
|
||||
</div>
|
||||
|
||||
<div className={styles.cardBody}>
|
||||
<h2 className={styles.cardTitle}>{name}</h2>
|
||||
|
||||
<p className={styles.cardDescription}>{description}</p>
|
||||
</div>
|
||||
|
||||
{(links.website || links.github || links.discord) && (
|
||||
<div className={styles.cardFooter}>
|
||||
{links.github && (
|
||||
<a aria-label="GitHub" href={links.github} rel="noopener noreferrer" target="_blank">
|
||||
<IconGithub />
|
||||
</a>
|
||||
)}
|
||||
|
||||
{links.website && (
|
||||
<a aria-label="Website" href={links.website} rel="noopener noreferrer" target="_blank">
|
||||
<IconWebsite />
|
||||
</a>
|
||||
)}
|
||||
|
||||
{links.discord && (
|
||||
<a aria-label="Discord" href={links.discord} rel="noopener noreferrer" target="_blank">
|
||||
<IconDiscord />
|
||||
</a>
|
||||
)}
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
export default ProjectCard;
|
||||
77
src/components/ProjectCard/styles.module.css
Normal file
77
src/components/ProjectCard/styles.module.css
Normal file
@@ -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;
|
||||
}
|
||||
}
|
||||
218
src/components/ProjectList/index.tsx
Normal file
218
src/components/ProjectList/index.tsx
Normal file
@@ -0,0 +1,218 @@
|
||||
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<Project[][]>(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 (
|
||||
<div className={`${styles.projectList} ${styles[colorMode]}`}>
|
||||
<div className={styles.filters}>
|
||||
<div className={styles.filterGroup}>
|
||||
<h3>Status</h3>
|
||||
|
||||
<div className={styles.filterOptions}>
|
||||
<button
|
||||
className={selectedStatus === "" ? styles.active : ""}
|
||||
type="button"
|
||||
onClick={() => {
|
||||
setSelectedStatus("");
|
||||
}}
|
||||
>
|
||||
All
|
||||
</button>
|
||||
|
||||
{statuses.map((status) => (
|
||||
<button
|
||||
key={status}
|
||||
className={selectedStatus === status ? styles.active : ""}
|
||||
type="button"
|
||||
onClick={() => {
|
||||
setSelectedStatus(status);
|
||||
}}
|
||||
>
|
||||
{status}
|
||||
</button>
|
||||
))}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div className={styles.filterGroup}>
|
||||
<h3>Hackathon</h3>
|
||||
|
||||
<div className={styles.filterOptions}>
|
||||
<button
|
||||
className={selectedHackathon === "" ? styles.active : ""}
|
||||
type="button"
|
||||
onClick={() => {
|
||||
setSelectedHackathon("");
|
||||
}}
|
||||
>
|
||||
All
|
||||
</button>
|
||||
|
||||
{hackathons.map((hackathon) => (
|
||||
<button
|
||||
key={hackathon}
|
||||
className={selectedHackathon === hackathon ? styles.active : ""}
|
||||
type="button"
|
||||
onClick={() => {
|
||||
setSelectedHackathon(hackathon);
|
||||
}}
|
||||
>
|
||||
{hackathon}
|
||||
</button>
|
||||
))}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div className={styles.projectsGrid}>
|
||||
{filteredProjects[currentPage]?.length > 0 ? (
|
||||
filteredProjects[currentPage].map((project) => (
|
||||
<ProjectCard
|
||||
key={project.name}
|
||||
description={project.description}
|
||||
hackathon={project.hackathon || undefined}
|
||||
links={project.links}
|
||||
name={project.name}
|
||||
status={project.status}
|
||||
/>
|
||||
))
|
||||
) : (
|
||||
<div className={styles.noResults}>
|
||||
<p>
|
||||
<strong>No results found.</strong>
|
||||
</p>
|
||||
|
||||
<p>No projects matching these filters. Try changing your search.</p>
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
|
||||
{filteredProjects.length > 1 && (
|
||||
<div className={styles.pagination}>
|
||||
<span
|
||||
className={`${styles.paginationArrow} ${currentPage === 0 ? styles.disabled : ""}`}
|
||||
role="button"
|
||||
tabIndex={0}
|
||||
onClick={() => {
|
||||
setCurrentPage((prev) => Math.max(0, prev - 1));
|
||||
}}
|
||||
onKeyDown={(e) => {
|
||||
if (e.key === "Enter" || e.key === " ") {
|
||||
setCurrentPage((prev) => Math.max(0, prev - 1));
|
||||
}
|
||||
}}
|
||||
>
|
||||
←
|
||||
</span>
|
||||
|
||||
{filteredProjects.map((_, index) => (
|
||||
<span
|
||||
key={`page-${String(index)}`}
|
||||
className={`${styles.paginationNumber} ${currentPage === index ? styles.active : ""}`}
|
||||
role="button"
|
||||
tabIndex={0}
|
||||
onClick={() => {
|
||||
setCurrentPage(index);
|
||||
}}
|
||||
onKeyDown={(e) => {
|
||||
if (e.key === "Enter" || e.key === " ") {
|
||||
setCurrentPage((prev) => Math.max(0, prev - 1));
|
||||
}
|
||||
}}
|
||||
>
|
||||
{index + 1}
|
||||
</span>
|
||||
))}
|
||||
|
||||
<span
|
||||
className={`${styles.paginationArrow} ${currentPage === filteredProjects.length - 1 ? styles.disabled : ""}`}
|
||||
role="button"
|
||||
tabIndex={0}
|
||||
onClick={() => {
|
||||
setCurrentPage((prev) => Math.min(filteredProjects.length - 1, prev + 1));
|
||||
}}
|
||||
onKeyDown={(e) => {
|
||||
if (e.key === "Enter" || e.key === " ") {
|
||||
setCurrentPage((prev) => Math.min(filteredProjects.length - 1, prev + 1));
|
||||
}
|
||||
}}
|
||||
>
|
||||
→
|
||||
</span>
|
||||
</div>
|
||||
)}
|
||||
|
||||
<div className={styles.actionCardContainer}>
|
||||
<ActionCard
|
||||
buttonText="Submit your project"
|
||||
buttonUrl="https://github.com/privacy-scaling-explorations/maci/issues/new?title=Add+a+Project+to+the+Projects+Showcase"
|
||||
description="We are missing your project! Add your project to this page and show your awesomeness to the world."
|
||||
title="Show what you have built"
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
export default ProjectList;
|
||||
180
src/components/ProjectList/styles.module.css
Normal file
180
src/components/ProjectList/styles.module.css
Normal file
@@ -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;
|
||||
}
|
||||
}
|
||||
294
src/content/projects.json
Normal file
294
src/content/projects.json
Normal file
@@ -0,0 +1,294 @@
|
||||
[
|
||||
{
|
||||
"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": "DoraHacks Light-Weight MACI",
|
||||
"description": "DoraHacks MACI is a light-weight implementation of MACI.",
|
||||
"hackathon": null,
|
||||
"status": "Production",
|
||||
"links": {
|
||||
"github": "https://github.com/dorahacksglobal/qf-maci",
|
||||
"website": "https://dorahacks.io/blog/guides/maci-qv-guide/"
|
||||
}
|
||||
},
|
||||
{
|
||||
"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/"
|
||||
}
|
||||
},
|
||||
{
|
||||
"name": "PriVote",
|
||||
"description": "An open-source, decentralized voting platform that uses MACI for privacy and offers various authentication methods for secure, private polling.",
|
||||
"hackathon": "ETHGlobal Singapore 2024",
|
||||
"status": "Hackathon",
|
||||
"links": {
|
||||
"github": "https://github.com/Privote-project"
|
||||
}
|
||||
},
|
||||
{
|
||||
"name": "PolyVote",
|
||||
"description": "An end-to-end private voting service using MPC, ZK and AI to recommend and process votes.",
|
||||
"hackathon": "ETHGlobal Singapore 2024",
|
||||
"status": "Hackathon",
|
||||
"links": {
|
||||
"github": "https://github.com/ElectionZap/eth-singapore"
|
||||
}
|
||||
},
|
||||
{
|
||||
"name": "ChillVille",
|
||||
"description": "A private onchain voting system with sybil resistance by combining MACI and zkTLS.",
|
||||
"hackathon": "ETHGlobal Singapore 2024",
|
||||
"status": "Hackathon",
|
||||
"links": {
|
||||
"website": "https://eth-sg.vercel.app/",
|
||||
"github": "https://github.com/wasabijiro/ChillVille"
|
||||
}
|
||||
},
|
||||
{
|
||||
"name": "MACPI",
|
||||
"description": "A reliable DAO voting infrastructure for next billion users",
|
||||
"hackathon": "ETHGlobal Singapore 2024",
|
||||
"status": "Hackathon",
|
||||
"links": {
|
||||
"website": "https://maci-tap.vercel.app/",
|
||||
"github": "https://github.com/0xhardman/tap-maci"
|
||||
}
|
||||
},
|
||||
{
|
||||
"name": "Silent Wars",
|
||||
"description": "A telegram mini-app guild based game with resource collection and voting.",
|
||||
"hackathon": "ETHGlobal Singapore 2024",
|
||||
"status": "Hackathon",
|
||||
"links": {
|
||||
"website": "https://2024-eth-singapore-5k28.vercel.app/",
|
||||
"github": "https://github.com/taijusanagi/2024-eth-singapore"
|
||||
}
|
||||
},
|
||||
{
|
||||
"name": "Judg3",
|
||||
"description": "A next-generation voting system that leverages the power of consensus over your conscious mind.",
|
||||
"hackathon": "ETHGlobal Singapore 2024",
|
||||
"status": "Hackathon",
|
||||
"links": {
|
||||
"github": "https://github.com/team-somehow/judg3"
|
||||
}
|
||||
},
|
||||
{
|
||||
"name": "EZKPoll",
|
||||
"description": "A polling system utilizing MACI to empower ZK on polling and A/B testing.",
|
||||
"hackathon": "ETHGlobal Singapore 2024",
|
||||
"status": "Hackathon",
|
||||
"links": {
|
||||
"website": "https://ezkpoll.onrender.com/",
|
||||
"github": "https://github.com/abcd5251/EzkPoll"
|
||||
}
|
||||
},
|
||||
{
|
||||
"name": "Geist",
|
||||
"description": "A Decentralized Autonomous Website (DAW) builder that enables censorship-resistant, trustless deployments and governance for DAOs through private previews, zk-proof-based deployments, and anti-collusion voting.",
|
||||
"hackathon": "ETHGlobal Singapore 2024",
|
||||
"status": "Hackathon",
|
||||
"links": {
|
||||
"website": "https://gateway.geist.network/",
|
||||
"github": "https://github.com/debuggingfuture/geist"
|
||||
}
|
||||
},
|
||||
{
|
||||
"name": "Sniper",
|
||||
"description": "An AI companion designed to improve personal productivity and growth.",
|
||||
"hackathon": "ETHGlobal Singapore 2024",
|
||||
"status": "Hackathon",
|
||||
"links": {
|
||||
"github": "https://github.com/MusubiLabs/sniper"
|
||||
}
|
||||
},
|
||||
{
|
||||
"name": "zkElect",
|
||||
"description": "A blockchain-based platform using zk-SNARKs and MACI for secure, anonymous, and transparent voting, preventing tampering, coercion, and bribery.",
|
||||
"hackathon": "ETHGlobal Singapore 2024",
|
||||
"status": "Hackathon",
|
||||
"links": {
|
||||
"github": "https://github.com/anvats/zkElect"
|
||||
}
|
||||
},
|
||||
{
|
||||
"name": "clr.wiki",
|
||||
"description": "Collusion-resistant information dispute resolution tool using MACI.",
|
||||
"hackathon": "ETHGlobal Singapore 2024",
|
||||
"status": "Hackathon",
|
||||
"links": {
|
||||
"website": "https://www.cl3r.wiki/",
|
||||
"github": "https://github.com/seigneur/clr-wiki"
|
||||
}
|
||||
},
|
||||
{
|
||||
"name": "DAOTown",
|
||||
"description": "The project streamlines DAO activities, governance, and tokenomics with decentralized infrastructure.",
|
||||
"hackathon": "ETHGlobal Singapore 2024",
|
||||
"status": "Hackathon",
|
||||
"links": {
|
||||
"website": "https://eth-global-sg.vercel.app/",
|
||||
"github": "https://github.com/MukulKolpe/ETHGlobalSG"
|
||||
}
|
||||
},
|
||||
{
|
||||
"name": "Pixel Game Assistant",
|
||||
"description": "PGA is a tool that helps Pixel Game players to give community feedback on game features.",
|
||||
"hackathon": null,
|
||||
"status": "Production",
|
||||
"links": {
|
||||
"website": "https://voting.guildpal.work/",
|
||||
"github": null
|
||||
}
|
||||
}
|
||||
]
|
||||
130
src/css/card.module.css
Normal file
130
src/css/card.module.css
Normal file
@@ -0,0 +1,130 @@
|
||||
/* card.module.css */
|
||||
|
||||
.blogContainer {
|
||||
display: grid;
|
||||
justify-content: center;
|
||||
padding: 20px;
|
||||
background-color: #f9f9f9;
|
||||
}
|
||||
|
||||
.blogList {
|
||||
display: grid;
|
||||
grid-template-columns: repeat(auto-fit, minmax(300px, 1fr));
|
||||
gap: 20px;
|
||||
max-width: 1200px;
|
||||
width: 100%;
|
||||
}
|
||||
|
||||
.blogCard {
|
||||
background-color: #ffffff;
|
||||
border: 1px solid #e0e0e0;
|
||||
border-radius: 8px;
|
||||
box-shadow: 0 2px 4px rgba(0, 0, 0, 0.1);
|
||||
padding: 20px;
|
||||
text-decoration: none;
|
||||
color: #333;
|
||||
transition:
|
||||
transform 0.2s ease-in-out,
|
||||
box-shadow 0.2s ease-in-out;
|
||||
}
|
||||
|
||||
.blogCard:hover {
|
||||
transform: translateY(-5px);
|
||||
box-shadow: 0 4px 8px rgba(0, 0, 0, 0.1);
|
||||
text-decoration: none; /* Ensure no underline on hover */
|
||||
}
|
||||
|
||||
.blogTitle,
|
||||
.blogDescription,
|
||||
.blogDate,
|
||||
.blogAuthors,
|
||||
.blogExcerpt,
|
||||
.blogReadMoreButton {
|
||||
text-decoration: none; /* Remove underline from text elements */
|
||||
}
|
||||
|
||||
.blogTitle {
|
||||
font-size: 24px;
|
||||
font-weight: bold;
|
||||
margin-bottom: 10px;
|
||||
color: #0a2540;
|
||||
max-height: 100px;
|
||||
overflow: hidden;
|
||||
text-overflow: ellipsis;
|
||||
white-space: nowrap;
|
||||
}
|
||||
|
||||
.blogDescription {
|
||||
font-size: 16px;
|
||||
margin-bottom: 10px;
|
||||
color: #666666;
|
||||
max-height: 100px;
|
||||
overflow: hidden;
|
||||
text-overflow: ellipsis;
|
||||
white-space: nowrap;
|
||||
}
|
||||
|
||||
.blogDate {
|
||||
font-size: 14px;
|
||||
color: #999999;
|
||||
margin-bottom: 10px;
|
||||
}
|
||||
|
||||
.blogAuthors {
|
||||
font-size: 14px;
|
||||
color: #555555;
|
||||
}
|
||||
|
||||
.blogReadMoreButton {
|
||||
display: inline-block;
|
||||
margin-top: 10px;
|
||||
padding: 8px 16px;
|
||||
background-color: #0a2540;
|
||||
color: #ffffff;
|
||||
border-radius: 4px;
|
||||
text-decoration: none;
|
||||
transition: background-color 0.2s ease-in-out;
|
||||
}
|
||||
|
||||
.blogReadMoreButton:hover {
|
||||
background-color: #0c3c60;
|
||||
}
|
||||
|
||||
.blogExcerpt {
|
||||
font-size: 16px;
|
||||
color: #555555;
|
||||
margin-bottom: 10px;
|
||||
-webkit-line-clamp: 2;
|
||||
line-clamp: 2;
|
||||
-webkit-box-orient: vertical;
|
||||
display: -webkit-box;
|
||||
overflow: hidden;
|
||||
}
|
||||
|
||||
[data-theme="dark"] .blogContainer {
|
||||
background-color: black;
|
||||
}
|
||||
|
||||
[data-theme="dark"] .blogCard {
|
||||
background-color: #212121;
|
||||
border: 1px solid #888888;
|
||||
}
|
||||
|
||||
[data-theme="dark"] .blogTitle,
|
||||
.blogReadMoreButton {
|
||||
color: #ffffff;
|
||||
}
|
||||
|
||||
[data-theme="dark"] .blogDescription {
|
||||
color: #cdcdcd;
|
||||
}
|
||||
|
||||
[data-theme="dark"] .blogDate,
|
||||
.blogAuthors,
|
||||
.blogExcerpt {
|
||||
color: #888888;
|
||||
}
|
||||
|
||||
[data-theme="dark"] .blogReadMoreButton {
|
||||
background-color: #1b2b50;
|
||||
}
|
||||
26
src/icons/IconChrome.tsx
Normal file
26
src/icons/IconChrome.tsx
Normal file
@@ -0,0 +1,26 @@
|
||||
import React from "react";
|
||||
|
||||
interface IconChromeProps extends React.SVGProps<SVGSVGElement> {
|
||||
size?: number;
|
||||
}
|
||||
|
||||
const IconChrome: React.FC<IconChromeProps> = ({ size = 18, ...props }) => (
|
||||
<svg xmlns="http://www.w3.org/2000/svg" width="32" height="32" viewBox="0 0 48 48" {...props}>
|
||||
<path
|
||||
fillRule="evenodd"
|
||||
clipRule="evenodd"
|
||||
fill="currentColor"
|
||||
d="M44,24c0,11.044-8.956,20-20,20S4,35.044,4,24S12.956,4,24,4S44,12.956,44,24z"></path>
|
||||
<path fill="#ffc107" d="M24,4v20l8,4l-8.843,16c0.317,0,0.526,0,0.843,0c11.053,0,20-8.947,20-20S35.053,4,24,4z"></path>
|
||||
<path fill="#4caf50" d="M44,24c0,11.044-8.956,20-20,20S4,35.044,4,24S12.956,4,24,4S44,12.956,44,24z"></path>
|
||||
<path fill="#ffc107" d="M24,4v20l8,4l-8.843,16c0.317,0,0.526,0,0.843,0c11.053,0,20-8.947,20-20S35.053,4,24,4z"></path>
|
||||
<path fill="#f44336" d="M41.84,15H24v13l-3-1L7.16,13.26H7.14C10.68,7.69,16.91,4,24,4C31.8,4,38.55,8.48,41.84,15z"></path>
|
||||
<path fill="#dd2c00" d="M7.158,13.264l8.843,14.862L21,27L7.158,13.264z"></path>
|
||||
<path fill="#558b2f" d="M23.157,44l8.934-16.059L28,25L23.157,44z"></path>
|
||||
<path fill="#f9a825" d="M41.865,15H24l-1.579,4.58L41.865,15z"></path>
|
||||
<path fill="#fff" d="M33,24c0,4.969-4.031,9-9,9s-9-4.031-9-9s4.031-9,9-9S33,19.031,33,24z"></path>
|
||||
<path fill="#2196f3" d="M31,24c0,3.867-3.133,7-7,7s-7-3.133-7-7s3.133-7,7-7S31,20.133,31,24z"></path>
|
||||
</svg>
|
||||
);
|
||||
|
||||
export default IconChrome;
|
||||
16
src/icons/IconDiscord.tsx
Normal file
16
src/icons/IconDiscord.tsx
Normal file
@@ -0,0 +1,16 @@
|
||||
import React from "react";
|
||||
|
||||
interface IconDiscordProps extends React.SVGProps<SVGSVGElement> {
|
||||
size?: number;
|
||||
}
|
||||
|
||||
const IconDiscord: React.FC<IconDiscordProps> = ({ size = 24, ...props }) => (
|
||||
<svg fill="none" height={size} viewBox="0 0 24 19" width={size} xmlns="http://www.w3.org/2000/svg" {...props}>
|
||||
<path
|
||||
d="M20.242 1.68563C18.7262 0.990122 17.1007 0.4777 15.4012 0.184215C15.3703 0.17855 15.3393 0.192705 15.3234 0.221016C15.1144 0.592826 14.8828 1.07788 14.7206 1.45913C12.8927 1.18547 11.0741 1.18547 9.28367 1.45913C9.12148 1.06941 8.88152 0.592826 8.67153 0.221016C8.65559 0.19365 8.62467 0.179495 8.59372 0.184215C6.89513 0.476762 5.26967 0.989184 3.75294 1.68563C3.73981 1.69129 3.72855 1.70074 3.72108 1.713C0.637926 6.31917 -0.206681 10.8121 0.207655 15.2494C0.20953 15.2711 0.221716 15.2919 0.23859 15.3051C2.27277 16.7989 4.24322 17.7058 6.17708 18.3069C6.20803 18.3164 6.24082 18.3051 6.26052 18.2796C6.71797 17.6549 7.12576 16.9962 7.47539 16.3035C7.49602 16.2629 7.47632 16.2148 7.43415 16.1987C6.78734 15.9534 6.17145 15.6542 5.57901 15.3145C5.53215 15.2871 5.5284 15.2201 5.57151 15.188C5.69618 15.0946 5.82088 14.9974 5.93993 14.8992C5.96147 14.8813 5.99148 14.8775 6.0168 14.8889C9.90891 16.6659 14.1226 16.6659 17.9688 14.8889C17.9941 14.8766 18.0241 14.8804 18.0466 14.8983C18.1656 14.9965 18.2903 15.0946 18.4159 15.188C18.459 15.2201 18.4562 15.2871 18.4094 15.3145C17.8169 15.6608 17.201 15.9534 16.5533 16.1978C16.5111 16.2138 16.4924 16.2629 16.513 16.3035C16.8701 16.9952 17.2779 17.6539 17.7269 18.2786C17.7457 18.3051 17.7794 18.3164 17.8104 18.3069C19.7536 17.7058 21.724 16.7989 23.7582 15.3051C23.776 15.2919 23.7873 15.272 23.7892 15.2503C24.285 10.1204 22.9586 5.66426 20.2729 1.71394C20.2663 1.70074 20.2551 1.69129 20.242 1.68563ZM8.05661 12.5476C6.88482 12.5476 5.9193 11.4718 5.9193 10.1506C5.9193 8.8294 6.86609 7.75361 8.05661 7.75361C9.25647 7.75361 10.2126 8.83884 10.1939 10.1506C10.1939 11.4718 9.24709 12.5476 8.05661 12.5476ZM15.959 12.5476C14.7872 12.5476 13.8217 11.4718 13.8217 10.1506C13.8217 8.8294 14.7684 7.75361 15.959 7.75361C17.1589 7.75361 18.115 8.83884 18.0963 10.1506C18.0963 11.4718 17.1589 12.5476 15.959 12.5476Z"
|
||||
fill="currentColor"
|
||||
/>
|
||||
</svg>
|
||||
);
|
||||
|
||||
export default IconDiscord;
|
||||
16
src/icons/IconGithub.tsx
Normal file
16
src/icons/IconGithub.tsx
Normal file
@@ -0,0 +1,16 @@
|
||||
import React from "react";
|
||||
|
||||
interface IconGithubProps extends React.SVGProps<SVGSVGElement> {
|
||||
size?: number;
|
||||
}
|
||||
|
||||
const IconGithub: React.FC<IconGithubProps> = ({ size = 22, ...props }) => (
|
||||
<svg fill="none" height={size} viewBox="0 0 22 23" width={size} xmlns="http://www.w3.org/2000/svg" {...props}>
|
||||
<path
|
||||
d="M10.9997 2.69641C5.93509 2.69641 1.83301 6.79849 1.83301 11.8631C1.83197 13.7874 2.43692 15.6632 3.56204 17.2244C4.68716 18.7855 6.27531 19.9527 8.10118 20.5604C8.55951 20.6402 8.73093 20.3652 8.73093 20.1241C8.73093 19.9068 8.71901 19.1854 8.71901 18.4172C6.41634 18.8417 5.82051 17.8562 5.63718 17.3402C5.53359 17.0762 5.08718 16.2631 4.69759 16.0449C4.37676 15.8735 3.91843 15.4491 4.68568 15.4381C5.40801 15.4262 5.92318 16.1027 6.09551 16.3777C6.92051 17.7637 8.23868 17.3741 8.76484 17.1339C8.84551 16.5381 9.08568 16.1375 9.34968 15.9083C7.31009 15.6792 5.17884 14.8881 5.17884 11.3818C5.17884 10.3845 5.53359 9.56041 6.11843 8.91783C6.02676 8.68866 5.70593 7.74908 6.21009 6.48866C6.21009 6.48866 6.97734 6.24849 8.73093 7.42916C9.47715 7.22205 10.2482 7.11781 11.0226 7.11933C11.8018 7.11933 12.5809 7.22199 13.3143 7.42824C15.0669 6.23658 15.8351 6.48958 15.8351 6.48958C16.3393 7.74999 16.0184 8.68958 15.9268 8.91874C16.5107 9.56041 16.8663 10.3735 16.8663 11.3818C16.8663 14.9 14.7241 15.6792 12.6845 15.9083C13.0163 16.1943 13.3033 16.7443 13.3033 17.6042C13.3033 18.8297 13.2913 19.8152 13.2913 20.125C13.2913 20.3652 13.4637 20.6512 13.922 20.5595C15.7416 19.9451 17.3228 18.7757 18.4429 17.2156C19.5631 15.6556 20.1658 13.7836 20.1663 11.8631C20.1663 6.79849 16.0643 2.69641 10.9997 2.69641Z"
|
||||
fill="currentColor"
|
||||
/>
|
||||
</svg>
|
||||
);
|
||||
|
||||
export default IconGithub;
|
||||
16
src/icons/IconWebsite.tsx
Normal file
16
src/icons/IconWebsite.tsx
Normal file
@@ -0,0 +1,16 @@
|
||||
import React from "react";
|
||||
|
||||
interface IconWebsiteProps extends React.SVGProps<SVGSVGElement> {
|
||||
size?: number;
|
||||
}
|
||||
|
||||
const IconWebsite: React.FC<IconWebsiteProps> = ({ size = 18, ...props }) => (
|
||||
<svg fill="none" height={size} viewBox="0 0 18 18" width={size} xmlns="http://www.w3.org/2000/svg" {...props}>
|
||||
<path
|
||||
d="M9.00033 17.1963C4.39783 17.1963 0.666992 13.4655 0.666992 8.863C0.666992 4.2605 4.39783 0.529663 9.00033 0.529663C13.6028 0.529663 17.3337 4.2605 17.3337 8.863C17.3337 13.4655 13.6028 17.1963 9.00033 17.1963ZM7.09199 15.2522C6.26984 13.5083 5.79355 11.6215 5.68949 9.69633H2.38533C2.54748 10.9787 3.07859 12.1865 3.91413 13.1728C4.74967 14.159 5.85367 14.8814 7.09199 15.2522V15.2522ZM7.35866 9.69633C7.48449 11.7288 8.06533 13.638 9.00033 15.323C9.96059 13.5935 10.5215 11.6709 10.642 9.69633H7.35866V9.69633ZM15.6153 9.69633H12.3112C12.2071 11.6215 11.7308 13.5083 10.9087 15.2522C12.147 14.8814 13.251 14.159 14.0865 13.1728C14.9221 12.1865 15.4532 10.9787 15.6153 9.69633V9.69633ZM2.38533 8.02966H5.68949C5.79355 6.10448 6.26984 4.21773 7.09199 2.47383C5.85367 2.84456 4.74967 3.56696 3.91413 4.55324C3.07859 5.53953 2.54748 6.74725 2.38533 8.02966V8.02966ZM7.35949 8.02966H10.6412C10.521 6.05516 9.9603 4.13261 9.00033 2.403C8.04006 4.13254 7.47912 6.0551 7.35866 8.02966H7.35949ZM10.9087 2.47383C11.7308 4.21773 12.2071 6.10448 12.3112 8.02966H15.6153C15.4532 6.74725 14.9221 5.53953 14.0865 4.55324C13.251 3.56696 12.147 2.84456 10.9087 2.47383V2.47383Z"
|
||||
fill="currentColor"
|
||||
/>
|
||||
</svg>
|
||||
);
|
||||
|
||||
export default IconWebsite;
|
||||
44
src/pages/projects.tsx
Normal file
44
src/pages/projects.tsx
Normal file
@@ -0,0 +1,44 @@
|
||||
import Layout from "@theme/Layout";
|
||||
import LayoutProvider from "@theme/Layout/Provider";
|
||||
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) => (
|
||||
<header className={clsx("hero hero--dark", styles.heroBanner)}>
|
||||
<div className={styles.heroTitle}>{title}</div>
|
||||
|
||||
<div className={styles.heroTagline}>
|
||||
{tagline}
|
||||
|
||||
<span className={styles.blue}>.</span>
|
||||
</div>
|
||||
</header>
|
||||
);
|
||||
|
||||
const Projects: React.FC = () => (
|
||||
<LayoutProvider>
|
||||
<Layout description="A list of projects built with the MACI protocol" title="Projects built with MACI">
|
||||
<div>
|
||||
<ProjectspageHeader tagline="Explore the projects built by the MACI community" title="Projects" />
|
||||
</div>
|
||||
|
||||
<main>
|
||||
<section className={styles.introduction}>
|
||||
<div className="container">
|
||||
<ProjectsList />
|
||||
</div>
|
||||
</section>
|
||||
</main>
|
||||
</Layout>
|
||||
</LayoutProvider>
|
||||
);
|
||||
|
||||
export default Projects;
|
||||
40
src/utils/getProjectsByFilter.ts
Normal file
40
src/utils/getProjectsByFilter.ts
Normal file
@@ -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));
|
||||
}
|
||||
Reference in New Issue
Block a user