feat(form): created initial mask for form input and validations

This commit is contained in:
Henrique Santos Santana 2023-01-09 00:43:54 -03:00
parent 8710e7d333
commit 4a6664726b
13 changed files with 413 additions and 285 deletions

View File

@ -0,0 +1,53 @@
import { ErrorMessage } from 'formik'
import { InputHTMLAttributes } from 'react'
import { CustomInput, CustomMaskInput } from './__Input'
interface FormGroupProps extends InputHTMLAttributes<HTMLInputElement> {
error: any
name: string | any
masked: boolean
mask?: any
label: string | any
}
export function FormGroup({
error,
masked,
mask,
label,
name,
...props
}: FormGroupProps) {
return (
<div className="form__group">
<label className="form__label" htmlFor={name}>
{label}
</label>
<div className="form__content">
<ErrorMessage
className="error-message"
component={'span'}
name={name}
/>
{masked ? (
<CustomMaskInput
mask={mask}
id={name}
name={name}
className={`form__input ${error}`}
{...props}
/>
) : (
<CustomInput
name={name}
className={`form__input ${error}`}
{...props}
/>
)}
</div>
</div>
)
}

View File

@ -0,0 +1,26 @@
import { Field } from 'formik'
import { InputHTMLAttributes } from 'react'
import MaskedInput, { MaskedInputProps } from 'react-text-mask'
interface CustomInputProps extends InputHTMLAttributes<HTMLInputElement> {}
export function CustomInput({ name, ...props }: CustomInputProps) {
return <Field id={name} name={name} {...props} />
}
interface CustomMaskInputProps
extends InputHTMLAttributes<HTMLInputElement>,
Omit<MaskedInputProps, 'mask'> {
mask?: any
name: string
}
export function CustomMaskInput({ name, ...props }: CustomMaskInputProps) {
return (
<Field id={name} name={name}>
{({ field }: any) => {
return <MaskedInput {...field} {...props} id={name} name={name} />
}}
</Field>
)
}

View File

@ -1,53 +0,0 @@
import { ErrorMessage, Field } from 'formik'
import { InputHTMLAttributes } from 'react'
import MaskedInput from 'react-text-mask'
interface InputProps extends InputHTMLAttributes<HTMLInputElement> {
name: string
label: string
error?: any
setValues: any
}
export function Input({ label, name, error, setValues, ...props }: InputProps) {
return (
<div className="form__group">
<label className="form__label" htmlFor={name}>
{label}
</label>
<div className="form__content">
<ErrorMessage
className="error-message"
component={'span'}
name={name}
/>
<Field
id={name}
name={name}
className={`form__input ${error}`}
render={({ field }: any) => {
return (
<MaskedInput
{...field}
mask="+7 (999) 999-99-99"
onChange={(e) => {
const value = e.target.value || ''
const changedValue = value
.replace(/\)/g, '')
.replace(/\(/g, '')
.replace(/-/g, '')
.replace(/ /g, '')
console.log({ value })
console.log({ changedValue })
setValues('phone', value)
}}
/>
)
}}
{...props}
/>
</div>
</div>
)
}

View File

@ -144,4 +144,21 @@
height: 71px;
}
}
.form__success {
opacity: 0;
transition: 200ms;
color: var(--clr-common-green);
font-size: var(--txt-xs);
line-height: 14.06px;
margin-top: 12px;
@media screen and (min-width: 2500px) {
line-height: 28.13px;
}
}
.form__success.form__success-active {
opacity: 1;
}
}

View File

