phase 1-2 done

This commit is contained in:
Ali Taghavi
2026-04-24 03:42:34 +03:30
commit 95a546df11
83 changed files with 17168 additions and 0 deletions

View File

@@ -0,0 +1,9 @@
export default function AboutPage() {
return (
<div className="mx-auto max-w-6xl px-6 py-32">
<span className="font-mono text-xs text-accent">// about</span>
<h1 className="mt-4 text-4xl font-bold text-text-primary">درباره علی تقوی</h1>
<p className="mt-4 text-text-secondary">در حال آمادهسازی Phase 3</p>
</div>
);
}

View File

@@ -0,0 +1,9 @@
export default function ContactPage() {
return (
<div className="mx-auto max-w-6xl px-6 py-32">
<span className="font-mono text-xs text-accent">// contact</span>
<h1 className="mt-4 text-4xl font-bold text-text-primary">تماس</h1>
<p className="mt-4 text-text-secondary">در حال آمادهسازی Phase 3</p>
</div>
);
}

View File

@@ -0,0 +1,96 @@
import type { Metadata } from "next";
import { notFound } from "next/navigation";
import { NextIntlClientProvider } from "next-intl";
import { getMessages } from "next-intl/server";
import { jetbrainsMono, plusJakartaSans } from "@/lib/fonts";
import { locales, localeDir, type Locale } from "@/lib/i18n/config";
import { Header } from "@/components/layout/Header";
import { Footer } from "@/components/layout/Footer";
import { cn } from "@/lib/utils";
interface LocaleLayoutProps {
children: React.ReactNode;
params: Promise<{ locale: string }>;
}
export function generateStaticParams() {
return locales.map((locale) => ({ locale }));
}
export async function generateMetadata({
params,
}: {
params: Promise<{ locale: string }>;
}): Promise<Metadata> {
const { locale } = await params;
const isRtl = locale === "fa";
return {
alternates: {
canonical: `https://biztaghavi.com/${locale}`,
languages: {
fa: "https://biztaghavi.com/fa",
en: "https://biztaghavi.com/en",
},
},
other: {
"content-language": isRtl ? "fa-IR" : "en-US",
},
};
}
export default async function LocaleLayout({
children,
params,
}: LocaleLayoutProps) {
const { locale } = await params;
if (!locales.includes(locale as Locale)) {
notFound();
}
const messages = await getMessages();
const dir = localeDir[locale as Locale];
const jsonLd = {
"@context": "https://schema.org",
"@type": "Person",
name: "Ali Taghavi",
alternateName: "علی تقوی",
url: "https://biztaghavi.com",
jobTitle: "CEO",
worksFor: { "@type": "Organization", name: "NODE-Group" },
sameAs: [
"https://github.com/alitaghavi",
"https://linkedin.com/in/alitaghavi",
"https://twitter.com/alitaghavi",
],
};
return (
<html
lang={locale}
dir={dir}
className={cn(
jetbrainsMono.variable,
plusJakartaSans.variable,
"h-full antialiased"
)}
>
<head>
<script
type="application/ld+json"
dangerouslySetInnerHTML={{ __html: JSON.stringify(jsonLd) }}
/>
<link rel="alternate" type="application/rss+xml" title="علی تقوی — نوشته‌ها" href="/api/rss" />
</head>
<body className="relative flex min-h-full flex-col bg-background text-text-primary">
<NextIntlClientProvider messages={messages}>
<Header />
<main className="flex-1 relative z-10">{children}</main>
<Footer />
</NextIntlClientProvider>
</body>
</html>
);
}

22
src/app/[locale]/page.tsx Normal file
View File

@@ -0,0 +1,22 @@
import { AnimatedSection } from "@/components/shared/AnimatedSection";
import { Hero } from "@/components/home/Hero";
import { BuildingNow } from "@/components/home/BuildingNow";
import { LatestWriting } from "@/components/home/LatestWriting";
import { SignalCTA } from "@/components/home/SignalCTA";
export default function HomePage() {
return (
<>
<Hero />
<AnimatedSection>
<BuildingNow />
</AnimatedSection>
<AnimatedSection delay={0.1}>
<LatestWriting />
</AnimatedSection>
<AnimatedSection delay={0.2}>
<SignalCTA />
</AnimatedSection>
</>
);
}

View File

