"use client" import * as React from "react" import Image from "next/image" import { CircleAlertIcon, Loader2Icon } from "lucide-react" import { Alert, AlertDescription, AlertTitle } from "@/components/ui/alert" import { Dialog, DialogContent, DialogDescription, DialogFooter, DialogHeader, DialogTitle, } from "@/components/ui/dialog" import { Button } from "@/components/ui/button" import { Checkbox } from "@/components/ui/checkbox" import { Input } from "@/components/ui/input" import { Field, FieldGroup, FieldLabel, FieldLegend, FieldSet, } from "@/components/ui/field" import { Select, SelectContent, SelectGroup, SelectItem, SelectTrigger, SelectValue, } from "@/components/ui/select" import { Combobox, ComboboxContent, ComboboxEmpty, ComboboxInput, ComboboxItem, ComboboxList, } from "@/components/ui/combobox" import { CARD_BRANDS, CARD_BRAND_LABELS, detectCardBrand, getCardBrandLogoUrl, normalizeCardBrand, type CardBrand, } from "@/lib/payment/card-brand" import { createChannelAction, updateChannelAction, type ChannelInput, type ChannelWithAccountNames, } from "@/lib/actions/channel" import { type AccountWithChannels } from "@/lib/actions/account" type ChannelType = "PAYMENT_CARD" | "E_WALLET" | "CASH" | "TRANSFER" const channelLabels = { PAYMENT_CARD: "支付卡", E_WALLET: "电子钱包", CASH: "现金", TRANSFER: "转账", } as const export function ChannelDialog({ open, onOpenChange, channel, accounts, onSuccess, }: { open: boolean onOpenChange: (open: boolean) => void channel?: ChannelWithAccountNames | null accounts: AccountWithChannels[] onSuccess: () => void }) { const editing = Boolean(channel) const [channelType, setChannelType] = React.useState( channel?.channelType || "PAYMENT_CARD" ) const [selectedAccounts, setSelectedAccounts] = React.useState( channel ? Array.isArray(channel.refAccounts) ? channel.refAccounts : [] : accounts.length ? [accounts[0].id] : [] ) const [desc, setDesc] = React.useState(channel?.desc || "") const [region, setRegion] = React.useState(channel?.region || "") const [issuerName, setIssuerName] = React.useState(channel?.issuerName || "") const [cardType, setCardType] = React.useState<"CREDIT" | "DEBIT" | "NONE">( channel?.cardType || "CREDIT" ) const [cardBrand, setCardBrand] = React.useState(channel?.cardBrand || "") const [cardNumberFull, setCardNumberFull] = React.useState( channel?.cardNumberFull || "" ) const [cardNumberSuffix, setCardNumberSuffix] = React.useState( channel?.cardNumberFull ? "" : channel?.cardNumberSuffix || "" ) const [platform, setPlatform] = React.useState(channel?.platform || "") const [platformAccountId, setPlatformAccountId] = React.useState( channel?.platformAccountId || "" ) const [subChannel, setSubChannel] = React.useState(channel?.subChannel || "") const [subChannelType, setSubChannelType] = React.useState< "CREDIT" | "DEBIT" | "NONE" >(channel?.subChannelType || "DEBIT") const [isPending, setIsPending] = React.useState(false) const [errorMessage, setErrorMessage] = React.useState(null) const cardBrandRequest = React.useRef(0) const submit = async (event: React.FormEvent) => { event.preventDefault() setErrorMessage(null) if (!selectedAccounts.length) { setErrorMessage("请至少关联一个资金账户") return } const full = cardNumberFull.trim() const payload: ChannelInput = { channelType, refAccounts: selectedAccounts, desc: desc.trim() || null, region: region.trim().toUpperCase() || null, issuerName: channelType === "PAYMENT_CARD" ? issuerName.trim() || null : null, cardType: channelType === "PAYMENT_CARD" && cardType !== "NONE" ? cardType : null, cardNumberFull: channelType === "PAYMENT_CARD" ? full || null : null, cardNumberSuffix: channelType === "PAYMENT_CARD" && !full ? cardNumberSuffix.trim() || null : null, cardBrand: channelType === "PAYMENT_CARD" ? normalizeCardBrand(cardBrand) : null, platform: channelType === "E_WALLET" ? platform.trim() || null : null, platformAccountId: channelType === "E_WALLET" ? platformAccountId.trim() || null : null, subChannel: channelType === "E_WALLET" ? subChannel.trim() || null : null, subChannelType: channelType === "E_WALLET" && subChannelType !== "NONE" ? subChannelType : null, } setIsPending(true) try { const result = editing && channel ? await updateChannelAction(channel.id, payload) : await createChannelAction(payload) if (!result.success) { setErrorMessage(result.error || "保存失败,请检查输入") return } onOpenChange(false) onSuccess() } catch { setErrorMessage("网络异常,提交失败") } finally { setIsPending(false) } } return ( {editing ? "编辑支付渠道" : "新建支付渠道"} 配置渠道信息,并指定实际结算的资金账户。
{errorMessage && ( 无法保存渠道 {errorMessage} )} 描述 setDesc(event.target.value)} placeholder="例如:日常主用渠道" disabled={isPending} /> 类型
归属账户 已选 {selectedAccounts.length} 个
{accounts.length ? ( accounts.map((account) => ( )) ) : (

暂无资金账户,请先创建账户。

)}
{channelType === "PAYMENT_CARD" && (
发卡机构 setIssuerName(event.target.value)} placeholder="例如:汇丰银行" disabled={isPending} /> 卡组织 { cardBrandRequest.current += 1 setCardBrand((value as CardBrand | null) || "") }} autoHighlight > 没有匹配的卡组织 {(brand: CardBrand) => ( {CARD_BRAND_LABELS[brand]} )} 卡片类型 地区代码 setRegion(event.target.value)} placeholder="例如:HK" disabled={isPending} /> {!cardNumberSuffix && ( 完整卡号 { const value = event.target.value const request = ++cardBrandRequest.current setCardNumberFull(value) if (value) setCardNumberSuffix("") if (!value) return void detectCardBrand(value).then((brand) => { if (request === cardBrandRequest.current && brand) setCardBrand(brand) }) }} placeholder="可留空" disabled={isPending} /> )} {!cardNumberFull && ( 卡号末尾四位 { const value = event.target.value setCardNumberSuffix(value) if (value) setCardNumberFull("") }} placeholder="例如:8888" disabled={isPending} /> )}
)} {channelType === "E_WALLET" && (
平台名称 setPlatform(event.target.value)} placeholder="例如:支付宝" disabled={isPending} /> 平台账号 setPlatformAccountId(event.target.value) } placeholder="手机号或邮箱" disabled={isPending} /> 子渠道名称 setSubChannel(event.target.value)} placeholder="例如:余额" disabled={isPending} /> 子渠道类型 地区代码 setRegion(event.target.value)} placeholder="例如:HK" disabled={isPending} />
)} {(channelType === "CASH" || channelType === "TRANSFER") && (

此类型无需额外信息。

)}
) }