mirror of
https://github.com/vercel/commerce.git
synced 2025-07-27 04:01:23 +00:00
✨ feat: register with validation
:%s
This commit is contained in:
@@ -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'
|
||||
|
Reference in New Issue
Block a user