Fix CustomerType and OwnerType enums: send int instead of string
Some checks failed
Build frontend / build (push) Failing after 45s
Some checks failed
Build frontend / build (push) Failing after 45s
- CustomerType: PERSONAL=0, FAMILY=1 (was 'Personal', 'Family') - OwnerType: PERSON=0, REAL_ESTATE_AGENCY=1 (was 'peerson', 'RealEstateAgency') - Backend Type column is int(11), sending strings caused 415 errors
This commit is contained in:
@ -36,6 +36,8 @@ import {
|
||||
} from "lucide-react";
|
||||
import { useState, useEffect, useRef } from "react";
|
||||
import { motion, AnimatePresence } from "framer-motion";
|
||||
import AuthService from "../services/AuthService";
|
||||
import { UserRole, UserRoleLabels } from "./enums/UserRole";
|
||||
import "./i18n/config";
|
||||
|
||||
export default function ClientLayout({ children }) {
|
||||
@ -55,11 +57,17 @@ export default function ClientLayout({ children }) {
|
||||
setCurrentLanguage(savedLanguage);
|
||||
i18n.changeLanguage(savedLanguage);
|
||||
|
||||
const storedUser = localStorage.getItem("user");
|
||||
if (storedUser) {
|
||||
const userData = JSON.parse(storedUser);
|
||||
console.log("User data loaded:", userData);
|
||||
setUser(userData);
|
||||
// Load user from JWT via AuthService
|
||||
const authUser = AuthService.getUser();
|
||||
if (authUser) {
|
||||
setUser({
|
||||
name: authUser.name || authUser.email,
|
||||
email: authUser.email,
|
||||
phone: authUser.phone,
|
||||
role: AuthService.isOwner() ? UserRole.OWNER : UserRole.CUSTOMER,
|
||||
});
|
||||
} else {
|
||||
setUser(null);
|
||||
}
|
||||
|
||||
if (savedLanguage === "ar") {
|
||||
@ -104,7 +112,7 @@ export default function ClientLayout({ children }) {
|
||||
};
|
||||
|
||||
const logout = () => {
|
||||
localStorage.removeItem("user");
|
||||
AuthService.deleteToken();
|
||||
setUser(null);
|
||||
setShowUserMenu(false);
|
||||
window.location.href = "/";
|
||||
@ -119,11 +127,10 @@ export default function ClientLayout({ children }) {
|
||||
|
||||
const isProfilePage = pathname === "/profile";
|
||||
|
||||
const isOwner = user?.role === "owner";
|
||||
const isAdmin = user?.role === "admin";
|
||||
|
||||
console.log("User role:", user?.role);
|
||||
console.log("Is Admin:", isAdmin);
|
||||
const isOwner = user?.role === UserRole.OWNER;
|
||||
const isAdmin = user?.role === UserRole.ADMIN;
|
||||
const isCustomer = user?.role === UserRole.CUSTOMER;
|
||||
const isAuthenticated = !!user;
|
||||
|
||||
const getUserInitial = () => {
|
||||
if (user?.name) {
|
||||
@ -295,11 +302,7 @@ export default function ClientLayout({ children }) {
|
||||
{user?.email || ""}
|
||||
</p>
|
||||
<p className="text-xs text-amber-100 mt-1">
|
||||
{isOwner
|
||||
? "مالك عقار"
|
||||
: isAdmin
|
||||
? "مدير النظام"
|
||||
: "مستأجر"}
|
||||
{UserRoleLabels[user?.role] || 'زائر'}
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
@ -486,7 +489,7 @@ export default function ClientLayout({ children }) {
|
||||
</>
|
||||
)}
|
||||
|
||||
{!isOwner && !isAdmin && user && (
|
||||
{isCustomer && (
|
||||
<>
|
||||
<div className="border-t border-gray-100 my-2"></div>
|
||||
|
||||
|
||||
@ -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
|
||||
|
||||
@ -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
|
||||
|
||||
@ -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',
|
||||
});
|
||||
|
||||
|
||||
@ -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' },
|
||||
|
||||
@ -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 {
|
||||
|
||||
@ -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 {
|
||||
|
||||
@ -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 {
|
||||
|
||||
@ -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 {
|
||||
|
||||
13
app/page.js
13
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);
|
||||
};
|
||||
|
||||
@ -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);
|
||||
}
|
||||
|
||||
|
||||
@ -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();
|
||||
},
|
||||
});
|
||||
|
||||
|
||||
Reference in New Issue
Block a user