@micha.bigler/ui-core-micha 2.1.16 → 2.1.18
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/dist/auth/AuthContext.js +34 -16
- package/dist/auth/authApi.js +19 -0
- package/dist/components/AccessCodeManager.js +8 -2
- package/dist/components/BulkInviteCsvTab.js +7 -1
- package/dist/components/LoginForm.js +2 -2
- package/dist/components/ProfileComponent.js +65 -5
- package/dist/components/SecurityComponent.js +23 -6
- package/dist/components/SocialLoginButtons.js +7 -4
- package/dist/components/UserInviteComponent.js +8 -2
- package/dist/components/UserListComponent.js +163 -21
- package/dist/i18n/authTranslations.js +30 -0
- package/dist/pages/AccountPage.js +11 -22
- package/dist/pages/LoginPage.js +16 -2
- package/dist/utils/authService.js +41 -2
- package/package.json +2 -1
- package/src/auth/AuthContext.jsx +64 -16
- package/src/auth/authApi.jsx +22 -1
- package/src/components/AccessCodeManager.jsx +14 -4
- package/src/components/BulkInviteCsvTab.jsx +9 -3
- package/src/components/LoginForm.jsx +58 -48
- package/src/components/ProfileComponent.jsx +117 -4
- package/src/components/SecurityComponent.jsx +58 -28
- package/src/components/SocialLoginButtons.jsx +57 -49
- package/src/components/UserInviteComponent.jsx +11 -4
- package/src/components/UserListComponent.jsx +252 -40
- package/src/i18n/authTranslations.ts +31 -1
- package/src/pages/AccountPage.jsx +34 -39
- package/src/pages/LoginPage.jsx +25 -6
- package/src/utils/authService.js +51 -3
package/dist/auth/AuthContext.js
CHANGED
|
@@ -2,33 +2,50 @@ import { jsx as _jsx } from "react/jsx-runtime";
|
|
|
2
2
|
// src/auth/AuthContext.jsx
|
|
3
3
|
import React, { createContext, useState, useEffect, } from 'react';
|
|
4
4
|
import { ensureCsrfToken } from './apiClient'; // <--- IMPORT ADDED
|
|
5
|
-
import { fetchCurrentUser, logoutSession, } from './authApi';
|
|
5
|
+
import { fetchAuthMethods, fetchCurrentUser, logoutSession, } from './authApi';
|
|
6
6
|
export const AuthContext = createContext(null);
|
|
7
|
+
const DEFAULT_AUTH_METHODS = {
|
|
8
|
+
password_login: true,
|
|
9
|
+
password_reset: true,
|
|
10
|
+
signup: true,
|
|
11
|
+
password_change: true,
|
|
12
|
+
social_login: true,
|
|
13
|
+
social_providers: ['google', 'microsoft'],
|
|
14
|
+
passkey_login: true,
|
|
15
|
+
passkeys_manage: true,
|
|
16
|
+
mfa_totp: true,
|
|
17
|
+
mfa_recovery_codes: true,
|
|
18
|
+
mfa_enabled: true,
|
|
19
|
+
};
|
|
7
20
|
export const AuthProvider = ({ children }) => {
|
|
8
21
|
const [user, setUser] = useState(null);
|
|
22
|
+
const [authMethods, setAuthMethods] = useState(DEFAULT_AUTH_METHODS);
|
|
9
23
|
const [loading, setLoading] = useState(true);
|
|
24
|
+
const mapUserFromApi = (data) => {
|
|
25
|
+
var _a, _b, _c, _d, _e, _f, _g, _h, _j, _k, _l;
|
|
26
|
+
const profile = (data === null || data === void 0 ? void 0 : data.profile) || {};
|
|
27
|
+
return Object.assign(Object.assign({}, data), { id: data === null || data === void 0 ? void 0 : data.id, username: data === null || data === void 0 ? void 0 : data.username, email: data === null || data === void 0 ? void 0 : data.email, first_name: data === null || data === void 0 ? void 0 : data.first_name, last_name: data === null || data === void 0 ? void 0 : data.last_name, role: (_b = (_a = data === null || data === void 0 ? void 0 : data.role) !== null && _a !== void 0 ? _a : profile === null || profile === void 0 ? void 0 : profile.role) !== null && _b !== void 0 ? _b : null, language: (_d = (_c = data === null || data === void 0 ? void 0 : data.language) !== null && _c !== void 0 ? _c : profile === null || profile === void 0 ? void 0 : profile.language) !== null && _d !== void 0 ? _d : 'en', is_superuser: Boolean(data === null || data === void 0 ? void 0 : data.is_superuser), is_new: Boolean((_e = data === null || data === void 0 ? void 0 : data.is_new) !== null && _e !== void 0 ? _e : profile === null || profile === void 0 ? void 0 : profile.is_new), is_invited: Boolean((_f = data === null || data === void 0 ? void 0 : data.is_invited) !== null && _f !== void 0 ? _f : profile === null || profile === void 0 ? void 0 : profile.is_invited), accepted_privacy_statement: Boolean((_g = data === null || data === void 0 ? void 0 : data.accepted_privacy_statement) !== null && _g !== void 0 ? _g : profile === null || profile === void 0 ? void 0 : profile.accepted_privacy_statement), accepted_convenience_cookies: Boolean((_h = data === null || data === void 0 ? void 0 : data.accepted_convenience_cookies) !== null && _h !== void 0 ? _h : profile === null || profile === void 0 ? void 0 : profile.accepted_convenience_cookies), is_support_agent: Boolean((_j = data === null || data === void 0 ? void 0 : data.is_support_agent) !== null && _j !== void 0 ? _j : profile === null || profile === void 0 ? void 0 : profile.is_support_agent), support_contact_id: (_l = (_k = data === null || data === void 0 ? void 0 : data.support_contact_id) !== null && _k !== void 0 ? _k : profile === null || profile === void 0 ? void 0 : profile.support_contact_id) !== null && _l !== void 0 ? _l : null, security_state: data === null || data === void 0 ? void 0 : data.security_state, available_roles: (data === null || data === void 0 ? void 0 : data.available_roles) || [], ui_permissions: (data === null || data === void 0 ? void 0 : data.ui_permissions) || {}, can_manage_support_agents: Boolean(data === null || data === void 0 ? void 0 : data.can_manage_support_agents), can_manage: Boolean(data === null || data === void 0 ? void 0 : data.can_manage), is_active: data === null || data === void 0 ? void 0 : data.is_active, last_login: data === null || data === void 0 ? void 0 : data.last_login, date_joined: data === null || data === void 0 ? void 0 : data.date_joined });
|
|
28
|
+
};
|
|
10
29
|
useEffect(() => {
|
|
11
30
|
let isMounted = true;
|
|
12
31
|
const initAuth = async () => {
|
|
13
32
|
try {
|
|
14
33
|
// 1) Ensure CSRF cookie exists using the specific client
|
|
15
34
|
await ensureCsrfToken();
|
|
16
|
-
// 2) Load
|
|
35
|
+
// 2) Load auth methods (public)
|
|
36
|
+
try {
|
|
37
|
+
const methods = await fetchAuthMethods();
|
|
38
|
+
if (isMounted && methods && typeof methods === 'object') {
|
|
39
|
+
setAuthMethods((prev) => (Object.assign(Object.assign({}, prev), methods)));
|
|
40
|
+
}
|
|
41
|
+
}
|
|
42
|
+
catch (_a) {
|
|
43
|
+
// Keep defaults; login UI remains usable.
|
|
44
|
+
}
|
|
45
|
+
// 3) Load user
|
|
17
46
|
const data = await fetchCurrentUser();
|
|
18
47
|
if (isMounted) {
|
|
19
|
-
|
|
20
|
-
setUser({
|
|
21
|
-
id: data.id,
|
|
22
|
-
username: data.username,
|
|
23
|
-
email: data.email,
|
|
24
|
-
first_name: data.first_name,
|
|
25
|
-
last_name: data.last_name,
|
|
26
|
-
role: data.role,
|
|
27
|
-
is_superuser: data.is_superuser,
|
|
28
|
-
security_state: data.security_state,
|
|
29
|
-
available_roles: data.available_roles,
|
|
30
|
-
ui_permissions: data.ui_permissions,
|
|
31
|
-
});
|
|
48
|
+
setUser(mapUserFromApi(data));
|
|
32
49
|
}
|
|
33
50
|
}
|
|
34
51
|
catch (err) {
|
|
@@ -45,7 +62,7 @@ export const AuthProvider = ({ children }) => {
|
|
|
45
62
|
return () => { isMounted = false; };
|
|
46
63
|
}, []);
|
|
47
64
|
const login = (userData) => {
|
|
48
|
-
setUser((prev) => (Object.assign(Object.assign({}, prev), userData)));
|
|
65
|
+
setUser((prev) => (Object.assign(Object.assign({}, prev), mapUserFromApi(userData))));
|
|
49
66
|
};
|
|
50
67
|
const logout = async () => {
|
|
51
68
|
try {
|
|
@@ -61,6 +78,7 @@ export const AuthProvider = ({ children }) => {
|
|
|
61
78
|
};
|
|
62
79
|
return (_jsx(AuthContext.Provider, { value: {
|
|
63
80
|
user,
|
|
81
|
+
authMethods,
|
|
64
82
|
loading,
|
|
65
83
|
login,
|
|
66
84
|
logout,
|
package/dist/auth/authApi.js
CHANGED
|
@@ -15,6 +15,10 @@ export async function fetchCurrentUser() {
|
|
|
15
15
|
const res = await apiClient.get(`${USERS_BASE}/current/`);
|
|
16
16
|
return res.data;
|
|
17
17
|
}
|
|
18
|
+
export async function fetchAuthMethods() {
|
|
19
|
+
const res = await apiClient.get('/api/auth-methods/');
|
|
20
|
+
return res.data || {};
|
|
21
|
+
}
|
|
18
22
|
export async function updateUserProfile(data) {
|
|
19
23
|
try {
|
|
20
24
|
const res = await apiClient.patch(`${USERS_BASE}/current/`, data);
|
|
@@ -24,6 +28,21 @@ export async function updateUserProfile(data) {
|
|
|
24
28
|
throw normaliseApiError(error, 'Auth.PROFILE_UPDATE_FAILED');
|
|
25
29
|
}
|
|
26
30
|
}
|
|
31
|
+
function normalizeStatementText(data) {
|
|
32
|
+
if (typeof data === 'string')
|
|
33
|
+
return data;
|
|
34
|
+
if (data && typeof data.content === 'string')
|
|
35
|
+
return data.content;
|
|
36
|
+
return '';
|
|
37
|
+
}
|
|
38
|
+
export async function fetchPrivacyStatement() {
|
|
39
|
+
const res = await apiClient.get('/api/utils/privacy/');
|
|
40
|
+
return normalizeStatementText(res.data);
|
|
41
|
+
}
|
|
42
|
+
export async function fetchCookieStatement() {
|
|
43
|
+
const res = await apiClient.get('/api/utils/cookie/');
|
|
44
|
+
return normalizeStatementText(res.data);
|
|
45
|
+
}
|
|
27
46
|
export async function fetchHeadlessSession() {
|
|
28
47
|
const res = await apiClient.get(`${HEADLESS_BASE}/auth/session`);
|
|
29
48
|
return res.data;
|
|
@@ -8,6 +8,12 @@ import { useTranslation } from 'react-i18next';
|
|
|
8
8
|
import { fetchAccessCodes, createAccessCode, deleteAccessCode, } from '../auth/authApi';
|
|
9
9
|
export function AccessCodeManager() {
|
|
10
10
|
const { t } = useTranslation();
|
|
11
|
+
const actionButtonSx = {
|
|
12
|
+
minWidth: 120,
|
|
13
|
+
height: 40,
|
|
14
|
+
textTransform: 'none',
|
|
15
|
+
whiteSpace: 'nowrap',
|
|
16
|
+
};
|
|
11
17
|
const [codes, setCodes] = useState([]);
|
|
12
18
|
const [loading, setLoading] = useState(true);
|
|
13
19
|
const [submitting, setSubmitting] = useState(false);
|
|
@@ -90,7 +96,7 @@ export function AccessCodeManager() {
|
|
|
90
96
|
if (loading) {
|
|
91
97
|
return (_jsx(Box, { sx: { py: 3, display: 'flex', justifyContent: 'center' }, children: _jsx(CircularProgress, {}) }));
|
|
92
98
|
}
|
|
93
|
-
return (_jsxs(Box, {
|
|
99
|
+
return (_jsxs(Box, { children: [errorKey && (_jsx(Alert, { severity: "error", sx: { mb: 2 }, children: t(errorKey) })), successKey && (_jsx(Alert, { severity: "success", sx: { mb: 2 }, children: t(successKey) })), _jsxs(Box, { sx: { mb: 3 }, children: [_jsx(Typography, { variant: "subtitle1", gutterBottom: true, children: t('Auth.ACCESS_CODE_SECTION_ACTIVE') }), codes.length === 0 ? (_jsx(Typography, { variant: "body2", children: t('Auth.ACCESS_CODE_NONE') })) : (_jsx(Stack, { direction: "row", flexWrap: "wrap", gap: 1, children: codes.map((code) => (_jsx(Chip, { label: code.code, onDelete: () => handleDelete(code.id), deleteIcon: _jsx(CloseIcon, {}) }, code.id))) }))] }), _jsxs(Box, { sx: { mb: 3 }, children: [_jsx(Typography, { variant: "subtitle1", gutterBottom: true, children: t('Auth.ACCESS_CODE_SECTION_GENERATE') }), _jsxs(Box, { sx: { maxWidth: 360 }, children: [_jsx(Typography, { variant: "body2", gutterBottom: true, children: t('Auth.ACCESS_CODE_LENGTH_LABEL', { length }) }), _jsx(Slider, { min: 6, max: 32, step: 1, value: length, onChange: (_, val) => setLength(val), valueLabelDisplay: "auto", disabled: submitting })] }), _jsx(Button, { variant: "contained", size: "small", sx: Object.assign(Object.assign({}, actionButtonSx), { mt: 1 }), onClick: handleGenerateClick, disabled: submitting, children: submitting
|
|
94
100
|
? t('Auth.SAVE_BUTTON_LOADING')
|
|
95
|
-
: t('Auth.ACCESS_CODE_GENERATE_BUTTON') })] }), _jsxs(Box, { sx: { mb: 2, maxWidth: 360 }, children: [_jsx(Typography, { variant: "subtitle1", gutterBottom: true, children: t('Auth.ACCESS_CODE_SECTION_MANUAL') }), _jsxs(Box, { sx: { display: 'flex', gap: 1 }, children: [_jsx(TextField, { label: t('Auth.ACCESS_CODE_LABEL'), fullWidth: true, value: manualCode, onChange: (e) => setManualCode(e.target.value), disabled: submitting }), _jsx(Button, { variant: "
|
|
101
|
+
: t('Auth.ACCESS_CODE_GENERATE_BUTTON') })] }), _jsxs(Box, { sx: { mb: 2, maxWidth: 360 }, children: [_jsx(Typography, { variant: "subtitle1", gutterBottom: true, children: t('Auth.ACCESS_CODE_SECTION_MANUAL') }), _jsxs(Box, { sx: { display: 'flex', gap: 1, alignItems: 'center' }, children: [_jsx(TextField, { label: t('Auth.ACCESS_CODE_LABEL'), fullWidth: true, size: "small", value: manualCode, onChange: (e) => setManualCode(e.target.value), disabled: submitting }), _jsx(Button, { variant: "contained", size: "small", onClick: handleAddManual, disabled: submitting, sx: actionButtonSx, children: t('Auth.ACCESS_CODE_ADD_BUTTON') })] })] })] }));
|
|
96
102
|
}
|
|
@@ -26,6 +26,12 @@ function parseEmailsFromCsv(text) {
|
|
|
26
26
|
}
|
|
27
27
|
export function BulkInviteCsvTab({ inviteFn = (email) => requestInviteWithCode(email, null), onCompleted, }) {
|
|
28
28
|
const { t } = useTranslation();
|
|
29
|
+
const actionButtonSx = {
|
|
30
|
+
minWidth: 120,
|
|
31
|
+
height: 40,
|
|
32
|
+
textTransform: 'none',
|
|
33
|
+
whiteSpace: 'nowrap',
|
|
34
|
+
};
|
|
29
35
|
const [emails, setEmails] = useState([]);
|
|
30
36
|
const [results, setResults] = useState({});
|
|
31
37
|
const [busy, setBusy] = useState(false);
|
|
@@ -89,7 +95,7 @@ export function BulkInviteCsvTab({ inviteFn = (email) => requestInviteWithCode(e
|
|
|
89
95
|
if (onCompleted)
|
|
90
96
|
onCompleted(nextResults);
|
|
91
97
|
};
|
|
92
|
-
return (_jsxs(Box, {
|
|
98
|
+
return (_jsxs(Box, { children: [_jsx(Typography, { variant: "h6", gutterBottom: true, children: t('Account.BULK_INVITE_TITLE', 'Bulk Invite via CSV') }), _jsx(Typography, { variant: "body2", sx: { mb: 2, color: 'text.secondary' }, children: t('Account.BULK_INVITE_HINT', 'Upload a CSV file containing email addresses. Header "email" is supported.') }), error && _jsx(Alert, { severity: "error", sx: { mb: 2 }, children: error }), success && _jsx(Alert, { severity: "success", sx: { mb: 2 }, children: success }), _jsxs(Box, { sx: { display: 'flex', gap: 2, alignItems: 'center', mb: 2, flexWrap: 'wrap' }, children: [_jsxs(Button, { variant: "outlined", size: "small", component: "label", disabled: busy, sx: actionButtonSx, children: [t('Account.BULK_INVITE_UPLOAD', 'Upload CSV'), _jsx("input", { type: "file", accept: ".csv,text/csv", hidden: true, onChange: handleFile })] }), _jsx(Button, { variant: "contained", size: "small", sx: actionButtonSx, onClick: handleInviteAll, disabled: busy || emails.length === 0, children: t('Account.BULK_INVITE_SEND', 'Send Invites') }), _jsx(Typography, { variant: "body2", children: t('Account.BULK_INVITE_COUNT', '{{count}} emails loaded', { count: emails.length }) })] }), busy && (_jsx(Box, { sx: { mb: 2 }, children: _jsx(LinearProgress, { variant: "determinate", value: progress }) })), emails.length > 0 && (_jsx(TableContainer, { component: Paper, children: _jsxs(Table, { size: "small", children: [_jsx(TableHead, { children: _jsxs(TableRow, { children: [_jsx(TableCell, { children: t('Auth.EMAIL_LABEL', 'Email') }), _jsx(TableCell, { children: t('Common.STATUS', 'Status') }), _jsx(TableCell, { children: t('Common.DETAILS', 'Details') })] }) }), _jsx(TableBody, { children: emails.map((email) => {
|
|
93
99
|
const row = results[email];
|
|
94
100
|
return (_jsxs(TableRow, { children: [_jsx(TableCell, { children: email }), _jsx(TableCell, { children: row ? (row.ok ? t('Common.SUCCESS', 'Success') : t('Common.ERROR', 'Error')) : '-' }), _jsx(TableCell, { children: (row === null || row === void 0 ? void 0 : row.message) || '-' })] }, email));
|
|
95
101
|
}) })] }) })), done > 0 && (_jsxs(Typography, { variant: "body2", sx: { mt: 2 }, children: [t('Account.BULK_INVITE_PROGRESS', '{{done}} / {{total}} processed', { done, total }), ' - ', t('Account.BULK_INVITE_SUCCESS_COUNT', '{{count}} successful', { count: successCount })] }))] }));
|
|
@@ -3,7 +3,7 @@ import React, { useState, useEffect } from 'react';
|
|
|
3
3
|
import { Box, TextField, Button, Typography, Divider, } from '@mui/material';
|
|
4
4
|
import { useTranslation } from 'react-i18next';
|
|
5
5
|
import { SocialLoginButtons } from './SocialLoginButtons';
|
|
6
|
-
export function LoginForm({ onSubmit, onForgotPassword, onSocialLogin, onPasskeyLogin, onSignUp, error, // bereits übersetzter Text oder t(errorKey) aus dem Parent
|
|
6
|
+
export function LoginForm({ onSubmit, onForgotPassword, onSocialLogin, socialProviders, onPasskeyLogin, onSignUp, error, // bereits übersetzter Text oder t(errorKey) aus dem Parent
|
|
7
7
|
disabled = false, initialIdentifier = '', }) {
|
|
8
8
|
const { t } = useTranslation();
|
|
9
9
|
const [identifier, setIdentifier] = useState(initialIdentifier);
|
|
@@ -21,6 +21,6 @@ disabled = false, initialIdentifier = '', }) {
|
|
|
21
21
|
const supportsPasskey = !!onPasskeyLogin &&
|
|
22
22
|
typeof window !== 'undefined' &&
|
|
23
23
|
!!window.PublicKeyCredential;
|
|
24
|
-
return (_jsxs(Box, { sx: { display: 'flex', flexDirection: 'column', gap: 3 }, children: [error && (_jsx(Typography, { color: "error", gutterBottom: true, children: error })), supportsPasskey && (_jsxs(_Fragment, { children: [_jsx(Button, { variant: "contained", fullWidth: true, type: "button", onClick: onPasskeyLogin, disabled: disabled, children: t('Auth.LOGIN_USE_PASSKEY_BUTTON') }), _jsx(Divider, { sx: { my: 2 }, children: t('Auth.LOGIN_OR') })] })), _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: identifier, onChange: (e) => setIdentifier(e.target.value), disabled: disabled }), _jsx(TextField, { label: t('Auth.LOGIN_PASSWORD_LABEL'), type: "password", required: true, fullWidth: true, value: password, onChange: (e) => setPassword(e.target.value), disabled: disabled }), _jsx(Button, { type: "submit", variant: "contained", fullWidth: true, disabled: disabled, children: t('Auth.PAGE_LOGIN_TITLE') })] }), _jsxs(Box, { children: [_jsx(Typography, { variant: "subtitle2", sx: { mb: 1 }, children: t('Auth.LOGIN_ACCOUNT_RECOVERY_TITLE') }), _jsxs(Box, { sx: { display: 'flex', gap: 1, flexWrap: 'wrap' }, children: [onSignUp && (_jsx(Button, { type: "button", variant: "outlined", onClick: onSignUp, disabled: disabled, children: t('Auth.LOGIN_SIGNUP_BUTTON') })), _jsx(Button, { type: "button", variant: "outlined", onClick: onForgotPassword, disabled: disabled, children: t('Auth.LOGIN_FORGOT_PASSWORD_BUTTON') })] })] })] }));
|
|
24
|
+
return (_jsxs(Box, { sx: { display: 'flex', flexDirection: 'column', gap: 3 }, children: [error && (_jsx(Typography, { color: "error", gutterBottom: true, children: error })), supportsPasskey && (_jsxs(_Fragment, { children: [_jsx(Button, { variant: "contained", fullWidth: true, type: "button", onClick: onPasskeyLogin, disabled: disabled, children: t('Auth.LOGIN_USE_PASSKEY_BUTTON') }), _jsx(Divider, { sx: { my: 2 }, children: t('Auth.LOGIN_OR') })] })), onSubmit && (_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: identifier, onChange: (e) => setIdentifier(e.target.value), disabled: disabled }), _jsx(TextField, { label: t('Auth.LOGIN_PASSWORD_LABEL'), type: "password", required: true, fullWidth: true, value: password, onChange: (e) => setPassword(e.target.value), disabled: disabled }), _jsx(Button, { type: "submit", variant: "contained", fullWidth: true, disabled: disabled, children: t('Auth.PAGE_LOGIN_TITLE') })] })), onSocialLogin && (_jsxs(Box, { children: [_jsx(Divider, { sx: { my: 2 }, children: t('Auth.LOGIN_OR') }), _jsx(SocialLoginButtons, { onProviderClick: onSocialLogin, providers: socialProviders })] })), (onSignUp || onForgotPassword) && (_jsxs(Box, { children: [_jsx(Typography, { variant: "subtitle2", sx: { mb: 1 }, children: t('Auth.LOGIN_ACCOUNT_RECOVERY_TITLE') }), _jsxs(Box, { sx: { display: 'flex', gap: 1, flexWrap: 'wrap' }, children: [onSignUp && (_jsx(Button, { type: "button", variant: "outlined", onClick: onSignUp, disabled: disabled, children: t('Auth.LOGIN_SIGNUP_BUTTON') })), onForgotPassword && (_jsx(Button, { type: "button", variant: "outlined", onClick: onForgotPassword, disabled: disabled, children: t('Auth.LOGIN_FORGOT_PASSWORD_BUTTON') }))] })] }))] }));
|
|
25
25
|
}
|
|
26
26
|
;
|
|
@@ -1,11 +1,12 @@
|
|
|
1
1
|
import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
|
|
2
2
|
// src/components/ProfileComponent.jsx
|
|
3
3
|
import React, { useEffect, useState, useContext } from 'react';
|
|
4
|
-
import { Box, Stack, TextField, FormControlLabel, Checkbox, Button, CircularProgress, Alert, Typography, } from '@mui/material';
|
|
4
|
+
import { Box, Stack, TextField, FormControlLabel, Checkbox, Button, CircularProgress, Alert, Typography, Accordion, AccordionSummary, AccordionDetails, } from '@mui/material';
|
|
5
5
|
import { useTranslation } from 'react-i18next';
|
|
6
6
|
// WICHTIG: Importiere den Context, um die bereits geladenen Daten zu nutzen
|
|
7
7
|
import { AuthContext } from '../auth/AuthContext';
|
|
8
|
-
|
|
8
|
+
import { fetchCookieStatement, fetchPrivacyStatement } from '../auth/authApi';
|
|
9
|
+
export function ProfileComponent({ onSubmit, submitText, showName = true, showPrivacy = true, showCookies = true, showStatements = true, privacyStatementText = null, cookieStatementText = null, }) {
|
|
9
10
|
const { t } = useTranslation();
|
|
10
11
|
// WICHTIG: Wir holen den User direkt aus dem globalen State
|
|
11
12
|
// Das verhindert den doppelten Request und den ReferenceError
|
|
@@ -20,6 +21,10 @@ export function ProfileComponent({ onSubmit, submitText, showName = true, showPr
|
|
|
20
21
|
const [lastName, setLastName] = useState('');
|
|
21
22
|
const [acceptedPrivacy, setAcceptedPrivacy] = useState(false);
|
|
22
23
|
const [acceptedCookies, setAcceptedCookies] = useState(false);
|
|
24
|
+
const [privacyStatement, setPrivacyStatement] = useState(privacyStatementText || '');
|
|
25
|
+
const [cookieStatement, setCookieStatement] = useState(cookieStatementText || '');
|
|
26
|
+
const [loadingPrivacyStatement, setLoadingPrivacyStatement] = useState(false);
|
|
27
|
+
const [loadingCookieStatement, setLoadingCookieStatement] = useState(false);
|
|
23
28
|
// Synchronisiere Formular-Daten, sobald der User aus dem Context da ist
|
|
24
29
|
useEffect(() => {
|
|
25
30
|
var _a, _b, _c, _d;
|
|
@@ -32,6 +37,59 @@ export function ProfileComponent({ onSubmit, submitText, showName = true, showPr
|
|
|
32
37
|
setAcceptedCookies(Boolean(user.accepted_convenience_cookies));
|
|
33
38
|
}
|
|
34
39
|
}, [user]);
|
|
40
|
+
useEffect(() => {
|
|
41
|
+
if (privacyStatementText !== null && privacyStatementText !== undefined) {
|
|
42
|
+
setPrivacyStatement(String(privacyStatementText));
|
|
43
|
+
}
|
|
44
|
+
}, [privacyStatementText]);
|
|
45
|
+
useEffect(() => {
|
|
46
|
+
if (cookieStatementText !== null && cookieStatementText !== undefined) {
|
|
47
|
+
setCookieStatement(String(cookieStatementText));
|
|
48
|
+
}
|
|
49
|
+
}, [cookieStatementText]);
|
|
50
|
+
useEffect(() => {
|
|
51
|
+
let cancelled = false;
|
|
52
|
+
const loadStatements = async () => {
|
|
53
|
+
if (!showStatements)
|
|
54
|
+
return;
|
|
55
|
+
if (showPrivacy && (privacyStatementText === null || privacyStatementText === undefined)) {
|
|
56
|
+
setLoadingPrivacyStatement(true);
|
|
57
|
+
try {
|
|
58
|
+
const text = await fetchPrivacyStatement();
|
|
59
|
+
if (!cancelled)
|
|
60
|
+
setPrivacyStatement(text || '');
|
|
61
|
+
}
|
|
62
|
+
catch (err) {
|
|
63
|
+
if (!cancelled)
|
|
64
|
+
setPrivacyStatement('');
|
|
65
|
+
}
|
|
66
|
+
finally {
|
|
67
|
+
if (!cancelled)
|
|
68
|
+
setLoadingPrivacyStatement(false);
|
|
69
|
+
}
|
|
70
|
+
}
|
|
71
|
+
if (showCookies && (cookieStatementText === null || cookieStatementText === undefined)) {
|
|
72
|
+
setLoadingCookieStatement(true);
|
|
73
|
+
try {
|
|
74
|
+
const text = await fetchCookieStatement();
|
|
75
|
+
if (!cancelled)
|
|
76
|
+
setCookieStatement(text || '');
|
|
77
|
+
}
|
|
78
|
+
catch (err) {
|
|
79
|
+
if (!cancelled)
|
|
80
|
+
setCookieStatement('');
|
|
81
|
+
}
|
|
82
|
+
finally {
|
|
83
|
+
if (!cancelled)
|
|
84
|
+
setLoadingCookieStatement(false);
|
|
85
|
+
}
|
|
86
|
+
}
|
|
87
|
+
};
|
|
88
|
+
loadStatements();
|
|
89
|
+
return () => {
|
|
90
|
+
cancelled = true;
|
|
91
|
+
};
|
|
92
|
+
}, [showStatements, showPrivacy, showCookies, privacyStatementText, cookieStatementText]);
|
|
35
93
|
const handleSubmit = async (event) => {
|
|
36
94
|
event.preventDefault();
|
|
37
95
|
if (!onSubmit)
|
|
@@ -39,12 +97,14 @@ export function ProfileComponent({ onSubmit, submitText, showName = true, showPr
|
|
|
39
97
|
setSaving(true);
|
|
40
98
|
setErrorKey(null);
|
|
41
99
|
setSuccessKey(null);
|
|
42
|
-
const payload = {
|
|
100
|
+
const payload = Object.assign(Object.assign(Object.assign({}, (showName && {
|
|
43
101
|
first_name: firstName,
|
|
44
102
|
last_name: lastName,
|
|
103
|
+
})), (showPrivacy && {
|
|
45
104
|
accepted_privacy_statement: acceptedPrivacy,
|
|
105
|
+
})), (showCookies && {
|
|
46
106
|
accepted_convenience_cookies: acceptedCookies,
|
|
47
|
-
};
|
|
107
|
+
}));
|
|
48
108
|
try {
|
|
49
109
|
await onSubmit(payload);
|
|
50
110
|
setSuccessKey('Profile.UPDATE_SUCCESS');
|
|
@@ -68,5 +128,5 @@ export function ProfileComponent({ onSubmit, submitText, showName = true, showPr
|
|
|
68
128
|
}
|
|
69
129
|
const submitLabel = submitText || t('Profile.SAVE_BUTTON');
|
|
70
130
|
const submitLabelLoading = t('Profile.SAVE_BUTTON_LOADING');
|
|
71
|
-
return (_jsxs(Box, { component: "form", onSubmit: handleSubmit, sx: { maxWidth: 600, display: 'flex', flexDirection: 'column', gap: 2 }, children: [errorKey && (_jsx(Alert, { severity: "error", children: t(errorKey) })), successKey && (_jsx(Alert, { severity: "success", children: t(successKey) })), _jsxs(Stack, { spacing: 2, children: [_jsx(TextField, { label: t('Profile.USERNAME_LABEL'), value: username, fullWidth: true, disabled: true }), _jsx(TextField, { label: t('Auth.EMAIL_LABEL'), type: "email", value: email, fullWidth: true, disabled: true })] }), showName && (_jsxs(Stack, { spacing: 2, direction: { xs: 'column', sm: 'row' }, children: [_jsx(TextField, { label: t('Profile.FIRST_NAME_LABEL'), value: firstName, onChange: (e) => setFirstName(e.target.value), fullWidth: true }), _jsx(TextField, { label: t('Profile.LAST_NAME_LABEL'), value: lastName, onChange: (e) => setLastName(e.target.value), fullWidth: true })] })), (showPrivacy || showCookies) && (_jsxs(Box, { sx: { mt: 1 }, children: [_jsx(Typography, { variant: "subtitle1", gutterBottom: true, children: t('Profile.PRIVACY_SECTION_TITLE') }), _jsxs(Stack, { spacing: 1, children: [showPrivacy && (_jsx(FormControlLabel, { control: _jsx(Checkbox, { checked: acceptedPrivacy, onChange: (e) => setAcceptedPrivacy(e.target.checked) }), label: t('Profile.ACCEPT_PRIVACY_LABEL') })), showCookies && (_jsx(FormControlLabel, { control: _jsx(Checkbox, { checked: acceptedCookies, onChange: (e) => setAcceptedCookies(e.target.checked) }), label: t('Profile.ACCEPT_COOKIES_LABEL') }))] })] })), _jsx(Box, { sx: { mt: 2 }, children: _jsx(Button, { type: "submit", variant: "contained", disabled: saving, children: saving ? submitLabelLoading : submitLabel }) })] }));
|
|
131
|
+
return (_jsxs(Box, { component: "form", onSubmit: handleSubmit, sx: { maxWidth: 600, display: 'flex', flexDirection: 'column', gap: 2 }, children: [errorKey && (_jsx(Alert, { severity: "error", children: t(errorKey) })), successKey && (_jsx(Alert, { severity: "success", children: t(successKey) })), _jsxs(Stack, { spacing: 2, children: [_jsx(TextField, { label: t('Profile.USERNAME_LABEL'), value: username, fullWidth: true, disabled: true }), _jsx(TextField, { label: t('Auth.EMAIL_LABEL'), type: "email", value: email, fullWidth: true, disabled: true })] }), showName && (_jsxs(Stack, { spacing: 2, direction: { xs: 'column', sm: 'row' }, children: [_jsx(TextField, { label: t('Profile.FIRST_NAME_LABEL'), value: firstName, onChange: (e) => setFirstName(e.target.value), fullWidth: true }), _jsx(TextField, { label: t('Profile.LAST_NAME_LABEL'), value: lastName, onChange: (e) => setLastName(e.target.value), fullWidth: true })] })), (showPrivacy || showCookies) && (_jsxs(Box, { sx: { mt: 1 }, children: [_jsx(Typography, { variant: "subtitle1", gutterBottom: true, children: t('Profile.PRIVACY_SECTION_TITLE') }), _jsxs(Stack, { spacing: 1, children: [showPrivacy && (_jsx(FormControlLabel, { control: _jsx(Checkbox, { checked: acceptedPrivacy, onChange: (e) => setAcceptedPrivacy(e.target.checked) }), label: t('Profile.ACCEPT_PRIVACY_LABEL') })), showCookies && (_jsx(FormControlLabel, { control: _jsx(Checkbox, { checked: acceptedCookies, onChange: (e) => setAcceptedCookies(e.target.checked) }), label: t('Profile.ACCEPT_COOKIES_LABEL') }))] }), showStatements && (_jsxs(Stack, { spacing: 1.25, sx: { mt: 1.5 }, children: [showPrivacy && (_jsxs(Accordion, { disableGutters: true, children: [_jsx(AccordionSummary, { children: _jsx(Typography, { variant: "body2", fontWeight: 600, children: t('Profile.PRIVACY_STATEMENT_TITLE', 'Privacy statement') }) }), _jsx(AccordionDetails, { children: loadingPrivacyStatement ? (_jsx(Typography, { variant: "body2", color: "text.secondary", children: t('Profile.STATEMENT_LOADING', 'Loading statement...') })) : (_jsx(Typography, { variant: "body2", sx: { whiteSpace: 'pre-wrap' }, children: privacyStatement || t('Profile.PRIVACY_STATEMENT_EMPTY', 'Privacy statement is currently unavailable.') })) })] })), showCookies && (_jsxs(Accordion, { disableGutters: true, children: [_jsx(AccordionSummary, { children: _jsx(Typography, { variant: "body2", fontWeight: 600, children: t('Profile.COOKIES_STATEMENT_TITLE', 'Cookie statement') }) }), _jsx(AccordionDetails, { children: loadingCookieStatement ? (_jsx(Typography, { variant: "body2", color: "text.secondary", children: t('Profile.STATEMENT_LOADING', 'Loading statement...') })) : (_jsx(Typography, { variant: "body2", sx: { whiteSpace: 'pre-wrap' }, children: cookieStatement || t('Profile.COOKIES_STATEMENT_EMPTY', 'Cookie statement is currently unavailable.') })) })] }))] }))] })), _jsx(Box, { sx: { mt: 2 }, children: _jsx(Button, { type: "submit", variant: "contained", disabled: saving, children: saving ? submitLabelLoading : submitLabel }) })] }));
|
|
72
132
|
}
|
|
@@ -1,6 +1,6 @@
|
|
|
1
|
-
import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
|
|
1
|
+
import { jsx as _jsx, Fragment as _Fragment, jsxs as _jsxs } from "react/jsx-runtime";
|
|
2
2
|
// src/auth/components/SecurityComponent.jsx
|
|
3
|
-
import React, { useState } from 'react';
|
|
3
|
+
import React, { useContext, useMemo, useState } from 'react';
|
|
4
4
|
import { Box, Typography, Divider, Alert, } from '@mui/material';
|
|
5
5
|
import { useTranslation } from 'react-i18next';
|
|
6
6
|
import { PasswordChangeForm } from './PasswordChangeForm';
|
|
@@ -9,17 +9,34 @@ import { PasskeysComponent } from './PasskeysComponent';
|
|
|
9
9
|
import { MFAComponent } from './MFAComponent';
|
|
10
10
|
import { changePassword } from '../auth/authApi';
|
|
11
11
|
import { startSocialLogin } from '../utils/authService';
|
|
12
|
-
|
|
13
|
-
}) {
|
|
12
|
+
import { AuthContext } from '../auth/AuthContext';
|
|
13
|
+
export function SecurityComponent({ fromRecovery = false, fromWeakLogin = false, }) {
|
|
14
14
|
const { t } = useTranslation();
|
|
15
|
+
const { authMethods } = useContext(AuthContext);
|
|
15
16
|
const [messageKey, setMessageKey] = useState(null);
|
|
16
17
|
const [errorKey, setErrorKey] = useState(null);
|
|
18
|
+
const socialProviders = Array.isArray(authMethods === null || authMethods === void 0 ? void 0 : authMethods.social_providers)
|
|
19
|
+
? authMethods.social_providers
|
|
20
|
+
: [];
|
|
21
|
+
const canChangePassword = Boolean(authMethods === null || authMethods === void 0 ? void 0 : authMethods.password_change);
|
|
22
|
+
const socialLoginEnabled = Boolean(authMethods === null || authMethods === void 0 ? void 0 : authMethods.social_login) && socialProviders.length > 0;
|
|
23
|
+
const passkeysEnabled = Boolean(authMethods === null || authMethods === void 0 ? void 0 : authMethods.passkeys_manage);
|
|
24
|
+
const mfaEnabled = Boolean(authMethods === null || authMethods === void 0 ? void 0 : authMethods.mfa_enabled);
|
|
25
|
+
const sectionOrder = useMemo(() => [
|
|
26
|
+
canChangePassword ? 'password' : null,
|
|
27
|
+
socialLoginEnabled ? 'social' : null,
|
|
28
|
+
passkeysEnabled ? 'passkeys' : null,
|
|
29
|
+
mfaEnabled ? 'mfa' : null,
|
|
30
|
+
].filter(Boolean), [canChangePassword, socialLoginEnabled, passkeysEnabled, mfaEnabled]);
|
|
31
|
+
const needsDividerAfter = (section) => {
|
|
32
|
+
const idx = sectionOrder.indexOf(section);
|
|
33
|
+
return idx !== -1 && idx < sectionOrder.length - 1;
|
|
34
|
+
};
|
|
17
35
|
const handleSocialClick = async (provider) => {
|
|
18
36
|
setMessageKey(null);
|
|
19
37
|
setErrorKey(null);
|
|
20
38
|
try {
|
|
21
39
|
await startSocialLogin(provider);
|
|
22
|
-
// Redirect läuft über den Provider-Flow, hier kein extra Text nötig
|
|
23
40
|
}
|
|
24
41
|
catch (err) {
|
|
25
42
|
setErrorKey(err.code || 'Auth.SOCIAL_LOGIN_FAILED');
|
|
@@ -36,6 +53,6 @@ export function SecurityComponent({ fromRecovery = false, fromWeakLogin = false,
|
|
|
36
53
|
setErrorKey(err.code || 'Auth.PASSWORD_CHANGE_FAILED');
|
|
37
54
|
}
|
|
38
55
|
};
|
|
39
|
-
return (_jsxs(Box, { children: [fromRecovery && (_jsx(Alert, { severity: "warning", sx: { mb: 2 }, children: t('Security.RECOVERY_LOGIN_WARNING') })), fromWeakLogin && (_jsx(Alert, { severity: "warning", sx: { mb: 2 }, children: t('Security.WEAK_LOGIN_WARNING') })), messageKey && (_jsx(Alert, { severity: "success", sx: { mb: 2 }, children: t(messageKey) })), errorKey && (_jsx(Alert, { severity: "error", sx: { mb: 2 }, children: t(errorKey) })), _jsx(Typography, { variant: "h6", gutterBottom: true, children: t('Auth.LOGIN_PASSWORD_LABEL') }), _jsx(PasswordChangeForm, { onSubmit: handlePasswordChange }), _jsx(Divider, { sx: { my: 3 } }), _jsx(Typography, { variant: "h6", gutterBottom: true, children: t('Security.SOCIAL_SECTION_TITLE') }), _jsx(Typography, { variant: "body2", sx: { mb: 1 }, children: t('Security.SOCIAL_SECTION_DESCRIPTION') }), _jsx(SocialLoginButtons, { onProviderClick: handleSocialClick }), _jsx(Divider, { sx: { my: 3 } }), _jsx(PasskeysComponent, {}), _jsx(Divider, { sx: { my: 3 } }), _jsx(MFAComponent, {})] }));
|
|
56
|
+
return (_jsxs(Box, { children: [fromRecovery && (_jsx(Alert, { severity: "warning", sx: { mb: 2 }, children: t('Security.RECOVERY_LOGIN_WARNING') })), fromWeakLogin && (_jsx(Alert, { severity: "warning", sx: { mb: 2 }, children: t('Security.WEAK_LOGIN_WARNING') })), messageKey && (_jsx(Alert, { severity: "success", sx: { mb: 2 }, children: t(messageKey) })), errorKey && (_jsx(Alert, { severity: "error", sx: { mb: 2 }, children: t(errorKey) })), canChangePassword && (_jsxs(_Fragment, { children: [_jsx(Typography, { variant: "h6", gutterBottom: true, children: t('Auth.LOGIN_PASSWORD_LABEL') }), _jsx(PasswordChangeForm, { onSubmit: handlePasswordChange }), needsDividerAfter('password') && _jsx(Divider, { sx: { my: 3 } })] })), socialLoginEnabled && (_jsxs(_Fragment, { children: [_jsx(Typography, { variant: "h6", gutterBottom: true, children: t('Security.SOCIAL_SECTION_TITLE') }), _jsx(Typography, { variant: "body2", sx: { mb: 1 }, children: t('Security.SOCIAL_SECTION_DESCRIPTION') }), _jsx(SocialLoginButtons, { onProviderClick: handleSocialClick, providers: socialProviders }), needsDividerAfter('social') && _jsx(Divider, { sx: { my: 3 } })] })), passkeysEnabled && (_jsxs(_Fragment, { children: [_jsx(PasskeysComponent, {}), needsDividerAfter('passkeys') && _jsx(Divider, { sx: { my: 3 } })] })), mfaEnabled && _jsx(MFAComponent, {})] }));
|
|
40
57
|
}
|
|
41
58
|
;
|
|
@@ -8,14 +8,17 @@ import { SOCIAL_PROVIDERS } from '../auth/authConfig';
|
|
|
8
8
|
* Renders buttons for social login providers.
|
|
9
9
|
* The caller passes a handler that receives the provider key.
|
|
10
10
|
*/
|
|
11
|
-
export function SocialLoginButtons({ onProviderClick }) {
|
|
11
|
+
export function SocialLoginButtons({ onProviderClick, providers }) {
|
|
12
12
|
const { t } = useTranslation();
|
|
13
13
|
const handleClick = (provider) => {
|
|
14
14
|
if (onProviderClick) {
|
|
15
15
|
onProviderClick(provider);
|
|
16
16
|
}
|
|
17
17
|
};
|
|
18
|
-
|
|
18
|
+
const activeProviders = Array.isArray(providers) && providers.length > 0
|
|
19
|
+
? providers
|
|
20
|
+
: [SOCIAL_PROVIDERS.google, SOCIAL_PROVIDERS.microsoft];
|
|
21
|
+
return (_jsxs(Stack, { spacing: 1.5, children: [activeProviders.includes(SOCIAL_PROVIDERS.google) && (_jsx(Button, { variant: "outlined", fullWidth: true, onClick: () => handleClick(SOCIAL_PROVIDERS.google), startIcon: _jsx(Box, { sx: {
|
|
19
22
|
width: 24,
|
|
20
23
|
height: 24,
|
|
21
24
|
borderRadius: '50%',
|
|
@@ -25,13 +28,13 @@ export function SocialLoginButtons({ onProviderClick }) {
|
|
|
25
28
|
justifyContent: 'center',
|
|
26
29
|
fontWeight: 700,
|
|
27
30
|
fontSize: 14,
|
|
28
|
-
}, children: "G" }), children: t('Auth.LOGIN_SOCIAL_GOOGLE') }), _jsx(Button, { variant: "outlined", fullWidth: true, onClick: () => handleClick(SOCIAL_PROVIDERS.microsoft), startIcon: _jsxs(Box, { sx: {
|
|
31
|
+
}, children: "G" }), children: t('Auth.LOGIN_SOCIAL_GOOGLE') })), activeProviders.includes(SOCIAL_PROVIDERS.microsoft) && (_jsx(Button, { variant: "outlined", fullWidth: true, onClick: () => handleClick(SOCIAL_PROVIDERS.microsoft), startIcon: _jsxs(Box, { sx: {
|
|
29
32
|
width: 24,
|
|
30
33
|
height: 24,
|
|
31
34
|
display: 'grid',
|
|
32
35
|
gridTemplateColumns: '1fr 1fr',
|
|
33
36
|
gridTemplateRows: '1fr 1fr',
|
|
34
37
|
gap: '1px',
|
|
35
|
-
}, children: [_jsx(Box, { sx: { bgcolor: 'primary.main', opacity: 0.9 } }), _jsx(Box, { sx: { bgcolor: 'primary.main', opacity: 0.7 } }), _jsx(Box, { sx: { bgcolor: 'primary.main', opacity: 0.7 } }), _jsx(Box, { sx: { bgcolor: 'primary.main', opacity: 0.9 } })] }), children: t('Auth.LOGIN_SOCIAL_MICROSOFT') })] }));
|
|
38
|
+
}, children: [_jsx(Box, { sx: { bgcolor: 'primary.main', opacity: 0.9 } }), _jsx(Box, { sx: { bgcolor: 'primary.main', opacity: 0.7 } }), _jsx(Box, { sx: { bgcolor: 'primary.main', opacity: 0.7 } }), _jsx(Box, { sx: { bgcolor: 'primary.main', opacity: 0.9 } })] }), children: t('Auth.LOGIN_SOCIAL_MICROSOFT') }))] }));
|
|
36
39
|
}
|
|
37
40
|
;
|
|
@@ -9,6 +9,12 @@ export function UserInviteComponent() {
|
|
|
9
9
|
const [message, setMessage] = useState('');
|
|
10
10
|
const [error, setError] = useState('');
|
|
11
11
|
const [loading, setLoading] = useState(false);
|
|
12
|
+
const actionButtonSx = {
|
|
13
|
+
minWidth: 120,
|
|
14
|
+
height: 40,
|
|
15
|
+
textTransform: 'none',
|
|
16
|
+
whiteSpace: 'nowrap',
|
|
17
|
+
};
|
|
12
18
|
const inviteUser = async () => {
|
|
13
19
|
setMessage('');
|
|
14
20
|
setError('');
|
|
@@ -32,8 +38,8 @@ export function UserInviteComponent() {
|
|
|
32
38
|
setLoading(false);
|
|
33
39
|
}
|
|
34
40
|
};
|
|
35
|
-
return (_jsxs(Box, { sx: { maxWidth: 600
|
|
41
|
+
return (_jsxs(Box, { sx: { maxWidth: 600 }, children: [_jsx(Typography, { variant: "h6", gutterBottom: true, children: t('Auth.INVITE_TITLE', 'Invite a new user') }), message && _jsx(Alert, { severity: "success", sx: { mb: 2 }, children: message }), error && _jsx(Alert, { severity: "error", sx: { mb: 2 }, children: error }), _jsxs(Box, { sx: { display: 'flex', gap: 2, alignItems: 'center' }, children: [_jsx(TextField, { label: t('Auth.EMAIL_LABEL', 'Email address'), type: "email", variant: "outlined", fullWidth: true, size: "small", value: inviteEmail, onChange: (e) => setInviteEmail(e.target.value), disabled: loading, onKeyPress: (e) => {
|
|
36
42
|
if (e.key === 'Enter')
|
|
37
43
|
inviteUser();
|
|
38
|
-
} }), _jsx(Button, { variant: "contained", onClick: inviteUser, disabled: loading || !inviteEmail, sx:
|
|
44
|
+
} }), _jsx(Button, { variant: "contained", size: "small", onClick: inviteUser, disabled: loading || !inviteEmail, sx: actionButtonSx, children: loading ? _jsx(CircularProgress, { size: 24, color: "inherit" }) : t('Auth.INVITE_BUTTON', 'Invite') })] })] }));
|
|
39
45
|
}
|