mirror of
https://github.com/vercel/commerce.git
synced 2025-07-22 04:14:18 +00:00
Merge pull request #49 from KieIO/m7-lytran
M7 Ly: Fix UI, update from common
This commit is contained in:
commit
2cd36b2d66
@ -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'],
|
||||
@ -46,10 +47,10 @@ module.exports = withCommerceConfig({
|
||||
// For Vendure, rewrite the local api url to the remote (external) api url. This is required
|
||||
// to make the session cookies work.
|
||||
isVendure &&
|
||||
process.env.NEXT_PUBLIC_VENDURE_LOCAL_URL && {
|
||||
source: `${process.env.NEXT_PUBLIC_VENDURE_LOCAL_URL}/:path*`,
|
||||
destination: `${process.env.NEXT_PUBLIC_VENDURE_SHOP_API_URL}/:path*`,
|
||||
},
|
||||
process.env.NEXT_PUBLIC_VENDURE_LOCAL_URL && {
|
||||
source: `${process.env.NEXT_PUBLIC_VENDURE_LOCAL_URL}/:path*`,
|
||||
destination: `${process.env.NEXT_PUBLIC_VENDURE_SHOP_API_URL}/:path*`,
|
||||
},
|
||||
].filter(Boolean)
|
||||
},
|
||||
})
|
||||
|
@ -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
12
pages/recipe/[slug].tsx
Normal 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
|
@ -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/> */}
|
||||
|
@ -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}
|
||||
/>
|
||||
)
|
||||
})
|
||||
|
@ -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;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -1,102 +1,127 @@
|
||||
@import "../../../styles/utilities";
|
||||
|
||||
.buttonCommon {
|
||||
@apply custom-border-radius 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;
|
||||
}
|
||||
@screen lg {
|
||||
padding: 0.8rem 3.2rem;
|
||||
}
|
||||
&:disabled {
|
||||
filter: brightness(0.9);
|
||||
cursor: not-allowed;
|
||||
color: var(--disabled);
|
||||
}
|
||||
&:hover {
|
||||
@apply shadow-md;
|
||||
&:not(:disabled) {
|
||||
@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;
|
||||
@screen md {
|
||||
padding: 1.6rem 1.6rem;
|
||||
}
|
||||
@screen lg {
|
||||
padding: 1.6rem 3.2rem;
|
||||
}
|
||||
&:disabled {
|
||||
filter: brightness(0.9);
|
||||
cursor: not-allowed;
|
||||
color: var(--disabled);
|
||||
}
|
||||
&:hover {
|
||||
@apply shadow-md;
|
||||
&:not(:disabled) {
|
||||
filter: brightness(1.05);
|
||||
}
|
||||
}
|
||||
&:focus {
|
||||
outline: none;
|
||||
filter: brightness(1.05);
|
||||
}
|
||||
&:focus-visible {
|
||||
outline: 2px solid var(--text-active);
|
||||
}
|
||||
}
|
||||
&:focus {
|
||||
outline: none;
|
||||
filter: brightness(1.05);
|
||||
}
|
||||
&:focus-visible {
|
||||
outline: 2px solid var(--text-active);
|
||||
}
|
||||
|
||||
&.loading {
|
||||
&::before {
|
||||
content: "";
|
||||
border-radius: 50%;
|
||||
width: 1.6rem;
|
||||
height: 1.6rem;
|
||||
border: 3px solid rgba(170, 170, 170, 0.5);
|
||||
border-top: 3px solid var(--white);
|
||||
-webkit-animation: spin 2s linear infinite;
|
||||
animation: spin 2s linear infinite;
|
||||
margin-right: 0.8rem;
|
||||
.inner {
|
||||
&::after {
|
||||
content: "";
|
||||
border-radius: 50%;
|
||||
width: 1.6rem;
|
||||
height: 1.6rem;
|
||||
border: 3px solid rgba(170, 170, 170, 0.5);
|
||||
border-top: 3px solid var(--white);
|
||||
-webkit-animation: spin 2s linear infinite;
|
||||
animation: spin 2s linear infinite;
|
||||
margin-right: 0.8rem;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
&.light {
|
||||
@apply text-base bg-white;
|
||||
border: 1px solid var(--text-active);
|
||||
@apply shape-common-border;
|
||||
&::before {
|
||||
background-color: var(--text-active);
|
||||
}
|
||||
.inner {
|
||||
@apply text-base bg-white;
|
||||
}
|
||||
&.loading {
|
||||
&::before {
|
||||
border-top-color: var(--primary);
|
||||
.inner {
|
||||
&::after {
|
||||
border-top-color: var(--primary);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
&.lightBorderNone {
|
||||
@apply bg-white text-primary;
|
||||
.inner {
|
||||
@apply bg-white text-primary;
|
||||
}
|
||||
&.loading {
|
||||
&::before {
|
||||
.inner::after {
|
||||
border-top-color: var(--primary);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
&.ghost {
|
||||
@apply bg-white text-primary;
|
||||
border: 1px solid var(--primary);
|
||||
@apply shape-common-border;
|
||||
.inner {
|
||||
@apply bg-white text-primary;
|
||||
}
|
||||
&::before {
|
||||
background-color: var(--primary);
|
||||
}
|
||||
&.loading {
|
||||
&::before {
|
||||
.inner::after {
|
||||
border-top-color: var(--text-active);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
&.onlyIcon {
|
||||
padding: 0.8rem;
|
||||
.icon {
|
||||
margin: 0;
|
||||
.inner {
|
||||
padding: 1rem;
|
||||
@screen md {
|
||||
padding: 1.6rem;
|
||||
}
|
||||
.icon {
|
||||
margin: 0;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
&.large {
|
||||
padding: 1rem 1.5rem;
|
||||
&.onlyIcon {
|
||||
padding: 1rem;
|
||||
}
|
||||
@screen md {
|
||||
padding: 1.6rem 3.2rem;
|
||||
.inner {
|
||||
padding: 1rem 1.6rem;
|
||||
&.onlyIcon {
|
||||
padding: 1.6rem;
|
||||
padding: 1rem;
|
||||
}
|
||||
@screen md {
|
||||
padding: 1.6rem 3.2rem;
|
||||
&.onlyIcon {
|
||||
padding: 1.6rem;
|
||||
}
|
||||
}
|
||||
@screen lg {
|
||||
padding: 1.6rem 4.8rem;
|
||||
}
|
||||
}
|
||||
@screen lg {
|
||||
padding: 1.6rem 4.8rem;
|
||||
}
|
||||
&.loading {
|
||||
&::before {
|
||||
.inner::before {
|
||||
width: 2.4rem;
|
||||
height: 2.4rem;
|
||||
}
|
||||
@ -104,16 +129,21 @@
|
||||
}
|
||||
|
||||
&.preserve {
|
||||
flex-direction: row-reverse;
|
||||
.icon {
|
||||
margin: 0 0 0 1.6rem;
|
||||
.inner {
|
||||
flex-direction: row-reverse;
|
||||
.icon {
|
||||
margin: 0 0 0 1.6rem;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.icon {
|
||||
margin: 0 1.6rem 0 0;
|
||||
svg path {
|
||||
fill: currentColor;
|
||||
svg {
|
||||
height: 2rem;
|
||||
path {
|
||||
fill: currentColor;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -27,10 +27,12 @@ const ButtonCommon = memo(({ type = 'primary', size = 'default', loading = false
|
||||
disabled={disabled}
|
||||
onClick={onClick}
|
||||
>
|
||||
{
|
||||
icon && <span className={s.icon}>{icon}</span>
|
||||
}
|
||||
<span className={s.label}>{children}</span>
|
||||
<div className={s.inner}>
|
||||
{
|
||||
icon && <span className={s.icon}>{icon}</span>
|
||||
}
|
||||
<span className={s.label}>{children}</span>
|
||||
</div>
|
||||
</button>
|
||||
)
|
||||
})
|
||||
|
@ -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) => (
|
||||
<div className="keen-slider__slide" key={`${itemKey}-${index}`}>
|
||||
<Component {...props} />
|
||||
</div>
|
||||
))}
|
||||
{data?.map((props, index) => {
|
||||
const allProps = defaultComponentProps ? { ...props, ...defaultComponentProps } : props
|
||||
return (
|
||||
<div className="keen-slider__slide" key={`${itemKey}-${index}`}>
|
||||
<Component {...allProps} />
|
||||
</div>
|
||||
)
|
||||
})}
|
||||
</div>
|
||||
{slider && isArrow && (
|
||||
<>
|
||||
|
@ -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;
|
||||
}
|
||||
}
|
@ -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>
|
||||
|
@ -1,5 +1,7 @@
|
||||
.cartCheckoutButton {
|
||||
display: block;
|
||||
padding: 1.6rem;
|
||||
width: 100%;
|
||||
button {
|
||||
width: 100%;
|
||||
}
|
||||
|
@ -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}>
|
||||
<ButtonCommon size='large'>Check out - Rp 120.500</ButtonCommon>
|
||||
</div>
|
||||
<Link href={ROUTE.CHECKOUT}>
|
||||
<a className={s.cartCheckoutButton}>
|
||||
<ButtonCommon size='large'>Check out - Rp 120.500</ButtonCommon>
|
||||
</a>
|
||||
</Link>
|
||||
)
|
||||
})
|
||||
|
||||
|
@ -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);
|
||||
}
|
||||
&:global(.rightArrow) {
|
||||
right: calc(-6.4rem - 2rem);
|
||||
}
|
||||
@apply bg-line shadow-md;
|
||||
opacity: 0.8;
|
||||
&:global(.rightArrow) {
|
||||
right: 1rem;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -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>
|
||||
|
@ -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} />
|
||||
|
@ -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;
|
||||
|
@ -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 {
|
||||
|
@ -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>
|
||||
@ -86,16 +85,16 @@ const HeaderMenu = memo(({ isFull, openModalAuthen, openModalInfo, toggleFilter,
|
||||
<IconBuy />
|
||||
</button>
|
||||
</li>
|
||||
|
||||
|
||||
{
|
||||
FILTER_PAGE.includes(router.pathname) && (
|
||||
<li className={s.iconFilterDesk}>
|
||||
<button className={s.iconFilter} onClick={toggleFilter}>
|
||||
<IconFilter/>
|
||||
</button>
|
||||
</li>
|
||||
)
|
||||
}
|
||||
FILTER_PAGE.includes(router.pathname) && (
|
||||
<li className={s.iconFilterDesk}>
|
||||
<button className={s.iconFilter} onClick={toggleFilter}>
|
||||
<IconFilter />
|
||||
</button>
|
||||
</li>
|
||||
)
|
||||
}
|
||||
</ul>
|
||||
</section>
|
||||
)
|
||||
|
@ -5,6 +5,7 @@
|
||||
@screen md {
|
||||
@apply block;
|
||||
padding-bottom: 2.4rem;
|
||||
margin-top: 1.6rem;
|
||||
transform: none;
|
||||
height: unset;
|
||||
@screen lg {
|
||||
|
@ -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 {
|
||||
|
@ -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',
|
||||
|
@ -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>
|
||||
)
|
||||
|
@ -15,10 +15,11 @@
|
||||
.icon + .inputCommon {
|
||||
padding-left: 4.8rem;
|
||||
}
|
||||
|
||||
|
||||
.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);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -1,12 +1,12 @@
|
||||
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';
|
||||
|
||||
type Ref = {
|
||||
focus: () => void
|
||||
getValue: () => string | number
|
||||
getValue: () => string | number
|
||||
} | null;
|
||||
interface Props {
|
||||
children?: React.ReactNode,
|
||||
@ -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>
|
||||
{
|
||||
|
@ -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);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -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}
|
||||
|
@ -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 {
|
||||
|
@ -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)' />
|
||||
|
@ -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{
|
||||
.cardBot {
|
||||
@apply flex justify-between items-center;
|
||||
min-height: 4rem;
|
||||
@apply flex justify-between items-center;
|
||||
.cardIcon{
|
||||
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;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -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}>
|
||||
<div className={s.cardIcon}>
|
||||
<ButtonIconBuy />
|
||||
</div>
|
||||
<div className={s.cardButton}>
|
||||
<ButtonCommon type="light">{buttonText}</ButtonCommon>
|
||||
</div>
|
||||
{
|
||||
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>
|
||||
)
|
||||
|
@ -1,8 +1,12 @@
|
||||
@import "../../../../styles/utilities";
|
||||
|
||||
.imgWrap {
|
||||
img {
|
||||
opacity: 0.5;
|
||||
> 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);
|
||||
|
@ -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,
|
||||
},
|
||||
},
|
||||
|
@ -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="#">
|
||||
<div className={s.image}>
|
||||
<img src={imageSrc} alt="image recipe" />
|
||||
</div>
|
||||
<Link href={`${ROUTE.RECIPE_DETAIL}/${slug}`}>
|
||||
<a>
|
||||
<div className={s.image}>
|
||||
<img src={imageSrc} alt="image recipe" />
|
||||
</div>
|
||||
</a>
|
||||
</Link>
|
||||
<Link href="#">
|
||||
<div className={s.title}>{title}</div>
|
||||
<Link href={`${ROUTE.RECIPE_DETAIL}/${slug}`}>
|
||||
<a>
|
||||
<div className={s.title}>{title}</div>
|
||||
</a>
|
||||
</Link>
|
||||
<div className={s.description}>{description}</div>
|
||||
</div>
|
||||
|
@ -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;
|
||||
}
|
||||
img {
|
||||
@apply w-full;
|
||||
object-fit: contain;
|
||||
max-height: 64rem;
|
||||
border-radius: 2.4rem;
|
||||
@screen md {
|
||||
max-height: 90rem;
|
||||
> div {
|
||||
min-height: 64rem;
|
||||
img {
|
||||
// object-fit: contain;
|
||||
// @apply w-full;
|
||||
min-height: 64rem;
|
||||
border-radius: 2.4rem;
|
||||
// @screen md {
|
||||
// max-height: 90rem;
|
||||
// }
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -55,6 +61,9 @@
|
||||
list-style: disc;
|
||||
margin-left: 2rem;
|
||||
}
|
||||
a {
|
||||
color: var(--info);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -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}>
|
||||
|
@ -1,4 +1,4 @@
|
||||
@import "../../../../styles/utilities";
|
||||
@import "../../../styles/utilities";
|
||||
|
||||
.recommendedRecipes {
|
||||
margin: 6rem auto;
|
@ -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);
|
||||
}
|
||||
|
@ -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'
|
||||
|
11
src/components/icons/IconNoti.tsx
Normal file
11
src/components/icons/IconNoti.tsx
Normal 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
|
@ -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'
|
@ -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;
|
||||
|
@ -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,
|
||||
}
|
||||
]
|
||||
}
|
||||
|
@ -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;
|
||||
|
@ -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>
|
||||
|
@ -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 {
|
||||
|
@ -0,0 +1,8 @@
|
||||
.productImgItem {
|
||||
@apply w-full h-full;
|
||||
min-height: 30rem;
|
||||
img {
|
||||
object-fit: contain;
|
||||
}
|
||||
}
|
||||
|
@ -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
|
@ -6,4 +6,7 @@
|
||||
@screen lg {
|
||||
max-width: 60rem;
|
||||
}
|
||||
> div {
|
||||
width: 100%;
|
||||
}
|
||||
}
|
||||
|
@ -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
|
||||
|
||||
|
@ -64,6 +64,12 @@
|
||||
margin-left: 0.8rem;
|
||||
}
|
||||
}
|
||||
svg {
|
||||
height: 2rem;
|
||||
path {
|
||||
fill: currentColor;
|
||||
}
|
||||
}
|
||||
}
|
||||
button {
|
||||
@apply w-full;
|
||||
|
@ -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}
|
||||
/>
|
||||
);
|
||||
};
|
||||
|
@ -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'
|
||||
|
@ -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
6
src/styles/_pages.scss
Normal file
@ -0,0 +1,6 @@
|
||||
.page-recipe-detail {
|
||||
margin-bottom: 5.4rem;
|
||||
@screen lg {
|
||||
margin-bottom: 12.8rem;
|
||||
}
|
||||
}
|
@ -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 {
|
||||
|
@ -5,3 +5,5 @@
|
||||
|
||||
@import "~tailwindcss/utilities";
|
||||
@import './utilities';
|
||||
|
||||
@import './pages'
|
||||
|
@ -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',
|
||||
},
|
||||
]
|
@ -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> {
|
||||
|
@ -106,9 +106,6 @@ module.exports = {
|
||||
fontSize: {
|
||||
base: ['16px', '24px'],
|
||||
},
|
||||
borderRadius: {
|
||||
rounded: '.8rem',
|
||||
},
|
||||
screens: {
|
||||
'sm-only': {'min': '0', 'max': '767px'},
|
||||
'sm': '640px',
|
||||
|
Loading…
x
Reference in New Issue
Block a user