- LoginForm: 登录成功后跳转回登录前的页面,而非固定 /console - RechargeCard: 输入金额低于最低值时显示警告提示 - .dockerignore: 排除 .claude、.plans、脚本等非项目文件 Co-Authored-By: Claude <noreply@anthropic.com>master
| @@ -7,4 +7,12 @@ Makefile | |||||
| docs | docs | ||||
| .eslintcache | .eslintcache | ||||
| .gocache | .gocache | ||||
| /web/node_modules | |||||
| /web/node_modules | |||||
| .claude | |||||
| .plans | |||||
| current-page* | |||||
| models-page* | |||||
| pricing-page.png | |||||
| login-page | |||||
| scripts | |||||
| relay/helper/price_test.go | |||||
| @@ -17,8 +17,8 @@ along with this program. If not, see <https://www.gnu.org/licenses/>. | |||||
| For commercial licensing, please contact support@quantumnous.com | For commercial licensing, please contact support@quantumnous.com | ||||
| */ | */ | ||||
| import React, { useContext, useEffect, useMemo, useRef, useState } from 'react'; | |||||
| import { Link, useNavigate, useSearchParams } from 'react-router-dom'; | |||||
| import React, { useCallback, useContext, useEffect, useMemo, useRef, useState } from 'react'; | |||||
| import { Link, useLocation, useNavigate, useSearchParams } from 'react-router-dom'; | |||||
| import { UserContext } from '../../context/User'; | import { UserContext } from '../../context/User'; | ||||
| import { StatusContext } from '../../context/Status'; | import { StatusContext } from '../../context/Status'; | ||||
| import { | import { | ||||
| @@ -69,6 +69,7 @@ import { SiDiscord } from 'react-icons/si'; | |||||
| const LoginForm = () => { | const LoginForm = () => { | ||||
| let navigate = useNavigate(); | let navigate = useNavigate(); | ||||
| const location = useLocation(); | |||||
| const { t } = useTranslation(); | const { t } = useTranslation(); | ||||
| const githubButtonTextKeyByState = { | const githubButtonTextKeyByState = { | ||||
| idle: '使用 GitHub 继续', | idle: '使用 GitHub 继续', | ||||
| @@ -113,6 +114,15 @@ const LoginForm = () => { | |||||
| const githubButtonText = t(githubButtonTextKeyByState[githubButtonState]); | const githubButtonText = t(githubButtonTextKeyByState[githubButtonState]); | ||||
| const [customOAuthLoading, setCustomOAuthLoading] = useState({}); | const [customOAuthLoading, setCustomOAuthLoading] = useState({}); | ||||
| const navigateAfterLogin = useCallback(() => { | |||||
| const from = location.state?.from; | |||||
| if (from && from.pathname) { | |||||
| navigate(from.pathname + (from.search || '')); | |||||
| } else { | |||||
| navigate('/console'); | |||||
| } | |||||
| }, [location.state, navigate]); | |||||
| const logo = getLogo(); | const logo = getLogo(); | ||||
| const systemName = getSystemName(); | const systemName = getSystemName(); | ||||
| @@ -198,7 +208,7 @@ const LoginForm = () => { | |||||
| localStorage.setItem('user', JSON.stringify(data)); | localStorage.setItem('user', JSON.stringify(data)); | ||||
| setUserData(data); | setUserData(data); | ||||
| updateAPI(); | updateAPI(); | ||||
| navigate('/'); | |||||
| navigateAfterLogin(); | |||||
| showSuccess(t('登录成功!')); | showSuccess(t('登录成功!')); | ||||
| setShowWeChatLoginModal(false); | setShowWeChatLoginModal(false); | ||||
| } else { | } else { | ||||
| @@ -255,7 +265,7 @@ const LoginForm = () => { | |||||
| centered: true, | centered: true, | ||||
| }); | }); | ||||
| } | } | ||||
| navigate('/console'); | |||||
| navigateAfterLogin(); | |||||
| } else { | } else { | ||||
| showError(message); | showError(message); | ||||
| } | } | ||||
| @@ -300,7 +310,7 @@ const LoginForm = () => { | |||||
| showSuccess(t('登录成功!')); | showSuccess(t('登录成功!')); | ||||
| setUserData(data); | setUserData(data); | ||||
| updateAPI(); | updateAPI(); | ||||
| navigate('/'); | |||||
| navigateAfterLogin(); | |||||
| } else { | } else { | ||||
| showError(message); | showError(message); | ||||
| } | } | ||||
| @@ -456,7 +466,7 @@ const LoginForm = () => { | |||||
| setUserData(finish.data); | setUserData(finish.data); | ||||
| updateAPI(); | updateAPI(); | ||||
| showSuccess(t('登录成功!')); | showSuccess(t('登录成功!')); | ||||
| navigate('/console'); | |||||
| navigateAfterLogin(); | |||||
| } else { | } else { | ||||
| showError(finish.message || t('Passkey 登录失败,请重试')); | showError(finish.message || t('Passkey 登录失败,请重试')); | ||||
| } | } | ||||
| @@ -491,7 +501,7 @@ const LoginForm = () => { | |||||
| setUserData(data); | setUserData(data); | ||||
| updateAPI(); | updateAPI(); | ||||
| showSuccess(t('登录成功!')); | showSuccess(t('登录成功!')); | ||||
| navigate('/console'); | |||||
| navigateAfterLogin(); | |||||
| }; | }; | ||||
| // 返回登录页面 | // 返回登录页面 | ||||
| @@ -102,6 +102,7 @@ const RechargeCard = ({ | |||||
| const initialTabSetRef = useRef(false); | const initialTabSetRef = useRef(false); | ||||
| const showAmountSkeleton = useMinimumLoadingTime(amountLoading); | const showAmountSkeleton = useMinimumLoadingTime(amountLoading); | ||||
| const [activeTab, setActiveTab] = useState('topup'); | const [activeTab, setActiveTab] = useState('topup'); | ||||
| const [amountWarning, setAmountWarning] = useState(''); | |||||
| const shouldShowSubscription = | const shouldShowSubscription = | ||||
| !subscriptionLoading && subscriptionPlans.length > 0; | !subscriptionLoading && subscriptionPlans.length > 0; | ||||
| @@ -255,18 +256,26 @@ const RechargeCard = ({ | |||||
| onChange={async (displayValue) => { | onChange={async (displayValue) => { | ||||
| const rawValue = needsConvert ? Math.round(displayValue / curRate) : displayValue; | const rawValue = needsConvert ? Math.round(displayValue / curRate) : displayValue; | ||||
| if (rawValue && rawValue >= 1) { | if (rawValue && rawValue >= 1) { | ||||
| if (rawValue < minTopUp) { | |||||
| setAmountWarning(t('金额过小,最低需充值') + displayMin + ' ' + curSymbol); | |||||
| } else { | |||||
| setAmountWarning(''); | |||||
| } | |||||
| setTopUpCount(rawValue); | setTopUpCount(rawValue); | ||||
| setSelectedPreset(null); | setSelectedPreset(null); | ||||
| await getAmount(rawValue); | await getAmount(rawValue); | ||||
| } else { | |||||
| setAmountWarning(t('金额过小,最低需充值') + displayMin + ' ' + curSymbol); | |||||
| } | } | ||||
| }} | }} | ||||
| onBlur={(e) => { | onBlur={(e) => { | ||||
| const displayVal = parseInt(e.target.value); | const displayVal = parseInt(e.target.value); | ||||
| const rawVal = needsConvert ? Math.round(displayVal / curRate) : displayVal; | const rawVal = needsConvert ? Math.round(displayVal / curRate) : displayVal; | ||||
| if (!rawVal || rawVal < 1) { | if (!rawVal || rawVal < 1) { | ||||
| setTopUpCount(1); | |||||
| getAmount(1); | |||||
| setTopUpCount(minTopUp); | |||||
| getAmount(minTopUp); | |||||
| } | } | ||||
| setAmountWarning(''); | |||||
| }} | }} | ||||
| formatter={(value) => (value ? `${value}` : '')} | formatter={(value) => (value ? `${value}` : '')} | ||||
| parser={(value) => | parser={(value) => | ||||
| @@ -275,26 +284,33 @@ const RechargeCard = ({ | |||||
| suffix={curSymbol} | suffix={curSymbol} | ||||
| style={{ width: '100%' }} | style={{ width: '100%' }} | ||||
| extraText={ | extraText={ | ||||
| <Skeleton | |||||
| loading={showAmountSkeleton} | |||||
| active | |||||
| placeholder={ | |||||
| <Skeleton.Title | |||||
| style={{ | |||||
| width: 120, | |||||
| height: 20, | |||||
| borderRadius: 6, | |||||
| }} | |||||
| /> | |||||
| } | |||||
| > | |||||
| <Text type='secondary' className='text-red-600'> | |||||
| {t('实付金额:')} | |||||
| <span style={{ color: 'red' }}> | |||||
| {renderAmount()} | |||||
| </span> | |||||
| </Text> | |||||
| </Skeleton> | |||||
| <> | |||||
| {amountWarning && ( | |||||
| <div style={{ color: '#e74c3c', fontWeight: 500, fontSize: '13px', marginBottom: '4px' }}> | |||||
| {amountWarning} | |||||
| </div> | |||||
| )} | |||||
| <Skeleton | |||||
| loading={showAmountSkeleton} | |||||
| active | |||||
| placeholder={ | |||||
| <Skeleton.Title | |||||
| style={{ | |||||
| width: 120, | |||||
| height: 20, | |||||
| borderRadius: 6, | |||||
| }} | |||||
| /> | |||||
| } | |||||
| > | |||||
| <Text type='secondary' className='text-red-600'> | |||||
| {t('实付金额:')} | |||||
| <span style={{ color: 'red' }}> | |||||
| {renderAmount()} | |||||
| </span> | |||||
| </Text> | |||||
| </Skeleton> | |||||
| </> | |||||
| } | } | ||||
| /> | /> | ||||
| ); | ); | ||||
| @@ -436,6 +452,7 @@ const RechargeCard = ({ | |||||
| bodyStyle={{ padding: '12px' }} | bodyStyle={{ padding: '12px' }} | ||||
| onClick={() => { | onClick={() => { | ||||
| selectPresetAmount(preset); | selectPresetAmount(preset); | ||||
| setAmountWarning(''); | |||||
| onlineFormApiRef.current?.setValue( | onlineFormApiRef.current?.setValue( | ||||
| 'topUpCount', | 'topUpCount', | ||||
| needsConvert ? Math.round(preset.value * pRate) : preset.value, | needsConvert ? Math.round(preset.value * pRate) : preset.value, | ||||