upgrade plane card and add discount coupon input box
This commit is contained in:
parent
5690ec8a69
commit
2042e179c7
25
src/assets/vendor/css/core.css
vendored
25
src/assets/vendor/css/core.css
vendored
@ -17596,6 +17596,31 @@ html:not([dir=rtl]) .toast.bs-toast .toast-header .btn-close {
|
||||
}
|
||||
}
|
||||
@media (min-width: 768px) {
|
||||
/* width */
|
||||
|
||||
.w-md-auto {
|
||||
width: auto !important;
|
||||
}
|
||||
|
||||
.w-md-20 {
|
||||
width: 20% !important;
|
||||
}
|
||||
|
||||
.w-md-25 {
|
||||
width: 25% !important;
|
||||
}
|
||||
|
||||
.w-md-50 {
|
||||
width: 50% !important;
|
||||
}
|
||||
|
||||
.w-md-60 {
|
||||
width: 60% !important;
|
||||
}
|
||||
|
||||
.w-md-75 {
|
||||
width: 75% !important;
|
||||
}
|
||||
.object-fit-md-contain {
|
||||
object-fit: contain !important;
|
||||
}
|
||||
|
||||
@ -43,15 +43,14 @@ const SubScriptionHistory = ({ tenantId }) => {
|
||||
);
|
||||
}
|
||||
|
||||
// Format dates
|
||||
|
||||
const end = plan?.endDate ? new Date(plan.endDate) : null;
|
||||
const today = new Date();
|
||||
|
||||
const daysLeft = end
|
||||
? Math.max(0, Math.ceil((end - today) / (1000 * 60 * 60 * 24)))
|
||||
? Math.max(0, Math.ceil((end - plan?.startDate) / (1000 * 60 * 60 * 24)))
|
||||
: 0;
|
||||
|
||||
// Render logic for subscription history table
|
||||
const renderSubscriptionHistory = () => {
|
||||
if (!subscriptionHistory || subscriptionHistory.length === 0) {
|
||||
return (
|
||||
@ -72,23 +71,35 @@ const SubScriptionHistory = ({ tenantId }) => {
|
||||
<table className="table border-top dataTable text-nowrap align-middle">
|
||||
<thead className="align-middle">
|
||||
<tr>
|
||||
<th>Date</th>
|
||||
<th>Type</th>
|
||||
<th>Invoice</th>
|
||||
<th>Amount</th>
|
||||
<th>Plan Name</th>
|
||||
<th className="text-center">Action</th>
|
||||
<th>Date</th>
|
||||
<th>Status</th>
|
||||
<th className="text-center actions-col">Action</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody className="align-middle">
|
||||
{sortedHistory.map((item) => (
|
||||
<tr key={item.id}>
|
||||
<td>{formatUTCToLocalTime(item?.createdAt)}</td>
|
||||
<td>{SUBSCRIPTION_PLAN_FREQUENCIES[item.frequency] || "N/A"}</td>
|
||||
<td>
|
||||
<td className="fw-medium">{item.planName}</td>
|
||||
|
||||
<td className="fw-medium">
|
||||
{item.currency?.symbol || "₹"} {item.price}
|
||||
</td>
|
||||
<td>{item.planName}</td>
|
||||
<td className="text-center">
|
||||
<td>
|
||||
<span className="text-tiny small text-muted">
|
||||
{formatUTCToLocalTime(item?.createdAt)} by,{" "}
|
||||
{item?.createdBy?.firstName}
|
||||
</span>
|
||||
</td>
|
||||
<td>
|
||||
<div className="d-flex align-items-center">
|
||||
<p className="p-1 my-0 mx-1 rounded-circle bg-success"></p>{" "}
|
||||
<small className="fw-medium">Paid</small>
|
||||
</div>
|
||||
</td>
|
||||
|
||||
<td className="text-center actions-col">
|
||||
<div className="dropdown">
|
||||
<button
|
||||
className="btn btn-icon btn-sm dropdown-toggle hide-arrow"
|
||||
@ -97,9 +108,7 @@ const SubScriptionHistory = ({ tenantId }) => {
|
||||
<i className="bx bx-dots-vertical-rounded"></i>
|
||||
</button>
|
||||
<div className="dropdown-menu dropdown-menu-end">
|
||||
<button
|
||||
className="dropdown-item py-1"
|
||||
>
|
||||
<button className="dropdown-item py-1">
|
||||
<i className="bx bx-detail bx-sm"></i> View
|
||||
</button>
|
||||
<button
|
||||
@ -108,7 +117,8 @@ const SubScriptionHistory = ({ tenantId }) => {
|
||||
console.log("Download clicked for", item.id)
|
||||
}
|
||||
>
|
||||
<i className="bx bx-cloud-download bx-sm"></i> Download
|
||||
<i className="bx bx-cloud-download bx-sm"></i>{" "}
|
||||
Download
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
@ -120,100 +130,172 @@ const SubScriptionHistory = ({ tenantId }) => {
|
||||
</div>
|
||||
|
||||
{/* Card-based view for smaller screens */}
|
||||
|
||||
</>
|
||||
);
|
||||
};
|
||||
|
||||
return (
|
||||
<div className="p-2 p-md-4">
|
||||
<div className="row g-4">
|
||||
{/* Left Card: Active Subscription */}
|
||||
<div className="col-12 col-lg-6">
|
||||
<div className="card shadow-sm border rounded p-3 h-100 text-start">
|
||||
<div className="divider text-start mb-3">
|
||||
<div className="divider-text">Active Subscription</div>
|
||||
</div>
|
||||
|
||||
<p className="text-primary fw-bold m-0 fs-4">
|
||||
{plan.planName || "N/A"}
|
||||
</p>
|
||||
{plan.description && (
|
||||
<p className="m-0 text-muted small">{plan.description}</p>
|
||||
)}
|
||||
|
||||
<div className="mt-2">
|
||||
<h3 className="m-0">
|
||||
{plan.currency?.symbol || "₹"} {plan.price}
|
||||
</h3>
|
||||
<small className="text-muted">
|
||||
{SUBSCRIPTION_PLAN_FREQUENCIES[plan.frequency] || ""}
|
||||
<div className="text-start">
|
||||
<div className="row d-flex justify-content-between py-1">
|
||||
<div className="col-md-4 d-flex flex-column gap-6">
|
||||
<div className="">
|
||||
<p className=" d-bolck fw-semibold fs-6">Current Plan</p>
|
||||
<small className="d-bolck">
|
||||
You can update your plan anytime for best benifit from the product
|
||||
and track your projct
|
||||
</small>
|
||||
</div>
|
||||
<p className="fw-semibold text-primary">Switch Plan</p>
|
||||
</div>
|
||||
<div className="col-md-6 ">
|
||||
<div className="row border bg-light-primary p-3">
|
||||
<div className="col-1">
|
||||
<small>
|
||||
<i className="bx bxs-package"></i>
|
||||
</small>
|
||||
</div>
|
||||
|
||||
<div className="mt-3 small text-muted">
|
||||
<div>
|
||||
Activated Since:{" "}
|
||||
{plan.startDate ? formatUTCToLocalTime(plan.startDate) : "N/A"} (
|
||||
{daysLeft} days left)
|
||||
<div className="col-11">
|
||||
<div className="d-flex justify-content-between">
|
||||
<div className="d-flex flex-row gap-4">
|
||||
<small className="fw-bold">{plan.planName || "N/A"}</small>
|
||||
<small className="fw-bold text-primary">
|
||||
{" "}
|
||||
{plan.currency?.symbol || "₹"} {plan.price}
|
||||
</small>
|
||||
<small className=" text-primary">
|
||||
{" "}
|
||||
{SUBSCRIPTION_PLAN_FREQUENCIES[plan.frequency] || ""}
|
||||
</small>
|
||||
</div>
|
||||
<span className=" bg-primary rounded-circle">
|
||||
<i className="bx bx-md bx-check text-white"></i>
|
||||
</span>
|
||||
</div>
|
||||
<div className="mt-1">
|
||||
Ends on:{" "}
|
||||
{plan.endDate ? formatUTCToLocalTime(plan.endDate) : "N/A"}
|
||||
<div className="col-12">
|
||||
<p className="m-0 text-muted text-tiny">
|
||||
Includes up to 100 userss, 10GB individual cloud storage and
|
||||
access maximum features
|
||||
</p>
|
||||
<div className="mt-1">
|
||||
Activated Since:{" "}
|
||||
<span className="fw-semibold">
|
||||
{" "}
|
||||
{plan.startDate
|
||||
? formatUTCToLocalTime(plan.startDate)
|
||||
: "N/A"}{" "}
|
||||
({daysLeft} days left)
|
||||
</span>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{/* Features list */}
|
||||
<div className="mt-4">
|
||||
<h6 className="text-secondary">Features</h6>
|
||||
<div className="row g-2">
|
||||
{features?.modules &&
|
||||
Object.entries(features.modules).map(([key, mod]) => {
|
||||
if (!mod?.name) return null;
|
||||
return (
|
||||
<div
|
||||
key={key}
|
||||
className="col-12 col-sm-6 d-flex align-items-center"
|
||||
>
|
||||
<i
|
||||
className={`fa-regular ${
|
||||
mod.enabled
|
||||
? "fa-circle-check text-success"
|
||||
: "fa-circle-xmark text-danger"
|
||||
}`}
|
||||
></i>
|
||||
<span className="ms-2">{mod.name}</span>
|
||||
</div>
|
||||
);
|
||||
})}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div className="mt-3 text-end">
|
||||
<button
|
||||
className="btn btn-sm btn-primary"
|
||||
onClick={handleUpgradePlan}
|
||||
>
|
||||
Upgrade Plan
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{/* Right Card: Subscription History */}
|
||||
<div className="col-12 col-lg-6">
|
||||
<div className="card shadow-sm border rounded p-3 h-100">
|
||||
<div className="divider text-start mb-3">
|
||||
<div className="divider-text">
|
||||
<i className="bx bx-history"></i> <small>History</small>
|
||||
</div>
|
||||
</div>
|
||||
{renderSubscriptionHistory()}
|
||||
<hr className="divider border-2 my-2" />
|
||||
|
||||
<div className="row d-flex justify-content-between py-1">
|
||||
<div className="col-md-4 d-flex flex-column gap-6">
|
||||
<div className="">
|
||||
<p className=" d-bolck fw-semibold fs-6">Billing History</p>
|
||||
<small className="d-bolck">
|
||||
Sumary on the payment history for the subscription plan of the
|
||||
application
|
||||
</small>
|
||||
</div>
|
||||
<p className="fw-semibold text-primary">Billing History</p>
|
||||
</div>
|
||||
<div className="col-md-6 ">{renderSubscriptionHistory()}</div>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
export default SubScriptionHistory;
|
||||
export default SubScriptionHistory;
|
||||
|
||||
// <div className="p-2 p-md-4">
|
||||
// <div className="row g-4">
|
||||
// {/* Left Card: Active Subscription */}
|
||||
// <div className="col-12 col-lg-6">
|
||||
// <div className="card shadow-sm border rounded p-3 h-100 text-start">
|
||||
// <div className="divider text-start mb-3">
|
||||
// <div className="divider-text">Active Subscription</div>
|
||||
// </div>
|
||||
|
||||
// <p className="text-primary fw-bold m-0 fs-4">
|
||||
// {plan.planName || "N/A"}
|
||||
// </p>
|
||||
// {plan.description && (
|
||||
// <p className="m-0 text-muted small">{plan.description}</p>
|
||||
// )}
|
||||
|
||||
// <div className="mt-2">
|
||||
// <h3 className="m-0">
|
||||
// {plan.currency?.symbol || "₹"} {plan.price}
|
||||
// </h3>
|
||||
// <small className="text-muted">
|
||||
// {SUBSCRIPTION_PLAN_FREQUENCIES[plan.frequency] || ""}
|
||||
// </small>
|
||||
// </div>
|
||||
|
||||
// <div className="mt-3 small text-muted">
|
||||
// <div>
|
||||
// Activated Since:{" "}
|
||||
// {plan.startDate ? formatUTCToLocalTime(plan.startDate) : "N/A"} (
|
||||
// {daysLeft} days left)
|
||||
// </div>
|
||||
// <div className="mt-1">
|
||||
// Ends on:{" "}
|
||||
// {plan.endDate ? formatUTCToLocalTime(plan.endDate) : "N/A"}
|
||||
// </div>
|
||||
// </div>
|
||||
|
||||
// {/* Features list */}
|
||||
// <div className="mt-4">
|
||||
// <h6 className="text-secondary">Features</h6>
|
||||
// <div className="row g-2">
|
||||
// {features?.modules &&
|
||||
// Object.entries(features.modules).map(([key, mod]) => {
|
||||
// if (!mod?.name) return null;
|
||||
// return (
|
||||
// <div
|
||||
// key={key}
|
||||
// className="col-12 col-sm-6 d-flex align-items-center"
|
||||
// >
|
||||
// <i
|
||||
// className={`fa-regular ${
|
||||
// mod.enabled
|
||||
// ? "fa-circle-check text-success"
|
||||
// : "fa-circle-xmark text-danger"
|
||||
// }`}
|
||||
// ></i>
|
||||
// <span className="ms-2">{mod.name}</span>
|
||||
// </div>
|
||||
// );
|
||||
// })}
|
||||
// </div>
|
||||
// </div>
|
||||
|
||||
// <div className="mt-3 text-end">
|
||||
// <button
|
||||
// className="btn btn-sm btn-primary"
|
||||
// onClick={handleUpgradePlan}
|
||||
// >
|
||||
// Upgrade Plan
|
||||
// </button>
|
||||
// </div>
|
||||
// </div>
|
||||
// </div>
|
||||
|
||||
// <div className="col-12 col-lg-6">
|
||||
// <div className="card shadow-sm border rounded p-3 h-100">
|
||||
// <div className="divider text-start mb-3">
|
||||
// <div className="divider-text">
|
||||
// <i className="bx bx-history"></i> <small>History</small>
|
||||
// </div>
|
||||
// </div>
|
||||
// {renderSubscriptionHistory()}
|
||||
// </div>
|
||||
// </div>
|
||||
// </div>
|
||||
// </div>
|
||||
|
||||
@ -37,7 +37,10 @@ const ProcessedPayment = ({
|
||||
const {
|
||||
data: plans,
|
||||
isError: isPlanError,
|
||||
isLoading,isError,isRefetching,refetch
|
||||
isLoading,
|
||||
isError,
|
||||
isRefetching,
|
||||
refetch,
|
||||
} = useSubscription(frequency);
|
||||
useEffect(() => {
|
||||
if (!plans || !selectedPlanId) return;
|
||||
@ -77,7 +80,11 @@ const ProcessedPayment = ({
|
||||
alert("Failed to load Razorpay SDK");
|
||||
return;
|
||||
}
|
||||
MakePayment({ amount: selectedPlan?.price });
|
||||
let price = 0;
|
||||
price =
|
||||
frequencyLabel(selectedPlan?.frequency, true, true)?.planDurationInInt *
|
||||
selectedPlan?.price;
|
||||
MakePayment({ amount: price });
|
||||
};
|
||||
|
||||
const handleRetry = () => {
|
||||
@ -188,21 +195,27 @@ const ProcessedPayment = ({
|
||||
<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-header d-flex justify-content-between align-items-center">
|
||||
<span className="h6 mb-0">
|
||||
{selectedPlan?.planName}
|
||||
{selectedPlan?.description}
|
||||
</span>
|
||||
<i className="bx bx-check-circle text-primary fs-4"></i>
|
||||
</div>
|
||||
|
||||
<div className="d-flex justify-content-between mt-1">
|
||||
<small className="text-muted">
|
||||
{selectedPlan?.currency?.symbol} {selectedPlan?.price}{" "}
|
||||
/{frequencyLabel(frequency, false)}
|
||||
<small>
|
||||
Price -{" "}
|
||||
<span className="fw-medium">
|
||||
{selectedPlan.currency?.symbol} {selectedPlan.price}{" "}
|
||||
per {frequencyLabel(frequency)}
|
||||
</span>
|
||||
</small>
|
||||
</div>
|
||||
|
||||
<span className="custom-option-body d-block mt-1">
|
||||
<small>{selectedPlan?.description}</small>
|
||||
</span>
|
||||
<div className="d-flex flex-row gap-3 mt-1 align-items-center">
|
||||
<small>{selectedPlan?.planName}</small>
|
||||
<small className="d-block mt-1 text-xs border border-primary text-center rounded text-secondary w-min text-nowrap px-1">
|
||||
billed {frequencyLabel(frequency, true)}
|
||||
</small>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
)}
|
||||
@ -307,11 +320,18 @@ const ProcessedPayment = ({
|
||||
<div className="d-flex justify-content-between">
|
||||
<h6 className="fs-4">Total Price</h6>
|
||||
<h5 className="fs-3">
|
||||
{formatFigure(selectedPlan?.price, {
|
||||
type: "currency",
|
||||
currency:
|
||||
selectedPlan?.currency.currencyCode,
|
||||
})}
|
||||
{formatFigure(
|
||||
frequencyLabel(
|
||||
selectedPlan?.frequency,
|
||||
true,
|
||||
true
|
||||
)?.planDurationInInt * price,
|
||||
{
|
||||
type: "currency",
|
||||
currency:
|
||||
selectedPlan?.currency.currencyCode,
|
||||
}
|
||||
)}
|
||||
</h5>
|
||||
</div>
|
||||
</div>
|
||||
@ -375,26 +395,28 @@ const ProcessedPayment = ({
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
<div className="col-12 d-flex justify-content-between">
|
||||
<div className="col-12 d-flex flex-column flex-md-row justify-content-between gap-2 mt-3">
|
||||
<button
|
||||
type="submit"
|
||||
className="btn btn-label-primary d-flex align-items-center me-2"
|
||||
className="btn btn-label-primary d-flex align-items-center justify-content-center w-md-auto"
|
||||
onClick={handlPrevious}
|
||||
>
|
||||
<i className="bx bx-chevron-left"></i> Previous
|
||||
<i className="bx bx-chevron-left me-1"></i> Previous
|
||||
</button>
|
||||
|
||||
<button
|
||||
type="submit"
|
||||
className="btn btn-label-primary d-flex align-items-center me-2"
|
||||
className="btn btn-label-primary d-flex align-items-center justify-content-center w-md-auto"
|
||||
onClick={() => ProcessToPayment(currentPlan?.price)}
|
||||
disabled={isPending}
|
||||
>
|
||||
{isPending ? (
|
||||
<span>
|
||||
<i className="bx bx-loader-alt bx-md bx-spin me-2"></i>Please
|
||||
<>
|
||||
<i className="bx bx-loader-alt bx-md bx-spin me-2"></i> Please
|
||||
Wait...
|
||||
</span>
|
||||
</>
|
||||
) : (
|
||||
"Processed To Payment"
|
||||
"Proceed To Payment"
|
||||
)}
|
||||
</button>
|
||||
</div>
|
||||
|
||||
@ -6,9 +6,12 @@ import { formatFigure, frequencyLabel } from "../../utils/appUtils";
|
||||
import { setSelfTenant } from "../../slices/localVariablesSlice";
|
||||
import SelectedPlanSkeleton from "./SelectedPlanSkeleton";
|
||||
import { error } from "pdf-lib";
|
||||
import { useForm } from "react-hook-form";
|
||||
import { zodResolver } from "@hookform/resolvers/zod";
|
||||
import { CouponDiscount } from "../../pages/Home/HomeSchema";
|
||||
|
||||
const SelectPlan = ({ currentStep, setStepStatus, onNext }) => {
|
||||
const { frequency, planName } = useParams();
|
||||
const { frequency, planId } = useParams();
|
||||
const [selectedFrequency, setSelectedFrequency] = useState(
|
||||
parseInt(frequency)
|
||||
);
|
||||
@ -17,7 +20,7 @@ const SelectPlan = ({ currentStep, setStepStatus, onNext }) => {
|
||||
const client = useSelector(
|
||||
(store) => store.localVariables.selfTenant.details
|
||||
);
|
||||
const [selectedPlan, setSelectedPlan] = useState(planName);
|
||||
const [selectedPlan, setSelectedPlan] = useState(planId);
|
||||
const [currentPlan, setCurrentPlan] = useState(null);
|
||||
const [failPayment, setFailPayment] = useState(null);
|
||||
|
||||
@ -30,25 +33,51 @@ const SelectPlan = ({ currentStep, setStepStatus, onNext }) => {
|
||||
isRefetching,
|
||||
} = useSubscription(selectedFrequency);
|
||||
|
||||
const handleChange = (e) => setSelectedPlan(e.target.value);
|
||||
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, frequency: selectedFrequency })
|
||||
);
|
||||
}
|
||||
}, [plans, selectedPlan, dispatch, selectedFrequency]);
|
||||
if (!plans || plans.length === 0) return;
|
||||
|
||||
// Prefer route param if exists, else default to first plan
|
||||
const matchingPlan = plans.find((p) => p.planId === planId) || plans[0];
|
||||
|
||||
setSelectedPlan(matchingPlan.id);
|
||||
setCurrentPlan(matchingPlan);
|
||||
|
||||
// Dispatch correct plan + frequency only once data is ready
|
||||
dispatch(
|
||||
setSelfTenant({
|
||||
planId: matchingPlan.id,
|
||||
frequency: selectedFrequency,
|
||||
})
|
||||
);
|
||||
}, [plans, selectedFrequency, planId, dispatch]);
|
||||
|
||||
const handleNextStep = () => {
|
||||
dispatch(setSelfTenant({ frequency: selectedFrequency }));
|
||||
if (!selectedPlan) {
|
||||
toast.warning("Please select a plan before continuing.");
|
||||
return;
|
||||
}
|
||||
|
||||
dispatch(
|
||||
setSelfTenant({
|
||||
planId: selectedPlan,
|
||||
frequency: selectedFrequency,
|
||||
})
|
||||
);
|
||||
|
||||
setStepStatus((prev) => ({ ...prev, 2: "success" }));
|
||||
onNext();
|
||||
};
|
||||
|
||||
const {
|
||||
register,
|
||||
handleSubmit,
|
||||
formState: { errors },
|
||||
} = useForm({ resolver: zodResolver(CouponDiscount) });
|
||||
|
||||
return (
|
||||
<div className="container text-start ">
|
||||
<div className="row gy-5 align-items-center justify-content-around">
|
||||
@ -171,51 +200,59 @@ const SelectPlan = ({ currentStep, setStepStatus, onNext }) => {
|
||||
<SelectedPlanSkeleton />
|
||||
) : (
|
||||
<>
|
||||
<div className="row">
|
||||
<div className="row">
|
||||
{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"
|
||||
: ""
|
||||
}`}
|
||||
key={plan?.id}
|
||||
className="col-12 col-md-4 mb-md-3 mb-2"
|
||||
>
|
||||
<label
|
||||
className="form-check-label custom-option-content w-100"
|
||||
htmlFor={`customRadioTemp${plan?.id}`}
|
||||
<div
|
||||
className={`form-check custom-option custom-option-basic text-start w-100 bg-light-primary ${
|
||||
selectedPlan === plan?.id
|
||||
? "border border-primary shadow-md"
|
||||
: ""
|
||||
}`}
|
||||
>
|
||||
<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)}
|
||||
<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?.id}
|
||||
id={`customRadioTemp${plan?.id}`}
|
||||
checked={selectedPlan === plan?.id}
|
||||
onChange={handleChange}
|
||||
/>
|
||||
<span className="custom-option-header d-flex justify-content-between align-items-center">
|
||||
<span className="h6 mb-0">{plan?.description}</span>
|
||||
</span>
|
||||
</span>
|
||||
<span className="custom-option-body d-block mt-1">
|
||||
<small>{plan?.description}</small>
|
||||
</span>
|
||||
</label>
|
||||
<span className="custom-option-body d-block ">
|
||||
<small>
|
||||
Price -{" "}
|
||||
<span className="fw-medium">
|
||||
{plan.currency?.symbol} {plan.price} per{" "}
|
||||
{frequencyLabel(selectedFrequency)}
|
||||
</span>
|
||||
</small>
|
||||
<small className="d-block mt-1 text-xs border border-primary text-center rounded text-secondary w-min text-nowrap px-1">
|
||||
billed {frequencyLabel(selectedFrequency, true)}
|
||||
</small>
|
||||
</span>
|
||||
</label>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
))}
|
||||
</div>
|
||||
))}
|
||||
</div>
|
||||
|
||||
{selectedPlan && (
|
||||
<div className="col-12 text-start">
|
||||
<div className="border-warning mt-3">
|
||||
{(() => {
|
||||
const selected = plans?.find(
|
||||
(p) => p.planName === selectedPlan
|
||||
(p) => p.id === selectedPlan
|
||||
);
|
||||
if (!selected) return null;
|
||||
|
||||
@ -251,7 +288,6 @@ const SelectPlan = ({ currentStep, setStepStatus, onNext }) => {
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<h6 className="fw-bold text-secondary mb-2">
|
||||
Included Features
|
||||
</h6>
|
||||
@ -275,7 +311,6 @@ const SelectPlan = ({ currentStep, setStepStatus, onNext }) => {
|
||||
</div>
|
||||
))}
|
||||
</div>
|
||||
|
||||
<h6 className="fw-bold text-secondary mt-3 mb-2">
|
||||
Support
|
||||
</h6>
|
||||
@ -299,8 +334,29 @@ const SelectPlan = ({ currentStep, setStepStatus, onNext }) => {
|
||||
</li>
|
||||
)}
|
||||
</ul>
|
||||
|
||||
<hr className="divider border-2" />
|
||||
<div className="d-flex flex-row align-items-end gap-2 mt-1">
|
||||
<div className="">
|
||||
<label className="form-lable ">
|
||||
Apply Coupon
|
||||
<input
|
||||
type="text"
|
||||
{...register("coupon")}
|
||||
className="form-control form-control-sm mt-1"
|
||||
/>
|
||||
</label>
|
||||
</div>
|
||||
<div>
|
||||
{" "}
|
||||
<button className="btn btn-primary btn-sm">
|
||||
Apply
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
{/* {errors.coupon && (<small>{error.coupon.message}</small>)} */}{" "}
|
||||
<small className="m-0">
|
||||
Currently, no coupon codes are available!{" "}
|
||||
</small>
|
||||
<hr className="divider border-2 my-1" />
|
||||
<div className="d-flex flex-column co-12">
|
||||
<div className="d-flex justify-content-between">
|
||||
<h6 className="fs-6 m-0">Duration</h6>
|
||||
@ -312,10 +368,17 @@ const SelectPlan = ({ currentStep, setStepStatus, onNext }) => {
|
||||
<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,
|
||||
})}
|
||||
{formatFigure(
|
||||
frequencyLabel(
|
||||
selectedFrequency,
|
||||
true,
|
||||
true
|
||||
)?.planDurationInInt * price,
|
||||
{
|
||||
type: "currency",
|
||||
currency: currency.currencyCode,
|
||||
}
|
||||
)}
|
||||
</h5>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@ -8,7 +8,7 @@ const SubscriptionLayout = ({
|
||||
}) => {
|
||||
return (
|
||||
<div className="container-fluid stepper-container align-items-start my-4 w-100">
|
||||
<ul className="timeline-horizontal list-unstyled d-flex justify-content-between align-items-center position-relative w-75">
|
||||
<ul className="timeline-horizontal list-unstyled d-flex justify-content-between align-items-center position-relative w-md-75">
|
||||
{configStep.map((step, index) => {
|
||||
const stepNumber = index + 1;
|
||||
const status = stepStatus[stepNumber] || "pending";
|
||||
|
||||
@ -24,4 +24,8 @@ export const OrganizationSchema = z.object({
|
||||
|
||||
export const OrganizationDefaultValue = {
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
export const CouponDiscount = z.object({
|
||||
coupon:z.string().optional()
|
||||
})
|
||||
@ -8,7 +8,7 @@ import SelectPlan from "../../components/UserSubscription/SelectPlan";
|
||||
import Review from "../../components/UserSubscription/Review";
|
||||
|
||||
const MakeSubscription = () => {
|
||||
const [currentStep, setCurrentStep] = useState(1);
|
||||
const [currentStep, setCurrentStep] = useState(2);
|
||||
const [responsePayment, setResponsePayment] = useState(null);
|
||||
|
||||
const [stepStatus, setStepStatus] = useState({
|
||||
|
||||
@ -113,7 +113,7 @@ const SubscriptionPlans = () => {
|
||||
{/* Button */}
|
||||
<div className="mt-auto">
|
||||
<Link
|
||||
to={`/auth/subscripe/${frequency}/${plan.planName}`}
|
||||
to={`/auth/subscripe/${frequency}/${plan.id}`}
|
||||
className="btn btn-outline-primary w-100 fw-bold mb-2"
|
||||
>
|
||||
Subscribe
|
||||
|
||||
@ -122,8 +122,8 @@ const TenantDetails = ({
|
||||
/>
|
||||
)}
|
||||
|
||||
<div className="nav-align-left nav-tabs-shadow mb-6">
|
||||
<ul className="nav nav-tabs py-2 page-min-h" role="tablist">
|
||||
<div className="nav-align-top nav-tabs-shadow mb-6">
|
||||
<ul className="nav nav-tabs " >
|
||||
{tabs.map((tab, index) => (
|
||||
<li key={tab.id} className="nav-item">
|
||||
<button
|
||||
|
||||
@ -74,7 +74,7 @@ const router = createBrowserRouter(
|
||||
],
|
||||
},
|
||||
{ path: "/auth/switch/org", element: <TenantSelectionPage /> },
|
||||
{ path: "/auth/subscripe/:frequency/:planName", element: <MakeSubscription /> },
|
||||
{ path: "/auth/subscripe/:frequency/:planId", element: <MakeSubscription /> },
|
||||
{
|
||||
element: <ProtectedRoute />,
|
||||
errorElement: <ErrorPage />,
|
||||
|
||||
@ -113,7 +113,7 @@ export const formatFigure = (
|
||||
type = "number",
|
||||
currency = "INR",
|
||||
locale = "en-US",
|
||||
notation = "standard", // standard or compact
|
||||
notation = "standard", // standard or compact
|
||||
compactDisplay = "short",
|
||||
minimumFractionDigits = 0,
|
||||
maximumFractionDigits = 2,
|
||||
@ -122,7 +122,12 @@ export const formatFigure = (
|
||||
if (amount == null || isNaN(amount)) return "-";
|
||||
|
||||
const formatterOptions = {
|
||||
style: type === "currency" ? "currency" : type === "percent" ? "percent" : "decimal",
|
||||
style:
|
||||
type === "currency"
|
||||
? "currency"
|
||||
: type === "percent"
|
||||
? "percent"
|
||||
: "decimal",
|
||||
notation: notation,
|
||||
compactDisplay,
|
||||
minimumFractionDigits,
|
||||
@ -136,17 +141,52 @@ export const formatFigure = (
|
||||
return new Intl.NumberFormat(locale, formatterOptions).format(amount);
|
||||
};
|
||||
|
||||
export const frequencyLabel = (freq, isLong = false) => {
|
||||
const frequency = parseInt(freq, 10);
|
||||
export const frequencyLabel = (
|
||||
freq,
|
||||
isLong = false,
|
||||
isMonthRequired = false
|
||||
) => {
|
||||
const frequency = parseInt(freq, 10);
|
||||
switch (frequency) {
|
||||
case 0:
|
||||
return isLong ? "1 Month" : "1 mo";
|
||||
if (isLong && isMonthRequired) {
|
||||
return { planDurationInString: "1 Month", planDurationInInt: 1 };
|
||||
}
|
||||
if (isLong) {
|
||||
return "1 Month";
|
||||
} else {
|
||||
return "1 mon";
|
||||
}
|
||||
case 1:
|
||||
return isLong ? "Quarterly (3 Months)" : "3 mo";
|
||||
if (isLong && isMonthRequired) {
|
||||
return {
|
||||
planDurationInString: "Quarterly (3 Months)",
|
||||
planDurationInInt: 3,
|
||||
};
|
||||
}
|
||||
if (isLong) {
|
||||
return "Quarterly (3 Months)";
|
||||
} else {
|
||||
return "3 mon";
|
||||
}
|
||||
case 2:
|
||||
return isLong ? "6 Months" : "6 mo";
|
||||
if (isLong && isMonthRequired) {
|
||||
return { planDurationInString: "6 Month", planDurationInInt: 6 };
|
||||
}
|
||||
if (isLong) {
|
||||
return "6 Month";
|
||||
} else {
|
||||
return "6 mon";
|
||||
}
|
||||
case 3:
|
||||
return isLong ? "1 Year" : "1 yr";
|
||||
if (isLong && isMonthRequired) {
|
||||
return { planDurationInString: "1 Year", planDurationInInt: 12 };
|
||||
}
|
||||
if (isLong) {
|
||||
return "1 Year";
|
||||
} else {
|
||||
return "1 yr";
|
||||
}
|
||||
default:
|
||||
return isLong ? "Unknown" : "N/A";
|
||||
}
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user