bug: fix bug conflict

This commit is contained in:
quangnhankie 2021-09-10 18:14:47 +07:00
commit 25558e5ce4
90 changed files with 2480 additions and 178 deletions

2
.gitignore vendored
View File

@ -35,3 +35,5 @@ yarn-error.log*
# vercel
.vercel
.eslintrc

View File

@ -1,27 +0,0 @@
{
"schema": {
"https://buybutton.store/graphql": {
"headers": {
"Authorization": "Bearer xzy"
}
}
},
"documents": [
{
"./framework/bigcommerce/api/**/*.ts": {
"noRequire": true
}
}
],
"generates": {
"./framework/bigcommerce/schema.d.ts": {
"plugins": ["typescript", "typescript-operations"]
},
"./framework/bigcommerce/schema.graphql": {
"plugins": ["schema-ast"]
}
},
"hooks": {
"afterAllFileWrite": ["prettier --write"]
}
}

3
next-env.d.ts vendored
View File

@ -1,3 +1,6 @@
/// <reference types="next" />
/// <reference types="next/types/global" />
/// <reference types="next/image-types/global" />
// NOTE: This file should not be edited
// see https://nextjs.org/docs/basic-features/typescript for more information.

View File

@ -13,6 +13,16 @@ const isVendure = provider === 'vendure'
module.exports = withCommerceConfig({
commerce,
typescript: { // todo: remove it
// !! WARN !!
// Dangerously allow production builds to successfully complete even if
// your project has type errors.
// !! WARN !!
ignoreBuildErrors: true,
},
eslint: {
ignoreDuringBuilds: true,
},
images: {
// todo: replace domains for images
domains: ['user-images.githubusercontent.com'],
@ -42,9 +52,6 @@ module.exports = withCommerceConfig({
},
].filter(Boolean)
},
eslint: {
ignoreDuringBuilds: true,
}
})
// Don't delete this console log, useful to see the commerce config in Vercel deployments

13
pages/checkout.tsx Normal file
View File

@ -0,0 +1,13 @@
import { Layout } from 'src/components/common';
import { CheckoutPage } from 'src/components/modules/checkout';
import { HomeBanner, HomeCategories, HomeCollection, HomeCTA, HomeFeature, HomeRecipe, HomeSubscribe, HomeVideo } from 'src/components/modules/home';
export default function Checkout() {
return (
<>
<CheckoutPage/>
</>
)
}
Checkout.Layout = Layout

View File

@ -13,7 +13,7 @@ export default function Home() {
<HomeRecipe />
<HomeSubscribe />
{/* // todo: uncomment */}
{/* // todo: uncomment
{/* <ModalCreateUserInfo/> */}
</>
)

17
pages/products.tsx Normal file
View File

@ -0,0 +1,17 @@
import { Layout } from 'src/components/common';
import ProductListFilter from 'src/components/modules/product-list/ProductListFilter/ProductListFilter';
import RecipeListBanner from 'src/components/modules/recipes-list/RecipeListBanner/RecipeListBanner';
import RecipesList from 'src/components/modules/recipes-list/RecipesList/RecipesList';
import ProductListBanner from '../src/components/modules/product-list/ProductListBanner/ProductListBanner';
export default function Products() {
return (
<>
<ProductListBanner />
<ProductListFilter/>
</>
)
}
Products.Layout = Layout

View File

@ -1,6 +1,7 @@
import React from 'react'
import { ROUTE } from 'src/utils/constanst.utils'
import s from './BreadcrumbCommon.module.scss'
import BreadcrumbItem from './components/BreadcrumbItem/BreadcrumbItem'
import BreadcrumbSeparator from './components/BreadcrumbSeparator/BreadcrumbSeparator'

View File

@ -0,0 +1,31 @@
.warpper{
max-height: 12rem;
padding:1.6rem 0;
@apply flex items-center;
@screen lg {
max-width: 49.9rem;
}
.image{
width: 13.3rem;
height: 8.8rem;
margin: 0.8rem;
@apply flex justify-center items-center;
img{
max-width: 100%;
max-height: 100%;
}
}
.right{
width: 100%;
.name{
color: var(--text-active);
margin-bottom: 0.5erm;
}
.quantity {
@apply flex justify-between text-label;
.price{
color: var(--text-base);
}
}
}
}

View File

@ -0,0 +1,29 @@
import React from 'react'
import s from "./CardItemCheckout.module.scss"
import { ProductProps } from 'src/utils/types.utils'
export interface CardItemCheckoutProps extends ProductProps {
quantity:number
}
const CardItemCheckout = ({imageSrc,name,price,weight,quantity,category}: CardItemCheckoutProps) => {
return (
<div className={s.warpper}>
<div className={s.image}>
<img src={imageSrc} alt="image" />
</div>
<div className={s.right}>
<div className={s.name}>
{`${name} (${weight})`}
</div>
<div className={s.quantity}>
Quantity:
<div className={s.price}>
{`${quantity} x ${price}`}
</div>
</div>
</div>
</div>
)
}
export default CardItemCheckout

View File

