812 lines
29 KiB
TypeScript
812 lines
29 KiB
TypeScript
import {
|
||
ChevronLeft,
|
||
ChevronRight,
|
||
Loader,
|
||
Loader2,
|
||
Plus,
|
||
Trash2,
|
||
TriangleAlert,
|
||
X,
|
||
} from "lucide-react";
|
||
import HeaderWithSupport from "../components/HeaderWithSupport";
|
||
import {
|
||
Carousel,
|
||
CarouselApi,
|
||
CarouselContent,
|
||
CarouselItem,
|
||
} from "../components/ui/carousel";
|
||
import { useCallback, useState, useRef, useEffect } from "react";
|
||
import { useNavigate } from "@remix-run/react";
|
||
import { useCarousel } from "../hooks/useCarousel";
|
||
import {
|
||
Drawer,
|
||
DrawerTitle,
|
||
DrawerContent,
|
||
DrawerHeader,
|
||
} from "../components/ui/drawer";
|
||
import {
|
||
useServiceCreateCashRequest,
|
||
useServiceGetSellerBalance,
|
||
useServiceGetBankAccounts,
|
||
useServiceRegisterBankAccount,
|
||
useServiceDeleteBankAccount,
|
||
} from "../requestHandler/use-wallet-hooks";
|
||
import {
|
||
formatAccountNumber,
|
||
formatPrice,
|
||
getPersianTextOfPrice,
|
||
safeGoBack,
|
||
} from "../utils/helpers";
|
||
import { Button } from "../components/ui/button";
|
||
import AppInput from "../components/AppInput";
|
||
import { Dialog, DialogContent, DialogOverlay } from "../components/ui/dialog";
|
||
import { Input } from "../components/ui/input";
|
||
import { MetaFunction } from "@remix-run/node";
|
||
import {
|
||
detectIranianBank,
|
||
validateIranianBankCard,
|
||
} from "~/utils/iranian-banks";
|
||
import { BankLogo } from "~/components/BankLogo";
|
||
|
||
export default function CashingFunds() {
|
||
const navigate = useNavigate();
|
||
const handleBack = () => {
|
||
safeGoBack(navigate);
|
||
};
|
||
const [currentIndex, setCurrentIndex] = useState(0);
|
||
const { api, setApi } = useCarousel({
|
||
onSelect: useCallback(
|
||
(carouselApi: CarouselApi) => {
|
||
if (!carouselApi) return;
|
||
setCurrentIndex(carouselApi.selectedScrollSnap());
|
||
},
|
||
[setCurrentIndex]
|
||
),
|
||
});
|
||
const [addCartDrawerOpen, setAddCartDrawerOpen] = useState(false);
|
||
const [deleteCardModalOpen, setDeleteCardModalOpen] = useState(false);
|
||
const [cardToDelete, setCardToDelete] = useState<string | undefined>();
|
||
const { data: balance, isLoading: isBalanceLoading } =
|
||
useServiceGetSellerBalance();
|
||
const { data: bankAccounts, isLoading: isBankAccountsLoading } =
|
||
useServiceGetBankAccounts();
|
||
const [cashDrawerIsOpen, setCashDrawerIsOpen] = useState(false);
|
||
return (
|
||
<div className="bg-WHITE pb-32">
|
||
<HeaderWithSupport title="برداشت وجه" onBack={handleBack} />
|
||
<div className="p-4 w-full flex flex-col gap-4 items-center">
|
||
<div className="flex w-full justify-between items-center">
|
||
<p className="text-B16 font-bold">کارتهای بانکی شما:</p>
|
||
<button
|
||
onClick={() => setAddCartDrawerOpen(true)}
|
||
className="bg-VITROWN_BLUE hover:bg-VITROWN_BLUE/90 transition-colors text-WHITE rounded-md p-3 shadow-lg flex items-center justify-center"
|
||
aria-label="افزودن کارت جدید"
|
||
>
|
||
<Plus size={24} />
|
||
</button>
|
||
</div>
|
||
{isBankAccountsLoading ? (
|
||
<div className="flex items-center justify-center h-[190px]">
|
||
<Loader2 className="w-8 h-8 animate-spin" />
|
||
</div>
|
||
) : bankAccounts && bankAccounts.length > 0 ? (
|
||
<>
|
||
<Carousel
|
||
setApi={setApi}
|
||
opts={{
|
||
align: "start",
|
||
loop: true,
|
||
}}
|
||
className="w-full"
|
||
>
|
||
<CarouselContent className="w-full">
|
||
{bankAccounts.map((account, index) => (
|
||
<CarouselItem
|
||
key={account.id || index}
|
||
className="pl-0 w-full"
|
||
>
|
||
{(() => {
|
||
const detectedBank = detectIranianBank(
|
||
account.cardNumber || ""
|
||
);
|
||
return (
|
||
<div
|
||
className="w-full flex flex-col justify-between h-[190px] text-WHITE rounded-xl aspect-square overflow-hidden relative p-4"
|
||
style={{
|
||
background: detectedBank
|
||
? `linear-gradient(to right, ${detectedBank.color.from}, ${detectedBank.color.to})`
|
||
: "linear-gradient(to right, var(--gradient-gray-from), var(--gradient-gray-to))",
|
||
}}
|
||
>
|
||
{/* Delete button */}
|
||
<button
|
||
onClick={() => {
|
||
setCardToDelete(account.id);
|
||
setDeleteCardModalOpen(true);
|
||
}}
|
||
className="absolute top-3 left-3 bg-red-500 hover:bg-red-600 transition-colors rounded-full p-2 shadow-lg z-10"
|
||
aria-label="حذف کارت"
|
||
>
|
||
<Trash2 size={18} className="text-WHITE" />
|
||
</button>
|
||
|
||
<div className="flex w-full justify-between items-center">
|
||
<p className="text-B16 font-bold">
|
||
{detectedBank ? detectedBank.name : "بانک"}
|
||
</p>
|
||
{detectedBank ? (
|
||
<BankLogo bank={detectedBank} size={32} />
|
||
) : (
|
||
<div className="w-8 h-8 rounded bg-white/20 flex items-center justify-center">
|
||
<span className="text-xs">؟</span>
|
||
</div>
|
||
)}
|
||
</div>
|
||
<p className="text-B20 font-bold text-center">
|
||
{formatAccountNumber(account.cardNumber || "")}
|
||
</p>
|
||
<p className="text-B14 text-left font-bold">
|
||
{account.iban}
|
||
</p>
|
||
</div>
|
||
);
|
||
})()}
|
||
</CarouselItem>
|
||
))}
|
||
</CarouselContent>
|
||
</Carousel>
|
||
|
||
{bankAccounts.length > 1 && (
|
||
<div className="flex items-center gap-2">
|
||
<ChevronRight
|
||
onClick={() => {
|
||
api?.scrollPrev();
|
||
}}
|
||
className="font-bold text-GRAY ml-1 cursor-pointer"
|
||
size={20}
|
||
/>
|
||
{bankAccounts.map((_, index) => (
|
||
<button
|
||
key={index}
|
||
onClick={() => api?.scrollTo(index)}
|
||
className={`w-2 h-2 rounded-full transition-colors ${
|
||
index === currentIndex ? "bg-BLACK" : "bg-BLACK/50"
|
||
}`}
|
||
/>
|
||
))}
|
||
<ChevronLeft
|
||
onClick={() => {
|
||
api?.scrollNext();
|
||
}}
|
||
className="font-bold text-GRAY mr-1 cursor-pointer"
|
||
size={20}
|
||
/>
|
||
</div>
|
||
)}
|
||
</>
|
||
) : (
|
||
<div className="flex flex-col items-center justify-center h-[190px] text-GRAY">
|
||
<p className="text-B16">هیچ کارت بانکی ثبت نشده است</p>
|
||
<p className="text-R12 mt-2">
|
||
لطفا ابتدا کارت بانکی خود را اضافه کنید
|
||
</p>
|
||
</div>
|
||
)}
|
||
<div className="w-full gap-4 p-4 flex flex-col items-center justify-center bg-WHITE2 mt-10 rounded-[12px]">
|
||
<p className="text-B18 font-bold">موجودی کیف پول (تومان):</p>
|
||
{isBalanceLoading ? (
|
||
<Loader2 className="w-4 h-4 animate-spin" />
|
||
) : (
|
||
<p className="text-B24 font-bold">
|
||
{formatPrice(balance?.availableFunds || "0")}
|
||
</p>
|
||
)}
|
||
</div>
|
||
</div>
|
||
{/* Floating action buttons */}
|
||
<div className="md:max-w-md mx-auto fixed flex flex-row gap-2 w-full bottom-[50px] left-0 right-0 border-t py-2 px-2 z-50 bg-WHITE">
|
||
{/* Withdraw button */}
|
||
<Button
|
||
variant="dark"
|
||
size="lg"
|
||
className="w-full flex-1 font-bold bg-VITROWN_BLUE text-white "
|
||
onClick={() => setCashDrawerIsOpen(true)}
|
||
disabled={!bankAccounts || bankAccounts.length === 0}
|
||
>
|
||
برداشت پول
|
||
</Button>
|
||
</div>
|
||
|
||
{/* Modals and Drawers */}
|
||
<AddCartDrawer
|
||
isOpen={addCartDrawerOpen}
|
||
onOpenChange={setAddCartDrawerOpen}
|
||
/>
|
||
<DeleteCardModal
|
||
isOpen={deleteCardModalOpen}
|
||
onOpenChange={setDeleteCardModalOpen}
|
||
bankAccountId={cardToDelete}
|
||
/>
|
||
{bankAccounts && bankAccounts.length > 0 && (
|
||
<CashDrawer
|
||
isOpen={cashDrawerIsOpen}
|
||
onOpenChange={setCashDrawerIsOpen}
|
||
currentAccount={{
|
||
...bankAccounts[currentIndex],
|
||
cardNumber: bankAccounts[currentIndex]?.cardNumber || "",
|
||
}}
|
||
availableFunds={Number(balance?.availableFunds) || 0}
|
||
/>
|
||
)}
|
||
</div>
|
||
);
|
||
}
|
||
const DeleteCardModal = ({
|
||
isOpen,
|
||
onOpenChange,
|
||
bankAccountId,
|
||
}: {
|
||
isOpen: boolean;
|
||
onOpenChange: (open: boolean) => void;
|
||
bankAccountId?: string;
|
||
}) => {
|
||
const { mutate: deleteBankAccount, isPending } =
|
||
useServiceDeleteBankAccount();
|
||
|
||
const onRemove = () => {
|
||
if (!bankAccountId) return;
|
||
|
||
deleteBankAccount(bankAccountId, {
|
||
onSuccess: () => {
|
||
onOpenChange(false);
|
||
},
|
||
onError: (error) => {
|
||
console.error("Failed to delete bank account:", error);
|
||
},
|
||
});
|
||
};
|
||
return (
|
||
<Dialog open={isOpen} onOpenChange={onOpenChange}>
|
||
<DialogOverlay className="bg-BLACK/50" />
|
||
<DialogContent className="p-4 bg-WHITE rounded-[15px] w-[calc(100%-40px)]">
|
||
<div className="flex flex-col gap-10">
|
||
<div className={"flex flex-col gap-3 items-center w-full"}>
|
||
<Trash2 className="text-RED text-6" />
|
||
<p className="text-B12 font-bold mt-1">حذف کارت</p>
|
||
<p className="text-R12">از حذف این کارت مطمئن هستید؟</p>
|
||
</div>
|
||
<div className="flex flex-row gap-6 w-full">
|
||
<Button
|
||
disabled={isPending}
|
||
className="w-[100%]"
|
||
size={"lg"}
|
||
variant="dark"
|
||
onClick={() => onRemove()}
|
||
>
|
||
{isPending ? (
|
||
<>
|
||
<div className="relative w-5 h-5 mr-2">
|
||
<div className="absolute top-0 left-0 w-full h-full border-2 border-t-RED border-r-transparent border-b-transparent border-l-transparent rounded-full animate-spin"></div>
|
||
<div
|
||
className="absolute top-0.5 left-0.5 w-4 h-4 border-2 border-t-transparent border-r-RED border-b-transparent border-l-transparent rounded-full animate-spin"
|
||
style={{
|
||
animationDirection: "reverse",
|
||
animationDuration: "0.7s",
|
||
}}
|
||
></div>
|
||
</div>
|
||
درحال حذف...
|
||
</>
|
||
) : (
|
||
"حذف کارت"
|
||
)}
|
||
</Button>
|
||
<Button
|
||
className="w-[100%]"
|
||
size={"lg"}
|
||
variant="primary"
|
||
onClick={() => onOpenChange(false)}
|
||
>
|
||
انصراف
|
||
</Button>
|
||
</div>
|
||
</div>
|
||
</DialogContent>
|
||
</Dialog>
|
||
);
|
||
};
|
||
const AddCartDrawer = ({
|
||
isOpen,
|
||
onOpenChange,
|
||
}: {
|
||
isOpen: boolean;
|
||
onOpenChange: (open: boolean) => void;
|
||
}) => {
|
||
const [cardPart1, setCardPart1] = useState("");
|
||
const [cardPart2, setCardPart2] = useState("");
|
||
const [cardPart3, setCardPart3] = useState("");
|
||
const [cardPart4, setCardPart4] = useState("");
|
||
const [shabaNumber, setShabaNumber] = useState("");
|
||
const [cardError, setCardError] = useState("");
|
||
|
||
const { mutate: registerBankAccount, isPending: isSubmitting } =
|
||
useServiceRegisterBankAccount();
|
||
|
||
const cardRef1 = useRef<HTMLInputElement>(null);
|
||
const cardRef2 = useRef<HTMLInputElement>(null);
|
||
const cardRef3 = useRef<HTMLInputElement>(null);
|
||
const cardRef4 = useRef<HTMLInputElement>(null);
|
||
|
||
// Detect bank from current card input
|
||
const fullCardNumber = cardPart1 + cardPart2 + cardPart3 + cardPart4;
|
||
const detectedBank = detectIranianBank(fullCardNumber);
|
||
|
||
const handleCardInputChange = (
|
||
value: string,
|
||
part: number,
|
||
setter: (value: string) => void
|
||
) => {
|
||
// فقط عدد قبول کند
|
||
const numericValue = value.replace(/\D/g, "");
|
||
|
||
if (numericValue.length <= 4) {
|
||
setter(numericValue);
|
||
|
||
// اگر 4 رقم پر شد، به بعدی برو
|
||
if (numericValue.length === 4) {
|
||
if (part === 1) cardRef2.current?.focus();
|
||
else if (part === 2) cardRef3.current?.focus();
|
||
else if (part === 3) cardRef4.current?.focus();
|
||
}
|
||
}
|
||
};
|
||
|
||
const handleSubmit = () => {
|
||
const formattedIban = `IR${shabaNumber}`;
|
||
|
||
// Validate Iranian bank card
|
||
if (!validateIranianBankCard(fullCardNumber)) {
|
||
setCardError("شماره کارت وارد شده متعلق به بانکهای ایران نیست");
|
||
return;
|
||
}
|
||
|
||
registerBankAccount(
|
||
{
|
||
iban: formattedIban,
|
||
cardNumber: fullCardNumber,
|
||
},
|
||
{
|
||
onSuccess: () => {
|
||
// Reset form
|
||
setCardPart1("");
|
||
setCardPart2("");
|
||
setCardPart3("");
|
||
setCardPart4("");
|
||
setShabaNumber("");
|
||
setCardError("");
|
||
onOpenChange(false);
|
||
},
|
||
onError: (error) => {
|
||
console.error("Failed to register bank account:", error);
|
||
setCardError("خطا در ثبت کارت بانکی");
|
||
},
|
||
}
|
||
);
|
||
};
|
||
|
||
const isFormValid =
|
||
cardPart1.length === 4 &&
|
||
cardPart2.length === 4 &&
|
||
cardPart3.length === 4 &&
|
||
cardPart4.length === 4 &&
|
||
shabaNumber.length === 24 &&
|
||
validateIranianBankCard(fullCardNumber);
|
||
|
||
return (
|
||
<Drawer open={isOpen} onOpenChange={onOpenChange}>
|
||
<DrawerContent>
|
||
<DrawerHeader className="flex items-center relative justify-center">
|
||
<X
|
||
size={24}
|
||
className="text-BLACK2 absolute left-4"
|
||
onClick={() => onOpenChange(false)}
|
||
/>
|
||
<DrawerTitle className="text-center text-B12 font-bold">
|
||
افزودن کارت جدید
|
||
</DrawerTitle>
|
||
</DrawerHeader>
|
||
<div className="flex flex-col gap-4 p-4 pb-6">
|
||
<div className="w-full flex flex-col gap-2">
|
||
<p className="text-R14">شماره کارت را وارد کنید:</p>
|
||
<div className="flex items-center" dir="ltr">
|
||
<AppInput
|
||
inputTitle=""
|
||
inputValue={cardPart1}
|
||
inputPlaceholder="----"
|
||
inputDir="ltr"
|
||
className="text-center w-20"
|
||
inputRef={cardRef1}
|
||
inputOnChange={(e) => {
|
||
handleCardInputChange(e.target.value, 1, setCardPart1);
|
||
setCardError("");
|
||
}}
|
||
/>
|
||
<AppInput
|
||
inputTitle=""
|
||
inputValue={cardPart2}
|
||
inputPlaceholder="----"
|
||
inputDir="ltr"
|
||
className="text-center w-20"
|
||
inputRef={cardRef2}
|
||
inputOnChange={(e) => {
|
||
handleCardInputChange(e.target.value, 2, setCardPart2);
|
||
setCardError("");
|
||
}}
|
||
/>
|
||
<AppInput
|
||
inputTitle=""
|
||
inputValue={cardPart3}
|
||
inputPlaceholder="----"
|
||
inputDir="ltr"
|
||
className="text-center w-20"
|
||
inputRef={cardRef3}
|
||
inputOnChange={(e) => {
|
||
handleCardInputChange(e.target.value, 3, setCardPart3);
|
||
setCardError("");
|
||
}}
|
||
/>
|
||
<AppInput
|
||
inputTitle=""
|
||
inputValue={cardPart4}
|
||
inputPlaceholder="----"
|
||
inputDir="ltr"
|
||
className="text-center w-20"
|
||
inputRef={cardRef4}
|
||
inputOnChange={(e) => {
|
||
handleCardInputChange(e.target.value, 4, setCardPart4);
|
||
setCardError("");
|
||
}}
|
||
/>
|
||
</div>
|
||
|
||
{/* Show detected bank */}
|
||
{detectedBank && fullCardNumber.length >= 6 && (
|
||
<div className="flex items-center gap-2 p-3 bg-green-50 border border-green-200 rounded-lg animate-in fade-in slide-in-from-top-2 duration-300">
|
||
<BankLogo bank={detectedBank} size={24} />
|
||
<div className="flex flex-col">
|
||
<p className="text-R12 font-bold text-green-800">
|
||
بانک {detectedBank.name}
|
||
</p>
|
||
<p className="text-R10 text-green-600">کارت شناسایی شد</p>
|
||
</div>
|
||
</div>
|
||
)}
|
||
|
||
{/* Show error if card is complete but invalid */}
|
||
{fullCardNumber.length === 16 && !detectedBank && (
|
||
<div className="flex items-center gap-2 p-3 bg-red-50 border border-red-200 rounded-lg animate-in fade-in slide-in-from-top-2 duration-300">
|
||
<TriangleAlert size={20} className="text-red-600" />
|
||
<p className="text-R12 text-red-700">
|
||
شماره کارت متعلق به بانکهای ایران نیست
|
||
</p>
|
||
</div>
|
||
)}
|
||
|
||
{/* Show custom error */}
|
||
{cardError && (
|
||
<div className="flex items-center gap-2 p-3 bg-red-50 border border-red-200 rounded-lg">
|
||
<TriangleAlert size={20} className="text-red-600" />
|
||
<p className="text-R12 text-red-700">{cardError}</p>
|
||
</div>
|
||
)}
|
||
</div>
|
||
|
||
<div className="w-full flex flex-col gap-2">
|
||
<p className="text-R14">شماره شبا حساب خود را وارد کنید:</p>
|
||
<div className="relative w-full">
|
||
<Input
|
||
type="text"
|
||
placeholder="شماره شبا"
|
||
value={shabaNumber}
|
||
onChange={(e) => setShabaNumber(e.target.value)}
|
||
dir={!shabaNumber ? "rtl" : "ltr"}
|
||
className="pl-14"
|
||
/>
|
||
<div className="absolute top-4 left-3 text-GRAY text-sm font-medium">
|
||
IR
|
||
</div>
|
||
</div>
|
||
</div>
|
||
|
||
<Button
|
||
variant="dark"
|
||
size="lg"
|
||
onClick={handleSubmit}
|
||
disabled={!isFormValid || isSubmitting}
|
||
className="w-full font-bold mt-4"
|
||
>
|
||
{isSubmitting ? (
|
||
<>
|
||
<Loader2 className="w-4 h-4 animate-spin mr-2" />
|
||
در حال ثبت...
|
||
</>
|
||
) : (
|
||
"ثبت و در انتظار تایید"
|
||
)}
|
||
</Button>
|
||
</div>
|
||
</DrawerContent>
|
||
</Drawer>
|
||
);
|
||
};
|
||
const CashDrawer = ({
|
||
isOpen,
|
||
onOpenChange,
|
||
currentAccount,
|
||
availableFunds,
|
||
}: {
|
||
isOpen: boolean;
|
||
onOpenChange: (open: boolean) => void;
|
||
currentAccount: {
|
||
id?: string;
|
||
iban: string;
|
||
cardNumber: string;
|
||
isVerified: boolean;
|
||
createdAt: string;
|
||
};
|
||
availableFunds: number;
|
||
}) => {
|
||
const [amount, setAmount] = useState<number | null>(null);
|
||
const [withdrawAll, setWithdrawAll] = useState(false);
|
||
const { mutate: createCashRequest, isPending: submitLoading } =
|
||
useServiceCreateCashRequest();
|
||
const [successModalIsOpen, setSuccessModalIsOpen] = useState(false);
|
||
const [errorModalIsOpen, setErrorModalIsOpen] = useState(false);
|
||
|
||
// Reset state when drawer closes
|
||
useEffect(() => {
|
||
if (!isOpen) {
|
||
setAmount(null);
|
||
setWithdrawAll(false);
|
||
}
|
||
}, [isOpen]);
|
||
|
||
return (
|
||
<Drawer open={isOpen} onOpenChange={onOpenChange}>
|
||
<DrawerContent>
|
||
<DrawerHeader className="flex items-center relative justify-center">
|
||
<X
|
||
size={24}
|
||
className="text-BLACK2 absolute left-4"
|
||
onClick={() => onOpenChange(false)}
|
||
/>
|
||
<DrawerTitle className="text-center text-B10 font-bold">
|
||
مبلغ برداشت را تعیین کنید:
|
||
</DrawerTitle>
|
||
</DrawerHeader>
|
||
<div className="flex flex-col w-full gap-6 p-4">
|
||
<div className="flex flex-col gap-3">
|
||
<div className="flex flex-col gap-2">
|
||
<AppInput
|
||
inputTitle="مبلغ"
|
||
type="number"
|
||
inputPlaceholder="مبلغ برداشت را وارد کنید"
|
||
inputValue={
|
||
withdrawAll
|
||
? availableFunds.toString()
|
||
: amount?.toString() || ""
|
||
}
|
||
inputDir={amount === null && !withdrawAll ? "rtl" : "ltr"}
|
||
disabled={withdrawAll}
|
||
inputOnChange={(e: React.ChangeEvent<HTMLInputElement>) => {
|
||
setAmount(parseInt(e.target.value) || null);
|
||
setWithdrawAll(false);
|
||
}}
|
||
/>
|
||
|
||
{/* Show Persian text of entered amount */}
|
||
{amount !== null && amount > 0 && (
|
||
<div className="px-2 py-2 bg-blue-50 border border-blue-200 rounded-lg">
|
||
<p className="text-R12 text-blue-800">
|
||
{getPersianTextOfPrice(amount.toString())}
|
||
</p>
|
||
</div>
|
||
)}
|
||
|
||
{/* Show maximum withdrawal amount */}
|
||
<div className="flex items-center justify-between px-2">
|
||
<p className="text-R12 text-GRAY">حداکثر قابل برداشت:</p>
|
||
<p className="text-R14 font-bold text-GREEN">
|
||
{formatPrice(availableFunds.toString())} تومان
|
||
</p>
|
||
</div>
|
||
</div>
|
||
|
||
{/* Toggle switch for withdraw all */}
|
||
<div className="flex items-center justify-between gap-3 pr-1">
|
||
<label
|
||
htmlFor="withdrawAll"
|
||
className="text-R14 cursor-pointer select-none"
|
||
>
|
||
برداشت کل موجودی ({formatPrice(availableFunds.toString())}{" "}
|
||
تومان)
|
||
</label>
|
||
<label className="relative inline-flex items-center cursor-pointer">
|
||
<input
|
||
type="checkbox"
|
||
id="withdrawAll"
|
||
checked={withdrawAll}
|
||
onChange={(e) => {
|
||
setWithdrawAll(e.target.checked);
|
||
if (e.target.checked) {
|
||
setAmount(availableFunds);
|
||
} else {
|
||
setAmount(null);
|
||
}
|
||
}}
|
||
className="sr-only peer"
|
||
aria-label="برداشت کل موجودی"
|
||
/>
|
||
<div className="w-11 h-6 bg-WHITE2 peer-focus:outline-none peer-focus:ring-4 peer-focus:ring-blue-300 rounded-full peer peer-checked:after:translate-x-full peer-checked:after:border-WHITE after:content-[''] after:absolute after:top-[2px] after:left-[2px] after:bg-WHITE after:border-GRAY3 after:border after:rounded-full after:h-5 after:w-5 after:transition-all peer-checked:bg-VITROWN_BLUE border"></div>
|
||
</label>
|
||
</div>
|
||
</div>
|
||
|
||
<div className="flex flex-col gap-2 items-center justify-center ">
|
||
<TriangleAlert size={20} />
|
||
<p className="text-B14 font-bold text-center">
|
||
مبلغ تعیین شده بعد از بررسی، به حساب{" "}
|
||
<span className="font-bold">
|
||
{formatAccountNumber(currentAccount.cardNumber)
|
||
.split("-")
|
||
.reverse()
|
||
.join("-")}
|
||
</span>{" "}
|
||
با شماره شبا{" "}
|
||
<span className="font-bold">{currentAccount.iban}</span> منتقل
|
||
خواهد شد
|
||
</p>
|
||
</div>
|
||
<Button
|
||
variant="dark"
|
||
size="lg"
|
||
onClick={() => {
|
||
const finalAmount = withdrawAll ? availableFunds : amount;
|
||
createCashRequest(
|
||
{
|
||
amount: finalAmount?.toString() || "0",
|
||
bank_account: currentAccount.id || "",
|
||
},
|
||
{
|
||
onError: () => {
|
||
onOpenChange(false);
|
||
setErrorModalIsOpen(true);
|
||
},
|
||
onSuccess: () => {
|
||
onOpenChange(false);
|
||
setSuccessModalIsOpen(true);
|
||
setAmount(null);
|
||
setWithdrawAll(false);
|
||
},
|
||
}
|
||
);
|
||
}}
|
||
disabled={
|
||
(amount === null && !withdrawAll) ||
|
||
amount === 0 ||
|
||
submitLoading ||
|
||
(amount !== null && amount > availableFunds)
|
||
}
|
||
className="w-full font-bold bg-VITROWN_BLUE text-white"
|
||
>
|
||
{submitLoading && <Loader2 className="w-4 h-4 animate-spin mr-2" />}
|
||
ثبت درخواست
|
||
</Button>
|
||
</div>
|
||
</DrawerContent>
|
||
<SuccessModal
|
||
isOpen={successModalIsOpen}
|
||
onOpenChange={setSuccessModalIsOpen}
|
||
/>
|
||
<ErrorModal
|
||
isOpen={errorModalIsOpen}
|
||
onOpenChange={setErrorModalIsOpen}
|
||
/>
|
||
</Drawer>
|
||
);
|
||
};
|
||
|
||
const SuccessModal = ({
|
||
isOpen,
|
||
onOpenChange,
|
||
}: {
|
||
isOpen: boolean;
|
||
onOpenChange: (open: boolean) => void;
|
||
}) => {
|
||
return (
|
||
<Dialog open={isOpen} onOpenChange={onOpenChange}>
|
||
<DialogOverlay className="bg-BLACK/50" />
|
||
<DialogContent className="p-6 bg-WHITE rounded-[15px] w-[calc(100%-40px)] max-w-sm">
|
||
<div className="flex flex-col items-center justify-center gap-4 py-4">
|
||
<Loader />
|
||
<p className="text-B14 font-bold text-center">در انتظار تایید مبلغ</p>
|
||
<p className="text-R12 text-center">
|
||
پس از تایید، مبلغ مورد نظر به حساب شما واریز میشود. این فرآیند کمتر
|
||
از یک ساعت زمان میبرد.
|
||
</p>
|
||
<Button
|
||
onClick={() => onOpenChange(false)}
|
||
variant="dark"
|
||
size="lg"
|
||
className="w-full font-bold"
|
||
>
|
||
فهمیدم
|
||
</Button>
|
||
</div>
|
||
</DialogContent>
|
||
</Dialog>
|
||
);
|
||
};
|
||
const ErrorModal = ({
|
||
isOpen,
|
||
onOpenChange,
|
||
}: {
|
||
isOpen: boolean;
|
||
onOpenChange: (open: boolean) => void;
|
||
}) => {
|
||
return (
|
||
<Dialog open={isOpen} onOpenChange={onOpenChange}>
|
||
<DialogOverlay className="bg-BLACK/50" />
|
||
<DialogContent className="p-6 bg-WHITE rounded-[15px] w-[calc(100%-40px)] max-w-sm">
|
||
<div className="flex flex-col items-center justify-center gap-4 py-4">
|
||
<TriangleAlert size={20} />
|
||
<p className="text-B14 font-bold text-center">
|
||
درخواست برداشت مبلغ با خطا مواجه شد
|
||
</p>
|
||
<p className="text-R12 text-center">
|
||
لطفا مجددا درخواست برداشت را انجام دهید. یا با پشتیبانی تماس بگیرید.
|
||
</p>
|
||
<Button
|
||
onClick={() => onOpenChange(false)}
|
||
variant="dark"
|
||
size="lg"
|
||
className="w-full font-bold"
|
||
>
|
||
فهمیدم
|
||
</Button>
|
||
</div>
|
||
</DialogContent>
|
||
</Dialog>
|
||
);
|
||
};
|
||
|
||
export const meta: MetaFunction = () => {
|
||
return [
|
||
{ title: "کیف پول - ویترون" },
|
||
{
|
||
name: "description",
|
||
content:
|
||
"مدیریت و مشاهده تمام کیف پول در فروشگاه شما. فیلتر بر اساس وضعیت و جستجو در کیف پول.",
|
||
},
|
||
{
|
||
name: "keywords",
|
||
content: "کیف پول، مدیریت کیف پول، فروشگاه، مشتری، ویترون",
|
||
},
|
||
{ name: "robots", content: "noindex, follow" },
|
||
{ property: "og:title", content: "کیف پول - ویترون" },
|
||
{
|
||
property: "og:description",
|
||
content:
|
||
"مدیریت و مشاهده تمام کیف پول در فروشگاه شما. فیلتر بر اساس وضعیت و جستجو در کیف پول.",
|
||
},
|
||
{ property: "og:type", content: "website" },
|
||
{ name: "twitter:card", content: "summary" },
|
||
{ name: "twitter:title", content: "کیف پول - ویترون" },
|
||
{
|
||
name: "twitter:description",
|
||
content:
|
||
"مدیریت و مشاهده تمام کیف پول در فروشگاه شما. فیلتر بر اساس وضعیت و جستجو در کیف پول.",
|
||
},
|
||
];
|
||
};
|