@striae-org/striae 4.0.3 → 4.2.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.
Files changed (118) hide show
  1. package/.env.example +8 -0
  2. package/app/components/actions/case-export/core-export.ts +14 -8
  3. package/app/components/actions/case-export/data-processing.ts +1 -0
  4. package/app/components/actions/case-export/download-handlers.ts +7 -0
  5. package/app/components/actions/case-export/metadata-helpers.ts +2 -1
  6. package/app/components/actions/case-import/confirmation-import.ts +12 -2
  7. package/app/components/actions/case-import/orchestrator.ts +78 -32
  8. package/app/components/actions/case-import/storage-operations.ts +97 -8
  9. package/app/components/actions/case-import/zip-processing.ts +159 -86
  10. package/app/components/actions/case-manage.ts +430 -8
  11. package/app/components/actions/confirm-export.ts +13 -4
  12. package/app/components/actions/generate-pdf.ts +10 -2
  13. package/app/components/actions/image-manage.ts +77 -44
  14. package/app/components/audit/user-audit-viewer.tsx +137 -945
  15. package/app/components/audit/user-audit.module.css +41 -0
  16. package/app/components/audit/viewer/audit-activity-summary.tsx +52 -0
  17. package/app/components/audit/viewer/audit-entries-list.tsx +207 -0
  18. package/app/components/audit/viewer/audit-filters-panel.tsx +307 -0
  19. package/app/components/audit/viewer/audit-user-info-card.tsx +44 -0
  20. package/app/components/audit/viewer/audit-viewer-header.tsx +55 -0
  21. package/app/components/audit/viewer/audit-viewer-utils.ts +123 -0
  22. package/app/components/audit/viewer/types.ts +1 -0
  23. package/app/components/audit/viewer/use-audit-viewer-data.ts +186 -0
  24. package/app/components/audit/viewer/use-audit-viewer-export.ts +176 -0
  25. package/app/components/audit/viewer/use-audit-viewer-filters.ts +141 -0
  26. package/app/components/auth/mfa-enrollment.module.css +13 -5
  27. package/app/components/auth/mfa-verification.module.css +13 -5
  28. package/app/components/canvas/box-annotations/box-annotations.module.css +22 -18
  29. package/app/components/canvas/box-annotations/box-annotations.tsx +15 -0
  30. package/app/components/canvas/canvas.module.css +64 -54
  31. package/app/components/canvas/canvas.tsx +17 -16
  32. package/app/components/canvas/confirmation/confirmation.module.css +1 -0
  33. package/app/components/canvas/confirmation/confirmation.tsx +17 -47
  34. package/app/components/navbar/case-modals/archive-case-modal.module.css +110 -0
  35. package/app/components/navbar/case-modals/archive-case-modal.tsx +129 -0
  36. package/app/components/navbar/case-modals/open-case-modal.module.css +81 -0
  37. package/app/components/navbar/case-modals/open-case-modal.tsx +120 -0
  38. package/app/components/navbar/case-modals/rename-case-modal.module.css +81 -0
  39. package/app/components/navbar/case-modals/rename-case-modal.tsx +107 -0
  40. package/app/components/navbar/navbar.module.css +447 -0
  41. package/app/components/navbar/navbar.tsx +377 -0
  42. package/app/components/public-signing-key-modal/public-signing-key-modal.module.css +2 -0
  43. package/app/components/public-signing-key-modal/public-signing-key-modal.tsx +21 -51
  44. package/app/components/sidebar/case-export/case-export.module.css +1 -0
  45. package/app/components/sidebar/case-export/case-export.tsx +14 -77
  46. package/app/components/sidebar/case-import/case-import.module.css +25 -0
  47. package/app/components/sidebar/case-import/case-import.tsx +64 -40
  48. package/app/components/sidebar/case-import/components/CasePreviewSection.tsx +20 -1
  49. package/app/components/sidebar/case-import/components/ConfirmationDialog.tsx +15 -0
  50. package/app/components/sidebar/cases/case-sidebar.tsx +25 -519
  51. package/app/components/sidebar/cases/cases-modal.module.css +45 -9
  52. package/app/components/sidebar/cases/cases-modal.tsx +16 -16
  53. package/app/components/sidebar/cases/cases.module.css +62 -21
  54. package/app/components/sidebar/files/files-modal.module.css +46 -10
  55. package/app/components/sidebar/files/files-modal.tsx +22 -23
  56. package/app/components/sidebar/notes/notes-editor-modal.module.css +49 -0
  57. package/app/components/sidebar/notes/notes-editor-modal.tsx +66 -0
  58. package/app/components/sidebar/notes/notes-modal.tsx +18 -17
  59. package/app/components/sidebar/notes/notes-sidebar.tsx +199 -113
  60. package/app/components/sidebar/notes/notes.module.css +155 -0
  61. package/app/components/sidebar/sidebar-container.tsx +15 -28
  62. package/app/components/sidebar/sidebar.module.css +7 -71
  63. package/app/components/sidebar/sidebar.tsx +24 -125
  64. package/app/components/sidebar/upload/image-upload-zone.module.css +13 -13
  65. package/app/components/toast/toast.module.css +2 -1
  66. package/app/components/toast/toast.tsx +16 -11
  67. package/app/components/user/delete-account.tsx +10 -31
  68. package/app/components/user/inactivity-warning.module.css +9 -6
  69. package/app/components/user/inactivity-warning.tsx +15 -2
  70. package/app/components/user/manage-profile.module.css +2 -0
  71. package/app/components/user/manage-profile.tsx +108 -40
  72. package/app/hooks/useOverlayDismiss.ts +116 -0
  73. package/app/routes/auth/login.example.tsx +19 -8
  74. package/app/routes/auth/login.tsx +785 -774
  75. package/app/routes/auth/passwordReset.module.css +23 -13
  76. package/app/routes/striae/striae.module.css +10 -3
  77. package/app/routes/striae/striae.tsx +477 -31
  78. package/app/routes.ts +7 -0
  79. package/app/services/audit/audit-export-csv.ts +2 -0
  80. package/app/services/audit/audit.service.ts +202 -32
  81. package/app/services/audit/builders/audit-entry-builder.ts +2 -1
  82. package/app/services/audit/builders/audit-event-builders-case-file.ts +43 -0
  83. package/app/services/audit/builders/audit-event-builders-user-security.ts +4 -2
  84. package/app/services/audit/builders/audit-event-builders-workflow.ts +8 -0
  85. package/app/services/audit/builders/index.ts +1 -0
  86. package/app/types/audit.ts +5 -2
  87. package/app/types/case.ts +29 -0
  88. package/app/types/import.ts +3 -0
  89. package/app/types/user.ts +1 -0
  90. package/app/utils/data/permissions.ts +17 -1
  91. package/app/utils/forensics/audit-export-signature.ts +5 -1
  92. package/app/utils/forensics/confirmation-signature.ts +3 -0
  93. package/app/utils/forensics/export-verification.ts +497 -22
  94. package/functions/api/pdf/[[path]].ts +32 -1
  95. package/load-context.ts +9 -0
  96. package/package.json +6 -2
  97. package/primershear.emails.example +6 -0
  98. package/scripts/deploy-pages-secrets.sh +6 -0
  99. package/scripts/deploy-primershear-emails.sh +167 -0
  100. package/worker-configuration.d.ts +7493 -7491
  101. package/workers/audit-worker/worker-configuration.d.ts +7448 -11323
  102. package/workers/audit-worker/wrangler.jsonc.example +1 -1
  103. package/workers/data-worker/worker-configuration.d.ts +7448 -11323
  104. package/workers/data-worker/wrangler.jsonc.example +1 -1
  105. package/workers/image-worker/worker-configuration.d.ts +7447 -11322
  106. package/workers/image-worker/wrangler.jsonc.example +1 -1
  107. package/workers/keys-worker/worker-configuration.d.ts +7447 -11322
  108. package/workers/keys-worker/wrangler.jsonc.example +1 -1
  109. package/workers/pdf-worker/src/formats/format-striae.ts +8 -7
  110. package/workers/pdf-worker/src/pdf-worker.example.ts +3 -0
  111. package/workers/pdf-worker/src/report-types.ts +3 -0
  112. package/workers/pdf-worker/worker-configuration.d.ts +7448 -11323
  113. package/workers/pdf-worker/wrangler.jsonc.example +1 -1
  114. package/workers/user-worker/src/user-worker.example.ts +6 -1
  115. package/workers/user-worker/worker-configuration.d.ts +7448 -11323
  116. package/workers/user-worker/wrangler.jsonc.example +1 -1
  117. package/wrangler.toml.example +1 -1
  118. package/public/.well-known/keybase.txt +0 -56
