@micha.bigler/ui-core-micha 2.2.2 → 2.2.3

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.
@@ -77,5 +77,5 @@ export function QrSignupManager({ enabled = false }) {
77
77
  active = false;
78
78
  };
79
79
  }, [enabled, label, t]);
80
- return (_jsxs(Box, { children: [_jsx(Typography, { variant: "h6", gutterBottom: true, children: t('Auth.SIGNUP_QR_MANAGER_TITLE', 'QR Signup') }), _jsx(Typography, { variant: "body2", sx: { mb: 2, color: 'text.secondary' }, children: t('Auth.SIGNUP_QR_MANAGER_HINT', 'When QR signup is enabled, a signup QR code is shown here immediately.') }), error && _jsx(Alert, { severity: "error", sx: { mb: 2 }, children: error }), success && _jsx(Alert, { severity: "success", sx: { mb: 2 }, children: success }), !enabled && (_jsx(Alert, { severity: "info", sx: { mb: 2 }, children: t('Auth.SIGNUP_QR_DISABLED_HINT', 'Enable self-signup by QR above to show a QR code here.') })), _jsx(TextField, { label: t('Common.LABEL', 'Label'), value: label, onChange: (event) => setLabel(event.target.value), fullWidth: true, disabled: !enabled || busy }), _jsx(Button, { variant: "contained", sx: { mt: 2 }, onClick: generate, disabled: !enabled || busy, children: t('Common.SAVE', 'Save') }), (result === null || result === void 0 ? void 0 : result.signup_url) && (_jsxs(Box, { sx: { mt: 3 }, children: [_jsx(QRCodeSVG, { value: result.signup_url, size: 180, includeMargin: true }), _jsx(TextField, { label: t('Auth.SIGNUP_QR_LINK_LABEL', 'Signup link'), value: result.signup_url, fullWidth: true, multiline: true, minRows: 2, sx: { mt: 2 }, InputProps: { readOnly: true } })] }))] }));
80
+ return (_jsxs(Box, { children: [_jsx(Typography, { variant: "h6", gutterBottom: true, children: t('Auth.SIGNUP_QR_MANAGER_TITLE', 'QR Signup') }), _jsx(Typography, { variant: "body2", sx: { mb: 2, color: 'text.secondary' }, children: t('Auth.SIGNUP_QR_MANAGER_HINT', 'When QR signup is enabled, a signup QR code is shown here immediately.') }), _jsx(Typography, { variant: "body2", sx: { mb: 2, color: 'text.secondary' }, children: t('Auth.SIGNUP_QR_POLICY_HINT', 'The default QR validity is configured in the authentication policy above.') }), error && _jsx(Alert, { severity: "error", sx: { mb: 2 }, children: error }), success && _jsx(Alert, { severity: "success", sx: { mb: 2 }, children: success }), !enabled && (_jsx(Alert, { severity: "info", sx: { mb: 2 }, children: t('Auth.SIGNUP_QR_DISABLED_HINT', 'Enable self-signup by QR above to show a QR code here.') })), _jsx(TextField, { label: t('Common.LABEL', 'Label'), value: label, onChange: (event) => setLabel(event.target.value), fullWidth: true, disabled: !enabled || busy }), _jsx(Button, { variant: "contained", sx: { mt: 2 }, onClick: generate, disabled: !enabled || busy, children: t('Common.SAVE', 'Save') }), (result === null || result === void 0 ? void 0 : result.signup_url) && (_jsxs(Box, { sx: { mt: 3 }, children: [_jsx(QRCodeSVG, { value: result.signup_url, size: 180, includeMargin: true }), _jsx(TextField, { label: t('Auth.SIGNUP_QR_LINK_LABEL', 'Signup link'), value: result.signup_url, fullWidth: true, multiline: true, minRows: 2, sx: { mt: 2 }, InputProps: { readOnly: true } }), (result === null || result === void 0 ? void 0 : result.expires_at) && (_jsxs(Typography, { variant: "body2", sx: { mt: 1.5, color: 'text.secondary' }, children: [t('Auth.SIGNUP_QR_VALID_UNTIL', 'Valid until'), ": ", result.expires_at] }))] }))] }));
81
81
  }
