From 0acd73371045bdbaee8c82799cddab24a983a5c6 Mon Sep 17 00:00:00 2001 From: pramod mahajan Date: Thu, 14 Aug 2025 17:45:01 +0530 Subject: [PATCH] handle upgrad plan --- src/components/Tenanat/Profile.jsx | 46 +++--- src/components/Tenanat/SubScription.jsx | 4 +- .../Tenanat/SubScriptionHistory.jsx | 111 ++++++++++++++ src/components/Tenanat/TenantForm.jsx | 135 ++++++++++++------ src/components/Tenanat/TenantSchema.js | 14 +- src/hooks/useTenant.js | 8 +- src/pages/Tenant/TenantDetails.jsx | 5 +- src/pages/Tenant/TenantPage.jsx | 14 +- src/utils/constants.jsx | 8 ++ 9 files changed, 260 insertions(+), 85 deletions(-) create mode 100644 src/components/Tenanat/SubScriptionHistory.jsx diff --git a/src/components/Tenanat/Profile.jsx b/src/components/Tenanat/Profile.jsx index 1a96d308..2c37812c 100644 --- a/src/components/Tenanat/Profile.jsx +++ b/src/components/Tenanat/Profile.jsx @@ -12,7 +12,7 @@ const Profile = ({ data }) => {
-
+
{ className="position-absolute top-0 end-0 cursor-auto" onClick={() => setEditTenant(true)} > - +
-
-
Profile
-
+ {data?.description && (

{data?.description}

)} + +
+
+
Personal
+
@@ -64,56 +67,55 @@ const Profile = ({ data }) => { {data.contactNumber}
-
+
Address: {data.billingAddress}
-
-
+
Organization
- + Industry: - {data.industry.name} + {data?.industry?.name}
- {data.taxId && ( + {data?.taxId && (
Tax Id: - {data.taxId} + {data?.taxId}
)}
Organization Size: - {data.organizationSize} + {data?.organizationSize}
On-Boarding Date: - {formatUTCToLocalTime(data.onBoardingDate)} + {formatUTCToLocalTime(data?.onBoardingDate)}
- - - -
+ Status + Active + In-Progress + On Hold @@ -150,13 +152,13 @@ const Profile = ({ data }) => {
Activite Employees: - {data.activeEmployees} + {data?.activeEmployees}
In-Active Employee: - {data.inActiveEmployees} + {data?.inActiveEmployees}
@@ -165,6 +167,4 @@ const Profile = ({ data }) => { }; export default Profile; -// -// {data?.tenantStatus?.name} -// + diff --git a/src/components/Tenanat/SubScription.jsx b/src/components/Tenanat/SubScription.jsx index 9f88acf7..0fae1adb 100644 --- a/src/components/Tenanat/SubScription.jsx +++ b/src/components/Tenanat/SubScription.jsx @@ -11,7 +11,7 @@ const SubScription = ({ onSubmitSubScription, onNext }) => { const [frequency, setFrequency] = useState(2); const [selectedPlanId, setSelectedPlanId] = useState(null); const selectedTenant = useSelector( - (store) => store.globalVariables.currentTenant + (store) => store.globalVariables.currentTenant?.data ); const naviget = useNavigate(); const { @@ -33,7 +33,7 @@ const SubScription = ({ onSubmitSubScription, onNext }) => { isPending, error, } = useAddSubscription(() => { - onNext(); + naviget("/tenants") }); const handleSubscriptionSubmit = async () => { const isValid = await trigger([ diff --git a/src/components/Tenanat/SubScriptionHistory.jsx b/src/components/Tenanat/SubScriptionHistory.jsx new file mode 100644 index 00000000..850a8401 --- /dev/null +++ b/src/components/Tenanat/SubScriptionHistory.jsx @@ -0,0 +1,111 @@ +import React, { useEffect } from "react"; +import { useTenantDetails } from "../../hooks/useTenant"; +import { useDispatch } from "react-redux"; +import { setCurrentTenant } from "../../slices/globalVariablesSlice"; +import { useNavigate } from "react-router-dom"; + +const SubScriptionHistory = ({ tenantId }) => { + const { data, isLoading, isError, error } = useTenantDetails(tenantId); + const dispatch = useDispatch(); + const navigate = useNavigate(); + + useEffect(() => { + if (data) { + // Tenant exists → set operationMode: 1 + dispatch(setCurrentTenant({ operationMode: 1, data })); + } else { + // No tenant yet → set operationMode: 0 + dispatch(setCurrentTenant({ operationMode: 0, data: null })); + } + }, [data, dispatch]); + + const handleUpgradePlan = () => { + navigate("/tenants/new-tenant"); + }; + + if (isLoading) return
Loading...
; + if (isError) return
{error}
; + + const plan = data?.currentPlan; + + // No subscription plan yet → show "Add Subscription" button + if (!plan) { + return ( +
+ +
+ Add your new subscription +
+
+ ); + } + + // Subscription plan exists → show details + const today = new Date(); + const start = new Date(plan.startDate); + const end = new Date(plan.endDate); + + const totalDays = Math.ceil((end - start) / (1000 * 60 * 60 * 24)); + const daysLeft = Math.max(0, Math.ceil((end - today) / (1000 * 60 * 60 * 24))); + const percentage = Math.min( + 100, + Math.round(((totalDays - daysLeft) / totalDays) * 100) + ); + + const getProgressVariant = () => { + if (percentage < 50) return "success"; + if (percentage < 80) return "warning"; + return "danger"; + }; + + return ( +
+ +
+

Active Subscription

+ +
+
+

{plan.planName || "N/A"}

+ +
+
+

+ {plan.currency?.symbol} {plan.price} +

+ {plan.description} +
+
+ +
+
+ +
+ {/* Progress bar placeholder */} +
+ {totalDays - daysLeft} days used + {daysLeft} days left +
+
+ +
+ Ends on {end.toLocaleDateString()} +
+
+
+
+ {/* Table */} +
+
+
+ ); +}; + +export default SubScriptionHistory; diff --git a/src/components/Tenanat/TenantForm.jsx b/src/components/Tenanat/TenantForm.jsx index 616effe1..a491b6ad 100644 --- a/src/components/Tenanat/TenantForm.jsx +++ b/src/components/Tenanat/TenantForm.jsx @@ -1,4 +1,4 @@ -import React, { useState } from "react"; +import React, { useState, useEffect } from "react"; import ContactInfro from "./ContactInfro"; import SubScription from "./SubScription"; import OrganizationInfo from "./OrganizationInfo"; @@ -12,12 +12,33 @@ import { tenantDefaultValues, } from "./TenantSchema"; import Congratulation from "./congratulation"; +import { useSelector } from "react-redux"; const TenantForm = () => { + const HasSelectedCurrentTenant = useSelector( + (store) => store.globalVariables.currentTenant + ); const [activeTab, setActiveTab] = useState(0); const [completedTabs, setCompletedTabs] = useState([]); + const PlanTextLabel = HasSelectedCurrentTenant?.operationMode === 1 ? "Upgrade Plan":"Select Plan" + // Jump to subscription if tenant already exists +useEffect(() => { + if (HasSelectedCurrentTenant) { + if (HasSelectedCurrentTenant.operationMode === 1) { + // Skip to subscription step + setActiveTab(2); // index for "SubScription" + setCompletedTabs([0, 1]); // mark previous steps as completed + } else if (HasSelectedCurrentTenant.operationMode === 0) { + // Start from the beginning (new tenant creation) + setActiveTab(0); + } + } else { + // Default: no tenant selected + setActiveTab(0); + } +}, [HasSelectedCurrentTenant,activeTab]); const tenantForm = useForm({ resolver: zodResolver(newTenantSchema), @@ -35,24 +56,30 @@ const TenantForm = () => { const handleNext = async () => { const currentStepFields = getStepFields(activeTab); const trigger = getCurrentTrigger(); - const valid = await trigger(currentStepFields); if (valid) { setCompletedTabs((prev) => [...new Set([...prev, activeTab])]); - setActiveTab((prev) => Math.min(prev + 1, newTenantConfig.length - 1)); + + setActiveTab((prev) => { + let nextStep = Math.min(prev + 1, newTenantConfig.length - 1); + if (HasSelectedCurrentTenant && nextStep === 2) { + nextStep = 3; // skip subscription if already upgraded + } + return nextStep; + }); } }; - const handlePrev = () => { - setActiveTab((prev) => Math.max(prev - 1, 0)); -}; + const handlePrev = () => { + setActiveTab((prev) => Math.max(prev - 1, 0)); + }; const onSubmitTenant = (data) => { - console.log(data); + // console.log("Tenant Data:", data); }; const onSubmitSubScription = (data) => { - console.log(data); + // console.log("Subscription Data:", data); }; const newTenantConfig = [ @@ -66,15 +93,26 @@ const TenantForm = () => { name: "Organization", icon: "bx bx-buildings bx-md", subtitle: "Organization Details", - component: , + component: ( + + ), }, { name: "SubScription", icon: "bx bx-star bx-md", - subtitle: "Select a plan", - component: , + subtitle: PlanTextLabel, + component: ( + + ), }, - { + { name: "congratulation", icon: "bx bx-star bx-md", subtitle: "Select a plan", @@ -83,45 +121,50 @@ const TenantForm = () => { ]; const isSubscriptionTab = activeTab === 2; + return (
- {newTenantConfig.filter((step) => step.name.toLowerCase() !== "congratulation").map((step, index) => { - const isActive = activeTab === index; - const isCompleted = completedTabs.includes(index); + {newTenantConfig + .filter((step) => step.name.toLowerCase() !== "congratulation") + .map((step, index) => { + const isActive = activeTab === index; + const isCompleted = completedTabs.includes(index); - return ( - -
- -
- {index < newTenantConfig.length - 1 && ( -
- )} -
- ); - })} + +
+ {index < newTenantConfig.length - 1 && ( +
+ )} + + ); + })}
diff --git a/src/components/Tenanat/TenantSchema.js b/src/components/Tenanat/TenantSchema.js index 9303b01e..305d9d94 100644 --- a/src/components/Tenanat/TenantSchema.js +++ b/src/components/Tenanat/TenantSchema.js @@ -12,10 +12,16 @@ export const newTenantSchema = z.object({ organizationName: z.string().nonempty("Organization name is required"), officeNumber: z.string().nonempty("Office number is required"), contactNumber: z.string().nonempty("Contact number is required"), - onBoardingDate: z.coerce.date({ - required_error: "Onboarding date is required", - invalid_type_error: "Invalid date format", - }), + onBoardingDate: z.preprocess((val) => { + if (typeof val === "string" && val.includes("-")) { + const [day, month, year] = val.split("-"); + return new Date(`${year}-${month}-${day}`); + } + return val; +}, z.date({ + required_error: "Onboarding date is required", + invalid_type_error: "Invalid date format", +})), organizationSize: z.string().nonempty("Organization size is required"), industryId: z.string().uuid("Invalid industry ID"), reference: z.string().nonempty("Reference is required"), diff --git a/src/hooks/useTenant.js b/src/hooks/useTenant.js index cce4064f..d2903740 100644 --- a/src/hooks/useTenant.js +++ b/src/hooks/useTenant.js @@ -90,11 +90,11 @@ export const useCreateTenant = (onSuccessCallback)=>{ }, onSuccess:(data,variables)=>{ showToast("Tenant Created SuccessFully","success") - dispatch(setCurrentTenant(data)) + dispatch(setCurrentTenant({operationMode:0,data:data})) if(onSuccessCallback) onSuccessCallback() }, onError:(error)=>{ - showToast(error.response.message || error.message || `Something went wrong`,"error") + showToast(error.response.message || error?.response?.data?.errors || `Something went wrong`,"error") } }) @@ -128,8 +128,8 @@ export const useAddSubscription =(onSuccessCallback)=>{ queryClient.invalidateQueries({queryKey:["Tenants"]}); if(onSuccessCallback) onSuccessCallback() }, - onError:(error)=>{ + onError:(error)=>{ showToast(error.response.message || error.message || `Something went wrong`,"error") - } + } }) } \ No newline at end of file diff --git a/src/pages/Tenant/TenantDetails.jsx b/src/pages/Tenant/TenantDetails.jsx index 997143c2..0e2b2ea3 100644 --- a/src/pages/Tenant/TenantDetails.jsx +++ b/src/pages/Tenant/TenantDetails.jsx @@ -7,6 +7,7 @@ import Organization from "../../components/Tenanat/Organization"; import { ComingSoonPage } from "../Misc/ComingSoonPage"; import GlobalModel from "../../components/common/GlobalModel"; import EditProfile from "../../components/Tenanat/EditProfile"; +import SubScriptionHistory from "../../components/Tenanat/SubScriptionHistory"; const TenantDetailsContext = createContext(); export const useTenantDetailsContext = () => useContext(TenantDetailsContext); @@ -32,12 +33,12 @@ const TenantDetails = () => { { id: "navs-left-bill", - label: "Bill", + label: "Bills and Plan ", icon: "bx bx-receipt", iconSize: "bx-sm", content: (
- +
), }, diff --git a/src/pages/Tenant/TenantPage.jsx b/src/pages/Tenant/TenantPage.jsx index 3a5cbc12..157cae46 100644 --- a/src/pages/Tenant/TenantPage.jsx +++ b/src/pages/Tenant/TenantPage.jsx @@ -14,6 +14,8 @@ import { filterSchema, } from "../../components/Tenanat/TenantSchema"; import TenantFilterPanel from "../../components/Tenanat/TenantFilterPanel"; +import { useDispatch } from "react-redux"; +import { setCurrentTenant } from "../../slices/globalVariablesSlice"; // This is context that wrapping all components tenant releated , but must pass inside 'TenantContext.Provider' export const TenantContext = createContext(); @@ -29,7 +31,8 @@ const TenantPage = () => { const [searchText, setSearchText] = useState(""); const [isRefetching,setRefetching] = useState(false) const [refetchFn, setRefetchFn] = useState(null); - const [filters, setFilter] = useState(); + const [filters, setFilter] = useState(); + const dispatch = useDispatch() const debouncedSearch = useDebounce(searchText, 500); const contextValue = { }; @@ -54,7 +57,10 @@ const TenantPage = () => { setOffcanvasContent("", null); }; }, []); - +const handleNewTenant =()=>{ + dispatch(setCurrentTenant(null)) + navigate("/tenants/new-tenant") +} return (
@@ -89,8 +95,8 @@ const TenantPage = () => { data-bs-placement="top" data-bs-custom-class="tooltip" title="Add New Tenant" - className="p-1 bg-primary rounded-circle" - onClick={() => navigate("/tenants/new-tenant")} + className="p-1 bg-primary rounded-circle cursror-pointer" + onClick={handleNewTenant} > diff --git a/src/utils/constants.jsx b/src/utils/constants.jsx index bad030a3..df32b9d1 100644 --- a/src/utils/constants.jsx +++ b/src/utils/constants.jsx @@ -76,6 +76,13 @@ export const TENANT_STATUS = [ export const CONSTANT_TEXT = { } + +export const SUBSCRIPTION_PLAN_FREQUENCIES = { + 0: "Monthly", + 1:"Quatery", + 2:"Half-Yearly", + 3:"Yearly" +} export const reference = [ { val: "google", name: "Google" }, { val: "frineds", name: "Friends" }, @@ -90,4 +97,5 @@ export const orgSize = [ ]; export const BASE_URL = process.env.VITE_BASE_URL; + // export const BASE_URL = "https://api.marcoaiot.com";