Audit fixes: VIP self-grant, draft leak, onboarding flag, hero personalization, gated downloads
Deploy GitHub Pages / deploy (push) Canceled after 0s
Deploy GitHub Pages / deploy (push) Canceled after 0s
This commit is contained in:
@@ -0,0 +1,134 @@
|
||||
import { getPayload } from 'payload'
|
||||
import { headers } from 'next/headers'
|
||||
import Topnav from '@/components/topnav'
|
||||
import Footer from '@/components/footer'
|
||||
import SubscribeButton from './subscribe-button'
|
||||
import Faq from './faq'
|
||||
import configPromise from '@payload-config'
|
||||
import Link from 'next/link'
|
||||
import { formatNumber, guidesLabel, minutesLabel, MEMBERSHIP_PRICE } from '@/lib/format'
|
||||
import type { Viewer } from '@/lib/viewer'
|
||||
|
||||
export const dynamic = 'force-dynamic'
|
||||
export const metadata = { title: 'العضوية' }
|
||||
|
||||
/* Member-library samples — real VIP guides, read from the CMS at request time. */
|
||||
const SAMPLES = [
|
||||
{ slug: 'agent-loop-stop-condition', cover: 'تشريح وكيل ناجح', variant: '', output: 'قالب حالة الوكيل + قائمة فحص قبل التشغيل' },
|
||||
{ slug: 'systematic-model-eval', cover: 'قِس قبل أن تبدّل', variant: 'guide-cover-light', output: 'مجموعة صغيرة من اختبارات التقييم قابلة لإعادة التشغيل' },
|
||||
{ slug: 'prototype-to-production', cover: 'من التجربة إلى الإنتاج', variant: '', output: 'قائمة قرارات ما قبل الإطلاق' },
|
||||
]
|
||||
|
||||
const FAQ_ITEMS = [
|
||||
{ q: 'هل أستطيع الإلغاء في أي وقت؟', a: 'نعم. يتوقف التجديد التالي، ويستمر وصولك حتى نهاية الفترة المدفوعة.' },
|
||||
{ q: 'هل المحتوى مناسب للمبتدئ؟', a: 'كل دليل يوضح مستواه. ابدأ بالأدلة المجانية في مسار الأساسيات، ثم انتقل إلى الوكلاء والأتمتة.' },
|
||||
{ q: 'هل توجد شهادة إكمال؟', a: 'لا حالياً. تركيز أنور بآي على التطبيق والملفات العملية، لا على شهادات الحضور.' },
|
||||
{ q: 'هل يمكنني استخدام الملفات في عملي؟', a: 'نعم للاستخدام الشخصي والمهني داخل مشاريعك. إعادة بيع الملفات أو نشرها كما هي غير مشمول.' },
|
||||
]
|
||||
|
||||
export default async function SubscriptionPage() {
|
||||
const payload = await getPayload({ config: configPromise })
|
||||
const { user } = await payload.auth({ headers: await headers() })
|
||||
const role = (user?.role as string) || null
|
||||
const entitled = role === 'vip' || role === 'admin'
|
||||
const viewer: Viewer = user ? { email: user.email, role: role ?? 'free' } : null
|
||||
const { docs: published } = await payload.find({
|
||||
collection: 'posts',
|
||||
where: { status: { equals: 'published' } },
|
||||
depth: 0,
|
||||
limit: 200,
|
||||
select: { title: true, slug: true, dek: true, category: true, readingMinutes: true, access: true },
|
||||
})
|
||||
const freeCount = published.filter(doc => doc.access === 'free').length
|
||||
const memberCount = published.length - freeCount
|
||||
const samples = SAMPLES.flatMap(sample => {
|
||||
const post = published.find(doc => doc.slug === sample.slug)
|
||||
return post ? [{ ...sample, post }] : []
|
||||
})
|
||||
|
||||
return (
|
||||
<div id="site" className="site-page v2 v2-membership">
|
||||
<Topnav viewer={viewer} />
|
||||
<main id="content">
|
||||
<section className="mem-hero" aria-labelledby="mem-title">
|
||||
<div className="wrap mem-hero-grid">
|
||||
<div className="mem-hero-copy">
|
||||
<h1 id="mem-title">كل الأدلة كاملة، مع ملفات العمل.</h1>
|
||||
<p className="lede">الأدلة المجانية تعرّفك بالمفاهيم. العضوية تفتح الأدلة المعمّقة التي تأخذك من التجربة إلى نظام يعمل، مع القوالب وقوائم الفحص التي نستخدمها فعلاً.</p>
|
||||
<ul className="mem-points" role="list">
|
||||
<li><strong>{guidesLabel(memberCount)} للأعضاء</strong><span>تُضاف أدلة جديدة كل شهر</span></li>
|
||||
<li><strong>ملفات قابلة للتنزيل</strong><span>قوالب حالة، قوائم فحص، سجلات أخطاء</span></li>
|
||||
<li><strong>نقاش مع الفريق</strong><span>اسأل في تعليقات أي دليل</span></li>
|
||||
</ul>
|
||||
</div>
|
||||
<article className="plan" aria-labelledby="plan-title">
|
||||
<h2 id="plan-title">عضوية فردية</h2>
|
||||
<p className="plan-price"><bdi dir="ltr">{MEMBERSHIP_PRICE}</bdi><span>شهرياً</span></p>
|
||||
<p className="plan-note">تلغي متى شئت، ويبقى حسابك المجاني ومحفوظاتك.</p>
|
||||
<div className="plan-cta">
|
||||
{entitled ? (
|
||||
<a className="btn btn-primary" href="/account-settings">عضويتك فعّالة، أدر حسابك</a>
|
||||
) : (
|
||||
<SubscribeButton loggedIn={Boolean(user)} />
|
||||
)}
|
||||
</div>
|
||||
<p className="fine">الدفع عبر Stripe. لا نحفظ بيانات بطاقتك.</p>
|
||||
</article>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
{samples.length > 0 && (
|
||||
<section className="mem-samples" aria-labelledby="samples-title">
|
||||
<div className="wrap">
|
||||
<header className="section-head">
|
||||
<h2 id="samples-title">من أدلة الأعضاء</h2>
|
||||
<p>كل دليل يبدأ بمشكلة حقيقية وينتهي بشيء تشغّله. اقرأ المقدمة مجاناً قبل أن تقرر.</p>
|
||||
</header>
|
||||
<ul className="sample-grid" role="list">
|
||||
{samples.map(({ slug, output, post }) => (
|
||||
<li key={slug}>
|
||||
<Link href={`/posts/${slug}`} className="sample">
|
||||
<span className="meta-row"><span>{post.category}</span><span>{minutesLabel(post.readingMinutes)}</span></span>
|
||||
<span className="sample-title">{post.title}</span>
|
||||
<span className="sample-dek">{post.dek}</span>
|
||||
<span className="sample-output"><span>ما تخرج به</span>{output}</span>
|
||||
</Link>
|
||||
</li>
|
||||
))}
|
||||
</ul>
|
||||
</div>
|
||||
</section>
|
||||
)}
|
||||
|
||||
<section className="mem-compare" aria-labelledby="compare-title">
|
||||
<div className="wrap narrow">
|
||||
<h2 id="compare-title">الفرق بين الحساب المجاني والعضوية</h2>
|
||||
<div className="table-wrap">
|
||||
<table>
|
||||
<thead><tr><th scope="col"><span className="sr-only">الميزة</span></th><th scope="col">حساب مجاني</th><th scope="col" className="is-member">العضوية</th></tr></thead>
|
||||
<tbody>
|
||||
<tr><th scope="row">الأدلة المجانية ({formatNumber(freeCount)})</th><td>كاملة</td><td className="is-member">كاملة</td></tr>
|
||||
<tr><th scope="row">أدلة الأعضاء ({formatNumber(memberCount)})</th><td>المقدمة فقط</td><td className="is-member">كاملة</td></tr>
|
||||
<tr><th scope="row">ملفات العمل</th><td>—</td><td className="is-member">تنزيل كامل</td></tr>
|
||||
<tr><th scope="row">التعليق والنقاش</th><td>على الأدلة المجانية</td><td className="is-member">على كل الأدلة</td></tr>
|
||||
<tr><th scope="row">حفظ الأدلة للعودة لاحقاً</th><td>—</td><td className="is-member">متاح</td></tr>
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
<section className="mem-faq" aria-labelledby="faq-title">
|
||||
<div className="wrap faq-grid">
|
||||
<div>
|
||||
<h2 id="faq-title">أسئلة قبل الاشتراك</h2>
|
||||
<p>لم تجد إجابتك؟ راسلنا على <a className="text-link" href="mailto:hello@anwarpy.ai" dir="ltr">hello@anwarpy.ai</a> ويرد عليك الفريق مباشرة.</p>
|
||||
</div>
|
||||
<Faq items={FAQ_ITEMS} />
|
||||
</div>
|
||||
</section>
|
||||
</main>
|
||||
<Footer />
|
||||
</div>
|
||||
)
|
||||
}
|
||||
Reference in New Issue
Block a user