migrated suscripton onfield to main sub-branch
This commit is contained in:
parent
31a8329254
commit
a656907695
@ -2,16 +2,19 @@ import React, { useState, useMemo, useEffect } from "react";
|
|||||||
import { useSubscription } from "../../hooks/useAuth";
|
import { useSubscription } from "../../hooks/useAuth";
|
||||||
import { useParams } from "react-router-dom";
|
import { useParams } from "react-router-dom";
|
||||||
import { useCreateTenant, useIndustries } from "../../hooks/useTenant";
|
import { useCreateTenant, useIndustries } from "../../hooks/useTenant";
|
||||||
|
import {
|
||||||
|
formatCurrency,
|
||||||
|
formatFigure,
|
||||||
|
frequencyLabel,
|
||||||
|
} from "../../utils/appUtils";
|
||||||
import { formatUTCToLocalTime } from "../../utils/dateUtils";
|
import { formatUTCToLocalTime } from "../../utils/dateUtils";
|
||||||
import { PaymentRepository } from "../../repositories/PaymentRepository";
|
import { PaymentRepository } from "../../repositories/PaymentRepository";
|
||||||
import { useDispatch, useSelector } from "react-redux";
|
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 "./SelectedPlaneSkeleton";
|
||||||
import { useMakePayment } from "../../hooks/usePayments";
|
import { useMakePayment } from "../../hooks/usePayments";
|
||||||
import SelectedPlaneSkeleton from "./SelectedPlaneSkeleton";
|
|
||||||
import { formatFigure, frequencyLabel } from "../../utils/appUtils";
|
|
||||||
|
|
||||||
const ProcessedPayment = ({
|
const ProcessedPayment = ({
|
||||||
onNext,
|
onNext,
|
||||||
@ -35,6 +38,9 @@ const ProcessedPayment = ({
|
|||||||
data: plans,
|
data: plans,
|
||||||
isError: isPlanError,
|
isError: isPlanError,
|
||||||
isLoading,
|
isLoading,
|
||||||
|
isError,
|
||||||
|
isRefetching,
|
||||||
|
refetch,
|
||||||
} = useSubscription(frequency);
|
} = useSubscription(frequency);
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
if (!plans || !selectedPlanId) return;
|
if (!plans || !selectedPlanId) return;
|
||||||
@ -74,11 +80,11 @@ const ProcessedPayment = ({
|
|||||||
alert("Failed to load Razorpay SDK");
|
alert("Failed to load Razorpay SDK");
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
const payload = {
|
let price = 0;
|
||||||
amount: { amount: selectedPlan?.price },
|
price =
|
||||||
currencyCode: selectedPlan?.currency?.currencyCode,
|
frequencyLabel(selectedPlan?.frequency, true, true)?.planDurationInInt *
|
||||||
};
|
selectedPlan?.price;
|
||||||
MakePayment(payload);
|
MakePayment({ amount: price });
|
||||||
};
|
};
|
||||||
|
|
||||||
const handleRetry = () => {
|
const handleRetry = () => {
|
||||||
@ -144,7 +150,7 @@ const ProcessedPayment = ({
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
return (
|
return (
|
||||||
<div className="container-sm text-start ">
|
<div className="container-md text-start ">
|
||||||
<div className="row gx-1 gy-3 justify-content-between">
|
<div className="row gx-1 gy-3 justify-content-between">
|
||||||
<div className="col-12 col-md-6">
|
<div className="col-12 col-md-6">
|
||||||
<div className="row">
|
<div className="row">
|
||||||
@ -155,9 +161,33 @@ const ProcessedPayment = ({
|
|||||||
and help you maximize productivity.
|
and help you maximize productivity.
|
||||||
</p>
|
</p>
|
||||||
</div>
|
</div>
|
||||||
|
{isError && (
|
||||||
|
<div className="col-12 col-md 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 ? (
|
{isLoading ? (
|
||||||
<SelectedPlaneSkeleton />
|
<SelectedPlanSkeleton />
|
||||||
) : (
|
) : (
|
||||||
<>
|
<>
|
||||||
{selectedPlan && (
|
{selectedPlan && (
|
||||||
@ -165,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>
|
||||||
)}
|
)}
|
||||||
@ -284,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>
|
||||||
@ -319,7 +362,7 @@ const ProcessedPayment = ({
|
|||||||
<div className="col-sm-6">
|
<div className="col-sm-6">
|
||||||
<strong>Email:</strong>
|
<strong>Email:</strong>
|
||||||
</div>
|
</div>
|
||||||
<div className="col-sm-6 mb-2">{client.email}</div>
|
<div className="col-sm-6 mb-2 text-wrap">{client.email}</div>
|
||||||
|
|
||||||
<div className="col-sm-6 mb-2">
|
<div className="col-sm-6 mb-2">
|
||||||
<strong>Contact Number:</strong>
|
<strong>Contact Number:</strong>
|
||||||
@ -352,20 +395,29 @@ 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 ? "Please Wait..." : "Processed To Payment"}
|
{isPending ? (
|
||||||
|
<>
|
||||||
|
<i className="bx bx-loader-alt bx-md bx-spin me-2"></i> Please
|
||||||
|
Wait...
|
||||||
|
</>
|
||||||
|
) : (
|
||||||
|
"Proceed To Payment"
|
||||||
|
)}
|
||||||
</button>
|
</button>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
11
src/components/UserSubscription/Review.jsx
Normal file
11
src/components/UserSubscription/Review.jsx
Normal file
@ -0,0 +1,11 @@
|
|||||||
|
import React from 'react'
|
||||||
|
|
||||||
|
const Review = () => {
|
||||||
|
return (
|
||||||
|
<div>
|
||||||
|
|
||||||
|
</div>
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
export default Review
|
||||||
@ -4,11 +4,14 @@ 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 { 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";
|
import SelectedPlanSkeleton from "./SelectedPlaneSkeleton";
|
||||||
|
|
||||||
|
|
||||||
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,39 +20,68 @@ 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);
|
||||||
|
|
||||||
const {
|
const {
|
||||||
data: plans,
|
data: plans,
|
||||||
isError: isPlanError,
|
isError,
|
||||||
isLoading,
|
isLoading,
|
||||||
|
error,
|
||||||
|
refetch,
|
||||||
|
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 gx-4 gy-5 align-items-center justify-content-around">
|
<div className="row gy-5 align-items-center justify-content-around">
|
||||||
<div className="col-sm-12 col-md-6">
|
<div className="col-sm-12 col-md-8">
|
||||||
<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>
|
||||||
@ -138,53 +170,89 @@ const SelectPlan = ({ currentStep, setStepStatus, onNext }) => {
|
|||||||
</div>
|
</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 ? (
|
{isLoading ? (
|
||||||
<SelectedPlanSkeleton />
|
<SelectedPlanSkeleton />
|
||||||
) : (
|
) : (
|
||||||
<>
|
<>
|
||||||
{plans?.map((plan) => (
|
<div className="row">
|
||||||
<div key={plan?.id} className="col-12 col-md-4 mb-md-3 mb-2">
|
{plans?.map((plan) => (
|
||||||
<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>
|
||||||
|
|
||||||
{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;
|
||||||
|
|
||||||
@ -201,26 +269,25 @@ const SelectPlan = ({ currentStep, setStepStatus, onNext }) => {
|
|||||||
return (
|
return (
|
||||||
<>
|
<>
|
||||||
<div className="row g-2 mb-3">
|
<div className="row g-2 mb-3">
|
||||||
<div className="col-sm-6 col-md-4">
|
<div className="col-sm-12 col-md-4 text-center">
|
||||||
<div className="border rounded-3 p-2 bg-light">
|
<div className="border rounded-3 p-2 bg-light">
|
||||||
<i className="bx bx-user me-1 text-primary"></i>
|
<i className="bx bx-user me-1 text-primary"></i>
|
||||||
<strong>Max Users:</strong> {maxUser}
|
<strong>Max Users:</strong> {maxUser}
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div className="col-sm-6 col-md-4">
|
<div className="col-sm-12 col-md-4 text-center">
|
||||||
<div className="border rounded-3 p-2 bg-light">
|
<div className="border rounded-3 p-2 bg-light">
|
||||||
<i className="bx bx-hdd me-1 text-primary"></i>
|
<i className="bx bx-hdd me-1 text-primary"></i>
|
||||||
<strong>Max Storage:</strong> {maxStorage} MB
|
<strong>Max Storage:</strong> {maxStorage} MB
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div className="col-sm-6 col-md-4">
|
<div className="col-sm-12 col-md-4 text-center">
|
||||||
<div className="border rounded-3 p-2 bg-light">
|
<div className="border rounded-3 p-2 bg-light">
|
||||||
<i className="bx bx-time-five me-1 text-primary"></i>
|
<i className="bx bx-time-five me-1 text-primary"></i>
|
||||||
<strong>Trial Days:</strong> {trialDays}
|
<strong>Trial Days:</strong> {trialDays}
|
||||||
</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>
|
||||||
@ -244,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>
|
||||||
@ -268,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>
|
||||||
@ -281,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>
|
||||||
@ -300,13 +394,13 @@ const SelectPlan = ({ currentStep, setStepStatus, onNext }) => {
|
|||||||
</div>
|
</div>
|
||||||
|
|
||||||
{/* Image Section */}
|
{/* Image Section */}
|
||||||
<div className="d-none d-md-flex col-md-6 justify-content-center align-items-center">
|
<div className="d-none d-md-flex col-md-4 justify-content-center align-items-center">
|
||||||
<img
|
<img
|
||||||
src="/public/img/illustrations/undraw_pricing.svg"
|
src="/img/illustrations/undraw_pricing.svg"
|
||||||
alt="image"
|
alt="image"
|
||||||
className="img-fluid"
|
className="img-fluid"
|
||||||
style={{
|
style={{
|
||||||
maxWidth: "70%",
|
maxWidth: "100%",
|
||||||
height: "auto",
|
height: "auto",
|
||||||
objectFit: "contain",
|
objectFit: "contain",
|
||||||
}}
|
}}
|
||||||
@ -314,7 +408,7 @@ const SelectPlan = ({ currentStep, setStepStatus, onNext }) => {
|
|||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div className="col-12 d-flex justify-content-end mt-3">
|
<div className="col-12 d-flex justify-content-end m-md-0 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 me-2"
|
||||||
|
|||||||
@ -11,7 +11,7 @@ const SkeletonLine = ({ height = 16, width = "100%", className = "" }) => (
|
|||||||
></div>
|
></div>
|
||||||
);
|
);
|
||||||
|
|
||||||
const SelectedPlaneSkeleton = () => {
|
const SelectedPlanSkeleton = () => {
|
||||||
return (
|
return (
|
||||||
<div className="row">
|
<div className="row">
|
||||||
{/* Plan Summary Card */}
|
{/* Plan Summary Card */}
|
||||||
@ -85,4 +85,4 @@ const SelectedPlaneSkeleton = () => {
|
|||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|
||||||
export default SelectedPlaneSkeleton;
|
export default SelectedPlanSkeleton;
|
||||||
|
|||||||
@ -27,7 +27,6 @@ const SubscriptionForm = ({ currentStep, setCurrentStep, setStepStatus }) => {
|
|||||||
|
|
||||||
const { mutate: CreateTenant, isPending } = useCreateSelfTenant(
|
const { mutate: CreateTenant, isPending } = useCreateSelfTenant(
|
||||||
(resp) => {
|
(resp) => {
|
||||||
debugger
|
|
||||||
setStepStatus((prev) => ({ ...prev, [currentStep]: "success" }));
|
setStepStatus((prev) => ({ ...prev, [currentStep]: "success" }));
|
||||||
setCurrentStep((prev) => prev + 1);
|
setCurrentStep((prev) => prev + 1);
|
||||||
},
|
},
|
||||||
@ -46,6 +45,7 @@ const SubscriptionForm = ({ currentStep, setCurrentStep, setStepStatus }) => {
|
|||||||
<div className="col-12 mt-8">
|
<div className="col-12 mt-8">
|
||||||
<div className="row px-4">
|
<div className="row px-4">
|
||||||
<div className="text-start">
|
<div className="text-start">
|
||||||
|
<p>Please provide your personal and organizational information to help us set up your account.</p>
|
||||||
<form onSubmit={handleSubmit(onSubmit)}>
|
<form onSubmit={handleSubmit(onSubmit)}>
|
||||||
<div className="row">
|
<div className="row">
|
||||||
{/* First Name */}
|
{/* First Name */}
|
||||||
@ -112,6 +112,7 @@ const SubscriptionForm = ({ currentStep, setCurrentStep, setStepStatus }) => {
|
|||||||
{...register("contactNumber")}
|
{...register("contactNumber")}
|
||||||
inputMode="tel"
|
inputMode="tel"
|
||||||
placeholder="+91 9876543210"
|
placeholder="+91 9876543210"
|
||||||
|
maxLength={13}
|
||||||
/>
|
/>
|
||||||
{errors.contactNumber && (
|
{errors.contactNumber && (
|
||||||
<div className="danger-text">
|
<div className="danger-text">
|
||||||
@ -160,6 +161,7 @@ const SubscriptionForm = ({ currentStep, setCurrentStep, setStepStatus }) => {
|
|||||||
<Label htmlFor="organizationSize" required>
|
<Label htmlFor="organizationSize" required>
|
||||||
Organization Size
|
Organization Size
|
||||||
</Label>
|
</Label>
|
||||||
|
|
||||||
<select
|
<select
|
||||||
id="organizationSize"
|
id="organizationSize"
|
||||||
className="form-select shadow-none border py-1 px-2"
|
className="form-select shadow-none border py-1 px-2"
|
||||||
@ -168,12 +170,14 @@ const SubscriptionForm = ({ currentStep, setCurrentStep, setStepStatus }) => {
|
|||||||
required: "Organization size is required",
|
required: "Organization size is required",
|
||||||
})}
|
})}
|
||||||
>
|
>
|
||||||
{orgSize.map((org) => (
|
<option value="">Select Organization Size</option>
|
||||||
|
{orgSize?.map((org) => (
|
||||||
<option key={org.val} value={org.val}>
|
<option key={org.val} value={org.val}>
|
||||||
{org.name}
|
{org.name}
|
||||||
</option>
|
</option>
|
||||||
))}
|
))}
|
||||||
</select>
|
</select>
|
||||||
|
|
||||||
{errors.organizationSize && (
|
{errors.organizationSize && (
|
||||||
<div className="danger-text">
|
<div className="danger-text">
|
||||||
{errors.organizationSize.message}
|
{errors.organizationSize.message}
|
||||||
@ -194,13 +198,17 @@ const SubscriptionForm = ({ currentStep, setCurrentStep, setStepStatus }) => {
|
|||||||
{industryLoading ? (
|
{industryLoading ? (
|
||||||
<option value="">Loading...</option>
|
<option value="">Loading...</option>
|
||||||
) : (
|
) : (
|
||||||
data?.map((indu) => (
|
<>
|
||||||
<option key={indu.id} value={indu.id}>
|
<option value="">Select Industry</option>
|
||||||
{indu.name}
|
{data?.map((indu) => (
|
||||||
</option>
|
<option key={indu.id} value={indu.id}>
|
||||||
))
|
{indu.name}
|
||||||
|
</option>
|
||||||
|
))}
|
||||||
|
</>
|
||||||
)}
|
)}
|
||||||
</select>
|
</select>
|
||||||
|
|
||||||
{errors.industryId && (
|
{errors.industryId && (
|
||||||
<div className="danger-text">
|
<div className="danger-text">
|
||||||
{errors.industryId.message}
|
{errors.industryId.message}
|
||||||
@ -218,6 +226,7 @@ const SubscriptionForm = ({ currentStep, setCurrentStep, setStepStatus }) => {
|
|||||||
className="form-select shadow-none border py-1 px-2 small"
|
className="form-select shadow-none border py-1 px-2 small"
|
||||||
{...register("reference")}
|
{...register("reference")}
|
||||||
>
|
>
|
||||||
|
<option value="">Select Reference</option>
|
||||||
{reference.map((org) => (
|
{reference.map((org) => (
|
||||||
<option key={org.val} value={org.val}>
|
<option key={org.val} value={org.val}>
|
||||||
{org.name}
|
{org.name}
|
||||||
@ -238,7 +247,10 @@ const SubscriptionForm = ({ currentStep, setCurrentStep, setStepStatus }) => {
|
|||||||
className="btn btn-label-primary d-flex align-items-center me-2"
|
className="btn btn-label-primary d-flex align-items-center me-2"
|
||||||
>
|
>
|
||||||
{isPending ? (
|
{isPending ? (
|
||||||
<span><i className='bx bx-loader-alt bx-md bx-spin me-2'></i>Please Wait...</span>
|
<span>
|
||||||
|
<i className="bx bx-loader-alt bx-md bx-spin me-2"></i>
|
||||||
|
Please Wait...
|
||||||
|
</span>
|
||||||
) : (
|
) : (
|
||||||
<>
|
<>
|
||||||
<span className="me-1">Next</span>
|
<span className="me-1">Next</span>
|
||||||
|
|||||||
@ -7,8 +7,8 @@ const SubscriptionLayout = ({
|
|||||||
stepStatus = {},
|
stepStatus = {},
|
||||||
}) => {
|
}) => {
|
||||||
return (
|
return (
|
||||||
<div className="container-fluid stepper-container align-items-start my-4">
|
<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-100 ">
|
<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";
|
||||||
|
|||||||
@ -144,3 +144,4 @@ const VerifiedPayment = ({ responsePayment, setStepStatus }) => {
|
|||||||
|
|
||||||
export default VerifiedPayment;
|
export default VerifiedPayment;
|
||||||
|
|
||||||
|
|
||||||
|
|||||||
@ -5,7 +5,7 @@ import { useDispatch, useSelector } from "react-redux";
|
|||||||
import { blockUI, unblockUI } from "../utils/blockUI";
|
import { blockUI, unblockUI } from "../utils/blockUI";
|
||||||
import { setSelfTenant } from "../slices/localVariablesSlice";
|
import { setSelfTenant } from "../slices/localVariablesSlice";
|
||||||
|
|
||||||
export const removeRazorpayArtifacts = () => {
|
export const removeRazorpayArtifacts=()=> {
|
||||||
try {
|
try {
|
||||||
document
|
document
|
||||||
.querySelectorAll("iframe[src*='razorpay'], iframe[name^='__PRIVATE']")
|
.querySelectorAll("iframe[src*='razorpay'], iframe[name^='__PRIVATE']")
|
||||||
@ -40,9 +40,9 @@ export const removeRazorpayArtifacts = () => {
|
|||||||
} catch (err) {
|
} catch (err) {
|
||||||
console.warn(" Error while cleaning Razorpay artifacts:", err);
|
console.warn(" Error while cleaning Razorpay artifacts:", err);
|
||||||
}
|
}
|
||||||
};
|
}
|
||||||
|
|
||||||
const closeRazorpayPopup = () => {
|
const closeRazorpayPopup=()=> {
|
||||||
try {
|
try {
|
||||||
if (window.Razorpay && typeof window.Razorpay.close === "function") {
|
if (window.Razorpay && typeof window.Razorpay.close === "function") {
|
||||||
window.Razorpay.close();
|
window.Razorpay.close();
|
||||||
@ -53,17 +53,17 @@ const closeRazorpayPopup = () => {
|
|||||||
console.warn(" Error closing Razorpay popup:", err);
|
console.warn(" Error closing Razorpay popup:", err);
|
||||||
removeRazorpayArtifacts();
|
removeRazorpayArtifacts();
|
||||||
}
|
}
|
||||||
};
|
}
|
||||||
|
|
||||||
export const useVerifyPayment = (onSuccessCallBack, onFailureCallBack) => {
|
export const useVerifyPayment = (onSuccessCallBack, onFailureCallBack) => {
|
||||||
const client = useQueryClient();
|
const client = useQueryClient();
|
||||||
const dispatch = useDispatch();
|
const dispatch = useDispatch()
|
||||||
|
|
||||||
return useMutation({
|
return useMutation({
|
||||||
mutationFn: async (payload) =>
|
mutationFn: (payload) => PaymentRepository.verifyPayment(payload),
|
||||||
await PaymentRepository.verifyPayment(payload),
|
|
||||||
|
|
||||||
onSuccess: (data) => {
|
onSuccess: (data) => {
|
||||||
dispatch(setSelfTenant({ paymentDetailId: data?.data?.id }));
|
dispatch(setSelfTenant({ paymentDetailId: data?.data?.id }));
|
||||||
if (onSuccessCallBack) onSuccessCallBack(data);
|
if (onSuccessCallBack) onSuccessCallBack(data);
|
||||||
},
|
},
|
||||||
|
|
||||||
@ -85,9 +85,8 @@ export const useMakePayment = (
|
|||||||
currentPlan
|
currentPlan
|
||||||
) => {
|
) => {
|
||||||
const client = useQueryClient();
|
const client = useQueryClient();
|
||||||
|
|
||||||
const { tenantEnquireId, planId } = useSelector(
|
const { tenantEnquireId, planId } = useSelector(
|
||||||
(store) => store?.localVariables?.selfTenant
|
(store) => store.localVariables.selfTenant
|
||||||
);
|
);
|
||||||
|
|
||||||
const { mutate: verifyPayment } = useVerifyPayment(
|
const { mutate: verifyPayment } = useVerifyPayment(
|
||||||
@ -96,11 +95,9 @@ export const useMakePayment = (
|
|||||||
);
|
);
|
||||||
|
|
||||||
return useMutation({
|
return useMutation({
|
||||||
mutationFn: async (payload) => {
|
mutationFn: (payload) => PaymentRepository.makePayment(payload),
|
||||||
return await PaymentRepository.makePayment(payload?.amount);
|
|
||||||
},
|
|
||||||
|
|
||||||
onSuccess: (data, varibales) => {
|
onSuccess: (data) => {
|
||||||
const orderId = data?.data?.orderId;
|
const orderId = data?.data?.orderId;
|
||||||
const key = data?.data?.key;
|
const key = data?.data?.key;
|
||||||
|
|
||||||
@ -113,16 +110,16 @@ export const useMakePayment = (
|
|||||||
|
|
||||||
const options = {
|
const options = {
|
||||||
key,
|
key,
|
||||||
amount: (varibales?.amount ?? 1) * 100,
|
amount: (currentPlan?.amount ?? 1) * 100,
|
||||||
currency: varibales.currencyCode || "INR",
|
currency: currentPlan?.currency?.currencyCode || "INR",
|
||||||
name: "MarcoAIOT Subscription",
|
name: "MarcoAIOT Subscription",
|
||||||
order_id: orderId,
|
order_id: orderId,
|
||||||
|
|
||||||
handler: async (response) => {
|
handler: async (response) => {
|
||||||
if (manuallyClosed) {
|
if (manuallyClosed) {
|
||||||
unblockUI();
|
unblockUI()
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
try {
|
try {
|
||||||
const payload = {
|
const payload = {
|
||||||
@ -148,7 +145,7 @@ export const useMakePayment = (
|
|||||||
|
|
||||||
modal: {
|
modal: {
|
||||||
ondismiss: () => {
|
ondismiss: () => {
|
||||||
manuallyClosed = true;
|
manuallyClosed = true;
|
||||||
unblockUI();
|
unblockUI();
|
||||||
closeRazorpayPopup();
|
closeRazorpayPopup();
|
||||||
},
|
},
|
||||||
@ -159,7 +156,7 @@ export const useMakePayment = (
|
|||||||
const razorpay = new window.Razorpay(options);
|
const razorpay = new window.Razorpay(options);
|
||||||
|
|
||||||
razorpay.on("payment.failed", (response) => {
|
razorpay.on("payment.failed", (response) => {
|
||||||
if (manuallyClosed) return;
|
if (manuallyClosed) return;
|
||||||
onFailureCallBack?.({
|
onFailureCallBack?.({
|
||||||
status: "failed",
|
status: "failed",
|
||||||
message: response.error?.description || "Payment failed.",
|
message: response.error?.description || "Payment failed.",
|
||||||
@ -184,7 +181,6 @@ export const useMakePayment = (
|
|||||||
"Something went wrong. Please try again later.",
|
"Something went wrong. Please try again later.",
|
||||||
"error"
|
"error"
|
||||||
);
|
);
|
||||||
closeRazorpayPopup();
|
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
};
|
};
|
||||||
|
|||||||
@ -8,7 +8,7 @@ export const OrganizationSchema = z.object({
|
|||||||
organizationName: z.string().min(1, "Organization Name is required"),
|
organizationName: z.string().min(1, "Organization Name is required"),
|
||||||
contactNumber: z
|
contactNumber: z
|
||||||
.string()
|
.string()
|
||||||
.min(5, "Contact Number is too short")
|
.min(10, "Contact Number is too short")
|
||||||
.regex(
|
.regex(
|
||||||
/^[+]?[(]?[0-9]{1,4}[)]?[-\s./0-9]*$/,
|
/^[+]?[(]?[0-9]{1,4}[)]?[-\s./0-9]*$/,
|
||||||
"Invalid phone number format"
|
"Invalid phone number format"
|
||||||
@ -24,4 +24,8 @@ export const OrganizationSchema = z.object({
|
|||||||
|
|
||||||
export const OrganizationDefaultValue = {
|
export const OrganizationDefaultValue = {
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export const CouponDiscount = z.object({
|
||||||
|
coupon:z.string().optional()
|
||||||
|
})
|
||||||
@ -5,7 +5,7 @@ import SubscriptionForm from "../../components/UserSubscription/SubscriptionForm
|
|||||||
import ProcessedPayment from "../../components/UserSubscription/ProcessedPayment";
|
import ProcessedPayment from "../../components/UserSubscription/ProcessedPayment";
|
||||||
import VerifiedPayment from "../../components/UserSubscription/VerifiedPayment";
|
import VerifiedPayment from "../../components/UserSubscription/VerifiedPayment";
|
||||||
import SelectPlan from "../../components/UserSubscription/SelectPlan";
|
import SelectPlan from "../../components/UserSubscription/SelectPlan";
|
||||||
import { Link } from "react-router-dom";
|
import Review from "../../components/UserSubscription/Review";
|
||||||
|
|
||||||
const MakeSubscription = () => {
|
const MakeSubscription = () => {
|
||||||
const [currentStep, setCurrentStep] = useState(1);
|
const [currentStep, setCurrentStep] = useState(1);
|
||||||
@ -24,7 +24,7 @@ const MakeSubscription = () => {
|
|||||||
if (resp?.success) {
|
if (resp?.success) {
|
||||||
setStepStatus((prev) => ({ ...prev, 4: "success" }));
|
setStepStatus((prev) => ({ ...prev, 4: "success" }));
|
||||||
setCurrentStep(5);
|
setCurrentStep(5);
|
||||||
} else {failed
|
} else {
|
||||||
setStepStatus((prev) => ({ ...prev, 4: "failed" }));
|
setStepStatus((prev) => ({ ...prev, 4: "failed" }));
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
@ -97,8 +97,9 @@ const MakeSubscription = () => {
|
|||||||
name: "Verified",
|
name: "Verified",
|
||||||
component: () => (
|
component: () => (
|
||||||
<VerifiedPayment
|
<VerifiedPayment
|
||||||
setStepStatus={setStepStatus}
|
responsePayment={responsePayment}
|
||||||
responsePayment={responsePayment}
|
setStepStatus={setStepStatus}
|
||||||
|
|
||||||
/>
|
/>
|
||||||
),
|
),
|
||||||
},
|
},
|
||||||
@ -106,15 +107,62 @@ const MakeSubscription = () => {
|
|||||||
|
|
||||||
return (
|
return (
|
||||||
<>
|
<>
|
||||||
<div className="container-fluid mt-3">
|
<nav className="navbar navbar-expand-lg navbar-light bg-white shadow-sm fixed-top custom-navbar py-2 mb-md-1 mb-12 ">
|
||||||
<div className="d-block align-items-start mt-6 ">
|
<div className="container-fluid px-4">
|
||||||
<SubscriptionLayout
|
<a
|
||||||
configStep={checkOut_Steps}
|
className="navbar-brand fw-bold text-green d-flex align-items-center"
|
||||||
currentStep={currentStep}
|
href="#"
|
||||||
setCurrentStep={setCurrentStep}
|
>
|
||||||
stepStatus={stepStatus}
|
<span className="text-blue">OnField</span>
|
||||||
/>
|
<span>Work</span>
|
||||||
|
<span className="text-dark">.com</span>
|
||||||
|
</a>
|
||||||
|
|
||||||
|
<button
|
||||||
|
className="navbar-toggler"
|
||||||
|
type="button"
|
||||||
|
data-bs-toggle="collapse"
|
||||||
|
data-bs-target="#navbarNav"
|
||||||
|
aria-controls="navbarNav"
|
||||||
|
aria-expanded="false"
|
||||||
|
aria-label="Toggle navigation"
|
||||||
|
>
|
||||||
|
<span className="navbar-toggler-icon"></span>
|
||||||
|
</button>
|
||||||
|
|
||||||
|
<div
|
||||||
|
className="collapse navbar-collapse justify-content-end"
|
||||||
|
id="navbarNav"
|
||||||
|
>
|
||||||
|
|
||||||
|
|
||||||
|
{/* Auth Buttons */}
|
||||||
|
<ul className="navbar-nav align-items-center">
|
||||||
|
<li className="nav-item">
|
||||||
|
<a
|
||||||
|
className="btn btn-outline-primary btn-sm px-3 fw-semibold"
|
||||||
|
href="/auth/login"
|
||||||
|
>
|
||||||
|
Login
|
||||||
|
</a>
|
||||||
|
</li>
|
||||||
|
<li className="nav-item ms-2">
|
||||||
|
<a className="btn btn-primary btn-sm px-3 fw-semibold" href="#">
|
||||||
|
Get Started
|
||||||
|
</a>
|
||||||
|
</li>
|
||||||
|
</ul>
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
</nav>
|
||||||
|
|
||||||
|
<div className="pt-5 d-block align-items-start mt-8 mt-2">
|
||||||
|
<SubscriptionLayout
|
||||||
|
configStep={checkOut_Steps}
|
||||||
|
currentStep={currentStep}
|
||||||
|
setCurrentStep={setCurrentStep}
|
||||||
|
stepStatus={stepStatus}
|
||||||
|
/>
|
||||||
</div>
|
</div>
|
||||||
</>
|
</>
|
||||||
);
|
);
|
||||||
|
|||||||
@ -125,8 +125,8 @@ 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
|
||||||
|
|||||||
@ -72,11 +72,13 @@ const router = createBrowserRouter(
|
|||||||
{ path: "/reset-password", element: <MainResetPasswordPage /> },
|
{ path: "/reset-password", element: <MainResetPasswordPage /> },
|
||||||
{ path: "/legal-info", element: <LegalInfoCard /> },
|
{ path: "/legal-info", element: <LegalInfoCard /> },
|
||||||
{ path: "/auth/changepassword", element: <ChangePasswordPage /> },
|
{ path: "/auth/changepassword", element: <ChangePasswordPage /> },
|
||||||
|
|
||||||
],
|
],
|
||||||
},
|
},
|
||||||
{ 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 />,
|
||||||
@ -95,7 +97,10 @@ const router = createBrowserRouter(
|
|||||||
{ path: "/directory", element: <DirectoryPage /> },
|
{ path: "/directory", element: <DirectoryPage /> },
|
||||||
{ path: "/inventory", element: <Inventory /> },
|
{ path: "/inventory", element: <Inventory /> },
|
||||||
{ path: "/activities/attendance", element: <AttendancePage /> },
|
{ path: "/activities/attendance", element: <AttendancePage /> },
|
||||||
{ path: "/activities/records/:projectId?", element: <DailyProgrssReport /> },
|
{
|
||||||
|
path: "/activities/records/:projectId?",
|
||||||
|
element: <DailyProgrssReport />,
|
||||||
|
},
|
||||||
{ path: "/activities/task", element: <TaskPlannng /> },
|
{ path: "/activities/task", element: <TaskPlannng /> },
|
||||||
{ path: "/activities/reports", element: <Reports /> },
|
{ path: "/activities/reports", element: <Reports /> },
|
||||||
{ path: "/gallary", element: <ImageGalleryPage /> },
|
{ path: "/gallary", element: <ImageGalleryPage /> },
|
||||||
|
|||||||
@ -31,6 +31,7 @@ const localVariablesSlice = createSlice({
|
|||||||
AuthModal: {
|
AuthModal: {
|
||||||
isOpen: false,
|
isOpen: false,
|
||||||
},
|
},
|
||||||
|
|
||||||
selfTenant: {
|
selfTenant: {
|
||||||
tenantEnquireId: null,
|
tenantEnquireId: null,
|
||||||
planId: null,
|
planId: null,
|
||||||
@ -106,12 +107,12 @@ const localVariablesSlice = createSlice({
|
|||||||
setSelfTenant: (state, action) => {
|
setSelfTenant: (state, action) => {
|
||||||
state.selfTenant.tenantEnquireId =
|
state.selfTenant.tenantEnquireId =
|
||||||
action.payload.tenantEnquireId ?? state.selfTenant.tenantEnquireId;
|
action.payload.tenantEnquireId ?? state.selfTenant.tenantEnquireId;
|
||||||
state.selfTenant.planId =
|
state.selfTenant.planId =
|
||||||
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;
|
state.selfTenant.frequency = action.payload.frequency ?? state.selfTenant.frequency;
|
||||||
state.selfTenant.paymentDetailId = action.payload.paymentDetailId ?? state.selfTenant.paymentDetailId;
|
state.selfTenant.paymentDetailId = action.payload.paymentDetailId ?? state.selfTenant.paymentDetailId;
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
@ -127,6 +128,10 @@ export const {
|
|||||||
toggleOrgModal,
|
toggleOrgModal,
|
||||||
openAuthModal,
|
openAuthModal,
|
||||||
closeAuthModal,
|
closeAuthModal,
|
||||||
setOrganization,openModal, closeModal, toggleModal , setSelfTenant
|
setOrganization,
|
||||||
|
openModal,
|
||||||
|
closeModal,
|
||||||
|
toggleModal,
|
||||||
|
setSelfTenant,
|
||||||
} = localVariablesSlice.actions;
|
} = localVariablesSlice.actions;
|
||||||
export default localVariablesSlice.reducer;
|
export default localVariablesSlice.reducer;
|
||||||
|
|||||||
@ -1,5 +1,5 @@
|
|||||||
import { useEffect, useState } from "react";
|
import { useEffect, useState } from "react";
|
||||||
import { format, parseISO } from "date-fns";
|
import { parseISO, formatISO } from "date-fns";
|
||||||
export const formatFileSize = (bytes) => {
|
export const formatFileSize = (bytes) => {
|
||||||
if (bytes < 1024) return bytes + " B";
|
if (bytes < 1024) return bytes + " B";
|
||||||
else if (bytes < 1024 * 1024) return (bytes / 1024).toFixed(2) + " KB";
|
else if (bytes < 1024 * 1024) return (bytes / 1024).toFixed(2) + " KB";
|
||||||
@ -70,50 +70,50 @@ export const normalizeAllowedContentTypes = (allowedContentType) => {
|
|||||||
return [];
|
return [];
|
||||||
};
|
};
|
||||||
export function localToUtc(dateString) {
|
export function localToUtc(dateString) {
|
||||||
if (!dateString || typeof dateString !== "string") return null;
|
if (!dateString || typeof dateString !== "string") return null;
|
||||||
|
|
||||||
const parts = dateString.trim().split("-");
|
const parts = dateString.trim().split("-");
|
||||||
if (parts.length !== 3) return null;
|
if (parts.length !== 3) return null;
|
||||||
|
|
||||||
let day, month, year;
|
let day, month, year;
|
||||||
|
|
||||||
if (parts[0].length === 4) {
|
if (parts[0].length === 4) {
|
||||||
// Format: yyyy-mm-dd
|
// Format: yyyy-mm-dd
|
||||||
[year, month, day] = parts;
|
[year, month, day] = parts;
|
||||||
} else {
|
} else {
|
||||||
// Format: dd-mm-yyyy
|
// Format: dd-mm-yyyy
|
||||||
[day, month, year] = parts;
|
[day, month, year] = parts;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!day || !month || !year) return null;
|
||||||
|
|
||||||
|
const date = new Date(
|
||||||
|
Date.UTC(Number(year), Number(month) - 1, Number(day), 0, 0, 0)
|
||||||
|
);
|
||||||
|
return isNaN(date.getTime()) ? null : date.toISOString();
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!day || !month || !year) return null;
|
export const formatCurrency = (amount, currency = "INR", locale = "en-US") => {
|
||||||
|
return new Intl.NumberFormat(locale, {
|
||||||
|
style: "currency",
|
||||||
|
notation: "compact",
|
||||||
|
compactDisplay: "short",
|
||||||
|
currency: currency,
|
||||||
|
minimumFractionDigits: 0,
|
||||||
|
maximumFractionDigits: 2,
|
||||||
|
}).format(amount);
|
||||||
|
};
|
||||||
|
|
||||||
const date = new Date(
|
export const countDigit = (num) => {
|
||||||
Date.UTC(Number(year), Number(month) - 1, Number(day), 0, 0, 0)
|
return Math.abs(num).toString().length;
|
||||||
);
|
};
|
||||||
return isNaN(date.getTime()) ? null : date.toISOString();
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Flexible number formatter for currency, numbers, or percentages.
|
|
||||||
*
|
|
||||||
* @param {number} amount - The value to format.
|
|
||||||
* @param {Object} options - Formatting options.
|
|
||||||
* @param {"currency"|"number"|"percent"} [options.type="number"] - Type of format.
|
|
||||||
* @param {string} [options.currency="INR"] - Currency code (only used when type="currency").
|
|
||||||
* @param {string} [options.locale="en-US"] - Locale for formatting.
|
|
||||||
* @param {"short"|"long"|"standard"} [options.notation="compact"] - Display style for large numbers.
|
|
||||||
* @param {number} [options.minimumFractionDigits=0] - Minimum decimal places.
|
|
||||||
* @param {number} [options.maximumFractionDigits=2] - Maximum decimal places.
|
|
||||||
* @returns {string} Formatted number string.
|
|
||||||
*/
|
|
||||||
export const formatFigure = (
|
export const formatFigure = (
|
||||||
amount,
|
amount,
|
||||||
{
|
{
|
||||||
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,
|
||||||
@ -135,18 +140,54 @@ 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) => {
|
|
||||||
const frequency = parseInt(freq, 10);
|
export const frequencyLabel = (
|
||||||
|
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