Files
rikkei_simple_care/management/src/components/AccountsTab.tsx
PhuocNTB d1411ad1df
All checks were successful
Deploy on Master Change / deploy (push) Successful in 39s
config token
2026-07-01 08:04:03 +07:00

261 lines
10 KiB
TypeScript

import { useState, useEffect } from 'react';
import { useAuth } from '../auth/AuthContext';
import { useGitHubConnection } from '../hooks/useGitHubConnection';
import { GitHubReposPanel } from './GitHubReposPanel';
import { OrganizationSection } from './OrganizationSection';
import { apiQldt } 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 EmailDomainsTab() {
return (
<div className="tab-page">
<header className="page-header">
<h1 className="page-title">Quản tổ chức</h1>
</header>
<OrganizationSection />
</div>
);
}
export function MyAccountTab() {
const { staff, changePassword } = useAuth();
const gh = useGitHubConnection();
const [oldPw, setOldPw] = useState('');
const [newPw, setNewPw] = useState('');
const [pwMsg, setPwMsg] = useState('');
const [pwErr, setPwErr] = useState('');
// Cấu hình QLĐT Token (Chỉ cho phuocntb@rikkeiacademy.com)
const isQldtAdmin = staff?.email === 'phuocntb@rikkeiacademy.com';
const [qldtToken, setQldtToken] = useState('');
const [qldtMsg, setQldtMsg] = useState('');
const [qldtErr, setQldtErr] = useState('');
const [loadingQldt, setLoadingQldt] = useState(false);
useEffect(() => {
if (!isQldtAdmin) return;
setLoadingQldt(true);
apiQldt.getToken()
.then((res) => {
setQldtToken(res.token || '');
})
.catch((err) => {
setQldtErr(err.message || 'Lỗi khi tải token QLĐT');
})
.finally(() => {
setLoadingQldt(false);
});
}, [isQldtAdmin]);
const saveQldtToken = async (e: React.FormEvent) => {
e.preventDefault();
setQldtMsg('');
setQldtErr('');
try {
await apiQldt.saveToken(qldtToken);
setQldtMsg('Đã lưu cấu hình QLĐT token');
} catch (err: any) {
setQldtErr(err.message || 'Lỗi khi lưu token QLĐT');
}
};
const submitPw = async (e: React.FormEvent) => {
e.preventDefault();
setPwErr('');
setPwMsg('');
try {
await changePassword(oldPw, newPw);
setPwMsg('Đã đổi mật khẩu');
setOldPw('');
setNewPw('');
} catch (e: any) {
setPwErr(e?.message || 'Lỗi');
}
};
const displayName = staff?.fullName?.trim() || staff?.email?.split('@')[0] || 'Tài khoản';
return (
<div className="page-stack">
<header className="page-header">
<h1 className="page-title">Tài khoản của tôi</h1>
<p className="page-desc">Thông tin đăng nhập, GitHub bảo mật nhân.</p>
</header>
<div className="card" style={{ padding: '1.25rem' }}>
<h2 className="section-title">Thông tin</h2>
<p style={{ margin: '0 0 0.25rem', fontWeight: 600 }}>{displayName}</p>
<p style={{ margin: 0, color: 'var(--text-muted)' }}>{staff?.email}</p>
</div>
<div className="card account-github-card" style={{ padding: '1.25rem' }}>
<h2 className="section-title">GitHub</h2>
<p className="page-desc" style={{ marginTop: 0 }}>
Kết nối tài khoản GitHub của bạn đ đy bài nộp phòng thi lên repo riêng. Mỗi giáo viên dùng GitHub của mình không dùng chung token server.
</p>
<div className="exam-git-oauth-row">
{gh.connected ? (
<>
<span className="exam-git-connected">
Đã kết nối: <strong>@{gh.githubLogin}</strong>
</span>
<button type="button" className="btn btn-secondary btn-sm" onClick={gh.disconnect}>
Ngắt kết nối
</button>
{!gh.canDeleteRepos && (
<button type="button" className="btn btn-primary btn-sm" disabled={gh.connecting} onClick={gh.connect}>
{gh.connecting ? 'Đang mở GitHub...' : 'Cấp quyền xóa repo'}
</button>
)}
</>
) : (
<button
type="button"
className="btn btn-primary btn-sm"
disabled={gh.connecting}
onClick={gh.connect}
>
{gh.connecting ? 'Đang mở GitHub...' : 'Kết nối GitHub'}
</button>
)}
</div>
{gh.connected && !gh.canDeleteRepos && (
<div className="login-error" style={{ marginTop: '0.75rem' }}>
Token hiện tại chưa quyền <strong>delete_repo</strong> không xóa đưc repo. Bấm <strong>Cấp quyền xóa repo</strong> (hoặc ngắt kết nối rồi kết nối lại).
</div>
)}
{gh.error && <div className="login-error">{gh.error}</div>}
{gh.message && <div className="login-success">{gh.message}</div>}
{!gh.connected && (
<p className="exam-git-hint">
Sau khi kết nối, vào <strong>Phòng thi</strong> tab <strong>Bài nộp</strong> đy Git (gộp hoặc từng sinh viên).
</p>
)}
{gh.connected && (
<>
<h3 className="section-subtitle" style={{ marginTop: '1rem' }}>Repo đã tạo</h3>
<GitHubReposPanel connected={gh.connected} canDeleteRepos={gh.canDeleteRepos} />
</>
)}
</div>
{isQldtAdmin && (
<div className="card" style={{ padding: '1.25rem' }}>
<h2 className="section-title">Cấu hình QLĐT Token</h2>
<p className="page-desc" style={{ marginTop: 0 }}>
Cấu hình token đng bộ dữ liệu lớp học, môn học điểm danh từ Cổng đào tạo (QLĐT).
</p>
{loadingQldt ? (
<p style={{ color: 'var(--text-muted)' }}>Đang tải cấu hình...</p>
) : (
<form onSubmit={saveQldtToken} className="login-form">
<label className="login-field">
<span>Token QLĐT</span>
<textarea
style={{
width: '100%',
minHeight: '100px',
fontFamily: 'monospace',
fontSize: '0.875rem',
padding: '0.5rem',
borderRadius: '8px',
border: '1px solid var(--border-color, #ccc)',
backgroundColor: 'var(--bg-input, #fff)',
color: 'var(--text, #333)',
resize: 'vertical'
}}
value={qldtToken}
onChange={(e) => setQldtToken(e.target.value)}
placeholder="Nhập JWT Token từ QLĐT..."
required
/>
</label>
{qldtErr && <div className="login-error">{qldtErr}</div>}
{qldtMsg && <div className="login-success">{qldtMsg}</div>}
<button type="submit" className="btn btn-primary" style={{ marginTop: '0.5rem' }}>Lưu cấu hình</button>
</form>
)}
</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>
{pwErr && <div className="login-error">{pwErr}</div>}
{pwMsg && <div className="login-success">{pwMsg}</div>}
<button type="submit" className="btn btn-primary">Lưu</button>
</form>
</div>
</div>
);
}