تخطَّ إلى المحتوى الرئيسي

Developer Page Specifications

Listing Page

/:lang/opportunity-radar — page sections, fields, sources, states.

مكتمل

المسار: /:lang/opportunity-radar. يَعرض كل إشارة فرصة مُنَسَّقة مع فلترة، فرز، tabs حالة، بحث، وكتلة اشتراك. وَثِّق كل قسم، كل حقل، كل مصدر بيانات، وكل حالة — المُنَفِّذون ما يجب أن يَحتاجوا قراءة أيّ مستند آخر.

أقسام الصفحة (من الأعلى للأسفل)

  1. A · قسم Hero
  2. B · ملخص الرادار / لوحة الإشارات
  3. C · tabs الحالة (الكل / Open / Closing soon / Rolling / Closed)
  4. D · بحث + فلاتر
  5. E · شبكة كروت الفرص (القائمة الأساسية)
  6. F · كتلة اشتراك Weekly Opportunity Signals
  7. G · أكمل رحلتك (كروت cross-module)
  8. H · Footer (مكوّن عام، أبداً ليس لكل صفحة)

A · قسم Hero — الحقول

Eyebrow label
سلسلة ثابتة. المصدر: تكوين صفحة CMS (PageConfig.opportunityRadarListing.eyebrow). مثال: "Opportunity Intelligence for Founders". مطلوب: نعم.
Page title (H1)
المصدر: PageConfig.opportunityRadarListing.title. مثال: "Opportunity Radar". مطلوب: نعم. يجب أن يَكون الـ <h1> الوحيد على الصفحة.
وصف الصفحة
المصدر: PageConfig.opportunityRadarListing.description. يَرسم كفقرة lede تحت H1. مطلوب: نعم.
ملاحظة دعم قصيرة
المصدر: PageConfig.opportunityRadarListing.supportingNote. سطر ثانوي اختياري تحت الوصف. مطلوب: لا.
CTA أساسي (التسمية + الفعل)
مصدر التسمية: PageConfig.opportunityRadarListing.primaryCtaLabel (افتراضي: "Explore Opportunities"). الفعل: smooth-scroll لـ #opportunities-listing. مطلوب: نعم.
CTA ثانوي
التسمية: "Get Weekly Digest". الفعل: smooth-scroll لـ #subscribe-block. مطلوب: نعم.
نقاط الميزات[]
المصدر: PageConfig.opportunityRadarListing.featureBullets (مصفوفة { icon, label }). أمثلة: Editorially curated · MENA-wide coverage · Free for founders · Verified by our editorial team. مطلوب: نعم (≥3).

B · ملخص الرادار / لوحة الإشارات

إجمالي عدد Open
الاستعلام: SELECT COUNT(*) FROM Opportunity WHERE Status = 'Open' AND IsActive = true. مُخَزَّن جانب-الخادم، يَتحدّث عند كل نشر/إلغاء-نشر.
عدد Closing soon
الاستعلام: WHERE Status = 'ClosingSoon' أو (DeadlineDate BETWEEN NOW() AND NOW() + INTERVAL '7 days'). عتبة الـ 7 أيام قابلة للتكوين عبر FeatureFlag.opportunityClosingSoonDays.
عدد Rolling
الاستعلام: WHERE IsRolling = true AND IsActive = true.
عدد Tracked
الاستعلام: WHERE IsActive = true. إجمالي الفرص النشطة عبر كل الحالات.
قائمة آخر الإشارات (آخر 5)
الاستعلام: ORDER BY GREATEST(PublishedAt, LastVerifiedAt) DESC LIMIT 5. كل عنصر: { id, slug, titleLang, typeLabel, countryCode, deadlineLabel, statusBadge, detailsUrl }.

C · tabs الحالة — الحقول + السلوك

  • عدد All signals (مجموع Open + ClosingSoon + Rolling + Closed، فقط IsActive = true).
  • عدد Open — انظر B أعلاه.
  • عدد Closing soon — انظر B أعلاه.
  • عدد Rolling — انظر B أعلاه.
  • عدد Closed — الاستعلام: WHERE Status = 'Closed' أو DeadlineDate < NOW(). إدخالات Closed تَبقى قابلة للاستعلام لكن مُستثناة من الفرز الافتراضي.

