diff --git a/public/assets/css/core-extend.css b/public/assets/css/core-extend.css index bd512d58..d6967023 100644 --- a/public/assets/css/core-extend.css +++ b/public/assets/css/core-extend.css @@ -71,21 +71,18 @@ transition: all 0.3s ease; } -/* ✅ Success */ .timeline-point.completed { background-color: var(--bs-success); color: #fff; box-shadow: 0 0 5px rgba(25, 135, 84, 0.5); } -/* ❌ Failed */ .timeline-point.failed { background-color: var(--bs-danger); color: #fff; box-shadow: 0 0 5px rgba(220, 53, 69, 0.5); } -/* ⏳ Pending (Active) */ .timeline-point.active { background-color: var(--bs-info); color: #fff; @@ -93,7 +90,6 @@ box-shadow: 0 0 6px rgba(13, 202, 240, 0.5); } -/* Connecting line */ .timeline-line-horizontal { content: ""; position: absolute; diff --git a/public/img/illustrations/undraw_pricing.png b/public/img/illustrations/undraw_pricing.png new file mode 100644 index 00000000..151bb16a Binary files /dev/null and b/public/img/illustrations/undraw_pricing.png differ diff --git a/src/components/ThemeColorChanger.jsx b/src/components/ThemeColorChanger.jsx new file mode 100644 index 00000000..fdf4b8fb --- /dev/null +++ b/src/components/ThemeColorChanger.jsx @@ -0,0 +1,39 @@ + +import React from "react"; + +const ThemeColorChanger = () => { + const changePrimaryColor = (color) => { + document.documentElement.style.setProperty("--primary-color", color); + }; + + return ( + <> +
+ Choose Primary Color: + + + + + +
+
+

Primary Color Demo

+ +
+ + ); +}; + +export default ThemeColorChanger; diff --git a/src/components/UserSubscription/ProcessedPayment.jsx b/src/components/UserSubscription/ProcessedPayment.jsx index 9d018e52..a27a044c 100644 --- a/src/components/UserSubscription/ProcessedPayment.jsx +++ b/src/components/UserSubscription/ProcessedPayment.jsx @@ -10,98 +10,90 @@ import { import { formatUTCToLocalTime } from "../../utils/dateUtils"; import { PaymentRepository } from "../../repositories/PaymentRepository"; import { useMakePayment } from "../../hooks/usePayment"; -import { useDispatch } from "react-redux"; +import { useDispatch, useSelector } from "react-redux"; import { setSelfTenant } from "../../slices/localVariablesSlice"; import { unblockUI } from "../../utils/blockUI"; +import showToast from "../../services/toastService"; -const ProcessedPayment = ({ onNext, resetPaymentStep }) => { +const ProcessedPayment = ({ + onNext, + resetPaymentStep, + setCurrentStep, + setStepStatus, + resetFormStep, +}) => { const { frequency, planName } = useParams(); - const dispatch = useDispatch(); - const [selectedPlan, setSelectedPlan] = useState(planName); + + const { details: client, planId: selectedPlanId } = useSelector( + (store) => store.localVariables.selfTenant + ); + const [selectedPlan, setSelectedPlan] = useState(null); const [currentPlan, setCurrentPlan] = useState(null); const [failPayment, setFailPayment] = useState(null); + const { data: plans, isError: isPlanError, isLoading, } = useSubscription(frequency); - const handleChange = (e) => { - setSelectedPlan(e.target.value); - }; - const visiblePlans = useMemo(() => { - if (!plans) return []; - const planOrder = ["basic", "pro", "super"]; - const currentIndex = planOrder.indexOf(planName?.toLowerCase()); + useEffect(() => { + if (!plans || !selectedPlanId) return; + const selected = plans.find((p) => p.id === selectedPlanId); + setSelectedPlan(selected); + }, [plans, selectedPlanId]); - if (currentIndex === -1) return plans; - const visibleNames = planOrder.slice(currentIndex); - const selected = plans?.find((p) => p.planName === selectedPlan); - debugger; - dispatch( - setSelfTenant({ - planId: selected?.id, - }) - ); - - setCurrentPlan(selected); - - return plans.filter((p) => - visibleNames.includes(p.planName?.toLowerCase()) - ); - }, [plans, selectedPlan]); - - const loadScript = (src) => { - return new Promise((resolve) => { + const loadScript = (src) => + new Promise((resolve) => { const script = document.createElement("script"); script.src = src; script.onload = () => resolve(true); script.onerror = () => resolve(false); document.body.appendChild(script); }); - }; const { mutate: MakePayment, isPending } = useMakePayment( (response) => { - unblockUI() + debugger + unblockUI(); onNext(response); }, (fail) => { - unblockUI() + debu + unblockUI(); setFailPayment(fail); onNext(fail); }, currentPlan ); - const ProcessToPayment = async (payload) => { + + const ProcessToPayment = async () => { + setStepStatus((prev) => ({ ...prev, 3: "success" })); + setCurrentStep(4); const res = await loadScript( "https://checkout.razorpay.com/v1/checkout.js" ); - + if (!res) { + alert("Failed to load Razorpay SDK"); + return; + } MakePayment({ amount: 1 }); }; - const clients = [ - { - firstName: "Alice", - lastName: "Smith", - email: "alice.smith@example.com", - billingAddress: - "456 Elm Street, Metropolis, USA 456 Elm Street, Metropolis, USA 456 Elm Street, Metropolis, USA", - organizationName: "BetaTech Ltd.", - contactNumber: "+44 20 7946 0958", - onBoardingDate: "2025-09-15", - organizationSize: "10-50", - industryId: "c4d5e6f7-8901-2345-6789-abcdef123456", - reference: "d2e3f4a5-6789-0123-4567-89abcdef0123", - }, - ]; - const handleRetry = () => { setFailPayment(null); - resetPaymentStep(); + if (typeof resetPaymentStep === "function") resetPaymentStep(); }; + // useEffect(() => { + // if (!client || Object.keys(client).length === 0) { + // setFailPayment(null); + // if (typeof resetFormStep === "function") { + // resetFormStep(); + // } + // } + // }, [client]); + if (failPayment) { return (
@@ -146,78 +138,43 @@ const ProcessedPayment = ({ onNext, resetPaymentStep }) => { ); } return ( -
-
- Make sure your selected details, and process to payment -
+
-

- Choose the Perfect Plan for Your Organization -

+

You’ve Selected the Perfect Plan for Your Organization

- Select a plan that fits your team’s needs and unlock the - features that drive productivity. + Great choice! This plan is tailored to meet your team’s needs + and help you maximize productivity.

- {visiblePlans?.map((plan) => { - let colSize = "8"; - - if (visiblePlans.length === 2) colSize = "6"; - else if (visiblePlans.length === 3) colSize = "4"; - else if (visiblePlans.length >= 4) colSize = "3"; - - return ( -
-
- + {selectedPlan && ( +
+
+
+ {selectedPlan?.planName} +
+ +
+ + {selectedPlan?.currency?.symbol} {selectedPlan?.price} /{" "} + {frequencyLabel(frequency)} + +
+ + + {selectedPlan?.description} +
- ); - })} +
+ )} + {selectedPlan && (
{(() => { - const selected = plans?.find( - (p) => p.planName === selectedPlan - ); - if (!selected) return null; - const { planName, description, @@ -228,7 +185,7 @@ const ProcessedPayment = ({ onNext, resetPaymentStep }) => { maxStorage, currency, features, - } = selected; + } = selectedPlan; return ( <>
@@ -262,7 +219,7 @@ const ProcessedPayment = ({ onNext, resetPaymentStep }) => { .map(([key, mod]) => (
{ ))}
-
+
Support
    @@ -299,22 +256,21 @@ const ProcessedPayment = ({ onNext, resetPaymentStep }) => { )}
- -
-
+
+
Duration
- {frequencyLabel(frequency, true)} + {frequencyLabel(selectedPlan?.frequency, true)}
-
+
Total Price
- {formatFigure(price, { + {formatFigure(selectedPlan?.price, { type: "currency", - currency: currency.currencyCode, + currency: selectedPlan?.currency.currencyCode, })}
@@ -327,19 +283,17 @@ const ProcessedPayment = ({ onNext, resetPaymentStep }) => { )}
-
- {clients.map((client, idx) => ( -
+
+ + {client && ( +
+
Confirm your organization details.
- First Name: + Name:
-
{client.firstName}
- -
- Last Name: -
-
{client.lastName}
+
{client.firstName} {client.lastName}
+
Email: @@ -356,10 +310,7 @@ const ProcessedPayment = ({ onNext, resetPaymentStep }) => {
{client.organizationName}
-
- Organization Size: -
-
{client.organizationSize}
+
Onboarding Date: @@ -374,17 +325,12 @@ const ProcessedPayment = ({ onNext, resetPaymentStep }) => {
{client.billingAddress}
- Industry ID: + Industry :
-
{client.industryId}
- - {/*
- Reference: -
-
{client.reference}
*/} +
{client?.industry?.name}
- ))} + )}
diff --git a/src/components/UserSubscription/Review.jsx b/src/components/UserSubscription/Review.jsx new file mode 100644 index 00000000..a7c6ccd4 --- /dev/null +++ b/src/components/UserSubscription/Review.jsx @@ -0,0 +1,11 @@ +import React from 'react' + +const Review = () => { + return ( +
+ +
+ ) +} + +export default Review diff --git a/src/components/UserSubscription/SelectPlan.jsx b/src/components/UserSubscription/SelectPlan.jsx new file mode 100644 index 00000000..ace3d2cf --- /dev/null +++ b/src/components/UserSubscription/SelectPlan.jsx @@ -0,0 +1,238 @@ +import React, { useEffect, useState } from "react"; +import { useDispatch, useSelector } from "react-redux"; +import { useParams } from "react-router-dom"; +import { useSubscription } from "../../hooks/useAuth"; +import { formatFigure, frequencyLabel } from "../../utils/appUtils"; +import { setSelfTenant } from "../../slices/localVariablesSlice"; + +const SelectPlan = ({ currentStep, setStepStatus, onNext }) => { + const { frequency, planName } = useParams(); + const dispatch = useDispatch(); + + const client = useSelector( + (store) => store.localVariables.selfTenant.details + ); + const [selectedPlan, setSelectedPlan] = useState(planName); + const [currentPlan, setCurrentPlan] = useState(null); + const [failPayment, setFailPayment] = useState(null); + + const { + data: plans, + isError: isPlanError, + isLoading, + } = useSubscription(frequency); + + const handleChange = (e) => setSelectedPlan(e.target.value); + + useEffect(() => { + if (!plans || !selectedPlan) return; + const selected = plans.find((p) => p.planName === selectedPlan); + if (selected) { + setCurrentPlan(selected); + dispatch(setSelfTenant({ planId: selected.id })); + } + }, [plans, selectedPlan, dispatch]); + + const handleNextStep = () => { + setStepStatus((prev) => ({ ...prev, 2: "success"})); + onNext(); + }; + + return ( +
+
+
+
+
+

Choose the Perfect Plan for Your Organization

+

+ Select a plan that fits your team’s needs and unlock the + features that drive productivity. +

+
+ + {plans?.map((plan) => ( +
+
+ +
+
+ ))} + + {selectedPlan && ( +
+
+ {(() => { + const selected = plans?.find( + (p) => p.planName === selectedPlan + ); + if (!selected) return null; + + const { + price, + frequency, + trialDays, + maxUser, + maxStorage, + currency, + features, + } = selected; + + return ( + <> +
+
+
+ + Max Users: {maxUser} +
+
+
+
+ + Max Storage: {maxStorage} MB +
+
+
+
+ + Trial Days: {trialDays} +
+
+
+ +
+ Included Features +
+
+ {features && + Object.entries(features?.modules || {}) + .filter(([key]) => key !== "id") + .map(([key, mod]) => ( +
+ + {mod.name} +
+ ))} +
+ +
+ Support +
+
    + {features?.supports?.emailSupport && ( +
  • + + Email Support +
  • + )} + {features?.supports?.phoneSupport && ( +
  • + + Phone Support +
  • + )} + {features?.supports?.prioritySupport && ( +
  • + + Priority Support +
  • + )} +
+ +
+
+
+
Duration
+
+ {frequencyLabel(frequency, true)} +
+
+ +
+
Total Price
+
+ {formatFigure(price, { + type: "currency", + currency: currency.currencyCode, + })} +
+
+
+ + ); + })()} +
+
+ )} +
+
+ + {/* Image Section */} +
+ image +
+
+ +
+ +
+
+ + ); +}; + +export default SelectPlan; diff --git a/src/components/UserSubscription/SubscriptionForm.jsx b/src/components/UserSubscription/SubscriptionForm.jsx index b033fb65..73353f1e 100644 --- a/src/components/UserSubscription/SubscriptionForm.jsx +++ b/src/components/UserSubscription/SubscriptionForm.jsx @@ -12,9 +12,7 @@ import { useCreateTenant, useIndustries } from "../../hooks/useTenant"; import { useCreateSelfTenant } from "../../hooks/useAuth"; import { blockUI } from "../../utils/blockUI"; -const SubscriptionForm = ({currentStep, - setCurrentStep, - setStepStatus }) => { +const SubscriptionForm = ({ currentStep, setCurrentStep, setStepStatus }) => { const { data, isError, isLoading: industryLoading } = useIndustries(); const { register, @@ -42,9 +40,9 @@ const SubscriptionForm = ({currentStep, // reset(); }; return ( -
-
-
+
+
+
@@ -233,13 +231,13 @@ const SubscriptionForm = ({currentStep,
-
+
-
- {/*
- -
-
- First slide -
-

First slide

-

- Eos mutat malis maluisset et, agam ancillae quo te, in vim - congue pertinacia. -

-
-
-
- Second slide -
-

Second slide

-

In numquam omittam sea.

-
-
-
- Third slide -
-

Third slide

-

- Lorem ipsum dolor sit amet, virtute consequat ea qui, minim - graeco mel no. -

-
-
-
- - - Previous - - - - Next - -
*/} -
-

- Provide organization information including name, size, industry, - and contact details. -

-
-
); diff --git a/src/components/UserSubscription/SubscriptionLayout.jsx b/src/components/UserSubscription/SubscriptionLayout.jsx index 34db25b2..9a7523c3 100644 --- a/src/components/UserSubscription/SubscriptionLayout.jsx +++ b/src/components/UserSubscription/SubscriptionLayout.jsx @@ -7,7 +7,7 @@ const SubscriptionLayout = ({ stepStatus = {}, }) => { return ( -
+
    {configStep.map((step, index) => { const stepNumber = index + 1; @@ -34,8 +34,10 @@ const SubscriptionLayout = ({ )}
-
+ {/* ${ status === "success" ? "text-success" : status === "failed" @@ -43,10 +45,9 @@ const SubscriptionLayout = ({ : stepNumber === currentStep ? "text-primary" : "text-muted" - }`} - > + } */} {step.name} -
+ {index !== configStep.length - 1 && (
diff --git a/src/hooks/useAuth.jsx b/src/hooks/useAuth.jsx index ad59f964..e0bf44a3 100644 --- a/src/hooks/useAuth.jsx +++ b/src/hooks/useAuth.jsx @@ -89,10 +89,12 @@ export const useCreateSelfTenant = (onSuccessCallBack, onFailureCallBack) => { dispatch( setSelfTenant({ - tenantEnquireId: response?.tenantEnquireId, + tenantEnquireId: response?.id, planId: null, + details:response }) ); + debugger if (onSuccessCallBack) onSuccessCallBack(response); }, onError: (error) => { diff --git a/src/pages/Home/MakeSubscription.jsx b/src/pages/Home/MakeSubscription.jsx index 3f9449c7..609dbd0b 100644 --- a/src/pages/Home/MakeSubscription.jsx +++ b/src/pages/Home/MakeSubscription.jsx @@ -4,26 +4,37 @@ import SubscriptionLayout from "../../components/UserSubscription/SubscriptionLa import SubscriptionForm from "../../components/UserSubscription/SubscriptionForm"; import ProcessedPayment from "../../components/UserSubscription/ProcessedPayment"; import VerifiedPayment from "../../components/UserSubscription/VerifiedPayment"; +import SelectPlan from "../../components/UserSubscription/SelectPlan"; +import Review from "../../components/UserSubscription/Review"; const MakeSubscription = () => { - const [currentStep, setCurrentStep] = useState(1); + const [currentStep, setCurrentStep] = useState(4); const [responsePayment, setResponsePayment] = useState(null); const [stepStatus, setStepStatus] = useState({ 1: "pending", 2: "pending", 3: "pending", + 4: "pending", + 5: "pending", }); const handleVerification = (resp) => { setResponsePayment(resp); if (resp?.success) { - setStepStatus((prev) => ({ ...prev, 2: "success", 3: "success" })); + setStepStatus((prev) => ({ ...prev, 4: "success" })); setCurrentStep(3); } else { - setStepStatus((prev) => ({ ...prev, 2: "failed" })); + setStepStatus((prev) => ({ ...prev, 4: "failed" })); } }; +const handleNext = () => { + setStepStatus((prev) => ({ + ...prev, + [currentStep + 1]: "pending", + })); + setCurrentStep((prev) => prev + 1); +}; const checkOut_Steps = [ { @@ -37,14 +48,48 @@ const MakeSubscription = () => { ), }, { - name: "Payment", + name: "Select Plan", + component: () => ( + + ), + }, + { + name: "Review", component: () => ( handleVerification(resp)} - onFail={() => setStepStatus((prev) => ({ ...prev, 2: "failed" }))} + resetPaymentStep={() => - setStepStatus((prev) => ({ ...prev, 2: "pending" })) + setStepStatus((prev) => ({ ...prev, 4: "pending" })) } + setCurrentStep={setCurrentStep} + setStepStatus={setStepStatus} + resetFormStep={() => { + setStepStatus((prev) => ({ ...prev, 1: "pending" })); + setCurrentStep(1); + }} + /> + ), + }, + { + name: "Payment", + component: () => ( + handleVerification(resp)} + + resetPaymentStep={() => + setStepStatus((prev) => ({ ...prev, 4: "pending" })) + } + setCurrentStep={setCurrentStep} + setStepStatus={setStepStatus} + resetFormStep={() => { + setStepStatus((prev) => ({ ...prev, 1: "pending" })); + setCurrentStep(1); + }} /> ), }, @@ -60,14 +105,96 @@ const MakeSubscription = () => { ]; return ( -
- -
+ <> + + +
+ +
+ ); }; diff --git a/src/slices/localVariablesSlice.jsx b/src/slices/localVariablesSlice.jsx index 83695f25..af70d936 100644 --- a/src/slices/localVariablesSlice.jsx +++ b/src/slices/localVariablesSlice.jsx @@ -35,6 +35,7 @@ const localVariablesSlice = createSlice({ selfTenant: { tenantEnquireId: null, planId: null, + details:null, }, }, reducers: { @@ -106,6 +107,8 @@ const localVariablesSlice = createSlice({ action.payload.tenantEnquireId ?? state.selfTenant.tenantEnquireId; state.selfTenant.planId = action.payload.planId ?? state.selfTenant.planId; + state.selfTenant.details = + action.payload.details ?? state.selfTenant.details; }, }, });