@ -8,9 +8,10 @@ interface CheckboxProps extends Omit<
>{
onChange?: (value: boolean) => void,
defaultChecked?: boolean
text?:string
}
const CheckboxCommon = ({onChange,defaultChecked = true,...props}: CheckboxProps) =>{
const CheckboxCommon = ({onChange,defaultChecked = true,text="Billing address is same as shipping", ...props}: CheckboxProps) =>{
const [value, setValue] = useState<boolean>(true);
@ -31,7 +32,7 @@ const CheckboxCommon = ({onChange,defaultChecked = true,...props}: CheckboxProps
<span className={s.checkMark}></span>
</label>
<div className={classNames(s.checkboxText)}>
<label htmlFor="check"> Billing address is same as shipping </label>
<label htmlFor="check"> {text} </label>
</div>
</div>
)

View File

@ -0,0 +1,51 @@
.warpper{
padding: 2.4rem 0;
@apply border-b border-solid border-line;
.note{
font-size: 1.2rem;
line-height: 2rem;
letter-spacing: 0.01em;
color: var(--text-label);
padding: 0 5.6rem;
}
.header{
@apply flex justify-between;
.left{
@apply flex items-center;
.number{
width: 3.2rem;
height: 3.2rem;
border-radius: 100%;
border: 1px solid var(--text-active);
color: var(--text-active);
@apply flex justify-center items-center font-bold;
&.visible{
background-color: var(--text-active);
border: none;
color: var(--white);
}
&.done{
@apply border-2 border-solid border-primary;
}
}
.title{
padding-left: 2.4rem;
@apply font-bold select-none cursor-pointer;
color: var(--text-active);
}
}
.edit{
@apply font-bold cursor-pointer;
text-decoration-line: underline;
margin-right: 5.6rem;
}
}
.body{
height: 0;
@apply overflow-hidden;
&.show{
margin-top: 3.2rem;
height: initial;
}
}
}

View File

@ -0,0 +1,59 @@
import classNames from 'classnames'
import { divide } from 'lodash'
import React from 'react'
import { IconDoneCheckout } from 'src/components/icons'
import { CheckOutForm } from 'src/utils/types.utils'
import s from './CheckoutCollapse.module.scss'
interface CheckoutCollapseProps {
visible: boolean
id: number
children: React.ReactNode
title: string
isEdit: boolean
onClose?: (id:number) => void
onOpen?: (id:number) => void
onEditClick?:(id:number) => void
note?:string
}
const CheckoutCollapse = ({
children,
id,
title,
isEdit,
visible,
note,
onOpen,
onClose,
onEditClick
}: CheckoutCollapseProps) => {
const handleTitleClick = () => {
if(visible){
onClose && onClose(id)
}else{
onOpen && onOpen(id)
}
}
const handleEdit = () => {
onEditClick && onEditClick(id)
}
return (
<div className={s.warpper}>
<div className={s.header}>
<div className={s.left}>
<div className={classNames(s.number, { [s.visible]: visible, [s.done]:isEdit })}>
{isEdit?<IconDoneCheckout/>:id}
</div>
<div className={s.title} onClick={handleTitleClick}>
{title}
</div>
</div>
{isEdit && <div className={s.edit} onClick={handleEdit}>{'Edit'}</div>}
</div>
{(!visible && isEdit) && (<div className={s.note}>{note}</div>) }
<div className={classNames(s.body, { [`${s.show}`]: visible })}>{children}</div>
</div>
)
}
export default CheckoutCollapse

View File

@ -9,9 +9,11 @@ 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 {
toggleFilter:()=>void
}
const Header = memo(() => {
const Header = memo(({toggleFilter}:props) => {
const headeFullRef = useRef<HTMLDivElement>(null)
const [isFullHeader, setIsFullHeader] = useState<boolean>(true)
const { visible: visibleModalAuthen, closeModal: closeModalAuthen, openModal: openModalAuthen } = useModalCommon({ initialValue: false })
@ -43,6 +45,7 @@ const Header = memo(() => {
<HeaderHighLight />
<div className={s.menu}>
<HeaderMenu
toggleFilter={toggleFilter}
isFull={isFullHeader}
openModalAuthen={openModalAuthen}
openModalInfo={openModalInfo} />
@ -56,6 +59,7 @@ const Header = memo(() => {
[s.show]: !isFullHeader
})}>
<HeaderMenu isFull={isFullHeader}
toggleFilter={toggleFilter}
openModalAuthen={openModalAuthen}
openModalInfo={openModalInfo} />
</div>

View File

@ -15,7 +15,16 @@
.left {
.top {
@apply flex justify-between items-center;
.iconGroup{
@apply flex justify-between items-center;
}
.iconCart {
margin-left: 1.6rem;
}
.iconFilter{
@screen md {
display: none;
}
}
}
.inputSearch {
@ -76,5 +85,10 @@
}
}
}
@screen xl {
.iconFilterDesk {
display:none;
}
}
}
}

View File

@ -3,19 +3,23 @@ import Link from 'next/link'
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 { IconBuy, IconFilter, IconHeart, IconHistory, IconUser } from 'src/components/icons'
import { ACCOUNT_TAB, QUERY_KEY, ROUTE } from 'src/utils/constanst.utils'
import Logo from '../../../Logo/Logo'
import s from './HeaderMenu.module.scss'
import { useRouter } from 'next/router'
interface Props {
children?: any,
isFull: boolean,
openModalAuthen: () => void,
openModalInfo: () => void,
toggleFilter:() => void,
}
const HeaderMenu = memo(({ isFull, openModalAuthen, openModalInfo }: Props) => {
const FILTER_PAGE = [ROUTE.HOME,ROUTE.PRODUCTS]
const HeaderMenu = memo(({ isFull, openModalAuthen, openModalInfo, toggleFilter }: Props) => {
const router = useRouter()
const optionMenu = useMemo(() => [
{
onClick: openModalAuthen,
@ -35,15 +39,24 @@ const HeaderMenu = memo(({ isFull, openModalAuthen, openModalInfo }: Props) => {
},
], [openModalAuthen])
return (
<section className={classNames({ [s.headerMenu]: true, [s.full]: isFull })}>
<div className={s.left}>
<div className={s.top}>
<Logo/>
<button className={s.iconCart}>
<IconBuy />
</button>
<div className={s.iconGroup}>
{
FILTER_PAGE.includes(router.pathname) && (
<button className={s.iconFilter} onClick={toggleFilter}>
<IconFilter/>
</button>
)
}
<button className={s.iconCart}>
<IconBuy />
</button>
</div>
</div>
<div className={s.inputSearch}>
<InputSearch />
@ -72,6 +85,16 @@ const HeaderMenu = memo(({ isFull, openModalAuthen, openModalInfo }: Props) => {
<IconBuy />
</button>
</li>
{
FILTER_PAGE.includes(router.pathname) && (
<li className={s.iconFilterDesk}>
<button className={s.iconFilter} onClick={toggleFilter}>
<IconFilter/>
</button>
</li>
)
}
</ul>
</section>
)

View File

@ -15,9 +15,9 @@
.icon + .inputCommon {
padding-left: 4.8rem;
}
.inputCommon {
@apply block w-full transition-all duration-200 rounded;
@apply block w-full transition-all duration-200 rounded bg-white;
padding: 1.2rem 1.6rem;
border: 1px solid var(--border-line);
&:hover,

View File

@ -6,6 +6,7 @@ import s from './InputCommon.module.scss';
type Ref = {
focus: () => void
getValue: () => string | number
} | null;
interface Props {
children?: React.ReactNode,

View File

@ -6,3 +6,8 @@
flex: 1;
}
}
.filter{
@screen xl {
display: none;
}
}

View File

@ -1,10 +1,13 @@
import { CommerceProvider } from '@framework'
import { useRouter } from 'next/router'
import { FC } from 'react'
import { FilterProvider } from 'src/components/contexts/FilterContext'
import { useModalCommon } from 'src/components/hooks'
import { BRAND, CATEGORY, FEATURED } from 'src/utils/constanst.utils'
import { CartDrawer, CustomShapeSvg } from '..'
import Footer from '../Footer/Footer'
import Header from '../Header/Header'
import MenuNavigationProductList from '../MenuNavigationProductList/MenuNavigationProductList'
import s from './Layout.module.scss'
interface Props {
@ -16,6 +19,7 @@ interface Props {
const Layout: FC<Props> = ({ children }) => {
const { locale = 'en-US' } = useRouter()
const { visible: visibleCartDrawer, openModal, closeModal: closeCartDrawer } = useModalCommon({ initialValue: false })
const { visible: visibleFilter, openModal: openFilter, closeModal: closeFilter } = useModalCommon({ initialValue: false })
const toggle = () => {
if (visibleCartDrawer) {
@ -24,18 +28,27 @@ const Layout: FC<Props> = ({ children }) => {
openModal()
}
}
const toggleFilter = () => {
console.log("click")
if (visibleFilter) {
closeFilter()
} else {
openFilter()
}
}
return (
<CommerceProvider locale={locale}>
<div className={s.mainLayout}>
<Header />
<main >{children}</main>
<button onClick={toggle}>toggle card: {visibleCartDrawer.toString()}</button>
<CustomShapeSvg/>
<CartDrawer
visible={visibleCartDrawer}
onClose={closeCartDrawer} />
<Footer />
</div>
<div className={s.mainLayout}>
<Header toggleFilter={toggleFilter}/>
<main >{children}</main>
<button onClick={toggle}>toggle card: {visibleCartDrawer.toString()}</button>
<CustomShapeSvg/>
<CartDrawer
visible={visibleCartDrawer}
onClose={closeCartDrawer} />
<div className={s.filter}><MenuNavigationProductList categories={CATEGORY} brands={BRAND} featured={FEATURED} visible={visibleFilter} onClose={closeFilter}/> </div>
<Footer />
</div>
</CommerceProvider>
)

View File

@ -1,9 +1,7 @@
@import "../../../styles/utilities";
.menuFilterWrapper{
@apply spacing-horizontal;
@screen md {
@apply hidden;
}
.menuFilterHeading{
@apply sub-headline font-bold ;
color: var(--text-active);

View File

@ -1,18 +1,17 @@
@import "../../../styles/utilities";
.menuNavigationProductListDesktop{
@screen sm-only {
@screen sm {
@apply hidden;
}
@screen xl {
@apply block;
}
}
.menuNavigationProductListMobile{
@apply relative transition-all duration-100;
@screen md{
@apply hidden;
}
@screen xl{
@apply hidden;
}
&.isShow{
&::after{
content: "";

View File

@ -1,9 +1,7 @@
@import "../../../../styles/utilities";
.menuSortWrapper{
@apply spacing-horizontal;
@screen md {
@apply hidden;
}
.menuSortHeading{
@apply sub-headline font-bold ;
color: var(--text-active);

View File

@ -1,6 +1,6 @@
import React, { useRef } from 'react'
import { Close } from 'src/components/icons'
import { useOnClickOutside } from 'src/utils/useClickOutSide'
import { useOnClickOutside } from 'src/components/hooks/useClickOutSide'
import s from './ModalCommon.module.scss'
export interface ModalCommonProps {
onClose: () => void

View File

@ -1,15 +1,28 @@
@import '../../../styles/utilities';
.tabCommonOutSide {
@apply spacing-horizontal;
.tabCommon {
.tabWapper{
@apply flex flex-col w-full;
.tabHeader{
@apply flex;
position: relative;
border-bottom: 2px solid #FBFBFB;
padding-top: 1.6rem;
padding-bottom: 1.6rem;
width: 100%;
.tabList {
@apply flex;
position: relative;
border-bottom: 2px solid #FBFBFB;
padding: 0.8rem 0;
&.center{
margin: auto;
}
.slider {
@apply inline-block;
height: .2rem;
border-radius: 3px;
background-color: var(--primary);
position: absolute;
z-index: 1200;
bottom: 0;
transition: all .25s linear;
}
}
}
}

View File

@ -1,47 +1,85 @@
import React, { useState } from "react"
import s from './TabCommon.module.scss'
import TabItem from './TabItem/TabItem'
interface TabCommonProps {
}
const TabCommon = ({ } : TabCommonProps) => {
const active = "active", unActive = "";
const [item1Active, setItem1Active] = useState(active);
const [item2Active, setItem2Active] = useState(unActive);
const [item3Active, setItem3Active] = useState(unActive);
function toggleItem1():void {
setItem1Active(active)
setItem2Active(unActive)
setItem3Active(unActive)
import React, {
Children,
PropsWithChildren,
ReactElement,
useEffect,
useRef,
useState,
cloneElement,
} from 'react'
import s from './TabCommon.module.scss'
import TabItem from './components/TabItem/TabItem'
import { TabPaneProps } from './components/TabPane/TabPane'
import classNames from 'classnames'
interface TabCommonProps {
defaultActiveTab?: number
children: React.ReactNode
center?:boolean
}
const TabCommon = ({
defaultActiveTab = 0,
children,
center
}: TabCommonProps) => {
const [active, setActive] = useState(0)
const slider = useRef<HTMLDivElement>(null)
const headerRef = useRef<HTMLUListElement>(null)
useEffect(() => {
setActive(defaultActiveTab)
}, [])
useEffect(() => {
slide(active)
}, [active])
function slide(index: number) {
const active = headerRef.current?.children
.item(index)
?.getBoundingClientRect()
const header = headerRef.current?.getBoundingClientRect()
const current = slider.current
if (current && active && header) {
let width = active.width - 24 <= 0 ? 24 : active.width - 24
let left = active.left - header.left
current.style.width = width.toString() + 'px'
current.style.left = left.toString() + 'px'
}
}
function toggleItem2():void {
setItem2Active(active)
setItem1Active(unActive)
setItem3Active(unActive)
const onTabClick = (index: number) => {
setActive(index)
}
function toggleItem3():void {
setItem3Active(active)
setItem1Active(unActive)
setItem2Active(unActive)
}
return (
<section className={s.tabCommonOutSide}>
<div className={s.tabCommon}>
<span onClick={toggleItem1}><TabItem active={item1Active}>Wait for Comfirmation</TabItem></span>
<span onClick={toggleItem2}><TabItem active={item2Active}>Delivering</TabItem></span>
<span onClick={toggleItem3}><TabItem active={item3Active}>Delivered</TabItem></span>
</div>
</section>
<section className={s.tabWapper}>
<div className={s.tabHeader}>
<ul className={classNames(s.tabList,{[s.center]:center})} ref={headerRef}>
{Children.map(children, (tab, index) => {
let item = tab as ReactElement<PropsWithChildren<TabPaneProps>>
return (
<li key={item.props.tabName}>
<TabItem
active={active === index}
onClick={onTabClick}
tabIndex={index}
>
{item.props.tabName}
</TabItem>
</li>
)
})}
<div ref={slider} className={s.slider}></div>
</ul>
</div>
<div className={s.tabBody}>
{Children.map(children, (tab, index) => {
let item = tab as ReactElement<PropsWithChildren<TabPaneProps>>
return cloneElement(item, { active:index===active });
})
}</div>
</section>
)
}
export default TabCommon;
}
export default TabCommon

View File

@ -5,10 +5,16 @@
padding-top: 1.6rem;
padding-bottom: 1.6rem;
&.active {
@apply font-bold;
border-bottom: 2px solid var(--primary);
&:hover {
@apply cursor-pointer;
}
}
.tabItemActive {
@apply font-bold;
margin-right: 4.8rem;
padding-top: 1.6rem;
padding-bottom: 1.6rem;
&:hover {
@apply cursor-pointer;

View File

@ -1,18 +1,16 @@
import classNames from "classnames";
import React from "react"
import s from './TabItem.module.scss'
interface TabItemProps {
active: string;
target?: string;
children?: string;
active: boolean;
children: string;
onClick: (tabIndex: number, tabPane?: string) => void;
}
const TabItem = ({ active = "", children } : TabItemProps) => {
const TabItem = ({ active = false, children, onClick } : TabItemProps) => {
return (
<span className={classNames(s.tabItem, {
[s[active]]: active
})}>
<span onClick={onClick} className={active ? s.tabItemActive : s.tabItem} >
{children}
</span>
)

View File

@ -0,0 +1,13 @@
@import '../../../../../styles/utilities';
.tabItem {
margin-right:2.4rem;
padding: 0.8rem 0;
min-width: 2.4rem;
&:hover {
@apply cursor-pointer;
}
&.tabItemActive {
@apply font-bold;
}
}

View File

@ -0,0 +1,39 @@
import classNames from 'classnames'
<<<<<<< HEAD
import React, { RefObject, useRef } from 'react'
=======
import React from 'react'
>>>>>>> 88f90912429447f6ae7bafa77484465965e0ee13
import s from './TabItem.module.scss'
interface TabItemProps {
active: boolean
children: string
onClick?: (tabIndex: number) => void
tabIndex: number
}
const TabItem = ({
active = false,
children,
onClick,
tabIndex,
}: TabItemProps) => {
const handleClick = () => {
onClick && onClick(tabIndex)
}
return (
<span
onClick={handleClick}
className={classNames(s.tabItem, {[s.tabItemActive]:active})}
>
{children}
</span>
)
}
<<<<<<< HEAD
export default TabItem
=======
export default TabItem
>>>>>>> 88f90912429447f6ae7bafa77484465965e0ee13

View File

@ -0,0 +1,23 @@
@import "../../../../../styles/utilities";
.tabPane {
@apply hidden;
transition: all 0.6s;
// animation-duration: 0.6s;
// animation-name: appear;
// @keyframes appear {
// from {
// margin-left: 100%;
// width: 200%;
// }
// to {
// margin-left: 0%;
// width: 100%;
// }
// }
&.active {
@apply block;
}
}

View File

@ -0,0 +1,21 @@
import classNames from "classnames"
import React from "react"
import s from './TabPane.module.scss'
export interface TabPaneProps {
active?: boolean;
children?: React.ReactNode;
tabName: string
}
const TabPane = ({ active, children } : TabPaneProps) => {
return (
<section className={classNames(s.tabPane, {
[s.active] : active
})}>
{children}
</section>
)
}
export default TabPane

View File

@ -32,6 +32,7 @@ export { default as ModalConfirm} from "./ModalConfirm/ModalConfirm"
export { default as ModalInfo} from "./ModalInfo/ModalInfo"
export { default as ProductList} from "./ProductList/ProductList"
export { default as ModalCreateUserInfo} from './ModalCreateUserInfo/ModalCreateUserInfo'
export { default as CardItemCheckout} from './CardItemCheckout/CardItemCheckout'
export { default as CardBlog} from './CardBlog/CardBlog'
export { default as RelevantBlogPosts} from './RelevantBlogPosts/RelevantBlogPosts'
export { default as CollapseCommon} from './CollapseCommon/CollapseCommon'
@ -40,6 +41,8 @@ export { default as ImgWithLink} from './ImgWithLink/ImgWithLink'
export { default as RecipeDetail} from './RecipeDetail/RecipeDetail'
export { default as DrawerCommon} from './DrawerCommon/DrawerCommon'
export { default as CartDrawer} from './CartDrawer/CartDrawer'
export { default as TabPane} from './TabCommon/components/TabPane/TabPane'
export { default as TabCommon} from './TabCommon/TabCommon'
export { default as StaticImage} from './StaticImage/StaticImage'
export { default as EmptyCommon} from './EmptyCommon/EmptyCommon'
export { default as CustomShapeSvg} from './CustomShapeSvg/CustomShapeSvg'

View File

@ -0,0 +1,43 @@
import { createContext, ReactNode, useContext, useState } from "react";
import { filterContextType } from "src/utils/types.utils";
const contextDefaultValues: filterContextType = {
visible: false,
open: () => {},
close: () => {},
};
const FilterContext = createContext<filterContextType>(contextDefaultValues);
export function useAuth() {
return useContext(FilterContext);
}
type FilterProviderProps = {
children: ReactNode;
};
export function FilterProvider({ children }: FilterProviderProps) {
const [visible, setVisible] = useState<boolean>(false);
const open = () => {
setVisible(true);
};
const close = () => {
setVisible(false);
};
const value = {
visible,
open,
close,
};
return (
<>
<FilterContext.Provider value={value}>
{children}
</FilterContext.Provider>
</>
);
}

View File

@ -1,5 +1,5 @@
import { RefObject, useEffect } from 'react'
import { MouseAndTouchEvent } from './types.utils'
import { MouseAndTouchEvent } from '../../utils/types.utils'
export function useOnClickOutside<T extends HTMLElement = HTMLElement>(
ref: RefObject<T>,

View File

@ -0,0 +1,20 @@
import React from 'react'
const IconCirclePlus = () => {
return (
<svg
width="20"
height="20"
viewBox="0 0 20 20"
fill="none"
xmlns="http://www.w3.org/2000/svg"
>
<path
d="M10 0C8.02219 0 6.08879 0.58649 4.4443 1.6853C2.79981 2.78412 1.51809 4.3459 0.761209 6.17317C0.00433284 8.00043 -0.193701 10.0111 0.192152 11.9509C0.578004 13.8907 1.53041 15.6725 2.92894 17.0711C4.32746 18.4696 6.10929 19.422 8.0491 19.8079C9.98891 20.1937 11.9996 19.9957 13.8268 19.2388C15.6541 18.4819 17.2159 17.2002 18.3147 15.5557C19.4135 13.9112 20 11.9778 20 10C20 8.68678 19.7413 7.38642 19.2388 6.17317C18.7363 4.95991 17.9997 3.85752 17.0711 2.92893C16.1425 2.00035 15.0401 1.26375 13.8268 0.761205C12.6136 0.258658 11.3132 0 10 0ZM10 18C8.41775 18 6.87104 17.5308 5.55544 16.6518C4.23985 15.7727 3.21447 14.5233 2.60897 13.0615C2.00347 11.5997 1.84504 9.99113 2.15372 8.43928C2.4624 6.88743 3.22433 5.46197 4.34315 4.34315C5.46197 3.22433 6.88743 2.4624 8.43928 2.15372C9.99113 1.84504 11.5997 2.00346 13.0615 2.60896C14.5233 3.21447 15.7727 4.23984 16.6518 5.55544C17.5308 6.87103 18 8.41775 18 10C18 12.1217 17.1572 14.1566 15.6569 15.6569C14.1566 17.1571 12.1217 18 10 18ZM14 9H11V6C11 5.73478 10.8946 5.48043 10.7071 5.29289C10.5196 5.10536 10.2652 5 10 5C9.73479 5 9.48043 5.10536 9.2929 5.29289C9.10536 5.48043 9 5.73478 9 6V9H6C5.73479 9 5.48043 9.10536 5.2929 9.29289C5.10536 9.48043 5 9.73478 5 10C5 10.2652 5.10536 10.5196 5.2929 10.7071C5.48043 10.8946 5.73479 11 6 11H9V14C9 14.2652 9.10536 14.5196 9.2929 14.7071C9.48043 14.8946 9.73479 15 10 15C10.2652 15 10.5196 14.8946 10.7071 14.7071C10.8946 14.5196 11 14.2652 11 14V11H14C14.2652 11 14.5196 10.8946 14.7071 10.7071C14.8946 10.5196 15 10.2652 15 10C15 9.73478 14.8946 9.48043 14.7071 9.29289C14.5196 9.10536 14.2652 9 14 9Z"
fill="#141414"
/>
</svg>
)
}
export default IconCirclePlus

View File

@ -0,0 +1,20 @@
import React from 'react'
const IconDoneCheckout = () => {
return (
<svg
width="16"
height="12"
viewBox="0 0 16 12"
fill="none"
xmlns="http://www.w3.org/2000/svg"
>
<path
d="M14.7099 1.20986C14.617 1.11613 14.5064 1.04174 14.3845 0.990969C14.2627 0.940201 14.132 0.914062 13.9999 0.914062C13.8679 0.914062 13.7372 0.940201 13.6154 0.990969C13.4935 1.04174 13.3829 1.11613 13.29 1.20986L5.83995 8.66986L2.70995 5.52986C2.61343 5.43662 2.49949 5.36331 2.37463 5.3141C2.24978 5.2649 2.11645 5.24077 1.98227 5.24309C1.84809 5.24541 1.71568 5.27414 1.5926 5.32763C1.46953 5.38113 1.35819 5.45834 1.26495 5.55486C1.17171 5.65138 1.0984 5.76532 1.04919 5.89018C0.999989 6.01503 0.975859 6.14836 0.97818 6.28254C0.980502 6.41672 1.00923 6.54913 1.06272 6.67221C1.11622 6.79529 1.19343 6.90662 1.28995 6.99986L5.12995 10.8399C5.22291 10.9336 5.33351 11.008 5.45537 11.0588C5.57723 11.1095 5.70794 11.1357 5.83995 11.1357C5.97196 11.1357 6.10267 11.1095 6.22453 11.0588C6.34639 11.008 6.45699 10.9336 6.54995 10.8399L14.7099 2.67986C14.8115 2.58622 14.8925 2.47257 14.9479 2.34607C15.0033 2.21957 15.0319 2.08296 15.0319 1.94486C15.0319 1.80676 15.0033 1.67015 14.9479 1.54365C14.8925 1.41715 14.8115 1.3035 14.7099 1.20986Z"
fill="#4EA674"
/>
</svg>
)
}
export default IconDoneCheckout

View File

@ -0,0 +1,22 @@
import React from 'react'
interface Props {}
const IconFilter = (props: Props) => {
return (
<svg
width="20"
height="20"
viewBox="0 0 20 20"
fill="none"
xmlns="http://www.w3.org/2000/svg"
>
<path
d="M17 0H3C2.20435 0 1.44129 0.316071 0.87868 0.87868C0.316071 1.44129 1.80878e-07 2.20435 1.80878e-07 3V4.17C-0.000143207 4.58294 0.0849664 4.99147 0.25 5.37V5.43C0.39128 5.75097 0.591392 6.04266 0.84 6.29L7 12.41V19C6.99966 19.1699 7.04264 19.3372 7.12487 19.4859C7.20711 19.6346 7.32589 19.7599 7.47 19.85C7.62914 19.9486 7.81277 20.0006 8 20C8.15654 19.9991 8.31068 19.9614 8.45 19.89L12.45 17.89C12.6149 17.8069 12.7536 17.6798 12.8507 17.5227C12.9478 17.3656 12.9994 17.1847 13 17V12.41L19.12 6.29C19.3686 6.04266 19.5687 5.75097 19.71 5.43V5.37C19.8888 4.99443 19.9876 4.58578 20 4.17V3C20 2.20435 19.6839 1.44129 19.1213 0.87868C18.5587 0.316071 17.7956 0 17 0ZM11.29 11.29C11.1973 11.3834 11.124 11.4943 11.0742 11.6161C11.0245 11.7379 10.9992 11.8684 11 12V16.38L9 17.38V12C9.00076 11.8684 8.97554 11.7379 8.92577 11.6161C8.87601 11.4943 8.80268 11.3834 8.71 11.29L3.41 6H16.59L11.29 11.29ZM18 4H2V3C2 2.73478 2.10536 2.48043 2.29289 2.29289C2.48043 2.10536 2.73478 2 3 2H17C17.2652 2 17.5196 2.10536 17.7071 2.29289C17.8946 2.48043 18 2.73478 18 3V4Z"
fill="#141414"
/>
</svg>
)
}
export default IconFilter

View File

@ -0,0 +1,20 @@
import React from 'react'
const Shipping = () => {
return (
<svg
width="22"
height="20"
viewBox="0 0 22 20"
fill="none"
xmlns="http://www.w3.org/2000/svg"
>
<path
d="M22 10.5V15.5C22 15.7652 21.8946 16.0196 21.7071 16.2071C21.5196 16.3946 21.2652 16.5 21 16.5H20C20 17.2956 19.6839 18.0587 19.1213 18.6213C18.5587 19.1839 17.7956 19.5 17 19.5C16.2044 19.5 15.4413 19.1839 14.8787 18.6213C14.3161 18.0587 14 17.2956 14 16.5H8C8 17.2956 7.68393 18.0587 7.12132 18.6213C6.55871 19.1839 5.79565 19.5 5 19.5C4.20435 19.5 3.44129 19.1839 2.87868 18.6213C2.31607 18.0587 2 17.2956 2 16.5H1C0.734783 16.5 0.480429 16.3946 0.292892 16.2071C0.105356 16.0196 0 15.7652 0 15.5V3.5C0 2.70435 0.316071 1.94129 0.878679 1.37868C1.44129 0.816071 2.20435 0.5 3 0.5H12C12.7956 0.5 13.5587 0.816071 14.1213 1.37868C14.6839 1.94129 15 2.70435 15 3.5V5.5H17C17.4657 5.5 17.9251 5.60844 18.3416 5.81672C18.7582 6.025 19.1206 6.32741 19.4 6.7L21.8 9.9C21.8292 9.94347 21.8528 9.99052 21.87 10.04L21.93 10.15C21.9741 10.2615 21.9978 10.3801 22 10.5ZM6 16.5C6 16.3022 5.94135 16.1089 5.83147 15.9444C5.72159 15.78 5.56541 15.6518 5.38268 15.5761C5.19996 15.5004 4.99889 15.4806 4.80491 15.5192C4.61093 15.5578 4.43274 15.653 4.29289 15.7929C4.15304 15.9327 4.0578 16.1109 4.01921 16.3049C3.98063 16.4989 4.00043 16.7 4.07612 16.8827C4.15181 17.0654 4.27998 17.2216 4.44443 17.3315C4.60888 17.4414 4.80222 17.5 5 17.5C5.26522 17.5 5.51957 17.3946 5.70711 17.2071C5.89464 17.0196 6 16.7652 6 16.5ZM13 3.5C13 3.23478 12.8946 2.98043 12.7071 2.79289C12.5196 2.60536 12.2652 2.5 12 2.5H3C2.73478 2.5 2.48043 2.60536 2.29289 2.79289C2.10536 2.98043 2 3.23478 2 3.5V14.5H2.78C3.06118 14.1906 3.40391 13.9435 3.78622 13.7743C4.16852 13.6052 4.58195 13.5178 5 13.5178C5.41805 13.5178 5.83148 13.6052 6.21378 13.7743C6.59609 13.9435 6.93882 14.1906 7.22 14.5H13V3.5ZM15 9.5H19L17.8 7.9C17.7069 7.7758 17.5861 7.675 17.4472 7.60557C17.3084 7.53615 17.1552 7.5 17 7.5H15V9.5ZM18 16.5C18 16.3022 17.9414 16.1089 17.8315 15.9444C17.7216 15.78 17.5654 15.6518 17.3827 15.5761C17.2 15.5004 16.9989 15.4806 16.8049 15.5192C16.6109 15.5578 16.4327 15.653 16.2929 15.7929C16.153 15.9327 16.0578 16.1109 16.0192 16.3049C15.9806 16.4989 16.0004 16.7 16.0761 16.8827C16.1518 17.0654 16.28 17.2216 16.4444 17.3315C16.6089 17.4414 16.8022 17.5 17 17.5C17.2652 17.5 17.5196 17.3946 17.7071 17.2071C17.8946 17.0196 18 16.7652 18 16.5ZM20 11.5H15V14.28C15.5902 13.7526 16.3649 13.4797 17.1553 13.5209C17.9457 13.5621 18.6879 13.914 19.22 14.5H20V11.5Z"
fill="#141414"
/>
</svg>
)
}
export default Shipping

View File

@ -22,10 +22,14 @@ export { default as IconPassword } from './IconPassword'
export { default as IconPasswordCross } from './IconPasswordCross'
export { default as IconError } from './IconError'
export { default as IconCheck } from './IconCheck'
export { default as Shipping} from './Shipping'
export { default as IconTime } from './IconTime'
export { default as IconPeople } from './IconPeople'
export { default as IconLocation } from './IconLocation'
export { default as IconClose } from './IconClose'
export { default as IconDelete } from './IconDelete'
export { default as IconPlus } from './IconPlus'
export { default as IconMinus } from './IconMinus'
export { default as IconMinus } from './IconMinus'
export { default as IconCirclePlus } from './IconCirclePlus'
export { default as IconDoneCheckout } from './IconDoneCheckout'
export { default as IconFilter } from './IconFilter'

View File

@ -1,5 +1,27 @@
@import '../../../../styles/utilities';
.accountNavigation {
@apply spacing-horizontal;
@apply flex;
width: 100%;
.slider {
@apply inline-block;
width: 0.2rem;
height: 4.8rem;
border-radius: 3px;
background-color: var(--primary);
position: absolute;
left: 11.2rem;
transition: all .2s linear;
}
.tabList {
margin-top: 3.8rem;
margin-right: 12.4rem;
}
.tabBody {
margin-top: -4.7rem;
width: 100%;
}
}

View File

@ -1,48 +1,69 @@
import React, { useState } from "react"
import React, { useRef, useEffect, Children, ReactElement, PropsWithChildren, useState, cloneElement } from "react"
import s from './AccountNavigation.module.scss'
import AccountNavigationItem from './components/AccountNavigationItem'
import AccountNavigationItem from './components/AccountNavigationItem/AccountNavigationItem'
import {TabPaneProps} from '../../../common/TabCommon/components/TabPane/TabPane'
interface AccountNavigationProps {
defaultActiveIndex: number;
children: React.ReactNode
}
const AccountNavigation = ({ } : AccountNavigationProps) => {
const active = "active", unActive = "";
const AccountNavigation = ({ defaultActiveIndex, children } : AccountNavigationProps) => {
const [active, setActive] = useState(defaultActiveIndex)
const sliderRef = useRef<HTMLDivElement>(null);
const headerRef = useRef<HTMLUListElement>(null)
const [item1Active, setItem1Active] = useState(active);
const [item2Active, setItem2Active] = useState(unActive);
const [item3Active, setItem3Active] = useState(unActive);
function toggleItem1():void {
setItem1Active(active)
setItem2Active(unActive)
setItem3Active(unActive)
const onTabClick = (index: number) => {
setActive(index)
}
function toggleItem2():void {
setItem2Active(active)
setItem1Active(unActive)
setItem3Active(unActive)
}
function toggleItem3():void {
setItem3Active(active)
function slide(index: number) {
const active = headerRef.current?.children.item(index)?.getBoundingClientRect()
const header = headerRef.current?.getBoundingClientRect()
const current = sliderRef.current
setItem1Active(unActive)
setItem2Active(unActive)
if (current && active && header) {
const top = active.top;
current.style.top = top.toString()+"px";
}
}
useEffect(() => {
slide(active);
}, [active])
return (
<section className={s.accountNavigation}>
<div onClick={toggleItem1}>
<AccountNavigationItem active={item1Active}>Customer Information</AccountNavigationItem>
</div>
<div onClick={toggleItem2}>
<AccountNavigationItem active={item2Active}>Your Orders</AccountNavigationItem>
</div>
<div onClick={toggleItem3}>
<AccountNavigationItem active={item3Active}>Favourites</AccountNavigationItem>
<ul className={s.tabList} ref={headerRef}>
{
Children.map(children, (tab, index) => {
let item = tab as ReactElement<PropsWithChildren<TabPaneProps>>
return (
<li key={item.props.tabName}>
<AccountNavigationItem
active={active === index}
onClick={onTabClick}
tabIndex={index}
>
{item.props.tabName}
</AccountNavigationItem>
</li>
)
})
}
<div ref={sliderRef} className={s.slider}></div>
</ul>
<div className={s.tabBody}>
{
Children.map(children, (tab, index) => {
let item = tab as ReactElement<PropsWithChildren<TabPaneProps>>
return cloneElement(item, { active: index === active });
})
}
</div>
<div ref={slider} className={s.slider}></div>
</section>
)
}

View File

@ -1,7 +1,6 @@
@import '../../../../../styles/utilities';
@import '../../../../../../styles/utilities';
.accountNavigationItem {
@apply bg-gray;
width: 28rem;
padding: 1.2rem 0 1.2rem 1.6rem;
margin-bottom: 1.2rem;
@ -14,6 +13,5 @@
&.active {
background-color: #FBFBFB;
border-radius: 0 1.6rem 1.6rem 0;
border-left: 2px solid var(--primary);
}
}

View File

@ -1,17 +1,22 @@
import React from "react";
import React, { RefObject } from "react";
import classNames from "classnames";
import s from './AccountNavigationItem.module.scss'
interface AccountNavigationItemProps {
children?: string;
active?: string;
target?: string;
active?: boolean;
tabIndex: number
onClick: (index: number) => void;
}
const AccountNavigationItem = ({ children, active="" } : AccountNavigationItemProps) => {
const AccountNavigationItem = ({ children, active, tabIndex, onClick } : AccountNavigationItemProps) => {
const handleClick = () => {
onClick(tabIndex)
}
return (
<div className={classNames(s.accountNavigationItem, {
[s[active]]:active
<div onClick={handleClick} className={classNames(s.accountNavigationItem, {
[s.active]:active
})}>
{children}
</div>

View File

@ -0,0 +1,26 @@
@import '../../../../styles/utilities';
.accountPage {
@apply spacing-horizontal;
background-color: #F5F4F2;
margin-top: -3.2rem;
padding-top: 3.2rem;
padding-bottom: 3.2rem;
@screen md {
padding-left: 3.2rem;
padding-right: 3.2rem;
}
@screen xl {
@apply spacing-horizontal
}
.header {
margin-bottom: 1.2rem;
@screen md {
margin-bottom: 3.8rem;
}
}
}

View File

@ -0,0 +1,86 @@
import React, { useState } from "react"
import s from './AccountPage.module.scss'
import AccountNavigation from '../AccountNavigation/AccountNavigation'
import HeadingCommon from '../../../common/HeadingCommon/HeadingCommon'
import AccountInfomation from "./components/AccountInfomation/AccountInfomation"
import OrderInfomation from './components/OrderInformation/OrderInformation'
import EditInfoModal from './components/EditInfoModal/EditInfoModal'
import TabPane from "src/components/common/TabCommon/components/TabPane/TabPane"
const waiting = [
{
id: "NO 123456",
products: ["Tomato", "Fish", "Pork", "Onion"],
totalPrice : 1000
}
]
const delivering = [
{
id: "NO 123456",
products: ["Tomato", "Fish", "Pork", "Onion", "Tomato", "Fish", "Pork", "Onion"],
totalPrice : 1000
}
]
const delivered = [
{
id: "NO 123456",
products: ["Tomato", "Fish", "Pork", "Onion", "Tomato", "Fish", "Pork", "Onion"],
totalPrice : 1000
}
]
let account = {
name: "vu duong",
email: "vuduong@gmail.com",
address: "234 Dien Bien Phu Bis, Dakao ward",
state: "District 1",
city: "HCMC",
postalCode: "700000",
phoneNumber: "(+84) 937 937 195"
}
interface AccountPageProps {
defaultActiveContent?: "info" | "orders" | "favorites"
}
const AccountPage = ({defaultActiveContent="orders"} : AccountPageProps) => {
const [activeTab] = useState(defaultActiveContent==="info" ? 0 : defaultActiveContent==="orders" ? 1 : 2)
const [modalVisible, setModalVisible] = useState(false);
function showModal() {
setModalVisible(true);
}
function closeModal() {
setModalVisible(false);
}
return (
<>
<section className={s.accountPage}>
<div className={s.header}>
<HeadingCommon>Account</HeadingCommon>
</div>
<AccountNavigation defaultActiveIndex={activeTab}>
<TabPane tabName="Customer Information">
<AccountInfomation account={account} onClick={showModal} />
</TabPane>
<TabPane tabName="Your Orders">
<OrderInfomation waiting={waiting} delivering={delivering} delivered={delivered} />
</TabPane>
<TabPane tabName="Favourite">
{/* <FavoriteProduct active={activeTab === 2} favProducts={favProducts} /> */}
</TabPane>
</AccountNavigation>
</section>
<EditInfoModal accountInfo={account} closeModal={closeModal} visible={modalVisible} />
</>
)
}
export default AccountPage

Binary file not shown.

After

Width:  |  Height:  |  Size: 4.5 KiB

View File

@ -0,0 +1,67 @@
@import '../../../../../../styles/utilities';
.accountInfomation {
@apply flex justify-center items-center;
text-align: center;
margin-top: 1.6rem;
@screen md {
@apply block;
text-align: left;
margin-top: 0;
}
.avatar {
height: 22rem;
width: 22rem;
border-radius: 50%;
margin: auto;
margin-bottom: 4rem;
@screen md {
margin-left: 0
}
}
.accountName {
@apply heading-3 font-heading;
}
.horizontalSeparator{
border: 1px solid var(--disabled);
max-width: 39.2rem;
min-width: 30rem;
margin-top: 2.4rem;
margin-bottom: 2.4rem;
}
.shippingInfo {
@apply heading-3 font-heading;
}
.accountAddress {
max-width: 31rem;
min-width: none;
}
.editInfoBtn {
@apply text-center font-bold custom-border-radius;
margin: auto;
margin-top: 2.4rem;
margin-bottom: 2.4rem;
padding: .8rem 1.6rem;
color: #141414;
border: 1px solid #141414;
max-width: 8.8rem;
&:hover {
@apply cursor-pointer;
background-color: #FBFBFB;
}
@screen md {
margin-left: 0;
}
}
}

View File

@ -0,0 +1,56 @@
import React from "react"
import s from './AccountInfomation.module.scss'
import Image from "next/image"
import avatar from '../../assets/avatar.png';
interface AccountProps {
name: string, email: string, address: string, state: string, city: string, postalCode: string, phoneNumber: string
}
interface AccountInfomationProps {
account: AccountProps;
onClick: () => void;
}
const AccountInfomation = ({ account, onClick } : AccountInfomationProps) => {
// need to handle call back when edit account information
const showEditForm = () => onClick()
return (
<section className={s.accountInfomation}>
{
<div>
<div className={s.avatar}>
<Image src={avatar} alt="avatar" />
</div>
<div className={s.accountName}>
{account.name}
</div>
<div className={s.accountEmail}>
{account.email}
</div>
<div className={s.horizontalSeparator}></div>
<div className={s.shippingInfo}>Shipping Infomation</div>
<div className={s.accountAddress}>
{account.address + `, ${account.state}, ${account.city}, ${account.postalCode}`}
</div>
<div className={s.accountPhoneNumber}>
{account.phoneNumber}
</div>
<div onClick={showEditForm} className={s.editInfoBtn}>Edit</div>
</div>
}
</section>
)
}
export default AccountInfomation

View File

@ -0,0 +1,81 @@
@import '../../../../../../styles/utilities';
.editInfoModal {
.input {
@apply bg-white;
margin-bottom: 1.6rem;
width: 100%;
border: 2px solid #EBEBEB;
border-radius: .8rem;
padding: 1.6rem;
}
.inputDisable {
margin-bottom: 1.6rem;
width: 100%;
border: 2px solid #EBEBEB;
border-radius: .8rem;
padding: 1.6rem;
background-color: #EBEBEB;
color: #CCCCCC;
}
.inputStateWrapper {
@apply bg-white;
margin-bottom: 1.6rem;
margin-right: 1.6rem;
border: 2px solid #EBEBEB;
border-radius: .8rem;
padding: 1.6rem;
.inputState {
@apply bg-white cursor-pointer;
border: white;
}
}
.inputPostalCode {
@apply bg-white;
margin-bottom: 1.6rem;
border: 2px solid #EBEBEB;
border-radius: .8rem;
padding: 1.6rem;
}
.inputPhoneNumber {
@apply bg-white;
margin-bottom: 4rem;
width: 100%;
border: 2px solid #EBEBEB;
border-radius: .8rem;
padding: 1.6rem;
}
.buttons {
@apply flex;
.buttonCancel {
@apply bg-white text-center font-bold custom-border-radius-lg;
color: #141414;
border: 1px solid #141414;
padding: 1.6rem;
margin-right: 1.6rem;
width: 100%;
&:hover {
@apply cursor-pointer;
}
}
.buttonSave {
@apply text-center font-bold custom-border-radius-lg;
background-color: var(--primary);
color: white;
padding: 1.6rem;
width: 100%;
&:hover {
@apply cursor-pointer;
}
}
}
}

View File

@ -0,0 +1,83 @@
import classNames from "classnames"
import React, { useState } from "react"
import s from './EditInfoModal.module.scss'
import {ModalCommon, MenuDropdown} from '../../../../../common'
import {ButtonCommon} from '../../../../../common'
interface EditInfoModalProps {
accountInfo: {name: string, email: string, address: string, state: string, city: string, postalCode: string, phoneNumber: string};
visible: boolean;
closeModal: () => void;
}
const EditInfoModal = ({ accountInfo, visible = false, closeModal }: EditInfoModalProps) => {
const [name, setName] = useState(accountInfo.name);
const [email, setEmail] = useState(accountInfo.email);
const [address, setAddress] = useState(accountInfo.address);
const [state, setState] = useState(accountInfo.state);
const [city, setCity] = useState(accountInfo.city);
const [postalCode, setPostalCode] = useState(accountInfo.postalCode);
const [phoneNumber, setPhoneNumber] = useState(accountInfo.phoneNumber);
function saveInfo() {
console.log("saved !!!");
closeModal();
}
const states = [
{name: "D1", onClick: () => {setState("D1")}},
{name: "D2", onClick: () => {setState("D2")}},
{name: "D3", onClick: () => {setState("D3")}}
]
return (
<ModalCommon onClose={closeModal} visible={visible} title="Edit Infomation">
<section className={s.editInfoModal}>
<div>
<input className={s.input} type="text" name="name" placeholder="Name"
value={name} onChange={e => {setName(e.target.value)}} />
</div>
<div>
<input className={s.inputDisable} type="text" name="email" placeholder="Email"
value={email} onChange={e => {setEmail(e.target.value)}} />
</div>
<div>
<input className={s.input} type="text" name="address" placeholder="Address"
value={address} onChange={e => {setAddress(e.target.value)}}/>
</div>
<div>
<input className={s.input} type="text" name="city" placeholder="City"
value={city} onChange={e => {setCity(e.target.value)}} />
</div>
<div className="flex">
<div className={s.inputStateWrapper}>
<MenuDropdown options={states} isHasArrow={true} >
<input className={s.inputState} type="text" name="state" placeholder="State"
value={state} disabled />
</MenuDropdown>
</div>
<input className={s.inputPostalCode} type="text" name="postalCode" placeholder="Postal code"
value={postalCode} onChange={e => {setPostalCode(e.target.value)}} />
</div>
<div>
<input className={s.inputPhoneNumber} type="text" name="phoneNumber" placeholder="Phone number"
value={phoneNumber} onChange={e => {setPhoneNumber(e.target.value)}} />
</div>
<div className={s.buttons}>
<div onClick={closeModal} className={s.buttonCancel}>Cancel</div>
<div onClick={saveInfo} className={s.buttonSave}>Save</div>
</div>
</section>
</ModalCommon>
)
}
export default EditInfoModal

View File

@ -0,0 +1,16 @@
@import '../../../../../../styles/utilities';
.orderInformation {
.title {
@apply heading-3 font-heading;
margin-top: 1.6rem;
}
.tabs {
margin-top: 3.2rem;
.blank {
margin-bottom: 2.4rem;
}
}
}

View File

@ -0,0 +1,66 @@
import React from "react"
import s from './OrderInformation.module.scss'
import { TabCommon } from '../../../../../common'
import TabPane from 'src/components/common/TabCommon/components/TabPane/TabPane'
import DeliveryItem from '../../../DeliveryItem/DeliveryItem'
interface OrderInformationProps {
waiting: {id: string, products: string[], totalPrice: number}[],
delivering: {id: string, products: string[], totalPrice: number}[],
delivered: {id: string, products: string[], totalPrice: number}[],
// active?: boolean
}
const OrderInformation = ({ waiting, delivering, delivered} : OrderInformationProps) => {
return (
<section className={s.orderInformation}>
{
<div>
<div className={s.title}>Order Information</div>
<div className={s.tabs}>
<TabCommon>
<TabPane tabName={"Wait for Comfirmation"} >
<div className={s.blank}></div>
{
waiting.map((order, i) => {
return (
<DeliveryItem key={order.id} id={order.id} status="waiting" products={order.products} totalPrice={order.totalPrice} />
)
})
}
</TabPane>
<TabPane tabName={"Delivering"}>
<div className={s.blank}></div>
{
delivering.map((order, i) => {
return (
<DeliveryItem key={order.id} id={order.id} status="delivering" products={order.products} totalPrice={order.totalPrice} />
)
})
}
</TabPane>
<TabPane tabName={"Delivered"}>
<div className={s.blank}></div>
{
delivered.map((order, i) => {
return (
<DeliveryItem key={order.id} id={order.id} status="delivered" products={order.products} totalPrice={order.totalPrice} />
)
})
}
</TabPane>
</TabCommon>
</div>
</div>
}
</section>
)
}
export default OrderInformation

View File

@ -0,0 +1,26 @@
@import '../../../../styles/utilities';
.deliveryItem {
@apply flex bg-white items-center custom-border-radius;
margin-bottom: 1.6rem;
border: 1px solid var(--primary)
}
.separator {
border-left: 2px dashed #EBEBEB;
max-height: 9.2rem;
min-height: 8.6rem;
margin-left: .6rem;
margin-right: .6rem;
@screen md {
margin-left: .8rem;
margin-right: .8rem;
}
@screen lg {
margin-left: 2.4rem;
margin-right: 2.4rem;
}
}

View File

@ -0,0 +1,30 @@
import React from "react"
import s from './DeliveryItem.module.scss'
import IdAndStatus from './components/IdAndStatus/IdAndStatus'
import Products from './components/Products/Products'
import TotalPrice from './components/TotalPrice/TotalPrice'
import ReOrder from './components/ReOrder/ReOrder'
interface DeliveryItemProps {
id: string;
status: "waiting" | "delivering" | "delivered";
products: string[];
totalPrice: number;
reOrderLink?: string;
}
const DeliveryItem = ({ id, status, products, totalPrice, reOrderLink } : DeliveryItemProps) => {
return (
<section className={s.deliveryItem}>
<IdAndStatus id={id} status={status} />
<div className={s.separator}></div>
<Products products={products} />
<TotalPrice totalPrice={totalPrice} />
<ReOrder show={status==="delivered" ? true : false} href={reOrderLink} />
</section>
)
}
export default DeliveryItem

View File

@ -0,0 +1,75 @@
@import '../../../../../../styles/utilities';
.idAndStatus {
@apply items-center;
padding: 2.4rem 0 2.4rem 1rem;
@screen md {
padding: 2.4rem 0 2.4rem 1.2rem;
}
@screen lg {
padding: 2.4rem 0 2.4rem 2.4rem;
}
.id {
@apply font-bold;
margin-bottom: .8rem;
}
.deliveryStatus {
@apply font-bold text-white;
font-size: 1.2rem;
line-height: 2rem;
padding: 0 .8rem;
border-radius: 0.5rem;
width: fit-content;
&.waiting {
background-color: #D9A645;
}
&.delivering {
background-color: var(--info-dark);
}
&.delivered {
background-color: var(--primary);
}
}
}@import '../../../../../../styles/utilities';
.idAndStatus {
@apply items-center;
padding: 2.4rem 0 2.4rem 1rem;
@screen md {
padding: 2.4rem 0 2.4rem 1.2rem;
}
@screen lg {
padding: 2.4rem 0 2.4rem 2.4rem;
}
.id {
@apply font-bold;
margin-bottom: .8rem;
}
.deliveryStatus {
@apply font-bold text-white;
font-size: 1.2rem;
line-height: 2rem;
padding: 0 .8rem;
border-radius: 0.5rem;
width: fit-content;
&.waiting {
background-color: #D9A645;
}
&.delivering {
background-color: var(--info-dark);
}
&.delivered {
background-color: var(--primary);
}
}
}

View File

@ -0,0 +1,25 @@
import classNames from "classnames"
import React from "react"
import s from './IdAndStatus.module.scss'
interface IdAndStatusProps {
id?: string;
status: "waiting" | "delivering" | "delivered";
}
const IdAndStatus = ({ id, status="waiting" } : IdAndStatusProps) => {
return (
<div className={s.idAndStatus}>
<div className={s.id}>
{id}
</div>
<div className={classNames(s.deliveryStatus, {
[s[status]]: status
})}> {status}
</div>
</div>
)
}
export default IdAndStatus

View File

@ -0,0 +1,12 @@
@import '../../../../../../styles/utilities';
.products {
margin-top: .8rem;
max-width: 32%;
min-width: none;
@screen lg {
margin-top: 0;
margin-bottom: 0;
}
}

View File

@ -0,0 +1,29 @@
import React from "react"
import s from './Products.module.scss'
interface ProductsProps {
products: string[];
}
const Products = ({ products } : ProductsProps) => {
function toString(products:string[]): string {
let strProducts = "";
products.map((prod, i) => {
if (i === 0) {
strProducts += prod;
} else {
strProducts += `, ${prod}`
}
});
return strProducts;
}
return (
<div className={s.products}>
{toString(products)}
</div>
)
}
export default Products

View File

@ -0,0 +1,27 @@
@import '../../../../../../styles/utilities';
.reOrder {
@apply text-white custom-border-radius hidden font-bold;
padding: .4rem .6rem;
margin-right: 1rem;
background-color: var(--primary);
@screen md {
padding: .4rem .6rem;
margin-right: 1.2rem;
}
@screen lg {
padding: .8rem 1.2rem;
margin-right: 2.4rem;
}
&.show {
@apply block;
}
&:hover {
@apply cursor-pointer;
}
}

View File

@ -0,0 +1,23 @@
import classNames from "classnames"
import React from "react"
import s from './ReOrder.module.scss'
import Link from 'next/link'
interface ReOrderProps {
show: boolean;
href?: string;
}
const ReOrder = ({ show=false, href="#" } : ReOrderProps) => {
return (
<div className={classNames(s.reOrder, {
[s.show]: show
})}>
<Link href={href}>
<a>Re-Order</a>
</Link>
</div>
)
}
export default ReOrder

View File

@ -0,0 +1,26 @@
@import '../../../../../../styles/utilities';
.totalPrice {
margin-left: auto;
margin-right: 1rem;
@screen md {
margin-right: 1.2rem;
}
@screen lg {
margin-right: 2.4rem;
}
.price {
@apply font-bold ;
@screen md {
@apply topline
}
@screen lg {
@apply sub-headline;
}
}
}

View File

@ -0,0 +1,18 @@
import React from "react"
import s from './TotalPrice.module.scss'
interface TotalPriceProps {
totalPrice: number;
}
const TotalPrice = ({ totalPrice } : TotalPriceProps) => {
return (
<section className={s.totalPrice}>
<div className="text-right">Total</div>
<div className={s.price}>Rp {totalPrice}</div>
</section>
)
}
export default TotalPrice

View File

@ -0,0 +1,3 @@
export { default as AccountNavigation } from './AccountNavigation/AccountNavigation'
export { default as DeliveryItem } from './DeliveryItem/DeliveryItem'
export { default as AccountPage } from './AccountPage/AccountPage'

View File

@ -0,0 +1,38 @@
.warpper {
padding: 3.2rem;
min-width: 100%;
@screen lg {
max-width: 56.3rem;
@apply flex justify-between flex-col border-l-2 border-solid border-line;
}
.title {
display: none;
font-weight: bold;
font-size: 2rem;
line-height: 2.8rem;
@screen md {
display: block;
}
}
.list {
min-height: 52.8rem;
}
.bot {
.promo {
// padding: 3.2rem;
@apply bg-gray flex justify-between items-center;
min-height: 6.4rem;
}
.price {
margin-top: 3.2rem;
.line {
@apply flex justify-between items-center text-label;
.total {
font-weight: bold;
font-size: 2rem;
line-height: 2.8rem;
}
}
}
}
}

View File

@ -0,0 +1,46 @@
import React from 'react'
import s from './CheckoutBill.module.scss'
import { CardItemCheckout } from '../../../common'
import { CardItemCheckoutProps } from '../../../common/CardItemCheckout/CardItemCheckout'
import { IconCirclePlus } from 'src/components/icons'
interface CheckoutBillProps {
data: CardItemCheckoutProps[]
}
const CheckoutBill = ({ data }: CheckoutBillProps) => {
return (
<div className={s.warpper}>
<div className = {s.title}>
Your cart ({data.length})
</div>
<div className={s.list}>
{data.map((item) => {
return <CardItemCheckout {...item} key={item.slug} />
})}
</div>
<div className={s.bot}>
<div className={s.promo}>
Apply Promotion Code
<IconCirclePlus />
</div>
<div className={s.price}>
<div className={s.line}>
Subtotal
<div className={s.subTotal}>RP 120.500</div>
</div>
<div className={s.line}>
Shipping
<div className={s.shipping}>Free</div>
</div>
<div className={s.line}>
Estimated Total
<div className={s.total}>RP 120.500</div>
</div>
</div>
</div>
</div>
)
}
export default CheckoutBill

View File

@ -0,0 +1,17 @@
.warpper{
@apply w-full;
padding: 3.2rem;
.title{
margin-bottom: 3.2rem;
@apply flex justify-between items-center;
.viewCart{
margin-right: 5.6rem;
@apply text-primary font-bold;
display: block;
cursor: pointer;
@screen lg {
display: none;
}
}
}
}

View File

@ -0,0 +1,94 @@
import React, { useState } from 'react'
import { Logo } from 'src/components/common'
import CheckoutCollapse from 'src/components/common/CheckoutCollapse/CheckoutCollapse'
import { removeItem } from 'src/utils/funtion.utils'
import { CheckOutForm } from 'src/utils/types.utils'
import s from './CheckoutInfo.module.scss'
import CustomerInfoForm from './components/CustomerInfoForm/CustomerInfoForm'
import PaymentInfoForm from './components/PaymentInfoForm/PaymentInfoForm'
import ShippingInfoForm from './components/ShippingInfoForm/ShippingInfoForm'
interface CheckoutInfoProps {
onViewCart:()=>void
}
const CheckoutInfo = ({onViewCart}: CheckoutInfoProps) => {
const [active, setActive] = useState(1)
const [done, setDone] = useState<number[]>([])
const [info, setInfo] = useState<CheckOutForm>({})
const onEdit = (id:number) => {
setActive(id)
setDone(removeItem<number>(done,id))
}
const onConfirm = (id:number,formInfo:CheckOutForm) => {
if(id+1>formList.length){
console.log({...info,...formInfo})
}else{
if(done.length>0){
for (let i = id+1; i <= formList.length; i++) {
if(!done.includes(i)){
setActive(i)
}
}
}else{
setActive(id+1)
}
setDone([...done,id])
}
setInfo({...info,...formInfo})
}
const getNote = (id:number) => {
switch (id) {
case 1:
return `${info.name}, ${info.email}`
case 2:
return `${info.address}, ${info.state}, ${info.city}, ${info.code}, ${info.phone}, `
default:
return ""
}
}
const formList = [
{
id: 1,
title: 'Customer Information',
form: <CustomerInfoForm onConfirm={onConfirm} id={1}/>,
},
{
id: 2,
title: 'Shipping Information',
form: <ShippingInfoForm onConfirm={onConfirm} id={2}/>,
},
{
id: 3,
title: 'Payment Information',
form: <PaymentInfoForm onConfirm={onConfirm} id={3}/>,
},
]
return (
<div className={s.warpper}>
<div className={s.title}>
<Logo />
<div className={s.viewCart} onClick={onViewCart}>View cart</div>
</div>
{formList.map((item) => {
let note = getNote(item.id)
return <CheckoutCollapse
key={item.title}
id={item.id}
visible={item.id === active}
title={item.title}
onEditClick={onEdit}
isEdit={done.includes(item.id)}
note={note}
>
{item.form}
</CheckoutCollapse>
})}
</div>
)
}
export default CheckoutInfo

View File

@ -0,0 +1,15 @@
.warpper{
.info{
.line{
@apply flex justify-start items-center;
.title{
margin-right: 3.2rem;
min-width: 19.4rem;
@apply text-label;
}
.hightlight{
@apply text-active;
}
}
}
}

View File

@ -0,0 +1,26 @@
import React from 'react'
import s from './BankTransfer.module.scss'
interface BankTransferProps {}
const BankTransfer = ({}: BankTransferProps) => {
return (
<div className={s.warpper}>
<div className={s.info}>
<div className={s.line}>
<div className={s.title}>Account Name:</div>
<div className={s.hightlight}>Duong Dinh Vu</div>
</div>
<div className={s.line}>
<div className={s.title}>Account Number:</div>
<div className={s.hightlight}>1234 1234 1234 1234</div>
</div>
<div className={s.line}>
<div className={s.title}>Bank Name:</div>
<div className={s.hightlight}>Techcombank - HCMC</div>
</div>
</div>
</div>
)
}
export default BankTransfer

View File

@ -0,0 +1,12 @@
@import "../../../../../../styles/utilities";
.warpper{
@apply u-form;
.line{
>div{
width: 50%;
}
}
.checkbox{
margin-top: 1.6rem;
}
}

View File

@ -0,0 +1,27 @@
import React, { useRef } from 'react'
import { CheckboxCommon, Inputcommon } from 'src/components/common'
import { CustomInputCommon } from 'src/utils/type.utils'
import s from "./CreditCardForm.module.scss"
interface CreditCardFormProps {
}
const CreditCardForm = ({}: CreditCardFormProps) => {
const cardNumberRef = useRef<CustomInputCommon>(null)
const dateRef = useRef<CustomInputCommon>(null)
const cvsRef = useRef<CustomInputCommon>(null)
return (
<div className={s.warpper}>
<div className={s.body}>
<Inputcommon type="text" placeholder="Cârd Number" ref={cardNumberRef} />
<div className={s.line}>
<Inputcommon type="text" placeholder="MM/YY" ref={dateRef} />
<Inputcommon type="text" placeholder="CVS" ref={cvsRef} />
</div>
</div>
<div className={s.checkbox}><CheckboxCommon/></div>
</div>
)
}
export default CreditCardForm

View File

@ -0,0 +1,15 @@
@import "../../../../../../styles/utilities";
.warpper{
@apply u-form;
@screen md {
padding: 0 5.6rem;
}
.bottom{
margin-top: 2.4rem;
@apply flex justify-between items-center;
.note{
font-size: 1.2rem;
line-height: 2rem;
}
}
}

View File

@ -0,0 +1,54 @@
import Link from 'next/link'
import React, { useRef } from 'react'
import { ButtonCommon, Inputcommon } from 'src/components/common'
import InputCommon from 'src/components/common/InputCommon/InputCommon'
import { CheckOutForm } from 'src/utils/types.utils'
import s from './CustomerInfoForm.module.scss'
interface CustomerInfoFormProps {
onConfirm?: (id: number, formInfo: CheckOutForm) => void
id: number
}
const CustomerInfoForm = ({ id, onConfirm }: CustomerInfoFormProps) => {
const nameRef = useRef<React.ElementRef<typeof InputCommon>>(null)
const emailRef = useRef<React.ElementRef<typeof InputCommon>>(null)
const handleConfirmClick = () => {
onConfirm &&
onConfirm(id, {
name: nameRef?.current?.getValue().toString(),
email: emailRef.current?.getValue().toString(),
})
}
return (
<div className={s.warpper}>
<div className={s.body}>
<Inputcommon type="text" placeholder="Full Name" ref={nameRef} />
<Inputcommon type="text" placeholder="Email Address" ref={emailRef} />
</div>
<div className={s.bottom}>
<div className={s.note}>
By clicking continue you agree to Casper's{' '}
{
<Link href="#">
<strong>terms and conditions</strong>
</Link>
}{' '}
and{' '}
{
<Link href="#">
<strong>privacy policy </strong>
</Link>
}
.
</div>
<ButtonCommon onClick={handleConfirmClick}>
Continue to Shipping
</ButtonCommon>
</div>
</div>
)
}
export default CustomerInfoForm

View File

@ -0,0 +1,16 @@
.wrapper{
@screen md {
padding: 0 5.6rem;
}
.inner{
padding: 4rem 0;
}
.bottom{
margin-top: 2.4rem;
@apply flex justify-between items-center;
.note{
font-size: 1.2rem;
line-height: 2rem;
}
}
}

View File

@ -0,0 +1,55 @@
import React from 'react'
import { ButtonCommon, TabCommon, TabPane } from 'src/components/common'
import { CheckOutForm } from 'src/utils/types.utils'
import BankTransfer from '../BankTransfer/BankTransfer'
import Link from 'next/link'
import s from './PaymentInfoForm.module.scss'
import CreditCardForm from '../CreditCardForm/CreditCardForm'
interface PaymentInfoFormProps {
onConfirm?: (id: number, formInfo: CheckOutForm) => void
id: number
}
const PaymentInfoForm = ({onConfirm,id}: PaymentInfoFormProps) => {
const handleConfirmClick = () => {
onConfirm && onConfirm(id,{})
}
return (
<div className={s.wrapper}>
<TabCommon>
<TabPane tabName="Bank Transfer">
<div className={s.inner}><BankTransfer /></div>
</TabPane>
<TabPane tabName="Ewallet">
<div className={s.inner}></div>
</TabPane>
<TabPane tabName="Credit Card">
<div className={s.inner}><CreditCardForm /></div>
</TabPane>
</TabCommon>
<div className={s.bottom}>
<div className={s.note}>
By clicking continue you agree to Casper's{' '}
{
<Link href="#">
<strong>terms and conditions</strong>
</Link>
}{' '}
and{' '}
{
<Link href="#">
<strong>privacy policy </strong>
</Link>
}
.
</div>
<ButtonCommon onClick={handleConfirmClick}>
Submit Order
</ButtonCommon>
</div>
</div>
)
}
export default PaymentInfoForm

View File

@ -0,0 +1,39 @@
@import "../../../../../../styles/utilities";
.warpper{
@apply u-form;
@screen md {
padding: 0 5.6rem;
}
.bottom{
margin-top: 2.4rem;
@apply flex justify-between items-center;
.note{
font-size: 1.2rem;
line-height: 2rem;
}
}
.line{
>div{
width: 50%;
}
}
.method{
width: 100%;
height: 5.6rem;
padding: 1.6rem;
border-radius: 0.8rem;
@apply flex justify-between items-center border border-solid border-line bg-gray;
.left{
@apply flex;
.name{
margin-left: 1.6rem;
color: var(--text-active);
}
}
.price{
font-weight: bold;
color: var(--text-active);
}
}
}

View File

@ -0,0 +1,97 @@
import React, { useRef } from 'react'
import { ButtonCommon, Inputcommon, SelectCommon } from 'src/components/common'
import s from './ShippingInfoForm.module.scss'
import Link from 'next/link'
import { CustomInputCommon } from 'src/utils/type.utils'
import { Shipping } from 'src/components/icons'
import { CheckOutForm } from 'src/utils/types.utils'
interface ShippingInfoFormProps {
onConfirm?: (id:number,formInfo:CheckOutForm)=>void
id:number
}
const option = [
{
name: 'Hồ Chí Minh',
},
{
name: 'Hà Nội',
},
]
const ShippingInfoForm = ({onConfirm,id}: ShippingInfoFormProps) => {
const addressRef = useRef<CustomInputCommon>(null)
const cityRef = useRef<CustomInputCommon>(null)
const stateRef = useRef<CustomInputCommon>(null)
const codeRef = useRef<CustomInputCommon>(null)
const phoneRef = useRef<CustomInputCommon>(null)
const handleConfirmClick = () => {
onConfirm && onConfirm(id,{
address: addressRef?.current?.getValue().toString(),
city: cityRef.current?.getValue().toString(),
state: stateRef?.current?.getValue().toString(),
code: Number(codeRef.current?.getValue()),
phone: Number(phoneRef?.current?.getValue()),
})
}
return (
<div className={s.warpper}>
<div className={s.body}>
<Inputcommon
type="text"
placeholder="Street Address"
ref={addressRef}
/>
<Inputcommon type="text" placeholder="City" ref={cityRef} />
<div className={s.line}>
<SelectCommon option={option} type="custom" size="large">State</SelectCommon>
<Inputcommon type="text" placeholder="Zip Code" ref={codeRef} />
</div>
<Inputcommon
type="text"
placeholder="Phone (delivery contact)"
ref={phoneRef}
/>
<div className={s.method}>
<div className={s.left}>
<div className={s.icon}>
<Shipping/>
</div>
<div className={s.name}>
Standard Delivery Method
</div>
</div>
<div className={s.right}>
<div className={s.price}>
Free
</div>
</div>
</div>
</div>
<div className={s.bottom}>
<div className={s.note}>
By clicking continue you agree to Casper's{' '}
{
<Link href="#">
<strong>terms and conditions</strong>
</Link>
}{' '}
and{' '}
{
<Link href="#">
<strong>privacy policy </strong>
</Link>
}
.
</div>
<ButtonCommon onClick={handleConfirmClick}>
Continue to Payment
</ButtonCommon>
</div>
</div>
)
}
export default ShippingInfoForm

View File

@ -0,0 +1,56 @@
@import "../../../../styles/utilities";
.warrper{
@apply flex;
.right{
display: none;
@screen lg {
display: block;
min-width: 45rem;
}
@screen xl {
min-width: 56.3rem;
}
}
.left{
@apply w-full;
}
.mobile{
@apply hidden;
&.isShow{
@apply block;
@screen lg {
@apply hidden;
}
}
.modal{
background: rgba(0, 0, 0, 0.5);
position: fixed;
left: 0;
top: 0;
width: 100%;
height: 100%;
z-index: 10000;
.content{
@apply spacing-horizontal;
margin-top: 3rem;
padding-top: 6.4rem ;
padding-bottom: 5rem;
background-color: white;
overflow: auto;
height: 100%;
border-radius: 2.4rem 2.4rem 0 0;
.head{
@apply flex justify-between;
h3{
@apply heading-3 font-bold;
color:var(--text-base);
}
}
button{
margin-top: 2rem;
width: 100%;
}
}
}
}
}

View File

@ -0,0 +1,37 @@
import classNames from 'classnames'
import React, { useState } from 'react'
import IconHide from 'src/components/icons/IconHide'
import { CHECKOUT_BILL_DATA } from 'src/utils/demo-data'
import { CheckoutBill, CheckoutInfo } from '..'
import s from "./CheckoutPage.module.scss"
interface CheckoutPageProps {
}
const CheckoutPage = ({}: CheckoutPageProps) => {
const [isShow, setIsShow] = useState(false)
const onClose = () => {
setIsShow(false)
}
const onViewCart =() => {
setIsShow(true)
}
return (
<div className={s.warrper}>
<div className={s.left}><CheckoutInfo onViewCart = {onViewCart}/></div>
<div className={s.right}><CheckoutBill data={CHECKOUT_BILL_DATA}/></div>
<div className={classNames({ [s.mobile] :true,[s.isShow]: isShow})}>
<div className={s.modal}>
<div className={s.content}>
<div className={s.head}>
<h3>Your Cart({CHECKOUT_BILL_DATA.length})</h3>
<div onClick={onClose}><IconHide/></div>
</div>
<CheckoutBill data={CHECKOUT_BILL_DATA}/>
</div>
</div>
</div>
</div>
)
}
export default CheckoutPage

View File

@ -0,0 +1,3 @@
export { default as CheckoutInfo } from './CheckoutInfo/CheckoutInfo'
export { default as CheckoutPage } from './CheckoutPage/CheckoutPage'
export { default as CheckoutBill } from './CheckoutBill/CheckoutBill'

View File

@ -0,0 +1,8 @@
@import "../../../../styles/_utilities";
.productListBanner{
@apply spacing-horizontal;
@screen md {
padding:0 3.2rem;
}
}

View File

@ -0,0 +1,27 @@
import React from 'react'
import { Banner } from 'src/components/common'
import BannerRight from './assets/bannerrecipes.png'
import s from './ProductListBanner.module.scss'
interface Props {
}
const ProductListBanner = ({ }: Props) => {
return (
<div className={s.productListBanner}>
<Banner
data={
[{
title: "Save 15% on your first order",
subtitle: "Last call! Shop deep deals on 100+ bulk picks while you can.",
imgLink: BannerRight.src,
size: "large",
},
]
}
/>
</div >
)
}
export default ProductListBanner

Binary file not shown.

After

Width:  |  Height:  |  Size: 780 KiB

View File

@ -0,0 +1,88 @@
@import "../../../../styles/_utilities";
.warpper {
@apply spacing-horizontal;
@screen md{
padding:0 3.2rem;
padding-bottom:5.6rem;
}
.breadcrumb{
padding:1rem 0;
}
.main{
@screen md {
@apply flex;
}
.categories{
@apply hidden;
@screen md {
@apply hidden;
}
@screen xl{
@apply block;
width:25%;
}
}
.list{
@screen md {
@apply flex justify-between flex-wrap w-full;
margin: 1rem 0;
}
@screen xl {
width:75%;
}
.inner{
@screen md {
@apply flex flex-col items-center justify-center;
}
.boxItem {
@screen md {
@apply flex justify-between flex-wrap;
margin: 1rem 0;
}
.item {
@screen md {
width: calc(97% / 2);
margin-top:1rem;
}
@screen lg{
width: calc(97% / 3);
margin-top:1rem;
}
}
}
}
.boxSelect{
@apply w-auto;
// padding: 2.5rem 0;
display: none;
@screen xl {
@apply block;
width: auto;
padding:0;
}
.categorySelectCate{
@screen xl {
@apply hidden;
}
}
label{
@apply font-bold topline ;
color:var(--text-active);
@screen xl {
@apply hidden;
}
}
.select{
margin-top: 1rem;
}
}
}
}
}

View File

@ -0,0 +1,66 @@
import React from 'react'
import { HeadingCommon, ProductList, SelectCommon } from 'src/components/common'
import BreadcrumbCommon from 'src/components/common/BreadcrumbCommon/BreadcrumbCommon'
import MenuNavigation from 'src/components/common/MenuNavigation/MenuNavigation'
import { BRAND, CATEGORY, FEATURED} from 'src/utils/constanst.utils'
import { PRODUCT_DATA_TEST_PAGE } from 'src/utils/demo-data'
import s from './ProductListFilter.module.scss'
interface ProductListFilterProps {}
const BREADCRUMB = [
{
name: 'Products',
link: `#`,
},
]
const OPTIONSLECT = [
{
name: 'Most Viewed',
value: 'most-viewed',
},
{
name: 'Lastest Products',
value: 'lastest-products',
},
{
name: 'Recent Products',
value: 'recent-products',
},
]
const onModalClose = () => {
}
const ProductListFilter = (props: ProductListFilterProps) => {
return (
<div className={s.warpper}>
<div className={s.breadcrumb}>
<BreadcrumbCommon crumbs={BREADCRUMB} />
</div>
<div className={s.main}>
<div className={s.categories}>
<MenuNavigation categories={CATEGORY} heading="Categories" />
<MenuNavigation categories={BRAND} heading="Brands" />
<MenuNavigation categories={FEATURED} heading="featured" />
</div>
<div className={s.list}>
<HeadingCommon align="left">SPECIAL RECIPES</HeadingCommon>
<div className={s.boxSelect}>
<div className={s.categorySelectSort}>
<div className={s.select}>
<SelectCommon option={OPTIONSLECT} placeholder="Sort By" />
</div>
</div>
</div>
<ProductList data={PRODUCT_DATA_TEST_PAGE} />
</div>
</div>
</div>
)
}
export default ProductListFilter

View File

@ -53,4 +53,67 @@ export const KEY = {
export const OPTION_ALL = 'all';
export const DEFAULT_PAGE_SIZE=20;
export const CATEGORY = [
{
name: 'All',
link: `${ROUTE.PRODUCTS}/?${QUERY_KEY.CATEGORY}=${OPTION_ALL}`,
},
{
name: 'Veggie',
link: `${ROUTE.PRODUCTS}/?${QUERY_KEY.CATEGORY}=veggie`,
},
{
name: 'Seafood',
link: `${ROUTE.PRODUCTS}/?${QUERY_KEY.CATEGORY}=seafood`,
},
{
name: 'Frozen',
link: `${ROUTE.PRODUCTS}/?${QUERY_KEY.CATEGORY}=frozen`,
},
{
name: 'Coffee Bean',
link: `${ROUTE.PRODUCTS}/?${QUERY_KEY.CATEGORY}=coffee_bean`,
},
{
name: 'Sauce',
link: `${ROUTE.PRODUCTS}/?${QUERY_KEY.CATEGORY}=sauce`,
},
]
export const BRAND = [
{
name: 'Maggi',
link: `${ROUTE.PRODUCTS}/?${QUERY_KEY.BRAND}=maggi`,
},
{
name: 'Chomilex',
link: `${ROUTE.PRODUCTS}/?${QUERY_KEY.BRAND}=chomilex`,
},
{
name: 'Chinsu',
link: `${ROUTE.PRODUCTS}/?${QUERY_KEY.BRAND}=chinsu`,
},
]
export const FEATURED = [
{
name: 'Best Sellers',
link: `${ROUTE.PRODUCTS}/?${QUERY_KEY.FEATURED}=best_sellers`,
},
{
name: 'Sales',
link: `${ROUTE.PRODUCTS}/?${QUERY_KEY.FEATURED}=sales`,
},
{
name: 'New Item',
link: `${ROUTE.PRODUCTS}/?${QUERY_KEY.FEATURED}=new_item`,
},
{
name: 'Viewed',
link: `${ROUTE.PRODUCTS}/?${QUERY_KEY.FEATURED}=viewed`,
},
]
export const DEFAULT_BLOG_PAGE_SIZE=6;

View File

@ -1,4 +1,5 @@
import { BlogCardProps } from "src/components/common/CardBlog/CardBlog"
import { CardItemCheckoutProps } from "src/components/common/CardItemCheckout/CardItemCheckout"
import { RecipeCardProps } from "src/components/common/RecipeCard/RecipeCard"
export const PRODUCT_DATA_TEST = [
@ -260,4 +261,34 @@ export const BLOGS_DATA_TEST: BlogCardProps[] = [
imageSrc: 'https://user-images.githubusercontent.com/76729908/132159262-f28a9fb9-4852-47e6-80b5-d600521b548a.png',
slug:"the-best-recipe-of-beef-noodle-soup"
},
];
];
export const CHECKOUT_BILL_DATA:CardItemCheckoutProps[] = [
{
name: 'Tomato',
slug: "tomato",
weight: '250g',
category: 'VEGGIE',
price: 'Rp 27.500',
imageSrc: "https://user-images.githubusercontent.com/76729908/131646227-b5705e64-3b45-47a3-9433-9f4b5ee8d40c.png",
quantity:10
},
{
name: 'Carrot',
slug: "carrot",
weight: '250g',
category: 'VEGGIE',
price: 'Rp 27.500',
imageSrc: "https://user-images.githubusercontent.com/76729908/131646217-23b86160-45c9-4845-8dcc-b3e1a4483edd.png",
quantity:1
},
{
name: 'Salad',
slug:"salad",
weight: '250g',
category: 'VEGGIE',
price: 'Rp 27.500',
imageSrc: "https://user-images.githubusercontent.com/76729908/131646221-aaa1d48d-bb80-470f-9400-ae2aa47285b6.png",
quantity:2
},
]
export const PRODUCT_DATA_TEST_PAGE = [...PRODUCT_DATA_TEST, ...PRODUCT_DATA_TEST, ...PRODUCT_DATA_TEST, ...PRODUCT_DATA_TEST, ...PRODUCT_DATA_TEST]

View File

@ -1,3 +1,11 @@
export function isMobile() {
return window.innerWidth <= 768
}
export function removeItem<T>(arr: Array<T>, value: T): Array<T> {
const index = arr.indexOf(value);
if (index > -1) {
arr.splice(index, 1);
}
return [...arr];
}

View File

@ -32,4 +32,22 @@ export interface BlogProps {
imageSrc: string
}
export type MouseAndTouchEvent = MouseEvent | TouchEvent
export interface CheckOutForm {
name?: string
email?:string
address?: string
city?:string
state?:string
code?:number
phone?:number
method?:string
shipping_fee?:number
}
export type MouseAndTouchEvent = MouseEvent | TouchEvent
export type filterContextType = {
visible: boolean;
open: () => void;
close: () => void;
};