diff --git a/app/components/ratings/RatingForm.js b/app/components/ratings/RatingForm.js
new file mode 100644
index 0000000..820ccfa
--- /dev/null
+++ b/app/components/ratings/RatingForm.js
@@ -0,0 +1,216 @@
+import { useState } from 'react';
+import { motion } from 'framer-motion';
+import { Star, Edit2, X, Check, Clock } from 'lucide-react';
+import StarRating from './StarRating.js';
+import toast, { Toaster } from 'react-hot-toast';
+import { rateProperty, rateCustomer, getUserPropertyRating, canRateProperty } from '../../utils/ratings.js';
+
+const RatingForm = ({
+ propertyId,
+ userId,
+ propertyOwner = false,
+ initialRating = 0,
+ initialComment = '',
+ onSubmitSuccess
+}) => {
+ const [rating, setRating] = useState(initialRating);
+ const [comment, setComment] = useState(initialComment);
+ const [loading, setLoading] = useState(false);
+ const [showForm, setShowForm] = useState(false);
+ const [userRating, setUserRating] = useState(null);
+
+ // Check if user has already rated
+ useState(() => {
+ async function fetchUserRating() {
+ try {
+ const rating = await getUserPropertyRating(propertyId, userId);
+ if (rating) {
+ setUserRating(rating);
+ setRating(rating.rating);
+ setComment(rating.comment || '');
+ }
+ } catch (error) {
+ console.error('[RatingForm] Failed to fetch user rating:', error);
+ }
+ }
+
+ if (propertyId && userId) {
+ fetchUserRating();
+ }
+ }, [propertyId, userId]);
+
+ const handleSubmit = async (e) => {
+ e.preventDefault();
+
+ if (!rating) {
+ toast.error('يرجى إعطاء تقييم من 1 إلى 5 نجوم');
+ return;
+ }
+
+ setLoading(true);
+
+ try {
+ const ratingData = {
+ propertyId,
+ customerId: userId,
+ rating,
+ comment: comment.trim() || null
+ };
+
+ await rateProperty(ratingData);
+
+ toast.success('تم إرسال التقييم بنجاح!');
+
+ // Reset form
+ setRating(0);
+ setComment('');
+ setShowForm(false);
+
+ if (onSubmitSuccess) {
+ onSubmitSuccess();
+ }
+ } catch (error) {
+ console.error('[RatingForm] Failed to submit rating:', error);
+ toast.error('حدث خطأ أثناء إرسال التقييم. يرجى المحاولة مرة أخرى.');
+ } finally {
+ setLoading(false);
+ }
+ };
+
+ const handleEdit = () => {
+ setShowForm(true);
+ setRating(userRating?.rating || 0);
+ setComment(userRating?.comment || '');
+ };
+
+ const handleCancel = () => {
+ setShowForm(false);
+ setRating(userRating?.rating || 0);
+ setComment(userRating?.comment || '');
+ };
+
+ if (!propertyId || !userId) {
+ return null;
+ }
+
+ return (
+
+
+
+ {/* Display existing rating */}
+ {userRating && !showForm && (
+
+
+
+
+ {userRating.rating}
+ من 5
+
+
+
+
+ {userRating.comment && (
+
+ "{userRating.comment}"
+
+ )}
+
+
+
+ {userRating.createdAt ? new Date(userRating.createdAt).toLocaleDateString('ar-SA') : ''}
+
+
+ )}
+
+ {/* Rating form */}
+ {showForm && (
+
+
+
+ )}
+
+ {/* Add rating button */}
+ {!userRating && !showForm && (
+
setShowForm(true)}
+ >
+
+ قيّم هذا العقار
+ شارك تجربتك مع المستأجرين الآخرين
+
+ )}
+
+ );
+};
+
+export default RatingForm;
\ No newline at end of file
diff --git a/app/components/ratings/RatingList.js b/app/components/ratings/RatingList.js
new file mode 100644
index 0000000..ee9f05a
--- /dev/null
+++ b/app/components/ratings/RatingList.js
@@ -0,0 +1,149 @@
+'use client';
+
+import { useState, useEffect } from 'react';
+import { motion } from 'framer-motion';
+import { Star } from 'lucide-react';
+import { getPropertyRatings } from '../../utils/ratings.js';
+import toast, { Toaster } from 'react-hot-toast';
+
+const RatingList = ({ propertyId, userId }) => {
+ const [reviews, setReviews] = useState([]);
+ const [loading, setLoading] = useState(true);
+ const [error, setError] = useState(null);
+
+ useEffect(() => {
+ const fetchReviews = async () => {
+ if (!propertyId) {
+ setLoading(false);
+ return;
+ }
+
+ try {
+ setLoading(true);
+ const data = await getPropertyReviews(propertyId);
+ setReviews(data || []);
+ setError(null);
+ } catch (err) {
+ console.error('[RatingList] Failed to fetch reviews:', err);
+ setError('فشل تحميل التقييمات');
+ setReviews([]);
+ } finally {
+ setLoading(false);
+ }
+ };
+
+ fetchReviews();
+ }, [propertyId]);
+
+ if (loading) {
+ return (
+
+
+ جاري تحميل التقييمات...
+
+ );
+ }
+
+ if (error) {
+ return (
+
+ {error}
+
+ );
+ }
+
+ if (reviews.length === 0) {
+ return (
+
+ لا توجد تقييمات حتى الآن. كن أول من يقيم هذا العقار!
+
+ );
+ }
+
+ // Calculate average rating
+ const averageRating = reviews.reduce((sum, review) => sum + review.rating, 0) / reviews.length;
+
+ return (
+
+
+
+
+ {/* Header with average rating */}
+
+
+
تقييمات المستأجرين
+
+ {reviews.length} تقييمات
+
+
+
+
+ {Array.from({ length: 5 }).map((_, index) => (
+
+ ))}
+ {averageRating % 1 !== 0 && (
+
+ )}
+ {averageRating.toFixed(1)}
+
+
+
+
+ {/* Reviews list */}
+
+ {reviews.map((review, index) => (
+
+
+
+
+
+
+
+
{review.userName || 'مستأجر'}
+
+ {Array.from({ length: 5 }).map((_, starIndex) => (
+
+ ))}
+ ({review.rating}/5)
+
+
+
+
+ {review.createdAt ? new Date(review.createdAt).toLocaleDateString('ar-SA') : ''}
+
+
+
+ {review.comment && (
+
{review.comment}
+ )}
+
+ ))}
+
+
+
+ );
+};
+
+export default RatingList;
\ No newline at end of file
diff --git a/app/components/ratings/StarRating.js b/app/components/ratings/StarRating.js
new file mode 100644
index 0000000..ac2d9bd
--- /dev/null
+++ b/app/components/ratings/StarRating.js
@@ -0,0 +1,90 @@
+import { useState } from 'react';
+import { motion } from 'framer-motion';
+import { Star } from 'lucide-react';
+
+const StarRating = ({
+ rating,
+ onRatingChange,
+ maxStars = 5,
+ size = 24,
+ color = '#ffc107',
+ readOnly = false,
+ className = ''
+}) => {
+ const [hoverRating, setHoverRating] = useState(null);
+
+ const handleClick = (value) => {
+ if (!readOnly && onRatingChange) {
+ onRatingChange(value);
+ }
+ };
+
+ const handleMouseEnter = (value) => {
+ if (!readOnly) {
+ setHoverRating(value);
+ }
+ };
+
+ const handleMouseLeave = () => {
+ if (!readOnly) {
+ setHoverRating(null);
+ }
+ };
+
+ const getStarIcon = (index) => {
+ const currentRating = hoverRating !== null ? hoverRating : rating;
+
+ if (currentRating > index) {
+ const hasHalfStar = currentRating % 1 > 0.5 && index + 0.5 <= currentRating;
+ if (hasHalfStar) {
+ // For half star, we'll use a combination approach or just show full star
+ // Since we don't have StarOutline, we'll approximate with full stars
+ return ;
+ }
+ return ;
+ }
+ return ;
+ };
+
+ return (
+
+ {[...Array(maxStars)].map((_, index) => (
+ handleClick(index + 1)}
+ onMouseEnter={() => handleMouseEnter(index + 1)}
+ >
+ {getStarIcon(index)}
+
+ ))}
+
+ );
+};
+
+export default StarRating;
+
+// Helper functions
+export function getStarCount(rating, maxStars = 5) {
+ return Math.round(rating * maxStars) / maxStars;
+}
+
+export function formatRating(rating) {
+ if (rating === 0) return 'لا يوجد تقييم';
+ return `${rating.toFixed(1)}`; // Show 1 decimal place
+}
+
+export function getRatingColor(rating) {
+ if (rating >= 4.5) return 'text-green-600';
+ if (rating >= 3.5) return 'text-yellow-600';
+ if (rating >= 2.5) return 'text-orange-600';
+ return 'text-red-600';
+}
+
+export function getRatingText(rating) {
+ if (rating >= 4.5) return 'ممتاز';
+ if (rating >= 3.5) return 'جيد جداً';
+ if (rating >= 2.5) return 'جيد';
+ if (rating >= 1.5) return 'مقبول';
+ return 'ضعيف';
+}
\ No newline at end of file
diff --git a/app/property/[id]/PropertyDetail.js b/app/property/[id]/PropertyDetail.js
index eb2d07c..82c0145 100644
--- a/app/property/[id]/PropertyDetail.js
+++ b/app/property/[id]/PropertyDetail.js
@@ -49,9 +49,9 @@ import { getRentProperty, getSaleProperty, bookReservation, checkAvailability, g
import AuthService from '../../services/AuthService';
import { useFavorites } from '@/app/contexts/FavoritesContext';
import { BuildingTypeKeys, PropertyStatusKeys, extractCity } from '../../enums';
-import RatingForm from '../../components/ratings/RatingForm.js';
-import RatingList from '../../components/ratings/RatingList.js';
-import StarRating from '../../components/ratings/StarRating.js';
+import RatingForm from '@/app/components/ratings/RatingForm.js';
+import RatingList from '@/app/components/ratings/RatingList.js';
+import StarRating from '@/app/components/ratings/StarRating.js';
// Copy to clipboard that works on HTTP too
async function copyToClipboard(text) {
diff --git a/app/utils/ratings.js b/app/utils/ratings.js
new file mode 100644
index 0000000..27c968d
--- /dev/null
+++ b/app/utils/ratings.js
@@ -0,0 +1,193 @@
+// Rating API endpoints for SweetHome
+// Handles both customer ratings and property ratings
+
+import AuthService from '../services/AuthService';
+
+const API_BASE = process.env.NEXT_PUBLIC_API_URL || 'https://45.93.137.91.nip.io/api';
+
+/**
+ * Rate a property as a customer
+ * @param {Object} data - Rating data
+ * @param {number} data.propertyId - ID of the property being rated
+ * @param {number} data.customerId - ID of the customer doing the rating
+ * @param {number} data.rating - Rating value (1-5)
+ * @param {string} data.comment - Optional comment
+ * @returns {Promise} - API response
+ */
+export async function rateProperty(data) {
+ console.log('[Rating] Customer rating property:', data);
+ return apiFetch('/Ratings/CustomerRateProperty', {
+ method: 'POST',
+ body: JSON.stringify(data),
+ });
+}
+
+/**
+ * Rate a customer as a property owner
+ * @param {Object} data - Rating data
+ * @param {number} data.propertyId - ID of the property
+ * @param {number} data.customerId - ID of the customer being rated
+ * @param {number} data.rating - Rating value (1-5)
+ * @param {string} data.comment - Optional comment
+ * @returns {Promise} - API response
+ */
+export async function rateCustomer(data) {
+ console.log('[Rating] Property owner rating customer:', data);
+ return apiFetch('/Ratings/PropertyRateCustomer', {
+ method: 'POST',
+ body: JSON.stringify(data),
+ });
+}
+
+/**
+ * Get all ratings for a property
+ * @param {number} propertyId - ID of the property
+ * @returns {Promise} - Array of ratings
+ */
+export async function getPropertyRatings(propertyId) {
+ console.log('[Rating] Fetching property ratings for:', propertyId);
+ return apiFetch(`/Ratings/GetPropertyRatings?propertyId=${propertyId}`);
+}
+
+/**
+ * Get all ratings for a customer
+ * @param {number} customerId - ID of the customer
+ * @returns {Promise} - Array of ratings
+ */
+export async function getCustomerRatings(customerId) {
+ console.log('[Rating] Fetching customer ratings for:', customerId);
+ return apiFetch(`/Ratings/GetCustomerRatings?customerId=${customerId}`);
+}
+
+/**
+ * Get average rating for a property
+ * @param {number} propertyId - ID of the property
+ * @returns {Promise} - Average rating
+ */
+export async function getPropertyAverageRating(propertyId) {
+ console.log('[Rating] Fetching average rating for property:', propertyId);
+ const ratings = await getPropertyRatings(propertyId);
+ if (!Array.isArray(ratings) || ratings.length === 0) return 0;
+
+ const total = ratings.reduce((sum, rating) => sum + rating.rating, 0);
+ return Math.round((total / ratings.length) * 10) / 10; // Round to 1 decimal
+}
+
+/**
+ * Get average rating for a customer
+ * @param {number} customerId - ID of the customer
+ * @returns {Promise} - Average rating
+ */
+export async function getCustomerAverageRating(customerId) {
+ console.log('[Rating] Fetching average rating for customer:', customerId);
+ const ratings = await getCustomerRatings(customerId);
+ if (!Array.isArray(ratings) || ratings.length === 0) return 0;
+
+ const total = ratings.reduce((sum, rating) => sum + rating.rating, 0);
+ return Math.round((total / ratings.length) * 10) / 10; // Round to 1 decimal
+}
+
+/**
+ * Get user's rating for a specific property (if any)
+ * @param {number} propertyId - ID of the property
+ * @param {number} userId - ID of the user
+ * @returns {Promise} - User's rating or null
+ */
+export async function getUserPropertyRating(propertyId, userId) {
+ console.log('[Rating] Fetching user rating for property:', propertyId, 'user:', userId);
+ const allRatings = await getPropertyRatings(propertyId);
+ if (!Array.isArray(allRatings)) return null;
+
+ return allRatings.find(r => r.userId === userId) || null;
+}
+
+/**
+ * Get user's rating for a specific customer (if any)
+ * @param {number} customerId - ID of the customer
+ * @param {number} userId - ID of the user
+ * @returns {Promise} - User's rating or null
+ */
+export async function getUserCustomerRating(customerId, userId) {
+ console.log('[Rating] Fetching user rating for customer:', customerId, 'user:', userId);
+ const allRatings = await getCustomerRatings(customerId);
+ if (!Array.isArray(allRatings)) return null;
+
+ return allRatings.find(r => r.userId === userId) || null;
+}
+
+/**
+ * Check if user can rate a property (after renting)
+ * @param {number} propertyId - ID of the property
+ * @param {number} userId - ID of the user
+ * @returns {Promise} - Boolean indicating if rating is allowed
+ */
+export async function canRateProperty(propertyId, userId) {
+ console.log('[Rating] Checking if user can rate property:', propertyId, 'user:', userId);
+
+ // Logic: User can rate if they have completed a rental in the past
+ // This would typically check reservation history
+ // For now, we'll simulate this with a simple check
+
+ // In a real implementation, this would check:
+ // 1. User's reservation history for this property
+ // 2. Whether the rental period has ended
+ // 3. Whether they've already rated
+
+ const userRating = await getUserPropertyRating(propertyId, userId);
+ return !userRating; // Can rate if no existing rating
+}
+
+/**
+ * Check if user can rate a customer (after renting to them)
+ * @param {number} customerId - ID of the customer
+ * @param {number} userId - ID of the user (owner)
+ * @returns {Promise} - Boolean indicating if rating is allowed
+ */
+export async function canRateCustomer(customerId, userId) {
+ console.log('[Rating] Checking if user can rate customer:', customerId, 'user:', userId);
+
+ // Logic: Owner can rate if they have rented to this customer
+ // This would typically check reservation history
+
+ const userRating = await getUserCustomerRating(customerId, userId);
+ return !userRating; // Can rate if no existing rating
+}
+
+// Helper function for API calls
+async function apiFetch(endpoint, options = {}) {
+ const token = AuthService.getToken();
+
+ const headers = {
+ 'Content-Type': 'application/json',
+ ...(token && { Authorization: `Bearer ${token}` }),
+ ...options.headers,
+ };
+
+ console.log('[Rating API] Request:', options.method || 'GET', `${API_BASE}${endpoint}`);
+
+ const res = await fetch(`${API_BASE}${endpoint}`, {
+ ...options,
+ headers,
+ });
+
+ console.log('[Rating API] Response:', res.status, endpoint);
+
+ if (!res.ok && res.status !== 206) {
+ const text = await res.text().catch(() => '');
+ console.error('[Rating API] Error:', res.status, text);
+ throw new Error(`Rating API ${res.status}: ${text || res.statusText}`);
+ }
+
+ const text = await res.text();
+ if (!text) return null;
+
+ try {
+ const json = JSON.parse(text);
+ if (json && typeof json === 'object' && 'data' in json) {
+ return json.data;
+ }
+ return json;
+ } catch {
+ return text;
+ }
+}
\ No newline at end of file