@@ -11,11 +11,13 @@ const EMPTY_POLICY = {
11
11
  allow_self_signup_qr: false,
12
12
  allowed_email_domains: [],
13
13
  required_auth_factor_count: 1,
14
+ signup_qr_expiry_days: 90,
14
15
  };
15
16
  export function RegistrationMethodsManager({ onPolicyChange }) {
16
17
  const { t } = useTranslation();
17
18
  const [policy, setPolicy] = useState(EMPTY_POLICY);
18
19
  const [domainsText, setDomainsText] = useState('');
20
+ const [signupQrExpiryDays, setSignupQrExpiryDays] = useState(String(EMPTY_POLICY.signup_qr_expiry_days));
19
21
  const [busy, setBusy] = useState(false);
20
22
  const [busyField, setBusyField] = useState('');
21
23
  const [error, setError] = useState('');
@@ -29,6 +31,7 @@ export function RegistrationMethodsManager({ onPolicyChange }) {
29
31
  return;
30
32
  setPolicy((prev) => (Object.assign(Object.assign({}, prev), data)));
31
33
  setDomainsText(((data === null || data === void 0 ? void 0 : data.allowed_email_domains) || []).join('\n'));
34
+ setSignupQrExpiryDays(String((data === null || data === void 0 ? void 0 : data.signup_qr_expiry_days) || EMPTY_POLICY.signup_qr_expiry_days));
32
35
  if (onPolicyChange)
33
36
  onPolicyChange(data);
34
37
  }
@@ -73,9 +76,16 @@ export function RegistrationMethodsManager({ onPolicyChange }) {
73
76
  .split(/\r?\n/)
74
77
  .map((value) => value.trim())
75
78
  .filter(Boolean);
76
- const next = await updateAuthPolicy({ allowed_email_domains });
79
+ const parsedExpiryDays = parseInt(signupQrExpiryDays, 10);
80
+ const next = await updateAuthPolicy({
81
+ allowed_email_domains,
82
+ signup_qr_expiry_days: Number.isFinite(parsedExpiryDays) && parsedExpiryDays > 0
83
+ ? parsedExpiryDays
84
+ : EMPTY_POLICY.signup_qr_expiry_days,
85
+ });
77
86
  setPolicy((prev) => (Object.assign(Object.assign({}, prev), next)));
78
87
  setDomainsText(((next === null || next === void 0 ? void 0 : next.allowed_email_domains) || allowed_email_domains).join('\n'));
88
+ setSignupQrExpiryDays(String((next === null || next === void 0 ? void 0 : next.signup_qr_expiry_days) || EMPTY_POLICY.signup_qr_expiry_days));
79
89
  setSuccess(t('Auth.AUTH_POLICY_SAVE_SUCCESS', 'Authentication settings saved.'));
80
90
  if (onPolicyChange)
81
91
  onPolicyChange(next);
@@ -87,5 +97,5 @@ export function RegistrationMethodsManager({ onPolicyChange }) {
87
97
  setBusy(false);
88
98
  }
89
99
  };
90
- return (_jsxs(Box, { children: [_jsx(Typography, { variant: "h6", gutterBottom: true, children: t('Auth.REGISTRATION_METHODS_TITLE', 'Registration Methods') }), _jsx(Typography, { variant: "body2", sx: { mb: 2, color: 'text.secondary' }, children: t('Auth.REGISTRATION_METHODS_HINT', 'Choose which signup and invite flows are active for this app.') }), error && _jsx(Alert, { severity: "error", sx: { mb: 2 }, children: error }), success && _jsx(Alert, { severity: "success", sx: { mb: 2 }, children: success }), _jsxs(Stack, { spacing: 1, children: [_jsx(FormControlLabel, { control: (_jsx(Switch, { checked: Boolean(policy.allow_admin_invite), onChange: toggle('allow_admin_invite'), disabled: Boolean(busyField) })), label: t('Auth.ADMIN_INVITE_LABEL', 'Admin invite') }), _jsx(FormControlLabel, { control: (_jsx(Switch, { checked: Boolean(policy.allow_self_signup_access_code), onChange: toggle('allow_self_signup_access_code'), disabled: Boolean(busyField) })), label: t('Auth.SIGNUP_ACCESS_CODE_LABEL', 'Self-signup with access code') }), _jsx(FormControlLabel, { control: (_jsx(Switch, { checked: Boolean(policy.allow_self_signup_open), onChange: toggle('allow_self_signup_open'), disabled: Boolean(busyField) })), label: t('Auth.SIGNUP_OPEN_LABEL', 'Open self-signup') }), _jsx(FormControlLabel, { control: (_jsx(Switch, { checked: Boolean(policy.allow_self_signup_email_domain), onChange: toggle('allow_self_signup_email_domain'), disabled: Boolean(busyField) })), label: t('Auth.SIGNUP_EMAIL_DOMAIN_LABEL', 'Self-signup by email domain') }), _jsx(FormControlLabel, { control: (_jsx(Switch, { checked: Boolean(policy.allow_self_signup_qr), onChange: toggle('allow_self_signup_qr'), disabled: Boolean(busyField) })), label: t('Auth.SIGNUP_QR_LABEL', 'Self-signup by QR') })] }), policy.allow_self_signup_email_domain && !(policy.allowed_email_domains || []).length && (_jsx(Alert, { severity: "info", sx: { mt: 2 }, children: t('Auth.EMAIL_DOMAIN_CURRENTLY_BLOCKED_HINT', 'Email-domain signup is enabled, but it stays blocked until at least one allowed domain is saved.') })), _jsx(TextField, { label: t('Auth.ALLOWED_EMAIL_DOMAINS_LABEL', 'Allowed email domains'), helperText: t('Auth.ALLOWED_EMAIL_DOMAINS_HINT', 'One domain per line, e.g. example.org. You can leave this empty temporarily.'), multiline: true, minRows: 3, fullWidth: true, sx: { mt: 2 }, value: domainsText, onChange: (event) => setDomainsText(event.target.value) }), _jsx(Button, { variant: "contained", sx: { mt: 2 }, onClick: save, disabled: busy, children: t('Common.SAVE', 'Save') })] }));
100
+ return (_jsxs(Box, { children: [_jsx(Typography, { variant: "h6", gutterBottom: true, children: t('Auth.REGISTRATION_METHODS_TITLE', 'Registration Methods') }), _jsx(Typography, { variant: "body2", sx: { mb: 2, color: 'text.secondary' }, children: t('Auth.REGISTRATION_METHODS_HINT', 'Choose which signup and invite flows are active for this app.') }), error && _jsx(Alert, { severity: "error", sx: { mb: 2 }, children: error }), success && _jsx(Alert, { severity: "success", sx: { mb: 2 }, children: success }), _jsxs(Stack, { spacing: 1, children: [_jsx(FormControlLabel, { control: (_jsx(Switch, { checked: Boolean(policy.allow_admin_invite), onChange: toggle('allow_admin_invite'), disabled: Boolean(busyField) })), label: t('Auth.ADMIN_INVITE_LABEL', 'Admin invite') }), _jsx(FormControlLabel, { control: (_jsx(Switch, { checked: Boolean(policy.allow_self_signup_access_code), onChange: toggle('allow_self_signup_access_code'), disabled: Boolean(busyField) })), label: t('Auth.SIGNUP_ACCESS_CODE_LABEL', 'Self-signup with access code') }), _jsx(FormControlLabel, { control: (_jsx(Switch, { checked: Boolean(policy.allow_self_signup_open), onChange: toggle('allow_self_signup_open'), disabled: Boolean(busyField) })), label: t('Auth.SIGNUP_OPEN_LABEL', 'Open self-signup') }), _jsx(FormControlLabel, { control: (_jsx(Switch, { checked: Boolean(policy.allow_self_signup_email_domain), onChange: toggle('allow_self_signup_email_domain'), disabled: Boolean(busyField) })), label: t('Auth.SIGNUP_EMAIL_DOMAIN_LABEL', 'Self-signup by email domain') }), _jsx(FormControlLabel, { control: (_jsx(Switch, { checked: Boolean(policy.allow_self_signup_qr), onChange: toggle('allow_self_signup_qr'), disabled: Boolean(busyField) })), label: t('Auth.SIGNUP_QR_LABEL', 'Self-signup by QR') })] }), policy.allow_self_signup_email_domain && !(policy.allowed_email_domains || []).length && (_jsx(Alert, { severity: "info", sx: { mt: 2 }, children: t('Auth.EMAIL_DOMAIN_CURRENTLY_BLOCKED_HINT', 'Email-domain signup is enabled, but it stays blocked until at least one allowed domain is saved.') })), _jsx(TextField, { label: t('Auth.ALLOWED_EMAIL_DOMAINS_LABEL', 'Allowed email domains'), helperText: t('Auth.ALLOWED_EMAIL_DOMAINS_HINT', 'One domain per line, e.g. example.org. You can leave this empty temporarily.'), multiline: true, minRows: 3, fullWidth: true, sx: { mt: 2 }, value: domainsText, onChange: (event) => setDomainsText(event.target.value) }), _jsx(TextField, { label: t('Auth.SIGNUP_QR_EXPIRY_DAYS_LABEL', 'QR signup validity (days)'), helperText: t('Auth.SIGNUP_QR_EXPIRY_DAYS_HINT', 'Default validity for newly generated QR signup links.'), type: "number", fullWidth: true, sx: { mt: 2 }, value: signupQrExpiryDays, onChange: (event) => setSignupQrExpiryDays(event.target.value) }), _jsx(Button, { variant: "contained", sx: { mt: 2 }, onClick: save, disabled: busy, children: t('Common.SAVE', 'Save') })] }));
91
101
  }
