Fix dragCounterRef going negative in audit list dropzone

Clamp the counter with Math.max(0, ...) and use <= 0 check to prevent
the drag overlay from getting stuck when dragLeave fires more than
dragEnter.

Signed-off-by: Bryan Frimin <bryan@getprobo.com>
This commit is contained in:
Bryan Frimin
2026-03-18 23:32:00 +01:00
parent e055b21bc3
commit 44cc650af2

View File

@@ -20,8 +20,10 @@ import {
Thead, Thead,
Tr, Tr,
useDialogRef, useDialogRef,
useToast,
} from "@probo/ui"; } from "@probo/ui";
import { useCallback, useEffect, useRef, useState } from "react"; import { useCallback, useEffect, useRef, useState } from "react";
import { createPortal } from "react-dom";
import { useDropzone } from "react-dropzone"; import { useDropzone } from "react-dropzone";
import { import {
graphql, graphql,
@@ -91,6 +93,7 @@ type Props = {
export default function AuditsPage(props: Props) { export default function AuditsPage(props: Props) {
const { __ } = useTranslate(); const { __ } = useTranslate();
const { toast } = useToast();
const organizationId = useOrganizationId(); const organizationId = useOrganizationId();
const data = usePreloadedQuery(auditsQuery, props.queryRef); const data = usePreloadedQuery(auditsQuery, props.queryRef);
@@ -115,14 +118,22 @@ export default function AuditsPage(props: Props) {
const dragCounterRef = useRef(0); const dragCounterRef = useRef(0);
const onDrop = useCallback( const onDrop = useCallback(
(acceptedFiles: File[]) => { (acceptedFiles: File[], fileRejections: { file: File }[]) => {
setIsDragging(false); setIsDragging(false);
dragCounterRef.current = 0; dragCounterRef.current = 0;
if (fileRejections.length > 0) {
toast({
title: __("Unsupported file type"),
description: __("Only PDF files are supported."),
variant: "error",
});
return;
}
if (!canCreateAudit || acceptedFiles.length === 0) return; if (!canCreateAudit || acceptedFiles.length === 0) return;
setDroppedFile(acceptedFiles[0]); setDroppedFile(acceptedFiles[0]);
dropDialogRef.current?.open(); dropDialogRef.current?.open();
}, },
[canCreateAudit, dropDialogRef], [canCreateAudit, dropDialogRef, toast, __],
); );
useEffect(() => { useEffect(() => {
@@ -138,8 +149,8 @@ export default function AuditsPage(props: Props) {
const handleDragLeave = (e: DragEvent) => { const handleDragLeave = (e: DragEvent) => {
e.preventDefault(); e.preventDefault();
dragCounterRef.current--; dragCounterRef.current = Math.max(0, dragCounterRef.current - 1);
if (dragCounterRef.current === 0) { if (dragCounterRef.current <= 0) {
setIsDragging(false); setIsDragging(false);
} }
}; };
@@ -179,19 +190,28 @@ export default function AuditsPage(props: Props) {
setDroppedFile(null); setDroppedFile(null);
}; };
const mainRef = useRef<HTMLElement | null>(null);
useEffect(() => {
mainRef.current = document.querySelector("main");
if (mainRef.current) {
mainRef.current.style.position = "relative";
}
}, []);
return ( return (
<div className="space-y-6"> <div className="space-y-6">
{isDragging && canCreateAudit && ( {isDragging && canCreateAudit && mainRef.current && createPortal(
<div <div
{...getRootProps()} {...getRootProps()}
className="border-primary bg-primary/5 pointer-events-auto fixed inset-0 top-12 z-40 flex flex-col items-center justify-center border-2 border-dashed" className="border-primary bg-primary/5 pointer-events-auto absolute inset-0 z-40 flex flex-col items-center justify-center border-2 border-dashed"
> >
<input {...getInputProps()} /> <input {...getInputProps()} />
<IconUpload className="text-primary mb-2 size-8" /> <IconUpload className="text-primary mb-2 size-8" />
<p className="text-primary text-sm font-medium"> <p className="text-primary text-sm font-medium">
{__("Drop a PDF to create an audit with a report")} {__("Drop a PDF to create an audit with a report")}
</p> </p>
</div> </div>,
mainRef.current,
)} )}
<PageHeader <PageHeader
title={__("Audits")} title={__("Audits")}