@ -1,12 +1,16 @@
import { Field, Form, Formik } from 'formik'
import { useMemo } from 'react'
import { Input } from './fragments/Input'
import { useEffect, useMemo, useState } from 'react'
/* import { Input } from './fragments/Input' */
import validadeShema from './schema/FormSchema'
import styles from './index.module.scss'
import { FormGroup } from './containers/_FormGroup'
export function Contact() {
const [isSubmiting, setIsSubmiting] = useState(false)
const initialValues = useMemo(() => {
return {
fullname: '',
@ -19,6 +23,14 @@ export function Contact() {
}
}, [])
useEffect(() => {
if (isSubmiting) {
setTimeout(() => {
setIsSubmiting(false)
}, 2000)
}
}, [isSubmiting])
return (
<section className={styles['contact']}>
<div>
@ -29,6 +41,8 @@ export function Contact() {
e.resetForm({
isSubmitting: true,
})
setIsSubmiting(true)
}}
initialValues={initialValues}
validationSchema={validadeShema}
@ -37,50 +51,103 @@ export function Contact() {
return (
<Form className={`${styles['contact__form']} ${styles['form']}`}>
<fieldset className={styles['form__container']}>
<Input
<FormGroup
label="Nome"
name="fullname"
type="text"
placeholder="Seu nome completo"
error={errors && touched && 'touched'}
masked={false}
/>
<Input
<FormGroup
label="Email"
type="email"
name="email"
placeholder="Seu e-mail"
error={errors && touched && 'touched'}
masked={false}
/>
<Input
<FormGroup
mask={[
/[0-9]/,
/\d/,
/\d/,
'.',
/[0-9]/,
/\d/,
/\d/,
'.',
/[0-9]/,
/\d/,
/\d/,
'-',
/[0-9]/,
/\d/,
]}
label="CPF"
type="text"
name="cpf"
placeholder="000.000.000-00"
error={errors && touched && 'touched'}
masked={true}
/>
<Input
<FormGroup
mask={[
/[0-3]/,
/[0-9]/,
'.',
/[0-1]/,
/[0-9]/,
'.',
/[0-9]/,
/[0-9]/,
/[0-9]/,
/[0-9]/,
]}
label="Data de Nascimento:"
type="text"
name="date"
placeholder="00.00.0000"
error={errors && touched && 'touched'}
masked={true}
/>
<Input
<FormGroup
mask={[
'(',
/[0-9]/,
/\d/,
')',
' ',
/[0-9]/,
/\d/,
/\d/,
/\d/,
/\d/,
'-',
/[0-9]/,
/\d/,
/\d/,
/\d/,
]}
label="Telefone:"
type="tel"
name="tel"
placeholder="(00) 00000-0000"
error={errors && touched && 'touched'}
masked={true}
/>
<Input
<FormGroup
label="Instagram"
type="text"
name="socials_instagram"
placeholder="@seuuser"
error={errors && touched && 'touched'}
masked={false}
/>
<div className="form-check">
@ -88,12 +155,20 @@ export function Contact() {
<span>*</span>
Declaro que li e aceito
</label>
<Field id="terms" name="terms" type="checkbox" />
<Field id="terms" name="terms" type="checkbox" required />
</div>
<button className={styles['submit']} type="submit">
Cadastre-se
</button>
<p
className={`form__success ${
isSubmiting ? 'form__success-active' : ''
}`}
>
*Formulário enviado com sucesso!
</p>
</fieldset>
</Form>
)

View File

@ -6,8 +6,7 @@ const messages = {
const emailRegexPattern = /^\w+@[a-zA-Z_]+?\.[a-zA-Z]{2,3}$/
const cpfRegexPattern = /^([0-9]){3}\.([0-9]){3}\.([0-9]){3}-([0-9]){2}$/
const telRegexPattern =
/\(?\+[0-9]{1,3}\)? ?-?[0-9]{1,3} ?-?[0-9]{3,5} ?-?[0-9]{4}( ?-?[0-9]{3})? ?(\w{1,10}\s?\d{1,6})?/
const telRegexPattern = /\(([0-9]{1,3})\) ([0-9]{5})-([0-9]{4})/
const dateRegexPattern =
/^(?:(?:31(\/|-|\.)(?:0?[13578]|1[02]))\1|(?:(?:29|30)(\/|-|\.)(?:0?[13-9]|1[0-2])\2))(?:(?:1[6-9]|[2-9]\d)?\d{2})$|^(?:29(\/|-|\.)0?2\3(?:(?:(?:1[6-9]|[2-9]\d)?(?:0[48]|[2468][048]|[13579][26])|(?:(?:16|[2468][048]|[3579][26])00))))$|^(?:0?[1-9]|1\d|2[0-8])(\/|-|\.)(?:(?:0?[1-9])|(?:1[0-2]))\4(?:(?:1[6-9]|[2-9]\d)?\d{2})$/

View File

@ -0,0 +1,13 @@
import { useMemo } from 'react'
import { Breadcrumb } from '../../components/Molecules/Breadcrumb'
import styles from '../index.module.scss'
export function RouterBreadcrumb() {
let list = useMemo(() => [{ name: 'Introduction', href: '/' }], [])
return (
<div className={styles['breadcrumb-container']}>
<Breadcrumb className={styles['breadcrumb']} list={list} />
</div>
)
}

View File

@ -0,0 +1,37 @@
import { useEffect, useState } from 'react'
import { ItemList } from '../../components/Molecules/ItemList'
import whatsappImg from '../../assets/brands/svgs/whatsapp.svg'
import scrollTopImg from '../../assets/icons/scroll-top.svg'
import styles from '../index.module.scss'
export function ScrollFixed() {
const [isScrollTop, setIsScrollTop] = useState<boolean>(false)
useEffect(() => {
window.addEventListener('scroll', () => {
if (window.scrollY > 100) {
setIsScrollTop(true)
} else {
setIsScrollTop(false)
}
})
}, [])
return (
<ul className={styles['scroll__fixed']}>
<ItemList
className={styles['scroll__fixed-whatsapp']}
href="https://wa.me/254777123456"
>
<img src={whatsappImg} alt="" />
</ItemList>
{isScrollTop && (
<ItemList className={styles['scroll__fixed-top']} href={'#root'}>
<img src={scrollTopImg} alt="" />
</ItemList>
)}
</ul>
)
}

View File

@ -1,56 +1,13 @@
import { useEffect, useMemo, useState } from 'react'
import { Outlet, Route, Routes } from 'react-router-dom'
import { Header } from '../template/Header'
import { Breadcrumb } from '../components/Molecules/Breadcrumb'
import { Sidebar } from '../template/Sidebar'
import { About } from '../pages/Institutional/About'
import { Contact } from '../pages/Institutional/Contact'
import { Newsletter } from '../template/Newsletter'
import { Footer } from '../template/Footer'
import whatsappImg from '../assets/brands/svgs/whatsapp.svg'
import scrollTopImg from '../assets/icons/scroll-top.svg'
import styles from './index.module.scss'
import { ItemList } from '../components/Molecules/ItemList'
export function RouterBreadcrumb() {
let list = useMemo(() => [{ name: 'Introduction', href: '/' }], [])
return (
<div className={styles['breadcrumb-container']}>
<Breadcrumb className={styles['breadcrumb']} list={list} />
</div>
)
}
export function ScrollFixed() {
const [isScrollTop, setIsScrollTop] = useState<boolean>(false)
useEffect(() => {
window.addEventListener('scroll', () => {
if (window.scrollY > 100) {
setIsScrollTop(true)
} else {
setIsScrollTop(false)
}
})
}, [])
return (
<ul className={styles['scroll__fixed']}>
<ItemList className={styles['scroll__fixed-whatsapp']} href="https://wa.me/254777123456">
<img src={whatsappImg} alt="" />
</ItemList>
{isScrollTop && (
<ItemList className={styles['scroll__fixed-top']} href={'#root'}>
<img src={scrollTopImg} alt="" />
</ItemList>
)}
</ul>
)
}
import { RouterBreadcrumb } from './containers/_BreadCrumb'
import { ScrollFixed } from './containers/_ScrollFixed'
export function Router() {
return (
@ -64,7 +21,6 @@ export function Router() {
<div className="window-routes">
<Outlet />
</div>
<Newsletter />
<Footer />
<ScrollFixed />
</>

View File

@ -1,7 +1,7 @@
import { Field, Form, Formik } from 'formik'
import * as Yup from 'yup'
import styles from './index.module.scss'
import styles from '../index.module.scss'
const schema = Yup.object({
email: Yup.string().required('Campo Obrigratorio').email('Email ínvalido'),

View File

@ -1,8 +1,6 @@
@use '../../styles/utils/helpers/functions' as function;
.footer {
border-top: 1px solid var(--clr-common-black);
&__container--top,
&__container--bottom {
width: 100%;
@ -14,6 +12,160 @@
}
}
.footer__content {
border-top: 1px solid var(--clr-common-black);
}
.newsletter {
width: 100%;
border-top: 1px solid var(--clr-common-black);
&__container {
width: 100%;
padding: 16px;
}
}
.newsletter {
h3 {
margin-bottom: 16px;
font-weight: 500;
font-size: var(--txt-normal);
line-height: 16.41px;
text-transform: uppercase;
letter-spacing: 0.05em;
color: var(--clr-gray-800);
@media screen and (min-width: 1025px) {
font-size: var(--txt-large);
line-height: 21.09px;
}
@media screen and (min-width: 2500px) {
line-height: 42.19px;
}
}
}
.newsletter__container {
@media screen and (min-width: 1025px) {
width: function.fluid(474px, 1280px);
padding: 16px 0;
margin: 0 auto;
}
@media screen and (min-width: 2500px) {
width: function.fluid(922px, 2500px);
}
}
.newsletter__container {
fieldset :global {
border: none;
.form-group {
width: 100%;
display: flex;
align-items: center;
flex-direction: column;
gap: 8px;
}
}
}
.newsletter__container :global {
.form-input {
width: 100%;
margin-bottom: 16px;
@media screen and (min-width: 1025px) {
width: function.fluid(340px, 474px);
}
@media screen and (min-width: 2500px) {
width: function.fluid(668px, 922px);
}
}
}
.newsletter__container :global {
input {
display: block;
width: 100%;
height: 50px;
padding: 0 16px;
border: 1px solid var(--clr-gray-400);
font-size: var(--txt-normal);
line-height: 16.41px;
&::placeholder {
color: var(--clr-gray-400);
}
@media screen and (min-width: 1025px) {
height: 42px;
border-radius: 4px;
}
@media screen and (min-width: 2500px) {
line-height: 32.81px;
height: 59px;
}
}
}
.newsletter__form {
button[type='submit'] {
width: 100%;
height: 50px;
display: flex;
align-items: center;
justify-content: center;
background-color: var(--clr-common-black);
color: var(--clr-common-white);
text-transform: uppercase;
font-size: var(--txt-normal);
line-height: 16.41px;
font-weight: 700;
letter-spacing: 0.05em;
@media screen and (min-width: 1025px) {
width: function.fluid(126px, 474px);
font-size: var(--txt-xs);
line-height: 14.06px;
height: 42px;
border-radius: 4px;
}
@media screen and (min-width: 2500px) {
width: function.fluid(246px, 922px);
line-height: 28.13px;
height: 59px;
}
}
}
.newsletter {
@media screen and (min-width: 1025px) {
&__container {
fieldset :global {
.form-group {
flex-direction: row;
}
.form-group {
.form-input {
margin-bottom: 0;
}
}
}
}
}
}
.footer__container--top {
padding-top: 17px;
padding-bottom: 24px;

View File

@ -7,6 +7,7 @@ import { List } from './containers/_List'
import { PaymentsList } from './containers/_Payment'
import css from './index.module.scss'
import { Newsletter } from './containers/_Newsletter'
export function Footer() {
const [accordionCurrentIsOpen, setAccordionCurrentIsOpen] = useState<
@ -25,27 +26,30 @@ export function Footer() {
return (
<footer className={css['footer']}>
<div className={css['footer__container--top']}>
<List
accordionCurrentIsOpen={accordionCurrentIsOpen}
handleSetCurrentAccordion={handleSetCurrentAccordion}
/>
</div>
<Newsletter />
<div className={css['footer__content']}>
<div className={css['footer__container--top']}>
<List
accordionCurrentIsOpen={accordionCurrentIsOpen}
handleSetCurrentAccordion={handleSetCurrentAccordion}
/>
</div>
<div className={css['footer__container--bottom']}>
<div className={css['footer__actions--bottom']}>
<p className={css['footer__phrase']}></p>
<PaymentsList />
<ul className={css['footer__credits']}>
<li>
<p>Powered by</p>
<img src={vtexIcon} alt="" />
</li>
<li>
<p>Developed by</p>
<img src={m3Icon} alt="" />
</li>
</ul>
<div className={css['footer__container--bottom']}>
<div className={css['footer__actions--bottom']}>
<p className={css['footer__phrase']}></p>
<PaymentsList />
<ul className={css['footer__credits']}>
<li>
<p>Powered by</p>
<img src={vtexIcon} alt="" />
</li>
<li>
<p>Developed by</p>
<img src={m3Icon} alt="" />
</li>
</ul>
</div>
</div>
</div>
</footer>

View File

@ -1,151 +0,0 @@
@use '../../styles/utils/helpers/functions' as function;
.newsletter {
width: 100%;
border-top: 1px solid var(--clr-common-black);
&__container {
width: 100%;
padding: 16px;
}
}
.newsletter {
h3 {
margin-bottom: 16px;
font-weight: 500;
font-size: var(--txt-normal);
line-height: 16.41px;
text-transform: uppercase;
letter-spacing: 0.05em;
color: var(--clr-gray-800);
@media screen and (min-width: 1025px) {
font-size: var(--txt-large);
line-height: 21.09px;
}
@media screen and (min-width: 2500px) {
line-height: 42.19px;
}
}
}
.newsletter__container {
@media screen and (min-width: 1025px) {
width: function.fluid(474px, 1280px);
padding: 16px 0;
margin: 0 auto;
}
@media screen and (min-width: 2500px) {
width: function.fluid(922px, 2500px);
}
}
.newsletter__container {
fieldset :global {
border: none;
.form-group {
width: 100%;
display: flex;
align-items: center;
flex-direction: column;
gap: 8px;
}
}
}
.newsletter__container :global {
.form-input {
width: 100%;
margin-bottom: 16px;
@media screen and (min-width: 1025px) {
width: function.fluid(340px, 474px);
}
@media screen and (min-width: 2500px) {
width: function.fluid(668px, 922px);
}
}
}
.newsletter__container :global {
input {
display: block;
width: 100%;
height: 50px;
padding: 0 16px;
border: 1px solid var(--clr-gray-400);
font-size: var(--txt-normal);
line-height: 16.41px;
&::placeholder {
color: var(--clr-gray-400);
}
@media screen and (min-width: 1025px) {
height: 42px;
border-radius: 4px;
}
@media screen and (min-width: 2500px) {
line-height: 32.81px;
height: 59px;
}
}
}
.newsletter__form {
button[type='submit'] {
width: 100%;
height: 50px;
display: flex;
align-items: center;
justify-content: center;
background-color: var(--clr-common-black);
color: var(--clr-common-white);
text-transform: uppercase;
font-size: var(--txt-normal);
line-height: 16.41px;
font-weight: 700;
letter-spacing: 0.05em;
@media screen and (min-width: 1025px) {
width: function.fluid(126px, 474px);
font-size: var(--txt-xs);
line-height: 14.06px;
height: 42px;
border-radius: 4px;
}
@media screen and (min-width: 2500px) {
width: function.fluid(246px, 922px);
line-height: 28.13px;
height: 59px;
}
}
}
.newsletter {
@media screen and (min-width: 1025px) {
&__container {
fieldset :global {
.form-group {
flex-direction: row;
}
.form-group {
.form-input {
margin-bottom: 0;
}
}
}
}
}
}