diff --git a/app/(pages)/about/page.tsx b/app/(pages)/about/page.tsx index d4ad881..100e804 100644 --- a/app/(pages)/about/page.tsx +++ b/app/(pages)/about/page.tsx @@ -10,6 +10,12 @@ import { Divider } from "@/components/divider" import { Icons } from "@/components/icons" import { LABELS } from "@/app/labels" import { interpolate } from "@/lib/utils" +import { Metadata } from "next" + +export const metadata: Metadata = { + title: "About", + description: "About the Privacy & Scaling Explorations community", +} export default async function AboutPage() { const principles = LABELS.ABOUT_PAGE.PRINCIPLES diff --git a/app/(pages)/not-found.tsx b/app/(pages)/not-found.tsx index 7a78171..fd1bb8a 100644 --- a/app/(pages)/not-found.tsx +++ b/app/(pages)/not-found.tsx @@ -12,6 +12,7 @@ import { LABELS } from "../labels" export const metadata: Metadata = { title: "404: Page Not Found", + description: "The requested page could not be found", } export default function NotFound() { diff --git a/app/(pages)/projects/[id]/page.tsx b/app/(pages)/projects/[id]/page.tsx index 0ac0a85..49cc4ae 100644 --- a/app/(pages)/projects/[id]/page.tsx +++ b/app/(pages)/projects/[id]/page.tsx @@ -2,6 +2,7 @@ import { Metadata } from "next" import { ProjectInterface } from "@/lib/types" import { ProjectContent } from "../sections/ProjectContent" +import { getProjects, Project } from "@/lib/content" type PageProps = { params: { id: string } @@ -12,18 +13,22 @@ export interface ProjectProps { project: ProjectInterface } -/* export async function generateMetadata({ params, }: PageProps): Promise { - const response = await fetch("/api/projects") - const projects = await response.json() - + const projects = await getProjects() const project = projects.find( - (p: ProjectInterface) => + (p: Project) => String(p.id?.toLowerCase()) === params.id.toString().toLowerCase() ) - + + if (!project) { + return { + title: "Project Not Found", + description: "The requested project could not be found", + } + } + const content = project?.content const imageUrl = (project?.image ?? "")?.length > 0 @@ -43,7 +48,7 @@ export async function generateMetadata({ ], }, } -}*/ +} export default async function ProjectDetailPage({ params }: PageProps) { return diff --git a/app/(pages)/resources/page.tsx b/app/(pages)/resources/page.tsx index ea13570..f9bf3be 100644 --- a/app/(pages)/resources/page.tsx +++ b/app/(pages)/resources/page.tsx @@ -1,9 +1,6 @@ -"use client" - -import { useCallback, useEffect, useRef, useState } from "react" import Link from "next/link" -import { cn, interpolate } from "@/lib/utils" +import { interpolate } from "@/lib/utils" import { siteConfig } from "@/config/site" import { LABELS } from "@/app/labels" @@ -15,6 +12,13 @@ import { Button } from "@/components/ui/button" import { Label } from "@/components/ui/label" import ResourcesContent from "@/content/resources.md" +import { Metadata } from "next" +import { ResourceNav } from "@/components/resources/ResourceNav" + +export const metadata: Metadata = { + title: "Resources", + description: "Resources for the Privacy & Scaling Explorations community", +} interface ResourceItemProps { label: string @@ -85,87 +89,6 @@ const ResourceCard = ({ id, title, children }: ResourceCardProps) => { ) } -const ResourceNav = () => { - const SCROLL_OFFSET = 80 - const [activeId, setActiveId] = useState("") - const [isManualScroll, setIsManualScroll] = useState(false) - const ID_LABELS_MAPPING: Record = { - "get-involved": LABELS.RESOURCES_PAGE.NAV.GET_INVOLVED, - learn: LABELS.RESOURCES_PAGE.NAV.LEARN, - build: LABELS.RESOURCES_PAGE.NAV.BUILD, - design: LABELS.RESOURCES_PAGE.NAV.DESIGN, - } - const sectionsRef = useRef | null>(null) // sections are constant so useRef might be better here - - useEffect(() => { - if (sectionsRef.current === null) - sectionsRef.current = document.querySelectorAll("div[data-section]") - if (!activeId) setActiveId("get-involved") - - const handleScroll = () => { - if (isManualScroll) return - - sectionsRef.current?.forEach((section: any) => { - const sectionTop = section.offsetTop - SCROLL_OFFSET - if (window.scrollY >= sectionTop && window.scrollY > 0) { - setActiveId(section.getAttribute("id")) - } - }) - } - - window.addEventListener("scroll", handleScroll) - return () => window.removeEventListener("scroll", handleScroll) - }, [activeId, isManualScroll]) - - const scrollToId = useCallback((id: string) => { - const element = document.getElementById(id) - const top = element?.offsetTop ?? 0 - - if (element) { - setActiveId(id) // active clicked id - setIsManualScroll(true) // tell the window event listener to ignore this scrolling - window?.scrollTo({ - behavior: "smooth", - top: (top ?? 0) - SCROLL_OFFSET, - }) - } - - setTimeout(() => setIsManualScroll(false), 800) - }, []) - - return ( -
-
-
- {LABELS.RESOURCES_PAGE.ON_THIS_PAGE} -
-
    - {Object.entries(ID_LABELS_MAPPING).map(([id, label]) => { - const active = id === activeId - return ( -
  • { - scrollToId(id) - }} - data-id={id} - className={cn( - "flex h-8 cursor-pointer items-center border-l-2 border-l-anakiwa-200 px-3 duration-200", - { - "border-l-anakiwa-500 text-anakiwa-500 font-medium": active, - } - )} - > - {label} -
  • - ) - })} -
-
-
- ) -} - export default function ResourcePage() { return (
diff --git a/app/layout.tsx b/app/layout.tsx index 2fa3677..dc6915e 100644 --- a/app/layout.tsx +++ b/app/layout.tsx @@ -54,8 +54,9 @@ export const viewport: Viewport = { export const metadata: Metadata = { title: { default: siteConfig.name, - template: `%s | ${siteConfig.name}`, + template: "%s | PSE", }, + metadataBase: new URL("https://pse.dev"), description: siteConfig.description, keywords: [ "Privacy & Scaling Explorations", diff --git a/components/resources/ResourceNav.tsx b/components/resources/ResourceNav.tsx new file mode 100644 index 0000000..282417b --- /dev/null +++ b/components/resources/ResourceNav.tsx @@ -0,0 +1,86 @@ +"use client" + +import { LABELS } from "@/app/labels" +import { useCallback, useEffect, useRef, useState } from "react" +import { cn } from "@/lib/utils" + +export const ResourceNav = () => { + const SCROLL_OFFSET = 80 + const [activeId, setActiveId] = useState("") + const [isManualScroll, setIsManualScroll] = useState(false) + const ID_LABELS_MAPPING: Record = { + "get-involved": LABELS.RESOURCES_PAGE.NAV.GET_INVOLVED, + learn: LABELS.RESOURCES_PAGE.NAV.LEARN, + build: LABELS.RESOURCES_PAGE.NAV.BUILD, + design: LABELS.RESOURCES_PAGE.NAV.DESIGN, + } + const sectionsRef = useRef | null>(null) // sections are constant so useRef might be better here + + useEffect(() => { + if (sectionsRef.current === null) + sectionsRef.current = document.querySelectorAll("div[data-section]") + if (!activeId) setActiveId("get-involved") + + const handleScroll = () => { + if (isManualScroll) return + + sectionsRef.current?.forEach((section: any) => { + const sectionTop = section.offsetTop - SCROLL_OFFSET + if (window.scrollY >= sectionTop && window.scrollY > 0) { + setActiveId(section.getAttribute("id")) + } + }) + } + + window.addEventListener("scroll", handleScroll) + return () => window.removeEventListener("scroll", handleScroll) + }, [activeId, isManualScroll]) + + const scrollToId = useCallback((id: string) => { + const element = document.getElementById(id) + const top = element?.offsetTop ?? 0 + + if (element) { + setActiveId(id) // active clicked id + setIsManualScroll(true) // tell the window event listener to ignore this scrolling + window?.scrollTo({ + behavior: "smooth", + top: (top ?? 0) - SCROLL_OFFSET, + }) + } + + setTimeout(() => setIsManualScroll(false), 800) + }, []) + + return ( +
+
+
+ {LABELS.RESOURCES_PAGE.ON_THIS_PAGE} +
+
    + {Object.entries(ID_LABELS_MAPPING).map(([id, label]) => { + const active = id === activeId + return ( +
  • { + scrollToId(id) + }} + data-id={id} + className={cn( + "flex h-8 cursor-pointer items-center border-l-2 border-l-anakiwa-200 px-3 duration-200", + { + "border-l-anakiwa-500 text-anakiwa-500 font-medium": active, + } + )} + > + {label} +
  • + ) + })} +
+
+
+ ) +} diff --git a/config/site.ts b/config/site.ts index c7b10cf..da99ec5 100644 --- a/config/site.ts +++ b/config/site.ts @@ -1,7 +1,7 @@ export type SiteConfig = typeof siteConfig export const siteConfig = { - name: "PSE", + name: "Privacy & Scaling Explorations", description: "Enhancing Ethereum through cryptographic research and collective experimentation.", url: "https://pse.dev",