Skip to content

Commit 40dc870

Browse files
feat(login): add login component (#111)
1 parent 0453bb1 commit 40dc870

File tree

10 files changed

+266
-12
lines changed

10 files changed

+266
-12
lines changed

package-lock.json

Lines changed: 114 additions & 8 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

package.json

Lines changed: 5 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
{
22
"name": "@indec/react-commons",
3-
"version": "7.0.3",
3+
"version": "7.1.0",
44
"description": "Common reactjs components for apps",
55
"private": false,
66
"main": "index.js",
@@ -30,6 +30,7 @@
3030
"hooks",
3131
"utils",
3232
"assets",
33+
"schemas",
3334
"index.js",
3435
"LICENSE.md",
3536
"README.md",
@@ -41,8 +42,10 @@
4142
"dependencies": {
4243
"autoprefixer": "^10.4.21",
4344
"clsx": "^2.1.1",
45+
"formik": "^2.4.6",
4446
"react": "^19.1.0",
45-
"react-dom": "^19.1.0"
47+
"react-dom": "^19.1.0",
48+
"yup": "^1.6.1"
4649
},
4750
"devDependencies": {
4851
"@babel/cli": "^7.27.0",
Lines changed: 23 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,23 @@
1+
import {getByText} from '@testing-library/react';
2+
3+
import Login from '../../components/Login/index.jsx';
4+
5+
describe('<Login>', () => {
6+
let props;
7+
const getComponent = () => render(Login, props);
8+
beforeEach(() => {
9+
props = {
10+
title: 'My Application'
11+
}
12+
})
13+
14+
it('should display `My Application`', () => {
15+
const {container} = getComponent();
16+
expect(getByText(container, 'My Application')).toBeInTheDocument();
17+
});
18+
19+
it('should display `Login`', () => {
20+
const {container} = getComponent();
21+
expect(getByText(container, 'Login')).toBeInTheDocument();
22+
});
23+
});

src/components/Field.jsx

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,7 @@ export default function Field({name, label, error, type = 'text', onChange, onBl
1414
onBlur(event);
1515
}
1616
};
17+
1718
return (
1819
<div className="w-full">
1920
<label htmlFor={name} className="block text-[17px] text-black text-xl font-medium">
@@ -24,13 +25,13 @@ export default function Field({name, label, error, type = 'text', onChange, onBl
2425
id={name}
2526
name={name}
2627
aria-label={label}
28+
{...rest}
2729
className={`w-full px-4 py-2 border-2 border-gray-400 rounded-lg bg-white focus:outline-none focus:ring-blue-500 focus:border-blue-500 disabled:bg-gray-50 disabled:opacity-50 disabled:cursor-not-allowed transition-colors duration-200 ${
2830
error ? 'border-error focus:ring-error' : 'border-gray-300 focus:ring-primary'
2931
} ${disabled ? 'bg-gray-100 cursor-not-allowed' : ''} ${tooltip ? 'pr-10' : ''}`}
3032
type={type}
3133
onChange={onChange}
3234
disabled={disabled}
33-
{...rest}
3435
onBlur={handleBlur}
3536
/>
3637
{tooltip && (

src/components/Login/LoginForm.jsx

Lines changed: 48 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,48 @@
1+
import React from 'react';
2+
import {Field, Formik, Form} from 'formik';
3+
4+
import Button from '../Button.jsx';
5+
import ErrorMessage from '../ErrorMessage.jsx';
6+
import TextField from '../Field.jsx';
7+
import loginSchema from '../../schemas/login';
8+
9+
export default function LoginForm({onLogin, isLoging, error}) {
10+
return (
11+
<Formik initialValues={{username: '', password: ''}} validationSchema={loginSchema} onSubmit={onLogin}>
12+
<Form>
13+
<div className="flex flex-col gap-6">
14+
<Field name="username">
15+
{({field, meta}) => (
16+
<TextField
17+
{...field}
18+
label="Usuario *"
19+
placeholder="[email protected]"
20+
error={meta.touched && meta.error ? meta.error : null}
21+
/>
22+
)}
23+
</Field>
24+
<Field name="password">
25+
{({field, meta}) => (
26+
<TextField
27+
{...field}
28+
type="password"
29+
label="Contraseña *"
30+
placeholder="123456"
31+
error={meta.touched && meta.error ? meta.error : null}
32+
/>
33+
)}
34+
</Field>
35+
<Button
36+
type="submit"
37+
data-testid="login-button"
38+
disabled={isLoging}
39+
label="Ingresar"
40+
/>
41+
{error && (
42+
<ErrorMessage error={error.status === 401 ? 'Credenciales incorrectas.' : 'Ha ocurrido un error.'}/>
43+
)}
44+
</div>
45+
</Form>
46+
</Formik>
47+
);
48+
}

src/components/Login/index.jsx

Lines changed: 25 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,25 @@
1+
import React from 'react';
2+
3+
import LoginForm from './LoginForm.jsx';
4+
5+
export default function Login({title, onLogin, isLoging, error}) {
6+
return (
7+
<>
8+
<div className="grid grid-cols-1 sm:grid-cols-2 h-screen">
9+
<div
10+
className="flex flex-col justify-center items-center text-white p-8"
11+
style={{
12+
backgroundColor: '#0093E9',
13+
background: 'linear-gradient(160deg, #0093E9 0%, #80D0C7 100%)'
14+
}}
15+
>
16+
<h1 className="text-5xl font-bold text-center uppercase">{title}</h1>
17+
</div>
18+
<div className="flex flex-col gap-3 justify-center p-3">
19+
<h2 className="font-bold text-2xl">Login</h2>
20+
<LoginForm isLoging={isLoging} onLogin={onLogin} error={error}/>
21+
</div>
22+
</div>
23+
</>
24+
);
25+
}

src/components/index.js

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@ export {default as Field} from './Field.jsx';
44
export {default as Footer} from './Footer.jsx';
55
export {default as Header} from './Header/index.jsx';
66
export {default as Loading} from './Loading.jsx';
7+
export {default as Login} from './Login/index.jsx';
78
export {default as Modal} from './Modal/index.jsx';
89
export {default as Pagination} from './Pagination.jsx';
910
export {default as Select} from './Select.jsx';

0 commit comments

Comments
 (0)