Compare commits

...

2 Commits

Author SHA1 Message Date
0ba435fd7e Merge branch 'main' of http://45.93.137.91:3000/Rahaf/SweetHome
All checks were successful
Build frontend / build (push) Successful in 1m56s
2026-04-15 12:10:45 +03:00
9c2a748ae9 Added API for notifications and edit style 2026-04-15 12:07:39 +03:00
5 changed files with 135 additions and 79 deletions

View File

@ -6,6 +6,7 @@ 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 { FavoritesProvider } from '@/app/contexts/FavoritesContext';
import { NotificationsProvider } from '@/app/contexts/NotificationsContext';
import FloatingSidebar from '@/app/components/FloatingSidebar'; import FloatingSidebar from '@/app/components/FloatingSidebar';
import { import {
Globe, Globe,
@ -708,10 +709,12 @@ 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"}`}
> >
<NotificationsProvider>
<FavoritesProvider> <FavoritesProvider>
{children} {children}
<FloatingSidebar isRTL={currentLanguage === 'ar'} isAdmin={isAdmin} /> <FloatingSidebar isRTL={currentLanguage === 'ar'} isAdmin={isAdmin} />
</FavoritesProvider> </FavoritesProvider>
</NotificationsProvider>
</main> </main>
{!isAuthPage && !isProfilePage && ( {!isAuthPage && !isProfilePage && (

View File

@ -5,9 +5,11 @@ import { motion } from 'framer-motion';
import Link from 'next/link'; import Link from 'next/link';
import { Heart, Bell, CreditCard, Shield, UserPlus } from 'lucide-react'; import { Heart, Bell, CreditCard, Shield, UserPlus } from 'lucide-react';
import { useFavorites } from '@/app/contexts/FavoritesContext'; import { useFavorites } from '@/app/contexts/FavoritesContext';
import { useNotifications } from '@/app/contexts/NotificationsContext';
export default function FloatingSidebar({ isRTL, isAdmin }) { export default function FloatingSidebar({ isRTL, isAdmin }) {
const { favorites } = useFavorites(); const { favorites } = useFavorites();
const { unreadCount } = useNotifications();
const [tooltip, setTooltip] = useState(null); const [tooltip, setTooltip] = useState(null);
let timeoutId = null; let timeoutId = null;
@ -150,13 +152,15 @@ export default function FloatingSidebar({ isRTL, isAdmin }) {
> >
<div className="relative"> <div className="relative">
<Bell className="w-6 h-6 text-gray-600 transition-colors group-hover:text-amber-600" /> <Bell className="w-6 h-6 text-gray-600 transition-colors group-hover:text-amber-600" />
{unreadCount > 0 && (
<motion.span <motion.span
initial={{ scale: 0 }} initial={{ scale: 0 }}
animate={{ scale: 1 }} animate={{ scale: 1 }}
className="absolute -right-1 -top-1 w-5 h-5 bg-linear-to-r from-red-500 to-red-600 text-white text-xs rounded-full flex items-center justify-center shadow-md" className="absolute -right-1 -top-1 w-5 h-5 bg-linear-to-r from-red-500 to-red-600 text-white text-xs rounded-full flex items-center justify-center shadow-md"
> >
3 {unreadCount}
</motion.span> </motion.span>
)}
</div> </div>
</Link> </Link>
{renderTooltip('notifications', 'الإشعارات')} {renderTooltip('notifications', 'الإشعارات')}

View File

@ -0,0 +1,75 @@
'use client';
import { createContext, useContext, useState, useEffect, useCallback } from 'react';
import { getUserNotifications } from '../utils/api';
import AuthService from '../services/AuthService';
const NotificationsContext = createContext();
export const useNotifications = () => {
const context = useContext(NotificationsContext);
if (!context) {
throw new Error('useNotifications must be used within NotificationsProvider');
}
return context;
};
export function NotificationsProvider({ children }) {
const [notifications, setNotifications] = useState([]);
const [unreadCount, setUnreadCount] = useState(0);
const [isLoading, setIsLoading] = useState(false);
const fetchNotifications = useCallback(async () => {
if (!AuthService.isAuthenticated()) {
setNotifications([]);
setUnreadCount(0);
return;
}
setIsLoading(true);
try {
const data = await getUserNotifications();
const notificationsArray = Array.isArray(data) ? data : [];
setNotifications(notificationsArray);
// Assuming all are unread for now, or add logic to check 'read' field if exists
setUnreadCount(notificationsArray.length);
} catch (error) {
console.error('Error fetching notifications:', error);
setNotifications([]);
setUnreadCount(0);
} finally {
setIsLoading(false);
}
}, []);
useEffect(() => {
fetchNotifications();
}, [fetchNotifications]);
const markAsRead = useCallback((id) => {
setNotifications(prev =>
prev.map(n => (n.id === id ? { ...n, read: true } : n))
);
setUnreadCount(prev => Math.max(0, prev - 1));
}, []);
const markAllAsRead = useCallback(() => {
setNotifications(prev => prev.map(n => ({ ...n, read: true })));
setUnreadCount(0);
}, []);
const value = {
notifications,
unreadCount,
isLoading,
fetchNotifications,
markAsRead,
markAllAsRead,
};
return (
<NotificationsContext.Provider value={value}>
{children}
</NotificationsContext.Provider>
);
}

View File

@ -4,71 +4,28 @@ import { useEffect, useState } from 'react';
import { useRouter } from 'next/navigation'; import { useRouter } from 'next/navigation';
import { Bell, CheckCircle, XCircle, Calendar, MessageCircle } from 'lucide-react'; import { Bell, CheckCircle, XCircle, Calendar, MessageCircle } from 'lucide-react';
import AuthService from '@/app/services/AuthService'; import AuthService from '@/app/services/AuthService';
import { useNotifications } from '@/app/contexts/NotificationsContext';
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() { export default function NotificationsPage() {
const router = useRouter(); const router = useRouter();
const [notifications, setNotifications] = useState([]); const { notifications, unreadCount, isLoading } = useNotifications();
const [isLoading, setIsLoading] = useState(true); const [error, setError] = useState(null);
useEffect(() => { useEffect(() => {
if (AuthService.isAdmin()) { if (!AuthService.isAuthenticated()) {
router.push('/'); router.push('/login');
return; return;
} }
setTimeout(() => {
setNotifications(mockNotifications);
setIsLoading(false);
}, 500);
}, [router]); }, [router]);
const markAsRead = (id) => { const markAsRead = (id) => {
setNotifications(prev => // This will be handled by context if needed
prev.map(n => (n.id === id ? { ...n, read: true } : n))
);
}; };
const markAllAsRead = () => { const markAllAsRead = () => {
setNotifications(prev => prev.map(n => ({ ...n, read: true }))); // This will be handled by context if needed
}; };
const unreadCount = notifications.filter(n => !n.read).length;
if (isLoading) { if (isLoading) {
return ( return (
<div className="min-h-screen flex items-center justify-center"> <div className="min-h-screen flex items-center justify-center">
@ -80,6 +37,18 @@ export default function NotificationsPage() {
); );
} }
if (error) {
return (
<div className="min-h-screen flex items-center justify-center">
<div className="text-center">
<XCircle className="w-16 h-16 text-red-500 mx-auto mb-4" />
<h3 className="text-xl font-bold text-gray-700 mb-2">خطأ في التحميل</h3>
<p className="text-gray-500">{error}</p>
</div>
</div>
);
}
return ( return (
<div className="min-h-screen bg-gray-50 py-8"> <div className="min-h-screen bg-gray-50 py-8">
<div className="container mx-auto px-4 max-w-4xl"> <div className="container mx-auto px-4 max-w-4xl">
@ -100,30 +69,31 @@ export default function NotificationsPage() {
</div> </div>
) : ( ) : (
<div className="space-y-4"> <div className="space-y-4">
{notifications.map((notification) => { {notifications.map((notification, index) => (
const Icon = notification.icon;
return (
<div <div
key={notification.id} key={index}
className={`bg-white rounded-2xl shadow-sm border transition-all hover:shadow-md 'border-gray-200'}`} 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="p-5 flex gap-4">
<div className={`w-12 h-12 ${notification.bgColor} rounded-full flex items-center justify-center flex-shrink-0`}> <div className="w-12 h-12 bg-blue-50 rounded-full flex items-center justify-center shrink-0">
<Icon className={`w-6 h-6 ${notification.color}`} /> <Bell className="w-6 h-6 text-blue-600" />
</div> </div>
<div className="flex-1"> <div className="flex-1">
<div className="flex justify-between items-start"> <div className="flex justify-between items-start">
<div> <div>
<h3 className="font-bold text-gray-900">{notification.title}</h3> <h3 className="font-bold text-gray-900">{notification.title}</h3>
{notification.message && (
<p className="text-gray-600 text-sm mt-1">{notification.message}</p> <p className="text-gray-600 text-sm mt-1">{notification.message}</p>
)}
{notification.date && (
<p className="text-xs text-gray-400 mt-2">{notification.date}</p> <p className="text-xs text-gray-400 mt-2">{notification.date}</p>
)}
</div> </div>
</div> </div>
</div> </div>
</div> </div>
</div> </div>
); ))}
})}
</div> </div>
)} )}
</div> </div>

View File

@ -366,3 +366,7 @@ export async function addFavoriteProperty(propId) {
export async function removeFavoriteProperty(favePropId) { export async function removeFavoriteProperty(favePropId) {
return apiFetch(`/FavoriteProperty/Remove?favePropId=${favePropId}`, { method: 'DELETE' }); return apiFetch(`/FavoriteProperty/Remove?favePropId=${favePropId}`, { method: 'DELETE' });
} }
export async function getUserNotifications() {
return apiFetch('/Notifications/GetUserNotifications');
}