From 6394f1d71a9de582741faa9a83a72b16e091c8db Mon Sep 17 00:00:00 2001
From: Claw AI
- {isOwner - ? "مالك عقار" - : isAdmin - ? "مدير النظام" - : "مستأجر"} + {UserRoleLabels[user?.role] || 'زائر'}
@@ -486,7 +489,7 @@ export default function ClientLayout({ children }) { > )} - {!isOwner && !isAdmin && user && ( + {isCustomer && ( <> diff --git a/app/enums/CustomerType.js b/app/enums/CustomerType.js index a230f8f..41fc401 100644 --- a/app/enums/CustomerType.js +++ b/app/enums/CustomerType.js @@ -4,8 +4,8 @@ * Used in: Customer registration (Customer/Add) */ const CustomerType = Object.freeze({ - PERSONAL: 'Personal', - FAMILY: 'Family', + PERSONAL: 0, + FAMILY: 1, }); // Map value → Arabic label diff --git a/app/enums/OwnerType.js b/app/enums/OwnerType.js index 18549c6..5b4b8c9 100644 --- a/app/enums/OwnerType.js +++ b/app/enums/OwnerType.js @@ -4,8 +4,8 @@ * Used in: Owner registration (Owner/Add) */ const OwnerType = Object.freeze({ - PERSON: 'peerson', - REAL_ESTATE_AGENCY: 'RealEstateAgency', + PERSON: 0, + REAL_ESTATE_AGENCY: 1, }); // Map value → Arabic label diff --git a/app/enums/UserRole.js b/app/enums/UserRole.js index 3bf79ee..ca2e712 100644 --- a/app/enums/UserRole.js +++ b/app/enums/UserRole.js @@ -1,25 +1,26 @@ /** * UserRole Enum * User account roles in the system - * Used in: JWT payload, registration, routing + * Derived from JWT token claims */ const UserRole = Object.freeze({ + GUEST: 'guest', + CUSTOMER: 'customer', OWNER: 'owner', - TENANT: 'tenant', ADMIN: 'admin', }); -// Map role → Arabic label const UserRoleLabels = Object.freeze({ + [UserRole.GUEST]: 'زائر', + [UserRole.CUSTOMER]: 'مستأجر', [UserRole.OWNER]: 'مالك عقار', - [UserRole.TENANT]: 'مستأجر', [UserRole.ADMIN]: 'مدير النظام', }); -// Map role → color theme (used in UI) const UserRoleColors = Object.freeze({ + [UserRole.GUEST]: 'gray', + [UserRole.CUSTOMER]: 'blue', [UserRole.OWNER]: 'amber', - [UserRole.TENANT]: 'blue', [UserRole.ADMIN]: 'red', }); diff --git a/app/login/page.js b/app/login/page.js index dcbb8b2..62ca3ba 100644 --- a/app/login/page.js +++ b/app/login/page.js @@ -93,24 +93,12 @@ export default function LoginPage() { AuthService.addToken(token); console.log('[Login] Token stored via AuthService'); - // Decode token to get user info (basic JWT decode) - try { - const payload = JSON.parse(atob(token.split('.')[1])); - const user = { - name: payload.name || payload.unique_name || formData.credential, - email: payload.email || (loginMethod === 'email' ? formData.credential : ''), - phone: payload.phone || (loginMethod === 'phone' ? formData.credential : ''), - role: payload.role || payload.Role || 'customer', - }; - localStorage.setItem('user', JSON.stringify(user)); - console.log('[Login] User stored:', user); - } catch (decodeErr) { - console.warn('[Login] Could not decode JWT, storing credential as user'); - localStorage.setItem('user', JSON.stringify({ - name: formData.credential, - role: 'customer', - })); - } + // Decode token to get user info via AuthService + const authUser = AuthService.getUser(); + const userRole = authUser?.roles?.includes('Owner') ? 'owner' + : authUser?.roles?.includes('Admin') ? 'admin' + : 'customer'; + console.log('[Login] User role:', userRole); setIsSuccess(true); toast.success('تم تسجيل الدخول بنجاح!', { @@ -118,9 +106,7 @@ export default function LoginPage() { }); setTimeout(() => { - const user = JSON.parse(localStorage.getItem('user') || '{}'); - console.log('[Login] Redirecting user:', user); - if (user.role === 'admin') { + if (userRole === 'admin') { router.push('/admin'); } else { router.push('/'); @@ -190,11 +176,6 @@ export default function LoginPage() { console.log('[OTP] Token stored via AuthService'); } - localStorage.setItem('user', JSON.stringify({ - name: formData.credential, - role: 'customer', - })); - setIsSuccess(true); toast.success('تم التحقق بنجاح!', { style: { background: '#dcfce7', color: '#166534' }, diff --git a/app/owner/bookings/page.js b/app/owner/bookings/page.js index c3ecb84..d367ab6 100644 --- a/app/owner/bookings/page.js +++ b/app/owner/bookings/page.js @@ -33,6 +33,7 @@ import { Building } from 'lucide-react'; import toast, { Toaster } from 'react-hot-toast'; +import AuthService from '../../../services/AuthService'; import Image from 'next/image'; const OwnerBookingCalendar = ({ property, onDateSelect, selectedDates }) => { @@ -425,8 +426,8 @@ export default function OwnerBookingsPage() { useEffect(() => { const storedUser = localStorage.getItem('user'); - if (storedUser) { - const userData = JSON.parse(storedUser); + // User loaded via AuthService + // Handled above if (userData.role !== 'owner') { router.push('/'); } else { diff --git a/app/owner/calendar/page.js b/app/owner/calendar/page.js index 29cf948..7e4624b 100644 --- a/app/owner/calendar/page.js +++ b/app/owner/calendar/page.js @@ -36,6 +36,7 @@ import { Calendar as CalendarIcon } from 'lucide-react'; import toast, { Toaster } from 'react-hot-toast'; +import AuthService from '../../../services/AuthService'; const MonthlyCalendar = ({ properties, selectedPropertyId, onDateClick, onPropertySelect }) => { const [currentMonth, setCurrentMonth] = useState(new Date()); @@ -484,8 +485,8 @@ export default function OwnerCalendarPage() { useEffect(() => { const storedUser = localStorage.getItem('user'); - if (storedUser) { - const userData = JSON.parse(storedUser); + // User loaded via AuthService + // Handled above if (userData.role !== 'owner') { router.push('/'); } else { diff --git a/app/owner/profits/page.js b/app/owner/profits/page.js index 779222c..bcc4ac9 100644 --- a/app/owner/profits/page.js +++ b/app/owner/profits/page.js @@ -28,6 +28,7 @@ import { XCircle } from 'lucide-react'; import toast, { Toaster } from 'react-hot-toast'; +import AuthService from '../../../services/AuthService'; const StatCard = ({ title, value, change, icon: Icon, color, trend }) => { return ( @@ -237,8 +238,8 @@ export default function OwnerProfitsPage() { useEffect(() => { const storedUser = localStorage.getItem('user'); - if (storedUser) { - const userData = JSON.parse(storedUser); + // User loaded via AuthService + // Handled above if (userData.role !== 'owner') { router.push('/'); } else { diff --git a/app/owner/properties/page.js b/app/owner/properties/page.js index 58eb998..7c16e5d 100644 --- a/app/owner/properties/page.js +++ b/app/owner/properties/page.js @@ -45,6 +45,7 @@ import { X } from 'lucide-react'; import toast, { Toaster } from 'react-hot-toast'; +import AuthService from '../../../services/AuthService'; const DeleteConfirmationModal = ({ isOpen, onClose, onConfirm, propertyTitle }) => { if (!isOpen) return null; @@ -693,8 +694,8 @@ export default function OwnerPropertiesPage() { useEffect(() => { const storedUser = localStorage.getItem('user'); - if (storedUser) { - const userData = JSON.parse(storedUser); + // User loaded via AuthService + // Handled above if (userData.role !== 'owner') { router.push('/'); } else { diff --git a/app/page.js b/app/page.js index 8ebaa72..5ce1c05 100644 --- a/app/page.js +++ b/app/page.js @@ -30,6 +30,7 @@ import Link from 'next/link'; import Image from 'next/image'; import { getRentProperties, getSaleProperties } from './utils/api'; import { BuildingTypeKeys, PropertyStatusKeys, extractCity } from './enums'; +import AuthService from './services/AuthService'; // Map API property data to the format the UI expects // API returns { propertyInformationId, deposit, monthlyRent, dailyRent, rating, propertyInformation: {...}, ... } @@ -180,9 +181,13 @@ export default function HomePage() { // Fetch properties from API on mount useEffect(() => { - const storedUser = localStorage.getItem('user'); - if (storedUser) { - setUser(JSON.parse(storedUser)); + const authUser = AuthService.getUser(); + if (authUser) { + setUser({ + name: authUser.name || authUser.email, + email: authUser.email, + role: AuthService.isOwner() ? 'owner' : 'customer', + }); } async function fetchProperties() { @@ -226,7 +231,7 @@ export default function HomePage() { }, []); const logout = () => { - localStorage.removeItem('user'); + AuthService.deleteToken(); setUser(null); setShowUserMenu(false); }; diff --git a/app/profile/page.js b/app/profile/page.js index 94043f6..e0f4038 100644 --- a/app/profile/page.js +++ b/app/profile/page.js @@ -27,6 +27,7 @@ import { Pencil } from 'lucide-react'; import toast, { Toaster } from 'react-hot-toast'; +import AuthService from '../services/AuthService'; export default function ProfilePage() { const router = useRouter(); @@ -62,9 +63,14 @@ export default function ProfilePage() { }; useEffect(() => { - const storedUser = localStorage.getItem('user'); - if (storedUser) { - const userData = JSON.parse(storedUser); + const authUser = AuthService.getUser(); + if (authUser) { + const userData = { + name: authUser.name || '', + email: authUser.email || '', + phone: authUser.phone || '', + role: AuthService.isOwner() ? 'owner' : 'customer', + }; setUser(userData); const savedProfile = localStorage.getItem('userProfile'); @@ -167,7 +173,6 @@ export default function ProfilePage() { if (field === 'name') { const updatedUser = { ...user, name: value }; - localStorage.setItem('user', JSON.stringify(updatedUser)); setUser(updatedUser); } diff --git a/app/services/AuthService.js b/app/services/AuthService.js index d572bde..0f7fd58 100644 --- a/app/services/AuthService.js +++ b/app/services/AuthService.js @@ -1,50 +1,117 @@ /** * AuthService - * Manages authentication tokens securely using localStorage. - * - * Methods: - * addToken(token) — store JWT token - * getToken() — retrieve JWT token - * deleteToken() — remove JWT token + * Manages authentication tokens and user role detection via JWT decoding. * - * Usage: - * import AuthService from '@/app/services/AuthService'; - * AuthService.addToken(token); - * const token = AuthService.getToken(); - * AuthService.deleteToken(); + * Roles (from JWT claims): + * - Owner: roles array contains "Owner" + * - Customer: authenticated but no "Owner" role + * - Guest: no token + * + * Methods: + * addToken(token) — store JWT token + * getToken() — retrieve JWT token + * deleteToken() — remove JWT token + * decodeToken() — decode JWT payload + * getUser() — get decoded user info + * getRoles() — get roles array from JWT + * isOwner() — check if user has Owner role + * isCustomer() — check if user is authenticated but not Owner + * isGuest() — check if no token exists + * isAuthenticated() — check if token exists */ + const TOKEN_KEY = 'auth_token'; const AuthService = Object.freeze({ - /** - * Store token in localStorage - * @param {string} token — JWT string - */ addToken(token) { - if (!token || typeof token !== 'string') { - console.error('[AuthService] addToken: invalid token', token); - return; - } + if (!token || typeof token !== 'string') return; localStorage.setItem(TOKEN_KEY, token); - console.log('[AuthService] Token stored'); }, - /** - * Retrieve token from localStorage - * @returns {string|null} - */ getToken() { - const token = localStorage.getItem(TOKEN_KEY); - console.log('[AuthService] getToken:', token ? '***exists***' : null); - return token; + return localStorage.getItem(TOKEN_KEY); }, - /** - * Remove token from localStorage - */ deleteToken() { localStorage.removeItem(TOKEN_KEY); - console.log('[AuthService] Token deleted'); + }, + + /** + * Decode JWT payload (base64) + * @returns {object|null} + */ + decodeToken() { + const token = this.getToken(); + if (!token) return null; + try { + const payload = token.split('.')[1]; + return JSON.parse(atob(payload)); + } catch { + return null; + } + }, + + /** + * Extract user info from JWT + * @returns {object|null} — { id, name, email, phone, roles } + */ + getUser() { + const payload = this.decodeToken(); + if (!payload) return null; + + return { + id: payload['http://schemas.xmlsoap.org/ws/2005/05/identity/claims/nameidentifier'] || payload.sub || null, + name: payload['http://schemas.xmlsoap.org/ws/2005/05/identity/claims/name'] || null, + email: payload['http://schemas.xmlsoap.org/ws/2005/05/identity/claims/emailaddress'] || null, + phone: payload['http://schemas.xmlsoap.org/ws/2005/05/identity/claims/mobilephone'] || null, + roles: this.getRoles(), + }; + }, + + /** + * Get roles array from JWT + * @returns {string[]} + */ + getRoles() { + const payload = this.decodeToken(); + if (!payload) return []; + const roles = payload['http://schemas.microsoft.com/ws/2008/06/identity/claims/role']; + if (Array.isArray(roles)) return roles; + if (typeof roles === 'string') return [roles]; + return []; + }, + + /** + * User has Owner role + * @returns {boolean} + */ + isOwner() { + const roles = this.getRoles(); + return roles.includes('Owner'); + }, + + /** + * Authenticated user without Owner role (i.e. customer) + * @returns {boolean} + */ + isCustomer() { + return this.isAuthenticated() && !this.isOwner(); + }, + + /** + * No token — guest user + * @returns {boolean} + */ + isGuest() { + return !this.getToken(); + }, + + /** + * Token exists + * @returns {boolean} + */ + isAuthenticated() { + return !!this.getToken(); }, });