@@ -0,0 +1,150 @@
import type { Metadata } from "next";
import { notFound } from "next/navigation";
import Image from "next/image";
import { getTranslations } from "next-intl/server";
import { ExternalLink, Github, ArrowLeft, ArrowRight } from "lucide-react";
import { Link } from "@/lib/i18n/navigation";
import { sanityFetch } from "@/lib/sanity/client";
import { projectBySlugQuery, allProjectSlugsQuery } from "@/lib/sanity/queries";
import { urlFor } from "@/lib/sanity/image";
import { PortableTextRenderer } from "@/components/writing/PortableTextRenderer";
import { Badge } from "@/components/ui/badge";
import { Button } from "@/components/ui/button";
import type { Project } from "@/lib/sanity/types";
interface Props {
params: Promise<{ locale: string; slug: string }>;
}
export async function generateStaticParams() {
const slugs = await sanityFetch<{ slug: string }[]>(allProjectSlugsQuery);
return (slugs ?? []).map((s) => ({ slug: s.slug }));
}
export async function generateMetadata({ params }: Props): Promise<Metadata> {
const { slug } = await params;
const project = await sanityFetch<Project>(projectBySlugQuery, { slug });
if (!project) return {};
return {
title: project.seo?.title ?? project.title,
description: project.seo?.description ?? project.description,
};
}
export default async function ProjectPage({ params }: Props) {
const { locale, slug } = await params;
const [project, t, tWork] = await Promise.all([
sanityFetch<Project>(projectBySlugQuery, { slug }),
getTranslations({ locale, namespace: "common" }),
getTranslations({ locale, namespace: "work" }),
]);
if (!project) notFound();
const isRtl = locale === "fa";
const BackArrow = isRtl ? ArrowRight : ArrowLeft;
const coverUrl = project.coverImage?.asset
? urlFor(project.coverImage).width(1400).height(600).url()
: null;
return (
<article className="relative z-10 pt-24">
<div className="mx-auto max-w-4xl px-6 py-4">
<Link
href="/work"
className="inline-flex items-center gap-2 text-sm text-text-secondary hover:text-accent transition-colors"
>
<BackArrow size={14} />
{t("back")}
</Link>
</div>
{/* Cover */}
{coverUrl && (
<div className="relative mb-12 h-64 w-full overflow-hidden md:h-96">
<Image src={coverUrl} alt={project.coverImage?.alt ?? project.title} fill className="object-cover" priority />
<div className="absolute inset-0 bg-gradient-to-b from-transparent to-background/80" />
</div>
)}
<div className="mx-auto max-w-4xl px-6 pb-16">
{/* Header */}
<header className="mb-10">
<Badge variant="secondary" className="mb-4">{project.projectType}</Badge>
<h1 className="mb-4 text-3xl font-bold text-text-primary md:text-4xl">{project.title}</h1>
{project.description && (
<p className="text-lg text-text-secondary leading-relaxed">{project.description}</p>
)}
{/* CTA buttons */}
<div className="mt-6 flex flex-wrap gap-3">
{project.liveUrl && (
<Button asChild>
<a href={project.liveUrl} target="_blank" rel="noopener noreferrer">
<ExternalLink size={15} /> {t("live_demo")}
</a>
</Button>
)}
{project.githubUrl && (
<Button asChild variant="secondary">
<a href={project.githubUrl} target="_blank" rel="noopener noreferrer">
<Github size={15} /> {t("source_code")}
</a>
</Button>
)}
</div>
</header>
{/* Body */}
{project.body && <PortableTextRenderer value={project.body as unknown[]} />}
{/* Tech stack + tools */}
<div className="mt-10 grid gap-6 border-t border-border pt-8 sm:grid-cols-2">
{project.techStack && project.techStack.length > 0 && (
<div>
<h3 className="mb-3 font-mono text-xs text-accent">// {tWork("tech_stack")}</h3>
<div className="flex flex-wrap gap-2">
{project.techStack.map((t) => (
<span key={t} className="rounded-full border border-border px-3 py-1 font-mono text-xs text-text-secondary">
{t}
</span>
))}
</div>
</div>
)}
{project.toolsUsed && project.toolsUsed.length > 0 && (
<div>
<h3 className="mb-3 font-mono text-xs text-accent">// {tWork("tools")}</h3>
<div className="flex flex-wrap gap-2">
{project.toolsUsed.map((tool) => (
<span key={tool} className="rounded-full border border-border px-3 py-1 font-mono text-xs text-text-secondary">
{tool}
</span>
))}
</div>
</div>
)}
</div>
{/* Gallery */}
{project.gallery && project.gallery.length > 0 && (
<div className="mt-12">
<h3 className="mb-4 font-mono text-xs text-accent">// gallery</h3>
<div className="grid gap-4 sm:grid-cols-2">
{project.gallery.map((img, i) => (
<div key={i} className="relative aspect-video overflow-hidden rounded-lg border border-border">
<Image
src={urlFor(img).width(800).url()}
alt={img.alt ?? `Gallery ${i + 1}`}
fill
className="object-cover"
/>
</div>
))}
</div>
</div>
)}
</div>
</article>
);
}

