diff --git a/src/components/Markdowns/MarkdownContent.js b/src/components/Markdowns/MarkdownContent.js index 57e2274..fe90cad 100644 --- a/src/components/Markdowns/MarkdownContent.js +++ b/src/components/Markdowns/MarkdownContent.js @@ -2,7 +2,8 @@ import React, { useEffect, useState } from "react"; import {Link, useParams} from "react-router-dom"; import "katex/dist/katex.min.css"; import "./MarkdownContent.css"; -import { Settings2, Pencil } from "lucide-react"; +import { Settings2, Pencil, User, Clock, History } from "lucide-react"; +import { formatDateTime } from "../../lib/utils"; import MarkdownView from "./MarkdownView"; import PatchCards from "./PatchCards"; import PermissionGuard from "../PermissionGuard"; @@ -85,6 +86,23 @@ const MarkdownContent = () => { +
+ + + {markdown.author || "—"} + + + + created {formatDateTime(markdown.created_at)} + + + + updated {formatDateTime(markdown.updated_at)} + {markdown.last_modified_by + ? ` by ${markdown.last_modified_by}` + : ""} + +
{ +
+ + + {patch.author || "—"} + + + + {formatDateTime(patch.created_at)} + + + edited {formatDateTime(patch.updated_at)} + {patch.last_modified_by + ? ` by ${patch.last_modified_by}` + : ""} + +
diff --git a/src/components/Modals/ApiKeyCreationModal.js b/src/components/Modals/ApiKeyCreationModal.js index 5587bc3..a715352 100644 --- a/src/components/Modals/ApiKeyCreationModal.js +++ b/src/components/Modals/ApiKeyCreationModal.js @@ -11,14 +11,16 @@ import { import { Button } from '../ui/button'; import { Input, Label } from '../ui/input'; -const AVAILABLE_ROLES = ['guest', 'creator', 'admin']; +// Must match the backend allowlist (admin|creator|user). +const AVAILABLE_ROLES = ['user', 'creator', 'admin']; const SELECT_CLASS = "flex h-9 w-full rounded-md border border-input bg-background/60 px-3 py-1 text-sm text-foreground transition-colors focus-visible:outline-none focus-visible:border-primary/60 focus-visible:ring-2 focus-visible:ring-ring/40 disabled:cursor-not-allowed disabled:opacity-50"; const ApiKeyCreationModal = ({ isOpen, onClose }) => { + const [alias, setAlias] = useState(''); const [name, setName] = useState(''); - const [roles, setRoles] = useState(['guest']); + const [roles, setRoles] = useState(['user']); const [generatedKey, setGeneratedKey] = useState(null); const createApiKeyMutation = useCreateApiKey(); @@ -44,12 +46,17 @@ const ApiKeyCreationModal = ({ isOpen, onClose }) => { }; const handleGenerate = async () => { + if (!alias.trim()) { + alert('Alias is required'); + return; + } if (!name.trim()) { alert('API key name is required'); return; } try { const result = await createApiKeyMutation.mutateAsync({ + alias: alias.trim(), name: name.trim(), roles: roles }); @@ -61,7 +68,7 @@ const ApiKeyCreationModal = ({ isOpen, onClose }) => { }; const handleCopy = () => { - navigator.clipboard.writeText(generatedKey) + navigator.clipboard.writeText(generatedKey.key) .then(() => alert('API key copied to clipboard')) .catch(err => console.error('failed to copy api key:', err)); }; @@ -80,6 +87,20 @@ const ApiKeyCreationModal = ({ isOpen, onClose }) => { {!generatedKey ? (
+
+ + setAlias(e.target.value)} + /> +

+ Unique. Using an existing alias renews that + key (same key string, validity reset). +

+
{ ) : (
- Please copy your API key immediately! It will only be displayed once! + {generatedKey.renewed + ? "Key renewed — same key string, validity reset 15 days." + : "Please copy your API key immediately! It will only be displayed once!"}
@@ -154,7 +177,7 @@ const ApiKeyCreationModal = ({ isOpen, onClose }) => { {!generatedKey && ( diff --git a/src/lib/utils.js b/src/lib/utils.js index 40063a6..d878808 100644 --- a/src/lib/utils.js +++ b/src/lib/utils.js @@ -5,3 +5,17 @@ import { twMerge } from "tailwind-merge"; export function cn(...inputs) { return twMerge(clsx(inputs)); } + +/** Format a backend datetime string for display; '—' when missing/invalid. */ +export function formatDateTime(value) { + if (!value) return "—"; + const d = new Date(value); + if (isNaN(d.getTime())) return "—"; + return d.toLocaleString(undefined, { + year: "numeric", + month: "short", + day: "numeric", + hour: "2-digit", + minute: "2-digit", + }); +}