diff --git a/Dockerfile b/Dockerfile index 09513915..95b0f03e 100644 --- a/Dockerfile +++ b/Dockerfile @@ -3,11 +3,17 @@ RUN apk add --no-cache git curl WORKDIR /builder -COPY package.json yarn.lock ./ +# Copy manifests, lock file, and yarn config +COPY package.json yarn.lock .yarnrc.yml ./ + +# Enable corepack and install dependencies using the lockfile RUN corepack enable -RUN yarn install +RUN yarn install --immutable +# Copy the rest of the application code COPY . . + +# Build the application RUN yarn build # Create image by copying build artifacts diff --git a/app/[lang]/about/page.tsx b/app/[lang]/about/page.tsx index 2899b3be..0aa12c48 100644 --- a/app/[lang]/about/page.tsx +++ b/app/[lang]/about/page.tsx @@ -1,4 +1,3 @@ -import Image from "next/image" import Link from "next/link" import { siteConfig } from "@/config/site" @@ -22,20 +21,13 @@ export default async function AboutPage({ params: { lang } }: any) { }) as any[]) ?? [] return ( - - - } - actions={ +
+
+ + +
+ {t("description")} +
- } - /> - -
- - - { - return { - label: principle.title, - value: index.toString(), - children: ( - - {principle.description.map( - (description: string, index: number) => { - return

{description}

- } - )} -
- ), - } - }), - ]} - />
+ +
+ + + { + return { + label: principle.title, + value: index.toString(), + children: ( + + {principle.description.map( + (description: string, index: number) => { + return

{description}

+ } + )} +
+ ), + } + }), + ]} + /> +
+
- - - - - -
+ + + + + + +
) } diff --git a/app/[lang]/blog/[slug]/page.tsx b/app/[lang]/blog/[slug]/page.tsx index 7e737ac0..2116b941 100644 --- a/app/[lang]/blog/[slug]/page.tsx +++ b/app/[lang]/blog/[slug]/page.tsx @@ -1,10 +1,12 @@ import { blogArticleCardTagCardVariants } from "@/components/blog/blog-article-card" import { BlogContent } from "@/components/blog/blog-content" import { AppContent } from "@/components/ui/app-content" +import { Button } from "@/components/ui/button" import { Label } from "@/components/ui/label" import { Markdown } from "@/components/ui/markdown" import { getArticles, getArticleById } from "@/lib/blog" import { Metadata } from "next" +import Link from "next/link" export const generateStaticParams = async () => { const articles = await getArticles() @@ -81,6 +83,18 @@ export default function BlogArticle({ params }: any) { {post?.tldr && {post?.tldr}}
) : null} + {(post?.tags ?? [])?.length > 0 && ( +
+ Tags: +
+ {post?.tags?.map((tag) => ( + + + + ))} +
+
+ )} diff --git a/app/[lang]/blog/page.tsx b/app/[lang]/blog/page.tsx index 28ba14c8..7935f800 100644 --- a/app/[lang]/blog/page.tsx +++ b/app/[lang]/blog/page.tsx @@ -2,6 +2,7 @@ import { useTranslation } from "@/app/i18n" import { BlogArticles } from "@/components/blog/blog-articles" import { AppContent } from "@/components/ui/app-content" import { Label } from "@/components/ui/label" +import { getArticles } from "@/lib/blog" import { Metadata } from "next" export const metadata: Metadata = { @@ -9,14 +10,30 @@ export const metadata: Metadata = { description: "", } -const BlogPage = async ({ params: { lang } }: any) => { +interface BlogPageProps { + params: { lang: string } + searchParams?: { [key: string]: string | string[] | undefined } +} + +const BlogPage = async ({ params: { lang }, searchParams }: BlogPageProps) => { const { t } = await useTranslation(lang, "blog-page") + // Get the tag from searchParams + const tag = searchParams?.tag as string | undefined + + // Fetch articles, filtering by tag if present + const articles = await getArticles({ tag }) + return (
-
+
+ {tag && ( +

+ {`Filtered by tag: "${tag}"`} +

+ )}
{t("subtitle")}
@@ -24,7 +41,8 @@ const BlogPage = async ({ params: { lang } }: any) => {
- + {/* Pass fetched articles and lang to the component */} +
) diff --git a/app/[lang]/projects/page.tsx b/app/[lang]/projects/page.tsx index e1bdb361..735d8f67 100644 --- a/app/[lang]/projects/page.tsx +++ b/app/[lang]/projects/page.tsx @@ -20,7 +20,7 @@ export default async function ProjectsPage({ params: { lang } }: any) { return (
-
+
diff --git a/app/[lang]/research/page.tsx b/app/[lang]/research/page.tsx index bd1dc0de..112c06ad 100644 --- a/app/[lang]/research/page.tsx +++ b/app/[lang]/research/page.tsx @@ -17,14 +17,9 @@ export const metadata: Metadata = { const ResearchPage = async ({ params: { lang } }: any) => { const { t } = await useTranslation(lang, "research-page") return ( -
-
- +
+
+
{t("subtitle")} diff --git a/app/[lang]/resources/page.tsx b/app/[lang]/resources/page.tsx index 88e840d0..b1aa2f12 100644 --- a/app/[lang]/resources/page.tsx +++ b/app/[lang]/resources/page.tsx @@ -1,7 +1,6 @@ "use client" import { useCallback, useEffect, useRef, useState } from "react" -import Image from "next/image" import Link from "next/link" import { LangProps } from "@/types/common" @@ -173,21 +172,13 @@ export default function ResourcePage({ params: { lang } }: LangProps) { const { t: common } = useTranslation(lang, "common") return ( - - - illustrations -
- } - actions={ +
+
+ + +
+ {t("subtitle")} +
- } - /> -
- -
-
- ( - - ), - ResourceCard: (props: ResourceCardProps) => ( - - ), - }} - /> -
-
-
-
- -
-
- - {t("addResourceQuestion")} -
- } - > - - - - - +
+
+ +
+
+
+
+ + {t("addResourceQuestion")} + + } + > + + + + + +
) } diff --git a/articles/README.md b/articles/README.md index 1129af47..3f77e734 100644 --- a/articles/README.md +++ b/articles/README.md @@ -19,6 +19,7 @@ image: "/articles/my-new-article/cover.webp" # Image used as cover tldr: "A brief summary of your article" #Short summary date: "YYYY-MM-DD" # Publication date in ISO format canonical: "mirror.xyz/my-new-article" # (Optional) The original source URL, this tells search engines the primary version of the content +tags: ["tag1", "tag2"] # (Optional) Add relevant tags as an array of strings to categorize the article --- ``` diff --git a/components/blog/blog-articles.tsx b/components/blog/blog-articles.tsx index 78d29b3a..33857d55 100644 --- a/components/blog/blog-articles.tsx +++ b/components/blog/blog-articles.tsx @@ -1,15 +1,24 @@ -import { Article, getArticles } from "@/lib/blog" +import { Article } from "@/lib/blog" import Link from "next/link" import { BlogArticleCard } from "./blog-article-card" -export const BlogArticles = () => { - const articles = getArticles() +interface BlogArticlesProps { + articles: Article[] + lang: string // Add lang prop for correct linking +} +export const BlogArticles = ({ articles, lang }: BlogArticlesProps) => { return (
+ {articles.length === 0 && ( +

+ No articles found for this tag. +

+ )} {articles.map( ({ id, title, image, tldr = "", date, authors, content }: Article) => { - const url = `/blog/${id}` + // Use lang parameter for correct article URL + const url = `/${lang}/blog/${id}` return ( { return (
@@ -27,7 +29,7 @@ const PageHeader = ({
- + {subtitle && (
{subtitle} diff --git a/components/research/research-list.tsx b/components/research/research-list.tsx index ab1a753d..8b340568 100644 --- a/components/research/research-list.tsx +++ b/components/research/research-list.tsx @@ -55,7 +55,7 @@ export const ResearchList = ({ lang }: LangProps["params"]) => { if (!isMounted) { return ( -
+
{
{!hasActiveFilters && ( diff --git a/components/sections/HomepageHeader.tsx b/components/sections/HomepageHeader.tsx index e4be411a..a29dd71f 100644 --- a/components/sections/HomepageHeader.tsx +++ b/components/sections/HomepageHeader.tsx @@ -22,7 +22,7 @@ export const HomepageHeader = ({ lang }: { lang: any }) => { animate={{ y: 0, opacity: 1 }} transition={{ duration: 0.8, cubicBezier: "easeOut" }} > - + } subtitle={t("headerSubtitle")} diff --git a/components/ui/button.tsx b/components/ui/button.tsx index dcf7373c..d3b849a5 100644 --- a/components/ui/button.tsx +++ b/components/ui/button.tsx @@ -25,6 +25,7 @@ const buttonVariants = cva( }, size: { default: "h-10 py-2 px-4", + xs: "h-8 px-2 rounded-md", sm: "h-9 px-3 rounded-md", lg: "h-11 px-8 rounded-md", }, diff --git a/components/ui/label.tsx b/components/ui/label.tsx index 60bf5d43..d97fc70b 100644 --- a/components/ui/label.tsx +++ b/components/ui/label.tsx @@ -3,6 +3,7 @@ import { cn } from "@/lib/utils" interface LabelProps { label: React.ReactNode className?: string + size?: "small" | "large" } const SectionTitle = ({ label, className = "" }: LabelProps) => { @@ -18,11 +19,16 @@ const SectionTitle = ({ label, className = "" }: LabelProps) => { ) } -const MainPageTitle = ({ label, className = "" }: LabelProps) => { +const MainPageTitle = ({ + label, + className = "", + size = "small", +}: LabelProps) => { return ( diff --git a/lib/blog.ts b/lib/blog.ts index 6ae1720e..7c190245 100644 --- a/lib/blog.ts +++ b/lib/blog.ts @@ -15,12 +15,14 @@ export interface Article { publicKey?: string hash?: string canonical?: string + tags?: string[] } const articlesDirectory = path.join(process.cwd(), "articles") // Get all articles from /articles -export function getArticles(limit: number = 1000) { +export function getArticles(options?: { limit?: number; tag?: string }) { + const { limit = 1000, tag } = options ?? {} // Get file names under /articles const fileNames = fs.readdirSync(articlesDirectory) const allArticlesData = fileNames.map((fileName: string) => { @@ -53,9 +55,18 @@ export function getArticles(limit: number = 1000) { }, }) + // Ensure tags are always an array, combining 'tags' and 'tag' + const tags = [ + ...(Array.isArray(matterResult.data?.tags) + ? matterResult.data.tags + : []), + ...(matterResult.data?.tag ? [matterResult.data.tag] : []), + ] + return { id, ...matterResult.data, + tags: tags, // Assign the combined and normalized tags array content: matterResult.content, } } catch (error) { @@ -66,14 +77,23 @@ export function getArticles(limit: number = 1000) { title: `Error processing ${id}`, content: "This article could not be processed due to an error.", date: new Date().toISOString().split("T")[0], + tags: [], } } }) + let filteredArticles = allArticlesData.filter(Boolean) as Article[] + + // Filter by tag if provided + if (tag) { + filteredArticles = filteredArticles.filter((article) => + article.tags?.includes(tag) + ) + } + // Sort posts by date - return allArticlesData - .filter(Boolean) - .sort((a: any, b: any) => { + return filteredArticles + .sort((a, b) => { const dateA = new Date(a.date) const dateB = new Date(b.date) @@ -81,11 +101,13 @@ export function getArticles(limit: number = 1000) { return dateB.getTime() - dateA.getTime() }) .slice(0, limit) - .filter((article: any) => article.id !== "_article-template") as Article[] + .filter((article) => article.id !== "_article-template") } export function getArticleById(slug?: string) { - const articles = getArticles() + // Note: This might need adjustment if you expect getArticleById to also have tags + // Currently relies on the base getArticles() which fetches all tags + const articles = getArticles() // Fetch all articles to find the one by ID return articles.find((article) => article.id === slug) } diff --git a/tailwind.config.js b/tailwind.config.js index 81ee6d8f..be1d4792 100644 --- a/tailwind.config.js +++ b/tailwind.config.js @@ -31,6 +31,8 @@ module.exports = { "linear-gradient(180deg, #C2E8F5 -17.44%, #FFF 17.72%)", "research-card-gradient": "linear-gradient(84deg, #FFF -1.95%, #EAFAFF 59.98%, #FFF 100.64%)", + "page-header-gradient": + "linear-gradient(180deg, #C2E8F5 -17.44%, #FFF 62.5%)", }, colors: { corduroy: "#4A5754",