View File

@@ -0,0 +1,35 @@
import type { Metadata } from "next";
import { getTranslations } from "next-intl/server";
import { SectionHeader } from "@/components/shared/SectionHeader";
import { WorkListClient } from "@/components/work/WorkListClient";
import { sanityFetch } from "@/lib/sanity/client";
import { allProjectsQuery } from "@/lib/sanity/queries";
import type { Project } from "@/lib/sanity/types";
interface Props {
params: Promise<{ locale: string }>;
}
export async function generateMetadata({ params }: Props): Promise<Metadata> {
const { locale } = await params;
const t = await getTranslations({ locale, namespace: "work" });
return { title: t("title"), description: t("subtitle") };
}
export default async function WorkPage({ params }: Props) {
const { locale } = await params;
const t = await getTranslations({ locale, namespace: "work" });
const projects = await sanityFetch<Project[]>(allProjectsQuery, { locale });
return (
<div className="relative z-10 mx-auto max-w-6xl px-6 py-24 pt-32">
<SectionHeader label="work" />
<div className="mb-12">
<h1 className="text-4xl font-bold text-text-primary md:text-5xl">{t("title")}</h1>
<p className="mt-4 max-w-2xl text-text-secondary">{t("subtitle")}</p>
</div>
<WorkListClient projects={projects ?? []} />
</div>
);
}

View File

