@micha.bigler/ui-core-micha 2.33.2 → 2.34.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/WORK_ORDERS.md +2 -2
- package/dist/auth/ReauthModal.js +3 -1
- package/dist/components/AccessCodeManager.js +16 -14
- package/dist/components/AccessCodeSingleUseToggle.js +2 -1
- package/dist/components/AllowedEmailDomainsManager.js +3 -1
- package/dist/components/AuthFactorRequirementCard.js +2 -1
- package/dist/components/BulkInviteCsvTab.js +7 -7
- package/dist/components/LoginForm.js +2 -1
- package/dist/components/MFAComponent.js +11 -5
- package/dist/components/MfaLoginComponent.js +4 -1
- package/dist/components/PasskeysComponent.js +4 -2
- package/dist/components/QrSignupManager.js +2 -1
- package/dist/components/QrSignupValidityManager.js +4 -1
- package/dist/components/RegistrationMethodsManager.js +3 -1
- package/dist/components/SecurityComponent.js +3 -1
- package/dist/components/SupportRecoveryRequestsTab.js +4 -1
- package/dist/components/UserInviteComponent.js +8 -8
- package/dist/components/UserListComponent.js +8 -4
- package/dist/components/charts/ChartFrame.js +5 -2
- package/dist/components/charts/TimeSeriesChart.js +7 -4
- package/dist/index.js +1 -1
- package/dist/layout/PageLayout.js +3 -2
- package/dist/messaging/AttachmentList.js +6 -1
- package/dist/messaging/Composer.js +2 -1
- package/dist/messaging/DirectMessageLauncher.js +2 -1
- package/dist/messaging/MessageBubble.js +29 -8
- package/dist/messaging/PollCard.js +6 -4
- package/dist/messaging/Thread.js +2 -1
- package/dist/notifications/NotificationSettings.js +17 -3
- package/dist/notifications/PopupSurface.js +2 -1
- package/dist/onboarding/steps/BrowserPushStep.js +2 -1
- package/dist/pages/AccountPage.js +2 -1
- package/dist/pages/LoginPage.js +2 -1
- package/dist/pages/SignUpPage.js +2 -1
- package/dist/pages/SignupConfirmPage.js +2 -1
- package/dist/theme/index.js +2 -1
- package/dist/theme/kitSxRegistry.js +187 -0
- package/dist/theme/themeCompleteness.js +217 -0
- package/package.json +1 -1
- package/src/auth/ReauthModal.jsx +5 -2
- package/src/components/AccessCodeManager.jsx +22 -20
- package/src/components/AccessCodeSingleUseToggle.jsx +4 -2
- package/src/components/AllowedEmailDomainsManager.jsx +6 -3
- package/src/components/AuthFactorRequirementCard.jsx +4 -2
- package/src/components/BulkInviteCsvTab.jsx +11 -10
- package/src/components/LoginForm.jsx +5 -3
- package/src/components/MFAComponent.jsx +14 -7
- package/src/components/MfaLoginComponent.jsx +9 -5
- package/src/components/PasskeysComponent.jsx +7 -4
- package/src/components/QrSignupManager.jsx +6 -4
- package/src/components/QrSignupValidityManager.jsx +8 -4
- package/src/components/RegistrationMethodsManager.jsx +7 -4
- package/src/components/SecurityComponent.jsx +10 -7
- package/src/components/SupportRecoveryRequestsTab.jsx +8 -4
- package/src/components/UserInviteComponent.jsx +10 -10
- package/src/components/UserListComponent.jsx +12 -7
- package/src/components/charts/ChartFrame.jsx +6 -2
- package/src/components/charts/TimeSeriesChart.jsx +8 -4
- package/src/index.js +3 -0
- package/src/layout/PageLayout.jsx +4 -2
- package/src/messaging/AttachmentList.jsx +9 -3
- package/src/messaging/Composer.jsx +3 -1
- package/src/messaging/DirectMessageLauncher.jsx +3 -1
- package/src/messaging/MessageBubble.jsx +31 -9
- package/src/messaging/PollCard.jsx +8 -6
- package/src/messaging/Thread.jsx +3 -1
- package/src/notifications/NotificationSettings.jsx +24 -9
- package/src/notifications/PopupSurface.jsx +3 -1
- package/src/onboarding/steps/BrowserPushStep.jsx +3 -1
- package/src/pages/AccountPage.jsx +11 -9
- package/src/pages/LoginPage.jsx +6 -4
- package/src/pages/SignUpPage.jsx +4 -2
- package/src/pages/SignupConfirmPage.jsx +4 -2
- package/src/theme/index.js +3 -0
- package/src/theme/kitSxRegistry.js +246 -0
- package/src/theme/themeCompleteness.js +227 -0
- package/tests/themeCompleteness.test.js +199 -0
- package/work-orders/THEME-4.md +86 -0
|
@@ -9,6 +9,9 @@ import {
|
|
|
9
9
|
import { useTranslation } from 'react-i18next';
|
|
10
10
|
import { updateAuthPolicy } from '../auth/authApi';
|
|
11
11
|
|
|
12
|
+
export const ALLOWED_EMAIL_DOMAINS_ALERT_SX = { mb: 2 };
|
|
13
|
+
export const ALLOWED_EMAIL_DOMAINS_BUTTON_SX = { mt: 2 };
|
|
14
|
+
|
|
12
15
|
export function AllowedEmailDomainsManager({
|
|
13
16
|
domains = [],
|
|
14
17
|
enabled = false,
|
|
@@ -63,8 +66,8 @@ export function AllowedEmailDomainsManager({
|
|
|
63
66
|
)}
|
|
64
67
|
</Typography>
|
|
65
68
|
|
|
66
|
-
{error && <Alert severity="error" sx={
|
|
67
|
-
{success && <Alert severity="success" sx={
|
|
69
|
+
{error && <Alert severity="error" sx={ALLOWED_EMAIL_DOMAINS_ALERT_SX}>{error}</Alert>}
|
|
70
|
+
{success && <Alert severity="success" sx={ALLOWED_EMAIL_DOMAINS_ALERT_SX}>{success}</Alert>}
|
|
68
71
|
|
|
69
72
|
<TextField
|
|
70
73
|
label={t('Auth.ALLOWED_EMAIL_DOMAINS_LABEL', 'Allowed email domains')}
|
|
@@ -80,7 +83,7 @@ export function AllowedEmailDomainsManager({
|
|
|
80
83
|
disabled={busy || !canEdit}
|
|
81
84
|
/>
|
|
82
85
|
|
|
83
|
-
<Button variant="contained" sx={
|
|
86
|
+
<Button variant="contained" sx={ALLOWED_EMAIL_DOMAINS_BUTTON_SX} onClick={handleSave} disabled={busy || !canEdit}>
|
|
84
87
|
{t('Common.SAVE', 'Save')}
|
|
85
88
|
</Button>
|
|
86
89
|
</Box>
|
|
@@ -11,6 +11,8 @@ import {
|
|
|
11
11
|
import { useTranslation } from 'react-i18next';
|
|
12
12
|
import { fetchAuthPolicy, updateAuthPolicy } from '../auth/authApi';
|
|
13
13
|
|
|
14
|
+
export const AUTH_FACTOR_REQUIREMENT_ALERT_SX = { mb: 2 };
|
|
15
|
+
|
|
14
16
|
export function AuthFactorRequirementCard({ canEdit = true, policy = null }) {
|
|
15
17
|
const { t } = useTranslation();
|
|
16
18
|
const [value, setValue] = useState('1');
|
|
@@ -71,8 +73,8 @@ export function AuthFactorRequirementCard({ canEdit = true, policy = null }) {
|
|
|
71
73
|
'Define the minimum number of authentication factors required for sign-in.',
|
|
72
74
|
)}
|
|
73
75
|
</Typography>
|
|
74
|
-
{error && <Alert severity="error" sx={
|
|
75
|
-
{success && <Alert severity="success" sx={
|
|
76
|
+
{error && <Alert severity="error" sx={AUTH_FACTOR_REQUIREMENT_ALERT_SX}>{error}</Alert>}
|
|
77
|
+
{success && <Alert severity="success" sx={AUTH_FACTOR_REQUIREMENT_ALERT_SX}>{success}</Alert>}
|
|
76
78
|
<FormControl>
|
|
77
79
|
<RadioGroup value={value} onChange={handleChange}>
|
|
78
80
|
<FormControlLabel
|
|
@@ -16,6 +16,13 @@ import {
|
|
|
16
16
|
import { useTranslation } from 'react-i18next';
|
|
17
17
|
import { sendAdminInvite } from '../auth/authApi';
|
|
18
18
|
|
|
19
|
+
export const BULK_INVITE_ACTION_SX = {
|
|
20
|
+
minWidth: 120,
|
|
21
|
+
height: 40,
|
|
22
|
+
whiteSpace: 'nowrap',
|
|
23
|
+
};
|
|
24
|
+
export const BULK_INVITE_ALERT_SX = { mb: 2 };
|
|
25
|
+
|
|
19
26
|
function parseEmailsFromCsv(text) {
|
|
20
27
|
if (!text) return [];
|
|
21
28
|
|
|
@@ -45,12 +52,6 @@ export function BulkInviteCsvTab({
|
|
|
45
52
|
onCompleted,
|
|
46
53
|
}) {
|
|
47
54
|
const { t } = useTranslation();
|
|
48
|
-
const actionButtonSx = {
|
|
49
|
-
minWidth: 120,
|
|
50
|
-
height: 40,
|
|
51
|
-
textTransform: 'none',
|
|
52
|
-
whiteSpace: 'nowrap',
|
|
53
|
-
};
|
|
54
55
|
const [emails, setEmails] = useState([]);
|
|
55
56
|
const [results, setResults] = useState({});
|
|
56
57
|
const [busy, setBusy] = useState(false);
|
|
@@ -134,15 +135,15 @@ export function BulkInviteCsvTab({
|
|
|
134
135
|
)}
|
|
135
136
|
</Typography>
|
|
136
137
|
|
|
137
|
-
{error && <Alert severity="error" sx={
|
|
138
|
-
{success && <Alert severity="success" sx={
|
|
138
|
+
{error && <Alert severity="error" sx={BULK_INVITE_ALERT_SX}>{error}</Alert>}
|
|
139
|
+
{success && <Alert severity="success" sx={BULK_INVITE_ALERT_SX}>{success}</Alert>}
|
|
139
140
|
|
|
140
141
|
<Box sx={{ display: 'flex', gap: 2, alignItems: 'center', mb: 2, flexWrap: 'wrap' }}>
|
|
141
|
-
<Button variant="outlined" size="small" component="label" disabled={busy} sx={
|
|
142
|
+
<Button variant="outlined" size="small" component="label" disabled={busy} sx={BULK_INVITE_ACTION_SX}>
|
|
142
143
|
{t('Account.BULK_INVITE_UPLOAD', 'Upload CSV')}
|
|
143
144
|
<input type="file" accept=".csv,text/csv" hidden onChange={handleFile} />
|
|
144
145
|
</Button>
|
|
145
|
-
<Button variant="contained" size="small" sx={
|
|
146
|
+
<Button variant="contained" size="small" sx={BULK_INVITE_ACTION_SX} onClick={handleInviteAll} disabled={busy || emails.length === 0}>
|
|
146
147
|
{t('Account.BULK_INVITE_SEND', 'Send Invites')}
|
|
147
148
|
</Button>
|
|
148
149
|
<Typography variant="body2">
|
|
@@ -9,6 +9,8 @@ import {
|
|
|
9
9
|
import { useTranslation } from 'react-i18next';
|
|
10
10
|
import { SocialLoginButtons } from './SocialLoginButtons';
|
|
11
11
|
|
|
12
|
+
export const LOGIN_FORM_DIVIDER_SX = { my: 2 };
|
|
13
|
+
|
|
12
14
|
export function LoginForm({
|
|
13
15
|
onSubmit,
|
|
14
16
|
onForgotPassword,
|
|
@@ -62,7 +64,7 @@ export function LoginForm({
|
|
|
62
64
|
{t('Auth.LOGIN_USE_PASSKEY_BUTTON')}
|
|
63
65
|
</Button>
|
|
64
66
|
|
|
65
|
-
<Divider sx={
|
|
67
|
+
<Divider sx={LOGIN_FORM_DIVIDER_SX}>
|
|
66
68
|
{t('Auth.LOGIN_OR')}
|
|
67
69
|
</Divider>
|
|
68
70
|
</>
|
|
@@ -109,7 +111,7 @@ export function LoginForm({
|
|
|
109
111
|
{/* Other ways to sign in */}
|
|
110
112
|
{onSocialLogin && (
|
|
111
113
|
<Box>
|
|
112
|
-
<Divider sx={
|
|
114
|
+
<Divider sx={LOGIN_FORM_DIVIDER_SX}>
|
|
113
115
|
{t('Auth.LOGIN_OR')}
|
|
114
116
|
</Divider>
|
|
115
117
|
<SocialLoginButtons
|
|
@@ -121,7 +123,7 @@ export function LoginForm({
|
|
|
121
123
|
{/* Account actions */}
|
|
122
124
|
{(onSignUp || onForgotPassword) && (
|
|
123
125
|
<Box>
|
|
124
|
-
<Divider sx={
|
|
126
|
+
<Divider sx={LOGIN_FORM_DIVIDER_SX}>
|
|
125
127
|
{t('Auth.LOGIN_OR')}
|
|
126
128
|
</Divider>
|
|
127
129
|
|
|
@@ -20,6 +20,13 @@ import ContentCopyIcon from '@mui/icons-material/ContentCopy';
|
|
|
20
20
|
import { useTranslation } from 'react-i18next';
|
|
21
21
|
import { fetchAuthenticators, requestTotpKey, activateTotp, deactivateTotp, fetchRecoveryCodes, generateRecoveryCodes } from '../auth/authApi';
|
|
22
22
|
|
|
23
|
+
export const MFA_ALERT_SX = { mb: 2 };
|
|
24
|
+
export const MFA_CARD_SX = { mb: 3 };
|
|
25
|
+
export const MFA_ACTIVE_CARD_SX = { bgcolor: '#f0fdf4' };
|
|
26
|
+
export const MFA_TEXT_FIELD_SX = { mb: 2 };
|
|
27
|
+
export const MFA_DIVIDER_SX = { my: 3 };
|
|
28
|
+
export const MFA_RECOVERY_BUTTON_SX = { mt: 2 };
|
|
29
|
+
|
|
23
30
|
export function MFAComponent() {
|
|
24
31
|
const { t } = useTranslation();
|
|
25
32
|
|
|
@@ -165,14 +172,14 @@ export function MFAComponent() {
|
|
|
165
172
|
</Typography>
|
|
166
173
|
|
|
167
174
|
{errorKey && (
|
|
168
|
-
<Alert severity="error" sx={
|
|
175
|
+
<Alert severity="error" sx={MFA_ALERT_SX}>
|
|
169
176
|
{t(errorKey)}
|
|
170
177
|
</Alert>
|
|
171
178
|
)}
|
|
172
179
|
|
|
173
180
|
{/* --- CASE 1: TOTP IS ACTIVE --- */}
|
|
174
181
|
{totpAuthenticator ? (
|
|
175
|
-
<Card variant="outlined" sx={
|
|
182
|
+
<Card variant="outlined" sx={[MFA_CARD_SX, MFA_ACTIVE_CARD_SX]}>
|
|
176
183
|
<CardContent
|
|
177
184
|
sx={{
|
|
178
185
|
display: 'flex',
|
|
@@ -209,7 +216,7 @@ export function MFAComponent() {
|
|
|
209
216
|
|
|
210
217
|
{/* --- WIZARD: SETUP --- */}
|
|
211
218
|
{isSettingUp && totpData && (
|
|
212
|
-
<Card variant="outlined" sx={
|
|
219
|
+
<Card variant="outlined" sx={MFA_CARD_SX}>
|
|
213
220
|
<CardContent>
|
|
214
221
|
<Typography variant="subtitle1" gutterBottom>
|
|
215
222
|
{t('Auth.MFA_TOTP_QR_TITLE')}
|
|
@@ -251,7 +258,7 @@ export function MFAComponent() {
|
|
|
251
258
|
onChange={(e) => setVerifyCode(e.target.value)}
|
|
252
259
|
fullWidth
|
|
253
260
|
required
|
|
254
|
-
sx={
|
|
261
|
+
sx={MFA_TEXT_FIELD_SX}
|
|
255
262
|
autoComplete="off"
|
|
256
263
|
/>
|
|
257
264
|
<Stack direction="row" spacing={1}>
|
|
@@ -278,7 +285,7 @@ export function MFAComponent() {
|
|
|
278
285
|
</Card>
|
|
279
286
|
)}
|
|
280
287
|
|
|
281
|
-
<Divider sx={
|
|
288
|
+
<Divider sx={MFA_DIVIDER_SX} />
|
|
282
289
|
|
|
283
290
|
{/* --- RECOVERY CODES --- */}
|
|
284
291
|
<Typography variant="h6" gutterBottom>
|
|
@@ -294,7 +301,7 @@ export function MFAComponent() {
|
|
|
294
301
|
</Button>
|
|
295
302
|
) : (
|
|
296
303
|
<Box sx={{ bgcolor: '#f5f5f5', p: 2, borderRadius: 1 }}>
|
|
297
|
-
<Alert severity="warning" sx={
|
|
304
|
+
<Alert severity="warning" sx={MFA_ALERT_SX}>
|
|
298
305
|
{t('Auth.MFA_RECOVERY_WARNING')}
|
|
299
306
|
</Alert>
|
|
300
307
|
<Stack direction="row" flexWrap="wrap" gap={2}>
|
|
@@ -326,7 +333,7 @@ export function MFAComponent() {
|
|
|
326
333
|
))}
|
|
327
334
|
</Stack>
|
|
328
335
|
<Button
|
|
329
|
-
sx={
|
|
336
|
+
sx={MFA_RECOVERY_BUTTON_SX}
|
|
330
337
|
size="small"
|
|
331
338
|
onClick={handleGenerateNewRecoveryCodes}
|
|
332
339
|
>
|
|
@@ -16,6 +16,10 @@ import { useTranslation } from 'react-i18next';
|
|
|
16
16
|
import { authenticateWithMFA, fetchCurrentUser, requestMfaSupportHelp } from '../auth/authApi';
|
|
17
17
|
import { loginWithPasskey } from '../utils/authService';
|
|
18
18
|
|
|
19
|
+
export const MFA_LOGIN_ALERT_SX = { mb: 2 };
|
|
20
|
+
export const MFA_LOGIN_DIVIDER_SX = { my: 2 };
|
|
21
|
+
export const MFA_LOGIN_TEXT_FIELD_SX = { mb: 2 };
|
|
22
|
+
|
|
19
23
|
export function MfaLoginComponent({ availableTypes, identifier, onSuccess, onCancel }) {
|
|
20
24
|
const { t } = useTranslation();
|
|
21
25
|
const [code, setCode] = useState('');
|
|
@@ -113,13 +117,13 @@ export function MfaLoginComponent({ availableTypes, identifier, onSuccess, onCan
|
|
|
113
117
|
</Typography>
|
|
114
118
|
|
|
115
119
|
{errorKey && (
|
|
116
|
-
<Alert severity="error" sx={
|
|
120
|
+
<Alert severity="error" sx={MFA_LOGIN_ALERT_SX}>
|
|
117
121
|
{t(errorKey)}
|
|
118
122
|
</Alert>
|
|
119
123
|
)}
|
|
120
124
|
|
|
121
125
|
{infoKey && (
|
|
122
|
-
<Alert severity="info" sx={
|
|
126
|
+
<Alert severity="info" sx={MFA_LOGIN_ALERT_SX}>
|
|
123
127
|
{t(infoKey)}
|
|
124
128
|
</Alert>
|
|
125
129
|
)}
|
|
@@ -136,7 +140,7 @@ export function MfaLoginComponent({ availableTypes, identifier, onSuccess, onCan
|
|
|
136
140
|
</Button>
|
|
137
141
|
)}
|
|
138
142
|
|
|
139
|
-
<Divider sx={
|
|
143
|
+
<Divider sx={MFA_LOGIN_DIVIDER_SX}>
|
|
140
144
|
{t('Auth.LOGIN_OR')}
|
|
141
145
|
</Divider>
|
|
142
146
|
|
|
@@ -152,7 +156,7 @@ export function MfaLoginComponent({ availableTypes, identifier, onSuccess, onCan
|
|
|
152
156
|
fullWidth
|
|
153
157
|
disabled={submitting || helpRequested}
|
|
154
158
|
autoComplete="one-time-code"
|
|
155
|
-
sx={
|
|
159
|
+
sx={MFA_LOGIN_TEXT_FIELD_SX}
|
|
156
160
|
/>
|
|
157
161
|
<Button
|
|
158
162
|
type="submit"
|
|
@@ -165,7 +169,7 @@ export function MfaLoginComponent({ availableTypes, identifier, onSuccess, onCan
|
|
|
165
169
|
</Box>
|
|
166
170
|
)}
|
|
167
171
|
|
|
168
|
-
<Divider sx={
|
|
172
|
+
<Divider sx={MFA_LOGIN_DIVIDER_SX}>
|
|
169
173
|
{t('Auth.LOGIN_OR')}
|
|
170
174
|
</Divider>
|
|
171
175
|
|
|
@@ -22,6 +22,9 @@ import { fetchPasskeys, deletePasskey } from '../auth/authApi';
|
|
|
22
22
|
import { registerPasskey } from '../utils/authService';
|
|
23
23
|
import { FEATURES } from '../auth/authConfig';
|
|
24
24
|
|
|
25
|
+
export const PASSKEYS_ALERT_SX = { mb: 2 };
|
|
26
|
+
export const PASSKEYS_DIVIDER_SX = { mb: 2 };
|
|
27
|
+
|
|
25
28
|
export function PasskeysComponent() {
|
|
26
29
|
const { t } = useTranslation();
|
|
27
30
|
|
|
@@ -131,18 +134,18 @@ export function PasskeysComponent() {
|
|
|
131
134
|
</Typography>
|
|
132
135
|
|
|
133
136
|
{!passkeysSupported && (
|
|
134
|
-
<Alert severity="info" sx={
|
|
137
|
+
<Alert severity="info" sx={PASSKEYS_ALERT_SX}>
|
|
135
138
|
{t('Auth.PASSKEY_BROWSER_NOT_SUPPORTED')}
|
|
136
139
|
</Alert>
|
|
137
140
|
)}
|
|
138
141
|
|
|
139
142
|
{messageKey && (
|
|
140
|
-
<Alert severity="success" sx={
|
|
143
|
+
<Alert severity="success" sx={PASSKEYS_ALERT_SX}>
|
|
141
144
|
{t(messageKey)}
|
|
142
145
|
</Alert>
|
|
143
146
|
)}
|
|
144
147
|
{errorKey && (
|
|
145
|
-
<Alert severity="error" sx={
|
|
148
|
+
<Alert severity="error" sx={PASSKEYS_ALERT_SX}>
|
|
146
149
|
{t(errorKey)}
|
|
147
150
|
</Alert>
|
|
148
151
|
)}
|
|
@@ -176,7 +179,7 @@ export function PasskeysComponent() {
|
|
|
176
179
|
</Box>
|
|
177
180
|
)}
|
|
178
181
|
|
|
179
|
-
<Divider sx={
|
|
182
|
+
<Divider sx={PASSKEYS_DIVIDER_SX} />
|
|
180
183
|
|
|
181
184
|
{/* List of existing passkeys */}
|
|
182
185
|
{loading ? (
|
|
@@ -11,6 +11,8 @@ import { useTranslation } from 'react-i18next';
|
|
|
11
11
|
import { QRCodeSVG } from 'qrcode.react';
|
|
12
12
|
import { createSignupQr } from '../auth/authApi';
|
|
13
13
|
|
|
14
|
+
export const QR_SIGNUP_MANAGER_ALERT_SX = { mb: 2 };
|
|
15
|
+
|
|
14
16
|
const DEFAULT_EXPIRY_DAYS = 90;
|
|
15
17
|
const DEFAULT_MAX_REDEMPTIONS = 1;
|
|
16
18
|
|
|
@@ -297,15 +299,15 @@ export function QrSignupManager({
|
|
|
297
299
|
/>
|
|
298
300
|
</Box>
|
|
299
301
|
|
|
300
|
-
{error && <Alert severity="error" sx={
|
|
301
|
-
{success && <Alert severity="success" sx={
|
|
302
|
+
{error && <Alert severity="error" sx={QR_SIGNUP_MANAGER_ALERT_SX}>{error}</Alert>}
|
|
303
|
+
{success && <Alert severity="success" sx={QR_SIGNUP_MANAGER_ALERT_SX}>{success}</Alert>}
|
|
302
304
|
{copyState === 'copied' && (
|
|
303
|
-
<Alert severity="success" sx={
|
|
305
|
+
<Alert severity="success" sx={QR_SIGNUP_MANAGER_ALERT_SX}>
|
|
304
306
|
{t('Auth.SIGNUP_QR_LINK_COPIED', 'Signup link copied.')}
|
|
305
307
|
</Alert>
|
|
306
308
|
)}
|
|
307
309
|
{copyState === 'error' && (
|
|
308
|
-
<Alert severity="warning" sx={
|
|
310
|
+
<Alert severity="warning" sx={QR_SIGNUP_MANAGER_ALERT_SX}>
|
|
309
311
|
{t('Auth.SIGNUP_QR_COPY_UNAVAILABLE', 'Copying the link is not available in this browser.')}
|
|
310
312
|
</Alert>
|
|
311
313
|
)}
|
|
@@ -10,6 +10,10 @@ import {
|
|
|
10
10
|
import { useTranslation } from 'react-i18next';
|
|
11
11
|
import { updateAuthPolicy } from '../auth/authApi';
|
|
12
12
|
|
|
13
|
+
export const QR_SIGNUP_VALIDITY_ALERT_SX = { mb: 2 };
|
|
14
|
+
export const QR_SIGNUP_VALIDITY_FIELD_SX = { flex: 1 };
|
|
15
|
+
export const QR_SIGNUP_VALIDITY_BUTTON_SX = { minWidth: 120, mt: { sm: '8px' } };
|
|
16
|
+
|
|
13
17
|
const DEFAULT_EXPIRY_DAYS = 90;
|
|
14
18
|
|
|
15
19
|
function clampExpiryDays(value) {
|
|
@@ -71,8 +75,8 @@ export function QrSignupValidityManager({
|
|
|
71
75
|
)}
|
|
72
76
|
</Typography>
|
|
73
77
|
|
|
74
|
-
{error && <Alert severity="error" sx={
|
|
75
|
-
{success && <Alert severity="success" sx={
|
|
78
|
+
{error && <Alert severity="error" sx={QR_SIGNUP_VALIDITY_ALERT_SX}>{error}</Alert>}
|
|
79
|
+
{success && <Alert severity="success" sx={QR_SIGNUP_VALIDITY_ALERT_SX}>{success}</Alert>}
|
|
76
80
|
|
|
77
81
|
<Stack direction={{ xs: 'column', sm: 'row' }} spacing={1.5} alignItems={{ sm: 'flex-start' }}>
|
|
78
82
|
<TextField
|
|
@@ -85,13 +89,13 @@ export function QrSignupValidityManager({
|
|
|
85
89
|
value={currentExpiryDays}
|
|
86
90
|
onChange={(event) => setCurrentExpiryDays(event.target.value)}
|
|
87
91
|
disabled={busy || !canEdit}
|
|
88
|
-
sx={
|
|
92
|
+
sx={QR_SIGNUP_VALIDITY_FIELD_SX}
|
|
89
93
|
/>
|
|
90
94
|
<Button
|
|
91
95
|
variant="contained"
|
|
92
96
|
onClick={handleSave}
|
|
93
97
|
disabled={busy || !canEdit}
|
|
94
|
-
sx={
|
|
98
|
+
sx={QR_SIGNUP_VALIDITY_BUTTON_SX}
|
|
95
99
|
>
|
|
96
100
|
{t('Common.SAVE', 'Save')}
|
|
97
101
|
</Button>
|
|
@@ -11,6 +11,9 @@ import {
|
|
|
11
11
|
import { useTranslation } from 'react-i18next';
|
|
12
12
|
import { updateAuthPolicy } from '../auth/authApi';
|
|
13
13
|
|
|
14
|
+
export const REGISTRATION_METHODS_ALERT_SX = { mb: 2 };
|
|
15
|
+
export const REGISTRATION_METHODS_INFO_SX = { mt: 2 };
|
|
16
|
+
|
|
14
17
|
const EMPTY_POLICY = {
|
|
15
18
|
allow_admin_invite: true,
|
|
16
19
|
allow_self_signup_access_code: false,
|
|
@@ -76,9 +79,9 @@ export function RegistrationMethodsManager({
|
|
|
76
79
|
<Typography variant="body2" sx={{ mb: 2, color: 'text.secondary' }}>
|
|
77
80
|
{t('Auth.REGISTRATION_METHODS_HINT', 'Choose which signup and invite flows are active for this app.')}
|
|
78
81
|
</Typography>
|
|
79
|
-
{error && <Alert severity="error" sx={
|
|
80
|
-
{saveError && <Alert severity="error" sx={
|
|
81
|
-
{success && <Alert severity="success" sx={
|
|
82
|
+
{error && <Alert severity="error" sx={REGISTRATION_METHODS_ALERT_SX}>{error}</Alert>}
|
|
83
|
+
{saveError && <Alert severity="error" sx={REGISTRATION_METHODS_ALERT_SX}>{saveError}</Alert>}
|
|
84
|
+
{success && <Alert severity="success" sx={REGISTRATION_METHODS_ALERT_SX}>{success}</Alert>}
|
|
82
85
|
|
|
83
86
|
<Stack spacing={1}>
|
|
84
87
|
<FormControlLabel
|
|
@@ -134,7 +137,7 @@ export function RegistrationMethodsManager({
|
|
|
134
137
|
</Stack>
|
|
135
138
|
|
|
136
139
|
{policyState.allow_self_signup_email_domain && !(policyState.allowed_email_domains || []).length && (
|
|
137
|
-
<Alert severity="info" sx={
|
|
140
|
+
<Alert severity="info" sx={REGISTRATION_METHODS_INFO_SX}>
|
|
138
141
|
{t(
|
|
139
142
|
'Auth.EMAIL_DOMAIN_CURRENTLY_BLOCKED_HINT',
|
|
140
143
|
'Email-domain signup is enabled, but it stays blocked until at least one allowed domain is saved.',
|
|
@@ -15,6 +15,9 @@ import { changePassword } from '../auth/authApi';
|
|
|
15
15
|
import { startSocialLogin } from '../utils/authService';
|
|
16
16
|
import { AuthContext } from '../auth/AuthContext';
|
|
17
17
|
|
|
18
|
+
export const SECURITY_ALERT_SX = { mb: 2 };
|
|
19
|
+
export const SECURITY_DIVIDER_SX = { my: 3 };
|
|
20
|
+
|
|
18
21
|
export function SecurityComponent({
|
|
19
22
|
fromRecovery = false,
|
|
20
23
|
fromWeakLogin = false,
|
|
@@ -75,24 +78,24 @@ export function SecurityComponent({
|
|
|
75
78
|
return (
|
|
76
79
|
<Box>
|
|
77
80
|
{fromRecovery && (
|
|
78
|
-
<Alert severity="warning" sx={
|
|
81
|
+
<Alert severity="warning" sx={SECURITY_ALERT_SX}>
|
|
79
82
|
{t('Security.RECOVERY_LOGIN_WARNING')}
|
|
80
83
|
</Alert>
|
|
81
84
|
)}
|
|
82
85
|
|
|
83
86
|
{fromWeakLogin && (
|
|
84
|
-
<Alert severity="warning" sx={
|
|
87
|
+
<Alert severity="warning" sx={SECURITY_ALERT_SX}>
|
|
85
88
|
{t('Security.WEAK_LOGIN_WARNING')}
|
|
86
89
|
</Alert>
|
|
87
90
|
)}
|
|
88
91
|
|
|
89
92
|
{messageKey && (
|
|
90
|
-
<Alert severity="success" sx={
|
|
93
|
+
<Alert severity="success" sx={SECURITY_ALERT_SX}>
|
|
91
94
|
{t(messageKey)}
|
|
92
95
|
</Alert>
|
|
93
96
|
)}
|
|
94
97
|
{errorKey && (
|
|
95
|
-
<Alert severity="error" sx={
|
|
98
|
+
<Alert severity="error" sx={SECURITY_ALERT_SX}>
|
|
96
99
|
{t(errorKey)}
|
|
97
100
|
</Alert>
|
|
98
101
|
)}
|
|
@@ -103,7 +106,7 @@ export function SecurityComponent({
|
|
|
103
106
|
{t('Auth.LOGIN_PASSWORD_LABEL')}
|
|
104
107
|
</Typography>
|
|
105
108
|
<PasswordChangeForm onSubmit={handlePasswordChange} />
|
|
106
|
-
{needsDividerAfter('password') && <Divider sx={
|
|
109
|
+
{needsDividerAfter('password') && <Divider sx={SECURITY_DIVIDER_SX} />}
|
|
107
110
|
</>
|
|
108
111
|
)}
|
|
109
112
|
|
|
@@ -119,14 +122,14 @@ export function SecurityComponent({
|
|
|
119
122
|
onProviderClick={handleSocialClick}
|
|
120
123
|
providers={socialProviders}
|
|
121
124
|
/>
|
|
122
|
-
{needsDividerAfter('social') && <Divider sx={
|
|
125
|
+
{needsDividerAfter('social') && <Divider sx={SECURITY_DIVIDER_SX} />}
|
|
123
126
|
</>
|
|
124
127
|
)}
|
|
125
128
|
|
|
126
129
|
{passkeysEnabled && (
|
|
127
130
|
<>
|
|
128
131
|
<PasskeysComponent />
|
|
129
|
-
{needsDividerAfter('passkeys') && <Divider sx={
|
|
132
|
+
{needsDividerAfter('passkeys') && <Divider sx={SECURITY_DIVIDER_SX} />}
|
|
130
133
|
</>
|
|
131
134
|
)}
|
|
132
135
|
|
|
@@ -33,6 +33,10 @@ import {
|
|
|
33
33
|
updateUserSupportStatus,
|
|
34
34
|
} from '../auth/authApi';
|
|
35
35
|
|
|
36
|
+
export const SUPPORT_RECOVERY_AGENT_PAPER_SX = { p: 2, mb: 3 };
|
|
37
|
+
export const SUPPORT_RECOVERY_ALERT_SX = { mb: 2 };
|
|
38
|
+
export const SUPPORT_RECOVERY_AGENT_BUTTON_SX = { minWidth: 160 };
|
|
39
|
+
|
|
36
40
|
export function SupportRecoveryRequestsTab() {
|
|
37
41
|
const { t, i18n } = useTranslation();
|
|
38
42
|
const { user } = useContext(AuthContext);
|
|
@@ -198,7 +202,7 @@ export function SupportRecoveryRequestsTab() {
|
|
|
198
202
|
return (
|
|
199
203
|
<Box>
|
|
200
204
|
{canManageAgents && (
|
|
201
|
-
<Paper variant="outlined" sx={
|
|
205
|
+
<Paper variant="outlined" sx={SUPPORT_RECOVERY_AGENT_PAPER_SX}>
|
|
202
206
|
<Typography variant="h6" gutterBottom>
|
|
203
207
|
{t('Support.AGENTS_TITLE', 'Support agents')}
|
|
204
208
|
</Typography>
|
|
@@ -211,7 +215,7 @@ export function SupportRecoveryRequestsTab() {
|
|
|
211
215
|
</Typography>
|
|
212
216
|
|
|
213
217
|
{agentErrorKey && (
|
|
214
|
-
<Alert severity="error" sx={
|
|
218
|
+
<Alert severity="error" sx={SUPPORT_RECOVERY_ALERT_SX}>
|
|
215
219
|
{t(agentErrorKey)}
|
|
216
220
|
</Alert>
|
|
217
221
|
)}
|
|
@@ -242,7 +246,7 @@ export function SupportRecoveryRequestsTab() {
|
|
|
242
246
|
variant="contained"
|
|
243
247
|
onClick={handleAddAgent}
|
|
244
248
|
disabled={!selectedAgentCandidate || agentLoading || Boolean(agentActionUserId)}
|
|
245
|
-
sx={
|
|
249
|
+
sx={SUPPORT_RECOVERY_AGENT_BUTTON_SX}
|
|
246
250
|
>
|
|
247
251
|
{t('Support.AGENTS_ADD', 'Add as agent')}
|
|
248
252
|
</Button>
|
|
@@ -305,7 +309,7 @@ export function SupportRecoveryRequestsTab() {
|
|
|
305
309
|
</Typography>
|
|
306
310
|
|
|
307
311
|
{errorKey && (
|
|
308
|
-
<Alert severity="error" sx={
|
|
312
|
+
<Alert severity="error" sx={SUPPORT_RECOVERY_ALERT_SX}>
|
|
309
313
|
{t(errorKey)}
|
|
310
314
|
</Alert>
|
|
311
315
|
)}
|
|
@@ -3,19 +3,19 @@ import { Box, TextField, Button, Typography, Alert, CircularProgress } from '@mu
|
|
|
3
3
|
import { sendAdminInvite } from '../auth/authApi';
|
|
4
4
|
import { useTranslation } from 'react-i18next';
|
|
5
5
|
|
|
6
|
+
export const USER_INVITE_ACTION_SX = {
|
|
7
|
+
minWidth: 120,
|
|
8
|
+
height: 40,
|
|
9
|
+
whiteSpace: 'nowrap',
|
|
10
|
+
};
|
|
11
|
+
export const USER_INVITE_ALERT_SX = { mb: 2 };
|
|
12
|
+
|
|
6
13
|
export function UserInviteComponent() { // FIX: Removed apiUrl prop
|
|
7
14
|
const { t } = useTranslation();
|
|
8
15
|
const [inviteEmail, setInviteEmail] = useState('');
|
|
9
16
|
const [message, setMessage] = useState('');
|
|
10
17
|
const [error, setError] = useState('');
|
|
11
18
|
const [loading, setLoading] = useState(false);
|
|
12
|
-
const actionButtonSx = {
|
|
13
|
-
minWidth: 120,
|
|
14
|
-
height: 40,
|
|
15
|
-
textTransform: 'none',
|
|
16
|
-
whiteSpace: 'nowrap',
|
|
17
|
-
};
|
|
18
|
-
|
|
19
19
|
const inviteUser = async () => {
|
|
20
20
|
setMessage('');
|
|
21
21
|
setError('');
|
|
@@ -43,8 +43,8 @@ export function UserInviteComponent() { // FIX: Removed apiUrl prop
|
|
|
43
43
|
{t('Auth.INVITE_TITLE', 'Invite a new user')}
|
|
44
44
|
</Typography>
|
|
45
45
|
|
|
46
|
-
{message && <Alert severity="success" sx={
|
|
47
|
-
{error && <Alert severity="error" sx={
|
|
46
|
+
{message && <Alert severity="success" sx={USER_INVITE_ALERT_SX}>{message}</Alert>}
|
|
47
|
+
{error && <Alert severity="error" sx={USER_INVITE_ALERT_SX}>{error}</Alert>}
|
|
48
48
|
|
|
49
49
|
<Box sx={{ display: 'flex', gap: 2, alignItems: 'center' }}>
|
|
50
50
|
<TextField
|
|
@@ -65,7 +65,7 @@ export function UserInviteComponent() { // FIX: Removed apiUrl prop
|
|
|
65
65
|
size="small"
|
|
66
66
|
onClick={inviteUser}
|
|
67
67
|
disabled={loading || !inviteEmail}
|
|
68
|
-
sx={
|
|
68
|
+
sx={USER_INVITE_ACTION_SX}
|
|
69
69
|
>
|
|
70
70
|
{loading ? <CircularProgress size={24} color="inherit" /> : t('Auth.INVITE_BUTTON', 'Invite')}
|
|
71
71
|
</Button>
|
|
@@ -27,6 +27,12 @@ import { fetchUsersList, deleteUser, updateUserRole } from '../auth/authApi';
|
|
|
27
27
|
|
|
28
28
|
const DEFAULT_ROLES = ['none', 'student', 'teacher', 'admin'];
|
|
29
29
|
|
|
30
|
+
export const USER_LIST_ACTION_SX = { minWidth: 90 };
|
|
31
|
+
export const USER_LIST_ALERT_SX = { mb: 2 };
|
|
32
|
+
export const USER_LIST_HEADER_CELL_SX = { fontWeight: 600, whiteSpace: 'nowrap' };
|
|
33
|
+
export const USER_LIST_EMPTY_CELL_SX = { py: 4, color: 'text.secondary' };
|
|
34
|
+
export const USER_LIST_BODY_CELL_SX = { verticalAlign: 'top', py: 1 };
|
|
35
|
+
|
|
30
36
|
export function UserListComponent({
|
|
31
37
|
roles = DEFAULT_ROLES,
|
|
32
38
|
currentUser,
|
|
@@ -54,7 +60,6 @@ export function UserListComponent({
|
|
|
54
60
|
const [page, setPage] = useState(0);
|
|
55
61
|
const [pageSize, setPageSize] = useState(25);
|
|
56
62
|
const controlSx = { minWidth: 140 };
|
|
57
|
-
const actionButtonSx = { textTransform: 'none', minWidth: 90 };
|
|
58
63
|
|
|
59
64
|
const loadUsers = useCallback(async () => {
|
|
60
65
|
setLoading(true);
|
|
@@ -419,7 +424,7 @@ export function UserListComponent({
|
|
|
419
424
|
variant="outlined"
|
|
420
425
|
onClick={() => runRowAction(action, row)}
|
|
421
426
|
disabled={isBusy || isDisabled}
|
|
422
|
-
sx={
|
|
427
|
+
sx={USER_LIST_ACTION_SX}
|
|
423
428
|
>
|
|
424
429
|
{typeof action.label === 'function'
|
|
425
430
|
? action.label({ user: row, t, currentUser, canEdit: canEdit(row) })
|
|
@@ -438,7 +443,7 @@ export function UserListComponent({
|
|
|
438
443
|
startIcon={<DeleteIcon />}
|
|
439
444
|
onClick={() => handleDelete(row.id)}
|
|
440
445
|
disabled={!canDelete(row)}
|
|
441
|
-
sx={
|
|
446
|
+
sx={USER_LIST_ACTION_SX}
|
|
442
447
|
>
|
|
443
448
|
{t('Common.DELETE', 'Delete')}
|
|
444
449
|
</Button>
|
|
@@ -508,7 +513,7 @@ export function UserListComponent({
|
|
|
508
513
|
/>
|
|
509
514
|
</Box>
|
|
510
515
|
|
|
511
|
-
{error && <Alert severity="error" sx={
|
|
516
|
+
{error && <Alert severity="error" sx={USER_LIST_ALERT_SX}>{t(error)}</Alert>}
|
|
512
517
|
|
|
513
518
|
{loading && (
|
|
514
519
|
<Box sx={{ display: 'flex', justifyContent: 'center', p: 3 }}>
|
|
@@ -527,7 +532,7 @@ export function UserListComponent({
|
|
|
527
532
|
key={col.field}
|
|
528
533
|
align={col.headerAlign || col.align || 'left'}
|
|
529
534
|
sortDirection={sortField === col.field ? sortDir : false}
|
|
530
|
-
sx={{ minWidth: col.minWidth
|
|
535
|
+
sx={[USER_LIST_HEADER_CELL_SX, { minWidth: col.minWidth }]}
|
|
531
536
|
>
|
|
532
537
|
{col.sortable !== false ? (
|
|
533
538
|
<TableSortLabel
|
|
@@ -550,7 +555,7 @@ export function UserListComponent({
|
|
|
550
555
|
<TableCell
|
|
551
556
|
colSpan={columns.length}
|
|
552
557
|
align="center"
|
|
553
|
-
sx={
|
|
558
|
+
sx={USER_LIST_EMPTY_CELL_SX}
|
|
554
559
|
>
|
|
555
560
|
{t('UserList.NO_USERS', 'No users found.')}
|
|
556
561
|
</TableCell>
|
|
@@ -562,7 +567,7 @@ export function UserListComponent({
|
|
|
562
567
|
<TableCell
|
|
563
568
|
key={col.field}
|
|
564
569
|
align={col.align || 'left'}
|
|
565
|
-
sx={
|
|
570
|
+
sx={USER_LIST_BODY_CELL_SX}
|
|
566
571
|
>
|
|
567
572
|
{col.renderCell(row)}
|
|
568
573
|
</TableCell>
|