Your Name f02b3f4098 feat: 前端对接 Profile/Messages/Settings 页面 API
- 3 消息页 + 2 资料编辑页 + 3 设置页 + 2 资料展示页
- api.ts 新增 Profile/Messages/ChangePassword 等类型和方法
- SSEContext 事件映射修复 + 断线重连修复
- 剩余页面加 USE_MOCK 双模式,52/55 页面已完成

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-02-10 10:27:59 +08:00

260 lines
10 KiB
TypeScript
Raw Permalink Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

'use client'
import { useState } from 'react'
import { useRouter } from 'next/navigation'
import { useToast } from '@/components/ui/Toast'
import { Card, CardContent, CardHeader, CardTitle } from '@/components/ui/Card'
import { Button } from '@/components/ui/Button'
import { Input } from '@/components/ui/Input'
import {
ArrowLeft,
Lock,
Shield,
Smartphone,
Mail,
Key,
Eye,
EyeOff,
CheckCircle,
AlertTriangle
} from 'lucide-react'
import { USE_MOCK } from '@/contexts/AuthContext'
import { api } from '@/lib/api'
export default function AgencyAccountSettingsPage() {
const router = useRouter()
const toast = useToast()
const [showOldPassword, setShowOldPassword] = useState(false)
const [showNewPassword, setShowNewPassword] = useState(false)
const [showConfirmPassword, setShowConfirmPassword] = useState(false)
const [passwordForm, setPasswordForm] = useState({
oldPassword: '',
newPassword: '',
confirmPassword: '',
})
const [isSaving, setIsSaving] = useState(false)
// 模拟账号安全状态
const securityStatus = {
phone: { bound: true, value: '138****8888' },
email: { bound: true, value: 'zhang@starmedia.com' },
twoFactor: { enabled: false },
}
const handleChangePassword = async () => {
if (!passwordForm.oldPassword || !passwordForm.newPassword || !passwordForm.confirmPassword) {
toast.error('请填写完整密码信息')
return
}
if (passwordForm.newPassword !== passwordForm.confirmPassword) {
toast.error('两次输入的新密码不一致')
return
}
if (passwordForm.newPassword.length < 8) {
toast.error('新密码长度不能少于8位')
return
}
setIsSaving(true)
if (USE_MOCK) {
await new Promise(resolve => setTimeout(resolve, 1000))
setIsSaving(false)
toast.success('密码修改成功')
setPasswordForm({ oldPassword: '', newPassword: '', confirmPassword: '' })
return
}
try {
await api.changePassword({
old_password: passwordForm.oldPassword,
new_password: passwordForm.newPassword,
})
toast.success('密码修改成功')
setPasswordForm({ oldPassword: '', newPassword: '', confirmPassword: '' })
} catch (err: any) {
toast.error(err.message || '密码修改失败')
} finally {
setIsSaving(false)
}
}
return (
<div className="space-y-6">
{/* 顶部导航 */}
<div className="flex items-center gap-4">
<button
type="button"
onClick={() => router.back()}
className="p-2 rounded-lg hover:bg-bg-elevated transition-colors"
>
<ArrowLeft size={20} className="text-text-secondary" />
</button>
<div>
<h1 className="text-2xl font-bold text-text-primary"></h1>
<p className="text-sm text-text-secondary mt-0.5"></p>
</div>
</div>
<div className="grid grid-cols-1 lg:grid-cols-2 gap-6">
{/* 修改密码 */}
<Card>
<CardHeader>
<CardTitle className="flex items-center gap-2">
<Lock size={18} className="text-accent-indigo" />
</CardTitle>
</CardHeader>
<CardContent className="space-y-4">
<div>
<label className="text-sm text-text-secondary mb-1.5 block"></label>
<div className="relative">
<Input
type={showOldPassword ? 'text' : 'password'}
value={passwordForm.oldPassword}
onChange={(e) => setPasswordForm({ ...passwordForm, oldPassword: e.target.value })}
placeholder="请输入当前密码"
/>
<button
type="button"
onClick={() => setShowOldPassword(!showOldPassword)}
className="absolute right-3 top-1/2 -translate-y-1/2 text-text-tertiary hover:text-text-secondary"
>
{showOldPassword ? <EyeOff size={18} /> : <Eye size={18} />}
</button>
</div>
</div>
<div>
<label className="text-sm text-text-secondary mb-1.5 block"></label>
<div className="relative">
<Input
type={showNewPassword ? 'text' : 'password'}
value={passwordForm.newPassword}
onChange={(e) => setPasswordForm({ ...passwordForm, newPassword: e.target.value })}
placeholder="请输入新密码至少8位"
/>
<button
type="button"
onClick={() => setShowNewPassword(!showNewPassword)}
className="absolute right-3 top-1/2 -translate-y-1/2 text-text-tertiary hover:text-text-secondary"
>
{showNewPassword ? <EyeOff size={18} /> : <Eye size={18} />}
</button>
</div>
</div>
<div>
<label className="text-sm text-text-secondary mb-1.5 block"></label>
<div className="relative">
<Input
type={showConfirmPassword ? 'text' : 'password'}
value={passwordForm.confirmPassword}
onChange={(e) => setPasswordForm({ ...passwordForm, confirmPassword: e.target.value })}
placeholder="请再次输入新密码"
/>
<button
type="button"
onClick={() => setShowConfirmPassword(!showConfirmPassword)}
className="absolute right-3 top-1/2 -translate-y-1/2 text-text-tertiary hover:text-text-secondary"
>
{showConfirmPassword ? <EyeOff size={18} /> : <Eye size={18} />}
</button>
</div>
</div>
<Button
variant="primary"
className="w-full"
onClick={handleChangePassword}
disabled={isSaving}
>
{isSaving ? '保存中...' : '确认修改'}
</Button>
</CardContent>
</Card>
{/* 账号安全 */}
<Card>
<CardHeader>
<CardTitle className="flex items-center gap-2">
<Shield size={18} className="text-accent-green" />
</CardTitle>
</CardHeader>
<CardContent className="space-y-4">
{/* 手机绑定 */}
<div className="flex items-center justify-between p-4 rounded-xl bg-bg-elevated">
<div className="flex items-center gap-3">
<div className="w-10 h-10 rounded-lg bg-accent-indigo/15 flex items-center justify-center">
<Smartphone size={20} className="text-accent-indigo" />
</div>
<div>
<p className="font-medium text-text-primary"></p>
<p className="text-sm text-text-secondary">
{securityStatus.phone.bound ? securityStatus.phone.value : '未绑定'}
</p>
</div>
</div>
<div className="flex items-center gap-2">
{securityStatus.phone.bound ? (
<CheckCircle size={18} className="text-accent-green" />
) : (
<AlertTriangle size={18} className="text-accent-amber" />
)}
<Button variant="secondary" size="sm">
{securityStatus.phone.bound ? '更换' : '绑定'}
</Button>
</div>
</div>
{/* 邮箱绑定 */}
<div className="flex items-center justify-between p-4 rounded-xl bg-bg-elevated">
<div className="flex items-center gap-3">
<div className="w-10 h-10 rounded-lg bg-accent-blue/15 flex items-center justify-center">
<Mail size={20} className="text-accent-blue" />
</div>
<div>
<p className="font-medium text-text-primary"></p>
<p className="text-sm text-text-secondary">
{securityStatus.email.bound ? securityStatus.email.value : '未绑定'}
</p>
</div>
</div>
<div className="flex items-center gap-2">
{securityStatus.email.bound ? (
<CheckCircle size={18} className="text-accent-green" />
) : (
<AlertTriangle size={18} className="text-accent-amber" />
)}
<Button variant="secondary" size="sm">
{securityStatus.email.bound ? '更换' : '绑定'}
</Button>
</div>
</div>
{/* 两步验证 */}
<div className="flex items-center justify-between p-4 rounded-xl bg-bg-elevated">
<div className="flex items-center gap-3">
<div className="w-10 h-10 rounded-lg bg-accent-amber/15 flex items-center justify-center">
<Key size={20} className="text-accent-amber" />
</div>
<div>
<p className="font-medium text-text-primary"></p>
<p className="text-sm text-text-secondary">
{securityStatus.twoFactor.enabled ? '已开启' : '未开启,建议开启以增强安全'}
</p>
</div>
</div>
<div className="flex items-center gap-2">
{securityStatus.twoFactor.enabled ? (
<CheckCircle size={18} className="text-accent-green" />
) : (
<AlertTriangle size={18} className="text-accent-amber" />
)}
<Button variant="secondary" size="sm">
{securityStatus.twoFactor.enabled ? '关闭' : '开启'}
</Button>
</div>
</div>
</CardContent>
</Card>
</div>
</div>
)
}