Files
app/src/admin/components/RichEditor.tsx
BOHA 519edce373 fix: 2026-06-09 full-codebase audit hardening
Validation: shared NaN-guarded Zod coercion helpers in schemas/common.ts replace
the raw number|string transform idiom across every schema (the root-cause NaN bug
class); emailOrEmpty + lenient isoDateString/timeString.

Security: roles privilege-escalation closed; refresh-token family revocation on
reuse; TOTP uses config params; read endpoints permission-guarded; received-invoices
gross VAT on all paths; orders-pdf custom-items authz.

Concurrency: $queryRaw SELECT...FOR UPDATE locks in ascending-id order (warehouse
confirm/cancel, attendance lockUserRow); uniqueness checks moved into create
transactions (TOCTOU -> 409); deterministic id tiebreak on second-precision
timestamp ordering (plan resolveCell/resolveGrid, warehouse FIFO).

Frontend: Rules-of-Hooks fixed across ~14 pages + PlanCellModal; UTC-date persisted
fields; dashboard invalidation gaps; stale-closure confirm bugs.

Tooling/tests: ESLint flat config (react-hooks/rules-of-hooks = error) + Prettier;
tsconfig.test.json so tsc -b type-checks the tests; removed 3 dead deps; npm audit
fix (8 -> 3). Suite 195 -> 247 (happy-path auth, FIFO oldest-first, flakiness fixes),
isolated on app_test via .env.test with a hard-throw setup guard.

Gates: tsc 0 | build 0 | vitest 247/247 | eslint 0 errors.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-09 06:45:26 +02:00

126 lines
2.5 KiB
TypeScript

import { useMemo, useRef, useCallback, useLayoutEffect } from "react";
import ReactQuill from "react-quill-new";
import "react-quill-new/dist/quill.snow.css";
import { RichEditorRoot } from "../ui/RichText";
const COLORS = [
"#000000",
"#1a1a1a",
"#333333",
"#555555",
"#777777",
"#999999",
"#bbbbbb",
"#dddddd",
"#ffffff",
"#de3a3a",
"#e57373",
"#c62828",
"#1565c0",
"#42a5f5",
"#0d47a1",
"#2e7d32",
"#66bb6a",
"#1b5e20",
"#f57f17",
"#ffca28",
"#e65100",
"#6a1b9a",
"#ab47bc",
"#4a148c",
"#00695c",
"#26a69a",
"#004d40",
"#37474f",
"#78909c",
"#263238",
];
const TOOLBAR = [
["bold", "italic", "underline", "strike"],
[{ color: COLORS }, { background: COLORS }],
[{ list: "ordered" }, { list: "bullet" }],
[{ indent: "-1" }, { indent: "+1" }],
[{ align: [] }],
["link"],
["clean"],
];
const FORMATS = [
"bold",
"italic",
"underline",
"strike",
"color",
"background",
"list",
"indent",
"align",
"link",
];
interface RichEditorProps {
value: string;
onChange: (value: string) => void;
placeholder?: string;
minHeight?: string;
readOnly?: boolean;
}
export default function RichEditor({
value,
onChange,
placeholder = "Obsah...",
minHeight = "120px",
readOnly = false,
}: RichEditorProps) {
const quillRef = useRef<ReactQuill>(null);
const lastValueRef = useRef(value);
const modules = useMemo(
() => ({
toolbar: readOnly ? false : TOOLBAR,
clipboard: {
matchVisual: false,
},
}),
[readOnly],
);
const handleChange = useCallback(
// `_delta` is react-quill-new's loosely-typed Delta object; we never read
// it, so `unknown` is enough and avoids an `any` escape hatch.
(content: string, _delta: unknown, source: string) => {
if (source !== "user") return;
if (content === lastValueRef.current) return;
lastValueRef.current = content;
onChange(content);
},
[onChange],
);
useLayoutEffect(() => {
if (!quillRef.current) return;
const editor = quillRef.current.getEditor();
editor.root.style.fontFamily = "Tahoma, sans-serif";
editor.root.style.fontSize = "14px";
}, []);
return (
<RichEditorRoot
style={{ "--re-min-height": minHeight } as React.CSSProperties}
>
<ReactQuill
ref={quillRef}
theme="snow"
value={value || ""}
onChange={handleChange}
modules={modules}
formats={FORMATS}
placeholder={placeholder}
readOnly={readOnly}
/>
</RichEditorRoot>
);
}