initially setup login

This commit is contained in:
Pramod 2025-09-21 10:06:38 +05:30
parent a1a935b0d5
commit 070fa93fca
10 changed files with 238 additions and 86 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 { z } from "zod";
import { AuthWrapper } from "./AuthWrapper";
import { setOrgToken } from "../../services/tenantService";
const LoginPage = () => {
const navigate = useNavigate();
const [loading, setLoading] = useState(false);
@ -41,10 +41,11 @@ const LoginPage = () => {
password: data.password,
};
const response = await AuthRepository.login(userCredential);
localStorage.setItem("jwtToken", response.data.token);
localStorage.setItem("refreshToken", response.data.refreshToken);
// localStorage.setItem("jwtToken", response.data.token);
// localStorage.setItem("refreshToken", response.data.refreshToken);
setLoading(false);
navigate("/dashboard");
setOrgToken(response.data.token, response.data.refreshToken);
navigate("/auth/user");
} else {
await AuthRepository.sendOTP({ email: data.username });
showToast("OTP has been sent to your email.", "success");
@ -146,7 +147,8 @@ const LoginPage = () => {
type={hidepass ? "password" : "text"}
autoComplete="new-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"
{...register("password")}
@ -170,13 +172,15 @@ const LoginPage = () => {
{/* ✅ Error message */}
{errors.password && (
<div className="invalid-feedback text-start" style={{ fontSize: "12px" }}>
<div
className="invalid-feedback text-start"
style={{ fontSize: "12px" }}
>
{errors.password.message}
</div>
)}
</div>
{/* Remember Me + Forgot Password */}
<div className="mb-3 d-flex justify-content-between align-items-center">
<div className="form-check">

View 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 youre 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;

View File

@ -1,13 +1,10 @@
import { api } from "../utils/axiosClient";
const AttendanceRepository = {
markAttendance:(data)=>api.post("/api/attendance/record",data),
getAttendance:(id)=>api.get(`api/attendance/project/team?projectId=${id}`),
getAttendanceFilteredByDate: ( projectId, fromDate, toDate ) =>
{
let url = `api/Attendance/project/log?projectId=${ projectId }`
markAttendance: (data) => api.post("/api/attendance/record", data),
getAttendance: (id) => api.get(`api/attendance/project/team?projectId=${id}`),
getAttendanceFilteredByDate: (projectId, fromDate, toDate) => {
let url = `api/Attendance/project/log?projectId=${projectId}`;
if (fromDate) {
url += `&dateFrom=${fromDate}`;
}
@ -15,16 +12,15 @@ const AttendanceRepository = {
if (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 }` ),
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 }?`
getAttendanceByEmployee: (employeeId, fromDate, toDate) => {
let url = `api/Attendance/log/employee/${employeeId}?`;
if (fromDate) {
url += `&dateFrom=${fromDate}`;
}
@ -32,10 +28,8 @@ const AttendanceRepository = {
if (toDate) {
url += `&dateTo=${toDate}`;
}
return api.get(url)
return api.get(url);
},
}
};
export default AttendanceRepository;

View File

@ -16,6 +16,22 @@ const AuthRepository = {
profile: () => api.get("/api/user/profile"),
changepassword: (data) => api.post("/api/auth/change-password", data),
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;

View File

@ -49,9 +49,9 @@ import MainResetPasswordPage from "../pages/authentication/MainResetPasswordPage
import TenantPage from "../pages/Tenant/TenantPage";
import { Navigate } from "react-router-dom";
import CreateTenant from "../pages/Tenant/CreateTenant";
;
import OrganizationPage from "../pages/Organization/OrganizationPage";
import LandingPage from "../pages/Home/LandingPage";
import TenantSelection from "../pages/authentication/TenantSelection";
const router = createBrowserRouter(
[
{
@ -62,6 +62,7 @@ const router = createBrowserRouter(
element: <AuthLayout />,
children: [
{ path: "/auth/login", element: <MainLogin /> },
{ path: "/auth/user", element: <TenantSelection /> },
{ path: "/auth/login-otp", element: <MainLoginWithOTPPage /> },
{ path: "/auth/reqest/demo", element: <MainRegisterPage /> },
{ path: "/auth/forgot-password", element: <MainForgetPage /> },

View 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");
};

View File

@ -3,23 +3,32 @@ import { createSlice } from "@reduxjs/toolkit";
const globalVariablesSlice = createSlice({
name: "globalVariables",
initialState: {
loginUser:null,
currentTenant:null
loginUser: null,
tenantList: [],
currentTenant: null,
permissions: [],
},
reducers: {
setGlobalVariable: (state, action) => {
const { key, value } = action.payload;
state[key] = value;
},
setLoginUserPermmisions: ( state, action ) =>
{
state.loginUser = action.payload
setLoginUserPermmisions: (state, action) => {
state.permissions = 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;

View File

@ -1,10 +1,10 @@
import axios from "axios";
import { useNavigate } from "react-router-dom";
import axiosRetry from "axios-retry";
import showToast from "../services/toastService";
import { startSignalR, stopSignalR } from "../services/signalRService";
import { BASE_URL } from "./constants";
const base_Url = BASE_URL
const base_Url = BASE_URL;
export const axiosClient = axios.create({
baseURL: base_Url,
@ -14,16 +14,15 @@ export const axiosClient = axios.create({
},
});
// Auto retry failed requests (e.g., network issues)
axiosRetry(axiosClient, { retries: 3 });
// Request Interceptor Add Bearer token if required
// Retry failed requests
axiosClient.interceptors.request.use(
async (config) => {
const requiresAuth = config.authRequired !== false; // default to true
const requiresAuth = config.authRequired !== false;
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) {
config.headers["Authorization"] = `Bearer ${token}`;
config._retry = true;
@ -37,23 +36,27 @@ axiosClient.interceptors.request.use(
(error) => Promise.reject(error)
);
// 🔄 Response Interceptor Handle 401, refresh token, etc.
// Response interceptor
axiosClient.interceptors.response.use(
(response) => response,
async (error) => {
const originalRequest = error.config;
// 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);
}
// Avoid showing multiple toasts
if (!originalRequest._toastShown) {
originalRequest._toastShown = true;
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") {
showToast("Network error. Please check your connection.", "error");
redirectToLogin();
@ -65,31 +68,25 @@ axiosClient.interceptors.response.use(
if (status === 401 && !isRefreshRequest) {
originalRequest._retry = true;
const refreshToken = localStorage.getItem("refreshToken");
if (!refreshToken || error.response.data?.errors === "Invalid or expired refresh token.") {
if (!refreshToken) {
redirectToLogin();
return Promise.reject(error);
}
stopSignalR();
try {
// Refresh token call
const res = await axiosClient.post("/api/Auth/refresh-token", {
token: localStorage.getItem("jwtToken"),
refreshToken,
});
const { token, refreshToken: newRefreshToken } = res.data.data;
// Save updated tokens
localStorage.setItem("jwtToken", token);
localStorage.setItem("refreshToken", newRefreshToken);
startSignalR()
// Set Authorization header
startSignalR();
originalRequest.headers["Authorization"] = `Bearer ${token}`;
return axiosClient(originalRequest);
} catch (refreshError) {
@ -124,40 +121,41 @@ const apiRequest = async (method, url, data = {}, config = {}) => {
// Exported API wrapper
export const api = {
// Public routes (no token required)
postPublic: (url, data = {}, customHeaders = {}) =>
apiRequest("post", url, data, {
headers: { ...customHeaders },
authRequired: false,
}),
// Authenticated routes
get: (url, params = {}, customHeaders = {}) =>
get: (url, params = {}, customHeaders = {}, orgToken = false) =>
apiRequest("get", url, params, {
headers: { ...customHeaders },
authRequired: true,
orgToken,
}),
post: (url, data = {}, customHeaders = {}) =>
post: (url, data = {}, customHeaders = {}, orgToken = false) =>
apiRequest("post", url, data, {
headers: { ...customHeaders },
authRequired: true,
orgToken,
}),
put: (url, data = {}, customHeaders = {}) =>
put: (url, data = {}, customHeaders = {}, orgToken = false) =>
apiRequest("put", url, data, {
headers: { ...customHeaders },
authRequired: true,
orgToken,
}),
delete: (url, data = {}, customHeaders = {}) =>
delete: (url, data = {}, customHeaders = {}, orgToken = false) =>
apiRequest("delete", url, data, {
headers: { ...customHeaders },
authRequired: true,
orgToken,
}),
};
// Redirect helper
function redirectToLogin() {
window.location.href = "/auth/login";
}