import { useState } from 'react'; import { useNavigate, useParams } from 'react-router'; import { useTranslation } from 'react-i18next'; import { Window, Card, Button, TextField, SegmentedControl, Dialog, AlertDialog, Menu, MenuTrigger, MenuContent, MenuItem, IconButton, Badge, Callout, } from '@olly/modern-sk'; import { Icon } from '../../components/common/Icon'; import { LoadingSkeleton } from '../../components/common/LoadingSkeleton'; import { EmptyState } from '../../components/common/EmptyState'; import { ErrorState } from '../../components/common/ErrorState'; import { usePermissions } from '../../hooks/usePermissions'; import { useGetUsersQuery, useGetUserQuery, useCreateUserMutation, useUpdateUserMutation, useDeleteUserMutation, useResetPasswordMutation, } from '../../api/endpoints/admin'; import type { User } from '../../api/types'; function StubPanel({ title }: { title: string }) { const { t } = useTranslation(); return (

{t('common.comingSoon')}

); } /** Role + active-status badges shared by the list and the detail view. */ function UserBadges({ user }: { user: User }) { const { t } = useTranslation(); return (
{t(`admin.users.role.${user.role}`)} {!user.isActive && ( {t('admin.users.status.blocked')} )}
); } /** `/admin/users` — user list with add / role toggle / (de)activate / reset password. */ export function AdminUsers() { const { t } = useTranslation(); const { data: users, isLoading, isError, refetch } = useGetUsersQuery(); const [createUser, { isLoading: isCreating }] = useCreateUserMutation(); const [createOpen, setCreateOpen] = useState(false); const [username, setUsername] = useState(''); const [password, setPassword] = useState(''); const [role, setRole] = useState<'admin' | 'user'>('user'); const [createError, setCreateError] = useState(false); const resetForm = () => { setUsername(''); setPassword(''); setRole('user'); setCreateError(false); }; const handleCreate = async (e: React.FormEvent) => { e.preventDefault(); const name = username.trim(); if (!name || !password) return; setCreateError(false); try { await createUser({ username: name, password, role }).unwrap(); setCreateOpen(false); resetForm(); } catch { setCreateError(true); } }; return (
{ setCreateOpen(open); if (!open) resetForm(); }} title={t('admin.users.newDialog.title')} trigger={ } footer={
} >
void handleCreate(e)} style={{ display: 'flex', flexDirection: 'column', gap: '0.75rem' }} > setUsername(e.target.value)} placeholder={t('admin.users.newDialog.username')} autoComplete="off" /> setPassword(e.target.value)} placeholder={t('admin.users.newDialog.password')} autoComplete="new-password" /> setRole(v === 'admin' ? 'admin' : 'user')} items={[ { value: 'user', label: t('admin.users.role.user') }, { value: 'admin', label: t('admin.users.role.admin') }, ]} /> {createError && ( {t('admin.users.newDialog.error')} )}
{isLoading && } {isError && } {!isLoading && !isError && (users?.length ?? 0) === 0 && ( )} {!isLoading && !isError && users && users.length > 0 && (
{users.map((user) => ( ))}
)}
); } /** One row in the user list: identity + badges + actions menu. */ function UserRow({ user }: { user: User }) { const { t } = useTranslation(); const navigate = useNavigate(); const { user: currentUser } = usePermissions(); const isSelf = currentUser?.id === user.id; const [updateUser] = useUpdateUserMutation(); const [deleteUser] = useDeleteUserMutation(); const [resetOpen, setResetOpen] = useState(false); const [deactivateOpen, setDeactivateOpen] = useState(false); const toggleRole = () => void updateUser({ id: user.id, role: user.role === 'admin' ? 'user' : 'admin', }); const toggleActive = () => void updateUser({ id: user.id, isActive: !user.isActive }); return ( ⋯ {user.role === 'admin' ? t('admin.users.menu.makeUser') : t('admin.users.menu.makeAdmin')} setResetOpen(true)}> {t('admin.users.menu.resetPassword')} {/* Guard against locking yourself out of the instance. */} {!isSelf && ( user.isActive ? setDeactivateOpen(true) : toggleActive() } > {user.isActive ? t('admin.users.menu.deactivate') : t('admin.users.menu.activate')} )} void deleteUser(user.id)} /> ); } /** Reset-password dialog: admin sets a new password for the account. */ function ResetPasswordDialog({ user, open, onOpenChange, }: { user: User; open: boolean; onOpenChange: (open: boolean) => void; }) { const { t } = useTranslation(); const [resetPassword, { isLoading }] = useResetPasswordMutation(); const [value, setValue] = useState(''); const [done, setDone] = useState(false); const handleSubmit = async (e: React.FormEvent) => { e.preventDefault(); if (!value) return; await resetPassword({ id: user.id, newPassword: value }).unwrap(); setValue(''); setDone(true); }; return ( { onOpenChange(o); if (!o) { setValue(''); setDone(false); } }} title={t('admin.users.resetDialog.title', { name: user.username })} footer={
} > {done ? ( {t('admin.users.resetDialog.done')} ) : (
void handleSubmit(e)}> setValue(e.target.value)} placeholder={t('admin.users.resetDialog.newPassword')} autoComplete="new-password" /> )}
); } /** `/admin/users/:userId` — per-user detail: identity, role toggle, reset, status. */ export function AdminUserDetail() { const { t } = useTranslation(); const navigate = useNavigate(); const { userId = '' } = useParams(); const { user: currentUser } = usePermissions(); const { data: user, isLoading, isError, refetch } = useGetUserQuery(userId, { skip: !userId, }); const [updateUser] = useUpdateUserMutation(); const [resetOpen, setResetOpen] = useState(false); const isSelf = currentUser?.id === user?.id; return (
{isLoading && } {isError && } {user && (

{user.username}

{t('admin.users.added', { date: new Date(user.createdAt).toLocaleDateString(), })}
{!isSelf && ( )}
)}
); } /** `/admin/sources` — pluggable source management (creds/cookies/status). Scaffold. */ export function AdminSources() { const { t } = useTranslation(); return ; } /** `/admin/instance` — service health, ML_SERVICE_URL, reindex. Scaffold. */ export function AdminInstance() { const { t } = useTranslation(); return ; }