WzGate
Build your own website

البداية السريعة

اقرأ ملف شركتك ونفّذ بحثًا من تطبيق Next.js — متغيّرات البيئة والترويسات وغلاف الاستجابة والتقسيم إلى صفحات.

تبني هذه الصفحة أصغر شيء يعمل: مشروع Next.js يقرأ ملف شركتك على الخادم ويشغّل بحثًا من المتصفّح. انسخ أولًا القيم الأربع من الإعدادات ← المواقع ← اربط موقعك.

١. البيئة

.env.local
# مضيف النظام مضافًا إليه /api. كل مسار أدناه نسبةً إليه.
NEXT_PUBLIC_API_BASE_URL=https://crm.example.com/api

# أيّ موقع يمثّله هذا النشر. يُرسَل في X-Site.
NEXT_PUBLIC_SITE_KEY=my-site

# المفتاح القابل للنشر (pk_…). عامّ بالتصميم: يُدمج في حزمة المتصفّح ولا يعمل
# إلا من نطاقات هذا الموقع نفسه.
NEXT_PUBLIC_SITE_PUBLISHABLE_KEY=pk_my-site_0123456789abcdef01234567

# المفتاح السرّي (sk_…) للنداءات من الخادم. بلا بادئة NEXT_PUBLIC_ عمدًا، حتى لا
# تضعه Next في حزمة المتصفّح أبدًا. اختياري — نداءات الخادم ترجع إلى المفتاح
# القابل للنشر عند غيابه.
SITE_SECRET_KEY=

المتغيّرات المسبوقة بـ NEXT_PUBLIC_ وحدها هي التي تصل إلى المتصفّح. إن رأيت يومًا sk_ داخل ملف JavaScript مبنيّ، فذلك المفتاح صار عامًّا — ألغِه من تبويب «اربط موقعك» وأنشئ غيره.

٢. مكان واحد يبني الترويسات

كلا القناتين — المتصفّح وخادمك — يجب أن ترسلا الهوية نفسها، فعرّفها مرّة واحدة.

lib/wzgate.ts
export function siteHeaders({ server = false } = {}) {
  const headers: Record<string, string> = { Accept: 'application/json' };

  const siteKey = process.env.NEXT_PUBLIC_SITE_KEY;
  if (siteKey) headers['X-Site'] = siteKey;

  // نداء الخادم لا يحمل Origin كي تفحصه الواجهة، وهذا بالضبط ما يحلّ محلّه
  // المفتاح السرّي.
  const apiKey =
    (server ? process.env.SITE_SECRET_KEY : undefined) ??
    process.env.NEXT_PUBLIC_SITE_PUBLISHABLE_KEY;
  if (apiKey) headers['X-Api-Key'] = apiKey;

  return headers;
}

export const API = process.env.NEXT_PUBLIC_API_BASE_URL!;

القيمة غير المضبوطة يجب ألّا تُرسل ترويسة أصلًا، لا ترويسة فارغة: الطلب بلا مفتاح هو طلب لا يدّعي هوية، أما X-Api-Key: '' فهو مفتاح لا يمكن التعرّف عليه — وهذا شيء آخر قابل للرفض.

٣. اقرأ ملف الشركة (من الخادم)

app/page.tsx
import { API, siteHeaders } from '@/lib/wzgate';

export default async function Page() {
  const res = await fetch(`${API}/public/company-profile`, {
    headers: siteHeaders({ server: true }),
    // الملف نادر التغيّر؛ أعد التحقّق بدل الجلب مع كل طلب.
    next: { revalidate: 300 },
  });
  if (!res.ok) throw new Error(`Company profile: ${res.status}`);

  const { data } = await res.json();
  return <h1>{data.name}</h1>;
}

٤. البحث (من المتصفّح)

lib/search.ts
import { API, siteHeaders } from '@/lib/wzgate';

export async function searchProperties(q: string, page = 1) {
  const params = new URLSearchParams({ q, page: String(page), perPage: '24' });

  const res = await fetch(`${API}/public/search?${params}`, {
    headers: siteHeaders(),
  });

  const body = await res.json();
  if (!res.ok) throw new Error(body.code ?? 'SEARCH_FAILED');

  return { rows: body.data, pagination: body.pagination };
}

غلاف الاستجابة

كل إجابة مغلّفة:

{ "success": true, "data": { }, "timestamp": "2026-09-14T09:30:27.019Z" }

والإجابة المقسّمة إلى صفحات تضع pagination بجوار data لا داخلها:

{
  "success": true,
  "data": [],
  "pagination": {
    "total": 100, "lastPage": 9, "currentPage": 1, "perPage": 12,
    "prev": null, "next": 2
  },
  "timestamp": "2026-09-14T09:30:27.019Z"
}

فكّ الغلاف عن data مرّة واحدة في طبقة الاتصال، ودَعْ بقيّة الكود يرى صفوفًا عادية.

ليست كل القوائم مقسّمة. GET /public/me/favorites و/public/me/saved-searches و/public/me/viewing-requests و/public/owner-listings/mine تعيد مصفوفة مجرّدة داخل data. عامِل «data ليست مصفوفة» على أنها «لا صفوف» بدل استدعاء .map عليها.

التقسيم إلى صفحات

القوائم تقبل page (يبدأ من ١) و**perPage**، وسقف perPage هو ١٠٠ — وطلب أكثر من ذلك يعطيك ١٠٠ بصمت، وليس خطأ. لا يوجد معامل اسمه limit.

GET /public/properties?page=2&perPage=24

معاملات الاستعلام غير المعروفة خطأ 400

تتحقّق الواجهة من سلاسل الاستعلام بصرامة: المعامل الذي لم تعلنه هو 400، وليس شيئًا يُتجاهل.

GET /public/properties?type=apartment
→ 400 { "message": ["property type should not exist"] }

احذف معاملات واجهتك الخاصة قبل النداء. لا تمرّر سلسلة استعلام المتصفّح كما هي.

بعد ذلك

On this page