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) {
|
@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-md-contain {
|
||||||
object-fit: contain !important;
|
object-fit: contain !important;
|
||||||
}
|
}
|
||||||
|
|||||||
@ -43,15 +43,14 @@ const SubScriptionHistory = ({ tenantId }) => {
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
// Format dates
|
|
||||||
const end = plan?.endDate ? new Date(plan.endDate) : null;
|
const end = plan?.endDate ? new Date(plan.endDate) : null;
|
||||||
const today = new Date();
|
const today = new Date();
|
||||||
|
|
||||||
const daysLeft = end
|
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;
|
: 0;
|
||||||
|
|
||||||
// Render logic for subscription history table
|
|
||||||
const renderSubscriptionHistory = () => {
|
const renderSubscriptionHistory = () => {
|
||||||
if (!subscriptionHistory || subscriptionHistory.length === 0) {
|
if (!subscriptionHistory || subscriptionHistory.length === 0) {
|
||||||
return (
|
return (
|
||||||
@ -72,23 +71,35 @@ const SubScriptionHistory = ({ tenantId }) => {
|
|||||||
<table className="table border-top dataTable text-nowrap align-middle">
|
<table className="table border-top dataTable text-nowrap align-middle">
|
||||||
<thead className="align-middle">
|
<thead className="align-middle">
|
||||||
<tr>
|
<tr>
|
||||||
<th>Date</th>
|
<th>Invoice</th>
|
||||||
<th>Type</th>
|
|
||||||
<th>Amount</th>
|
<th>Amount</th>
|
||||||
<th>Plan Name</th>
|
<th>Date</th>
|
||||||
<th className="text-center">Action</th>
|
<th>Status</th>
|
||||||
|
<th className="text-center actions-col">Action</th>
|
||||||
</tr>
|
</tr>
|
||||||
</thead>
|
</thead>
|
||||||
<tbody className="align-middle">
|
<tbody className="align-middle">
|
||||||
{sortedHistory.map((item) => (
|
{sortedHistory.map((item) => (
|
||||||
<tr key={item.id}>
|
<tr key={item.id}>
|
||||||
<td>{formatUTCToLocalTime(item?.createdAt)}</td>
|
<td className="fw-medium">{item.planName}</td>
|
||||||
<td>{SUBSCRIPTION_PLAN_FREQUENCIES[item.frequency] || "N/A"}</td>
|
|
||||||
<td>
|
<td className="fw-medium">
|
||||||
{item.currency?.symbol || "₹"} {item.price}
|
{item.currency?.symbol || "₹"} {item.price}
|
||||||
</td>
|
</td>
|
||||||
<td>{item.planName}</td>
|
<td>
|
||||||
<td className="text-center">
|
<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">
|
<div className="dropdown">
|
||||||
<button
|
<button
|
||||||
className="btn btn-icon btn-sm dropdown-toggle hide-arrow"
|
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>
|
<i className="bx bx-dots-vertical-rounded"></i>
|
||||||
</button>
|
</button>
|
||||||
<div className="dropdown-menu dropdown-menu-end">
|
<div className="dropdown-menu dropdown-menu-end">
|
||||||
<button
|
<button className="dropdown-item py-1">
|
||||||
className="dropdown-item py-1"
|
|
||||||
>
|
|
||||||
<i className="bx bx-detail bx-sm"></i> View
|
<i className="bx bx-detail bx-sm"></i> View
|
||||||
</button>
|
</button>
|
||||||
<button
|
<button
|
||||||
@ -108,7 +117,8 @@ const SubScriptionHistory = ({ tenantId }) => {
|
|||||||
console.log("Download clicked for", item.id)
|
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>
|
</button>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
@ -120,100 +130,172 @@ const SubScriptionHistory = ({ tenantId }) => {
|
|||||||
</div>
|
</div>
|
||||||
|
|
||||||
{/* Card-based view for smaller screens */}
|
{/* Card-based view for smaller screens */}
|
||||||
|
|
||||||
</>
|
</>
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className="p-2 p-md-4">
|
<div className="text-start">
|
||||||
<div className="row g-4">
|
<div className="row d-flex justify-content-between py-1">
|
||||||
{/* Left Card: Active Subscription */}
|
<div className="col-md-4 d-flex flex-column gap-6">
|
||||||
<div className="col-12 col-lg-6">
|
<div className="">
|
||||||
<div className="card shadow-sm border rounded p-3 h-100 text-start">
|
<p className=" d-bolck fw-semibold fs-6">Current Plan</p>
|
||||||
<div className="divider text-start mb-3">
|
<small className="d-bolck">
|
||||||
<div className="divider-text">Active Subscription</div>
|
You can update your plan anytime for best benifit from the product
|
||||||
</div>
|
and track your projct
|
||||||
|
</small>
|
||||||
<p className="text-primary fw-bold m-0 fs-4">
|
</div>
|
||||||
{plan.planName || "N/A"}
|
<p className="fw-semibold text-primary">Switch Plan</p>
|
||||||
</p>
|
</div>
|
||||||
{plan.description && (
|
<div className="col-md-6 ">
|
||||||
<p className="m-0 text-muted small">{plan.description}</p>
|
<div className="row border bg-light-primary p-3">
|
||||||
)}
|
<div className="col-1">
|
||||||
|
<small>
|
||||||
<div className="mt-2">
|
<i className="bx bxs-package"></i>
|
||||||
<h3 className="m-0">
|
|
||||||
{plan.currency?.symbol || "₹"} {plan.price}
|
|
||||||
</h3>
|
|
||||||
<small className="text-muted">
|
|
||||||
{SUBSCRIPTION_PLAN_FREQUENCIES[plan.frequency] || ""}
|
|
||||||
</small>
|
</small>
|
||||||
</div>
|
</div>
|
||||||
|
<div className="col-11">
|
||||||
<div className="mt-3 small text-muted">
|
<div className="d-flex justify-content-between">
|
||||||
<div>
|
<div className="d-flex flex-row gap-4">
|
||||||
Activated Since:{" "}
|
<small className="fw-bold">{plan.planName || "N/A"}</small>
|
||||||
{plan.startDate ? formatUTCToLocalTime(plan.startDate) : "N/A"} (
|
<small className="fw-bold text-primary">
|
||||||
{daysLeft} days left)
|
{" "}
|
||||||
|
{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>
|
||||||
<div className="mt-1">
|
<div className="col-12">
|
||||||
Ends on:{" "}
|
<p className="m-0 text-muted text-tiny">
|
||||||
{plan.endDate ? formatUTCToLocalTime(plan.endDate) : "N/A"}
|
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>
|
||||||
</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>
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
{/* Right Card: Subscription History */}
|
<hr className="divider border-2 my-2" />
|
||||||
<div className="col-12 col-lg-6">
|
|
||||||
<div className="card shadow-sm border rounded p-3 h-100">
|
<div className="row d-flex justify-content-between py-1">
|
||||||
<div className="divider text-start mb-3">
|
<div className="col-md-4 d-flex flex-column gap-6">
|
||||||
<div className="divider-text">
|
<div className="">
|
||||||
<i className="bx bx-history"></i> <small>History</small>
|
<p className=" d-bolck fw-semibold fs-6">Billing History</p>
|
||||||
</div>
|
<small className="d-bolck">
|
||||||
</div>
|
Sumary on the payment history for the subscription plan of the
|
||||||
{renderSubscriptionHistory()}
|
application
|
||||||
|
</small>
|
||||||
</div>
|
</div>
|
||||||
|
<p className="fw-semibold text-primary">Billing History</p>
|
||||||
</div>
|
</div>
|
||||||
|
<div className="col-md-6 ">{renderSubscriptionHistory()}</div>
|
||||||
</div>
|
</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 {
|
const {
|
||||||
data: plans,
|
data: plans,
|
||||||
isError: isPlanError,
|
isError: isPlanError,
|
||||||
isLoading,isError,isRefetching,refetch
|
isLoading,
|
||||||
|
isError,
|
||||||
|
isRefetching,
|
||||||
|
refetch,
|
||||||
} = useSubscription(frequency);
|
} = useSubscription(frequency);
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
if (!plans || !selectedPlanId) return;
|
if (!plans || !selectedPlanId) return;
|
||||||
@ -77,7 +80,11 @@ const ProcessedPayment = ({
|
|||||||
alert("Failed to load Razorpay SDK");
|
alert("Failed to load Razorpay SDK");
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
MakePayment({ amount: selectedPlan?.price });
|
let price = 0;
|
||||||
|
price =
|
||||||
|
frequencyLabel(selectedPlan?.frequency, true, true)?.planDurationInInt *
|
||||||
|
selectedPlan?.price;
|
||||||
|
MakePayment({ amount: price });
|
||||||
};
|
};
|
||||||
|
|
||||||
const handleRetry = () => {
|
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 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">
|
<div className="custom-option-header d-flex justify-content-between align-items-center">
|
||||||
<span className="h6 mb-0">
|
<span className="h6 mb-0">
|
||||||
{selectedPlan?.planName}
|
{selectedPlan?.description}
|
||||||
</span>
|
</span>
|
||||||
<i className="bx bx-check-circle text-primary fs-4"></i>
|
<i className="bx bx-check-circle text-primary fs-4"></i>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div className="d-flex justify-content-between mt-1">
|
<div className="d-flex justify-content-between mt-1">
|
||||||
<small className="text-muted">
|
<small>
|
||||||
{selectedPlan?.currency?.symbol} {selectedPlan?.price}{" "}
|
Price -{" "}
|
||||||
/{frequencyLabel(frequency, false)}
|
<span className="fw-medium">
|
||||||
|
{selectedPlan.currency?.symbol} {selectedPlan.price}{" "}
|
||||||
|
per {frequencyLabel(frequency)}
|
||||||
|
</span>
|
||||||
</small>
|
</small>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<span className="custom-option-body d-block mt-1">
|
<div className="d-flex flex-row gap-3 mt-1 align-items-center">
|
||||||
<small>{selectedPlan?.description}</small>
|
<small>{selectedPlan?.planName}</small>
|
||||||
</span>
|
<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>
|
||||||
</div>
|
</div>
|
||||||
)}
|
)}
|
||||||
@ -307,11 +320,18 @@ const ProcessedPayment = ({
|
|||||||
<div className="d-flex justify-content-between">
|
<div className="d-flex justify-content-between">
|
||||||
<h6 className="fs-4">Total Price</h6>
|
<h6 className="fs-4">Total Price</h6>
|
||||||
<h5 className="fs-3">
|
<h5 className="fs-3">
|
||||||
{formatFigure(selectedPlan?.price, {
|
{formatFigure(
|
||||||
type: "currency",
|
frequencyLabel(
|
||||||
currency:
|
selectedPlan?.frequency,
|
||||||
selectedPlan?.currency.currencyCode,
|
true,
|
||||||
})}
|
true
|
||||||
|
)?.planDurationInInt * price,
|
||||||
|
{
|
||||||
|
type: "currency",
|
||||||
|
currency:
|
||||||
|
selectedPlan?.currency.currencyCode,
|
||||||
|
}
|
||||||
|
)}
|
||||||
</h5>
|
</h5>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
@ -375,26 +395,28 @@ const ProcessedPayment = ({
|
|||||||
)}
|
)}
|
||||||
</div>
|
</div>
|
||||||
</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
|
<button
|
||||||
type="submit"
|
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}
|
onClick={handlPrevious}
|
||||||
>
|
>
|
||||||
<i className="bx bx-chevron-left"></i> Previous
|
<i className="bx bx-chevron-left me-1"></i> Previous
|
||||||
</button>
|
</button>
|
||||||
|
|
||||||
<button
|
<button
|
||||||
type="submit"
|
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)}
|
onClick={() => ProcessToPayment(currentPlan?.price)}
|
||||||
|
disabled={isPending}
|
||||||
>
|
>
|
||||||
{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...
|
Wait...
|
||||||
</span>
|
</>
|
||||||
) : (
|
) : (
|
||||||
"Processed To Payment"
|
"Proceed To Payment"
|
||||||
)}
|
)}
|
||||||
</button>
|
</button>
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
@ -6,9 +6,12 @@ import { formatFigure, frequencyLabel } from "../../utils/appUtils";
|
|||||||
import { setSelfTenant } from "../../slices/localVariablesSlice";
|
import { setSelfTenant } from "../../slices/localVariablesSlice";
|
||||||
import SelectedPlanSkeleton from "./SelectedPlanSkeleton";
|
import SelectedPlanSkeleton from "./SelectedPlanSkeleton";
|
||||||
import { error } from "pdf-lib";
|
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 SelectPlan = ({ currentStep, setStepStatus, onNext }) => {
|
||||||
const { frequency, planName } = useParams();
|
const { frequency, planId } = useParams();
|
||||||
const [selectedFrequency, setSelectedFrequency] = useState(
|
const [selectedFrequency, setSelectedFrequency] = useState(
|
||||||
parseInt(frequency)
|
parseInt(frequency)
|
||||||
);
|
);
|
||||||
@ -17,7 +20,7 @@ const SelectPlan = ({ currentStep, setStepStatus, onNext }) => {
|
|||||||
const client = useSelector(
|
const client = useSelector(
|
||||||
(store) => store.localVariables.selfTenant.details
|
(store) => store.localVariables.selfTenant.details
|
||||||
);
|
);
|
||||||
const [selectedPlan, setSelectedPlan] = useState(planName);
|
const [selectedPlan, setSelectedPlan] = useState(planId);
|
||||||
const [currentPlan, setCurrentPlan] = useState(null);
|
const [currentPlan, setCurrentPlan] = useState(null);
|
||||||
const [failPayment, setFailPayment] = useState(null);
|
const [failPayment, setFailPayment] = useState(null);
|
||||||
|
|
||||||
@ -30,25 +33,51 @@ const SelectPlan = ({ currentStep, setStepStatus, onNext }) => {
|
|||||||
isRefetching,
|
isRefetching,
|
||||||
} = useSubscription(selectedFrequency);
|
} = useSubscription(selectedFrequency);
|
||||||
|
|
||||||
const handleChange = (e) => setSelectedPlan(e.target.value);
|
const handleChange = (e) => {
|
||||||
|
setSelectedPlan(e.target.value);
|
||||||
|
};
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
if (!plans || !selectedPlan) return;
|
if (!plans || plans.length === 0) return;
|
||||||
const selected = plans.find((p) => p.planName === selectedPlan);
|
|
||||||
if (selected) {
|
// Prefer route param if exists, else default to first plan
|
||||||
setCurrentPlan(selected);
|
const matchingPlan = plans.find((p) => p.planId === planId) || plans[0];
|
||||||
dispatch(
|
|
||||||
setSelfTenant({ planId: selected.id, frequency: selectedFrequency })
|
setSelectedPlan(matchingPlan.id);
|
||||||
);
|
setCurrentPlan(matchingPlan);
|
||||||
}
|
|
||||||
}, [plans, selectedPlan, dispatch, selectedFrequency]);
|
// Dispatch correct plan + frequency only once data is ready
|
||||||
|
dispatch(
|
||||||
|
setSelfTenant({
|
||||||
|
planId: matchingPlan.id,
|
||||||
|
frequency: selectedFrequency,
|
||||||
|
})
|
||||||
|
);
|
||||||
|
}, [plans, selectedFrequency, planId, dispatch]);
|
||||||
|
|
||||||
const handleNextStep = () => {
|
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" }));
|
setStepStatus((prev) => ({ ...prev, 2: "success" }));
|
||||||
onNext();
|
onNext();
|
||||||
};
|
};
|
||||||
|
|
||||||
|
const {
|
||||||
|
register,
|
||||||
|
handleSubmit,
|
||||||
|
formState: { errors },
|
||||||
|
} = useForm({ resolver: zodResolver(CouponDiscount) });
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className="container text-start ">
|
<div className="container text-start ">
|
||||||
<div className="row gy-5 align-items-center justify-content-around">
|
<div className="row gy-5 align-items-center justify-content-around">
|
||||||
@ -171,51 +200,59 @@ const SelectPlan = ({ currentStep, setStepStatus, onNext }) => {
|
|||||||
<SelectedPlanSkeleton />
|
<SelectedPlanSkeleton />
|
||||||
) : (
|
) : (
|
||||||
<>
|
<>
|
||||||
<div className="row">
|
<div className="row">
|
||||||
{plans?.map((plan) => (
|
{plans?.map((plan) => (
|
||||||
<div key={plan?.id} className="col-12 col-md-4 mb-md-3 mb-2">
|
|
||||||
<div
|
<div
|
||||||
className={`form-check custom-option custom-option-basic text-start w-100 bg-light-primary ${
|
key={plan?.id}
|
||||||
selectedPlan === plan?.planName
|
className="col-12 col-md-4 mb-md-3 mb-2"
|
||||||
? "border border-primary shadow-md"
|
|
||||||
: ""
|
|
||||||
}`}
|
|
||||||
>
|
>
|
||||||
<label
|
<div
|
||||||
className="form-check-label custom-option-content w-100"
|
className={`form-check custom-option custom-option-basic text-start w-100 bg-light-primary ${
|
||||||
htmlFor={`customRadioTemp${plan?.id}`}
|
selectedPlan === plan?.id
|
||||||
|
? "border border-primary shadow-md"
|
||||||
|
: ""
|
||||||
|
}`}
|
||||||
>
|
>
|
||||||
<input
|
<label
|
||||||
name="customRadioTemp"
|
className="form-check-label custom-option-content w-100"
|
||||||
className="form-check-input"
|
htmlFor={`customRadioTemp${plan?.id}`}
|
||||||
type="radio"
|
>
|
||||||
value={plan?.planName}
|
<input
|
||||||
id={`customRadioTemp${plan?.id}`}
|
name="customRadioTemp"
|
||||||
checked={selectedPlan === plan?.planName}
|
className="form-check-input"
|
||||||
onChange={handleChange}
|
type="radio"
|
||||||
/>
|
value={plan?.id}
|
||||||
<span className="custom-option-header d-flex justify-content-between align-items-center">
|
id={`customRadioTemp${plan?.id}`}
|
||||||
<span className="h6 mb-0">{plan?.planName}</span>
|
checked={selectedPlan === plan?.id}
|
||||||
<span>
|
onChange={handleChange}
|
||||||
{plan.currency?.symbol} {plan.price} /{" "}
|
/>
|
||||||
{frequencyLabel(selectedFrequency)}
|
<span className="custom-option-header d-flex justify-content-between align-items-center">
|
||||||
|
<span className="h6 mb-0">{plan?.description}</span>
|
||||||
</span>
|
</span>
|
||||||
</span>
|
<span className="custom-option-body d-block ">
|
||||||
<span className="custom-option-body d-block mt-1">
|
<small>
|
||||||
<small>{plan?.description}</small>
|
Price -{" "}
|
||||||
</span>
|
<span className="fw-medium">
|
||||||
</label>
|
{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>
|
||||||
</div>
|
|
||||||
|
|
||||||
{selectedPlan && (
|
{selectedPlan && (
|
||||||
<div className="col-12 text-start">
|
<div className="col-12 text-start">
|
||||||
<div className="border-warning mt-3">
|
<div className="border-warning mt-3">
|
||||||
{(() => {
|
{(() => {
|
||||||
const selected = plans?.find(
|
const selected = plans?.find(
|
||||||
(p) => p.planName === selectedPlan
|
(p) => p.id === selectedPlan
|
||||||
);
|
);
|
||||||
if (!selected) return null;
|
if (!selected) return null;
|
||||||
|
|
||||||
@ -251,7 +288,6 @@ const SelectPlan = ({ currentStep, setStepStatus, onNext }) => {
|
|||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<h6 className="fw-bold text-secondary mb-2">
|
<h6 className="fw-bold text-secondary mb-2">
|
||||||
Included Features
|
Included Features
|
||||||
</h6>
|
</h6>
|
||||||
@ -275,7 +311,6 @@ const SelectPlan = ({ currentStep, setStepStatus, onNext }) => {
|
|||||||
</div>
|
</div>
|
||||||
))}
|
))}
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<h6 className="fw-bold text-secondary mt-3 mb-2">
|
<h6 className="fw-bold text-secondary mt-3 mb-2">
|
||||||
Support
|
Support
|
||||||
</h6>
|
</h6>
|
||||||
@ -299,8 +334,29 @@ const SelectPlan = ({ currentStep, setStepStatus, onNext }) => {
|
|||||||
</li>
|
</li>
|
||||||
)}
|
)}
|
||||||
</ul>
|
</ul>
|
||||||
|
<div className="d-flex flex-row align-items-end gap-2 mt-1">
|
||||||
<hr className="divider border-2" />
|
<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 flex-column co-12">
|
||||||
<div className="d-flex justify-content-between">
|
<div className="d-flex justify-content-between">
|
||||||
<h6 className="fs-6 m-0">Duration</h6>
|
<h6 className="fs-6 m-0">Duration</h6>
|
||||||
@ -312,10 +368,17 @@ const SelectPlan = ({ currentStep, setStepStatus, onNext }) => {
|
|||||||
<div className="d-flex justify-content-between">
|
<div className="d-flex justify-content-between">
|
||||||
<h6 className="fs-4">Total Price</h6>
|
<h6 className="fs-4">Total Price</h6>
|
||||||
<h5 className="fs-3">
|
<h5 className="fs-3">
|
||||||
{formatFigure(price, {
|
{formatFigure(
|
||||||
type: "currency",
|
frequencyLabel(
|
||||||
currency: currency.currencyCode,
|
selectedFrequency,
|
||||||
})}
|
true,
|
||||||
|
true
|
||||||
|
)?.planDurationInInt * price,
|
||||||
|
{
|
||||||
|
type: "currency",
|
||||||
|
currency: currency.currencyCode,
|
||||||
|
}
|
||||||
|
)}
|
||||||
</h5>
|
</h5>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
@ -8,7 +8,7 @@ const SubscriptionLayout = ({
|
|||||||
}) => {
|
}) => {
|
||||||
return (
|
return (
|
||||||
<div className="container-fluid stepper-container align-items-start my-4 w-100">
|
<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) => {
|
{configStep.map((step, index) => {
|
||||||
const stepNumber = index + 1;
|
const stepNumber = index + 1;
|
||||||
const status = stepStatus[stepNumber] || "pending";
|
const status = stepStatus[stepNumber] || "pending";
|
||||||
|
|||||||
@ -24,4 +24,8 @@ export const OrganizationSchema = z.object({
|
|||||||
|
|
||||||
export const OrganizationDefaultValue = {
|
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";
|
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({
|
||||||
|
|||||||
@ -113,7 +113,7 @@ const SubscriptionPlans = () => {
|
|||||||
{/* Button */}
|
{/* Button */}
|
||||||
<div className="mt-auto">
|
<div className="mt-auto">
|
||||||
<Link
|
<Link
|
||||||
to={`/auth/subscripe/${frequency}/${plan.planName}`}
|
to={`/auth/subscripe/${frequency}/${plan.id}`}
|
||||||
className="btn btn-outline-primary w-100 fw-bold mb-2"
|
className="btn btn-outline-primary w-100 fw-bold mb-2"
|
||||||
>
|
>
|
||||||
Subscribe
|
Subscribe
|
||||||
|
|||||||
@ -122,8 +122,8 @@ const TenantDetails = ({
|
|||||||
/>
|
/>
|
||||||
)}
|
)}
|
||||||
|
|
||||||
<div className="nav-align-left nav-tabs-shadow mb-6">
|
<div className="nav-align-top nav-tabs-shadow mb-6">
|
||||||
<ul className="nav nav-tabs py-2 page-min-h" role="tablist">
|
<ul className="nav nav-tabs " >
|
||||||
{tabs.map((tab, index) => (
|
{tabs.map((tab, index) => (
|
||||||
<li key={tab.id} className="nav-item">
|
<li key={tab.id} className="nav-item">
|
||||||
<button
|
<button
|
||||||
|
|||||||
@ -74,7 +74,7 @@ const router = createBrowserRouter(
|
|||||||
],
|
],
|
||||||
},
|
},
|
||||||
{ path: "/auth/switch/org", element: <TenantSelectionPage /> },
|
{ path: "/auth/switch/org", element: <TenantSelectionPage /> },
|
||||||
{ path: "/auth/subscripe/:frequency/:planName", element: <MakeSubscription /> },
|
{ path: "/auth/subscripe/:frequency/:planId", element: <MakeSubscription /> },
|
||||||
{
|
{
|
||||||
element: <ProtectedRoute />,
|
element: <ProtectedRoute />,
|
||||||
errorElement: <ErrorPage />,
|
errorElement: <ErrorPage />,
|
||||||
|
|||||||
@ -113,7 +113,7 @@ export const formatFigure = (
|
|||||||
type = "number",
|
type = "number",
|
||||||
currency = "INR",
|
currency = "INR",
|
||||||
locale = "en-US",
|
locale = "en-US",
|
||||||
notation = "standard", // standard or compact
|
notation = "standard", // standard or compact
|
||||||
compactDisplay = "short",
|
compactDisplay = "short",
|
||||||
minimumFractionDigits = 0,
|
minimumFractionDigits = 0,
|
||||||
maximumFractionDigits = 2,
|
maximumFractionDigits = 2,
|
||||||
@ -122,7 +122,12 @@ export const formatFigure = (
|
|||||||
if (amount == null || isNaN(amount)) return "-";
|
if (amount == null || isNaN(amount)) return "-";
|
||||||
|
|
||||||
const formatterOptions = {
|
const formatterOptions = {
|
||||||
style: type === "currency" ? "currency" : type === "percent" ? "percent" : "decimal",
|
style:
|
||||||
|
type === "currency"
|
||||||
|
? "currency"
|
||||||
|
: type === "percent"
|
||||||
|
? "percent"
|
||||||
|
: "decimal",
|
||||||
notation: notation,
|
notation: notation,
|
||||||
compactDisplay,
|
compactDisplay,
|
||||||
minimumFractionDigits,
|
minimumFractionDigits,
|
||||||
@ -136,17 +141,52 @@ 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 = (
|
||||||
const frequency = parseInt(freq, 10);
|
freq,
|
||||||
|
isLong = false,
|
||||||
|
isMonthRequired = false
|
||||||
|
) => {
|
||||||
|
const frequency = parseInt(freq, 10);
|
||||||
switch (frequency) {
|
switch (frequency) {
|
||||||
case 0:
|
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:
|
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:
|
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:
|
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:
|
default:
|
||||||
return isLong ? "Unknown" : "N/A";
|
return isLong ? "Unknown" : "N/A";
|
||||||
}
|
}
|
||||||
|
|||||||
Loading…
x
Reference in New Issue
Block a user