From 070fa93fca562b6a264c62f192fb0faa92bdf634 Mon Sep 17 00:00:00 2001 From: Pramod Date: Sun, 21 Sep 2025 10:06:38 +0530 Subject: [PATCH] initially setup login --- src/hooks/useAuth.jsx | 39 +++++++++++++ src/pages/authentication/LoginPage.jsx | 38 ++++++------ src/pages/authentication/TenantSelection.jsx | 61 ++++++++++++++++++++ src/repositories/AttendanceRepository.jsx | 50 +++++++--------- src/repositories/AuthRepository.jsx | 16 +++++ src/router/AppRoutes.jsx | 3 +- src/router/ProtectedRoute.jsx | 4 +- src/services/tenantService.js | 30 ++++++++++ src/slices/globalVariablesSlice.jsx | 27 ++++++--- src/utils/axiosClient.jsx | 56 +++++++++--------- 10 files changed, 238 insertions(+), 86 deletions(-) create mode 100644 src/hooks/useAuth.jsx create mode 100644 src/pages/authentication/TenantSelection.jsx create mode 100644 src/services/tenantService.js diff --git a/src/hooks/useAuth.jsx b/src/hooks/useAuth.jsx new file mode 100644 index 00000000..94b9f2c5 --- /dev/null +++ b/src/hooks/useAuth.jsx @@ -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. + }) +} \ No newline at end of file diff --git a/src/pages/authentication/LoginPage.jsx b/src/pages/authentication/LoginPage.jsx index 3e331376..e75b9f0c 100644 --- a/src/pages/authentication/LoginPage.jsx +++ b/src/pages/authentication/LoginPage.jsx @@ -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); @@ -16,13 +16,13 @@ const LoginPage = () => { const loginSchema = IsLoginWithOTP ? z.object({ - username: z.string().trim().email({ message: "Valid email required" }), - }) + username: z.string().trim().email({ message: "Valid email required" }), + }) : z.object({ - username: z.string().trim().email({ message: "Valid email required" }), - password: z.string().trim().min(1, { message: "Password required" }), - rememberMe: z.boolean(), - }); + username: z.string().trim().email({ message: "Valid email required" }), + password: z.string().trim().min(1, { message: "Password required" }), + rememberMe: z.boolean(), + }); const { register, @@ -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,8 +147,9 @@ 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")} placeholder="••••••••••••" @@ -170,13 +172,15 @@ const LoginPage = () => { {/* ✅ Error message */} {errors.password && ( -
+
{errors.password.message}
)}
- {/* Remember Me + Forgot Password */}
@@ -209,8 +213,8 @@ const LoginPage = () => { {loading ? "Please Wait..." : IsLoginWithOTP - ? "Send OTP" - : "Sign In"} + ? "Send OTP" + : "Sign In"} {/* Login With OTP Button */} @@ -254,4 +258,4 @@ const LoginPage = () => { ); }; -export default LoginPage; \ No newline at end of file +export default LoginPage; diff --git a/src/pages/authentication/TenantSelection.jsx b/src/pages/authentication/TenantSelection.jsx new file mode 100644 index 00000000..04223282 --- /dev/null +++ b/src/pages/authentication/TenantSelection.jsx @@ -0,0 +1,61 @@ +import React from "react"; + +const TenantSelection = () => { + return ( +
+ {/* Header */} +
+

Welcome Pramod

+

+ Please select which dashboard you want to explore!!! +

+
+ + {/* Card Section */} +
+
+
+ {/* Image */} +
+ logo +
+ + {/* Content */} +
+

+ Marco Secure Solution Pvt Limited +

+ +
+
+

Industry:

+

Information Technology (IT) Service

+
+ +

+ 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, +

+ + +
+
+
+
+
+
+ ); +}; + +export default TenantSelection; diff --git a/src/repositories/AttendanceRepository.jsx b/src/repositories/AttendanceRepository.jsx index 679001f8..a8f1a0b1 100644 --- a/src/repositories/AttendanceRepository.jsx +++ b/src/repositories/AttendanceRepository.jsx @@ -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 }` +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}`; if (fromDate) { url += `&dateFrom=${fromDate}`; } @@ -15,27 +12,24 @@ 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 }` ), - - 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; - diff --git a/src/repositories/AuthRepository.jsx b/src/repositories/AuthRepository.jsx index 5004c7ec..e6c6411a 100644 --- a/src/repositories/AuthRepository.jsx +++ b/src/repositories/AuthRepository.jsx @@ -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; diff --git a/src/router/AppRoutes.jsx b/src/router/AppRoutes.jsx index 8e32e0e1..423e57dd 100644 --- a/src/router/AppRoutes.jsx +++ b/src/router/AppRoutes.jsx @@ -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: , children: [ { path: "/auth/login", element: }, + { path: "/auth/user", element: }, { path: "/auth/login-otp", element: }, { path: "/auth/reqest/demo", element: }, { path: "/auth/forgot-password", element: }, diff --git a/src/router/ProtectedRoute.jsx b/src/router/ProtectedRoute.jsx index a1879e85..68dd847f 100644 --- a/src/router/ProtectedRoute.jsx +++ b/src/router/ProtectedRoute.jsx @@ -8,7 +8,7 @@ const ProtectedRoute = () => { // // const isAuthenticated = true; // isTokenValid(); // return isAuthenticated ? : - + const [isAuthenticated, setIsAuthenticated] = useState(null); useEffect(() => { @@ -66,7 +66,7 @@ const attemptTokenRefresh = async (storedRefreshToken) => { localStorage.setItem("jwtToken", response.data.token); localStorage.setItem("refreshToken", response.data.refreshToken); return true; - // api + // api // .post("/api/auth/refresh-token", { // token: localStorage.getItem("jwtToken"), // refreshToken: refreshToken, diff --git a/src/services/tenantService.js b/src/services/tenantService.js new file mode 100644 index 00000000..5d08b577 --- /dev/null +++ b/src/services/tenantService.js @@ -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"); +}; diff --git a/src/slices/globalVariablesSlice.jsx b/src/slices/globalVariablesSlice.jsx index 7bdadd51..70e6a479 100644 --- a/src/slices/globalVariablesSlice.jsx +++ b/src/slices/globalVariablesSlice.jsx @@ -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; diff --git a/src/utils/axiosClient.jsx b/src/utils/axiosClient.jsx index 9c4bd3ae..427b596a 100644 --- a/src/utils/axiosClient.jsx +++ b/src/utils/axiosClient.jsx @@ -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"; -} \ No newline at end of file +}