success fully created tenant with subscription

This commit is contained in:
pramod mahajan 2025-08-07 11:26:23 +05:30
parent fbc53e9ea1
commit 4ff01d0e60
9 changed files with 214 additions and 146 deletions

View File

@ -0,0 +1,17 @@
import React from 'react'
import { Link, useNavigate } from 'react-router-dom'
const Congratulation = () => {
const navigate = useNavigate()
return (
<div className="text-center p-4">
<h2>🎉 Congratulations!</h2>
<p>Your tenant is successfully onboarded.</p>
<div className="d-flex justify-content-center gap-3">
<p className='btn btn-sm btn-primary' onClick={()=>navigate('/tenants')}>Go To Tenant list</p> <p className='btn btn-sm btn-secondary' >Preview Tenant</p>
</div>
</div>
)
}
export default Congratulation

View File

@ -32,11 +32,11 @@ const ContactInfro = ({ onNext }) => {
<input
id="firstName"
type="text"
className={`form-control form-control-sm ${errors.firstName ? "is-invalid" : ""}`}
className={`form-control form-control-sm`}
{...register("firstName")}
/>
{errors.firstName && (
<div className="invalid-feedback">{errors.firstName.message}</div>
<div className="danger-text">{errors.firstName.message}</div>
)}
</div>
<div className="col-sm-6">
@ -46,11 +46,11 @@ const ContactInfro = ({ onNext }) => {
<input
id="lastName"
type="text"
className={`form-control form-control-sm ${errors.lastName ? "is-invalid" : ""}`}
className={`form-control form-control-sm `}
{...register("lastName")}
/>
{errors.lastName && (
<div className="invalid-feedback">{errors.lastName.message}</div>
<div className="danger-text">{errors.lastName.message}</div>
)}
</div>
<div className="col-sm-6">
@ -60,11 +60,11 @@ const ContactInfro = ({ onNext }) => {
<input
id="email"
type="email"
className={`form-control form-control-sm ${errors.email ? "is-invalid" : ""}`}
className={`form-control form-control-sm `}
{...register("email")}
/>
{errors.email && (
<div className="invalid-feedback">{errors.email.message}</div>
<div className="danger-text">{errors.email.message}</div>
)}
</div>
<div className="col-sm-6">
@ -74,11 +74,11 @@ const ContactInfro = ({ onNext }) => {
<input
id="contactNumber"
type="text"
className={`form-control form-control-sm ${errors.contactNumber ? "is-invalid" : ""}`}
className={`form-control form-control-sm `}
{...register("contactNumber")}
/>
{errors.contactNumber && (
<div className="invalid-feedback">{errors.contactNumber.message}</div>
<div className="danger-text">{errors.contactNumber.message}</div>
)}
</div>
<div className="col-12">
@ -87,12 +87,12 @@ const ContactInfro = ({ onNext }) => {
</Label>
<textarea
id="billingAddress"
className={`form-control ${errors.billingAddress ? "is-invalid" : ""}`}
className={`form-control `}
{...register("billingAddress")}
rows={3}
/>
{errors.billingAddress && (
<div className="invalid-feedback">{errors.billingAddress.message}</div>
<div className="danger-text">{errors.billingAddress.message}</div>
)}
</div>
<div className="d-flex justify-content-end mt-3">

View File

@ -70,13 +70,11 @@ const OrganizationInfo = ({ onNext, onPrev, onSubmitTenant }) => {
<input
id="organizationName"
className={`form-control form-control-sm ${
errors.organizationName ? "is-invalid" : ""
}`}
className={`form-control form-control-sm `}
{...register("organizationName")}
/>
{errors.organizationName && (
<div className="invalid-feedback">
<div className="danger-text">
{errors.organizationName.message}
</div>
)}
@ -88,13 +86,11 @@ const OrganizationInfo = ({ onNext, onPrev, onSubmitTenant }) => {
</Label>
<input
id="officeNumber"
className={`form-control form-control-sm ${
errors.officeNumber ? "is-invalid" : ""
}`}
className={`form-control form-control-sm `}
{...register("officeNumber")}
/>
{errors.officeNumber && (
<div className="invalid-feedback">{errors.officeNumber.message}</div>
<div className="danger-text">{errors.officeNumber.message}</div>
)}
</div>
@ -104,13 +100,11 @@ const OrganizationInfo = ({ onNext, onPrev, onSubmitTenant }) => {
</Label>
<input
id="domainName"
className={`form-control form-control-sm ${
errors.domainName ? "is-invalid" : ""
}`}
className={`form-control form-control-sm `}
{...register("domainName")}
/>
{errors.domainName && (
<div className="invalid-feedback">{errors.domainName.message}</div>
<div className="danger-text">{errors.domainName.message}</div>
)}
</div>
@ -120,13 +114,11 @@ const OrganizationInfo = ({ onNext, onPrev, onSubmitTenant }) => {
</Label>
<input
id="taxId"
className={`form-control form-control-sm ${
errors.taxId ? "is-invalid" : ""
}`}
className={`form-control form-control-sm `}
{...register("taxId")}
/>
{errors.taxId && (
<div className="invalid-feedback">{errors.taxId.message}</div>
<div className="danger-text">{errors.taxId.message}</div>
)}
</div>
@ -150,12 +142,10 @@ const OrganizationInfo = ({ onNext, onPrev, onSubmitTenant }) => {
type="date"
id="onBoardingDate"
{...register("onBoardingDate")}
className={`form-control form-control-sm ${
errors.onBoardingDate ? "is-invalid" : ""
}`}
className={`form-control form-control-sm `}
/>
{errors.onBoardingDate && (
<div className="invalid-feedback">
<div className="danger-text">
{errors.onBoardingDate.message}
</div>
)}
@ -185,7 +175,7 @@ const OrganizationInfo = ({ onNext, onPrev, onSubmitTenant }) => {
))}
</select>
{errors.organizationSize && (
<div className="invalid-feedback">
<div className="danger-text">
{errors.organizationSize.message}
</div>
)}
@ -210,7 +200,7 @@ const OrganizationInfo = ({ onNext, onPrev, onSubmitTenant }) => {
)}
</select>
{errors.industryId && (
<div className="invalid-feedback">{errors.industryId.message}</div>
<div className="danger-text">{errors.industryId.message}</div>
)}
</div>
@ -234,7 +224,7 @@ const OrganizationInfo = ({ onNext, onPrev, onSubmitTenant }) => {
))}
</select>
{errors.reference && (
<div className="invalid-feedback">{errors.reference.message}</div>
<div className="danger-text">{errors.reference.message}</div>
)}
</div>
@ -243,13 +233,11 @@ const OrganizationInfo = ({ onNext, onPrev, onSubmitTenant }) => {
<textarea
id="description"
rows={3}
className={`form-control form-control-sm ${
errors.description ? "is-invalid" : ""
}`}
className={`form-control form-control-sm `}
{...register("description")}
/>
{errors.description && (
<div className="invalid-feedback">{errors.description.message}</div>
<div className="danger-text">{errors.description.message}</div>
)}
</div>

View File

@ -5,11 +5,13 @@ import { useFormContext } from "react-hook-form";
import { CONSTANT_TEXT } from "../../utils/constants";
import Label from "../common/Label";
import { useSelector } from "react-redux";
import { useNavigate } from "react-router-dom";
const SubScription = ({ onSubmitSubScription }) => {
const SubScription = ({ onSubmitSubScription, onNext }) => {
const [frequency, setFrequency] = useState(2);
const [selectedPlanId, setSelectedPlanId] = useState(null);
const selectedTenant = useSelector((store)=>store.globalVariables.currentTenant)
const naviget = useNavigate()
const {
data: plans = [],
isError,
@ -24,7 +26,9 @@ const SubScription = ({ onSubmitSubScription }) => {
formState: { errors },
} = useFormContext();
const {mutate:AddSubScription,isPending,error} = useAddSubscription()
const {mutate:AddSubScription,isPending,error} = useAddSubscription(()=>{
onNext()
} )
const handleSubscriptionSubmit = async () => {
const isValid = await trigger([
"planId",

View File

@ -11,6 +11,7 @@ import {
subscriptionSchema,
tenantDefaultValues,
} from "./TenantSchema";
import Congratulation from "./congratulation";
const TenantForm = () => {
const [activeTab, setActiveTab] = useState(0);
@ -42,6 +43,10 @@ const TenantForm = () => {
setActiveTab((prev) => Math.min(prev + 1, newTenantConfig.length - 1));
}
};
const handlePrev = () => {
setActiveTab((prev) => Math.max(prev - 1, 0));
};
const onSubmitTenant = (data) => {
console.log(data);
@ -61,13 +66,19 @@ const TenantForm = () => {
name: "Organization",
icon: "bx bx-buildings bx-md",
subtitle: "Organization Details",
component: <OrganizationInfo onNext={handleNext} onSubmitTenant={onSubmitTenant} />,
component: <OrganizationInfo onNext={handleNext} onPrev={handlePrev} onSubmitTenant={onSubmitTenant} />,
},
{
name: "SubScription",
icon: "bx bx-star bx-md",
subtitle: "Select a plan",
component: <SubScription onSubmitSubScription={onSubmitSubScription} />,
component: <SubScription onSubmitSubScription={onSubmitSubScription} onNext={handleNext}/>,
},
{
name: "congratulation",
icon: "bx bx-star bx-md",
subtitle: "Select a plan",
component: <Congratulation />,
},
];
@ -75,7 +86,7 @@ const TenantForm = () => {
return (
<div id="wizard-property-listing" className="bs-stepper horizontically mt-2">
<div className="bs-stepper-header border-end text-start ">
{newTenantConfig.map((step, index) => {
{newTenantConfig.filter((step) => step.name.toLowerCase() !== "congratulation").map((step, index) => {
const isActive = activeTab === index;
const isCompleted = completedTabs.includes(index);
@ -90,7 +101,7 @@ const TenantForm = () => {
<button
type="button"
className={`step-trigger ${isActive ? "active" : ""}`}
onClick={() => setActiveTab(index)}
// onClick={() => setActiveTab(index)}
>
<span className="bs-stepper-circle">
{isCompleted ? (

View File

@ -4,7 +4,7 @@ import { ITEMS_PER_PAGE } from "../../utils/constants";
import { getTenantStatus } from "../../utils/dateUtils";
import IconButton from "../common/IconButton";
const TenantsList = () => {
const TenantsList = ({searchText}) => {
const [currentPage, setCurrentPage] = useState(1);
const { data, isLoading, isError, isInitialLoading, error } = useTenants(
ITEMS_PER_PAGE,
@ -20,7 +20,7 @@ const TenantsList = () => {
if (isInitialLoading)
return (
<div>
<h1>Loading...</h1>
<p>Loading...</p>
</div>
);
if (isError) return <div>{error.message}</div>;
@ -29,29 +29,40 @@ const TenantsList = () => {
{
key: "name",
label: "Organization",
getValue: (t) => (<div className="d-flex align-items-center py-1">
<IconButton iconClass="bx bx-sm bx-building" color="warning" size={8}/>{t.name || "N/A"}
</div>),
getValue: (t) => (
<div className="d-flex align-items-center py-1">
<IconButton
iconClass="bx bx-sm bx-building"
color="warning"
size={8}
/>
{t.name || "N/A"}
</div>
),
align: "text-start",
},
{
key: "domainName",
label: "Domain",
getValue: (t) =>(
<div style={{width:'160px'}} className="text-truncate">
getValue: (t) => (
<div style={{ width: "160px" }} className="text-truncate">
<a href={t.domainName} className="text-decoration-none">
<i className="bx bx-globe text-primary bx-xs me-2"></i>
{t.domainName || "N/A"}
</a>
</div>
</a>
</div>
),
align: "text-start",
},
{
key: "contactName",
label: "Contact Person",
getValue: (t) => (<div className="d-flex align-items-center"><i className="bx bx-sm bx-user me-1"/>{t.contactName || "N/A"}</div>),
getValue: (t) => (
<div className="d-flex align-items-center">
<i className="bx bx-sm bx-user me-1" />
{t.contactName || "N/A"}
</div>
),
align: "text-start",
},
{
@ -76,10 +87,8 @@ const TenantsList = () => {
},
];
return (
<>
<div className="card p-2 mt-3">
<div className="card-datatable text-nowrap table-responsive">
<table className="table border-top dataTable text-nowrap">
@ -90,7 +99,6 @@ const TenantsList = () => {
<div className={col.align}>{col.label}</div>
</th>
))}
</tr>
</thead>
<tbody>
@ -100,7 +108,9 @@ const TenantsList = () => {
{TenantColumns.map((col) => (
<td
key={col.key}
className={`d-table-cell px-3 py-2 align-middle ${col.align ?? ""}`}
className={`d-table-cell px-3 py-2 align-middle ${
col.align ?? ""
}`}
>
{col.customRender
? col.customRender(tenant)
@ -119,14 +129,12 @@ const TenantsList = () => {
</td>
</tr>
)}
</tbody>
</tbody>
</table>
</div>
</div>
</>
);
};
export default TenantsList
export default TenantsList;

View File

@ -1,4 +1,4 @@
import { useMutation, useQuery } from "@tanstack/react-query";
import { useMutation, useQuery, useQueryClient } from "@tanstack/react-query";
import { TenantRepository } from "../repositories/TenantRepository";
import { MarketRepository } from "../repositories/MarketRepository";
import showToast from "../services/toastService";
@ -14,7 +14,7 @@ export const useTenants = (
return useQuery({
queryKey: ["Tenants", pageNumber, pageSize],
queryFn: async () => {
const response = await TenantRepository.getTenanatList(
const response = await TenantRepository.getTenantList(
pageSize,
pageNumber,
);
@ -67,14 +67,19 @@ export const useCreateTenant = (onSuccessCallback)=>{
}
export const useAddSubscription =(onSuccessCallback)=>{
const queryClient = useQueryClient()
return useMutation({
mutationFn:async(subscriptionPayload)=>{
const res = await TenantRepository.createTenant(subscriptionPayload);
const res = await TenantRepository.addSubscription(subscriptionPayload);
return res.data;
},
onSuccess:(data,variables)=>{
showToast("Tenant Plan Added SuccessFully","success")
queryClient.invalidateQueries({queryKey:["Tenants"]});
if(onSuccessCallback) onSuccessCallback()
},
onError:(error)=>{
showToast(error.response.message || error.message || `Something went wrong`,"error")
}
})
}

View File

@ -1,20 +1,45 @@
import React from 'react'
import Breadcrumb from '../../components/common/Breadcrumb'
import TenantsList from '../../components/Tenanat/TenantsList'
import { useNavigate } from 'react-router-dom'
import React, { useState,createContext } from "react";
import Breadcrumb from "../../components/common/Breadcrumb";
import TenantsList from "../../components/Tenanat/TenantsList";
import { useNavigate } from "react-router-dom";
export const TenantContext = createContext();
export const useTenantContext = () => {
const context = useContext(TenantContext);
if (!context) {
throw new Error("useTenantContext must be used within an TenantProvider");
}
return context;
};
const TenantPage = () => {
const navigate = useNavigate()
const [searchText, setSearchText] = useState("");
const navigate = useNavigate();
const contextValue = {
};
return (
<div className='container-fluid'>
<TenantContext.Provider value={contextValue}>
<div className="container-fluid">
<Breadcrumb
data={[
{ label: "Home", link: "/dashboard" },
{ label: "Tenant", link: null },
]}
/>
<div className="card p-2">
<div className="text-end">
<div className="card d-flex p-3">
<div className="row align-items-center">
<div className="col-6 col-md-6 col-lg-3 mb-md-0">
<input
type="search"
className="form-control form-control-sm"
placeholder="Search..."
/>
</div>
<div className="col-6 col-md-6 col-lg-9 text-end">
<button
type="button"
data-bs-toggle="tooltip"
@ -22,17 +47,19 @@ const TenantPage = () => {
data-bs-placement="top"
data-bs-custom-class="tooltip"
title="Add New Tenant"
className={`p-1 me-2 bg-primary rounded-circle`} onClick={()=>navigate('/tenants/new-tenant')}
className="p-1 bg-primary rounded-circle"
onClick={() => navigate("/tenants/new-tenant")}
>
<i className="bx bx-plus fs-4 text-white"></i>
</button>
</div>
</div>
<TenantsList />
</div>
)
}
export default TenantPage
<TenantsList searchText ={searchText} />
</div>
</TenantContext.Provider>
);
};
export default TenantPage;

View File

@ -1,16 +1,24 @@
import { api } from "../utils/axiosClient";
export const TenantRepository = {
getTenantList: (pageSize, pageNumber, filter, searchString) => {
const params = new URLSearchParams();
getTenanatList:(pageSize, pageNumber, filter,searchString)=>{
const payloadJsonString = JSON.stringify(filter);
return api.get(`/api/Tenant/list?pageSize=${pageSize}&pageNumber=${pageNumber}`)
},
if (pageSize) params.append("pageSize", pageSize);
if (pageNumber) params.append("pageNumber", pageNumber);
if (filter && Object.keys(filter).length > 0) {
params.append("filter", JSON.stringify(filter));
}
if (searchString) params.append("searchString", searchString);
getSubscriptionPlan:(freq)=>api.get(`/api/Tenant/list/subscription-plan?frequency=${freq}`),
return api.get(`/api/Tenant/list?${params.toString()}`);
},
createTenant:(data)=>api.post('/api/Tenant/create',data),
addSubscription:(data)=>api.post('/api/Tenant/create',data)
}
getSubscriptionPlan: (freq) =>
api.get(`/api/Tenant/list/subscription-plan?frequency=${freq}`),
createTenant: (data) => api.post("/api/Tenant/create", data),
addSubscription: (data) => api.post("/api/Tenant/add-subscription", data),
};