/* Copyright (C) 2025 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 React, { useEffect, useState, useRef } from 'react'; import { Avatar, Button, Card, Col, Form, Row, Select, SideSheet, Space, Spin, Tag, Typography, } from '@douyinfe/semi-ui'; import { IconCalendarClock, IconClose, IconCreditCard, IconSave, } from '@douyinfe/semi-icons'; import { Clock, RefreshCw } from 'lucide-react'; import { API, showError, showSuccess } from '../../../../helpers'; import { quotaToDisplayAmount, displayAmountToQuota, } from '../../../../helpers/quota'; import { useIsMobile } from '../../../../hooks/common/useIsMobile'; const { Text, Title } = Typography; const durationUnitOptions = [ { value: 'year', label: '年' }, { value: 'month', label: '月' }, { value: 'day', label: '日' }, { value: 'hour', label: '小时' }, { value: 'custom', label: '自定义(秒)' }, ]; const resetPeriodOptions = [ { value: 'never', label: '不重置' }, { value: 'daily', label: '每天' }, { value: 'weekly', label: '每周' }, { value: 'monthly', label: '每月' }, { value: 'custom', label: '自定义(秒)' }, ]; const AddEditSubscriptionModal = ({ visible, handleClose, editingPlan, placement = 'left', refresh, t, }) => { const [loading, setLoading] = useState(false); const [groupOptions, setGroupOptions] = useState([]); const [groupLoading, setGroupLoading] = useState(false); const isMobile = useIsMobile(); const formApiRef = useRef(null); const isEdit = editingPlan?.plan?.id !== undefined; const formKey = isEdit ? `edit-${editingPlan?.plan?.id}` : 'create'; const getInitValues = () => ({ title: '', subtitle: '', price_amount: 0, currency: 'USD', duration_unit: 'month', duration_value: 1, custom_seconds: 0, quota_reset_period: 'never', quota_reset_custom_seconds: 0, enabled: true, sort_order: 0, max_purchase_per_user: 0, total_amount: 0, upgrade_group: '', stripe_price_id: '', creem_product_id: '', }); const buildFormValues = () => { const base = getInitValues(); if (editingPlan?.plan?.id === undefined) return base; const p = editingPlan.plan || {}; return { ...base, title: p.title || '', subtitle: p.subtitle || '', price_amount: Number(p.price_amount || 0), currency: 'USD', duration_unit: p.duration_unit || 'month', duration_value: Number(p.duration_value || 1), custom_seconds: Number(p.custom_seconds || 0), quota_reset_period: p.quota_reset_period || 'never', quota_reset_custom_seconds: Number(p.quota_reset_custom_seconds || 0), enabled: p.enabled !== false, sort_order: Number(p.sort_order || 0), max_purchase_per_user: Number(p.max_purchase_per_user || 0), total_amount: Number( quotaToDisplayAmount(p.total_amount || 0).toFixed(2), ), upgrade_group: p.upgrade_group || '', stripe_price_id: p.stripe_price_id || '', creem_product_id: p.creem_product_id || '', }; }; useEffect(() => { if (!visible) return; setGroupLoading(true); API.get('/api/group') .then((res) => { if (res.data?.success) { setGroupOptions(res.data?.data || []); } else { setGroupOptions([]); } }) .catch(() => setGroupOptions([])) .finally(() => setGroupLoading(false)); }, [visible]); const submit = async (values) => { if (!values.title || values.title.trim() === '') { showError(t('套餐标题不能为空')); return; } setLoading(true); try { const payload = { plan: { ...values, price_amount: Number(values.price_amount || 0), currency: 'USD', duration_value: Number(values.duration_value || 0), custom_seconds: Number(values.custom_seconds || 0), quota_reset_period: values.quota_reset_period || 'never', quota_reset_custom_seconds: values.quota_reset_period === 'custom' ? Number(values.quota_reset_custom_seconds || 0) : 0, sort_order: Number(values.sort_order || 0), max_purchase_per_user: Number(values.max_purchase_per_user || 0), total_amount: displayAmountToQuota(values.total_amount), upgrade_group: values.upgrade_group || '', }, }; if (editingPlan?.plan?.id) { const res = await API.put( `/api/subscription/admin/plans/${editingPlan.plan.id}`, payload, ); if (res.data?.success) { showSuccess(t('更新成功')); handleClose(); refresh?.(); } else { showError(res.data?.message || t('更新失败')); } } else { const res = await API.post('/api/subscription/admin/plans', payload); if (res.data?.success) { showSuccess(t('创建成功')); handleClose(); refresh?.(); } else { showError(res.data?.message || t('创建失败')); } } } catch (e) { showError(t('请求失败')); } finally { setLoading(false); } }; return ( <> {isEdit ? ( {t('更新')} ) : ( {t('新建')} )} {isEdit ? t('更新套餐信息') : t('创建新的订阅套餐')} } bodyStyle={{ padding: '0' }} visible={visible} width={isMobile ? '100%' : 600} footer={ formApiRef.current?.submitForm()} icon={} loading={loading} > {t('提交')} } > {t('取消')} } closeIcon={null} onCancel={handleClose} > (formApiRef.current = api)} onSubmit={submit} > {({ values }) => ( {/* 基本信息 */} {t('基本信息')} {t('套餐的基本信息和定价')} {t('不升级')} {(groupOptions || []).map((g) => ( {g} ))} {/* 有效期设置 */} {t('有效期设置')} {t('配置套餐的有效时长')} {durationUnitOptions.map((o) => ( {o.label} ))} {values.duration_unit === 'custom' ? ( ) : ( )} {/* 额度重置 */} {t('额度重置')} {t('支持周期性重置套餐权益额度')} {resetPeriodOptions.map((o) => ( {o.label} ))} {values.quota_reset_period === 'custom' ? ( ) : ( )} {/* 第三方支付配置 */} {t('第三方支付配置')} {t('Stripe/Creem 商品ID(可选)')} )} > ); }; export default AddEditSubscriptionModal;