السلوك: نقرة على tab تُطَبِّق فلتر على شبكة الكروت (E). الـ tab النشط يَستخدم صبغة primary لنظام التصميم + وزن غامق. العَدَّادات تُحدَّث تفاعلياً بحسب الفلاتر/البحث. الحالة الفارغة — انظر قسم حالات UI أدناه.

D · بحث + فلاتر

حقل البحث (q)
Debounced 300ms. يَبحث عبر: OpportunityLang.Title، OpportunityLang.ShortDescription، Organizer.Name، Country.Name، OpportunityType.Name، OpportunitySector.Name. بحث نصّي كامل جانب-الخادم عبر PostgreSQL tsvector أو ما يُكافئ. الحد الأدنى: 2 حرف.
فلتر — النوع
تعدّد-اختيار. المصدر: جدول OpportunityType. القيم: accelerator، incubator، grant، competition، fellowship، soft-landing، residency، vc-program. query param: type=accelerator,grant.
فلتر — الدولة
تعدّد-اختيار. المصدر: جدول Country (16 رمز MENA من §9.6). query param: country=eg,sa.
فلتر — مرحلة الشركة
تعدّد-اختيار. القيم: idea، pre-seed، seed، series-a، growth، sme، scaleup. يُرآة StartupStage type في §9.6. query param: stage=pre-seed,seed.
فلتر — القطاع
تعدّد-اختيار. المصدر: جدول OpportunitySector. taxonomy حرّة مُعَبَّأة تحريرياً: fintech، healthtech، edtech، climatetech، deeptech، agritech، إلخ. query param: sector=fintech,climatetech.
فلتر — الحالة
اختيار-واحد (يُرآة status tabs C). query param: status=open|closing-soon|rolling|closed|all.
منسدلة الفرز
القيم: most-relevant (افتراضي عند وجود q، وإلا deadline-soonest) · deadline-soonest · newest · recently-verified · closing-soon. query param: sort=deadline-soonest.

E · شبكة كروت الفرص — حقول لكل كرت

id
المصدر: Opportunity.Id (UUID). داخلي فقط، لا يُعرض أبداً.
slug
المصدر: Opportunity.Slug. kebab-case، ASCII، فريد. يُستخدم في رابط الكرت → /:lang/opportunity-radar/{slug}.
العنوان
المصدر: OpportunityLang.Title حيث Lang = المحلّية الحالية. يَعود لـ English لو متغيّر AR مفقود. مطلوب: نعم.
تسمية النوع
المصدر: OpportunityType.NameLang. اعرض كـ chip أحرف-كبيرة صغير في صفّ meta للكرت. مطلوب: نعم.
شارة الحالة
المصدر: Opportunity.Status. صبغة بالقيمة (انظر صفحة Status Logic). مطلوب: نعم.
شارة sponsored
المصدر: Opportunity.IsSponsored. اعرض فقط عند true. معالجة بصرية مميَّزة (صبغة برتقالية، تسمية "SPONSORED").
شارة verified
المصدر: Opportunity.IsVerified. اعرض فقط عند true. إيقونة ✓ صغيرة + tooltip "Verified".
تسمية الموعد
محتسب من Opportunity.DeadlineDate و Opportunity.IsRolling. المنطق: لو IsRolling → "Rolling applications"؛ وإلا لو days_to(DeadlineDate) ≤ 1 → "Last day to apply"؛ وإلا لو days_to ≤ 7 → "{N} days left"؛ وإلا → سلسلة تاريخ مُهيَّأة بالمحلّية الحالية. مطلوب: نعم.
تسمية closing-soon
شَرطي. يُعرض حين الحالة المُحتسَبة === ClosingSoon. يَتراكب pill "Closing soon" بصبغة warning في الزاوية العليا اليمنى للكرت.
الوصف القصير
المصدر: OpportunityLang.ShortDescription. حد أقصى 200 حرف، 3-سطر clamp عبر CSS. مطلوب: نعم.
الدولة
المصدر: Country.Code (ISO 3166-1 alpha-2) + Country.NameLang. العرض: علم emoji + الاسم. مطلوب: نعم.
stage[]
المصدر: جدول join OpportunityStage. العرض: حتى 2 chip؛ لو أكثر، chip overflow "+N". مطلوب: اختياري.
sector[]
المصدر: جدول join OpportunitySector. نفس قواعد العرض كـ stage. مطلوب: اختياري.
اسم المنظِّم
المصدر: Organizer.Name. العرض: نص صغير تحت العنوان مع logo اختياري (Organizer.LogoUrl). مطلوب: نعم.
officialUrl
المصدر: Opportunity.OfficialUrl. مطلوب حين Status ∈ { Open، ClosingSoon، Rolling }. لو مفقود، CTA "Apply via Official Source" يُرسم disabled مع tooltip.
CTA #1 — Apply
التسمية: "Apply via Official Source". الفعل: window.open(officialUrl، '_blank'، 'noopener,noreferrer'). يُطلق حدث تحليلات opportunity_apply_click.
CTA #2 — View Opportunity
الفعل: routerLink لـ /:lang/opportunity-radar/{slug}. يُطلق opportunity_card_click.

