@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.
Files changed (124) hide show
  1. package/.env.example +8 -0
  2. package/LICENSE +1 -1
  3. package/app/components/actions/case-export/core-export.ts +14 -8
  4. package/app/components/actions/case-export/data-processing.ts +1 -0
  5. package/app/components/actions/case-export/download-handlers.ts +7 -0
  6. package/app/components/actions/case-export/metadata-helpers.ts +2 -1
  7. package/app/components/actions/case-import/confirmation-import.ts +12 -2
  8. package/app/components/actions/case-import/orchestrator.ts +78 -32
  9. package/app/components/actions/case-import/storage-operations.ts +97 -8
  10. package/app/components/actions/case-import/zip-processing.ts +159 -86
  11. package/app/components/actions/case-manage.ts +463 -8
  12. package/app/components/actions/confirm-export.ts +9 -2
  13. package/app/components/actions/image-manage.ts +77 -44
  14. package/app/components/audit/user-audit-viewer.tsx +19 -8
  15. package/app/components/audit/user-audit.module.css +21 -0
  16. package/app/components/audit/viewer/audit-entries-list.tsx +12 -2
  17. package/app/components/audit/viewer/audit-filters-panel.tsx +1 -0
  18. package/app/components/audit/viewer/audit-viewer-utils.ts +2 -0
  19. package/app/components/audit/viewer/use-audit-viewer-data.ts +24 -1
  20. package/app/components/audit/viewer/use-audit-viewer-export.ts +1 -1
  21. package/app/components/canvas/box-annotations/box-annotations.module.css +22 -18
  22. package/app/components/canvas/box-annotations/box-annotations.tsx +15 -0
  23. package/app/components/canvas/canvas.module.css +64 -54
  24. package/app/components/canvas/canvas.tsx +14 -16
  25. package/app/components/canvas/confirmation/confirmation.module.css +1 -0
  26. package/app/components/canvas/confirmation/confirmation.tsx +12 -14
  27. package/app/components/colors/colors.module.css +4 -3
  28. package/app/components/navbar/case-modals/archive-case-modal.module.css +110 -0
  29. package/app/components/navbar/case-modals/archive-case-modal.tsx +129 -0
  30. package/app/components/navbar/case-modals/open-case-modal.module.css +81 -0
  31. package/app/components/navbar/case-modals/open-case-modal.tsx +120 -0
  32. package/app/components/navbar/case-modals/rename-case-modal.module.css +81 -0
  33. package/app/components/navbar/case-modals/rename-case-modal.tsx +107 -0
  34. package/app/components/navbar/navbar.module.css +447 -0
  35. package/app/components/navbar/navbar.tsx +402 -0
  36. package/app/components/public-signing-key-modal/public-signing-key-modal.module.css +1 -0
  37. package/app/components/public-signing-key-modal/public-signing-key-modal.tsx +15 -16
  38. package/app/components/sidebar/case-export/case-export.module.css +1 -0
  39. package/app/components/sidebar/case-export/case-export.tsx +8 -46
  40. package/app/components/sidebar/case-import/case-import.module.css +23 -0
  41. package/app/components/sidebar/case-import/case-import.tsx +64 -16
  42. package/app/components/sidebar/case-import/components/CasePreviewSection.tsx +20 -1
  43. package/app/components/sidebar/case-import/components/ConfirmationDialog.tsx +15 -0
  44. package/app/components/sidebar/cases/case-sidebar.tsx +68 -588
  45. package/app/components/sidebar/cases/cases-modal.module.css +1 -0
  46. package/app/components/sidebar/cases/cases-modal.tsx +82 -43
  47. package/app/components/sidebar/cases/cases.module.css +82 -21
  48. package/app/components/sidebar/files/files-modal.module.css +1 -0
  49. package/app/components/sidebar/files/files-modal.tsx +49 -52
  50. package/app/components/sidebar/notes/addl-notes-modal.tsx +82 -0
  51. package/app/components/sidebar/notes/{notes-sidebar.tsx → notes-editor-form.tsx} +187 -138
  52. package/app/components/sidebar/notes/notes-editor-modal.module.css +49 -0
  53. package/app/components/sidebar/notes/notes-editor-modal.tsx +64 -0
  54. package/app/components/sidebar/notes/notes.module.css +170 -1
  55. package/app/components/sidebar/sidebar-container.tsx +16 -28
  56. package/app/components/sidebar/sidebar.module.css +5 -69
  57. package/app/components/sidebar/sidebar.tsx +27 -125
  58. package/app/components/sidebar/upload/image-upload-zone.module.css +13 -13
  59. package/app/components/user/inactivity-warning.module.css +1 -0
  60. package/app/components/user/inactivity-warning.tsx +15 -2
  61. package/app/components/user/manage-profile.tsx +23 -10
  62. package/app/{tailwind.css → global.css} +1 -3
  63. package/app/hooks/useOverlayDismiss.ts +54 -4
  64. package/app/root.tsx +1 -1
  65. package/app/routes/auth/login.tsx +785 -774
  66. package/app/routes/striae/striae.module.css +10 -3
  67. package/app/routes/striae/striae.tsx +475 -30
  68. package/app/services/audit/audit.service.ts +173 -27
  69. package/app/services/audit/builders/audit-event-builders-case-file.ts +43 -0
  70. package/app/services/audit/builders/audit-event-builders-workflow.ts +2 -0
  71. package/app/services/audit/builders/index.ts +1 -0
  72. package/app/types/audit.ts +4 -1
  73. package/app/types/case.ts +29 -0
  74. package/app/types/import.ts +3 -0
  75. package/app/utils/data/confirmation-summary/summary-core.ts +279 -0
  76. package/app/utils/data/data-operations.ts +17 -861
  77. package/app/utils/data/index.ts +11 -1
  78. package/app/utils/data/operations/batch-operations.ts +113 -0
  79. package/app/utils/data/operations/case-operations.ts +168 -0
  80. package/app/utils/data/operations/confirmation-summary-operations.ts +301 -0
  81. package/app/utils/data/operations/file-annotation-operations.ts +196 -0
  82. package/app/utils/data/operations/index.ts +7 -0
  83. package/app/utils/data/operations/signing-operations.ts +225 -0
  84. package/app/utils/data/operations/types.ts +42 -0
  85. package/app/utils/data/operations/validation-operations.ts +48 -0
  86. package/app/utils/data/permissions.ts +16 -1
  87. package/app/utils/forensics/audit-export-signature.ts +5 -1
  88. package/app/utils/forensics/confirmation-signature.ts +3 -0
  89. package/app/utils/forensics/export-verification.ts +426 -22
  90. package/functions/api/_shared/firebase-auth.ts +2 -7
  91. package/functions/api/image/[[path]].ts +20 -23
  92. package/functions/api/pdf/[[path]].ts +27 -8
  93. package/package.json +7 -12
  94. package/scripts/deploy-primershear-emails.sh +2 -1
  95. package/worker-configuration.d.ts +3 -3
  96. package/workers/audit-worker/package.json +1 -1
  97. package/workers/audit-worker/worker-configuration.d.ts +7448 -11323
  98. package/workers/audit-worker/wrangler.jsonc.example +1 -1
  99. package/workers/data-worker/package.json +1 -1
  100. package/workers/data-worker/worker-configuration.d.ts +7448 -11323
  101. package/workers/data-worker/wrangler.jsonc.example +1 -1
  102. package/workers/image-worker/package.json +1 -1
  103. package/workers/image-worker/src/image-worker.example.ts +16 -5
  104. package/workers/image-worker/worker-configuration.d.ts +7447 -11322
  105. package/workers/image-worker/wrangler.jsonc.example +1 -1
  106. package/workers/keys-worker/package.json +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/package.json +1 -1
  110. package/workers/pdf-worker/src/formats/format-striae.ts +9 -14
  111. package/workers/pdf-worker/src/pdf-worker.example.ts +37 -58
  112. package/workers/pdf-worker/src/report-types.ts +3 -3
  113. package/workers/pdf-worker/worker-configuration.d.ts +7448 -11323
  114. package/workers/pdf-worker/wrangler.jsonc.example +1 -1
  115. package/workers/user-worker/package.json +1 -1
  116. package/workers/user-worker/src/user-worker.example.ts +17 -0
  117. package/workers/user-worker/worker-configuration.d.ts +7448 -11323
  118. package/workers/user-worker/wrangler.jsonc.example +1 -1
  119. package/wrangler.toml.example +1 -1
  120. package/NOTICE +0 -13
  121. package/app/components/sidebar/notes/notes-modal.tsx +0 -53
  122. package/postcss.config.js +0 -6
  123. package/public/.well-known/keybase.txt +0 -56
  124. package/tailwind.config.ts +0 -22
