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>
This commit is contained in:
@@ -250,6 +250,11 @@ export default function ProjectFileManager({
|
||||
const queryClient = useQueryClient();
|
||||
const fileInputRef = useRef<HTMLInputElement>(null);
|
||||
const isCancelling = useRef(false);
|
||||
// dragenter/dragleave fire on every child boundary crossed during a drag.
|
||||
// Counting enter/leave events keeps the overlay steady instead of flickering
|
||||
// as the cursor moves over nested elements; the overlay only hides when the
|
||||
// depth returns to 0 (the cursor truly left the drop zone).
|
||||
const dragDepth = useRef(0);
|
||||
|
||||
const [currentPath, setCurrentPath] = useState("");
|
||||
|
||||
@@ -333,7 +338,8 @@ export default function ProjectFileManager({
|
||||
} else {
|
||||
errorMsg = data.error || "Chyba při nahrávání";
|
||||
}
|
||||
} catch {
|
||||
} catch (e) {
|
||||
console.error("ProjectFileManager: upload failed", e);
|
||||
errorMsg = "Chyba připojení";
|
||||
}
|
||||
}
|
||||
@@ -362,21 +368,32 @@ export default function ProjectFileManager({
|
||||
|
||||
const handleDrop = (e: React.DragEvent) => {
|
||||
e.preventDefault();
|
||||
dragDepth.current = 0;
|
||||
setDragOver(false);
|
||||
if (!canManage) return;
|
||||
handleUpload(e.dataTransfer.files);
|
||||
};
|
||||
|
||||
const handleDragOver = (e: React.DragEvent) => {
|
||||
const handleDragEnter = (e: React.DragEvent) => {
|
||||
e.preventDefault();
|
||||
if (!canManage) return;
|
||||
dragDepth.current += 1;
|
||||
setDragOver(true);
|
||||
};
|
||||
|
||||
const handleDragOver = (e: React.DragEvent) => {
|
||||
// Must preventDefault on dragover too, otherwise the browser rejects the
|
||||
// drop. The overlay state is driven by enter/leave (see dragDepth).
|
||||
e.preventDefault();
|
||||
if (canManage) {
|
||||
setDragOver(true);
|
||||
}
|
||||
};
|
||||
|
||||
const handleDragLeave = (e: React.DragEvent) => {
|
||||
e.preventDefault();
|
||||
setDragOver(false);
|
||||
if (!canManage) return;
|
||||
dragDepth.current = Math.max(0, dragDepth.current - 1);
|
||||
if (dragDepth.current === 0) {
|
||||
setDragOver(false);
|
||||
}
|
||||
};
|
||||
|
||||
const handleCreateFolder = async () => {
|
||||
@@ -405,7 +422,8 @@ export default function ProjectFileManager({
|
||||
} else {
|
||||
alert.error(data.error || "Nepodařilo se vytvořit složku");
|
||||
}
|
||||
} catch {
|
||||
} catch (e) {
|
||||
console.error("ProjectFileManager: create folder failed", e);
|
||||
alert.error("Chyba připojení");
|
||||
} finally {
|
||||
setCreatingFolder(false);
|
||||
@@ -435,7 +453,8 @@ export default function ProjectFileManager({
|
||||
a.click();
|
||||
a.remove();
|
||||
URL.revokeObjectURL(url);
|
||||
} catch {
|
||||
} catch (e) {
|
||||
console.error("ProjectFileManager: download failed", e);
|
||||
alert.error("Chyba připojení");
|
||||
}
|
||||
};
|
||||
@@ -468,7 +487,8 @@ export default function ProjectFileManager({
|
||||
} else {
|
||||
alert.error(data.error || "Nepodařilo se smazat");
|
||||
}
|
||||
} catch {
|
||||
} catch (e) {
|
||||
console.error("ProjectFileManager: delete failed", e);
|
||||
alert.error("Chyba připojení");
|
||||
} finally {
|
||||
setDeleting(false);
|
||||
@@ -505,7 +525,8 @@ export default function ProjectFileManager({
|
||||
} else {
|
||||
alert.error(data.error || "Nepodařilo se přejmenovat");
|
||||
}
|
||||
} catch {
|
||||
} catch (e) {
|
||||
console.error("ProjectFileManager: rename failed", e);
|
||||
alert.error("Chyba připojení");
|
||||
} finally {
|
||||
setRenamingItem(null);
|
||||
@@ -887,6 +908,7 @@ export default function ProjectFileManager({
|
||||
{/* Drop zone + table */}
|
||||
<Box
|
||||
onDrop={handleDrop}
|
||||
onDragEnter={handleDragEnter}
|
||||
onDragOver={handleDragOver}
|
||||
onDragLeave={handleDragLeave}
|
||||
sx={{
|
||||
|
||||
Reference in New Issue
Block a user