diff --git a/app/(pages)/not-found.tsx b/app/(pages)/not-found.tsx new file mode 100644 index 0000000..7a78171 --- /dev/null +++ b/app/(pages)/not-found.tsx @@ -0,0 +1,48 @@ +"use client" + +import "@/styles/globals.css" +import React from "react" +import Image from "next/image" +import Link from "next/link" +import { Metadata } from "next" + +import { Button } from "@/components/ui/button" + +import { LABELS } from "../labels" + +export const metadata: Metadata = { + title: "404: Page Not Found", +} + +export default function NotFound() { + return ( +
+
+
+
+
+ emotion sad +
+
+ + {LABELS.COMMON.ERROR["404"].TITLE} + + + {LABELS.COMMON.ERROR["404"].DESCRIPTION} + +
+
+ + + +
+
+
+ ) +} diff --git a/app/(pages)/projects/sections/ProjectContent.tsx b/app/(pages)/projects/sections/ProjectContent.tsx index 7e3f86b..07de2ae 100644 --- a/app/(pages)/projects/sections/ProjectContent.tsx +++ b/app/(pages)/projects/sections/ProjectContent.tsx @@ -162,7 +162,7 @@ export const ProjectContent = ({ id }: { id: string }) => { {projectStatusMessage?.length > 0 && (
- + {projectStatusMessage} diff --git a/app/components/wrappers/SectionWrapper.tsx b/app/components/wrappers/SectionWrapper.tsx new file mode 100644 index 0000000..bb64a7c --- /dev/null +++ b/app/components/wrappers/SectionWrapper.tsx @@ -0,0 +1,50 @@ +import { cn } from "@/lib/utils" +import { classed } from "@tw-classed/react" +import { ReactNode } from "react" + +interface SectionWrapperProps { + title: string + description?: string + showHeader?: boolean + children?: ReactNode + className?: string +} + +export const SectionWrapperTitle = classed.h3( + "relative font-sans text-base font-bold uppercase tracking-[3.36px] text-anakiwa-950 dark:text-anakiwa-400 dark:text-white", + { + variants: { + variant: { + default: + "after:ml-8 after:absolute after:top-1/2 after:h-[1px] after:w-full after:translate-y-1/2 after:bg-anakiwa-300 after:content-['']", + }, + }, + defaultVariants: { + variant: "default", + }, + } +) + +export const SectionWrapper = ({ + title = "", + description = "", + showHeader = true, + children = null, + className = "", +}: SectionWrapperProps) => { + return ( +
+ {showHeader && ( +
+ {title} + {description?.length > 0 && ( + + {description} + + )} +
+ )} + {children} +
+ ) +} diff --git a/app/labels.ts b/app/labels.ts index ff521ba..ee931d8 100644 --- a/app/labels.ts +++ b/app/labels.ts @@ -47,6 +47,7 @@ export const LABELS = { RECENT_ARTICLES: "Recent articles", SEE_MORE: "See more", READ_MORE: "Read more", + SEARCH_PLACEHOLDER: "Search PSE's blog", }, BLOG_TAGS_PAGE: { TITLE: "Blog tags", @@ -168,6 +169,7 @@ export const LABELS = { ERROR_LOADING_VIDEOS: "Error loading videos", PROJECT_TEAM: "Team", YOUTUBE_VIDEOS: "YouTube Videos", + MORE_POSTS: "More posts", }, HOMEPAGE: { HEADER_TITLE: "Privacy + Scaling Explorations", diff --git a/components/blog/article-in-evidance-card.tsx b/components/blog/article-in-evidance-card.tsx index 2c9b8f2..71e37fa 100644 --- a/components/blog/article-in-evidance-card.tsx +++ b/components/blog/article-in-evidance-card.tsx @@ -17,27 +17,30 @@ interface ArticleInEvidenceCardProps { titleClassName?: string contentClassName?: string showDate?: boolean + backgroundCover?: boolean } const AsLinkWrapper = ({ children, href, asLink, + className = "", }: { children: React.ReactNode href: string asLink: boolean + className?: string }) => { return asLink ? ( - + {children} ) : ( - <>{children} +
{children}
) } -export const ArticleInEvidenceCard = async ({ +export const ArticleInEvidenceCard = ({ article, showReadMore = false, size = "lg", @@ -47,6 +50,7 @@ export const ArticleInEvidenceCard = async ({ titleClassName = "", contentClassName = "", showDate = true, + backgroundCover = true, }: ArticleInEvidenceCardProps) => { const hideTldr = variant === "compact" @@ -59,42 +63,40 @@ export const ArticleInEvidenceCard = async ({ }) } - return ( - + const ArticleContent = ({ backgroundCover = true }: any) => { + return (
-
- {article.date && showDate && ( - - {formatDate(article.date)} - - )} + {article.date && showDate && ( + + {formatDate(article.date)} + + )} +
{article.title} - - {article.authors?.join(", ")} - + {(article?.authors ?? [])?.length > 0 && ( + + {article.authors?.join(", ")} + + )} {article.tldr && !hideTldr && ( )} - {showReadMore && ( - - - - )}
+ {(article?.tags ?? [])?.length > 0 && ( +
+ {article?.tags?.slice(0, 3)?.map((tag) => ( + + + + ))} +
+ )} + {showReadMore && ( + + + + )}
+ ) + } + + return ( + +
+ {backgroundCover && ( + + )} +
+ {!backgroundCover && }
) } diff --git a/components/blog/article-list-card.tsx b/components/blog/article-list-card.tsx index f79fec1..5bdfea6 100644 --- a/components/blog/article-list-card.tsx +++ b/components/blog/article-list-card.tsx @@ -56,9 +56,11 @@ export const ArticleListCard = ({ {article.title} - - {article.authors?.map((author) => author).join(", ")} - + {(article?.authors ?? [])?.length > 0 && ( + + {article.authors?.map((author) => author).join(", ")} + + )}
{ - const hideTldr = variant === "compact" - - const formatDate = (dateString: string) => { - const date = new Date(dateString) - return date.toLocaleDateString("en-US", { - month: "long", - day: "numeric", - year: "numeric", - }) - } - - const AsLinkWrapper = ({ - children, - href, - asLink, - }: { - children: React.ReactNode - href: string - asLink: boolean - }) => { - return asLink ? ( - - {children} - - ) : ( -
{children}
- ) - } - - const backgroundImage = getBackgroundImage(article?.image) - - return ( - -
-
- {article.date && showDate && ( - - {formatDate(article.date)} - - )} - {asLink === false ? ( - - {article.title} - - ) : ( - - {article.title} - - )} - - {article.authors?.join(", ")} - - {article.tldr && !hideTldr && ( - - )} -
-
-
- ) -} - async function fetchArticles(tag?: string) { try { const params = new URLSearchParams() @@ -164,6 +56,12 @@ interface ArticlesListProps { export const ArticlesList: React.FC = ({ tag, }: ArticlesListProps) => { + const router = useRouter() + + const params = useSearchParams() + const query = params.get("query") + const [searchQuery, setSearchQuery] = useState(query ?? "") + const { data: articles = [], isLoading, @@ -185,25 +83,70 @@ export const ArticlesList: React.FC = ({ ) } + const hasSearchParams = + (searchQuery ?? "")?.length > 0 && searchQuery !== "all" + const lastArticle = articles[0] const featuredArticles = !tag ? articles.slice(1, 3) : [] - const otherArticles = !tag ? articles.slice(3) : articles + let otherArticles = !tag && !hasSearchParams ? articles.slice(3, 6) : [] + + if (searchQuery === "all") { + otherArticles = articles + } else if (searchQuery?.length > 0) { + otherArticles = articles.filter((article: Article) => { + const title = article.title.toLowerCase() + const content = article.content.toLowerCase() + const tags = + article.tags?.map((tag: ArticleTag) => tag.name.toLowerCase()) ?? [] + return ( + title.includes(searchQuery.toLowerCase()) || + tags.some((tag: string) => tag.includes(searchQuery.toLowerCase())) + ) + }) + } const hasTag = tag !== undefined + const onSearchArticles = (query: string) => { + setSearchQuery(query) + router.push(`/blog?query=${query}`) + } + + useDebounce( + () => { + if (searchQuery === "") return null + onSearchArticles(searchQuery) + }, + 500, // debounce timeout in ms when user is typing + [searchQuery] + ) + return (
- {!hasTag && ( -
-
+ {!hasTag && !hasSearchParams && searchQuery !== "all" && ( +
+
+ <> + {featuredArticles?.map((article: Article) => { + return ( + + ) + })} +
-
+
{featuredArticles?.map((article: Article) => { return ( = ({ variant="compact" size="sm" className="h-full" + backgroundCover={false} asLink /> ) @@ -219,10 +163,37 @@ export const ArticlesList: React.FC = ({
)} -
- {otherArticles.map((article: Article) => { - return - })} +
+
+ { + onSearchArticles(e?.target?.value ?? "") + }} + onIconClick={() => { + onSearchArticles(searchQuery) + }} + /> +
+ {otherArticles + .filter((article: Article) => article.id !== lastArticle.id) + .map((article: Article) => { + return + })} +
+
+ {searchQuery?.length === 0 && ( + + )}
) diff --git a/components/blog/blog-content.tsx b/components/blog/blog-content.tsx index 8fbf35f..f2c1cef 100644 --- a/components/blog/blog-content.tsx +++ b/components/blog/blog-content.tsx @@ -75,7 +75,7 @@ export function BlogContent({ post, isNewsletter = false }: BlogContentProps) { View all diff --git a/components/cards/wiki-card.tsx b/components/cards/wiki-card.tsx index 4f9166c..fa675ea 100644 --- a/components/cards/wiki-card.tsx +++ b/components/cards/wiki-card.tsx @@ -70,7 +70,7 @@ export const WikiCard = ({ project, className = "" }: WikiCardProps) => { return (
-
+
{ return ( @@ -178,14 +163,11 @@ export const ProjectList = () => { className="flex justify-between gap-10" >
- {!hasSearchParams && ( -
-

{status}

- - {description} - -
- )} +
{projects.map((project: any) => ( { return ( @@ -38,10 +36,9 @@ const ProjectStatusOrderList = ["active", "maintained", "inactive"] export const ResearchList = () => { const [isMounted, setIsMounted] = useState(false) - const { researchs, searchQuery, queryString } = useProjects() + const { researchs } = useProjects() const noItems = researchs?.length === 0 - const hasActiveFilters = searchQuery !== "" || queryString !== "" useEffect(() => { setIsMounted(true) @@ -65,14 +62,11 @@ export const ResearchList = () => { return (
-
-
+
@@ -92,14 +86,7 @@ export const ResearchList = () => { data-section="active-researchs" className="flex flex-col justify-between gap-10" > -
- {!hasActiveFilters && ( -
-

- {LABELS.RESEARCH_PAGE.ACTIVE_RESEARCH} -

-
- )} +
{activeResearchs.map((project: ProjectInterface) => ( { /> ))}
-
-
-
-

- {LABELS.RESEARCH_PAGE.PAST_RESEARCH} -

-
+ + +
{pastResearchs.map((project: ProjectInterface) => ( { ))}
-
+
) diff --git a/components/search/search-modal.tsx b/components/search/search-modal.tsx index 3ee629b..9c39f79 100644 --- a/components/search/search-modal.tsx +++ b/components/search/search-modal.tsx @@ -109,7 +109,7 @@ function Hit({ }) return ( - + {textContent} ) diff --git a/components/sections/HomepageVideoFeed.tsx b/components/sections/HomepageVideoFeed.tsx index ff8d22c..682a793 100644 --- a/components/sections/HomepageVideoFeed.tsx +++ b/components/sections/HomepageVideoFeed.tsx @@ -73,7 +73,7 @@ export const HomepageVideoFeed = () => {
-

+

{LABELS.HOMEPAGE.VIDEOS}

diff --git a/components/site-header-mobile.tsx b/components/site-header-mobile.tsx index 53e5d05..4152c0a 100644 --- a/components/site-header-mobile.tsx +++ b/components/site-header-mobile.tsx @@ -34,7 +34,6 @@ export const SiteHeaderMobile = () => { className="text-[#171C1B] dark:text-anakiwa-400" /> - {header && (
{ })}
diff --git a/components/ui/app-content.tsx b/components/ui/app-content.tsx index 9e256ff..6e55a63 100644 --- a/components/ui/app-content.tsx +++ b/components/ui/app-content.tsx @@ -1,10 +1,10 @@ import React from "react" interface AppContentProps extends React.HTMLAttributes { - children: React.ReactNode + children?: React.ReactNode className?: string } -export const AppContent = ({ children, className }: AppContentProps) => { +export const AppContent = ({ children = null, className }: AppContentProps) => { return
{children}
} diff --git a/components/ui/input.tsx b/components/ui/input.tsx index 0300446..147df30 100644 --- a/components/ui/input.tsx +++ b/components/ui/input.tsx @@ -1,11 +1,12 @@ import { InputHTMLAttributes, forwardRef } from "react" import { cva, type VariantProps } from "class-variance-authority" +import { LucideIcon } from "lucide-react" import { cn } from "@/lib/utils" const inputVariants = cva( [ - "rounded-md bg-zinc-50", + "w-full rounded-md bg-zinc-50", "text-anakiwa-950 placeholder-anakiwa-950", "border-[1.5px] border-tuatara-200", "transition-colors duration-100 animate", @@ -30,16 +31,63 @@ const inputVariants = cva( interface InputProps extends VariantProps, - Omit, "size" | "id" | "children"> {} + Omit, "size" | "id" | "children"> { + icon?: LucideIcon + iconPosition?: "left" | "right" + onIconClick?: () => void +} const Input = forwardRef( - ({ size, className, ...props }, ref) => { + ( + { + size, + className, + icon: Icon, + iconPosition = "left", + onIconClick, + ...props + }, + ref + ) => { + if (!Icon) { + return ( + + ) + } + return ( - +
+ + {onIconClick ? ( + + ) : ( + + )} +
) } ) diff --git a/components/ui/markdown.tsx b/components/ui/markdown.tsx index dc951ed..cb95b6b 100644 --- a/components/ui/markdown.tsx +++ b/components/ui/markdown.tsx @@ -563,7 +563,7 @@ const REACT_MARKDOWN_CONFIG = (darkMode: boolean): CustomComponents => ({ if (containsMath(text)) { return (

@@ -572,7 +572,7 @@ const REACT_MARKDOWN_CONFIG = (darkMode: boolean): CustomComponents => ({ return (

{children}

@@ -591,31 +591,27 @@ const REACT_MARKDOWN_CONFIG = (darkMode: boolean): CustomComponents => ({ if (containsMath(text)) { return ( -
  • +
  • ) } return ( -
  • +
  • {children}
  • ) }, ul: ({ ordered, ...props }) => createMarkdownElement(ordered ? "ol" : "ul", { - className: - "ml-6 list-disc text-secondary font-sans text-base font-normal", + className: "ml-6 list-disc text-secondary font-sans text-lg font-normal", ...props, }), ol: ({ ordered, ...props }) => createMarkdownElement(ordered ? "ol" : "ul", { className: - "list-decimal text-secondary font-sans text-base font-normal mt-3", + "list-decimal text-secondary font-sans text-lg font-normal mt-3", ...props, }), table: Table, diff --git a/config/site.ts b/config/site.ts index da99ec5..c7b10cf 100644 --- a/config/site.ts +++ b/config/site.ts @@ -1,7 +1,7 @@ export type SiteConfig = typeof siteConfig export const siteConfig = { - name: "Privacy & Scaling Explorations", + name: "PSE", description: "Enhancing Ethereum through cryptographic research and collective experimentation.", url: "https://pse.dev", diff --git a/content/articles/ethereum-privacy-pir.md b/content/articles/ethereum-privacy-pir.md index ede1ce4..d30fcef 100644 --- a/content/articles/ethereum-privacy-pir.md +++ b/content/articles/ethereum-privacy-pir.md @@ -8,8 +8,6 @@ tags: ["ethereum", "privacy", "pir"] projects: ["semaphore", "scaling-semaphore-pir"] --- -# Ethereum Privacy: Private Information Retrieval - _Thanks to [Cperezz](https://github.com/cperezz), [Vivian](https://github.com/vplasencia) and [Oskar](https://github.com/oskarth) for feedback and review._ Ethereum presents several [privacy challenges](https://hackmd.io/@pcaversaccio/ethereum-privacy-the-road-to-self-sovereignty#Status-Quo). One of them is that read operations can expose user data and behavior patterns, potentially leading to [deanonymization](https://en.wikipedia.org/wiki/Data_re-identification). In this scenario, users are safe only if they run their own node or host the data locally. Otherwise, all requests go through third parties, such as relayers, providers, and [wallets that process IP addresses](https://consensys.io/privacy-notice). diff --git a/package.json b/package.json index 67e0b7e..2b507b8 100644 --- a/package.json +++ b/package.json @@ -27,6 +27,7 @@ "@radix-ui/react-dropdown-menu": "^2.0.5", "@radix-ui/react-slot": "^1.0.2", "@tanstack/react-query": "^5.74.4", + "@tw-classed/react": "^1.8.0", "@types/prismjs": "^1.26.5", "algoliasearch": "^4", "class-variance-authority": "^0.4.0", diff --git a/styles/globals.css b/styles/globals.css index 9eb0526..de70eeb 100644 --- a/styles/globals.css +++ b/styles/globals.css @@ -13,11 +13,6 @@ --gradient-research-card: linear-gradient(84deg, #FFF -1.95%, #EAFAFF 59.98%, #FFF 100.64%); --skeleton-background: #f3f4f6; - - /* TODO: */ - - - --active-selection: #50C3E0; --primary: 222.2 47.4% 11.2%; @@ -66,12 +61,6 @@ --gradient-research-card: linear-gradient(179deg, #29ACCE -202.54%, rgba(0, 0, 0, 0.00) 192.47%); --skeleton-background: #175e75; - - - - - - /* TODO: */ --active-selection: #E1523A; --primary: #E1523A; diff --git a/yarn.lock b/yarn.lock index c5c8a97..8f34dc5 100644 --- a/yarn.lock +++ b/yarn.lock @@ -1804,6 +1804,24 @@ __metadata: languageName: node linkType: hard +"@tw-classed/core@npm:1.7.0": + version: 1.7.0 + resolution: "@tw-classed/core@npm:1.7.0" + checksum: 10/7a3e1fb154a2297276d46d0be5847124b01dcea30e13b2b6b82cf2b99b8dacd59f6cbb88124a1dfffae274d71eb62c38df1086abd9485d69e0327186c990f272 + languageName: node + linkType: hard + +"@tw-classed/react@npm:^1.8.0": + version: 1.8.0 + resolution: "@tw-classed/react@npm:1.8.0" + dependencies: + "@tw-classed/core": "npm:1.7.0" + peerDependencies: + react: ">=16.8.0" + checksum: 10/7bfc82d7d099da8429e8178860a92b6f08987e84e8b94d3167aa03bae1619c4479712a9696f1fcaede54267b20856aed9914cb958978a4cf6a72ca19767fcb05 + languageName: node + linkType: hard + "@tybys/wasm-util@npm:^0.9.0": version: 0.9.0 resolution: "@tybys/wasm-util@npm:0.9.0" @@ -6795,6 +6813,7 @@ __metadata: "@radix-ui/react-dropdown-menu": "npm:^2.0.5" "@radix-ui/react-slot": "npm:^1.0.2" "@tanstack/react-query": "npm:^5.74.4" + "@tw-classed/react": "npm:^1.8.0" "@types/js-yaml": "npm:^4.0.9" "@types/node": "npm:^17.0.45" "@types/prismjs": "npm:^1.26.5"