classify web
This commit is contained in:
120
components/PagesComponent/Subscription/BankDetailsModal.jsx
Normal file
120
components/PagesComponent/Subscription/BankDetailsModal.jsx
Normal file
@@ -0,0 +1,120 @@
|
||||
import { useNavigate } from "@/components/Common/useNavigate";
|
||||
import NoData from "@/components/EmptyStates/NoData";
|
||||
import {
|
||||
Dialog,
|
||||
DialogContent,
|
||||
DialogDescription,
|
||||
DialogHeader,
|
||||
DialogTitle,
|
||||
} from "@/components/ui/dialog";
|
||||
import {
|
||||
getIsShowBankDetails,
|
||||
hideBankDetails,
|
||||
} from "@/redux/reducer/globalStateSlice";
|
||||
import { t } from "@/utils";
|
||||
import { createPaymentIntentApi } from "@/utils/api";
|
||||
import { useState } from "react";
|
||||
import { useSelector } from "react-redux";
|
||||
import { toast } from "sonner";
|
||||
|
||||
const BankDetailsModal = ({ packageId, bankDetails }) => {
|
||||
const { navigate } = useNavigate();
|
||||
const IsShowBankDetails = useSelector(getIsShowBankDetails);
|
||||
const IsBankDetails = bankDetails && Object.keys(bankDetails).length > 0;
|
||||
const [IsConfirmingPayment, setIsConfirmingPayment] = useState(false);
|
||||
|
||||
const handleConfirmPayment = async () => {
|
||||
try {
|
||||
setIsConfirmingPayment(true);
|
||||
const res = await createPaymentIntentApi.createIntent({
|
||||
package_id: packageId,
|
||||
payment_method: "bankTransfer",
|
||||
});
|
||||
if (res?.data?.error === false) {
|
||||
toast.success(t("paymentConfirmed"));
|
||||
hideBankDetails();
|
||||
navigate("/transactions");
|
||||
} else {
|
||||
toast.error(res?.data?.message);
|
||||
}
|
||||
} catch (error) {
|
||||
console.log("Failed to confirm Payment", error);
|
||||
} finally {
|
||||
setIsConfirmingPayment(false);
|
||||
}
|
||||
};
|
||||
return (
|
||||
<Dialog open={IsShowBankDetails} onOpenChange={hideBankDetails}>
|
||||
<DialogContent onInteractOutside={(e) => e.preventDefault()}>
|
||||
<DialogHeader>
|
||||
<DialogTitle className="text-xl font-semibold">
|
||||
{t("bankAccountDetails")}
|
||||
</DialogTitle>
|
||||
<DialogDescription className="!text-base">
|
||||
{t("pleaseTransferAmount")}
|
||||
</DialogDescription>
|
||||
{IsBankDetails ? (
|
||||
<>
|
||||
<div className="flex flex-col gap-3 !mt-8">
|
||||
<div className="flex items-center gap-2">
|
||||
<p className="w-[35%] opacity-60 font-medium text-base">
|
||||
{t("accountHolder")}
|
||||
</p>
|
||||
<p className="text-base px-3 py-2 bg-muted w-[75%] rounded">
|
||||
{bankDetails?.account_holder_name}
|
||||
</p>
|
||||
</div>
|
||||
<div className="flex items-center gap-2">
|
||||
<p className="w-[35%] opacity-60 font-medium text-base">
|
||||
{t("accountNumber")}
|
||||
</p>
|
||||
<p className="text-base px-3 py-2 bg-muted w-[75%] rounded">
|
||||
{bankDetails?.account_number}
|
||||
</p>
|
||||
</div>
|
||||
<div className="flex items-center gap-2">
|
||||
<p className="w-[35%] opacity-60 font-medium text-base">
|
||||
{t("bankName")}
|
||||
</p>
|
||||
<p className="text-base px-3 py-2 bg-muted w-[75%] rounded">
|
||||
{bankDetails?.bank_name}
|
||||
</p>
|
||||
</div>
|
||||
<div className="flex items-center gap-2">
|
||||
<p className="w-[35%] opacity-60 font-medium text-base">
|
||||
SWIFT/IFSC Code
|
||||
</p>
|
||||
<p className="text-base px-3 py-2 bg-muted w-[75%] rounded">
|
||||
{bankDetails?.ifsc_swift_code}
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div className="flex items-center flex-wrap gap-4 !mt-8">
|
||||
<button
|
||||
className="px-4 py-2 border flex-1 rounded whitespace-nowrap"
|
||||
onClick={hideBankDetails}
|
||||
>
|
||||
{t("cancel")}
|
||||
</button>
|
||||
<button
|
||||
className="px-4 py-2 bg-primary flex-1 text-white rounded whitespace-nowrap disabled:opacity-66"
|
||||
onClick={handleConfirmPayment}
|
||||
disabled={IsConfirmingPayment}
|
||||
>
|
||||
{IsConfirmingPayment
|
||||
? t("confirmingPayment")
|
||||
: t("confirmPayment")}
|
||||
</button>
|
||||
</div>
|
||||
</>
|
||||
) : (
|
||||
<NoData name={t("bankAccountDetails")} />
|
||||
)}
|
||||
</DialogHeader>
|
||||
</DialogContent>
|
||||
</Dialog>
|
||||
);
|
||||
};
|
||||
|
||||
export default BankDetailsModal;
|
||||
@@ -0,0 +1,24 @@
|
||||
import { showBankDetails } from "@/redux/reducer/globalStateSlice";
|
||||
import { FaAngleRight } from "react-icons/fa";
|
||||
import { AiOutlineBank } from "react-icons/ai"
|
||||
import { t } from "@/utils";
|
||||
|
||||
|
||||
const BankTransferPayment = ({ closePaymentModal }) => {
|
||||
const handleBankTransfer = () => {
|
||||
closePaymentModal();
|
||||
showBankDetails();
|
||||
};
|
||||
|
||||
return (
|
||||
<button onClick={handleBankTransfer} className="flex items-center gap-2 justify-between p-2">
|
||||
<div className="flex items-center gap-2">
|
||||
<AiOutlineBank size={30} />
|
||||
<span className="text-lg font-semibold">{t("bankTransfer")}</span>
|
||||
</div>
|
||||
<FaAngleRight size={18} className="rtl:scale-x-[-1]" />
|
||||
</button>
|
||||
);
|
||||
};
|
||||
|
||||
export default BankTransferPayment;
|
||||
@@ -0,0 +1,77 @@
|
||||
import { useState } from "react";
|
||||
import FlutterwaveLogo from "../../../public/assets/flutterwave.png";
|
||||
import { FaAngleRight } from "react-icons/fa";
|
||||
import { createPaymentIntentApi } from "@/utils/api";
|
||||
import { toast } from "sonner";
|
||||
import { t } from "@/utils";
|
||||
import CustomImage from "@/components/Common/CustomImage";
|
||||
import { Loader2 } from "lucide-react";
|
||||
|
||||
const FlutterwavePayment = ({ selectedPackage }) => {
|
||||
const [isLoading, setIsLoading] = useState(false);
|
||||
const handleFlutterwavePayment = async () => {
|
||||
setIsLoading(true);
|
||||
try {
|
||||
const res = await createPaymentIntentApi.createIntent({
|
||||
package_id: selectedPackage.id,
|
||||
payment_method: "FlutterWave",
|
||||
});
|
||||
if (res.data.error) {
|
||||
toast.error(res.data.message);
|
||||
return;
|
||||
}
|
||||
const payment_gateway_response =
|
||||
res?.data?.data?.payment_intent?.payment_gateway_response?.data?.link;
|
||||
|
||||
if (payment_gateway_response) {
|
||||
const popupWidth = 600;
|
||||
const popupHeight = 700;
|
||||
const popupLeft = window.innerWidth / 2 - popupWidth / 2;
|
||||
const popupTop = window.innerHeight / 2 - popupHeight / 2;
|
||||
|
||||
window.open(
|
||||
payment_gateway_response,
|
||||
"paymentWindow",
|
||||
`width=${popupWidth},height=${popupHeight},top=${popupTop},left=${popupLeft}`
|
||||
);
|
||||
} else {
|
||||
throw new Error("Unable to retrieve payment gateway response.");
|
||||
}
|
||||
} catch (error) {
|
||||
console.error("Error during Flutterwave payment", error);
|
||||
toast.error(t("errorOccurred"));
|
||||
} finally {
|
||||
setIsLoading(false);
|
||||
}
|
||||
};
|
||||
|
||||
return (
|
||||
<div>
|
||||
<button onClick={handleFlutterwavePayment} className="w-full p-2">
|
||||
<div className="flex items-center gap-2 justify-between ">
|
||||
<div className="flex gap-3 items-center">
|
||||
<div className="w-8 h-8">
|
||||
<CustomImage
|
||||
height={32}
|
||||
width={32}
|
||||
src={FlutterwaveLogo.src}
|
||||
alt="Flutterwave"
|
||||
className="w-full h-full "
|
||||
/>
|
||||
</div>
|
||||
<p className="text-lg font-semibold">{t("flutterwave")}</p>
|
||||
</div>
|
||||
<div>
|
||||
{isLoading ? (
|
||||
<Loader2 className="w-5 h-5 animate-spin" />
|
||||
) : (
|
||||
<FaAngleRight size={18} className="rtl:scale-x-[-1]" />
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
</button>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
export default FlutterwavePayment;
|
||||
79
components/PagesComponent/Subscription/PayStackPayment.jsx
Normal file
79
components/PagesComponent/Subscription/PayStackPayment.jsx
Normal file
@@ -0,0 +1,79 @@
|
||||
import React, { useState } from "react";
|
||||
import PayStackLogo from "../../../public/assets/ic_paystack.png";
|
||||
import { FaAngleRight } from "react-icons/fa";
|
||||
import { createPaymentIntentApi } from "@/utils/api";
|
||||
import { Loader2 } from "lucide-react";
|
||||
import { toast } from "sonner";
|
||||
import { t } from "@/utils";
|
||||
import CustomImage from "@/components/Common/CustomImage";
|
||||
const PayStackPayment = ({ packageSettings, selectedPackage }) => {
|
||||
const [isLoading, setIsLoading] = useState(false);
|
||||
const handlePayStackPayment = async () => {
|
||||
try {
|
||||
setIsLoading(true);
|
||||
const res = await createPaymentIntentApi.createIntent({
|
||||
package_id: selectedPackage.id,
|
||||
payment_method: packageSettings.Paystack.payment_method,
|
||||
platform_type: "web",
|
||||
});
|
||||
|
||||
if (res.data.error) {
|
||||
throw new Error(res.data.message);
|
||||
}
|
||||
|
||||
const paymentIntent = res.data.data.payment_intent;
|
||||
const authorizationUrl =
|
||||
paymentIntent?.payment_gateway_response?.data?.authorization_url;
|
||||
|
||||
if (authorizationUrl) {
|
||||
const popupWidth = 600;
|
||||
const popupHeight = 700;
|
||||
const popupLeft = window.innerWidth / 2 - popupWidth / 2;
|
||||
const popupTop = window.innerHeight / 2 - popupHeight / 2;
|
||||
|
||||
window.open(
|
||||
authorizationUrl,
|
||||
"paymentWindow",
|
||||
`width=${popupWidth},height=${popupHeight},top=${popupTop},left=${popupLeft}`
|
||||
);
|
||||
} else {
|
||||
throw new Error("Unable to retrieve authorization URL.");
|
||||
}
|
||||
} catch (error) {
|
||||
console.error("An error occurred while processing the payment:", error);
|
||||
toast.error(t("errorOccurred"));
|
||||
} finally {
|
||||
setIsLoading(false);
|
||||
}
|
||||
};
|
||||
|
||||
return (
|
||||
<div>
|
||||
<button onClick={handlePayStackPayment} className="w-full p-2">
|
||||
<div className="flex items-center gap-2 justify-between ">
|
||||
<div className="flex gap-3 items-center">
|
||||
<div className="w-8 h-8">
|
||||
<CustomImage
|
||||
height={32}
|
||||
width={32}
|
||||
src={PayStackLogo.src}
|
||||
alt="Paystack"
|
||||
className="w-full h-full "
|
||||
/>
|
||||
</div>
|
||||
<p className="text-lg font-semibold">{t("payStack")}</p>
|
||||
</div>
|
||||
<div>
|
||||
{isLoading ? (
|
||||
<Loader2 className="w-5 h-5 animate-spin" />
|
||||
) : (
|
||||
<FaAngleRight size={18} className="rtl:scale-x-[-1]" />
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
</button>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
export default PayStackPayment;
|
||||
146
components/PagesComponent/Subscription/PaymentModal.jsx
Normal file
146
components/PagesComponent/Subscription/PaymentModal.jsx
Normal file
@@ -0,0 +1,146 @@
|
||||
import { useState, useEffect } from "react";
|
||||
import {
|
||||
Dialog,
|
||||
DialogContent,
|
||||
DialogHeader,
|
||||
DialogTitle,
|
||||
} from "@/components/ui/dialog";
|
||||
import { t } from "@/utils";
|
||||
import RazorpayPayment from "./RazorpayPayment";
|
||||
import PayStackPayment from "./PayStackPayment";
|
||||
import FlutterwavePayment from "./FlutterwavePayment";
|
||||
import PhonepePayment from "./PhonepePayment";
|
||||
import StripePayment from "./StripePayment";
|
||||
import StripeLogo from "../../../public/assets/ic_stripe.png";
|
||||
import { FaAngleRight } from "react-icons/fa";
|
||||
import PaymentModalLoading from "./PaymentModalLoading";
|
||||
import { toast } from "sonner";
|
||||
import BankTransferPayment from "./BankTransferPayment";
|
||||
import CustomImage from "@/components/Common/CustomImage";
|
||||
import PaypalPayment from "./PaypalPayment";
|
||||
|
||||
const PaymentModal = ({
|
||||
showPaymentModal,
|
||||
setShowPaymentModal,
|
||||
selectedPackage,
|
||||
packageSettings,
|
||||
isLoading,
|
||||
setToggleApiAfterPaymentSuccess
|
||||
}) => {
|
||||
const [showStripePayment, setShowStripePayment] = useState(false);
|
||||
const isBankTransferActive =
|
||||
Number(packageSettings?.bankTransfer?.status) === 1;
|
||||
const PaymentModalClose = () => {
|
||||
setShowPaymentModal(false);
|
||||
setShowStripePayment(false);
|
||||
};
|
||||
|
||||
const handleMessage = (event) => {
|
||||
if (event.origin === process.env.NEXT_PUBLIC_API_URL) {
|
||||
const { status } = event.data;
|
||||
if (status === "success") {
|
||||
toast.success(t("paymentSuccess"));
|
||||
setToggleApiAfterPaymentSuccess((prev) => !prev)
|
||||
} else if (status === "cancel") {
|
||||
toast.error(t("paymentCancelled"));
|
||||
} else {
|
||||
toast.error(t("paymentFailed"));
|
||||
}
|
||||
PaymentModalClose();
|
||||
}
|
||||
};
|
||||
|
||||
useEffect(() => {
|
||||
window.addEventListener("message", handleMessage);
|
||||
return () => {
|
||||
window.removeEventListener("message", handleMessage);
|
||||
};
|
||||
}, [handleMessage]);
|
||||
|
||||
return (
|
||||
<div>
|
||||
<Dialog open={showPaymentModal} onOpenChange={PaymentModalClose}>
|
||||
<DialogContent
|
||||
className="!max-w-[520px]"
|
||||
onInteractOutside={(e) => {
|
||||
e.preventDefault();
|
||||
}}
|
||||
>
|
||||
<DialogHeader className="border-b border-gray-400 pb-5 ">
|
||||
<DialogTitle className="text-lg">{t("paymentWith")} {showStripePayment && t("stripe")}</DialogTitle>
|
||||
</DialogHeader>
|
||||
{isLoading ? (
|
||||
<div className="flex justify-center items-center h-full flex-col gap-4">
|
||||
{Array.from({ length: 7 }).map((_, index) => (
|
||||
<PaymentModalLoading key={index} />
|
||||
))}
|
||||
</div>
|
||||
) : showStripePayment ? (
|
||||
<StripePayment
|
||||
selectedPackage={selectedPackage}
|
||||
packageSettings={packageSettings}
|
||||
PaymentModalClose={PaymentModalClose}
|
||||
setShowStripePayment={setShowStripePayment}
|
||||
setToggleApiAfterPaymentSuccess={setToggleApiAfterPaymentSuccess}
|
||||
/>
|
||||
) : (
|
||||
<div className="flex flex-col gap-4 mt-2">
|
||||
{packageSettings?.Stripe?.status == 1 && (
|
||||
<button
|
||||
onClick={() => setShowStripePayment(true)}
|
||||
className="w-full p-2"
|
||||
>
|
||||
<div className="flex items-center gap-2 justify-between ">
|
||||
<div className="flex gap-3 items-center">
|
||||
<div className="w-8 h-8">
|
||||
<CustomImage
|
||||
height={32}
|
||||
width={32}
|
||||
src={StripeLogo.src}
|
||||
alt="Stripe"
|
||||
className="w-full h-full "
|
||||
/>
|
||||
</div>
|
||||
<p className="text-lg font-semibold">{t("stripe")}</p>
|
||||
</div>
|
||||
<FaAngleRight size={18} className="rtl:scale-x-[-1]" />
|
||||
</div>
|
||||
</button>
|
||||
)}
|
||||
{packageSettings?.Razorpay?.status == 1 && (
|
||||
<RazorpayPayment
|
||||
packageSettings={packageSettings}
|
||||
selectedPackage={selectedPackage}
|
||||
setShowPaymentModal={setShowPaymentModal}
|
||||
setToggleApiAfterPaymentSuccess={setToggleApiAfterPaymentSuccess}
|
||||
/>
|
||||
)}
|
||||
{packageSettings?.Paystack?.status == 1 && (
|
||||
<PayStackPayment
|
||||
packageSettings={packageSettings}
|
||||
selectedPackage={selectedPackage}
|
||||
/>
|
||||
)}
|
||||
{packageSettings?.flutterwave?.status == 1 && (
|
||||
<FlutterwavePayment selectedPackage={selectedPackage} />
|
||||
)}
|
||||
{packageSettings?.PhonePe?.status == 1 && (
|
||||
<PhonepePayment selectedPackage={selectedPackage} />
|
||||
)}
|
||||
|
||||
{packageSettings?.Paypal?.status == 1 && (
|
||||
<PaypalPayment selectedPackage={selectedPackage} />
|
||||
)}
|
||||
|
||||
{isBankTransferActive && (
|
||||
<BankTransferPayment closePaymentModal={PaymentModalClose} />
|
||||
)}
|
||||
</div>
|
||||
)}
|
||||
</DialogContent>
|
||||
</Dialog>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
export default PaymentModal;
|
||||
@@ -0,0 +1,22 @@
|
||||
|
||||
|
||||
const PaymentModalLoading = () => {
|
||||
return (
|
||||
<div className="w-full p-2 animate-pulse">
|
||||
<div className="flex items-center justify-between">
|
||||
<div className="flex items-center gap-3">
|
||||
{/* Logo skeleton */}
|
||||
<div className="w-8 h-8 bg-gray-200 rounded-md"></div>
|
||||
|
||||
{/* Text skeleton */}
|
||||
<div className="h-7 bg-gray-200 rounded-md w-24"></div>
|
||||
</div>
|
||||
|
||||
{/* Arrow skeleton */}
|
||||
<div className="w-4 h-4 bg-gray-200 rounded-md"></div>
|
||||
</div>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
|
||||
export default PaymentModalLoading
|
||||
79
components/PagesComponent/Subscription/PaypalPayment.jsx
Normal file
79
components/PagesComponent/Subscription/PaypalPayment.jsx
Normal file
@@ -0,0 +1,79 @@
|
||||
import { useState } from "react";
|
||||
import PaypalLogo from "../../../public/assets/paypal-logo.png";
|
||||
import { FaAngleRight } from "react-icons/fa";
|
||||
import { createPaymentIntentApi } from "@/utils/api";
|
||||
import { Loader2 } from "lucide-react";
|
||||
import { toast } from "sonner";
|
||||
import { t } from "@/utils";
|
||||
import CustomImage from "@/components/Common/CustomImage";
|
||||
|
||||
const PaypalPayment = ({ selectedPackage }) => {
|
||||
const [isLoading, setIsLoading] = useState(false);
|
||||
const handlePaypalPayment = async () => {
|
||||
try {
|
||||
setIsLoading(true);
|
||||
const res = await createPaymentIntentApi.createIntent({
|
||||
package_id: selectedPackage.id,
|
||||
payment_method: "PayPal",
|
||||
platform_type: "web",
|
||||
});
|
||||
if (res.data.error) {
|
||||
toast.error(res.data.message);
|
||||
return;
|
||||
}
|
||||
|
||||
const payment_gateway_response =
|
||||
res?.data?.data?.payment_intent?.approval_url;
|
||||
|
||||
if (payment_gateway_response) {
|
||||
const popupWidth = 600;
|
||||
const popupHeight = 700;
|
||||
const popupLeft = window.innerWidth / 2 - popupWidth / 2;
|
||||
const popupTop = window.innerHeight / 2 - popupHeight / 2;
|
||||
|
||||
window.open(
|
||||
payment_gateway_response,
|
||||
"paymentWindow",
|
||||
`width=${popupWidth},height=${popupHeight},top=${popupTop},left=${popupLeft}`
|
||||
);
|
||||
} else {
|
||||
throw new Error("Unable to retrieve payment gateway response.");
|
||||
}
|
||||
} catch (error) {
|
||||
console.error("Error during Flutterwave payment", error);
|
||||
toast.error(t("errorOccurred"));
|
||||
} finally {
|
||||
setIsLoading(false);
|
||||
}
|
||||
};
|
||||
|
||||
return (
|
||||
<div>
|
||||
<button onClick={handlePaypalPayment} className="w-full p-2">
|
||||
<div className="flex items-center gap-2 justify-between ">
|
||||
<div className="flex gap-3 items-center">
|
||||
<div className="w-8 h-8">
|
||||
<CustomImage
|
||||
height={32}
|
||||
width={32}
|
||||
src={PaypalLogo}
|
||||
alt="Paypal"
|
||||
className="w-full h-full "
|
||||
/>
|
||||
</div>
|
||||
<p className="text-lg font-semibold">{t("paypal")}</p>
|
||||
</div>
|
||||
<div>
|
||||
{isLoading ? (
|
||||
<Loader2 className="w-5 h-5 animate-spin" />
|
||||
) : (
|
||||
<FaAngleRight size={18} className="rtl:scale-x-[-1]" />
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
</button>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
export default PaypalPayment;
|
||||
88
components/PagesComponent/Subscription/PhonepePayment.jsx
Normal file
88
components/PagesComponent/Subscription/PhonepePayment.jsx
Normal file
@@ -0,0 +1,88 @@
|
||||
import React, { useState } from "react";
|
||||
import PhonepeLogo from "../../../public/assets/phonepe-icon.png";
|
||||
import { FaAngleRight } from "react-icons/fa";
|
||||
import { useSelector } from "react-redux";
|
||||
import { createPaymentIntentApi } from "@/utils/api";
|
||||
import { toast } from "sonner";
|
||||
import { t } from "@/utils";
|
||||
import CustomImage from "@/components/Common/CustomImage";
|
||||
import { Loader2 } from "lucide-react";
|
||||
|
||||
const PhonepePayment = ({ selectedPackage }) => {
|
||||
const [isLoading, setIsLoading] = useState(false);
|
||||
const userData = useSelector((state) => state.UserSignup.data.data);
|
||||
|
||||
const handlePhonepePayment = async () => {
|
||||
if (!userData?.mobile) {
|
||||
toast.error(t("addMobileNumberToProceed"));
|
||||
return;
|
||||
}
|
||||
try {
|
||||
setIsLoading(true);
|
||||
const res = await createPaymentIntentApi.createIntent({
|
||||
package_id: selectedPackage.id,
|
||||
payment_method: "PhonePe",
|
||||
platform_type: "web",
|
||||
});
|
||||
if (res.data.error) {
|
||||
console.log("Error in payment intent response:", res.data.message);
|
||||
toast.error(res.data.message);
|
||||
return;
|
||||
}
|
||||
|
||||
const payment_gateway_response =
|
||||
res.data.data.payment_intent.payment_gateway_response
|
||||
?.payment_gateway_response;
|
||||
|
||||
if (payment_gateway_response) {
|
||||
const popupWidth = 600;
|
||||
const popupHeight = 700;
|
||||
const popupLeft = window.innerWidth / 2 - popupWidth / 2;
|
||||
const popupTop = window.innerHeight / 2 - popupHeight / 2;
|
||||
|
||||
window.open(
|
||||
payment_gateway_response,
|
||||
"paymentWindow",
|
||||
`width=${popupWidth},height=${popupHeight},top=${popupTop},left=${popupLeft}`
|
||||
);
|
||||
} else {
|
||||
throw new Error("Unable to retrieve payment gateway response.");
|
||||
}
|
||||
} catch (error) {
|
||||
console.error("Error during PhonePe payment", error);
|
||||
toast.error(t("errorOccurred"));
|
||||
} finally {
|
||||
setIsLoading(false);
|
||||
}
|
||||
};
|
||||
|
||||
return (
|
||||
<div>
|
||||
<button onClick={handlePhonepePayment} className="w-full p-2">
|
||||
<div className="flex items-center gap-2 justify-between ">
|
||||
<div className="flex gap-3 items-center">
|
||||
<div className="w-8 h-8">
|
||||
<CustomImage
|
||||
height={32}
|
||||
width={32}
|
||||
src={PhonepeLogo.src}
|
||||
alt="Phonepe"
|
||||
className="w-full h-full "
|
||||
/>
|
||||
</div>
|
||||
<p className="text-lg font-semibold">{t("phonepe")}</p>
|
||||
</div>
|
||||
<div>
|
||||
{isLoading ? (
|
||||
<Loader2 className="w-5 h-5 animate-spin" />
|
||||
) : (
|
||||
<FaAngleRight size={18} className="rtl:scale-x-[-1]" />
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
</button>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
export default PhonepePayment;
|
||||
224
components/PagesComponent/Subscription/ProfileSubscription.jsx
Normal file
224
components/PagesComponent/Subscription/ProfileSubscription.jsx
Normal file
@@ -0,0 +1,224 @@
|
||||
"use client";
|
||||
import { useEffect, useState } from "react";
|
||||
import AddListingPlanCard from "@/components/PagesComponent/Cards/AddListingPlanCard";
|
||||
import {
|
||||
assigFreePackageApi,
|
||||
getPackageApi,
|
||||
getPaymentSettingsApi,
|
||||
} from "@/utils/api";
|
||||
import {
|
||||
Carousel,
|
||||
CarouselContent,
|
||||
CarouselItem,
|
||||
CarouselNext,
|
||||
CarouselPrevious,
|
||||
} from "@/components/ui/carousel";
|
||||
import { t } from "@/utils";
|
||||
import PaymentModal from "./PaymentModal";
|
||||
import AddListingPlanCardSkeleton from "@/components/Skeletons/AddListingPlanCardSkeleton";
|
||||
import { CurrentLanguageData, getIsRtl } from "@/redux/reducer/languageSlice";
|
||||
import { useSelector } from "react-redux";
|
||||
import { toast } from "sonner";
|
||||
import BankDetailsModal from "./BankDetailsModal";
|
||||
import { getIsFreAdListing } from "@/redux/reducer/settingSlice";
|
||||
import { useNavigate } from "@/components/Common/useNavigate";
|
||||
|
||||
const ProfileSubscription = () => {
|
||||
const isRTL = useSelector(getIsRtl);
|
||||
const { navigate } = useNavigate();
|
||||
const CurrentLanguage = useSelector(CurrentLanguageData);
|
||||
const [listingPackages, setListingPackages] = useState([]);
|
||||
|
||||
const hasListingDiscount = listingPackages?.some(
|
||||
(p) => p?.discount_in_percentage > 0
|
||||
);
|
||||
|
||||
const [adPackages, setAdPackages] = useState([]);
|
||||
const hasAdDiscount = adPackages.some((p) => p.discount_in_percentage > 0);
|
||||
const [showPaymentModal, setShowPaymentModal] = useState(false);
|
||||
const [selectedPackage, setSelectedPackage] = useState(null);
|
||||
const [isListingPackagesLoading, setIsListingPackagesLoading] =
|
||||
useState(false);
|
||||
const [isAdPackagesLoading, setIsAdPackagesLoading] = useState(false);
|
||||
const [packageSettings, setPackageSettings] = useState(null);
|
||||
const [isLoading, setIsLoading] = useState(false);
|
||||
const isFreeAdListing = useSelector(getIsFreAdListing);
|
||||
const [toggleApiAfterPaymentSuccess, setToggleApiAfterPaymentSuccess] = useState(false)
|
||||
|
||||
|
||||
useEffect(() => {
|
||||
if (!isFreeAdListing) {
|
||||
handleFetchListingPackages();
|
||||
}
|
||||
handleFetchFeaturedPackages();
|
||||
}, [CurrentLanguage?.id, toggleApiAfterPaymentSuccess]);
|
||||
|
||||
useEffect(() => {
|
||||
if (showPaymentModal) {
|
||||
handleFetchPaymentSetting();
|
||||
}
|
||||
}, [showPaymentModal]);
|
||||
|
||||
const handleFetchPaymentSetting = async () => {
|
||||
setIsLoading(true);
|
||||
try {
|
||||
const res = await getPaymentSettingsApi.getPaymentSettings();
|
||||
const { data } = res.data;
|
||||
setPackageSettings(data);
|
||||
} catch (error) {
|
||||
console.log(error);
|
||||
} finally {
|
||||
setIsLoading(false);
|
||||
}
|
||||
};
|
||||
|
||||
const handleFetchListingPackages = async () => {
|
||||
try {
|
||||
setIsListingPackagesLoading(true);
|
||||
const res = await getPackageApi.getPackage({ type: "item_listing" });
|
||||
setListingPackages(res?.data?.data);
|
||||
} catch (error) {
|
||||
console.log(error);
|
||||
} finally {
|
||||
setIsListingPackagesLoading(false);
|
||||
}
|
||||
};
|
||||
|
||||
const handleFetchFeaturedPackages = async () => {
|
||||
try {
|
||||
setIsAdPackagesLoading(true);
|
||||
const res = await getPackageApi.getPackage({ type: "advertisement" });
|
||||
setAdPackages(res.data?.data);
|
||||
} catch (error) {
|
||||
console.log(error);
|
||||
} finally {
|
||||
setIsAdPackagesLoading(false);
|
||||
}
|
||||
};
|
||||
|
||||
const handlePurchasePackage = (pckg) => {
|
||||
if (pckg?.final_price === 0) {
|
||||
assignPackage(pckg.id);
|
||||
} else {
|
||||
setShowPaymentModal(true);
|
||||
setSelectedPackage(pckg);
|
||||
}
|
||||
};
|
||||
|
||||
const assignPackage = async (id) => {
|
||||
try {
|
||||
const res = await assigFreePackageApi.assignFreePackage({
|
||||
package_id: id,
|
||||
});
|
||||
const data = res?.data;
|
||||
if (data?.error === false) {
|
||||
toast.success(data.message);
|
||||
navigate("/");
|
||||
} else {
|
||||
toast.error(data.message);
|
||||
}
|
||||
} catch (error) {
|
||||
toast.error(data.message);
|
||||
console.log(error);
|
||||
}
|
||||
};
|
||||
|
||||
return (
|
||||
<>
|
||||
{isListingPackagesLoading ? (
|
||||
<AddListingPlanCardSkeleton />
|
||||
) : (
|
||||
<>
|
||||
{listingPackages.length > 0 && (
|
||||
<div className="flex flex-col gap-4">
|
||||
<h1 className="text-2xl font-medium">{t("adListingPlan")}</h1>
|
||||
<div className="relative">
|
||||
<Carousel
|
||||
key={isRTL ? "rtl" : "ltr"}
|
||||
opts={{
|
||||
align: "start",
|
||||
containScroll: "trim",
|
||||
direction: isRTL ? "rtl" : "ltr",
|
||||
}}
|
||||
className="w-full"
|
||||
>
|
||||
<CarouselPrevious className="flex absolute top-1/2 ltr:left-2 rtl:right-2 rtl:scale-x-[-1] -translate-y-1/2 bg-primary text-white rounded-full z-10" />
|
||||
<CarouselNext className="flex absolute top-1/2 ltr:right-2 rtl:left-2 rtl:scale-x-[-1] -translate-y-1/2 bg-primary text-white rounded-full z-10" />
|
||||
<CarouselContent
|
||||
className={`gap-4 ${hasListingDiscount ? "pt-6" : ""}`}
|
||||
>
|
||||
{listingPackages?.map((pckg) => (
|
||||
<CarouselItem
|
||||
key={pckg.id}
|
||||
className="basis-[100%] sm:basis-3/4 md:basis-1/2 lg:basis-[66.66%] xl:basis-[45%] first:pl-4"
|
||||
>
|
||||
<AddListingPlanCard
|
||||
pckg={pckg}
|
||||
handlePurchasePackage={handlePurchasePackage}
|
||||
/>
|
||||
</CarouselItem>
|
||||
))}
|
||||
</CarouselContent>
|
||||
</Carousel>
|
||||
</div>
|
||||
</div>
|
||||
)}
|
||||
</>
|
||||
)}
|
||||
{isAdPackagesLoading ? (
|
||||
<AddListingPlanCardSkeleton />
|
||||
) : (
|
||||
<div
|
||||
className={`flex flex-col gap-4 ${listingPackages.length > 0 ? "mt-8" : ""
|
||||
}`}
|
||||
>
|
||||
<h1 className="text-2xl font-medium">{t("featuredAdPlan")}</h1>
|
||||
<div className="relative">
|
||||
<Carousel
|
||||
key={isRTL ? "rtl" : "ltr"}
|
||||
opts={{
|
||||
align: "start",
|
||||
containScroll: "trim",
|
||||
direction: isRTL ? "rtl" : "ltr",
|
||||
}}
|
||||
className="w-full"
|
||||
>
|
||||
<CarouselPrevious className="flex absolute top-1/2 ltr:left-2 rtl:right-2 rtl:scale-x-[-1] -translate-y-1/2 bg-primary text-white rounded-full z-10" />
|
||||
<CarouselNext className="flex absolute top-1/2 ltr:right-2 rtl:left-2 rtl:scale-x-[-1] -translate-y-1/2 bg-primary text-white rounded-full z-10" />
|
||||
|
||||
<CarouselContent
|
||||
className={`gap-4 ${hasAdDiscount ? "pt-6" : ""}`}
|
||||
>
|
||||
{adPackages?.map((pckg) => (
|
||||
<CarouselItem
|
||||
key={pckg.id}
|
||||
className="basis-[100%] sm:basis-3/4 md:basis-1/2 lg:basis-[66.66%] xl:basis-[45%] first:pl-4"
|
||||
>
|
||||
<AddListingPlanCard
|
||||
pckg={pckg}
|
||||
handlePurchasePackage={handlePurchasePackage}
|
||||
/>
|
||||
</CarouselItem>
|
||||
))}
|
||||
</CarouselContent>
|
||||
</Carousel>
|
||||
</div>
|
||||
</div>
|
||||
)}
|
||||
<PaymentModal
|
||||
showPaymentModal={showPaymentModal}
|
||||
setShowPaymentModal={setShowPaymentModal}
|
||||
selectedPackage={selectedPackage}
|
||||
packageSettings={packageSettings}
|
||||
isLoading={isLoading}
|
||||
setToggleApiAfterPaymentSuccess={setToggleApiAfterPaymentSuccess}
|
||||
/>
|
||||
<BankDetailsModal
|
||||
packageId={selectedPackage?.id}
|
||||
bankDetails={packageSettings?.bankTransfer}
|
||||
/>
|
||||
</>
|
||||
);
|
||||
};
|
||||
|
||||
export default ProfileSubscription;
|
||||
109
components/PagesComponent/Subscription/RazorpayPayment.jsx
Normal file
109
components/PagesComponent/Subscription/RazorpayPayment.jsx
Normal file
@@ -0,0 +1,109 @@
|
||||
import { createPaymentIntentApi } from "@/utils/api";
|
||||
import { useState } from "react";
|
||||
import { useRazorpay } from "react-razorpay";
|
||||
import RazorpayLogo from "../../../public/assets/ic_razorpay.png";
|
||||
import { FaAngleRight } from "react-icons/fa";
|
||||
import { useSelector } from "react-redux";
|
||||
import { toast } from "sonner";
|
||||
import { t } from "@/utils";
|
||||
import CustomImage from "@/components/Common/CustomImage";
|
||||
import { Loader2 } from "lucide-react";
|
||||
|
||||
const RazorpayPayment = ({
|
||||
packageSettings,
|
||||
selectedPackage,
|
||||
setShowPaymentModal,
|
||||
setToggleApiAfterPaymentSuccess
|
||||
}) => {
|
||||
const { Razorpay } = useRazorpay();
|
||||
const { data: settingsData } = useSelector((state) => state.Settings);
|
||||
const { data: user } = useSelector((state) => state.UserSignup);
|
||||
const [isLoading, setIsLoading] = useState(false);
|
||||
let rzpay;
|
||||
|
||||
const PayWithRazorPay = async () => {
|
||||
try {
|
||||
setIsLoading(true);
|
||||
const res = await createPaymentIntentApi.createIntent({
|
||||
package_id: selectedPackage.id,
|
||||
payment_method: packageSettings.Razorpay.payment_method,
|
||||
});
|
||||
if (res.data.error) {
|
||||
toast.error(res.data.message);
|
||||
return;
|
||||
}
|
||||
setShowPaymentModal(false);
|
||||
const paymentIntent = res.data.data.payment_intent;
|
||||
const options = {
|
||||
key: packageSettings.Razorpay.api_key,
|
||||
name: settingsData.company_name,
|
||||
description: settingsData.company_name,
|
||||
image: settingsData.company_logo,
|
||||
order_id: paymentIntent.id,
|
||||
handler: function (response) {
|
||||
toast.success(t("paymentSuccess"));
|
||||
setToggleApiAfterPaymentSuccess((prev) => !prev)
|
||||
},
|
||||
prefill: {
|
||||
name: user.name,
|
||||
email: user.email,
|
||||
contact: user.mobile,
|
||||
},
|
||||
notes: {
|
||||
address: user.address,
|
||||
user_id: user.id,
|
||||
package_id: selectedPackage.id,
|
||||
},
|
||||
theme: {
|
||||
color: settingsData.web_theme_color,
|
||||
},
|
||||
};
|
||||
|
||||
rzpay = new Razorpay(options); // Assign rzpay outside the function
|
||||
|
||||
rzpay.on("payment.failed", function (response) {
|
||||
console.error(response.error.description);
|
||||
if (rzpay) {
|
||||
rzpay?.close();
|
||||
}
|
||||
});
|
||||
|
||||
rzpay.open();
|
||||
} catch (error) {
|
||||
console.error("Error during payment", error);
|
||||
toast.error(t("errorProcessingPayment"));
|
||||
} finally {
|
||||
setIsLoading(false);
|
||||
}
|
||||
};
|
||||
|
||||
return (
|
||||
<div>
|
||||
<button onClick={PayWithRazorPay} className="w-full p-2">
|
||||
<div className="flex items-center gap-2 justify-between ">
|
||||
<div className="flex gap-3 items-center">
|
||||
<div className="w-8 h-8">
|
||||
<CustomImage
|
||||
src={RazorpayLogo.src}
|
||||
alt="Razorpay"
|
||||
width={32}
|
||||
height={32}
|
||||
className="w-full h-full "
|
||||
/>
|
||||
</div>
|
||||
<p className="text-lg font-semibold">{t("razorPay")}</p>
|
||||
</div>
|
||||
<div>
|
||||
{isLoading ? (
|
||||
<Loader2 className="w-5 h-5 animate-spin" />
|
||||
) : (
|
||||
<FaAngleRight size={18} className="rtl:scale-x-[-1]" />
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
</button>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
export default RazorpayPayment;
|
||||
179
components/PagesComponent/Subscription/StripePayment.jsx
Normal file
179
components/PagesComponent/Subscription/StripePayment.jsx
Normal file
@@ -0,0 +1,179 @@
|
||||
import React, { useState, useEffect, useCallback } from "react";
|
||||
|
||||
import {
|
||||
Elements,
|
||||
ElementsConsumer,
|
||||
PaymentElement,
|
||||
} from "@stripe/react-stripe-js";
|
||||
import { loadStripe } from "@stripe/stripe-js";
|
||||
import { createPaymentIntentApi } from "@/utils/api";
|
||||
import { toast } from "sonner";
|
||||
import { t } from "@/utils";
|
||||
import { Loader2 } from "lucide-react";
|
||||
|
||||
const StripePayment = ({
|
||||
selectedPackage,
|
||||
packageSettings,
|
||||
PaymentModalClose,
|
||||
setShowStripePayment,
|
||||
setToggleApiAfterPaymentSuccess
|
||||
}) => {
|
||||
const [stripePromise, setStripePromise] = useState(null);
|
||||
const [clientSecret, setClientSecret] = useState("");
|
||||
const [loading, setLoading] = useState(true);
|
||||
const [formattedPrice, setFormattedPrice] = useState('')
|
||||
|
||||
useEffect(() => {
|
||||
const loadStripeInstance = async () => {
|
||||
if (packageSettings?.Stripe?.api_key) {
|
||||
const stripeInstance = await loadStripe(packageSettings.Stripe.api_key);
|
||||
setStripePromise(stripeInstance);
|
||||
}
|
||||
};
|
||||
loadStripeInstance();
|
||||
}, [packageSettings?.Stripe?.api_key]);
|
||||
|
||||
const handleStripePayment = useCallback(async () => {
|
||||
try {
|
||||
const res = await createPaymentIntentApi.createIntent({
|
||||
package_id: selectedPackage.id,
|
||||
payment_method: packageSettings.Stripe.payment_method,
|
||||
});
|
||||
if (res.data.error === true) {
|
||||
toast.error(res.data.message);
|
||||
return;
|
||||
}
|
||||
// Extract payment intent data from response
|
||||
const paymentIntent =
|
||||
res.data.data.payment_intent?.payment_gateway_response;
|
||||
|
||||
// Extract formatted price from payment_intent (not from payment_gateway_response)
|
||||
const formattedPrice = res.data.data.payment_intent?.formatted_price;
|
||||
if (formattedPrice) {
|
||||
setFormattedPrice(formattedPrice);
|
||||
}
|
||||
|
||||
const clientSecret = paymentIntent.client_secret;
|
||||
setClientSecret(clientSecret);
|
||||
setShowStripePayment(true);
|
||||
} catch (error) {
|
||||
console.error("Error during Stripe payment", error);
|
||||
toast.error(t("errorOccurred"));
|
||||
} finally {
|
||||
setLoading(false);
|
||||
}
|
||||
}, [
|
||||
selectedPackage.id,
|
||||
packageSettings?.Stripe?.payment_method,
|
||||
setShowStripePayment,
|
||||
]);
|
||||
|
||||
useEffect(() => {
|
||||
handleStripePayment();
|
||||
}, [handleStripePayment]);
|
||||
|
||||
// PaymentForm component that uses the formattedPrice from parent scope
|
||||
const PaymentForm = ({ elements, stripe }) => {
|
||||
const [IsProcessing, setIsProcessing] = useState(false);
|
||||
|
||||
const handleSubmit = async (event) => {
|
||||
event.preventDefault();
|
||||
setIsProcessing(true);
|
||||
|
||||
// First, submit the elements to validate and prepare the payment method
|
||||
// This must be called before confirmPayment
|
||||
const { error: submitError } = await elements.submit();
|
||||
|
||||
if (submitError) {
|
||||
// Handle validation errors
|
||||
toast.error(submitError.message || t("paymentFailed"));
|
||||
setIsProcessing(false);
|
||||
return;
|
||||
}
|
||||
|
||||
// PaymentElement handles payment method creation internally
|
||||
// Just confirm the payment with the client secret
|
||||
try {
|
||||
const { error: confirmError } = await stripe.confirmPayment({
|
||||
elements,
|
||||
clientSecret,
|
||||
redirect: "if_required",
|
||||
});
|
||||
|
||||
if (confirmError) {
|
||||
// Handle payment confirmation error
|
||||
console.error(confirmError.message);
|
||||
toast.error(confirmError.message || t("paymentFailed"));
|
||||
} else {
|
||||
// Payment succeeded
|
||||
toast.success(t("paymentSuccess"));
|
||||
setToggleApiAfterPaymentSuccess((prev) => !prev)
|
||||
PaymentModalClose();
|
||||
}
|
||||
} catch (error) {
|
||||
console.error("Error during payment:", error);
|
||||
} finally {
|
||||
setIsProcessing(false);
|
||||
}
|
||||
};
|
||||
|
||||
return (
|
||||
<form onSubmit={handleSubmit}>
|
||||
<div className="stripe_module">
|
||||
{/* <div className="mb-4 text-lg font-semibold">
|
||||
{t("pay")} {amountToPay / 100} {currency?.toUpperCase()}
|
||||
</div> */}
|
||||
<PaymentElement />
|
||||
<button
|
||||
className="w-full bg-primary text-white p-2 rounded-md my-4 flex items-center justify-center"
|
||||
type="submit"
|
||||
disabled={!stripePromise || IsProcessing}
|
||||
>
|
||||
{IsProcessing ? (
|
||||
<Loader2 className="size-4 animate-spin" />
|
||||
) : (
|
||||
t("pay") + " " + formattedPrice
|
||||
)}
|
||||
</button>
|
||||
</div>
|
||||
</form>
|
||||
);
|
||||
};
|
||||
|
||||
return (
|
||||
<>
|
||||
{loading ? (
|
||||
<div className="">
|
||||
<div className="animate-pulse">
|
||||
<div className="w-full h-10 bg-gray-200 rounded-md mb-2"></div>
|
||||
<div className="flex justify-between mb-4">
|
||||
<div className="w-1/2 h-5 bg-gray-200 rounded-md"></div>
|
||||
<div className="w-1/4 h-5 bg-gray-200 rounded-md"></div>
|
||||
</div>
|
||||
<div className="w-full h-12 bg-gray-200 rounded-md mt-6"></div>
|
||||
</div>
|
||||
</div>
|
||||
) : (
|
||||
stripePromise &&
|
||||
clientSecret && (
|
||||
<div className="card">
|
||||
{/* <div className="card-header">{t("payWithStripe")} :</div> */}
|
||||
<div className="card-body">
|
||||
<Elements stripe={stripePromise} options={{
|
||||
clientSecret
|
||||
}} >
|
||||
<ElementsConsumer>
|
||||
{({ stripe, elements }) => (
|
||||
<PaymentForm elements={elements} stripe={stripe} />
|
||||
)}
|
||||
</ElementsConsumer>
|
||||
</Elements>
|
||||
</div>
|
||||
</div>
|
||||
)
|
||||
)}
|
||||
</>
|
||||
);
|
||||
};
|
||||
|
||||
export default StripePayment;
|
||||
234
components/PagesComponent/Subscription/Subscription.jsx
Normal file
234
components/PagesComponent/Subscription/Subscription.jsx
Normal file
@@ -0,0 +1,234 @@
|
||||
"use client";
|
||||
import { useEffect, useState } from "react";
|
||||
import AddListingPlanCard from "@/components/PagesComponent/Cards/AddListingPlanCard";
|
||||
import {
|
||||
assigFreePackageApi,
|
||||
getPackageApi,
|
||||
getPaymentSettingsApi,
|
||||
} from "@/utils/api";
|
||||
import {
|
||||
Carousel,
|
||||
CarouselContent,
|
||||
CarouselItem,
|
||||
CarouselNext,
|
||||
CarouselPrevious,
|
||||
} from "@/components/ui/carousel";
|
||||
import { t } from "@/utils";
|
||||
import PaymentModal from "./PaymentModal";
|
||||
import { CurrentLanguageData, getIsRtl } from "@/redux/reducer/languageSlice";
|
||||
import { useSelector } from "react-redux";
|
||||
import Layout from "@/components/Layout/Layout";
|
||||
import { getIsLoggedIn } from "@/redux/reducer/authSlice";
|
||||
import { setIsLoginOpen } from "@/redux/reducer/globalStateSlice";
|
||||
import { toast } from "sonner";
|
||||
import BankDetailsModal from "./BankDetailsModal";
|
||||
import BreadCrumb from "@/components/BreadCrumb/BreadCrumb";
|
||||
import AdListingPublicPlanCardSkeleton from "@/components/Skeletons/AdListingPublicPlanCardSkeleton";
|
||||
import { getIsFreAdListing } from "@/redux/reducer/settingSlice";
|
||||
import { useNavigate } from "@/components/Common/useNavigate";
|
||||
|
||||
const Subscription = () => {
|
||||
const isRTL = useSelector(getIsRtl);
|
||||
const { navigate } = useNavigate();
|
||||
const CurrentLanguage = useSelector(CurrentLanguageData);
|
||||
|
||||
const [listingPackages, setListingPackages] = useState([]);
|
||||
const hasListingDiscount = listingPackages?.some(
|
||||
(p) => p?.discount_in_percentage > 0
|
||||
);
|
||||
const [isListingPackagesLoading, setIsListingPackagesLoading] =
|
||||
useState(false);
|
||||
|
||||
const [selectedPackage, setSelectedPackage] = useState(null);
|
||||
|
||||
const [adPackages, setAdPackages] = useState([]);
|
||||
const hasAdDiscount = adPackages.some((p) => p.discount_in_percentage > 0);
|
||||
const [isAdPackagesLoading, setIsAdPackagesLoading] = useState(false);
|
||||
const [toggleApiAfterPaymentSuccess, setToggleApiAfterPaymentSuccess] = useState(false)
|
||||
|
||||
|
||||
const [showPaymentModal, setShowPaymentModal] = useState(false);
|
||||
const [isLoading, setIsLoading] = useState(false);
|
||||
const [packageSettings, setPackageSettings] = useState(null);
|
||||
const isLoggedIn = useSelector(getIsLoggedIn);
|
||||
const isFreeAdListing = useSelector(getIsFreAdListing);
|
||||
|
||||
useEffect(() => {
|
||||
if (!isFreeAdListing) {
|
||||
handleFetchListingPackages();
|
||||
}
|
||||
handleFetchFeaturedPackages();
|
||||
}, [CurrentLanguage?.id, toggleApiAfterPaymentSuccess]);
|
||||
|
||||
useEffect(() => {
|
||||
if (showPaymentModal) {
|
||||
handleFetchPaymentSetting();
|
||||
}
|
||||
}, [showPaymentModal]);
|
||||
|
||||
const handleFetchPaymentSetting = async () => {
|
||||
setIsLoading(true);
|
||||
try {
|
||||
const res = await getPaymentSettingsApi.getPaymentSettings();
|
||||
const { data } = res.data;
|
||||
setPackageSettings(data);
|
||||
} catch (error) {
|
||||
console.log(error);
|
||||
} finally {
|
||||
setIsLoading(false);
|
||||
}
|
||||
};
|
||||
|
||||
const handleFetchListingPackages = async () => {
|
||||
try {
|
||||
setIsListingPackagesLoading(true);
|
||||
const res = await getPackageApi.getPackage({ type: "item_listing" });
|
||||
setListingPackages(res?.data?.data);
|
||||
} catch (error) {
|
||||
console.log(error);
|
||||
} finally {
|
||||
setIsListingPackagesLoading(false);
|
||||
}
|
||||
};
|
||||
|
||||
const handleFetchFeaturedPackages = async () => {
|
||||
try {
|
||||
setIsAdPackagesLoading(true);
|
||||
const res = await getPackageApi.getPackage({ type: "advertisement" });
|
||||
setAdPackages(res.data?.data);
|
||||
} catch (error) {
|
||||
console.log(error);
|
||||
} finally {
|
||||
setIsAdPackagesLoading(false);
|
||||
}
|
||||
};
|
||||
|
||||
const handlePurchasePackage = (pckg) => {
|
||||
if (!isLoggedIn) {
|
||||
setIsLoginOpen(true);
|
||||
return;
|
||||
}
|
||||
if (pckg?.final_price === 0) {
|
||||
assignPackage(pckg.id);
|
||||
} else {
|
||||
setShowPaymentModal(true);
|
||||
setSelectedPackage(pckg);
|
||||
}
|
||||
};
|
||||
|
||||
const assignPackage = async (id) => {
|
||||
try {
|
||||
const res = await assigFreePackageApi.assignFreePackage({
|
||||
package_id: id,
|
||||
});
|
||||
const data = res?.data;
|
||||
if (data?.error === false) {
|
||||
toast.success(data.message);
|
||||
navigate("/");
|
||||
} else {
|
||||
toast.error(data.message);
|
||||
}
|
||||
} catch (error) {
|
||||
toast.error(data.message);
|
||||
console.log(error);
|
||||
}
|
||||
};
|
||||
|
||||
return (
|
||||
<Layout>
|
||||
<BreadCrumb title2={t("subscription")} />
|
||||
<div className="container">
|
||||
{isListingPackagesLoading ? (
|
||||
<AdListingPublicPlanCardSkeleton />
|
||||
) : (
|
||||
listingPackages?.length > 0 && (
|
||||
<div className="flex flex-col gap-4 mt-8">
|
||||
<h1 className="text-2xl font-medium">{t("adListingPlan")}</h1>
|
||||
<div className="relative">
|
||||
<Carousel
|
||||
key={isRTL ? "rtl" : "ltr"}
|
||||
opts={{
|
||||
align: "start",
|
||||
containScroll: "trim",
|
||||
direction: isRTL ? "rtl" : "ltr",
|
||||
}}
|
||||
>
|
||||
<CarouselPrevious className="hidden md:flex absolute top-1/2 ltr:left-2 rtl:right-2 rtl:scale-x-[-1] -translate-y-1/2 bg-primary text-white rounded-full z-10" />
|
||||
<CarouselNext className="hidden md:flex absolute top-1/2 ltr:right-2 rtl:left-2 rtl:scale-x-[-1] -translate-y-1/2 bg-primary text-white rounded-full z-10" />
|
||||
<CarouselContent
|
||||
className={`sm:gap-4 ${hasListingDiscount ? "pt-6" : ""}`}
|
||||
>
|
||||
{listingPackages?.map((pckg) => (
|
||||
<CarouselItem
|
||||
key={pckg.id}
|
||||
className="basis-[90%] sm:basis-[75%] md:basis-[55%] lg:basis-[45%] xl:basis-[35%] 2xl:basis-[30%]"
|
||||
>
|
||||
<AddListingPlanCard
|
||||
pckg={pckg}
|
||||
handlePurchasePackage={handlePurchasePackage}
|
||||
/>
|
||||
</CarouselItem>
|
||||
))}
|
||||
</CarouselContent>
|
||||
</Carousel>
|
||||
</div>
|
||||
</div>
|
||||
)
|
||||
)}
|
||||
|
||||
{isAdPackagesLoading ? (
|
||||
<AdListingPublicPlanCardSkeleton />
|
||||
) : (
|
||||
<div className="flex flex-col gap-4 mt-8">
|
||||
<h1 className="text-2xl font-medium">{t("featuredAdPlan")}</h1>
|
||||
<div className="relative">
|
||||
<Carousel
|
||||
key={isRTL ? "rtl" : "ltr"}
|
||||
opts={{
|
||||
align: "start",
|
||||
containScroll: "trim",
|
||||
direction: isRTL ? "rtl" : "ltr",
|
||||
}}
|
||||
className="w-full"
|
||||
>
|
||||
<CarouselPrevious className="hidden md:flex absolute top-1/2 ltr:left-2 rtl:right-2 rtl:scale-x-[-1] -translate-y-1/2 bg-primary text-white rounded-full z-10" />
|
||||
<CarouselNext className="hidden md:flex absolute top-1/2 ltr:right-2 rtl:left-2 rtl:scale-x-[-1] -translate-y-1/2 bg-primary text-white rounded-full z-10" />
|
||||
<CarouselContent
|
||||
className={`sm:gap-4 ${hasAdDiscount ? "pt-6" : ""}`}
|
||||
>
|
||||
{adPackages?.map((pckg) => (
|
||||
<CarouselItem
|
||||
key={pckg.id}
|
||||
className="basis-[90%] sm:basis-[75%] md:basis-[55%] lg:basis-[45%] xl:basis-[35%] 2xl:basis-[30%]"
|
||||
>
|
||||
<AddListingPlanCard
|
||||
pckg={pckg}
|
||||
handlePurchasePackage={handlePurchasePackage}
|
||||
/>
|
||||
</CarouselItem>
|
||||
))}
|
||||
</CarouselContent>
|
||||
</Carousel>
|
||||
</div>
|
||||
</div>
|
||||
)}
|
||||
|
||||
<PaymentModal
|
||||
showPaymentModal={showPaymentModal}
|
||||
setShowPaymentModal={setShowPaymentModal}
|
||||
selectedPackage={selectedPackage}
|
||||
packageSettings={packageSettings}
|
||||
isLoading={isLoading}
|
||||
setToggleApiAfterPaymentSuccess={setToggleApiAfterPaymentSuccess}
|
||||
|
||||
/>
|
||||
<BankDetailsModal
|
||||
packageId={selectedPackage?.id}
|
||||
bankDetails={packageSettings?.bankTransfer}
|
||||
/>
|
||||
</div>
|
||||
</Layout>
|
||||
);
|
||||
};
|
||||
|
||||
export default Subscription;
|
||||
Reference in New Issue
Block a user