425 lines
17 KiB
JavaScript
425 lines
17 KiB
JavaScript
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";
|
||
import { error } from "pdf-lib";
|
||
import { useForm } from "react-hook-form";
|
||
import { zodResolver } from "@hookform/resolvers/zod";
|
||
import { CouponDiscount } from "../../pages/Home/HomeSchema";
|
||
import SelectedPlanSkeleton from "./SelectedPlaneSkeleton";
|
||
|
||
const SelectPlan = ({ currentStep, setStepStatus, onNext }) => {
|
||
const { frequency, planId } = useParams();
|
||
const [selectedFrequency, setSelectedFrequency] = useState(
|
||
parseInt(frequency)
|
||
);
|
||
const dispatch = useDispatch();
|
||
|
||
const client = useSelector(
|
||
(store) => store.localVariables.selfTenant.details
|
||
);
|
||
const [selectedPlan, setSelectedPlan] = useState(planId);
|
||
const [currentPlan, setCurrentPlan] = useState(null);
|
||
const [failPayment, setFailPayment] = useState(null);
|
||
|
||
const {
|
||
data: plans,
|
||
isError,
|
||
isLoading,
|
||
error,
|
||
refetch,
|
||
isRefetching,
|
||
} = useSubscription(selectedFrequency);
|
||
|
||
const handleChange = (e) => {
|
||
setSelectedPlan(e.target.value);
|
||
};
|
||
|
||
useEffect(() => {
|
||
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 = () => {
|
||
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">
|
||
<div className="col-sm-12 col-md-8">
|
||
<div className="row">
|
||
<div className="col-12 mb-3 text-start">
|
||
<h4>Choose the Perfect Plan for Your Organization</h4>
|
||
<p className="text-muted small mb-3">
|
||
Select a plan that fits your team’s needs and unlock the
|
||
features that drive productivity.
|
||
</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>
|
||
|
||
{isError && (
|
||
<div className="col-12 col-md-6 text-center">
|
||
<p className="text-muted">{error?.message}</p>
|
||
<small>{error?.name}</small>
|
||
<small
|
||
className={`text-muted ${
|
||
isRefetching ? "cursor-notallowed" : "cursor-pointer"
|
||
}`}
|
||
onClick={refetch}
|
||
>
|
||
{isRefetching ? (
|
||
<>
|
||
<i
|
||
className={`bx bx-loader-alt ${
|
||
isRefetching ? "bx-spin" : ""
|
||
}`}
|
||
></i>{" "}
|
||
Retrying...
|
||
</>
|
||
) : (
|
||
"Try to refetch"
|
||
)}
|
||
</small>
|
||
</div>
|
||
)}
|
||
|
||
{isLoading ? (
|
||
<SelectedPlanSkeleton />
|
||
) : (
|
||
<>
|
||
<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?.id
|
||
? "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?.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 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>
|
||
|
||
{selectedPlan && (
|
||
<div className="col-12 text-start">
|
||
<div className="border-warning mt-3">
|
||
{(() => {
|
||
const selected = plans?.find(
|
||
(p) => p.id === 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-12 col-md-4 text-center">
|
||
<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-12 col-md-4 text-center">
|
||
<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-12 col-md-4 text-center">
|
||
<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>
|
||
<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>
|
||
<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(
|
||
frequencyLabel(
|
||
selectedFrequency,
|
||
true,
|
||
true
|
||
)?.planDurationInInt * price,
|
||
{
|
||
type: "currency",
|
||
currency: currency.currencyCode,
|
||
}
|
||
)}
|
||
</h5>
|
||
</div>
|
||
</div>
|
||
</>
|
||
);
|
||
})()}
|
||
</div>
|
||
</div>
|
||
)}
|
||
</>
|
||
)}
|
||
</div>
|
||
</div>
|
||
|
||
{/* Image Section */}
|
||
<div className="d-none d-md-flex col-md-4 justify-content-center align-items-center">
|
||
<img
|
||
src="/img/illustrations/undraw_pricing.svg"
|
||
alt="image"
|
||
className="img-fluid"
|
||
style={{
|
||
maxWidth: "100%",
|
||
height: "auto",
|
||
objectFit: "contain",
|
||
}}
|
||
/>
|
||
</div>
|
||
</div>
|
||
|
||
<div className="col-12 d-flex justify-content-end m-md-0 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>
|
||
);
|
||
};
|
||
|
||
export default SelectPlan;
|