diff --git a/app/ClientLayout.js b/app/ClientLayout.js
index 262d9a4..1a50712 100644
--- a/app/ClientLayout.js
+++ b/app/ClientLayout.js
@@ -126,6 +126,7 @@ export default function ClientLayout({ children }) {
const isAuthPage = [
"/login",
+ "/blocked",
"/register",
"/forgot-password",
"/auth/choose-role",
diff --git a/app/blocked/page.js b/app/blocked/page.js
new file mode 100644
index 0000000..2270b97
--- /dev/null
+++ b/app/blocked/page.js
@@ -0,0 +1,166 @@
+'use client';
+
+import { useState } from 'react';
+import { useRouter } from 'next/navigation';
+import { motion } from 'framer-motion';
+import { ShieldAlert, LogOut, MessageSquare, Send, Loader2 } from 'lucide-react';
+import toast, { Toaster } from 'react-hot-toast';
+import AuthService from '../services/AuthService';
+import { sendGeneralReport } from '../utils/api';
+
+export default function BlockedPage() {
+ const router = useRouter();
+ const [form, setForm] = useState({ subject: '', body: '' });
+ const [isSubmitting, setIsSubmitting] = useState(false);
+ const [isSent, setIsSent] = useState(false);
+
+ const handleLogout = () => {
+ AuthService.deleteToken();
+ router.replace('/');
+ };
+
+ const updateField = (field, value) => {
+ setForm((current) => ({ ...current, [field]: value }));
+ if (isSent) setIsSent(false);
+ };
+
+ const handleSubmit = async (event) => {
+ event.preventDefault();
+
+ if (!form.subject.trim() || !form.body.trim()) {
+ toast.error('يرجى تعبئة الموضوع والرسالة');
+ return;
+ }
+
+ setIsSubmitting(true);
+
+ try {
+ await sendGeneralReport(form.subject.trim(), form.body.trim());
+ setIsSent(true);
+ setForm({ subject: '', body: '' });
+ toast.success('تم إرسال طلب الدعم بنجاح');
+ } catch (error) {
+ toast.error('حدث خطأ أثناء إرسال طلب الدعم. حاول مرة أخرى');
+ } finally {
+ setIsSubmitting(false);
+ }
+ };
+
+ return (
+
+
+
+
+
+
+
+
+
الحساب محظور
+
+ تم تقييد وصولك إلى التطبيق. يمكنك تسجيل الخروج أو مراسلة دعم العملاء للمساعدة في حل المشكلة.
+
+
+
+
+
+
+
+
+
+
تسجيل الخروج
+
+ إنهاء الجلسة الحالية وإزالة بيانات الدخول من هذا الجهاز.
+
+
+
+
+
+
+
+
+
+
+
+
+
مراسلة دعم العملاء
+
أرسل تفاصيل المشكلة وسنقوم بمراجعتها.
+
+
+
+
+
+
+
+
+ );
+}
diff --git a/app/utils/api.js b/app/utils/api.js
index 083137e..a1555ad 100644
--- a/app/utils/api.js
+++ b/app/utils/api.js
@@ -450,13 +450,43 @@
// }
import AuthService from '../services/AuthService';
-
const API_BASE = process.env.NEXT_PUBLIC_API_URL || 'https://45.93.137.91.nip.io/api';
+const REPORT_API_BASE = process.env.NEXT_PUBLIC_REPORT_API_URL || 'http://45.93.137.91/api';
+
function isFormData(value) {
return typeof FormData !== 'undefined' && value instanceof FormData;
}
+class ApiBlockedError extends Error {
+ constructor(message = 'Your account is blocked') {
+ super(message);
+ this.name = 'ApiBlockedError';
+ this.status = 451;
+ }
+}
+
+export function isApiBlockedError(error) {
+ return error instanceof ApiBlockedError || error?.status === 451;
+}
+
+function redirectToBlockedPage() {
+ if (typeof window !== 'undefined' && window.location.pathname !== '/blocked') {
+ window.location.replace('/blocked');
+ }
+}
+
+function assertNotBlocked(response) {
+ if (response.status === 451) {
+ redirectToBlockedPage();
+ throw new ApiBlockedError();
+ }
+}
+
+function buildApiUrl(base, endpoint) {
+ return `${base.replace(/\/$/, '')}${endpoint.startsWith('/') ? endpoint : `/${endpoint}`}`;
+}
+
/**
* Generic API fetch — attaches auth token, unwraps { data } envelope
*/
@@ -484,6 +514,8 @@ async function apiFetch(endpoint, options = {}) {
: options.body,
});
+ assertNotBlocked(res);
+
if (!res.ok && res.status !== 206) {
const text = await res.text().catch(() => '');
throw new Error(`API ${res.status}: ${text || res.statusText}`);
@@ -524,6 +556,36 @@ async function authFetch(endpoint, body, token = null) {
body: bodyIsFormData ? body : JSON.stringify(body),
});
+ assertNotBlocked(res);
+
+ const text = await res.text();
+ let data = null;
+
+ try {
+ data = text ? JSON.parse(text) : null;
+ if (data && typeof data === 'object' && 'data' in data) {
+ data = data.data;
+ }
+ } catch {
+ data = text;
+ }
+
+ const message = typeof data === 'object' && data?.message ? data.message : null;
+
+ return { status: res.status, data, ok: res.ok || res.status === 206, message };
+}
+
+async function reportFetch(endpoint, body) {
+ const res = await fetch(buildApiUrl(REPORT_API_BASE, endpoint), {
+ method: 'POST',
+ headers: {
+ 'Content-Type': 'application/json',
+ },
+ body: JSON.stringify(body),
+ });
+
+ assertNotBlocked(res);
+
const text = await res.text();
let data = null;
@@ -721,6 +783,8 @@ export async function uploadPicture(file) {
body: formData,
});
+ assertNotBlocked(res);
+
const text = await res.text();
if (!res.ok) throw new Error(`Upload failed: ${res.status} ${text}`);
@@ -746,6 +810,8 @@ async function multipartAuthFetch(endpoint, formData) {
body: formData,
});
+ assertNotBlocked(res);
+
const text = await res.text();
let data = null;
@@ -948,6 +1014,8 @@ export async function registerRealEstateAgent(formData) {
body: formData,
});
+ assertNotBlocked(res);
+
const text = await res.text();
let data = null;
@@ -1028,6 +1096,13 @@ export async function filterRentProperties(params = {}) {
// ─── Reports ───
+export async function sendGeneralReport(subject, reportBody) {
+ return reportFetch('/Reports/SendGeneralReport', {
+ subject,
+ body: reportBody,
+ });
+}
+
export async function submitReport(subject, body) {
return apiFetch('/Reports', {
method: 'POST',