From 6e2835c0981de3616aac389aee1e78b501168477 Mon Sep 17 00:00:00 2001 From: Math Date: Fri, 14 Jun 2024 09:02:20 -0300 Subject: [PATCH] feat: add login form (#259) * feat: add login form * refactor: renamed variables and functions to enhance readability and maintainability * refactor: change p tag to span for less robust text * refactor: resolve issue with position:absolute and background color dependencies in login component * refactor: keep style import as last import * refactor: isolate breakpoint * refactor: fix the use of spacing and relative positions * refactor: improve visibility of the text in the input when there is an error * refactor: add class to control error display * refactor: remove line-height with unity * refactor: improve error message display for form inputs * refactor: remove early return * refactor: fix breakpoint * refactor: decrease button spacing * refactor: improve code reading * refactor: add emit login event --- src/components/LoginForm/images/eye-slash.svg | 9 + .../LoginForm/images/facebook-icon.svg | 2 + .../LoginForm/images/google-icon.svg | 28 +++ .../LoginForm/images/paw-form-icon.svg | 3 + src/components/LoginForm/index.js | 150 ++++++++++++++ src/components/LoginForm/index.scss | 189 ++++++++++++++++++ src/stories/LoginForm.stories.js | 29 +++ 7 files changed, 410 insertions(+) create mode 100644 src/components/LoginForm/images/eye-slash.svg create mode 100644 src/components/LoginForm/images/facebook-icon.svg create mode 100644 src/components/LoginForm/images/google-icon.svg create mode 100644 src/components/LoginForm/images/paw-form-icon.svg create mode 100644 src/components/LoginForm/index.js create mode 100644 src/components/LoginForm/index.scss create mode 100644 src/stories/LoginForm.stories.js diff --git a/src/components/LoginForm/images/eye-slash.svg b/src/components/LoginForm/images/eye-slash.svg new file mode 100644 index 00000000..59ae217f --- /dev/null +++ b/src/components/LoginForm/images/eye-slash.svg @@ -0,0 +1,9 @@ + + + + + + + + + diff --git a/src/components/LoginForm/images/facebook-icon.svg b/src/components/LoginForm/images/facebook-icon.svg new file mode 100644 index 00000000..6681697f --- /dev/null +++ b/src/components/LoginForm/images/facebook-icon.svg @@ -0,0 +1,2 @@ + + \ No newline at end of file diff --git a/src/components/LoginForm/images/google-icon.svg b/src/components/LoginForm/images/google-icon.svg new file mode 100644 index 00000000..d5f4dbeb --- /dev/null +++ b/src/components/LoginForm/images/google-icon.svg @@ -0,0 +1,28 @@ + + + + + Google-color + Created with Sketch. + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/src/components/LoginForm/images/paw-form-icon.svg b/src/components/LoginForm/images/paw-form-icon.svg new file mode 100644 index 00000000..6e30ac02 --- /dev/null +++ b/src/components/LoginForm/images/paw-form-icon.svg @@ -0,0 +1,3 @@ + + + diff --git a/src/components/LoginForm/index.js b/src/components/LoginForm/index.js new file mode 100644 index 00000000..91f1d62f --- /dev/null +++ b/src/components/LoginForm/index.js @@ -0,0 +1,150 @@ +import { Component } from 'pet-dex-utilities'; +import TextInput from '../TextInput'; +import Button from '../Button'; +import Toggle from '../Toggle'; +import './index.scss'; + +import pawIcon from './images/paw-form-icon.svg'; +import eyeSlashIcon from './images/eye-slash.svg'; +import googleIcon from './images/google-icon.svg'; +import facebookIcon from './images/facebook-icon.svg'; + +const events = ['login']; + +const html = ` +
+ + + +
+`; + +export default function LoginForm() { + Component.call(this, { html, events }); + const $loginForm = this.selected.get('login-form'); + const $emailInputContainer = this.selected.get('email-input-container'); + const $passwordInputContainer = this.selected.get('password-input-container'); + const $rememberOption = this.selected.get('remember-option'); + const $emailErrorMessage = this.selected.get('email-error-message'); + const $passwordErrorMessage = this.selected.get('password-error-message'); + + const emailInput = new TextInput({ placeholder: 'E-mail' }); + const passwordInput = new TextInput({ + placeholder: 'Senha', + assetUrl: eyeSlashIcon, + assetPosition: 'suffix', + }); + const toggle = new Toggle({ checked: false }); + const submitButton = new Button({ + text: 'Entrar', + isFullWidth: true, + isDisabled: true, + }); + + emailInput.mount($emailInputContainer); + passwordInput.mount($passwordInputContainer); + submitButton.mount($loginForm); + toggle.mount($rememberOption); + + emailInput.selected.get('input-text').type = 'email'; + emailInput.selected.get('input-text').id = 'email'; + passwordInput.selected.get('input-text').type = 'password'; + + const validateFields = () => { + const email = emailInput.selected.get('input-text').value; + const password = passwordInput.selected.get('input-text').value; + + if (email.trim() && password) { + submitButton.enable(); + } else { + submitButton.disable(); + } + }; + + emailInput.selected + .get('input-text') + .addEventListener('input', validateFields); + passwordInput.selected + .get('input-text') + .addEventListener('input', validateFields); + + submitButton.listen('click', () => { + const email = emailInput.selected.get('input-text').value; + const password = passwordInput.selected.get('input-text').value; + let validEmail = true; + let validPassword = true; + + if (!this.validateEmail(email)) { + validEmail = false; + $emailErrorMessage.classList.add('show-error'); + $emailErrorMessage.innerText = 'E-mail inválido'; + emailInput.inputError(); + } + + if (!this.validatePassword(password)) { + validPassword = false; + $passwordErrorMessage.classList.add('show-error'); + $passwordErrorMessage.innerText = + 'Senha inválida. Sua senha deve conter no mínimo 10 caracteres, incluindo pelo menos um caractere especial e uma letra maiúscula.'; + passwordInput.inputError(); + } + + if (validEmail) $emailErrorMessage.classList.remove('show-error'); + if (validPassword) $passwordErrorMessage.classList.remove('show-error'); + + if (validEmail && validPassword) { + this.login(); + } + }); +} + +LoginForm.prototype = Object.assign(LoginForm.prototype, Component.prototype, { + login() { + this.emit('login'); + }, + validateEmail(email) { + const emailRegex = /^[^\s@]+@[^\s@]+\.[^\s@]{2,}$/; + + return emailRegex.test(email); + }, + validatePassword(password) { + const hasMinLength = password.length >= 10; + const hasUppercase = /[A-Z]/g.test(password); + const hasNumber = /[0-9]/g.test(password); + const hasSpecialCharacter = /[!@#$%^&*(),.?":{}|<>]/g.test(password); + + return hasMinLength && hasUppercase && hasNumber && hasSpecialCharacter; + }, +}); diff --git a/src/components/LoginForm/index.scss b/src/components/LoginForm/index.scss new file mode 100644 index 00000000..6d54b5ae --- /dev/null +++ b/src/components/LoginForm/index.scss @@ -0,0 +1,189 @@ +@use '~styles/colors.scss' as colors; +@use '~styles/fonts.scss' as fonts; +@use '~styles/breakpoints.scss' as breakpoints; + +$pawIconSize: 128px; + +.login-form-container { + font-family: 'Poppins', sans-serif; + + padding: 6.2rem 4rem; + + background-color: rgb(255, 255, 255); + border-radius: 1.5rem; + + &__container-paw-icon { + width: 100%; + max-width: $pawIconSize; + height: $pawIconSize; + + display: flex; + + justify-content: center; + + margin: 0 auto; + margin-bottom: calc($pawIconSize / -2); + + position: relative; + z-index: 1; + + background-color: rgb(255, 255, 255); + border-radius: 100%; + + .paw-icon { + width: 100%; + + margin-bottom: 4.5rem; + padding: 1.6rem; + } + } + + &__title { + color: colors.$gray800; + text-align: center; + font-size: fonts.$xl2; + font-weight: 700; + line-height: 1.6; + + margin-bottom: 4rem; + } + + &__login-form { + display: flex; + flex-direction: column; + gap: 2rem; + + .input-text-container { + &__input { + background-size: auto 40%; + + &.input-error { + color: rgb(255, 255, 255); + } + } + } + + .button { + margin-top: 3rem; + } + + .error-message { + display: none; + + color: colors.$error100; + + &.show-error { + display: block; + } + } + } + + &__login-options { + display: flex; + gap: 0.6rem; + + align-items: center; + justify-content: space-between; + + .remember-option { + display: flex; + flex-direction: row-reverse; + gap: 1rem; + + align-items: center; + } + + .forgot-password { + color: colors.$blue600; + text-align: right; + text-decoration: none; + } + } + + &__signup { + display: block; + + text-align: center; + line-height: 2.4rem; + + margin: 3rem 0; + + .signup-link { + color: colors.$blue600; + font-weight: 600; + text-decoration: none; + } + } + + &__error-message { + display: block; + + color: colors.$error100; + + margin-top: 0.8rem; + + user-select: all; + } + + &__separator { + display: flex; + + .divisor { + width: 100%; + + border: unset; + border-top: 0.1rem solid colors.$gray200; + } + + .separator-text { + margin: 0 2.8rem; + } + } + + &__provider-container { + display: flex; + gap: 1.6rem; + + justify-content: space-between; + + margin-top: 3rem; + + .provider-button { + width: 100%; + + display: flex; + gap: 1.6rem; + + align-items: center; + justify-content: center; + + font-size: 1.6rem; + + padding: 1.6rem; + + border: unset; + + background-color: colors.$secondary100; + box-shadow: 0 2px 10px 0 rgba(0, 0, 0, 0.084); + border-radius: 1.4rem; + + transition: 0.3s ease-in-out; + + cursor: pointer; + + &:hover:not(:disabled) { + background-color: colors.$gray200; + } + + .icon { + max-width: 2.4rem; + } + } + } +} + +@include breakpoints.from667 { + .login-form-container { + padding: 6.2rem 8rem; + } +} diff --git a/src/stories/LoginForm.stories.js b/src/stories/LoginForm.stories.js new file mode 100644 index 00000000..a275e433 --- /dev/null +++ b/src/stories/LoginForm.stories.js @@ -0,0 +1,29 @@ +import LoginForm from '../components/LoginForm'; + +export default { + title: 'Components/LoginForm', + parameters: { + backgrounds: { + default: 'petdex', + values: [ + { + name: 'default', + value: '#F8F8F8', + }, + { + name: 'petdex', + value: '#003459', + }, + ], + }, + }, + render: () => { + const loginForm = new LoginForm(); + const $container = document.createElement('div'); + loginForm.mount($container); + + return $container; + }, +}; + +export const Default = {};