This commit is contained in:
2026-06-30 10:39:29 +07:00
parent aaafa6ada9
commit d9c8e3930b
28 changed files with 3040 additions and 167 deletions

View File

@@ -0,0 +1,167 @@
import { useEffect, useState } from 'react';
import { useAuth } from '../auth/AuthContext';
import { apiAdmin, type EmailDomainItem } from '../api';
export function ChangePasswordPage({ forced }: { forced?: boolean }) {
const { changePassword, logout } = useAuth();
const [oldPassword, setOldPassword] = useState('');
const [newPassword, setNewPassword] = useState('');
const [confirm, setConfirm] = useState('');
const [error, setError] = useState('');
const [busy, setBusy] = useState(false);
const submit = async (e: React.FormEvent) => {
e.preventDefault();
setError('');
if (newPassword.length < 8) {
setError('Mật khẩu mới tối thiểu 8 ký tự');
return;
}
if (newPassword !== confirm) {
setError('Xác nhận mật khẩu không khớp');
return;
}
setBusy(true);
try {
await changePassword(oldPassword, newPassword);
} catch (err: any) {
setError(err?.message || 'Đổi mật khẩu thất bại');
} finally {
setBusy(false);
}
};
return (
<div className="login-screen">
<div className="login-card">
<h1 className="login-heading">{forced ? 'Đổi mật khẩu bắt buộc' : 'Đổi mật khẩu'}</h1>
{forced && <p className="login-hint">Lần đăng nhập đu tiên vui lòng đt mật khẩu mới trước khi tiếp tục.</p>}
<form onSubmit={submit} className="login-form">
<label className="login-field">
<span>Mật khẩu hiện tại</span>
<input type="password" value={oldPassword} onChange={(e) => setOldPassword(e.target.value)} required />
</label>
<label className="login-field">
<span>Mật khẩu mới</span>
<input type="password" value={newPassword} onChange={(e) => setNewPassword(e.target.value)} required minLength={8} />
</label>
<label className="login-field">
<span>Xác nhận mật khẩu mới</span>
<input type="password" value={confirm} onChange={(e) => setConfirm(e.target.value)} required minLength={8} />
</label>
{error && <div className="login-error">{error}</div>}
<button type="submit" className="btn btn-primary login-submit" disabled={busy}>
{busy ? 'Đang lưu...' : 'Lưu mật khẩu'}
</button>
{!forced && (
<button type="button" className="link-btn" style={{ marginTop: '0.5rem' }} onClick={logout}>
Đăng xuất
</button>
)}
</form>
</div>
</div>
);
}
export function AccountsTab() {
const { staff, changePassword } = useAuth();
const [domains, setDomains] = useState<EmailDomainItem[]>([]);
const [newDomain, setNewDomain] = useState('');
const [oldPw, setOldPw] = useState('');
const [newPw, setNewPw] = useState('');
const [msg, setMsg] = useState('');
const [err, setErr] = useState('');
const load = async () => {
const res = await apiAdmin.listEmailDomains();
setDomains(res.data);
};
useEffect(() => {
load().catch(console.error);
}, []);
const addDomain = async () => {
if (!newDomain.trim()) return;
await apiAdmin.addEmailDomain(newDomain.trim());
setNewDomain('');
await load();
};
const removeDomain = async (id: number) => {
if (!confirm('Xóa đuôi email này?')) return;
await apiAdmin.deleteEmailDomain(id);
await load();
};
const submitPw = async (e: React.FormEvent) => {
e.preventDefault();
setErr('');
setMsg('');
try {
await changePassword(oldPw, newPw);
setMsg('Đã đổi mật khẩu');
setOldPw('');
setNewPw('');
} catch (e: any) {
setErr(e?.message || 'Lỗi');
}
};
return (
<div className="page-stack">
<header className="page-header">
<h1 className="page-title">Tài khoản & bảo mật</h1>
<p className="page-desc">Quản đuôi email đưc phép đi mật khẩu.</p>
</header>
<div className="card" style={{ padding: '1.25rem' }}>
<h2 className="section-title">Tài khoản hiện tại</h2>
<p style={{ margin: 0, color: 'var(--text-muted)' }}>{staff?.email}</p>
</div>
<div className="card" style={{ padding: '1.25rem' }}>
<h2 className="section-title">Đuôi email đưc phép</h2>
<p className="page-desc" style={{ marginTop: 0 }}>
Nếu chưa bản ghi nào, hệ thống chấp nhận mọi đuôi email. Thêm đuôi đ giới hạn truy cập.
</p>
<div style={{ display: 'flex', gap: '0.5rem', marginBottom: '1rem' }}>
<input
placeholder="vd: rikkeiacademy.com"
value={newDomain}
onChange={(e) => setNewDomain(e.target.value)}
style={{ flex: 1 }}
/>
<button type="button" className="btn btn-primary" onClick={addDomain}>Thêm</button>
</div>
<ul className="domain-list">
{domains.length === 0 && <li className="domain-empty">Chưa cấu hình chấp nhận tất cả đuôi email</li>}
{domains.map((d) => (
<li key={d.id} className="domain-item">
<span>@{d.domain}</span>
<button type="button" className="btn btn-ghost btn-sm" onClick={() => removeDomain(d.id)}>Xóa</button>
</li>
))}
</ul>
</div>
<div className="card" style={{ padding: '1.25rem' }}>
<h2 className="section-title">Đi mật khẩu</h2>
<form onSubmit={submitPw} className="login-form" style={{ maxWidth: 400 }}>
<label className="login-field">
<span>Mật khẩu hiện tại</span>
<input type="password" value={oldPw} onChange={(e) => setOldPw(e.target.value)} required />
</label>
<label className="login-field">
<span>Mật khẩu mới</span>
<input type="password" value={newPw} onChange={(e) => setNewPw(e.target.value)} required minLength={8} />
</label>
{err && <div className="login-error">{err}</div>}
{msg && <div className="login-success">{msg}</div>}
<button type="submit" className="btn btn-primary">Lưu</button>
</form>
</div>
</div>
);
}