Audit fixes: VIP self-grant, draft leak, onboarding flag, hero personalization, gated downloads
Deploy GitHub Pages / deploy (push) Canceled after 0s

This commit is contained in:
AHMED
2026-10-09 16:20:55 +03:00
parent c34611dc36
commit 5ee706ebd6
150 changed files with 24054 additions and 0 deletions
@@ -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>
)
}