AcadeDoc/apps/client/src/features/acadenice/api-keys/components/token-created-modal.tsx
Corentin 5b512e6324 feat(acadedoc): replace EE Settings with open source UI (audit log, API keys, OIDC status) — R4.5
Server (NestJS):
- AcadeniceAuditLogModule: GET /api/acadenice/audit-log (admin/owner, Kysely, paginated + filtered)
- AcadeniceApiKeysModule: GET/POST/DELETE /api/acadenice/api-keys (JWT, bcrypt hash, acdk_ prefix)
- AcadeniceSecurityModule: GET /api/acadenice/security/oidc-status (admin, no secrets exposed)
- Migration 20260510T100000: acadenice_api_key table with token_hash + bcrypt
- Permissions catalog: added audit_log:read

Client (React 18 + Mantine v7):
- Audit log page: paginated table with filters (event, userId, date range)
- API keys page: list/create/revoke personal tokens, one-time display modal
- Security/OIDC status page: read-only, env-var config reference
- Sidebar rewired: Security & SSO, API keys, Audit log -> acadenice/* routes (no EE feature gates)
- Prefetch functions for new routes

Tests: 36 server (Jest) + client typecheck clean

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-05-08 12:24:00 +02:00

76 lines
1.8 KiB
TypeScript

import {
Alert,
Button,
Group,
Modal,
Stack,
Text,
TextInput,
} from "@mantine/core";
import { IconAlertTriangle } from "@tabler/icons-react";
import { useTranslation } from "react-i18next";
import { CreateAcadeniceApiKeyResponse } from "../types/api-key.types";
import CopyTextButton from "@/components/common/copy";
interface TokenCreatedModalProps {
opened: boolean;
onClose: () => void;
result: CreateAcadeniceApiKeyResponse | null;
}
export function AcadeniceTokenCreatedModal({
opened,
onClose,
result,
}: TokenCreatedModalProps) {
const { t } = useTranslation();
if (!result) return null;
return (
<Modal
opened={opened}
onClose={onClose}
title={t("API key created")}
size="lg"
>
<Stack gap="md">
<Alert
icon={<IconAlertTriangle size={16} />}
title={t("Important")}
color="red"
>
{t(
"This is the only time you can see this token. Copy it now — it cannot be retrieved later.",
)}
</Alert>
<Text fz="sm" fw={500}>
{t("Your new API key")}
</Text>
<Group gap="xs" wrap="nowrap">
<TextInput
variant="filled"
style={{ flex: 1 }}
value={result.token}
readOnly
ff="monospace"
aria-label={t("Generated API key token")}
/>
<CopyTextButton text={result.token} />
</Group>
<Text fz="xs" c="dimmed">
{t(
"This token grants full access to your account. Treat it like a password.",
)}
</Text>
<Button fullWidth onClick={onClose} mt="sm" aria-label={t("Confirm token saved")}>
{t("I have saved my token")}
</Button>
</Stack>
</Modal>
);
}