initially setup login
This commit is contained in:
parent
a1a935b0d5
commit
070fa93fca
39
src/hooks/useAuth.jsx
Normal file
39
src/hooks/useAuth.jsx
Normal 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.
|
||||||
|
})
|
||||||
|
}
|
@ -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 */}
|
||||||
@ -254,4 +258,4 @@ const LoginPage = () => {
|
|||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|
||||||
export default LoginPage;
|
export default LoginPage;
|
||||||
|
61
src/pages/authentication/TenantSelection.jsx
Normal file
61
src/pages/authentication/TenantSelection.jsx
Normal file
@ -0,0 +1,61 @@
|
|||||||
|
import React from "react";
|
||||||
|
|
||||||
|
const TenantSelection = () => {
|
||||||
|
return (
|
||||||
|
<div className="container-fluid py-4">
|
||||||
|
{/* Header */}
|
||||||
|
<div className="text-center mb-4">
|
||||||
|
<p className="fs-4 mb-1">Welcome Pramod</p>
|
||||||
|
<p className="fs-5">
|
||||||
|
Please select which dashboard you want to explore!!!
|
||||||
|
</p>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
{/* Card Section */}
|
||||||
|
<div className="row justify-content-center">
|
||||||
|
<div className="col-12 col-md-10 col-lg-8">
|
||||||
|
<div className="d-flex flex-column flex-md-row gap-3 align-items-center align-items-md-start p-3 border rounded shadow-sm bg-white">
|
||||||
|
{/* Image */}
|
||||||
|
<div className="flex-shrink-0 text-center">
|
||||||
|
<img
|
||||||
|
src="/assets/img/SP-Placeholdeer.svg"
|
||||||
|
alt="logo"
|
||||||
|
className="img-fluid"
|
||||||
|
style={{ maxWidth: "180px", height: "auto" }}
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
{/* Content */}
|
||||||
|
<div className="d-flex flex-column text-start gap-2">
|
||||||
|
<p className="fs-5 text-muted fw-semibold mb-1">
|
||||||
|
Marco Secure Solution Pvt Limited
|
||||||
|
</p>
|
||||||
|
|
||||||
|
<div className="d-flex flex-column gap-2">
|
||||||
|
<div className="d-flex flex-wrap gap-2 align-items-center">
|
||||||
|
<p className="fw-semibold m-0">Industry:</p>
|
||||||
|
<p className="m-0">Information Technology (IT) Service</p>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<p className="text-start text-wrap m-0">
|
||||||
|
Pune has emerged as a major IT hub in India, home to a variety
|
||||||
|
of global technology companies providing cutting-edge
|
||||||
|
solutions. In this blog, we cover the top 50 IT companies in
|
||||||
|
Pune, highlighting their addresses, key services, and
|
||||||
|
specialties. Whether you’re looking for career opportunities,
|
||||||
|
partnerships, or business solutions,
|
||||||
|
</p>
|
||||||
|
|
||||||
|
<button className="btn btn-primary btn-sm mt-2 align-self-start">
|
||||||
|
Go To Dashboard
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
export default TenantSelection;
|
@ -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 }` ),
|
|
||||||
getRegularizeList: ( id ) => api.get( `api/attendance/regularize?projectId=${ id }` ),
|
|
||||||
|
|
||||||
getAttendanceByEmployee: ( employeeId, fromDate, toDate ) =>
|
|
||||||
{
|
|
||||||
|
|
||||||
let url = `api/Attendance/log/employee/${ employeeId }?`
|
|
||||||
if (fromDate) {
|
|
||||||
url += `&dateFrom=${fromDate}`;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (toDate) {
|
|
||||||
url += `&dateTo=${toDate}`;
|
|
||||||
}
|
|
||||||
return api.get(url)
|
|
||||||
},
|
|
||||||
|
|
||||||
}
|
getAttendanceLogs: (id) => api.get(`api/attendance/log/attendance/${id}`),
|
||||||
|
getRegularizeList: (id) =>
|
||||||
|
api.get(`api/attendance/regularize?projectId=${id}`),
|
||||||
|
|
||||||
|
getAttendanceByEmployee: (employeeId, fromDate, toDate) => {
|
||||||
|
let url = `api/Attendance/log/employee/${employeeId}?`;
|
||||||
|
if (fromDate) {
|
||||||
|
url += `&dateFrom=${fromDate}`;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (toDate) {
|
||||||
|
url += `&dateTo=${toDate}`;
|
||||||
|
}
|
||||||
|
return api.get(url);
|
||||||
|
},
|
||||||
|
};
|
||||||
|
|
||||||
export default AttendanceRepository;
|
export default AttendanceRepository;
|
||||||
|
|
||||||
|
@ -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;
|
||||||
|
@ -49,9 +49,9 @@ 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";
|
||||||
|
import TenantSelection from "../pages/authentication/TenantSelection";
|
||||||
const router = createBrowserRouter(
|
const router = createBrowserRouter(
|
||||||
[
|
[
|
||||||
{
|
{
|
||||||
@ -62,6 +62,7 @@ const router = createBrowserRouter(
|
|||||||
element: <AuthLayout />,
|
element: <AuthLayout />,
|
||||||
children: [
|
children: [
|
||||||
{ path: "/auth/login", element: <MainLogin /> },
|
{ path: "/auth/login", element: <MainLogin /> },
|
||||||
|
{ path: "/auth/user", element: <TenantSelection /> },
|
||||||
{ path: "/auth/login-otp", element: <MainLoginWithOTPPage /> },
|
{ path: "/auth/login-otp", element: <MainLoginWithOTPPage /> },
|
||||||
{ path: "/auth/reqest/demo", element: <MainRegisterPage /> },
|
{ path: "/auth/reqest/demo", element: <MainRegisterPage /> },
|
||||||
{ path: "/auth/forgot-password", element: <MainForgetPage /> },
|
{ path: "/auth/forgot-password", element: <MainForgetPage /> },
|
||||||
|
@ -8,7 +8,7 @@ const ProtectedRoute = () => {
|
|||||||
// // const isAuthenticated = true;
|
// // const isAuthenticated = true;
|
||||||
// isTokenValid();
|
// isTokenValid();
|
||||||
// return isAuthenticated ? <Outlet /> : <Navigate to="/auth/login" />
|
// return isAuthenticated ? <Outlet /> : <Navigate to="/auth/login" />
|
||||||
|
|
||||||
const [isAuthenticated, setIsAuthenticated] = useState(null);
|
const [isAuthenticated, setIsAuthenticated] = useState(null);
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
@ -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,
|
||||||
|
30
src/services/tenantService.js
Normal file
30
src/services/tenantService.js
Normal file
@ -0,0 +1,30 @@
|
|||||||
|
// Save org-level token
|
||||||
|
export const setOrgToken = (token, refreshToken) => {
|
||||||
|
localStorage.setItem("orgJwtToken", token);
|
||||||
|
localStorage.setItem("orgRefreshToken", refreshToken);
|
||||||
|
};
|
||||||
|
|
||||||
|
// Save selected tenant token
|
||||||
|
export const setTenantToken = (token, refreshToken, tenantId) => {
|
||||||
|
localStorage.setItem("jwtToken", token); // tenant JWT
|
||||||
|
localStorage.setItem("refreshToken", refreshToken);
|
||||||
|
localStorage.setItem("tenantId", tenantId);
|
||||||
|
};
|
||||||
|
|
||||||
|
// Get tenant token
|
||||||
|
export const getTenantToken = () => {
|
||||||
|
return {
|
||||||
|
token: localStorage.getItem("jwtToken"),
|
||||||
|
refreshToken: localStorage.getItem("refreshToken"),
|
||||||
|
tenantId: localStorage.getItem("tenantId"),
|
||||||
|
};
|
||||||
|
};
|
||||||
|
|
||||||
|
// Clear all tokens
|
||||||
|
export const clearAllTokens = () => {
|
||||||
|
localStorage.removeItem("jwtToken");
|
||||||
|
localStorage.removeItem("refreshToken");
|
||||||
|
localStorage.removeItem("orgJwtToken");
|
||||||
|
localStorage.removeItem("orgRefreshToken");
|
||||||
|
localStorage.removeItem("tenantId");
|
||||||
|
};
|
@ -3,23 +3,32 @@ import { createSlice } from "@reduxjs/toolkit";
|
|||||||
const globalVariablesSlice = createSlice({
|
const globalVariablesSlice = createSlice({
|
||||||
name: "globalVariables",
|
name: "globalVariables",
|
||||||
initialState: {
|
initialState: {
|
||||||
loginUser:null,
|
loginUser: null,
|
||||||
currentTenant:null
|
tenantList: [],
|
||||||
|
currentTenant: null,
|
||||||
|
permissions: [],
|
||||||
},
|
},
|
||||||
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.permissions = action.payload;
|
||||||
state.loginUser = action.payload
|
},
|
||||||
|
setLoginUserTenants: (state, action) => {
|
||||||
|
state.tenantList = 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,
|
||||||
|
setLoginUserTenants,
|
||||||
|
} = globalVariablesSlice.actions;
|
||||||
export default globalVariablesSlice.reducer;
|
export default globalVariablesSlice.reducer;
|
||||||
|
@ -1,10 +1,10 @@
|
|||||||
import axios from "axios";
|
import axios from "axios";
|
||||||
import { useNavigate } from "react-router-dom";
|
|
||||||
import axiosRetry from "axios-retry";
|
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,
|
||||||
@ -14,16 +14,15 @@ export const axiosClient = axios.create({
|
|||||||
},
|
},
|
||||||
});
|
});
|
||||||
|
|
||||||
// Auto retry failed requests (e.g., network issues)
|
// Retry failed requests
|
||||||
axiosRetry(axiosClient, { retries: 3 });
|
|
||||||
|
|
||||||
// Request Interceptor — Add Bearer token if required
|
|
||||||
axiosClient.interceptors.request.use(
|
axiosClient.interceptors.request.use(
|
||||||
async (config) => {
|
async (config) => {
|
||||||
const requiresAuth = config.authRequired !== false; // default to true
|
const requiresAuth = config.authRequired !== false;
|
||||||
|
|
||||||
if (requiresAuth) {
|
if (requiresAuth) {
|
||||||
const token = localStorage.getItem("jwtToken");
|
let token = localStorage.getItem("jwtToken"); // tenant token by default
|
||||||
|
if (config.orgToken) token = localStorage.getItem("orgJwtToken"); // org token if requested
|
||||||
|
|
||||||
if (token) {
|
if (token) {
|
||||||
config.headers["Authorization"] = `Bearer ${token}`;
|
config.headers["Authorization"] = `Bearer ${token}`;
|
||||||
config._retry = true;
|
config._retry = true;
|
||||||
@ -37,23 +36,27 @@ axiosClient.interceptors.request.use(
|
|||||||
(error) => Promise.reject(error)
|
(error) => Promise.reject(error)
|
||||||
);
|
);
|
||||||
|
|
||||||
// 🔄 Response Interceptor — Handle 401, refresh token, etc.
|
// Response interceptor
|
||||||
axiosClient.interceptors.response.use(
|
axiosClient.interceptors.response.use(
|
||||||
(response) => response,
|
(response) => response,
|
||||||
async (error) => {
|
async (error) => {
|
||||||
const originalRequest = error.config;
|
const originalRequest = error.config;
|
||||||
|
|
||||||
// Skip retry for public requests or already retried ones
|
if (
|
||||||
if (!originalRequest && originalRequest._retry || originalRequest.authRequired === false) {
|
(!originalRequest && originalRequest._retry) ||
|
||||||
|
originalRequest.authRequired === false
|
||||||
|
) {
|
||||||
return Promise.reject(error);
|
return Promise.reject(error);
|
||||||
}
|
}
|
||||||
|
|
||||||
// Avoid showing multiple toasts
|
|
||||||
if (!originalRequest._toastShown) {
|
if (!originalRequest._toastShown) {
|
||||||
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();
|
||||||
@ -65,31 +68,25 @@ axiosClient.interceptors.response.use(
|
|||||||
|
|
||||||
if (status === 401 && !isRefreshRequest) {
|
if (status === 401 && !isRefreshRequest) {
|
||||||
originalRequest._retry = true;
|
originalRequest._retry = true;
|
||||||
|
|
||||||
const refreshToken = localStorage.getItem("refreshToken");
|
const refreshToken = localStorage.getItem("refreshToken");
|
||||||
|
|
||||||
if (!refreshToken || error.response.data?.errors === "Invalid or expired refresh token.") {
|
if (!refreshToken) {
|
||||||
redirectToLogin();
|
redirectToLogin();
|
||||||
return Promise.reject(error);
|
return Promise.reject(error);
|
||||||
}
|
}
|
||||||
|
|
||||||
stopSignalR();
|
stopSignalR();
|
||||||
|
|
||||||
try {
|
try {
|
||||||
// Refresh token call
|
|
||||||
const res = await axiosClient.post("/api/Auth/refresh-token", {
|
const res = await axiosClient.post("/api/Auth/refresh-token", {
|
||||||
token: localStorage.getItem("jwtToken"),
|
token: localStorage.getItem("jwtToken"),
|
||||||
refreshToken,
|
refreshToken,
|
||||||
});
|
});
|
||||||
|
|
||||||
const { token, refreshToken: newRefreshToken } = res.data.data;
|
const { token, refreshToken: newRefreshToken } = res.data.data;
|
||||||
|
|
||||||
// Save updated tokens
|
|
||||||
localStorage.setItem("jwtToken", token);
|
localStorage.setItem("jwtToken", token);
|
||||||
localStorage.setItem("refreshToken", newRefreshToken);
|
localStorage.setItem("refreshToken", newRefreshToken);
|
||||||
|
|
||||||
startSignalR()
|
startSignalR();
|
||||||
// Set Authorization header
|
|
||||||
originalRequest.headers["Authorization"] = `Bearer ${token}`;
|
originalRequest.headers["Authorization"] = `Bearer ${token}`;
|
||||||
return axiosClient(originalRequest);
|
return axiosClient(originalRequest);
|
||||||
} catch (refreshError) {
|
} catch (refreshError) {
|
||||||
@ -124,40 +121,41 @@ const apiRequest = async (method, url, data = {}, config = {}) => {
|
|||||||
|
|
||||||
// Exported API wrapper
|
// Exported API wrapper
|
||||||
export const api = {
|
export const api = {
|
||||||
// Public routes (no token required)
|
|
||||||
postPublic: (url, data = {}, customHeaders = {}) =>
|
postPublic: (url, data = {}, customHeaders = {}) =>
|
||||||
apiRequest("post", url, data, {
|
apiRequest("post", url, data, {
|
||||||
headers: { ...customHeaders },
|
headers: { ...customHeaders },
|
||||||
authRequired: false,
|
authRequired: false,
|
||||||
}),
|
}),
|
||||||
|
|
||||||
// Authenticated routes
|
get: (url, params = {}, customHeaders = {}, orgToken = false) =>
|
||||||
get: (url, params = {}, customHeaders = {}) =>
|
|
||||||
apiRequest("get", url, params, {
|
apiRequest("get", url, params, {
|
||||||
headers: { ...customHeaders },
|
headers: { ...customHeaders },
|
||||||
authRequired: true,
|
authRequired: true,
|
||||||
|
orgToken,
|
||||||
}),
|
}),
|
||||||
|
|
||||||
post: (url, data = {}, customHeaders = {}) =>
|
post: (url, data = {}, customHeaders = {}, orgToken = false) =>
|
||||||
apiRequest("post", url, data, {
|
apiRequest("post", url, data, {
|
||||||
headers: { ...customHeaders },
|
headers: { ...customHeaders },
|
||||||
authRequired: true,
|
authRequired: true,
|
||||||
|
orgToken,
|
||||||
}),
|
}),
|
||||||
|
|
||||||
put: (url, data = {}, customHeaders = {}) =>
|
put: (url, data = {}, customHeaders = {}, orgToken = false) =>
|
||||||
apiRequest("put", url, data, {
|
apiRequest("put", url, data, {
|
||||||
headers: { ...customHeaders },
|
headers: { ...customHeaders },
|
||||||
authRequired: true,
|
authRequired: true,
|
||||||
|
orgToken,
|
||||||
}),
|
}),
|
||||||
|
|
||||||
delete: (url, data = {}, customHeaders = {}) =>
|
delete: (url, data = {}, customHeaders = {}, orgToken = false) =>
|
||||||
apiRequest("delete", url, data, {
|
apiRequest("delete", url, data, {
|
||||||
headers: { ...customHeaders },
|
headers: { ...customHeaders },
|
||||||
authRequired: true,
|
authRequired: true,
|
||||||
|
orgToken,
|
||||||
}),
|
}),
|
||||||
};
|
};
|
||||||
|
|
||||||
// Redirect helper
|
|
||||||
function redirectToLogin() {
|
function redirectToLogin() {
|
||||||
window.location.href = "/auth/login";
|
window.location.href = "/auth/login";
|
||||||
}
|
}
|
||||||
|
Loading…
x
Reference in New Issue
Block a user