@@ -0,0 +1,169 @@
import type { Metadata } from "next";
import { notFound } from "next/navigation";
import Image from "next/image";
import { getTranslations } from "next-intl/server";
import { ArrowRight, ArrowLeft, Clock } from "lucide-react";
import { Link } from "@/lib/i18n/navigation";
import { sanityFetch } from "@/lib/sanity/client";
import { postBySlugQuery, relatedPostsQuery, allPostSlugsQuery } from "@/lib/sanity/queries";
import { urlFor } from "@/lib/sanity/image";
import { formatDate, estimateReadTime } from "@/lib/sanity/utils";
import { PortableTextRenderer } from "@/components/writing/PortableTextRenderer";
import { ReadingProgress } from "@/components/writing/ReadingProgress";
import { ShareButtons } from "@/components/writing/ShareButtons";
import { PostCard } from "@/components/writing/PostCard";
import { Badge } from "@/components/ui/badge";
import type { Post } from "@/lib/sanity/types";
interface Props {
params: Promise<{ locale: string; slug: string }>;
}
export async function generateStaticParams() {
const slugs = await sanityFetch<{ slug: string; locale: string }[]>(allPostSlugsQuery);
return (slugs ?? []).map((s) => ({ slug: s.slug }));
}
export async function generateMetadata({ params }: Props): Promise<Metadata> {
const { slug } = await params;
const post = await sanityFetch<Post>(postBySlugQuery, { slug });
if (!post) return {};
return {
title: post.seo?.title ?? post.title,
description: post.seo?.description ?? post.excerpt,
openGraph: {
title: post.seo?.title ?? post.title,
description: post.seo?.description ?? post.excerpt,
images: post.seo?.ogImage?.url
? [{ url: post.seo.ogImage.url }]
: post.coverImage?.asset
? [{ url: urlFor(post.coverImage).width(1200).height(630).url() }]
: [],
},
};
}
export default async function PostPage({ params }: Props) {
const { locale, slug } = await params;
const [post, t, tCommon, tCat] = await Promise.all([
sanityFetch<Post>(postBySlugQuery, { slug }),
getTranslations({ locale, namespace: "writing" }),
getTranslations({ locale, namespace: "common" }),
getTranslations({ locale, namespace: "writing.categories" }),
]);
if (!post) notFound();
const related = await sanityFetch<Post[]>(relatedPostsQuery, {
locale,
category: post.category,
slug,
});
const readTime = post.body ? estimateReadTime(post.body as unknown[]) : null;
const coverUrl = post.coverImage?.asset
? urlFor(post.coverImage).width(1400).height(600).url()
: null;
const postUrl = `https://biztaghavi.com/${locale}/writing/${slug}`;
const isRtl = locale === "fa";
const BackArrow = isRtl ? ArrowRight : ArrowLeft;
return (
<>
<ReadingProgress />
<article className="relative z-10 pt-24">
{/* Back link */}
<div className="mx-auto max-w-4xl px-6 py-4">
<Link
href="/writing"
className="inline-flex items-center gap-2 text-sm text-text-secondary hover:text-accent transition-colors"
>
<BackArrow size={14} />
{tCommon("back")}
</Link>
</div>
{/* Header */}
<header className="mx-auto max-w-4xl px-6 pb-8">
<div className="mb-4 flex flex-wrap items-center gap-3">
<Badge>{tCat(post.category)}</Badge>
<span className="font-mono text-xs text-text-secondary">
{formatDate(post.publishedAt, locale)}
</span>
{readTime && (
<span className="flex items-center gap-1 font-mono text-xs text-text-secondary">
<Clock size={12} />
{readTime} {tCommon("min_read")}
</span>
)}
</div>
<h1 className="mb-4 text-3xl font-bold leading-tight text-text-primary md:text-4xl lg:text-5xl">
{post.title}
</h1>
{post.excerpt && (
<p className="text-lg text-text-secondary leading-relaxed">{post.excerpt}</p>
)}
<div className="mt-6 flex items-center justify-between gap-4">
{post.author && (
<div className="flex items-center gap-2 text-sm text-text-secondary">
<span>{post.author.name}</span>
</div>
)}
<ShareButtons title={post.title} url={postUrl} />
</div>
</header>
{/* Cover image */}
{coverUrl && (
<div className="relative mb-12 h-64 w-full overflow-hidden md:h-96">
<Image
src={coverUrl}
alt={post.coverImage?.alt ?? post.title}
fill
className="object-cover"
priority
/>
<div className="absolute inset-0 bg-gradient-to-b from-transparent to-background/80" />
</div>
)}
{/* Body */}
<div className="mx-auto max-w-3xl px-6 pb-16">
{post.body && <PortableTextRenderer value={post.body as unknown[]} />}
{/* Tags */}
{post.tags && post.tags.length > 0 && (
<div className="mt-10 flex flex-wrap gap-2 border-t border-border pt-6">
{post.tags.map((tag) => (
<Badge key={tag._id} variant="secondary">{tag.title}</Badge>
))}
</div>
)}
{/* Bottom share */}
<div className="mt-8 flex justify-end">
<ShareButtons title={post.title} url={postUrl} />
</div>
</div>
{/* Related posts */}
{related && related.length > 0 && (
<section className="border-t border-border bg-surface/30 px-6 py-16">
<div className="mx-auto max-w-6xl">
<h2 className="mb-8 font-mono text-xs text-accent">// {tCommon("related_posts")}</h2>
<div className="grid gap-5 sm:grid-cols-2 lg:grid-cols-3">
{related.map((p) => (
<PostCard key={p._id} post={p} categoryLabel={tCat(p.category)} />
))}
</div>
</div>
</section>
)}
</article>
</>
);
}

View File

