199 lines
9.2 KiB
TypeScript
199 lines
9.2 KiB
TypeScript
"use client";
|
||
import React, { useState } from "react";
|
||
import { motion, AnimatePresence } from "motion/react";
|
||
import { X, CreditCard, TrendingUp, ShieldCheck } from "lucide-react";
|
||
import { toPersian, cn } from "../lib/utils";
|
||
import { toast } from "sonner";
|
||
import { useUserStore } from "../lib/store/userStore";
|
||
import { useSettingsStore } from "../lib/store/settingsStore";
|
||
import api from "../lib/services/api";
|
||
|
||
interface TopUpModalProps {
|
||
isOpen: boolean;
|
||
onClose: () => void;
|
||
onConfirm?: (amount: number) => void;
|
||
initialAmount?: number;
|
||
}
|
||
|
||
const PRESET_AMOUNTS = [
|
||
{ label: "۵۰۰,۰۰۰ تومان", value: 500000 },
|
||
{ label: "۱,۰۰۰,۰۰۰ تومان", value: 1000000 },
|
||
{ label: "۲,۰۰۰,۰۰۰ تومان", value: 2000000 },
|
||
];
|
||
|
||
export default function TopUpModal({ isOpen, onClose, initialAmount }: TopUpModalProps) {
|
||
const [amount, setAmount] = useState<string>(() => (initialAmount ? String(initialAmount) : ""));
|
||
const [selectedPreset, setSelectedPreset] = useState<number | null>(null);
|
||
const [loading, setLoading] = useState(false);
|
||
|
||
React.useEffect(() => {
|
||
if (initialAmount && initialAmount > 0) {
|
||
setAmount(String(initialAmount));
|
||
}
|
||
}, [initialAmount, isOpen]);
|
||
|
||
const isCardToCardEnabled = useSettingsStore((state) =>
|
||
state.getBoolean("payment_card_to_card_enabled", false),
|
||
);
|
||
|
||
const handlePresetSelect = (val: number) => {
|
||
setSelectedPreset(val);
|
||
setAmount(val.toString());
|
||
};
|
||
|
||
const handleInputChange = (e: React.ChangeEvent<HTMLInputElement>) => {
|
||
const val = e.target.value.replace(/,/g, "");
|
||
if (/^\d*$/.test(val)) {
|
||
setAmount(val);
|
||
setSelectedPreset(null);
|
||
}
|
||
};
|
||
|
||
const handleSubmit = async (e: React.FormEvent) => {
|
||
e.preventDefault();
|
||
if (!useUserStore.getState().isLoggedIn) {
|
||
toast.error("جهت افزایش موجودی کیف پول لطفاً ابتدا وارد حساب کاربری خود شوید.");
|
||
return;
|
||
}
|
||
const finalAmount = parseInt(amount);
|
||
if (isNaN(finalAmount) || finalAmount < 10000) {
|
||
toast.error("حداقل مبلغ شارژ ۱۰,۰۰۰ تومان است");
|
||
return;
|
||
}
|
||
|
||
setLoading(true);
|
||
try {
|
||
const res = await api.post("/payment/zibal/wallet/initiate", {
|
||
amount: finalAmount,
|
||
});
|
||
if (res.data?.paymentUrl) {
|
||
toast.success("در حال انتقال امن به درگاه پرداخت زیبال...");
|
||
window.location.href = res.data.paymentUrl;
|
||
} else {
|
||
toast.error("خطا در ایجاد تراکنش درگاه پرداخت زیبال");
|
||
setLoading(false);
|
||
}
|
||
} catch (err: unknown) {
|
||
const gErr = err as { response?: { data?: { message?: string } }; message?: string };
|
||
const errMsg = gErr.response?.data?.message || gErr.message || "خطا در برقراری ارتباط با درگاه پرداخت";
|
||
toast.error(errMsg);
|
||
setLoading(false);
|
||
}
|
||
};
|
||
|
||
return (
|
||
<AnimatePresence>
|
||
{isOpen && (
|
||
<div className="fixed inset-0 z-[110] flex items-center justify-center p-4">
|
||
<motion.div
|
||
initial={{ opacity: 0 }}
|
||
animate={{ opacity: 1 }}
|
||
exit={{ opacity: 0 }}
|
||
onClick={onClose}
|
||
className="absolute inset-0 bg-medical-gray-900/60 backdrop-blur-md"
|
||
/>
|
||
<motion.div
|
||
initial={{ opacity: 0, scale: 0.9, y: 20 }}
|
||
animate={{ opacity: 1, scale: 1, y: 0 }}
|
||
exit={{ opacity: 0, scale: 0.9, y: 20 }}
|
||
className="bg-white w-full max-w-md rounded-[3rem] relative z-10 shadow-2xl overflow-hidden font-vazir text-right"
|
||
dir="rtl"
|
||
>
|
||
<div className="bg-medical-gray-50 px-8 py-6 border-b border-medical-gray-100 flex items-center justify-between">
|
||
<div className="flex items-center gap-3">
|
||
<div className="w-10 h-10 bg-canina-blue rounded-xl flex items-center justify-center text-white shadow-md shadow-canina-blue/20">
|
||
<TrendingUp className="w-5 h-5" />
|
||
</div>
|
||
<h3 className="text-xl font-black text-medical-gray-900 italic">شارژ آنلاین کیف پول</h3>
|
||
</div>
|
||
<button
|
||
onClick={onClose}
|
||
className="w-10 h-10 flex items-center justify-center rounded-xl bg-white border border-medical-gray-200 text-medical-gray-400 hover:text-red-500 transition-all cursor-pointer"
|
||
>
|
||
<X className="w-5 h-5" />
|
||
</button>
|
||
</div>
|
||
|
||
<form onSubmit={handleSubmit} className="p-8 space-y-6">
|
||
<div className="space-y-3">
|
||
<label className="text-[10px] font-black text-medical-gray-400 uppercase tracking-widest block pr-2">یکی از مبالغ پیشنهادی را انتخاب کنید</label>
|
||
<div className="flex flex-wrap gap-2">
|
||
{PRESET_AMOUNTS.map((preset) => (
|
||
<button
|
||
key={preset.value}
|
||
type="button"
|
||
onClick={() => handlePresetSelect(preset.value)}
|
||
className={cn(
|
||
"px-5 py-3 rounded-2xl text-sm font-black transition-all border cursor-pointer",
|
||
selectedPreset === preset.value
|
||
? "bg-canina-blue text-white border-canina-blue shadow-lg shadow-canina-blue/20"
|
||
: "bg-white text-medical-gray-700 border-medical-gray-100 hover:border-canina-blue"
|
||
)}
|
||
>
|
||
{preset.label}
|
||
</button>
|
||
))}
|
||
</div>
|
||
</div>
|
||
|
||
<div className="space-y-3">
|
||
<label className="text-[10px] font-black text-medical-gray-400 uppercase tracking-widest block pr-2">یا مبلغ دلخواه خود را وارد کنید (تومان)</label>
|
||
<div className="relative">
|
||
<input
|
||
autoFocus
|
||
type="text"
|
||
value={amount ? parseInt(amount).toLocaleString() : ""}
|
||
onChange={handleInputChange}
|
||
className="w-full bg-medical-gray-50 border border-medical-gray-100 rounded-3xl py-5 px-8 outline-none font-black text-2xl text-center text-medical-gray-900 transition-all focus:ring-4 focus:ring-canina-blue/10 focus:border-canina-blue italic"
|
||
placeholder="۰"
|
||
/>
|
||
{amount && (
|
||
<div className="absolute left-6 top-1/2 -translate-y-1/2 text-xs font-bold text-medical-gray-400">تومان</div>
|
||
)}
|
||
</div>
|
||
</div>
|
||
|
||
{isCardToCardEnabled ? (
|
||
<div className="bg-blue-50 border border-blue-200 rounded-2xl p-4 space-y-2 text-right">
|
||
<div className="flex items-center gap-2 text-canina-blue font-bold text-xs">
|
||
<CreditCard className="w-4 h-4" />
|
||
<span>اطلاعات کارت جهت واریز کارتبهکارت:</span>
|
||
</div>
|
||
<div className="text-[11px] text-gray-700 space-y-1 font-mono dir-ltr bg-white p-2.5 rounded-xl border border-blue-100">
|
||
<div>کارت: <strong>۶۲۱۹-۸۶۱۹-۷۴۰۱-۲۰۷۱</strong> (بانک سامان)</div>
|
||
<div>شبا: <strong>IR-65 0560 6118 2800 5725 1015 01</strong></div>
|
||
<div className="text-gray-500 font-sans text-[10px] pt-1 border-t">به نام: پارسادرمانی کنینا (نوواگارد) - پارسا آقایی</div>
|
||
</div>
|
||
<p className="text-[10px] font-bold text-blue-800 leading-relaxed pt-1">
|
||
پس از واریز، فیش توسط تیم پشتیبانی بررسی و تایید خواهد شد.
|
||
</p>
|
||
</div>
|
||
) : (
|
||
<div className="bg-emerald-50 border border-emerald-200 rounded-2xl p-3.5 flex items-center gap-3 text-emerald-800 text-xs font-bold">
|
||
<ShieldCheck className="w-5 h-5 text-emerald-600 shrink-0" />
|
||
<span>پرداخت امن از طریق درگاه مستقیم بانکی زیبال و اتصال به کلیه کارتهای عضو شتاب</span>
|
||
</div>
|
||
)}
|
||
|
||
<button
|
||
type="submit"
|
||
disabled={loading || !amount}
|
||
className="w-full py-5 bg-canina-blue text-white rounded-[2rem] font-black text-base flex items-center justify-center gap-3 hover:bg-indigo-700 transition-all shadow-xl shadow-canina-blue/20 disabled:opacity-50 disabled:cursor-not-allowed group cursor-pointer"
|
||
>
|
||
{loading ? (
|
||
<div className="w-6 h-6 border-4 border-white/20 border-t-white rounded-full animate-spin" />
|
||
) : (
|
||
<>
|
||
<CreditCard className="w-6 h-6 group-hover:scale-110 transition-transform" />
|
||
اتصال به درگاه و شارژ آنلاین
|
||
</>
|
||
)}
|
||
</button>
|
||
</form>
|
||
</motion.div>
|
||
</div>
|
||
)}
|
||
</AnimatePresence>
|
||
);
|
||
}
|