diff --git a/src/app/admin/blog/[id]/assets/page.tsx b/src/app/admin/blog/[id]/assets/page.tsx new file mode 100644 index 0000000..ac8f24c --- /dev/null +++ b/src/app/admin/blog/[id]/assets/page.tsx @@ -0,0 +1,8 @@ +import AdminBlogAssets from "@/views/AdminBlogAssets"; + +type Params = Promise<{ id: string }>; + +export default async function AdminBlogAssetsPage({ params }: { params: Params }) { + const { id } = await params; + return ; +} diff --git a/src/app/blog/[slug]/page.tsx b/src/app/blog/[slug]/page.tsx index 603269d..0b97071 100644 --- a/src/app/blog/[slug]/page.tsx +++ b/src/app/blog/[slug]/page.tsx @@ -8,6 +8,7 @@ import { Button } from "@/components/ui/button"; import { Link } from "@/lib/router"; import { PublicApiError, getPublicPost } from "@/lib/public-api"; import { apiBaseUrl, siteUrl, toAbsoluteUrl } from "@/lib/site"; +import { blogPostPath, blogPostUrl, normalizeBlogSlugParam } from "@/lib/blog-routes"; import { formatJalali } from "@/lib/utils"; type Params = Promise<{ slug: string }>; @@ -36,11 +37,11 @@ export async function generateMetadata({ params: Params; }): Promise { const { slug } = await params; - const post = await loadPost(slug); + const post = await loadPost(normalizeBlogSlugParam(slug)); const description = cleanText(post.excerpt || post.content).slice(0, 160); const metaTitle = post.seo_title || post.og_title || post.title; const metaDescription = post.seo_description || post.og_description || description; - const canonical = post.canonical_url || `/blog/${post.slug}`; + const canonical = post.canonical_url || blogPostPath(post.slug); const image = toAbsoluteUrl( post.og_image_url || post.absolute_featured_image_url || post.featured_image, apiBaseUrl, @@ -54,7 +55,7 @@ export async function generateMetadata({ openGraph: { title: post.og_title || metaTitle, description: post.og_description || metaDescription, - url: `${siteUrl}/blog/${post.slug}`, + url: blogPostUrl(siteUrl, post.slug), siteName: "انجمن علمی کامپیوتر شرق گیلان", type: "article", images: [image], @@ -77,7 +78,7 @@ export default async function BlogDetailPage({ params: Params; }) { const { slug } = await params; - const post = await loadPost(slug); + const post = await loadPost(normalizeBlogSlugParam(slug)); const description = cleanText(post.excerpt || post.content).slice(0, 160); const metaDescription = post.seo_description || post.og_description || description; const image = toAbsoluteUrl( @@ -93,7 +94,7 @@ export default async function BlogDetailPage({ image: [image], datePublished: post.published_at || post.created_at, dateModified: post.updated_at, - url: `${siteUrl}/blog/${post.slug}`, + url: blogPostUrl(siteUrl, post.slug), author: { "@type": "Person", name: [post.author.first_name, post.author.last_name].filter(Boolean).join(" ") || post.author.username, diff --git a/src/app/sitemap.ts b/src/app/sitemap.ts index 3b42910..f949870 100644 --- a/src/app/sitemap.ts +++ b/src/app/sitemap.ts @@ -1,4 +1,5 @@ import type { MetadataRoute } from "next"; +import { blogPostUrl } from "@/lib/blog-routes"; import { getPublicEvents, getPublicPosts } from "@/lib/public-api"; import { siteUrl } from "@/lib/site"; @@ -34,7 +35,7 @@ export default async function sitemap(): Promise { routes.push( ...posts.map((post) => ({ - url: `${siteUrl}/blog/${post.slug}`, + url: blogPostUrl(siteUrl, post.slug), lastModified: new Date(post.published_at || post.created_at), changeFrequency: "monthly" as const, priority: 0.7, diff --git a/src/components/ModeToggle.tsx b/src/components/ModeToggle.tsx index d2fae08..eb1445e 100644 --- a/src/components/ModeToggle.tsx +++ b/src/components/ModeToggle.tsx @@ -1,33 +1,37 @@ -// src/components/ModeToggle.tsx -import { Button } from '@/components/ui/button'; -import { Moon, Sun } from 'lucide-react'; -import { useTheme } from '@/components/ThemeProvider'; +import { Button } from "@/components/ui/button"; +import { useTheme } from "@/components/ThemeProvider"; +import { cn } from "@/lib/utils"; +import { Moon, Sun } from "lucide-react"; -export default function ModeToggle() { +export default function ModeToggle({ className }: { className?: string }) { const { theme, setTheme } = useTheme(); const handleToggle = () => { - if (theme === 'system' && typeof window !== 'undefined') { - const prefersDark = window.matchMedia?.('(prefers-color-scheme: dark)').matches ?? false; - setTheme(prefersDark ? 'light' : 'dark'); + if (theme === "system" && typeof window !== "undefined") { + const prefersDark = window.matchMedia?.("(prefers-color-scheme: dark)").matches ?? false; + setTheme(prefersDark ? "light" : "dark"); return; } - setTheme(theme === 'dark' ? 'light' : 'dark'); + setTheme(theme === "dark" ? "light" : "dark"); }; const isDark = - theme === 'dark' || - (theme === 'system' && - typeof document !== 'undefined' && - document.documentElement.classList.contains('dark')); + theme === "dark" || + (theme === "system" && + typeof document !== "undefined" && + document.documentElement.classList.contains("dark")); - const nextThemeLabel = isDark ? 'روشن' : 'تاریک'; + const nextThemeLabel = isDark ? "روشن" : "تاریک"; return ( + + ); + } + + return ( + + + + + + + {[user?.first_name, user?.last_name].filter(Boolean).join(" ") || user?.username || "حساب کاربری"} + + + + + + مشاهده پروفایل + + + + + + ویرایش پروفایل + + + + + + تغییر یا بازیابی رمز + + + {isAdminUser ? ( + + + + داشبورد مدیریت + + + ) : null} + + + + + خروج از حساب + + + + + ); +} + +export default function Navbar() { + const { isAuthenticated } = useAuth(); + return (