mirror of
https://github.com/vercel/commerce.git
synced 2025-07-27 04:01:23 +00:00
Merge pull request #17 from KieIO/m3-lytran
M3: home page, modal login, register
This commit is contained in:
@@ -5,7 +5,10 @@
|
||||
display: flex;
|
||||
justify-content: center;
|
||||
align-items: center;
|
||||
padding: 0.8rem 3.2rem;
|
||||
padding: 1rem 2rem;
|
||||
@screen md {
|
||||
padding: 0.8rem 3.2rem;
|
||||
}
|
||||
&:disabled {
|
||||
filter: brightness(0.9);
|
||||
cursor: not-allowed;
|
||||
@@ -76,9 +79,15 @@
|
||||
}
|
||||
|
||||
&.large {
|
||||
padding: 1.6rem 4.8rem;
|
||||
padding: 1rem 1.5rem;
|
||||
&.onlyIcon {
|
||||
padding: 1.6rem;
|
||||
padding: 1rem;
|
||||
}
|
||||
@screen md {
|
||||
padding: 1.6rem 4.8rem;
|
||||
&.onlyIcon {
|
||||
padding: 1.6rem;
|
||||
}
|
||||
}
|
||||
&.loading {
|
||||
&::before {
|
||||
@@ -97,10 +106,6 @@
|
||||
|
||||
.icon {
|
||||
margin: 0 1.6rem 0 0;
|
||||
}
|
||||
|
||||
.label,
|
||||
.icon {
|
||||
svg path {
|
||||
fill: currentColor;
|
||||
}
|
||||
|
@@ -1,19 +1,18 @@
|
||||
import classNames from 'classnames'
|
||||
import React, { memo, useEffect, useState } from 'react'
|
||||
import { useModalCommon } from 'src/components/hooks/useModalCommon'
|
||||
import { isMobile } from 'src/utils/funtion.utils'
|
||||
import ModalAuthenticate from '../ModalAuthenticate/ModalAuthenticate'
|
||||
import HeaderHighLight from './components/HeaderHighLight/HeaderHighLight'
|
||||
import HeaderMenu from './components/HeaderMenu/HeaderMenu'
|
||||
import HeaderSubMenu from './components/HeaderSubMenu/HeaderSubMenu'
|
||||
import HeaderSubMenuMobile from './components/HeaderSubMenuMobile/HeaderSubMenuMobile'
|
||||
import s from './Header.module.scss'
|
||||
|
||||
interface Props {
|
||||
className?: string
|
||||
children?: any
|
||||
}
|
||||
|
||||
const Header = memo(({ }: Props) => {
|
||||
const Header = memo(() => {
|
||||
const [isFullHeader, setIsFullHeader] = useState<boolean>(true)
|
||||
const { visible: visibleModalAuthen, closeModal: closeModalAuthen, openModal: openModalAuthen } = useModalCommon({ initialValue: true })
|
||||
|
||||
useEffect(() => {
|
||||
window.addEventListener('scroll', handleScroll)
|
||||
@@ -36,11 +35,12 @@ const Header = memo(({ }: Props) => {
|
||||
<header className={classNames({ [s.header]: true, [s.full]: isFullHeader })}>
|
||||
<HeaderHighLight isShow={isFullHeader} />
|
||||
<div className={s.menu}>
|
||||
<HeaderMenu isFull={isFullHeader} />
|
||||
<HeaderMenu isFull={isFullHeader} openModalAuthen={openModalAuthen} />
|
||||
<HeaderSubMenu isShow={isFullHeader} />
|
||||
</div>
|
||||
</header>
|
||||
<HeaderSubMenuMobile />
|
||||
<ModalAuthenticate visible={visibleModalAuthen} closeModal={closeModalAuthen} />
|
||||
</>
|
||||
)
|
||||
})
|
||||
|
@@ -1,30 +1,35 @@
|
||||
import classNames from 'classnames'
|
||||
import Link from 'next/link'
|
||||
import { memo } from 'react'
|
||||
import { memo, useMemo } from 'react'
|
||||
import InputSearch from 'src/components/common/InputSearch/InputSearch'
|
||||
import MenuDropdown from 'src/components/common/MenuDropdown/MenuDropdown'
|
||||
import { IconBuy, IconHeart, IconHistory, IconUser } from 'src/components/icons'
|
||||
import { ACCOUNT_TAB, QUERY_KEY, ROUTE } from 'src/utils/constanst.utils'
|
||||
import s from './HeaderMenu.module.scss'
|
||||
|
||||
const OPTION_MENU = [
|
||||
{
|
||||
link: ROUTE.ACCOUNT,
|
||||
name: 'Account',
|
||||
},
|
||||
{
|
||||
link: '/',
|
||||
name: 'Logout',
|
||||
},
|
||||
|
||||
]
|
||||
|
||||
interface Props {
|
||||
children?: any,
|
||||
isFull: boolean,
|
||||
openModalAuthen: () => void,
|
||||
}
|
||||
|
||||
const HeaderMenu = memo(({ isFull }: Props) => {
|
||||
const HeaderMenu = memo(({ isFull, openModalAuthen }: Props) => {
|
||||
const optionMenu = useMemo(() => [
|
||||
{
|
||||
onClick: openModalAuthen,
|
||||
name: 'Login (Demo)',
|
||||
},
|
||||
{
|
||||
link: ROUTE.ACCOUNT,
|
||||
name: 'Account',
|
||||
},
|
||||
{
|
||||
link: '/',
|
||||
name: 'Logout',
|
||||
},
|
||||
|
||||
], [openModalAuthen])
|
||||
|
||||
return (
|
||||
<section className={classNames({ [s.headerMenu]: true, [s.full]: isFull })}>
|
||||
<div className={s.left}>
|
||||
@@ -54,7 +59,7 @@ const HeaderMenu = memo(({ isFull }: Props) => {
|
||||
</Link>
|
||||
</li>
|
||||
<li>
|
||||
<MenuDropdown options={OPTION_MENU} isHasArrow={false}><IconUser /></MenuDropdown>
|
||||
<MenuDropdown options={optionMenu} isHasArrow={false}><IconUser /></MenuDropdown>
|
||||
</li>
|
||||
<li>
|
||||
<button>
|
||||
|
@@ -7,6 +7,7 @@
|
||||
padding: 2rem 1rem;
|
||||
border-top: 1px solid var(--border-line);
|
||||
box-shadow: -5px 6px 10px rgba(0, 0, 0, 0.2);
|
||||
z-index: 9999;
|
||||
.menu {
|
||||
@apply grid grid-cols-4;
|
||||
li {
|
||||
|
@@ -10,7 +10,7 @@ interface Props {
|
||||
children?: React.ReactNode,
|
||||
value?: string | number,
|
||||
placeholder?: string,
|
||||
type?: 'text' | 'number' | 'email',
|
||||
type?: 'text' | 'number' | 'email' | 'password',
|
||||
styleType?: 'default' | 'custom',
|
||||
backgroundTransparent?: boolean,
|
||||
icon?: React.ReactNode,
|
||||
|
@@ -62,17 +62,25 @@
|
||||
.menuIner {
|
||||
@apply rounded list-none bg-white;
|
||||
border: 1px solid var(--text-active);
|
||||
margin-top: .4rem;
|
||||
margin-top: 0.4rem;
|
||||
li {
|
||||
@apply block w-full transition-all duration-200 cursor-pointer text-active;
|
||||
padding: 0.8rem 1.6rem;
|
||||
&:hover {
|
||||
@apply bg-primary-lightest;
|
||||
color: var(--primary);
|
||||
button {
|
||||
all: unset;
|
||||
color: currentColor;
|
||||
@apply text-left w-full;
|
||||
}
|
||||
button,
|
||||
a {
|
||||
padding: 0.8rem 1.6rem;
|
||||
}
|
||||
a {
|
||||
@apply block;
|
||||
}
|
||||
&:hover {
|
||||
@apply bg-primary-lightest;
|
||||
color: var(--primary);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@@ -5,7 +5,7 @@ import s from './MenuDropdown.module.scss';
|
||||
|
||||
interface Props {
|
||||
children?: React.ReactNode,
|
||||
options: { link: string, name: string }[],
|
||||
options: { link?: string, name: string, onClick?: () => void }[],
|
||||
isHasArrow?: boolean,
|
||||
align?: 'left'
|
||||
}
|
||||
@@ -26,11 +26,16 @@ const MenuDropdown = ({ options, children, isHasArrow = true, align }: Props) =>
|
||||
<ul className={s.menuIner}>
|
||||
{
|
||||
options.map(item => <li key={item.name}>
|
||||
<Link href={item.link}>
|
||||
<a >
|
||||
{item.onClick ?
|
||||
<button onClick={item.onClick}>
|
||||
{item.name}
|
||||
</a>
|
||||
</Link>
|
||||
</button>
|
||||
:
|
||||
<Link href={item.link || ''}>
|
||||
<a >
|
||||
{item.name}
|
||||
</a>
|
||||
</Link>}
|
||||
</li>)
|
||||
}
|
||||
</ul>
|
||||
|
@@ -0,0 +1,10 @@
|
||||
.formAuthenticate {
|
||||
@apply overflow-hidden;
|
||||
.inner {
|
||||
@apply grid grid-cols-2 overflow-hidden transition-all duration-200;
|
||||
width: 200%;
|
||||
&.register {
|
||||
transform: translateX(-50%);
|
||||
}
|
||||
}
|
||||
}
|
@@ -0,0 +1,36 @@
|
||||
import classNames from 'classnames'
|
||||
import React, { useState } from 'react'
|
||||
import ModalCommon from '../ModalCommon/ModalCommon'
|
||||
import FormLogin from './components/FormLogin/FormLogin'
|
||||
import FormRegister from './components/FormRegister/FormRegister'
|
||||
import s from './ModalAuthenticate.module.scss'
|
||||
|
||||
interface Props {
|
||||
visible: boolean,
|
||||
closeModal: () => void,
|
||||
}
|
||||
|
||||
const ModalAuthenticate = ({ visible, closeModal }: Props) => {
|
||||
const [isLogin, setIsLogin] = useState<boolean>(true)
|
||||
|
||||
const onSwitch = () => {
|
||||
setIsLogin(!isLogin)
|
||||
}
|
||||
|
||||
return (
|
||||
<ModalCommon visible={visible} onClose={closeModal} title={isLogin ? 'Sign In' : 'Create Account'}>
|
||||
<section className={s.formAuthenticate}>
|
||||
<div className={classNames({
|
||||
[s.inner]: true,
|
||||
[s.register]: !isLogin,
|
||||
})}>
|
||||
<FormLogin isHide={!isLogin} onSwitch={onSwitch} />
|
||||
<FormRegister isHide={isLogin} onSwitch={onSwitch} />
|
||||
</div>
|
||||
</section>
|
||||
</ModalCommon>
|
||||
|
||||
)
|
||||
}
|
||||
|
||||
export default ModalAuthenticate
|
@@ -0,0 +1,35 @@
|
||||
.formAuthen {
|
||||
@apply bg-white w-full;
|
||||
.inner {
|
||||
@screen md {
|
||||
max-width: 52rem;
|
||||
margin: auto;
|
||||
}
|
||||
.body {
|
||||
> div {
|
||||
&:not(:last-child) {
|
||||
margin-bottom: 1.6rem;
|
||||
}
|
||||
}
|
||||
}
|
||||
.others {
|
||||
@apply font-bold text-center;
|
||||
margin-top: 4rem;
|
||||
|
||||
span {
|
||||
@apply text-active;
|
||||
margin-right: 0.8rem;
|
||||
}
|
||||
button {
|
||||
all: unset;
|
||||
@apply text-primary cursor-pointer;
|
||||
&:focus-visible {
|
||||
outline: 2px solid #000;
|
||||
}
|
||||
&:focus {
|
||||
outline: none;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
@@ -0,0 +1,9 @@
|
||||
.bottom {
|
||||
@apply flex justify-between items-center;
|
||||
margin: 4rem auto;
|
||||
.forgotPassword {
|
||||
@apply font-bold;
|
||||
color: var(--primary);
|
||||
}
|
||||
}
|
||||
|
@@ -0,0 +1,53 @@
|
||||
import Link from 'next/link'
|
||||
import React, { useRef, useEffect } from 'react'
|
||||
import { Inputcommon, ButtonCommon } from 'src/components/common'
|
||||
import { ROUTE } from 'src/utils/constanst.utils'
|
||||
import SocialAuthen from '../SocialAuthen/SocialAuthen'
|
||||
import s from '../FormAuthen.module.scss'
|
||||
import styles from './FormLogin.module.scss'
|
||||
import classNames from 'classnames'
|
||||
import { CustomInputCommon } from 'src/utils/type.utils'
|
||||
|
||||
interface Props {
|
||||
isHide: boolean,
|
||||
onSwitch: () => void
|
||||
}
|
||||
|
||||
const FormLogin = ({ onSwitch, isHide }: Props) => {
|
||||
const emailRef = useRef<CustomInputCommon>(null)
|
||||
|
||||
useEffect(() => {
|
||||
if (!isHide) {
|
||||
emailRef.current?.focus()
|
||||
}
|
||||
}, [isHide])
|
||||
|
||||
return (
|
||||
<section className={classNames({
|
||||
[s.formAuthen]: true,
|
||||
// [styles.hide]: isHide
|
||||
})}>
|
||||
<div className={s.inner}>
|
||||
<div className={s.body}>
|
||||
<Inputcommon placeholder='Email Address' type='email' ref={emailRef} />
|
||||
<Inputcommon placeholder='Password' type='password' />
|
||||
</div>
|
||||
<div className={styles.bottom}>
|
||||
<Link href={ROUTE.FORGOT_PASSWORD}>
|
||||
<a href="" className={styles.forgotPassword}>
|
||||
Forgot Password?
|
||||
</a>
|
||||
</Link>
|
||||
<ButtonCommon size='large'>Sign in</ButtonCommon>
|
||||
</div>
|
||||
<SocialAuthen />
|
||||
<div className={s.others}>
|
||||
<span>Don't have an account?</span>
|
||||
<button onClick={onSwitch}>Create Account</button>
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
)
|
||||
}
|
||||
|
||||
export default FormLogin
|
@@ -0,0 +1,15 @@
|
||||
@import "../../../../../styles/utilities";
|
||||
|
||||
.formRegister {
|
||||
.passwordNote {
|
||||
@apply text-center caption text-label;
|
||||
margin-top: 0.8rem;
|
||||
}
|
||||
.bottom {
|
||||
@apply flex justify-between items-center w-full;
|
||||
margin: 4rem auto;
|
||||
button {
|
||||
@apply w-full;
|
||||
}
|
||||
}
|
||||
}
|
@@ -0,0 +1,50 @@
|
||||
import React, { useEffect, useRef } from 'react'
|
||||
import { ButtonCommon, Inputcommon } from 'src/components/common'
|
||||
import s from '../FormAuthen.module.scss'
|
||||
import styles from './FormRegister.module.scss'
|
||||
import SocialAuthen from '../SocialAuthen/SocialAuthen'
|
||||
import classNames from 'classnames'
|
||||
import { CustomInputCommon } from 'src/utils/type.utils'
|
||||
|
||||
interface Props {
|
||||
isHide: boolean,
|
||||
onSwitch: () => void
|
||||
}
|
||||
|
||||
const FormRegister = ({ onSwitch, isHide }: Props) => {
|
||||
const emailRef = useRef<CustomInputCommon>(null)
|
||||
|
||||
useEffect(() => {
|
||||
if (!isHide) {
|
||||
emailRef.current?.focus()
|
||||
}
|
||||
}, [isHide])
|
||||
|
||||
return (
|
||||
<section className={classNames({
|
||||
[s.formAuthen]: true,
|
||||
[styles.formRegister]: true,
|
||||
// [styles.hide]: isHide
|
||||
})}>
|
||||
<div className={s.inner}>
|
||||
<div className={s.body}>
|
||||
<Inputcommon placeholder='Email Address' type='email' ref={emailRef}/>
|
||||
<Inputcommon placeholder='Password' type='password' />
|
||||
<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'>Create Account</ButtonCommon>
|
||||
</div>
|
||||
<SocialAuthen />
|
||||
<div className={s.others}>
|
||||
<span>Already an account?</span>
|
||||
<button onClick={onSwitch}>Sign In</button>
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
)
|
||||
}
|
||||
|
||||
export default FormRegister
|
@@ -0,0 +1,36 @@
|
||||
@import "../../../../../styles/utilities";
|
||||
|
||||
.socialAuthen {
|
||||
.captionText {
|
||||
@apply relative text-center;
|
||||
margin-bottom: 4rem;
|
||||
span {
|
||||
@apply relative bg-white uppercase text-label caption;
|
||||
padding: 0 0.8rem;
|
||||
z-index: 10;
|
||||
}
|
||||
&::after {
|
||||
@apply absolute bg-line;
|
||||
content: "";
|
||||
width: 100%;
|
||||
height: 1px;
|
||||
top: 50%;
|
||||
left: 0;
|
||||
transform: translateY(-50%);
|
||||
}
|
||||
}
|
||||
.btns {
|
||||
@apply grid grid-cols-3;
|
||||
grid-gap: 1.6rem;
|
||||
.buttonWithIcon {
|
||||
@apply flex items-center;
|
||||
.label {
|
||||
@apply hidden;
|
||||
@screen md {
|
||||
@apply inline-block;
|
||||
margin-left: 0.8rem;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
@@ -0,0 +1,37 @@
|
||||
import React from 'react'
|
||||
import ButtonCommon from 'src/components/common/ButtonCommon/ButtonCommon'
|
||||
import { IconApple, IconFacebookColor, IconGoogleColor } from 'src/components/icons'
|
||||
import s from './SocialAuthen.module.scss'
|
||||
|
||||
const SocialAuthen = () => {
|
||||
return (
|
||||
<section className={s.socialAuthen}>
|
||||
<div className={s.captionText}>
|
||||
<span>
|
||||
OR CONTINUE WITH
|
||||
</span>
|
||||
</div>
|
||||
<div className={s.btns}>
|
||||
<ButtonCommon type='light' size='large'>
|
||||
<span className={s.buttonWithIcon}>
|
||||
<IconFacebookColor /><span className={s.label}>Facebook</span>
|
||||
</span>
|
||||
</ButtonCommon>
|
||||
<ButtonCommon type='light' size='large'>
|
||||
<span className={s.buttonWithIcon}>
|
||||
<IconApple />
|
||||
<span className={s.label}>Apple</span>
|
||||
</span>
|
||||
</ButtonCommon>
|
||||
<ButtonCommon type='light' size='large'>
|
||||
<span className={s.buttonWithIcon}>
|
||||
<IconGoogleColor />
|
||||
<span className={s.label}>Google</span>
|
||||
</span>
|
||||
</ButtonCommon>
|
||||
</div>
|
||||
</section>
|
||||
)
|
||||
}
|
||||
|
||||
export default SocialAuthen
|
@@ -1,3 +1,5 @@
|
||||
@import '../../../styles/utilities';
|
||||
|
||||
.background{
|
||||
@apply fixed inset-0 overflow-y-auto;
|
||||
background: rgba(20, 20, 20, 0.65);
|
||||
@@ -10,10 +12,12 @@
|
||||
padding: 3.2rem;
|
||||
box-shadow: 0px 8px 16px rgba(0, 0, 0, 0.24);
|
||||
border-radius: 1.2rem;
|
||||
.top {
|
||||
margin-bottom: 4rem;
|
||||
}
|
||||
.title{
|
||||
@apply font-heading heading-3;
|
||||
padding: 0 0.8rem 0 0.8rem;
|
||||
font-size: 3.2rem;
|
||||
line-height: 4rem;
|
||||
}
|
||||
.close{
|
||||
@apply absolute;
|
||||
|
Reference in New Issue
Block a user