|
@@ -14,12 +14,12 @@ import {
|
|
|
} from "@/lib/withdrawal-api";
|
|
} from "@/lib/withdrawal-api";
|
|
|
import { cn } from "@/lib/utils";
|
|
import { cn } from "@/lib/utils";
|
|
|
|
|
|
|
|
-// 保留原有的所有辅助函数 (channelGroupLabel, groupOrder, formatAmountRange 等)
|
|
|
|
|
function channelGroupLabel(channel: WithdrawChannel): string {
|
|
function channelGroupLabel(channel: WithdrawChannel): string {
|
|
|
const type = channel.type;
|
|
const type = channel.type;
|
|
|
const code = (channel.code || "").toUpperCase();
|
|
const code = (channel.code || "").toUpperCase();
|
|
|
const name = `${channel.name || ""} ${channel.enName || ""}`.toUpperCase();
|
|
const name = `${channel.name || ""} ${channel.enName || ""}`.toUpperCase();
|
|
|
const aliHint = code.includes("ALI") || code.includes("ALIPAY") || name.includes("ALIPAY");
|
|
const aliHint = code.includes("ALI") || code.includes("ALIPAY") || name.includes("ALIPAY");
|
|
|
|
|
+
|
|
|
if (type === "BANK_TELEGRAPHIC") return "国际转账";
|
|
if (type === "BANK_TELEGRAPHIC") return "国际转账";
|
|
|
if (type === "BANK") return "网银支付";
|
|
if (type === "BANK") return "网银支付";
|
|
|
if (type === "DIGITAL_CURRENCY") return "数字货币";
|
|
if (type === "DIGITAL_CURRENCY") return "数字货币";
|
|
@@ -29,26 +29,60 @@ function channelGroupLabel(channel: WithdrawChannel): string {
|
|
|
if (type === "UCARD_WALLET") return "电子卡";
|
|
if (type === "UCARD_WALLET") return "电子卡";
|
|
|
return "其他";
|
|
return "其他";
|
|
|
}
|
|
}
|
|
|
|
|
+
|
|
|
function groupOrder(label: string): number {
|
|
function groupOrder(label: string): number {
|
|
|
- if (label === "数字货币") return 1; if (label === "网银支付") return 2; if (label === "国际转账") return 3;
|
|
|
|
|
- if (label === "电子钱包") return 4; if (label === "电子卡") return 5; if (label === "支付宝") return 6; return 99;
|
|
|
|
|
|
|
+ if (label === "数字货币") return 1;
|
|
|
|
|
+ if (label === "网银支付") return 2;
|
|
|
|
|
+ if (label === "国际转账") return 3;
|
|
|
|
|
+ if (label === "电子钱包") return 4;
|
|
|
|
|
+ if (label === "电子卡") return 5;
|
|
|
|
|
+ if (label === "支付宝") return 6;
|
|
|
|
|
+ return 99;
|
|
|
}
|
|
}
|
|
|
|
|
+
|
|
|
function formatAmountRange(item: WithdrawChannel): string {
|
|
function formatAmountRange(item: WithdrawChannel): string {
|
|
|
- const min = item.minAmount || 0; const max = item.maxAmount > 0 ? item.maxAmount : "-"; return `$${min} - $${max} ${item.currency || "USD"}`;
|
|
|
|
|
|
|
+ const min = item.minAmount || 0;
|
|
|
|
|
+ const max = item.maxAmount > 0 ? item.maxAmount : "-";
|
|
|
|
|
+ return `$${min} - $${max} ${item.currency || "USD"}`;
|
|
|
}
|
|
}
|
|
|
|
|
+
|
|
|
function formatFee(item: WithdrawChannel): string {
|
|
function formatFee(item: WithdrawChannel): string {
|
|
|
- if (item.feeType === 1) return `${item.free ?? 0}%`; if (item.feeType === 2) return `$${item.feeAmount ?? 0}`;
|
|
|
|
|
- if (item.free !== null && item.free !== undefined) return `${item.free}%`; return "0%";
|
|
|
|
|
|
|
+ if (item.feeType === 1) return `${item.free ?? 0}%`;
|
|
|
|
|
+ if (item.feeType === 2) return `$${item.feeAmount ?? 0}`;
|
|
|
|
|
+ if (item.free !== null && item.free !== undefined) return `${item.free}%`;
|
|
|
|
|
+ return "0%";
|
|
|
}
|
|
}
|
|
|
|
|
+
|
|
|
function sanitizeHtml(input: string): string {
|
|
function sanitizeHtml(input: string): string {
|
|
|
- if (!input) return ""; return input.replace(/<script[\s\S]*?>[\s\S]*?<\/script>/gi, "").replace(/\son\w+="[^"]*"/gi, "").replace(/\son\w+='[^']*'/gi, "");
|
|
|
|
|
|
|
+ if (!input) return "";
|
|
|
|
|
+ return input
|
|
|
|
|
+ .replace(/<script[\s\S]*?>[\s\S]*?<\/script>/gi, "")
|
|
|
|
|
+ .replace(/\son\w+="[^"]*"/gi, "")
|
|
|
|
|
+ .replace(/\son\w+='[^']*'/gi, "");
|
|
|
|
|
+}
|
|
|
|
|
+
|
|
|
|
|
+function isWalletType(type: string): boolean {
|
|
|
|
|
+ return type === "CHANNEL_TYPE_WALLET" || type === "CHANNEL_TYPE_ALI_WALLET";
|
|
|
|
|
+}
|
|
|
|
|
+
|
|
|
|
|
+function isBankType(type: string): boolean {
|
|
|
|
|
+ return type === "BANK";
|
|
|
|
|
+}
|
|
|
|
|
+
|
|
|
|
|
+function isCardType(type: string): boolean {
|
|
|
|
|
+ return type === "CHANNEL_TYPE_CARD";
|
|
|
|
|
+}
|
|
|
|
|
+
|
|
|
|
|
+function isDigitalCurrencyType(type: string): boolean {
|
|
|
|
|
+ return type === "DIGITAL_CURRENCY";
|
|
|
}
|
|
}
|
|
|
-function isWalletType(type: string) { return type === "CHANNEL_TYPE_WALLET" || type === "CHANNEL_TYPE_ALI_WALLET"; }
|
|
|
|
|
-function isBankType(type: string) { return type === "BANK"; }
|
|
|
|
|
-function isCardType(type: string) { return type === "CHANNEL_TYPE_CARD"; }
|
|
|
|
|
-function isDigitalCurrencyType(type: string) { return type === "DIGITAL_CURRENCY"; }
|
|
|
|
|
|
|
+
|
|
|
function savedAccountType(type: string): number | null {
|
|
function savedAccountType(type: string): number | null {
|
|
|
- if (type === "BANK") return 1; if (type === "BANK_TELEGRAPHIC") return 2; if (type === "CHANNEL_TYPE_CARD") return 3; if (type === "DIGITAL_CURRENCY") return 4; return null;
|
|
|
|
|
|
|
+ if (type === "BANK") return 1;
|
|
|
|
|
+ if (type === "BANK_TELEGRAPHIC") return 2;
|
|
|
|
|
+ if (type === "CHANNEL_TYPE_CARD") return 3;
|
|
|
|
|
+ if (type === "DIGITAL_CURRENCY") return 4;
|
|
|
|
|
+ return null;
|
|
|
}
|
|
}
|
|
|
|
|
|
|
|
export default function WithdrawApplyPage() {
|
|
export default function WithdrawApplyPage() {
|
|
@@ -56,11 +90,11 @@ export default function WithdrawApplyPage() {
|
|
|
const [channelsLoading, setChannelsLoading] = useState(false);
|
|
const [channelsLoading, setChannelsLoading] = useState(false);
|
|
|
const [channelsError, setChannelsError] = useState<string | null>(null);
|
|
const [channelsError, setChannelsError] = useState<string | null>(null);
|
|
|
const [walletBalance, setWalletBalance] = useState<number | null>(null);
|
|
const [walletBalance, setWalletBalance] = useState<number | null>(null);
|
|
|
-
|
|
|
|
|
|
|
+ const [walletBalanceLoading, setWalletBalanceLoading] = useState(false);
|
|
|
|
|
+
|
|
|
const [savedAccounts] = useState<SavedWithdrawAccount[]>([]);
|
|
const [savedAccounts] = useState<SavedWithdrawAccount[]>([]);
|
|
|
const [bankOptions, setBankOptions] = useState<WithdrawBankOption[]>([]);
|
|
const [bankOptions, setBankOptions] = useState<WithdrawBankOption[]>([]);
|
|
|
|
|
|
|
|
- // 表单状态
|
|
|
|
|
const [selectedChannelId, setSelectedChannelId] = useState("");
|
|
const [selectedChannelId, setSelectedChannelId] = useState("");
|
|
|
const [selectedSavedId, setSelectedSavedId] = useState("");
|
|
const [selectedSavedId, setSelectedSavedId] = useState("");
|
|
|
const [selectedBankCode, setSelectedBankCode] = useState("");
|
|
const [selectedBankCode, setSelectedBankCode] = useState("");
|
|
@@ -69,7 +103,6 @@ export default function WithdrawApplyPage() {
|
|
|
const [amount, setAmount] = useState("");
|
|
const [amount, setAmount] = useState("");
|
|
|
const [agree, setAgree] = useState(false);
|
|
const [agree, setAgree] = useState(false);
|
|
|
const [agreeExtra, setAgreeExtra] = useState(false);
|
|
const [agreeExtra, setAgreeExtra] = useState(false);
|
|
|
- // ... 其他银行/电汇字段
|
|
|
|
|
const [agencyNo, setAgencyNo] = useState("");
|
|
const [agencyNo, setAgencyNo] = useState("");
|
|
|
const [cpf, setCpf] = useState("");
|
|
const [cpf, setCpf] = useState("");
|
|
|
const [bankUnameInput, setBankUnameInput] = useState("");
|
|
const [bankUnameInput, setBankUnameInput] = useState("");
|
|
@@ -89,12 +122,33 @@ export default function WithdrawApplyPage() {
|
|
|
const [confirmOpen, setConfirmOpen] = useState(false);
|
|
const [confirmOpen, setConfirmOpen] = useState(false);
|
|
|
const [expandedGroup, setExpandedGroup] = useState<string>("数字货币");
|
|
const [expandedGroup, setExpandedGroup] = useState<string>("数字货币");
|
|
|
const [applyDialogOpen, setApplyDialogOpen] = useState(false);
|
|
const [applyDialogOpen, setApplyDialogOpen] = useState(false);
|
|
|
- const [resultDialog, setResultDialog] = useState({ open: false, status: "success", title: "", message: "" });
|
|
|
|
|
|
|
+ const [resultDialog, setResultDialog] = useState<{
|
|
|
|
|
+ open: boolean;
|
|
|
|
|
+ status: "success" | "error";
|
|
|
|
|
+ title: string;
|
|
|
|
|
+ message: string;
|
|
|
|
|
+ }>({
|
|
|
|
|
+ open: false,
|
|
|
|
|
+ status: "success",
|
|
|
|
|
+ title: "",
|
|
|
|
|
+ message: "",
|
|
|
|
|
+ });
|
|
|
|
|
+
|
|
|
|
|
+ const selectedChannel = useMemo(
|
|
|
|
|
+ () => channels.find((item) => item.id === selectedChannelId) ?? null,
|
|
|
|
|
+ [channels, selectedChannelId],
|
|
|
|
|
+ );
|
|
|
|
|
+
|
|
|
|
|
+ const selectedSavedAccount = useMemo(
|
|
|
|
|
+ () => savedAccounts.find((item) => item.id === selectedSavedId) ?? null,
|
|
|
|
|
+ [savedAccounts, selectedSavedId],
|
|
|
|
|
+ );
|
|
|
|
|
|
|
|
- const selectedChannel = useMemo(() => channels.find((item) => item.id === selectedChannelId) ?? null, [channels, selectedChannelId]);
|
|
|
|
|
- const selectedSavedAccount = useMemo(() => savedAccounts.find((item) => item.id === selectedSavedId) ?? null, [savedAccounts, selectedSavedId]);
|
|
|
|
|
const filteredSavedAccounts = useMemo(() => {
|
|
const filteredSavedAccounts = useMemo(() => {
|
|
|
- if (!selectedChannel) return []; const type = savedAccountType(selectedChannel.type); if (type === null) return []; return savedAccounts.filter((item) => item.type === type);
|
|
|
|
|
|
|
+ if (!selectedChannel) return [];
|
|
|
|
|
+ const type = savedAccountType(selectedChannel.type);
|
|
|
|
|
+ if (type === null) return [];
|
|
|
|
|
+ return savedAccounts.filter((item) => item.type === type);
|
|
|
}, [savedAccounts, selectedChannel]);
|
|
}, [savedAccounts, selectedChannel]);
|
|
|
|
|
|
|
|
const shouldRequireSavedAccount = false;
|
|
const shouldRequireSavedAccount = false;
|
|
@@ -103,92 +157,243 @@ export default function WithdrawApplyPage() {
|
|
|
const needCpf = selectedChannel?.code === "PAY_RETAILER_REMIT_PAY_KEY_BRW";
|
|
const needCpf = selectedChannel?.code === "PAY_RETAILER_REMIT_PAY_KEY_BRW";
|
|
|
|
|
|
|
|
function resetApplyForm() {
|
|
function resetApplyForm() {
|
|
|
- setSelectedSavedId(""); setSelectedBankCode(""); setAddressName(""); setAddress(""); setAmount(""); setAgree(false); setAgreeExtra(false); setAgencyNo(""); setCpf(""); setBankUnameInput(""); setBankCardNumInput(""); setBankNameInput(""); setBankBranchNameInput(""); setSwiftCodeInput(""); setCustomBankCodeInput(""); setBankAddrInput(""); setTelegraphicCurrency("USD"); setCardUnameInput(""); setCardNumInput(""); setCardCvvInput(""); setCardExpiryInput("");
|
|
|
|
|
|
|
+ setSelectedSavedId("");
|
|
|
|
|
+ setSelectedBankCode("");
|
|
|
|
|
+ setAddressName("");
|
|
|
|
|
+ setAddress("");
|
|
|
|
|
+ setAmount("");
|
|
|
|
|
+ setAgree(false);
|
|
|
|
|
+ setAgreeExtra(false);
|
|
|
|
|
+ setAgencyNo("");
|
|
|
|
|
+ setCpf("");
|
|
|
|
|
+ setBankUnameInput("");
|
|
|
|
|
+ setBankCardNumInput("");
|
|
|
|
|
+ setBankNameInput("");
|
|
|
|
|
+ setBankBranchNameInput("");
|
|
|
|
|
+ setSwiftCodeInput("");
|
|
|
|
|
+ setCustomBankCodeInput("");
|
|
|
|
|
+ setBankAddrInput("");
|
|
|
|
|
+ setTelegraphicCurrency("USD");
|
|
|
|
|
+ setCardUnameInput("");
|
|
|
|
|
+ setCardNumInput("");
|
|
|
|
|
+ setCardCvvInput("");
|
|
|
|
|
+ setCardExpiryInput("");
|
|
|
}
|
|
}
|
|
|
|
|
|
|
|
useEffect(() => {
|
|
useEffect(() => {
|
|
|
let cancelled = false;
|
|
let cancelled = false;
|
|
|
async function loadBase() {
|
|
async function loadBase() {
|
|
|
- setChannelsLoading(true); setChannelsError(null);
|
|
|
|
|
|
|
+ setWalletBalanceLoading(true);
|
|
|
|
|
+ setChannelsLoading(true);
|
|
|
|
|
+ setChannelsError(null);
|
|
|
try {
|
|
try {
|
|
|
- const [balanceResult, channelsResult] = await Promise.all([fetchWalletBalance(), fetchWithdrawChannels()]);
|
|
|
|
|
|
|
+ const [balanceResult, channelsResult] = await Promise.all([
|
|
|
|
|
+ fetchWalletBalance(),
|
|
|
|
|
+ fetchWithdrawChannels(),
|
|
|
|
|
+ ]);
|
|
|
if (cancelled) return;
|
|
if (cancelled) return;
|
|
|
- setWalletBalance(balanceResult); setChannels(channelsResult);
|
|
|
|
|
|
|
+ setWalletBalance(balanceResult);
|
|
|
|
|
+ setChannels(channelsResult);
|
|
|
} catch (e) {
|
|
} catch (e) {
|
|
|
- if (!cancelled) setChannelsError((e as Error)?.message || "通道加载失败");
|
|
|
|
|
|
|
+ if (cancelled) return;
|
|
|
|
|
+ const err = e as Error;
|
|
|
|
|
+ setChannelsError(err?.message || "领取通道加载失败");
|
|
|
|
|
+ setChannels([]);
|
|
|
|
|
+ setWalletBalance(null);
|
|
|
} finally {
|
|
} finally {
|
|
|
- if (!cancelled) setChannelsLoading(false);
|
|
|
|
|
|
|
+ if (!cancelled) {
|
|
|
|
|
+ setWalletBalanceLoading(false);
|
|
|
|
|
+ setChannelsLoading(false);
|
|
|
|
|
+ }
|
|
|
}
|
|
}
|
|
|
}
|
|
}
|
|
|
- void loadBase(); return () => { cancelled = true; };
|
|
|
|
|
|
|
+ void loadBase();
|
|
|
|
|
+ return () => {
|
|
|
|
|
+ cancelled = true;
|
|
|
|
|
+ };
|
|
|
}, []);
|
|
}, []);
|
|
|
|
|
|
|
|
useEffect(() => {
|
|
useEffect(() => {
|
|
|
- if (!selectedChannel || !applyDialogOpen) { setBankOptions([]); setSelectedBankCode(""); return; }
|
|
|
|
|
- resetApplyForm();
|
|
|
|
|
- if (!selectedChannel.bankValid) return;
|
|
|
|
|
|
|
+ if (!selectedChannel || !applyDialogOpen) {
|
|
|
|
|
+ setBankOptions([]);
|
|
|
|
|
+ setSelectedBankCode("");
|
|
|
|
|
+ return;
|
|
|
|
|
+ }
|
|
|
|
|
+ const currentChannel = selectedChannel;
|
|
|
|
|
+ resetApplyForm(); // 重置表单状态
|
|
|
|
|
+
|
|
|
|
|
+ if (!currentChannel.bankValid) {
|
|
|
|
|
+ setBankOptions([]);
|
|
|
|
|
+ setSelectedBankCode("");
|
|
|
|
|
+ return;
|
|
|
|
|
+ }
|
|
|
let cancelled = false;
|
|
let cancelled = false;
|
|
|
async function loadBankOptions() {
|
|
async function loadBankOptions() {
|
|
|
try {
|
|
try {
|
|
|
- const list = await fetchWithdrawBankOptions(selectedChannel!.code);
|
|
|
|
|
- if (cancelled) return; setBankOptions(list); setSelectedBankCode((prev) => prev || list[0]?.code || "");
|
|
|
|
|
- } catch { if (!cancelled) setBankOptions([]); }
|
|
|
|
|
|
|
+ const list = await fetchWithdrawBankOptions(currentChannel.code);
|
|
|
|
|
+ if (cancelled) return;
|
|
|
|
|
+ setBankOptions(list);
|
|
|
|
|
+ setSelectedBankCode((prev) => prev || list[0]?.code || "");
|
|
|
|
|
+ } catch {
|
|
|
|
|
+ if (cancelled) return;
|
|
|
|
|
+ setBankOptions([]);
|
|
|
|
|
+ }
|
|
|
}
|
|
}
|
|
|
- void loadBankOptions(); return () => { cancelled = true; };
|
|
|
|
|
|
|
+ void loadBankOptions();
|
|
|
|
|
+ return () => {
|
|
|
|
|
+ cancelled = true;
|
|
|
|
|
+ };
|
|
|
}, [selectedChannel, applyDialogOpen]);
|
|
}, [selectedChannel, applyDialogOpen]);
|
|
|
|
|
|
|
|
useEffect(() => {
|
|
useEffect(() => {
|
|
|
if (!selectedSavedAccount) return;
|
|
if (!selectedSavedAccount) return;
|
|
|
if (!isBankType(selectedChannel?.type || "") && !isBankTelegraphic) return;
|
|
if (!isBankType(selectedChannel?.type || "") && !isBankTelegraphic) return;
|
|
|
- setBankUnameInput(selectedSavedAccount.bankUname || ""); setBankCardNumInput(selectedSavedAccount.bankCardNum || ""); setBankNameInput(selectedSavedAccount.bankName || ""); setBankBranchNameInput(selectedSavedAccount.bankBranchName || ""); setSwiftCodeInput(selectedSavedAccount.swiftCode || ""); setCustomBankCodeInput(selectedSavedAccount.customBankCode || ""); setBankAddrInput(selectedSavedAccount.bankAddr || "");
|
|
|
|
|
|
|
+ setBankUnameInput(selectedSavedAccount.bankUname || "");
|
|
|
|
|
+ setBankCardNumInput(selectedSavedAccount.bankCardNum || "");
|
|
|
|
|
+ setBankNameInput(selectedSavedAccount.bankName || "");
|
|
|
|
|
+ setBankBranchNameInput(selectedSavedAccount.bankBranchName || "");
|
|
|
|
|
+ setSwiftCodeInput(selectedSavedAccount.swiftCode || "");
|
|
|
|
|
+ setCustomBankCodeInput(selectedSavedAccount.customBankCode || "");
|
|
|
|
|
+ setBankAddrInput(selectedSavedAccount.bankAddr || "");
|
|
|
}, [selectedSavedAccount, selectedChannel?.type, isBankTelegraphic]);
|
|
}, [selectedSavedAccount, selectedChannel?.type, isBankTelegraphic]);
|
|
|
|
|
|
|
|
function validate(): string | null {
|
|
function validate(): string | null {
|
|
|
if (!selectedChannel) return "请选择领取通道";
|
|
if (!selectedChannel) return "请选择领取通道";
|
|
|
if (!/^[0-9]+([.][0-9]{1,2})?$/.test(amount.trim())) return "请输入正确的领取金额";
|
|
if (!/^[0-9]+([.][0-9]{1,2})?$/.test(amount.trim())) return "请输入正确的领取金额";
|
|
|
const amountNum = Number(amount);
|
|
const amountNum = Number(amount);
|
|
|
- if (!Number.isFinite(amountNum) || amountNum <= 0) return "金额必须大于0";
|
|
|
|
|
- if (selectedChannel.minAmount > 0 && amountNum < selectedChannel.minAmount) return `不能低于 ${selectedChannel.minAmount}`;
|
|
|
|
|
- if (selectedChannel.maxAmount > 0 && amountNum > selectedChannel.maxAmount) return `不能高于 ${selectedChannel.maxAmount}`;
|
|
|
|
|
|
|
+ if (!Number.isFinite(amountNum) || amountNum <= 0) return "领取金额必须大于 0";
|
|
|
|
|
+ if (selectedChannel.minAmount > 0 && amountNum < selectedChannel.minAmount) {
|
|
|
|
|
+ return `领取金额不能低于 ${selectedChannel.minAmount}`;
|
|
|
|
|
+ }
|
|
|
|
|
+ if (selectedChannel.maxAmount > 0 && amountNum > selectedChannel.maxAmount) {
|
|
|
|
|
+ return `领取金额不能高于 ${selectedChannel.maxAmount}`;
|
|
|
|
|
+ }
|
|
|
if (isWalletType(selectedChannel.type) && !address.trim()) return "请填写领取地址";
|
|
if (isWalletType(selectedChannel.type) && !address.trim()) return "请填写领取地址";
|
|
|
- if (isDigitalCurrencyType(selectedChannel.type)) { if (!addressName.trim()) return "请填写区块链名称"; if (!address.trim()) return "请填写钱包地址"; }
|
|
|
|
|
- if (isBankType(selectedChannel.type)) { if (!bankUnameInput.trim()) return "请输入户名"; if (!bankCardNumInput.trim()) return "请输入卡号"; if (!bankNameInput.trim()) return "请输入银行名称"; if (!bankBranchNameInput.trim()) return "请输入支行"; }
|
|
|
|
|
- if (isBankTelegraphic) { if (!swiftCodeInput.trim() || !bankAddrInput.trim()) return "请完善电汇信息"; }
|
|
|
|
|
- if (!agree) return "请同意领取条款"; if (!agreeExtra) return "请确认信息无误";
|
|
|
|
|
|
|
+ if (isDigitalCurrencyType(selectedChannel.type) && !addressName.trim()) return "请填写区块链名称";
|
|
|
|
|
+ if (isDigitalCurrencyType(selectedChannel.type) && !address.trim()) return "请填写钱包地址";
|
|
|
|
|
+ if (shouldRequireSavedAccount && filteredSavedAccounts.length === 0) {
|
|
|
|
|
+ return "当前通道暂无可用收款信息,请更换通道或先补充收款信息";
|
|
|
|
|
+ }
|
|
|
|
|
+ if (shouldShowSavedAccountSelector && !selectedSavedId) return "请选择收款信息";
|
|
|
|
|
+ if (isBankType(selectedChannel.type)) {
|
|
|
|
|
+ if (!bankUnameInput.trim()) return "请输入户名";
|
|
|
|
|
+ if (!bankCardNumInput.trim()) return "请输入银行卡号";
|
|
|
|
|
+ if (!bankNameInput.trim()) return "请输入银行名称";
|
|
|
|
|
+ if (!bankBranchNameInput.trim()) return "请输入支行名称";
|
|
|
|
|
+ }
|
|
|
|
|
+ if (isBankTelegraphic) {
|
|
|
|
|
+ if (!bankUnameInput.trim()) return "请输入户名";
|
|
|
|
|
+ if (!bankCardNumInput.trim()) return "请输入银行卡号";
|
|
|
|
|
+ if (!bankNameInput.trim()) return "请输入银行名称";
|
|
|
|
|
+ if (!swiftCodeInput.trim()) return "请输入Swift Code";
|
|
|
|
|
+ if (!customBankCodeInput.trim()) return "请输入银行代码";
|
|
|
|
|
+ if (!bankAddrInput.trim()) return "请输入银行地址";
|
|
|
|
|
+ }
|
|
|
|
|
+ if (isBankTelegraphic && !agencyNo.trim()) return "请填写 Account Agency NO";
|
|
|
|
|
+ if (isBankTelegraphic && needCpf && !cpf.trim()) return "请填写 CPF";
|
|
|
|
|
+ if (isCardType(selectedChannel.type)) {
|
|
|
|
|
+ if (!cardUnameInput.trim()) return "请输入信用卡户名";
|
|
|
|
|
+ if (!cardNumInput.trim()) return "请输入信用卡账户";
|
|
|
|
|
+ if (!cardCvvInput.trim()) return "请输入CVV";
|
|
|
|
|
+ if (!cardExpiryInput.trim()) return "请输入到期年份/月 份";
|
|
|
|
|
+ }
|
|
|
|
|
+ if (!agree) return "请先勾选并同意领取条款";
|
|
|
|
|
+ if (!agreeExtra) return "请勾选第二条领取确认条款";
|
|
|
return null;
|
|
return null;
|
|
|
}
|
|
}
|
|
|
|
|
|
|
|
async function doSubmit() {
|
|
async function doSubmit() {
|
|
|
if (!selectedChannel) return;
|
|
if (!selectedChannel) return;
|
|
|
- const payload: Record<string, unknown> = { payType: selectedChannel.code, amount: Number(amount), currency: selectedChannel.type === "BANK_TELEGRAPHIC" ? "USD" : selectedChannel.currency, agree2: true };
|
|
|
|
|
- // ... 组装 payload 的逻辑保持原样
|
|
|
|
|
|
|
+ const amountNum = Number(amount);
|
|
|
|
|
+ const payload: Record<string, unknown> = {
|
|
|
|
|
+ payType: selectedChannel.code,
|
|
|
|
|
+ amount: amountNum,
|
|
|
|
|
+ currency: selectedChannel.type === "BANK_TELEGRAPHIC" ? "USD" : selectedChannel.currency,
|
|
|
|
|
+ agree2: true,
|
|
|
|
|
+ };
|
|
|
|
|
+ if (selectedBankCode) payload.bankCode = selectedBankCode;
|
|
|
if (address.trim()) payload.address = address.trim();
|
|
if (address.trim()) payload.address = address.trim();
|
|
|
if (addressName.trim()) payload.addressName = addressName.trim();
|
|
if (addressName.trim()) payload.addressName = addressName.trim();
|
|
|
- if (isBankType(selectedChannel.type)) { payload.bankUname = bankUnameInput; payload.bankCardNum = bankCardNumInput; payload.bankName = bankNameInput; payload.bankBranchName = bankBranchNameInput; }
|
|
|
|
|
-
|
|
|
|
|
|
|
+ if (selectedSavedAccount) {
|
|
|
|
|
+ payload.id = selectedSavedAccount.id;
|
|
|
|
|
+ payload.bankUname = selectedSavedAccount.bankUname;
|
|
|
|
|
+ payload.bankCardNum = selectedSavedAccount.bankCardNum;
|
|
|
|
|
+ payload.bankName = selectedSavedAccount.bankName;
|
|
|
|
|
+ payload.bankBranchName = selectedSavedAccount.bankBranchName;
|
|
|
|
|
+ payload.bankAddr = selectedSavedAccount.bankAddr;
|
|
|
|
|
+ payload.swiftCode = selectedSavedAccount.swiftCode;
|
|
|
|
|
+ payload.customBankCode = selectedSavedAccount.customBankCode;
|
|
|
|
|
+ payload.addressName = selectedSavedAccount.addressName;
|
|
|
|
|
+ payload.address = payload.address ?? selectedSavedAccount.address;
|
|
|
|
|
+ payload.cvv = selectedSavedAccount.cvv;
|
|
|
|
|
+ payload.expiryYearMonth = selectedSavedAccount.expiryYearMonth;
|
|
|
|
|
+ }
|
|
|
|
|
+ if (isBankType(selectedChannel.type)) {
|
|
|
|
|
+ payload.bankUname = bankUnameInput.trim();
|
|
|
|
|
+ payload.bankCardNum = bankCardNumInput.trim();
|
|
|
|
|
+ payload.bankName = bankNameInput.trim();
|
|
|
|
|
+ payload.bankBranchName = bankBranchNameInput.trim();
|
|
|
|
|
+ }
|
|
|
|
|
+ if (isBankTelegraphic) {
|
|
|
|
|
+ payload.bankUname = bankUnameInput.trim();
|
|
|
|
|
+ payload.bankCardNum = bankCardNumInput.trim();
|
|
|
|
|
+ payload.bankName = bankNameInput.trim();
|
|
|
|
|
+ payload.swiftCode = swiftCodeInput.trim();
|
|
|
|
|
+ payload.customBankCode = customBankCodeInput.trim();
|
|
|
|
|
+ payload.bankAddr = bankAddrInput.trim();
|
|
|
|
|
+ payload.currency = telegraphicCurrency || "USD";
|
|
|
|
|
+ payload.agencyNo = agencyNo.trim();
|
|
|
|
|
+ if (needCpf) payload.cpf = cpf.trim();
|
|
|
|
|
+ }
|
|
|
|
|
+ if (isCardType(selectedChannel.type)) {
|
|
|
|
|
+ payload.bankUname = cardUnameInput.trim();
|
|
|
|
|
+ payload.bankCardNum = cardNumInput.trim();
|
|
|
|
|
+ payload.cvv = cardCvvInput.trim();
|
|
|
|
|
+ payload.expiryYearMonth = cardExpiryInput.trim();
|
|
|
|
|
+ }
|
|
|
setSubmitting(true);
|
|
setSubmitting(true);
|
|
|
try {
|
|
try {
|
|
|
- await submitWithdrawApply({ requestUrl: selectedChannel.requestUrl, payload });
|
|
|
|
|
- setResultDialog({ open: true, status: "success", title: "提交成功", message: "申请已提交审核。" });
|
|
|
|
|
|
|
+ await submitWithdrawApply({
|
|
|
|
|
+ requestUrl: selectedChannel.requestUrl,
|
|
|
|
|
+ payload,
|
|
|
|
|
+ });
|
|
|
|
|
+ setResultDialog({
|
|
|
|
|
+ open: true,
|
|
|
|
|
+ status: "success",
|
|
|
|
|
+ title: "提交成功",
|
|
|
|
|
+ message: "领取申请已提交,请等待平台审核。",
|
|
|
|
|
+ });
|
|
|
resetApplyForm();
|
|
resetApplyForm();
|
|
|
} catch (e) {
|
|
} catch (e) {
|
|
|
- setResultDialog({ open: true, status: "error", title: "提交失败", message: (e as Error).message });
|
|
|
|
|
|
|
+ const err = e as Error;
|
|
|
|
|
+ setResultDialog({
|
|
|
|
|
+ open: true,
|
|
|
|
|
+ status: "error",
|
|
|
|
|
+ title: "提交失败",
|
|
|
|
|
+ message: err.message || "领取申请失败,请稍后重试。",
|
|
|
|
|
+ });
|
|
|
} finally {
|
|
} finally {
|
|
|
- setSubmitting(false); setConfirmOpen(false);
|
|
|
|
|
|
|
+ setSubmitting(false);
|
|
|
|
|
+ setConfirmOpen(false);
|
|
|
}
|
|
}
|
|
|
}
|
|
}
|
|
|
|
|
|
|
|
const channelGroups = useMemo(() => {
|
|
const channelGroups = useMemo(() => {
|
|
|
const groups: Record<string, WithdrawChannel[]> = {};
|
|
const groups: Record<string, WithdrawChannel[]> = {};
|
|
|
- for (const item of channels) { const key = channelGroupLabel(item); if (!groups[key]) groups[key] = []; groups[key].push(item); }
|
|
|
|
|
|
|
+ for (const item of channels) {
|
|
|
|
|
+ const key = channelGroupLabel(item);
|
|
|
|
|
+ if (!groups[key]) groups[key] = [];
|
|
|
|
|
+ groups[key].push(item);
|
|
|
|
|
+ }
|
|
|
return Object.entries(groups).sort((a, b) => groupOrder(a[0]) - groupOrder(b[0]));
|
|
return Object.entries(groups).sort((a, b) => groupOrder(a[0]) - groupOrder(b[0]));
|
|
|
}, [channels]);
|
|
}, [channels]);
|
|
|
|
|
|
|
|
- // 表单通用 Input 组件
|
|
|
|
|
- const InputCls = "mt-2 w-full rounded-xl border border-white/10 bg-black/20 px-4 py-3 text-sm text-white placeholder-slate-500 focus:border-[#b89458] focus:outline-none focus:ring-1 focus:ring-[#b89458]";
|
|
|
|
|
|
|
+ // 高定表单样式
|
|
|
|
|
+ const InputCls = "mt-2 w-full rounded-xl border border-white/10 bg-[#0a1120] px-4 py-3.5 text-sm text-white placeholder-slate-600 focus:border-[#b89458] focus:outline-none focus:ring-1 focus:ring-[#b89458] transition-all";
|
|
|
|
|
|
|
|
return (
|
|
return (
|
|
|
<div className="min-h-screen bg-[#050b14] pb-24 text-slate-300 font-sans relative">
|
|
<div className="min-h-screen bg-[#050b14] pb-24 text-slate-300 font-sans relative">
|
|
|
|
|
+ {/* 沉浸式全局光晕 */}
|
|
|
<div className="pointer-events-none fixed inset-0 z-0">
|
|
<div className="pointer-events-none fixed inset-0 z-0">
|
|
|
<div className="absolute left-1/4 top-0 h-[500px] w-[500px] rounded-full bg-blue-900/10 blur-[120px]" />
|
|
<div className="absolute left-1/4 top-0 h-[500px] w-[500px] rounded-full bg-blue-900/10 blur-[120px]" />
|
|
|
<div className="absolute right-1/4 bottom-0 h-[500px] w-[500px] rounded-full bg-[#b89458]/5 blur-[120px]" />
|
|
<div className="absolute right-1/4 bottom-0 h-[500px] w-[500px] rounded-full bg-[#b89458]/5 blur-[120px]" />
|
|
@@ -198,13 +403,19 @@ export default function WithdrawApplyPage() {
|
|
|
<div className="flex flex-col md:flex-row md:items-center justify-between gap-6 mb-8">
|
|
<div className="flex flex-col md:flex-row md:items-center justify-between gap-6 mb-8">
|
|
|
<div>
|
|
<div>
|
|
|
<h1 className="font-serif text-3xl font-bold text-white">发起领取申请</h1>
|
|
<h1 className="font-serif text-3xl font-bold text-white">发起领取申请</h1>
|
|
|
- <p className="mt-2 text-sm text-slate-400">当前可领取余额: <span className="font-bold text-[#f3deae] text-lg ml-1">${(walletBalance ?? 0).toFixed(2)}</span></p>
|
|
|
|
|
|
|
+ <p className="mt-2 text-sm text-slate-400">
|
|
|
|
|
+ 当前可领取余额:
|
|
|
|
|
+ <span className="font-bold text-[#f3deae] text-lg ml-2">
|
|
|
|
|
+ {walletBalanceLoading ? "..." : `$${(walletBalance ?? 0).toFixed(2)}`}
|
|
|
|
|
+ </span>
|
|
|
|
|
+ </p>
|
|
|
</div>
|
|
</div>
|
|
|
<Link href="/account" className="inline-flex items-center justify-center gap-2 rounded-full border border-white/10 bg-white/5 px-6 py-3 text-sm font-semibold text-slate-300 transition hover:bg-white/10 hover:text-white backdrop-blur-md">
|
|
<Link href="/account" className="inline-flex items-center justify-center gap-2 rounded-full border border-white/10 bg-white/5 px-6 py-3 text-sm font-semibold text-slate-300 transition hover:bg-white/10 hover:text-white backdrop-blur-md">
|
|
|
<ArrowLeft size={16} /> 返回控制中心
|
|
<ArrowLeft size={16} /> 返回控制中心
|
|
|
</Link>
|
|
</Link>
|
|
|
</div>
|
|
</div>
|
|
|
|
|
|
|
|
|
|
+ {/* 提款通道列表 */}
|
|
|
<section className="rounded-[2.5rem] border border-white/10 bg-white/5 p-6 md:p-10 backdrop-blur-2xl shadow-2xl">
|
|
<section className="rounded-[2.5rem] border border-white/10 bg-white/5 p-6 md:p-10 backdrop-blur-2xl shadow-2xl">
|
|
|
<h2 className="text-xl font-bold text-white mb-6">选择提款通道</h2>
|
|
<h2 className="text-xl font-bold text-white mb-6">选择提款通道</h2>
|
|
|
|
|
|
|
@@ -230,7 +441,9 @@ export default function WithdrawApplyPage() {
|
|
|
<thead className="text-slate-400 border-b border-white/10">
|
|
<thead className="text-slate-400 border-b border-white/10">
|
|
|
<tr>
|
|
<tr>
|
|
|
<th className="pb-3 px-4 font-semibold">通道类型</th>
|
|
<th className="pb-3 px-4 font-semibold">通道类型</th>
|
|
|
|
|
+ <th className="pb-3 px-4 font-semibold">描述</th>
|
|
|
<th className="pb-3 px-4 font-semibold">限额</th>
|
|
<th className="pb-3 px-4 font-semibold">限额</th>
|
|
|
|
|
+ <th className="pb-3 px-4 font-semibold">处理时间</th>
|
|
|
<th className="pb-3 px-4 font-semibold">手续费</th>
|
|
<th className="pb-3 px-4 font-semibold">手续费</th>
|
|
|
<th className="pb-3 px-4 font-semibold text-right">操作</th>
|
|
<th className="pb-3 px-4 font-semibold text-right">操作</th>
|
|
|
</tr>
|
|
</tr>
|
|
@@ -244,7 +457,9 @@ export default function WithdrawApplyPage() {
|
|
|
<span className="font-bold text-slate-200">{item.name || item.code}</span>
|
|
<span className="font-bold text-slate-200">{item.name || item.code}</span>
|
|
|
</div>
|
|
</div>
|
|
|
</td>
|
|
</td>
|
|
|
|
|
+ <td className="py-4 px-4 text-slate-400">{item.enName || item.name || "-"}</td>
|
|
|
<td className="py-4 px-4 font-medium text-slate-300">{formatAmountRange(item)}</td>
|
|
<td className="py-4 px-4 font-medium text-slate-300">{formatAmountRange(item)}</td>
|
|
|
|
|
+ <td className="py-4 px-4 font-medium text-slate-300">{item.fundingTime || "1 hours"}</td>
|
|
|
<td className="py-4 px-4 font-medium text-[#b89458]">{formatFee(item)}</td>
|
|
<td className="py-4 px-4 font-medium text-[#b89458]">{formatFee(item)}</td>
|
|
|
<td className="py-4 px-4 text-right">
|
|
<td className="py-4 px-4 text-right">
|
|
|
<button
|
|
<button
|
|
@@ -267,67 +482,192 @@ export default function WithdrawApplyPage() {
|
|
|
</section>
|
|
</section>
|
|
|
</div>
|
|
</div>
|
|
|
|
|
|
|
|
- {/* 填写表单大弹窗 */}
|
|
|
|
|
|
|
+ {/* ================= 填写表单大弹窗 (完美对齐设计图) ================= */}
|
|
|
{applyDialogOpen && selectedChannel && (
|
|
{applyDialogOpen && selectedChannel && (
|
|
|
<div className="fixed inset-0 z-50 flex items-center justify-center bg-[#050b14]/90 p-4 backdrop-blur-md overflow-y-auto">
|
|
<div className="fixed inset-0 z-50 flex items-center justify-center bg-[#050b14]/90 p-4 backdrop-blur-md overflow-y-auto">
|
|
|
- <div className="my-auto w-full max-w-2xl overflow-hidden rounded-[2.5rem] border border-white/10 bg-[#0a1120] shadow-2xl">
|
|
|
|
|
- <div className="flex items-center justify-between border-b border-white/5 p-6 md:px-8">
|
|
|
|
|
- <h2 className="text-xl font-bold text-white flex items-center gap-2"><Wallet className="text-[#b89458]"/> 填写提款信息</h2>
|
|
|
|
|
- <button onClick={() => setApplyDialogOpen(false)} className="text-sm font-bold text-slate-500 hover:text-white">关闭</button>
|
|
|
|
|
|
|
+ <div className="my-auto w-full max-w-2xl overflow-hidden rounded-[2.5rem] border border-white/10 bg-[#121826] shadow-2xl">
|
|
|
|
|
+
|
|
|
|
|
+ {/* 弹窗 Header */}
|
|
|
|
|
+ <div className="flex items-center justify-between p-8 pb-6">
|
|
|
|
|
+ <h2 className="text-[1.35rem] font-bold text-white flex items-center gap-3">
|
|
|
|
|
+ <Wallet className="text-[#d9be88]"/> 填写提款信息
|
|
|
|
|
+ </h2>
|
|
|
|
|
+ <button onClick={() => setApplyDialogOpen(false)} className="text-sm font-bold text-slate-500 hover:text-white transition-colors">
|
|
|
|
|
+ 关闭
|
|
|
|
|
+ </button>
|
|
|
</div>
|
|
</div>
|
|
|
|
|
|
|
|
- <div className="p-6 md:p-8 max-h-[70vh] overflow-y-auto space-y-6 custom-scrollbar">
|
|
|
|
|
- {/* 提示信息 */}
|
|
|
|
|
- <div className="rounded-2xl border border-[#b89458]/30 bg-[#b89458]/10 p-5 text-sm text-[#f3deae] leading-relaxed">
|
|
|
|
|
- 当前通道: <span className="font-bold ml-1">{selectedChannel.name || selectedChannel.code}</span>
|
|
|
|
|
|
|
+ {/* 弹窗 Body */}
|
|
|
|
|
+ <div className="px-8 max-h-[65vh] overflow-y-auto space-y-6 custom-scrollbar">
|
|
|
|
|
+
|
|
|
|
|
+ {/* 余额显示框 (补充) */}
|
|
|
|
|
+ <div className="flex items-center justify-between rounded-2xl border border-white/5 bg-[#1a2130] p-4 text-sm">
|
|
|
|
|
+ <span className="text-slate-400 font-medium">账户余额</span>
|
|
|
|
|
+ <span className="font-bold text-white tracking-wide">
|
|
|
|
|
+ {walletBalanceLoading ? "..." : `$ ${(walletBalance ?? 0).toFixed(2)}`}
|
|
|
|
|
+ </span>
|
|
|
|
|
+ </div>
|
|
|
|
|
+
|
|
|
|
|
+ {/* 当前通道提示框 (完美还原设计图样式) */}
|
|
|
|
|
+ <div className="rounded-2xl border border-[#b89458]/30 bg-[#1c1d24] p-5 text-sm">
|
|
|
|
|
+ <span className="text-slate-400">当前通道:</span>
|
|
|
|
|
+ <span className="font-bold text-[#d9be88] ml-2 tracking-wide">{selectedChannel.name || selectedChannel.code}</span>
|
|
|
</div>
|
|
</div>
|
|
|
|
|
+
|
|
|
|
|
+ {/* 如果有说明文字则展示 */}
|
|
|
|
|
+ {selectedChannel.introduce || selectedChannel.enIntroduce ? (
|
|
|
|
|
+ <div
|
|
|
|
|
+ className="rounded-2xl border border-white/5 bg-[#1a2130] p-4 text-sm leading-7 text-slate-300"
|
|
|
|
|
+ dangerouslySetInnerHTML={{
|
|
|
|
|
+ __html: sanitizeHtml(selectedChannel.introduce || selectedChannel.enIntroduce || ""),
|
|
|
|
|
+ }}
|
|
|
|
|
+ />
|
|
|
|
|
+ ) : null}
|
|
|
|
|
|
|
|
{/* 表单渲染区域 */}
|
|
{/* 表单渲染区域 */}
|
|
|
- <div className="space-y-5">
|
|
|
|
|
|
|
+ <div className="space-y-6">
|
|
|
|
|
+
|
|
|
|
|
+ {/* ====== 补充:银行下拉选项 ====== */}
|
|
|
|
|
+ {bankOptions.length > 0 && (
|
|
|
|
|
+ <div>
|
|
|
|
|
+ <label className="text-sm font-bold text-slate-300">银行通道</label>
|
|
|
|
|
+ <select
|
|
|
|
|
+ value={selectedBankCode}
|
|
|
|
|
+ onChange={(e) => setSelectedBankCode(e.target.value)}
|
|
|
|
|
+ className={cn(InputCls, "appearance-none")}
|
|
|
|
|
+ >
|
|
|
|
|
+ <option value="" className="bg-[#0a1120] text-slate-400">请选择银行通道</option>
|
|
|
|
|
+ {bankOptions.map((item) => (
|
|
|
|
|
+ <option key={item.code} value={item.code} className="bg-[#0a1120] text-white">
|
|
|
|
|
+ {item.name || item.enName || item.code}
|
|
|
|
|
+ </option>
|
|
|
|
|
+ ))}
|
|
|
|
|
+ </select>
|
|
|
|
|
+ </div>
|
|
|
|
|
+ )}
|
|
|
|
|
+
|
|
|
|
|
+ {/* ====== 补充:历史收款信息下拉选项 ====== */}
|
|
|
|
|
+ {shouldShowSavedAccountSelector && (
|
|
|
|
|
+ <div>
|
|
|
|
|
+ <label className="text-sm font-bold text-slate-300">收款信息</label>
|
|
|
|
|
+ <select
|
|
|
|
|
+ value={selectedSavedId}
|
|
|
|
|
+ onChange={(e) => setSelectedSavedId(e.target.value)}
|
|
|
|
|
+ className={cn(InputCls, "appearance-none")}
|
|
|
|
|
+ >
|
|
|
|
|
+ <option value="" className="bg-[#0a1120] text-slate-400">请选择收款信息</option>
|
|
|
|
|
+ {filteredSavedAccounts.map((item) => (
|
|
|
|
|
+ <option key={item.id} value={item.id} disabled={item.type === 4 && item.authStatus === 0} className="bg-[#0a1120] text-white">
|
|
|
|
|
+ {item.type === 4
|
|
|
|
|
+ ? `${item.addressName || "-"} - ${item.address || "-"}`
|
|
|
|
|
+ : `${item.bankName || "-"} - ${item.bankCardNum || "-"}`}
|
|
|
|
|
+ </option>
|
|
|
|
|
+ ))}
|
|
|
|
|
+ </select>
|
|
|
|
|
+ </div>
|
|
|
|
|
+ )}
|
|
|
|
|
+
|
|
|
|
|
+ {/* 数字货币 */}
|
|
|
{isDigitalCurrencyType(selectedChannel.type) && (
|
|
{isDigitalCurrencyType(selectedChannel.type) && (
|
|
|
<div className="grid gap-5 md:grid-cols-2">
|
|
<div className="grid gap-5 md:grid-cols-2">
|
|
|
<div><label className="text-sm font-bold text-slate-300">区块链网络</label><input className={InputCls} placeholder="例如: TRC20" value={addressName} onChange={(e)=>setAddressName(e.target.value)} /></div>
|
|
<div><label className="text-sm font-bold text-slate-300">区块链网络</label><input className={InputCls} placeholder="例如: TRC20" value={addressName} onChange={(e)=>setAddressName(e.target.value)} /></div>
|
|
|
<div><label className="text-sm font-bold text-slate-300">收款钱包地址</label><input className={InputCls} placeholder="请输入您的钱包地址" value={address} onChange={(e)=>setAddress(e.target.value)} /></div>
|
|
<div><label className="text-sm font-bold text-slate-300">收款钱包地址</label><input className={InputCls} placeholder="请输入您的钱包地址" value={address} onChange={(e)=>setAddress(e.target.value)} /></div>
|
|
|
</div>
|
|
</div>
|
|
|
)}
|
|
)}
|
|
|
|
|
+
|
|
|
|
|
+ {/* 电子钱包 */}
|
|
|
|
|
+ {isWalletType(selectedChannel.type) && (
|
|
|
|
|
+ <div>
|
|
|
|
|
+ <label className="text-sm font-bold text-slate-300">领取地址 / 账号</label>
|
|
|
|
|
+ <input className={InputCls} placeholder="请输入账号或地址" value={address} onChange={(e) => setAddress(e.target.value)} />
|
|
|
|
|
+ </div>
|
|
|
|
|
+ )}
|
|
|
|
|
+
|
|
|
|
|
+ {/* 普通银行 */}
|
|
|
{isBankType(selectedChannel.type) && (
|
|
{isBankType(selectedChannel.type) && (
|
|
|
<div className="grid gap-5 md:grid-cols-2">
|
|
<div className="grid gap-5 md:grid-cols-2">
|
|
|
- <div><label className="text-sm font-bold text-slate-300">开户姓名</label><input className={InputCls} value={bankUnameInput} onChange={(e)=>setBankUnameInput(e.target.value)} /></div>
|
|
|
|
|
- <div><label className="text-sm font-bold text-slate-300">银行卡号</label><input className={InputCls} value={bankCardNumInput} onChange={(e)=>setBankCardNumInput(e.target.value)} /></div>
|
|
|
|
|
- <div><label className="text-sm font-bold text-slate-300">银行名称</label><input className={InputCls} value={bankNameInput} onChange={(e)=>setBankNameInput(e.target.value)} /></div>
|
|
|
|
|
- <div><label className="text-sm font-bold text-slate-300">支行信息</label><input className={InputCls} value={bankBranchNameInput} onChange={(e)=>setBankBranchNameInput(e.target.value)} /></div>
|
|
|
|
|
|
|
+ <div><label className="text-sm font-bold text-slate-300">户名</label><input className={InputCls} placeholder="持卡人姓名" value={bankUnameInput} onChange={(e)=>setBankUnameInput(e.target.value)} /></div>
|
|
|
|
|
+ <div><label className="text-sm font-bold text-slate-300">银行卡号</label><input className={InputCls} placeholder="请输入卡号" value={bankCardNumInput} onChange={(e)=>setBankCardNumInput(e.target.value)} /></div>
|
|
|
|
|
+ <div><label className="text-sm font-bold text-slate-300">银行名称</label><input className={InputCls} placeholder="例如: 招商银行" value={bankNameInput} onChange={(e)=>setBankNameInput(e.target.value)} /></div>
|
|
|
|
|
+ <div><label className="text-sm font-bold text-slate-300">支行信息</label><input className={InputCls} placeholder="例如: 深圳分行" value={bankBranchNameInput} onChange={(e)=>setBankBranchNameInput(e.target.value)} /></div>
|
|
|
|
|
+ </div>
|
|
|
|
|
+ )}
|
|
|
|
|
+
|
|
|
|
|
+ {/* 电汇银行 */}
|
|
|
|
|
+ {isBankTelegraphic && (
|
|
|
|
|
+ <>
|
|
|
|
|
+ <div className="grid gap-5 md:grid-cols-2">
|
|
|
|
|
+ <div><label className="text-sm font-bold text-slate-300">户名</label><input className={InputCls} value={bankUnameInput} onChange={(e)=>setBankUnameInput(e.target.value)} /></div>
|
|
|
|
|
+ <div><label className="text-sm font-bold text-slate-300">银行卡号</label><input className={InputCls} value={bankCardNumInput} onChange={(e)=>setBankCardNumInput(e.target.value)} /></div>
|
|
|
|
|
+ <div><label className="text-sm font-bold text-slate-300">银行名称</label><input className={InputCls} value={bankNameInput} onChange={(e)=>setBankNameInput(e.target.value)} /></div>
|
|
|
|
|
+ <div><label className="text-sm font-bold text-slate-300">Swift Code</label><input className={InputCls} value={swiftCodeInput} onChange={(e)=>setSwiftCodeInput(e.target.value)} /></div>
|
|
|
|
|
+ <div><label className="text-sm font-bold text-slate-300">银行代码</label><input className={InputCls} value={customBankCodeInput} onChange={(e)=>setCustomBankCodeInput(e.target.value)} /></div>
|
|
|
|
|
+ <div><label className="text-sm font-bold text-slate-300">银行地址</label><input className={InputCls} value={bankAddrInput} onChange={(e)=>setBankAddrInput(e.target.value)} /></div>
|
|
|
|
|
+ </div>
|
|
|
|
|
+ <div className="grid gap-5 md:grid-cols-2">
|
|
|
|
|
+ <div><label className="text-sm font-bold text-slate-300">Account Agency NO</label><input className={InputCls} value={agencyNo} onChange={(e)=>setAgencyNo(e.target.value)} /></div>
|
|
|
|
|
+ {needCpf && <div><label className="text-sm font-bold text-slate-300">CPF</label><input className={InputCls} value={cpf} onChange={(e)=>setCpf(e.target.value)} /></div>}
|
|
|
|
|
+ </div>
|
|
|
|
|
+ </>
|
|
|
|
|
+ )}
|
|
|
|
|
+
|
|
|
|
|
+ {/* 信用卡 */}
|
|
|
|
|
+ {isCardType(selectedChannel.type) && (
|
|
|
|
|
+ <div className="grid gap-5 md:grid-cols-2">
|
|
|
|
|
+ <div><label className="text-sm font-bold text-slate-300">信用卡户名</label><input className={InputCls} placeholder="John Doe" value={cardUnameInput} onChange={(e)=>setCardUnameInput(e.target.value)} /></div>
|
|
|
|
|
+ <div><label className="text-sm font-bold text-slate-300">信用卡账号</label><input className={InputCls} placeholder="xxxx xxxx xxxx xxxx" value={cardNumInput} onChange={(e)=>setCardNumInput(e.target.value)} /></div>
|
|
|
|
|
+ <div><label className="text-sm font-bold text-slate-300">CVV</label><input className={InputCls} placeholder="123" value={cardCvvInput} onChange={(e)=>setCardCvvInput(e.target.value)} /></div>
|
|
|
|
|
+ <div><label className="text-sm font-bold text-slate-300">到期月份/年份</label><input className={InputCls} placeholder="09/30" value={cardExpiryInput} onChange={(e)=>setCardExpiryInput(e.target.value)} /></div>
|
|
|
</div>
|
|
</div>
|
|
|
)}
|
|
)}
|
|
|
|
|
|
|
|
- {/* 金额 */}
|
|
|
|
|
|
|
+ {/* 金额输入框 (还原设计图) */}
|
|
|
<div>
|
|
<div>
|
|
|
- <label className="text-sm font-bold text-slate-300">提取金额 ({selectedChannel.currency || "USD"})</label>
|
|
|
|
|
|
|
+ <label className="text-[13px] font-bold text-slate-300">提取金额 ({selectedChannel.currency || "USD"})</label>
|
|
|
<div className="relative mt-2">
|
|
<div className="relative mt-2">
|
|
|
- <span className="absolute left-4 top-3.5 text-slate-500 font-bold">$</span>
|
|
|
|
|
- <input type="number" className={cn(InputCls, "pl-8 text-xl font-bold text-[#f3deae] mt-0")} placeholder="0.00" value={amount} onChange={(e)=>setAmount(e.target.value)} />
|
|
|
|
|
|
|
+ <span className="absolute left-4 top-4 text-slate-500 font-bold text-lg">$</span>
|
|
|
|
|
+ <input
|
|
|
|
|
+ type="number"
|
|
|
|
|
+ className={cn(InputCls, "pl-9 text-xl font-bold text-white mt-0 h-14")}
|
|
|
|
|
+ placeholder="0.00"
|
|
|
|
|
+ value={amount}
|
|
|
|
|
+ onChange={(e)=>setAmount(e.target.value)}
|
|
|
|
|
+ />
|
|
|
</div>
|
|
</div>
|
|
|
</div>
|
|
</div>
|
|
|
|
|
|
|
|
- {/* 条款 */}
|
|
|
|
|
- <div className="mt-6 space-y-4 rounded-2xl bg-white/5 p-5">
|
|
|
|
|
|
|
+ {/* 条款复选框 */}
|
|
|
|
|
+ <div className="mt-6 space-y-4 rounded-2xl bg-[#1a2130] p-5">
|
|
|
<label className="flex items-start gap-3 text-sm text-slate-400 cursor-pointer group">
|
|
<label className="flex items-start gap-3 text-sm text-slate-400 cursor-pointer group">
|
|
|
- <input type="checkbox" checked={agree} onChange={(e)=>setAgree(e.target.checked)} className="mt-1 h-4 w-4 rounded border-white/20 bg-black/50 text-[#b89458] focus:ring-[#b89458]" />
|
|
|
|
|
- <span className="group-hover:text-slate-300 transition-colors">我已仔细核对上述收款信息,因填写错误导致的资金损失由本人承担。</span>
|
|
|
|
|
|
|
+ <div className="relative flex items-center justify-center">
|
|
|
|
|
+ <input type="checkbox" checked={agree} onChange={(e)=>setAgree(e.target.checked)} className="peer appearance-none h-4 w-4 rounded border border-slate-500 bg-transparent checked:bg-white checked:border-white transition-all cursor-pointer" />
|
|
|
|
|
+ <CheckCircle2 size={12} className="absolute text-slate-900 opacity-0 peer-checked:opacity-100 pointer-events-none" />
|
|
|
|
|
+ </div>
|
|
|
|
|
+ <span className="group-hover:text-slate-300 transition-colors leading-tight">我已仔细核对上述收款信息,因填写错误导致的资金损失由本人承担。</span>
|
|
|
</label>
|
|
</label>
|
|
|
<label className="flex items-start gap-3 text-sm text-slate-400 cursor-pointer group">
|
|
<label className="flex items-start gap-3 text-sm text-slate-400 cursor-pointer group">
|
|
|
- <input type="checkbox" checked={agreeExtra} onChange={(e)=>setAgreeExtra(e.target.checked)} className="mt-1 h-4 w-4 rounded border-white/20 bg-black/50 text-[#b89458] focus:ring-[#b89458]" />
|
|
|
|
|
- <span className="group-hover:text-slate-300 transition-colors">我知悉并同意提款手续费规则,且了解资金到账受区块网络/银行处理时间影响。</span>
|
|
|
|
|
|
|
+ <div className="relative flex items-center justify-center">
|
|
|
|
|
+ <input type="checkbox" checked={agreeExtra} onChange={(e)=>setAgreeExtra(e.target.checked)} className="peer appearance-none h-4 w-4 rounded border border-slate-500 bg-transparent checked:bg-white checked:border-white transition-all cursor-pointer" />
|
|
|
|
|
+ <CheckCircle2 size={12} className="absolute text-slate-900 opacity-0 peer-checked:opacity-100 pointer-events-none" />
|
|
|
|
|
+ </div>
|
|
|
|
|
+ <span className="group-hover:text-slate-300 transition-colors leading-tight">我知悉并同意提款手续费规则,且了解资金到账受区块网络/银行处理时间影响。</span>
|
|
|
</label>
|
|
</label>
|
|
|
</div>
|
|
</div>
|
|
|
</div>
|
|
</div>
|
|
|
</div>
|
|
</div>
|
|
|
|
|
|
|
|
- <div className="border-t border-white/5 p-6 bg-black/20 flex gap-4">
|
|
|
|
|
- <button onClick={() => setApplyDialogOpen(false)} className="flex-1 rounded-xl bg-white/10 py-4 font-bold text-white hover:bg-white/20 transition-all">取消</button>
|
|
|
|
|
|
|
+ {/* 弹窗 Footer 按钮区 */}
|
|
|
|
|
+ <div className="p-8 pt-6 flex gap-4">
|
|
|
|
|
+ <button
|
|
|
|
|
+ onClick={() => setApplyDialogOpen(false)}
|
|
|
|
|
+ className="flex-[0.8] rounded-xl bg-[#2a2f3a] py-4 text-sm font-bold text-white hover:bg-white/20 transition-all"
|
|
|
|
|
+ >
|
|
|
|
|
+ 取消
|
|
|
|
|
+ </button>
|
|
|
<button
|
|
<button
|
|
|
onClick={() => { const msg = validate(); if(msg) { alert(msg); return; } setConfirmOpen(true); }}
|
|
onClick={() => { const msg = validate(); if(msg) { alert(msg); return; } setConfirmOpen(true); }}
|
|
|
disabled={submitting}
|
|
disabled={submitting}
|
|
|
- className="flex-[2] rounded-xl bg-gradient-to-br from-[#f3deae] to-[#d9be88] py-4 font-bold text-[#5c461a] shadow-lg hover:opacity-90 transition-all disabled:opacity-50"
|
|
|
|
|
|
|
+ className="flex-[1.2] rounded-xl bg-[#d9be88] py-4 text-sm font-bold text-[#1c1d24] shadow-lg hover:opacity-90 transition-all disabled:opacity-50"
|
|
|
>
|
|
>
|
|
|
校验并进入最后确认
|
|
校验并进入最后确认
|
|
|
</button>
|
|
</button>
|
|
@@ -336,7 +676,7 @@ export default function WithdrawApplyPage() {
|
|
|
</div>
|
|
</div>
|
|
|
)}
|
|
)}
|
|
|
|
|
|
|
|
- {/* 二次确认弹窗 */}
|
|
|
|
|
|
|
+ {/* ================= 二次确认弹窗 ================= */}
|
|
|
{confirmOpen && (
|
|
{confirmOpen && (
|
|
|
<div className="fixed inset-0 z-[60] flex items-center justify-center bg-[#050b14]/80 p-4 backdrop-blur-md">
|
|
<div className="fixed inset-0 z-[60] flex items-center justify-center bg-[#050b14]/80 p-4 backdrop-blur-md">
|
|
|
<div className="w-full max-w-sm rounded-[2.5rem] border border-white/10 bg-[#0a1120] text-center shadow-2xl overflow-hidden">
|
|
<div className="w-full max-w-sm rounded-[2.5rem] border border-white/10 bg-[#0a1120] text-center shadow-2xl overflow-hidden">
|
|
@@ -355,7 +695,7 @@ export default function WithdrawApplyPage() {
|
|
|
</div>
|
|
</div>
|
|
|
)}
|
|
)}
|
|
|
|
|
|
|
|
- {/* 结果弹窗 */}
|
|
|
|
|
|
|
+ {/* ================= 结果弹窗 ================= */}
|
|
|
{resultDialog.open && (
|
|
{resultDialog.open && (
|
|
|
<div className="fixed inset-0 z-[70] flex items-center justify-center bg-[#050b14]/80 p-4 backdrop-blur-md">
|
|
<div className="fixed inset-0 z-[70] flex items-center justify-center bg-[#050b14]/80 p-4 backdrop-blur-md">
|
|
|
<div className="w-full max-w-sm rounded-[2.5rem] border border-white/10 bg-[#0a1120] text-center shadow-2xl overflow-hidden p-10">
|
|
<div className="w-full max-w-sm rounded-[2.5rem] border border-white/10 bg-[#0a1120] text-center shadow-2xl overflow-hidden p-10">
|