add account web
This commit is contained in:
@@ -4,7 +4,7 @@ import { ClassesTab } from './components/ClassesTab';
|
||||
import { StudentsTab } from './components/StudentsTab';
|
||||
import { LearningTab } from './components/LearningTab';
|
||||
import { NetworkTab } from './components/NetworkTab';
|
||||
import { AccountsTab } from './components/AccountsTab';
|
||||
import { EmailDomainsTab, MyAccountTab } from './components/AccountsTab';
|
||||
import { ChatWidget } from './components/ChatWidget';
|
||||
import { StaffChatSocket } from './hooks/useStaffChatSocket';
|
||||
import { ClassWorkspace } from './components/ClassWorkspace';
|
||||
@@ -50,10 +50,10 @@ const IconNetwork = () => (
|
||||
</svg>
|
||||
);
|
||||
|
||||
const IconAccounts = () => (
|
||||
const IconEmail = () => (
|
||||
<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="2" strokeLinecap="round" strokeLinejoin="round">
|
||||
<path d="M20 21v-2a4 4 0 0 0-4-4H8a4 4 0 0 0-4 4v2" />
|
||||
<circle cx="12" cy="7" r="4" />
|
||||
<rect x="2" y="4" width="20" height="16" rx="2" />
|
||||
<path d="m22 7-8.97 5.7a1.94 1.94 0 0 1-2.06 0L2 7" />
|
||||
</svg>
|
||||
);
|
||||
|
||||
@@ -144,21 +144,29 @@ function App() {
|
||||
<div className="nav-header">Hệ thống</div>
|
||||
<li className="nav-item">
|
||||
<button
|
||||
className={`nav-btn ${route.tab === 'accounts' && !route.classId ? 'active' : ''}`}
|
||||
onClick={() => navigate('accounts')}
|
||||
className={`nav-btn ${route.tab === 'email-domains' && !route.classId ? 'active' : ''}`}
|
||||
onClick={() => navigate('email-domains')}
|
||||
>
|
||||
<span className="nav-icon"><IconAccounts /></span>
|
||||
Tài khoản
|
||||
<span className="nav-icon"><IconEmail /></span>
|
||||
Đuôi email
|
||||
</button>
|
||||
</li>
|
||||
</ul>
|
||||
</nav>
|
||||
|
||||
<div className="sidebar-footer">
|
||||
<div style={{ fontSize: '0.75rem', color: 'var(--text-muted)', fontWeight: 500 }}>
|
||||
{staff?.email}
|
||||
</div>
|
||||
<button type="button" className="link-btn" onClick={logout} style={{ marginTop: '0.35rem' }}>
|
||||
<button
|
||||
type="button"
|
||||
className={`sidebar-user-btn ${route.tab === 'profile' && !route.classId ? 'active' : ''}`}
|
||||
onClick={() => navigate('profile')}
|
||||
title="Tài khoản của tôi"
|
||||
>
|
||||
<span className="sidebar-user-name">
|
||||
{staff?.fullName?.trim() || staff?.email?.split('@')[0] || 'Tài khoản'}
|
||||
</span>
|
||||
<span className="sidebar-user-email">{staff?.email}</span>
|
||||
</button>
|
||||
<button type="button" className="link-btn" onClick={logout}>
|
||||
Đăng xuất
|
||||
</button>
|
||||
</div>
|
||||
@@ -182,7 +190,8 @@ function App() {
|
||||
{route.tab === 'students' && <StudentsTab />}
|
||||
{route.tab === 'learning' && <LearningTab />}
|
||||
{route.tab === 'network' && <NetworkTab />}
|
||||
{route.tab === 'accounts' && <AccountsTab />}
|
||||
{route.tab === 'email-domains' && <EmailDomainsTab />}
|
||||
{route.tab === 'profile' && <MyAccountTab />}
|
||||
</>
|
||||
)}
|
||||
</div>
|
||||
|
||||
@@ -64,14 +64,9 @@ export function ChangePasswordPage({ forced }: { forced?: boolean }) {
|
||||
);
|
||||
}
|
||||
|
||||
export function AccountsTab() {
|
||||
const { staff, changePassword } = useAuth();
|
||||
export function EmailDomainsTab() {
|
||||
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();
|
||||
@@ -95,32 +90,13 @@ export function AccountsTab() {
|
||||
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 lý đuôi email được phép và đổi mật khẩu.</p>
|
||||
<h1 className="page-title">Đuôi email</h1>
|
||||
<p className="page-desc">Cấu hình đuôi email được phép đăng ký và đăng nhập.</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 }}>
|
||||
@@ -145,6 +121,45 @@ export function AccountsTab() {
|
||||
))}
|
||||
</ul>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
export function MyAccountTab() {
|
||||
const { staff, changePassword } = useAuth();
|
||||
const [oldPw, setOldPw] = useState('');
|
||||
const [newPw, setNewPw] = useState('');
|
||||
const [msg, setMsg] = useState('');
|
||||
const [err, setErr] = useState('');
|
||||
|
||||
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');
|
||||
}
|
||||
};
|
||||
|
||||
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 và bảo mật cá 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" style={{ padding: '1.25rem' }}>
|
||||
<h2 className="section-title">Đổi mật khẩu</h2>
|
||||
|
||||
@@ -237,6 +237,51 @@ body {
|
||||
flex-shrink: 0;
|
||||
}
|
||||
|
||||
.sidebar-user-btn {
|
||||
width: 100%;
|
||||
text-align: left;
|
||||
border: 1px solid transparent;
|
||||
border-radius: var(--radius-sm);
|
||||
background: none;
|
||||
padding: 0.5rem 0.6rem;
|
||||
cursor: pointer;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: 0.15rem;
|
||||
transition: background 0.15s, border-color 0.15s;
|
||||
}
|
||||
|
||||
.sidebar-user-btn:hover {
|
||||
background: var(--bg-subtle);
|
||||
}
|
||||
|
||||
.sidebar-user-btn.active {
|
||||
background: var(--accent-light);
|
||||
border-color: var(--accent);
|
||||
}
|
||||
|
||||
.sidebar-user-btn.active .sidebar-user-name,
|
||||
.sidebar-user-btn.active .sidebar-user-email {
|
||||
color: var(--accent);
|
||||
}
|
||||
|
||||
.sidebar-user-name {
|
||||
font-size: 0.82rem;
|
||||
font-weight: 600;
|
||||
color: var(--text-primary);
|
||||
white-space: nowrap;
|
||||
overflow: hidden;
|
||||
text-overflow: ellipsis;
|
||||
}
|
||||
|
||||
.sidebar-user-email {
|
||||
font-size: 0.72rem;
|
||||
color: var(--text-muted);
|
||||
white-space: nowrap;
|
||||
overflow: hidden;
|
||||
text-overflow: ellipsis;
|
||||
}
|
||||
|
||||
.token-badge {
|
||||
background-color: var(--bg-subtle);
|
||||
border: 1px solid var(--border-color);
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
export type TabId = 'dashboard' | 'classes' | 'students' | 'learning' | 'network' | 'accounts';
|
||||
export type TabId = 'dashboard' | 'classes' | 'students' | 'learning' | 'network' | 'email-domains' | 'profile';
|
||||
|
||||
export interface NavEntry {
|
||||
id: string;
|
||||
@@ -15,20 +15,22 @@ export const TAB_LABELS: Record<TabId, string> = {
|
||||
students: 'Sinh viên',
|
||||
learning: 'Giám sát & Lịch học',
|
||||
network: 'Quản lý mạng',
|
||||
accounts: 'Tài khoản',
|
||||
'email-domains': 'Đuôi email',
|
||||
profile: 'Tài khoản của tôi',
|
||||
};
|
||||
|
||||
const HISTORY_KEY = 'sc_nav_history';
|
||||
const MAX_HISTORY = 10;
|
||||
|
||||
function isTabId(value: string | null): value is TabId {
|
||||
return value === 'dashboard' || value === 'classes' || value === 'students' || value === 'learning' || value === 'network' || value === 'accounts';
|
||||
return value === 'dashboard' || value === 'classes' || value === 'students' || value === 'learning' || value === 'network' || value === 'email-domains' || value === 'profile';
|
||||
}
|
||||
|
||||
export function parseRoute(): { tab: TabId; classId: number | null } {
|
||||
const params = new URLSearchParams(window.location.search);
|
||||
const tabParam = params.get('tab');
|
||||
const tab: TabId = isTabId(tabParam) ? tabParam : 'dashboard';
|
||||
let tab: TabId = isTabId(tabParam) ? tabParam : 'dashboard';
|
||||
if (tabParam === 'accounts') tab = 'email-domains';
|
||||
const classIdRaw = params.get('classId');
|
||||
const classId = classIdRaw ? Number(classIdRaw) : null;
|
||||
return {
|
||||
|
||||
Reference in New Issue
Block a user