diff --git a/.env.sample b/.env.sample index ecee98d..2234f43 100644 --- a/.env.sample +++ b/.env.sample @@ -1,3 +1,3 @@ NEXT_PUBLIC_API_BASE_URL=http://127.0.0.1:8000 NEXT_PUBLIC_SITE_URL=http://localhost:8080 -CONTENT_CMS_ENABLED=false +CONTENT_CMS_ENABLED=true diff --git a/src/app/about/page.tsx b/src/app/about/page.tsx index 541bdbc..64ac9e8 100644 --- a/src/app/about/page.tsx +++ b/src/app/about/page.tsx @@ -1,6 +1,7 @@ import type { Metadata } from "next"; import AboutUs from "@/views/AboutUs"; import { getCmsPageContent, getCmsSiteContent } from "@/lib/content-cms"; +import { cmsPageMetadata, organizationStructuredData, safeJsonLd } from "@/lib/seo"; import { siteUrl } from "@/lib/site"; export const revalidate = 300; @@ -12,23 +13,13 @@ export async function generateMetadata(): Promise { ]); const title = content.seo.title || content.title || siteContent.official_name; const description = content.seo.description || content.intro; - const image = content.seo.image?.url || siteContent.seo.image?.url || `${siteUrl}/favicon.ico`; - - return { - title: { absolute: title }, + return cmsPageMetadata({ + title, description, - alternates: { canonical: "/about" }, - openGraph: { - title, - description, - url: `${siteUrl}/about`, - siteName: siteContent.official_name, - type: "website", - images: [image], - locale: "fa_IR", - }, - twitter: { card: "summary_large_image", title, description, images: [image] }, - }; + image: content.seo.image || siteContent.seo.image || siteContent.logo, + path: "/about", + siteName: siteContent.official_name, + }); } export default async function AboutPage() { @@ -43,11 +34,7 @@ export default async function AboutPage() { description: content.seo.description || content.intro, url: `${siteUrl}/about`, mainEntity: { - "@type": "Organization", - name: siteContent.official_name, - url: siteUrl, - logo: siteContent.logo?.url || `${siteUrl}/favicon.ico`, - sameAs: [siteContent.contacts.instagram, siteContent.contacts.telegram].filter(Boolean), + ...organizationStructuredData(siteContent), }, }; @@ -55,7 +42,7 @@ export default async function AboutPage() { <> - )} - - ); - - const withHelmet = (node: JSX.Element) => ( - <> - {helmet} - {node} - - ); - if (loading) { - return withHelmet( -
- در حال بارگذاری رویداد... -
, - ); + return
در حال بارگذاری رویداد...
; } if (!event) { - return withHelmet( -
- رویداد مورد نظر یافت نشد. -
, - ); + return
رویداد مورد نظر یافت نشد.
; } const beforeStart = rsTs != null && nowTs < rsTs; const ended = deadlineTs !== null && remainingMs === 0; const showCountdown = !beforeStart && deadlineTs !== null && (remainingMs ?? 0) > 0; - return withHelmet( + return (
{beforeStart && (
diff --git a/src/views/Events.tsx b/src/views/Events.tsx index ca82e28..8c7a9a5 100644 --- a/src/views/Events.tsx +++ b/src/views/Events.tsx @@ -1,7 +1,6 @@ "use client"; -import { useCallback, useEffect, useMemo, useState } from "react"; -import { Helmet } from "@/lib/helmet"; +import { useCallback, useEffect, useState } from "react"; import { Link, useLocation, useNavigate } from "@/lib/router"; import { api } from "@/lib/api"; import { Card, CardContent, CardDescription, CardHeader, CardTitle } from "@/components/ui/card"; @@ -11,12 +10,10 @@ import { Button } from "@/components/ui/button"; import ProgressiveImage from "@/components/ProgressiveImage"; import type * as Types from "@/lib/types"; import { formatJalali, formatNumberPersian, formatToman, getEventCardImageUrl } from "@/lib/utils"; -import { siteUrl } from "@/lib/site"; import type { EventsContentPage } from "@/lib/content-cms"; type EventsProps = { content: EventsContentPage; - siteName: string; initialEvents?: Types.EventListItemSchema[]; initialSearch?: string; }; @@ -53,7 +50,6 @@ function notAvailableReasonFa(event: Types.EventListItemSchema) { export default function Events({ content, - siteName, initialEvents = [], initialSearch = "", }: EventsProps) { @@ -63,71 +59,6 @@ export default function Events({ const [search, setSearch] = useState(initialSearch); const [loading, setLoading] = useState(!initialEvents.length && !initialSearch); - const pageTitle = content.seo.title || content.title; - const pageDescription = content.seo.description || content.intro; - const canonicalUrl = `${siteUrl}/events`; - - const toAbsoluteUrl = (url?: string | null) => { - if (!url) return undefined; - if (url.startsWith("http")) return url; - const normalizedSite = siteUrl.endsWith("/") ? siteUrl.slice(0, -1) : siteUrl; - const normalizedPath = url.startsWith("/") ? url.slice(1) : url; - return `${normalizedSite}/${normalizedPath}`; - }; - - const ogImage = useMemo(() => { - if (!events.length) return `${siteUrl}/favicon.ico`; - return toAbsoluteUrl(getEventCardImageUrl(events[0])) ?? `${siteUrl}/favicon.ico`; - }, [events]); - - const listStructuredData = useMemo(() => { - if (!events.length) return null; - - const itemListElement = events.map((eventItem, index) => { - const listItem: Record = { - "@type": "ListItem", - position: index + 1, - url: `${siteUrl}/events/${eventItem.slug}`, - name: eventItem.title, - description: eventItem.description, - startDate: eventItem.start_time, - }; - - if (eventItem.end_time) { - listItem.endDate = eventItem.end_time; - } - - const imageUrl = toAbsoluteUrl(getEventCardImageUrl(eventItem)); - if (imageUrl) { - listItem.image = imageUrl; - } - - const placeName = eventItem.location || eventItem.address; - if (placeName) { - const place: Record = { - "@type": "Place", - name: placeName, - }; - if (eventItem.address) { - place.address = eventItem.address; - } - listItem.location = place; - } - - return listItem; - }); - - return { - "@context": "https://schema.org", - "@type": "ItemList", - name: pageTitle, - description: pageDescription, - url: canonicalUrl, - numberOfItems: events.length, - itemListElement, - }; - }, [canonicalUrl, events, pageDescription, pageTitle]); - useEffect(() => { setEvents(initialEvents); }, [initialEvents]); @@ -169,28 +100,7 @@ export default function Events({ }, [location.pathname, navigate, search]); return ( - <> - - {pageTitle} - - - - - - - - - - - - - - {listStructuredData && ( - - )} - - -
+

{content.title}

{content.intro ?

{content.intro}

: null} @@ -199,6 +109,7 @@ export default function Events({ setSearch(e.target.value)} className="max-w-md" @@ -260,7 +171,6 @@ export default function Events({
)}
-
- + ); }