fixed frquency invalid display

This commit is contained in:
pramod.mahajan 2025-10-29 15:42:36 +05:30
parent 4bbe289c95
commit c00ab582cc
7 changed files with 403 additions and 214 deletions

View File

@ -14,6 +14,7 @@ import { useDispatch, useSelector } from "react-redux";
import { setSelfTenant } from "../../slices/localVariablesSlice"; import { setSelfTenant } from "../../slices/localVariablesSlice";
import { unblockUI } from "../../utils/blockUI"; import { unblockUI } from "../../utils/blockUI";
import showToast from "../../services/toastService"; import showToast from "../../services/toastService";
import SelectedPlanSkeleton from "./SelectedPlanSkeleton";
const ProcessedPayment = ({ const ProcessedPayment = ({
onNext, onNext,
@ -22,11 +23,12 @@ const ProcessedPayment = ({
setStepStatus, setStepStatus,
resetFormStep, resetFormStep,
}) => { }) => {
const { frequency, planName } = useParams(); const { planName } = useParams();
const { details: client, planId: selectedPlanId } = useSelector( const { details: client, planId: selectedPlanId,frequency } = useSelector(
(store) => store.localVariables.selfTenant (store) => store.localVariables.selfTenant
); );
console.log(frequency)
const [selectedPlan, setSelectedPlan] = useState(null); const [selectedPlan, setSelectedPlan] = useState(null);
const [currentPlan, setCurrentPlan] = useState(null); const [currentPlan, setCurrentPlan] = useState(null);
const [failPayment, setFailPayment] = useState(null); const [failPayment, setFailPayment] = useState(null);
@ -36,7 +38,6 @@ const ProcessedPayment = ({
isError: isPlanError, isError: isPlanError,
isLoading, isLoading,
} = useSubscription(frequency); } = useSubscription(frequency);
useEffect(() => { useEffect(() => {
if (!plans || !selectedPlanId) return; if (!plans || !selectedPlanId) return;
const selected = plans.find((p) => p.id === selectedPlanId); const selected = plans.find((p) => p.id === selectedPlanId);
@ -77,7 +78,7 @@ const ProcessedPayment = ({
alert("Failed to load Razorpay SDK"); alert("Failed to load Razorpay SDK");
return; return;
} }
MakePayment({ amount: 1 }); MakePayment({ amount:selectedPlan?.price });
}; };
const handleRetry = () => { const handleRetry = () => {
@ -154,6 +155,9 @@ const ProcessedPayment = ({
and help you maximize productivity. and help you maximize productivity.
</p> </p>
</div> </div>
{isLoading ? <SelectedPlanSkeleton/> : (<>
{selectedPlan && ( {selectedPlan && (
<div className="col-12 col-md-8 mb-md-3 mb-2"> <div className="col-12 col-md-8 mb-md-3 mb-2">
<div className="custom-option custom-option-basic text-start w-100 bg-light-primary border border-primary shadow-md p-3 rounded-3"> <div className="custom-option custom-option-basic text-start w-100 bg-light-primary border border-primary shadow-md p-3 rounded-3">
@ -164,8 +168,8 @@ const ProcessedPayment = ({
<div className="d-flex justify-content-between mt-1"> <div className="d-flex justify-content-between mt-1">
<small className="text-muted"> <small className="text-muted">
{selectedPlan?.currency?.symbol} {selectedPlan?.price} /{" "} {selectedPlan?.currency?.symbol} {selectedPlan?.price} /
{frequencyLabel(frequency)} {frequencyLabel(frequency,false)}
</small> </small>
</div> </div>
@ -286,6 +290,8 @@ const ProcessedPayment = ({
</div> </div>
</div> </div>
)} )}
</>)}
</div> </div>
</div> </div>
<div className="col-12 col-md-4 "> <div className="col-12 col-md-4 ">

View File

@ -4,9 +4,13 @@ import { useParams } from "react-router-dom";
import { useSubscription } from "../../hooks/useAuth"; import { useSubscription } from "../../hooks/useAuth";
import { formatFigure, frequencyLabel } from "../../utils/appUtils"; import { formatFigure, frequencyLabel } from "../../utils/appUtils";
import { setSelfTenant } from "../../slices/localVariablesSlice"; import { setSelfTenant } from "../../slices/localVariablesSlice";
import SelectedPlanSkeleton from "./SelectedPlanSkeleton";
const SelectPlan = ({ currentStep, setStepStatus, onNext }) => { const SelectPlan = ({ currentStep, setStepStatus, onNext }) => {
const { frequency, planName } = useParams(); const { frequency, planName } = useParams();
const [selectedFrequency, setSelectedFrequency] = useState(
parseInt(frequency)
);
const dispatch = useDispatch(); const dispatch = useDispatch();
const client = useSelector( const client = useSelector(
@ -20,7 +24,7 @@ const SelectPlan = ({ currentStep, setStepStatus, onNext }) => {
data: plans, data: plans,
isError: isPlanError, isError: isPlanError,
isLoading, isLoading,
} = useSubscription(frequency); } = useSubscription(selectedFrequency);
const handleChange = (e) => setSelectedPlan(e.target.value); const handleChange = (e) => setSelectedPlan(e.target.value);
@ -29,209 +33,296 @@ const SelectPlan = ({ currentStep, setStepStatus, onNext }) => {
const selected = plans.find((p) => p.planName === selectedPlan); const selected = plans.find((p) => p.planName === selectedPlan);
if (selected) { if (selected) {
setCurrentPlan(selected); setCurrentPlan(selected);
dispatch(setSelfTenant({ planId: selected.id })); dispatch(
setSelfTenant({ planId: selected.id, frequency: selectedFrequency })
);
} }
}, [plans, selectedPlan, dispatch]); }, [plans, selectedPlan, dispatch, selectedFrequency]);
const handleNextStep = () => { const handleNextStep = () => {
setStepStatus((prev) => ({ ...prev, 2: "success"})); dispatch(setSelfTenant({ frequency: selectedFrequency }));
setStepStatus((prev) => ({ ...prev, 2: "success" }));
onNext(); onNext();
}; };
return ( return (
<div className="container text-start "> <div className="container text-start ">
<div className="row gx-4 gy-5 align-items-center justify-content-around"> <div className="row gx-4 gy-5 align-items-center justify-content-around">
<div className="col-sm-12 col-md-6"> <div className="col-sm-12 col-md-6">
<div className="row"> <div className="row">
<div className="col-12 mb-3 text-start"> <div className="col-12 mb-3 text-start">
<h4>Choose the Perfect Plan for Your Organization</h4> <h4>Choose the Perfect Plan for Your Organization</h4>
<p className="text-muted small mb-3"> <p className="text-muted small mb-3">
Select a plan that fits your teams needs and unlock the Select a plan that fits your teams needs and unlock the
features that drive productivity. features that drive productivity.
</p> </p>
<div>
<div className="form-check form-check-inline mt-4">
<input
className="form-check-input"
type="radio"
name="frequencyOptions"
id="frequencyMonthly"
value={0}
checked={selectedFrequency === 0}
onChange={(e) =>
setSelectedFrequency(Number(e.target.value))
}
/>
<label
className="form-check-label"
htmlFor="frequencyMonthly"
>
Monthly
</label>
</div>
<div className="form-check form-check-inline">
<input
className="form-check-input"
type="radio"
name="frequencyOptions"
id="frequencyQuarterly"
value={1}
checked={selectedFrequency === 1}
onChange={(e) =>
setSelectedFrequency(Number(e.target.value))
}
/>
<label
className="form-check-label"
htmlFor="frequencyQuarterly"
>
Quarterly
</label>
</div>
<div className="form-check form-check-inline">
<input
className="form-check-input"
type="radio"
name="frequencyOptions"
id="frequencyHalfYear"
value={2}
checked={selectedFrequency === 2}
onChange={(e) =>
setSelectedFrequency(Number(e.target.value))
}
/>
<label
className="form-check-label"
htmlFor="frequencyHalfYear"
>
Half-Yearly
</label>
</div>
<div className="form-check form-check-inline">
<input
className="form-check-input"
type="radio"
name="frequencyOptions"
id="frequencyYearly"
value={3}
checked={selectedFrequency === 3}
onChange={(e) =>
setSelectedFrequency(Number(e.target.value))
}
/>
<label className="form-check-label" htmlFor="frequencyYearly">
Yearly
</label>
</div>
</div>
</div>
{isLoading ? (
<SelectedPlanSkeleton />
) : (
<>
{plans?.map((plan) => (
<div key={plan?.id} className="col-12 col-md-4 mb-md-3 mb-2">
<div
className={`form-check custom-option custom-option-basic text-start w-100 bg-light-primary ${
selectedPlan === plan?.planName
? "border border-primary shadow-md"
: ""
}`}
>
<label
className="form-check-label custom-option-content w-100"
htmlFor={`customRadioTemp${plan?.id}`}
>
<input
name="customRadioTemp"
className="form-check-input"
type="radio"
value={plan?.planName}
id={`customRadioTemp${plan?.id}`}
checked={selectedPlan === plan?.planName}
onChange={handleChange}
/>
<span className="custom-option-header d-flex justify-content-between align-items-center">
<span className="h6 mb-0">{plan?.planName}</span>
<span>
{plan.currency?.symbol} {plan.price} /{" "}
{frequencyLabel(selectedFrequency)}
</span>
</span>
<span className="custom-option-body d-block mt-1">
<small>{plan?.description}</small>
</span>
</label>
</div>
</div>
))}
{selectedPlan && (
<div className="col-12 text-start">
<div className="border-warning mt-3">
{(() => {
const selected = plans?.find(
(p) => p.planName === selectedPlan
);
if (!selected) return null;
const {
price,
frequency,
trialDays,
maxUser,
maxStorage,
currency,
features,
} = selected;
return (
<>
<div className="row g-2 mb-3">
<div className="col-sm-6 col-md-4">
<div className="border rounded-3 p-2 bg-light">
<i className="bx bx-user me-1 text-primary"></i>
<strong>Max Users:</strong> {maxUser}
</div>
</div>
<div className="col-sm-6 col-md-4">
<div className="border rounded-3 p-2 bg-light">
<i className="bx bx-hdd me-1 text-primary"></i>
<strong>Max Storage:</strong> {maxStorage} MB
</div>
</div>
<div className="col-sm-6 col-md-4">
<div className="border rounded-3 p-2 bg-light">
<i className="bx bx-time-five me-1 text-primary"></i>
<strong>Trial Days:</strong> {trialDays}
</div>
</div>
</div>
<h6 className="fw-bold text-secondary mb-2">
Included Features
</h6>
<div className="row">
{features &&
Object.entries(features?.modules || {})
.filter(([key]) => key !== "id")
.map(([key, mod]) => (
<div
key={key}
className="col-4 mb-2 d-flex align-items-center"
>
<i
className={`fa-regular ${
mod.enabled
? "fa-circle-check text-success"
: "fa-circle-xmark text-danger"
}`}
></i>
<small className="ms-1">{mod.name}</small>
</div>
))}
</div>
<h6 className="fw-bold text-secondary mt-3 mb-2">
Support
</h6>
<ul className="list-unstyled d-flex flex-wrap gap-3 align-items-center mb-0 small">
{features?.supports?.emailSupport && (
<li className="d-flex align-items-center">
<i className="bx bx-envelope text-primary me-1 fs-5"></i>
Email Support
</li>
)}
{features?.supports?.phoneSupport && (
<li className="d-flex align-items-center">
<i className="bx bx-phone text-primary me-1 fs-5"></i>
Phone Support
</li>
)}
{features?.supports?.prioritySupport && (
<li className="d-flex align-items-center">
<i className="bx bx-star text-warning me-1 fs-5"></i>
Priority Support
</li>
)}
</ul>
<hr className="divider border-2" />
<div className="d-flex flex-column co-12">
<div className="d-flex justify-content-between">
<h6 className="fs-6 m-0">Duration</h6>
<h5 className="fs-6 m-0">
{frequencyLabel(frequency, true)}
</h5>
</div>
<div className="d-flex justify-content-between">
<h6 className="fs-4">Total Price</h6>
<h5 className="fs-3">
{formatFigure(price, {
type: "currency",
currency: currency.currencyCode,
})}
</h5>
</div>
</div>
</>
);
})()}
</div>
</div>
)}
</>
)}
</div>
</div> </div>
{plans?.map((plan) => ( {/* Image Section */}
<div key={plan?.id} className="col-12 col-md-4 mb-md-3 mb-2"> <div className="d-none d-md-flex col-md-6 justify-content-center align-items-center">
<div <img
className={`form-check custom-option custom-option-basic text-start w-100 bg-light-primary ${ src="/public/img/illustrations/undraw_pricing.png"
selectedPlan === plan?.planName alt="image"
? "border border-primary shadow-md" className="img-fluid"
: "" style={{
}`} maxWidth: "70%",
> height: "auto",
<label objectFit: "contain",
className="form-check-label custom-option-content w-100" }}
htmlFor={`customRadioTemp${plan?.id}`} />
> </div>
<input </div>
name="customRadioTemp"
className="form-check-input"
type="radio"
value={plan?.planName}
id={`customRadioTemp${plan?.id}`}
checked={selectedPlan === plan?.planName}
onChange={handleChange}
/>
<span className="custom-option-header d-flex justify-content-between align-items-center">
<span className="h6 mb-0">{plan?.planName}</span>
<span>
{plan.currency?.symbol} {plan.price} /{" "}
{frequencyLabel(frequency)}
</span>
</span>
<span className="custom-option-body d-block mt-1">
<small>{plan?.description}</small>
</span>
</label>
</div>
</div>
))}
{selectedPlan && ( <div className="col-12 d-flex justify-content-end mt-3">
<div className="col-12 text-start"> <button
<div className="border-warning mt-3"> type="submit"
{(() => { className="btn btn-label-primary d-flex align-items-center me-2"
const selected = plans?.find( onClick={handleNextStep}
(p) => p.planName === selectedPlan >
); Next <i className="bx bx-chevron-right"></i>
if (!selected) return null; </button>
const {
price,
frequency,
trialDays,
maxUser,
maxStorage,
currency,
features,
} = selected;
return (
<>
<div className="row g-2 mb-3">
<div className="col-sm-6 col-md-4">
<div className="border rounded-3 p-2 bg-light">
<i className="bx bx-user me-1 text-primary"></i>
<strong>Max Users:</strong> {maxUser}
</div>
</div>
<div className="col-sm-6 col-md-4">
<div className="border rounded-3 p-2 bg-light">
<i className="bx bx-hdd me-1 text-primary"></i>
<strong>Max Storage:</strong> {maxStorage} MB
</div>
</div>
<div className="col-sm-6 col-md-4">
<div className="border rounded-3 p-2 bg-light">
<i className="bx bx-time-five me-1 text-primary"></i>
<strong>Trial Days:</strong> {trialDays}
</div>
</div>
</div>
<h6 className="fw-bold text-secondary mb-2">
Included Features
</h6>
<div className="row">
{features &&
Object.entries(features?.modules || {})
.filter(([key]) => key !== "id")
.map(([key, mod]) => (
<div
key={key}
className="col-4 mb-2 d-flex align-items-center"
>
<i
className={`fa-regular ${
mod.enabled
? "fa-circle-check text-success"
: "fa-circle-xmark text-danger"
}`}
></i>
<small className="ms-1">{mod.name}</small>
</div>
))}
</div>
<h6 className="fw-bold text-secondary mt-3 mb-2">
Support
</h6>
<ul className="list-unstyled d-flex flex-wrap gap-3 align-items-center mb-0 small">
{features?.supports?.emailSupport && (
<li className="d-flex align-items-center">
<i className="bx bx-envelope text-primary me-1 fs-5"></i>
Email Support
</li>
)}
{features?.supports?.phoneSupport && (
<li className="d-flex align-items-center">
<i className="bx bx-phone text-primary me-1 fs-5"></i>
Phone Support
</li>
)}
{features?.supports?.prioritySupport && (
<li className="d-flex align-items-center">
<i className="bx bx-star text-warning me-1 fs-5"></i>
Priority Support
</li>
)}
</ul>
<hr className="divider border-2" />
<div className="d-flex flex-column co-12">
<div className="d-flex justify-content-between">
<h6 className="fs-6 m-0">Duration</h6>
<h5 className="fs-6 m-0">
{frequencyLabel(frequency, true)}
</h5>
</div>
<div className="d-flex justify-content-between">
<h6 className="fs-4">Total Price</h6>
<h5 className="fs-3">
{formatFigure(price, {
type: "currency",
currency: currency.currencyCode,
})}
</h5>
</div>
</div>
</>
);
})()}
</div>
</div>
)}
</div> </div>
</div> </div>
{/* Image Section */}
<div className="d-none d-md-flex col-md-6 justify-content-center align-items-center">
<img
src="/public/img/illustrations/undraw_pricing.png"
alt="image"
className="img-fluid"
style={{
maxWidth: "70%",
height: "auto",
objectFit: "contain",
}}
/>
</div>
</div>
<div className="col-12 d-flex justify-content-end mt-3">
<button
type="submit"
className="btn btn-label-primary d-flex align-items-center me-2"
onClick={handleNextStep}
>
Next <i className="bx bx-chevron-right"></i>
</button>
</div>
</div>
); );
}; };

View File

@ -0,0 +1,88 @@
import React from "react";
const SkeletonLine = ({ height = 16, width = "100%", className = "" }) => (
<div
className={`skeleton mb-2 ${className}`}
style={{
height,
width,
}}
></div>
);
const SelectedPlanSkeleton = () => {
return (
<div className="row">
{/* Plan Summary Card */}
<div className="col-12 col-md-8 mb-md-3 mb-2">
<div className="custom-option custom-option-basic text-start w-100 bg-light border shadow-md p-3 rounded-3">
<div className="d-flex justify-content-between align-items-center mb-2">
<SkeletonLine height={20} width="40%" />
<SkeletonLine height={20} width="25px" />
</div>
<SkeletonLine height={16} width="60%" />
<SkeletonLine height={14} width="80%" />
</div>
</div>
{/* Plan Details */}
<div className="col-12 text-start mt-2">
<div className="border rounded-3 p-3 bg-light">
{/* Stats (Max Users, Storage, Trial Days) */}
<div className="row g-2 mb-3">
{[...Array(3)].map((_, i) => (
<div className="col-sm-6 col-md-4" key={i}>
<div className="border rounded-3 p-2 bg-white d-flex align-items-center">
<SkeletonLine height={18} width="80%" />
</div>
</div>
))}
</div>
{/* Included Features */}
<h6 className="fw-bold text-secondary mb-2">
<SkeletonLine height={18} width="40%" />
</h6>
<div className="row">
{[...Array(6)].map((_, i) => (
<div
className="col-4 mb-2 d-flex align-items-center"
key={i}
>
<SkeletonLine height={14} width="70%" />
</div>
))}
</div>
{/* Support */}
<h6 className="fw-bold text-secondary mt-3 mb-2">
<SkeletonLine height={18} width="30%" />
</h6>
<div className="d-flex flex-wrap gap-3 mb-2">
{[...Array(3)].map((_, i) => (
<SkeletonLine key={i} height={14} width="100px" />
))}
</div>
<hr className="divider border-2" />
{/* Duration and Total */}
<div className="d-flex flex-column">
<div className="d-flex justify-content-between mb-1">
<SkeletonLine height={16} width="40%" />
<SkeletonLine height={16} width="30%" />
</div>
<div className="d-flex justify-content-between">
<SkeletonLine height={20} width="50%" />
<SkeletonLine height={22} width="40%" />
</div>
</div>
</div>
</div>
</div>
);
};
export default SelectedPlanSkeleton;

View File

@ -15,6 +15,7 @@ import {
} from "../slices/localVariablesSlice.jsx"; } from "../slices/localVariablesSlice.jsx";
import { removeSession } from "../utils/authUtils.js"; import { removeSession } from "../utils/authUtils.js";
import showToast from "../services/toastService.tsx"; import showToast from "../services/toastService.tsx";
import eventBus from "../services/eventBus.js";
// ----------------------------Modal-------------------------- // ----------------------------Modal--------------------------
@ -38,6 +39,7 @@ export const useSubscription = (frequency) => {
const resp = await AuthRepository.getSubscription(frequency); const resp = await AuthRepository.getSubscription(frequency);
return resp.data; return resp.data;
}, },
enabled: frequency !== null && frequency !== undefined
}); });
}; };
@ -94,7 +96,6 @@ export const useCreateSelfTenant = (onSuccessCallBack, onFailureCallBack) => {
details:response details:response
}) })
); );
debugger
if (onSuccessCallBack) onSuccessCallBack(response); if (onSuccessCallBack) onSuccessCallBack(response);
}, },
onError: (error) => { onError: (error) => {

View File

@ -8,7 +8,7 @@ import SelectPlan from "../../components/UserSubscription/SelectPlan";
import Review from "../../components/UserSubscription/Review"; import Review from "../../components/UserSubscription/Review";
const MakeSubscription = () => { const MakeSubscription = () => {
const [currentStep, setCurrentStep] = useState(1); const [currentStep, setCurrentStep] = useState(2);
const [responsePayment, setResponsePayment] = useState(null); const [responsePayment, setResponsePayment] = useState(null);
const [stepStatus, setStepStatus] = useState({ const [stepStatus, setStepStatus] = useState({

View File

@ -36,6 +36,7 @@ const localVariablesSlice = createSlice({
tenantEnquireId: null, tenantEnquireId: null,
planId: null, planId: null,
details:null, details:null,
frequency:null,
}, },
}, },
reducers: { reducers: {
@ -109,6 +110,7 @@ const localVariablesSlice = createSlice({
action.payload.planId ?? state.selfTenant.planId; action.payload.planId ?? state.selfTenant.planId;
state.selfTenant.details = state.selfTenant.details =
action.payload.details ?? state.selfTenant.details; action.payload.details ?? state.selfTenant.details;
state.selfTenant.frequency = action.payload.frequency ?? state.selfTenant.frequency;
}, },
}, },
}); });

View File

@ -136,17 +136,18 @@ export const formatFigure = (
return new Intl.NumberFormat(locale, formatterOptions).format(amount); return new Intl.NumberFormat(locale, formatterOptions).format(amount);
}; };
export const frequencyLabel = (freq,isLong=false) => { export const frequencyLabel = (freq, isLong = false) => {
switch (freq) { const frequency = parseInt(freq, 10);
case 0: switch (frequency) {
return isLong ? "1 Month" : "1 mo"; case 0:
case 1: return isLong ? "1 Month" : "1 mo";
return isLong ? "3 Months" : "3 mo"; case 1:
case 2: return isLong ? "Quarterly (3 Months)" : "3 mo";
return isLong ? "6 Months" : "6 mo"; case 2:
case 3: return isLong ? "6 Months" : "6 mo";
return isLong ? "1 Year" : "1 yr"; case 3:
default: return isLong ? "1 Year" : "1 yr";
return "mo"; default:
} return isLong ? "Unknown" : "N/A";
}; }
};