/* Copyright (C) 2023-2026 QuantumNous This program is free software: you can redistribute it and/or modify it under the terms of the GNU Affero General Public License as published by the Free Software Foundation, either version 3 of the License, or (at your option) any later version. This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Affero General Public License for more details. You should have received a copy of the GNU Affero General Public License along with this program. If not, see . For commercial licensing, please contact support@quantumnous.com */ import { zodResolver } from '@hookform/resolvers/zod' import { useEffect } from 'react' import { useForm } from 'react-hook-form' import { useTranslation } from 'react-i18next' import * as z from 'zod' import { Dialog } from '@/components/dialog' import { ReactIconByName } from '@/components/react-icon-by-name' import { Button } from '@/components/ui/button' import { Combobox } from '@/components/ui/combobox' import { Form, FormControl, FormDescription, FormField, FormItem, FormLabel, FormMessage, } from '@/components/ui/form' import { Input } from '@/components/ui/input' const createPaymentMethodDialogSchema = (t: (key: string) => string) => z.object({ name: z.string().min(1, t('Payment method name is required')), type: z.string().min(1, t('Payment type key is required')), icon: z.string().optional(), min_topup: z.string().optional(), }) type PaymentMethodDialogFormValues = z.infer< ReturnType > const PAYMENT_METHOD_FORM_ID = 'payment-method-form' export type PaymentMethodData = { name: string type: string icon?: string min_topup?: string color?: string } type PaymentMethodDialogProps = { open: boolean onOpenChange: (open: boolean) => void onSave: (data: PaymentMethodData) => void editData?: PaymentMethodData | null } const PAYMENT_TYPE_ICON_NAMES: Record = { alipay: 'SiAlipay', stripe: 'SiStripe', waffo_pancake: 'LuCreditCard', wxpay: 'SiWechat', } const getDefaultIconName = (type: string) => PAYMENT_TYPE_ICON_NAMES[type] ?? '' export function PaymentMethodDialog({ open, onOpenChange, onSave, editData, }: PaymentMethodDialogProps) { const { t } = useTranslation() const isEditMode = !!editData const paymentMethodDialogSchema = createPaymentMethodDialogSchema(t) const paymentTypeOptions = [ { iconName: 'SiAlipay', label: `${t('Alipay')} (Epay: alipay)`, name: t('Alipay'), value: 'alipay', }, { iconName: 'SiWechat', label: `${t('WeChat Pay')} (Epay: wxpay)`, name: t('WeChat Pay'), value: 'wxpay', }, { iconName: 'SiStripe', label: `${t('Stripe')} (stripe)`, name: t('Stripe'), value: 'stripe', }, { iconName: 'LuCreditCard', label: 'Waffo Pancake (waffo_pancake)', name: 'Waffo Pancake', value: 'waffo_pancake', }, ] const getPaymentTypeOption = (value: string) => paymentTypeOptions.find((option) => option.value === value) const form = useForm({ resolver: zodResolver(paymentMethodDialogSchema), defaultValues: { name: '', type: '', icon: '', min_topup: '', }, }) const iconValue = form.watch('icon') useEffect(() => { if (editData) { form.reset({ name: editData.name, type: editData.type, icon: editData.icon ?? getDefaultIconName(editData.type), min_topup: editData.min_topup ?? '', }) } else { form.reset({ name: '', type: '', icon: '', min_topup: '', }) } }, [editData, form, open]) const handleSubmit = (values: PaymentMethodDialogFormValues) => { const data: PaymentMethodData = { name: values.name, type: values.type, } if (values.icon && values.icon.trim() !== '') { data.icon = values.icon.trim() } if (values.min_topup && values.min_topup.trim() !== '') { data.min_topup = values.min_topup } onSave(data) form.reset() onOpenChange(false) } return ( } >
( {t('Name')} {t('Display name for this payment method.')} )} /> ( {t('Payment type key')} { if (value === null) return const currentIcon = form.getValues('icon')?.trim() const currentName = form.getValues('name')?.trim() const previousOption = getPaymentTypeOption(field.value) const nextOption = getPaymentTypeOption(value) field.onChange(value) if ( nextOption?.iconName && (!currentIcon || currentIcon === previousOption?.iconName) ) { form.setValue('icon', nextOption.iconName, { shouldDirty: true, }) } if ( nextOption?.name && (!currentName || currentName === previousOption?.name) ) { form.setValue('name', nextOption.name, { shouldDirty: true, }) } }} placeholder={t('Select or enter payment type key')} searchPlaceholder={t('Search payment type keys...')} allowCustomValue /> {t( 'Used to decide the payment flow. Built-in keys include stripe for Stripe and waffo_pancake for Waffo Pancake; other values are sent to Epay as the type parameter.' )} )} /> ( {t('Icon')}
{iconValue && ( )}
{t( 'Enter a react-icons component name. Invalid names show no icon.' )}
)} /> ( {t('Minimum top-up (optional)')} {t('Optional minimum recharge amount for this method.')} )} />
) }