diff --git a/apps/examlense/frontend/src/components/shared/ExamModeRedirect.tsx b/apps/examlense/frontend/src/components/shared/ExamModeRedirect.tsx new file mode 100644 index 0000000..576c6d9 --- /dev/null +++ b/apps/examlense/frontend/src/components/shared/ExamModeRedirect.tsx @@ -0,0 +1,22 @@ +import { Navigate } from "react-router-dom"; +import { + examModePath, + examModeSlug, + type Exam, + type ExamModeSlug, +} from "@/lib/exam/exam-helpers"; + +/** + * Returns a `` to the exam's canonical mode when `exam`'s + * status doesn't belong in `expected`, else `null`. Call it AFTER the loading + * and `!exam` guards so `exam` is known to be present. + * + * Routing keeps each exam in the single mode its status maps to (see + * `examModeSlug`); visiting another mode's URL redirects instead of silently + * transitioning the exam. + */ +export function examModeRedirect(exam: Exam, expected: ExamModeSlug) { + return examModeSlug(exam.status) !== expected ? ( + + ) : null; +} diff --git a/apps/examlense/frontend/src/components/shared/ModelLogo.tsx b/apps/examlense/frontend/src/components/shared/ModelLogo.tsx index e86e35f..2f816ee 100644 --- a/apps/examlense/frontend/src/components/shared/ModelLogo.tsx +++ b/apps/examlense/frontend/src/components/shared/ModelLogo.tsx @@ -1,5 +1,5 @@ import { cn } from "@/lib/utils/utils"; -import { solverModelMeta } from "@/lib/exam/solver-model-meta"; +import { modelMeta } from "@/lib/exam/model-meta"; interface Props { modelId: string; @@ -12,7 +12,7 @@ interface Props { * logo is mapped. Callers own the surrounding chip/container. */ export const ModelLogo = ({ modelId, className }: Props) => { - const meta = solverModelMeta(modelId); + const meta = modelMeta(modelId); if (meta?.logoSrc) { return ( diff --git a/apps/examlense/frontend/src/components/shared/chrome/InlineTitle.tsx b/apps/examlense/frontend/src/components/shared/chrome/InlineTitle.tsx index 1871c6c..c404465 100644 --- a/apps/examlense/frontend/src/components/shared/chrome/InlineTitle.tsx +++ b/apps/examlense/frontend/src/components/shared/chrome/InlineTitle.tsx @@ -1,6 +1,6 @@ -import { useEffect, useState } from "react"; import { Pencil } from "lucide-react"; import { Input } from "@/components/ui/input"; +import { useClickToEdit } from "@/hooks/ui/use-click-to-edit"; interface Props { value: string; @@ -8,15 +8,13 @@ interface Props { } export const InlineTitle = ({ value, onSave }: Props) => { - const [editing, setEditing] = useState(false); - const [draft, setDraft] = useState(value); - useEffect(() => setDraft(value), [value]); + const { editing, startEditing, inputProps } = useClickToEdit(value, onSave); if (!editing) { return (