@@ -0,0 +1,38 @@
import type { Metadata } from "next";
import { getTranslations } from "next-intl/server";
import { SectionHeader } from "@/components/shared/SectionHeader";
import { WritingListClient } from "@/components/writing/WritingListClient";
import { sanityFetch } from "@/lib/sanity/client";
import { allPostsQuery } from "@/lib/sanity/queries";
import type { Post } from "@/lib/sanity/types";
interface Props {
params: Promise<{ locale: string }>;
}
export async function generateMetadata({ params }: Props): Promise<Metadata> {
const { locale } = await params;
const t = await getTranslations({ locale, namespace: "writing" });
return {
title: t("title"),
description: t("subtitle"),
};
}
export default async function WritingPage({ params }: Props) {
const { locale } = await params;
const t = await getTranslations({ locale, namespace: "writing" });
const posts = await sanityFetch<Post[]>(allPostsQuery, { locale });
return (
<div className="relative z-10 mx-auto max-w-6xl px-6 py-24 pt-32">
<SectionHeader label="writing" />
<div className="mb-12">
<h1 className="text-4xl font-bold text-text-primary md:text-5xl">{t("title")}</h1>
<p className="mt-4 max-w-2xl text-text-secondary">{t("subtitle")}</p>
</div>
<WritingListClient posts={posts ?? []} />
</div>
);
}

90
src/app/api/og/route.tsx Normal file
View File

@@ -0,0 +1,90 @@
import { ImageResponse } from "next/og";
import type { NextRequest } from "next/server";
export const runtime = "edge";
export async function GET(req: NextRequest) {
const { searchParams } = new URL(req.url);
const title = searchParams.get("title") ?? "Ali Taghavi";
const category = searchParams.get("category") ?? "";
return new ImageResponse(
(
<div
style={{
width: "100%",
height: "100%",
display: "flex",
flexDirection: "column",
justifyContent: "flex-end",
padding: "60px",
background: "#0A0A0B",
fontFamily: "sans-serif",
position: "relative",
}}
>
{/* Dot grid */}
<div
style={{
position: "absolute",
inset: 0,
backgroundImage: "radial-gradient(circle, rgba(255,255,255,0.04) 1px, transparent 1px)",
backgroundSize: "28px 28px",
}}
/>
{/* Accent glow */}
<div
style={{
position: "absolute",
top: "-100px",
right: "-100px",
width: "400px",
height: "400px",
borderRadius: "50%",
background: "rgba(204,253,101,0.07)",
filter: "blur(60px)",
}}
/>
{/* Category label */}
{category && (
<div
style={{
display: "flex",
alignItems: "center",
gap: "8px",
marginBottom: "20px",
}}
>
<span style={{ color: "#CCFD65", fontSize: "14px", fontFamily: "monospace" }}>
// {category}
</span>
</div>
)}
{/* Title */}
<div
style={{
fontSize: title.length > 60 ? "36px" : "48px",
fontWeight: "700",
color: "#FAFAFA",
lineHeight: 1.3,
maxWidth: "900px",
marginBottom: "32px",
}}
>
{title}
</div>
{/* Footer */}
<div style={{ display: "flex", alignItems: "center", justifyContent: "space-between" }}>
<span style={{ color: "#CCFD65", fontSize: "18px", fontFamily: "monospace", fontWeight: "700" }}>
// ali taghavi
</span>
<span style={{ color: "#A1A1AA", fontSize: "14px" }}>biztaghavi.com</span>
</div>
</div>
),
{ width: 1200, height: 630 }
);
}

View File

@@ -0,0 +1,13 @@
import { revalidateTag } from "next/cache";
import type { NextRequest } from "next/server";
export async function POST(req: NextRequest) {
const secret = req.nextUrl.searchParams.get("secret");
if (secret !== process.env.REVALIDATE_SECRET) {
return Response.json({ message: "Invalid secret" }, { status: 401 });
}
revalidateTag("sanity", "default");
return Response.json({ revalidated: true, now: Date.now() });
}

45
src/app/api/rss/route.ts Normal file
View File

@@ -0,0 +1,45 @@
import { sanityFetch } from "@/lib/sanity/client";
import { rssPostsQuery } from "@/lib/sanity/queries";
const SITE_URL = "https://biztaghavi.com";
export async function GET() {
const posts = await sanityFetch<
Array<{ _id: string; title: string; slug: { current: string }; excerpt?: string; publishedAt: string; category: string }>
>(rssPostsQuery);
const items = (posts ?? [])
.map((post) => {
const url = `${SITE_URL}/writing/${post.slug.current}`;
const date = post.publishedAt ? new Date(post.publishedAt).toUTCString() : "";
return `
<item>
<title><![CDATA[${post.title}]]></title>
<link>${url}</link>
<guid>${url}</guid>
<pubDate>${date}</pubDate>
<category>${post.category}</category>
${post.excerpt ? `<description><![CDATA[${post.excerpt}]]></description>` : ""}
</item>`;
})
.join("\n");
const xml = `<?xml version="1.0" encoding="UTF-8"?>
<rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom">
<channel>
<title>علی تقوی — نوشته‌ها</title>
<link>${SITE_URL}</link>
<description>یادداشت‌ها، تحلیل‌ها و آموخته‌هایم از ساختن کسب‌وکار، محصول و برند</description>
<language>fa</language>
<atom:link href="${SITE_URL}/api/rss" rel="self" type="application/rss+xml" />
${items}
</channel>
</rss>`;
return new Response(xml, {
headers: {
"Content-Type": "application/rss+xml; charset=utf-8",
"Cache-Control": "public, max-age=3600, s-maxage=3600",
},
});
}

