@striae-org/striae 4.0.3 → 4.1.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/app/components/actions/confirm-export.ts +4 -2
- package/app/components/actions/generate-pdf.ts +10 -2
- package/app/components/audit/user-audit-viewer.tsx +121 -940
- package/app/components/audit/user-audit.module.css +20 -0
- package/app/components/audit/viewer/audit-activity-summary.tsx +52 -0
- package/app/components/audit/viewer/audit-entries-list.tsx +200 -0
- package/app/components/audit/viewer/audit-filters-panel.tsx +306 -0
- package/app/components/audit/viewer/audit-user-info-card.tsx +44 -0
- package/app/components/audit/viewer/audit-viewer-header.tsx +55 -0
- package/app/components/audit/viewer/audit-viewer-utils.ts +121 -0
- package/app/components/audit/viewer/types.ts +1 -0
- package/app/components/audit/viewer/use-audit-viewer-data.ts +166 -0
- package/app/components/audit/viewer/use-audit-viewer-export.ts +176 -0
- package/app/components/audit/viewer/use-audit-viewer-filters.ts +141 -0
- package/app/components/auth/mfa-enrollment.module.css +13 -5
- package/app/components/auth/mfa-verification.module.css +13 -5
- package/app/components/canvas/canvas.tsx +3 -0
- package/app/components/canvas/confirmation/confirmation.tsx +13 -37
- package/app/components/public-signing-key-modal/public-signing-key-modal.module.css +1 -0
- package/app/components/public-signing-key-modal/public-signing-key-modal.tsx +8 -37
- package/app/components/sidebar/case-export/case-export.tsx +9 -34
- package/app/components/sidebar/case-import/case-import.module.css +2 -0
- package/app/components/sidebar/case-import/case-import.tsx +10 -34
- package/app/components/sidebar/cases/cases-modal.module.css +44 -9
- package/app/components/sidebar/cases/cases-modal.tsx +16 -14
- package/app/components/sidebar/files/files-modal.module.css +45 -10
- package/app/components/sidebar/files/files-modal.tsx +16 -16
- package/app/components/sidebar/notes/notes-modal.tsx +17 -15
- package/app/components/sidebar/notes/notes.module.css +2 -0
- package/app/components/sidebar/sidebar.module.css +2 -2
- package/app/components/toast/toast.module.css +2 -1
- package/app/components/toast/toast.tsx +16 -11
- package/app/components/user/delete-account.tsx +10 -31
- package/app/components/user/inactivity-warning.module.css +8 -6
- package/app/components/user/manage-profile.module.css +2 -0
- package/app/components/user/manage-profile.tsx +85 -30
- package/app/hooks/useOverlayDismiss.ts +68 -0
- package/app/routes/auth/login.example.tsx +19 -8
- package/app/routes/auth/passwordReset.module.css +23 -13
- package/app/routes/striae/striae.tsx +8 -1
- package/app/routes.ts +7 -0
- package/app/services/audit/audit-export-csv.ts +2 -0
- package/app/services/audit/audit.service.ts +29 -5
- package/app/services/audit/builders/audit-entry-builder.ts +2 -1
- package/app/services/audit/builders/audit-event-builders-user-security.ts +4 -2
- package/app/services/audit/builders/audit-event-builders-workflow.ts +6 -0
- package/app/types/audit.ts +2 -1
- package/app/types/user.ts +1 -0
- package/app/utils/data/permissions.ts +1 -0
- package/functions/api/pdf/[[path]].ts +32 -1
- package/load-context.ts +9 -0
- package/package.json +5 -1
- package/primershear.emails.example +6 -0
- package/scripts/deploy-pages-secrets.sh +6 -0
- package/scripts/deploy-primershear-emails.sh +166 -0
- package/worker-configuration.d.ts +7493 -7491
- package/workers/audit-worker/wrangler.jsonc.example +1 -1
- package/workers/data-worker/wrangler.jsonc.example +1 -1
- package/workers/image-worker/wrangler.jsonc.example +1 -1
- package/workers/keys-worker/wrangler.jsonc.example +1 -1
- package/workers/pdf-worker/src/pdf-worker.example.ts +3 -0
- package/workers/pdf-worker/src/report-types.ts +3 -0
- package/workers/pdf-worker/wrangler.jsonc.example +1 -1
- package/workers/user-worker/src/user-worker.example.ts +6 -1
- package/workers/user-worker/wrangler.jsonc.example +1 -1
- package/wrangler.toml.example +1 -1
|
@@ -9,6 +9,7 @@
|
|
|
9
9
|
justify-content: center;
|
|
10
10
|
align-items: center;
|
|
11
11
|
z-index: 1000;
|
|
12
|
+
cursor: default;
|
|
12
13
|
}
|
|
13
14
|
|
|
14
15
|
.modal {
|
|
@@ -18,6 +19,7 @@
|
|
|
18
19
|
width: 100%;
|
|
19
20
|
max-width: 400px;
|
|
20
21
|
box-shadow: 0 var(--spaceM) var(--spaceXL) rgba(0, 0, 0, 0.2);
|
|
22
|
+
cursor: default;
|
|
21
23
|
}
|
|
22
24
|
|
|
23
25
|
.title {
|
|
@@ -63,7 +65,8 @@
|
|
|
63
65
|
}
|
|
64
66
|
|
|
65
67
|
.errorMessage {
|
|
66
|
-
background: linear-gradient(
|
|
68
|
+
background: linear-gradient(
|
|
69
|
+
135deg,
|
|
67
70
|
color-mix(in lab, var(--error) 12%, transparent),
|
|
68
71
|
color-mix(in lab, var(--error) 8%, transparent)
|
|
69
72
|
);
|
|
@@ -83,13 +86,17 @@
|
|
|
83
86
|
}
|
|
84
87
|
|
|
85
88
|
.errorMessage::before {
|
|
86
|
-
content:
|
|
89
|
+
content: "";
|
|
87
90
|
position: absolute;
|
|
88
91
|
top: 0;
|
|
89
92
|
left: 0;
|
|
90
93
|
right: 0;
|
|
91
94
|
height: 2px;
|
|
92
|
-
background: linear-gradient(
|
|
95
|
+
background: linear-gradient(
|
|
96
|
+
90deg,
|
|
97
|
+
var(--error),
|
|
98
|
+
color-mix(in lab, var(--error) 60%, transparent)
|
|
99
|
+
);
|
|
93
100
|
animation: shimmer 2s ease-in-out infinite;
|
|
94
101
|
}
|
|
95
102
|
|
|
@@ -229,7 +236,8 @@
|
|
|
229
236
|
}
|
|
230
237
|
|
|
231
238
|
@keyframes shimmer {
|
|
232
|
-
0%,
|
|
239
|
+
0%,
|
|
240
|
+
100% {
|
|
233
241
|
opacity: 0.6;
|
|
234
242
|
transform: translateX(-100%);
|
|
235
243
|
}
|
|
@@ -244,7 +252,7 @@
|
|
|
244
252
|
.errorMessage {
|
|
245
253
|
animation: none;
|
|
246
254
|
}
|
|
247
|
-
|
|
255
|
+
|
|
248
256
|
.errorMessage::before {
|
|
249
257
|
animation: none;
|
|
250
258
|
}
|
|
@@ -10,6 +10,7 @@ interface CanvasProps {
|
|
|
10
10
|
imageUrl?: string;
|
|
11
11
|
filename?: string;
|
|
12
12
|
company?: string;
|
|
13
|
+
badgeId?: string;
|
|
13
14
|
firstName?: string;
|
|
14
15
|
error?: string;
|
|
15
16
|
activeAnnotations?: Set<string>;
|
|
@@ -32,6 +33,7 @@ export const Canvas = ({
|
|
|
32
33
|
imageUrl,
|
|
33
34
|
filename,
|
|
34
35
|
company,
|
|
36
|
+
badgeId,
|
|
35
37
|
firstName,
|
|
36
38
|
error,
|
|
37
39
|
activeAnnotations,
|
|
@@ -453,6 +455,7 @@ export const Canvas = ({
|
|
|
453
455
|
onClose={() => setIsConfirmationModalOpen(false)}
|
|
454
456
|
onConfirm={handleConfirmation}
|
|
455
457
|
company={company}
|
|
458
|
+
defaultBadgeId={badgeId}
|
|
456
459
|
existingConfirmation={annotationData?.confirmationData || null}
|
|
457
460
|
/>
|
|
458
461
|
</div>
|
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
import { useState, useEffect, useContext } from 'react';
|
|
2
2
|
import { type ConfirmationData } from '~/types/annotations';
|
|
3
3
|
import { AuthContext } from '~/contexts/auth.context';
|
|
4
|
+
import { useOverlayDismiss } from '~/hooks/useOverlayDismiss';
|
|
4
5
|
import { generateConfirmationId } from '~/utils/common';
|
|
5
6
|
import styles from './confirmation.module.css';
|
|
6
7
|
|
|
@@ -9,6 +10,7 @@ interface ConfirmationModalProps {
|
|
|
9
10
|
onClose: () => void;
|
|
10
11
|
onConfirm?: (confirmationData: ConfirmationData) => void;
|
|
11
12
|
company?: string;
|
|
13
|
+
defaultBadgeId?: string;
|
|
12
14
|
existingConfirmation?: ConfirmationData | null;
|
|
13
15
|
}
|
|
14
16
|
|
|
@@ -26,7 +28,7 @@ const formatTimestamp = (): string => {
|
|
|
26
28
|
});
|
|
27
29
|
};
|
|
28
30
|
|
|
29
|
-
export const ConfirmationModal = ({ isOpen, onClose, onConfirm, company, existingConfirmation }: ConfirmationModalProps) => {
|
|
31
|
+
export const ConfirmationModal = ({ isOpen, onClose, onConfirm, company, defaultBadgeId, existingConfirmation }: ConfirmationModalProps) => {
|
|
30
32
|
const { user } = useContext(AuthContext);
|
|
31
33
|
const [badgeId, setBadgeId] = useState('');
|
|
32
34
|
const [error, setError] = useState('');
|
|
@@ -38,38 +40,29 @@ export const ConfirmationModal = ({ isOpen, onClose, onConfirm, company, existin
|
|
|
38
40
|
const timestamp = formatTimestamp();
|
|
39
41
|
const confirmationId = generateConfirmationId();
|
|
40
42
|
|
|
43
|
+
const {
|
|
44
|
+
handleOverlayMouseDown,
|
|
45
|
+
handleOverlayKeyDown
|
|
46
|
+
} = useOverlayDismiss({
|
|
47
|
+
isOpen,
|
|
48
|
+
onClose
|
|
49
|
+
});
|
|
50
|
+
|
|
41
51
|
// Check if this is an existing confirmation
|
|
42
52
|
const hasExistingConfirmation = !!existingConfirmation;
|
|
43
53
|
|
|
44
|
-
// Handle Escape key to close modal
|
|
45
|
-
useEffect(() => {
|
|
46
|
-
const handleEscapeKey = (event: KeyboardEvent) => {
|
|
47
|
-
if (event.key === 'Escape' && isOpen) {
|
|
48
|
-
onClose();
|
|
49
|
-
}
|
|
50
|
-
};
|
|
51
|
-
|
|
52
|
-
if (isOpen) {
|
|
53
|
-
document.addEventListener('keydown', handleEscapeKey);
|
|
54
|
-
}
|
|
55
|
-
|
|
56
|
-
return () => {
|
|
57
|
-
document.removeEventListener('keydown', handleEscapeKey);
|
|
58
|
-
};
|
|
59
|
-
}, [isOpen, onClose]);
|
|
60
|
-
|
|
61
54
|
// Reset form when modal opens
|
|
62
55
|
useEffect(() => {
|
|
63
56
|
if (isOpen) {
|
|
64
57
|
if (existingConfirmation) {
|
|
65
58
|
setBadgeId(existingConfirmation.badgeId);
|
|
66
59
|
} else {
|
|
67
|
-
setBadgeId('');
|
|
60
|
+
setBadgeId(defaultBadgeId || '');
|
|
68
61
|
}
|
|
69
62
|
setError('');
|
|
70
63
|
setIsConfirming(false);
|
|
71
64
|
}
|
|
72
|
-
}, [isOpen, existingConfirmation]);
|
|
65
|
+
}, [isOpen, defaultBadgeId, existingConfirmation]);
|
|
73
66
|
|
|
74
67
|
if (!isOpen) return null;
|
|
75
68
|
|
|
@@ -104,23 +97,6 @@ export const ConfirmationModal = ({ isOpen, onClose, onConfirm, company, existin
|
|
|
104
97
|
}
|
|
105
98
|
};
|
|
106
99
|
|
|
107
|
-
const handleOverlayMouseDown = (e: React.MouseEvent<HTMLDivElement>) => {
|
|
108
|
-
if (e.target === e.currentTarget) {
|
|
109
|
-
onClose();
|
|
110
|
-
}
|
|
111
|
-
};
|
|
112
|
-
|
|
113
|
-
const handleOverlayKeyDown = (e: React.KeyboardEvent<HTMLDivElement>) => {
|
|
114
|
-
if (e.target !== e.currentTarget) {
|
|
115
|
-
return;
|
|
116
|
-
}
|
|
117
|
-
|
|
118
|
-
if (e.key === 'Enter' || e.key === ' ') {
|
|
119
|
-
e.preventDefault();
|
|
120
|
-
onClose();
|
|
121
|
-
}
|
|
122
|
-
};
|
|
123
|
-
|
|
124
100
|
return (
|
|
125
101
|
<div
|
|
126
102
|
className={styles.overlay}
|
|
@@ -5,10 +5,9 @@ import {
|
|
|
5
5
|
useState,
|
|
6
6
|
type ChangeEvent,
|
|
7
7
|
type DragEvent,
|
|
8
|
-
type KeyboardEvent,
|
|
9
|
-
type MouseEvent
|
|
10
8
|
} from 'react';
|
|
11
9
|
import styles from './public-signing-key-modal.module.css';
|
|
10
|
+
import { useOverlayDismiss } from '~/hooks/useOverlayDismiss';
|
|
12
11
|
import { verifyExportFile } from '~/utils/forensics';
|
|
13
12
|
|
|
14
13
|
const NO_PUBLIC_KEY_MESSAGE = 'No public signing key is configured for this environment.';
|
|
@@ -230,6 +229,13 @@ export const PublicSigningKeyModal = ({
|
|
|
230
229
|
const publicSigningKeyTitleId = useId();
|
|
231
230
|
const publicKeyInputId = useId();
|
|
232
231
|
const exportFileInputId = useId();
|
|
232
|
+
const {
|
|
233
|
+
handleOverlayMouseDown,
|
|
234
|
+
handleOverlayKeyDown
|
|
235
|
+
} = useOverlayDismiss({
|
|
236
|
+
isOpen,
|
|
237
|
+
onClose
|
|
238
|
+
});
|
|
233
239
|
|
|
234
240
|
useEffect(() => {
|
|
235
241
|
if (!isOpen) {
|
|
@@ -242,45 +248,10 @@ export const PublicSigningKeyModal = ({
|
|
|
242
248
|
}
|
|
243
249
|
}, [isOpen]);
|
|
244
250
|
|
|
245
|
-
useEffect(() => {
|
|
246
|
-
if (!isOpen) {
|
|
247
|
-
return;
|
|
248
|
-
}
|
|
249
|
-
|
|
250
|
-
const handleEscapeKey = (event: globalThis.KeyboardEvent) => {
|
|
251
|
-
if (event.key === 'Escape') {
|
|
252
|
-
onClose();
|
|
253
|
-
}
|
|
254
|
-
};
|
|
255
|
-
|
|
256
|
-
document.addEventListener('keydown', handleEscapeKey);
|
|
257
|
-
|
|
258
|
-
return () => {
|
|
259
|
-
document.removeEventListener('keydown', handleEscapeKey);
|
|
260
|
-
};
|
|
261
|
-
}, [isOpen, onClose]);
|
|
262
|
-
|
|
263
251
|
if (!isOpen) {
|
|
264
252
|
return null;
|
|
265
253
|
}
|
|
266
254
|
|
|
267
|
-
const handleOverlayMouseDown = (event: MouseEvent<HTMLDivElement>) => {
|
|
268
|
-
if (event.target === event.currentTarget) {
|
|
269
|
-
onClose();
|
|
270
|
-
}
|
|
271
|
-
};
|
|
272
|
-
|
|
273
|
-
const handleOverlayKeyDown = (event: KeyboardEvent<HTMLDivElement>) => {
|
|
274
|
-
if (event.target !== event.currentTarget) {
|
|
275
|
-
return;
|
|
276
|
-
}
|
|
277
|
-
|
|
278
|
-
if (event.key === 'Enter' || event.key === ' ') {
|
|
279
|
-
event.preventDefault();
|
|
280
|
-
onClose();
|
|
281
|
-
}
|
|
282
|
-
};
|
|
283
|
-
|
|
284
255
|
const resetVerificationState = () => {
|
|
285
256
|
setVerificationOutcome(null);
|
|
286
257
|
};
|
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
import { useState, useEffect, useContext } from 'react';
|
|
2
2
|
import styles from './case-export.module.css';
|
|
3
3
|
import { AuthContext } from '~/contexts/auth.context';
|
|
4
|
+
import { useOverlayDismiss } from '~/hooks/useOverlayDismiss';
|
|
4
5
|
import { PublicSigningKeyModal } from '~/components/public-signing-key-modal/public-signing-key-modal';
|
|
5
6
|
import { getCurrentPublicSigningKeyDetails } from '~/utils/forensics';
|
|
6
7
|
import { getCaseConfirmations, exportConfirmationData } from '../../actions/confirm-export';
|
|
@@ -36,6 +37,14 @@ export const CaseExport = ({
|
|
|
36
37
|
const [hasConfirmationData, setHasConfirmationData] = useState(false);
|
|
37
38
|
const [isPublicKeyModalOpen, setIsPublicKeyModalOpen] = useState(false);
|
|
38
39
|
const { keyId: publicSigningKeyId, publicKeyPem } = getCurrentPublicSigningKeyDetails();
|
|
40
|
+
const {
|
|
41
|
+
handleOverlayMouseDown,
|
|
42
|
+
handleOverlayKeyDown
|
|
43
|
+
} = useOverlayDismiss({
|
|
44
|
+
isOpen,
|
|
45
|
+
onClose,
|
|
46
|
+
canDismiss: !isPublicKeyModalOpen
|
|
47
|
+
});
|
|
39
48
|
|
|
40
49
|
// Update caseNumber when currentCaseNumber prop changes
|
|
41
50
|
useEffect(() => {
|
|
@@ -100,23 +109,6 @@ export const CaseExport = ({
|
|
|
100
109
|
}
|
|
101
110
|
}, [isOpen]);
|
|
102
111
|
|
|
103
|
-
// Handle Escape key to close modal
|
|
104
|
-
useEffect(() => {
|
|
105
|
-
const handleEscapeKey = (event: KeyboardEvent) => {
|
|
106
|
-
if (event.key === 'Escape' && isOpen && !isPublicKeyModalOpen) {
|
|
107
|
-
onClose();
|
|
108
|
-
}
|
|
109
|
-
};
|
|
110
|
-
|
|
111
|
-
if (isOpen) {
|
|
112
|
-
document.addEventListener('keydown', handleEscapeKey);
|
|
113
|
-
}
|
|
114
|
-
|
|
115
|
-
return () => {
|
|
116
|
-
document.removeEventListener('keydown', handleEscapeKey);
|
|
117
|
-
};
|
|
118
|
-
}, [isOpen, isPublicKeyModalOpen, onClose]);
|
|
119
|
-
|
|
120
112
|
if (!isOpen) return null;
|
|
121
113
|
|
|
122
114
|
const handleExport = async () => {
|
|
@@ -182,23 +174,6 @@ export const CaseExport = ({
|
|
|
182
174
|
}
|
|
183
175
|
};
|
|
184
176
|
|
|
185
|
-
const handleOverlayMouseDown = (e: React.MouseEvent<HTMLDivElement>) => {
|
|
186
|
-
if (e.target === e.currentTarget) {
|
|
187
|
-
onClose();
|
|
188
|
-
}
|
|
189
|
-
};
|
|
190
|
-
|
|
191
|
-
const handleOverlayKeyDown = (e: React.KeyboardEvent<HTMLDivElement>) => {
|
|
192
|
-
if (e.target !== e.currentTarget) {
|
|
193
|
-
return;
|
|
194
|
-
}
|
|
195
|
-
|
|
196
|
-
if (e.key === 'Enter' || e.key === ' ') {
|
|
197
|
-
e.preventDefault();
|
|
198
|
-
onClose();
|
|
199
|
-
}
|
|
200
|
-
};
|
|
201
|
-
|
|
202
177
|
return (
|
|
203
178
|
<div
|
|
204
179
|
className={styles.overlay}
|
|
@@ -521,6 +521,7 @@
|
|
|
521
521
|
justify-content: center;
|
|
522
522
|
align-items: center;
|
|
523
523
|
z-index: 9999;
|
|
524
|
+
cursor: default;
|
|
524
525
|
}
|
|
525
526
|
|
|
526
527
|
.confirmationModal {
|
|
@@ -530,6 +531,7 @@
|
|
|
530
531
|
max-width: 400px;
|
|
531
532
|
box-shadow: 0 var(--spaceM) var(--spaceXL)
|
|
532
533
|
color-mix(in lab, var(--black) 20%, transparent);
|
|
534
|
+
cursor: default;
|
|
533
535
|
}
|
|
534
536
|
|
|
535
537
|
.confirmationContent {
|
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import { useState, useEffect, useRef, useContext, useCallback } from 'react';
|
|
2
2
|
import { AuthContext } from '~/contexts/auth.context';
|
|
3
|
+
import { useOverlayDismiss } from '~/hooks/useOverlayDismiss';
|
|
3
4
|
import {
|
|
4
5
|
listReadOnlyCases,
|
|
5
6
|
deleteReadOnlyCase
|
|
@@ -49,6 +50,15 @@ export const CaseImport = ({
|
|
|
49
50
|
resetImportState,
|
|
50
51
|
setImportProgress
|
|
51
52
|
} = useImportState();
|
|
53
|
+
const canDismissOverlay = !importState.isImporting && !importState.isClearing;
|
|
54
|
+
const {
|
|
55
|
+
handleOverlayMouseDown,
|
|
56
|
+
handleOverlayKeyDown
|
|
57
|
+
} = useOverlayDismiss({
|
|
58
|
+
isOpen,
|
|
59
|
+
onClose,
|
|
60
|
+
canDismiss: canDismissOverlay
|
|
61
|
+
});
|
|
52
62
|
|
|
53
63
|
const [existingReadOnlyCase, setExistingReadOnlyCase] = useState<string | null>(null);
|
|
54
64
|
const fileInputRef = useRef<HTMLInputElement>(null);
|
|
@@ -229,23 +239,6 @@ export const CaseImport = ({
|
|
|
229
239
|
onClose();
|
|
230
240
|
}, [clearImportData, onClose]);
|
|
231
241
|
|
|
232
|
-
const handleOverlayMouseDown = useCallback((e: React.MouseEvent<HTMLDivElement>) => {
|
|
233
|
-
if (e.target === e.currentTarget && !importState.isImporting && !importState.isClearing) {
|
|
234
|
-
onClose();
|
|
235
|
-
}
|
|
236
|
-
}, [importState.isImporting, importState.isClearing, onClose]);
|
|
237
|
-
|
|
238
|
-
const handleOverlayKeyDown = useCallback((e: React.KeyboardEvent<HTMLDivElement>) => {
|
|
239
|
-
if (e.target !== e.currentTarget || importState.isImporting || importState.isClearing) {
|
|
240
|
-
return;
|
|
241
|
-
}
|
|
242
|
-
|
|
243
|
-
if (e.key === 'Enter' || e.key === ' ') {
|
|
244
|
-
e.preventDefault();
|
|
245
|
-
onClose();
|
|
246
|
-
}
|
|
247
|
-
}, [importState.isImporting, importState.isClearing, onClose]);
|
|
248
|
-
|
|
249
242
|
// Effects
|
|
250
243
|
useEffect(() => {
|
|
251
244
|
if (user && isOpen) {
|
|
@@ -253,23 +246,6 @@ export const CaseImport = ({
|
|
|
253
246
|
}
|
|
254
247
|
}, [user, isOpen, checkForExistingReadOnlyCase]);
|
|
255
248
|
|
|
256
|
-
// Handle keyboard events
|
|
257
|
-
useEffect(() => {
|
|
258
|
-
const handleEscapeKey = (event: KeyboardEvent) => {
|
|
259
|
-
if (event.key === 'Escape' && isOpen && !importState.isImporting && !importState.isClearing) {
|
|
260
|
-
onClose();
|
|
261
|
-
}
|
|
262
|
-
};
|
|
263
|
-
|
|
264
|
-
if (isOpen) {
|
|
265
|
-
document.addEventListener('keydown', handleEscapeKey);
|
|
266
|
-
}
|
|
267
|
-
|
|
268
|
-
return () => {
|
|
269
|
-
document.removeEventListener('keydown', handleEscapeKey);
|
|
270
|
-
};
|
|
271
|
-
}, [isOpen, onClose, importState.isImporting, importState.isClearing]);
|
|
272
|
-
|
|
273
249
|
// Reset state when modal closes
|
|
274
250
|
useEffect(() => {
|
|
275
251
|
if (!isOpen) {
|
|
@@ -6,6 +6,7 @@
|
|
|
6
6
|
justify-content: center;
|
|
7
7
|
align-items: center;
|
|
8
8
|
z-index: var(--zIndex5);
|
|
9
|
+
cursor: default;
|
|
9
10
|
transition: background-color var(--durationM) var(--bezierFastoutSlowin);
|
|
10
11
|
}
|
|
11
12
|
|
|
@@ -15,7 +16,9 @@
|
|
|
15
16
|
width: 90%;
|
|
16
17
|
max-width: var(--maxWidthL);
|
|
17
18
|
max-height: 80vh;
|
|
18
|
-
box-shadow: 0 var(--spaceXS) var(--spaceL)
|
|
19
|
+
box-shadow: 0 var(--spaceXS) var(--spaceL)
|
|
20
|
+
color-mix(in lab, var(--black) 10%, transparent);
|
|
21
|
+
cursor: default;
|
|
19
22
|
transition: background-color var(--durationM) var(--bezierFastoutSlowin);
|
|
20
23
|
display: flex;
|
|
21
24
|
flex-direction: column;
|
|
@@ -134,33 +137,65 @@
|
|
|
134
137
|
|
|
135
138
|
/* Case Confirmation Status Indicators */
|
|
136
139
|
.caseItemNotConfirmed {
|
|
137
|
-
background-color: color-mix(
|
|
140
|
+
background-color: color-mix(
|
|
141
|
+
in lab,
|
|
142
|
+
var(--warning) 15%,
|
|
143
|
+
var(--backgroundLight)
|
|
144
|
+
);
|
|
138
145
|
}
|
|
139
146
|
|
|
140
147
|
.caseItemNotConfirmed:hover {
|
|
141
|
-
background-color: color-mix(
|
|
148
|
+
background-color: color-mix(
|
|
149
|
+
in lab,
|
|
150
|
+
var(--warning) 20%,
|
|
151
|
+
var(--backgroundLight)
|
|
152
|
+
);
|
|
142
153
|
}
|
|
143
154
|
|
|
144
155
|
.caseItem.active.caseItemNotConfirmed {
|
|
145
|
-
background-color: color-mix(
|
|
156
|
+
background-color: color-mix(
|
|
157
|
+
in lab,
|
|
158
|
+
var(--warning) 15%,
|
|
159
|
+
var(--backgroundLight)
|
|
160
|
+
);
|
|
146
161
|
}
|
|
147
162
|
|
|
148
163
|
.caseItem.active.caseItemNotConfirmed:hover {
|
|
149
|
-
background-color: color-mix(
|
|
164
|
+
background-color: color-mix(
|
|
165
|
+
in lab,
|
|
166
|
+
var(--warning) 20%,
|
|
167
|
+
var(--backgroundLight)
|
|
168
|
+
);
|
|
150
169
|
}
|
|
151
170
|
|
|
152
171
|
.caseItemConfirmed {
|
|
153
|
-
background-color: color-mix(
|
|
172
|
+
background-color: color-mix(
|
|
173
|
+
in lab,
|
|
174
|
+
var(--success) 20%,
|
|
175
|
+
var(--backgroundLight)
|
|
176
|
+
);
|
|
154
177
|
}
|
|
155
178
|
|
|
156
179
|
.caseItemConfirmed:hover {
|
|
157
|
-
background-color: color-mix(
|
|
180
|
+
background-color: color-mix(
|
|
181
|
+
in lab,
|
|
182
|
+
var(--success) 28%,
|
|
183
|
+
var(--backgroundLight)
|
|
184
|
+
);
|
|
158
185
|
}
|
|
159
186
|
|
|
160
187
|
.caseItem.active.caseItemConfirmed {
|
|
161
|
-
background-color: color-mix(
|
|
188
|
+
background-color: color-mix(
|
|
189
|
+
in lab,
|
|
190
|
+
var(--success) 20%,
|
|
191
|
+
var(--backgroundLight)
|
|
192
|
+
);
|
|
162
193
|
}
|
|
163
194
|
|
|
164
195
|
.caseItem.active.caseItemConfirmed:hover {
|
|
165
|
-
background-color: color-mix(
|
|
196
|
+
background-color: color-mix(
|
|
197
|
+
in lab,
|
|
198
|
+
var(--success) 28%,
|
|
199
|
+
var(--backgroundLight)
|
|
200
|
+
);
|
|
166
201
|
}
|
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import { useState, useEffect } from 'react';
|
|
2
2
|
import type { User } from 'firebase/auth';
|
|
3
|
+
import { useOverlayDismiss } from '~/hooks/useOverlayDismiss';
|
|
3
4
|
import { listCases } from '~/components/actions/case-manage';
|
|
4
5
|
import { getFileAnnotations } from '~/utils/data';
|
|
5
6
|
import { fetchFiles } from '~/components/actions/image-manage';
|
|
@@ -18,6 +19,13 @@ export const CasesModal = ({ isOpen, onClose, onSelectCase, currentCase, user }:
|
|
|
18
19
|
const [isLoading, setIsLoading] = useState(false);
|
|
19
20
|
const [error, setError] = useState<string>('');
|
|
20
21
|
const [currentPage, setCurrentPage] = useState(0);
|
|
22
|
+
const {
|
|
23
|
+
handleOverlayMouseDown,
|
|
24
|
+
handleOverlayKeyDown
|
|
25
|
+
} = useOverlayDismiss({
|
|
26
|
+
isOpen,
|
|
27
|
+
onClose
|
|
28
|
+
});
|
|
21
29
|
const [caseConfirmationStatus, setCaseConfirmationStatus] = useState<{
|
|
22
30
|
[caseNum: string]: { includeConfirmation: boolean; isConfirmed: boolean }
|
|
23
31
|
}>({});
|
|
@@ -28,19 +36,6 @@ export const CasesModal = ({ isOpen, onClose, onSelectCase, currentCase, user }:
|
|
|
28
36
|
setError('');
|
|
29
37
|
};
|
|
30
38
|
|
|
31
|
-
useEffect(() => {
|
|
32
|
-
const handleEscape = (e: KeyboardEvent) => {
|
|
33
|
-
if (e.key === 'Escape') {
|
|
34
|
-
onClose();
|
|
35
|
-
}
|
|
36
|
-
};
|
|
37
|
-
|
|
38
|
-
if (isOpen) {
|
|
39
|
-
document.addEventListener('keydown', handleEscape);
|
|
40
|
-
return () => document.removeEventListener('keydown', handleEscape);
|
|
41
|
-
}
|
|
42
|
-
}, [isOpen, onClose]);
|
|
43
|
-
|
|
44
39
|
useEffect(() => {
|
|
45
40
|
if (isOpen) {
|
|
46
41
|
const loadingTimer = window.setTimeout(() => {
|
|
@@ -144,7 +139,14 @@ export const CasesModal = ({ isOpen, onClose, onSelectCase, currentCase, user }:
|
|
|
144
139
|
if (!isOpen) return null;
|
|
145
140
|
|
|
146
141
|
return (
|
|
147
|
-
<div
|
|
142
|
+
<div
|
|
143
|
+
className={styles.modalOverlay}
|
|
144
|
+
onMouseDown={handleOverlayMouseDown}
|
|
145
|
+
onKeyDown={handleOverlayKeyDown}
|
|
146
|
+
role="button"
|
|
147
|
+
tabIndex={0}
|
|
148
|
+
aria-label="Close cases dialog"
|
|
149
|
+
>
|
|
148
150
|
<div className={styles.modal}>
|
|
149
151
|
<header className={styles.modalHeader}>
|
|
150
152
|
<h2>All Cases</h2>
|
|
@@ -6,6 +6,7 @@
|
|
|
6
6
|
justify-content: center;
|
|
7
7
|
align-items: center;
|
|
8
8
|
z-index: var(--zIndex5);
|
|
9
|
+
cursor: default;
|
|
9
10
|
transition: background-color var(--durationM) var(--bezierFastoutSlowin);
|
|
10
11
|
}
|
|
11
12
|
|
|
@@ -15,7 +16,9 @@
|
|
|
15
16
|
width: 90%;
|
|
16
17
|
max-width: var(--maxWidthL);
|
|
17
18
|
max-height: 80vh;
|
|
18
|
-
box-shadow: 0 var(--spaceXS) var(--spaceL)
|
|
19
|
+
box-shadow: 0 var(--spaceXS) var(--spaceL)
|
|
20
|
+
color-mix(in lab, var(--black) 10%, transparent);
|
|
21
|
+
cursor: default;
|
|
19
22
|
transition: background-color var(--durationM) var(--bezierFastoutSlowin);
|
|
20
23
|
display: flex;
|
|
21
24
|
flex-direction: column;
|
|
@@ -177,33 +180,65 @@
|
|
|
177
180
|
}
|
|
178
181
|
/* Confirmation Status Indicators */
|
|
179
182
|
.fileItemNotConfirmed {
|
|
180
|
-
background-color: color-mix(
|
|
183
|
+
background-color: color-mix(
|
|
184
|
+
in lab,
|
|
185
|
+
var(--warning) 15%,
|
|
186
|
+
var(--backgroundLight)
|
|
187
|
+
);
|
|
181
188
|
}
|
|
182
189
|
|
|
183
190
|
.fileItemNotConfirmed:hover {
|
|
184
|
-
background-color: color-mix(
|
|
191
|
+
background-color: color-mix(
|
|
192
|
+
in lab,
|
|
193
|
+
var(--warning) 20%,
|
|
194
|
+
var(--backgroundLight)
|
|
195
|
+
);
|
|
185
196
|
}
|
|
186
197
|
|
|
187
198
|
.fileItem.active.fileItemNotConfirmed {
|
|
188
|
-
background-color: color-mix(
|
|
199
|
+
background-color: color-mix(
|
|
200
|
+
in lab,
|
|
201
|
+
var(--warning) 15%,
|
|
202
|
+
var(--backgroundLight)
|
|
203
|
+
);
|
|
189
204
|
}
|
|
190
205
|
|
|
191
206
|
.fileItem.active.fileItemNotConfirmed:hover {
|
|
192
|
-
background-color: color-mix(
|
|
207
|
+
background-color: color-mix(
|
|
208
|
+
in lab,
|
|
209
|
+
var(--warning) 20%,
|
|
210
|
+
var(--backgroundLight)
|
|
211
|
+
);
|
|
193
212
|
}
|
|
194
213
|
|
|
195
214
|
.fileItemConfirmed {
|
|
196
|
-
background-color: color-mix(
|
|
215
|
+
background-color: color-mix(
|
|
216
|
+
in lab,
|
|
217
|
+
var(--success) 20%,
|
|
218
|
+
var(--backgroundLight)
|
|
219
|
+
);
|
|
197
220
|
}
|
|
198
221
|
|
|
199
222
|
.fileItemConfirmed:hover {
|
|
200
|
-
background-color: color-mix(
|
|
223
|
+
background-color: color-mix(
|
|
224
|
+
in lab,
|
|
225
|
+
var(--success) 28%,
|
|
226
|
+
var(--backgroundLight)
|
|
227
|
+
);
|
|
201
228
|
}
|
|
202
229
|
|
|
203
230
|
.fileItem.active.fileItemConfirmed {
|
|
204
|
-
background-color: color-mix(
|
|
231
|
+
background-color: color-mix(
|
|
232
|
+
in lab,
|
|
233
|
+
var(--success) 20%,
|
|
234
|
+
var(--backgroundLight)
|
|
235
|
+
);
|
|
205
236
|
}
|
|
206
237
|
|
|
207
238
|
.fileItem.active.fileItemConfirmed:hover {
|
|
208
|
-
background-color: color-mix(
|
|
209
|
-
|
|
239
|
+
background-color: color-mix(
|
|
240
|
+
in lab,
|
|
241
|
+
var(--success) 28%,
|
|
242
|
+
var(--backgroundLight)
|
|
243
|
+
);
|
|
244
|
+
}
|