@striae-org/striae 5.3.1 → 5.4.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 +3 -0
- package/app/components/actions/generate-pdf.ts +22 -0
- package/app/components/auth/auth.module.css +531 -0
- package/app/components/auth/mfa-enrollment.tsx +132 -79
- package/app/components/auth/mfa-totp-enrollment.tsx +231 -0
- package/app/components/auth/mfa-verification.tsx +155 -33
- package/app/components/{sidebar/cases/cases-modal.tsx → navbar/case-modals/all-cases-modal.tsx} +4 -4
- package/app/components/navbar/case-modals/archive-case-modal.tsx +9 -10
- package/app/components/navbar/case-modals/case-modal-shared.module.css +88 -0
- package/app/components/navbar/case-modals/delete-case-modal.tsx +9 -10
- package/app/components/navbar/case-modals/export-case-modal.tsx +9 -10
- package/app/components/navbar/case-modals/export-confirmations-modal.tsx +9 -10
- package/app/components/navbar/case-modals/open-case-modal.tsx +4 -4
- package/app/components/navbar/case-modals/rename-case-modal.tsx +9 -10
- package/app/components/navbar/navbar.tsx +1 -1
- package/app/components/sidebar/files/delete-files-modal.tsx +3 -3
- package/app/components/sidebar/files/files-modal.module.css +29 -0
- package/app/components/sidebar/notes/{class-details-fields.tsx → class-details/class-details-fields.tsx} +1 -1
- package/app/components/sidebar/notes/{class-details-modal.tsx → class-details/class-details-modal.tsx} +1 -1
- package/app/components/sidebar/notes/{class-details-sections.tsx → class-details/class-details-sections.tsx} +1 -1
- package/app/components/sidebar/notes/notes-editor-form.tsx +2 -2
- package/app/components/sidebar/notes/notes-editor-modal.tsx +6 -6
- package/app/components/sidebar/notes/notes.module.css +52 -0
- package/app/components/toolbar/toolbar-color-selector.tsx +8 -8
- package/app/components/toolbar/toolbar.module.css +181 -2
- package/app/components/user/delete-account.tsx +7 -7
- package/app/components/user/inactivity-warning.tsx +6 -6
- package/app/components/user/manage-profile.tsx +18 -1
- package/app/components/user/mfa-enrolled-factors.tsx +117 -0
- package/app/components/user/mfa-phone-update.tsx +8 -4
- package/app/components/user/mfa-totp-section.tsx +446 -0
- package/app/components/user/user.module.css +665 -0
- package/app/routes/striae/striae.tsx +1 -1
- package/app/services/audit/audit.service.ts +1 -1
- package/app/services/audit/builders/audit-event-builders-user-security.ts +4 -2
- package/app/services/firebase/errors.ts +2 -0
- package/app/utils/auth/mfa.ts +35 -1
- package/functions/api/image/[[path]].ts +19 -3
- package/package.json +16 -21
- package/scripts/deploy-all.sh +166 -0
- package/scripts/deploy-config/modules/env-utils.sh +322 -0
- package/scripts/deploy-config/modules/keys.sh +404 -0
- package/scripts/deploy-config/modules/prompt.sh +375 -0
- package/scripts/deploy-config/modules/scaffolding.sh +310 -0
- package/scripts/deploy-config/modules/validation.sh +354 -0
- package/scripts/deploy-config.sh +236 -0
- package/scripts/deploy-pages-secrets.sh +231 -0
- package/scripts/deploy-pages.sh +34 -0
- package/scripts/deploy-primershear-emails.sh +167 -0
- package/scripts/deploy-worker-secrets.sh +385 -0
- package/scripts/dev.cjs +23 -0
- package/scripts/enable-totp-mfa.mjs +57 -0
- package/scripts/install-workers.sh +87 -0
- package/scripts/run-eslint.cjs +43 -0
- package/scripts/update-compatibility-dates.cjs +124 -0
- package/scripts/update-markdown-versions.cjs +43 -0
- package/workers/audit-worker/package.json +1 -1
- package/workers/audit-worker/wrangler.jsonc.example +1 -1
- package/workers/data-worker/package.json +1 -1
- package/workers/data-worker/wrangler.jsonc.example +1 -1
- package/workers/image-worker/package.json +1 -1
- package/workers/image-worker/src/image-worker.example.ts +36 -2
- package/workers/image-worker/wrangler.jsonc.example +1 -1
- package/workers/pdf-worker/package.json +1 -1
- package/workers/pdf-worker/wrangler.jsonc.example +1 -1
- package/workers/user-worker/package.json +1 -1
- package/workers/user-worker/wrangler.jsonc.example +1 -1
- package/wrangler.toml.example +1 -1
- package/app/components/auth/mfa-enrollment.module.css +0 -276
- package/app/components/auth/mfa-verification.module.css +0 -259
- package/app/components/navbar/case-modals/archive-case-modal.module.css +0 -34
- package/app/components/navbar/case-modals/delete-case-modal.module.css +0 -9
- package/app/components/navbar/case-modals/export-case-modal.module.css +0 -27
- package/app/components/navbar/case-modals/export-confirmations-modal.module.css +0 -24
- package/app/components/navbar/case-modals/open-case-modal.module.css +0 -82
- package/app/components/navbar/case-modals/rename-case-modal.module.css +0 -9
- package/app/components/sidebar/files/delete-files-modal.module.css +0 -26
- package/app/components/sidebar/notes/notes-editor-modal.module.css +0 -49
- package/app/components/toolbar/toolbar-color-selector.module.css +0 -171
- package/app/components/user/delete-account.module.css +0 -277
- package/app/components/user/inactivity-warning.module.css +0 -148
- package/app/components/user/manage-profile.module.css +0 -192
- package/app/routes/auth/login.module.css +0 -523
- package/app/routes/auth/login.tsx +0 -705
- /package/app/components/{sidebar → navbar}/case-import/case-import.module.css +0 -0
- /package/app/components/{sidebar → navbar}/case-import/case-import.tsx +0 -0
- /package/app/components/{sidebar → navbar}/case-import/components/CasePreviewSection.tsx +0 -0
- /package/app/components/{sidebar → navbar}/case-import/components/ConfirmationDialog.tsx +0 -0
- /package/app/components/{sidebar → navbar}/case-import/components/ConfirmationPreviewSection.tsx +0 -0
- /package/app/components/{sidebar → navbar}/case-import/components/ExistingCaseSection.tsx +0 -0
- /package/app/components/{sidebar → navbar}/case-import/components/FileSelector.tsx +0 -0
- /package/app/components/{sidebar → navbar}/case-import/components/ProgressSection.tsx +0 -0
- /package/app/components/{sidebar → navbar}/case-import/hooks/useFilePreview.ts +0 -0
- /package/app/components/{sidebar → navbar}/case-import/hooks/useImportExecution.ts +0 -0
- /package/app/components/{sidebar → navbar}/case-import/hooks/useImportState.ts +0 -0
- /package/app/components/{sidebar → navbar}/case-import/index.ts +0 -0
- /package/app/components/{sidebar → navbar}/case-import/utils/file-validation.ts +0 -0
- /package/app/components/{sidebar/cases/cases-modal.module.css → navbar/case-modals/all-cases-modal.module.css} +0 -0
- /package/app/components/sidebar/notes/{class-details-shared.ts → class-details/class-details-shared.ts} +0 -0
- /package/app/components/sidebar/notes/{use-class-details-state.ts → class-details/use-class-details-state.ts} +0 -0
|
@@ -1,7 +1,6 @@
|
|
|
1
1
|
import { useEffect, useRef, useState } from 'react';
|
|
2
2
|
import { useOverlayDismiss } from '~/hooks/useOverlayDismiss';
|
|
3
|
-
import
|
|
4
|
-
import styles from './rename-case-modal.module.css';
|
|
3
|
+
import styles from './case-modal-shared.module.css';
|
|
5
4
|
|
|
6
5
|
interface RenameCaseModalProps {
|
|
7
6
|
isOpen: boolean;
|
|
@@ -60,22 +59,22 @@ export const RenameCaseModal = ({
|
|
|
60
59
|
|
|
61
60
|
return (
|
|
62
61
|
<div
|
|
63
|
-
className={
|
|
62
|
+
className={styles.overlay}
|
|
64
63
|
aria-label="Close rename case dialog"
|
|
65
64
|
{...overlayProps}
|
|
66
65
|
>
|
|
67
|
-
<div className={`${
|
|
66
|
+
<div className={`${styles.modal} ${styles.modalStandard}`} role="dialog" aria-modal="true" aria-label="Rename Case">
|
|
68
67
|
<button {...getCloseButtonProps({ ariaLabel: 'Close rename case dialog' })}>
|
|
69
68
|
×
|
|
70
69
|
</button>
|
|
71
|
-
<h3 className={
|
|
72
|
-
<p className={
|
|
70
|
+
<h3 className={styles.title}>Rename Case</h3>
|
|
71
|
+
<p className={styles.subtitle}>Current case: {currentCase}</p>
|
|
73
72
|
<input
|
|
74
73
|
ref={inputRef}
|
|
75
74
|
type="text"
|
|
76
75
|
value={newCaseName}
|
|
77
76
|
onChange={(event) => setNewCaseName(event.target.value)}
|
|
78
|
-
className={
|
|
77
|
+
className={styles.input}
|
|
79
78
|
placeholder="New case number"
|
|
80
79
|
disabled={isSubmitting}
|
|
81
80
|
onKeyDown={(event) => {
|
|
@@ -84,10 +83,10 @@ export const RenameCaseModal = ({
|
|
|
84
83
|
}
|
|
85
84
|
}}
|
|
86
85
|
/>
|
|
87
|
-
<div className={
|
|
86
|
+
<div className={styles.actions}>
|
|
88
87
|
<button
|
|
89
88
|
type="button"
|
|
90
|
-
className={
|
|
89
|
+
className={styles.cancelButton}
|
|
91
90
|
onClick={requestClose}
|
|
92
91
|
disabled={isCloseBlocked}
|
|
93
92
|
>
|
|
@@ -95,7 +94,7 @@ export const RenameCaseModal = ({
|
|
|
95
94
|
</button>
|
|
96
95
|
<button
|
|
97
96
|
type="button"
|
|
98
|
-
className={`${
|
|
97
|
+
className={`${styles.confirmButton} ${styles.confirmButtonWarning}`}
|
|
99
98
|
onClick={() => void handleSubmit()}
|
|
100
99
|
disabled={isSubmitting || !newCaseName.trim()}
|
|
101
100
|
>
|
|
@@ -2,7 +2,7 @@ import { useEffect, useRef, useState, useContext } from 'react';
|
|
|
2
2
|
import styles from './navbar.module.css';
|
|
3
3
|
import { SignOut } from '../actions/signout';
|
|
4
4
|
import { ManageProfile } from '../user/manage-profile';
|
|
5
|
-
import { CaseImport } from '
|
|
5
|
+
import { CaseImport } from './case-import/case-import';
|
|
6
6
|
import { AuthContext } from '~/contexts/auth.context';
|
|
7
7
|
import { getUserData } from '~/utils/data';
|
|
8
8
|
import { type ImportResult, type ConfirmationImportResult } from '~/types';
|
|
@@ -2,7 +2,7 @@ import { useMemo } from 'react';
|
|
|
2
2
|
import { useOverlayDismiss } from '~/hooks/useOverlayDismiss';
|
|
3
3
|
import { type FileData } from '~/types';
|
|
4
4
|
import sharedStyles from '~/components/navbar/case-modals/case-modal-shared.module.css';
|
|
5
|
-
import styles from './
|
|
5
|
+
import styles from './files-modal.module.css';
|
|
6
6
|
|
|
7
7
|
interface DeleteFilesModalProps {
|
|
8
8
|
isOpen: boolean;
|
|
@@ -45,7 +45,7 @@ export const DeleteFilesModal = ({
|
|
|
45
45
|
|
|
46
46
|
return (
|
|
47
47
|
<div className={sharedStyles.overlay} aria-label="Close delete files dialog" {...overlayProps}>
|
|
48
|
-
<div className={`${sharedStyles.modal} ${styles.
|
|
48
|
+
<div className={`${sharedStyles.modal} ${styles.deleteFilesModal}`} role="dialog" aria-modal="true" aria-label="Delete Selected Files">
|
|
49
49
|
<button {...getCloseButtonProps({ ariaLabel: 'Close delete files dialog' })}>×</button>
|
|
50
50
|
|
|
51
51
|
<h3 className={sharedStyles.title}>Delete Selected Files</h3>
|
|
@@ -79,7 +79,7 @@ export const DeleteFilesModal = ({
|
|
|
79
79
|
</button>
|
|
80
80
|
<button
|
|
81
81
|
type="button"
|
|
82
|
-
className={`${sharedStyles.confirmButton} ${styles.
|
|
82
|
+
className={`${sharedStyles.confirmButton} ${styles.deleteFilesConfirmButton}`}
|
|
83
83
|
onClick={() => {
|
|
84
84
|
void onSubmit();
|
|
85
85
|
}}
|
|
@@ -457,6 +457,35 @@
|
|
|
457
457
|
);
|
|
458
458
|
}
|
|
459
459
|
|
|
460
|
+
/* ─── Delete Files Modal ────────────────────────────────────────────────── */
|
|
461
|
+
|
|
462
|
+
.deleteFilesModal {
|
|
463
|
+
width: min(620px, calc(100vw - 2rem));
|
|
464
|
+
}
|
|
465
|
+
|
|
466
|
+
.filePreviewList {
|
|
467
|
+
margin: 0.6rem 0 0;
|
|
468
|
+
padding-left: 1.1rem;
|
|
469
|
+
color: #3f2a2e;
|
|
470
|
+
font-size: 0.86rem;
|
|
471
|
+
}
|
|
472
|
+
|
|
473
|
+
.filePreviewList li + li {
|
|
474
|
+
margin-top: 0.3rem;
|
|
475
|
+
}
|
|
476
|
+
|
|
477
|
+
.remainingNote {
|
|
478
|
+
margin-top: 0.5rem;
|
|
479
|
+
font-size: 0.84rem;
|
|
480
|
+
color: #6b2f35;
|
|
481
|
+
}
|
|
482
|
+
|
|
483
|
+
.deleteFilesConfirmButton {
|
|
484
|
+
background: #dc3545;
|
|
485
|
+
color: #ffffff;
|
|
486
|
+
border-color: #c82333;
|
|
487
|
+
}
|
|
488
|
+
|
|
460
489
|
@media (max-width: 980px) {
|
|
461
490
|
.controlsSection {
|
|
462
491
|
grid-template-columns: repeat(2, minmax(0, 1fr));
|
|
@@ -9,7 +9,7 @@ import {
|
|
|
9
9
|
} from './class-details-shared';
|
|
10
10
|
import { BulletSection, CartridgeCaseSection, ShotshellSection } from './class-details-sections';
|
|
11
11
|
import { useClassDetailsState } from './use-class-details-state';
|
|
12
|
-
import styles from '
|
|
12
|
+
import styles from '../notes.module.css';
|
|
13
13
|
|
|
14
14
|
interface ClassDetailsModalProps {
|
|
15
15
|
isOpen: boolean;
|
|
@@ -2,8 +2,8 @@ import { useState, useEffect } from 'react';
|
|
|
2
2
|
import type { User } from 'firebase/auth';
|
|
3
3
|
import { ColorSelector } from '~/components/colors/colors';
|
|
4
4
|
import { AddlNotesModal } from './addl-notes-modal';
|
|
5
|
-
import { ClassDetailsModal } from './class-details-modal';
|
|
6
|
-
import { buildClassDetailsSummary } from './class-details-shared';
|
|
5
|
+
import { ClassDetailsModal } from './class-details/class-details-modal';
|
|
6
|
+
import { buildClassDetailsSummary } from './class-details/class-details-shared';
|
|
7
7
|
import { getNotes, saveNotes } from '~/components/actions/notes-manage';
|
|
8
8
|
import { type AnnotationData, type BulletAnnotationData, type CartridgeCaseAnnotationData, type ShotshellAnnotationData } from '~/types/annotations';
|
|
9
9
|
import { resolveEarliestAnnotationTimestamp } from '~/utils/ui';
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import type { User } from 'firebase/auth';
|
|
2
2
|
import { useOverlayDismiss } from '~/hooks/useOverlayDismiss';
|
|
3
3
|
import { NotesEditorForm } from './notes-editor-form';
|
|
4
|
-
import styles from './notes
|
|
4
|
+
import styles from './notes.module.css';
|
|
5
5
|
|
|
6
6
|
interface NotesEditorModalProps {
|
|
7
7
|
isOpen: boolean;
|
|
@@ -40,14 +40,14 @@ export const NotesEditorModal = ({
|
|
|
40
40
|
|
|
41
41
|
return (
|
|
42
42
|
<div className={styles.overlay} aria-label="Close image notes dialog" {...overlayProps}>
|
|
43
|
-
<div className={styles.
|
|
44
|
-
<div className={styles.
|
|
45
|
-
<h2 className={styles.
|
|
46
|
-
<button className={styles.
|
|
43
|
+
<div className={styles.editorModal} role="dialog" aria-modal="true" aria-label="Image Notes">
|
|
44
|
+
<div className={styles.editorModalHeader}>
|
|
45
|
+
<h2 className={styles.editorModalTitle}>Image Notes</h2>
|
|
46
|
+
<button className={styles.editorModalCloseButton} {...getCloseButtonProps({ ariaLabel: 'Close image notes dialog' })}>
|
|
47
47
|
×
|
|
48
48
|
</button>
|
|
49
49
|
</div>
|
|
50
|
-
<div className={styles.
|
|
50
|
+
<div className={styles.editorModalContent}>
|
|
51
51
|
<NotesEditorForm
|
|
52
52
|
currentCase={currentCase}
|
|
53
53
|
user={user}
|
|
@@ -773,6 +773,58 @@ textarea:focus {
|
|
|
773
773
|
animation: fadeIn 0.3s ease-in;
|
|
774
774
|
}
|
|
775
775
|
|
|
776
|
+
/* ─── Notes Editor Modal ────────────────────────────────────────────────── */
|
|
777
|
+
|
|
778
|
+
.overlay {
|
|
779
|
+
position: fixed;
|
|
780
|
+
inset: 0;
|
|
781
|
+
background-color: color-mix(in lab, var(--background) 56%, transparent);
|
|
782
|
+
display: flex;
|
|
783
|
+
justify-content: center;
|
|
784
|
+
align-items: center;
|
|
785
|
+
z-index: var(--zIndex5);
|
|
786
|
+
}
|
|
787
|
+
|
|
788
|
+
.editorModal {
|
|
789
|
+
position: relative;
|
|
790
|
+
width: min(900px, calc(100vw - 2rem));
|
|
791
|
+
max-height: calc(100vh - 4rem);
|
|
792
|
+
background: var(--backgroundLight);
|
|
793
|
+
border-radius: var(--spaceXS);
|
|
794
|
+
box-shadow: 0 var(--spaceXS) var(--spaceL)
|
|
795
|
+
color-mix(in lab, var(--black) 16%, transparent);
|
|
796
|
+
display: flex;
|
|
797
|
+
flex-direction: column;
|
|
798
|
+
overflow: hidden;
|
|
799
|
+
}
|
|
800
|
+
|
|
801
|
+
.editorModalHeader {
|
|
802
|
+
display: flex;
|
|
803
|
+
align-items: center;
|
|
804
|
+
justify-content: space-between;
|
|
805
|
+
padding: var(--spaceM) var(--spaceL);
|
|
806
|
+
border-bottom: 1px solid color-mix(in lab, var(--text) 12%, transparent);
|
|
807
|
+
}
|
|
808
|
+
|
|
809
|
+
.editorModalTitle {
|
|
810
|
+
margin: 0;
|
|
811
|
+
color: var(--textTitle);
|
|
812
|
+
font-size: var(--fontSizeBodyM);
|
|
813
|
+
font-weight: var(--fontWeightMedium);
|
|
814
|
+
}
|
|
815
|
+
|
|
816
|
+
.editorModalCloseButton {
|
|
817
|
+
background: none;
|
|
818
|
+
border: none;
|
|
819
|
+
color: var(--textLight);
|
|
820
|
+
}
|
|
821
|
+
|
|
822
|
+
.editorModalContent {
|
|
823
|
+
padding: var(--spaceM) var(--spaceL);
|
|
824
|
+
overflow-y: auto;
|
|
825
|
+
max-height: calc(100vh - 11rem);
|
|
826
|
+
}
|
|
827
|
+
|
|
776
828
|
@keyframes fadeIn {
|
|
777
829
|
from {
|
|
778
830
|
opacity: 0;
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import { useState, useEffect, useCallback } from 'react';
|
|
2
|
-
import styles from './toolbar
|
|
2
|
+
import styles from './toolbar.module.css';
|
|
3
3
|
|
|
4
4
|
interface ToolbarColorSelectorProps {
|
|
5
5
|
selectedColor: string;
|
|
@@ -66,15 +66,15 @@ export const ToolbarColorSelector = ({
|
|
|
66
66
|
|
|
67
67
|
return (
|
|
68
68
|
<div className={styles.toolbarColorSelector}>
|
|
69
|
-
<div className={styles.
|
|
70
|
-
<span className={styles.
|
|
69
|
+
<div className={styles.colorSelectorHeader}>
|
|
70
|
+
<span className={styles.colorSelectorTitle}>Select Box Color</span>
|
|
71
71
|
</div>
|
|
72
72
|
|
|
73
|
-
<div className={styles.
|
|
73
|
+
<div className={styles.colorSelectorContent}>
|
|
74
74
|
<div className={styles.toggleSection}>
|
|
75
75
|
<button
|
|
76
76
|
onClick={() => setShowColorWheel(!showColorWheel)}
|
|
77
|
-
className={styles.
|
|
77
|
+
className={styles.colorToggleButton}
|
|
78
78
|
>
|
|
79
79
|
{showColorWheel ? 'Presets' : 'Custom'}
|
|
80
80
|
</button>
|
|
@@ -106,11 +106,11 @@ export const ToolbarColorSelector = ({
|
|
|
106
106
|
)}
|
|
107
107
|
</div>
|
|
108
108
|
|
|
109
|
-
<div className={styles.
|
|
109
|
+
<div className={styles.colorSelectorActions}>
|
|
110
110
|
<button
|
|
111
111
|
type="button"
|
|
112
112
|
onClick={handleConfirm}
|
|
113
|
-
className={`${styles.actionButton} ${styles.
|
|
113
|
+
className={`${styles.actionButton} ${styles.colorSelectorConfirmButton}`}
|
|
114
114
|
title="Apply selected color"
|
|
115
115
|
>
|
|
116
116
|
✓ Apply Color
|
|
@@ -118,7 +118,7 @@ export const ToolbarColorSelector = ({
|
|
|
118
118
|
<button
|
|
119
119
|
type="button"
|
|
120
120
|
onClick={handleCancel}
|
|
121
|
-
className={`${styles.actionButton} ${styles.
|
|
121
|
+
className={`${styles.actionButton} ${styles.colorSelectorCancelButton}`}
|
|
122
122
|
title="Cancel color selection"
|
|
123
123
|
>
|
|
124
124
|
✕ Cancel
|
|
@@ -9,7 +9,8 @@
|
|
|
9
9
|
padding: var(--spaceS);
|
|
10
10
|
background: var(--background);
|
|
11
11
|
border-radius: 5px;
|
|
12
|
-
box-shadow: 0 var(--spaceXS) var(--spaceM)
|
|
12
|
+
box-shadow: 0 var(--spaceXS) var(--spaceM)
|
|
13
|
+
color-mix(in lab, var(--black) 10%, transparent);
|
|
13
14
|
height: auto;
|
|
14
15
|
overflow: hidden;
|
|
15
16
|
transition: opacity var(--durationM) var(--bezierFastoutSlowin);
|
|
@@ -39,4 +40,182 @@
|
|
|
39
40
|
pointer-events: none;
|
|
40
41
|
height: 0;
|
|
41
42
|
overflow: hidden;
|
|
42
|
-
}
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
/* ─── Color Selector ────────────────────────────────────────────────────── */
|
|
46
|
+
|
|
47
|
+
.toolbarColorSelector {
|
|
48
|
+
position: absolute;
|
|
49
|
+
left: calc(100% + var(--spaceM));
|
|
50
|
+
bottom: 0;
|
|
51
|
+
background: var(--background);
|
|
52
|
+
border-radius: 5px;
|
|
53
|
+
box-shadow: 0 var(--spaceXS) var(--spaceM)
|
|
54
|
+
color-mix(in lab, var(--black) 10%, transparent);
|
|
55
|
+
padding: var(--spaceS);
|
|
56
|
+
width: 200px;
|
|
57
|
+
z-index: 50;
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
.colorSelectorHeader {
|
|
61
|
+
display: flex;
|
|
62
|
+
justify-content: flex-start;
|
|
63
|
+
align-items: center;
|
|
64
|
+
margin-bottom: var(--spaceS);
|
|
65
|
+
padding-bottom: var(--spaceXS);
|
|
66
|
+
border-bottom: 1px solid var(--border);
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
.colorSelectorTitle {
|
|
70
|
+
font-size: var(--fontSizeS);
|
|
71
|
+
font-weight: 600;
|
|
72
|
+
color: var(--foreground);
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
.colorSelectorActions {
|
|
76
|
+
display: flex;
|
|
77
|
+
flex-direction: column;
|
|
78
|
+
gap: var(--spaceXS);
|
|
79
|
+
margin-top: var(--spaceS);
|
|
80
|
+
padding-top: var(--spaceS);
|
|
81
|
+
border-top: 1px solid var(--border);
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
.actionButton {
|
|
85
|
+
width: 100%;
|
|
86
|
+
min-height: 38px;
|
|
87
|
+
border: none;
|
|
88
|
+
border-radius: 4px;
|
|
89
|
+
display: flex;
|
|
90
|
+
align-items: center;
|
|
91
|
+
justify-content: center;
|
|
92
|
+
cursor: pointer;
|
|
93
|
+
font-size: var(--fontSizeS);
|
|
94
|
+
font-weight: 600;
|
|
95
|
+
color: white;
|
|
96
|
+
transition: background-color var(--durationS) var(--bezierFastoutSlowin);
|
|
97
|
+
}
|
|
98
|
+
|
|
99
|
+
.colorSelectorConfirmButton {
|
|
100
|
+
background: var(--success);
|
|
101
|
+
}
|
|
102
|
+
|
|
103
|
+
.colorSelectorConfirmButton:hover {
|
|
104
|
+
background: color-mix(in lab, var(--success) 80%, var(--black));
|
|
105
|
+
}
|
|
106
|
+
|
|
107
|
+
.colorSelectorCancelButton {
|
|
108
|
+
background: var(--error);
|
|
109
|
+
}
|
|
110
|
+
|
|
111
|
+
.colorSelectorCancelButton:hover {
|
|
112
|
+
background: color-mix(in lab, var(--error) 80%, var(--black));
|
|
113
|
+
}
|
|
114
|
+
|
|
115
|
+
.colorSelectorContent {
|
|
116
|
+
display: flex;
|
|
117
|
+
flex-direction: column;
|
|
118
|
+
gap: var(--spaceS);
|
|
119
|
+
}
|
|
120
|
+
|
|
121
|
+
.toggleSection {
|
|
122
|
+
display: flex;
|
|
123
|
+
justify-content: center;
|
|
124
|
+
}
|
|
125
|
+
|
|
126
|
+
.colorToggleButton {
|
|
127
|
+
background: var(--backgroundSecondary);
|
|
128
|
+
color: var(--foreground);
|
|
129
|
+
border: 1px solid var(--border);
|
|
130
|
+
border-radius: 3px;
|
|
131
|
+
padding: var(--spaceXS) var(--spaceS);
|
|
132
|
+
cursor: pointer;
|
|
133
|
+
font-size: var(--fontSizeBodyXS);
|
|
134
|
+
}
|
|
135
|
+
|
|
136
|
+
.colorToggleButton:hover {
|
|
137
|
+
background: var(--backgroundTertiary);
|
|
138
|
+
}
|
|
139
|
+
|
|
140
|
+
.colorWheelSection {
|
|
141
|
+
display: flex;
|
|
142
|
+
justify-content: center;
|
|
143
|
+
}
|
|
144
|
+
|
|
145
|
+
.colorWheel {
|
|
146
|
+
width: 60px;
|
|
147
|
+
height: 40px;
|
|
148
|
+
border: 1px solid var(--border);
|
|
149
|
+
border-radius: 3px;
|
|
150
|
+
cursor: pointer;
|
|
151
|
+
}
|
|
152
|
+
|
|
153
|
+
.colorGrid {
|
|
154
|
+
display: grid;
|
|
155
|
+
grid-template-columns: repeat(5, 1fr);
|
|
156
|
+
gap: var(--spaceXS);
|
|
157
|
+
padding: var(--spaceXS);
|
|
158
|
+
}
|
|
159
|
+
|
|
160
|
+
.colorSwatch {
|
|
161
|
+
width: 28px;
|
|
162
|
+
height: 28px;
|
|
163
|
+
border: 2px solid var(--border);
|
|
164
|
+
border-radius: 3px;
|
|
165
|
+
cursor: pointer;
|
|
166
|
+
position: relative;
|
|
167
|
+
transition: all 0.2s ease;
|
|
168
|
+
}
|
|
169
|
+
|
|
170
|
+
.colorSwatch:hover {
|
|
171
|
+
transform: scale(1.1);
|
|
172
|
+
border-color: var(--foreground);
|
|
173
|
+
}
|
|
174
|
+
|
|
175
|
+
.colorSwatch.selected {
|
|
176
|
+
border-color: var(--foreground);
|
|
177
|
+
border-width: 3px;
|
|
178
|
+
box-shadow:
|
|
179
|
+
0 0 0 2px var(--background),
|
|
180
|
+
0 0 0 4px var(--foreground);
|
|
181
|
+
transform: scale(1.15);
|
|
182
|
+
position: relative;
|
|
183
|
+
}
|
|
184
|
+
|
|
185
|
+
.colorSwatch.selected::after {
|
|
186
|
+
content: "✓";
|
|
187
|
+
position: absolute;
|
|
188
|
+
top: 50%;
|
|
189
|
+
left: 50%;
|
|
190
|
+
transform: translate(-50%, -50%);
|
|
191
|
+
color: var(--foreground);
|
|
192
|
+
font-weight: bold;
|
|
193
|
+
font-size: 14px;
|
|
194
|
+
text-shadow:
|
|
195
|
+
0 0 3px var(--background),
|
|
196
|
+
0 0 6px var(--background);
|
|
197
|
+
pointer-events: none;
|
|
198
|
+
}
|
|
199
|
+
|
|
200
|
+
.preview {
|
|
201
|
+
display: flex;
|
|
202
|
+
align-items: center;
|
|
203
|
+
gap: var(--spaceS);
|
|
204
|
+
padding: var(--spaceXS);
|
|
205
|
+
background: var(--backgroundSecondary);
|
|
206
|
+
border-radius: 3px;
|
|
207
|
+
}
|
|
208
|
+
|
|
209
|
+
.previewLabel {
|
|
210
|
+
font-size: var(--fontSizeBodyXS);
|
|
211
|
+
color: var(--foreground);
|
|
212
|
+
font-weight: 500;
|
|
213
|
+
}
|
|
214
|
+
|
|
215
|
+
.previewSwatch {
|
|
216
|
+
width: 20px;
|
|
217
|
+
height: 20px;
|
|
218
|
+
border: 1px solid var(--border);
|
|
219
|
+
border-radius: 2px;
|
|
220
|
+
flex-shrink: 0;
|
|
221
|
+
}
|
|
@@ -4,7 +4,7 @@ import { auth } from '~/services/firebase';
|
|
|
4
4
|
import { fetchUserApi } from '~/utils/api';
|
|
5
5
|
import { useOverlayDismiss } from '~/hooks/useOverlayDismiss';
|
|
6
6
|
import { auditService } from '~/services/audit';
|
|
7
|
-
import styles from './
|
|
7
|
+
import styles from './user.module.css';
|
|
8
8
|
|
|
9
9
|
interface DeletionProgress {
|
|
10
10
|
totalCases: number;
|
|
@@ -323,17 +323,17 @@ export const DeleteAccount = ({ isOpen, onClose, user, company }: DeleteAccountP
|
|
|
323
323
|
aria-label="Close delete account dialog"
|
|
324
324
|
>
|
|
325
325
|
<div
|
|
326
|
-
className={styles.
|
|
326
|
+
className={styles.deleteAccountModal}
|
|
327
327
|
role="dialog"
|
|
328
328
|
aria-modal="true"
|
|
329
329
|
aria-labelledby="modal-title"
|
|
330
330
|
>
|
|
331
331
|
{/* Header */}
|
|
332
|
-
<header className={styles.
|
|
332
|
+
<header className={styles.dangerModalHeader}>
|
|
333
333
|
<h1 id="modal-title" className={styles.dangerTitle}>Delete Striae Account</h1>
|
|
334
334
|
<button
|
|
335
335
|
onClick={onClose}
|
|
336
|
-
className={styles.
|
|
336
|
+
className={styles.dangerCloseButton}
|
|
337
337
|
aria-label="Close modal"
|
|
338
338
|
>
|
|
339
339
|
×
|
|
@@ -421,7 +421,7 @@ export const DeleteAccount = ({ isOpen, onClose, user, company }: DeleteAccountP
|
|
|
421
421
|
{/* Confirmation Form */}
|
|
422
422
|
{!success && (
|
|
423
423
|
<form className={styles.confirmationForm}>
|
|
424
|
-
<div className={styles.
|
|
424
|
+
<div className={styles.deleteFormGroup}>
|
|
425
425
|
<label htmlFor="uid-confirmation" className={styles.formLabel}>
|
|
426
426
|
Enter UID to confirm account deletion:
|
|
427
427
|
</label>
|
|
@@ -436,7 +436,7 @@ export const DeleteAccount = ({ isOpen, onClose, user, company }: DeleteAccountP
|
|
|
436
436
|
/>
|
|
437
437
|
</div>
|
|
438
438
|
|
|
439
|
-
<div className={styles.
|
|
439
|
+
<div className={styles.deleteFormGroup}>
|
|
440
440
|
<label htmlFor="email-confirmation" className={styles.formLabel}>
|
|
441
441
|
Enter your email address to confirm account deletion:
|
|
442
442
|
</label>
|
|
@@ -455,7 +455,7 @@ export const DeleteAccount = ({ isOpen, onClose, user, company }: DeleteAccountP
|
|
|
455
455
|
<button
|
|
456
456
|
type="button"
|
|
457
457
|
onClick={handleDeleteAccount}
|
|
458
|
-
className={styles.
|
|
458
|
+
className={styles.deleteAccountButton}
|
|
459
459
|
disabled={!isConfirmationValid || isDeleting}
|
|
460
460
|
>
|
|
461
461
|
{isDeleting ? 'Deleting Account...' : 'Delete Striae Account'}
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import { useState, useEffect } from 'react';
|
|
2
2
|
import { useOverlayDismiss } from '~/hooks/useOverlayDismiss';
|
|
3
|
-
import styles from './
|
|
3
|
+
import styles from './user.module.css';
|
|
4
4
|
|
|
5
5
|
interface InactivityWarningProps {
|
|
6
6
|
isOpen: boolean;
|
|
@@ -56,16 +56,16 @@ export const InactivityWarning = ({
|
|
|
56
56
|
const seconds = countdown % 60;
|
|
57
57
|
|
|
58
58
|
return (
|
|
59
|
-
<div className={styles.
|
|
60
|
-
<div className={styles.
|
|
59
|
+
<div className={styles.inactivityOverlay} aria-label="Close inactivity warning" {...overlayProps}>
|
|
60
|
+
<div className={styles.inactivityModal}>
|
|
61
61
|
<button className={styles.closeButton} {...getCloseButtonProps({ ariaLabel: 'Close inactivity warning' })}>
|
|
62
62
|
×
|
|
63
63
|
</button>
|
|
64
|
-
<div className={styles.
|
|
64
|
+
<div className={styles.inactivityHeader}>
|
|
65
65
|
<h3>Session Timeout Warning</h3>
|
|
66
66
|
</div>
|
|
67
67
|
|
|
68
|
-
<div className={styles.
|
|
68
|
+
<div className={styles.inactivityContent}>
|
|
69
69
|
<p>
|
|
70
70
|
Your session will expire due to inactivity in:
|
|
71
71
|
</p>
|
|
@@ -77,7 +77,7 @@ export const InactivityWarning = ({
|
|
|
77
77
|
</p>
|
|
78
78
|
</div>
|
|
79
79
|
|
|
80
|
-
<div className={styles.
|
|
80
|
+
<div className={styles.inactivityActions}>
|
|
81
81
|
<button
|
|
82
82
|
onClick={requestClose}
|
|
83
83
|
className={styles.extendButton}
|
|
@@ -11,7 +11,9 @@ import { handleAuthError, ERROR_MESSAGES } from '~/services/firebase/errors';
|
|
|
11
11
|
import { FormField, FormButton } from '../form';
|
|
12
12
|
import { Toast } from '~/components/toast/toast';
|
|
13
13
|
import { MfaPhoneUpdateSection } from './mfa-phone-update';
|
|
14
|
-
import
|
|
14
|
+
import { MfaTotpSection } from './mfa-totp-section';
|
|
15
|
+
import { MfaEnrolledFactors } from './mfa-enrolled-factors';
|
|
16
|
+
import styles from './user.module.css';
|
|
15
17
|
|
|
16
18
|
interface ManageProfileProps {
|
|
17
19
|
isOpen: boolean;
|
|
@@ -27,6 +29,7 @@ export const ManageProfile = ({ isOpen, onClose }: ManageProfileProps) => {
|
|
|
27
29
|
const [email, setEmail] = useState('');
|
|
28
30
|
const [isLoading, setIsLoading] = useState(false);
|
|
29
31
|
const [isMfaBusy, setIsMfaBusy] = useState(false);
|
|
32
|
+
const [enrolledFactorsRefreshKey, setEnrolledFactorsRefreshKey] = useState(0);
|
|
30
33
|
const [showToast, setShowToast] = useState(false);
|
|
31
34
|
const [toastMessage, setToastMessage] = useState('');
|
|
32
35
|
const [toastType, setToastType] = useState<'success' | 'error'>('success');
|
|
@@ -298,6 +301,20 @@ export const ManageProfile = ({ isOpen, onClose }: ManageProfileProps) => {
|
|
|
298
301
|
|
|
299
302
|
<MfaPhoneUpdateSection user={user} isOpen={isOpen} onBusyChange={handleMfaBusyChange} />
|
|
300
303
|
|
|
304
|
+
<MfaTotpSection
|
|
305
|
+
user={user}
|
|
306
|
+
isOpen={isOpen}
|
|
307
|
+
onBusyChange={handleMfaBusyChange}
|
|
308
|
+
onTotpEnrolled={() => setEnrolledFactorsRefreshKey((k) => k + 1)}
|
|
309
|
+
/>
|
|
310
|
+
|
|
311
|
+
<MfaEnrolledFactors
|
|
312
|
+
user={user}
|
|
313
|
+
refreshKey={enrolledFactorsRefreshKey}
|
|
314
|
+
onFactorRemoved={() => setEnrolledFactorsRefreshKey((k) => k + 1)}
|
|
315
|
+
onBusyChange={handleMfaBusyChange}
|
|
316
|
+
/>
|
|
317
|
+
|
|
301
318
|
<div className={styles.buttonGroup}>
|
|
302
319
|
<FormButton variant="primary" type="submit" isLoading={isLoading} loadingText="Updating...">
|
|
303
320
|
Update Profile
|