@striae-org/striae 3.3.0 → 4.0.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 +1 -1
- package/README.md +1 -1
- package/app/components/actions/case-export/core-export.ts +5 -2
- package/app/components/actions/case-export/download-handlers.ts +1 -1
- package/app/components/actions/case-import/confirmation-import.ts +24 -23
- package/app/components/actions/case-import/image-operations.ts +20 -49
- package/app/components/actions/case-import/orchestrator.ts +1 -1
- package/app/components/actions/case-import/storage-operations.ts +54 -89
- package/app/components/actions/case-import/validation.ts +2 -13
- package/app/components/actions/case-manage.ts +15 -27
- package/app/components/actions/generate-pdf.ts +3 -7
- package/app/components/actions/image-manage.ts +64 -129
- package/app/components/button/button.module.css +12 -8
- package/app/components/sidebar/case-export/case-export.tsx +11 -6
- package/app/components/sidebar/cases/case-sidebar.tsx +21 -6
- package/app/components/sidebar/sidebar.module.css +0 -2
- package/app/components/user/delete-account.tsx +7 -7
- package/app/config-example/config.json +2 -8
- package/app/hooks/useInactivityTimeout.ts +2 -5
- package/app/root.tsx +94 -63
- package/app/routes/auth/emailActionHandler.tsx +1 -1
- package/app/routes/auth/emailVerification.tsx +1 -1
- package/app/routes/auth/login.tsx +7 -9
- package/app/routes/auth/passwordReset.tsx +1 -1
- package/app/routes/auth/route.ts +4 -3
- package/app/routes/striae/striae.tsx +4 -8
- package/app/services/audit/audit-api-client.ts +40 -0
- package/app/services/audit/audit-worker-client.ts +14 -17
- package/app/styles/root.module.css +13 -101
- package/app/tailwind.css +9 -2
- package/app/utils/auth.ts +5 -32
- package/app/utils/data-api-client.ts +43 -0
- package/app/utils/data-operations.ts +59 -75
- package/app/utils/image-api-client.ts +130 -0
- package/app/utils/pdf-api-client.ts +43 -0
- package/app/utils/permissions.ts +10 -23
- package/app/utils/user-api-client.ts +90 -0
- package/functions/api/_shared/firebase-auth.ts +255 -0
- package/functions/api/audit/[[path]].ts +150 -0
- package/functions/api/data/[[path]].ts +141 -0
- package/functions/api/image/[[path]].ts +143 -0
- package/functions/api/pdf/[[path]].ts +110 -0
- package/functions/api/user/[[path]].ts +196 -0
- package/package.json +3 -2
- package/public/.well-known/security.txt +3 -4
- package/scripts/deploy-all.sh +22 -8
- package/scripts/deploy-config.sh +194 -165
- package/scripts/deploy-pages-secrets.sh +231 -0
- package/scripts/deploy-worker-secrets.sh +1 -1
- package/worker-configuration.d.ts +7491 -11363
- package/workers/audit-worker/worker-configuration.d.ts +11323 -7448
- package/workers/audit-worker/wrangler.jsonc.example +1 -8
- package/workers/data-worker/worker-configuration.d.ts +11323 -7448
- package/workers/data-worker/wrangler.jsonc.example +1 -8
- package/workers/image-worker/src/image-worker.example.ts +10 -2
- package/workers/image-worker/worker-configuration.d.ts +11322 -7447
- package/workers/image-worker/wrangler.jsonc.example +1 -8
- package/workers/keys-worker/src/keys.ts +2 -1
- package/workers/keys-worker/worker-configuration.d.ts +11322 -7447
- package/workers/keys-worker/wrangler.jsonc.example +2 -9
- package/workers/pdf-worker/src/assets/icon-256.png +0 -0
- package/workers/pdf-worker/worker-configuration.d.ts +11323 -7448
- package/workers/pdf-worker/wrangler.jsonc.example +1 -8
- package/workers/user-worker/src/user-worker.example.ts +121 -41
- package/workers/user-worker/worker-configuration.d.ts +11323 -7448
- package/workers/user-worker/wrangler.jsonc.example +1 -8
- package/wrangler.toml.example +1 -1
- package/app/styles/legal-pages.module.css +0 -113
|
@@ -1,16 +1,13 @@
|
|
|
1
1
|
import type { User } from 'firebase/auth';
|
|
2
|
-
import paths from '~/config/config.json';
|
|
3
2
|
import {
|
|
4
|
-
getImageApiKey,
|
|
5
3
|
getAccountHash
|
|
6
4
|
} from '~/utils/auth';
|
|
5
|
+
import { fetchImageApi, uploadImageApi } from '~/utils/image-api-client';
|
|
7
6
|
import { canUploadFile } from '~/utils/permissions';
|
|
8
7
|
import { getCaseData, updateCaseData, deleteFileAnnotations } from '~/utils/data-operations';
|
|
9
8
|
import type { CaseData, FileData, ImageUploadResponse } from '~/types';
|
|
10
9
|
import { auditService } from '~/services/audit';
|
|
11
10
|
|
|
12
|
-
const IMAGE_URL = paths.image_worker_url;
|
|
13
|
-
|
|
14
11
|
export const fetchFiles = async (
|
|
15
12
|
user: User,
|
|
16
13
|
caseNumber: string,
|
|
@@ -52,127 +49,70 @@ export const uploadFile = async (
|
|
|
52
49
|
throw new Error(permission.reason || 'You cannot upload more files to this case.');
|
|
53
50
|
}
|
|
54
51
|
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
xhr.upload.addEventListener('progress', (event) => {
|
|
63
|
-
if (event.lengthComputable && onProgress) {
|
|
64
|
-
const progress = Math.round((event.loaded / event.total) * 100);
|
|
65
|
-
onProgress(progress);
|
|
66
|
-
}
|
|
67
|
-
});
|
|
68
|
-
|
|
69
|
-
xhr.addEventListener('load', async () => {
|
|
70
|
-
const endTime = Date.now();
|
|
71
|
-
|
|
72
|
-
if (xhr.status === 200) {
|
|
73
|
-
try {
|
|
74
|
-
const imageData = JSON.parse(xhr.responseText) as ImageUploadResponse;
|
|
75
|
-
if (!imageData.success) throw new Error('Upload failed');
|
|
76
|
-
|
|
77
|
-
const newFile: FileData = {
|
|
78
|
-
id: imageData.result.id,
|
|
79
|
-
originalFilename: file.name,
|
|
80
|
-
uploadedAt: new Date().toISOString()
|
|
81
|
-
};
|
|
52
|
+
try {
|
|
53
|
+
const imageData: ImageUploadResponse = await uploadImageApi(user, file, onProgress);
|
|
54
|
+
const uploadedImageId = imageData.result?.id;
|
|
55
|
+
if (!uploadedImageId) {
|
|
56
|
+
throw new Error('Upload failed');
|
|
57
|
+
}
|
|
82
58
|
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
59
|
+
const newFile: FileData = {
|
|
60
|
+
id: uploadedImageId,
|
|
61
|
+
originalFilename: file.name,
|
|
62
|
+
uploadedAt: new Date().toISOString()
|
|
63
|
+
};
|
|
88
64
|
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
65
|
+
// Update case data using centralized function
|
|
66
|
+
const existingData = await getCaseData(user, caseNumber);
|
|
67
|
+
if (!existingData) {
|
|
68
|
+
throw new Error('Case not found');
|
|
69
|
+
}
|
|
93
70
|
|
|
94
|
-
|
|
71
|
+
const updatedData = {
|
|
72
|
+
...existingData,
|
|
73
|
+
files: [...(existingData.files || []), newFile]
|
|
74
|
+
};
|
|
95
75
|
|
|
96
|
-
|
|
97
|
-
try {
|
|
98
|
-
await auditService.logFileUpload(
|
|
99
|
-
user,
|
|
100
|
-
file.name,
|
|
101
|
-
file.size,
|
|
102
|
-
file.type,
|
|
103
|
-
'file-picker',
|
|
104
|
-
caseNumber,
|
|
105
|
-
'success',
|
|
106
|
-
endTime - startTime,
|
|
107
|
-
imageData.result.id
|
|
108
|
-
);
|
|
109
|
-
} catch (auditError) {
|
|
110
|
-
console.error('Failed to log successful file upload:', auditError);
|
|
111
|
-
}
|
|
76
|
+
await updateCaseData(user, caseNumber, updatedData);
|
|
112
77
|
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
console.error('Failed to log file upload failure:', auditError);
|
|
130
|
-
}
|
|
131
|
-
reject(error);
|
|
132
|
-
}
|
|
133
|
-
} else {
|
|
134
|
-
// Log failed file upload
|
|
135
|
-
try {
|
|
136
|
-
await auditService.logFileUpload(
|
|
137
|
-
user,
|
|
138
|
-
file.name,
|
|
139
|
-
file.size,
|
|
140
|
-
file.type,
|
|
141
|
-
'file-picker',
|
|
142
|
-
caseNumber,
|
|
143
|
-
'failure',
|
|
144
|
-
endTime - startTime
|
|
145
|
-
);
|
|
146
|
-
} catch (auditError) {
|
|
147
|
-
console.error('Failed to log file upload failure:', auditError);
|
|
148
|
-
}
|
|
149
|
-
reject(new Error('Upload failed'));
|
|
150
|
-
}
|
|
151
|
-
});
|
|
78
|
+
// Log successful file upload
|
|
79
|
+
try {
|
|
80
|
+
await auditService.logFileUpload(
|
|
81
|
+
user,
|
|
82
|
+
file.name,
|
|
83
|
+
file.size,
|
|
84
|
+
file.type,
|
|
85
|
+
'file-picker',
|
|
86
|
+
caseNumber,
|
|
87
|
+
'success',
|
|
88
|
+
Date.now() - startTime,
|
|
89
|
+
uploadedImageId
|
|
90
|
+
);
|
|
91
|
+
} catch (auditError) {
|
|
92
|
+
console.error('Failed to log successful file upload:', auditError);
|
|
93
|
+
}
|
|
152
94
|
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
}
|
|
95
|
+
console.log(`✅ File uploaded: ${file.name} (${file.size} bytes) (${Date.now() - startTime}ms)`);
|
|
96
|
+
return newFile;
|
|
97
|
+
} catch (error) {
|
|
98
|
+
// Log failed file upload
|
|
99
|
+
try {
|
|
100
|
+
await auditService.logFileUpload(
|
|
101
|
+
user,
|
|
102
|
+
file.name,
|
|
103
|
+
file.size,
|
|
104
|
+
file.type,
|
|
105
|
+
'file-picker',
|
|
106
|
+
caseNumber,
|
|
107
|
+
'failure',
|
|
108
|
+
Date.now() - startTime
|
|
109
|
+
);
|
|
110
|
+
} catch (auditError) {
|
|
111
|
+
console.error('Failed to log file upload failure:', auditError);
|
|
112
|
+
}
|
|
171
113
|
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
xhr.send(formData);
|
|
175
|
-
});
|
|
114
|
+
throw error;
|
|
115
|
+
}
|
|
176
116
|
};
|
|
177
117
|
|
|
178
118
|
export const deleteFile = async (user: User, caseNumber: string, fileId: string, deleteReason: string = 'User-requested deletion via file list'): Promise<void> => {
|
|
@@ -197,12 +137,8 @@ export const deleteFile = async (user: User, caseNumber: string, fileId: string,
|
|
|
197
137
|
let imageDeleteError = '';
|
|
198
138
|
|
|
199
139
|
// Attempt to delete image file
|
|
200
|
-
const
|
|
201
|
-
|
|
202
|
-
method: 'DELETE',
|
|
203
|
-
headers: {
|
|
204
|
-
'Authorization': `Bearer ${imagesApiToken}`
|
|
205
|
-
}
|
|
140
|
+
const imageResponse = await fetchImageApi(user, `/${encodeURIComponent(fileId)}`, {
|
|
141
|
+
method: 'DELETE'
|
|
206
142
|
});
|
|
207
143
|
|
|
208
144
|
// Handle image deletion response
|
|
@@ -306,14 +242,13 @@ export const getImageUrl = async (user: User, fileData: FileData, caseNumber: st
|
|
|
306
242
|
const defaultAccessReason = accessReason || 'Image viewer access';
|
|
307
243
|
|
|
308
244
|
try {
|
|
309
|
-
const { accountHash } = await getImageConfig();
|
|
310
|
-
const imagesApiToken = await getImageApiKey();
|
|
245
|
+
const { accountHash } = await getImageConfig();
|
|
311
246
|
const imageDeliveryUrl = `https://imagedelivery.net/${accountHash}/${fileData.id}/${DEFAULT_VARIANT}`;
|
|
312
|
-
|
|
313
|
-
|
|
247
|
+
const encodedImageDeliveryUrl = encodeURIComponent(imageDeliveryUrl);
|
|
248
|
+
|
|
249
|
+
const workerResponse = await fetchImageApi(user, `/${encodedImageDeliveryUrl}`, {
|
|
314
250
|
method: 'GET',
|
|
315
251
|
headers: {
|
|
316
|
-
'Authorization': `Bearer ${imagesApiToken}`,
|
|
317
252
|
'Accept': 'text/plain'
|
|
318
253
|
}
|
|
319
254
|
});
|
|
@@ -9,13 +9,14 @@
|
|
|
9
9
|
background: var(--backgroundLight);
|
|
10
10
|
cursor: pointer;
|
|
11
11
|
transition: all var(--durationS) var(--bezierFastoutSlowin);
|
|
12
|
-
box-shadow: 0 1px 3px
|
|
12
|
+
box-shadow: 0 1px 3px
|
|
13
|
+
color-mix(in lab, var(--backgroundLight) 30%, transparent);
|
|
13
14
|
}
|
|
14
15
|
|
|
15
16
|
.button:hover {
|
|
16
17
|
background: color-mix(in lab, var(--backgroundLight) 85%, var(--black));
|
|
17
|
-
|
|
18
|
-
|
|
18
|
+
box-shadow: 0 2px 6px
|
|
19
|
+
color-mix(in lab, var(--backgroundLight) 40%, transparent);
|
|
19
20
|
}
|
|
20
21
|
|
|
21
22
|
.button.active {
|
|
@@ -25,7 +26,6 @@
|
|
|
25
26
|
|
|
26
27
|
.button.active:hover {
|
|
27
28
|
background: color-mix(in lab, var(--success) 85%, var(--black));
|
|
28
|
-
transform: translateY(-1px);
|
|
29
29
|
box-shadow: 0 2px 6px color-mix(in lab, var(--success) 40%, transparent);
|
|
30
30
|
}
|
|
31
31
|
|
|
@@ -43,7 +43,7 @@
|
|
|
43
43
|
box-shadow: none;
|
|
44
44
|
}
|
|
45
45
|
|
|
46
|
-
.icon {
|
|
46
|
+
.icon {
|
|
47
47
|
color: var(--text);
|
|
48
48
|
transition: color var(--durationS) var(--bezierFastoutSlowin);
|
|
49
49
|
}
|
|
@@ -58,6 +58,10 @@
|
|
|
58
58
|
}
|
|
59
59
|
|
|
60
60
|
@keyframes spin {
|
|
61
|
-
0% {
|
|
62
|
-
|
|
63
|
-
}
|
|
61
|
+
0% {
|
|
62
|
+
transform: rotate(0deg);
|
|
63
|
+
}
|
|
64
|
+
100% {
|
|
65
|
+
transform: rotate(360deg);
|
|
66
|
+
}
|
|
67
|
+
}
|
|
@@ -10,8 +10,8 @@ export type ExportFormat = 'json' | 'csv';
|
|
|
10
10
|
interface CaseExportProps {
|
|
11
11
|
isOpen: boolean;
|
|
12
12
|
onClose: () => void;
|
|
13
|
-
onExport: (caseNumber: string, format: ExportFormat, includeImages?: boolean) => void
|
|
14
|
-
onExportAll: (onProgress: (current: number, total: number, caseName: string) => void, format: ExportFormat) => void
|
|
13
|
+
onExport: (caseNumber: string, format: ExportFormat, includeImages?: boolean, onProgress?: (progress: number, label: string) => void) => Promise<void>;
|
|
14
|
+
onExportAll: (onProgress: (current: number, total: number, caseName: string) => void, format: ExportFormat) => Promise<void>;
|
|
15
15
|
currentCaseNumber?: string;
|
|
16
16
|
isReadOnly?: boolean;
|
|
17
17
|
}
|
|
@@ -30,7 +30,7 @@ export const CaseExport = ({
|
|
|
30
30
|
const [isExportingAll, setIsExportingAll] = useState(false);
|
|
31
31
|
const [isExportingConfirmations, setIsExportingConfirmations] = useState(false);
|
|
32
32
|
const [error, setError] = useState<string>('');
|
|
33
|
-
const [exportProgress, setExportProgress] = useState<{ current: number; total: number; caseName: string } | null>(null);
|
|
33
|
+
const [exportProgress, setExportProgress] = useState<{ current: number; total: number; caseName: string; mode?: 'single' | 'all' } | null>(null);
|
|
34
34
|
const [selectedFormat, setSelectedFormat] = useState<ExportFormat>('json');
|
|
35
35
|
const [includeImages, setIncludeImages] = useState(false);
|
|
36
36
|
const [hasConfirmationData, setHasConfirmationData] = useState(false);
|
|
@@ -130,13 +130,16 @@ export const CaseExport = ({
|
|
|
130
130
|
setExportProgress(null);
|
|
131
131
|
|
|
132
132
|
try {
|
|
133
|
-
await onExport(caseNumber.trim(), selectedFormat, includeImages)
|
|
133
|
+
await onExport(caseNumber.trim(), selectedFormat, includeImages, (progress, label) => {
|
|
134
|
+
setExportProgress({ current: progress, total: 100, caseName: label, mode: 'single' });
|
|
135
|
+
});
|
|
134
136
|
onClose();
|
|
135
137
|
} catch (error) {
|
|
136
138
|
console.error('Export failed:', error);
|
|
137
139
|
setError(error instanceof Error ? error.message : 'Export failed. Please try again.');
|
|
138
140
|
} finally {
|
|
139
141
|
setIsExporting(false);
|
|
142
|
+
setExportProgress(null);
|
|
140
143
|
}
|
|
141
144
|
};
|
|
142
145
|
|
|
@@ -315,7 +318,9 @@ export const CaseExport = ({
|
|
|
315
318
|
{exportProgress && exportProgress.total > 0 && (
|
|
316
319
|
<div className={styles.progressSection}>
|
|
317
320
|
<div className={styles.progressText}>
|
|
318
|
-
|
|
321
|
+
{exportProgress.mode === 'single'
|
|
322
|
+
? `${exportProgress.caseName} (${exportProgress.current}%)`
|
|
323
|
+
: `Exporting case ${exportProgress.current} of ${exportProgress.total}: ${exportProgress.caseName}`}
|
|
319
324
|
</div>
|
|
320
325
|
<div className={styles.progressBar}>
|
|
321
326
|
<div
|
|
@@ -326,7 +331,7 @@ export const CaseExport = ({
|
|
|
326
331
|
</div>
|
|
327
332
|
)}
|
|
328
333
|
|
|
329
|
-
{isExportingAll && !exportProgress && (
|
|
334
|
+
{(isExporting || isExportingAll) && !exportProgress && (
|
|
330
335
|
<div className={styles.progressSection}>
|
|
331
336
|
<div className={styles.progressText}>
|
|
332
337
|
Preparing export...
|
|
@@ -539,25 +539,40 @@ const handleImageSelect = (file: FileData) => {
|
|
|
539
539
|
? 'Select an image first'
|
|
540
540
|
: undefined;
|
|
541
541
|
|
|
542
|
-
const handleExport = async (exportCaseNumber: string, format: ExportFormat, includeImages?: boolean) => {
|
|
542
|
+
const handleExport = async (exportCaseNumber: string, format: ExportFormat, includeImages?: boolean, onProgress?: (progress: number, label: string) => void) => {
|
|
543
543
|
try {
|
|
544
544
|
const caseExportActions = await loadCaseExportActions();
|
|
545
545
|
|
|
546
546
|
if (includeImages) {
|
|
547
547
|
// ZIP export with images - only available for single case exports
|
|
548
|
-
await caseExportActions.downloadCaseAsZip(user, exportCaseNumber, format)
|
|
548
|
+
await caseExportActions.downloadCaseAsZip(user, exportCaseNumber, format, (progress) => {
|
|
549
|
+
const label = progress < 30 ? 'Loading case data' :
|
|
550
|
+
progress < 50 ? 'Preparing archive' :
|
|
551
|
+
progress < 80 ? 'Adding images' :
|
|
552
|
+
progress < 96 ? 'Finalizing' : 'Downloading';
|
|
553
|
+
onProgress?.(Math.round(progress), label);
|
|
554
|
+
});
|
|
549
555
|
} else {
|
|
550
556
|
// Standard data-only export
|
|
551
|
-
|
|
552
|
-
|
|
553
|
-
|
|
554
|
-
|
|
557
|
+
onProgress?.(5, 'Loading case data');
|
|
558
|
+
const exportData = await caseExportActions.exportCaseData(
|
|
559
|
+
user,
|
|
560
|
+
exportCaseNumber,
|
|
561
|
+
{ includeMetadata: true },
|
|
562
|
+
(current, total, label) => {
|
|
563
|
+
const p = total > 0 ? Math.round(10 + (current / total) * 60) : 10;
|
|
564
|
+
onProgress?.(p, label);
|
|
565
|
+
}
|
|
566
|
+
);
|
|
567
|
+
onProgress?.(75, 'Preparing download');
|
|
568
|
+
|
|
555
569
|
// Download the exported data in the selected format
|
|
556
570
|
if (format === 'json') {
|
|
557
571
|
await caseExportActions.downloadCaseAsJSON(user, exportData);
|
|
558
572
|
} else {
|
|
559
573
|
await caseExportActions.downloadCaseAsCSV(user, exportData);
|
|
560
574
|
}
|
|
575
|
+
onProgress?.(100, 'Complete');
|
|
561
576
|
}
|
|
562
577
|
|
|
563
578
|
} catch (error) {
|
|
@@ -109,7 +109,6 @@
|
|
|
109
109
|
.footerSectionButton:hover {
|
|
110
110
|
background-color: #5c636a;
|
|
111
111
|
box-shadow: 0 4px 8px rgba(0, 0, 0, 0.15);
|
|
112
|
-
transform: translateY(-1px);
|
|
113
112
|
}
|
|
114
113
|
|
|
115
114
|
/* Footer Modal */
|
|
@@ -163,7 +162,6 @@
|
|
|
163
162
|
|
|
164
163
|
.footerModalClose:hover {
|
|
165
164
|
background-color: #f8f9fa;
|
|
166
|
-
transform: translateY(-1px);
|
|
167
165
|
}
|
|
168
166
|
|
|
169
167
|
.footerModalContent {
|
|
@@ -1,8 +1,7 @@
|
|
|
1
1
|
import { useState, useEffect } from 'react';
|
|
2
2
|
import { signOut } from 'firebase/auth';
|
|
3
3
|
import { auth } from '~/services/firebase';
|
|
4
|
-
import
|
|
5
|
-
import { getUserApiKey } from '~/utils/auth';
|
|
4
|
+
import { fetchUserApi } from '~/utils/user-api-client';
|
|
6
5
|
import { auditService } from '~/services/audit';
|
|
7
6
|
import styles from './delete-account.module.css';
|
|
8
7
|
|
|
@@ -220,14 +219,15 @@ export const DeleteAccount = ({ isOpen, onClose, user, company }: DeleteAccountP
|
|
|
220
219
|
false // emailNotificationSent - deletion emails disabled
|
|
221
220
|
);
|
|
222
221
|
|
|
223
|
-
|
|
224
|
-
|
|
222
|
+
const currentUser = auth.currentUser;
|
|
223
|
+
if (!currentUser || currentUser.uid !== user.uid) {
|
|
224
|
+
throw new Error('User session mismatch. Please sign in again.');
|
|
225
|
+
}
|
|
225
226
|
|
|
226
|
-
// Delete the user account via user
|
|
227
|
-
const deleteResponse = await
|
|
227
|
+
// Delete the user account via user proxy
|
|
228
|
+
const deleteResponse = await fetchUserApi(currentUser, `/${encodeURIComponent(user.uid)}?stream=true`, {
|
|
228
229
|
method: 'DELETE',
|
|
229
230
|
headers: {
|
|
230
|
-
'X-Custom-Auth-Key': apiKey,
|
|
231
231
|
'Accept': 'text/event-stream'
|
|
232
232
|
}
|
|
233
233
|
});
|
|
@@ -1,12 +1,6 @@
|
|
|
1
1
|
{
|
|
2
|
-
"url": "PAGES_CUSTOM_DOMAIN",
|
|
3
|
-
"
|
|
4
|
-
"keys_url": "KEYS_WORKER_CUSTOM_DOMAIN",
|
|
5
|
-
"image_worker_url": "IMAGE_WORKER_CUSTOM_DOMAIN",
|
|
6
|
-
"user_worker_url": "USER_WORKER_CUSTOM_DOMAIN",
|
|
7
|
-
"pdf_worker_url": "PDF_WORKER_CUSTOM_DOMAIN",
|
|
8
|
-
"audit_worker_url": "AUDIT_WORKER_CUSTOM_DOMAIN",
|
|
9
|
-
"keys_auth": "YOUR_KEYS_AUTH_TOKEN",
|
|
2
|
+
"url": "PAGES_CUSTOM_DOMAIN",
|
|
3
|
+
"account_hash": "ACCOUNT_HASH",
|
|
10
4
|
"manifest_signing_key_id": "MANIFEST_SIGNING_KEY_ID",
|
|
11
5
|
"manifest_signing_public_key": "MANIFEST_SIGNING_PUBLIC_KEY",
|
|
12
6
|
"manifest_signing_public_keys": {
|
|
@@ -1,5 +1,4 @@
|
|
|
1
1
|
import { useEffect, useRef, useCallback } from 'react';
|
|
2
|
-
import { useLocation } from 'react-router';
|
|
3
2
|
import { signOut } from 'firebase/auth';
|
|
4
3
|
import { auth } from '~/services/firebase';
|
|
5
4
|
import { INACTIVITY_CONFIG } from '~/config/inactivity';
|
|
@@ -19,13 +18,11 @@ export const useInactivityTimeout = ({
|
|
|
19
18
|
onTimeout,
|
|
20
19
|
enabled = true
|
|
21
20
|
}: UseInactivityTimeoutOptions = {}) => {
|
|
22
|
-
const location = useLocation();
|
|
23
21
|
const timeoutRef = useRef<NodeJS.Timeout | null>(null);
|
|
24
22
|
const warningTimeoutRef = useRef<NodeJS.Timeout | null>(null);
|
|
25
23
|
const lastActivityRef = useRef<number>(0);
|
|
26
24
|
|
|
27
|
-
const
|
|
28
|
-
const shouldEnable = enabled && isAuthRoute;
|
|
25
|
+
const shouldEnable = enabled;
|
|
29
26
|
|
|
30
27
|
useEffect(() => {
|
|
31
28
|
lastActivityRef.current = Date.now();
|
|
@@ -104,7 +101,7 @@ export const useInactivityTimeout = ({
|
|
|
104
101
|
});
|
|
105
102
|
clearTimeouts();
|
|
106
103
|
};
|
|
107
|
-
}, [shouldEnable, resetTimer, clearTimeouts
|
|
104
|
+
}, [shouldEnable, resetTimer, clearTimeouts]);
|
|
108
105
|
|
|
109
106
|
return {
|
|
110
107
|
extendSession,
|