| | |
| | |
| | |
| | |
| | |
| | |
| | |
| | |
| | |
| | |
| | |
| | |
| | |
| | |
| | |
| | |
| | |
| | |
| |
|
| | import React, { useEffect, useState, useRef } from 'react'; |
| | import { useTranslation } from 'react-i18next'; |
| | import { |
| | API, |
| | downloadTextAsFile, |
| | showError, |
| | showSuccess, |
| | renderQuota, |
| | renderQuotaWithPrompt, |
| | } from '../../../../helpers'; |
| | import { useIsMobile } from '../../../../hooks/common/useIsMobile'; |
| | import { |
| | Button, |
| | Modal, |
| | SideSheet, |
| | Space, |
| | Spin, |
| | Typography, |
| | Card, |
| | Tag, |
| | Form, |
| | Avatar, |
| | Row, |
| | Col, |
| | } from '@douyinfe/semi-ui'; |
| | import { |
| | IconCreditCard, |
| | IconSave, |
| | IconClose, |
| | IconGift, |
| | } from '@douyinfe/semi-icons'; |
| |
|
| | const { Text, Title } = Typography; |
| |
|
| | const EditRedemptionModal = (props) => { |
| | const { t } = useTranslation(); |
| | const isEdit = props.editingRedemption.id !== undefined; |
| | const [loading, setLoading] = useState(isEdit); |
| | const isMobile = useIsMobile(); |
| | const formApiRef = useRef(null); |
| |
|
| | const getInitValues = () => ({ |
| | name: '', |
| | quota: 100000, |
| | count: 1, |
| | expired_time: null, |
| | }); |
| |
|
| | const handleCancel = () => { |
| | props.handleClose(); |
| | }; |
| |
|
| | const loadRedemption = async () => { |
| | setLoading(true); |
| | let res = await API.get(`/api/redemption/${props.editingRedemption.id}`); |
| | const { success, message, data } = res.data; |
| | if (success) { |
| | if (data.expired_time === 0) { |
| | data.expired_time = null; |
| | } else { |
| | data.expired_time = new Date(data.expired_time * 1000); |
| | } |
| | formApiRef.current?.setValues({ ...getInitValues(), ...data }); |
| | } else { |
| | showError(message); |
| | } |
| | setLoading(false); |
| | }; |
| |
|
| | useEffect(() => { |
| | if (formApiRef.current) { |
| | if (isEdit) { |
| | loadRedemption(); |
| | } else { |
| | formApiRef.current.setValues(getInitValues()); |
| | } |
| | } |
| | }, [props.editingRedemption.id]); |
| |
|
| | const submit = async (values) => { |
| | let name = values.name; |
| | if (!isEdit && (!name || name === '')) { |
| | name = renderQuota(values.quota); |
| | } |
| | setLoading(true); |
| | let localInputs = { ...values }; |
| | localInputs.count = parseInt(localInputs.count) || 0; |
| | localInputs.quota = parseInt(localInputs.quota) || 0; |
| | localInputs.name = name; |
| | if (!localInputs.expired_time) { |
| | localInputs.expired_time = 0; |
| | } else { |
| | localInputs.expired_time = Math.floor( |
| | localInputs.expired_time.getTime() / 1000, |
| | ); |
| | } |
| | let res; |
| | if (isEdit) { |
| | res = await API.put(`/api/redemption/`, { |
| | ...localInputs, |
| | id: parseInt(props.editingRedemption.id), |
| | }); |
| | } else { |
| | res = await API.post(`/api/redemption/`, { |
| | ...localInputs, |
| | }); |
| | } |
| | const { success, message, data } = res.data; |
| | if (success) { |
| | if (isEdit) { |
| | showSuccess(t('兑换码更新成功!')); |
| | props.refresh(); |
| | props.handleClose(); |
| | } else { |
| | showSuccess(t('兑换码创建成功!')); |
| | props.refresh(); |
| | formApiRef.current?.setValues(getInitValues()); |
| | props.handleClose(); |
| | } |
| | } else { |
| | showError(message); |
| | } |
| | if (!isEdit && data) { |
| | let text = ''; |
| | for (let i = 0; i < data.length; i++) { |
| | text += data[i] + '\n'; |
| | } |
| | Modal.confirm({ |
| | title: t('兑换码创建成功'), |
| | content: ( |
| | <div> |
| | <p>{t('兑换码创建成功,是否下载兑换码?')}</p> |
| | <p>{t('兑换码将以文本文件的形式下载,文件名为兑换码的名称。')}</p> |
| | </div> |
| | ), |
| | onOk: () => { |
| | downloadTextAsFile(text, `${localInputs.name}.txt`); |
| | }, |
| | }); |
| | } |
| | setLoading(false); |
| | }; |
| |
|
| | return ( |
| | <> |
| | <SideSheet |
| | placement={isEdit ? 'right' : 'left'} |
| | title={ |
| | <Space> |
| | {isEdit ? ( |
| | <Tag color='blue' shape='circle'> |
| | {t('更新')} |
| | </Tag> |
| | ) : ( |
| | <Tag color='green' shape='circle'> |
| | {t('新建')} |
| | </Tag> |
| | )} |
| | <Title heading={4} className='m-0'> |
| | {isEdit ? t('更新兑换码信息') : t('创建新的兑换码')} |
| | </Title> |
| | </Space> |
| | } |
| | bodyStyle={{ padding: '0' }} |
| | visible={props.visiable} |
| | width={isMobile ? '100%' : 600} |
| | footer={ |
| | <div className='flex justify-end bg-white'> |
| | <Space> |
| | <Button |
| | theme='solid' |
| | onClick={() => formApiRef.current?.submitForm()} |
| | icon={<IconSave />} |
| | loading={loading} |
| | > |
| | {t('提交')} |
| | </Button> |
| | <Button |
| | theme='light' |
| | type='primary' |
| | onClick={handleCancel} |
| | icon={<IconClose />} |
| | > |
| | {t('取消')} |
| | </Button> |
| | </Space> |
| | </div> |
| | } |
| | closeIcon={null} |
| | onCancel={() => handleCancel()} |
| | > |
| | <Spin spinning={loading}> |
| | <Form |
| | initValues={getInitValues()} |
| | getFormApi={(api) => (formApiRef.current = api)} |
| | onSubmit={submit} |
| | > |
| | {({ values }) => ( |
| | <div className='p-2'> |
| | <Card className='!rounded-2xl shadow-sm border-0 mb-6'> |
| | {/* Header: Basic Info */} |
| | <div className='flex items-center mb-2'> |
| | <Avatar |
| | size='small' |
| | color='blue' |
| | className='mr-2 shadow-md' |
| | > |
| | <IconGift size={16} /> |
| | </Avatar> |
| | <div> |
| | <Text className='text-lg font-medium'> |
| | {t('基本信息')} |
| | </Text> |
| | <div className='text-xs text-gray-600'> |
| | {t('设置兑换码的基本信息')} |
| | </div> |
| | </div> |
| | </div> |
| | |
| | <Row gutter={12}> |
| | <Col span={24}> |
| | <Form.Input |
| | field='name' |
| | label={t('名称')} |
| | placeholder={t('请输入名称')} |
| | style={{ width: '100%' }} |
| | rules={ |
| | !isEdit |
| | ? [] |
| | : [{ required: true, message: t('请输入名称') }] |
| | } |
| | showClear |
| | /> |
| | </Col> |
| | <Col span={24}> |
| | <Form.DatePicker |
| | field='expired_time' |
| | label={t('过期时间')} |
| | type='dateTime' |
| | placeholder={t('选择过期时间(可选,留空为永久)')} |
| | style={{ width: '100%' }} |
| | showClear |
| | /> |
| | </Col> |
| | </Row> |
| | </Card> |
| | |
| | <Card className='!rounded-2xl shadow-sm border-0'> |
| | {/* Header: Quota Settings */} |
| | <div className='flex items-center mb-2'> |
| | <Avatar |
| | size='small' |
| | color='green' |
| | className='mr-2 shadow-md' |
| | > |
| | <IconCreditCard size={16} /> |
| | </Avatar> |
| | <div> |
| | <Text className='text-lg font-medium'> |
| | {t('额度设置')} |
| | </Text> |
| | <div className='text-xs text-gray-600'> |
| | {t('设置兑换码的额度和数量')} |
| | </div> |
| | </div> |
| | </div> |
| | |
| | <Row gutter={12}> |
| | <Col span={12}> |
| | <Form.AutoComplete |
| | field='quota' |
| | label={t('额度')} |
| | placeholder={t('请输入额度')} |
| | style={{ width: '100%' }} |
| | type='number' |
| | rules={[ |
| | { required: true, message: t('请输入额度') }, |
| | { |
| | validator: (rule, v) => { |
| | const num = parseInt(v, 10); |
| | return num > 0 |
| | ? Promise.resolve() |
| | : Promise.reject(t('额度必须大于0')); |
| | }, |
| | }, |
| | ]} |
| | extraText={renderQuotaWithPrompt( |
| | Number(values.quota) || 0, |
| | )} |
| | data={[ |
| | { value: 500000, label: '1$' }, |
| | { value: 5000000, label: '10$' }, |
| | { value: 25000000, label: '50$' }, |
| | { value: 50000000, label: '100$' }, |
| | { value: 250000000, label: '500$' }, |
| | { value: 500000000, label: '1000$' }, |
| | ]} |
| | showClear |
| | /> |
| | </Col> |
| | {!isEdit && ( |
| | <Col span={12}> |
| | <Form.InputNumber |
| | field='count' |
| | label={t('生成数量')} |
| | min={1} |
| | rules={[ |
| | { required: true, message: t('请输入生成数量') }, |
| | { |
| | validator: (rule, v) => { |
| | const num = parseInt(v, 10); |
| | return num > 0 |
| | ? Promise.resolve() |
| | : Promise.reject(t('生成数量必须大于0')); |
| | }, |
| | }, |
| | ]} |
| | style={{ width: '100%' }} |
| | showClear |
| | /> |
| | </Col> |
| | )} |
| | </Row> |
| | </Card> |
| | </div> |
| | )} |
| | </Form> |
| | </Spin> |
| | </SideSheet> |
| | </> |
| | ); |
| | }; |
| |
|
| | export default EditRedemptionModal; |
| |
|