@@ -1,8 +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 { PublicSigningKeyModal } from '~/components/public-signing-key-modal/public-signing-key-modal';
5
- import { getCurrentPublicSigningKeyDetails } from '~/utils/forensics';
4
+ import { useOverlayDismiss } from '~/hooks/useOverlayDismiss';
6
5
  import { getCaseConfirmations, exportConfirmationData } from '../../actions/confirm-export';
7
6
 
8
7
  export type ExportFormat = 'json' | 'csv';
@@ -34,8 +33,14 @@ export const CaseExport = ({
34
33
  const [selectedFormat, setSelectedFormat] = useState<ExportFormat>('json');
35
34
  const [includeImages, setIncludeImages] = useState(false);
36
35
  const [hasConfirmationData, setHasConfirmationData] = useState(false);
37
- const [isPublicKeyModalOpen, setIsPublicKeyModalOpen] = useState(false);
38
- const { keyId: publicSigningKeyId, publicKeyPem } = getCurrentPublicSigningKeyDetails();
36
+ const {
37
+ requestClose,
38
+ overlayProps,
39
+ getCloseButtonProps
40
+ } = useOverlayDismiss({
41
+ isOpen,
42
+ onClose,
43
+ });
39
44
 
40
45
  // Update caseNumber when currentCaseNumber prop changes
41
46
  useEffect(() => {
@@ -94,29 +99,6 @@ export const CaseExport = ({
94
99
  }
95
100
  }, [isReadOnly]);
96
101
 
97
- useEffect(() => {
98
- if (!isOpen) {
99
- setIsPublicKeyModalOpen(false);
100
- }
101
- }, [isOpen]);
102
-
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
102
  if (!isOpen) return null;
121
103
 
122
104
  const handleExport = async () => {
@@ -133,7 +115,7 @@ export const CaseExport = ({
133
115
  await onExport(caseNumber.trim(), selectedFormat, includeImages, (progress, label) => {
134
116
  setExportProgress({ current: progress, total: 100, caseName: label, mode: 'single' });
135
117
  });
136
- onClose();
118
+ requestClose();
137
119
  } catch (error) {
138
120
  console.error('Export failed:', error);
139
121
  setError(error instanceof Error ? error.message : 'Export failed. Please try again.');
@@ -152,7 +134,7 @@ export const CaseExport = ({
152
134
  await onExportAll((current: number, total: number, caseName: string) => {
153
135
  setExportProgress({ current, total, caseName });
154
136
  }, selectedFormat);
155
- onClose();
137
+ requestClose();
156
138
  } catch (error) {
157
139
  console.error('Export all failed:', error);
158
140
  setError(error instanceof Error ? error.message : 'Export all cases failed. Please try again.');
@@ -173,7 +155,7 @@ export const CaseExport = ({
173
155
 
174
156
  try {
175
157
  await exportConfirmationData(user, caseNumber.trim());
176
- onClose();
158
+ requestClose();
177
159
  } catch (error) {
178
160
  console.error('Confirmation export failed:', error);
179
161
  setError(error instanceof Error ? error.message : 'Confirmation export failed. Please try again.');
@@ -182,40 +164,16 @@ export const CaseExport = ({
182
164
  }
183
165
  };
184
166
 
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
167
  return (
203
168
  <div
204
169
  className={styles.overlay}
205
- onMouseDown={handleOverlayMouseDown}
206
- onKeyDown={handleOverlayKeyDown}
207
- role="button"
208
- tabIndex={0}
209
170
  aria-label="Close case export dialog"
171
+ {...overlayProps}
210
172
  >
211
173
  <div className={styles.modal}>
212
174
  <div className={styles.header}>
213
175
  <h2 className={styles.title}>Export Case Data</h2>
214
- <button
215
- className={styles.closeButton}
216
- onClick={onClose}
217
- aria-label="Close modal"
218
- >
176
+ <button className={styles.closeButton} {...getCloseButtonProps({ ariaLabel: 'Close case export dialog' })}>
219
177
  ×
220
178
  </button>
221
179
  </div>
@@ -338,20 +296,6 @@ export const CaseExport = ({
338
296
  </div>
339
297
  </div>
340
298
  )}
341
-
342
- <div className={styles.divider}>
343
- <span>Verification</span>
344
- </div>
345
-
346
- <div className={styles.publicKeySection}>
347
- <button
348
- type="button"
349
- className={styles.publicKeyButton}
350
- onClick={() => setIsPublicKeyModalOpen(true)}
351
- >
352
- View Public Signing Key
353
- </button>
354
- </div>
355
299
 
356
300
  {error && (
357
301
  <div className={styles.error}>
@@ -361,13 +305,6 @@ export const CaseExport = ({
361
305
  </div>
362
306
  </div>
363
307
  </div>
364
-
365
- <PublicSigningKeyModal
366
- isOpen={isPublicKeyModalOpen}
367
- onClose={() => setIsPublicKeyModalOpen(false)}
368
- publicSigningKeyId={publicSigningKeyId}
369
- publicKeyPem={publicKeyPem}
370
- />
371
308
  </div>
372
309
  );
373
310
  };
@@ -11,6 +11,7 @@
11
11
  }
12
12
 
13
13
  .modal {
14
+ position: relative;
14
15
  background: var(--backgroundLight);
15
16
  border-radius: var(--spaceXS);
16
17
  width: 90%;
@@ -424,6 +425,28 @@
424
425
  color: var(--textTitle);
425
426
  }
426
427
 
428
+ .archivedImportNote {
429
+ margin-bottom: var(--spaceM);
430
+ padding: var(--spaceS) var(--spaceM);
431
+ border-radius: var(--spaceXS);
432
+ background: color-mix(in lab, var(--success) 10%, transparent);
433
+ border: 1px solid color-mix(in lab, var(--success) 25%, transparent);
434
+ color: color-mix(in lab, var(--success) 80%, var(--black));
435
+ font-size: var(--fontSizeBodyXS);
436
+ font-weight: var(--fontWeightMedium);
437
+ }
438
+
439
+ .archivedRegularCaseRiskNote {
440
+ margin-bottom: var(--spaceM);
441
+ padding: var(--spaceS) var(--spaceM);
442
+ border-radius: var(--spaceXS);
443
+ background: color-mix(in lab, var(--warning) 12%, transparent);
444
+ border: 1px solid color-mix(in lab, var(--warning) 30%, transparent);
445
+ color: color-mix(in lab, var(--warning) 85%, var(--black));
446
+ font-size: var(--fontSizeBodyXS);
447
+ font-weight: var(--fontWeightMedium);
448
+ }
449
+
427
450
  /* Validation Section - Green/Red Based on Status */
428
451
  .validationSection {
429
452
  border-radius: var(--spaceXS);
@@ -521,6 +544,7 @@
521
544
  justify-content: center;
522
545
  align-items: center;
523
546
  z-index: 9999;
547
+ cursor: default;
524
548
  }
525
549
 
526
550
  .confirmationModal {
@@ -530,6 +554,7 @@
530
554
  max-width: 400px;
531
555
  box-shadow: 0 var(--spaceM) var(--spaceXL)
532
556
  color-mix(in lab, var(--black) 20%, transparent);
557
+ cursor: default;
533
558
  }
534
559
 
535
560
  .confirmationContent {
@@ -1,9 +1,11 @@
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
6
7
  } from '~/components/actions/case-review';
8
+ import { listCases } from '~/components/actions/case-manage';
7
9
  import {
8
10
  type ImportResult,
9
11
  type ConfirmationImportResult
@@ -49,13 +51,25 @@ export const CaseImport = ({
49
51
  resetImportState,
50
52
  setImportProgress
51
53
  } = useImportState();
54
+ const canDismissOverlay = !importState.isImporting && !importState.isClearing;
55
+ const {
56
+ requestClose,
57
+ overlayProps,
58
+ getCloseButtonProps
59
+ } = useOverlayDismiss({
60
+ isOpen,
61
+ onClose,
62
+ canDismiss: canDismissOverlay
63
+ });
52
64
 
53
65
  const [existingReadOnlyCase, setExistingReadOnlyCase] = useState<string | null>(null);
66
+ const [showArchivedRegularCaseRiskWarning, setShowArchivedRegularCaseRiskWarning] = useState(false);
54
67
  const fileInputRef = useRef<HTMLInputElement>(null);
55
68
 
56
69
  // Clear import selection state (used by preview hook on validation failure)
57
70
  const clearImportSelection = useCallback(() => {
58
71
  updateImportState({ selectedFile: null, importType: null });
72
+ setShowArchivedRegularCaseRiskWarning(false);
59
73
  resetFileInput(fileInputRef);
60
74
  }, [updateImportState]);
61
75
 
@@ -113,10 +127,21 @@ export const CaseImport = ({
113
127
  updateImportState({ isClearing: true });
114
128
 
115
129
  try {
116
- await deleteReadOnlyCase(user, existingReadOnlyCase);
117
-
118
130
  const clearedCaseName = existingReadOnlyCase;
119
- setExistingReadOnlyCase(null);
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
+
120
145
  setSuccess(`Removed read-only case "${clearedCaseName}"`);
121
146
 
122
147
  onImportComplete?.({
@@ -226,25 +251,8 @@ export const CaseImport = ({
226
251
 
227
252
  const handleModalCancel = useCallback(() => {
228
253
  clearImportData();
229
- onClose();
230
- }, [clearImportData, onClose]);
231
-
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]);
254
+ requestClose();
255
+ }, [clearImportData, requestClose]);
248
256
 
249
257
  // Effects
250
258
  useEffect(() => {
@@ -253,22 +261,41 @@ export const CaseImport = ({
253
261
  }
254
262
  }, [user, isOpen, checkForExistingReadOnlyCase]);
255
263
 
256
- // Handle keyboard events
257
264
  useEffect(() => {
258
- const handleEscapeKey = (event: KeyboardEvent) => {
259
- if (event.key === 'Escape' && isOpen && !importState.isImporting && !importState.isClearing) {
260
- onClose();
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
+ }
261
290
  }
262
291
  };
263
292
 
264
- if (isOpen) {
265
- document.addEventListener('keydown', handleEscapeKey);
266
- }
293
+ void checkArchivedRegularCaseRisk();
267
294
 
268
295
  return () => {
269
- document.removeEventListener('keydown', handleEscapeKey);
296
+ isMounted = false;
270
297
  };
271
- }, [isOpen, onClose, importState.isImporting, importState.isClearing]);
298
+ }, [user, isOpen, importState.importType, casePreview?.archived, casePreview?.caseNumber]);
272
299
 
273
300
  // Reset state when modal closes
274
301
  useEffect(() => {
@@ -289,20 +316,15 @@ export const CaseImport = ({
289
316
  <>
290
317
  <div
291
318
  className={styles.overlay}
292
- onMouseDown={handleOverlayMouseDown}
293
- onKeyDown={handleOverlayKeyDown}
294
- role="button"
295
- tabIndex={0}
296
319
  aria-label="Close case import dialog"
320
+ {...overlayProps}
297
321
  >
298
322
  <div className={styles.modal}>
299
323
  <div className={styles.header}>
300
- <h2 className={styles.title}>Import RO Case or Confirmations</h2>
301
- <button
324
+ <h2 className={styles.title}>Import Case or Confirmations</h2>
325
+ <button
302
326
  className={styles.closeButton}
303
- onClick={onClose}
304
- aria-label="Close modal"
305
- disabled={importState.isImporting || importState.isClearing}
327
+ {...getCloseButtonProps({ ariaLabel: 'Close case import dialog' })}
306
328
  >
307
329
  ×
308
330
  </button>
@@ -340,6 +362,7 @@ export const CaseImport = ({
340
362
  <CasePreviewSection
341
363
  casePreview={casePreview}
342
364
  isLoadingPreview={importState.isLoadingPreview}
365
+ showArchivedRegularCaseRiskWarning={showArchivedRegularCaseRiskWarning}
343
366
  />
344
367
  )}
345
368
 
@@ -431,6 +454,7 @@ export const CaseImport = ({
431
454
  <ConfirmationDialog
432
455
  showConfirmation={importState.showConfirmation}
433
456
  casePreview={casePreview}
457
+ showArchivedRegularCaseRiskWarning={showArchivedRegularCaseRiskWarning}
434
458
  onConfirm={handleConfirmImport}
435
459
  onCancel={handleCancelImport}
436
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 = ({ casePreview, isLoadingPreview }: CasePreviewSectionProps) => {
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>