mirror of
https://github.com/vercel/commerce.git
synced 2025-07-22 20:26:49 +00:00
✨ feat: register with validation
:%s
This commit is contained in:
@@ -29,6 +29,7 @@
|
||||
"email-validator": "^2.0.4",
|
||||
"eslint": "^7.32.0",
|
||||
"eslint-config-next": "^11.1.2",
|
||||
"formik": "^2.2.9",
|
||||
"immutability-helper": "^3.1.1",
|
||||
"js-cookie": "^2.2.1",
|
||||
"lodash.debounce": "^4.0.8",
|
||||
@@ -51,7 +52,8 @@
|
||||
"swr": "^0.5.6",
|
||||
"tabbable": "^5.2.0",
|
||||
"tailwindcss": "^2.2.2",
|
||||
"uuidv4": "^6.2.10"
|
||||
"uuidv4": "^6.2.10",
|
||||
"yup": "^0.32.9"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@graphql-codegen/cli": "^1.21.5",
|
||||
|
@@ -3,38 +3,51 @@ import React, { memo } from 'react'
|
||||
import s from './ButtonCommon.module.scss'
|
||||
|
||||
interface Props {
|
||||
children?: React.ReactNode,
|
||||
type?: 'primary' | 'light' | 'ghost' | 'lightBorderNone',
|
||||
size?: 'default' | 'large' | 'small',
|
||||
icon?: React.ReactNode,
|
||||
isIconSuffix?: boolean,
|
||||
loading?: boolean,
|
||||
disabled?: boolean,
|
||||
onClick?: () => void,
|
||||
children?: React.ReactNode
|
||||
type?: 'primary' | 'light' | 'ghost' | 'lightBorderNone'
|
||||
HTMLType?: "submit" | "button" | "reset"
|
||||
size?: 'default' | 'large' | 'small'
|
||||
icon?: React.ReactNode
|
||||
isIconSuffix?: boolean
|
||||
loading?: boolean
|
||||
disabled?: boolean
|
||||
onClick?: () => void
|
||||
}
|
||||
|
||||
const ButtonCommon = memo(({ type = 'primary', size = 'default', loading = false, isIconSuffix = false,
|
||||
icon, disabled, children, onClick }: Props) => {
|
||||
const ButtonCommon = memo(
|
||||
({
|
||||
type = 'primary',
|
||||
HTMLType,
|
||||
size = 'default',
|
||||
loading = false,
|
||||
isIconSuffix = false,
|
||||
icon,
|
||||
disabled,
|
||||
children,
|
||||
onClick,
|
||||
}: Props) => {
|
||||
return (
|
||||
<button className={classNames({
|
||||
[s.buttonCommon]: true,
|
||||
[s[type]]: !!type,
|
||||
[s[size]]: !!size,
|
||||
[s.loading]: loading,
|
||||
[s.preserve]: isIconSuffix,
|
||||
[s.onlyIcon]: icon && !children,
|
||||
<button
|
||||
className={classNames({
|
||||
[s.buttonCommon]: true,
|
||||
[s[type]]: !!type,
|
||||
[s[size]]: !!size,
|
||||
[s.loading]: loading,
|
||||
[s.preserve]: isIconSuffix,
|
||||
[s.onlyIcon]: icon && !children,
|
||||
})}
|
||||
disabled={disabled || loading}
|
||||
onClick={onClick}
|
||||
>
|
||||
<div className={s.inner}>
|
||||
{
|
||||
icon && <span className={s.icon}>{icon}</span>
|
||||
}
|
||||
<span className={s.label}>{children}</span>
|
||||
</div>
|
||||
</button>
|
||||
disabled={disabled || loading}
|
||||
onClick={onClick}
|
||||
type={HTMLType}
|
||||
>
|
||||
<div className={s.inner}>
|
||||
{icon && <span className={s.icon}>{icon}</span>}
|
||||
<span className={s.label}>{children}</span>
|
||||
</div>
|
||||
</button>
|
||||
)
|
||||
})
|
||||
}
|
||||
)
|
||||
|
||||
ButtonCommon.displayName = 'ButtonCommon'
|
||||
export default ButtonCommon
|
||||
|
@@ -1,100 +1,5 @@
|
||||
@import "../../../styles/utilities";
|
||||
@import "../../../styles/form";
|
||||
|
||||
.inputWrap {
|
||||
.inputInner {
|
||||
@apply flex items-center relative;
|
||||
.icon {
|
||||
@apply absolute flex justify-center items-center;
|
||||
content: "";
|
||||
left: 1.6rem;
|
||||
margin-right: 1.6rem;
|
||||
svg path {
|
||||
fill: currentColor;
|
||||
}
|
||||
}
|
||||
.icon + .inputCommon {
|
||||
padding-left: 4.8rem;
|
||||
}
|
||||
|
||||
.inputCommon {
|
||||
@apply block w-full transition-all duration-200 bg-white;
|
||||
border-radius: .8rem;
|
||||
padding: 1.6rem;
|
||||
border: 1px solid var(--border-line);
|
||||
&:hover,
|
||||
&:focus,
|
||||
&:active {
|
||||
outline: none;
|
||||
border: 1px solid var(--primary);
|
||||
@apply shadow-md;
|
||||
}
|
||||
|
||||
&::placeholder {
|
||||
@apply text-label;
|
||||
}
|
||||
}
|
||||
|
||||
&.preserve {
|
||||
@apply flex-row-reverse;
|
||||
.icon {
|
||||
left: unset;
|
||||
right: 1.6rem;
|
||||
margin-left: 1.6rem;
|
||||
margin-right: 0;
|
||||
svg path {
|
||||
fill: var(--text-label);
|
||||
}
|
||||
}
|
||||
.icon + .inputCommon {
|
||||
padding-left: 1.6rem;
|
||||
padding-right: 4.8rem;
|
||||
}
|
||||
}
|
||||
&.success {
|
||||
.icon {
|
||||
svg path {
|
||||
fill: var(--primary);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
&.error {
|
||||
.icon {
|
||||
svg path {
|
||||
fill: var(--negative);
|
||||
}
|
||||
}
|
||||
input {
|
||||
border-color: var(--negative) !important;
|
||||
}
|
||||
}
|
||||
}
|
||||
.errorMessage {
|
||||
@apply caption;
|
||||
color: var(--negative);
|
||||
margin-top: 0.4rem;
|
||||
}
|
||||
|
||||
&.custom {
|
||||
@apply shape-common;
|
||||
.inputCommon {
|
||||
border: none;
|
||||
background: var(--background-gray);
|
||||
&:hover,
|
||||
&:focus,
|
||||
&:active {
|
||||
@apply shadow-md;
|
||||
border: none;
|
||||
}
|
||||
}
|
||||
}
|
||||
&.bgTransparent {
|
||||
.inputCommon {
|
||||
background: rgb(227, 242, 233, 0.3);
|
||||
color: var(--white);
|
||||
&::placeholder {
|
||||
color: var(--white);
|
||||
}
|
||||
}
|
||||
}
|
||||
@extend .formInputWrap;
|
||||
}
|
||||
|
@@ -1,95 +1,127 @@
|
||||
import classNames from 'classnames';
|
||||
import React, { forwardRef, useImperativeHandle, useMemo, useRef } from 'react';
|
||||
import { IconCheck, IconError } from 'src/components/icons';
|
||||
import { KEY } from 'src/utils/constanst.utils';
|
||||
import s from './InputCommon.module.scss';
|
||||
import classNames from 'classnames'
|
||||
import React, { forwardRef, useImperativeHandle, useMemo, useRef } from 'react'
|
||||
import { IconCheck, IconError } from 'src/components/icons'
|
||||
import { KEY } from 'src/utils/constanst.utils'
|
||||
import s from './InputCommon.module.scss'
|
||||
|
||||
type Ref = {
|
||||
focus: () => void
|
||||
getValue: () => string | number
|
||||
} | null;
|
||||
focus: () => void
|
||||
getValue: () => string | number
|
||||
} | null
|
||||
interface Props {
|
||||
children?: React.ReactNode,
|
||||
value?: string | number,
|
||||
placeholder?: string,
|
||||
type?: 'text' | 'number' | 'email' | 'password',
|
||||
styleType?: 'default' | 'custom',
|
||||
backgroundTransparent?: boolean,
|
||||
icon?: React.ReactNode,
|
||||
isIconSuffix?: boolean,
|
||||
isShowIconSuccess?: boolean,
|
||||
error?: string,
|
||||
onChange?: (value: string | number) => void,
|
||||
onEnter?: (value: string | number) => void,
|
||||
children?: React.ReactNode
|
||||
value?: string | number
|
||||
placeholder?: string
|
||||
type?: 'text' | 'number' | 'email' | 'password'
|
||||
styleType?: 'default' | 'custom'
|
||||
backgroundTransparent?: boolean
|
||||
icon?: React.ReactNode
|
||||
isIconSuffix?: boolean
|
||||
isShowIconSuccess?: boolean
|
||||
error?: string
|
||||
onChange?: (value: string | number) => void
|
||||
onChangeEvent?: (e: React.ChangeEvent<HTMLInputElement>) => void
|
||||
onBlur?: (e: any) => void
|
||||
onEnter?: (value: string | number) => void
|
||||
}
|
||||
|
||||
const InputCommon = forwardRef<Ref, Props>(({ value, placeholder, type, styleType = 'default', icon, backgroundTransparent = false,
|
||||
isIconSuffix, isShowIconSuccess, error,
|
||||
onChange, onEnter }: Props, ref) => {
|
||||
const inputElementRef = useRef<HTMLInputElement>(null);
|
||||
const InputCommon = forwardRef<Ref, Props>(
|
||||
(
|
||||
{
|
||||
value,
|
||||
placeholder,
|
||||
type,
|
||||
styleType = 'default',
|
||||
icon,
|
||||
backgroundTransparent = false,
|
||||
isIconSuffix,
|
||||
isShowIconSuccess,
|
||||
error,
|
||||
onChange,
|
||||
onChangeEvent,
|
||||
onEnter,
|
||||
onBlur,
|
||||
}: Props,
|
||||
ref
|
||||
) => {
|
||||
const inputElementRef = useRef<HTMLInputElement>(null)
|
||||
|
||||
const iconElement = useMemo(() => {
|
||||
if (error) {
|
||||
return <span className={s.icon}><IconError /> </span>
|
||||
} else if (isShowIconSuccess) {
|
||||
return <span className={s.icon}><IconCheck /> </span>
|
||||
} else if (icon) {
|
||||
return <span className={s.icon}>{icon} </span>
|
||||
}
|
||||
return <></>
|
||||
if (error) {
|
||||
return (
|
||||
<span className={s.icon}>
|
||||
<IconError />{' '}
|
||||
</span>
|
||||
)
|
||||
} else if (isShowIconSuccess) {
|
||||
return (
|
||||
<span className={s.icon}>
|
||||
<IconCheck />{' '}
|
||||
</span>
|
||||
)
|
||||
} else if (icon) {
|
||||
return <span className={s.icon}>{icon} </span>
|
||||
}
|
||||
return <></>
|
||||
}, [icon, error, isShowIconSuccess])
|
||||
|
||||
useImperativeHandle(ref, () => ({
|
||||
focus: () => {
|
||||
inputElementRef.current?.focus();
|
||||
},
|
||||
getValue: () => {
|
||||
const value = inputElementRef.current?.value || ''
|
||||
return value
|
||||
}
|
||||
}));
|
||||
focus: () => {
|
||||
inputElementRef.current?.focus()
|
||||
},
|
||||
getValue: () => {
|
||||
const value = inputElementRef.current?.value || ''
|
||||
return value
|
||||
},
|
||||
}))
|
||||
|
||||
const handleChange = (e: React.ChangeEvent<HTMLInputElement>) => {
|
||||
if (onChangeEvent) {
|
||||
onChangeEvent(e)
|
||||
} else {
|
||||
onChange && onChange(e.target.value)
|
||||
}
|
||||
}
|
||||
|
||||
const handleKeyDown = (e: any) => {
|
||||
if (e.key === KEY.ENTER && onEnter) {
|
||||
const value = inputElementRef.current?.value || ''
|
||||
onEnter(value)
|
||||
}
|
||||
if (e.key === KEY.ENTER && onEnter) {
|
||||
const value = inputElementRef.current?.value || ''
|
||||
onEnter(value)
|
||||
}
|
||||
}
|
||||
|
||||
return (
|
||||
<div className={classNames({
|
||||
[s.inputWrap]: true,
|
||||
[s[styleType]]: true,
|
||||
[s.bgTransparent]: backgroundTransparent
|
||||
|
||||
})}>
|
||||
<div className={classNames({
|
||||
[s.inputInner]: true,
|
||||
[s.preserve]: isIconSuffix,
|
||||
[s.success]: isShowIconSuccess,
|
||||
[s.error]: !!error,
|
||||
})}>
|
||||
{iconElement}
|
||||
<input
|
||||
ref={inputElementRef}
|
||||
value={value}
|
||||
type={type}
|
||||
placeholder={placeholder}
|
||||
onChange={handleChange}
|
||||
onKeyDown={handleKeyDown}
|
||||
className={s.inputCommon}
|
||||
/>
|
||||
</div>
|
||||
{
|
||||
error && <div className={s.errorMessage}>{error}</div>
|
||||
}
|
||||
<div
|
||||
className={classNames({
|
||||
[s.inputWrap]: true,
|
||||
[s[styleType]]: true,
|
||||
[s.bgTransparent]: backgroundTransparent,
|
||||
})}
|
||||
>
|
||||
<div
|
||||
className={classNames({
|
||||
[s.inputInner]: true,
|
||||
[s.preserve]: isIconSuffix,
|
||||
[s.success]: isShowIconSuccess,
|
||||
[s.error]: !!error,
|
||||
})}
|
||||
>
|
||||
{iconElement}
|
||||
<input
|
||||
ref={inputElementRef}
|
||||
value={value}
|
||||
type={type}
|
||||
placeholder={placeholder}
|
||||
onChange={handleChange}
|
||||
onKeyDown={handleKeyDown}
|
||||
onBlur={onBlur}
|
||||
/>
|
||||
</div>
|
||||
{error && <div className={s.errorMessage}>{error}</div>}
|
||||
</div>
|
||||
)
|
||||
}
|
||||
)
|
||||
|
||||
})
|
||||
|
||||
InputCommon.displayName = 'InputCommon'
|
||||
export default InputCommon
|
||||
|
@@ -0,0 +1,5 @@
|
||||
@import "../../../styles/form";
|
||||
|
||||
.inputWrap {
|
||||
@extend .formInputWrap;
|
||||
}
|
94
src/components/common/InputFiledInForm/InputFiledInForm.tsx
Normal file
94
src/components/common/InputFiledInForm/InputFiledInForm.tsx
Normal file
@@ -0,0 +1,94 @@
|
||||
import classNames from 'classnames'
|
||||
import { Field } from 'formik'
|
||||
import React, { useMemo, useRef } from 'react'
|
||||
import { IconCheck, IconError } from 'src/components/icons'
|
||||
import { KEY } from 'src/utils/constanst.utils'
|
||||
import s from './InputFiledInForm.module.scss'
|
||||
|
||||
interface Props {
|
||||
placeholder?: string
|
||||
type?: 'text' | 'number' | 'email' | 'password'
|
||||
styleType?: 'default' | 'custom'
|
||||
backgroundTransparent?: boolean
|
||||
icon?: React.ReactNode
|
||||
isIconSuffix?: boolean
|
||||
isShowIconSuccess?: boolean
|
||||
name: string
|
||||
error?: string
|
||||
onChange?: (value: string | number) => void
|
||||
onChangeEvent?: (e: React.ChangeEvent<HTMLInputElement>) => void
|
||||
onBlur?: (e: any) => void
|
||||
onEnter?: (value: string | number) => void
|
||||
}
|
||||
|
||||
const InputFiledInForm = ({
|
||||
name,
|
||||
placeholder,
|
||||
type,
|
||||
styleType = 'default',
|
||||
icon,
|
||||
backgroundTransparent = false,
|
||||
isIconSuffix = true,
|
||||
isShowIconSuccess,
|
||||
error,
|
||||
onEnter,
|
||||
}: Props) => {
|
||||
const inputElementRef = useRef<HTMLInputElement>(null)
|
||||
|
||||
const iconElement = useMemo(() => {
|
||||
if (error) {
|
||||
return (
|
||||
<span className={s.icon}>
|
||||
<IconError />{' '}
|
||||
</span>
|
||||
)
|
||||
} else if (isShowIconSuccess) {
|
||||
return (
|
||||
<span className={s.icon}>
|
||||
<IconCheck />{' '}
|
||||
</span>
|
||||
)
|
||||
} else if (icon) {
|
||||
return <span className={s.icon}>{icon} </span>
|
||||
}
|
||||
return <></>
|
||||
}, [icon, error, isShowIconSuccess])
|
||||
|
||||
const handleKeyDown = (e: any) => {
|
||||
if (e.key === KEY.ENTER && onEnter) {
|
||||
const value = inputElementRef.current?.value || ''
|
||||
onEnter(value)
|
||||
}
|
||||
}
|
||||
|
||||
return (
|
||||
<div
|
||||
className={classNames({
|
||||
[s.inputWrap]: true,
|
||||
[s[styleType]]: true,
|
||||
[s.bgTransparent]: backgroundTransparent,
|
||||
})}
|
||||
>
|
||||
<div
|
||||
className={classNames({
|
||||
[s.inputInner]: true,
|
||||
[s.preserve]: isIconSuffix,
|
||||
[s.success]: isShowIconSuccess,
|
||||
[s.error]: !!error,
|
||||
})}
|
||||
>
|
||||
{iconElement}
|
||||
<Field
|
||||
name={name}
|
||||
placeholder={placeholder}
|
||||
onKeyDown={handleKeyDown}
|
||||
type={type}
|
||||
/>
|
||||
</div>
|
||||
{error && <div className={s.errorMessage}>{error}</div>}
|
||||
</div>
|
||||
)
|
||||
}
|
||||
|
||||
InputFiledInForm.displayName = 'InputFiledInForm'
|
||||
export default InputFiledInForm
|
@@ -0,0 +1,10 @@
|
||||
.iconPassword {
|
||||
all: unset;
|
||||
cursor: pointer;
|
||||
&:focus {
|
||||
outline: none;
|
||||
}
|
||||
&:focus-visible {
|
||||
outline: 2px solid var(--text-active);
|
||||
}
|
||||
}
|
@@ -0,0 +1,49 @@
|
||||
import React, { useState } from 'react'
|
||||
import { IconPassword, IconPasswordCross } from 'src/components/icons'
|
||||
import InputFiledInForm from '../InputFiledInForm/InputFiledInForm'
|
||||
import s from './InputPasswordFiledInForm.module.scss'
|
||||
|
||||
interface Props {
|
||||
name?: string
|
||||
placeholder?: string
|
||||
styleType?: 'default' | 'custom'
|
||||
error?: string
|
||||
onChange?: (value: string | number) => void
|
||||
onEnter?: (value: string | number) => void
|
||||
}
|
||||
|
||||
const InputPasswordFiledInForm = ({
|
||||
name = 'password',
|
||||
placeholder,
|
||||
styleType = 'default',
|
||||
error,
|
||||
onChange,
|
||||
onEnter,
|
||||
}: Props) => {
|
||||
const [isShowPassword, setIsShowPassword] = useState<boolean>(false)
|
||||
const toggleShowPassword = (e: React.MouseEvent) => {
|
||||
e.stopPropagation()
|
||||
e.preventDefault()
|
||||
setIsShowPassword(!isShowPassword)
|
||||
}
|
||||
|
||||
return (
|
||||
<InputFiledInForm
|
||||
name={name}
|
||||
type={isShowPassword ? 'text' : 'password'}
|
||||
styleType={styleType}
|
||||
error={error}
|
||||
placeholder={placeholder}
|
||||
icon={
|
||||
<button className={s.iconPassword} onClick={toggleShowPassword}>
|
||||
{isShowPassword ? <IconPassword /> : <IconPasswordCross />}
|
||||
</button>
|
||||
}
|
||||
isIconSuffix={true}
|
||||
onChange={onChange}
|
||||
onEnter={onEnter}
|
||||
/>
|
||||
)
|
||||
}
|
||||
|
||||
export default InputPasswordFiledInForm
|
@@ -1,83 +1,121 @@
|
||||
import classNames from 'classnames'
|
||||
import React, { useEffect, useRef, useState } from 'react'
|
||||
import { ButtonCommon, Inputcommon, InputPassword } from 'src/components/common'
|
||||
import { Form, Formik } from 'formik'
|
||||
import React, { useEffect, useRef } from 'react'
|
||||
import {
|
||||
ButtonCommon,
|
||||
InputFiledInForm,
|
||||
InputPasswordFiledInForm
|
||||
} from 'src/components/common'
|
||||
import { CustomInputCommon } from 'src/utils/type.utils'
|
||||
import * as Yup from 'yup'
|
||||
import { useSignup } from '../../../../hooks'
|
||||
import s from '../FormAuthen.module.scss'
|
||||
import SocialAuthen from '../SocialAuthen/SocialAuthen'
|
||||
import styles from './FormRegister.module.scss'
|
||||
|
||||
interface Props {
|
||||
isHide: boolean,
|
||||
onSwitch: () => void
|
||||
isHide: boolean
|
||||
onSwitch: () => void
|
||||
}
|
||||
|
||||
const DisplayingErrorMessagesSchema = Yup.object().shape({
|
||||
email: Yup.string().email('Your email was wrong').required('Required'),
|
||||
password: Yup.string()
|
||||
.matches(
|
||||
/^(?=.{8,})(?=.*[a-z])(?=.*[A-Z])((?=.*[0-9!@#$%^&*()\-_=+{};:,<.>]){1}).*$/,
|
||||
'Must contain 8 characters with at least 1 uppercase and 1 lowercase letter and either 1 number or 1 special character.'
|
||||
)
|
||||
.max(30, 'Password is too long')
|
||||
.required('Required'),
|
||||
})
|
||||
|
||||
const FormRegister = ({ onSwitch, isHide }: Props) => {
|
||||
const emailRef = useRef<CustomInputCommon>(null)
|
||||
const { loading, signup, error } = useSignup()
|
||||
const [email, setEmail] = useState<string>('')
|
||||
const [password, setPassword] = useState<string>('')
|
||||
const emailRef = useRef<CustomInputCommon>(null)
|
||||
const { loading, signup, error } = useSignup()
|
||||
|
||||
|
||||
useEffect(() => {
|
||||
if (!isHide) {
|
||||
emailRef.current?.focus()
|
||||
}
|
||||
}, [isHide])
|
||||
|
||||
const onSignup = () => {
|
||||
// TODO: validate fields
|
||||
signup({ email, password })
|
||||
// TODO:
|
||||
alert("User created. Please verify your email")
|
||||
useEffect(() => {
|
||||
if (!isHide) {
|
||||
emailRef.current?.focus()
|
||||
}
|
||||
}, [isHide])
|
||||
|
||||
const onSignup = (values: { email: string; password: string }) => {
|
||||
signup({ email: values.email, password: values.password })
|
||||
// TODO: flow
|
||||
alert('User created. Please verify your email')
|
||||
}
|
||||
|
||||
useEffect(() => {
|
||||
if (error) {
|
||||
alert(error.message)
|
||||
}
|
||||
}, [error])
|
||||
useEffect(() => {
|
||||
if (error) {
|
||||
alert(error.message)
|
||||
}
|
||||
}, [error])
|
||||
|
||||
return (
|
||||
<section className={classNames({
|
||||
[s.formAuthen]: true,
|
||||
[styles.formRegister]: true,
|
||||
})}>
|
||||
<div className={s.inner}>
|
||||
<div className={s.body}>
|
||||
<Inputcommon
|
||||
placeholder='Email Address'
|
||||
type='email'
|
||||
ref={emailRef}
|
||||
value={email}
|
||||
onChange={(val) => setEmail(val.toString())}
|
||||
/>
|
||||
<InputPassword
|
||||
placeholder='Password'
|
||||
value={password}
|
||||
onChange={(val) => setPassword(val.toString())}
|
||||
/>
|
||||
|
||||
<div className={styles.passwordNote}>
|
||||
Must contain 8 characters with at least 1 uppercase and 1 lowercase letter and either 1 number or 1 special character.
|
||||
</div>
|
||||
return (
|
||||
<section
|
||||
className={classNames({
|
||||
[s.formAuthen]: true,
|
||||
[styles.formRegister]: true,
|
||||
})}
|
||||
>
|
||||
<div className={s.inner}>
|
||||
<div className={s.body}>
|
||||
<Formik
|
||||
initialValues={{
|
||||
password: '',
|
||||
email: '',
|
||||
}}
|
||||
validationSchema={DisplayingErrorMessagesSchema}
|
||||
onSubmit={onSignup}
|
||||
>
|
||||
{({ errors, touched }) => (
|
||||
<Form className="u-form">
|
||||
<div className="body">
|
||||
<InputFiledInForm
|
||||
name="email"
|
||||
placeholder="Email Address"
|
||||
error={
|
||||
touched.email && errors.email
|
||||
? errors.email.toString()
|
||||
: ''
|
||||
}
|
||||
isShowIconSuccess={touched.email && !errors.email}
|
||||
/>
|
||||
<InputPasswordFiledInForm
|
||||
name="password"
|
||||
placeholder="Password"
|
||||
error={
|
||||
touched.password && errors.password
|
||||
? errors.password.toString()
|
||||
: ''
|
||||
}
|
||||
/>
|
||||
<div className={styles.passwordNote}>
|
||||
Must contain 8 characters with at least 1 uppercase and 1
|
||||
lowercase letter and either 1 number or 1 special character.
|
||||
</div>
|
||||
</div>
|
||||
<div className={styles.bottom}>
|
||||
<ButtonCommon size='large'
|
||||
loading={loading}
|
||||
onClick={onSignup}>
|
||||
Create Account
|
||||
</ButtonCommon>
|
||||
<ButtonCommon
|
||||
HTMLType="submit"
|
||||
size="large"
|
||||
loading={loading}
|
||||
>
|
||||
Create Account
|
||||
</ButtonCommon>
|
||||
</div>
|
||||
<SocialAuthen />
|
||||
<div className={s.others}>
|
||||
<span>Already an account?</span>
|
||||
<button onClick={onSwitch}>Sign In</button>
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
)
|
||||
</Form>
|
||||
)}
|
||||
</Formik>
|
||||
</div>
|
||||
<SocialAuthen />
|
||||
<div className={s.others}>
|
||||
<span>Already an account?</span>
|
||||
<button onClick={onSwitch}>Sign In</button>
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
)
|
||||
}
|
||||
|
||||
export default FormRegister
|
||||
export default FormRegister
|
||||
|
@@ -1,3 +1,4 @@
|
||||
import { InputFiledInForm } from 'src/components/common/InputFiledInForm/InputFiledInForm';
|
||||
export { default as ButtonCommon } from './ButtonCommon/ButtonCommon'
|
||||
export { default as Layout } from './Layout/Layout'
|
||||
export { default as CarouselCommon } from './CarouselCommon/CarouselCommon'
|
||||
@@ -47,4 +48,6 @@ export { default as StaticImage} from './StaticImage/StaticImage'
|
||||
export { default as EmptyCommon} from './EmptyCommon/EmptyCommon'
|
||||
export { default as CustomShapeSvg} from './CustomShapeSvg/CustomShapeSvg'
|
||||
export { default as RecommendedRecipes} from './RecommendedRecipes/RecommendedRecipes'
|
||||
export { default as LayoutCheckout} from './LayoutCheckout/LayoutCheckout'
|
||||
export { default as LayoutCheckout} from './LayoutCheckout/LayoutCheckout'
|
||||
export { default as InputPasswordFiledInForm} from './InputPasswordFiledInForm/InputPasswordFiledInForm'
|
||||
export { default as InputFiledInForm} from './InputFiledInForm/InputFiledInForm'
|
||||
|
100
src/styles/_form.scss
Normal file
100
src/styles/_form.scss
Normal file
@@ -0,0 +1,100 @@
|
||||
@import './utilities';
|
||||
|
||||
.formInputWrap {
|
||||
.inputInner {
|
||||
@apply flex items-center relative;
|
||||
.icon {
|
||||
@apply absolute flex justify-center items-center;
|
||||
content: "";
|
||||
left: 1.6rem;
|
||||
margin-right: 1.6rem;
|
||||
svg path {
|
||||
fill: currentColor;
|
||||
}
|
||||
}
|
||||
.icon + input {
|
||||
padding-left: 4.8rem;
|
||||
}
|
||||
|
||||
input {
|
||||
@apply block w-full transition-all duration-200 bg-white;
|
||||
border-radius: .8rem;
|
||||
padding: 1.6rem;
|
||||
border: 1px solid var(--border-line);
|
||||
&:hover,
|
||||
&:focus,
|
||||
&:active {
|
||||
outline: none;
|
||||
border: 1px solid var(--primary);
|
||||
@apply shadow-md;
|
||||
}
|
||||
|
||||
&::placeholder {
|
||||
@apply text-label;
|
||||
}
|
||||
}
|
||||
|
||||
&.preserve {
|
||||
@apply flex-row-reverse;
|
||||
.icon {
|
||||
left: unset;
|
||||
right: 1.6rem;
|
||||
margin-left: 1.6rem;
|
||||
margin-right: 0;
|
||||
svg path {
|
||||
fill: var(--text-label);
|
||||
}
|
||||
}
|
||||
.icon + input {
|
||||
padding-left: 1.6rem;
|
||||
padding-right: 4.8rem;
|
||||
}
|
||||
}
|
||||
&.success {
|
||||
.icon {
|
||||
svg path {
|
||||
fill: var(--primary);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
&.error {
|
||||
.icon {
|
||||
svg path {
|
||||
fill: var(--negative);
|
||||
}
|
||||
}
|
||||
input {
|
||||
border-color: var(--negative) !important;
|
||||
}
|
||||
}
|
||||
}
|
||||
.errorMessage {
|
||||
@apply caption;
|
||||
color: var(--negative);
|
||||
margin-top: 0.4rem;
|
||||
}
|
||||
|
||||
&.custom {
|
||||
@apply shape-common;
|
||||
input {
|
||||
border: none;
|
||||
background: var(--background-gray);
|
||||
&:hover,
|
||||
&:focus,
|
||||
&:active {
|
||||
@apply shadow-md;
|
||||
border: none;
|
||||
}
|
||||
}
|
||||
}
|
||||
&.bgTransparent {
|
||||
input {
|
||||
background: rgb(227, 242, 233, 0.3);
|
||||
color: var(--white);
|
||||
&::placeholder {
|
||||
color: var(--white);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
@@ -6,4 +6,5 @@
|
||||
@import "~tailwindcss/utilities";
|
||||
@import './utilities';
|
||||
|
||||
@import './form';
|
||||
@import './pages'
|
||||
|
79
yarn.lock
79
yarn.lock
@@ -445,7 +445,7 @@
|
||||
dependencies:
|
||||
regenerator-runtime "^0.13.2"
|
||||
|
||||
"@babel/runtime@^7.0.0", "@babel/runtime@^7.10.2", "@babel/runtime@^7.11.2", "@babel/runtime@^7.12.13", "@babel/runtime@^7.14.0":
|
||||
"@babel/runtime@^7.0.0", "@babel/runtime@^7.10.2", "@babel/runtime@^7.10.5", "@babel/runtime@^7.11.2", "@babel/runtime@^7.12.13", "@babel/runtime@^7.14.0":
|
||||
version "7.15.4"
|
||||
resolved "https://registry.yarnpkg.com/@babel/runtime/-/runtime-7.15.4.tgz#fd17d16bfdf878e6dd02d19753a39fa8a8d9c84a"
|
||||
integrity sha512-99catp6bHCaxr4sJ/DbTGgHS4+Rs2RVd2g7iOap6SLGPDknRK9ztKNsE/Fg6QhSeh1FGE5f6gHGQmvvn3I3xhw==
|
||||
@@ -1213,6 +1213,11 @@
|
||||
resolved "https://registry.yarnpkg.com/@types/lodash/-/lodash-4.14.173.tgz#9d3b674c67a26cf673756f6aca7b429f237f91ed"
|
||||
integrity sha512-vv0CAYoaEjCw/mLy96GBTnRoZrSxkGE0BKzKimdR8P3OzrNYNvBgtW7p055A+E8C31vXNUhWKoFCbhq7gbyhFg==
|
||||
|
||||
"@types/lodash@^4.14.165":
|
||||
version "4.14.175"
|
||||
resolved "https://registry.yarnpkg.com/@types/lodash/-/lodash-4.14.175.tgz#b78dfa959192b01fae0ad90e166478769b215f45"
|
||||
integrity sha512-XmdEOrKQ8a1Y/yxQFOMbC47G/V2VDO1GvMRnl4O75M4GW/abC5tnfzadQYkqEveqRM1dEJGFFegfPNA2vvx2iw==
|
||||
|
||||
"@types/lru-cache@4.1.1":
|
||||
version "4.1.1"
|
||||
resolved "https://registry.yarnpkg.com/@types/lru-cache/-/lru-cache-4.1.1.tgz#b2d87a5e3df8d4b18ca426c5105cd701c2306d40"
|
||||
@@ -2572,6 +2577,11 @@ deepmerge@4.2.2, deepmerge@^4.0.0, deepmerge@^4.2.2:
|
||||
resolved "https://registry.yarnpkg.com/deepmerge/-/deepmerge-4.2.2.tgz#44d2ea3679b8f4d4ffba33f03d865fc1e7bf4955"
|
||||
integrity sha512-FJ3UgI4gIl+PHZm53knsuSFpE+nESMr7M4v9QcgB7S63Kj/6WqMiFQJpBBYz1Pt+66bZpP3Q7Lye0Oo9MPKEdg==
|
||||
|
||||
deepmerge@^2.1.1:
|
||||
version "2.2.1"
|
||||
resolved "https://registry.yarnpkg.com/deepmerge/-/deepmerge-2.2.1.tgz#5d3ff22a01c00f645405a2fbc17d0778a1801170"
|
||||
integrity sha512-R9hc1Xa/NOBi9WRVUWg19rl1UB7Tt4kuPd+thNJgFZoxXsTz7ncaPaeIm+40oSGuP33DfMb4sZt1QIGiJzC4EA==
|
||||
|
||||
defer-to-connect@^1.0.1:
|
||||
version "1.1.3"
|
||||
resolved "https://registry.yarnpkg.com/defer-to-connect/-/defer-to-connect-1.1.3.tgz#331ae050c08dcf789f8c83a7b81f0ed94f4ac591"
|
||||
@@ -3279,6 +3289,19 @@ form-data@^3.0.0:
|
||||
combined-stream "^1.0.8"
|
||||
mime-types "^2.1.12"
|
||||
|
||||
formik@^2.2.9:
|
||||
version "2.2.9"
|
||||
resolved "https://registry.yarnpkg.com/formik/-/formik-2.2.9.tgz#8594ba9c5e2e5cf1f42c5704128e119fc46232d0"
|
||||
integrity sha512-LQLcISMmf1r5at4/gyJigGn0gOwFbeEAlji+N9InZF6LIMXnFNkO42sCI8Jt84YZggpD4cPWObAZaxpEFtSzNA==
|
||||
dependencies:
|
||||
deepmerge "^2.1.1"
|
||||
hoist-non-react-statics "^3.3.0"
|
||||
lodash "^4.17.21"
|
||||
lodash-es "^4.17.21"
|
||||
react-fast-compare "^2.0.1"
|
||||
tiny-warning "^1.0.2"
|
||||
tslib "^1.10.0"
|
||||
|
||||
fraction.js@^4.1.1:
|
||||
version "4.1.1"
|
||||
resolved "https://registry.yarnpkg.com/fraction.js/-/fraction.js-4.1.1.tgz#ac4e520473dae67012d618aab91eda09bcb400ff"
|
||||
@@ -3595,6 +3618,13 @@ hmac-drbg@^1.0.1:
|
||||
minimalistic-assert "^1.0.0"
|
||||
minimalistic-crypto-utils "^1.0.1"
|
||||
|
||||
hoist-non-react-statics@^3.3.0:
|
||||
version "3.3.2"
|
||||
resolved "https://registry.yarnpkg.com/hoist-non-react-statics/-/hoist-non-react-statics-3.3.2.tgz#ece0acaf71d62c2969c2ec59feff42a4b1a85b45"
|
||||
integrity sha512-/gGivxi8JPKWNm/W0jSmzcMPpfpPLc3dY/6GxhX2hQ9iGj3aDfklV4ET7NjKpSinLpJ5vafa9iiGIEZg10SfBw==
|
||||
dependencies:
|
||||
react-is "^16.7.0"
|
||||
|
||||
hosted-git-info@^2.1.4:
|
||||
version "2.8.9"
|
||||
resolved "https://registry.yarnpkg.com/hosted-git-info/-/hosted-git-info-2.8.9.tgz#dffc0bf9a21c02209090f2aa69429e1414daf3f9"
|
||||
@@ -4415,6 +4445,11 @@ locate-path@^5.0.0:
|
||||
dependencies:
|
||||
p-locate "^4.1.0"
|
||||
|
||||
lodash-es@^4.17.15, lodash-es@^4.17.21:
|
||||
version "4.17.21"
|
||||
resolved "https://registry.yarnpkg.com/lodash-es/-/lodash-es-4.17.21.tgz#43e626c46e6591b7750beb2b50117390c609e3ee"
|
||||
integrity sha512-mKnC+QJ9pWVzv+C4/U3rRsHapFfHvQFoFB92e52xeyGMcX6/OlIl78je1u8vePzYZSkkogMPJ2yjxxsb89cxyw==
|
||||
|
||||
lodash.camelcase@^4.3.0:
|
||||
version "4.3.0"
|
||||
resolved "https://registry.yarnpkg.com/lodash.camelcase/-/lodash.camelcase-4.3.0.tgz#b28aa6288a2b9fc651035c7711f65ab6190331a6"
|
||||
@@ -4752,6 +4787,11 @@ mute-stream@0.0.8:
|
||||
resolved "https://registry.yarnpkg.com/mute-stream/-/mute-stream-0.0.8.tgz#1630c42b2251ff81e2a283de96a5497ea92e5e0d"
|
||||
integrity sha512-nnbWWOkoWyUsTjKrhgD0dcz22mdkSnpYqbEjIm2nhwhuxlSkpywJmBo8h0ZqJdkp73mb90SssHkN4rsRaBAfAA==
|
||||
|
||||
nanoclone@^0.2.1:
|
||||
version "0.2.1"
|
||||
resolved "https://registry.yarnpkg.com/nanoclone/-/nanoclone-0.2.1.tgz#dd4090f8f1a110d26bb32c49ed2f5b9235209ed4"
|
||||
integrity sha512-wynEP02LmIbLpcYw8uBKpcfF6dmg2vcpKqxeH5UcoKEYdExslsdUA4ugFauuaeYdTB76ez6gJW8XAZ6CgkXYxA==
|
||||
|
||||
nanoid@^3.1.23:
|
||||
version "3.1.25"
|
||||
resolved "https://registry.yarnpkg.com/nanoid/-/nanoid-3.1.25.tgz#09ca32747c0e543f0e1814b7d3793477f9c8e152"
|
||||
@@ -5795,6 +5835,11 @@ prop-types@^15.7.2:
|
||||
object-assign "^4.1.1"
|
||||
react-is "^16.8.1"
|
||||
|
||||
property-expr@^2.0.4:
|
||||
version "2.0.4"
|
||||
resolved "https://registry.yarnpkg.com/property-expr/-/property-expr-2.0.4.tgz#37b925478e58965031bb612ec5b3260f8241e910"
|
||||
integrity sha512-sFPkHQjVKheDNnPvotjQmm3KD3uk1fWKUN7CrpdbwmUx3CrG3QiM8QpTSimvig5vTXmTvjz7+TDvXOI9+4rkcg==
|
||||
|
||||
public-encrypt@^4.0.0:
|
||||
version "4.0.3"
|
||||
resolved "https://registry.yarnpkg.com/public-encrypt/-/public-encrypt-4.0.3.tgz#4fcc9d77a07e48ba7527e7cbe0de33d0701331e0"
|
||||
@@ -5921,6 +5966,11 @@ react-dom@^17.0.2:
|
||||
object-assign "^4.1.1"
|
||||
scheduler "^0.20.2"
|
||||
|
||||
react-fast-compare@^2.0.1:
|
||||
version "2.0.4"
|
||||
resolved "https://registry.yarnpkg.com/react-fast-compare/-/react-fast-compare-2.0.4.tgz#e84b4d455b0fec113e0402c329352715196f81f9"
|
||||
integrity sha512-suNP+J1VU1MWFKcyt7RtjiSWUjvidmQSlqu+eHslq+342xCbGTYmC0mEhPCOHxlW0CywylOC1u2DFAT+bv4dBw==
|
||||
|
||||
react-fast-compare@^3.0.1:
|
||||
version "3.2.0"
|
||||
resolved "https://registry.yarnpkg.com/react-fast-compare/-/react-fast-compare-3.2.0.tgz#641a9da81b6a6320f270e89724fb45a0b39e43bb"
|
||||
@@ -5936,7 +5986,7 @@ react-is@17.0.2:
|
||||
resolved "https://registry.yarnpkg.com/react-is/-/react-is-17.0.2.tgz#e691d4a8e9c789365655539ab372762b0efb54f0"
|
||||
integrity sha512-w2GsyukL62IJnlaff/nRegPQR94C/XXamvMWmSHRJ4y7Ts/4ocGRmTHvOs8PSE6pB3dWOrD/nueuU5sduBsQ4w==
|
||||
|
||||
react-is@^16.8.1:
|
||||
react-is@^16.7.0, react-is@^16.8.1:
|
||||
version "16.13.1"
|
||||
resolved "https://registry.yarnpkg.com/react-is/-/react-is-16.13.1.tgz#789729a4dc36de2999dc156dd6c1d9c18cea56a4"
|
||||
integrity sha512-24e6ynE2H+OKt4kqsOvNd8kBpV65zoxbA4BVsEOB3ARVWQki/DHzaUoC5KuON/BiccDaCCTZBuOcfZs70kR8bQ==
|
||||
@@ -6878,6 +6928,11 @@ timers-browserify@2.0.12, timers-browserify@^2.0.4:
|
||||
dependencies:
|
||||
setimmediate "^1.0.4"
|
||||
|
||||
tiny-warning@^1.0.2:
|
||||
version "1.0.3"
|
||||
resolved "https://registry.yarnpkg.com/tiny-warning/-/tiny-warning-1.0.3.tgz#94a30db453df4c643d0fd566060d60a875d84754"
|
||||
integrity sha512-lBN9zLN/oAf68o3zNXYrdCt1kP8WsiGW8Oo2ka41b2IM5JL/S1CTyX1rW0mb/zSuJun0ZUrDxx4sqvYS2FWzPA==
|
||||
|
||||
title-case@^3.0.3:
|
||||
version "3.0.3"
|
||||
resolved "https://registry.yarnpkg.com/title-case/-/title-case-3.0.3.tgz#bc689b46f02e411f1d1e1d081f7c3deca0489982"
|
||||
@@ -6926,6 +6981,11 @@ toidentifier@1.0.0:
|
||||
resolved "https://registry.yarnpkg.com/toidentifier/-/toidentifier-1.0.0.tgz#7e1be3470f1e77948bc43d94a3c8f4d7752ba553"
|
||||
integrity sha512-yaOH/Pk/VEhBWWTlhI+qXxDFXlejDGcQipMlyxda9nthulaxLZUNcUqFxokp0vcYnvteJln5FNQDRrxj3YcbVw==
|
||||
|
||||
toposort@^2.0.2:
|
||||
version "2.0.2"
|
||||
resolved "https://registry.yarnpkg.com/toposort/-/toposort-2.0.2.tgz#ae21768175d1559d48bef35420b2f4962f09c330"
|
||||
integrity sha1-riF2gXXRVZ1IvvNUILL0li8JwzA=
|
||||
|
||||
totalist@^1.0.0:
|
||||
version "1.1.0"
|
||||
resolved "https://registry.yarnpkg.com/totalist/-/totalist-1.1.0.tgz#a4d65a3e546517701e3e5c37a47a70ac97fe56df"
|
||||
@@ -6970,7 +7030,7 @@ tsconfig-paths@^3.11.0, tsconfig-paths@^3.9.0:
|
||||
minimist "^1.2.0"
|
||||
strip-bom "^3.0.0"
|
||||
|
||||
tslib@^1.8.1, tslib@^1.9.0:
|
||||
tslib@^1.10.0, tslib@^1.8.1, tslib@^1.9.0:
|
||||
version "1.14.1"
|
||||
resolved "https://registry.yarnpkg.com/tslib/-/tslib-1.14.1.tgz#cf2d38bdc34a134bcaf1091c41f6619e2f672d00"
|
||||
integrity sha512-Xni35NKzjgMrwevysHTCArtLDpPvye8zV/0E4EyYn43P7/7qvQwPh9BGkHewbMulVntbigmcT7rdX3BNo9wRJg==
|
||||
@@ -7419,3 +7479,16 @@ yocto-queue@^0.1.0:
|
||||
version "0.1.0"
|
||||
resolved "https://registry.yarnpkg.com/yocto-queue/-/yocto-queue-0.1.0.tgz#0294eb3dee05028d31ee1a5fa2c556a6aaf10a1b"
|
||||
integrity sha512-rVksvsnNCdJ/ohGc6xgPwyN8eheCxsiLM8mxuE/t/mOVqJewPuO1miLpTHQiRgTKCLexL4MeAFVagts7HmNZ2Q==
|
||||
|
||||
yup@^0.32.9:
|
||||
version "0.32.9"
|
||||
resolved "https://registry.yarnpkg.com/yup/-/yup-0.32.9.tgz#9367bec6b1b0e39211ecbca598702e106019d872"
|
||||
integrity sha512-Ci1qN+i2H0XpY7syDQ0k5zKQ/DoxO0LzPg8PAR/X4Mpj6DqaeCoIYEEjDJwhArh3Fa7GWbQQVDZKeXYlSH4JMg==
|
||||
dependencies:
|
||||
"@babel/runtime" "^7.10.5"
|
||||
"@types/lodash" "^4.14.165"
|
||||
lodash "^4.17.20"
|
||||
lodash-es "^4.17.15"
|
||||
nanoclone "^0.2.1"
|
||||
property-expr "^2.0.4"
|
||||
toposort "^2.0.2"
|
||||
|
Reference in New Issue
Block a user