- LoginForm: 登录成功后跳转回登录前的页面,而非固定 /console - RechargeCard: 输入金额低于最低值时显示警告提示 - .dockerignore: 排除 .claude、.plans、脚本等非项目文件 Co-Authored-By: Claude <noreply@anthropic.com>master
| @@ -7,4 +7,12 @@ Makefile | |||
| docs | |||
| .eslintcache | |||
| .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 | |||
| */ | |||
| 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 { StatusContext } from '../../context/Status'; | |||
| import { | |||
| @@ -69,6 +69,7 @@ import { SiDiscord } from 'react-icons/si'; | |||
| const LoginForm = () => { | |||
| let navigate = useNavigate(); | |||
| const location = useLocation(); | |||
| const { t } = useTranslation(); | |||
| const githubButtonTextKeyByState = { | |||
| idle: '使用 GitHub 继续', | |||
| @@ -113,6 +114,15 @@ const LoginForm = () => { | |||
| const githubButtonText = t(githubButtonTextKeyByState[githubButtonState]); | |||
| 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 systemName = getSystemName(); | |||
| @@ -198,7 +208,7 @@ const LoginForm = () => { | |||
| localStorage.setItem('user', JSON.stringify(data)); | |||
| setUserData(data); | |||
| updateAPI(); | |||
| navigate('/'); | |||
| navigateAfterLogin(); | |||
| showSuccess(t('登录成功!')); | |||
| setShowWeChatLoginModal(false); | |||
| } else { | |||
| @@ -255,7 +265,7 @@ const LoginForm = () => { | |||
| centered: true, | |||
| }); | |||
| } | |||
| navigate('/console'); | |||
| navigateAfterLogin(); | |||
| } else { | |||
| showError(message); | |||
| } | |||
| @@ -300,7 +310,7 @@ const LoginForm = () => { | |||
| showSuccess(t('登录成功!')); | |||
| setUserData(data); | |||
| updateAPI(); | |||
| navigate('/'); | |||
| navigateAfterLogin(); | |||
| } else { | |||
| showError(message); | |||
| } | |||
| @@ -456,7 +466,7 @@ const LoginForm = () => { | |||
| setUserData(finish.data); | |||
| updateAPI(); | |||
| showSuccess(t('登录成功!')); | |||
| navigate('/console'); | |||
| navigateAfterLogin(); | |||
| } else { | |||
| showError(finish.message || t('Passkey 登录失败,请重试')); | |||
| } | |||
| @@ -491,7 +501,7 @@ const LoginForm = () => { | |||
| setUserData(data); | |||
| updateAPI(); | |||
| showSuccess(t('登录成功!')); | |||
| navigate('/console'); | |||
| navigateAfterLogin(); | |||
| }; | |||
| // 返回登录页面 | |||
| @@ -102,6 +102,7 @@ const RechargeCard = ({ | |||
| const initialTabSetRef = useRef(false); | |||
| const showAmountSkeleton = useMinimumLoadingTime(amountLoading); | |||
| const [activeTab, setActiveTab] = useState('topup'); | |||
| const [amountWarning, setAmountWarning] = useState(''); | |||
| const shouldShowSubscription = | |||
| !subscriptionLoading && subscriptionPlans.length > 0; | |||
| @@ -255,18 +256,26 @@ const RechargeCard = ({ | |||
| onChange={async (displayValue) => { | |||
| const rawValue = needsConvert ? Math.round(displayValue / curRate) : displayValue; | |||
| if (rawValue && rawValue >= 1) { | |||
| if (rawValue < minTopUp) { | |||
| setAmountWarning(t('金额过小,最低需充值') + displayMin + ' ' + curSymbol); | |||
| } else { | |||
| setAmountWarning(''); | |||
| } | |||
| setTopUpCount(rawValue); | |||
| setSelectedPreset(null); | |||
| await getAmount(rawValue); | |||
| } else { | |||
| setAmountWarning(t('金额过小,最低需充值') + displayMin + ' ' + curSymbol); | |||
| } | |||
| }} | |||
| onBlur={(e) => { | |||
| const displayVal = parseInt(e.target.value); | |||
| const rawVal = needsConvert ? Math.round(displayVal / curRate) : displayVal; | |||
| if (!rawVal || rawVal < 1) { | |||
| setTopUpCount(1); | |||
| getAmount(1); | |||
| setTopUpCount(minTopUp); | |||
| getAmount(minTopUp); | |||
| } | |||
| setAmountWarning(''); | |||
| }} | |||
| formatter={(value) => (value ? `${value}` : '')} | |||
| parser={(value) => | |||
| @@ -275,26 +284,33 @@ const RechargeCard = ({ | |||
| suffix={curSymbol} | |||
| style={{ width: '100%' }} | |||
| 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' }} | |||
| onClick={() => { | |||
| selectPresetAmount(preset); | |||
| setAmountWarning(''); | |||
| onlineFormApiRef.current?.setValue( | |||
| 'topUpCount', | |||
| needsConvert ? Math.round(preset.value * pRate) : preset.value, | |||