✨ refactor: unify layout, adopt Semi UI Forms, dynamic presets, and fix duplicate requests
- Unify TopUp into a single-page layout and remove tabs - Replace custom inputs with Semi UI Form components (Form.Input, Form.InputNumber, Form.Slot) - Move online recharge form into the stats Card content for tighter, consistent layout - Add account stats Card with blue theme (consistent with InvitationCard style) - Remove RightStatsCard and inline the stats UI directly in RechargeCard - Change preset amount UI to horizontal quick-action Buttons; swap order with payment methods - Replace payment method Cards with Semi UI Buttons - Use Button icon prop for Alipay/WeChat/Stripe with brand colors - Use built-in Button loading; remove custom “processing...” text - Replace custom spinners with Semi UI Spin and keep Skeleton for amount loading - Wrap Redeem Code in a Card; use Typography for “Looking for a code? Buy Redeem Code” link - Show info Banner when online recharge is disabled (instead of warning) TopUp data flow and logic - Generate preset amounts from min_topup using multipliers [1,5,10,30,50,100,300,500] - Deduplicate /api/user/aff using a ref guard; fetch only once on mount - Simplify user self fetch: update context only; remove unused local states and helpers - Normalize payment method keys to alipay/wxpay/stripe and assign default colors Cleanup - Delete web/src/components/topup/RightStatsCard.jsx - Remove unused helpers and local states in index.jsx (userQuota, userDataLoading, getUsername) Dev notes - No API changes; UI/UX refactor only - Lint clean (no new linter errors) Files - web/src/components/topup/RechargeCard.jsx - web/src/components/topup/index.jsx - web/src/components/topup/InvitationCard.jsx (visual parity reference) - web/src/components/topup/RightStatsCard.jsx (removed)
This commit is contained in:
@@ -17,7 +17,7 @@ along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||
For commercial licensing, please contact support@quantumnous.com
|
||||
*/
|
||||
|
||||
import React, { useEffect, useState, useContext } from 'react';
|
||||
import React, { useEffect, useState, useContext, useRef } from 'react';
|
||||
import {
|
||||
API,
|
||||
showError,
|
||||
@@ -63,42 +63,25 @@ const TopUp = () => {
|
||||
const [enableStripeTopUp, setEnableStripeTopUp] = useState(statusState?.status?.enable_stripe_topup || false);
|
||||
const [statusLoading, setStatusLoading] = useState(true);
|
||||
|
||||
const [userQuota, setUserQuota] = useState(0);
|
||||
const [isSubmitting, setIsSubmitting] = useState(false);
|
||||
const [open, setOpen] = useState(false);
|
||||
const [payWay, setPayWay] = useState('');
|
||||
const [userDataLoading, setUserDataLoading] = useState(true);
|
||||
const [amountLoading, setAmountLoading] = useState(false);
|
||||
const [paymentLoading, setPaymentLoading] = useState(false);
|
||||
const [confirmLoading, setConfirmLoading] = useState(false);
|
||||
const [payMethods, setPayMethods] = useState([]);
|
||||
|
||||
const affFetchedRef = useRef(false);
|
||||
|
||||
// 邀请相关状态
|
||||
const [affLink, setAffLink] = useState('');
|
||||
const [openTransfer, setOpenTransfer] = useState(false);
|
||||
const [transferAmount, setTransferAmount] = useState(0);
|
||||
|
||||
// 预设充值额度选项
|
||||
const [presetAmounts, setPresetAmounts] = useState([
|
||||
{ value: 5 },
|
||||
{ value: 10 },
|
||||
{ value: 30 },
|
||||
{ value: 50 },
|
||||
{ value: 100 },
|
||||
{ value: 300 },
|
||||
{ value: 500 },
|
||||
{ value: 1000 },
|
||||
]);
|
||||
const [presetAmounts, setPresetAmounts] = useState([]);
|
||||
const [selectedPreset, setSelectedPreset] = useState(null);
|
||||
|
||||
const getUsername = () => {
|
||||
if (userState.user) {
|
||||
return userState.user.username;
|
||||
} else {
|
||||
return 'null';
|
||||
}
|
||||
};
|
||||
|
||||
const topUp = async () => {
|
||||
if (redemptionCode === '') {
|
||||
showInfo(t('请输入兑换码!'));
|
||||
@@ -117,9 +100,6 @@ const TopUp = () => {
|
||||
content: t('成功兑换额度:') + renderQuota(data),
|
||||
centered: true,
|
||||
});
|
||||
setUserQuota((quota) => {
|
||||
return quota + data;
|
||||
});
|
||||
if (userState.user) {
|
||||
const updatedUser = {
|
||||
...userState.user,
|
||||
@@ -260,16 +240,13 @@ const TopUp = () => {
|
||||
};
|
||||
|
||||
const getUserQuota = async () => {
|
||||
setUserDataLoading(true);
|
||||
let res = await API.get(`/api/user/self`);
|
||||
const { success, message, data } = res.data;
|
||||
if (success) {
|
||||
setUserQuota(data.quota);
|
||||
userDispatch({ type: 'login', payload: data });
|
||||
} else {
|
||||
showError(message);
|
||||
}
|
||||
setUserDataLoading(false);
|
||||
};
|
||||
|
||||
// 获取邀请链接
|
||||
@@ -310,13 +287,9 @@ const TopUp = () => {
|
||||
};
|
||||
|
||||
useEffect(() => {
|
||||
if (userState?.user?.id) {
|
||||
setUserDataLoading(false);
|
||||
setUserQuota(userState.user.quota);
|
||||
} else {
|
||||
if (!userState?.user?.id) {
|
||||
getUserQuota().then();
|
||||
}
|
||||
getAffLink().then();
|
||||
setTransferAmount(getQuotaPerUnit());
|
||||
|
||||
let payMethods = localStorage.getItem('pay_methods');
|
||||
@@ -330,9 +303,9 @@ const TopUp = () => {
|
||||
// 如果没有color,则设置默认颜色
|
||||
payMethods = payMethods.map((method) => {
|
||||
if (!method.color) {
|
||||
if (method.type === 'zfb') {
|
||||
if (method.type === 'alipay') {
|
||||
method.color = 'rgba(var(--semi-blue-5), 1)';
|
||||
} else if (method.type === 'wx') {
|
||||
} else if (method.type === 'wxpay') {
|
||||
method.color = 'rgba(var(--semi-green-5), 1)';
|
||||
} else if (method.type === 'stripe') {
|
||||
method.color = 'rgba(var(--semi-purple-5), 1)';
|
||||
@@ -365,14 +338,27 @@ const TopUp = () => {
|
||||
}
|
||||
}, [statusState?.status?.enable_stripe_topup]);
|
||||
|
||||
useEffect(() => {
|
||||
if (affFetchedRef.current) return;
|
||||
affFetchedRef.current = true;
|
||||
getAffLink().then();
|
||||
}, []);
|
||||
|
||||
useEffect(() => {
|
||||
if (statusState?.status) {
|
||||
setMinTopUp(statusState.status.min_topup || 1);
|
||||
setTopUpCount(statusState.status.min_topup || 1);
|
||||
const minTopUpValue = statusState.status.min_topup || 1;
|
||||
setMinTopUp(minTopUpValue);
|
||||
setTopUpCount(minTopUpValue);
|
||||
setTopUpLink(statusState.status.top_up_link || '');
|
||||
setEnableOnlineTopUp(statusState.status.enable_online_topup || false);
|
||||
setPriceRatio(statusState.status.price || 1);
|
||||
setEnableStripeTopUp(statusState.status.enable_stripe_topup || false);
|
||||
|
||||
// 根据最小充值金额生成预设充值额度选项
|
||||
setPresetAmounts(generatePresetAmounts(minTopUpValue));
|
||||
// 初始化显示实付金额
|
||||
getAmount(minTopUpValue);
|
||||
|
||||
setStatusLoading(false);
|
||||
}
|
||||
}, [statusState?.status]);
|
||||
@@ -454,6 +440,14 @@ const TopUp = () => {
|
||||
return num.toString();
|
||||
};
|
||||
|
||||
// 根据最小充值金额生成预设充值额度选项
|
||||
const generatePresetAmounts = (minAmount) => {
|
||||
const multipliers = [1, 5, 10, 30, 50, 100, 300, 500];
|
||||
return multipliers.map(multiplier => ({
|
||||
value: minAmount * multiplier
|
||||
}));
|
||||
};
|
||||
|
||||
return (
|
||||
<div className='mx-auto relative min-h-screen lg:min-h-0 mt-[60px] px-2'>
|
||||
{/* 划转模态框 */}
|
||||
|
||||
Reference in New Issue
Block a user