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={
{t('admin.users.add')}
}
footer={
setCreateOpen(false)}>
{t('admin.users.newDialog.cancel')}
void handleCreate(e)}
>
{t('admin.users.newDialog.create')}
}
>
{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 (
void navigate(`/admin/users/${user.id}`)}
style={{
flex: 1,
minWidth: 0,
textAlign: 'left',
background: 'none',
border: 'none',
cursor: 'pointer',
padding: 0,
font: 'inherit',
color: 'inherit',
}}
>
{user.username}
{isSelf && (
{t('admin.users.you')}
)}
{t('admin.users.added', {
date: new Date(user.createdAt).toLocaleDateString(),
})}
⋯
{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={
onOpenChange(false)}>
{t('admin.users.resetDialog.cancel')}
void handleSubmit(e)}
>
{t('admin.users.resetDialog.submit')}
}
>
{done ? (
{t('admin.users.resetDialog.done')}
) : (
)}
);
}
/** `/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 (
void navigate('/admin/users')}>
← {t('common.back')}
{isLoading &&
}
{isError &&
}
{user && (
{user.username}
{t('admin.users.added', {
date: new Date(user.createdAt).toLocaleDateString(),
})}
void updateUser({
id: user.id,
role: user.role === 'admin' ? 'user' : 'admin',
})
}
>
{user.role === 'admin'
? t('admin.users.menu.makeUser')
: t('admin.users.menu.makeAdmin')}
setResetOpen(true)}>
{t('admin.users.menu.resetPassword')}
{!isSelf && (
void updateUser({ id: user.id, isActive: !user.isActive })
}
>
{user.isActive
? t('admin.users.menu.deactivate')
: t('admin.users.menu.activate')}
)}
)}
);
}
/** `/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 ;
}