diff --git a/app/controllers/auth.controller.js b/app/controllers/auth.controller.js
index 85d005e..c2cafc7 100644
--- a/app/controllers/auth.controller.js
+++ b/app/controllers/auth.controller.js
@@ -47,7 +47,6 @@ const ALL_PERMISSIONS = [
'Edit_Vehicle info_Repair Records',
'Add_New Vehicle',
'Archive_Vehicle',
- 'Delete_Vehicle',
'Export_Vehicle Report',
'View_Transportation Schedule_Route Overview',
'Create&Edit_Transportation Schedule',
@@ -145,4 +144,31 @@ exports.login = (req, res) => {
} else {
throw(Error('email or username is required'));
}
-}
\ No newline at end of file
+}
+
+exports.me = (req, res) => {
+ const condition = splitSite.splitSiteGet(req, { _id: req.userId });
+ Employee.findOne(condition)
+ .then((employee) => {
+ if (!employee) {
+ return res.status(404).send({ message: "User not found" });
+ }
+ if (!isEmployeeActive(employee)) {
+ return res.status(403).send({ message: "User is not activated" });
+ }
+ return res.send({
+ username: employee.username,
+ email: employee.email,
+ roles: employee.roles,
+ permissions: getEffectivePermissions(employee),
+ id: employee.id,
+ name: employee.name,
+ name_cn: employee.name_cn
+ });
+ })
+ .catch((err) => {
+ res.status(500).send({
+ message: err.message || "Failed to fetch current user"
+ });
+ });
+};
\ No newline at end of file
diff --git a/app/routes/auth.routes.js b/app/routes/auth.routes.js
index 93538f6..ba82823 100644
--- a/app/routes/auth.routes.js
+++ b/app/routes/auth.routes.js
@@ -1,5 +1,6 @@
module.exports = app => {
const auth = require("../controllers/auth.controller");
+ const { authJwt } = require("../middlewares");
app.use(function(req, res, next) {
res.header(
"Access-Control-Allow-Headers",
@@ -8,4 +9,5 @@ module.exports = app => {
next();
});
app.post('/api/auth/login', auth.login);
+ app.get('/api/auth/me', [authJwt.verifyToken], auth.me);
};
\ No newline at end of file
diff --git a/client/src/App.js b/client/src/App.js
index a816d5f..270172e 100644
--- a/client/src/App.js
+++ b/client/src/App.js
@@ -89,6 +89,7 @@ import { AuthService } from "./services";
function App() {
const [showMenu, setShowMenu] = useState(false);
+ const [isPermissionReady, setIsPermissionReady] = useState(false);
// const getLogo = () => {
// return (window.location.hostname.includes('worldshine2.mayo.llc') || window.location.hostname.includes('site2') || window.location.host.includes('ws2') ||window.location.hostname.includes('localhost')) ? "/images/logo2.png" : ((window.location.hostname.includes('worldshine3.mayo.llc') ||window.location.hostname.includes('site3') || window.location.hostname.includes('ws3')) ? "/images/logo3.png" : "/images/logo1.png");
// }
@@ -158,6 +159,32 @@ function App() {
};
}, []);
+ useEffect(() => {
+ let isMounted = true;
+ const recheckPermissions = async () => {
+ try {
+ if (localStorage.getItem('token')) {
+ await AuthService.refreshCurrentUserPermissions();
+ }
+ } catch (error) {
+ // Token failures are handled by axios interceptor (logout + redirect).
+ console.log(error);
+ } finally {
+ if (isMounted) {
+ setIsPermissionReady(true);
+ }
+ }
+ };
+ recheckPermissions();
+ return () => {
+ isMounted = false;
+ };
+ }, []);
+
+ if (!isPermissionReady) {
+ return null;
+ }
+
return (
<>
{/*
diff --git a/client/src/components/info-screen/InfoScreen.js b/client/src/components/info-screen/InfoScreen.js
index 84ac7c9..8c5e528 100644
--- a/client/src/components/info-screen/InfoScreen.js
+++ b/client/src/components/info-screen/InfoScreen.js
@@ -7,6 +7,7 @@ import moment from 'moment';
const InfoScreen = () => {
const navigate = useNavigate();
+ const canEditInfoScreen = AuthService.canEditInfoScreen();
const canViewAppointmentCalendar = AuthService.hasPermission('View_Appointment Calendar');
const [isLoading, setIsLoading] = useState(true);
const [medicalEvents, setMedicalEvents] = useState([]);
@@ -793,14 +794,16 @@ const InfoScreen = () => {
Worldshine
-
+ {canEditInfoScreen && (
+
+ )}
{/* Content Layout */}
@@ -848,14 +851,16 @@ const InfoScreen = () => {
{/* Header with Title and Edit Button */}
Gallery
-
+ {canEditInfoScreen && (
+
+ )}
{/* Image Carousel */}
@@ -1173,9 +1178,11 @@ const InfoScreen = () => {
{!isFullScreen && (
-
+ {canEditInfoScreen && (
+
+ )}
@@ -1185,7 +1192,7 @@ const InfoScreen = () => {
{/* Edit AttendanceNote Modal */}
-
+ {canEditInfoScreen &&
Edit Attendance Note
@@ -1264,10 +1271,10 @@ const InfoScreen = () => {
Save Changes
-
+ }
{/* Gallery Edit Modal */}
-
+ {canEditInfoScreen &&
Update Images
@@ -1333,10 +1340,10 @@ const InfoScreen = () => {
Save Changes
-
+ }
{/* Background Selection Modal */}
-
+ {canEditInfoScreen &&
Choose Background Image
@@ -1386,7 +1393,7 @@ const InfoScreen = () => {
Save Background
-
+ }
{/* Full Screen Hint */}
{isFullScreen && (
diff --git a/client/src/components/messages/MessageList.js b/client/src/components/messages/MessageList.js
index a23080e..d09c1e1 100644
--- a/client/src/components/messages/MessageList.js
+++ b/client/src/components/messages/MessageList.js
@@ -11,6 +11,7 @@ import 'react-datepicker/dist/react-datepicker.css';
const MessageList = () => {
const navigate = useNavigate();
+ const canEditMessagingTemplate = AuthService.hasPermission('Create&Edit_Messaging Template');
const [messages, setMessages] = useState([]);
const [activeTab, setActiveTab] = useState('allMessages');
@@ -90,6 +91,7 @@ const MessageList = () => {
// ---- Custom Template CRUD ----
const openCreateTemplateModal = () => {
+ if (!canEditMessagingTemplate) return;
setEditingTemplate(null);
setTemplateTitle('');
setTemplateChinese('');
@@ -98,6 +100,7 @@ const MessageList = () => {
};
const openEditTemplateModal = (template) => {
+ if (!canEditMessagingTemplate) return;
setEditingTemplate(template);
setTemplateTitle(template.title || '');
setTemplateChinese(template.chinese || '');
@@ -114,6 +117,7 @@ const MessageList = () => {
};
const handleSaveTemplate = () => {
+ if (!canEditMessagingTemplate) return;
if (!templateTitle.trim()) { window.alert('Template Title is required.'); return; }
if (!templateChinese.trim()) { window.alert('Message Content (Chinese) is required.'); return; }
if (!templateEnglish.trim()) { window.alert('Message Content (English) is required.'); return; }
@@ -131,6 +135,7 @@ const MessageList = () => {
};
const handleDeleteTemplate = (id) => {
+ if (!canEditMessagingTemplate) return;
if (window.confirm('Are you sure you want to delete this template?')) {
MessageService.deleteCustomTemplate(id).then(() => { fetchCustomTemplates(); });
}
@@ -399,7 +404,7 @@ const MessageList = () => {
Template Title |
Message Content (Chinese) |
Message Content (English) |
- Actions |
+ {canEditMessagingTemplate && Actions | }
@@ -409,14 +414,14 @@ const MessageList = () => {
{template?.title} |
{template?.chinese} |
{template?.english} |
-
+ {canEditMessagingTemplate && |
openEditTemplateModal(template)} />
handleDeleteTemplate(template.id)} />
- |
+ }
))}
{customTemplates.length === 0 && (
- | No templates yet. Create one to get started. |
+ | No templates yet. Create one to get started. |
)}
@@ -482,14 +487,14 @@ const MessageList = () => {
>
)}
{activeTab === 'messageTemplate' && (
-
+ canEditMessagingTemplate &&
)}
{/* Create / Edit Message Template Modal */}
-
+
{editingTemplate ? 'Edit Message Template' : 'Create New Message Template'}
diff --git a/client/src/components/vehicles/UpdateVehicle.js b/client/src/components/vehicles/UpdateVehicle.js
index c4edbc2..e982474 100644
--- a/client/src/components/vehicles/UpdateVehicle.js
+++ b/client/src/components/vehicles/UpdateVehicle.js
@@ -18,6 +18,7 @@ import {
const UpdateVehicle = () => {
const navigate = useNavigate();
const dispatch = useDispatch();
+ const canArchiveVehicle = AuthService.canArchiveVehicle();
const params = useParams();
const [searchParams] = useSearchParams();
const validTabs = ['basicInfo', 'complianceDeadlines'];
@@ -209,6 +210,7 @@ const UpdateVehicle = () => {
}
const triggerShowDeleteModal = () => {
+ if (!canArchiveVehicle) return;
setShowDeleteModal(true);
}
@@ -217,6 +219,7 @@ const UpdateVehicle = () => {
}
const deactivateVehicle = () => {
+ if (!canArchiveVehicle) return;
const data = buildVehicleData();
data.status = 'inactive';
dispatch(deleteVehicle({id: params.id, data}));
@@ -451,7 +454,7 @@ const UpdateVehicle = () => {
-
+ {canArchiveVehicle &&
}
diff --git a/client/src/services/AuthService.js b/client/src/services/AuthService.js
index d634c62..3b53447 100644
--- a/client/src/services/AuthService.js
+++ b/client/src/services/AuthService.js
@@ -4,6 +4,20 @@ const login = (data) => {
return http.post('/auth/login', data);
};
+const getCurrentUserFromServer = () => {
+ return http.get('/auth/me');
+};
+
+const refreshCurrentUserPermissions = async () => {
+ const token = localStorage.getItem('token');
+ if (!token) return null;
+ const { data } = await getCurrentUserFromServer();
+ if (data) {
+ localStorage.setItem('user', JSON.stringify(data));
+ }
+ return data;
+};
+
const logout = (data, shouldClearSession = false) => {
if (shouldClearSession) {
localStorage.removeItem('user');
@@ -162,10 +176,9 @@ const canViewVehicleRepairs = () => hasAnyPermission(['View_Vehicle info_Repair
const canEditVehicleRepairs = () => hasPermission('Edit_Vehicle info_Repair Records');
const canAddVehicle = () => hasPermission('Add_New Vehicle');
const canArchiveVehicle = () => hasPermission('Archive_Vehicle');
-const canDeleteVehicle = () => hasPermission('Delete_Vehicle');
const canExportVehicleReport = () => hasPermission('Export_Vehicle Report');
-const canViewVehicleAny = () => canViewVehicleBasic() || canViewVehicleDocuments() || canViewVehicleRepairs() || canEditVehicleBasic() || canEditVehicleDocuments() || canEditVehicleRepairs() || canAddVehicle() || canArchiveVehicle() || canDeleteVehicle() || canExportVehicleReport();
-const canEditVehicleAny = () => canEditVehicleBasic() || canEditVehicleDocuments() || canEditVehicleRepairs() || canAddVehicle() || canArchiveVehicle() || canDeleteVehicle();
+const canViewVehicleAny = () => canViewVehicleBasic() || canViewVehicleDocuments() || canViewVehicleRepairs() || canEditVehicleBasic() || canEditVehicleDocuments() || canEditVehicleRepairs() || canAddVehicle() || canArchiveVehicle() || canExportVehicleReport();
+const canEditVehicleAny = () => canEditVehicleBasic() || canEditVehicleDocuments() || canEditVehicleRepairs() || canAddVehicle() || canArchiveVehicle();
const canViewDashboard = () => {
return hasPermission('Dashboard');
@@ -178,6 +191,10 @@ const canViewInfoScreen = () => {
]);
}
+const canEditInfoScreen = () => {
+ return hasPermission('Edit_Info Screen');
+}
+
const canViewCalendar = () => {
return canViewAnyCalendarTab() || canEditAnyCalendarTab();
}
@@ -395,10 +412,13 @@ const getLocalAccessToken = () => {
export const AuthService = {
login,
+ getCurrentUserFromServer,
+ refreshCurrentUserPermissions,
logout,
hasPermission,
canViewDashboard,
canViewInfoScreen,
+ canEditInfoScreen,
canViewCalendarTab,
canEditCalendarTab,
canViewAnyCalendarTab,
@@ -443,7 +463,6 @@ export const AuthService = {
canEditVehicleRepairs,
canAddVehicle,
canArchiveVehicle,
- canDeleteVehicle,
canExportVehicleReport,
canViewVehicleAny,
canEditVehicleAny,
diff --git a/client/src/shared/constants/employee.constant.js b/client/src/shared/constants/employee.constant.js
index 3097116..a457e97 100644
--- a/client/src/shared/constants/employee.constant.js
+++ b/client/src/shared/constants/employee.constant.js
@@ -180,7 +180,6 @@ export const EMPLOYEE_PERMISSION_GROUPS = {
'Edit_Vehicle info_Repair Records',
'Add_New Vehicle',
'Archive_Vehicle',
- 'Delete_Vehicle',
'Export_Vehicle Report'
],
Transportation: [