"use client"; import dynamic from "next/dynamic"; import { useCallback, useEffect, useMemo, useState } from "react"; import { motion, AnimatePresence } from "framer-motion"; import { Users, Award, TrendingUp, Building2, UserPlus, Filter, Download, Route, Sparkles, Loader2, } from "lucide-react"; import { apiFetch } from "@/lib/api-client"; const AffiliateNetworkOrb = dynamic( () => import("./affiliate-network-orb").then((m) => m.AffiliateNetworkOrb), { ssr: false, loading: () => (
), }, ); type JourneyStep = { step: number; title: string; detail_ar: string }; type ProgramPayload = { title_ar?: string; journey_ar: JourneyStep[]; commission_rates: Record; bonus_tiers: { min_deals: number; bonus: number }[]; auto_employ_rule_ar?: string; }; type LeaderRow = { name: string; deals: number; commission: number; status: string; }; function formatSar(n: number) { return `${n.toLocaleString("ar-SA", { maximumFractionDigits: 0 })} ر.س`; } function statusLabelAr(s: string) { const m: Record = { active: "نشط", employed: "مُوظّف / مرشح توظيف", pending: "قيد المراجعة", suspended: "معلّق", terminated: "منتهي", }; return m[s] ?? s; } export function AffiliatesView() { const [program, setProgram] = useState(null); const [leaderboard, setLeaderboard] = useState([]); const [loadErr, setLoadErr] = useState(null); const [loading, setLoading] = useState(true); const load = useCallback(async () => { setLoading(true); setLoadErr(null); try { const [pRes, lRes] = await Promise.all([ apiFetch("/api/v1/affiliates/program"), apiFetch("/api/v1/affiliates/leaderboard/top?limit=20"), ]); if (!pRes.ok) throw new Error("program"); if (!lRes.ok) throw new Error("leaderboard"); setProgram((await pRes.json()) as ProgramPayload); setLeaderboard((await lRes.json()) as LeaderRow[]); } catch { setLoadErr("تعذر تحميل بيانات البرنامج أو لوحة الصدارة. تحقق من الاتصال بالـ API."); } finally { setLoading(false); } }, []); useEffect(() => { void load(); }, [load]); const stats = useMemo(() => { const n = leaderboard.length; const totalComm = leaderboard.reduce((a, r) => a + (r.commission || 0), 0); const hireReady = leaderboard.filter((r) => r.status === "employed" || r.deals >= 10).length; return { n, totalComm, hireReady }; }, [leaderboard]); const shareOnboarding = (name: string, hint: string) => { const text = `مرحباً ${name}، رابط انضمامك كشريك Dealix: https://dealix.sa/affiliate — مرجع: ${hint}`; if (typeof navigator !== "undefined" && navigator.share) { void navigator.share({ title: "Dealix — شراكة", text, url: "https://dealix.sa" }); } else { window.open(`https://wa.me/?text=${encodeURIComponent(text)}`, "_blank"); } }; return (

👥 الشركاء والمسوقين

رحلة كاملة من التسجيل إلى العمولة والترقية، مع لوحة صدارة حية من الـ API ومشهد ثلاثي الأبعاد تفاعلي.

{loadErr && (
{loadErr}
)}

{program?.title_ar ?? "رحلة المسوق"}

{loading && !program ? (
جاري تحميل الخطوات…
) : (
    {(program?.journey_ar ?? []).map((j, i) => ( {j.step}
    {j.title}

    {j.detail_ar}

    ))}
)} {program?.auto_employ_rule_ar && (

{program.auto_employ_rule_ar}

)}
{program?.commission_rates && (

شرائح العمولة (من الـ API)

{Object.entries(program.commission_rates).map(([plan, v]) => (
{plan}
{formatSar(v.price)}
{(v.rate * 100).toFixed(0)}% عمولة
))}
{program.bonus_tiers?.length ? (
    {program.bonus_tiers.map((t) => (
  • من {t.min_deals} صفقات: مكافأة {formatSar(t.bonus)}
  • ))}
) : null}
)}

تفاعل ثلاثي الأبعاد عبر React Three Fiber — مناسب لصفحات التسويق والشراكة دون إعادة تحميل كاملة للصفحة.

{stats.n}

في لوحة الصدارة (نشط / مُوظّف)

{formatSar(stats.totalComm)}

مجموع عمولات المعروضين

{stats.hireReady}

بمعايير أداء عالية (10+ صفقات أو employed)

لوحة الصدارة (من الـ API)

{leaderboard.length === 0 && !loading ? ( ) : ( leaderboard.map((aff, i) => ( )) )}
# الاسم الحالة الصفقات العمولة المتراكمة إجراء
لا بيانات بعد — سجّل أول مسوق عبر{" "} POST /api/v1/affiliates/register
{i + 1}
{aff.name}
{statusLabelAr(aff.status)} {aff.deals} {formatSar(aff.commission)}
{(aff.deals >= 10 || aff.status === "employed") && ( )}
); }