Merge pull request #49 from KieIO/m7-lytran

M7 Ly: Fix UI, update from common
This commit is contained in:
lytrankieio123 2021-09-14 14:48:12 +07:00 committed by GitHub
commit 2cd36b2d66
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
62 changed files with 1519 additions and 327 deletions

View File

@ -26,6 +26,7 @@ module.exports = withCommerceConfig({
images: {
// todo: replace domains for images
domains: ['user-images.githubusercontent.com'],
minimumCacheTTL: 60,
},
i18n: {
locales: ['en-US', 'es'],

View File

@ -1,7 +1,7 @@
import { Layout, RecipeDetail } from 'src/components/common'
import { ProductInfoDetail, RecommendedRecipes, ReleventProducts, ViewedProducts } from 'src/components/modules/product-detail'
import { INGREDIENT_DATA_TEST, RECIPE_DATA_TEST } from 'src/utils/demo-data'
import { Layout, RecipeDetail, RecommendedRecipes, RelevantBlogPosts } from 'src/components/common'
import { ProductInfoDetail, ReleventProducts, ViewedProducts } from 'src/components/modules/product-detail'
import { BLOGS_DATA_TEST, INGREDIENT_DATA_TEST, RECIPE_DATA_TEST } from 'src/utils/demo-data'
export default function Slug() {
return <>
@ -10,6 +10,7 @@ export default function Slug() {
<RecommendedRecipes data={RECIPE_DATA_TEST} />
<ReleventProducts />
<ViewedProducts />
<RelevantBlogPosts data={BLOGS_DATA_TEST} title="relevent blog posts"/>
</>
}

12
pages/recipe/[slug].tsx Normal file
View File

@ -0,0 +1,12 @@
import { Layout, RecipeDetail, RecommendedRecipes } from 'src/components/common'
import { INGREDIENT_DATA_TEST, RECIPE_DATA_TEST } from 'src/utils/demo-data'
export default function Slug() {
return <div className="page-recipe-detail">
<RecipeDetail ingredients={INGREDIENT_DATA_TEST} />
<RecommendedRecipes data={RECIPE_DATA_TEST} />
</div>
}
Slug.Layout = Layout

View File

@ -78,6 +78,11 @@ export default function Test() {
}
return (
<>
<div className="shape-common-border">
<div className="inner">
Lorem ipsum dolor sit amet.
</div>
</div>
{/* <BlogDetailPage /> */}
{/* <RecipeListPage/> */}

View File

@ -11,13 +11,24 @@ const option = {
breakpoints: {}
}
const Banner = memo(({ data }: Props) => {
if (data.length === 1) {
const item = data[0]
return <BannerItem
title={item.title}
imgLink={item.imgLink}
subtitle={item.subtitle}
buttonLabel={item.buttonLabel}
linkButton={item.linkButton}
size={item.size}
/>
}
return (
<CarouselCommon<BannerItemProps>
data={data}
itemKey="banner"
Component={BannerItem}
option={option}
isDot = {true}
isDot={true}
/>
)
})

View File

@ -1,16 +1,15 @@
@import "../../../../styles/utilities";
.bannerItem {
@apply bg-primary-light custom-border-radius-lg overflow-hidden;
@screen md {
border: 1px solid var(--primary);
}
@apply bg-primary-light shape-common-lg overflow-hidden;
padding: 0;
&.large {
margin-bottom: 2.8rem;
.inner {
background-size: cover;
background-position: center bottom;
@screen xl {
@apply bg-right-bottom;
background-size: unset;
background-position: right bottom;
}
}
}
@ -19,13 +18,10 @@
background-size: 90%;
background-position: right -500% bottom 0%;
.content {
background-image: linear-gradient(
to right,
rgb(227, 242, 233, 0.9),
rgb(227, 242, 233, 0.5) 80%,
rgb(227, 242, 233, 0)
);
padding: 1.6rem;
background-image: linear-gradient(to right, rgb(227, 242, 233) 63%, rgb(227, 242, 233, 0));
padding: 2rem;
padding-bottom: 4rem;
max-width: 37rem;
@screen md {
max-width: 49.6rem;
@ -38,9 +34,6 @@
}
.subHeading {
@apply sub-headline;
@screen md {
@apply caption;
}
}
}

View File

@ -1,16 +1,18 @@
@import "../../../styles/utilities";
.buttonCommon {
@apply custom-border-radius bg-primary transition-all duration-200 text-white font-bold;
@apply shape-common;
.inner {
padding: 1rem 2rem;
@apply bg-primary transition-all duration-200 text-white font-bold;
display: flex;
justify-content: center;
align-items: center;
padding: 1rem 2rem;
@screen md {
padding: 0.8rem 1.6rem;
padding: 1.6rem 1.6rem;
}
@screen lg {
padding: 0.8rem 3.2rem;
padding: 1.6rem 3.2rem;
}
&:disabled {
filter: brightness(0.9);
@ -30,9 +32,10 @@
&:focus-visible {
outline: 2px solid var(--text-active);
}
}
&.loading {
&::before {
.inner {
&::after {
content: "";
border-radius: 50%;
width: 1.6rem;
@ -44,45 +47,66 @@
margin-right: 0.8rem;
}
}
}
&.light {
@apply text-base bg-white;
border: 1px solid var(--text-active);
&.loading {
@apply shape-common-border;
&::before {
background-color: var(--text-active);
}
.inner {
@apply text-base bg-white;
}
&.loading {
.inner {
&::after {
border-top-color: var(--primary);
}
}
}
}
&.lightBorderNone {
.inner {
@apply bg-white text-primary;
}
&.loading {
&::before {
.inner::after {
border-top-color: var(--primary);
}
}
}
&.ghost {
@apply shape-common-border;
.inner {
@apply bg-white text-primary;
border: 1px solid var(--primary);
&.loading {
}
&::before {
background-color: var(--primary);
}
&.loading {
.inner::after {
border-top-color: var(--text-active);
}
}
}
&.onlyIcon {
padding: 0.8rem;
.inner {
padding: 1rem;
@screen md {
padding: 1.6rem;
}
.icon {
margin: 0;
}
}
}
&.large {
padding: 1rem 1.5rem;
.inner {
padding: 1rem 1.6rem;
&.onlyIcon {
padding: 1rem;
}
@ -95,8 +119,9 @@
@screen lg {
padding: 1.6rem 4.8rem;
}
}
&.loading {
&::before {
.inner::before {
width: 2.4rem;
height: 2.4rem;
}
@ -104,18 +129,23 @@
}
&.preserve {
.inner {
flex-direction: row-reverse;
.icon {
margin: 0 0 0 1.6rem;
}
}
}
.icon {
margin: 0 1.6rem 0 0;
svg path {
svg {
height: 2rem;
path {
fill: currentColor;
}
}
}
}
@keyframes spin {

View File

@ -27,10 +27,12 @@ const ButtonCommon = memo(({ type = 'primary', size = 'default', loading = false
disabled={disabled}
onClick={onClick}
>
<div className={s.inner}>
{
icon && <span className={s.icon}>{icon}</span>
}
<span className={s.label}>{children}</span>
</div>
</button>
)
})

View File

@ -15,6 +15,7 @@ export interface CarouselCommonProps<T> {
option: TOptionsEvents
keenClassname?: string
isPadding?: boolean
defaultComponentProps?: object
}
const CarouselCommon = <T,>({
@ -25,6 +26,7 @@ const CarouselCommon = <T,>({
isPadding = false,
isArrow = true,
isDot = false,
defaultComponentProps,
option: { slideChanged,slidesPerView, ...sliderOption },
}: CarouselCommonProps<T>) => {
const [currentSlide, setCurrentSlide] = React.useState(0)
@ -68,11 +70,14 @@ const CarouselCommon = <T,>({
[s.isPadding]: isPadding,
})}
>
{data?.map((props, index) => (
{data?.map((props, index) => {
const allProps = defaultComponentProps ? { ...props, ...defaultComponentProps } : props
return (
<div className="keen-slider__slide" key={`${itemKey}-${index}`}>
<Component {...props} />
<Component {...allProps} />
</div>
))}
)
})}
</div>
{slider && isArrow && (
<>

View File

@ -4,9 +4,6 @@
.cartDrawer {
@apply flex flex-col h-full;
.body {
@apply overflow-y-auto overflow-x-hidden h-full custom-scroll;
}
.bottom {
padding-top: 1.6rem;
@apply flex flex-col justify-center overflow-y-auto overflow-x-hidden h-full custom-scroll;
}
}

View File

@ -23,7 +23,7 @@ const CartDrawer = ({ visible, onClose }: Props) => {
<ProductsInCart data={PRODUCT_CART_DATA_TEST}/>
<CartRecommendation />
</div>
<div className={s.bottom}>
<div>
<CartMessage />
<CartCheckoutButton />
</div>

View File

@ -1,5 +1,7 @@
.cartCheckoutButton {
display: block;
padding: 1.6rem;
width: 100%;
button {
width: 100%;
}

View File

@ -1,12 +1,16 @@
import React, { memo } from 'react';
import { ButtonCommon } from 'src/components/common';
import s from './CartCheckoutButton.module.scss';
import Link from 'next/link'
import { ROUTE } from 'src/utils/constanst.utils';
const CartCheckoutButton = memo(() => {
return (
<div className={s.cartCheckoutButton}>
<Link href={ROUTE.CHECKOUT}>
<a className={s.cartCheckoutButton}>
<ButtonCommon size='large'>Check out - Rp 120.500</ButtonCommon>
</div>
</a>
</Link>
)
})

View File

@ -1,7 +1,8 @@
@import '../../../../../styles/utilities';
@import "../../../../../styles/utilities";
.cartRecommendation {
@apply w-full bg-background-gray;
padding-bottom: 5.4rem;
.top {
@apply flex justify-between items-center;
padding: 1.6rem;
@ -12,14 +13,10 @@
.productCardWarpper {
padding-left: 1.6rem;
:global(.customArrow) {
@apply bg-line;
@screen lg {
&:global(.leftArrow) {
left: calc(-6.4rem - 2rem);
}
@apply bg-line shadow-md;
opacity: 0.8;
&:global(.rightArrow) {
right: calc(-6.4rem - 2rem);
}
right: 1rem;
}
}
}

View File

@ -7,14 +7,20 @@ import { PRODUCT_DATA_TEST } from 'src/utils/demo-data';
import s from './CartRecommendation.module.scss';
const option: TOptionsEvents = {
slidesPerView: 2,
slidesPerView: 1.5,
mode: 'free',
breakpoints: {
'(min-width: 640px)': {
slidesPerView: 1,
slidesPerView: 1.5,
},
'(min-width: 768px)': {
slidesPerView: 2.5,
},
'(min-width: 1008px)': {
slidesPerView: 2.2,
},
'(min-width: 1440px)': {
slidesPerView: 2.5,
}
},
}
@ -34,6 +40,7 @@ const CartRecommendation = () => {
Component={ProductCard}
itemKey="cart-recommendation"
option={option}
defaultComponentProps={{ isSingleButton: true }}
/>
</div>
</div>

View File

@ -41,6 +41,8 @@ const Header = memo(({ toggleFilter }: props) => {
} else {
setIsFullHeader(true)
}
} else {
setIsFullHeader(true)
}
}
window.addEventListener('scroll', handleScroll)
@ -51,31 +53,29 @@ const Header = memo(({ toggleFilter }: props) => {
return (
<>
<div className={classNames({
[s.headerSticky]: true,
[s.show]: !isFullHeader
})}>
<HeaderMenu
toggleFilter={toggleFilter}
toggleCart={toggleCart}
openModalAuthen={openModalAuthen}
openModalInfo={openModalInfo} />
</div>
<header ref={headeFullRef} className={classNames({ [s.header]: true, [s.full]: isFullHeader })}>
<HeaderHighLight />
<div className={s.menu}>
<HeaderMenu
toggleFilter={toggleFilter}
toggleCart={toggleCart}
isFull={isFullHeader}
openModalAuthen={openModalAuthen}
openModalInfo={openModalInfo} />
<HeaderSubMenu />
</div>
</header>
<div className={classNames({
[s.headerSticky]: true,
[s.show]: !isFullHeader
})}>
<HeaderMenu isFull={isFullHeader}
toggleFilter={toggleFilter}
toggleCart={toggleCart}
openModalAuthen={openModalAuthen}
openModalInfo={openModalInfo} />
</div>
<HeaderSubMenuMobile />
<ModalAuthenticate visible={visibleModalAuthen} closeModal={closeModalAuthen} />
<ModalCreateUserInfo demoVisible={visibleModalInfo} demoCloseModal={closeModalInfo} />

View File

@ -7,6 +7,7 @@
padding-top: 0.8rem;
padding-bottom: 0.8rem;
color: var(--white);
margin-bottom: 1.6rem;
.menu {
@apply flex items-center list-none;
padding: 0.8rem 0;

View File

@ -7,10 +7,6 @@
@apply flex justify-between items-center;
padding-top: 0.8rem;
padding-bottom: 0.8rem;
&.full {
padding-top: 2.4rem;
padding-bottom: 2.4rem;
}
}
.left {
.top {
@ -47,6 +43,19 @@
}
}
}
.btnCart {
all: unset;
cursor: pointer;
&:focus-visible {
outline: 2px solid #000;
}
&:hover {
svg path {
fill: var(--primary);
opacity: 0.8;
}
}
}
.menu {
@apply hidden;
@screen md {
@ -70,19 +79,6 @@
}
}
}
.btnCart {
all: unset;
cursor: pointer;
&:focus-visible {
outline: 2px solid #000;
}
&:hover {
svg path {
fill: var(--primary);
opacity: 0.8;
}
}
}
}
}
@screen xl {

View File

@ -10,16 +10,15 @@ import s from './HeaderMenu.module.scss'
import { useRouter } from 'next/router'
interface Props {
children?: any,
isFull: boolean,
openModalAuthen: () => void,
openModalInfo: () => void,
toggleFilter:() => void,
toggleCart:() => void,
toggleFilter: () => void,
toggleCart: () => void,
}
const FILTER_PAGE = [ROUTE.HOME,ROUTE.PRODUCTS]
const FILTER_PAGE = [ROUTE.HOME, ROUTE.PRODUCTS]
const HeaderMenu = memo(({ isFull, openModalAuthen, openModalInfo, toggleFilter, toggleCart }: Props) => {
const HeaderMenu = memo(({ openModalAuthen, openModalInfo, toggleFilter, toggleCart }: Props) => {
const router = useRouter()
const optionMenu = useMemo(() => [
{
@ -41,19 +40,19 @@ const HeaderMenu = memo(({ isFull, openModalAuthen, openModalInfo, toggleFilter,
], [openModalAuthen])
return (
<section className={classNames({ [s.headerMenu]: true, [s.full]: isFull })}>
<section className={s.headerMenu}>
<div className={s.left}>
<div className={s.top}>
<Logo/>
<Logo />
<div className={s.iconGroup}>
{
FILTER_PAGE.includes(router.pathname) && (
<button className={s.iconFilter} onClick={toggleFilter}>
<IconFilter/>
<IconFilter />
</button>
)
}
<button className={s.iconCart}>
<button className={`${s.iconCart} ${s.btnCart}`} onClick={toggleCart}>
<IconBuy />
</button>
</div>
@ -91,7 +90,7 @@ const HeaderMenu = memo(({ isFull, openModalAuthen, openModalInfo, toggleFilter,
FILTER_PAGE.includes(router.pathname) && (
<li className={s.iconFilterDesk}>
<button className={s.iconFilter} onClick={toggleFilter}>
<IconFilter/>
<IconFilter />
</button>
</li>
)

View File

@ -5,6 +5,7 @@
@screen md {
@apply block;
padding-bottom: 2.4rem;
margin-top: 1.6rem;
transform: none;
height: unset;
@screen lg {

View File

@ -4,30 +4,32 @@
@apply fixed w-full bg-white;
bottom: 0;
left: 0;
padding: 2rem 1rem;
padding: 0 1rem 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;
@apply grid grid-cols-5;
li {
a {
@apply transition-all duration-200 no-underline;
&:hover {
color: var(--primary);
}
-webkit-tap-highlight-color: unset;
}
.menuItem {
@apply flex flex-col justify-center items-center sm-label;
padding-top: 1rem;
border-top: 2px solid transparent;
.icon {
position: relative;
margin-bottom: 0.5rem;
height: 3rem;
svg path {
fill: currentColor;
}
}
&.active {
@apply text-primary;
border-top: 2px solid var(--primary);
}
&.dot {
.icon {

View File

@ -2,7 +2,7 @@ import classNames from 'classnames'
import Link from 'next/link'
import { useRouter } from 'next/router'
import { memo } from 'react'
import { IconHeart, IconHome, IconShopping, IconUser } from 'src/components/icons'
import { IconHeart, IconHome, IconNoti, IconShopping, IconUser } from 'src/components/icons'
import { ACCOUNT_TAB, QUERY_KEY, ROUTE } from 'src/utils/constanst.utils'
import s from './HeaderSubMenuMobile.module.scss'
@ -11,7 +11,6 @@ const OPTION_MENU = [
link: ROUTE.HOME,
name: 'Home',
icon: <IconHome />,
isMarked: true,
},
{
link: ROUTE.PRODUCTS,
@ -25,6 +24,12 @@ const OPTION_MENU = [
icon: <IconHeart />,
isMarked: false,
},
{
link: `${ROUTE.ACCOUNT}?${QUERY_KEY.TAB}=${ACCOUNT_TAB.NOTIFICATION}`,
name: 'Notifications',
icon: <IconNoti />,
isMarked: true,
},
{
link: ROUTE.ACCOUNT,
name: 'Account',

View File

@ -6,16 +6,17 @@ import { BLUR_DATA_IMG } from 'src/utils/constanst.utils'
export interface ImgWithLinkProps {
src: string,
alt?: string,
blurDataURL?: string,
}
const ImgWithLink = ({ src, alt }: ImgWithLinkProps) => {
const ImgWithLink = ({ src, alt, blurDataURL = BLUR_DATA_IMG }: ImgWithLinkProps) => {
return (
<div className={s.imgWithLink}>
<Image src={src} alt={alt}
layout="fill"
className={s.imgWithLink}
placeholder="blur"
blurDataURL={BLUR_DATA_IMG}
blurDataURL={blurDataURL}
/>
</div>
)

View File

@ -17,8 +17,9 @@
}
.inputCommon {
@apply block w-full transition-all duration-200 rounded bg-white;
padding: 1.2rem 1.6rem;
@apply block w-full transition-all duration-200 bg-white;
border-radius: .8rem;
padding: 1.6rem;
border: 1px solid var(--border-line);
&:hover,
&:focus,
@ -31,24 +32,6 @@
&::placeholder {
@apply text-label;
}
&.custom {
@apply custom-border-radius;
border: 1px solid transparent;
background: var(--gray);
&:hover,
&:focus,
&:active {
border: 1px solid var(--primary);
}
}
&.bgTransparent {
background: rgb(227, 242, 233, 0.3);
color: var(--white);
&::placeholder {
color: var(--white);
}
}
}
&.preserve {
@ -91,4 +74,27 @@
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);
}
}
}
}

View File

@ -1,6 +1,6 @@
import classNames from 'classnames';
import React, { forwardRef, useImperativeHandle, useMemo, useRef, useState } from 'react';
import { IconCheck, IconError, IconPassword, IconPasswordCross } from 'src/components/icons';
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';
@ -63,6 +63,9 @@ const InputCommon = forwardRef<Ref, Props>(({ value, placeholder, type, styleTyp
return (
<div className={classNames({
[s.inputWrap]: true,
[s[styleType]]: true,
[s.bgTransparent]: backgroundTransparent
})}>
<div className={classNames({
[s.inputInner]: true,
@ -78,11 +81,7 @@ const InputCommon = forwardRef<Ref, Props>(({ value, placeholder, type, styleTyp
placeholder={placeholder}
onChange={handleChange}
onKeyDown={handleKeyDown}
className={classNames({
[s.inputCommon]: true,
[s[styleType]]: true,
[s.bgTransparent]: backgroundTransparent
})}
className={s.inputCommon}
/>
</div>
{

View File

@ -3,9 +3,14 @@
.listProductWithInfo {
background-color: var(--background);
border-top: 1rem solid var(--gray);
border-bottom: 1rem solid var(--gray);
padding-top: 6rem;
padding-bottom: 6rem;
&.borderBottom {
border-bottom: 1rem solid var(--gray);
@screen lg {
border-bottom: none;
}
}
@screen lg {
@apply flex spacing-horizontal-left;
padding-top: 5.6rem;
@ -17,8 +22,11 @@
@apply spacing-horizontal-left;
@screen lg {
max-width: 75%;
@apply custom-border-radius-lg bg-white;
padding: 4rem .8rem;
padding: 0 0.8rem;
> div > div {
@apply shape-common-lg bg-white;
padding: 4rem 0;
}
:global(.customArrow) {
@screen lg {
&:global(.leftArrow) {
@ -31,18 +39,8 @@
}
}
@screen xl {
padding: 4rem 2.4rem;
padding: 0 2.4rem;
max-width: 80%;
:global(.customArrow) {
@screen lg {
&:global(.leftArrow) {
left: calc(-6.4rem + 1rem);
}
&:global(.rightArrow) {
right: calc(-6.4rem + 1rem);
}
}
}
}
}
}

View File

@ -1,3 +1,4 @@
import classNames from 'classnames';
import { TOptionsEvents } from 'keen-slider';
import React from 'react';
import CarouselCommon from '../CarouselCommon/CarouselCommon';
@ -9,6 +10,7 @@ interface Props {
data: ProductCardProps[],
title: string,
subtitle?: string,
hasBorderBottomMobile?: boolean,
}
const OPTION_DEFAULT: TOptionsEvents = {
slidesPerView: 2,
@ -18,20 +20,29 @@ const OPTION_DEFAULT: TOptionsEvents = {
slidesPerView: 3,
},
'(min-width: 768px)': {
slidesPerView: 4,
},
'(min-width: 1024px)': {
slidesPerView: 3,
},
'(min-width: 1008px)': {
slidesPerView: 3.5,
},
'(min-width: 1024px)': {
slidesPerView: 2.5,
},
'(min-width: 1280px)': {
slidesPerView: 3.5,
},
'(min-width: 1440px)': {
slidesPerView: 4.5,
},
},
}
const ListProductWithInfo = ({ data, title, subtitle }: Props) => {
const ListProductWithInfo = ({ data, title, subtitle, hasBorderBottomMobile }: Props) => {
return (
<div className={s.listProductWithInfo}>
<div className={classNames({
[s.listProductWithInfo]: true,
[s.borderBottom]: hasBorderBottomMobile,
})}>
<InfoProducts
title={title}
subtitle={subtitle}

View File

@ -71,7 +71,8 @@
@apply block shadow-md;
}
.menuIner {
@apply rounded list-none bg-white;
@apply list-none bg-white;
border-radius: 0.8rem;
border: 1px solid var(--text-active);
margin-top: 0.4rem;
> li {

View File

@ -1,9 +1,7 @@
import classNames from 'classnames';
import Link from 'next/link';
import React, { useRef } from 'react';
import { useModalCommon } from 'src/components/hooks/useModalCommon';
import { STATE_OPTIONS } from 'src/utils/constanst.utils';
import { CustomInputCommon } from 'src/utils/type.utils';
import { Inputcommon } from '..';
import { Inputcommon, SelectCommon } from '..';
import ButtonCommon from '../ButtonCommon/ButtonCommon';
import ModalCommon from '../ModalCommon/ModalCommon';
import s from './ModalCreateUserInfo.module.scss';
@ -27,8 +25,7 @@ const ModalCreateUserInfo = ({ demoVisible: visible, demoCloseModal: closeModal
<Inputcommon placeholder='Street Address' ref={firstInputRef} />
<Inputcommon placeholder='City' />
<div className={s.line}>
{/* todo: select, not input */}
<Inputcommon placeholder='State' />
<SelectCommon option={STATE_OPTIONS} type="custom" size="large" placeholder='State'/>
<Inputcommon placeholder='Zip code' />
</div>
<Inputcommon placeholder='Phone (delivery contact)' />

View File

@ -1,5 +1,5 @@
.productCardWarpper{
max-width: 20.8rem;
.productCardWarpper {
max-width: 22.4rem;
min-height: 31.8rem;
padding: 1.2rem 1.2rem 0 1.2rem;
margin: auto;
@ -8,47 +8,53 @@
&.notSell {
@apply justify-center;
}
.cardTop{
.cardTop {
@apply relative;
height: 13.8rem;
width: 100%;
.productImage{
.productImage {
height: 100%;
width: 100%;
@apply flex justify-center items-center;
img{
> div {
min-height: 13rem;
img {
object-fit: contain;
}
}
img {
@apply inline;
}
&:hover{
&:hover {
cursor: pointer;
}
}
.productLabel{
.productLabel {
@apply absolute left-0 bottom-0;
}
}
.cardMid{
.cardMid {
min-height: 10.4rem;
@apply flex flex-col justify-between;
.cardMidTop{
.productname{
.cardMidTop {
.productname {
font-weight: bold;
color: var(--text-active);
&:hover{
&:hover {
cursor: pointer;
}
}
.productWeight{
.productWeight {
font-size: 1.2rem;
line-height: 2rem;
letter-spacing: 0.01em;
color: var(--text-base);
}
}
.cardMidBot{
.cardMidBot {
padding-top: 0.8rem;
@apply flex justify-between items-center border-t border-solid border-line;
.productPrice{
.productPrice {
@apply font-bold;
font-size: 2rem;
line-height: 2.8rem;
@ -56,11 +62,27 @@
}
}
}
.cardBot{
min-height: 4rem;
.cardBot {
@apply flex justify-between items-center;
.cardIcon{
min-height: 4rem;
margin-top: 1.6rem;
.cardIcon {
margin-right: 0.8rem;
}
.cardButton {
width: 100%;
button {
width: 100%;
> div {
span {
display: -webkit-box;
-webkit-line-clamp: 1;
-webkit-box-orient: vertical;
overflow-y: hidden;
text-overflow: ellipsis;
}
}
}
}
}
}

View File

@ -1,7 +1,9 @@
import Link from 'next/link'
import React from 'react'
import { IconBuy } from 'src/components/icons'
import { ROUTE } from 'src/utils/constanst.utils'
import { ProductProps } from 'src/utils/types.utils'
import { ImgWithLink } from '..'
import ButtonCommon from '../ButtonCommon/ButtonCommon'
import ButtonIconBuy from '../ButtonIconBuy/ButtonIconBuy'
import ItemWishList from '../ItemWishList/ItemWishList'
@ -11,6 +13,7 @@ import ProductNotSell from './ProductNotSell/ProductNotSell'
export interface ProductCardProps extends ProductProps {
buttonText?: string
isSingleButton?: boolean,
}
const ProductCard = ({
@ -21,6 +24,7 @@ const ProductCard = ({
buttonText = 'Buy Now',
imageSrc,
isNotSell,
isSingleButton,
}: ProductCardProps) => {
if (isNotSell) {
return <div className={`${s.productCardWarpper} ${s.notSell}`}>
@ -34,7 +38,7 @@ const ProductCard = ({
<div className={s.cardTop}>
<Link href={`${ROUTE.PRODUCT_DETAIL}/test`}>
<div className={s.productImage}>
<img src={imageSrc} alt="image" />
<ImgWithLink src={imageSrc} alt={name}/>
</div>
</Link>
<div className={s.productLabel}>
@ -56,12 +60,22 @@ const ProductCard = ({
</div>
</div>
<div className={s.cardBot}>
{
isSingleButton ?
<div className={s.cardButton}>
<ButtonCommon type="light" icon={<IconBuy />}>Add to cart</ButtonCommon>
</div>
:
<>
<div className={s.cardIcon}>
<ButtonIconBuy />
</div>
<div className={s.cardButton}>
<ButtonCommon type="light">{buttonText}</ButtonCommon>
</div>
</>
}
</div>
</div>
)

View File

@ -1,8 +1,12 @@
@import "../../../../styles/utilities";
.imgWrap {
> div {
min-height: 13rem;
img {
opacity: 0.5;
object-fit: contain
}
}
}
@ -11,7 +15,7 @@
}
.info {
@apply flex justify-center items-center custom-border-radius bg-info-light text-center;
@apply flex justify-center items-center shape-common bg-info-light text-center;
padding: .8rem 1.6rem;
margin-top: 1.6rem;
color: var(--info);

View File

@ -7,9 +7,9 @@ import ProductCard, { ProductCardProps } from '../ProductCard/ProductCard'
import s from "./ProductCarousel.module.scss"
interface ProductCarouselProps
extends Omit<CarouselCommonProps<ProductCardProps>, 'Component'|"option"> {
option?:TOptionsEvents
}
extends Omit<CarouselCommonProps<ProductCardProps>, 'Component' | "option"> {
option?: TOptionsEvents
}
const OPTION_DEFAULT: TOptionsEvents = {
slidesPerView: 2,
@ -19,11 +19,15 @@ const OPTION_DEFAULT: TOptionsEvents = {
slidesPerView: 3,
},
'(min-width: 768px)': {
slidesPerView: 4,
slidesPerView: 3,
},
'(min-width: 1024px)': {
'(min-width: 1008px)': {
slidesPerView: 3.5,
},
'(min-width: 1280px)': {
slidesPerView: 4.5,
},'(min-width: 1280px)': {
},
'(min-width: 1440px)': {
slidesPerView: 5.5,
},
},

View File

@ -1,19 +1,24 @@
import Link from 'next/link'
import React from 'react'
import { ROUTE } from 'src/utils/constanst.utils'
import { RecipeProps } from 'src/utils/types.utils'
import s from './RecipeCard.module.scss'
export interface RecipeCardProps extends RecipeProps {}
export interface RecipeCardProps extends RecipeProps { }
const RecipeCard = ({ imageSrc, title, description }: RecipeCardProps) => {
const RecipeCard = ({ imageSrc, title, description, slug }: RecipeCardProps) => {
return (
<div className={s.recipeCardWarpper}>
<Link href="#">
<Link href={`${ROUTE.RECIPE_DETAIL}/${slug}`}>
<a>
<div className={s.image}>
<img src={imageSrc} alt="image recipe" />
</div>
</a>
</Link>
<Link href="#">
<Link href={`${ROUTE.RECIPE_DETAIL}/${slug}`}>
<a>
<div className={s.title}>{title}</div>
</a>
</Link>
<div className={s.description}>{description}</div>
</div>

View File

@ -2,13 +2,16 @@
.recipeDetailInfo {
@apply spacing-horizontal;
margin: 5.6rem auto;
margin: 0 auto 5.6rem;
@screen md {
@apply flex;
margin: 5.6rem auto;
}
.img {
width: fit-content;
width: 100%;
margin-top: 0;
min-height: 50rem;
@screen sm-only {
margin-bottom: 2rem;
@ -16,13 +19,16 @@
@screen lg {
max-width: 60rem;
}
> div {
min-height: 64rem;
img {
@apply w-full;
object-fit: contain;
max-height: 64rem;
// object-fit: contain;
// @apply w-full;
min-height: 64rem;
border-radius: 2.4rem;
@screen md {
max-height: 90rem;
// @screen md {
// max-height: 90rem;
// }
}
}
}
@ -55,6 +61,9 @@
list-style: disc;
margin-left: 2rem;
}
a {
color: var(--info);
}
}
}
}

View File

@ -1,4 +1,5 @@
import React from 'react'
import { ImgWithLink } from 'src/components/common'
import RecipeBriefInfo from '../RecipeBriefInfo/RecipeBriefInfo'
import s from './RecipeDetailInfo.module.scss'
@ -12,7 +13,7 @@ const RecipeDetailInfo = ({ }: Props) => {
return (
<section className={s.recipeDetailInfo}>
<div className={s.img}>
<img src="https://user-images.githubusercontent.com/76729908/131634880-8ae1437b-d3f8-421e-a546-d5a4f9a28e5f.png" alt="Recipe" />
<ImgWithLink src="https://user-images.githubusercontent.com/76729908/131634880-8ae1437b-d3f8-421e-a546-d5a4f9a28e5f.png" alt="Recipe" />
</div>
<div className={s.recipeInfo}>
<div className={s.top}>

View File

@ -1,4 +1,4 @@
@import "../../../../styles/utilities";
@import "../../../styles/utilities";
.recommendedRecipes {
margin: 6rem auto;

View File

@ -28,7 +28,7 @@
}
&.custom {
.selectTrigger {
@apply border-2;
border-width: 1px;
border-color: var(--border-line);
color: var(--text-label);
}
@ -68,7 +68,7 @@
@apply border-solid border border-current;
}
&.custom {
@apply border-2;
border-width: 1px;
border-color: var(--border-line);
color: var(--text-label);
}

View File

@ -46,3 +46,4 @@ 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'
export { default as RecommendedRecipes} from './RecommendedRecipes/RecommendedRecipes'

View File

@ -0,0 +1,11 @@
import React from 'react'
const IconNoti = () => {
return (
<svg width="20" height="24" viewBox="0 0 20 24" fill="none" xmlns="http://www.w3.org/2000/svg">
<path d="M17.0003 13.3763V9.66634C16.9986 8.01332 16.412 6.41422 15.3444 5.15224C14.2767 3.89027 12.7969 3.04684 11.167 2.77134V1.49967C11.167 1.19026 11.0441 0.893509 10.8253 0.674717C10.6065 0.455924 10.3097 0.333008 10.0003 0.333008C9.6909 0.333008 9.39416 0.455924 9.17537 0.674717C8.95657 0.893509 8.83366 1.19026 8.83366 1.49967V2.77134C7.20375 3.04684 5.72394 3.89027 4.65627 5.15224C3.58861 6.41422 3.002 8.01332 3.00033 9.66634V13.3763C2.31952 13.617 1.72982 14.0624 1.31209 14.6514C0.89435 15.2404 0.669022 15.9442 0.666992 16.6663V18.9997C0.666992 19.3091 0.789909 19.6058 1.0087 19.8246C1.22749 20.0434 1.52424 20.1663 1.83366 20.1663H5.49699C5.76566 21.1549 6.35216 22.0277 7.16602 22.6499C7.97988 23.2721 8.97587 23.6092 10.0003 23.6092C11.0248 23.6092 12.0208 23.2721 12.8346 22.6499C13.6485 22.0277 14.235 21.1549 14.5037 20.1663H18.167C18.4764 20.1663 18.7732 20.0434 18.9919 19.8246C19.2107 19.6058 19.3337 19.3091 19.3337 18.9997V16.6663C19.3316 15.9442 19.1063 15.2404 18.6886 14.6514C18.2708 14.0624 17.6811 13.617 17.0003 13.3763ZM5.33366 9.66634C5.33366 8.42866 5.82532 7.24168 6.70049 6.36651C7.57566 5.49134 8.76265 4.99967 10.0003 4.99967C11.238 4.99967 12.425 5.49134 13.3002 6.36651C14.1753 7.24168 14.667 8.42866 14.667 9.66634V13.1663H5.33366V9.66634ZM10.0003 21.333C9.59313 21.3305 9.19366 21.2216 8.84163 21.0169C8.48959 20.8122 8.19725 20.519 7.99366 20.1663H12.007C11.8034 20.519 11.5111 20.8122 11.159 21.0169C10.807 21.2216 10.4075 21.3305 10.0003 21.333ZM17.0003 17.833H3.00033V16.6663C3.00033 16.3569 3.12324 16.0602 3.34203 15.8414C3.56083 15.6226 3.85757 15.4997 4.16699 15.4997H15.8337C16.1431 15.4997 16.4398 15.6226 16.6586 15.8414C16.8774 16.0602 17.0003 16.3569 17.0003 16.6663V17.833Z" fill="#5B9A74" />
</svg>
)
}
export default IconNoti

View File

@ -33,3 +33,4 @@ export { default as IconMinus } from './IconMinus'
export { default as IconCirclePlus } from './IconCirclePlus'
export { default as IconDoneCheckout } from './IconDoneCheckout'
export { default as IconFilter } from './IconFilter'
export { default as IconNoti } from './IconNoti'

View File

@ -11,14 +11,14 @@
@apply grid;
grid-template-columns: 1fr 1.8fr;
.left {
@apply relative flex items-end justify-center custom-border-radius-lg;
@apply relative flex items-end justify-center shape-common-lg;
margin-right: 1.6rem;
.imgWrap {
@apply absolute w-full h-full;
top: 0;
left: 0;
> div {
@apply w-full h-full custom-border-radius-lg;
@apply w-full h-full shape-common-lg;
}
img {
object-fit: cover;

View File

@ -1,9 +1,9 @@
import React from 'react'
import { Banner } from 'src/components/common'
import { Banner, StaticImage } from 'src/components/common'
import { ROUTE } from 'src/utils/constanst.utils'
import BannerImgRight from './assets/banner_full.png'
import HomeBannerImg from './assets/home_banner.png'
import s from './HomeBanner.module.scss'
import Image from 'next/image'
interface Props {
className?: string
@ -15,7 +15,7 @@ const HomeBanner = ({ }: Props) => {
<div className={s.homeBanner}>
<section className={s.left}>
<div className={s.imgWrap}>
<Image src={HomeBannerImg} placeholder='blur' />
<StaticImage src={HomeBannerImg} />
</div>
<div className={s.text}>
Freshness<br />guaranteed
@ -28,12 +28,14 @@ const HomeBanner = ({ }: Props) => {
subtitle: "Last call! Shop deep deals on 100+ bulk picks while you can.",
imgLink: BannerImgRight.src,
size: "small",
linkButton: ROUTE.PRODUCTS,
},
{
title: "Save 15% on your first order 2",
subtitle: "Last call! Shop deep deals on 100+ bulk picks while you can.",
imgLink: BannerImgRight.src,
size: "small",
linkButton: ROUTE.PRODUCTS,
}
]
}

View File

@ -2,7 +2,7 @@
.categoryItem {
.categoryItemImage {
@apply transition-all duration-200;
@apply flex justify-center items-center transition-all duration-200;
width: 10.6rem;
margin: 0 auto;
max-height: 14rem;

View File

@ -4,6 +4,7 @@ import s from './CategoryItem.module.scss'
import classNames from 'classnames';
import Image from "next/image";
import Link from 'next/link';
import { StaticImage } from 'src/components/common';
interface CategoryItem {
image: StaticImageData,
@ -17,7 +18,7 @@ const CategoryItem = ({ image, name, link }: CategoryItem) => {
<div className={classNames(s.categoryItemImage)}>
<Link href={link}>
<a>
<Image src={image} />
<StaticImage src={image} />
</a>
</Link>
</div>

View File

@ -4,7 +4,7 @@
margin: 3rem auto;
.inner {
@apply relative spacing-horizontal;
@apply relative spacing-horizontal w-full;
padding-top: 1.6rem;
padding-bottom: 1.6rem;
@ -15,7 +15,7 @@
}
@screen md {
@apply relative;
margin: 5.6rem auto;
margin: 12.8rem auto;
&::before,
&::after {

View File

@ -0,0 +1,8 @@
.productImgItem {
@apply w-full h-full;
min-height: 30rem;
img {
object-fit: contain;
}
}

View File

@ -0,0 +1,19 @@
import React from 'react'
import { ImgWithLink } from 'src/components/common'
import s from './ProductImgItem.module.scss'
export interface ProductImgItemProps {
src: string
alt?: string
}
const ProductImgItem = ({ src, alt }: ProductImgItemProps) => {
return (
<section className={s.productImgItem}>
<ImgWithLink src={src} alt={alt} />
</section >
)
}
export default ProductImgItem

View File

@ -6,4 +6,7 @@
@screen lg {
max-width: 60rem;
}
> div {
width: 100%;
}
}

View File

@ -1,6 +1,6 @@
import React from 'react'
import { CarouselCommon, ImgWithLink } from 'src/components/common'
import { ImgWithLinkProps } from 'src/components/common/ImgWithLink/ImgWithLink'
import { CarouselCommon } from 'src/components/common'
import ProductImgItem, { ProductImgItemProps } from '../ProductImgItem/ProductImgItem'
import s from './ProductImgs.module.scss'
interface Props {
@ -9,6 +9,10 @@ interface Props {
}
const DATA = [
{
src: 'https://user-images.githubusercontent.com/76729908/133026929-199799fc-bd75-4445-a24d-15c0e41796eb.png',
alt: 'Meat',
},
{
src: 'https://user-images.githubusercontent.com/76729908/130574371-3b75fa72-9552-4605-aba9-a4b31cd9dce7.png',
alt: 'Broccoli',
@ -26,10 +30,10 @@ const option = {
const ProductImgs = ({ }: Props) => {
return (
<section className={s.productImgs}>
<CarouselCommon<ImgWithLinkProps>
<CarouselCommon<ProductImgItemProps>
data={DATA}
itemKey="product-detail-img"
Component={ImgWithLink}
Component={ProductImgItem}
option={option}
isDot={true}
/>
@ -38,3 +42,4 @@ const ProductImgs = ({ }: Props) => {
}
export default ProductImgs

View File

@ -64,6 +64,12 @@
margin-left: 0.8rem;
}
}
svg {
height: 2rem;
path {
fill: currentColor;
}
}
}
button {
@apply w-full;

View File

@ -8,6 +8,7 @@ const ViewedProducts = () => {
title="viewed Products"
subtitle="Last call! Shop deep deals on 100+ bulk picks while you can."
data={PRODUCT_DATA_TEST}
hasBorderBottomMobile={true}
/>
);
};

View File

@ -1,4 +1,3 @@
export { default as ProductInfoDetail } from './ProductInfoDetail/ProductInfoDetail'
export { default as ViewedProducts } from './ViewedProducts/ViewedProducts'
export { default as ReleventProducts } from './ReleventProducts/ReleventProducts'
export { default as RecommendedRecipes } from './RecommendedRecipes/RecommendedRecipes'

View File

@ -16,7 +16,7 @@ const ProductListBanner = ({ }: Props) => {
subtitle: "Last call! Shop deep deals on 100+ bulk picks while you can.",
imgLink: BannerRight.src,
size: "large",
},
}
]
}
/>

6
src/styles/_pages.scss Normal file
View File

@ -0,0 +1,6 @@
.page-recipe-detail {
margin-bottom: 5.4rem;
@screen lg {
margin-bottom: 12.8rem;
}
}

View File

@ -121,12 +121,13 @@
.shape-common-border {
position: relative;
$border: 2px;
$border: 1px;
margin: $border;
.inner {
background: var(--white);
clip-path: url(#svg-custom-shape);
margin: 1px;
}
&::before {
position: absolute;
@ -152,6 +153,7 @@
.inner {
padding: 2rem;
margin: 1px;
clip-path: url(#svg-custom-shape-lg);
}
&::before {

View File

@ -5,3 +5,5 @@
@import "~tailwindcss/utilities";
@import './utilities';
@import './pages'

View File

@ -9,19 +9,25 @@ export const SOCIAL_LINKS = {
export const ROUTE = {
HOME: '/',
ABOUT: '/about',
ACCOUNT: '/account',
PRODUCTS: '/products',
PRODUCT_DETAIL: '/product',
ABOUT: '/about',
BLOGS: '/blogs',
BLOG_DETAIL: '/blog',
ACCOUNT: '/account',
RECIPES: '/recipes',
RECIPE_DETAIL: '/recipe',
BUSSINESS: '/bussiness',
CONTACT: '/contact',
CHECKOUT: '/checkout',
FAQ: '/faq',
CUSTOMER_SERVICE: '/customer-service',
TERM_CONDITION: '/term-condition',
PRIVACY_POLICY: '/privacy-policy',
BLOGS: '/blogs',
FORGOT_PASSWORD: '/forgot-password'
}
@ -29,6 +35,7 @@ export const ACCOUNT_TAB = {
CUSTOMER_INFO: '',
ORDER: 'orders',
FAVOURITE: 'wishlist',
NOTIFICATION: 'notification',
}
export const QUERY_KEY = {
@ -117,3 +124,14 @@ export const FEATURED = [
]
export const DEFAULT_BLOG_PAGE_SIZE=6;
export const STATE_OPTIONS = [
{
name: 'Hồ Chí Minh',
value: 'Hồ Chí Minh',
},
{
name: 'Hà Nội',
value: 'Hà Nội',
},
]

View File

@ -1,5 +1,5 @@
export function isMobile() {
return window.innerWidth <= 768
return window.innerWidth < 768
}
export function removeItem<T>(arr: Array<T>, value: T): Array<T> {

View File

@ -106,9 +106,6 @@ module.exports = {
fontSize: {
base: ['16px', '24px'],
},
borderRadius: {
rounded: '.8rem',
},
screens: {
'sm-only': {'min': '0', 'max': '767px'},
'sm': '640px',

1007
yarn.lock

File diff suppressed because it is too large Load Diff