/* File: src/config.ts */
export const EASY_ORDERS_FEED_URL =
"https://api.easy-orders.net/api/v1/products/feed/0a7d9edabe47401d9f6257c4f9112f9e/channel/meta";
export const WHATSAPP_NUMBER = "201099425354";
export const PRODUCT_KEYWORDS = {
omega3: ["omega 3", "omega-3", "fish oil", "أوميغا", "اوميغا", "fishoil"],
magnesium: ["magnesium", "مغنيسيوم", "magnesium glycinate", "glycinate"],
vitaminD3: ["vitamin d", "vitamin d3", "d3", "فيتامين د", "فيتامين د3"],
multivitamin: [
"multivitamin",
"multi vitamin",
"multivitamins",
"مالتي",
"متعدد الفيتامينات",
],
probiotic: ["probiotic", "probiotics", "بروبيوتك", "probiotic 10", "digestive"],
} as const;
/* File: src/types.ts */
export type NutrientKey =
| "omega3"
| "magnesium"
| "vitaminD3"
| "multivitamin"
| "probiotic";
export interface Product {
id: string;
name: string;
price: number | null;
image: string;
url: string;
brand?: string;
description?: string;
category?: string;
raw?: unknown;
}
export interface QuizResult {
realAge: number;
biologicalAge: number;
wellnessScore: number;
topNeeds: NutrientKey[];
}
/* File: src/productFeed.ts */
import { EASY_ORDERS_FEED_URL, PRODUCT_KEYWORDS } from "./config";
import { NutrientKey, Product } from "./types";
function normalizeText(value: unknown): string {
return String(value ?? "")
.toLowerCase()
.replace(/\s+/g, " ")
.trim();
}
function mapFeedItemToProduct(item: any): Product {
return {
id: String(
item?.id ??
item?.product_id ??
item?.sku ??
item?.uid ??
Math.random().toString(36).slice(2)
),
name: String(
item?.name ??
item?.title ??
item?.product_name ??
item?.label ??
"منتج بدون اسم"
),
price:
typeof item?.price === "number"
? item.price
: typeof item?.sale_price === "number"
? item.sale_price
: typeof item?.regular_price === "number"
? item.regular_price
: item?.price
? Number(item.price)
: null,
image:
item?.image ??
item?.image_link ??
item?.thumbnail ??
item?.main_image ??
item?.imageUrl ??
"/placeholder-product.png",
url:
item?.url ??
item?.link ??
item?.product_url ??
item?.url_link ??
"#",
brand: item?.brand ?? item?.vendor ?? item?.manufacturer ?? "",
description: item?.description ?? item?.short_description ?? item?.desc ?? "",
category: item?.category ?? item?.google_product_category ?? item?.cat ?? "",
raw: item,
};
}
export async function fetchProducts(): Promise<Product[]> {
const res = await fetch(EASY_ORDERS_FEED_URL, {
method: "GET",
headers: {
Accept: "application/json",
},
});
if (!res.ok) {
throw new Error(`Feed request failed: ${res.status}`);
}
const data = await res.json();
const items = Array.isArray(data)
? data
: Array.isArray(data?.products)
? data.products
: Array.isArray(data?.items)
? data.items
: Array.isArray(data?.data)
? data.data
: [];
return (items as any[]).map(mapFeedItemToProduct);
}
function scoreProductForNeed(product: Product, need: NutrientKey): number {
const keywords = PRODUCT_KEYWORDS[need];
const haystack = normalizeText(
[product.name, product.brand, product.description, product.category].join(" ")
);
let score = 0;
for (const keyword of keywords) {
if (haystack.includes(normalizeText(keyword))) {
score += 10;
}
}
if (product.price !== null) score += 1;
if (product.image && product.image !== "/placeholder-product.png") score += 1;
if (product.url && product.url !== "#") score += 1;
return score;
}
export function getTopProductsForNeed(
products: Product[],
need: NutrientKey,
limit: number = 3
): Product[] {
return [...products]
.map((product) => ({ product, score: scoreProductForNeed(product, need) }))
.filter((item) => item.score > 0)
.sort((a, b) => b.score - a.score)
.slice(0, limit)
.map((item) => item.product);
}
/* File: src/productLabels.ts */
import { Product } from "./types";
export function getMarketingLabel(index: number, product: Product): string {
switch (index) {
case 0:
return "الأكثر توافقًا مع نتيجتك";
case 1:
return "أفضل قيمة مقابل السعر";
case 2:
return "خيار بديل ممتاز";
default:
return "خيار مقترح";
}
}
export function getArabicNeedLabel(need: string): string {
switch (need) {
case "omega3":
return "أوميغا 3";
case "magnesium":
return "مغنيسيوم";
case "vitaminD3":
return "فيتامين D3";
case "multivitamin":
return "مالتي فيتامين";
case "probiotic":
return "بروبيوتك";
default:
return "مكمل مناسب";
}
}
/* File: src/whatsapp.ts */
import { Product } from "./types";
import { WHATSAPP_NUMBER } from "./config";
export function openWhatsAppOrder(product: Product, needLabel?: string) {
const text = `مرحباً، أريد طلب هذا المنتج:\n${product.name}\n${
needLabel ? `الاحتياج المقترح: ${needLabel}\n` : ""
}${product.url && product.url !== "#" ? `رابط المنتج: ${product.url}` : ""}`;
const url = `https://wa.me/${WHATSAPP_NUMBER}?text=${encodeURIComponent(text)}`;
window.open(url, "_blank");
}
/* File: src/RecommendedProducts.tsx */
import React, { useEffect, useMemo, useState } from "react";
import { NutrientKey, Product } from "./types";
import {
fetchProducts,
getTopProductsForNeed,
} from "./productFeed";
import {
getMarketingLabel,
getArabicNeedLabel,
} from "./productLabels";
import { openWhatsAppOrder } from "./whatsapp";
interface Props {
primaryNeed: NutrientKey;
}
const RecommendedProducts: React.FC<Props> = ({ primaryNeed }) => {
const [products, setProducts] = useState<Product[]>([]);
const [loading, setLoading] = useState(true);
const [error, setError] = useState("");
useEffect(() => {
let mounted = true;
async function load() {
try {
setLoading(true);
const data = await fetchProducts();
if (mounted) {
setProducts(data);
}
} catch (err) {
if (mounted) {
setError("تعذر تحميل المنتجات الآن");
}
} finally {
if (mounted) {
setLoading(false);
}
}
}
load();
return () => {
mounted = false;
};
}, []);
const topThree = useMemo(() => {
return getTopProductsForNeed(products, primaryNeed, 3);
}, [products, primaryNeed]);
if (loading) {
return (
<section className="rounded-3xl bg-white p-6 shadow-sm">
<h3 className="mb-2 text-xl font-bold">جارٍ تجهيز أفضل الخيارات لك...</h3>
<p className="text-sm text-gray-500">نقوم بمطابقة نتيجتك مع المنتجات الأنسب.</p>
</section>
);
}
if (error) {
return (
<section className="rounded-3xl bg-white p-6 shadow-sm">
<h3 className="mb-2 text-xl font-bold">تعذر تحميل المنتجات</h3>
<p className="text-sm text-red-500">{error}</p>
</section>
);
}
if (topThree.length === 0) {
return (
<section className="rounded-3xl bg-white p-6 shadow-sm">
<h3 className="mb-2 text-xl font-bold">لم نجد 3 منتجات مناسبة الآن</h3>
<p className="text-sm text-gray-500">
يمكن تعديل الكلمات المفتاحية أو بيانات المنتجات لاحقًا.
</p>
</section>
);
}
return (
<section className="space-y-4">
<div className="rounded-3xl bg-white p-6 shadow-sm">
<h3 className="text-2xl font-bold">
أفضل 3 منتجات مقترحة لاحتياج {getArabicNeedLabel(primaryNeed)}
</h3>
<p className="mt-2 text-sm text-gray-600">
اختر المنتج الأنسب لك، ثم أكمل الطلب مباشرة عبر واتساب.
</p>
</div>
<div className="grid gap-4 md:grid-cols-3">
{topThree.map((product, index) => (
<article
key={product.id}
className="overflow-hidden rounded-3xl bg-white shadow-sm border border-gray-100"
>
<div className="aspect-square bg-gray-50">
<img
src={product.image}
alt={product.name}
className="h-full w-full object-cover"
/>
</div>
<div className="space-y-3 p-5">
<span className="inline-flex rounded-full bg-emerald-50 px-3 py-1 text-xs font-medium text-emerald-700">
{getMarketingLabel(index, product)}
</span>
<h4 className="text-lg font-bold leading-7">{product.name}</h4>
{product.price !== null && (
<p className="text-base font-semibold">
{product.price} ج.م
</p>
)}
<p className="text-sm leading-6 text-gray-600">
مناسب لاحتياج {getArabicNeedLabel(primaryNeed)} حسب نتيجتك الحالية.
</p>
<div className="grid gap-2">
<button
onClick={() =>
openWhatsAppOrder(
product,
getArabicNeedLabel(primaryNeed)
)
}
className="rounded-2xl bg-black px-4 py-3 text-sm font-semibold text-white"
>
اطلب الآن عبر واتساب
</button>
{product.url && product.url !== "#" && (
<a
href={product.url}
target="_blank"
rel="noreferrer"
className="rounded-2xl border border-gray-200 px-4 py-3 text-center text-sm font-medium"
>
عرض تفاصيل المنتج
</a>
)}
</div>
</div>
</article>
))}
</div>
</section>
);
};
export default RecommendedProducts;
/* File: src/ReportPage.tsx */
import React from "react";
import { QuizResult } from "./types";
import RecommendedProducts from "./RecommendedProducts";
interface ReportPageProps {
result: QuizResult;
}
const ReportPage: React.FC<ReportPageProps> = ({ result }) => {
const primaryNeed = result.topNeeds[0];
return (
<main className="mx-auto max-w-6xl space-y-8 px-4 py-8">
<section className="rounded-3xl bg-white p-6 shadow-sm">
<h1 className="text-3xl font-bold">تقريرك الصحي الكامل</h1>
<p className="mt-2 text-gray-600">
العمر الحقيقي: {result.realAge} / العمر البيولوجي: {result.biologicalAge}
</p>
<p className="mt-2 text-gray-600">
درجة العافية: {result.wellnessScore}/100
</p>
</section>
{/* Recommended Products Section */}
<RecommendedProducts primaryNeed={primaryNeed} />
</main>
);
};
export default ReportPage;