BIN
src/app/favicon.ico Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 25 KiB

89
src/app/globals.css Normal file
View File

@@ -0,0 +1,89 @@
@import "tailwindcss";
@import "@fontsource-variable/vazirmatn";
@theme inline {
/* Brand colors */
--color-background: var(--background);
--color-surface: var(--surface);
--color-surface-hover: var(--surface-hover);
--color-border: var(--border-color);
--color-text-primary: var(--text-primary);
--color-text-secondary: var(--text-secondary);
--color-accent: var(--accent);
--color-accent-hover: var(--accent-hover);
--color-accent-muted: var(--accent-muted);
--color-accent-glow: var(--accent-glow);
--color-danger: var(--danger);
--color-success: var(--success);
/* Fonts */
--font-sans: var(--font-jakarta);
--font-mono: var(--font-jetbrains);
--font-persian: "Vazirmatn Variable", "Vazirmatn", system-ui, sans-serif;
/* Radius */
--radius-sm: 6px;
--radius-md: 10px;
--radius-lg: 14px;
--radius-xl: 20px;
}
:root {
--background: #0A0A0B;
--surface: #141416;
--surface-hover: #1C1C1F;
--border-color: #27272A;
--text-primary: #FAFAFA;
--text-secondary: #A1A1AA;
--accent: #CCFD65;
--accent-hover: #B8E85A;
--accent-muted: rgba(204, 253, 101, 0.1);
--accent-glow: rgba(204, 253, 101, 0.25);
--danger: #EF4444;
--success: #22C55E;
}
* {
box-sizing: border-box;
border-color: var(--border-color);
}
html {
color-scheme: dark;
}
body {
background: var(--background);
color: var(--text-primary);
-webkit-font-smoothing: antialiased;
-moz-osx-font-smoothing: grayscale;
}
[lang="fa"] body,
[dir="rtl"] {
font-family: "Vazirmatn Variable", "Vazirmatn", system-ui, sans-serif;
line-height: 1.7;
}
/* Dot-grid background texture */
body::before {
content: "";
position: fixed;
inset: 0;
background-image: radial-gradient(circle, rgba(255,255,255,0.03) 1px, transparent 1px);
background-size: 24px 24px;
pointer-events: none;
z-index: 0;
}
/* Scrollbar */
::-webkit-scrollbar { width: 6px; }
::-webkit-scrollbar-track { background: var(--background); }
::-webkit-scrollbar-thumb { background: var(--border-color); border-radius: 3px; }
::-webkit-scrollbar-thumb:hover { background: var(--text-secondary); }
/* Selection */
::selection { background: var(--accent-muted); color: var(--accent); }
/* Focus ring */
:focus-visible { outline: 2px solid var(--accent); outline-offset: 2px; }

19
src/app/layout.tsx Normal file
View File

@@ -0,0 +1,19 @@
import type { Metadata } from "next";
import "./globals.css";
export const metadata: Metadata = {
title: {
default: "Ali Taghavi | علی تقوی",
template: "%s | Ali Taghavi",
},
description: "CEO @ NODE-Group · Builder · Strategist · Tehran, Iran",
metadataBase: new URL("https://biztaghavi.com"),
};
export default function RootLayout({
children,
}: Readonly<{
children: React.ReactNode;
}>) {
return children;
}

View File

@@ -0,0 +1,10 @@
import { NextStudio } from "next-sanity/studio";
import config from "../../../../sanity/sanity.config";
export const dynamic = "force-dynamic";
export { metadata, viewport } from "next-sanity/studio";
export default function StudioPage() {
return <NextStudio config={config} />;
}