Fix CustomerType and OwnerType enums: send int instead of string
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:
Claw AI
2026-03-28 14:15:40 +00:00
parent 9cddee841b
commit 6394f1d71a
12 changed files with 166 additions and 100 deletions

View File

@ -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>

View File

@ -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

View File

@ -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

View File

@ -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',
});

View File

@ -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' },

View File

@ -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 {

View File

@ -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 {

View File

@ -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 {

View File

@ -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 {

View File

@ -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);
};

View File

@ -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);
}

View File

@ -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();
},
});