Merge branch 'organization_management' of https://git.marcoaiot.com/admin/marco.pms.web into Organization_Management

This commit is contained in:
Kartik Sharma 2025-09-21 16:42:26 +05:30
commit 7e6020e3db
8 changed files with 124 additions and 63 deletions

39
src/hooks/useAuth.jsx Normal file
View File

@ -0,0 +1,39 @@
import { useState, useEffect, useCallback } from "react";
export const useTenantList = ({ autoFetch = true } = {}) => {
const [data, setData] = useState([]);
const [loading, setLoading] = useState(false);
const [error, setError] = useState(null);
const fetchTenantList = useCallback(async () => {
setLoading(true);
setError(null);
try {
const response = setData(response.data || []);
} catch (err) {
setError(err);
} finally {
setLoading(false);
}
}, []);
useEffect(() => {
if (autoFetch) fetchTenantList();
}, [autoFetch, fetchTenantList]);
return {
data,
loading,
error,
refetch: fetchTenantList, // manual trigger
};
};
export const useTenants =()=>{
get
return useQueery({
queryKey:["tenantlist",localhost.get("orgJwtToken")],
queryFun:async()=> await AuthRepository.
})
}

View File

@ -6,7 +6,7 @@ import { useForm } from "react-hook-form";
import { zodResolver } from "@hookform/resolvers/zod"; import { zodResolver } from "@hookform/resolvers/zod";
import { z } from "zod"; import { z } from "zod";
import { AuthWrapper } from "./AuthWrapper"; import { AuthWrapper } from "./AuthWrapper";
import { setOrgToken } from "../../services/tenantService";
const LoginPage = () => { const LoginPage = () => {
const navigate = useNavigate(); const navigate = useNavigate();
const [loading, setLoading] = useState(false); const [loading, setLoading] = useState(false);
@ -16,13 +16,13 @@ const LoginPage = () => {
const loginSchema = IsLoginWithOTP const loginSchema = IsLoginWithOTP
? z.object({ ? z.object({
username: z.string().trim().email({ message: "Valid email required" }), username: z.string().trim().email({ message: "Valid email required" }),
}) })
: z.object({ : z.object({
username: z.string().trim().email({ message: "Valid email required" }), username: z.string().trim().email({ message: "Valid email required" }),
password: z.string().trim().min(1, { message: "Password required" }), password: z.string().trim().min(1, { message: "Password required" }),
rememberMe: z.boolean(), rememberMe: z.boolean(),
}); });
const { const {
register, register,
@ -41,10 +41,11 @@ const LoginPage = () => {
password: data.password, password: data.password,
}; };
const response = await AuthRepository.login(userCredential); const response = await AuthRepository.login(userCredential);
localStorage.setItem("jwtToken", response.data.token); // localStorage.setItem("jwtToken", response.data.token);
localStorage.setItem("refreshToken", response.data.refreshToken); // localStorage.setItem("refreshToken", response.data.refreshToken);
setLoading(false); setLoading(false);
navigate("/dashboard"); setOrgToken(response.data.token, response.data.refreshToken);
navigate("/auth/user");
} else { } else {
await AuthRepository.sendOTP({ email: data.username }); await AuthRepository.sendOTP({ email: data.username });
showToast("OTP has been sent to your email.", "success"); showToast("OTP has been sent to your email.", "success");
@ -146,8 +147,9 @@ const LoginPage = () => {
type={hidepass ? "password" : "text"} type={hidepass ? "password" : "text"}
autoComplete="new-password" autoComplete="new-password"
id="password" id="password"
className={`form-control form-control-xl shadow-none ${errors.password ? "is-invalid" : "" className={`form-control form-control-xl shadow-none ${
}`} errors.password ? "is-invalid" : ""
}`}
name="password" name="password"
{...register("password")} {...register("password")}
placeholder="••••••••••••" placeholder="••••••••••••"
@ -170,13 +172,15 @@ const LoginPage = () => {
{/* ✅ Error message */} {/* ✅ Error message */}
{errors.password && ( {errors.password && (
<div className="invalid-feedback text-start" style={{ fontSize: "12px" }}> <div
className="invalid-feedback text-start"
style={{ fontSize: "12px" }}
>
{errors.password.message} {errors.password.message}
</div> </div>
)} )}
</div> </div>
{/* Remember Me + Forgot Password */} {/* Remember Me + Forgot Password */}
<div className="mb-3 d-flex justify-content-between align-items-center"> <div className="mb-3 d-flex justify-content-between align-items-center">
<div className="form-check"> <div className="form-check">
@ -209,8 +213,8 @@ const LoginPage = () => {
{loading {loading
? "Please Wait..." ? "Please Wait..."
: IsLoginWithOTP : IsLoginWithOTP
? "Send OTP" ? "Send OTP"
: "Sign In"} : "Sign In"}
</button> </button>
{/* Login With OTP Button */} {/* Login With OTP Button */}

View File

@ -1,13 +1,10 @@
import { api } from "../utils/axiosClient"; import { api } from "../utils/axiosClient";
const AttendanceRepository = {
const AttendanceRepository = { markAttendance: (data) => api.post("/api/attendance/record", data),
markAttendance:(data)=>api.post("/api/attendance/record",data), getAttendance: (id) => api.get(`api/attendance/project/team?projectId=${id}`),
getAttendance:(id)=>api.get(`api/attendance/project/team?projectId=${id}`), getAttendanceFilteredByDate: (projectId, fromDate, toDate) => {
getAttendanceFilteredByDate: ( projectId, fromDate, toDate ) => let url = `api/Attendance/project/log?projectId=${projectId}`;
{
let url = `api/Attendance/project/log?projectId=${ projectId }`
if (fromDate) { if (fromDate) {
url += `&dateFrom=${fromDate}`; url += `&dateFrom=${fromDate}`;
} }
@ -15,27 +12,24 @@ const AttendanceRepository = {
if (toDate) { if (toDate) {
url += `&dateTo=${toDate}`; url += `&dateTo=${toDate}`;
} }
return api.get(url) return api.get(url);
}, },
getAttendanceLogs: ( id ) => api.get( `api/attendance/log/attendance/${ id }` ), getAttendanceLogs: (id) => api.get(`api/attendance/log/attendance/${id}`),
getRegularizeList: ( id ) => api.get( `api/attendance/regularize?projectId=${ id }` ), getRegularizeList: (id) =>
api.get(`api/attendance/regularize?projectId=${id}`),
getAttendanceByEmployee: ( employeeId, fromDate, toDate ) => getAttendanceByEmployee: (employeeId, fromDate, toDate) => {
{ let url = `api/Attendance/log/employee/${employeeId}?`;
if (fromDate) {
url += `&dateFrom=${fromDate}`;
}
let url = `api/Attendance/log/employee/${ employeeId }?` if (toDate) {
if (fromDate) { url += `&dateTo=${toDate}`;
url += `&dateFrom=${fromDate}`; }
} return api.get(url);
},
if (toDate) { };
url += `&dateTo=${toDate}`;
}
return api.get(url)
},
}
export default AttendanceRepository; export default AttendanceRepository;

View File

@ -16,6 +16,22 @@ const AuthRepository = {
profile: () => api.get("/api/user/profile"), profile: () => api.get("/api/user/profile"),
changepassword: (data) => api.post("/api/auth/change-password", data), changepassword: (data) => api.post("/api/auth/change-password", data),
appmenu: () => api.get("/api/appmenu/get/menu"), appmenu: () => api.get("/api/appmenu/get/menu"),
// getTenantList: () =>
// api.get("/api/Auth/get/user/tenants", {}, { useTenantToken: false }),
// selectTenant: (tenantId) =>
// api.post(`/api/Auth/select-tenant/${tenantId}`),
// ---------------- Org-level routes (use org token) ---------------
// Note: pass `orgToken: true` (4th arg) so axios interceptor uses orgJwtToken.
getTenantList: () => api.get("/api/Auth/get/user/tenants", {}, {}, true),
// Exchange tenantId (with org token) -> server returns tenant JWT
// Using POST with body { tenantId } (adjust if your API expects path param).
selectTenant: (tenantId) =>
api.post("/api/Auth/select-tenant", { tenantId }, {}, true),
// If your backend expects tenantId in URL instead of body, use this variant:
// selectTenant: (tenantId) => api.post(`/api/Auth/select-tenant/${tenantId}`, {}, {}, true),
}; };
export default AuthRepository; export default AuthRepository;

View File

@ -49,7 +49,6 @@ import MainResetPasswordPage from "../pages/authentication/MainResetPasswordPage
import TenantPage from "../pages/Tenant/TenantPage"; import TenantPage from "../pages/Tenant/TenantPage";
import { Navigate } from "react-router-dom"; import { Navigate } from "react-router-dom";
import CreateTenant from "../pages/Tenant/CreateTenant"; import CreateTenant from "../pages/Tenant/CreateTenant";
;
import OrganizationPage from "../pages/Organization/OrganizationPage"; import OrganizationPage from "../pages/Organization/OrganizationPage";
import LandingPage from "../pages/Home/LandingPage"; import LandingPage from "../pages/Home/LandingPage";
const router = createBrowserRouter( const router = createBrowserRouter(

View File

@ -66,7 +66,7 @@ const attemptTokenRefresh = async (storedRefreshToken) => {
localStorage.setItem("jwtToken", response.data.token); localStorage.setItem("jwtToken", response.data.token);
localStorage.setItem("refreshToken", response.data.refreshToken); localStorage.setItem("refreshToken", response.data.refreshToken);
return true; return true;
// api // api
// .post("/api/auth/refresh-token", { // .post("/api/auth/refresh-token", {
// token: localStorage.getItem("jwtToken"), // token: localStorage.getItem("jwtToken"),
// refreshToken: refreshToken, // refreshToken: refreshToken,

View File

@ -3,23 +3,23 @@ import { createSlice } from "@reduxjs/toolkit";
const globalVariablesSlice = createSlice({ const globalVariablesSlice = createSlice({
name: "globalVariables", name: "globalVariables",
initialState: { initialState: {
loginUser:null, loginUser: null,
currentTenant:null currentTenant: null,
}, },
reducers: { reducers: {
setGlobalVariable: (state, action) => { setGlobalVariable: (state, action) => {
const { key, value } = action.payload; const { key, value } = action.payload;
state[key] = value; state[key] = value;
}, },
setLoginUserPermmisions: ( state, action ) => setLoginUserPermmisions: (state, action) => {
{ state.loginUser = action.payload;
state.loginUser = action.payload },
setCurrentTenant: (state, action) => {
state.currentTenant = action.payload;
}, },
setCurrentTenant:(state,action)=>{
state.currentTenant = action.payload
}
}, },
}); });
export const { setGlobalVariable,setLoginUserPermmisions,setCurrentTenant } = globalVariablesSlice.actions; export const { setGlobalVariable, setLoginUserPermmisions, setCurrentTenant } =
globalVariablesSlice.actions;
export default globalVariablesSlice.reducer; export default globalVariablesSlice.reducer;

View File

@ -4,7 +4,7 @@ import axiosRetry from "axios-retry";
import showToast from "../services/toastService"; import showToast from "../services/toastService";
import { startSignalR, stopSignalR } from "../services/signalRService"; import { startSignalR, stopSignalR } from "../services/signalRService";
import { BASE_URL } from "./constants"; import { BASE_URL } from "./constants";
const base_Url = BASE_URL const base_Url = BASE_URL;
export const axiosClient = axios.create({ export const axiosClient = axios.create({
baseURL: base_Url, baseURL: base_Url,
@ -44,7 +44,10 @@ axiosClient.interceptors.response.use(
const originalRequest = error.config; const originalRequest = error.config;
// Skip retry for public requests or already retried ones // Skip retry for public requests or already retried ones
if (!originalRequest && originalRequest._retry || originalRequest.authRequired === false) { if (
(!originalRequest && originalRequest._retry) ||
originalRequest.authRequired === false
) {
return Promise.reject(error); return Promise.reject(error);
} }
@ -53,7 +56,10 @@ axiosClient.interceptors.response.use(
originalRequest._toastShown = true; originalRequest._toastShown = true;
if (error.code === "ERR_CONNECTION_REFUSED") { if (error.code === "ERR_CONNECTION_REFUSED") {
showToast("Unable to connect to the server. Please try again later.", "error"); showToast(
"Unable to connect to the server. Please try again later.",
"error"
);
} else if (error.code === "ERR_NETWORK") { } else if (error.code === "ERR_NETWORK") {
showToast("Network error. Please check your connection.", "error"); showToast("Network error. Please check your connection.", "error");
redirectToLogin(); redirectToLogin();
@ -68,7 +74,10 @@ axiosClient.interceptors.response.use(
const refreshToken = localStorage.getItem("refreshToken"); const refreshToken = localStorage.getItem("refreshToken");
if (!refreshToken || error.response.data?.errors === "Invalid or expired refresh token.") { if (
!refreshToken ||
error.response.data?.errors === "Invalid or expired refresh token."
) {
redirectToLogin(); redirectToLogin();
return Promise.reject(error); return Promise.reject(error);
} }
@ -88,7 +97,7 @@ axiosClient.interceptors.response.use(
localStorage.setItem("jwtToken", token); localStorage.setItem("jwtToken", token);
localStorage.setItem("refreshToken", newRefreshToken); localStorage.setItem("refreshToken", newRefreshToken);
startSignalR() startSignalR();
// Set Authorization header // Set Authorization header
originalRequest.headers["Authorization"] = `Bearer ${token}`; originalRequest.headers["Authorization"] = `Bearer ${token}`;
return axiosClient(originalRequest); return axiosClient(originalRequest);