anima-ds-nucleus 1.0.16 → 1.0.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/anima-ds-nucleus.css +1 -1
- package/dist/anima-ds.cjs.js +150 -124
- package/dist/anima-ds.esm.js +6799 -5651
- package/package.json +1 -1
- package/src/components/DataDisplay/Card/CardTituloCorto.jsx +23 -2
- package/src/components/DataDisplay/Card/CardTituloCortoMasEstado.jsx +23 -2
- package/src/components/DataDisplay/Card/CardTituloLargo.jsx +23 -2
- package/src/components/DataDisplay/Card/CardTituloLargoMasEstado.jsx +23 -2
- package/src/components/Inputs/PasswordInput/PasswordInput.jsx +85 -0
- package/src/components/Layout/Header/HeaderCompartido.jsx +192 -0
- package/src/components/Layout/Header/HeaderCompartido.stories.jsx +66 -0
- package/src/components/Layout/Header/HeaderConBuscador.stories.jsx +1 -0
- package/src/components/Layout/Header/HeaderCore.jsx +12 -12
- package/src/components/Layout/Header/HeaderGeneral.jsx +248 -53
- package/src/components/Layout/Header/HeaderGeneral.stories.jsx +34 -0
- package/src/components/Layout/Header/HeaderPoint.jsx +3 -3
- package/src/components/Layout/NavPoint/NavPoint.jsx +76 -55
- package/src/components/Layout/SaludoConFechaDashboard/SaludoConFechaDashboard.jsx +0 -1
- package/src/components/Layout/Sidebar/SidebarCore.jsx +82 -30
- package/src/components/Views/ForgotPassword/ForgotPassword.jsx +341 -0
- package/src/components/Views/ForgotPassword/ForgotPassword.stories.jsx +186 -0
- package/src/components/Views/LoginForm/LoginForm.jsx +332 -43
- package/src/components/Views/LoginForm/LoginForm.stories.jsx +182 -4
- package/src/i18n/config.js +12 -0
- package/src/index.js +2 -0
|
@@ -0,0 +1,186 @@
|
|
|
1
|
+
import { ForgotPassword } from './ForgotPassword';
|
|
2
|
+
import { I18nProvider } from '../../../providers/I18nProvider';
|
|
3
|
+
|
|
4
|
+
export default {
|
|
5
|
+
title: 'Views/ForgotPassword',
|
|
6
|
+
component: ForgotPassword,
|
|
7
|
+
tags: ['autodocs'],
|
|
8
|
+
decorators: [
|
|
9
|
+
(Story) => (
|
|
10
|
+
<I18nProvider language="es-AR">
|
|
11
|
+
<Story />
|
|
12
|
+
</I18nProvider>
|
|
13
|
+
),
|
|
14
|
+
],
|
|
15
|
+
argTypes: {
|
|
16
|
+
variant: {
|
|
17
|
+
control: 'select',
|
|
18
|
+
options: ['default', 'hexa-login'],
|
|
19
|
+
description: 'Variante del componente',
|
|
20
|
+
},
|
|
21
|
+
loading: {
|
|
22
|
+
control: 'boolean',
|
|
23
|
+
description: 'Estado de carga',
|
|
24
|
+
},
|
|
25
|
+
error: {
|
|
26
|
+
control: 'text',
|
|
27
|
+
description: 'Error general del servidor',
|
|
28
|
+
},
|
|
29
|
+
},
|
|
30
|
+
};
|
|
31
|
+
|
|
32
|
+
// Estado por defecto
|
|
33
|
+
export const Default = {
|
|
34
|
+
args: {
|
|
35
|
+
onBackToLogin: () => {
|
|
36
|
+
console.log('Back to login clicked');
|
|
37
|
+
alert('Volver al login');
|
|
38
|
+
},
|
|
39
|
+
onSubmit: async ({ email, countryCode }) => {
|
|
40
|
+
console.log('Forgot password data:', { email, countryCode });
|
|
41
|
+
await new Promise((resolve) => setTimeout(resolve, 2000));
|
|
42
|
+
return { resetLink: 'https://example.com/reset-password' };
|
|
43
|
+
},
|
|
44
|
+
},
|
|
45
|
+
};
|
|
46
|
+
|
|
47
|
+
// Estado de carga
|
|
48
|
+
export const Loading = {
|
|
49
|
+
args: {
|
|
50
|
+
loading: true,
|
|
51
|
+
onBackToLogin: () => {
|
|
52
|
+
alert('Volver al login');
|
|
53
|
+
},
|
|
54
|
+
onSubmit: async () => {
|
|
55
|
+
await new Promise((resolve) => setTimeout(resolve, 5000));
|
|
56
|
+
},
|
|
57
|
+
},
|
|
58
|
+
};
|
|
59
|
+
|
|
60
|
+
// Estado de éxito
|
|
61
|
+
export const Success = {
|
|
62
|
+
args: {
|
|
63
|
+
onBackToLogin: () => {
|
|
64
|
+
alert('Volver al login');
|
|
65
|
+
},
|
|
66
|
+
onSubmit: async ({ email }) => {
|
|
67
|
+
console.log('Email:', email);
|
|
68
|
+
await new Promise((resolve) => setTimeout(resolve, 1000));
|
|
69
|
+
return { resetLink: 'https://example.com/reset-password?token=abc123' };
|
|
70
|
+
},
|
|
71
|
+
},
|
|
72
|
+
};
|
|
73
|
+
|
|
74
|
+
// Con error del servidor
|
|
75
|
+
export const WithError = {
|
|
76
|
+
args: {
|
|
77
|
+
error: 'No se encontró una cuenta asociada a este correo electrónico.',
|
|
78
|
+
onBackToLogin: () => {
|
|
79
|
+
alert('Volver al login');
|
|
80
|
+
},
|
|
81
|
+
onSubmit: async () => {
|
|
82
|
+
throw new Error('Usuario no encontrado');
|
|
83
|
+
},
|
|
84
|
+
},
|
|
85
|
+
};
|
|
86
|
+
|
|
87
|
+
// Error 404
|
|
88
|
+
export const Error404 = {
|
|
89
|
+
args: {
|
|
90
|
+
onBackToLogin: () => {
|
|
91
|
+
alert('Volver al login');
|
|
92
|
+
},
|
|
93
|
+
onSubmit: async () => {
|
|
94
|
+
const error = new Error('Not found');
|
|
95
|
+
error.response = { status: 404, data: { message: 'Usuario no encontrado' } };
|
|
96
|
+
throw error;
|
|
97
|
+
},
|
|
98
|
+
},
|
|
99
|
+
};
|
|
100
|
+
|
|
101
|
+
// Error 400
|
|
102
|
+
export const Error400 = {
|
|
103
|
+
args: {
|
|
104
|
+
onBackToLogin: () => {
|
|
105
|
+
alert('Volver al login');
|
|
106
|
+
},
|
|
107
|
+
onSubmit: async () => {
|
|
108
|
+
const error = new Error('Bad request');
|
|
109
|
+
error.response = { status: 400, data: { message: 'Email inválido' } };
|
|
110
|
+
throw error;
|
|
111
|
+
},
|
|
112
|
+
},
|
|
113
|
+
};
|
|
114
|
+
|
|
115
|
+
// Error 500
|
|
116
|
+
export const Error500 = {
|
|
117
|
+
args: {
|
|
118
|
+
onBackToLogin: () => {
|
|
119
|
+
alert('Volver al login');
|
|
120
|
+
},
|
|
121
|
+
onSubmit: async () => {
|
|
122
|
+
const error = new Error('Server error');
|
|
123
|
+
error.response = { status: 500 };
|
|
124
|
+
throw error;
|
|
125
|
+
},
|
|
126
|
+
},
|
|
127
|
+
};
|
|
128
|
+
|
|
129
|
+
// Variante hexa-login
|
|
130
|
+
export const HexaLogin = {
|
|
131
|
+
args: {
|
|
132
|
+
variant: 'hexa-login',
|
|
133
|
+
onBackToLogin: () => {
|
|
134
|
+
alert('Volver al login');
|
|
135
|
+
},
|
|
136
|
+
onSubmit: async ({ email }) => {
|
|
137
|
+
console.log('Email:', email);
|
|
138
|
+
await new Promise((resolve) => setTimeout(resolve, 2000));
|
|
139
|
+
return { resetLink: 'https://example.com/reset-password' };
|
|
140
|
+
},
|
|
141
|
+
},
|
|
142
|
+
};
|
|
143
|
+
|
|
144
|
+
// Versión en español
|
|
145
|
+
export const Spanish = {
|
|
146
|
+
decorators: [
|
|
147
|
+
(Story) => (
|
|
148
|
+
<I18nProvider language="es-AR">
|
|
149
|
+
<Story />
|
|
150
|
+
</I18nProvider>
|
|
151
|
+
),
|
|
152
|
+
],
|
|
153
|
+
args: {
|
|
154
|
+
onBackToLogin: () => {
|
|
155
|
+
alert('Volver al login');
|
|
156
|
+
},
|
|
157
|
+
onSubmit: async ({ email }) => {
|
|
158
|
+
console.log('Email:', email);
|
|
159
|
+
await new Promise((resolve) => setTimeout(resolve, 2000));
|
|
160
|
+
return { resetLink: 'https://example.com/reset-password' };
|
|
161
|
+
},
|
|
162
|
+
},
|
|
163
|
+
};
|
|
164
|
+
|
|
165
|
+
// Versión en portugués
|
|
166
|
+
export const Portuguese = {
|
|
167
|
+
decorators: [
|
|
168
|
+
(Story) => (
|
|
169
|
+
<I18nProvider language="pt-BR">
|
|
170
|
+
<Story />
|
|
171
|
+
</I18nProvider>
|
|
172
|
+
),
|
|
173
|
+
],
|
|
174
|
+
args: {
|
|
175
|
+
onBackToLogin: () => {
|
|
176
|
+
alert('Volver ao login');
|
|
177
|
+
},
|
|
178
|
+
onSubmit: async ({ email }) => {
|
|
179
|
+
console.log('Email:', email);
|
|
180
|
+
await new Promise((resolve) => setTimeout(resolve, 2000));
|
|
181
|
+
return { resetLink: 'https://example.com/reset-password' };
|
|
182
|
+
},
|
|
183
|
+
},
|
|
184
|
+
};
|
|
185
|
+
|
|
186
|
+
|
|
@@ -1,15 +1,30 @@
|
|
|
1
1
|
import { useState } from 'react';
|
|
2
2
|
import { useTranslation } from 'react-i18next';
|
|
3
|
+
import { Button } from '../../Atoms/Button/Button';
|
|
4
|
+
import { Typography } from '../../Atoms/Typography/Typography';
|
|
3
5
|
import { Input } from '../../Inputs/Input/Input';
|
|
4
6
|
import { Checkbox } from '../../Inputs/Checkbox/Checkbox';
|
|
5
|
-
import {
|
|
7
|
+
import { PasswordInput } from '../../Inputs/PasswordInput/PasswordInput';
|
|
6
8
|
|
|
7
9
|
export const LoginForm = ({
|
|
10
|
+
variant,
|
|
8
11
|
onSubmit,
|
|
12
|
+
loading = false,
|
|
13
|
+
onForgotPassword,
|
|
14
|
+
onGoogleLogin,
|
|
15
|
+
onMicrosoftLogin,
|
|
16
|
+
showSocialLogin = true,
|
|
17
|
+
showDivider = true,
|
|
18
|
+
showPasswordRequirementsOnError = true,
|
|
19
|
+
labels,
|
|
20
|
+
generalError,
|
|
21
|
+
generalErrorTitle,
|
|
22
|
+
generalErrorSubtitle,
|
|
9
23
|
className = '',
|
|
10
24
|
...props
|
|
11
25
|
}) => {
|
|
12
26
|
const { t } = useTranslation();
|
|
27
|
+
const isHexaLogin = variant === 'hexa-login';
|
|
13
28
|
const [formData, setFormData] = useState({
|
|
14
29
|
email: '',
|
|
15
30
|
password: '',
|
|
@@ -45,52 +60,326 @@ export const LoginForm = ({
|
|
|
45
60
|
}
|
|
46
61
|
};
|
|
47
62
|
|
|
63
|
+
const resolvedLabels = {
|
|
64
|
+
emailLabel: labels?.emailLabel ?? t('form.email') ?? 'Email',
|
|
65
|
+
passwordLabel: labels?.passwordLabel ?? t('form.password') ?? 'Contraseña',
|
|
66
|
+
rememberMeLabel: labels?.rememberMeLabel ?? t('form.rememberMe') ?? 'Recordarme',
|
|
67
|
+
forgotPasswordLabel: labels?.forgotPasswordLabel ?? t('form.forgotPassword') ?? 'Olvidé mi contraseña',
|
|
68
|
+
continueWithLabel: labels?.continueWithLabel ?? 'Continuar con',
|
|
69
|
+
googleLabel: labels?.googleLabel ?? 'Google',
|
|
70
|
+
microsoftLabel: labels?.microsoftLabel ?? 'Microsoft',
|
|
71
|
+
};
|
|
72
|
+
|
|
73
|
+
const loginButtonText = loading ? (t('form.loggingIn') ?? 'Ingresando...') : (t('form.login') ?? 'Ingresar');
|
|
74
|
+
|
|
48
75
|
return (
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
{
|
|
76
|
+
isHexaLogin ? (
|
|
77
|
+
<div
|
|
78
|
+
style={{
|
|
79
|
+
width: '427px',
|
|
80
|
+
minHeight: '409px',
|
|
81
|
+
borderRadius: '14px',
|
|
82
|
+
border: '1px solid #0000001A',
|
|
83
|
+
padding: '24px',
|
|
84
|
+
background: '#FFFFFF',
|
|
85
|
+
boxShadow: '0px 4px 6px -4px #0000001A, 0px 10px 15px -3px #0000001A',
|
|
86
|
+
}}
|
|
87
|
+
>
|
|
88
|
+
<form onSubmit={handleSubmit} className={className} {...props}>
|
|
89
|
+
{/* 1) Email */}
|
|
90
|
+
<div className="w-full">
|
|
91
|
+
<label
|
|
92
|
+
className="block mb-2 font-medium text-sm"
|
|
93
|
+
style={{ color: '#2D5C63' }}
|
|
94
|
+
>
|
|
95
|
+
{resolvedLabels.emailLabel}
|
|
96
|
+
</label>
|
|
97
|
+
<input
|
|
98
|
+
type="email"
|
|
99
|
+
value={formData.email}
|
|
100
|
+
onChange={(e) => handleChange('email', e.target.value)}
|
|
101
|
+
placeholder="Ingresa tu correo electronico"
|
|
102
|
+
className="w-full px-3 py-2 border rounded-lg focus:outline-none focus:ring-2 focus:ring-blue-500 focus:border-transparent text-sm text-gray-900"
|
|
103
|
+
style={{ borderColor: errors.email ? '#ef4444' : '#2D5C63' }}
|
|
104
|
+
/>
|
|
105
|
+
</div>
|
|
106
|
+
|
|
107
|
+
{/* 2) Password */}
|
|
108
|
+
<div className="w-full mt-4">
|
|
109
|
+
<PasswordInput
|
|
110
|
+
variant="hexa-login"
|
|
111
|
+
label={resolvedLabels.passwordLabel}
|
|
112
|
+
labelClassName="block mb-2 font-medium text-sm"
|
|
113
|
+
labelStyle={{ color: '#2D5C63' }}
|
|
114
|
+
placeholder={t('placeholder.password')}
|
|
115
|
+
value={formData.password}
|
|
116
|
+
onChange={(e) => handleChange('password', e.target.value)}
|
|
117
|
+
inputClassName="w-full px-3 py-2 border rounded-lg focus:outline-none focus:ring-2 focus:ring-blue-500 focus:border-transparent text-sm text-gray-900"
|
|
118
|
+
style={{
|
|
119
|
+
paddingRight: '2.75rem',
|
|
120
|
+
borderColor: errors.password ? '#ef4444' : '#2D5C63',
|
|
121
|
+
}}
|
|
122
|
+
/>
|
|
123
|
+
</div>
|
|
124
|
+
|
|
125
|
+
{/* 3) Password requirements box (solo si hay error de password) */}
|
|
126
|
+
{showPasswordRequirementsOnError && !!errors.password && (
|
|
127
|
+
<div
|
|
128
|
+
className="rounded-lg p-4 border mt-4"
|
|
129
|
+
style={{
|
|
130
|
+
background: '#f5f3ff',
|
|
131
|
+
borderColor: '#a78bfa',
|
|
132
|
+
color: '#0c4a6e',
|
|
133
|
+
fontFamily: 'IBM Plex Mono',
|
|
134
|
+
}}
|
|
135
|
+
>
|
|
136
|
+
<ul className="list-disc list-inside text-sm space-y-1" style={{ color: '#0c4a6e' }}>
|
|
137
|
+
<li>Al menos 8 caracteres</li>
|
|
138
|
+
<li>Al menos una letra mayúscula</li>
|
|
139
|
+
<li>Al menos un número</li>
|
|
140
|
+
<li>Al menos un caracter especial</li>
|
|
141
|
+
</ul>
|
|
142
|
+
</div>
|
|
143
|
+
)}
|
|
144
|
+
|
|
145
|
+
{/* 4) Row remember/forgot */}
|
|
146
|
+
<div className="flex items-center justify-between mt-4">
|
|
147
|
+
<label className="flex items-center">
|
|
148
|
+
<input
|
|
149
|
+
type="checkbox"
|
|
150
|
+
checked={formData.rememberMe}
|
|
151
|
+
onChange={(e) => handleChange('rememberMe', e.target.checked)}
|
|
152
|
+
className="w-4 h-4 border-gray-300 rounded focus:outline-none focus:ring-0"
|
|
153
|
+
style={{
|
|
154
|
+
accentColor: '#2D5C63',
|
|
155
|
+
borderColor: formData.rememberMe ? '#2D5C63' : '#d1d5db',
|
|
156
|
+
}}
|
|
157
|
+
/>
|
|
158
|
+
<span className="ml-2 text-sm font-medium" style={{ color: '#2D5C63' }}>
|
|
159
|
+
{resolvedLabels.rememberMeLabel}
|
|
160
|
+
</span>
|
|
161
|
+
</label>
|
|
162
|
+
|
|
163
|
+
<a
|
|
164
|
+
href="#"
|
|
165
|
+
className="text-body-md hover:underline font-medium"
|
|
166
|
+
style={{ color: '#2D5C63' }}
|
|
167
|
+
onClick={(e) => {
|
|
168
|
+
e.preventDefault();
|
|
169
|
+
if (onForgotPassword) onForgotPassword();
|
|
170
|
+
}}
|
|
171
|
+
>
|
|
172
|
+
{resolvedLabels.forgotPasswordLabel}
|
|
90
173
|
</a>
|
|
91
174
|
</div>
|
|
175
|
+
|
|
176
|
+
{/* 5) Error general box rojo (si existe) */}
|
|
177
|
+
{(generalError || errors.general) && (
|
|
178
|
+
<div
|
|
179
|
+
className="mt-4"
|
|
180
|
+
style={{
|
|
181
|
+
width: '377px',
|
|
182
|
+
height: '68px',
|
|
183
|
+
paddingTop: '12px',
|
|
184
|
+
paddingRight: '16px',
|
|
185
|
+
paddingBottom: '12px',
|
|
186
|
+
paddingLeft: '16px',
|
|
187
|
+
gap: '4px',
|
|
188
|
+
borderRadius: '8px',
|
|
189
|
+
borderWidth: '1px',
|
|
190
|
+
border: '1px solid #B30808',
|
|
191
|
+
backgroundColor: '#F8DBDB',
|
|
192
|
+
display: 'flex',
|
|
193
|
+
flexDirection: 'column',
|
|
194
|
+
}}
|
|
195
|
+
>
|
|
196
|
+
<Typography
|
|
197
|
+
variant="body2"
|
|
198
|
+
style={{
|
|
199
|
+
fontFamily: "'IBM Plex Mono', monospace",
|
|
200
|
+
fontWeight: 400,
|
|
201
|
+
fontStyle: 'normal',
|
|
202
|
+
fontSize: '14px',
|
|
203
|
+
lineHeight: '21px',
|
|
204
|
+
letterSpacing: '0.02em',
|
|
205
|
+
color: '#B30808',
|
|
206
|
+
margin: 0,
|
|
207
|
+
}}
|
|
208
|
+
>
|
|
209
|
+
{generalErrorTitle ??
|
|
210
|
+
(typeof generalError === 'string' ? generalError : null) ??
|
|
211
|
+
(errors.general && typeof errors.general === 'string' ? errors.general : null) ??
|
|
212
|
+
(t('errors.invalidCredentials') ?? 'Correo o contraseña incorrectos.')}
|
|
213
|
+
</Typography>
|
|
214
|
+
<Typography
|
|
215
|
+
variant="body2"
|
|
216
|
+
style={{
|
|
217
|
+
fontFamily: "'IBM Plex Mono', monospace",
|
|
218
|
+
fontWeight: 400,
|
|
219
|
+
fontStyle: 'normal',
|
|
220
|
+
fontSize: '14px',
|
|
221
|
+
lineHeight: '21px',
|
|
222
|
+
letterSpacing: '0.02em',
|
|
223
|
+
color: '#B30808',
|
|
224
|
+
margin: 0,
|
|
225
|
+
}}
|
|
226
|
+
>
|
|
227
|
+
{generalErrorSubtitle ??
|
|
228
|
+
(t('errors.tryAgain') ?? 'Intentá nuevamente.')}
|
|
229
|
+
</Typography>
|
|
230
|
+
</div>
|
|
231
|
+
)}
|
|
232
|
+
|
|
233
|
+
{/* 6) Botón login */}
|
|
234
|
+
<div className="mt-4">
|
|
235
|
+
<Button
|
|
236
|
+
type="submit"
|
|
237
|
+
tipo="Primary"
|
|
238
|
+
color="Teal"
|
|
239
|
+
tamaño="Default"
|
|
240
|
+
className="w-full"
|
|
241
|
+
disabled={loading}
|
|
242
|
+
>
|
|
243
|
+
<span
|
|
244
|
+
style={{
|
|
245
|
+
fontFamily: "'IBM Plex Sans', sans-serif",
|
|
246
|
+
fontWeight: 400,
|
|
247
|
+
fontStyle: 'normal',
|
|
248
|
+
fontSize: '16px',
|
|
249
|
+
lineHeight: '24px',
|
|
250
|
+
letterSpacing: '0%',
|
|
251
|
+
}}
|
|
252
|
+
>
|
|
253
|
+
{loginButtonText}
|
|
254
|
+
</span>
|
|
255
|
+
</Button>
|
|
256
|
+
</div>
|
|
257
|
+
|
|
258
|
+
{/* 7) Divider */}
|
|
259
|
+
{showDivider && (
|
|
260
|
+
<div className="flex items-center gap-3 my-6">
|
|
261
|
+
<div className="flex-1" style={{ borderTop: '1px solid #2D5C63' }} />
|
|
262
|
+
<Typography
|
|
263
|
+
variant="body2"
|
|
264
|
+
className="text-body-md font-medium"
|
|
265
|
+
style={{ color: '#2D5C63', whiteSpace: 'nowrap' }}
|
|
266
|
+
>
|
|
267
|
+
{resolvedLabels.continueWithLabel}
|
|
268
|
+
</Typography>
|
|
269
|
+
<div className="flex-1" style={{ borderTop: '1px solid #2D5C63' }} />
|
|
270
|
+
</div>
|
|
271
|
+
)}
|
|
272
|
+
|
|
273
|
+
{/* 8) Social buttons */}
|
|
274
|
+
{showSocialLogin && (
|
|
275
|
+
<div
|
|
276
|
+
style={{
|
|
277
|
+
width: '377px',
|
|
278
|
+
height: '36px',
|
|
279
|
+
gap: '20px',
|
|
280
|
+
marginLeft: 'auto',
|
|
281
|
+
marginRight: 'auto',
|
|
282
|
+
display: 'flex',
|
|
283
|
+
}}
|
|
284
|
+
>
|
|
285
|
+
<button
|
|
286
|
+
type="button"
|
|
287
|
+
onClick={onGoogleLogin}
|
|
288
|
+
className="flex-1 flex items-center justify-center bg-white hover:bg-gray-50 transition-colors"
|
|
289
|
+
style={{
|
|
290
|
+
width: '178.5px',
|
|
291
|
+
height: '36px',
|
|
292
|
+
padding: '8px 43px',
|
|
293
|
+
gap: '16px',
|
|
294
|
+
borderRadius: '8px',
|
|
295
|
+
border: '1px solid #38656D',
|
|
296
|
+
}}
|
|
297
|
+
>
|
|
298
|
+
<svg width="18" height="18" viewBox="0 0 48 48" aria-hidden="true">
|
|
299
|
+
<path fill="#FFC107" d="M43.611 20.083H42V20H24v8h11.303C33.651 32.659 29.227 36 24 36c-6.627 0-12-5.373-12-12s5.373-12 12-12c3.059 0 5.842 1.154 7.961 3.039l5.657-5.657C34.047 6.053 29.272 4 24 4 12.955 4 4 12.955 4 24s8.955 20 20 20 20-8.955 20-20c0-1.341-.138-2.65-.389-3.917z" />
|
|
300
|
+
<path fill="#FF3D00" d="M6.306 14.691l6.571 4.819C14.655 16.108 19.002 12 24 12c3.059 0 5.842 1.154 7.961 3.039l5.657-5.657C34.047 6.053 29.272 4 24 4 16.318 4 9.656 8.337 6.306 14.691z" />
|
|
301
|
+
<path fill="#4CAF50" d="M24 44c5.166 0 9.86-1.977 13.409-5.197l-6.19-5.238C29.211 35.091 26.715 36 24 36c-5.205 0-9.616-3.318-11.281-7.946l-6.522 5.025C9.505 39.556 16.227 44 24 44z" />
|
|
302
|
+
<path fill="#1976D2" d="M43.611 20.083H42V20H24v8h11.303c-.793 2.116-2.318 3.91-4.284 5.238l.003-.002 6.19 5.238C36.971 39.205 44 34 44 24c0-1.341-.138-2.65-.389-3.917z" />
|
|
303
|
+
</svg>
|
|
304
|
+
<Typography variant="body2" className="text-gray-900 font-medium">
|
|
305
|
+
{resolvedLabels.googleLabel}
|
|
306
|
+
</Typography>
|
|
307
|
+
</button>
|
|
308
|
+
|
|
309
|
+
<button
|
|
310
|
+
type="button"
|
|
311
|
+
onClick={onMicrosoftLogin}
|
|
312
|
+
className="flex-1 flex items-center justify-center bg-white hover:bg-gray-50 transition-colors"
|
|
313
|
+
style={{
|
|
314
|
+
width: '178.5px',
|
|
315
|
+
height: '36px',
|
|
316
|
+
padding: '8px 43px',
|
|
317
|
+
gap: '16px',
|
|
318
|
+
borderRadius: '8px',
|
|
319
|
+
border: '1px solid #38656D',
|
|
320
|
+
}}
|
|
321
|
+
>
|
|
322
|
+
<svg width="18" height="18" viewBox="0 0 48 48" aria-hidden="true">
|
|
323
|
+
<path fill="#F25022" d="M6 6h17v17H6z" />
|
|
324
|
+
<path fill="#7FBA00" d="M25 6h17v17H25z" />
|
|
325
|
+
<path fill="#00A4EF" d="M6 25h17v17H6z" />
|
|
326
|
+
<path fill="#FFB900" d="M25 25h17v17H25z" />
|
|
327
|
+
</svg>
|
|
328
|
+
<Typography variant="body2" className="text-gray-900 font-medium">
|
|
329
|
+
{resolvedLabels.microsoftLabel}
|
|
330
|
+
</Typography>
|
|
331
|
+
</button>
|
|
332
|
+
</div>
|
|
333
|
+
)}
|
|
334
|
+
</form>
|
|
92
335
|
</div>
|
|
93
|
-
|
|
336
|
+
) : (
|
|
337
|
+
<form
|
|
338
|
+
onSubmit={handleSubmit}
|
|
339
|
+
className={`max-w-md mx-auto bg-white p-6 rounded-lg shadow-md ${className}`}
|
|
340
|
+
{...props}
|
|
341
|
+
>
|
|
342
|
+
<h2 className="text-2xl font-bold mb-6 text-center">{t('form.login')}</h2>
|
|
343
|
+
|
|
344
|
+
<div className="space-y-4">
|
|
345
|
+
<Input
|
|
346
|
+
label={t('form.email')}
|
|
347
|
+
type="email"
|
|
348
|
+
placeholder={t('placeholder.email')}
|
|
349
|
+
value={formData.email}
|
|
350
|
+
onChange={(e) => handleChange('email', e.target.value)}
|
|
351
|
+
error={errors.email}
|
|
352
|
+
required
|
|
353
|
+
/>
|
|
354
|
+
|
|
355
|
+
<Input
|
|
356
|
+
label={t('form.password')}
|
|
357
|
+
type="password"
|
|
358
|
+
placeholder={t('placeholder.password')}
|
|
359
|
+
value={formData.password}
|
|
360
|
+
onChange={(e) => handleChange('password', e.target.value)}
|
|
361
|
+
error={errors.password}
|
|
362
|
+
required
|
|
363
|
+
/>
|
|
364
|
+
|
|
365
|
+
<Checkbox
|
|
366
|
+
label={t('form.rememberMe')}
|
|
367
|
+
checked={formData.rememberMe}
|
|
368
|
+
onChange={(e) => handleChange('rememberMe', e.target.checked)}
|
|
369
|
+
/>
|
|
370
|
+
|
|
371
|
+
<Button type="submit" variant="primary" className="w-full">
|
|
372
|
+
{t('form.login')}
|
|
373
|
+
</Button>
|
|
374
|
+
|
|
375
|
+
<div className="text-center">
|
|
376
|
+
<a href="#" className="text-sm text-blue-600 hover:underline">
|
|
377
|
+
{t('form.forgotPassword')}
|
|
378
|
+
</a>
|
|
379
|
+
</div>
|
|
380
|
+
</div>
|
|
381
|
+
</form>
|
|
382
|
+
)
|
|
94
383
|
);
|
|
95
384
|
};
|
|
96
385
|
|