@@ -840,6 +840,12 @@ export const authTranslations = {
840
840
  "en": "Profile updated successfully.",
841
841
  "sw": "Wasifu umesasishwa kwa mafanikio."
842
842
  },
843
+ "Profile.NAME_LABEL": {
844
+ "de": "Name",
845
+ "fr": "Nom",
846
+ "en": "Name",
847
+ "sw": "Jina"
848
+ },
843
849
  "Profile.USERNAME_LABEL": {
844
850
  "de": "Benutzername",
845
851
  "fr": "Nom d’utilisateur",
@@ -92,7 +92,7 @@ export function SignUpPage() {
92
92
  const handleGoToLogin = () => {
93
93
  navigate('/login');
94
94
  };
95
- return (_jsxs(NarrowPage, { title: t('Auth.LOGIN_SIGNUP_BUTTON'), subtitle: pageSubtitle, children: [_jsx(Helmet, { children: _jsxs("title", { children: [t('App.NAME'), " \u2013 ", t('Auth.LOGIN_SIGNUP_BUTTON')] }) }), successKey && (_jsx(Alert, { severity: "success", sx: { mb: 2 }, children: t(successKey, { email }) })), errorKey && (_jsx(Alert, { severity: "error", sx: { mb: 2 }, children: t(errorKey, t('Auth.INVITE_FAILED', 'Could not complete signup.')) })), signupModes.length > 1 && (_jsx(Stack, { spacing: 1, sx: { mb: 2 }, children: _jsx(Stack, { spacing: 1, children: signupModes.map((entry) => (_jsx(Button, { variant: mode === entry ? 'contained' : 'outlined', onClick: () => setMode(entry), disabled: submitting, fullWidth: true, children: t(MODE_LABELS[entry] || entry, entry) }, entry))) }) })), _jsxs(Box, { component: "form", onSubmit: handleSubmit, sx: { display: 'flex', flexDirection: 'column', gap: 2 }, children: [_jsx(TextField, { label: t('Auth.EMAIL_LABEL'), type: "email", required: true, fullWidth: true, value: email, onChange: (e) => setEmail(e.target.value), disabled: submitting }), mode === 'self_signup_access_code' && (_jsx(TextField, { label: t('Auth.ACCESS_CODE_LABEL'), type: "text", required: true, fullWidth: true, value: accessCode, onChange: (e) => setAccessCode(e.target.value), disabled: submitting })), mode === 'self_signup_qr' && (_jsx(Stack, { spacing: 1, children: _jsx(TextField, { label: t('Auth.SIGNUP_QR_TOKEN_LABEL', 'QR token'), value: tokenFromUrl, fullWidth: true, InputProps: { readOnly: true } }) })), _jsx(Button, { type: "submit", variant: "contained", disabled: submitting || signupModes.length === 0, children: submitting
95
+ return (_jsxs(NarrowPage, { title: t('Auth.LOGIN_SIGNUP_BUTTON'), subtitle: pageSubtitle, children: [_jsx(Helmet, { children: _jsxs("title", { children: [t('App.NAME'), " \u2013 ", t('Auth.LOGIN_SIGNUP_BUTTON')] }) }), successKey && (_jsx(Alert, { severity: "success", sx: { mb: 2 }, children: t(successKey, { email }) })), errorKey && (_jsx(Alert, { severity: "error", sx: { mb: 2 }, children: t(errorKey, t('Auth.INVITE_FAILED', 'Could not complete signup.')) })), signupModes.length > 1 && (_jsx(Stack, { spacing: 1, sx: { mb: 2 }, children: _jsx(Stack, { spacing: 1, children: signupModes.map((entry) => (_jsx(Button, { variant: mode === entry ? 'contained' : 'outlined', onClick: () => setMode(entry), disabled: submitting, fullWidth: true, children: t(MODE_LABELS[entry] || entry, entry) }, entry))) }) })), _jsxs(Box, { component: "form", onSubmit: handleSubmit, sx: { display: 'flex', flexDirection: 'column', gap: 2 }, children: [_jsx(TextField, { label: t('Auth.EMAIL_LABEL'), type: "email", required: true, fullWidth: true, value: email, onChange: (e) => setEmail(e.target.value), disabled: submitting }), mode === 'self_signup_access_code' && (_jsx(TextField, { label: t('Auth.ACCESS_CODE_LABEL'), type: "text", required: true, fullWidth: true, value: accessCode, onChange: (e) => setAccessCode(e.target.value), disabled: submitting })), _jsx(Button, { type: "submit", variant: "contained", disabled: submitting || signupModes.length === 0, children: submitting
96
96
  ? t('Auth.SIGNUP_SUBMITTING')
97
97
  : t('Auth.SIGNUP_SUBMIT') })] }), _jsx(Box, { sx: { mt: 3 }, children: _jsxs(Typography, { variant: "body2", children: [t('Auth.SIGNUP_ALREADY_HAVE_ACCOUNT'), ' ', _jsx(Button, { onClick: handleGoToLogin, variant: "text", size: "small", children: t('Auth.SIGNUP_GO_TO_LOGIN') })] }) })] }));
98
98
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@micha.bigler/ui-core-micha",
3
- "version": "2.2.2",
3
+ "version": "2.2.3",
4
4
  "main": "dist/index.js",
5
5
  "module": "dist/index.js",
6
6
  "private": false,
@@ -88,6 +88,9 @@ export function QrSignupManager({ enabled = false }) {
88
88
  <Typography variant="body2" sx={{ mb: 2, color: 'text.secondary' }}>
89
89
  {t('Auth.SIGNUP_QR_MANAGER_HINT', 'When QR signup is enabled, a signup QR code is shown here immediately.')}
90
90
  </Typography>
91
+ <Typography variant="body2" sx={{ mb: 2, color: 'text.secondary' }}>
92
+ {t('Auth.SIGNUP_QR_POLICY_HINT', 'The default QR validity is configured in the authentication policy above.')}
93
+ </Typography>
91
94
  {error && <Alert severity="error" sx={{ mb: 2 }}>{error}</Alert>}
92
95
  {success && <Alert severity="success" sx={{ mb: 2 }}>{success}</Alert>}
93
96
 
@@ -121,6 +124,11 @@ export function QrSignupManager({ enabled = false }) {
121
124
  sx={{ mt: 2 }}
122
125
  InputProps={{ readOnly: true }}
123
126
  />
127
+ {result?.expires_at && (
128
+ <Typography variant="body2" sx={{ mt: 1.5, color: 'text.secondary' }}>
129
+ {t('Auth.SIGNUP_QR_VALID_UNTIL', 'Valid until')}: {result.expires_at}
130
+ </Typography>
131
+ )}
124
132
  </Box>
125
133
  )}
126
134
  </Box>
@@ -20,12 +20,14 @@ const EMPTY_POLICY = {
20
20
  allow_self_signup_qr: false,
21
21
  allowed_email_domains: [],
22
22
  required_auth_factor_count: 1,
23
+ signup_qr_expiry_days: 90,
23
24
  };
24
25
 
25
26
  export function RegistrationMethodsManager({ onPolicyChange }) {
26
27
  const { t } = useTranslation();
27
28
  const [policy, setPolicy] = useState(EMPTY_POLICY);
28
29
  const [domainsText, setDomainsText] = useState('');
30
+ const [signupQrExpiryDays, setSignupQrExpiryDays] = useState(String(EMPTY_POLICY.signup_qr_expiry_days));
29
31
  const [busy, setBusy] = useState(false);
30
32
  const [busyField, setBusyField] = useState('');
31
33
  const [error, setError] = useState('');
@@ -39,6 +41,7 @@ export function RegistrationMethodsManager({ onPolicyChange }) {
39
41
  if (!active) return;
40
42
  setPolicy((prev) => ({ ...prev, ...data }));
41
43
  setDomainsText((data?.allowed_email_domains || []).join('\n'));
44
+ setSignupQrExpiryDays(String(data?.signup_qr_expiry_days || EMPTY_POLICY.signup_qr_expiry_days));
42
45
  if (onPolicyChange) onPolicyChange(data);
43
46
  } catch (err) {
44
47
  if (active) {
@@ -80,9 +83,16 @@ export function RegistrationMethodsManager({ onPolicyChange }) {
80
83
  .split(/\r?\n/)
81
84
  .map((value) => value.trim())
82
85
  .filter(Boolean);
83
- const next = await updateAuthPolicy({ allowed_email_domains });
86
+ const parsedExpiryDays = parseInt(signupQrExpiryDays, 10);
87
+ const next = await updateAuthPolicy({
88
+ allowed_email_domains,
89
+ signup_qr_expiry_days: Number.isFinite(parsedExpiryDays) && parsedExpiryDays > 0
90
+ ? parsedExpiryDays
91
+ : EMPTY_POLICY.signup_qr_expiry_days,
92
+ });
84
93
  setPolicy((prev) => ({ ...prev, ...next }));
85
94
  setDomainsText((next?.allowed_email_domains || allowed_email_domains).join('\n'));
95
+ setSignupQrExpiryDays(String(next?.signup_qr_expiry_days || EMPTY_POLICY.signup_qr_expiry_days));
86
96
  setSuccess(t('Auth.AUTH_POLICY_SAVE_SUCCESS', 'Authentication settings saved.'));
87
97
  if (onPolicyChange) onPolicyChange(next);
88
98
  } catch (err) {
@@ -176,6 +186,19 @@ export function RegistrationMethodsManager({ onPolicyChange }) {
176
186
  onChange={(event) => setDomainsText(event.target.value)}
177
187
  />
178
188
 
189
+ <TextField
190
+ label={t('Auth.SIGNUP_QR_EXPIRY_DAYS_LABEL', 'QR signup validity (days)')}
191
+ helperText={t(
192
+ 'Auth.SIGNUP_QR_EXPIRY_DAYS_HINT',
193
+ 'Default validity for newly generated QR signup links.',
194
+ )}
195
+ type="number"
196
+ fullWidth
197
+ sx={{ mt: 2 }}
198
+ value={signupQrExpiryDays}
199
+ onChange={(event) => setSignupQrExpiryDays(event.target.value)}
200
+ />
201
+
179
202
  <Button variant="contained" sx={{ mt: 2 }} onClick={save} disabled={busy}>
180
203
  {t('Common.SAVE', 'Save')}
181
204
  </Button>
@@ -885,6 +885,12 @@ export const authTranslations = {
885
885
  "en": "Profile updated successfully.",
886
886
  "sw": "Wasifu umesasishwa kwa mafanikio."
887
887
  },
888
+ "Profile.NAME_LABEL": {
889
+ "de": "Name",
890
+ "fr": "Nom",
891
+ "en": "Name",
892
+ "sw": "Jina"
893
+ },
888
894
  "Profile.USERNAME_LABEL": {
889
895
  "de": "Benutzername",
890
896
  "fr": "Nom d’utilisateur",
@@ -186,17 +186,6 @@ export function SignUpPage() {
186
186
  />
187
187
  )}
188
188
 
189
- {mode === 'self_signup_qr' && (
190
- <Stack spacing={1}>
191
- <TextField
192
- label={t('Auth.SIGNUP_QR_TOKEN_LABEL', 'QR token')}
193
- value={tokenFromUrl}
194
- fullWidth
195
- InputProps={{ readOnly: true }}
196
- />
197
- </Stack>
198
- )}
199
-
200
189
  <Button
201
190
  type="submit"
202
191
  variant="contained"