fix: consolidate Waffo payment settings save flow (#5110)
This commit is contained in:
+94
-107
@@ -16,8 +16,7 @@ along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||
|
||||
For commercial licensing, please contact support@quantumnous.com
|
||||
*/
|
||||
import { type ChangeEvent, useEffect, useRef, useState } from 'react'
|
||||
import { useForm } from 'react-hook-form'
|
||||
import { type ChangeEvent, useRef, type SetStateAction, useState } from 'react'
|
||||
import { Plus, Pencil, Trash2 } from 'lucide-react'
|
||||
import { useTranslation } from 'react-i18next'
|
||||
import { toast } from 'sonner'
|
||||
@@ -43,8 +42,6 @@ import {
|
||||
} from '@/components/ui/table'
|
||||
import { Textarea } from '@/components/ui/textarea'
|
||||
import { SettingsSwitchField } from '../components/settings-form-layout'
|
||||
import { SettingsPageActionsPortal } from '../components/settings-page-context'
|
||||
import { useUpdateOption } from '../hooks/use-update-option'
|
||||
|
||||
export interface WaffoSettingsValues {
|
||||
WaffoEnabled: boolean
|
||||
@@ -64,34 +61,33 @@ export interface WaffoSettingsValues {
|
||||
WaffoPayMethods: string
|
||||
}
|
||||
|
||||
interface PayMethod {
|
||||
export interface PayMethod {
|
||||
name: string
|
||||
icon: string
|
||||
payMethodType: string
|
||||
payMethodName: string
|
||||
}
|
||||
|
||||
type WaffoFieldValues = Omit<WaffoSettingsValues, 'WaffoPayMethods'>
|
||||
|
||||
interface Props {
|
||||
defaultValues: WaffoSettingsValues
|
||||
values: WaffoSettingsValues
|
||||
onValueChange: <K extends keyof WaffoFieldValues>(
|
||||
key: K,
|
||||
value: WaffoFieldValues[K]
|
||||
) => void
|
||||
payMethods: PayMethod[]
|
||||
onPayMethodsChange: (value: SetStateAction<PayMethod[]>) => void
|
||||
}
|
||||
|
||||
export function WaffoSettingsSection(props: Props) {
|
||||
export function WaffoSettingsSection({
|
||||
values,
|
||||
onValueChange,
|
||||
payMethods,
|
||||
onPayMethodsChange,
|
||||
}: Props) {
|
||||
const { t } = useTranslation()
|
||||
const updateOption = useUpdateOption()
|
||||
const [loading, setLoading] = useState(false)
|
||||
const iconFileInputRef = useRef<HTMLInputElement | null>(null)
|
||||
|
||||
const form = useForm<Omit<WaffoSettingsValues, 'WaffoPayMethods'>>({
|
||||
defaultValues: props.defaultValues,
|
||||
})
|
||||
|
||||
const [payMethods, setPayMethods] = useState<PayMethod[]>(() => {
|
||||
try {
|
||||
return JSON.parse(props.defaultValues.WaffoPayMethods || '[]')
|
||||
} catch {
|
||||
return []
|
||||
}
|
||||
})
|
||||
const [methodDialogOpen, setMethodDialogOpen] = useState(false)
|
||||
const [editingIdx, setEditingIdx] = useState(-1)
|
||||
const [methodForm, setMethodForm] = useState<PayMethod>({
|
||||
@@ -101,61 +97,6 @@ export function WaffoSettingsSection(props: Props) {
|
||||
payMethodName: '',
|
||||
})
|
||||
|
||||
useEffect(() => {
|
||||
form.reset(props.defaultValues)
|
||||
try {
|
||||
setPayMethods(JSON.parse(props.defaultValues.WaffoPayMethods || '[]'))
|
||||
} catch {
|
||||
setPayMethods([])
|
||||
}
|
||||
}, [props.defaultValues, form])
|
||||
|
||||
const handleSave = async () => {
|
||||
setLoading(true)
|
||||
try {
|
||||
const values = form.getValues()
|
||||
const options: { key: string; value: string }[] = [
|
||||
{ key: 'WaffoEnabled', value: String(values.WaffoEnabled) },
|
||||
{ key: 'WaffoSandbox', value: String(values.WaffoSandbox) },
|
||||
{ key: 'WaffoMerchantId', value: values.WaffoMerchantId || '' },
|
||||
{ key: 'WaffoCurrency', value: values.WaffoCurrency || 'USD' },
|
||||
{ key: 'WaffoUnitPrice', value: String(values.WaffoUnitPrice || 1) },
|
||||
{ key: 'WaffoMinTopUp', value: String(values.WaffoMinTopUp || 1) },
|
||||
{ key: 'WaffoNotifyUrl', value: values.WaffoNotifyUrl || '' },
|
||||
{ key: 'WaffoReturnUrl', value: values.WaffoReturnUrl || '' },
|
||||
{ key: 'WaffoPublicCert', value: values.WaffoPublicCert || '' },
|
||||
{
|
||||
key: 'WaffoSandboxPublicCert',
|
||||
value: values.WaffoSandboxPublicCert || '',
|
||||
},
|
||||
{ key: 'WaffoPayMethods', value: JSON.stringify(payMethods) },
|
||||
]
|
||||
if (values.WaffoApiKey)
|
||||
options.push({ key: 'WaffoApiKey', value: values.WaffoApiKey })
|
||||
if (values.WaffoPrivateKey)
|
||||
options.push({ key: 'WaffoPrivateKey', value: values.WaffoPrivateKey })
|
||||
if (values.WaffoSandboxApiKey)
|
||||
options.push({
|
||||
key: 'WaffoSandboxApiKey',
|
||||
value: values.WaffoSandboxApiKey,
|
||||
})
|
||||
if (values.WaffoSandboxPrivateKey)
|
||||
options.push({
|
||||
key: 'WaffoSandboxPrivateKey',
|
||||
value: values.WaffoSandboxPrivateKey,
|
||||
})
|
||||
|
||||
for (const opt of options) {
|
||||
await updateOption.mutateAsync(opt)
|
||||
}
|
||||
toast.success(t('Updated successfully'))
|
||||
} catch {
|
||||
toast.error(t('Update failed'))
|
||||
} finally {
|
||||
setLoading(false)
|
||||
}
|
||||
}
|
||||
|
||||
const openAdd = () => {
|
||||
setEditingIdx(-1)
|
||||
setMethodForm({ name: '', icon: '', payMethodType: '', payMethodName: '' })
|
||||
@@ -172,9 +113,9 @@ export function WaffoSettingsSection(props: Props) {
|
||||
if (!methodForm.name.trim())
|
||||
return toast.error(t('Payment method name is required'))
|
||||
if (editingIdx === -1) {
|
||||
setPayMethods((prev) => [...prev, methodForm])
|
||||
onPayMethodsChange((prev) => [...prev, methodForm])
|
||||
} else {
|
||||
setPayMethods((prev) =>
|
||||
onPayMethodsChange((prev) =>
|
||||
prev.map((m, i) => (i === editingIdx ? methodForm : m))
|
||||
)
|
||||
}
|
||||
@@ -213,16 +154,6 @@ export function WaffoSettingsSection(props: Props) {
|
||||
return (
|
||||
<>
|
||||
<div className='space-y-4 pt-4'>
|
||||
<SettingsPageActionsPortal>
|
||||
<Button
|
||||
type='button'
|
||||
size='sm'
|
||||
onClick={handleSave}
|
||||
disabled={loading}
|
||||
>
|
||||
{loading ? t('Saving...') : t('Save Waffo settings')}
|
||||
</Button>
|
||||
</SettingsPageActionsPortal>
|
||||
<div>
|
||||
<h3 className='text-lg font-medium'>
|
||||
{t('Waffo Aggregator Gateway')}
|
||||
@@ -243,14 +174,14 @@ export function WaffoSettingsSection(props: Props) {
|
||||
|
||||
<div className='grid gap-4 sm:grid-cols-2'>
|
||||
<SettingsSwitchField
|
||||
checked={form.watch('WaffoEnabled')}
|
||||
onCheckedChange={(v) => form.setValue('WaffoEnabled', v)}
|
||||
checked={values.WaffoEnabled}
|
||||
onCheckedChange={(v) => onValueChange('WaffoEnabled', v)}
|
||||
label={t('Enable Waffo')}
|
||||
className='border-b-0 py-0'
|
||||
/>
|
||||
<SettingsSwitchField
|
||||
checked={form.watch('WaffoSandbox')}
|
||||
onCheckedChange={(v) => form.setValue('WaffoSandbox', v)}
|
||||
checked={values.WaffoSandbox}
|
||||
onCheckedChange={(v) => onValueChange('WaffoSandbox', v)}
|
||||
label={t('Sandbox mode')}
|
||||
className='border-b-0 py-0'
|
||||
/>
|
||||
@@ -259,17 +190,34 @@ export function WaffoSettingsSection(props: Props) {
|
||||
<div className='grid grid-cols-2 gap-4'>
|
||||
<div className='grid gap-1.5'>
|
||||
<Label>{t('API Key (Production)')}</Label>
|
||||
<Input type='password' {...form.register('WaffoApiKey')} />
|
||||
<Input
|
||||
type='password'
|
||||
value={values.WaffoApiKey}
|
||||
onChange={(event) =>
|
||||
onValueChange('WaffoApiKey', event.target.value)
|
||||
}
|
||||
/>
|
||||
</div>
|
||||
<div className='grid gap-1.5'>
|
||||
<Label>{t('API Key (Sandbox)')}</Label>
|
||||
<Input type='password' {...form.register('WaffoSandboxApiKey')} />
|
||||
<Input
|
||||
type='password'
|
||||
value={values.WaffoSandboxApiKey}
|
||||
onChange={(event) =>
|
||||
onValueChange('WaffoSandboxApiKey', event.target.value)
|
||||
}
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div className='grid gap-1.5'>
|
||||
<Label>{t('Merchant ID')}</Label>
|
||||
<Input {...form.register('WaffoMerchantId')} />
|
||||
<Input
|
||||
value={values.WaffoMerchantId}
|
||||
onChange={(event) =>
|
||||
onValueChange('WaffoMerchantId', event.target.value)
|
||||
}
|
||||
/>
|
||||
</div>
|
||||
|
||||
<div className='grid grid-cols-2 gap-4'>
|
||||
@@ -277,7 +225,10 @@ export function WaffoSettingsSection(props: Props) {
|
||||
<Label>{t('RSA Private Key (Production)')}</Label>
|
||||
<Textarea
|
||||
rows={3}
|
||||
{...form.register('WaffoPrivateKey')}
|
||||
value={values.WaffoPrivateKey}
|
||||
onChange={(event) =>
|
||||
onValueChange('WaffoPrivateKey', event.target.value)
|
||||
}
|
||||
className='font-mono text-xs'
|
||||
/>
|
||||
</div>
|
||||
@@ -285,7 +236,10 @@ export function WaffoSettingsSection(props: Props) {
|
||||
<Label>{t('RSA Private Key (Sandbox)')}</Label>
|
||||
<Textarea
|
||||
rows={3}
|
||||
{...form.register('WaffoSandboxPrivateKey')}
|
||||
value={values.WaffoSandboxPrivateKey}
|
||||
onChange={(event) =>
|
||||
onValueChange('WaffoSandboxPrivateKey', event.target.value)
|
||||
}
|
||||
className='font-mono text-xs'
|
||||
/>
|
||||
</div>
|
||||
@@ -296,7 +250,10 @@ export function WaffoSettingsSection(props: Props) {
|
||||
<Label>{t('Waffo Public Key (Production)')}</Label>
|
||||
<Textarea
|
||||
rows={3}
|
||||
{...form.register('WaffoPublicCert')}
|
||||
value={values.WaffoPublicCert}
|
||||
onChange={(event) =>
|
||||
onValueChange('WaffoPublicCert', event.target.value)
|
||||
}
|
||||
className='font-mono text-xs'
|
||||
/>
|
||||
</div>
|
||||
@@ -304,7 +261,10 @@ export function WaffoSettingsSection(props: Props) {
|
||||
<Label>{t('Waffo Public Key (Sandbox)')}</Label>
|
||||
<Textarea
|
||||
rows={3}
|
||||
{...form.register('WaffoSandboxPublicCert')}
|
||||
value={values.WaffoSandboxPublicCert}
|
||||
onChange={(event) =>
|
||||
onValueChange('WaffoSandboxPublicCert', event.target.value)
|
||||
}
|
||||
className='font-mono text-xs'
|
||||
/>
|
||||
</div>
|
||||
@@ -313,7 +273,7 @@ export function WaffoSettingsSection(props: Props) {
|
||||
<div className='grid grid-cols-3 gap-4'>
|
||||
<div className='grid gap-1.5'>
|
||||
<Label>{t('Currency')}</Label>
|
||||
<Input {...form.register('WaffoCurrency')} disabled />
|
||||
<Input value={values.WaffoCurrency} disabled />
|
||||
</div>
|
||||
<div className='grid gap-1.5'>
|
||||
<Label>{t('Unit price (USD)')}</Label>
|
||||
@@ -321,12 +281,28 @@ export function WaffoSettingsSection(props: Props) {
|
||||
type='number'
|
||||
step={0.1}
|
||||
min={0}
|
||||
{...form.register('WaffoUnitPrice')}
|
||||
value={values.WaffoUnitPrice}
|
||||
onChange={(event) =>
|
||||
onValueChange(
|
||||
'WaffoUnitPrice',
|
||||
event.target.value === '' ? 0 : event.target.valueAsNumber
|
||||
)
|
||||
}
|
||||
/>
|
||||
</div>
|
||||
<div className='grid gap-1.5'>
|
||||
<Label>{t('Minimum top-up quantity')}</Label>
|
||||
<Input type='number' min={1} {...form.register('WaffoMinTopUp')} />
|
||||
<Input
|
||||
type='number'
|
||||
min={1}
|
||||
value={values.WaffoMinTopUp}
|
||||
onChange={(event) =>
|
||||
onValueChange(
|
||||
'WaffoMinTopUp',
|
||||
event.target.value === '' ? 1 : event.target.valueAsNumber
|
||||
)
|
||||
}
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@@ -335,14 +311,20 @@ export function WaffoSettingsSection(props: Props) {
|
||||
<Label>{t('Callback notification URL')}</Label>
|
||||
<Input
|
||||
placeholder='https://example.com/api/waffo/webhook'
|
||||
{...form.register('WaffoNotifyUrl')}
|
||||
value={values.WaffoNotifyUrl}
|
||||
onChange={(event) =>
|
||||
onValueChange('WaffoNotifyUrl', event.target.value)
|
||||
}
|
||||
/>
|
||||
</div>
|
||||
<div className='grid gap-1.5'>
|
||||
<Label>{t('Payment return URL')}</Label>
|
||||
<Input
|
||||
placeholder='https://example.com/console/topup'
|
||||
{...form.register('WaffoReturnUrl')}
|
||||
value={values.WaffoReturnUrl}
|
||||
onChange={(event) =>
|
||||
onValueChange('WaffoReturnUrl', event.target.value)
|
||||
}
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
@@ -351,7 +333,7 @@ export function WaffoSettingsSection(props: Props) {
|
||||
|
||||
<div className='flex items-center justify-between'>
|
||||
<h4 className='font-medium'>{t('Payment Methods')}</h4>
|
||||
<Button variant='outline' size='sm' onClick={openAdd}>
|
||||
<Button type='button' variant='outline' size='sm' onClick={openAdd}>
|
||||
<Plus className='mr-1 h-3 w-3' />
|
||||
{t('Add payment method')}
|
||||
</Button>
|
||||
@@ -398,6 +380,7 @@ export function WaffoSettingsSection(props: Props) {
|
||||
<TableCell className='text-right'>
|
||||
<div className='flex justify-end gap-1'>
|
||||
<Button
|
||||
type='button'
|
||||
variant='ghost'
|
||||
size='icon'
|
||||
className='h-7 w-7'
|
||||
@@ -406,11 +389,12 @@ export function WaffoSettingsSection(props: Props) {
|
||||
<Pencil className='h-3 w-3' />
|
||||
</Button>
|
||||
<Button
|
||||
type='button'
|
||||
variant='ghost'
|
||||
size='icon'
|
||||
className='h-7 w-7'
|
||||
onClick={() =>
|
||||
setPayMethods((prev) =>
|
||||
onPayMethodsChange((prev) =>
|
||||
prev.filter((_, i) => i !== idx)
|
||||
)
|
||||
}
|
||||
@@ -523,12 +507,15 @@ export function WaffoSettingsSection(props: Props) {
|
||||
</div>
|
||||
<DialogFooter>
|
||||
<Button
|
||||
type='button'
|
||||
variant='outline'
|
||||
onClick={() => setMethodDialogOpen(false)}
|
||||
>
|
||||
{t('Cancel')}
|
||||
</Button>
|
||||
<Button onClick={saveMethod}>{t('Confirm')}</Button>
|
||||
<Button type='button' onClick={saveMethod}>
|
||||
{t('Confirm')}
|
||||
</Button>
|
||||
</DialogFooter>
|
||||
</DialogContent>
|
||||
</Dialog>
|
||||
|
||||
Reference in New Issue
Block a user