diff --git a/src/components/home/HealthScoreCard/HealthScoreCard.css b/src/components/home/HealthScoreCard/HealthScoreCard.css index 18b7c48..fc63d26 100644 --- a/src/components/home/HealthScoreCard/HealthScoreCard.css +++ b/src/components/home/HealthScoreCard/HealthScoreCard.css @@ -4,7 +4,16 @@ justify-content: space-between; height: 100%; padding: 1.5rem !important; - /* Override standard bento padding for tighter fit if needed */ + background: linear-gradient(145deg, rgba(255, 255, 255, 0.05) 0%, rgba(255, 255, 255, 0.01) 100%); + border: 1px solid rgba(255, 255, 255, 0.05); + transition: all 0.3s cubic-bezier(0.25, 0.8, 0.25, 1); +} + +.health-score-card:hover { + transform: translateY(-5px); + box-shadow: 0 10px 30px -5px rgba(0, 0, 0, 0.1); + background: linear-gradient(145deg, rgba(255, 255, 255, 0.08) 0%, rgba(255, 255, 255, 0.02) 100%); + border-color: rgba(255, 255, 255, 0.1); } .health-score-header { @@ -18,10 +27,16 @@ font-size: 0.9rem; font-weight: 600; color: var(--text-secondary); + letter-spacing: 0.02em; } .health-trend { color: var(--text-tertiary); + transition: transform 0.3s; +} + +.health-score-card:hover .health-trend { + transform: scale(1.1); } .health-score-content { @@ -35,8 +50,14 @@ .health-ring-container { position: relative; - width: 80px; - height: 80px; + width: 90px; + height: 90px; + filter: drop-shadow(0 0 10px rgba(0, 0, 0, 0.05)); + transition: transform 0.3s cubic-bezier(0.34, 1.56, 0.64, 1); +} + +.health-score-card:hover .health-ring-container { + transform: scale(1.05); } .health-ring-svg { @@ -45,6 +66,7 @@ .health-ring-progress { transition: stroke-dashoffset 1.5s cubic-bezier(0.34, 1.56, 0.64, 1); + filter: drop-shadow(0 0 2px rgba(0, 0, 0, 0.1)); } .health-score-value { @@ -59,18 +81,22 @@ } .score-number { - font-size: 1.75rem; + font-size: 2rem; font-weight: 800; color: var(--text-primary); font-family: 'Outfit', sans-serif; letter-spacing: -0.05em; + background: linear-gradient(180deg, var(--text-primary) 0%, var(--text-secondary) 100%); + -webkit-background-clip: text; + background-clip: text; + -webkit-text-fill-color: transparent; } .health-score-status { text-align: center; display: flex; flex-direction: column; - gap: 0; + gap: 2px; } .status-text { @@ -83,4 +109,5 @@ font-size: 0.75rem; color: var(--text-tertiary); font-weight: 500; + opacity: 0.8; } \ No newline at end of file diff --git a/src/components/home/HealthScoreCard/HealthScoreCard.tsx b/src/components/home/HealthScoreCard/HealthScoreCard.tsx index a1bb087..e507bf7 100644 --- a/src/components/home/HealthScoreCard/HealthScoreCard.tsx +++ b/src/components/home/HealthScoreCard/HealthScoreCard.tsx @@ -36,7 +36,7 @@ export const HealthScoreCard: React.FC = ({ score, label, = ({ ) : ( // --- Score View --- <> -
+ +
- - + + + + + + + + + {/* Background Ring */} - {/* Progress Ring */} + {/* Progress Ring with Glow */}
@@ -203,9 +212,13 @@ export const HealthScoreModal: React.FC = ({
-
- - {level.title} · {level.desc} +
+ + {level.title} · {level.desc}
diff --git a/src/components/settings/ChangePassword/ChangePassword.css b/src/components/settings/ChangePassword/ChangePassword.css new file mode 100644 index 0000000..9de00ae --- /dev/null +++ b/src/components/settings/ChangePassword/ChangePassword.css @@ -0,0 +1,163 @@ +/* ChangePassword.css - Premium Glassmorphism */ + +.change-password-settings { + width: 100%; +} + +.password-section { + background: var(--glass-panel-bg); + backdrop-filter: blur(12px); + border: 1px solid var(--glass-border); + border-radius: var(--radius-xl); + padding: var(--spacing-xl); + margin-bottom: var(--spacing-lg); + box-shadow: var(--shadow-sm); + transition: all 0.3s ease; +} + +.password-section:hover { + box-shadow: var(--shadow-md); + border-color: rgba(99, 102, 241, 0.2); + /* Indigo hint */ +} + +.password-section h3 { + font-size: var(--font-lg); + font-weight: 700; + margin: 0 0 var(--spacing-lg) 0; + color: var(--color-text); + display: flex; + align-items: center; + gap: 0.5rem; +} + +/* Forms */ +.password-form { + display: flex; + flex-direction: column; + gap: var(--spacing-lg); +} + +.form-group { + display: flex; + flex-direction: column; + gap: var(--spacing-xs); +} + +.form-group label { + font-weight: 600; + color: var(--color-text); + font-size: var(--font-sm); +} + +.form-group input { + padding: var(--spacing-md); + border: 1px solid var(--glass-border); + border-radius: var(--radius-lg); + background: var(--glass-bg); + color: var(--color-text); + font-size: var(--font-base); + transition: all 0.2s; +} + +.form-group input:focus { + outline: none; + border-color: var(--color-primary); + box-shadow: 0 0 0 3px var(--color-primary-lighter); + background: #fff; +} + +/* Buttons */ +.password-btn { + padding: var(--spacing-md) var(--spacing-xl); + font-weight: 600; + border-radius: var(--radius-full); + cursor: pointer; + border: none; + transition: all 0.2s; + font-size: var(--font-sm); + display: inline-flex; + align-items: center; + justify-content: center; + gap: 0.5rem; + background: var(--gradient-primary); + color: white; + box-shadow: 0 4px 6px rgba(99, 102, 241, 0.2); + align-self: flex-start; + min-width: 120px; +} + +.password-btn:hover:not(:disabled) { + transform: translateY(-2px); + box-shadow: 0 6px 12px rgba(99, 102, 241, 0.3); +} + +.password-btn:disabled { + opacity: 0.6; + cursor: not-allowed; + transform: none; +} + +/* Messages */ +.message { + padding: var(--spacing-md); + border-radius: var(--radius-lg); + margin-bottom: var(--spacing-lg); + font-size: var(--font-sm); + font-weight: 500; + animation: slideUpFade 0.3s ease; + display: flex; + align-items: center; + gap: 0.5rem; +} + +.message-success { + background: rgba(5, 150, 105, 0.1); + color: var(--color-success); + border: 1px solid rgba(5, 150, 105, 0.2); +} + +.message-error { + background: rgba(220, 38, 38, 0.1); + color: var(--color-error); + border: 1px solid rgba(220, 38, 38, 0.2); +} + +/* Info */ +.password-info { + background: rgba(255, 255, 255, 0.4); + border: 1px dashed var(--glass-border); + border-radius: var(--radius-xl); + padding: var(--spacing-xl); +} + +.password-info h4 { + font-size: var(--font-base); + margin-bottom: var(--spacing-md); + color: var(--color-text); + font-weight: 600; +} + +.password-info ul { + margin: 0; + padding-left: 1.25rem; + color: var(--color-text-secondary); + font-size: var(--font-sm); + line-height: 1.6; +} + +.password-info li { + margin-bottom: var(--spacing-xs); +} + +@keyframes slideUpFade { + from { + opacity: 0; + transform: translateY(10px); + } + + to { + opacity: 1; + transform: translateY(0); + } +} \ No newline at end of file diff --git a/src/components/settings/ChangePassword/ChangePassword.tsx b/src/components/settings/ChangePassword/ChangePassword.tsx new file mode 100644 index 0000000..0c8cabc --- /dev/null +++ b/src/components/settings/ChangePassword/ChangePassword.tsx @@ -0,0 +1,139 @@ +import { useState } from 'react'; +import { Icon } from '@iconify/react'; +import { updatePassword } from '../../../services/authService'; +import './ChangePassword.css'; + +/** + * ChangePassword Component + * Allows users to change their login password + */ +function ChangePassword() { + const [oldPassword, setOldPassword] = useState(''); + const [newPassword, setNewPassword] = useState(''); + const [confirmPassword, setConfirmPassword] = useState(''); + const [loading, setLoading] = useState(false); + const [message, setMessage] = useState<{ type: 'success' | 'error'; text: string } | null>(null); + + const handleSubmit = async (e: React.FormEvent) => { + e.preventDefault(); + + // Validations + if (newPassword !== confirmPassword) { + setMessage({ type: 'error', text: '两次输入的新密码不一致' }); + return; + } + + if (newPassword.length < 8) { + setMessage({ type: 'error', text: '新密码长度至少为8位' }); + return; + } + + if (oldPassword === newPassword) { + setMessage({ type: 'error', text: '新密码不能与旧密码相同' }); + return; + } + + setLoading(true); + setMessage(null); + + try { + await updatePassword(oldPassword, newPassword); + setMessage({ type: 'success', text: '登录密码修改成功' }); + // Clear forms + setOldPassword(''); + setNewPassword(''); + setConfirmPassword(''); + } catch (error) { + setMessage({ + type: 'error', + text: error instanceof Error ? error.message : '修改密码失败,请检查旧密码是否正确' + }); + } finally { + setLoading(false); + } + }; + + return ( +
+
+

+ + 登录密码 +

+ + {message && ( +
+ + {message.text} +
+ )} + +
+
+ + setOldPassword(e.target.value)} + placeholder="输入当前使用的登录密码" + disabled={loading} + required + /> +
+ +
+ + setNewPassword(e.target.value)} + placeholder="输入新密码(至少8位)" + disabled={loading} + required + minLength={8} + /> +
+ +
+ + setConfirmPassword(e.target.value)} + placeholder="再次输入新密码" + disabled={loading} + required + minLength={8} + /> +
+ + +
+
+ +
+

安全建议

+
    +
  • 使用包含字母、数字和符号的强密码
  • +
  • 不要使用与其他网站相同的密码
  • +
  • 定期更换密码可以提高账户安全性
  • +
+
+
+ ); +} + +export default ChangePassword; diff --git a/src/components/settings/ChangePassword/index.ts b/src/components/settings/ChangePassword/index.ts new file mode 100644 index 0000000..dd4bab7 --- /dev/null +++ b/src/components/settings/ChangePassword/index.ts @@ -0,0 +1 @@ +export { default } from './ChangePassword'; diff --git a/src/pages/Settings/Settings.tsx b/src/pages/Settings/Settings.tsx index 5e9ef5d..430f646 100644 --- a/src/pages/Settings/Settings.tsx +++ b/src/pages/Settings/Settings.tsx @@ -2,6 +2,7 @@ import { useState, useEffect } from 'react'; import { useTheme } from '../../hooks/useTheme'; import BackupManager from '../../components/settings/BackupManager'; import AppLockSettings from '../../components/settings/AppLockSettings'; +import ChangePassword from '../../components/settings/ChangePassword'; import { CapsuleSelector } from '../../components/common/CapsuleSelector/CapsuleSelector'; import { CustomSelect } from '../../components/common/CustomSelect/CustomSelect'; import { getSupportedCurrencies } from '../../services/exchangeRateService'; @@ -445,6 +446,8 @@ function Settings() { {activeTab === 'security' && (
+ +
)}