diff --git a/src/components/Button/index.js b/src/components/Button/index.js index bfe08a34..8697b06c 100644 --- a/src/components/Button/index.js +++ b/src/components/Button/index.js @@ -1,13 +1,14 @@ import { Component } from 'pet-dex-utilities'; import './index.scss'; -const events = ['click', 'text:change', 'enable', 'disable']; +const events = ['click', 'text:change', 'enable', 'disable', 'id:changed']; const html = ` `; export default function Button({ + id = '', text = '', isFullWidth = false, isDisabled = false, @@ -17,6 +18,7 @@ export default function Button({ this.setText(text); this.setIsFullWidth(isFullWidth); this.setIsDisabled(isDisabled); + this.setID(id); const $button = this.selected.get('button'); @@ -76,4 +78,13 @@ Button.prototype = Object.assign(Button.prototype, Component.prototype, { if (this.isDisabled()) return; this.emit('click'); }, + + getID() { + return this.selected.get('button').id; + }, + + setID(id = '') { + this.selected.get('button').id = id; + this.emit('id:changed', id); + }, }); diff --git a/src/components/Dropdown/index.js b/src/components/Dropdown/index.js index 75856a83..8987ae64 100644 --- a/src/components/Dropdown/index.js +++ b/src/components/Dropdown/index.js @@ -14,6 +14,8 @@ const events = [ 'unselect', 'value:change', 'value:clear', + 'id:changed', + 'error', ]; const html = ` @@ -33,12 +35,14 @@ const html = ` export default function Dropdown({ items = [], placeholder = 'Select an option', + id = '', } = {}) { Component.call(this, { html, events }); this.placeholder = placeholder; this.items = new Map(); this.selectedItem = null; + this.setID(id); this.onSelect = (item) => { const existsAndIsDifferent = @@ -191,4 +195,13 @@ Dropdown.prototype = Object.assign(Dropdown.prototype, Component.prototype, { placeholder: this.getPlaceholder(), }; }, + + getID() { + return this.selected.get('dropdown-toggle').id; + }, + + setID(id = '') { + this.selected.get('dropdown-toggle').id = id; + this.emit('id:changed', id); + }, }); diff --git a/src/components/Dropdown/index.scss b/src/components/Dropdown/index.scss index af3c4283..f5d1165b 100644 --- a/src/components/Dropdown/index.scss +++ b/src/components/Dropdown/index.scss @@ -2,9 +2,10 @@ @use '~styles/fonts.scss' as fonts; .dropdown { - width: fit-content; + width: 100%; font-family: fonts.$primaryFont; + color: colors.$gray800; font-size: 1.4rem; font-weight: fonts.$semiBold; @@ -41,7 +42,7 @@ list-style: none; - background-color: rgb(255, 255, 255); + background-color: colors.$white; box-shadow: 0 0 5px 0 rgba(0, 0, 0, 0.2); border-radius: 1.4rem; @@ -50,20 +51,19 @@ } &__toggle { - width: 100%; - display: flex; gap: 2rem; justify-content: space-between; - padding: 1rem; + padding: 1.8rem 1.6rem; + + border: 1px solid colors.$gray200; box-sizing: border-box; - background-color: rgb(255, 255, 255); + background-color: colors.$white; - box-shadow: 0 0 5px 0 rgb(216, 216, 216); border-radius: 1.4rem; .dropdown__icon { diff --git a/src/components/RegisterForm/components/Field/index.js b/src/components/RegisterForm/components/Field/index.js new file mode 100644 index 00000000..435eb84e --- /dev/null +++ b/src/components/RegisterForm/components/Field/index.js @@ -0,0 +1,69 @@ +import { Component } from 'pet-dex-utilities'; +import './index.scss'; + +const events = [ + 'label:changed', + 'error:message', + 'content:changed', + 'error:visible', + 'error:resolved', +]; + +const html = ` +
+ +
+ +
+`; + +export default function Field({ label = '', error = '', content } = {}) { + Component.call(this, { html, events }); + this.setLabel(label); + this.setError(error); + this.setContent(content); +} + +Field.prototype = Object.assign(Field.prototype, Component.prototype, { + getLabel() { + return this.selected.get('field-label').innerText; + }, + + setLabel(label = '') { + this.selected.get('field-label').innerText = label; + this.emit('label:changed', label); + }, + + getError() { + return this.selected.get('field-error').innerText; + }, + + setError(error = '') { + this.selected.get('field-error').innerText = error; + this.emit('error:message', error); + }, + + showError(error) { + this.selected.get('field-error').classList.add('field__error--show-error'); + this.emit('error:visible', error); + }, + + resolveError(error) { + this.selected + .get('field-error') + .classList.remove('field__error--show-error'); + this.emit('error:resolved', error); + }, + + getContent() { + return this.content; + }, + + setContent(content) { + if (content?.mount == null) return; + + this.content = content; + this.content.mount(this.selected.get('field-input')); + this.emit('content:changed', this.content); + }, +}); diff --git a/src/components/RegisterForm/components/Field/index.scss b/src/components/RegisterForm/components/Field/index.scss new file mode 100644 index 00000000..0ac91a5a --- /dev/null +++ b/src/components/RegisterForm/components/Field/index.scss @@ -0,0 +1,29 @@ +@use '~styles/colors.scss' as colors; +@use '~styles/fonts.scss' as fonts; + +.field { + &__label { + display: block; + + color: colors.$gray800; + + font-size: fonts.$xs; + font-weight: fonts.$medium; + + padding: 0 0 0.5rem 0.5rem; + } + + &__error { + display: none; + + color: colors.$error100; + + text-align: justify; + + margin: 0.8rem 0 0 0.5rem; + + &--show-error { + display: block; + } + } +} diff --git a/src/components/RegisterForm/images/facebook-icon.svg b/src/components/RegisterForm/images/facebook-icon.svg new file mode 100644 index 00000000..6681697f --- /dev/null +++ b/src/components/RegisterForm/images/facebook-icon.svg @@ -0,0 +1,2 @@ + + \ No newline at end of file diff --git a/src/components/RegisterForm/images/google-icon.svg b/src/components/RegisterForm/images/google-icon.svg new file mode 100644 index 00000000..cf464af1 --- /dev/null +++ b/src/components/RegisterForm/images/google-icon.svg @@ -0,0 +1,7 @@ + + + + + + + \ No newline at end of file diff --git a/src/components/RegisterForm/index.js b/src/components/RegisterForm/index.js new file mode 100644 index 00000000..3cad6787 --- /dev/null +++ b/src/components/RegisterForm/index.js @@ -0,0 +1,286 @@ +import { Component } from 'pet-dex-utilities'; +import Field from './components/Field/index'; +import TextInput from '../TextInput'; +import Dropdown from '../Dropdown'; +import Button from '../Button/index'; +import { + isNameValid, + isBirthValid, + isLocalValid, + isEmailValid, + isPhoneValid, + isPasswordValid, +} from '../../utils/validations'; +import googleIcon from './images/google-icon.svg'; +import facebookIcon from './images/facebook-icon.svg'; +import './index.scss'; + +const events = ['register']; + +const html = ` +
+

Crie sua petconta

+
+ + +
+
+
+ Ou +
+
+
+
+
+
+
+`; + +export default function RegisterForm() { + Component.call(this, { html, events }); + + const $formButton = this.selected.get('form-button'); + const $fields = this.selected.get('fields'); + + const name = new Field({ + label: 'Nome', + error: 'Informe seu nome', + content: new TextInput({ + id: 'name', + placeholder: 'Devhat', + }), + }); + + const surname = new Field({ + label: 'Sobrenome', + error: 'Informe seu sobrenome', + content: new TextInput({ + id: 'surname', + placeholder: 'Devhat', + }), + }); + + const birth = new Field({ + label: 'Data de nascimento', + error: 'Informe sua data de nascimento', + content: new TextInput({ + id: 'birth', + placeholder: '13/12/1995', + }), + }); + + const local = new Field({ + label: 'Cidade', + error: 'Informe sua cidade', + content: new Dropdown({ + items: [ + { + text: 'Fortaleza', + value: 'FOR', + }, + { + text: 'São Paulo', + value: 'SP', + }, + ], + id: 'local', + placeholder: 'São Paulo, SP', + }), + }); + + const email = new Field({ + label: 'E-mail', + error: 'Informe um E-mail válido', + content: new TextInput({ + id: 'email', + placeholder: 'dev@devhat.com.br', + type: 'email', + }), + }); + + const phone = new Field({ + label: 'Telefone', + error: 'Informe um número de telefone válido', + content: new TextInput({ + id: 'phone', + placeholder: '(11) 92875-3356', + }), + }); + + const password = new Field({ + label: 'Senha', + error: + 'Senha inválida. Sua senha deve conter no mínimo 10 caracteres, incluindo pelo menos um caractere especial e uma letra maiúscula.', + content: new TextInput({ + id: 'password', + placeholder: '*********', + type: 'password', + }), + }); + + const repeatPassword = new Field({ + label: 'Confirmar senha', + error: 'Senha inválida.', + content: new TextInput({ + id: 'repeat-password', + placeholder: '*********', + type: 'password', + }), + }); + + const registerButton = new Button({ + id: 'register-button', + text: 'Cadastrar', + isFullWidth: true, + isDisabled: false, + }); + + name.mount($fields); + surname.mount($fields); + birth.mount($fields); + local.mount($fields); + email.mount($fields); + phone.mount($fields); + password.mount($fields); + repeatPassword.mount($fields); + registerButton.mount($formButton); + + registerButton.listen('click', () => { + const nameValue = name.getContent().getValue(); + const surnameValue = surname.getContent().getValue(); + const birthValue = birth.getContent().getValue(); + const localValue = local.getContent().getValue(); + const emailValue = email.getContent().getValue(); + const phoneValue = phone.getContent().getValue(); + const passwordValue = password.getContent().getValue(); + const repeatPasswordValue = repeatPassword.getContent().getValue(); + + let nameValid = true; + let surnameValid = true; + let birthValid = true; + let localValid = true; + let emailValid = true; + let phoneValid = true; + let passwordValid = true; + let repeatPasswordValid = true; + + if (!isNameValid(nameValue)) { + nameValid = false; + + name.showError(); + name.getContent().inputError(); + } + + if (!isNameValid(surnameValue)) { + surnameValid = false; + + surname.showError(); + surname.getContent().inputError(); + } + + if (!isBirthValid(birthValue)) { + birthValid = false; + + birth.showError(); + birth.getContent().inputError(); + } + + if (!isLocalValid(localValue)) { + localValid = false; + + local.showError(); + } + + if (!isEmailValid(emailValue)) { + emailValid = false; + + email.showError(); + email.getContent().inputError(); + } + + if (!isPhoneValid(phoneValue)) { + phoneValid = false; + + phone.showError(); + phone.getContent().inputError(); + } + + if (!isPasswordValid(passwordValue)) { + passwordValid = false; + + password.showError(); + password.getContent().inputError(); + } + + if ( + !isPasswordValid(repeatPasswordValue) || + repeatPasswordValue !== passwordValue + ) { + repeatPasswordValid = false; + + repeatPassword.showError(); + repeatPassword.getContent().inputError(); + } + + if (nameValid) { + name.resolveError(); + } + + if (surnameValid) { + surname.resolveError(); + } + + if (birthValid) { + birth.resolveError(); + } + + if (localValid) { + local.resolveError(); + } + + if (emailValid) { + email.resolveError(); + } + + if (phoneValid) { + phone.resolveError(); + } + + if (passwordValid) { + password.resolveError(); + } + + if (repeatPasswordValid) { + repeatPassword.resolveError(); + } + + if ( + nameValid && + surnameValid && + birthValid && + localValid && + emailValid && + phoneValid && + passwordValid && + repeatPasswordValid + ) { + this.register(); + } + }); +} + +RegisterForm.prototype = Object.assign( + RegisterForm.prototype, + Component.prototype, + { + register() { + this.emit('register'); + }, + }, +); diff --git a/src/components/RegisterForm/index.scss b/src/components/RegisterForm/index.scss new file mode 100644 index 00000000..9278920c --- /dev/null +++ b/src/components/RegisterForm/index.scss @@ -0,0 +1,143 @@ +@use '~styles/colors.scss' as colors; +@use '~styles/fonts.scss' as fonts; +@use '~styles/breakpoints.scss' as breakpoints; + +.register-form { + display: flex; + flex-direction: column; + + align-items: start; + + justify-content: center; + + font-family: fonts.$primaryFont; + + margin: 0 auto; + + &__title { + color: colors.$gray800; + font-size: fonts.$lg; + font-weight: fonts.$bold; + + margin-bottom: 3.2rem; + } + + &__socials, + &__divisor { + max-width: 45rem; + } + + &__socials { + width: 100%; + + display: flex; + flex-direction: row; + gap: 2rem; + } + + &__social { + max-width: 21.7rem; + + display: flex; + flex-direction: row; + + flex-grow: 1; + + gap: 1rem; + + align-items: center; + + justify-content: center; + + font-family: fonts.$fifthFont; + font-size: fonts.$sm; + font-weight: fonts.$medium; + + margin-bottom: 3.2rem; + + padding: 1.8rem 0; + + border: 0; + + background-color: colors.$secondary100; + + box-shadow: 0 4px 10px 0 rgba(0, 0, 0, 8%); + + border-radius: 10px; + + cursor: pointer; + + &:hover { + transform: scale(1.02); + + transition: all 0.4s ease-in-out; + } + } + + &__social-img { + width: 2.4rem; + height: 2.4rem; + } + + &__divisor { + width: 100%; + + display: flex; + flex-direction: row; + + align-items: center; + + justify-content: center; + + margin-bottom: 3rem; + } + + &__divisor-line { + width: 100%; + height: 1px; + + border: 0; + + background-color: colors.$gray200; + } + + &__divisor-text { + font-size: fonts.$sm; + + margin: 0 2.9rem; + } + + &__form { + width: 100%; + } + + &__form-fields { + display: grid; + grid-template-columns: 1fr; + gap: 3rem; + + margin-bottom: 3rem; + } + + &__form-button { + max-width: 45rem; + } +} + +@include breakpoints.from667 { + .register-form { + max-width: 72.5rem; + + &__title { + font-size: fonts.$xl2; + } + + &__form-fields { + display: grid; + grid-template-columns: repeat(2, 1fr); + gap: 3rem; + + margin-bottom: 6rem; + } + } +} diff --git a/src/components/TextInput/index.js b/src/components/TextInput/index.js index 8bc6057c..1af3798f 100644 --- a/src/components/TextInput/index.js +++ b/src/components/TextInput/index.js @@ -11,6 +11,7 @@ const events = [ 'enabled', 'value:change', 'type:change', + 'id:changed', ]; const html = ` @@ -23,6 +24,7 @@ const html = ` `; export default function TextInput({ + id = '', placeholder = '', assetUrl, assetPosition, @@ -42,6 +44,7 @@ export default function TextInput({ input.classList.add(assetPosition); this.setValue(value); this.setType(type); + this.setID(id); if (type === 'password') { iconBtn.classList.remove('input-text-container__button--hidden'); @@ -110,4 +113,13 @@ TextInput.prototype = Object.assign(TextInput.prototype, Component.prototype, { this.selected.get('input-text').type = type; this.emit('type:change', type); }, + + getID() { + return this.selected.get('input-text').id; + }, + + setID(id = '') { + this.selected.get('input-text').id = id; + this.emit('id:changed', id); + }, }); diff --git a/src/components/TextInput/index.scss b/src/components/TextInput/index.scss index 8c2c034d..e3fad0ba 100644 --- a/src/components/TextInput/index.scss +++ b/src/components/TextInput/index.scss @@ -6,6 +6,8 @@ border: 1px solid colors.$gray200; + background-color: colors.$white; + border-radius: 14px; transition: border-color 0.3s ease-in-out; @@ -17,7 +19,6 @@ &:has(> &__input.standard.input-error) { border-color: colors.$error100; - background-color: colors.$error100; outline-color: colors.$error100; } @@ -50,6 +51,7 @@ box-sizing: border-box; + background-color: transparent; background-repeat: no-repeat; background-size: auto 60%; filter: opacity(0.85); @@ -83,8 +85,7 @@ } &.input-error { - background-color: colors.$error100; - filter: opacity(0.75); + color: colors.$secondary100; } } diff --git a/src/layouts/index.html b/src/layouts/index.html index f7dd1311..9c2cfa1d 100644 --- a/src/layouts/index.html +++ b/src/layouts/index.html @@ -7,7 +7,7 @@ Pet Hat diff --git a/src/stories/RegisterForm.stories.js b/src/stories/RegisterForm.stories.js new file mode 100644 index 00000000..a7e3f03d --- /dev/null +++ b/src/stories/RegisterForm.stories.js @@ -0,0 +1,15 @@ +import RegisterForm from '../components/RegisterForm'; + +export default { + title: 'Components/RegisterForm', + render: () => { + const registerForm = new RegisterForm(); + const $container = document.createElement('div'); + registerForm.mount($container); + + return $container; + }, + argTypes: {}, +}; + +export const Default = {}; diff --git a/src/styles/colors.scss b/src/styles/colors.scss index 0dbdbfc8..a2bddb97 100644 --- a/src/styles/colors.scss +++ b/src/styles/colors.scss @@ -21,6 +21,10 @@ $success200: rgb(49, 138, 94); // error $error100: rgb(179, 38, 30); +// White + +$white: rgb(255, 255, 255); + // neutrals (Gray) $gray100: rgb(236, 239, 242); $gray150: rgb(236, 239, 242); diff --git a/src/utils/validations.js b/src/utils/validations.js new file mode 100644 index 00000000..36a529b1 --- /dev/null +++ b/src/utils/validations.js @@ -0,0 +1,42 @@ +export function isNameValid(name) { + const nameRegex = /[a-zA-Z]$/; + + return nameRegex.test(name); +} + +export function isBirthValid(birth) { + const birthRegex = /(\d{2})\/?(\d{2})\/?(\d{4})$/; + + return birthRegex.test(birth); +} + +export function isEmailValid(email) { + const emailRegex = /[a-zA-Z0-9._-]+@[a-zA-Z0-9._-]+\.[a-zA-z]{2,}/; + + return emailRegex.test(email); +} + +export function isPhoneValid(phone) { + const phoneRegex = /(\d{2})\d{5}\d{4}/; + + return phoneRegex.test(phone); +} + +export function isPasswordValid(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; +} + +export function isLocalValid(local) { + let isFilled = true; + + if (local === '' || local === undefined) { + isFilled = false; + } + + return isFilled; +}