@striae-org/striae 4.1.0 → 4.2.1
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/.env.example +8 -0
- package/LICENSE +1 -1
- package/app/components/actions/case-export/core-export.ts +14 -8
- package/app/components/actions/case-export/data-processing.ts +1 -0
- package/app/components/actions/case-export/download-handlers.ts +7 -0
- package/app/components/actions/case-export/metadata-helpers.ts +2 -1
- package/app/components/actions/case-import/confirmation-import.ts +12 -2
- package/app/components/actions/case-import/orchestrator.ts +78 -32
- package/app/components/actions/case-import/storage-operations.ts +97 -8
- package/app/components/actions/case-import/zip-processing.ts +159 -86
- package/app/components/actions/case-manage.ts +463 -8
- package/app/components/actions/confirm-export.ts +9 -2
- package/app/components/actions/image-manage.ts +77 -44
- package/app/components/audit/user-audit-viewer.tsx +19 -8
- package/app/components/audit/user-audit.module.css +21 -0
- package/app/components/audit/viewer/audit-entries-list.tsx +12 -2
- package/app/components/audit/viewer/audit-filters-panel.tsx +1 -0
- package/app/components/audit/viewer/audit-viewer-utils.ts +2 -0
- package/app/components/audit/viewer/use-audit-viewer-data.ts +24 -1
- package/app/components/audit/viewer/use-audit-viewer-export.ts +1 -1
- package/app/components/canvas/box-annotations/box-annotations.module.css +22 -18
- package/app/components/canvas/box-annotations/box-annotations.tsx +15 -0
- package/app/components/canvas/canvas.module.css +64 -54
- package/app/components/canvas/canvas.tsx +14 -16
- package/app/components/canvas/confirmation/confirmation.module.css +1 -0
- package/app/components/canvas/confirmation/confirmation.tsx +12 -14
- package/app/components/colors/colors.module.css +4 -3
- package/app/components/navbar/case-modals/archive-case-modal.module.css +110 -0
- package/app/components/navbar/case-modals/archive-case-modal.tsx +129 -0
- package/app/components/navbar/case-modals/open-case-modal.module.css +81 -0
- package/app/components/navbar/case-modals/open-case-modal.tsx +120 -0
- package/app/components/navbar/case-modals/rename-case-modal.module.css +81 -0
- package/app/components/navbar/case-modals/rename-case-modal.tsx +107 -0
- package/app/components/navbar/navbar.module.css +447 -0
- package/app/components/navbar/navbar.tsx +402 -0
- 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 +15 -16
- package/app/components/sidebar/case-export/case-export.module.css +1 -0
- package/app/components/sidebar/case-export/case-export.tsx +8 -46
- package/app/components/sidebar/case-import/case-import.module.css +23 -0
- package/app/components/sidebar/case-import/case-import.tsx +64 -16
- package/app/components/sidebar/case-import/components/CasePreviewSection.tsx +20 -1
- package/app/components/sidebar/case-import/components/ConfirmationDialog.tsx +15 -0
- package/app/components/sidebar/cases/case-sidebar.tsx +68 -588
- package/app/components/sidebar/cases/cases-modal.module.css +1 -0
- package/app/components/sidebar/cases/cases-modal.tsx +82 -43
- package/app/components/sidebar/cases/cases.module.css +82 -21
- package/app/components/sidebar/files/files-modal.module.css +1 -0
- package/app/components/sidebar/files/files-modal.tsx +49 -52
- package/app/components/sidebar/notes/addl-notes-modal.tsx +82 -0
- package/app/components/sidebar/notes/{notes-sidebar.tsx → notes-editor-form.tsx} +187 -138
- package/app/components/sidebar/notes/notes-editor-modal.module.css +49 -0
- package/app/components/sidebar/notes/notes-editor-modal.tsx +64 -0
- package/app/components/sidebar/notes/notes.module.css +170 -1
- package/app/components/sidebar/sidebar-container.tsx +16 -28
- package/app/components/sidebar/sidebar.module.css +5 -69
- package/app/components/sidebar/sidebar.tsx +27 -125
- package/app/components/sidebar/upload/image-upload-zone.module.css +13 -13
- package/app/components/user/inactivity-warning.module.css +1 -0
- package/app/components/user/inactivity-warning.tsx +15 -2
- package/app/components/user/manage-profile.tsx +23 -10
- package/app/{tailwind.css → global.css} +1 -3
- package/app/hooks/useOverlayDismiss.ts +54 -4
- package/app/root.tsx +1 -1
- package/app/routes/auth/login.tsx +785 -774
- package/app/routes/striae/striae.module.css +10 -3
- package/app/routes/striae/striae.tsx +475 -30
- package/app/services/audit/audit.service.ts +173 -27
- package/app/services/audit/builders/audit-event-builders-case-file.ts +43 -0
- package/app/services/audit/builders/audit-event-builders-workflow.ts +2 -0
- package/app/services/audit/builders/index.ts +1 -0
- package/app/types/audit.ts +4 -1
- package/app/types/case.ts +29 -0
- package/app/types/import.ts +3 -0
- package/app/utils/data/confirmation-summary/summary-core.ts +279 -0
- package/app/utils/data/data-operations.ts +17 -861
- package/app/utils/data/index.ts +11 -1
- package/app/utils/data/operations/batch-operations.ts +113 -0
- package/app/utils/data/operations/case-operations.ts +168 -0
- package/app/utils/data/operations/confirmation-summary-operations.ts +301 -0
- package/app/utils/data/operations/file-annotation-operations.ts +196 -0
- package/app/utils/data/operations/index.ts +7 -0
- package/app/utils/data/operations/signing-operations.ts +225 -0
- package/app/utils/data/operations/types.ts +42 -0
- package/app/utils/data/operations/validation-operations.ts +48 -0
- package/app/utils/data/permissions.ts +16 -1
- package/app/utils/forensics/audit-export-signature.ts +5 -1
- package/app/utils/forensics/confirmation-signature.ts +3 -0
- package/app/utils/forensics/export-verification.ts +426 -22
- package/functions/api/_shared/firebase-auth.ts +2 -7
- package/functions/api/image/[[path]].ts +20 -23
- package/functions/api/pdf/[[path]].ts +27 -8
- package/package.json +7 -12
- package/scripts/deploy-primershear-emails.sh +2 -1
- package/worker-configuration.d.ts +3 -3
- package/workers/audit-worker/package.json +1 -1
- package/workers/audit-worker/worker-configuration.d.ts +7448 -11323
- package/workers/audit-worker/wrangler.jsonc.example +1 -1
- package/workers/data-worker/package.json +1 -1
- package/workers/data-worker/worker-configuration.d.ts +7448 -11323
- package/workers/data-worker/wrangler.jsonc.example +1 -1
- package/workers/image-worker/package.json +1 -1
- package/workers/image-worker/src/image-worker.example.ts +16 -5
- package/workers/image-worker/worker-configuration.d.ts +7447 -11322
- package/workers/image-worker/wrangler.jsonc.example +1 -1
- package/workers/keys-worker/package.json +1 -1
- package/workers/keys-worker/worker-configuration.d.ts +7447 -11322
- package/workers/keys-worker/wrangler.jsonc.example +1 -1
- package/workers/pdf-worker/package.json +1 -1
- package/workers/pdf-worker/src/formats/format-striae.ts +9 -14
- package/workers/pdf-worker/src/pdf-worker.example.ts +37 -58
- package/workers/pdf-worker/src/report-types.ts +3 -3
- package/workers/pdf-worker/worker-configuration.d.ts +7448 -11323
- package/workers/pdf-worker/wrangler.jsonc.example +1 -1
- package/workers/user-worker/package.json +1 -1
- package/workers/user-worker/src/user-worker.example.ts +17 -0
- package/workers/user-worker/worker-configuration.d.ts +7448 -11323
- package/workers/user-worker/wrangler.jsonc.example +1 -1
- package/wrangler.toml.example +1 -1
- package/NOTICE +0 -13
- package/app/components/sidebar/notes/notes-modal.tsx +0 -53
- package/postcss.config.js +0 -6
- package/public/.well-known/keybase.txt +0 -56
- package/tailwind.config.ts +0 -22
|
@@ -5,6 +5,7 @@ import {
|
|
|
5
5
|
listReadOnlyCases,
|
|
6
6
|
deleteReadOnlyCase
|
|
7
7
|
} from '~/components/actions/case-review';
|
|
8
|
+
import { listCases } from '~/components/actions/case-manage';
|
|
8
9
|
import {
|
|
9
10
|
type ImportResult,
|
|
10
11
|
type ConfirmationImportResult
|
|
@@ -52,8 +53,9 @@ export const CaseImport = ({
|
|
|
52
53
|
} = useImportState();
|
|
53
54
|
const canDismissOverlay = !importState.isImporting && !importState.isClearing;
|
|
54
55
|
const {
|
|
55
|
-
|
|
56
|
-
|
|
56
|
+
requestClose,
|
|
57
|
+
overlayProps,
|
|
58
|
+
getCloseButtonProps
|
|
57
59
|
} = useOverlayDismiss({
|
|
58
60
|
isOpen,
|
|
59
61
|
onClose,
|
|
@@ -61,11 +63,13 @@ export const CaseImport = ({
|
|
|
61
63
|
});
|
|
62
64
|
|
|
63
65
|
const [existingReadOnlyCase, setExistingReadOnlyCase] = useState<string | null>(null);
|
|
66
|
+
const [showArchivedRegularCaseRiskWarning, setShowArchivedRegularCaseRiskWarning] = useState(false);
|
|
64
67
|
const fileInputRef = useRef<HTMLInputElement>(null);
|
|
65
68
|
|
|
66
69
|
// Clear import selection state (used by preview hook on validation failure)
|
|
67
70
|
const clearImportSelection = useCallback(() => {
|
|
68
71
|
updateImportState({ selectedFile: null, importType: null });
|
|
72
|
+
setShowArchivedRegularCaseRiskWarning(false);
|
|
69
73
|
resetFileInput(fileInputRef);
|
|
70
74
|
}, [updateImportState]);
|
|
71
75
|
|
|
@@ -123,10 +127,21 @@ export const CaseImport = ({
|
|
|
123
127
|
updateImportState({ isClearing: true });
|
|
124
128
|
|
|
125
129
|
try {
|
|
126
|
-
await deleteReadOnlyCase(user, existingReadOnlyCase);
|
|
127
|
-
|
|
128
130
|
const clearedCaseName = existingReadOnlyCase;
|
|
129
|
-
|
|
131
|
+
const deleteSuccess = await deleteReadOnlyCase(user, clearedCaseName);
|
|
132
|
+
const remainingReadOnlyCases = await listReadOnlyCases(user);
|
|
133
|
+
const stillExists = remainingReadOnlyCases.some((caseMeta) => caseMeta.caseNumber === clearedCaseName);
|
|
134
|
+
|
|
135
|
+
setExistingReadOnlyCase(remainingReadOnlyCases.length > 0 ? remainingReadOnlyCases[0].caseNumber : null);
|
|
136
|
+
|
|
137
|
+
if (!deleteSuccess || stillExists) {
|
|
138
|
+
setError(
|
|
139
|
+
`Failed to fully clear read-only case "${clearedCaseName}". ` +
|
|
140
|
+
'Please try again. If this was an archived import that overlaps a regular case, verify that all case images are accessible before retrying.'
|
|
141
|
+
);
|
|
142
|
+
return;
|
|
143
|
+
}
|
|
144
|
+
|
|
130
145
|
setSuccess(`Removed read-only case "${clearedCaseName}"`);
|
|
131
146
|
|
|
132
147
|
onImportComplete?.({
|
|
@@ -236,8 +251,8 @@ export const CaseImport = ({
|
|
|
236
251
|
|
|
237
252
|
const handleModalCancel = useCallback(() => {
|
|
238
253
|
clearImportData();
|
|
239
|
-
|
|
240
|
-
}, [clearImportData,
|
|
254
|
+
requestClose();
|
|
255
|
+
}, [clearImportData, requestClose]);
|
|
241
256
|
|
|
242
257
|
// Effects
|
|
243
258
|
useEffect(() => {
|
|
@@ -246,6 +261,42 @@ export const CaseImport = ({
|
|
|
246
261
|
}
|
|
247
262
|
}, [user, isOpen, checkForExistingReadOnlyCase]);
|
|
248
263
|
|
|
264
|
+
useEffect(() => {
|
|
265
|
+
let isMounted = true;
|
|
266
|
+
|
|
267
|
+
const checkArchivedRegularCaseRisk = async () => {
|
|
268
|
+
if (
|
|
269
|
+
!user ||
|
|
270
|
+
!isOpen ||
|
|
271
|
+
importState.importType !== 'case' ||
|
|
272
|
+
!casePreview?.archived ||
|
|
273
|
+
!casePreview.caseNumber
|
|
274
|
+
) {
|
|
275
|
+
if (isMounted) {
|
|
276
|
+
setShowArchivedRegularCaseRiskWarning(false);
|
|
277
|
+
}
|
|
278
|
+
return;
|
|
279
|
+
}
|
|
280
|
+
|
|
281
|
+
try {
|
|
282
|
+
const regularCases = await listCases(user);
|
|
283
|
+
if (isMounted) {
|
|
284
|
+
setShowArchivedRegularCaseRiskWarning(regularCases.includes(casePreview.caseNumber));
|
|
285
|
+
}
|
|
286
|
+
} catch {
|
|
287
|
+
if (isMounted) {
|
|
288
|
+
setShowArchivedRegularCaseRiskWarning(false);
|
|
289
|
+
}
|
|
290
|
+
}
|
|
291
|
+
};
|
|
292
|
+
|
|
293
|
+
void checkArchivedRegularCaseRisk();
|
|
294
|
+
|
|
295
|
+
return () => {
|
|
296
|
+
isMounted = false;
|
|
297
|
+
};
|
|
298
|
+
}, [user, isOpen, importState.importType, casePreview?.archived, casePreview?.caseNumber]);
|
|
299
|
+
|
|
249
300
|
// Reset state when modal closes
|
|
250
301
|
useEffect(() => {
|
|
251
302
|
if (!isOpen) {
|
|
@@ -265,20 +316,15 @@ export const CaseImport = ({
|
|
|
265
316
|
<>
|
|
266
317
|
<div
|
|
267
318
|
className={styles.overlay}
|
|
268
|
-
onMouseDown={handleOverlayMouseDown}
|
|
269
|
-
onKeyDown={handleOverlayKeyDown}
|
|
270
|
-
role="button"
|
|
271
|
-
tabIndex={0}
|
|
272
319
|
aria-label="Close case import dialog"
|
|
320
|
+
{...overlayProps}
|
|
273
321
|
>
|
|
274
322
|
<div className={styles.modal}>
|
|
275
323
|
<div className={styles.header}>
|
|
276
|
-
<h2 className={styles.title}>Import
|
|
277
|
-
<button
|
|
324
|
+
<h2 className={styles.title}>Import Case or Confirmations</h2>
|
|
325
|
+
<button
|
|
278
326
|
className={styles.closeButton}
|
|
279
|
-
|
|
280
|
-
aria-label="Close modal"
|
|
281
|
-
disabled={importState.isImporting || importState.isClearing}
|
|
327
|
+
{...getCloseButtonProps({ ariaLabel: 'Close case import dialog' })}
|
|
282
328
|
>
|
|
283
329
|
×
|
|
284
330
|
</button>
|
|
@@ -316,6 +362,7 @@ export const CaseImport = ({
|
|
|
316
362
|
<CasePreviewSection
|
|
317
363
|
casePreview={casePreview}
|
|
318
364
|
isLoadingPreview={importState.isLoadingPreview}
|
|
365
|
+
showArchivedRegularCaseRiskWarning={showArchivedRegularCaseRiskWarning}
|
|
319
366
|
/>
|
|
320
367
|
)}
|
|
321
368
|
|
|
@@ -407,6 +454,7 @@ export const CaseImport = ({
|
|
|
407
454
|
<ConfirmationDialog
|
|
408
455
|
showConfirmation={importState.showConfirmation}
|
|
409
456
|
casePreview={casePreview}
|
|
457
|
+
showArchivedRegularCaseRiskWarning={showArchivedRegularCaseRiskWarning}
|
|
410
458
|
onConfirm={handleConfirmImport}
|
|
411
459
|
onCancel={handleCancelImport}
|
|
412
460
|
/>
|
|
@@ -4,9 +4,14 @@ import styles from '../case-import.module.css';
|
|
|
4
4
|
interface CasePreviewSectionProps {
|
|
5
5
|
casePreview: CaseImportPreview | null;
|
|
6
6
|
isLoadingPreview: boolean;
|
|
7
|
+
showArchivedRegularCaseRiskWarning?: boolean;
|
|
7
8
|
}
|
|
8
9
|
|
|
9
|
-
export const CasePreviewSection = ({
|
|
10
|
+
export const CasePreviewSection = ({
|
|
11
|
+
casePreview,
|
|
12
|
+
isLoadingPreview,
|
|
13
|
+
showArchivedRegularCaseRiskWarning = false
|
|
14
|
+
}: CasePreviewSectionProps) => {
|
|
10
15
|
if (isLoadingPreview) {
|
|
11
16
|
return (
|
|
12
17
|
<div className={styles.previewSection}>
|
|
@@ -24,6 +29,16 @@ export const CasePreviewSection = ({ casePreview, isLoadingPreview }: CasePrevie
|
|
|
24
29
|
{/* Case Information - Always Blue */}
|
|
25
30
|
<div className={styles.previewSection}>
|
|
26
31
|
<h3 className={styles.previewTitle}>Case Information</h3>
|
|
32
|
+
{casePreview.archived && (
|
|
33
|
+
<div className={styles.archivedImportNote}>
|
|
34
|
+
Archived export detected. Original exporter imports are allowed for archived cases.
|
|
35
|
+
</div>
|
|
36
|
+
)}
|
|
37
|
+
{showArchivedRegularCaseRiskWarning && (
|
|
38
|
+
<div className={styles.archivedRegularCaseRiskNote}>
|
|
39
|
+
Warning: This archived import matches a case already in your regular case list. If you later clear the imported read-only case, the regular case images will be deleted and become inaccessible.
|
|
40
|
+
</div>
|
|
41
|
+
)}
|
|
27
42
|
<div className={styles.previewGrid}>
|
|
28
43
|
<div className={styles.previewItem}>
|
|
29
44
|
<span className={styles.previewLabel}>Case Number:</span>
|
|
@@ -49,6 +64,10 @@ export const CasePreviewSection = ({ casePreview, isLoadingPreview }: CasePrevie
|
|
|
49
64
|
<span className={styles.previewLabel}>Total Images:</span>
|
|
50
65
|
<span className={styles.previewValue}>{casePreview.totalFiles}</span>
|
|
51
66
|
</div>
|
|
67
|
+
<div className={styles.previewItem}>
|
|
68
|
+
<span className={styles.previewLabel}>Archived Export:</span>
|
|
69
|
+
<span className={styles.previewValue}>{casePreview.archived ? 'Yes' : 'No'}</span>
|
|
70
|
+
</div>
|
|
52
71
|
</div>
|
|
53
72
|
</div>
|
|
54
73
|
|
|
@@ -4,6 +4,7 @@ import styles from '../case-import.module.css';
|
|
|
4
4
|
interface ConfirmationDialogProps {
|
|
5
5
|
showConfirmation: boolean;
|
|
6
6
|
casePreview: CaseImportPreview | null;
|
|
7
|
+
showArchivedRegularCaseRiskWarning?: boolean;
|
|
7
8
|
onConfirm: () => void;
|
|
8
9
|
onCancel: () => void;
|
|
9
10
|
}
|
|
@@ -11,6 +12,7 @@ interface ConfirmationDialogProps {
|
|
|
11
12
|
export const ConfirmationDialog = ({
|
|
12
13
|
showConfirmation,
|
|
13
14
|
casePreview,
|
|
15
|
+
showArchivedRegularCaseRiskWarning = false,
|
|
14
16
|
onConfirm,
|
|
15
17
|
onCancel
|
|
16
18
|
}: ConfirmationDialogProps) => {
|
|
@@ -41,6 +43,19 @@ export const ConfirmationDialog = ({
|
|
|
41
43
|
<div className={styles.confirmationItem}>
|
|
42
44
|
<strong>Total Images:</strong> {casePreview.totalFiles}
|
|
43
45
|
</div>
|
|
46
|
+
<div className={styles.confirmationItem}>
|
|
47
|
+
<strong>Archived Export:</strong> {casePreview.archived ? 'Yes' : 'No'}
|
|
48
|
+
</div>
|
|
49
|
+
{casePreview.archived && (
|
|
50
|
+
<div className={styles.archivedImportNote}>
|
|
51
|
+
Archived export detected. Original exporter imports are allowed for archived cases.
|
|
52
|
+
</div>
|
|
53
|
+
)}
|
|
54
|
+
{showArchivedRegularCaseRiskWarning && (
|
|
55
|
+
<div className={styles.archivedRegularCaseRiskNote}>
|
|
56
|
+
Warning: This archived import matches a case in your regular case list. If you clear the imported read-only case later, the regular case images will be deleted and inaccessible.
|
|
57
|
+
</div>
|
|
58
|
+
)}
|
|
44
59
|
{casePreview.hashValid !== undefined && (
|
|
45
60
|
<div className={`${styles.confirmationItem} ${casePreview.hashValid ? styles.confirmationItemValid : styles.confirmationItemInvalid}`}>
|
|
46
61
|
<strong>Data Integrity:</strong>
|