@@ -0,0 +1,81 @@
1
+ .overlay {
2
+ position: fixed;
3
+ inset: 0;
4
+ background: rgba(0, 0, 0, 0.45);
5
+ display: flex;
6
+ align-items: center;
7
+ justify-content: center;
8
+ z-index: 120;
9
+ }
10
+
11
+ .modal {
12
+ position: relative;
13
+ width: min(460px, calc(100vw - 2rem));
14
+ background: #ffffff;
15
+ border-radius: 12px;
16
+ border: 1px solid #d9e0e7;
17
+ box-shadow: 0 14px 36px rgba(0, 0, 0, 0.2);
18
+ padding: 1.1rem;
19
+ }
20
+
21
+ .title {
22
+ margin: 0;
23
+ color: #212529;
24
+ font-size: 1.02rem;
25
+ }
26
+
27
+ .helperText {
28
+ margin: 0.4rem 0 0.9rem;
29
+ color: #6c757d;
30
+ font-size: 0.85rem;
31
+ }
32
+
33
+ .input {
34
+ width: 100%;
35
+ box-sizing: border-box;
36
+ border: 1px solid #cdd5dd;
37
+ border-radius: 8px;
38
+ padding: 0.6rem 0.75rem;
39
+ font-size: 0.92rem;
40
+ }
41
+
42
+ .input:focus {
43
+ outline: none;
44
+ border-color: #1f6feb;
45
+ box-shadow: 0 0 0 2px rgba(31, 111, 235, 0.2);
46
+ }
47
+
48
+ .actions {
49
+ display: flex;
50
+ justify-content: flex-end;
51
+ gap: 0.65rem;
52
+ margin-top: 1rem;
53
+ }
54
+
55
+ .cancelButton,
56
+ .confirmButton {
57
+ border: 1px solid transparent;
58
+ border-radius: 8px;
59
+ padding: 0.55rem 0.9rem;
60
+ font-size: 0.86rem;
61
+ font-weight: 500;
62
+ cursor: pointer;
63
+ }
64
+
65
+ .cancelButton {
66
+ background: #f3f4f6;
67
+ color: #3c4651;
68
+ border-color: #d6dce2;
69
+ }
70
+
71
+ .confirmButton {
72
+ background: #198754;
73
+ color: #ffffff;
74
+ border-color: #157347;
75
+ }
76
+
77
+ .cancelButton:disabled,
78
+ .confirmButton:disabled {
79
+ cursor: not-allowed;
80
+ opacity: 0.6;
81
+ }
@@ -0,0 +1,120 @@
1
+ import { useEffect, useRef, useState } from 'react';
2
+ import { useOverlayDismiss } from '~/hooks/useOverlayDismiss';
3
+ import styles from './open-case-modal.module.css';
4
+
5
+ interface OpenCaseModalProps {
6
+ isOpen: boolean;
7
+ isSubmitting?: boolean;
8
+ title?: string;
9
+ helperText?: string;
10
+ onClose: () => void;
11
+ onSubmit: (caseNumber: string) => Promise<void>;
12
+ }
13
+
14
+ export const OpenCaseModal = ({
15
+ isOpen,
16
+ isSubmitting = false,
17
+ title = 'Open Case',
18
+ helperText,
19
+ onClose,
20
+ onSubmit,
21
+ }: OpenCaseModalProps) => {
22
+ const [caseNumber, setCaseNumber] = useState('');
23
+ const inputRef = useRef<HTMLInputElement>(null);
24
+ const isCloseBlocked = isSubmitting;
25
+
26
+ useEffect(() => {
27
+ if (!isOpen) {
28
+ return;
29
+ }
30
+
31
+ const focusId = window.requestAnimationFrame(() => {
32
+ inputRef.current?.focus();
33
+ });
34
+
35
+ return () => {
36
+ window.cancelAnimationFrame(focusId);
37
+ };
38
+ }, [isOpen]);
39
+
40
+ const handleClose = () => {
41
+ if (isCloseBlocked) {
42
+ return;
43
+ }
44
+
45
+ setCaseNumber('');
46
+ onClose();
47
+ };
48
+
49
+ const {
50
+ requestClose,
51
+ overlayProps,
52
+ getCloseButtonProps,
53
+ } = useOverlayDismiss({
54
+ isOpen,
55
+ onClose: handleClose,
56
+ canDismiss: !isCloseBlocked,
57
+ });
58
+
59
+ const handleSubmit = async () => {
60
+ const trimmedCaseNumber = caseNumber.trim();
61
+ if (!trimmedCaseNumber || isSubmitting) {
62
+ return;
63
+ }
64
+
65
+ await onSubmit(trimmedCaseNumber);
66
+ setCaseNumber('');
67
+ };
68
+
69
+ if (!isOpen) return null;
70
+
71
+ return (
72
+ <div
73
+ className={styles.overlay}
74
+ aria-label="Close open case dialog"
75
+ {...overlayProps}
76
+ >
77
+ <div className={styles.modal} role="dialog" aria-modal="true" aria-label="Open Case">
78
+ <button {...getCloseButtonProps({ ariaLabel: 'Close open case dialog' })}>
79
+ ×
80
+ </button>
81
+ <h3 className={styles.title}>{title}</h3>
82
+ {helperText ? <p className={styles.helperText}>{helperText}</p> : null}
83
+ <input
84
+ ref={inputRef}
85
+ type="text"
86
+ className={styles.input}
87
+ value={caseNumber}
88
+ onChange={(event) => setCaseNumber(event.target.value)}
89
+ placeholder="Case #"
90
+ disabled={isSubmitting}
91
+ onKeyDown={(event) => {
92
+ if (event.key === 'Enter') {
93
+ void handleSubmit();
94
+ }
95
+ }}
96
+ />
97
+ <div className={styles.actions}>
98
+ <button
99
+ type="button"
100
+ className={styles.cancelButton}
101
+ onClick={requestClose}
102
+ disabled={isCloseBlocked}
103
+ >
104
+ Cancel
105
+ </button>
106
+ <button
107
+ type="button"
108
+ className={styles.confirmButton}
109
+ onClick={() => {
110
+ void handleSubmit();
111
+ }}
112
+ disabled={isSubmitting || !caseNumber.trim()}
113
+ >
114
+ {isSubmitting ? 'Opening...' : 'Load/Create Case'}
115
+ </button>
116
+ </div>
117
+ </div>
118
+ </div>
119
+ );
120
+ };
@@ -0,0 +1,81 @@
1
+ .overlay {
2
+ position: fixed;
3
+ inset: 0;
4
+ background: rgba(0, 0, 0, 0.45);
5
+ display: flex;
6
+ align-items: center;
7
+ justify-content: center;
8
+ z-index: 120;
9
+ }
10
+
11
+ .modal {
12
+ position: relative;
13
+ width: min(460px, calc(100vw - 2rem));
14
+ background: #ffffff;
15
+ border-radius: 12px;
16
+ border: 1px solid #d9e0e7;
17
+ box-shadow: 0 14px 36px rgba(0, 0, 0, 0.2);
18
+ padding: 1.1rem;
19
+ }
20
+
21
+ .title {
22
+ margin: 0;
23
+ color: #212529;
24
+ font-size: 1.02rem;
25
+ }
26
+
27
+ .subtitle {
28
+ margin: 0.4rem 0 0.9rem;
29
+ color: #6c757d;
30
+ font-size: 0.85rem;
31
+ }
32
+
33
+ .input {
34
+ width: 100%;
35
+ box-sizing: border-box;
36
+ border: 1px solid #cdd5dd;
37
+ border-radius: 8px;
38
+ padding: 0.6rem 0.75rem;
39
+ font-size: 0.92rem;
40
+ }
41
+
42
+ .input:focus {
43
+ outline: none;
44
+ border-color: #1f6feb;
45
+ box-shadow: 0 0 0 2px rgba(31, 111, 235, 0.2);
46
+ }
47
+
48
+ .actions {
49
+ display: flex;
50
+ justify-content: flex-end;
51
+ gap: 0.65rem;
52
+ margin-top: 1rem;
53
+ }
54
+
55
+ .cancelButton,
56
+ .confirmButton {
57
+ border: 1px solid transparent;
58
+ border-radius: 8px;
59
+ padding: 0.55rem 0.9rem;
60
+ font-size: 0.86rem;
61
+ font-weight: 500;
62
+ cursor: pointer;
63
+ }
64
+
65
+ .cancelButton {
66
+ background: #f3f4f6;
67
+ color: #3c4651;
68
+ border-color: #d6dce2;
69
+ }
70
+
71
+ .confirmButton {
72
+ background: #ffc107;
73
+ color: #3f2f00;
74
+ border-color: #e8b103;
75
+ }
76
+
77
+ .cancelButton:disabled,
78
+ .confirmButton:disabled {
79
+ cursor: not-allowed;
80
+ opacity: 0.6;
81
+ }
@@ -0,0 +1,107 @@
1
+ import { useEffect, useRef, useState } from 'react';
2
+ import { useOverlayDismiss } from '~/hooks/useOverlayDismiss';
3
+ import styles from './rename-case-modal.module.css';
4
+
5
+ interface RenameCaseModalProps {
6
+ isOpen: boolean;
7
+ currentCase: string;
8
+ isSubmitting?: boolean;
9
+ onClose: () => void;
10
+ onSubmit: (newCaseName: string) => Promise<void>;
11
+ }
12
+
13
+ export const RenameCaseModal = ({
14
+ isOpen,
15
+ currentCase,
16
+ isSubmitting = false,
17
+ onClose,
18
+ onSubmit,
19
+ }: RenameCaseModalProps) => {
20
+ const [newCaseName, setNewCaseName] = useState('');
21
+ const inputRef = useRef<HTMLInputElement>(null);
22
+ const isCloseBlocked = isSubmitting;
23
+
24
+ const handleClose = () => {
25
+ setNewCaseName('');
26
+ onClose();
27
+ };
28
+
29
+ const {
30
+ requestClose,
31
+ overlayProps,
32
+ getCloseButtonProps,
33
+ } = useOverlayDismiss({
34
+ isOpen,
35
+ onClose: handleClose,
36
+ canDismiss: !isCloseBlocked,
37
+ });
38
+
39
+ useEffect(() => {
40
+ if (!isOpen) {
41
+ return;
42
+ }
43
+
44
+ const focusId = window.requestAnimationFrame(() => {
45
+ inputRef.current?.focus();
46
+ });
47
+
48
+ return () => {
49
+ window.cancelAnimationFrame(focusId);
50
+ };
51
+ }, [isOpen]);
52
+
53
+ if (!isOpen) return null;
54
+
55
+ const handleSubmit = async () => {
56
+ await onSubmit(newCaseName.trim());
57
+ setNewCaseName('');
58
+ };
59
+
60
+ return (
61
+ <div
62
+ className={styles.overlay}
63
+ aria-label="Close rename case dialog"
64
+ {...overlayProps}
65
+ >
66
+ <div className={styles.modal} role="dialog" aria-modal="true" aria-label="Rename Case">
67
+ <button {...getCloseButtonProps({ ariaLabel: 'Close rename case dialog' })}>
68
+ ×
69
+ </button>
70
+ <h3 className={styles.title}>Rename Case</h3>
71
+ <p className={styles.subtitle}>Current case: {currentCase}</p>
72
+ <input
73
+ ref={inputRef}
74
+ type="text"
75
+ value={newCaseName}
76
+ onChange={(event) => setNewCaseName(event.target.value)}
77
+ className={styles.input}
78
+ placeholder="New case number"
79
+ disabled={isSubmitting}
80
+ onKeyDown={(event) => {
81
+ if (event.key === 'Enter' && newCaseName.trim() && !isSubmitting) {
82
+ void handleSubmit();
83
+ }
84
+ }}
85
+ />
86
+ <div className={styles.actions}>
87
+ <button
88
+ type="button"
89
+ className={styles.cancelButton}
90
+ onClick={requestClose}
91
+ disabled={isCloseBlocked}
92
+ >
93
+ Cancel
94
+ </button>
95
+ <button
96
+ type="button"
97
+ className={styles.confirmButton}
98
+ onClick={() => void handleSubmit()}
99
+ disabled={isSubmitting || !newCaseName.trim()}
100
+ >
101
+ {isSubmitting ? 'Renaming...' : 'Rename Case'}
102
+ </button>
103
+ </div>
104
+ </div>
105
+ </div>
106
+ );
107
+ };