`AppFooter` — версия инстанса из публичного `/api/health` (не требует авторизации, поэтому работает и на экранах входа; поля `version` может не быть — тогда версия не показывается) плюс копирайт с ТЕКУЩИМ годом и ссылкой на профиль автора. Подключён во все раскладки: `page-shell` (календарь, админка, профиль, мои конференции), `AuthLayout` (вход, регистрация, подтверждение email), `join-shell` и `lobby-shell`. В комнате конференции футера нет — там полноэкранное видео. Прежний футер админки (`.admin-footer`) удалён вместе со стилем — дубля не осталось. В брендовой панели экранов auth строка «© 2026 VidConf · self-hosted» ужата до «self-hosted»: год там был захардкожен, а два копирайта на одном экране — дубль.
66 lines
2.5 KiB
TypeScript
66 lines
2.5 KiB
TypeScript
import { useState } from 'react'
|
||
import { ShellTopbar } from '@/components/layout/ShellTopbar'
|
||
import { AppFooter } from '@/components/layout/AppFooter'
|
||
import { AdminConferencesTab } from '@/components/admin/AdminConferencesTab'
|
||
import { AdminUsersTab } from '@/components/admin/AdminUsersTab'
|
||
import { AdminTeamsTab } from '@/components/admin/AdminTeamsTab'
|
||
import { AdminSettingsTab } from '@/components/admin/AdminSettingsTab'
|
||
import '@/styles/lobby.css'
|
||
import '@/styles/calendar.css'
|
||
import '@/styles/admin.css'
|
||
|
||
type AdminTab = 'conferences' | 'users' | 'teams' | 'settings'
|
||
|
||
const TABS: { value: AdminTab; label: string }[] = [
|
||
{ value: 'conferences', label: 'Конференции' },
|
||
{ value: 'users', label: 'Пользователи' },
|
||
{ value: 'teams', label: 'Команды' },
|
||
{ value: 'settings', label: 'Настройки' },
|
||
]
|
||
|
||
/**
|
||
* Администрирование VidConf: управление конференциями, доступом
|
||
* пользователей и настройками инстанса. Доступ ограничен ролью `admin`
|
||
* (route-guard — см. `RequireAdmin` в App.tsx).
|
||
*
|
||
* Вкладки «Команды» и «Настройки» отсутствуют в макете
|
||
* `design/mockups/admin.html` — реализованы по общему паттерну
|
||
* `section-tab`/`table-card`/`toolbar-row` того же макета и DESIGN_SYSTEM.md.
|
||
*/
|
||
export function AdminPage() {
|
||
const [tab, setTab] = useState<AdminTab>('conferences')
|
||
|
||
return (
|
||
<div className="admin-page page-shell">
|
||
<ShellTopbar />
|
||
<main className="page-main">
|
||
<div className="page-head">
|
||
<div>
|
||
<h1>Администрирование</h1>
|
||
<p>Управление конференциями, доступом и пользователями VidConf</p>
|
||
</div>
|
||
</div>
|
||
|
||
<div className="section-tabs">
|
||
{TABS.map((t) => (
|
||
<button
|
||
key={t.value}
|
||
type="button"
|
||
className={`section-tab${tab === t.value ? ' is-active' : ''}`}
|
||
onClick={() => setTab(t.value)}
|
||
>
|
||
{t.label}
|
||
</button>
|
||
))}
|
||
</div>
|
||
|
||
{tab === 'conferences' && <AdminConferencesTab />}
|
||
{tab === 'users' && <AdminUsersTab />}
|
||
{tab === 'teams' && <AdminTeamsTab />}
|
||
{tab === 'settings' && <AdminSettingsTab />}
|
||
</main>
|
||
<AppFooter />
|
||
</div>
|
||
)
|
||
}
|