صبغات حالة الكرت

  • Open → accent أخضر (color-token: --status-open / --badge-verified-like).
  • ClosingSoon → accent برتقالي (--brand-orange).
  • Rolling → accent بنفسجي (--primary tint عند opacity 60%).
  • Closed → رمادي خفيف (--muted-foreground). opacity الكرت يَنزل لـ 0.6.

F · كتلة اشتراك Weekly Opportunity Signals

عنوان الكتلة + الوصف
المصدر: PageConfig.opportunityRadarListing.subscribeBlock.{title,description}.
حقل البريد
مطلوب. التحقّق: regex بريد مُمتثل RFC. الإرسال مُعطَّل حتى الصلاحية.
تفضيل الدولة
اختياري. نفس خيارات قائمة filter D country. الافتراضي "All MENA".
تفضيل المرحلة
اختياري. نفس خيارات قائمة filter D stage. الافتراضي "All stages".
مربع الموافقة (consentPlatform)
مطلوب. النص: "I agree to receive weekly opportunity signals from StartupHub.today." بوّابة GDPR / PDPL. الإرسال مُعطَّل حتى التأشير.
مربع موافقة الشركاء (consentPartners)
اختياري. النص: "Also share my profile with relevant sponsor partners." يُوَجِّه العميل لـ CRM الراعي لو مُؤشَّر. الافتراضي غير مُؤشَّر.
زر Subscribe
POST /api/subscriptions/opportunity-digest. الـ Body: { email, country?, stage?, consentPlatform, consentPartners, source: 'listing' }. عند النجاح: تأكيد inline. عند الخطأ: banner خطأ inline.

G · أكمل رحلتك — كروت cross-module

  • 4 كروت ثابتة. المصدر: PageConfig.opportunityRadarListing.crossModuleCards[].
  • كل كرت: { title, shortDescription, targetModule, targetRoute, displayOrder, icon }.
  • الأهداف القياسية: Build your Founder Profile (/founders/edit) · Read Founder Files (/founder-files) · Apply to Startup Showcase (/showcase) · Track MENA Events (/calendar).
  • نقرة كل كرت تُطلق opportunity_cross_module_click مع { target_module, target_route }.

حالات UI

Loading (الجلب الأوّلي)
كروت skeleton: نفس تخطيط الكروت الحقيقية، shimmer متحرّك. اعرض 6 skeletons (يطابق الحجم الافتراضي للصفحة).
Empty (لا نتائج بعد الفلتر)
ارسم حالة فارغة موثّقة — رسم توضيحي + رسالة محلّية صحيحة تَقتبس الفلاتر النشطة + CTA "Clear filters" + رابط لـ /:lang/opportunity-radar (لا فلاتر).
Error (فشل API)
إشعار toast (يمين-أعلى LTR، يسار-أعلى RTL) + CTA retry inline فوق شبكة الكروت. منطقة الكروت تَحتفظ بأيّ بيانات مُحَمَّلة سابقاً.
Pagination — infinite scroll
IntersectionObserver على sentinel تحت الشبكة. كل دفعة = 24 كرت. أطلق opportunity_listing_load_more على كل جلب بعد الأول.