mirror of
https://github.com/AtHeartEngineer/website-v2.git
synced 2026-01-08 20:38:03 -05:00
Merge branch 'main' into about-page
This commit is contained in:
57
app/not-found.tsx
Normal file
57
app/not-found.tsx
Normal file
@@ -0,0 +1,57 @@
|
||||
import "@/styles/globals.css"
|
||||
import React from "react"
|
||||
import Image from "next/image"
|
||||
import Link from "next/link"
|
||||
|
||||
import { fontDisplay, fontSans } from "@/lib/fonts"
|
||||
import { Button } from "@/components/ui/button"
|
||||
import { SiteHeader } from "@/components/site-header"
|
||||
import { TailwindIndicator } from "@/components/tailwind-indicator"
|
||||
|
||||
export default function NotFound() {
|
||||
return (
|
||||
<html
|
||||
lang="en"
|
||||
className={`${fontSans.variable} ${fontDisplay.variable}`}
|
||||
suppressHydrationWarning
|
||||
>
|
||||
<head />
|
||||
<body className="min-h-screen">
|
||||
<div className="relative flex flex-col h-screen bg-anakiwa-50">
|
||||
<SiteHeader />
|
||||
<div className="container m-auto">
|
||||
<div className="flex flex-col -mt-16 gap-7">
|
||||
<div className="flex flex-col items-center justify-center gap-3 text-center">
|
||||
<div className="flex flex-col gap-2">
|
||||
<Image
|
||||
width={80}
|
||||
height={80}
|
||||
src="/icons/404-search.svg"
|
||||
alt="emotion sad"
|
||||
className="w-12 h-12 mx-auto md:w-24 md:h-24 text-anakiwa-400"
|
||||
/>
|
||||
<span className="text-5xl font-bold text-anakiwa-400 md:text-8xl font-display">
|
||||
404
|
||||
</span>
|
||||
</div>
|
||||
<div className="flex flex-col gap-5">
|
||||
<span className="text-2xl font-bold md:text-6xl text-tuatara-950 font-display">
|
||||
Oops! Page not found
|
||||
</span>
|
||||
<span className="font-sans text-base font-normal md:text-lg">
|
||||
The page you are looking for might have been removed, had
|
||||
its name changed or is temporarily unavailable.
|
||||
</span>
|
||||
</div>
|
||||
</div>
|
||||
<Link href="/" className="mx-auto">
|
||||
<Button variant="black">Go to homepage</Button>
|
||||
</Link>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<TailwindIndicator />
|
||||
</body>
|
||||
</html>
|
||||
)
|
||||
}
|
||||
@@ -143,6 +143,21 @@ export const Icons = {
|
||||
/>
|
||||
</svg>
|
||||
),
|
||||
arrowDown: (props: LucideProps) => (
|
||||
<svg
|
||||
width="25"
|
||||
height="25"
|
||||
viewBox="0 0 25 25"
|
||||
fill="currentColor"
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
{...props}
|
||||
>
|
||||
<path
|
||||
d="M12.4997 13.6719L17.4497 8.72192L18.8637 10.1359L12.4997 16.4999L6.13574 10.1359L7.54974 8.72192L12.4997 13.6719Z"
|
||||
fill="currentColor"
|
||||
/>
|
||||
</svg>
|
||||
),
|
||||
plus: (props: LucideProps) => (
|
||||
<svg
|
||||
width="35"
|
||||
|
||||
@@ -2,61 +2,79 @@
|
||||
|
||||
import {
|
||||
ProjectFilter,
|
||||
ProjectSortBy,
|
||||
useProjectFiltersState,
|
||||
} from "@/state/useProjectFiltersState"
|
||||
|
||||
import { CategoryTag } from "../ui/categoryTag"
|
||||
import { Dropdown } from "../ui/dropdown"
|
||||
|
||||
const labelClass = "h-5 text-xs text-base md:h-6 text-slate-900/70 md:text-lg"
|
||||
|
||||
const projectSortItems: { label: string; value: ProjectSortBy }[] = [
|
||||
{ label: "Random", value: "random" },
|
||||
{ label: "Title: A-Z", value: "asc" },
|
||||
{ label: "Title: Z-A", value: "desc" },
|
||||
{ label: "Relevance", value: "relevance" },
|
||||
]
|
||||
|
||||
const getSortLabel = (sortBy: ProjectSortBy) => {
|
||||
return projectSortItems.find((item) => item.value === sortBy)?.label || sortBy
|
||||
}
|
||||
|
||||
export const ProjectResultBar = () => {
|
||||
const { activeFilters, toggleFilter, projects } = useProjectFiltersState(
|
||||
(state) => state
|
||||
)
|
||||
const { activeFilters, toggleFilter, projects, sortProjectBy, sortBy } =
|
||||
useProjectFiltersState((state) => state)
|
||||
|
||||
const haveActiveFilters = Object.entries(activeFilters).some(
|
||||
([_key, values]) => values?.length > 0
|
||||
)
|
||||
|
||||
if (!haveActiveFilters)
|
||||
return (
|
||||
<span className={labelClass}>
|
||||
{`Showing ${projects.length} projects`}{" "}
|
||||
</span>
|
||||
)
|
||||
const resultLabel = haveActiveFilters
|
||||
? `Showing ${projects?.length} projects with:`
|
||||
: `Showing ${projects.length} projects`
|
||||
|
||||
return (
|
||||
<div className="flex flex-col gap-2">
|
||||
<span className={labelClass}>
|
||||
{`Showing ${projects?.length} projects with:`}{" "}
|
||||
</span>
|
||||
<div className="inline-flex flex-wrap gap-1 md:gap-4">
|
||||
{Object.entries(activeFilters).map(([key, filters], index) => {
|
||||
return (
|
||||
<>
|
||||
{filters?.map((filter) => {
|
||||
if (filter?.length === 0) return null
|
||||
|
||||
return (
|
||||
<CategoryTag
|
||||
closable
|
||||
variant="gray"
|
||||
onClose={() =>
|
||||
toggleFilter({
|
||||
tag: key as ProjectFilter,
|
||||
value: filter,
|
||||
})
|
||||
}
|
||||
key={`${index}-${filter}`}
|
||||
>
|
||||
{filter}
|
||||
</CategoryTag>
|
||||
)
|
||||
})}
|
||||
</>
|
||||
)
|
||||
})}
|
||||
<div className="flex items-center justify-between">
|
||||
<span className={labelClass}>{resultLabel}</span>
|
||||
<Dropdown
|
||||
label={`Sort by: ${getSortLabel(sortBy)}`}
|
||||
defaultItem="random"
|
||||
items={projectSortItems}
|
||||
onChange={(sortBy) => sortProjectBy(sortBy as ProjectSortBy)}
|
||||
disabled={!projects?.length}
|
||||
/>
|
||||
</div>
|
||||
{haveActiveFilters && (
|
||||
<div className="inline-flex flex-wrap gap-1 md:gap-4">
|
||||
{Object.entries(activeFilters).map(([key, filters], index) => {
|
||||
return (
|
||||
<>
|
||||
{filters?.map((filter) => {
|
||||
if (filter?.length === 0) return null
|
||||
|
||||
return (
|
||||
<CategoryTag
|
||||
closable
|
||||
variant="gray"
|
||||
onClose={() =>
|
||||
toggleFilter({
|
||||
tag: key as ProjectFilter,
|
||||
value: filter,
|
||||
})
|
||||
}
|
||||
key={`${index}-${filter}`}
|
||||
>
|
||||
{filter}
|
||||
</CategoryTag>
|
||||
)
|
||||
})}
|
||||
</>
|
||||
)
|
||||
})}
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
)
|
||||
}
|
||||
|
||||
@@ -6,7 +6,7 @@ import { LucideIcon } from "lucide-react"
|
||||
import { cn } from "@/lib/utils"
|
||||
|
||||
const buttonVariants = cva(
|
||||
"inline-flex items-center justify-center duration-100 rounded-md text-sm font-medium transition-colors focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring focus-visible:ring-offset-2 disabled:opacity-50 disabled:pointer-events-none ring-offset-background",
|
||||
"font-sans inline-flex items-center justify-center duration-100 rounded-md text-sm font-medium transition-colors focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring focus-visible:ring-offset-2 disabled:opacity-50 disabled:pointer-events-none ring-offset-background",
|
||||
{
|
||||
variants: {
|
||||
variant: {
|
||||
|
||||
87
components/ui/dropdown.tsx
Normal file
87
components/ui/dropdown.tsx
Normal file
@@ -0,0 +1,87 @@
|
||||
import React, { useState } from "react"
|
||||
import * as DropdownMenu from "@radix-ui/react-dropdown-menu"
|
||||
|
||||
import { cn } from "@/lib/utils"
|
||||
|
||||
import { Icons } from "../icons"
|
||||
|
||||
interface DropdownItemProps {
|
||||
label: string
|
||||
value?: string | number
|
||||
}
|
||||
|
||||
interface DropdownProps {
|
||||
label: string
|
||||
items?: DropdownItemProps[]
|
||||
defaultItem?: string | number
|
||||
onChange?: (value: DropdownItemProps["value"]) => void
|
||||
disabled?: boolean
|
||||
}
|
||||
|
||||
const Dropdown = ({
|
||||
label,
|
||||
onChange,
|
||||
defaultItem,
|
||||
disabled,
|
||||
items,
|
||||
}: DropdownProps) => {
|
||||
const [selected, setSelected] =
|
||||
useState<DropdownItemProps["value"]>(defaultItem)
|
||||
|
||||
const onSelectCallback = ({ value }: DropdownItemProps) => {
|
||||
setSelected(value)
|
||||
if (typeof onChange === "function") onChange(value)
|
||||
}
|
||||
|
||||
return (
|
||||
<DropdownMenu.Root>
|
||||
<DropdownMenu.Trigger asChild disabled={disabled}>
|
||||
<button
|
||||
className={cn("focus:outline-none ring-0", {
|
||||
"opacity-70 cursor-not-allowed": disabled,
|
||||
})}
|
||||
aria-label="dropdown menu"
|
||||
>
|
||||
<div className="flex items-center gap-1">
|
||||
<span className="text-sm font-medium break-words text-tuatara-950">
|
||||
{label}
|
||||
</span>
|
||||
<Icons.arrowDown />
|
||||
</div>
|
||||
</button>
|
||||
</DropdownMenu.Trigger>
|
||||
|
||||
<DropdownMenu.Portal>
|
||||
<DropdownMenu.Content
|
||||
className="min-w-[136px] max-h-[250px] overflow-scroll border border-tuatara-200 rounded-md bg-white py-2"
|
||||
sideOffset={5}
|
||||
>
|
||||
{items?.map((item, index) => {
|
||||
const active = selected === item.value
|
||||
return (
|
||||
<DropdownMenu.Item
|
||||
key={index}
|
||||
className={cn(
|
||||
"relative py-3 px-5 w-full font-sans text-sm cursor-pointer hover:font-medium focus:outline-none ring-0 hover:text-anakiwa-500 text-duration-200",
|
||||
{
|
||||
"text-tuatara-950 font-normal": !active,
|
||||
"text-anakiwa-500 font-medium": active,
|
||||
}
|
||||
)}
|
||||
onSelect={() => onSelectCallback(item)}
|
||||
>
|
||||
{active && (
|
||||
<div className="bg-anakiwa-500 w-[3px] absolute left-0 top-0 bottom-0"></div>
|
||||
)}
|
||||
{item.label}
|
||||
</DropdownMenu.Item>
|
||||
)
|
||||
})}
|
||||
</DropdownMenu.Content>
|
||||
</DropdownMenu.Portal>
|
||||
</DropdownMenu.Root>
|
||||
)
|
||||
}
|
||||
|
||||
Dropdown.displayName = "Dropdown"
|
||||
export { Dropdown }
|
||||
@@ -7,7 +7,7 @@ Anon Aadhaar is a project that allows individuals to prove their citizenship ano
|
||||
export const anonAadhaar: ProjectInterface = {
|
||||
id: "anon-aadhaar",
|
||||
projectStatus: "active",
|
||||
image: "",
|
||||
image: "anon-aadhaar.svg",
|
||||
name: "Anon Aadhaar",
|
||||
tldr: "Tools for building build privacy-preserving applications using government ID cards, specifically Aadhaar cards in India.",
|
||||
description,
|
||||
@@ -17,7 +17,7 @@ export const anonAadhaar: ProjectInterface = {
|
||||
tags: {
|
||||
keywords: ["Anonymity/privacy", "Social", "Identity", "Voting/governance"],
|
||||
themes: ["build", "play"],
|
||||
type: ["Legos/dev tools", "Lego sets/toolkits", "Proof of concept"],
|
||||
types: ["Legos/dev tools", "Lego sets/toolkits", "Proof of concept"],
|
||||
builtWith: ["circom", "rsa"],
|
||||
},
|
||||
}
|
||||
|
||||
@@ -24,7 +24,7 @@ export const anonKlub: ProjectInterface = {
|
||||
"Voting/governance",
|
||||
],
|
||||
themes: ["build", "play"],
|
||||
type: ["Infrastructure/protocol", "Prototype", "Proof of concept"],
|
||||
types: ["Infrastructure/protocol", "Prototype", "Proof of concept"],
|
||||
builtWith: ["circom", "snarkjs", "halo2"],
|
||||
},
|
||||
}
|
||||
|
||||
@@ -27,7 +27,7 @@ export const bandada: ProjectInterface = {
|
||||
"Scaling",
|
||||
"Key management",
|
||||
],
|
||||
type: [
|
||||
types: [
|
||||
"Legos/dev tools",
|
||||
"Lego sets/toolkits",
|
||||
"Prototype",
|
||||
|
||||
@@ -19,7 +19,7 @@ export const channel4: ProjectInterface = {
|
||||
tags: {
|
||||
keywords: ["Scaling"],
|
||||
themes: ["play"],
|
||||
type: ["Application"],
|
||||
types: ["Application"],
|
||||
builtWith: ["state channel", "smart contract"],
|
||||
},
|
||||
}
|
||||
|
||||
@@ -15,7 +15,7 @@ export const dslWorkingGroup: ProjectInterface = {
|
||||
github: "https://github.com/privacy-scaling-explorations/chiquito/",
|
||||
},
|
||||
tags: {
|
||||
type: ["Legos/dev tools", "Proof of concept", "Developer tooling"],
|
||||
types: ["Legos/dev tools", "Proof of concept", "Developer tooling"],
|
||||
keywords: [],
|
||||
themes: ["research"],
|
||||
builtWith: [],
|
||||
|
||||
@@ -17,7 +17,7 @@ export const eigenTrust: ProjectInterface = {
|
||||
tags: {
|
||||
keywords: ["Reputation", "Identity"],
|
||||
themes: ["build"],
|
||||
type: ["Infrastructure/protocol"],
|
||||
types: ["Infrastructure/protocol"],
|
||||
builtWith: ["ethereum attestation service", "halo2", "ethers.rs"],
|
||||
},
|
||||
}
|
||||
|
||||
@@ -18,7 +18,7 @@ export const pollenLabs: ProjectInterface = {
|
||||
tags: {
|
||||
keywords: ["Anonymity/privacy", "Scaling"],
|
||||
themes: ["play"],
|
||||
type: ["Application"],
|
||||
types: ["Application"],
|
||||
builtWith: [],
|
||||
},
|
||||
}
|
||||
|
||||
@@ -7,7 +7,7 @@ Rate-Limiting Nullifier (RLN) is a protocol designed to combat spam and denial o
|
||||
export const rln: ProjectInterface = {
|
||||
id: "rln",
|
||||
projectStatus: "active",
|
||||
image: "rln.webp",
|
||||
image: "rln.svg",
|
||||
name: "Rate-Limiting Nullifier",
|
||||
tldr: "A protocol for deterring spam and maintaining anonymity in communication systems.",
|
||||
description,
|
||||
|
||||
@@ -17,7 +17,7 @@ export const summa: ProjectInterface = {
|
||||
tags: {
|
||||
keywords: ["Anonymity/privacy", "Computational Integrity"],
|
||||
themes: ["build", "play"],
|
||||
type: ["Infrastructure/protocol", "Application"],
|
||||
types: ["Infrastructure/protocol", "Application"],
|
||||
builtWith: ["halo2"],
|
||||
},
|
||||
}
|
||||
|
||||
@@ -18,7 +18,7 @@ export const tlsn: ProjectInterface = {
|
||||
},
|
||||
tags: {
|
||||
themes: ["build"],
|
||||
type: [],
|
||||
types: [],
|
||||
builtWith: [],
|
||||
keywords: [],
|
||||
},
|
||||
|
||||
@@ -15,7 +15,7 @@ export const trustedSetups: ProjectInterface = {
|
||||
},
|
||||
tags: {
|
||||
themes: ["play"],
|
||||
type: ["Legos/dev tools", "Lego sets/toolkits"],
|
||||
types: ["Legos/dev tools", "Lego sets/toolkits"],
|
||||
builtWith: [],
|
||||
keywords: ["Scaling", "Education"],
|
||||
},
|
||||
|
||||
@@ -20,7 +20,7 @@ export const unirepProtocol: ProjectInterface = {
|
||||
tags: {
|
||||
keywords: ["Anonymity/privacy", "Social", "Identity", "Reputation"],
|
||||
themes: ["build"],
|
||||
type: ["Legos/dev tools, Protocol"],
|
||||
types: ["Legos/dev tools, Protocol"],
|
||||
builtWith: ["semaphore", "circom"],
|
||||
},
|
||||
}
|
||||
|
||||
@@ -18,7 +18,7 @@ export const zk3: ProjectInterface = {
|
||||
},
|
||||
tags: {
|
||||
themes: ["play"],
|
||||
type: [
|
||||
types: [
|
||||
"Legos/dev tools",
|
||||
"Lego sets/toolkits",
|
||||
"Infrastructure/protocol",
|
||||
|
||||
@@ -17,7 +17,7 @@ export const zkml: ProjectInterface = {
|
||||
tags: {
|
||||
keywords: ["Anonymity/privacy", "Scaling"],
|
||||
themes: ["research"],
|
||||
type: ["Proof of concept", "Infrastructure/protocol"],
|
||||
types: ["Proof of concept", "Infrastructure/protocol"],
|
||||
builtWith: ["circom", "halo2", "nova"],
|
||||
},
|
||||
}
|
||||
|
||||
@@ -18,8 +18,8 @@ export const zkp2p: ProjectInterface = {
|
||||
},
|
||||
tags: {
|
||||
keywords: ["Private communications"],
|
||||
theme: ["play"],
|
||||
type: ["Proof of concept", "Application"],
|
||||
themes: ["play"],
|
||||
types: ["Proof of concept", "Application"],
|
||||
builtWith: ["circom", "halo2"],
|
||||
},
|
||||
}
|
||||
|
||||
@@ -19,6 +19,8 @@ export type ProjectLinkWebsite =
|
||||
|
||||
export type ProjectLinkType = Partial<Record<ProjectLinkWebsite, string>>
|
||||
export type ProjectExtraLinkType = "buildWith" | "play" | "research" | "learn"
|
||||
export type TagType = "types" | "themes" | "builtWith" | "keywords"
|
||||
export type ProjectTags = Partial<Record<TagType, string[]>>
|
||||
export type ActionLinkTypeLink = {
|
||||
label: string
|
||||
url: string
|
||||
@@ -36,6 +38,6 @@ export interface ProjectInterface {
|
||||
description: string
|
||||
links?: ProjectLinkType
|
||||
projectStatus: ProjectStatusType
|
||||
tags?: Record<string, string[]>
|
||||
tags?: ProjectTags
|
||||
extraLinks?: ActionLinkType
|
||||
}
|
||||
|
||||
@@ -23,6 +23,7 @@
|
||||
"@radix-ui/react-accordion": "^1.1.2",
|
||||
"@radix-ui/react-checkbox": "^1.0.4",
|
||||
"@radix-ui/react-dialog": "^1.0.4",
|
||||
"@radix-ui/react-dropdown-menu": "^2.0.5",
|
||||
"@radix-ui/react-slot": "^1.0.2",
|
||||
"class-variance-authority": "^0.4.0",
|
||||
"clsx": "^1.2.1",
|
||||
|
||||
205
pnpm-lock.yaml
generated
205
pnpm-lock.yaml
generated
@@ -14,6 +14,9 @@ dependencies:
|
||||
'@radix-ui/react-dialog':
|
||||
specifier: ^1.0.4
|
||||
version: 1.0.4(@types/react-dom@18.2.4)(@types/react@18.2.7)(react-dom@18.2.0)(react@18.2.0)
|
||||
'@radix-ui/react-dropdown-menu':
|
||||
specifier: ^2.0.5
|
||||
version: 2.0.5(@types/react-dom@18.2.4)(@types/react@18.2.7)(react-dom@18.2.0)(react@18.2.0)
|
||||
'@radix-ui/react-slot':
|
||||
specifier: ^1.0.2
|
||||
version: 1.0.2(@types/react@18.2.7)(react@18.2.0)
|
||||
@@ -360,6 +363,34 @@ packages:
|
||||
engines: {node: ^12.22.0 || ^14.17.0 || >=16.0.0}
|
||||
dev: true
|
||||
|
||||
/@floating-ui/core@1.4.1:
|
||||
resolution: {integrity: sha512-jk3WqquEJRlcyu7997NtR5PibI+y5bi+LS3hPmguVClypenMsCY3CBa3LAQnozRCtCrYWSEtAdiskpamuJRFOQ==}
|
||||
dependencies:
|
||||
'@floating-ui/utils': 0.1.1
|
||||
dev: false
|
||||
|
||||
/@floating-ui/dom@1.5.1:
|
||||
resolution: {integrity: sha512-KwvVcPSXg6mQygvA1TjbN/gh///36kKtllIF8SUm0qpFj8+rvYrpvlYdL1JoA71SHpDqgSSdGOSoQ0Mp3uY5aw==}
|
||||
dependencies:
|
||||
'@floating-ui/core': 1.4.1
|
||||
'@floating-ui/utils': 0.1.1
|
||||
dev: false
|
||||
|
||||
/@floating-ui/react-dom@2.0.2(react-dom@18.2.0)(react@18.2.0):
|
||||
resolution: {integrity: sha512-5qhlDvjaLmAst/rKb3VdlCinwTF4EYMiVxuuc/HVUjs46W0zgtbMmAZ1UTsDrRTxRmUEzl92mOtWbeeXL26lSQ==}
|
||||
peerDependencies:
|
||||
react: '>=16.8.0'
|
||||
react-dom: '>=16.8.0'
|
||||
dependencies:
|
||||
'@floating-ui/dom': 1.5.1
|
||||
react: 18.2.0
|
||||
react-dom: 18.2.0(react@18.2.0)
|
||||
dev: false
|
||||
|
||||
/@floating-ui/utils@0.1.1:
|
||||
resolution: {integrity: sha512-m0G6wlnhm/AX0H12IOWtK8gASEMffnX08RtKkCgTdHb9JpHKGloI7icFfLg9ZmQeavcvR0PKmzxClyuFPSjKWw==}
|
||||
dev: false
|
||||
|
||||
/@humanwhocodes/config-array@0.11.8:
|
||||
resolution: {integrity: sha512-UybHIJzJnR5Qc/MsD9Kr+RpO2h+/P1GhOwdiLPXK5TWk5sgTdu88bTD9UP+CKbPPh5Rni1u0GjAdYQLemG8g+g==}
|
||||
engines: {node: '>=10.10.0'}
|
||||
@@ -574,6 +605,35 @@ packages:
|
||||
react-dom: 18.2.0(react@18.2.0)
|
||||
dev: false
|
||||
|
||||
/@radix-ui/react-arrow@1.0.3(@types/react-dom@18.2.4)(@types/react@18.2.7)(react-dom@18.2.0)(react@18.2.0):
|
||||
resolution: {integrity: sha512-wSP+pHsB/jQRaL6voubsQ/ZlrGBHHrOjmBnr19hxYgtS0WvAFwZhK2WP/YY5yF9uKECCEEDGxuLxq1NBK51wFA==}
|
||||
peerDependencies:
|
||||
'@types/react': '*'
|
||||
'@types/react-dom': '*'
|
||||
react: ^16.8 || ^17.0 || ^18.0
|
||||
react-dom: ^16.8 || ^17.0 || ^18.0
|
||||
peerDependenciesMeta:
|
||||
'@types/react':
|
||||
optional: true
|
||||
'@types/react-dom':
|
||||
optional: true
|
||||
dependencies:
|
||||
'@babel/runtime': 7.22.3
|
||||
'@radix-ui/primitive': 1.0.1
|
||||
'@radix-ui/react-collapsible': 1.0.3(@types/react-dom@18.2.4)(@types/react@18.2.7)(react-dom@18.2.0)(react@18.2.0)
|
||||
'@radix-ui/react-collection': 1.0.3(@types/react-dom@18.2.4)(@types/react@18.2.7)(react-dom@18.2.0)(react@18.2.0)
|
||||
'@radix-ui/react-compose-refs': 1.0.1(@types/react@18.2.7)(react@18.2.0)
|
||||
'@radix-ui/react-context': 1.0.1(@types/react@18.2.7)(react@18.2.0)
|
||||
'@radix-ui/react-direction': 1.0.1(@types/react@18.2.7)(react@18.2.0)
|
||||
'@radix-ui/react-id': 1.0.1(@types/react@18.2.7)(react@18.2.0)
|
||||
'@radix-ui/react-primitive': 1.0.3(@types/react-dom@18.2.4)(@types/react@18.2.7)(react-dom@18.2.0)(react@18.2.0)
|
||||
'@radix-ui/react-use-controllable-state': 1.0.1(@types/react@18.2.7)(react@18.2.0)
|
||||
'@types/react': 18.2.7
|
||||
'@types/react-dom': 18.2.4
|
||||
react: 18.2.0
|
||||
react-dom: 18.2.0(react@18.2.0)
|
||||
dev: false
|
||||
|
||||
/@radix-ui/react-checkbox@1.0.4(@types/react-dom@18.2.4)(@types/react@18.2.7)(react-dom@18.2.0)(react@18.2.0):
|
||||
resolution: {integrity: sha512-CBuGQa52aAYnADZVt/KBQzXrwx6TqnlwtcIPGtVt5JkkzQwMOLJjPukimhfKEr4GQNd43C+djUh5Ikopj8pSLg==}
|
||||
peerDependencies:
|
||||
@@ -755,6 +815,33 @@ packages:
|
||||
react-dom: 18.2.0(react@18.2.0)
|
||||
dev: false
|
||||
|
||||
/@radix-ui/react-dropdown-menu@2.0.5(@types/react-dom@18.2.4)(@types/react@18.2.7)(react-dom@18.2.0)(react@18.2.0):
|
||||
resolution: {integrity: sha512-xdOrZzOTocqqkCkYo8yRPCib5OkTkqN7lqNCdxwPOdE466DOaNl4N8PkUIlsXthQvW5Wwkd+aEmWpfWlBoDPEw==}
|
||||
peerDependencies:
|
||||
'@types/react': '*'
|
||||
'@types/react-dom': '*'
|
||||
react: ^16.8 || ^17.0 || ^18.0
|
||||
react-dom: ^16.8 || ^17.0 || ^18.0
|
||||
peerDependenciesMeta:
|
||||
'@types/react':
|
||||
optional: true
|
||||
'@types/react-dom':
|
||||
optional: true
|
||||
dependencies:
|
||||
'@babel/runtime': 7.22.3
|
||||
'@radix-ui/primitive': 1.0.1
|
||||
'@radix-ui/react-compose-refs': 1.0.1(@types/react@18.2.7)(react@18.2.0)
|
||||
'@radix-ui/react-context': 1.0.1(@types/react@18.2.7)(react@18.2.0)
|
||||
'@radix-ui/react-id': 1.0.1(@types/react@18.2.7)(react@18.2.0)
|
||||
'@radix-ui/react-menu': 2.0.5(@types/react-dom@18.2.4)(@types/react@18.2.7)(react-dom@18.2.0)(react@18.2.0)
|
||||
'@radix-ui/react-primitive': 1.0.3(@types/react-dom@18.2.4)(@types/react@18.2.7)(react-dom@18.2.0)(react@18.2.0)
|
||||
'@radix-ui/react-use-controllable-state': 1.0.1(@types/react@18.2.7)(react@18.2.0)
|
||||
'@types/react': 18.2.7
|
||||
'@types/react-dom': 18.2.4
|
||||
react: 18.2.0
|
||||
react-dom: 18.2.0(react@18.2.0)
|
||||
dev: false
|
||||
|
||||
/@radix-ui/react-focus-guards@1.0.1(@types/react@18.2.7)(react@18.2.0):
|
||||
resolution: {integrity: sha512-Rect2dWbQ8waGzhMavsIbmSVCgYxkXLxxR3ZvCX79JOglzdEy4JXMb98lq4hPxUbLr77nP0UOGf4rcMU+s1pUA==}
|
||||
peerDependencies:
|
||||
@@ -807,6 +894,74 @@ packages:
|
||||
react: 18.2.0
|
||||
dev: false
|
||||
|
||||
/@radix-ui/react-menu@2.0.5(@types/react-dom@18.2.4)(@types/react@18.2.7)(react-dom@18.2.0)(react@18.2.0):
|
||||
resolution: {integrity: sha512-Gw4f9pwdH+w5w+49k0gLjN0PfRDHvxmAgG16AbyJZ7zhwZ6PBHKtWohvnSwfusfnK3L68dpBREHpVkj8wEM7ZA==}
|
||||
peerDependencies:
|
||||
'@types/react': '*'
|
||||
'@types/react-dom': '*'
|
||||
react: ^16.8 || ^17.0 || ^18.0
|
||||
react-dom: ^16.8 || ^17.0 || ^18.0
|
||||
peerDependenciesMeta:
|
||||
'@types/react':
|
||||
optional: true
|
||||
'@types/react-dom':
|
||||
optional: true
|
||||
dependencies:
|
||||
'@babel/runtime': 7.22.3
|
||||
'@radix-ui/primitive': 1.0.1
|
||||
'@radix-ui/react-collection': 1.0.3(@types/react-dom@18.2.4)(@types/react@18.2.7)(react-dom@18.2.0)(react@18.2.0)
|
||||
'@radix-ui/react-compose-refs': 1.0.1(@types/react@18.2.7)(react@18.2.0)
|
||||
'@radix-ui/react-context': 1.0.1(@types/react@18.2.7)(react@18.2.0)
|
||||
'@radix-ui/react-direction': 1.0.1(@types/react@18.2.7)(react@18.2.0)
|
||||
'@radix-ui/react-dismissable-layer': 1.0.4(@types/react-dom@18.2.4)(@types/react@18.2.7)(react-dom@18.2.0)(react@18.2.0)
|
||||
'@radix-ui/react-focus-guards': 1.0.1(@types/react@18.2.7)(react@18.2.0)
|
||||
'@radix-ui/react-focus-scope': 1.0.3(@types/react-dom@18.2.4)(@types/react@18.2.7)(react-dom@18.2.0)(react@18.2.0)
|
||||
'@radix-ui/react-id': 1.0.1(@types/react@18.2.7)(react@18.2.0)
|
||||
'@radix-ui/react-popper': 1.1.2(@types/react-dom@18.2.4)(@types/react@18.2.7)(react-dom@18.2.0)(react@18.2.0)
|
||||
'@radix-ui/react-portal': 1.0.3(@types/react-dom@18.2.4)(@types/react@18.2.7)(react-dom@18.2.0)(react@18.2.0)
|
||||
'@radix-ui/react-presence': 1.0.1(@types/react-dom@18.2.4)(@types/react@18.2.7)(react-dom@18.2.0)(react@18.2.0)
|
||||
'@radix-ui/react-primitive': 1.0.3(@types/react-dom@18.2.4)(@types/react@18.2.7)(react-dom@18.2.0)(react@18.2.0)
|
||||
'@radix-ui/react-roving-focus': 1.0.4(@types/react-dom@18.2.4)(@types/react@18.2.7)(react-dom@18.2.0)(react@18.2.0)
|
||||
'@radix-ui/react-slot': 1.0.2(@types/react@18.2.7)(react@18.2.0)
|
||||
'@radix-ui/react-use-callback-ref': 1.0.1(@types/react@18.2.7)(react@18.2.0)
|
||||
'@types/react': 18.2.7
|
||||
'@types/react-dom': 18.2.4
|
||||
aria-hidden: 1.2.3
|
||||
react: 18.2.0
|
||||
react-dom: 18.2.0(react@18.2.0)
|
||||
react-remove-scroll: 2.5.5(@types/react@18.2.7)(react@18.2.0)
|
||||
dev: false
|
||||
|
||||
/@radix-ui/react-popper@1.1.2(@types/react-dom@18.2.4)(@types/react@18.2.7)(react-dom@18.2.0)(react@18.2.0):
|
||||
resolution: {integrity: sha512-1CnGGfFi/bbqtJZZ0P/NQY20xdG3E0LALJaLUEoKwPLwl6PPPfbeiCqMVQnhoFRAxjJj4RpBRJzDmUgsex2tSg==}
|
||||
peerDependencies:
|
||||
'@types/react': '*'
|
||||
'@types/react-dom': '*'
|
||||
react: ^16.8 || ^17.0 || ^18.0
|
||||
react-dom: ^16.8 || ^17.0 || ^18.0
|
||||
peerDependenciesMeta:
|
||||
'@types/react':
|
||||
optional: true
|
||||
'@types/react-dom':
|
||||
optional: true
|
||||
dependencies:
|
||||
'@babel/runtime': 7.22.3
|
||||
'@floating-ui/react-dom': 2.0.2(react-dom@18.2.0)(react@18.2.0)
|
||||
'@radix-ui/react-arrow': 1.0.3(@types/react-dom@18.2.4)(@types/react@18.2.7)(react-dom@18.2.0)(react@18.2.0)
|
||||
'@radix-ui/react-compose-refs': 1.0.1(@types/react@18.2.7)(react@18.2.0)
|
||||
'@radix-ui/react-context': 1.0.1(@types/react@18.2.7)(react@18.2.0)
|
||||
'@radix-ui/react-primitive': 1.0.3(@types/react-dom@18.2.4)(@types/react@18.2.7)(react-dom@18.2.0)(react@18.2.0)
|
||||
'@radix-ui/react-use-callback-ref': 1.0.1(@types/react@18.2.7)(react@18.2.0)
|
||||
'@radix-ui/react-use-layout-effect': 1.0.1(@types/react@18.2.7)(react@18.2.0)
|
||||
'@radix-ui/react-use-rect': 1.0.1(@types/react@18.2.7)(react@18.2.0)
|
||||
'@radix-ui/react-use-size': 1.0.1(@types/react@18.2.7)(react@18.2.0)
|
||||
'@radix-ui/rect': 1.0.1
|
||||
'@types/react': 18.2.7
|
||||
'@types/react-dom': 18.2.4
|
||||
react: 18.2.0
|
||||
react-dom: 18.2.0(react@18.2.0)
|
||||
dev: false
|
||||
|
||||
/@radix-ui/react-portal@1.0.3(@types/react-dom@18.2.4)(@types/react@18.2.7)(react-dom@18.2.0)(react@18.2.0):
|
||||
resolution: {integrity: sha512-xLYZeHrWoPmA5mEKEfZZevoVRK/Q43GfzRXkWV6qawIWWK8t6ifIiLQdd7rmQ4Vk1bmI21XhqF9BN3jWf+phpA==}
|
||||
peerDependencies:
|
||||
@@ -871,6 +1026,35 @@ packages:
|
||||
react-dom: 18.2.0(react@18.2.0)
|
||||
dev: false
|
||||
|
||||
/@radix-ui/react-roving-focus@1.0.4(@types/react-dom@18.2.4)(@types/react@18.2.7)(react-dom@18.2.0)(react@18.2.0):
|
||||
resolution: {integrity: sha512-2mUg5Mgcu001VkGy+FfzZyzbmuUWzgWkj3rvv4yu+mLw03+mTzbxZHvfcGyFp2b8EkQeMkpRQ5FiA2Vr2O6TeQ==}
|
||||
peerDependencies:
|
||||
'@types/react': '*'
|
||||
'@types/react-dom': '*'
|
||||
react: ^16.8 || ^17.0 || ^18.0
|
||||
react-dom: ^16.8 || ^17.0 || ^18.0
|
||||
peerDependenciesMeta:
|
||||
'@types/react':
|
||||
optional: true
|
||||
'@types/react-dom':
|
||||
optional: true
|
||||
dependencies:
|
||||
'@babel/runtime': 7.22.3
|
||||
'@radix-ui/primitive': 1.0.1
|
||||
'@radix-ui/react-collection': 1.0.3(@types/react-dom@18.2.4)(@types/react@18.2.7)(react-dom@18.2.0)(react@18.2.0)
|
||||
'@radix-ui/react-compose-refs': 1.0.1(@types/react@18.2.7)(react@18.2.0)
|
||||
'@radix-ui/react-context': 1.0.1(@types/react@18.2.7)(react@18.2.0)
|
||||
'@radix-ui/react-direction': 1.0.1(@types/react@18.2.7)(react@18.2.0)
|
||||
'@radix-ui/react-id': 1.0.1(@types/react@18.2.7)(react@18.2.0)
|
||||
'@radix-ui/react-primitive': 1.0.3(@types/react-dom@18.2.4)(@types/react@18.2.7)(react-dom@18.2.0)(react@18.2.0)
|
||||
'@radix-ui/react-use-callback-ref': 1.0.1(@types/react@18.2.7)(react@18.2.0)
|
||||
'@radix-ui/react-use-controllable-state': 1.0.1(@types/react@18.2.7)(react@18.2.0)
|
||||
'@types/react': 18.2.7
|
||||
'@types/react-dom': 18.2.4
|
||||
react: 18.2.0
|
||||
react-dom: 18.2.0(react@18.2.0)
|
||||
dev: false
|
||||
|
||||
/@radix-ui/react-slot@1.0.2(@types/react@18.2.7)(react@18.2.0):
|
||||
resolution: {integrity: sha512-YeTpuq4deV+6DusvVUW4ivBgnkHwECUu0BiN43L5UCDFgdhsRUWAghhTF5MbvNTPzmiFOx90asDSUjWuCNapwg==}
|
||||
peerDependencies:
|
||||
@@ -958,6 +1142,21 @@ packages:
|
||||
react: 18.2.0
|
||||
dev: false
|
||||
|
||||
/@radix-ui/react-use-rect@1.0.1(@types/react@18.2.7)(react@18.2.0):
|
||||
resolution: {integrity: sha512-Cq5DLuSiuYVKNU8orzJMbl15TXilTnJKUCltMVQg53BQOF1/C5toAaGrowkgksdBQ9H+SRL23g0HDmg9tvmxXw==}
|
||||
peerDependencies:
|
||||
'@types/react': '*'
|
||||
react: ^16.8 || ^17.0 || ^18.0
|
||||
peerDependenciesMeta:
|
||||
'@types/react':
|
||||
optional: true
|
||||
dependencies:
|
||||
'@babel/runtime': 7.22.3
|
||||
'@radix-ui/rect': 1.0.1
|
||||
'@types/react': 18.2.7
|
||||
react: 18.2.0
|
||||
dev: false
|
||||
|
||||
/@radix-ui/react-use-size@1.0.1(@types/react@18.2.7)(react@18.2.0):
|
||||
resolution: {integrity: sha512-ibay+VqrgcaI6veAojjofPATwledXiSmX+C0KrBk/xgpX9rBzPV3OsfwlhQdUOFbh+LKQorLYT+xTXW9V8yd0g==}
|
||||
peerDependencies:
|
||||
@@ -973,6 +1172,12 @@ packages:
|
||||
react: 18.2.0
|
||||
dev: false
|
||||
|
||||
/@radix-ui/rect@1.0.1:
|
||||
resolution: {integrity: sha512-fyrgCaedtvMg9NK3en0pnOYJdtfwxUcNolezkNPUsoX57X8oQk+NkqcvzHXD2uKNij6GXmWU9NDru2IWjrO4BQ==}
|
||||
dependencies:
|
||||
'@babel/runtime': 7.22.3
|
||||
dev: false
|
||||
|
||||
/@rushstack/eslint-patch@1.3.0:
|
||||
resolution: {integrity: sha512-IthPJsJR85GhOkp3Hvp8zFOPK5ynKn6STyHa/WZpioK7E1aYDiBzpqQPrngc14DszIUkIrdd3k9Iu0XSzlP/1w==}
|
||||
dev: true
|
||||
|
||||
9
public/icons/404-search.svg
Normal file
9
public/icons/404-search.svg
Normal file
File diff suppressed because one or more lines are too long
|
After Width: | Height: | Size: 48 KiB |
24
public/project-banners/anon-aadhaar.svg
Normal file
24
public/project-banners/anon-aadhaar.svg
Normal file
File diff suppressed because one or more lines are too long
|
After Width: | Height: | Size: 34 KiB |
File diff suppressed because one or more lines are too long
|
Before Width: | Height: | Size: 7.3 KiB After Width: | Height: | Size: 2.0 KiB |
17
public/project-banners/rln.svg
Normal file
17
public/project-banners/rln.svg
Normal file
File diff suppressed because one or more lines are too long
|
After Width: | Height: | Size: 206 KiB |
@@ -5,9 +5,24 @@ import { create } from "zustand"
|
||||
import { ProjectInterface } from "@/lib/types"
|
||||
import { uniq } from "@/lib/utils"
|
||||
|
||||
export type ProjectSortBy = "random" | "asc" | "desc" | "relevance"
|
||||
export type ProjectFilter = "keywords" | "builtWith" | "themes"
|
||||
export type FiltersProps = Record<ProjectFilter, string[]>
|
||||
|
||||
interface ProjectInterfaceScore extends ProjectInterface {
|
||||
score: number
|
||||
}
|
||||
|
||||
export const SortByFnMapping: Record<
|
||||
ProjectSortBy,
|
||||
(a: ProjectInterfaceScore, b: ProjectInterfaceScore) => number
|
||||
> = {
|
||||
random: () => Math.random() - 0.5,
|
||||
asc: (a, b) => a.name.localeCompare(b.name),
|
||||
desc: (a, b) => b.name.localeCompare(a.name),
|
||||
relevance: (a, b) => b?.score - a?.score, // sort from most relevant to least relevant
|
||||
}
|
||||
|
||||
export const FilterLabelMapping: Record<ProjectFilter, string> = {
|
||||
keywords: "Keywords",
|
||||
builtWith: "Built with",
|
||||
@@ -20,6 +35,7 @@ export const FilterTypeMapping: Record<ProjectFilter, "checkbox" | "button"> = {
|
||||
themes: "button",
|
||||
}
|
||||
interface ProjectStateProps {
|
||||
sortBy: ProjectSortBy
|
||||
projects: ProjectInterface[]
|
||||
filters: FiltersProps
|
||||
activeFilters: Partial<FiltersProps>
|
||||
@@ -43,6 +59,7 @@ interface ProjectActionsProps {
|
||||
setFilterFromQueryString: (filters: Partial<FiltersProps>) => void
|
||||
onFilterProject: (searchPattern: string) => void
|
||||
onSelectTheme: (theme: string, searchPattern?: string) => void
|
||||
sortProjectBy: (sortBy: ProjectSortBy) => void
|
||||
}
|
||||
|
||||
const createURLQueryString = (params: Partial<FiltersProps>): string => {
|
||||
@@ -141,17 +158,34 @@ export const filterProjects = ({
|
||||
const fuse = new Fuse(projectList, {
|
||||
threshold: 0.2,
|
||||
useExtendedSearch: true,
|
||||
includeScore: true,
|
||||
keys,
|
||||
})
|
||||
|
||||
const result = fuse.search(query)?.map(({ item }) => item)
|
||||
|
||||
const result = fuse.search(query)?.map(({ item, score }) => {
|
||||
return {
|
||||
...item,
|
||||
score, // 0 indicates a perfect match, while a score of 1 indicates a complete mismatch.
|
||||
}
|
||||
})
|
||||
return result ?? []
|
||||
}
|
||||
|
||||
const sortProjectByFn = (
|
||||
projects: ProjectInterface[],
|
||||
sortBy: ProjectSortBy
|
||||
) => {
|
||||
const sortedProjectList: ProjectInterface[] = [
|
||||
...(projects as ProjectInterfaceScore[]),
|
||||
].sort(SortByFnMapping[sortBy])
|
||||
|
||||
return sortedProjectList
|
||||
}
|
||||
|
||||
export const useProjectFiltersState = create<
|
||||
ProjectStateProps & ProjectActionsProps
|
||||
>()((set) => ({
|
||||
sortBy: "random",
|
||||
projects,
|
||||
queryString: "",
|
||||
filters: getProjectFilters(), // list of filters with all possible values from projects
|
||||
@@ -183,7 +217,7 @@ export const useProjectFiltersState = create<
|
||||
...state,
|
||||
activeFilters,
|
||||
queryString,
|
||||
projects: filteredProjects,
|
||||
projects: sortProjectByFn(filteredProjects, state.sortBy),
|
||||
}
|
||||
}),
|
||||
onSelectTheme: (theme: string, searchQuery = "") => {
|
||||
@@ -206,7 +240,7 @@ export const useProjectFiltersState = create<
|
||||
return {
|
||||
...state,
|
||||
activeFilters,
|
||||
projects: filteredProjects,
|
||||
projects: sortProjectByFn(filteredProjects, state.sortBy),
|
||||
}
|
||||
})
|
||||
},
|
||||
@@ -219,7 +253,7 @@ export const useProjectFiltersState = create<
|
||||
|
||||
return {
|
||||
...state,
|
||||
projects: filteredProjects,
|
||||
projects: sortProjectByFn(filteredProjects, state.sortBy),
|
||||
}
|
||||
})
|
||||
},
|
||||
@@ -232,4 +266,13 @@ export const useProjectFiltersState = create<
|
||||
}
|
||||
})
|
||||
},
|
||||
sortProjectBy(sortBy: ProjectSortBy) {
|
||||
set((state: any) => {
|
||||
return {
|
||||
...state,
|
||||
sortBy,
|
||||
projects: sortProjectByFn(state.projects, sortBy),
|
||||
}
|
||||
})
|
||||
},
|
||||
}))
|
||||
|
||||
@@ -30,10 +30,12 @@ module.exports = {
|
||||
input: "hsl(var(--input))",
|
||||
ring: "hsl(var(--ring))",
|
||||
anakiwa: {
|
||||
50: "#F2FAFD",
|
||||
100: "hsl(var(--anakiwa))",
|
||||
100: "#E4F3FA",
|
||||
200: "#C2E8F5",
|
||||
300: "#A3DFF0",
|
||||
400: "#50C3E0",
|
||||
500: "#29ACCE",
|
||||
950: "#103241",
|
||||
},
|
||||
|
||||
Reference in New Issue
Block a user