Compare commits
2 Commits
d375ed9d89
...
68cb802d60
| Author | SHA1 | Date | |
|---|---|---|---|
| 68cb802d60 | |||
| d22248248d |
@ -5,6 +5,8 @@ import { useTranslation } from "react-i18next";
|
|||||||
import Link from "next/link";
|
import Link from "next/link";
|
||||||
import Image from "next/image";
|
import Image from "next/image";
|
||||||
import { NavLink, MobileNavLink } from "./components/NavLinks";
|
import { NavLink, MobileNavLink } from "./components/NavLinks";
|
||||||
|
import { FavoritesProvider } from '@/app/contexts/FavoritesContext';
|
||||||
|
import FloatingSidebar from '@/app/components/FloatingSidebar';
|
||||||
import {
|
import {
|
||||||
Globe,
|
Globe,
|
||||||
LogIn,
|
LogIn,
|
||||||
@ -705,7 +707,10 @@ export default function ClientLayout({ children }) {
|
|||||||
<main
|
<main
|
||||||
className={`${!isAuthPage && !isProfilePage ? "pt-20" : ""} min-h-screen bg-gradient-to-b from-gray-50 to-white ${currentLanguage === "ar" ? "text-right" : "text-left"}`}
|
className={`${!isAuthPage && !isProfilePage ? "pt-20" : ""} min-h-screen bg-gradient-to-b from-gray-50 to-white ${currentLanguage === "ar" ? "text-right" : "text-left"}`}
|
||||||
>
|
>
|
||||||
|
<FavoritesProvider>
|
||||||
{children}
|
{children}
|
||||||
|
{!isAdmin && <FloatingSidebar isRTL={currentLanguage === 'ar'} />}
|
||||||
|
</FavoritesProvider>
|
||||||
</main>
|
</main>
|
||||||
|
|
||||||
{!isAuthPage && !isProfilePage && (
|
{!isAuthPage && !isProfilePage && (
|
||||||
|
|||||||
165
app/components/FloatingSidebar.js
Normal file
165
app/components/FloatingSidebar.js
Normal file
@ -0,0 +1,165 @@
|
|||||||
|
'use client';
|
||||||
|
|
||||||
|
import { useState } from 'react';
|
||||||
|
import { motion } from 'framer-motion';
|
||||||
|
import Link from 'next/link';
|
||||||
|
import { Heart, Bell, CreditCard } from 'lucide-react';
|
||||||
|
import { useFavorites } from '@/app/contexts/FavoritesContext';
|
||||||
|
|
||||||
|
export default function FloatingSidebar({ isRTL }) {
|
||||||
|
const { favorites } = useFavorites();
|
||||||
|
const [tooltip, setTooltip] = useState(null);
|
||||||
|
let timeoutId = null;
|
||||||
|
|
||||||
|
const showTooltip = (id) => {
|
||||||
|
timeoutId = setTimeout(() => {
|
||||||
|
setTooltip(id);
|
||||||
|
}, 300);
|
||||||
|
};
|
||||||
|
|
||||||
|
const hideTooltip = () => {
|
||||||
|
clearTimeout(timeoutId);
|
||||||
|
setTooltip(null);
|
||||||
|
};
|
||||||
|
|
||||||
|
const side = isRTL ? 'left' : 'right';
|
||||||
|
const positionStyle = {
|
||||||
|
[side]: 0,
|
||||||
|
top: '50%',
|
||||||
|
transform: 'translateY(-50%)',
|
||||||
|
};
|
||||||
|
|
||||||
|
const cardVariants = {
|
||||||
|
initial: { opacity: 0, x: isRTL ? -20 : 20 },
|
||||||
|
animate: { opacity: 1, x: 0, transition: { duration: 0.4, ease: 'easeOut' } },
|
||||||
|
};
|
||||||
|
|
||||||
|
const buttonVariants = {
|
||||||
|
rest: { scale: 1, backgroundColor: 'rgba(255,255,255,0)' },
|
||||||
|
hover: { scale: 1.05, backgroundColor: 'rgba(245,158,11,0.1)', transition: { duration: 0.2 } },
|
||||||
|
tap: { scale: 0.95 },
|
||||||
|
};
|
||||||
|
|
||||||
|
return (
|
||||||
|
<motion.div
|
||||||
|
className="fixed z-50"
|
||||||
|
style={positionStyle}
|
||||||
|
variants={cardVariants}
|
||||||
|
initial="initial"
|
||||||
|
animate="animate"
|
||||||
|
>
|
||||||
|
<div className="bg-white/80 backdrop-blur-md rounded-2xl shadow-lg border border-gray-200/50 py-3 px-2 flex flex-col gap-3 transition-all duration-300 hover:shadow-xl hover:bg-white/90">
|
||||||
|
<motion.div
|
||||||
|
className="relative group"
|
||||||
|
variants={buttonVariants}
|
||||||
|
initial="rest"
|
||||||
|
whileHover="hover"
|
||||||
|
whileTap="tap"
|
||||||
|
onMouseEnter={() => showTooltip('favorites')}
|
||||||
|
onMouseLeave={hideTooltip}
|
||||||
|
>
|
||||||
|
<Link
|
||||||
|
href="/favorites"
|
||||||
|
className="flex items-center justify-center w-12 h-12 rounded-xl transition-colors"
|
||||||
|
>
|
||||||
|
<div className="relative">
|
||||||
|
<Heart className="w-6 h-6 text-gray-600 transition-colors group-hover:text-amber-600" />
|
||||||
|
{favorites.length > 0 && (
|
||||||
|
<motion.span
|
||||||
|
initial={{ scale: 0 }}
|
||||||
|
animate={{ scale: 1 }}
|
||||||
|
className="absolute -right-1 -top-1 w-5 h-5 bg-gradient-to-r from-amber-500 to-amber-600 text-white text-xs rounded-full flex items-center justify-center shadow-md"
|
||||||
|
>
|
||||||
|
{favorites.length}
|
||||||
|
</motion.span>
|
||||||
|
)}
|
||||||
|
</div>
|
||||||
|
</Link>
|
||||||
|
{tooltip === 'favorites' && (
|
||||||
|
<div
|
||||||
|
className={`absolute ${isRTL ? 'right-full mr-2' : 'left-full ml-2'} top-1/2 -translate-y-1/2 px-2 py-1 bg-gray-800 text-white text-xs rounded-lg whitespace-nowrap z-20 shadow-lg flex items-center`}
|
||||||
|
>
|
||||||
|
<span className="relative">
|
||||||
|
المفضلة
|
||||||
|
<span
|
||||||
|
className={`absolute ${isRTL ? 'right-full -mr-1' : 'left-full -ml-1'} top-1/2 -translate-y-1/2 w-0 h-0 border-t-4 border-b-4 border-transparent ${
|
||||||
|
isRTL ? 'border-r-4 border-r-gray-800' : 'border-l-4 border-l-gray-800'
|
||||||
|
}`}
|
||||||
|
></span>
|
||||||
|
</span>
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
|
</motion.div>
|
||||||
|
<motion.div
|
||||||
|
className="relative group"
|
||||||
|
variants={buttonVariants}
|
||||||
|
initial="rest"
|
||||||
|
whileHover="hover"
|
||||||
|
whileTap="tap"
|
||||||
|
onMouseEnter={() => showTooltip('notifications')}
|
||||||
|
onMouseLeave={hideTooltip}
|
||||||
|
>
|
||||||
|
<Link
|
||||||
|
href="/notifications"
|
||||||
|
className="flex items-center justify-center w-12 h-12 rounded-xl transition-colors"
|
||||||
|
>
|
||||||
|
<div className="relative">
|
||||||
|
<Bell className="w-6 h-6 text-gray-600 transition-colors group-hover:text-amber-600" />
|
||||||
|
<motion.span
|
||||||
|
initial={{ scale: 0 }}
|
||||||
|
animate={{ scale: 1 }}
|
||||||
|
className="absolute -right-1 -top-1 w-5 h-5 bg-gradient-to-r from-red-500 to-red-600 text-white text-xs rounded-full flex items-center justify-center shadow-md"
|
||||||
|
>
|
||||||
|
3
|
||||||
|
</motion.span>
|
||||||
|
</div>
|
||||||
|
</Link>
|
||||||
|
{tooltip === 'notifications' && (
|
||||||
|
<div
|
||||||
|
className={`absolute ${isRTL ? 'right-full mr-2' : 'left-full ml-2'} top-1/2 -translate-y-1/2 px-2 py-1 bg-gray-800 text-white text-xs rounded-lg whitespace-nowrap z-20 shadow-lg flex items-center`}
|
||||||
|
>
|
||||||
|
<span className="relative">
|
||||||
|
الإشعارات
|
||||||
|
<span
|
||||||
|
className={`absolute ${isRTL ? 'right-full -mr-1' : 'left-full -ml-1'} top-1/2 -translate-y-1/2 w-0 h-0 border-t-4 border-b-4 border-transparent ${
|
||||||
|
isRTL ? 'border-r-4 border-r-gray-800' : 'border-l-4 border-l-gray-800'
|
||||||
|
}`}
|
||||||
|
></span>
|
||||||
|
</span>
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
|
</motion.div>
|
||||||
|
<motion.div
|
||||||
|
className="relative group"
|
||||||
|
variants={buttonVariants}
|
||||||
|
initial="rest"
|
||||||
|
whileHover="hover"
|
||||||
|
whileTap="tap"
|
||||||
|
onMouseEnter={() => showTooltip('payments')}
|
||||||
|
onMouseLeave={hideTooltip}
|
||||||
|
>
|
||||||
|
<Link
|
||||||
|
href="/payments"
|
||||||
|
className="flex items-center justify-center w-12 h-12 rounded-xl transition-colors"
|
||||||
|
>
|
||||||
|
<CreditCard className="w-6 h-6 text-gray-600 transition-colors group-hover:text-amber-600" />
|
||||||
|
</Link>
|
||||||
|
{tooltip === 'payments' && (
|
||||||
|
<div
|
||||||
|
className={`absolute ${isRTL ? 'right-full mr-2' : 'left-full ml-2'} top-1/2 -translate-y-1/2 px-2 py-1 bg-gray-800 text-white text-xs rounded-lg whitespace-nowrap z-20 shadow-lg flex items-center`}
|
||||||
|
>
|
||||||
|
<span className="relative">
|
||||||
|
المدفوعات
|
||||||
|
<span
|
||||||
|
className={`absolute ${isRTL ? 'right-full -mr-1' : 'left-full -ml-1'} top-1/2 -translate-y-1/2 w-0 h-0 border-t-4 border-b-4 border-transparent ${
|
||||||
|
isRTL ? 'border-r-4 border-r-gray-800' : 'border-l-4 border-l-gray-800'
|
||||||
|
}`}
|
||||||
|
></span>
|
||||||
|
</span>
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
|
</motion.div>
|
||||||
|
</div>
|
||||||
|
</motion.div>
|
||||||
|
);
|
||||||
|
}
|
||||||
53
app/contexts/FavoritesContext.js
Normal file
53
app/contexts/FavoritesContext.js
Normal file
@ -0,0 +1,53 @@
|
|||||||
|
'use client';
|
||||||
|
|
||||||
|
import { createContext, useContext, useState, useEffect } from 'react';
|
||||||
|
|
||||||
|
const FavoritesContext = createContext();
|
||||||
|
|
||||||
|
export const useFavorites = () => {
|
||||||
|
const context = useContext(FavoritesContext);
|
||||||
|
if (!context) {
|
||||||
|
throw new Error('useFavorites must be used within FavoritesProvider');
|
||||||
|
}
|
||||||
|
return context;
|
||||||
|
};
|
||||||
|
|
||||||
|
export const FavoritesProvider = ({ children }) => {
|
||||||
|
const [favorites, setFavorites] = useState([]);
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
const stored = localStorage.getItem('favorites');
|
||||||
|
if (stored) {
|
||||||
|
try {
|
||||||
|
setFavorites(JSON.parse(stored));
|
||||||
|
} catch (e) {
|
||||||
|
console.error('Failed to parse favorites', e);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}, []);
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
localStorage.setItem('favorites', JSON.stringify(favorites));
|
||||||
|
}, [favorites]);
|
||||||
|
|
||||||
|
const addFavorite = (property) => {
|
||||||
|
setFavorites(prev => {
|
||||||
|
if (prev.some(p => p.id === property.id)) return prev;
|
||||||
|
return [...prev, property];
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
|
const removeFavorite = (propertyId) => {
|
||||||
|
setFavorites(prev => prev.filter(p => p.id !== propertyId));
|
||||||
|
};
|
||||||
|
|
||||||
|
const isFavorite = (propertyId) => {
|
||||||
|
return favorites.some(p => p.id === propertyId);
|
||||||
|
};
|
||||||
|
|
||||||
|
return (
|
||||||
|
<FavoritesContext.Provider value={{ favorites, addFavorite, removeFavorite, isFavorite }}>
|
||||||
|
{children}
|
||||||
|
</FavoritesContext.Provider>
|
||||||
|
);
|
||||||
|
};
|
||||||
146
app/favorites/page.js
Normal file
146
app/favorites/page.js
Normal file
@ -0,0 +1,146 @@
|
|||||||
|
'use client';
|
||||||
|
|
||||||
|
import { useEffect, useState } from 'react';
|
||||||
|
import { useRouter } from 'next/navigation';
|
||||||
|
import { motion } from 'framer-motion';
|
||||||
|
import Link from 'next/link';
|
||||||
|
import Image from 'next/image';
|
||||||
|
import { Heart, MapPin, Bed, Bath, Square, X, ImageIcon } from 'lucide-react';
|
||||||
|
import { useFavorites } from '@/app/contexts/FavoritesContext';
|
||||||
|
import AuthService from '@/app/services/AuthService';
|
||||||
|
|
||||||
|
export default function FavoritesPage() {
|
||||||
|
const router = useRouter();
|
||||||
|
const { favorites, removeFavorite } = useFavorites();
|
||||||
|
const [isLoading, setIsLoading] = useState(true);
|
||||||
|
const [isAdmin, setIsAdmin] = useState(false);
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
if (AuthService.isAdmin()) {
|
||||||
|
router.push('/');
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
setIsAdmin(AuthService.isAdmin());
|
||||||
|
setIsLoading(false);
|
||||||
|
}, [router]);
|
||||||
|
|
||||||
|
const formatCurrency = (amount) => {
|
||||||
|
return amount?.toLocaleString() + ' ل.س';
|
||||||
|
};
|
||||||
|
|
||||||
|
if (isLoading) {
|
||||||
|
return (
|
||||||
|
<div className="min-h-screen flex items-center justify-center">
|
||||||
|
<div className="text-center">
|
||||||
|
<div className="w-16 h-16 border-4 border-amber-500 border-t-transparent rounded-full animate-spin mx-auto mb-4" />
|
||||||
|
<p className="text-gray-600">جاري التحميل...</p>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
return (
|
||||||
|
<div className="min-h-screen bg-gray-50 py-8">
|
||||||
|
<div className="container mx-auto px-4 max-w-6xl">
|
||||||
|
<div className="mb-8">
|
||||||
|
<h1 className="text-3xl font-bold text-gray-900 mb-2">المفضلة</h1>
|
||||||
|
<p className="text-gray-600">العقارات التي قمت بحفظها</p>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
{favorites.length === 0 ? (
|
||||||
|
<div className="bg-white rounded-2xl p-12 text-center border-2 border-dashed border-gray-300">
|
||||||
|
<Heart className="w-16 h-16 text-gray-300 mx-auto mb-4" />
|
||||||
|
<h3 className="text-xl font-bold text-gray-700 mb-2">لا توجد عقارات في المفضلة</h3>
|
||||||
|
<p className="text-gray-500 mb-6">يمكنك إضافة العقارات التي تعجبك بالنقر على أيقونة القلب</p>
|
||||||
|
<Link
|
||||||
|
href="/properties"
|
||||||
|
className="inline-flex items-center gap-2 bg-amber-500 text-white px-6 py-3 rounded-xl font-medium hover:bg-amber-600 transition-colors"
|
||||||
|
>
|
||||||
|
استعرض العقارات
|
||||||
|
</Link>
|
||||||
|
</div>
|
||||||
|
) : (
|
||||||
|
<div className="grid grid-cols-1 md:grid-cols-2 lg:grid-cols-3 gap-6">
|
||||||
|
{favorites.map((property) => (
|
||||||
|
<motion.div
|
||||||
|
key={property.id}
|
||||||
|
initial={{ opacity: 0, y: 20 }}
|
||||||
|
animate={{ opacity: 1, y: 0 }}
|
||||||
|
className="bg-white rounded-2xl shadow-sm hover:shadow-md transition-all overflow-hidden border border-gray-200"
|
||||||
|
>
|
||||||
|
<div className="relative h-48 bg-gray-100">
|
||||||
|
{property.images && property.images[0] ? (
|
||||||
|
<Image
|
||||||
|
src={property.images[0]}
|
||||||
|
alt={property.title}
|
||||||
|
fill
|
||||||
|
className="object-cover"
|
||||||
|
sizes="(max-width: 768px) 100vw, (max-width: 1200px) 50vw, 33vw"
|
||||||
|
/>
|
||||||
|
) : (
|
||||||
|
<div className="w-full h-full flex items-center justify-center">
|
||||||
|
<ImageIcon className="w-12 h-12 text-gray-400" />
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
|
<button
|
||||||
|
onClick={() => removeFavorite(property.id)}
|
||||||
|
className="absolute top-2 right-2 w-8 h-8 bg-white/90 rounded-full flex items-center justify-center hover:bg-red-50 transition-colors shadow-sm"
|
||||||
|
>
|
||||||
|
<X className="w-4 h-4 text-red-500" />
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div className="p-5">
|
||||||
|
<div className="flex justify-between items-start mb-3">
|
||||||
|
<div>
|
||||||
|
<div className="flex items-center gap-2 mb-2">
|
||||||
|
<span className="px-2 py-1 bg-gray-100 text-gray-700 rounded-lg text-xs font-medium">
|
||||||
|
{property.type === 'apartment' ? 'شقة' : property.type === 'villa' ? 'فيلا' : 'بيت'}
|
||||||
|
</span>
|
||||||
|
</div>
|
||||||
|
<h3 className="font-bold text-gray-900 mb-1 line-clamp-1">{property.title}</h3>
|
||||||
|
<div className="flex items-center gap-1 text-gray-500 text-xs mb-2">
|
||||||
|
<MapPin className="w-3 h-3" />
|
||||||
|
<span className="line-clamp-1">
|
||||||
|
{property.location.city}، {property.location.district}
|
||||||
|
</span>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div className="text-left">
|
||||||
|
<div className="text-xl font-bold text-gray-900">{formatCurrency(property.price)}</div>
|
||||||
|
<div className="text-xs text-gray-500">/{property.priceUnit === 'daily' ? 'يوم' : 'شهر'}</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div className="flex justify-between items-center mb-4">
|
||||||
|
<div className="flex items-center gap-3 text-gray-600 text-sm">
|
||||||
|
<div className="flex items-center gap-1">
|
||||||
|
<Bed className="w-4 h-4" />
|
||||||
|
<span>{property.bedrooms}</span>
|
||||||
|
</div>
|
||||||
|
<div className="flex items-center gap-1">
|
||||||
|
<Bath className="w-4 h-4" />
|
||||||
|
<span>{property.bathrooms}</span>
|
||||||
|
</div>
|
||||||
|
<div className="flex items-center gap-1">
|
||||||
|
<Square className="w-4 h-4" />
|
||||||
|
<span>{property.area}م²</span>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<Link
|
||||||
|
href={`/property/${property.id}`}
|
||||||
|
className="block w-full bg-amber-500 text-white py-3 rounded-xl font-medium hover:bg-amber-600 transition-colors text-center"
|
||||||
|
>
|
||||||
|
عرض التفاصيل
|
||||||
|
</Link>
|
||||||
|
</div>
|
||||||
|
</motion.div>
|
||||||
|
))}
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
}
|
||||||
132
app/notifications/page.js
Normal file
132
app/notifications/page.js
Normal file
@ -0,0 +1,132 @@
|
|||||||
|
'use client';
|
||||||
|
|
||||||
|
import { useEffect, useState } from 'react';
|
||||||
|
import { useRouter } from 'next/navigation';
|
||||||
|
import { Bell, CheckCircle, XCircle, Calendar, MessageCircle } from 'lucide-react';
|
||||||
|
import AuthService from '@/app/services/AuthService';
|
||||||
|
|
||||||
|
const mockNotifications = [
|
||||||
|
{
|
||||||
|
id: 1,
|
||||||
|
type: 'booking',
|
||||||
|
title: 'تأكيد الحجز',
|
||||||
|
message: 'تم تأكيد حجزك في فيلا المزة للفترة 10-15 مارس',
|
||||||
|
date: '2024-03-01',
|
||||||
|
read: false,
|
||||||
|
icon: CheckCircle,
|
||||||
|
color: 'text-green-600',
|
||||||
|
bgColor: 'bg-green-50'
|
||||||
|
},
|
||||||
|
{
|
||||||
|
id: 2,
|
||||||
|
type: 'payment',
|
||||||
|
title: 'دفعة مستلمة',
|
||||||
|
message: 'تم استلام دفعة الإيجار بقيمة 500,000 ل.س',
|
||||||
|
date: '2024-02-28',
|
||||||
|
read: false,
|
||||||
|
icon: MessageCircle,
|
||||||
|
color: 'text-blue-600',
|
||||||
|
bgColor: 'bg-blue-50'
|
||||||
|
},
|
||||||
|
{
|
||||||
|
id: 3,
|
||||||
|
type: 'reminder',
|
||||||
|
title: 'تذكير بالإيجار',
|
||||||
|
message: 'ينتهي عقد الإيجار خلال 3 أيام',
|
||||||
|
date: '2024-02-25',
|
||||||
|
read: true,
|
||||||
|
icon: Calendar,
|
||||||
|
color: 'text-amber-600',
|
||||||
|
bgColor: 'bg-amber-50'
|
||||||
|
}
|
||||||
|
];
|
||||||
|
|
||||||
|
export default function NotificationsPage() {
|
||||||
|
const router = useRouter();
|
||||||
|
const [notifications, setNotifications] = useState([]);
|
||||||
|
const [isLoading, setIsLoading] = useState(true);
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
if (AuthService.isAdmin()) {
|
||||||
|
router.push('/');
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
setTimeout(() => {
|
||||||
|
setNotifications(mockNotifications);
|
||||||
|
setIsLoading(false);
|
||||||
|
}, 500);
|
||||||
|
}, [router]);
|
||||||
|
|
||||||
|
const markAsRead = (id) => {
|
||||||
|
setNotifications(prev =>
|
||||||
|
prev.map(n => (n.id === id ? { ...n, read: true } : n))
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
const markAllAsRead = () => {
|
||||||
|
setNotifications(prev => prev.map(n => ({ ...n, read: true })));
|
||||||
|
};
|
||||||
|
|
||||||
|
const unreadCount = notifications.filter(n => !n.read).length;
|
||||||
|
|
||||||
|
if (isLoading) {
|
||||||
|
return (
|
||||||
|
<div className="min-h-screen flex items-center justify-center">
|
||||||
|
<div className="text-center">
|
||||||
|
<div className="w-16 h-16 border-4 border-amber-500 border-t-transparent rounded-full animate-spin mx-auto mb-4" />
|
||||||
|
<p className="text-gray-600">جاري التحميل...</p>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
return (
|
||||||
|
<div className="min-h-screen bg-gray-50 py-8">
|
||||||
|
<div className="container mx-auto px-4 max-w-4xl">
|
||||||
|
<div className="flex justify-between items-center mb-8">
|
||||||
|
<div>
|
||||||
|
<h1 className="text-3xl font-bold text-gray-900 mb-2">الإشعارات</h1>
|
||||||
|
<p className="text-gray-600">
|
||||||
|
{unreadCount > 0 ? `لديك ${unreadCount} إشعار غير مقروء` : 'جميع الإشعارات مقروءة'}
|
||||||
|
</p>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
{notifications.length === 0 ? (
|
||||||
|
<div className="bg-white rounded-2xl p-12 text-center border-2 border-dashed border-gray-300">
|
||||||
|
<Bell className="w-16 h-16 text-gray-300 mx-auto mb-4" />
|
||||||
|
<h3 className="text-xl font-bold text-gray-700 mb-2">لا توجد إشعارات</h3>
|
||||||
|
<p className="text-gray-500">ستظهر هنا الإشعارات المتعلقة بحجوزاتك ومدفوعاتك</p>
|
||||||
|
</div>
|
||||||
|
) : (
|
||||||
|
<div className="space-y-4">
|
||||||
|
{notifications.map((notification) => {
|
||||||
|
const Icon = notification.icon;
|
||||||
|
return (
|
||||||
|
<div
|
||||||
|
key={notification.id}
|
||||||
|
className={`bg-white rounded-2xl shadow-sm border transition-all hover:shadow-md 'border-gray-200'}`}
|
||||||
|
>
|
||||||
|
<div className="p-5 flex gap-4">
|
||||||
|
<div className={`w-12 h-12 ${notification.bgColor} rounded-full flex items-center justify-center flex-shrink-0`}>
|
||||||
|
<Icon className={`w-6 h-6 ${notification.color}`} />
|
||||||
|
</div>
|
||||||
|
<div className="flex-1">
|
||||||
|
<div className="flex justify-between items-start">
|
||||||
|
<div>
|
||||||
|
<h3 className="font-bold text-gray-900">{notification.title}</h3>
|
||||||
|
<p className="text-gray-600 text-sm mt-1">{notification.message}</p>
|
||||||
|
<p className="text-xs text-gray-400 mt-2">{notification.date}</p>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
})}
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
}
|
||||||
96
app/payments/page.js
Normal file
96
app/payments/page.js
Normal file
@ -0,0 +1,96 @@
|
|||||||
|
'use client';
|
||||||
|
|
||||||
|
import { useEffect, useState } from 'react';
|
||||||
|
import { useRouter } from 'next/navigation';
|
||||||
|
import { CreditCard, Download, Eye } from 'lucide-react';
|
||||||
|
import AuthService from '@/app/services/AuthService';
|
||||||
|
import Link from 'next/link';
|
||||||
|
|
||||||
|
const mockPayments = [
|
||||||
|
{
|
||||||
|
id: 1,
|
||||||
|
property: 'فيلا فاخرة في المزة',
|
||||||
|
amount: 2500000,
|
||||||
|
date: '2024-03-10',
|
||||||
|
status: 'completed',
|
||||||
|
invoiceId: 'INV-001'
|
||||||
|
},
|
||||||
|
{
|
||||||
|
id: 2,
|
||||||
|
property: 'شقة حديثة في الشهباء',
|
||||||
|
amount: 750000,
|
||||||
|
date: '2024-03-05',
|
||||||
|
status: 'completed',
|
||||||
|
invoiceId: 'INV-002'
|
||||||
|
}
|
||||||
|
];
|
||||||
|
|
||||||
|
export default function PaymentsPage() {
|
||||||
|
const router = useRouter();
|
||||||
|
const [payments, setPayments] = useState([]);
|
||||||
|
const [isLoading, setIsLoading] = useState(true);
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
if (AuthService.isAdmin()) {
|
||||||
|
router.push('/');
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
setTimeout(() => {
|
||||||
|
setPayments(mockPayments);
|
||||||
|
setIsLoading(false);
|
||||||
|
}, 500);
|
||||||
|
}, [router]);
|
||||||
|
|
||||||
|
const formatCurrency = (amount) => amount?.toLocaleString() + ' ل.س';
|
||||||
|
|
||||||
|
if (isLoading) {
|
||||||
|
return (
|
||||||
|
<div className="min-h-screen flex items-center justify-center">
|
||||||
|
<div className="text-center">
|
||||||
|
<div className="w-16 h-16 border-4 border-amber-500 border-t-transparent rounded-full animate-spin mx-auto mb-4" />
|
||||||
|
<p className="text-gray-600">جاري التحميل...</p>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
return (
|
||||||
|
<div className="min-h-screen bg-gray-50 py-8">
|
||||||
|
<div className="container mx-auto px-4 max-w-4xl">
|
||||||
|
<div className="mb-8">
|
||||||
|
<h1 className="text-3xl font-bold text-gray-900 mb-2">المدفوعات</h1>
|
||||||
|
<p className="text-gray-600">سجل المعاملات المالية والفواتير</p>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
{payments.length === 0 ? (
|
||||||
|
<div className="bg-white rounded-2xl p-12 text-center border-2 border-dashed border-gray-300">
|
||||||
|
<CreditCard className="w-16 h-16 text-gray-300 mx-auto mb-4" />
|
||||||
|
<h3 className="text-xl font-bold text-gray-700 mb-2">لا توجد معاملات</h3>
|
||||||
|
<p className="text-gray-500">ستظهر هنا مدفوعاتك للحجوزات</p>
|
||||||
|
</div>
|
||||||
|
) : (
|
||||||
|
<div className="space-y-4">
|
||||||
|
{payments.map((payment) => (
|
||||||
|
<div key={payment.id} className="bg-white rounded-2xl shadow-sm border border-gray-200 p-5 hover:shadow-md transition-all">
|
||||||
|
<div className="flex flex-col md:flex-row justify-between items-start md:items-center gap-4">
|
||||||
|
<div>
|
||||||
|
<h3 className="font-bold text-gray-900">{payment.property}</h3>
|
||||||
|
<p className="text-sm text-gray-500 mt-1">رقم الفاتورة: {payment.invoiceId}</p>
|
||||||
|
<p className="text-xs text-gray-400 mt-2">{payment.date}</p>
|
||||||
|
</div>
|
||||||
|
<div className="text-right">
|
||||||
|
<div className="text-xl font-bold text-amber-600">{formatCurrency(payment.amount)}</div>
|
||||||
|
<span className="inline-block px-2 py-1 bg-green-100 text-green-800 rounded-lg text-xs mt-1">
|
||||||
|
مكتمل
|
||||||
|
</span>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
</div>
|
||||||
|
))}
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
}
|
||||||
Reference in New Issue
Block a user