@striae-org/striae 3.2.2 → 4.0.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.
- package/.env.example +1 -1
- package/app/components/actions/case-export/core-export.ts +5 -2
- package/app/components/actions/case-export/download-handlers.ts +51 -3
- package/app/components/actions/case-import/confirmation-import.ts +65 -40
- package/app/components/actions/case-import/confirmation-package.ts +86 -0
- package/app/components/actions/case-import/image-operations.ts +20 -49
- package/app/components/actions/case-import/index.ts +1 -0
- package/app/components/actions/case-import/orchestrator.ts +13 -3
- package/app/components/actions/case-import/storage-operations.ts +54 -89
- package/app/components/actions/case-import/validation.ts +7 -111
- package/app/components/actions/case-import/zip-processing.ts +44 -2
- package/app/components/actions/case-manage.ts +15 -27
- package/app/components/actions/confirm-export.ts +44 -13
- package/app/components/actions/generate-pdf.ts +3 -7
- package/app/components/actions/image-manage.ts +63 -129
- package/app/components/button/button.module.css +12 -8
- package/app/components/form/form-button.tsx +1 -1
- package/app/components/form/form.module.css +9 -0
- package/app/components/public-signing-key-modal/public-signing-key-modal.module.css +163 -49
- package/app/components/public-signing-key-modal/public-signing-key-modal.tsx +365 -88
- package/app/components/sidebar/case-export/case-export.tsx +13 -60
- package/app/components/sidebar/case-import/case-import.tsx +18 -6
- package/app/components/sidebar/case-import/hooks/useFilePreview.ts +6 -4
- package/app/components/sidebar/case-import/utils/file-validation.ts +57 -2
- package/app/components/sidebar/cases/case-sidebar.tsx +122 -52
- package/app/components/sidebar/cases/cases.module.css +101 -18
- package/app/components/sidebar/notes/notes.module.css +33 -13
- package/app/components/sidebar/sidebar.module.css +0 -2
- package/app/components/user/delete-account.tsx +7 -7
- package/app/components/user/manage-profile.tsx +1 -1
- package/app/components/user/mfa-phone-update.tsx +15 -12
- package/app/config-example/config.json +2 -8
- package/app/hooks/useInactivityTimeout.ts +2 -5
- package/app/root.tsx +96 -65
- package/app/routes/auth/login.tsx +132 -11
- 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/SHA256.ts +5 -1
- package/app/utils/auth.ts +5 -32
- package/app/utils/confirmation-signature.ts +5 -1
- package/app/utils/data-api-client.ts +43 -0
- package/app/utils/data-operations.ts +59 -75
- package/app/utils/export-verification.ts +353 -0
- 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/signature-utils.ts +74 -4
- 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 +127 -0
- package/functions/api/pdf/[[path]].ts +110 -0
- package/functions/api/user/[[path]].ts +196 -0
- package/package.json +8 -4
- package/public/favicon.ico +0 -0
- package/public/icon-256.png +0 -0
- package/public/icon-512.png +0 -0
- package/public/manifest.json +39 -0
- package/public/shortcut.png +0 -0
- package/public/social-image.png +0 -0
- package/react-router.config.ts +5 -0
- package/scripts/deploy-all.sh +22 -8
- package/scripts/deploy-config.sh +143 -148
- package/scripts/deploy-pages-secrets.sh +231 -0
- package/scripts/deploy-worker-secrets.sh +1 -1
- package/workers/audit-worker/wrangler.jsonc.example +1 -8
- package/workers/data-worker/wrangler.jsonc.example +1 -8
- package/workers/image-worker/wrangler.jsonc.example +1 -8
- package/workers/keys-worker/wrangler.jsonc.example +2 -9
- package/workers/pdf-worker/scripts/generate-assets.js +94 -0
- package/workers/pdf-worker/src/assets/icon-256.png +0 -0
- package/workers/pdf-worker/wrangler.jsonc.example +1 -8
- package/workers/user-worker/src/user-worker.example.ts +121 -41
- package/workers/user-worker/wrangler.jsonc.example +1 -8
- package/wrangler.toml.example +1 -1
- package/app/styles/legal-pages.module.css +0 -113
- package/public/favicon.svg +0 -9
|
@@ -10,12 +10,12 @@ hr {
|
|
|
10
10
|
}
|
|
11
11
|
|
|
12
12
|
.section {
|
|
13
|
-
margin-bottom: 2rem;
|
|
13
|
+
margin-bottom: 2rem;
|
|
14
14
|
}
|
|
15
15
|
|
|
16
16
|
.sectionTitle {
|
|
17
17
|
font-size: 1.3rem;
|
|
18
|
-
font-weight: 600;
|
|
18
|
+
font-weight: 600;
|
|
19
19
|
color: #495057;
|
|
20
20
|
margin-bottom: 1rem;
|
|
21
21
|
}
|
|
@@ -50,7 +50,7 @@ input[type="color"]:focus,
|
|
|
50
50
|
textarea:focus {
|
|
51
51
|
outline: none;
|
|
52
52
|
border-color: #0d6efd;
|
|
53
|
-
box-shadow: 0 0 0 2px rgba(13,110,253
|
|
53
|
+
box-shadow: 0 0 0 2px rgba(13, 110, 253, 0.25);
|
|
54
54
|
}
|
|
55
55
|
|
|
56
56
|
.caseNumbers {
|
|
@@ -74,10 +74,14 @@ textarea:focus {
|
|
|
74
74
|
box-sizing: border-box;
|
|
75
75
|
}
|
|
76
76
|
|
|
77
|
+
.caseInput input:not(:disabled) {
|
|
78
|
+
background-color: #ffffff;
|
|
79
|
+
}
|
|
80
|
+
|
|
77
81
|
.caseInput input:focus {
|
|
78
82
|
outline: none;
|
|
79
83
|
border-color: #0d6efd;
|
|
80
|
-
box-shadow: 0 0 0 2px rgba(13,110,253
|
|
84
|
+
box-shadow: 0 0 0 2px rgba(13, 110, 253, 0.25);
|
|
81
85
|
}
|
|
82
86
|
|
|
83
87
|
.checkboxLabel {
|
|
@@ -108,6 +112,11 @@ textarea:focus {
|
|
|
108
112
|
transition: border-color 0.2s;
|
|
109
113
|
}
|
|
110
114
|
|
|
115
|
+
.classCharacteristics input:not(:disabled),
|
|
116
|
+
.classCharacteristics textarea:not(:disabled) {
|
|
117
|
+
background-color: #ffffff;
|
|
118
|
+
}
|
|
119
|
+
|
|
111
120
|
.classCharacteristics select,
|
|
112
121
|
.support select {
|
|
113
122
|
width: 100%;
|
|
@@ -124,7 +133,7 @@ textarea:focus {
|
|
|
124
133
|
.support select:focus {
|
|
125
134
|
outline: none;
|
|
126
135
|
border-color: #0d6efd;
|
|
127
|
-
box-shadow: 0 0 0 2px rgba(13,110,253
|
|
136
|
+
box-shadow: 0 0 0 2px rgba(13, 110, 253, 0.25);
|
|
128
137
|
}
|
|
129
138
|
|
|
130
139
|
.classCharacteristics textarea {
|
|
@@ -138,11 +147,14 @@ textarea:focus {
|
|
|
138
147
|
box-sizing: border-box;
|
|
139
148
|
}
|
|
140
149
|
|
|
150
|
+
.classCharacteristics input + textarea {
|
|
151
|
+
margin-top: 1rem;
|
|
152
|
+
}
|
|
153
|
+
|
|
141
154
|
.classCharacteristics textarea:focus {
|
|
142
155
|
outline: none;
|
|
143
156
|
border-color: #0d6efd;
|
|
144
|
-
box-shadow: 0 0 0 2px rgba(13,110,253
|
|
145
|
-
margin-top: 1rem;
|
|
157
|
+
box-shadow: 0 0 0 2px rgba(13, 110, 253, 0.25);
|
|
146
158
|
resize: vertical;
|
|
147
159
|
}
|
|
148
160
|
|
|
@@ -188,13 +200,17 @@ textarea:focus {
|
|
|
188
200
|
box-sizing: border-box;
|
|
189
201
|
}
|
|
190
202
|
|
|
203
|
+
.indexing input[type="text"]:not(:disabled) {
|
|
204
|
+
background-color: #ffffff;
|
|
205
|
+
}
|
|
206
|
+
|
|
191
207
|
.indexing input[type="text"]:focus {
|
|
192
208
|
outline: none;
|
|
193
209
|
border-color: #0d6efd;
|
|
194
|
-
box-shadow: 0 0 0 2px rgba(13,110,253
|
|
210
|
+
box-shadow: 0 0 0 2px rgba(13, 110, 253, 0.25);
|
|
195
211
|
}
|
|
196
212
|
|
|
197
|
-
.confirmation {
|
|
213
|
+
.confirmation {
|
|
198
214
|
padding: 0.75rem;
|
|
199
215
|
background-color: #f8f9fa;
|
|
200
216
|
border-radius: 6px;
|
|
@@ -232,7 +248,7 @@ textarea:focus {
|
|
|
232
248
|
}
|
|
233
249
|
|
|
234
250
|
.notesButton:hover {
|
|
235
|
-
background-color: color-mix(in lab, var(--primary) 95%, transparent);
|
|
251
|
+
background-color: color-mix(in lab, var(--primary) 95%, transparent);
|
|
236
252
|
}
|
|
237
253
|
|
|
238
254
|
.modalOverlay {
|
|
@@ -355,6 +371,10 @@ textarea:focus {
|
|
|
355
371
|
}
|
|
356
372
|
|
|
357
373
|
@keyframes fadeIn {
|
|
358
|
-
from {
|
|
359
|
-
|
|
360
|
-
}
|
|
374
|
+
from {
|
|
375
|
+
opacity: 0;
|
|
376
|
+
}
|
|
377
|
+
to {
|
|
378
|
+
opacity: 1;
|
|
379
|
+
}
|
|
380
|
+
}
|
|
@@ -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
|
});
|
|
@@ -234,7 +234,7 @@ export const ManageProfile = ({ isOpen, onClose }: ManageProfileProps) => {
|
|
|
234
234
|
<FormButton variant="primary" type="submit" isLoading={isLoading} loadingText="Updating...">
|
|
235
235
|
Update Profile
|
|
236
236
|
</FormButton>
|
|
237
|
-
<FormButton variant="
|
|
237
|
+
<FormButton variant="audit" type="button" onClick={() => setShowAuditViewer(true)}>
|
|
238
238
|
View My Audit Trail
|
|
239
239
|
</FormButton>
|
|
240
240
|
<FormButton variant="secondary" type="button" onClick={() => setShowResetForm(true)}>
|
|
@@ -58,6 +58,7 @@ export const MfaPhoneUpdateSection = ({
|
|
|
58
58
|
const [recaptchaVerifier, setRecaptchaVerifier] = useState<RecaptchaVerifier | null>(null);
|
|
59
59
|
|
|
60
60
|
const isMfaBusy = isMfaLoading || isMfaReauthLoading;
|
|
61
|
+
const hasMfaPhoneInput = mfaPhoneInput.trim().length > 0;
|
|
61
62
|
|
|
62
63
|
const resetMfaReauthFlow = useCallback(() => {
|
|
63
64
|
setShowMfaReauthPrompt(false);
|
|
@@ -665,18 +666,20 @@ export const MfaPhoneUpdateSection = ({
|
|
|
665
666
|
)}
|
|
666
667
|
</div>
|
|
667
668
|
) : !isMfaCodeSent ? (
|
|
668
|
-
|
|
669
|
-
<
|
|
670
|
-
|
|
671
|
-
|
|
672
|
-
|
|
673
|
-
|
|
674
|
-
|
|
675
|
-
|
|
676
|
-
|
|
677
|
-
|
|
678
|
-
|
|
679
|
-
|
|
669
|
+
hasMfaPhoneInput ? (
|
|
670
|
+
<div className={styles.mfaButtonGroup}>
|
|
671
|
+
<FormButton
|
|
672
|
+
variant="secondary"
|
|
673
|
+
type="button"
|
|
674
|
+
onClick={handleSendMfaVerificationCode}
|
|
675
|
+
isLoading={isMfaLoading}
|
|
676
|
+
loadingText="Sending Code..."
|
|
677
|
+
disabled={!hasMfaPhoneInput}
|
|
678
|
+
>
|
|
679
|
+
Send Verification Code
|
|
680
|
+
</FormButton>
|
|
681
|
+
</div>
|
|
682
|
+
) : null
|
|
680
683
|
) : (
|
|
681
684
|
<div className={styles.mfaVerificationSection}>
|
|
682
685
|
<input
|
|
@@ -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,
|
package/app/root.tsx
CHANGED
|
@@ -7,8 +7,6 @@ import {
|
|
|
7
7
|
ScrollRestoration,
|
|
8
8
|
isRouteErrorResponse,
|
|
9
9
|
useRouteError,
|
|
10
|
-
Link,
|
|
11
|
-
useLocation,
|
|
12
10
|
useMatches,
|
|
13
11
|
} from 'react-router';
|
|
14
12
|
import {
|
|
@@ -16,6 +14,7 @@ import {
|
|
|
16
14
|
themeStyles
|
|
17
15
|
} from '~/components/theme-provider/theme-provider';
|
|
18
16
|
import { AuthProvider } from '~/components/auth/auth-provider';
|
|
17
|
+
import { auth } from '~/services/firebase';
|
|
19
18
|
import styles from '~/styles/root.module.css';
|
|
20
19
|
import './tailwind.css';
|
|
21
20
|
|
|
@@ -30,8 +29,8 @@ export const links: LinksFunction = () => [
|
|
|
30
29
|
rel: "stylesheet",
|
|
31
30
|
href: "https://fonts.googleapis.com/css2?family=Inter:ital,opsz,wght@0,14..32,100..900;1,14..32,100..900&display=swap",
|
|
32
31
|
},
|
|
32
|
+
{ rel: 'manifest', href: '/manifest.json' },
|
|
33
33
|
{ rel: 'icon', href: '/favicon.ico' },
|
|
34
|
-
{ rel: 'icon', href: '/favicon.svg', type: 'image/svg+xml' },
|
|
35
34
|
];
|
|
36
35
|
|
|
37
36
|
type AppTheme = 'dark' | 'light';
|
|
@@ -61,7 +60,7 @@ const resolveRouteTheme = (matches: ReturnType<typeof useMatches>): AppTheme =>
|
|
|
61
60
|
export function Layout({ children }: { children: React.ReactNode }) {
|
|
62
61
|
const matches = useMatches();
|
|
63
62
|
const theme = resolveRouteTheme(matches);
|
|
64
|
-
const themeColor = theme === 'dark' ? '#000000' : '#
|
|
63
|
+
const themeColor = theme === 'dark' ? '#000000' : '#377087';
|
|
65
64
|
|
|
66
65
|
return (
|
|
67
66
|
<html lang="en" data-theme={theme}>
|
|
@@ -88,80 +87,112 @@ export function Layout({ children }: { children: React.ReactNode }) {
|
|
|
88
87
|
}
|
|
89
88
|
|
|
90
89
|
export default function App() {
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
90
|
+
return (
|
|
91
|
+
<AuthProvider>
|
|
92
|
+
<Outlet />
|
|
93
|
+
</AuthProvider>
|
|
94
|
+
);
|
|
95
|
+
}
|
|
97
96
|
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
97
|
+
interface ErrorBoundaryShellProps {
|
|
98
|
+
title: string;
|
|
99
|
+
children: React.ReactNode;
|
|
100
|
+
}
|
|
101
|
+
|
|
102
|
+
const LOGIN_REDIRECT_PATH = '/';
|
|
103
|
+
|
|
104
|
+
const errorActionStyle = {
|
|
105
|
+
alignItems: 'center',
|
|
106
|
+
appearance: 'none',
|
|
107
|
+
backgroundColor: '#0d6efd',
|
|
108
|
+
border: '1px solid #0b5ed7',
|
|
109
|
+
borderRadius: '8px',
|
|
110
|
+
color: '#ffffff',
|
|
111
|
+
cursor: 'pointer',
|
|
112
|
+
display: 'inline-flex',
|
|
113
|
+
fontSize: '1rem',
|
|
114
|
+
fontWeight: 600,
|
|
115
|
+
justifyContent: 'center',
|
|
116
|
+
lineHeight: 1,
|
|
117
|
+
marginTop: '1rem',
|
|
118
|
+
minWidth: '220px',
|
|
119
|
+
padding: '0.9rem 1.6rem',
|
|
120
|
+
textDecoration: 'none',
|
|
121
|
+
} as const;
|
|
122
|
+
|
|
123
|
+
async function returnToLogin() {
|
|
124
|
+
try {
|
|
125
|
+
await auth.signOut();
|
|
126
|
+
} catch (error) {
|
|
127
|
+
console.error('Error boundary sign out failed:', error);
|
|
128
|
+
} finally {
|
|
129
|
+
if (typeof window !== 'undefined') {
|
|
130
|
+
localStorage.clear();
|
|
131
|
+
window.location.href = LOGIN_REDIRECT_PATH;
|
|
132
|
+
}
|
|
104
133
|
}
|
|
134
|
+
}
|
|
105
135
|
|
|
106
|
-
|
|
136
|
+
function ErrorBoundaryShell({ title, children }: ErrorBoundaryShellProps) {
|
|
137
|
+
return (
|
|
138
|
+
<html lang="en" data-theme="light">
|
|
139
|
+
<head>
|
|
140
|
+
<meta charSet="utf-8" />
|
|
141
|
+
<meta name="viewport" content="width=device-width, initial-scale=1" />
|
|
142
|
+
<meta name="theme-color" content="#377087" />
|
|
143
|
+
<meta name="color-scheme" content="light" />
|
|
144
|
+
<style dangerouslySetInnerHTML={{ __html: themeStyles }} />
|
|
145
|
+
<title>{title}</title>
|
|
146
|
+
<Meta />
|
|
147
|
+
<Links />
|
|
148
|
+
</head>
|
|
149
|
+
<body className="flex flex-col h-screen w-full overflow-x-hidden">
|
|
150
|
+
<ThemeProvider theme="light" className="">
|
|
151
|
+
<main>{children}</main>
|
|
152
|
+
</ThemeProvider>
|
|
153
|
+
<ScrollRestoration />
|
|
154
|
+
<Scripts />
|
|
155
|
+
</body>
|
|
156
|
+
</html>
|
|
157
|
+
);
|
|
107
158
|
}
|
|
108
159
|
|
|
109
160
|
export function ErrorBoundary() {
|
|
110
161
|
const error = useRouteError();
|
|
111
162
|
|
|
112
163
|
if (isRouteErrorResponse(error)) {
|
|
164
|
+
const statusText = error.statusText || 'Unexpected error';
|
|
165
|
+
|
|
113
166
|
return (
|
|
114
|
-
<
|
|
115
|
-
<
|
|
116
|
-
<
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
to="https://striae.org"
|
|
128
|
-
className={styles.errorLink}>
|
|
129
|
-
Return Home
|
|
130
|
-
</Link>
|
|
131
|
-
</div>
|
|
132
|
-
</main>
|
|
133
|
-
</ThemeProvider>
|
|
134
|
-
<ScrollRestoration />
|
|
135
|
-
<Scripts />
|
|
136
|
-
</body>
|
|
137
|
-
</html>
|
|
167
|
+
<ErrorBoundaryShell title={`${error.status} ${statusText}`}>
|
|
168
|
+
<div className={styles.errorContainer}>
|
|
169
|
+
<div className={styles.errorTitle}>{error.status}</div>
|
|
170
|
+
<p className={styles.errorMessage}>{statusText}</p>
|
|
171
|
+
<button
|
|
172
|
+
type="button"
|
|
173
|
+
onClick={() => void returnToLogin()}
|
|
174
|
+
style={errorActionStyle}
|
|
175
|
+
className={styles.errorLink}>
|
|
176
|
+
Return to Login
|
|
177
|
+
</button>
|
|
178
|
+
</div>
|
|
179
|
+
</ErrorBoundaryShell>
|
|
138
180
|
);
|
|
139
181
|
}
|
|
140
182
|
|
|
141
183
|
return (
|
|
142
|
-
<
|
|
143
|
-
<
|
|
144
|
-
<
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
to="https://striae.org"
|
|
156
|
-
className={styles.errorLink}>
|
|
157
|
-
Return Home
|
|
158
|
-
</Link>
|
|
159
|
-
</div>
|
|
160
|
-
</main>
|
|
161
|
-
</ThemeProvider>
|
|
162
|
-
<ScrollRestoration />
|
|
163
|
-
<Scripts />
|
|
164
|
-
</body>
|
|
165
|
-
</html>
|
|
184
|
+
<ErrorBoundaryShell title="Oops! Something went wrong">
|
|
185
|
+
<div className={styles.errorContainer}>
|
|
186
|
+
<div className={styles.errorTitle}>500</div>
|
|
187
|
+
<p className={styles.errorMessage}>Something went wrong. Please try again later.</p>
|
|
188
|
+
<button
|
|
189
|
+
type="button"
|
|
190
|
+
onClick={() => void returnToLogin()}
|
|
191
|
+
style={errorActionStyle}
|
|
192
|
+
className={styles.errorLink}>
|
|
193
|
+
Return to Login
|
|
194
|
+
</button>
|
|
195
|
+
</div>
|
|
196
|
+
</ErrorBoundaryShell>
|
|
166
197
|
);
|
|
167
198
|
}
|