admin/lib/seo/legalPages.ts
alisaza e1eaf5eff5 feat: initial ghabilee-admin backoffice app
Extract admin dashboard from ghabilee-frontend2 into a dedicated Next.js
app for backoffice.ghabilee.ir (no SEO indexing / Clarity).
2026-09-05 13:12:59 +03:30

153 lines
8.0 KiB
TypeScript

import type { Metadata } from 'next'
import { ORGANIZATION_ID, WEBSITE_ID, faqPage } from '@/lib/seo/JsonLd'
import { buildCanonicalUrl, SITE_URL } from '@/lib/seo/metadata'
import { buildPublicPageMetadata } from '@/lib/seo/publicPageMetadata'
/** Jalali «۸ شهریور ۱۴۰۵» → Gregorian calendar date used in schema / sitemap. */
export const LEGAL_DOCUMENT_UPDATED_AT_ISO = '2026-08-30'
export const LEGAL_DOCUMENT_UPDATED_AT_FA = '۸ شهریور ۱۴۰۵'
export type LegalPageKey = 'terms' | 'privacy' | 'refund-policy'
interface LegalPageSeoConfig {
path: `/${LegalPageKey}`
/** Visible H1 — primary on-page topic signal. */
h1: string
breadcrumbName: string
/** Absolute document title (no root template suffix). Keep ≤ ~60 chars for SERP. */
metaTitle: string
metaDescription: string
/** schema.org primary type(s) beyond WebPage. */
schemaType: string | string[]
pageId: string
}
/**
* SEO source of truth for public legal pages.
* Titles are tuned for Persian search intent + SERP length; body copy stays in page components.
*/
export const LEGAL_PAGE_SEO: Record<LegalPageKey, LegalPageSeoConfig> = {
terms: {
path: '/terms',
h1: 'شرایط استفاده از قبیله',
breadcrumbName: 'شرایط استفاده',
metaTitle: 'شرایط استفاده قبیله | قوانین رزرو و میزبانی',
metaDescription:
'شرایط استفاده از قبیله: قوانین ساخت حساب، رزرو رویداد، پرداخت، مسئولیت مهمان و میزبان، فهرست انتظار و ارتباط با پشتیبانی را بخوانید.',
schemaType: ['WebPage', 'TermsOfService'],
pageId: `${SITE_URL}/terms#webpage`,
},
privacy: {
path: '/privacy',
h1: 'حریم خصوصی قبیله',
breadcrumbName: 'حریم خصوصی',
metaTitle: 'حریم خصوصی قبیله | حفاظت از اطلاعات کاربران',
metaDescription:
'سیاست حریم خصوصی قبیله: چه داده‌هایی جمع می‌شود، چرا استفاده می‌شود، چه اطلاعاتی به میزبان می‌رسد، نگهداری، حذف حساب و امنیت اطلاعات.',
schemaType: ['WebPage', 'PrivacyPolicy'],
pageId: `${SITE_URL}/privacy#webpage`,
},
'refund-policy': {
path: '/refund-policy',
h1: 'لغو و بازگشت وجه قبیله',
breadcrumbName: 'لغو و بازگشت وجه',
metaTitle: 'لغو و بازگشت وجه قبیله | قوانین کنسلی رویداد',
metaDescription:
'در قبیله هر رویداد قوانین لغو و بازگشت وجه خودش را دارد؛ جزئیات را در صفحه همان رویداد ببین. چارچوب کلی، کیف پول و پشتیبانی اینجا.',
schemaType: 'WebPage',
pageId: `${SITE_URL}/refund-policy#webpage`,
},
}
export function buildLegalPageMetadata(key: LegalPageKey): Metadata {
const page = LEGAL_PAGE_SEO[key]
const pageMetadata = buildPublicPageMetadata({
path: page.path,
metaTitle: page.metaTitle,
metaDescription: page.metaDescription,
})
return {
...pageMetadata,
title: { absolute: page.metaTitle },
openGraph: {
...pageMetadata.openGraph,
title: page.metaTitle,
},
twitter: { ...pageMetadata.twitter, title: page.metaTitle },
robots: { index: true, follow: true },
}
}
export function legalWebPageSchema(key: LegalPageKey) {
const page = LEGAL_PAGE_SEO[key]
const url = buildCanonicalUrl(page.path)
return {
'@context': 'https://schema.org',
'@type': page.schemaType,
'@id': page.pageId,
name: page.metaTitle,
headline: page.h1,
description: page.metaDescription,
url,
inLanguage: 'fa-IR',
isPartOf: { '@id': WEBSITE_ID },
publisher: { '@id': ORGANIZATION_ID },
datePublished: LEGAL_DOCUMENT_UPDATED_AT_ISO,
dateModified: LEGAL_DOCUMENT_UPDATED_AT_ISO,
mainEntityOfPage: {
'@type': 'WebPage',
'@id': page.pageId,
},
}
}
/** Question-shaped sections on the refund policy — used for FAQ rich results + AI extraction. */
export const REFUND_POLICY_FAQS = [
{
question: 'قوانین لغو و بازگشت وجه در قبیله چطور مشخص می‌شود؟',
answer:
'هر رویداد قوانین لغو و بازگشت وجه خودش را دارد. برگزارکننده هنگام ساخت رویداد قواعد و هزینه لغو را تنظیم می‌کند؛ مرجع نهایی صفحه همان رویداد است و قبل از رزرو باید جزئیات آنجا را بخوانی.',
},
{
question: 'اگر از کد تخفیف استفاده کرده باشم بازگشت وجه چطور حساب می‌شود؟',
answer:
'اگر هنگام خرید از کد تخفیف استفاده کرده باشی، محاسبه بازگشت وجه (در صورت وجود طبق قوانین همان رویداد) بر اساس مبلغ واقعی پرداخت‌شده انجام می‌شود؛ نه قیمت اولیه رویداد.',
},
{
question: 'اگر برگزارکننده رویداد را لغو کند چه اتفاقی می‌افتد؟',
answer:
'رزروهای آن رویداد لغو می‌شوند، شرکت‌کنندگان مطلع می‌شوند و مبلغ پرداخت‌شده به‌طور کامل به کیف پول قبیله بازمی‌گردد. در حال حاضر بابت لغو رویداد توسط برگزارکننده جریمه‌ای از شرکت‌کننده گرفته نمی‌شود.',
},
{
question: 'اگر زمان یا مکان رویداد تغییر کند چه می‌شود؟',
answer:
'بعد از رزرو فعال، تغییر زمان، مکان، قیمت یا شرایط اصلی حضور باید از مسیر پشتیبانی قبیله پیگیری شود و شرکت‌کنندگان مطلع می‌شوند. اگر به‌خاطر تغییر دیگر نتوانی شرکت کنی، موضوع را از پشتیبانی پیگیری کن.',
},
{
question: 'اگر در رویداد حاضر نشوم مبلغ برمی‌گردد؟',
answer:
'اگر رزرو داشته باشی، طبق قوانین همان رویداد در بازه مجاز لغو نکرده باشی و در رویداد حاضر نشوی، مبلغ پرداختی معمولاً بازگردانده نمی‌شود. قبیله برای رزروهای تأییدشده یادآوری ارسال می‌کند، اما مسئولیت حضور با شرکت‌کننده است.',
},
{
question: 'اگر پرداخت انجام شد ولی رزرو ثبت نشد چه کار کنم؟',
answer:
'این موضوع لغو رزرو نیست. ابتدا وضعیت تراکنش بررسی می‌شود؛ اگر پرداخت ناموفق باشد مبلغ طبق فرایند شبکه پرداخت برمی‌گردد. برای پیگیری، شماره موبایل حساب، زمان پرداخت، مبلغ و شماره پیگیری بانکی را آماده کن و با پشتیبانی تماس بگیر.',
},
{
question: 'آیا برگزارکننده می‌تواند رزرو یک نفر را لغو کند؟',
answer: 'در حال حاضر برگزارکننده امکان لغو مستقیم رزرو یک شرکت‌کننده مشخص را ندارد و باید موضوع را از طریق پشتیبانی قبیله مطرح کند.',
},
{
question: 'آیا می‌توانم رزرو را به شخص دیگر منتقل کنم؟',
answer:
'در حال حاضر انتقال رزرو یا بلیط به شخص دیگر امکان‌پذیر نیست. هر رزرو به حساب همان شخصی که ثبت‌نام کرده متصل است و برای عدم حضور باید مطابق قوانین لغو همان رویداد اقدام کنی.',
},
] as const
export function refundPolicyFaqSchema() {
return faqPage(REFUND_POLICY_FAQS)
}