This commit is contained in:
@ -1,62 +1,143 @@
|
|||||||
'use client';
|
'use client';
|
||||||
|
|
||||||
import { useState, useEffect } from 'react';
|
import { useEffect, useState } from 'react';
|
||||||
import { motion } from 'framer-motion';
|
import { motion } from 'framer-motion';
|
||||||
import { FileText, Shield, CheckCircle } from 'lucide-react';
|
import { FileText, Shield, CheckCircle, Languages, Loader2, AlertCircle } from 'lucide-react';
|
||||||
import { getTerms } from '../utils/api';
|
import { getARTerms, getENTerms } from '../utils/api';
|
||||||
|
|
||||||
const staticTerms = [
|
const containerVariants = {
|
||||||
|
hidden: { opacity: 0 },
|
||||||
|
visible: {
|
||||||
|
opacity: 1,
|
||||||
|
transition: { staggerChildren: 0.08 },
|
||||||
|
},
|
||||||
|
};
|
||||||
|
|
||||||
|
const itemVariants = {
|
||||||
|
hidden: { opacity: 0, y: 24 },
|
||||||
|
visible: { opacity: 1, y: 0 },
|
||||||
|
};
|
||||||
|
|
||||||
|
const FALLBACK_TERMS = {
|
||||||
|
ar: [
|
||||||
{
|
{
|
||||||
title: 'مقدمة',
|
title: 'مقدمة',
|
||||||
content:
|
description:
|
||||||
'مرحباً بك في منصة SweetHome. باستخدامك للمنصة، فإنك توافق على الالتزام بشروط الاستخدام هذه. إذا كنت لا توافق على أي جزء من هذه الشروط، يرجى عدم استخدام المنصة. تحتفظ المنصة بحق تعديل هذه الشروط في أي وقت مع إشعار المستخدمين.',
|
'مرحباً بك في منصة SweetHome. باستخدامك للمنصة، فإنك توافق على الالتزام بشروط الاستخدام هذه. إذا كنت لا توافق على أي جزء من هذه الشروط، يرجى عدم استخدام المنصة.',
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
title: 'استخدام المنصة',
|
title: 'استخدام المنصة',
|
||||||
content:
|
description:
|
||||||
'يُسمح باستخدام المنصة للأغراض المشروعة فقط. يلتزم المستخدم بعدم استخدام المنصة في أي نشاط غير قانوني أو مخالف للقوانين السارية. كما يلتزم المستخدم بعدم محاولة الوصول غير المصرح به إلى أي جزء من المنصة أو الخوادم أو الأنظمة المتصلة بها.',
|
'يُسمح باستخدام المنصة للأغراض المشروعة فقط. يلتزم المستخدم بعدم استخدام المنصة في أي نشاط غير قانوني.',
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
title: 'حقوق ومسؤوليات المالك',
|
title: 'حقوق ومسؤوليات المالك',
|
||||||
content:
|
description:
|
||||||
'يتحمل المالك مسؤولية دقة المعلومات المقدمة عن العقار بما في ذلك الصور والوصف والسعر والتوفر. يلتزم المالك بتحديث معلومات العقار بشكل دوري. المنصة غير مسؤولة عن أي نزاعات تنشأ بين المالك والمستأجر. يجب على المالك الالتزام بجميع القوانين المحلية المتعلقة بتأجير العقارات.',
|
'يتحمل المالك مسؤولية دقة المعلومات المقدمة عن العقار بما في ذلك الصور والوصف والسعر والتوفر.',
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
title: 'حقوق ومسؤوليات المستأجر',
|
title: 'حقوق ومسؤوليات المستأجر',
|
||||||
content:
|
description:
|
||||||
'يلتزم المستأجر باستخدام العقار بطريقة مسؤولة وعدم التسبب في أي ضرر للممتلكات. يجب على المستأجر الالتزام بقوانين المنزل ومواعيد تسجيل الوصول والمغادرة. المنصة غير مسؤولة عن أي سلوك غير لائق من قبل المستأجرين.',
|
'يلتزم المستأجر باستخدام العقار بطريقة مسؤولة وعدم التسبب في أي ضرر للممتلكات.',
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
title: 'الدفع والعمولات',
|
title: 'الدفع والعمولات',
|
||||||
content:
|
description:
|
||||||
'تتقاضى المنصة عمولة على كل حصة ناجحة وفقاً للنسبة المحددة في وقت الحجز. جميع المدفوعات تتم عبر قنوات الدفع الآمنة في المنصة. أي رسوم إلغاء أو استرداد تخضع لسياسة الإلغاء المحددة في كل عقار.',
|
'تتقاضى المنصة عمولة على كل حصة ناجحة وفقاً للنسبة المحددة في وقت الحجز.',
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
title: 'خصوصية البيانات',
|
title: 'خصوصية البيانات',
|
||||||
content:
|
description:
|
||||||
'نحن نأخذ خصوصية بياناتك على محمل الجد. يتم جمع واستخدام البيانات الشخصية وفقاً لسياسة الخصوصية الخاصة بنا. نحن لا نشارك معلوماتك مع أطراف ثالثة دون موافقتك، إلا عندما يقتضي القانون ذلك.',
|
'نحن نأخذ خصوصية بياناتك على محمل الجد. يتم جمع واستخدام البيانات الشخصية وفقاً لسياسة الخصوصية الخاصة بنا.',
|
||||||
},
|
},
|
||||||
];
|
],
|
||||||
|
en: [
|
||||||
|
{
|
||||||
|
title: 'Introduction',
|
||||||
|
description:
|
||||||
|
'Welcome to SweetHome. By using our platform, you agree to comply with these terms. If you do not agree, please do not use the platform.',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
title: 'Platform Usage',
|
||||||
|
description:
|
||||||
|
'The platform may only be used for lawful purposes. Users must not engage in any illegal activity.',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
title: 'Owner Rights & Responsibilities',
|
||||||
|
description:
|
||||||
|
'Owners are responsible for the accuracy of property information including images, description, price, and availability.',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
title: 'Tenant Rights & Responsibilities',
|
||||||
|
description:
|
||||||
|
'Tenants must use the property responsibly and not cause any damage to the property.',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
title: 'Payment & Commissions',
|
||||||
|
description:
|
||||||
|
'The platform charges a commission on each successful booking according to the rate specified at the time of booking.',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
title: 'Data Privacy',
|
||||||
|
description:
|
||||||
|
'We take your data privacy seriously. Personal data is collected and used in accordance with our Privacy Policy.',
|
||||||
|
},
|
||||||
|
],
|
||||||
|
};
|
||||||
|
|
||||||
export default function TermsPage() {
|
export default function TermsPage() {
|
||||||
const [terms, setTerms] = useState(staticTerms);
|
const [terms, setTerms] = useState([]);
|
||||||
|
const [language, setLanguage] = useState('ar');
|
||||||
|
const [loading, setLoading] = useState(true);
|
||||||
|
const [error, setError] = useState('');
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
async function fetchTerms() {
|
const controller = new AbortController();
|
||||||
|
|
||||||
|
const fetchTerms = async () => {
|
||||||
try {
|
try {
|
||||||
const data = await getTerms();
|
setLoading(true);
|
||||||
if (data && Array.isArray(data) && data.length > 0) {
|
setError('');
|
||||||
setTerms(data);
|
|
||||||
|
const fetcher = language === 'ar' ? getARTerms : getENTerms;
|
||||||
|
const data = await fetcher();
|
||||||
|
|
||||||
|
if (!data) {
|
||||||
|
setTerms(FALLBACK_TERMS[language]);
|
||||||
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const raw = Array.isArray(data) ? data : data.terms || data.items || data.data || [];
|
||||||
|
|
||||||
|
if (!Array.isArray(raw) || raw.length === 0) {
|
||||||
|
setTerms(FALLBACK_TERMS[language]);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
const mapped = raw.map((item) => ({
|
||||||
|
title: item.title || item.name || '',
|
||||||
|
description: item.description || item.content || item.body || item.text || '',
|
||||||
|
}));
|
||||||
|
|
||||||
|
setTerms(mapped);
|
||||||
} catch {
|
} catch {
|
||||||
// fall back to static terms
|
setTerms(FALLBACK_TERMS[language]);
|
||||||
}
|
setError('');
|
||||||
|
} finally {
|
||||||
|
setLoading(false);
|
||||||
}
|
}
|
||||||
|
};
|
||||||
|
|
||||||
fetchTerms();
|
fetchTerms();
|
||||||
}, []);
|
|
||||||
|
return () => controller.abort();
|
||||||
|
}, [language]);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className="min-h-screen bg-gradient-to-b from-amber-50/50 to-white py-12" dir="rtl">
|
<div
|
||||||
|
dir={language === 'ar' ? 'rtl' : 'ltr'}
|
||||||
|
className="min-h-screen bg-gradient-to-b from-amber-50/50 to-white py-12"
|
||||||
|
>
|
||||||
<div className="container mx-auto px-4 max-w-4xl">
|
<div className="container mx-auto px-4 max-w-4xl">
|
||||||
<motion.div
|
<motion.div
|
||||||
initial={{ opacity: 0, y: -20 }}
|
initial={{ opacity: 0, y: -20 }}
|
||||||
@ -66,33 +147,95 @@ export default function TermsPage() {
|
|||||||
<div className="w-20 h-20 bg-amber-100 rounded-2xl flex items-center justify-center mx-auto mb-6 shadow-lg shadow-amber-100">
|
<div className="w-20 h-20 bg-amber-100 rounded-2xl flex items-center justify-center mx-auto mb-6 shadow-lg shadow-amber-100">
|
||||||
<FileText className="w-10 h-10 text-amber-600" />
|
<FileText className="w-10 h-10 text-amber-600" />
|
||||||
</div>
|
</div>
|
||||||
<h1 className="text-4xl font-bold text-gray-900 mb-4">شروط الاستخدام</h1>
|
|
||||||
|
<div className="flex items-center justify-center gap-4 mb-4">
|
||||||
|
<h1 className="text-4xl font-bold text-gray-900">
|
||||||
|
{language === 'ar' ? 'شروط الاستخدام' : 'Terms of Use'}
|
||||||
|
</h1>
|
||||||
|
<button
|
||||||
|
type="button"
|
||||||
|
onClick={() => setLanguage(language === 'ar' ? 'en' : 'ar')}
|
||||||
|
className="inline-flex items-center gap-2 rounded-full border border-amber-200 bg-white px-4 py-2 text-sm font-semibold text-gray-700 shadow-sm transition hover:shadow-md"
|
||||||
|
>
|
||||||
|
<Languages className="h-4 w-4" />
|
||||||
|
{language === 'ar' ? 'English' : 'العربية'}
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
|
||||||
<p className="text-lg text-gray-600 max-w-2xl mx-auto">
|
<p className="text-lg text-gray-600 max-w-2xl mx-auto">
|
||||||
يرجى قراءة شروط الاستخدام التالية بعناية قبل استخدام المنصة
|
{language === 'ar'
|
||||||
|
? 'يرجى قراءة شروط الاستخدام التالية بعناية قبل استخدام المنصة'
|
||||||
|
: 'Please read the following terms of use carefully before using the platform'}
|
||||||
</p>
|
</p>
|
||||||
</motion.div>
|
</motion.div>
|
||||||
|
|
||||||
<div className="space-y-6">
|
{loading && (
|
||||||
|
<div className="flex items-center justify-center gap-3 mb-8 rounded-2xl border border-amber-200 bg-amber-50 px-4 py-4 text-amber-800">
|
||||||
|
<Loader2 className="h-5 w-5 animate-spin" />
|
||||||
|
<span>
|
||||||
|
{language === 'ar'
|
||||||
|
? 'جاري تحميل شروط الاستخدام...'
|
||||||
|
: 'Loading terms of use...'}
|
||||||
|
</span>
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
|
|
||||||
|
{error && (
|
||||||
|
<motion.div
|
||||||
|
initial={{ opacity: 0, y: -10 }}
|
||||||
|
animate={{ opacity: 1, y: 0 }}
|
||||||
|
className="mb-8 rounded-2xl border border-red-200 bg-red-50 p-4 flex items-center gap-3 text-red-700"
|
||||||
|
>
|
||||||
|
<AlertCircle className="h-5 w-5 shrink-0" />
|
||||||
|
<span>{error}</span>
|
||||||
|
</motion.div>
|
||||||
|
)}
|
||||||
|
|
||||||
|
{!loading && terms.length === 0 && !error && (
|
||||||
|
<motion.div
|
||||||
|
initial={{ opacity: 0 }}
|
||||||
|
animate={{ opacity: 1 }}
|
||||||
|
className="text-center py-16 text-gray-500"
|
||||||
|
>
|
||||||
|
<FileText className="h-16 w-16 mx-auto mb-4 text-gray-300" />
|
||||||
|
<p className="text-xl font-medium">
|
||||||
|
{language === 'ar'
|
||||||
|
? 'لا توجد شروط استخدام متاحة حالياً'
|
||||||
|
: 'No terms of use available'}
|
||||||
|
</p>
|
||||||
|
</motion.div>
|
||||||
|
)}
|
||||||
|
|
||||||
|
{terms.length > 0 && (
|
||||||
|
<motion.div
|
||||||
|
variants={containerVariants}
|
||||||
|
initial="hidden"
|
||||||
|
animate="visible"
|
||||||
|
className="space-y-6"
|
||||||
|
>
|
||||||
{terms.map((term, index) => (
|
{terms.map((term, index) => (
|
||||||
<motion.div
|
<motion.div
|
||||||
key={index}
|
key={index}
|
||||||
initial={{ opacity: 0, y: 20 }}
|
variants={itemVariants}
|
||||||
animate={{ opacity: 1, y: 0 }}
|
|
||||||
transition={{ delay: index * 0.1 }}
|
|
||||||
className="bg-white rounded-2xl shadow-sm border border-gray-200 p-6 hover:shadow-md transition-shadow"
|
className="bg-white rounded-2xl shadow-sm border border-gray-200 p-6 hover:shadow-md transition-shadow"
|
||||||
>
|
>
|
||||||
<div className="flex items-start gap-4">
|
<div className="flex items-start gap-4">
|
||||||
<div className="w-10 h-10 bg-amber-100 rounded-xl flex items-center justify-center shrink-0 mt-1">
|
<div className="w-10 h-10 bg-amber-100 rounded-xl flex items-center justify-center shrink-0 mt-1">
|
||||||
<Shield className="w-5 h-5 text-amber-600" />
|
<Shield className="w-5 h-5 text-amber-600" />
|
||||||
</div>
|
</div>
|
||||||
<div>
|
<div className="min-w-0 flex-1">
|
||||||
|
{term.title && (
|
||||||
<h2 className="text-xl font-bold text-gray-900 mb-3">{term.title}</h2>
|
<h2 className="text-xl font-bold text-gray-900 mb-3">{term.title}</h2>
|
||||||
<p className="text-gray-600 leading-relaxed">{term.content}</p>
|
)}
|
||||||
|
<p className="text-gray-600 leading-relaxed whitespace-pre-wrap">
|
||||||
|
{term.description}
|
||||||
|
</p>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</motion.div>
|
</motion.div>
|
||||||
))}
|
))}
|
||||||
</div>
|
</motion.div>
|
||||||
|
)}
|
||||||
|
|
||||||
<motion.div
|
<motion.div
|
||||||
initial={{ opacity: 0 }}
|
initial={{ opacity: 0 }}
|
||||||
@ -102,9 +245,13 @@ export default function TermsPage() {
|
|||||||
>
|
>
|
||||||
<CheckCircle className="w-6 h-6 text-amber-600 shrink-0 mt-0.5" />
|
<CheckCircle className="w-6 h-6 text-amber-600 shrink-0 mt-0.5" />
|
||||||
<div>
|
<div>
|
||||||
<p className="font-bold text-amber-800 mb-1">آخر تحديث</p>
|
<p className="font-bold text-amber-800 mb-1">
|
||||||
|
{language === 'ar' ? 'آخر تحديث' : 'Last Updated'}
|
||||||
|
</p>
|
||||||
<p className="text-amber-700">
|
<p className="text-amber-700">
|
||||||
تم آخر تحديث لشروط الاستخدام في 1 مايو 2026. يرجى مراجعة هذه الصفحة بشكل دوري للاطلاع على أي تغييرات.
|
{language === 'ar'
|
||||||
|
? 'تم آخر تحديث لشروط الاستخدام في 1 مايو 2026. يرجى مراجعة هذه الصفحة بشكل دوري للاطلاع على أي تغييرات.'
|
||||||
|
: 'Last updated on May 1, 2026. Please review this page periodically for any changes.'}
|
||||||
</p>
|
</p>
|
||||||
</div>
|
</div>
|
||||||
</motion.div>
|
</motion.div>
|
||||||
|
|||||||
@ -704,8 +704,12 @@ export async function bookReservation(propertyInfoId, startDate, endDate) {
|
|||||||
|
|
||||||
// ─── Terms ───
|
// ─── Terms ───
|
||||||
|
|
||||||
export async function getTerms() {
|
export async function getARTerms() {
|
||||||
return apiFetch('/Terms/GetTerms');
|
return apiFetch('/Configuration/GetARTerms');
|
||||||
|
}
|
||||||
|
|
||||||
|
export async function getENTerms() {
|
||||||
|
return apiFetch('/Configuration/GetENTerms');
|
||||||
}
|
}
|
||||||
|
|
||||||
// ─── Profile ───
|
// ─── Profile ───
|
||||||
@ -1151,11 +1155,11 @@ export async function updateSaleReport(id, data) {
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
// ─── Terms (Add) ───
|
// ─── Terms (Add or Update) ───
|
||||||
|
|
||||||
export async function addTerm(name, description) {
|
export async function addOrUpdateTerms(terms) {
|
||||||
return apiFetch('/Terms', {
|
return apiFetch('/Terms/AddOrUpdateTerms', {
|
||||||
method: 'POST',
|
method: 'POST',
|
||||||
body: { name, description },
|
body: terms,
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
Reference in New Issue
Block a user