migrated suscripton onfield to main sub-branch

This commit is contained in:
pramod.mahajan 2025-11-07 14:47:09 +05:30
parent 31a8329254
commit a656907695
14 changed files with 459 additions and 190 deletions

View File

@ -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>

View File

@ -0,0 +1,11 @@
import React from 'react'
const Review = () => {
return (
<div>
</div>
)
}
export default Review

View File

@ -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"

View File

@ -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;

View File

@ -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>

View File

@ -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";

View File

@ -144,3 +144,4 @@ const VerifiedPayment = ({ responsePayment, setStepStatus }) => {
export default VerifiedPayment; export default VerifiedPayment;

View File

@ -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();
}, },
}); });
}; };

View File

@ -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()
})

View File

@ -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>
</> </>
); );

View File

@ -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

View File

@ -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 /> },

View File

@ -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;

View File

@ -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";
} }
}; };