feat(website): Next.js 16 marketing website with RTL/Farsi

Marketing website for Meezi platform:
- Server-side rendered pages: home, demo, blog, pricing
- RTL/Farsi layout with Vazirmatn font
- SEO metadata and Open Graph tags
- proxy.ts for Next.js 16 middleware convention
- MEEZI_API_URL internal Docker network routing

Co-Authored-By: Claude Sonnet 4.5 <noreply@anthropic.com>
This commit is contained in:
soroush.asadi
2026-05-27 21:34:32 +03:30
parent 131ecdbbe6
commit d62bb8d3ad
84 changed files with 16985 additions and 0 deletions
+51
View File
@@ -0,0 +1,51 @@
import fs from "fs";
import path from "path";
import matter from "gray-matter";
import readingTime from "reading-time";
const BLOG_DIR = path.join(process.cwd(), "src/content/blog");
export interface BlogPost {
slug: string;
title: string;
excerpt: string;
date: string;
author: string;
category: string;
tags: string[];
coverImage?: string;
readingTime: string;
content: string;
}
export function getAllPosts(locale: "fa" | "en" = "fa"): BlogPost[] {
if (!fs.existsSync(BLOG_DIR)) return [];
const files = fs.readdirSync(BLOG_DIR).filter((f) => f.endsWith(`.${locale}.mdx`));
return files
.map((file) => {
const slug = file.replace(`.${locale}.mdx`, "");
return getPostBySlug(slug, locale);
})
.filter(Boolean)
.sort((a, b) => new Date(b!.date).getTime() - new Date(a!.date).getTime()) as BlogPost[];
}
export function getPostBySlug(slug: string, locale: "fa" | "en" = "fa"): BlogPost | null {
const filePath = path.join(BLOG_DIR, `${slug}.${locale}.mdx`);
if (!fs.existsSync(filePath)) return null;
const raw = fs.readFileSync(filePath, "utf8");
const { data, content } = matter(raw);
const rt = readingTime(content);
return {
slug,
title: data.title ?? "",
excerpt: data.excerpt ?? "",
date: data.date ?? "",
author: data.author ?? "",
category: data.category ?? "",
tags: data.tags ?? [],
coverImage: data.coverImage,
readingTime: locale === "fa" ? `${Math.ceil(rt.minutes)} دقیقه مطالعه` : rt.text,
content,
};
}
+6
View File
@@ -0,0 +1,6 @@
import { clsx, type ClassValue } from "clsx";
import { twMerge } from "tailwind-merge";
export function cn(...inputs: ClassValue[]) {
return twMerge(clsx(inputs));
}