feat: 新增储蓄罐存取款模态框,支持金额输入、快速选择、进度展示及账户余额校验。
This commit is contained in:
27
package-lock.json
generated
27
package-lock.json
generated
@@ -21,6 +21,7 @@
|
|||||||
"react": "^19.2.0",
|
"react": "^19.2.0",
|
||||||
"react-confetti": "^6.4.0",
|
"react-confetti": "^6.4.0",
|
||||||
"react-dom": "^19.2.0",
|
"react-dom": "^19.2.0",
|
||||||
|
"react-hot-toast": "^2.6.0",
|
||||||
"react-icons": "^5.5.0",
|
"react-icons": "^5.5.0",
|
||||||
"react-markdown": "^10.1.0",
|
"react-markdown": "^10.1.0",
|
||||||
"react-router-dom": "^7.12.0",
|
"react-router-dom": "^7.12.0",
|
||||||
@@ -3938,6 +3939,15 @@
|
|||||||
"url": "https://github.com/sponsors/sindresorhus"
|
"url": "https://github.com/sponsors/sindresorhus"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"node_modules/goober": {
|
||||||
|
"version": "2.1.18",
|
||||||
|
"resolved": "https://registry.npmjs.org/goober/-/goober-2.1.18.tgz",
|
||||||
|
"integrity": "sha512-2vFqsaDVIT9Gz7N6kAL++pLpp41l3PfDuusHcjnGLfR6+huZkl6ziX+zgVC3ZxpqWhzH6pyDdGrCeDhMIvwaxw==",
|
||||||
|
"license": "MIT",
|
||||||
|
"peerDependencies": {
|
||||||
|
"csstype": "^3.0.10"
|
||||||
|
}
|
||||||
|
},
|
||||||
"node_modules/gopd": {
|
"node_modules/gopd": {
|
||||||
"version": "1.2.0",
|
"version": "1.2.0",
|
||||||
"resolved": "https://registry.npmjs.org/gopd/-/gopd-1.2.0.tgz",
|
"resolved": "https://registry.npmjs.org/gopd/-/gopd-1.2.0.tgz",
|
||||||
@@ -5867,6 +5877,23 @@
|
|||||||
"react": "^19.2.3"
|
"react": "^19.2.3"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"node_modules/react-hot-toast": {
|
||||||
|
"version": "2.6.0",
|
||||||
|
"resolved": "https://registry.npmjs.org/react-hot-toast/-/react-hot-toast-2.6.0.tgz",
|
||||||
|
"integrity": "sha512-bH+2EBMZ4sdyou/DPrfgIouFpcRLCJ+HoCA32UoAYHn6T3Ur5yfcDCeSr5mwldl6pFOsiocmrXMuoCJ1vV8bWg==",
|
||||||
|
"license": "MIT",
|
||||||
|
"dependencies": {
|
||||||
|
"csstype": "^3.1.3",
|
||||||
|
"goober": "^2.1.16"
|
||||||
|
},
|
||||||
|
"engines": {
|
||||||
|
"node": ">=10"
|
||||||
|
},
|
||||||
|
"peerDependencies": {
|
||||||
|
"react": ">=16",
|
||||||
|
"react-dom": ">=16"
|
||||||
|
}
|
||||||
|
},
|
||||||
"node_modules/react-icons": {
|
"node_modules/react-icons": {
|
||||||
"version": "5.5.0",
|
"version": "5.5.0",
|
||||||
"resolved": "https://registry.npmjs.org/react-icons/-/react-icons-5.5.0.tgz",
|
"resolved": "https://registry.npmjs.org/react-icons/-/react-icons-5.5.0.tgz",
|
||||||
|
|||||||
@@ -29,6 +29,7 @@
|
|||||||
"react": "^19.2.0",
|
"react": "^19.2.0",
|
||||||
"react-confetti": "^6.4.0",
|
"react-confetti": "^6.4.0",
|
||||||
"react-dom": "^19.2.0",
|
"react-dom": "^19.2.0",
|
||||||
|
"react-hot-toast": "^2.6.0",
|
||||||
"react-icons": "^5.5.0",
|
"react-icons": "^5.5.0",
|
||||||
"react-markdown": "^10.1.0",
|
"react-markdown": "^10.1.0",
|
||||||
"react-router-dom": "^7.12.0",
|
"react-router-dom": "^7.12.0",
|
||||||
|
|||||||
@@ -4,6 +4,7 @@ import { router } from './router';
|
|||||||
|
|
||||||
import { ThemeProvider, PrivacyProvider, NotificationProvider, GuideProvider, useAutoTokenRefresh } from './hooks';
|
import { ThemeProvider, PrivacyProvider, NotificationProvider, GuideProvider, useAutoTokenRefresh } from './hooks';
|
||||||
import { SettingsProvider } from './contexts/SettingsContext';
|
import { SettingsProvider } from './contexts/SettingsContext';
|
||||||
|
import { Toaster } from 'react-hot-toast';
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* App Content Component
|
* App Content Component
|
||||||
@@ -28,6 +29,7 @@ function App() {
|
|||||||
<GuideProvider>
|
<GuideProvider>
|
||||||
<SettingsProvider>
|
<SettingsProvider>
|
||||||
<AppContent />
|
<AppContent />
|
||||||
|
<Toaster position="top-center" reverseOrder={false} />
|
||||||
</SettingsProvider>
|
</SettingsProvider>
|
||||||
</GuideProvider>
|
</GuideProvider>
|
||||||
</NotificationProvider>
|
</NotificationProvider>
|
||||||
|
|||||||
@@ -4,6 +4,7 @@
|
|||||||
*/
|
*/
|
||||||
|
|
||||||
import React, { useState } from 'react';
|
import React, { useState } from 'react';
|
||||||
|
import toast from 'react-hot-toast';
|
||||||
import type { PiggyBank, Account } from '../../../types';
|
import type { PiggyBank, Account } from '../../../types';
|
||||||
import { formatCurrency } from '../../../utils/format';
|
import { formatCurrency } from '../../../utils/format';
|
||||||
import { Icon } from '@iconify/react';
|
import { Icon } from '@iconify/react';
|
||||||
@@ -28,7 +29,6 @@ export const PiggyBankTransactionModal: React.FC<PiggyBankTransactionModalProps>
|
|||||||
}) => {
|
}) => {
|
||||||
const [amount, setAmount] = useState<string>('');
|
const [amount, setAmount] = useState<string>('');
|
||||||
const [note, setNote] = useState<string>('');
|
const [note, setNote] = useState<string>('');
|
||||||
const [error, setError] = useState<string>('');
|
|
||||||
|
|
||||||
const isDeposit = type === 'deposit';
|
const isDeposit = type === 'deposit';
|
||||||
const title = isDeposit ? '存入' : '取出';
|
const title = isDeposit ? '存入' : '取出';
|
||||||
@@ -38,7 +38,6 @@ export const PiggyBankTransactionModal: React.FC<PiggyBankTransactionModalProps>
|
|||||||
const handleAmountChange = (e: React.ChangeEvent<HTMLInputElement>) => {
|
const handleAmountChange = (e: React.ChangeEvent<HTMLInputElement>) => {
|
||||||
const value = e.target.value;
|
const value = e.target.value;
|
||||||
setAmount(value);
|
setAmount(value);
|
||||||
setError('');
|
|
||||||
};
|
};
|
||||||
|
|
||||||
const handleSubmit = (e: React.FormEvent) => {
|
const handleSubmit = (e: React.FormEvent) => {
|
||||||
@@ -48,17 +47,17 @@ export const PiggyBankTransactionModal: React.FC<PiggyBankTransactionModalProps>
|
|||||||
|
|
||||||
// Validation
|
// Validation
|
||||||
if (!amount || isNaN(numAmount) || numAmount <= 0) {
|
if (!amount || isNaN(numAmount) || numAmount <= 0) {
|
||||||
setError('请输入有效的金额');
|
toast.error('请输入有效的金额');
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!isDeposit && numAmount > piggyBank.currentAmount) {
|
if (!isDeposit && numAmount > piggyBank.currentAmount) {
|
||||||
setError('取出金额不能超过当前余额');
|
toast.error('取出金额不能超过当前余额');
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (isDeposit && linkedAccount && numAmount > linkedAccount.balance) {
|
if (isDeposit && linkedAccount && numAmount > linkedAccount.balance) {
|
||||||
setError(`账户余额不足 (可用: ${formatCurrency(linkedAccount.balance, linkedAccount.currency)})`);
|
toast.error(`账户余额不足 (可用: ${formatCurrency(linkedAccount.balance, linkedAccount.currency)})`);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -67,7 +66,6 @@ export const PiggyBankTransactionModal: React.FC<PiggyBankTransactionModalProps>
|
|||||||
|
|
||||||
const handleQuickAmount = (value: number) => {
|
const handleQuickAmount = (value: number) => {
|
||||||
setAmount(value.toString());
|
setAmount(value.toString());
|
||||||
setError('');
|
|
||||||
};
|
};
|
||||||
|
|
||||||
// Quick amount buttons
|
// Quick amount buttons
|
||||||
@@ -163,8 +161,7 @@ export const PiggyBankTransactionModal: React.FC<PiggyBankTransactionModalProps>
|
|||||||
id="amount"
|
id="amount"
|
||||||
value={amount}
|
value={amount}
|
||||||
onChange={handleAmountChange}
|
onChange={handleAmountChange}
|
||||||
className={`piggy-bank-transaction-modal__input ${error ? 'piggy-bank-transaction-modal__input--error' : ''
|
className="piggy-bank-transaction-modal__input"
|
||||||
}`}
|
|
||||||
placeholder="0.00"
|
placeholder="0.00"
|
||||||
step="0.01"
|
step="0.01"
|
||||||
min="0"
|
min="0"
|
||||||
@@ -172,7 +169,7 @@ export const PiggyBankTransactionModal: React.FC<PiggyBankTransactionModalProps>
|
|||||||
disabled={isLoading}
|
disabled={isLoading}
|
||||||
autoFocus
|
autoFocus
|
||||||
/>
|
/>
|
||||||
{error && <span className="piggy-bank-transaction-modal__error">{error}</span>}
|
{/* Error handled by toast */}
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
{linkedAccount ? (
|
{linkedAccount ? (
|
||||||
|
|||||||
Reference in New Issue
Block a user