@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.
@@ -23,6 +23,12 @@ import {
23
23
 
24
24
  export function AccessCodeManager() {
25
25
  const { t } = useTranslation();
26
+ const actionButtonSx = {
27
+ minWidth: 120,
28
+ height: 40,
29
+ textTransform: 'none',
30
+ whiteSpace: 'nowrap',
31
+ };
26
32
 
27
33
  const [codes, setCodes] = useState([]);
28
34
  const [loading, setLoading] = useState(true);
@@ -118,7 +124,7 @@ export function AccessCodeManager() {
118
124
  }
119
125
 
120
126
  return (
121
- <Box sx={{ mt: 2 }}>
127
+ <Box>
122
128
  {errorKey && (
123
129
  <Alert severity="error" sx={{ mb: 2 }}>
124
130
  {t(errorKey)}
@@ -176,7 +182,8 @@ export function AccessCodeManager() {
176
182
 
177
183
  <Button
178
184
  variant="contained"
179
- sx={{ mt: 1 }}
185
+ size="small"
186
+ sx={{ ...actionButtonSx, mt: 1 }}
180
187
  onClick={handleGenerateClick}
181
188
  disabled={submitting}
182
189
  >
@@ -191,18 +198,21 @@ export function AccessCodeManager() {
191
198
  <Typography variant="subtitle1" gutterBottom>
192
199
  {t('Auth.ACCESS_CODE_SECTION_MANUAL')}
193
200
  </Typography>
194
- <Box sx={{ display: 'flex', gap: 1 }}>
201
+ <Box sx={{ display: 'flex', gap: 1, alignItems: 'center' }}>
195
202
  <TextField
196
203
  label={t('Auth.ACCESS_CODE_LABEL')}
197
204
  fullWidth
205
+ size="small"
198
206
  value={manualCode}
199
207
  onChange={(e) => setManualCode(e.target.value)}
200
208
  disabled={submitting}
201
209
  />
202
210
  <Button
203
- variant="outlined"
211
+ variant="contained"
212
+ size="small"
204
213
  onClick={handleAddManual}
205
214
  disabled={submitting}
215
+ sx={actionButtonSx}
206
216
  >
207
217
  {t('Auth.ACCESS_CODE_ADD_BUTTON')}
208
218
  </Button>
@@ -45,6 +45,12 @@ export function BulkInviteCsvTab({
45
45
  onCompleted,
46
46
  }) {
47
47
  const { t } = useTranslation();
48
+ const actionButtonSx = {
49
+ minWidth: 120,
50
+ height: 40,
51
+ textTransform: 'none',
52
+ whiteSpace: 'nowrap',
53
+ };
48
54
  const [emails, setEmails] = useState([]);
49
55
  const [results, setResults] = useState({});
50
56
  const [busy, setBusy] = useState(false);
@@ -117,7 +123,7 @@ export function BulkInviteCsvTab({
117
123
  };
118
124
 
119
125
  return (
120
- <Box sx={{ mt: 1 }}>
126
+ <Box>
121
127
  <Typography variant="h6" gutterBottom>
122
128
  {t('Account.BULK_INVITE_TITLE', 'Bulk Invite via CSV')}
123
129
  </Typography>
@@ -132,11 +138,11 @@ export function BulkInviteCsvTab({
132
138
  {success && <Alert severity="success" sx={{ mb: 2 }}>{success}</Alert>}
133
139
 
134
140
  <Box sx={{ display: 'flex', gap: 2, alignItems: 'center', mb: 2, flexWrap: 'wrap' }}>
135
- <Button variant="outlined" component="label" disabled={busy}>
141
+ <Button variant="outlined" size="small" component="label" disabled={busy} sx={actionButtonSx}>
136
142
  {t('Account.BULK_INVITE_UPLOAD', 'Upload CSV')}
137
143
  <input type="file" accept=".csv,text/csv" hidden onChange={handleFile} />
138
144
  </Button>
139
- <Button variant="contained" onClick={handleInviteAll} disabled={busy || emails.length === 0}>
145
+ <Button variant="contained" size="small" sx={actionButtonSx} onClick={handleInviteAll} disabled={busy || emails.length === 0}>
140
146
  {t('Account.BULK_INVITE_SEND', 'Send Invites')}
141
147
  </Button>
142
148
  <Typography variant="body2">
@@ -13,6 +13,7 @@ export function LoginForm({
13
13
  onSubmit,
14
14
  onForgotPassword,
15
15
  onSocialLogin,
16
+ socialProviders,
16
17
  onPasskeyLogin,
17
18
  onSignUp,
18
19
  error, // bereits übersetzter Text oder t(errorKey) aus dem Parent
@@ -68,52 +69,58 @@ export function LoginForm({
68
69
  )}
69
70
 
70
71
  {/* Sign in: E-Mail + Passwort */}
71
- <Box
72
- component="form"
73
- onSubmit={handleSubmit}
74
- sx={{ display: 'flex', flexDirection: 'column', gap: 2 }}
75
- >
76
- <TextField
77
- label={t('Auth.EMAIL_LABEL')}
78
- type="email"
79
- required
80
- fullWidth
81
- value={identifier}
82
- onChange={(e) => setIdentifier(e.target.value)}
83
- disabled={disabled}
84
- />
72
+ {onSubmit && (
73
+ <Box
74
+ component="form"
75
+ onSubmit={handleSubmit}
76
+ sx={{ display: 'flex', flexDirection: 'column', gap: 2 }}
77
+ >
78
+ <TextField
79
+ label={t('Auth.EMAIL_LABEL')}
80
+ type="email"
81
+ required
82
+ fullWidth
83
+ value={identifier}
84
+ onChange={(e) => setIdentifier(e.target.value)}
85
+ disabled={disabled}
86
+ />
85
87
 
86
- <TextField
87
- label={t('Auth.LOGIN_PASSWORD_LABEL')}
88
- type="password"
89
- required
90
- fullWidth
91
- value={password}
92
- onChange={(e) => setPassword(e.target.value)}
93
- disabled={disabled}
94
- />
88
+ <TextField
89
+ label={t('Auth.LOGIN_PASSWORD_LABEL')}
90
+ type="password"
91
+ required
92
+ fullWidth
93
+ value={password}
94
+ onChange={(e) => setPassword(e.target.value)}
95
+ disabled={disabled}
96
+ />
95
97
 
96
- <Button
97
- type="submit"
98
- variant="contained"
99
- fullWidth
100
- disabled={disabled}
101
- >
102
- {t('Auth.PAGE_LOGIN_TITLE')}
103
- </Button>
104
- </Box>
98
+ <Button
99
+ type="submit"
100
+ variant="contained"
101
+ fullWidth
102
+ disabled={disabled}
103
+ >
104
+ {t('Auth.PAGE_LOGIN_TITLE')}
105
+ </Button>
106
+ </Box>
107
+ )}
105
108
 
106
109
  {/* Other ways to sign in */}
107
- {/*
108
- <Box>
109
- <Divider sx={{ my: 2 }}>
110
- {t('Auth.LOGIN_OR')}
111
- </Divider>
112
- <SocialLoginButtons onProviderClick={onSocialLogin} />
113
- </Box>
114
- */}
110
+ {onSocialLogin && (
111
+ <Box>
112
+ <Divider sx={{ my: 2 }}>
113
+ {t('Auth.LOGIN_OR')}
114
+ </Divider>
115
+ <SocialLoginButtons
116
+ onProviderClick={onSocialLogin}
117
+ providers={socialProviders}
118
+ />
119
+ </Box>
120
+ )}
115
121
  {/* Account & Recovery */}
116
122
 
123
+ {(onSignUp || onForgotPassword) && (
117
124
  <Box>
118
125
  <Typography variant="subtitle2" sx={{ mb: 1 }}>
119
126
  {t('Auth.LOGIN_ACCOUNT_RECOVERY_TITLE')}
@@ -131,16 +138,19 @@ export function LoginForm({
131
138
  </Button>
132
139
  )}
133
140
 
134
- <Button
135
- type="button"
136
- variant="outlined"
137
- onClick={onForgotPassword}
138
- disabled={disabled}
139
- >
140
- {t('Auth.LOGIN_FORGOT_PASSWORD_BUTTON')}
141
- </Button>
141
+ {onForgotPassword && (
142
+ <Button
143
+ type="button"
144
+ variant="outlined"
145
+ onClick={onForgotPassword}
146
+ disabled={disabled}
147
+ >
148
+ {t('Auth.LOGIN_FORGOT_PASSWORD_BUTTON')}
149
+ </Button>
150
+ )}
142
151
  </Box>
143
152
  </Box>
153
+ )}
144
154
  </Box>
145
155
  );
146
156
  };
@@ -10,10 +10,14 @@ import {
10
10
  CircularProgress,
11
11
  Alert,
12
12
  Typography,
13
+ Accordion,
14
+ AccordionSummary,
15
+ AccordionDetails,
13
16
  } from '@mui/material';
14
17
  import { useTranslation } from 'react-i18next';
15
18
  // WICHTIG: Importiere den Context, um die bereits geladenen Daten zu nutzen
16
19
  import { AuthContext } from '../auth/AuthContext';
20
+ import { fetchCookieStatement, fetchPrivacyStatement } from '../auth/authApi';
17
21
 
18
22
  export function ProfileComponent({
19
23
  onSubmit,
@@ -21,6 +25,9 @@ export function ProfileComponent({
21
25
  showName = true,
22
26
  showPrivacy = true,
23
27
  showCookies = true,
28
+ showStatements = true,
29
+ privacyStatementText = null,
30
+ cookieStatementText = null,
24
31
  }) {
25
32
  const { t } = useTranslation();
26
33
 
@@ -39,6 +46,10 @@ export function ProfileComponent({
39
46
  const [lastName, setLastName] = useState('');
40
47
  const [acceptedPrivacy, setAcceptedPrivacy] = useState(false);
41
48
  const [acceptedCookies, setAcceptedCookies] = useState(false);
49
+ const [privacyStatement, setPrivacyStatement] = useState(privacyStatementText || '');
50
+ const [cookieStatement, setCookieStatement] = useState(cookieStatementText || '');
51
+ const [loadingPrivacyStatement, setLoadingPrivacyStatement] = useState(false);
52
+ const [loadingCookieStatement, setLoadingCookieStatement] = useState(false);
42
53
 
43
54
  // Synchronisiere Formular-Daten, sobald der User aus dem Context da ist
44
55
  useEffect(() => {
@@ -52,6 +63,56 @@ export function ProfileComponent({
52
63
  }
53
64
  }, [user]);
54
65
 
66
+ useEffect(() => {
67
+ if (privacyStatementText !== null && privacyStatementText !== undefined) {
68
+ setPrivacyStatement(String(privacyStatementText));
69
+ }
70
+ }, [privacyStatementText]);
71
+
72
+ useEffect(() => {
73
+ if (cookieStatementText !== null && cookieStatementText !== undefined) {
74
+ setCookieStatement(String(cookieStatementText));
75
+ }
76
+ }, [cookieStatementText]);
77
+
78
+ useEffect(() => {
79
+ let cancelled = false;
80
+
81
+ const loadStatements = async () => {
82
+ if (!showStatements) return;
83
+
84
+ if (showPrivacy && (privacyStatementText === null || privacyStatementText === undefined)) {
85
+ setLoadingPrivacyStatement(true);
86
+ try {
87
+ const text = await fetchPrivacyStatement();
88
+ if (!cancelled) setPrivacyStatement(text || '');
89
+ } catch (err) {
90
+ if (!cancelled) setPrivacyStatement('');
91
+ } finally {
92
+ if (!cancelled) setLoadingPrivacyStatement(false);
93
+ }
94
+ }
95
+
96
+ if (showCookies && (cookieStatementText === null || cookieStatementText === undefined)) {
97
+ setLoadingCookieStatement(true);
98
+ try {
99
+ const text = await fetchCookieStatement();
100
+ if (!cancelled) setCookieStatement(text || '');
101
+ } catch (err) {
102
+ if (!cancelled) setCookieStatement('');
103
+ } finally {
104
+ if (!cancelled) setLoadingCookieStatement(false);
105
+ }
106
+ }
107
+ };
108
+
109
+ loadStatements();
110
+
111
+ return () => {
112
+ cancelled = true;
113
+ };
114
+ }, [showStatements, showPrivacy, showCookies, privacyStatementText, cookieStatementText]);
115
+
55
116
  const handleSubmit = async (event) => {
56
117
  event.preventDefault();
57
118
  if (!onSubmit) return;
@@ -61,10 +122,16 @@ export function ProfileComponent({
61
122
  setSuccessKey(null);
62
123
 
63
124
  const payload = {
64
- first_name: firstName,
65
- last_name: lastName,
66
- accepted_privacy_statement: acceptedPrivacy,
67
- accepted_convenience_cookies: acceptedCookies,
125
+ ...(showName && {
126
+ first_name: firstName,
127
+ last_name: lastName,
128
+ }),
129
+ ...(showPrivacy && {
130
+ accepted_privacy_statement: acceptedPrivacy,
131
+ }),
132
+ ...(showCookies && {
133
+ accepted_convenience_cookies: acceptedCookies,
134
+ }),
68
135
  };
69
136
 
70
137
  try {
@@ -183,6 +250,52 @@ export function ProfileComponent({
183
250
  />
184
251
  )}
185
252
  </Stack>
253
+
254
+ {showStatements && (
255
+ <Stack spacing={1.25} sx={{ mt: 1.5 }}>
256
+ {showPrivacy && (
257
+ <Accordion disableGutters>
258
+ <AccordionSummary>
259
+ <Typography variant="body2" fontWeight={600}>
260
+ {t('Profile.PRIVACY_STATEMENT_TITLE', 'Privacy statement')}
261
+ </Typography>
262
+ </AccordionSummary>
263
+ <AccordionDetails>
264
+ {loadingPrivacyStatement ? (
265
+ <Typography variant="body2" color="text.secondary">
266
+ {t('Profile.STATEMENT_LOADING', 'Loading statement...')}
267
+ </Typography>
268
+ ) : (
269
+ <Typography variant="body2" sx={{ whiteSpace: 'pre-wrap' }}>
270
+ {privacyStatement || t('Profile.PRIVACY_STATEMENT_EMPTY', 'Privacy statement is currently unavailable.')}
271
+ </Typography>
272
+ )}
273
+ </AccordionDetails>
274
+ </Accordion>
275
+ )}
276
+
277
+ {showCookies && (
278
+ <Accordion disableGutters>
279
+ <AccordionSummary>
280
+ <Typography variant="body2" fontWeight={600}>
281
+ {t('Profile.COOKIES_STATEMENT_TITLE', 'Cookie statement')}
282
+ </Typography>
283
+ </AccordionSummary>
284
+ <AccordionDetails>
285
+ {loadingCookieStatement ? (
286
+ <Typography variant="body2" color="text.secondary">
287
+ {t('Profile.STATEMENT_LOADING', 'Loading statement...')}
288
+ </Typography>
289
+ ) : (
290
+ <Typography variant="body2" sx={{ whiteSpace: 'pre-wrap' }}>
291
+ {cookieStatement || t('Profile.COOKIES_STATEMENT_EMPTY', 'Cookie statement is currently unavailable.')}
292
+ </Typography>
293
+ )}
294
+ </AccordionDetails>
295
+ </Accordion>
296
+ )}
297
+ </Stack>
298
+ )}
186
299
  </Box>
187
300
  )}
188
301
 
@@ -1,5 +1,5 @@
1
1
  // src/auth/components/SecurityComponent.jsx
2
- import React, { useState } from 'react';
2
+ import React, { useContext, useMemo, useState } from 'react';
3
3
  import {
4
4
  Box,
5
5
  Typography,
@@ -13,22 +13,46 @@ import { PasskeysComponent } from './PasskeysComponent';
13
13
  import { MFAComponent } from './MFAComponent';
14
14
  import { changePassword } from '../auth/authApi';
15
15
  import { startSocialLogin } from '../utils/authService';
16
+ import { AuthContext } from '../auth/AuthContext';
16
17
 
17
18
  export function SecurityComponent({
18
19
  fromRecovery = false,
19
- fromWeakLogin = false, // optional: wenn du später weak-login-Redirect nutzt
20
+ fromWeakLogin = false,
20
21
  }) {
21
22
  const { t } = useTranslation();
23
+ const { authMethods } = useContext(AuthContext);
22
24
 
23
25
  const [messageKey, setMessageKey] = useState(null);
24
26
  const [errorKey, setErrorKey] = useState(null);
25
27
 
28
+ const socialProviders = Array.isArray(authMethods?.social_providers)
29
+ ? authMethods.social_providers
30
+ : [];
31
+ const canChangePassword = Boolean(authMethods?.password_change);
32
+ const socialLoginEnabled = Boolean(authMethods?.social_login) && socialProviders.length > 0;
33
+ const passkeysEnabled = Boolean(authMethods?.passkeys_manage);
34
+ const mfaEnabled = Boolean(authMethods?.mfa_enabled);
35
+
36
+ const sectionOrder = useMemo(
37
+ () => [
38
+ canChangePassword ? 'password' : null,
39
+ socialLoginEnabled ? 'social' : null,
40
+ passkeysEnabled ? 'passkeys' : null,
41
+ mfaEnabled ? 'mfa' : null,
42
+ ].filter(Boolean),
43
+ [canChangePassword, socialLoginEnabled, passkeysEnabled, mfaEnabled],
44
+ );
45
+
46
+ const needsDividerAfter = (section) => {
47
+ const idx = sectionOrder.indexOf(section);
48
+ return idx !== -1 && idx < sectionOrder.length - 1;
49
+ };
50
+
26
51
  const handleSocialClick = async (provider) => {
27
52
  setMessageKey(null);
28
53
  setErrorKey(null);
29
54
  try {
30
55
  await startSocialLogin(provider);
31
- // Redirect läuft über den Provider-Flow, hier kein extra Text nötig
32
56
  } catch (err) {
33
57
  setErrorKey(err.code || 'Auth.SOCIAL_LOGIN_FAILED');
34
58
  }
@@ -47,14 +71,12 @@ export function SecurityComponent({
47
71
 
48
72
  return (
49
73
  <Box>
50
- {/* Hinweis nach Recovery-Login */}
51
74
  {fromRecovery && (
52
75
  <Alert severity="warning" sx={{ mb: 2 }}>
53
76
  {t('Security.RECOVERY_LOGIN_WARNING')}
54
77
  </Alert>
55
78
  )}
56
79
 
57
- {/* Optional: Hinweis nach „schwachem“ Login, wenn du das nutzt */}
58
80
  {fromWeakLogin && (
59
81
  <Alert severity="warning" sx={{ mb: 2 }}>
60
82
  {t('Security.WEAK_LOGIN_WARNING')}
@@ -72,32 +94,40 @@ export function SecurityComponent({
72
94
  </Alert>
73
95
  )}
74
96
 
75
- {/* Password section */}
76
- <Typography variant="h6" gutterBottom>
77
- {t('Auth.LOGIN_PASSWORD_LABEL')}
78
- </Typography>
79
- <PasswordChangeForm onSubmit={handlePasswordChange} />
80
-
81
- <Divider sx={{ my: 3 }} />
82
-
83
- {/* Social logins section */}
84
- <Typography variant="h6" gutterBottom>
85
- {t('Security.SOCIAL_SECTION_TITLE')}
86
- </Typography>
87
- <Typography variant="body2" sx={{ mb: 1 }}>
88
- {t('Security.SOCIAL_SECTION_DESCRIPTION')}
89
- </Typography>
90
- <SocialLoginButtons onProviderClick={handleSocialClick} />
91
-
92
- <Divider sx={{ my: 3 }} />
97
+ {canChangePassword && (
98
+ <>
99
+ <Typography variant="h6" gutterBottom>
100
+ {t('Auth.LOGIN_PASSWORD_LABEL')}
101
+ </Typography>
102
+ <PasswordChangeForm onSubmit={handlePasswordChange} />
103
+ {needsDividerAfter('password') && <Divider sx={{ my: 3 }} />}
104
+ </>
105
+ )}
93
106
 
94
- {/* Passkeys */}
95
- <PasskeysComponent />
107
+ {socialLoginEnabled && (
108
+ <>
109
+ <Typography variant="h6" gutterBottom>
110
+ {t('Security.SOCIAL_SECTION_TITLE')}
111
+ </Typography>
112
+ <Typography variant="body2" sx={{ mb: 1 }}>
113
+ {t('Security.SOCIAL_SECTION_DESCRIPTION')}
114
+ </Typography>
115
+ <SocialLoginButtons
116
+ onProviderClick={handleSocialClick}
117
+ providers={socialProviders}
118
+ />
119
+ {needsDividerAfter('social') && <Divider sx={{ my: 3 }} />}
120
+ </>
121
+ )}
96
122
 
97
- <Divider sx={{ my: 3 }} />
123
+ {passkeysEnabled && (
124
+ <>
125
+ <PasskeysComponent />
126
+ {needsDividerAfter('passkeys') && <Divider sx={{ my: 3 }} />}
127
+ </>
128
+ )}
98
129
 
99
- {/* MFA (TOTP + Recovery-Codes) */}
100
- <MFAComponent />
130
+ {mfaEnabled && <MFAComponent />}
101
131
  </Box>
102
132
  );
103
133
  };
@@ -8,7 +8,7 @@ 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
 
14
14
  const handleClick = (provider) => {
@@ -17,57 +17,65 @@ export function SocialLoginButtons({ onProviderClick }) {
17
17
  }
18
18
  };
19
19
 
20
+ const activeProviders = Array.isArray(providers) && providers.length > 0
21
+ ? providers
22
+ : [SOCIAL_PROVIDERS.google, SOCIAL_PROVIDERS.microsoft];
23
+
20
24
  return (
21
25
  <Stack spacing={1.5}>
22
- <Button
23
- variant="outlined"
24
- fullWidth
25
- onClick={() => handleClick(SOCIAL_PROVIDERS.google)}
26
- startIcon={
27
- <Box
28
- sx={{
29
- width: 24,
30
- height: 24,
31
- borderRadius: '50%',
32
- border: '1px solid rgba(0,0,0,0.2)',
33
- display: 'flex',
34
- alignItems: 'center',
35
- justifyContent: 'center',
36
- fontWeight: 700,
37
- fontSize: 14,
38
- }}
39
- >
40
- G
41
- </Box>
42
- }
43
- >
44
- {t('Auth.LOGIN_SOCIAL_GOOGLE')}
45
- </Button>
26
+ {activeProviders.includes(SOCIAL_PROVIDERS.google) && (
27
+ <Button
28
+ variant="outlined"
29
+ fullWidth
30
+ onClick={() => handleClick(SOCIAL_PROVIDERS.google)}
31
+ startIcon={
32
+ <Box
33
+ sx={{
34
+ width: 24,
35
+ height: 24,
36
+ borderRadius: '50%',
37
+ border: '1px solid rgba(0,0,0,0.2)',
38
+ display: 'flex',
39
+ alignItems: 'center',
40
+ justifyContent: 'center',
41
+ fontWeight: 700,
42
+ fontSize: 14,
43
+ }}
44
+ >
45
+ G
46
+ </Box>
47
+ }
48
+ >
49
+ {t('Auth.LOGIN_SOCIAL_GOOGLE')}
50
+ </Button>
51
+ )}
46
52
 
47
- <Button
48
- variant="outlined"
49
- fullWidth
50
- onClick={() => handleClick(SOCIAL_PROVIDERS.microsoft)}
51
- startIcon={
52
- <Box
53
- sx={{
54
- width: 24,
55
- height: 24,
56
- display: 'grid',
57
- gridTemplateColumns: '1fr 1fr',
58
- gridTemplateRows: '1fr 1fr',
59
- gap: '1px',
60
- }}
61
- >
62
- <Box sx={{ bgcolor: 'primary.main', opacity: 0.9 }} />
63
- <Box sx={{ bgcolor: 'primary.main', opacity: 0.7 }} />
64
- <Box sx={{ bgcolor: 'primary.main', opacity: 0.7 }} />
65
- <Box sx={{ bgcolor: 'primary.main', opacity: 0.9 }} />
66
- </Box>
67
- }
68
- >
69
- {t('Auth.LOGIN_SOCIAL_MICROSOFT')}
70
- </Button>
53
+ {activeProviders.includes(SOCIAL_PROVIDERS.microsoft) && (
54
+ <Button
55
+ variant="outlined"
56
+ fullWidth
57
+ onClick={() => handleClick(SOCIAL_PROVIDERS.microsoft)}
58
+ startIcon={
59
+ <Box
60
+ sx={{
61
+ width: 24,
62
+ height: 24,
63
+ display: 'grid',
64
+ gridTemplateColumns: '1fr 1fr',
65
+ gridTemplateRows: '1fr 1fr',
66
+ gap: '1px',
67
+ }}
68
+ >
69
+ <Box sx={{ bgcolor: 'primary.main', opacity: 0.9 }} />
70
+ <Box sx={{ bgcolor: 'primary.main', opacity: 0.7 }} />
71
+ <Box sx={{ bgcolor: 'primary.main', opacity: 0.7 }} />
72
+ <Box sx={{ bgcolor: 'primary.main', opacity: 0.9 }} />
73
+ </Box>
74
+ }
75
+ >
76
+ {t('Auth.LOGIN_SOCIAL_MICROSOFT')}
77
+ </Button>
78
+ )}
71
79
  </Stack>
72
80
  );
73
81
  };