@striae-org/striae 6.1.5 → 6.1.7

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 CHANGED
@@ -149,4 +149,4 @@ PRIMERSHEAR_EMAILS=
149
149
  # Comma-separated list of email addresses that may register an account.
150
150
  # Leave empty to disable the feature. Never commit this value to source control.
151
151
  # Example: REGISTRATION_EMAILS=analyst@org.com,user2@org.com
152
- REGISTRATION_EMAILS=
152
+ REGISTRATION_EMAILS=
@@ -33,6 +33,7 @@ interface CasesModalProps {
33
33
  isOpen: boolean;
34
34
  onClose: () => void;
35
35
  onSelectCase: (caseNum: string) => void;
36
+ onCurrentCaseDeleted?: () => void;
36
37
  currentCase: string;
37
38
  user: User;
38
39
  confirmationSaveVersion?: number;
@@ -64,6 +65,7 @@ export const CasesModal = ({
64
65
  isOpen,
65
66
  onClose,
66
67
  onSelectCase,
68
+ onCurrentCaseDeleted,
67
69
  currentCase,
68
70
  user,
69
71
  confirmationSaveVersion = 0,
@@ -224,16 +226,14 @@ export const CasesModal = ({
224
226
  );
225
227
 
226
228
  const canDeleteSelectedCase = Boolean(
227
- selectedCase && selectedCase.caseNumber !== currentCase && !selectedCase.isReadOnly
229
+ selectedCase && !selectedCase.isReadOnly
228
230
  );
229
231
 
230
232
  const deleteSelectedCaseTitle = !selectedCase
231
233
  ? 'Select a case to delete.'
232
- : selectedCase.caseNumber === currentCase
233
- ? 'Open a different case before deleting this one.'
234
- : selectedCase.isReadOnly
235
- ? 'Read-only review cases cannot be deleted. Use Clear RO Case under Case Management first.'
236
- : undefined;
234
+ : selectedCase.isReadOnly
235
+ ? 'Read-only review cases cannot be deleted here. Use Clear RO Case under Case Management first.'
236
+ : undefined;
237
237
 
238
238
  const effectiveFocusedIndex = paginatedCases.length === 0 ? 0 : Math.min(focusedIndex, paginatedCases.length - 1);
239
239
 
@@ -475,16 +475,13 @@ export const CasesModal = ({
475
475
 
476
476
  const handleDeleteSelectedCase = async () => {
477
477
  if (!selectedCase || !canDeleteSelectedCase) {
478
- const isCurrentCaseSelection = selectedCase?.caseNumber === currentCase;
479
478
  const isReadOnlyReviewSelection = selectedCase?.isReadOnly === true;
480
479
 
481
480
  setActionNotice({
482
481
  type: 'warning',
483
- message: isCurrentCaseSelection
484
- ? 'Open a different case before deleting this one.'
485
- : isReadOnlyReviewSelection
486
- ? 'Read-only review cases cannot be deleted. Use Clear RO Case under Case Management first.'
487
- : 'Selected case cannot be deleted.',
482
+ message: isReadOnlyReviewSelection
483
+ ? 'Read-only review cases cannot be deleted here. Use Clear RO Case under Case Management first.'
484
+ : 'Selected case cannot be deleted.',
488
485
  });
489
486
  return;
490
487
  }
@@ -506,11 +503,16 @@ export const CasesModal = ({
506
503
  setActionNotice(null);
507
504
 
508
505
  try {
506
+ const wasCurrentCase = selectedCase.caseNumber === currentCase;
509
507
  const deleteResult = await deleteCase(user, selectedCase.caseNumber);
510
508
  setSelectedCaseNumber(null);
511
509
  setIsDeleteModalOpen(false);
512
510
  setRefreshKey((k) => k + 1);
513
511
 
512
+ if (wasCurrentCase) {
513
+ onCurrentCaseDeleted?.();
514
+ }
515
+
514
516
  if (deleteResult.missingImages.length > 0) {
515
517
  setActionNotice({
516
518
  type: 'warning',
@@ -113,11 +113,11 @@ export const SidebarContainer: React.FC<SidebarContainerProps> = (props) => {
113
113
  Security Policy
114
114
  </Link>
115
115
  <Link
116
- to="https://community.striae.org"
116
+ to="https://account.striae.org/p/login/dRmcMZ3HC5vVfFZ4St4wM00"
117
117
  target="_blank"
118
118
  rel="noopener noreferrer"
119
119
  className={styles.footerModalLink}>
120
- Striae Community
120
+ Manage Membership
121
121
  </Link>
122
122
  </div>
123
123
 
@@ -20,7 +20,7 @@ interface EmailActionHandlerProps {
20
20
  lang: string | null;
21
21
  }
22
22
 
23
- type HandlerState = 'loading' | 'ready-reset' | 'success' | 'error' | 'unsupported';
23
+ type HandlerState = 'loading' | 'ready-reset' | 'success' | 'error';
24
24
 
25
25
  const getUserAgent = (): string | undefined => {
26
26
  if (typeof navigator === 'undefined') {
@@ -171,16 +171,6 @@ export const EmailActionHandler = ({ mode, oobCode, continueUrl, lang }: EmailAc
171
171
  return;
172
172
  }
173
173
 
174
- if (mode === 'recoverEmail') {
175
- if (!isMounted) {
176
- return;
177
- }
178
-
179
- setState('unsupported');
180
- setError('Email change recovery is not supported for Striae accounts.');
181
- return;
182
- }
183
-
184
174
  if (!isMounted) {
185
175
  return;
186
176
  }
@@ -378,7 +368,7 @@ export const EmailActionHandler = ({ mode, oobCode, continueUrl, lang }: EmailAc
378
368
  </form>
379
369
  )}
380
370
 
381
- {(state === 'success' || state === 'error' || state === 'unsupported') && (
371
+ {(state === 'success' || state === 'error') && (
382
372
  <div className={styles.actions}>
383
373
  {showContinueButton && (
384
374
  <button
@@ -28,9 +28,7 @@ import { generateUniqueId } from '~/utils/common';
28
28
  import { evaluatePasswordPolicy, buildActionCodeSettings, userHasMFA } from '~/utils/auth';
29
29
  import type { UserData } from '~/types';
30
30
 
31
- const DEMO_COMPANY_NAME = 'STRIAE DEMO';
32
-
33
- const SUPPORTED_EMAIL_ACTION_MODES = new Set(['resetPassword', 'verifyEmail', 'recoverEmail']);
31
+ const SUPPORTED_EMAIL_ACTION_MODES = new Set(['resetPassword', 'verifyEmail']);
34
32
 
35
33
  const getUserFirstName = (user: User): string => {
36
34
  const displayName = user.displayName?.trim();
@@ -69,7 +67,7 @@ export const Login = () => {
69
67
  const [isClient, setIsClient] = useState(false);
70
68
  const [firstName, setFirstName] = useState('');
71
69
  const [lastName, setLastName] = useState('');
72
- const [company, setCompany] = useState(DEMO_COMPANY_NAME);
70
+ const [company, setCompany] = useState('');
73
71
  const [badgeId, setBadgeId] = useState('');
74
72
  const [confirmPasswordValue, setConfirmPasswordValue] = useState('');
75
73
 
@@ -22,7 +22,7 @@ import { fetchUserApi } from '~/utils/api';
22
22
  import { type AnnotationData, type FileData, type ExportOptions } from '~/types';
23
23
  import { validateCaseNumber, renameCase, deleteCase, checkExistingCase, createNewCase, archiveCase, deriveCaseArchiveDetails } from '~/components/actions/case-manage';
24
24
  import { checkReadOnlyCaseExists, deleteReadOnlyCase } from '~/components/actions/case-review';
25
- import { canCreateCase, getCaseConfirmationSummary, getCaseData, getConfirmationSummaryDocument, type UserConfirmationSummaryDocument } from '~/utils/data';
25
+ import { canCreateCase, ensureCaseConfirmationSummary, getCaseData, getConfirmationSummaryDocument, type UserConfirmationSummaryDocument } from '~/utils/data';
26
26
  import {
27
27
  resolveEarliestAnnotationTimestamp,
28
28
  CREATE_READ_ONLY_CASE_EXISTS_ERROR,
@@ -382,7 +382,9 @@ export const Striae = ({ user }: StriaePage) => {
382
382
  if (!currentCase || !user) return;
383
383
 
384
384
  try {
385
- const summary = await getCaseConfirmationSummary(user, currentCase);
385
+ const summary = await ensureCaseConfirmationSummary(user, currentCase, files, {
386
+ forceRefresh: true,
387
+ });
386
388
  const filesById = summary?.filesById ?? {};
387
389
  const values = Object.values(filesById);
388
390
  const confirmedCount = values.filter((f) => f.includeConfirmation && f.isConfirmed).length;
@@ -729,7 +731,7 @@ export const Striae = ({ user }: StriaePage) => {
729
731
  supportLevel: notes.supportLevel || 'Inconclusive',
730
732
  includeConfirmation: notes.includeConfirmation ?? false,
731
733
  boxAnnotations: notes.boxAnnotations || [],
732
- updatedAt: notes.updatedAt || new Date().toISOString()
734
+ updatedAt: notes.updatedAt || ''
733
735
  });
734
736
  } else {
735
737
  setAnnotationData(null);
@@ -794,6 +796,7 @@ export const Striae = ({ user }: StriaePage) => {
794
796
  const now = new Date().toISOString();
795
797
  const dataWithEarliestTimestamp: AnnotationData = {
796
798
  ...data,
799
+ updatedAt: now,
797
800
  earliestAnnotationTimestamp: resolveEarliestAnnotationTimestamp(
798
801
  data.earliestAnnotationTimestamp,
799
802
  annotationData?.earliestAnnotationTimestamp,
@@ -954,6 +957,7 @@ export const Striae = ({ user }: StriaePage) => {
954
957
  onSelectCase={(selectedCase) => {
955
958
  void loadCaseIntoWorkspace(selectedCase);
956
959
  }}
960
+ onCurrentCaseDeleted={clearLoadedCaseState}
957
961
  currentCase={currentCase || ''}
958
962
  user={user}
959
963
  confirmationSaveVersion={confirmationSaveVersion}
@@ -38,6 +38,7 @@ export async function fetchDataApi(
38
38
 
39
39
  return fetch(`${DATA_API_BASE}${normalizedPath}`, {
40
40
  ...init,
41
+ cache: 'no-store',
41
42
  headers
42
43
  });
43
44
  }
@@ -550,6 +550,8 @@ export const addUserCase = async (user: User, caseData: CaseMetadata): Promise<v
550
550
  const errorText = await response.text();
551
551
  throw new Error(`Failed to add case to user: ${response.status} - ${errorText}`);
552
552
  }
553
+
554
+ invalidateUserDataCache(user.uid);
553
555
 
554
556
  } catch (error) {
555
557
  console.error('Error adding case to user:', error);
@@ -595,6 +597,8 @@ export const removeUserCase = async (user: User, caseNumber: string): Promise<vo
595
597
  const errorText = await response.text();
596
598
  throw new Error(`Failed to remove case from user: ${response.status} - ${errorText}`);
597
599
  }
600
+
601
+ invalidateUserDataCache(user.uid);
598
602
 
599
603
  } catch (error) {
600
604
  console.error('Error removing case from user:', error);
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@striae-org/striae",
3
- "version": "6.1.5",
3
+ "version": "6.1.7",
4
4
  "private": false,
5
5
  "description": "Striae is a specialized, cloud-native platform designed to streamline forensic firearms identification by providing an intuitive environment for digital comparison image annotation, authenticated confirmations, and automated report generation.",
6
6
  "license": "Apache-2.0",
@@ -124,10 +124,9 @@
124
124
  "eslint-plugin-react-hooks": "^7.1.1",
125
125
  "firebase-admin": "^13.8.0",
126
126
  "modern-normalize": "^3.0.1",
127
- "typescript": "^5.9.3",
128
- "vite": "^7.3.2",
129
- "vite-tsconfig-paths": "^6.1.1",
130
- "wrangler": "^4.83.0"
127
+ "typescript": "^6.0.3",
128
+ "vite": "^8.0.9",
129
+ "wrangler": "^4.84.0"
131
130
  },
132
131
  "overrides": {
133
132
  "@tootallnate/once": "3.0.1"
package/vite.config.ts CHANGED
@@ -1,7 +1,6 @@
1
1
  import { reactRouter } from "@react-router/dev/vite";
2
2
  import { cloudflareDevProxy } from "@react-router/dev/vite/cloudflare";
3
3
  import { defineConfig } from "vite";
4
- import tsconfigPaths from "vite-tsconfig-paths";
5
4
 
6
5
  export default defineConfig({
7
6
  server: {
@@ -11,9 +10,11 @@ export default defineConfig({
11
10
  chunkSizeWarningLimit: 500,
12
11
  minify: true,
13
12
  },
13
+ resolve: {
14
+ tsconfigPaths: true,
15
+ },
14
16
  plugins: [
15
17
  cloudflareDevProxy(),
16
18
  reactRouter(),
17
- tsconfigPaths(),
18
19
  ],
19
20
  });
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "audit-worker",
3
- "version": "6.1.5",
3
+ "version": "6.1.7",
4
4
  "private": true,
5
5
  "scripts": {
6
6
  "deploy": "wrangler deploy",
@@ -8,6 +8,6 @@
8
8
  "start": "wrangler dev"
9
9
  },
10
10
  "devDependencies": {
11
- "wrangler": "^4.83.0"
11
+ "wrangler": "^4.84.0"
12
12
  }
13
13
  }
@@ -7,7 +7,7 @@
7
7
  "name": "AUDIT_WORKER_NAME",
8
8
  "account_id": "ACCOUNT_ID",
9
9
  "main": "src/audit-worker.ts",
10
- "compatibility_date": "2026-04-18",
10
+ "compatibility_date": "2026-04-20",
11
11
  "compatibility_flags": [
12
12
  "nodejs_compat"
13
13
  ],
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "data-worker",
3
- "version": "6.1.5",
3
+ "version": "6.1.7",
4
4
  "private": true,
5
5
  "scripts": {
6
6
  "deploy": "wrangler deploy",
@@ -8,6 +8,6 @@
8
8
  "start": "wrangler dev"
9
9
  },
10
10
  "devDependencies": {
11
- "wrangler": "^4.83.0"
11
+ "wrangler": "^4.84.0"
12
12
  }
13
13
  }
@@ -5,7 +5,7 @@
5
5
  "name": "DATA_WORKER_NAME",
6
6
  "account_id": "ACCOUNT_ID",
7
7
  "main": "src/data-worker.ts",
8
- "compatibility_date": "2026-04-18",
8
+ "compatibility_date": "2026-04-20",
9
9
  "compatibility_flags": [
10
10
  "nodejs_compat"
11
11
  ],
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "image-worker",
3
- "version": "6.1.5",
3
+ "version": "6.1.7",
4
4
  "private": true,
5
5
  "scripts": {
6
6
  "deploy": "wrangler deploy",
@@ -8,6 +8,6 @@
8
8
  "start": "wrangler dev"
9
9
  },
10
10
  "devDependencies": {
11
- "wrangler": "^4.83.0"
11
+ "wrangler": "^4.84.0"
12
12
  }
13
13
  }
@@ -2,7 +2,7 @@
2
2
  "name": "IMAGES_WORKER_NAME",
3
3
  "account_id": "ACCOUNT_ID",
4
4
  "main": "src/image-worker.ts",
5
- "compatibility_date": "2026-04-18",
5
+ "compatibility_date": "2026-04-20",
6
6
  "compatibility_flags": [
7
7
  "nodejs_compat"
8
8
  ],
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "pdf-worker",
3
- "version": "6.1.5",
3
+ "version": "6.1.7",
4
4
  "private": true,
5
5
  "scripts": {
6
6
  "generate:assets": "node scripts/generate-assets.js",
@@ -9,6 +9,6 @@
9
9
  "start": "wrangler dev"
10
10
  },
11
11
  "devDependencies": {
12
- "wrangler": "^4.83.0"
12
+ "wrangler": "^4.84.0"
13
13
  }
14
14
  }
@@ -2,7 +2,7 @@
2
2
  "name": "PDF_WORKER_NAME",
3
3
  "account_id": "ACCOUNT_ID",
4
4
  "main": "src/pdf-worker.ts",
5
- "compatibility_date": "2026-04-18",
5
+ "compatibility_date": "2026-04-20",
6
6
  "compatibility_flags": [
7
7
  "nodejs_compat"
8
8
  ],
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "user-worker",
3
- "version": "6.1.5",
3
+ "version": "6.1.7",
4
4
  "private": true,
5
5
  "scripts": {
6
6
  "deploy": "wrangler deploy",
@@ -8,6 +8,6 @@
8
8
  "start": "wrangler dev"
9
9
  },
10
10
  "devDependencies": {
11
- "wrangler": "^4.83.0"
11
+ "wrangler": "^4.84.0"
12
12
  }
13
13
  }
@@ -2,7 +2,7 @@
2
2
  "name": "USER_WORKER_NAME",
3
3
  "account_id": "ACCOUNT_ID",
4
4
  "main": "src/user-worker.ts",
5
- "compatibility_date": "2026-04-18",
5
+ "compatibility_date": "2026-04-20",
6
6
  "compatibility_flags": [
7
7
  "nodejs_compat"
8
8
  ],
@@ -1,6 +1,6 @@
1
1
  #:schema node_modules/wrangler/config-schema.json
2
2
  name = "PAGES_PROJECT_NAME"
3
- compatibility_date = "2026-04-18"
3
+ compatibility_date = "2026-04-20"
4
4
  compatibility_flags = ["nodejs_compat"]
5
5
  pages_build_output_dir = "./build/client"
6
6