configured tenant level login
This commit is contained in:
parent
aee510f527
commit
b9b3788dda
@ -1,12 +1,19 @@
|
||||
import React, { useEffect } from 'react'
|
||||
import { useOrganizationModal } from './hooks/useOrganization';
|
||||
import OrganizationModal from './components/Organization/OrganizationModal';
|
||||
import React, { useEffect } from "react";
|
||||
import { useOrganizationModal } from "./hooks/useOrganization";
|
||||
import OrganizationModal from "./components/Organization/OrganizationModal";
|
||||
import { useAuthModal } from "./hooks/useAuth";
|
||||
import SwitchTenant from "./pages/authentication/SwitchTenant";
|
||||
|
||||
const ModalProvider = () => {
|
||||
const { isOpen,onClose } = useOrganizationModal();
|
||||
|
||||
return <>{isOpen && <OrganizationModal />}</>;
|
||||
const { isOpen, onClose } = useOrganizationModal();
|
||||
const { isOpen: isAuthOpen } = useAuthModal();
|
||||
|
||||
return (
|
||||
<>
|
||||
{isOpen && <OrganizationModal />}
|
||||
{isAuthOpen && <SwitchTenant />}
|
||||
</>
|
||||
);
|
||||
};
|
||||
|
||||
|
||||
export default ModalProvider
|
||||
export default ModalProvider;
|
@ -19,6 +19,7 @@ import { useProjectName } from "../../hooks/useProjects";
|
||||
import eventBus from "../../services/eventBus";
|
||||
import { useHasUserPermission } from "../../hooks/useHasUserPermission";
|
||||
import { MANAGE_PROJECT } from "../../utils/constants";
|
||||
import { useAuthModal } from "../../hooks/useAuth";
|
||||
|
||||
const Header = () => {
|
||||
const { profile } = useProfile();
|
||||
@ -26,6 +27,7 @@ const Header = () => {
|
||||
const dispatch = useDispatch();
|
||||
const { data, loading } = useMaster();
|
||||
const navigate = useNavigate();
|
||||
const {onOpen} = useAuthModal()
|
||||
const HasManageProjectPermission = useHasUserPermission(MANAGE_PROJECT);
|
||||
// {
|
||||
// console.log(location.pathname);
|
||||
@ -455,6 +457,16 @@ const Header = () => {
|
||||
<span className="align-middle">Change Password</span>
|
||||
</a>
|
||||
</li>
|
||||
|
||||
<li onClick={()=>onOpen()}>
|
||||
{" "}
|
||||
<a
|
||||
className="dropdown-item cusor-pointer"
|
||||
>
|
||||
<i className="bx bx-transfer-alt me-2"></i>
|
||||
<span className="align-middle">Switch Tenant</span>
|
||||
</a>
|
||||
</li>
|
||||
<li>
|
||||
<div className="dropdown-divider"></div>
|
||||
</li>
|
||||
|
@ -1,39 +1,51 @@
|
||||
import { useState, useEffect, useCallback } from "react";
|
||||
import {
|
||||
Mutation,
|
||||
useMutation,
|
||||
useQuery,
|
||||
useQueryClient,
|
||||
} from "@tanstack/react-query";
|
||||
import { Link, useNavigate } from "react-router-dom";
|
||||
import AuthRepository from "../repositories/AuthRepository.jsx";
|
||||
import { useDispatch, useSelector } from "react-redux";
|
||||
import {
|
||||
closeAuthModal,
|
||||
openAuthModal,
|
||||
} from "../slices/localVariablesSlice.jsx";
|
||||
|
||||
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 = () => {
|
||||
return useQuery({
|
||||
queryKey: ["tenantlist"],
|
||||
queryFn: async () => await AuthRepository.getTenantList(),
|
||||
});
|
||||
};
|
||||
|
||||
export const useSelectTenant = (onSuccessCallBack) => {
|
||||
const queryClient = useQueryClient();
|
||||
|
||||
export const useTenants =()=>{
|
||||
get
|
||||
return useQueery({
|
||||
queryKey:["tenantlist",localhost.get("orgJwtToken")],
|
||||
queryFun:async()=> await AuthRepository.
|
||||
})
|
||||
}
|
||||
return useMutation({
|
||||
mutationFn: async (tenantId) => {
|
||||
const res = await AuthRepository.selectTenant(tenantId);
|
||||
return res.data;
|
||||
},
|
||||
onSuccess: (data) => {
|
||||
localStorage.setItem("ltkn", data.token);
|
||||
localStorage.setItem("rtkn", data.refreshToken);
|
||||
if (onSuccessCallBack) onSuccessCallBack();
|
||||
},
|
||||
onError: (error) => {
|
||||
showToast(error.message || "Error while creating project", "error");
|
||||
},
|
||||
});
|
||||
};
|
||||
|
||||
export const useAuthModal = () => {
|
||||
const dispatch = useDispatch();
|
||||
const { isOpen } = useSelector((state) => state.localVariables.AuthModal);
|
||||
|
||||
return {
|
||||
isOpen,
|
||||
onOpen: () => dispatch(openAuthModal()),
|
||||
onClose: () => dispatch(closeAuthModal()),
|
||||
};
|
||||
};
|
@ -44,7 +44,7 @@ const LoginPage = () => {
|
||||
localStorage.setItem("jwtToken", response.data.token);
|
||||
localStorage.setItem("refreshToken", response.data.refreshToken);
|
||||
setLoading(false);
|
||||
navigate("/dashboard");
|
||||
navigate("/auth/switch/org");
|
||||
} else {
|
||||
await AuthRepository.sendOTP({ email: data.username });
|
||||
showToast("OTP has been sent to your email.", "success");
|
||||
|
101
src/pages/authentication/SwitchTenant.jsx
Normal file
101
src/pages/authentication/SwitchTenant.jsx
Normal file
@ -0,0 +1,101 @@
|
||||
import React, { useState } from "react";
|
||||
import Modal from "../../components/common/Modal";
|
||||
import { useAuthModal, useSelectTenant, useTenants } from "../../hooks/useAuth";
|
||||
import { useProfile } from "../../hooks/useProfile";
|
||||
import { useQueryClient } from "@tanstack/react-query";
|
||||
import AuthRepository from "../../repositories/AuthRepository";
|
||||
|
||||
const SwitchTenant = () => {
|
||||
const queryClient = useQueryClient();
|
||||
const { profile } = useProfile();
|
||||
const [pendingTenant, setPendingTenant] = useState(null);
|
||||
const { isOpen, onClose, onOpen } = useAuthModal();
|
||||
const { data, isLoading, isError, error } = useTenants();
|
||||
const { mutate: chooseTenant, isPending } = useSelectTenant(() => {
|
||||
onClose();
|
||||
queryClient.clear();
|
||||
|
||||
// 2. Force fetch profile fresh for the new tenant
|
||||
queryClient.fetchQuery({
|
||||
queryKey: ["profile"],
|
||||
queryFn: () => AuthRepository.profile(),
|
||||
});
|
||||
});
|
||||
const currentTenant = localStorage.getItem("ctnt");
|
||||
const handleTenantselection = (tenantId) => {
|
||||
setPendingTenant(tenantId);
|
||||
localStorage.setItem("ctnt", tenantId);
|
||||
chooseTenant(tenantId);
|
||||
};
|
||||
const contentBody = (
|
||||
<div className="container text-black">
|
||||
<p className=" fs-5">Switch Workplace</p>
|
||||
<div className="row justify-content-center g-4">
|
||||
{data?.data.map((tenant) => (
|
||||
<div key={tenant.id} className="col-12 ">
|
||||
<div
|
||||
className={`d-flex flex-column flex-md-row gap-3 align-items-center align-items-md-start p-1 border ${
|
||||
currentTenant === tenant.id ? "border-primary" : ""
|
||||
} `}
|
||||
>
|
||||
<div
|
||||
className="flex-shrink-0 text-center"
|
||||
style={{
|
||||
width: "80px",
|
||||
aspectRatio: "1 / 1",
|
||||
overflow: "hidden",
|
||||
display: "flex",
|
||||
alignItems: "center",
|
||||
justifyContent: "center",
|
||||
}}
|
||||
>
|
||||
<img
|
||||
src={tenant?.logoImage || "/assets/img/SP-Placeholdeer.svg"}
|
||||
alt={tenant.name}
|
||||
className="img-fluid"
|
||||
style={{
|
||||
width: "100%",
|
||||
height: "100%",
|
||||
objectFit: "contain",
|
||||
}}
|
||||
/>
|
||||
</div>
|
||||
|
||||
<div className="d-flex flex-column text-start gap-2">
|
||||
<p className="fs-5 text-muted fw-semibold mb-1">
|
||||
{tenant?.name}
|
||||
</p>
|
||||
|
||||
<div className="d-flex flex-wrap gap-2 align-items-center">
|
||||
<p className="fw-semibold m-0">Industry:</p>
|
||||
<p className="m-0">
|
||||
{tenant?.industry?.name || "Not Available"}
|
||||
</p>
|
||||
</div>
|
||||
|
||||
{tenant?.description && (
|
||||
<p className="text-start text-wrap m-0">
|
||||
{tenant?.description}
|
||||
</p>
|
||||
)}
|
||||
|
||||
<button
|
||||
className="btn btn-primary btn-sm mt-2 align-self-start"
|
||||
onClick={() => handleTenantselection(tenant?.id)}
|
||||
disabled={isPending && pendingTenant === tenant.id}
|
||||
>
|
||||
{isPending && pendingTenant === tenant.id
|
||||
? "Please Wait.."
|
||||
: "Go To Dashboard"}
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
))}
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
return <Modal isOpen={isOpen} onClose={onClose} body={contentBody} />;
|
||||
};
|
||||
|
||||
export default SwitchTenant;
|
102
src/pages/authentication/TenantSelectionPage.jsx
Normal file
102
src/pages/authentication/TenantSelectionPage.jsx
Normal file
@ -0,0 +1,102 @@
|
||||
import { useEffect, useState } from "react";
|
||||
import { useTenants, useSelectTenant } from "../../hooks/useAuth.jsx";
|
||||
import { Link, useNavigate } from "react-router-dom";
|
||||
import Dashboard from "../../components/Dashboard/Dashboard.jsx";
|
||||
|
||||
const TenantSelectionPage = () => {
|
||||
const [pendingTenant, setPendingTenant] = useState(null);
|
||||
const navigate = useNavigate();
|
||||
|
||||
const { data, isLoading, isError, error } = useTenants();
|
||||
const { mutate: chooseTenant, isPending } = useSelectTenant(() => {
|
||||
navigate("/dashboard");
|
||||
});
|
||||
const handleTenantselection = (tenantId) => {
|
||||
setPendingTenant(tenantId);
|
||||
localStorage.setItem("ctnt", tenantId);
|
||||
chooseTenant(tenantId);
|
||||
};
|
||||
|
||||
useEffect(() => {
|
||||
if (localStorage.getItem("ctnt")) {
|
||||
return navigate("/dashboard");
|
||||
}
|
||||
}, []);
|
||||
|
||||
if (isLoading) {
|
||||
return (
|
||||
<div className="container-fluid py-5 text-center">
|
||||
<div className="spinner-border text-primary" role="status">
|
||||
<span className="visually-hidden">Loading tenants...</span>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
return (
|
||||
<div className="container-fluid py-4">
|
||||
{/* Header */}
|
||||
<div className="text-center mb-4">
|
||||
<p className="fs-4 mb-1">Welcome</p>
|
||||
<p className="fs-5">
|
||||
Please select which dashboard you want to explore!!!
|
||||
</p>
|
||||
</div>
|
||||
|
||||
{/* Card Section */}
|
||||
<div className="row justify-content-center g-4 m-auto">
|
||||
{data?.data.map((tenant) => (
|
||||
<div key={tenant.id} className="col-12 col-md-10 col-lg-8">
|
||||
<div className="d-flex flex-column flex-md-row gap-5 align-items-center align-items-md-start p-3 border rounded shadow-sm bg-white h-100">
|
||||
{/* Image */}
|
||||
<div className="flex-shrink-0 text-center">
|
||||
<img
|
||||
src={tenant?.logoImage || "/assets/img/SP-Placeholdeer.svg"}
|
||||
alt={tenant.name}
|
||||
className="img-fluid"
|
||||
style={{
|
||||
maxWidth: "140px",
|
||||
height: "auto",
|
||||
aspectRatio: "3 / 2",
|
||||
}}
|
||||
/>
|
||||
</div>
|
||||
|
||||
{/* Content */}
|
||||
<div className="d-flex flex-column text-start gap-2">
|
||||
<p className="fs-5 text-muted fw-semibold mb-1">
|
||||
{tenant?.name}
|
||||
</p>
|
||||
|
||||
<div className="d-flex flex-wrap gap-2 align-items-center">
|
||||
<p className="fw-semibold m-0">Industry:</p>
|
||||
<p className="m-0">
|
||||
{tenant?.industry?.name || "Not Available"}
|
||||
</p>
|
||||
</div>
|
||||
|
||||
{tenant?.description && (
|
||||
<p className="text-start text-wrap m-0">
|
||||
{tenant?.description}
|
||||
</p>
|
||||
)}
|
||||
|
||||
<button
|
||||
className="btn btn-primary btn-sm mt-2 align-self-start"
|
||||
onClick={() => handleTenantselection(tenant?.id)}
|
||||
disabled={pendingTenant === tenant.id && isPending}
|
||||
>
|
||||
{isPending && pendingTenant === tenant.id
|
||||
? "Please Wait.."
|
||||
: "Go To Dashboard"}
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
))}
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
export default TenantSelectionPage;
|
@ -15,7 +15,9 @@ const AuthRepository = {
|
||||
logout: (data) => api.post("/api/auth/logout", data),
|
||||
profile: () => api.get("/api/user/profile"),
|
||||
changepassword: (data) => api.post("/api/auth/change-password", data),
|
||||
appmenu:()=>api.get('/api/appmenu/get/menu')
|
||||
appmenu: () => api.get('/api/appmenu/get/menu'),
|
||||
selectTenant: (tenantId) => api.post(`/api/Auth/select-tenant/${tenantId}`),
|
||||
getTenantList: () => api.get("/api/Auth/get/user/tenants"),
|
||||
|
||||
};
|
||||
|
||||
|
@ -51,6 +51,7 @@ 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 TenantSelectionPage from "../pages/authentication/TenantSelectionPage";
|
||||
const router = createBrowserRouter(
|
||||
[
|
||||
{
|
||||
@ -69,6 +70,7 @@ const router = createBrowserRouter(
|
||||
{ path: "/auth/changepassword", element: <ChangePasswordPage /> },
|
||||
],
|
||||
},
|
||||
{ path: "/auth/switch/org", element: <TenantSelectionPage /> },
|
||||
{
|
||||
element: <ProtectedRoute />,
|
||||
errorElement: <ErrorPage />,
|
||||
|
@ -3,54 +3,56 @@ import { createSlice } from "@reduxjs/toolkit";
|
||||
const localVariablesSlice = createSlice({
|
||||
name: "localVariables",
|
||||
initialState: {
|
||||
selectedMaster:"Application Role",
|
||||
regularizationCount:0,
|
||||
defaultDateRange: {
|
||||
selectedMaster: "Application Role",
|
||||
regularizationCount: 0,
|
||||
defaultDateRange: {
|
||||
startDate: null,
|
||||
endDate: null,
|
||||
},
|
||||
projectId: null,
|
||||
reload:false,
|
||||
reload: false,
|
||||
|
||||
OrganizationModal:{
|
||||
OrganizationModal: {
|
||||
isOpen: false,
|
||||
orgData: null,
|
||||
prevStep:null,
|
||||
prevStep: null,
|
||||
startStep: 1,
|
||||
flowType: "default",
|
||||
}
|
||||
|
||||
flowType: "default",
|
||||
},
|
||||
|
||||
AuthModal: {
|
||||
isOpen: false,
|
||||
},
|
||||
},
|
||||
reducers: {
|
||||
changeMaster: (state, action) => {
|
||||
state.selectedMaster = action.payload;
|
||||
state.selectedMaster = action.payload;
|
||||
},
|
||||
updateRegularizationCount: (state, action) => {
|
||||
state.regularizationCount = action.payload;
|
||||
},
|
||||
setProjectId: (state, action) => {
|
||||
localStorage.setItem("project",null)
|
||||
setProjectId: (state, action) => {
|
||||
localStorage.setItem("project", null);
|
||||
state.projectId = action.payload;
|
||||
localStorage.setItem("project",state.projectId || null)
|
||||
localStorage.setItem("project", state.projectId || null);
|
||||
},
|
||||
refreshData: ( state, action ) =>
|
||||
{
|
||||
state.reload = action.payload
|
||||
refreshData: (state, action) => {
|
||||
state.reload = action.payload;
|
||||
},
|
||||
setDefaultDateRange: (state, action) => {
|
||||
state.defaultDateRange = action.payload;
|
||||
},
|
||||
|
||||
openOrgModal: (state, action) => {
|
||||
state.OrganizationModal.isOpen = true;
|
||||
state.OrganizationModal.orgData = action.payload?.orgData || null;
|
||||
openOrgModal: (state, action) => {
|
||||
state.OrganizationModal.isOpen = true;
|
||||
state.OrganizationModal.orgData = action.payload?.orgData || null;
|
||||
|
||||
if (state.OrganizationModal.startStep) {
|
||||
state.OrganizationModal.prevStep = state.OrganizationModal.startStep;
|
||||
}
|
||||
if (state.OrganizationModal.startStep) {
|
||||
state.OrganizationModal.prevStep = state.OrganizationModal.startStep;
|
||||
}
|
||||
|
||||
state.OrganizationModal.startStep = action.payload?.startStep || 1;
|
||||
state.OrganizationModal.flowType = action.payload?.flowType || "default";
|
||||
state.OrganizationModal.startStep = action.payload?.startStep || 1;
|
||||
state.OrganizationModal.flowType = action.payload?.flowType || "default";
|
||||
},
|
||||
closeOrgModal: (state) => {
|
||||
state.OrganizationModal.isOpen = false;
|
||||
@ -61,8 +63,26 @@ state.OrganizationModal.isOpen = true;
|
||||
toggleOrgModal: (state) => {
|
||||
state.OrganizationModal.isOpen = !state.OrganizationModal.isOpen;
|
||||
},
|
||||
|
||||
openAuthModal: (state, action) => {
|
||||
state.AuthModal.isOpen = true;
|
||||
},
|
||||
closeAuthModal: (state, action) => {
|
||||
state.AuthModal.isOpen = false;
|
||||
},
|
||||
},
|
||||
});
|
||||
|
||||
export const { changeMaster ,updateRegularizationCount,setProjectId,refreshData,setDefaultDateRange,openOrgModal,closeOrgModal,toggleOrgModal} = localVariablesSlice.actions;
|
||||
export default localVariablesSlice.reducer;
|
||||
export const {
|
||||
changeMaster,
|
||||
updateRegularizationCount,
|
||||
setProjectId,
|
||||
refreshData,
|
||||
setDefaultDateRange,
|
||||
openOrgModal,
|
||||
closeOrgModal,
|
||||
toggleOrgModal,
|
||||
openAuthModal,
|
||||
closeAuthModal,
|
||||
} = localVariablesSlice.actions;
|
||||
export default localVariablesSlice.reducer;
|
Loading…
x
Reference in New Issue
Block a user