fix: fix conflic
@ -1,25 +1,4 @@
|
||||
import type { GetStaticPropsContext } from 'next'
|
||||
import commerce from '@lib/api/commerce'
|
||||
import { Layout } from '@components/common'
|
||||
|
||||
export async function getStaticProps({
|
||||
preview,
|
||||
locale,
|
||||
locales,
|
||||
}: GetStaticPropsContext) {
|
||||
const config = { locale, locales }
|
||||
const { pages } = await commerce.getAllPages({ config, preview })
|
||||
const { categories, brands } = await commerce.getSiteInfo({ config, preview })
|
||||
return {
|
||||
props: {
|
||||
pages,
|
||||
categories,
|
||||
brands,
|
||||
},
|
||||
revalidate: 200,
|
||||
}
|
||||
}
|
||||
|
||||
export default function NotFound() {
|
||||
return (
|
||||
<div>
|
||||
|
@ -1,86 +0,0 @@
|
||||
import type {
|
||||
GetStaticPathsContext,
|
||||
GetStaticPropsContext,
|
||||
InferGetStaticPropsType,
|
||||
} from 'next'
|
||||
import commerce from '@lib/api/commerce'
|
||||
import { Text } from '@components/ui'
|
||||
import { Layout } from '@components/common'
|
||||
import getSlug from '@lib/get-slug'
|
||||
import { missingLocaleInPages } from '@lib/usage-warns'
|
||||
import type { Page } from '@commerce/types/page'
|
||||
import { useRouter } from 'next/router'
|
||||
|
||||
export async function getStaticProps({
|
||||
preview,
|
||||
params,
|
||||
locale,
|
||||
locales,
|
||||
}: GetStaticPropsContext<{ pages: string[] }>) {
|
||||
const config = { locale, locales }
|
||||
const pagesPromise = commerce.getAllPages({ config, preview })
|
||||
const siteInfoPromise = commerce.getSiteInfo({ config, preview })
|
||||
const { pages } = await pagesPromise
|
||||
const { categories } = await siteInfoPromise
|
||||
const path = params?.pages.join('/')
|
||||
const slug = locale ? `${locale}/${path}` : path
|
||||
const pageItem = pages.find((p: Page) =>
|
||||
p.url ? getSlug(p.url) === slug : false
|
||||
)
|
||||
const data =
|
||||
pageItem &&
|
||||
(await commerce.getPage({
|
||||
variables: { id: pageItem.id! },
|
||||
config,
|
||||
preview,
|
||||
}))
|
||||
|
||||
const page = data?.page
|
||||
|
||||
if (!page) {
|
||||
// We throw to make sure this fails at build time as this is never expected to happen
|
||||
throw new Error(`Page with slug '${slug}' not found`)
|
||||
}
|
||||
|
||||
return {
|
||||
props: { pages, page, categories },
|
||||
revalidate: 60 * 60, // Every hour
|
||||
}
|
||||
}
|
||||
|
||||
export async function getStaticPaths({ locales }: GetStaticPathsContext) {
|
||||
const config = { locales }
|
||||
const { pages }: { pages: Page[] } = await commerce.getAllPages({ config })
|
||||
const [invalidPaths, log] = missingLocaleInPages()
|
||||
const paths = pages
|
||||
.map((page) => page.url)
|
||||
.filter((url) => {
|
||||
if (!url || !locales) return url
|
||||
// If there are locales, only include the pages that include one of the available locales
|
||||
if (locales.includes(getSlug(url).split('/')[0])) return url
|
||||
|
||||
invalidPaths.push(url)
|
||||
})
|
||||
log()
|
||||
|
||||
return {
|
||||
paths,
|
||||
fallback: 'blocking',
|
||||
}
|
||||
}
|
||||
|
||||
export default function Pages({
|
||||
page,
|
||||
}: InferGetStaticPropsType<typeof getStaticProps>) {
|
||||
const router = useRouter()
|
||||
|
||||
return router.isFallback ? (
|
||||
<h1>Loading...</h1> // TODO (BC) Add Skeleton Views
|
||||
) : (
|
||||
<div className="max-w-2xl mx-8 sm:mx-auto py-20">
|
||||
{page?.body && <Text html={page.body} />}
|
||||
</div>
|
||||
)
|
||||
}
|
||||
|
||||
Pages.Layout = Layout
|
@ -1,50 +0,0 @@
|
||||
import type { GetStaticPropsContext } from 'next'
|
||||
import commerce from '@lib/api/commerce'
|
||||
import { Layout } from '@components/common'
|
||||
// import useCart from '@framework/cart/use-cart'
|
||||
// import usePrice from '@framework/product/use-price'
|
||||
// import { Button, Text } from '@components/ui'
|
||||
// import { Bag, Cross, Check, MapPin, CreditCard } from '@components/icons'
|
||||
// import { CartItem } from '@components/cart'
|
||||
|
||||
export async function getStaticProps({
|
||||
preview,
|
||||
locale,
|
||||
locales,
|
||||
}: GetStaticPropsContext) {
|
||||
const config = { locale, locales }
|
||||
const pagesPromise = commerce.getAllPages({ config, preview })
|
||||
const siteInfoPromise = commerce.getSiteInfo({ config, preview })
|
||||
const { pages } = await pagesPromise
|
||||
const { categories } = await siteInfoPromise
|
||||
return {
|
||||
props: { pages, categories },
|
||||
}
|
||||
}
|
||||
|
||||
export default function Cart() {
|
||||
// const error = null
|
||||
// const success = null
|
||||
// const { data, isLoading, isEmpty } = useCart()
|
||||
|
||||
// const { price: subTotal } = usePrice(
|
||||
// data && {
|
||||
// amount: Number(data.subtotalPrice),
|
||||
// currencyCode: data.currency.code,
|
||||
// }
|
||||
// )
|
||||
// const { price: total } = usePrice(
|
||||
// data && {
|
||||
// amount: Number(data.totalPrice),
|
||||
// currencyCode: data.currency.code,
|
||||
// }
|
||||
// )
|
||||
|
||||
return (
|
||||
<div>
|
||||
This is cart page
|
||||
</div>
|
||||
)
|
||||
}
|
||||
|
||||
Cart.Layout = Layout
|
17
pages/demo.tsx
Normal file
@ -0,0 +1,17 @@
|
||||
import { Layout, RecipeDetail } from 'src/components/common';
|
||||
import { ProductInfoDetail, ViewedProducts, ReleventProducts, RecommendedRecipes } from 'src/components/modules/product-detail';
|
||||
import { INGREDIENT_DATA_TEST, RECIPE_DATA_TEST } from 'src/utils/demo-data';
|
||||
|
||||
|
||||
|
||||
export default function Demo() {
|
||||
return <>
|
||||
<ProductInfoDetail />
|
||||
<RecipeDetail ingredients={INGREDIENT_DATA_TEST}/>
|
||||
<RecommendedRecipes data={RECIPE_DATA_TEST}/>
|
||||
<ReleventProducts/>
|
||||
<ViewedProducts/>
|
||||
</>
|
||||
}
|
||||
|
||||
Demo.Layout = Layout
|
@ -1,41 +1,36 @@
|
||||
|
||||
import { Layout } from 'src/components/common';
|
||||
import { HomeBanner, HomeCategories, HomeCTA, HomeSubscribe, HomeVideo } from 'src/components/modules/home';
|
||||
import { OPTION_ALL, QUERY_KEY, ROUTE } from 'src/utils/constanst.utils'
|
||||
const CATEGORY = [
|
||||
import { HomeBanner, HomeCategories, HomeCollection, HomeCTA, HomeFeature, HomeRecipe, HomeSubscribe, HomeVideo } from 'src/components/modules/home';
|
||||
import {SelectCommon} from 'src/components/common'
|
||||
|
||||
const OPTION_SORT = [
|
||||
{
|
||||
name: 'All',
|
||||
link: `${ROUTE.PRODUCTS}/?${QUERY_KEY.BRAND}=${OPTION_ALL}`,
|
||||
name: "By Name"
|
||||
},
|
||||
{
|
||||
name: 'Veggie',
|
||||
link: `${ROUTE.PRODUCTS}/?${QUERY_KEY.BRAND}=veggie`,
|
||||
name: "Price (High to Low)"
|
||||
},
|
||||
{
|
||||
name: 'Seafood',
|
||||
link: `${ROUTE.PRODUCTS}/?${QUERY_KEY.BRAND}=seafood`,
|
||||
},
|
||||
{
|
||||
name: 'Frozen',
|
||||
link: `${ROUTE.PRODUCTS}/?${QUERY_KEY.BRAND}=frozen`,
|
||||
},
|
||||
{
|
||||
name: 'Coffee Bean',
|
||||
link: `${ROUTE.PRODUCTS}/?${QUERY_KEY.BRAND}=coffee-bean`,
|
||||
},
|
||||
{
|
||||
name: 'Sauce',
|
||||
link: `${ROUTE.PRODUCTS}/?${QUERY_KEY.BRAND}=sauce`,
|
||||
},
|
||||
name: "On Sale"
|
||||
}
|
||||
]
|
||||
|
||||
export default function Home() {
|
||||
return (
|
||||
<>
|
||||
{/* <HomeBanner />
|
||||
<HomeBanner/>
|
||||
<HomeFeature />
|
||||
<HomeCategories />
|
||||
<HomeCollection />
|
||||
<HomeVideo />
|
||||
<HomeCTA />
|
||||
<HomeSubscribe />
|
||||
<HomeRecipe />
|
||||
<HomeSubscribe /> */}
|
||||
<SelectCommon option={OPTION_SORT}>Sort By</SelectCommon>
|
||||
<SelectCommon option={OPTION_SORT} size="large" type="custom">Sort By</SelectCommon>
|
||||
|
||||
{/* // todo: uncomment */}
|
||||
{/* <ModalCreateUserInfo/> */}
|
||||
</>
|
||||
)
|
||||
}
|
||||
|
@ -1,76 +1,16 @@
|
||||
import { Layout } from '@components/common'
|
||||
import commerce from '@lib/api/commerce'
|
||||
import type {
|
||||
GetStaticPathsContext,
|
||||
GetStaticPropsContext,
|
||||
InferGetStaticPropsType
|
||||
} from 'next'
|
||||
import { useRouter } from 'next/router'
|
||||
|
||||
export async function getStaticProps({
|
||||
params,
|
||||
locale,
|
||||
locales,
|
||||
preview,
|
||||
}: GetStaticPropsContext<{ slug: string }>) {
|
||||
const config = { locale, locales }
|
||||
const pagesPromise = commerce.getAllPages({ config, preview })
|
||||
const siteInfoPromise = commerce.getSiteInfo({ config, preview })
|
||||
const productPromise = commerce.getProduct({
|
||||
variables: { slug: params!.slug },
|
||||
config,
|
||||
preview,
|
||||
})
|
||||
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'
|
||||
|
||||
const allProductsPromise = commerce.getAllProducts({
|
||||
variables: { first: 4 },
|
||||
config,
|
||||
preview,
|
||||
})
|
||||
const { pages } = await pagesPromise
|
||||
const { categories } = await siteInfoPromise
|
||||
const { product } = await productPromise
|
||||
const { products: relatedProducts } = await allProductsPromise
|
||||
|
||||
if (!product) {
|
||||
throw new Error(`Product with slug '${params!.slug}' not found`)
|
||||
}
|
||||
|
||||
return {
|
||||
props: {
|
||||
pages,
|
||||
product,
|
||||
relatedProducts,
|
||||
categories,
|
||||
},
|
||||
revalidate: 200,
|
||||
}
|
||||
}
|
||||
|
||||
export async function getStaticPaths({ locales }: GetStaticPathsContext) {
|
||||
const { products } = await commerce.getAllProductPaths()
|
||||
|
||||
return {
|
||||
paths: locales
|
||||
? locales.reduce<string[]>((arr, locale) => {
|
||||
// Add a product path for every locale
|
||||
products.forEach((product: any) => {
|
||||
arr.push(`/${locale}/product${product.path}`)
|
||||
})
|
||||
return arr
|
||||
}, [])
|
||||
: products.map((product: any) => `/product${product.path}`),
|
||||
fallback: 'blocking',
|
||||
}
|
||||
}
|
||||
|
||||
export default function Slug({
|
||||
product,
|
||||
relatedProducts,
|
||||
}: InferGetStaticPropsType<typeof getStaticProps>) {
|
||||
const router = useRouter()
|
||||
|
||||
return <div>This is product page</div>
|
||||
export default function Slug() {
|
||||
return <>
|
||||
<ProductInfoDetail />
|
||||
<RecipeDetail ingredients={INGREDIENT_DATA_TEST} />
|
||||
<RecommendedRecipes data={RECIPE_DATA_TEST} />
|
||||
<ReleventProducts />
|
||||
<ViewedProducts />
|
||||
</>
|
||||
}
|
||||
|
||||
Slug.Layout = Layout
|
||||
|
@ -1,81 +1,24 @@
|
||||
|
||||
import { Layout } from 'src/components/common';
|
||||
import { RecipesListPage } from 'src/components/modules/recipes';
|
||||
import { BlogDetailImg } from 'src/components/modules/blogs';
|
||||
import BlogDetail from '../src/components/modules/blogs/BlogDetailImg/img/blogdetail.png';
|
||||
import { OPTION_ALL, QUERY_KEY, ROUTE } from 'src/utils/constanst.utils'
|
||||
import MenuNavigation from 'src/components/common/MenuNavigation/MenuNavigation';
|
||||
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`,
|
||||
},
|
||||
];
|
||||
// const BRANDS = [
|
||||
|
||||
// {
|
||||
// name: 'Maggi',
|
||||
// link: `${ROUTE.PRODUCTS}/?${QUERY_KEY.BRAND}=veggie`,
|
||||
// },
|
||||
// {
|
||||
// name: 'Chomilex',
|
||||
// link: `${ROUTE.PRODUCTS}/?${QUERY_KEY.BRAND}=chomilex`,
|
||||
// },
|
||||
// {
|
||||
// name: 'Chinsu',
|
||||
// link: `${ROUTE.PRODUCTS}/?${QUERY_KEY.BRAND}=chinsu`,
|
||||
// }
|
||||
// ];
|
||||
|
||||
// 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`,
|
||||
// }
|
||||
// ]
|
||||
import { useState } from 'react'
|
||||
import {
|
||||
ButtonCommon,
|
||||
Layout, ModalInfo
|
||||
} from 'src/components/common'
|
||||
export default function Test() {
|
||||
const [visible, setVisible] = useState(false)
|
||||
const onClose = () => {
|
||||
setVisible(false)
|
||||
}
|
||||
const onOpen = () => {
|
||||
setVisible(true)
|
||||
}
|
||||
return (
|
||||
<>
|
||||
{/* <RecipesListPage/> */}
|
||||
{/* <MenuNavigationProductList categories={CATEGORY} brands={BRANDS} featured={FEATURED}/> */}
|
||||
{/* <MenuFilter categories={CATEGORY} heading="Categories"/> */}
|
||||
{/* <MenuNavigation categories={CATEGORY} heading="Categories"/> */}
|
||||
<MenuNavigation categories={CATEGORY} heading="Categories"/>
|
||||
{/* <BlogDetailImg image={BlogDetail}/> */}
|
||||
<ButtonCommon onClick={onOpen}>open</ButtonCommon>
|
||||
<ModalInfo visible={visible} onClose={onClose}>
|
||||
Lorem, ipsum dolor sit amet consectetur adipisicing elit. Nisi qui, esse eos nobis soluta suscipit aliquid nostrum corporis. Nihil eligendi similique recusandae minus mollitia aliquam, molestias fugit tenetur voluptatibus maiores et. Quaerat labore corporis inventore nostrum, amet autem exercitationem eligendi?
|
||||
</ModalInfo>
|
||||
</>
|
||||
)
|
||||
}
|
||||
|
||||
Test.Layout = Layout
|
||||
|
BIN
public/assets/images/image10.png
Normal file
After Width: | Height: | Size: 11 KiB |
BIN
public/assets/images/image11.png
Normal file
After Width: | Height: | Size: 18 KiB |
BIN
public/assets/images/image12.png
Normal file
After Width: | Height: | Size: 52 KiB |
BIN
public/assets/images/image13.png
Normal file
After Width: | Height: | Size: 46 KiB |
BIN
public/assets/images/image14.png
Normal file
After Width: | Height: | Size: 49 KiB |
BIN
public/assets/images/image5.png
Normal file
After Width: | Height: | Size: 6.8 KiB |
BIN
public/assets/images/image6.png
Normal file
After Width: | Height: | Size: 7.3 KiB |
BIN
public/assets/images/image7.png
Normal file
After Width: | Height: | Size: 6.1 KiB |
BIN
public/assets/images/image8.png
Normal file
After Width: | Height: | Size: 7.7 KiB |
BIN
public/assets/images/image9.png
Normal file
After Width: | Height: | Size: 15 KiB |
@ -1,52 +0,0 @@
|
||||
@import "../../../styles/utilities";
|
||||
|
||||
.banner {
|
||||
@apply bg-primary-light custom-border-radius-lg overflow-hidden;
|
||||
@screen md {
|
||||
border: 1px solid var(--primary);
|
||||
}
|
||||
&.large {
|
||||
margin-bottom: 2.8rem;
|
||||
.inner {
|
||||
@screen xl {
|
||||
@apply bg-right-bottom;
|
||||
background-size: unset;
|
||||
}
|
||||
}
|
||||
}
|
||||
.inner {
|
||||
@apply bg-no-repeat;
|
||||
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;
|
||||
max-width: 37rem;
|
||||
@screen md {
|
||||
max-width: 49.6rem;
|
||||
padding: 4.8rem;
|
||||
}
|
||||
.top {
|
||||
.heading {
|
||||
@apply heading-1 font-heading;
|
||||
margin-bottom: 1.6rem;
|
||||
}
|
||||
.subHeading {
|
||||
@apply sub-headline;
|
||||
@screen md {
|
||||
@apply caption;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.bottom {
|
||||
margin-top: 4rem;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
@ -1,47 +1,24 @@
|
||||
import classNames from 'classnames'
|
||||
import Link from 'next/link'
|
||||
import React, { memo } from 'react'
|
||||
import { IconArrowRight } from 'src/components/icons'
|
||||
import { ROUTE } from 'src/utils/constanst.utils'
|
||||
import { LANGUAGE } from 'src/utils/language.utils'
|
||||
import ButtonCommon from '../ButtonCommon/ButtonCommon'
|
||||
import s from './Banner.module.scss'
|
||||
import CarouselCommon from '../CarouselCommon/CarouselCommon'
|
||||
import BannerItem, { BannerItemProps } from './BannerItem/BannerItem'
|
||||
|
||||
interface Props {
|
||||
imgLink: string,
|
||||
title: string,
|
||||
subtitle: string,
|
||||
buttonLabel?: string,
|
||||
linkButton?: string,
|
||||
size?: 'small' | 'large',
|
||||
data: BannerItemProps[],
|
||||
}
|
||||
|
||||
const Banner = memo(({ imgLink, title, subtitle, buttonLabel = LANGUAGE.BUTTON_LABEL.SHOP_NOW, linkButton = ROUTE.HOME, size = 'large' }: Props) => {
|
||||
const option = {
|
||||
slidesPerView: 1,
|
||||
breakpoints: {}
|
||||
}
|
||||
const Banner = memo(({ data }: Props) => {
|
||||
return (
|
||||
<div className={classNames({
|
||||
[s.banner]: true,
|
||||
[s[size]]: true,
|
||||
})}>
|
||||
<div className={s.inner} style={{ backgroundImage: `url(${imgLink})` }}>
|
||||
<div className={s.content}>
|
||||
<div className={s.top}>
|
||||
<h1 className={s.heading}>
|
||||
{title}
|
||||
</h1>
|
||||
<div className={s.subHeading}>
|
||||
{subtitle}
|
||||
</div>
|
||||
</div>
|
||||
<div className={s.bottom}>
|
||||
<Link href={linkButton}>
|
||||
<a>
|
||||
<ButtonCommon icon={<IconArrowRight />} isIconSuffix={true}>{buttonLabel}</ButtonCommon>
|
||||
</a>
|
||||
</Link>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<CarouselCommon<BannerItemProps>
|
||||
data={data}
|
||||
itemKey="banner"
|
||||
Component={BannerItem}
|
||||
option={option}
|
||||
isDot = {true}
|
||||
/>
|
||||
)
|
||||
})
|
||||
|
||||
|
@ -0,0 +1,52 @@
|
||||
@import "../../../../styles/utilities";
|
||||
|
||||
.bannerItem {
|
||||
@apply bg-primary-light custom-border-radius-lg overflow-hidden;
|
||||
@screen md {
|
||||
border: 1px solid var(--primary);
|
||||
}
|
||||
&.large {
|
||||
margin-bottom: 2.8rem;
|
||||
.inner {
|
||||
@screen xl {
|
||||
@apply bg-right-bottom;
|
||||
background-size: unset;
|
||||
}
|
||||
}
|
||||
}
|
||||
.inner {
|
||||
@apply bg-no-repeat;
|
||||
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;
|
||||
max-width: 37rem;
|
||||
@screen md {
|
||||
max-width: 49.6rem;
|
||||
padding: 4.8rem;
|
||||
}
|
||||
.top {
|
||||
.heading {
|
||||
@apply heading-1 font-heading;
|
||||
margin-bottom: 1.6rem;
|
||||
}
|
||||
.subHeading {
|
||||
@apply sub-headline;
|
||||
@screen md {
|
||||
@apply caption;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.bottom {
|
||||
margin-top: 4rem;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
48
src/components/common/Banner/BannerItem/BannerItem.tsx
Normal file
@ -0,0 +1,48 @@
|
||||
import classNames from 'classnames'
|
||||
import Link from 'next/link'
|
||||
import React, { memo } from 'react'
|
||||
import { IconArrowRight } from 'src/components/icons'
|
||||
import { ROUTE } from 'src/utils/constanst.utils'
|
||||
import { LANGUAGE } from 'src/utils/language.utils'
|
||||
import ButtonCommon from '../../ButtonCommon/ButtonCommon'
|
||||
import s from './BannerItem.module.scss'
|
||||
|
||||
export interface BannerItemProps {
|
||||
imgLink: string,
|
||||
title: string,
|
||||
subtitle: string,
|
||||
buttonLabel?: string,
|
||||
linkButton?: string,
|
||||
size?: 'small' | 'large',
|
||||
}
|
||||
|
||||
const BannerItem = memo(({ imgLink, title, subtitle, buttonLabel = LANGUAGE.BUTTON_LABEL.SHOP_NOW, linkButton = ROUTE.HOME, size = 'large' }: BannerItemProps) => {
|
||||
return (
|
||||
<div className={classNames({
|
||||
[s.bannerItem]: true,
|
||||
[s[size]]: true,
|
||||
})}>
|
||||
<div className={s.inner} style={{ backgroundImage: `url(${imgLink})` }}>
|
||||
<div className={s.content}>
|
||||
<div className={s.top}>
|
||||
<h1 className={s.heading}>
|
||||
{title}
|
||||
</h1>
|
||||
<div className={s.subHeading}>
|
||||
{subtitle}
|
||||
</div>
|
||||
</div>
|
||||
<div className={s.bottom}>
|
||||
<Link href={linkButton}>
|
||||
<a>
|
||||
<ButtonCommon icon={<IconArrowRight />} isIconSuffix={true}>{buttonLabel}</ButtonCommon>
|
||||
</a>
|
||||
</Link>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
)
|
||||
})
|
||||
|
||||
export default BannerItem
|
@ -2,4 +2,10 @@
|
||||
|
||||
.breadcrumbCommon {
|
||||
color: var(--text-base);
|
||||
<<<<<<< HEAD
|
||||
=======
|
||||
.currentItem {
|
||||
cursor: default;
|
||||
}
|
||||
>>>>>>> a9f9f06eb9dee2a1ddefe907ff804237a78c5210
|
||||
}
|
||||
|
@ -1,6 +1,6 @@
|
||||
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'
|
||||
|
||||
@ -10,54 +10,25 @@ interface BreadcrumbCommonProps {
|
||||
}
|
||||
|
||||
const BreadcrumbCommon = ({ crumbs, showHomePage = true }: BreadcrumbCommonProps) => {
|
||||
console.log(crumbs);
|
||||
return (
|
||||
<section className={s.breadcrumbCommon}>
|
||||
{
|
||||
showHomePage && crumbs[0].link==="/" && crumbs.map((crumb, i) => {
|
||||
if (i === 0) {
|
||||
return (
|
||||
<BreadcrumbItem key={crumb.name} text={crumb.name} href={crumb.link} />
|
||||
)
|
||||
}
|
||||
if (i === crumbs.length-1) {
|
||||
return (
|
||||
<BreadcrumbSeparator key={crumb.name}>
|
||||
<span>{crumb.name}</span>
|
||||
</BreadcrumbSeparator>
|
||||
)
|
||||
}
|
||||
return (
|
||||
<BreadcrumbSeparator key={crumb.name}>
|
||||
<BreadcrumbItem text={crumb.name} href={crumb.link} />
|
||||
</BreadcrumbSeparator>
|
||||
)
|
||||
})
|
||||
|
||||
showHomePage && <BreadcrumbItem key='Home' text='Home' href={ROUTE.HOME} />
|
||||
}
|
||||
{
|
||||
crumbs.length > 0 && <>
|
||||
|
||||
{
|
||||
!showHomePage && crumbs.map((crumb, i) => {
|
||||
if (i === 0) {
|
||||
return
|
||||
}
|
||||
if (i === 1) {
|
||||
return (
|
||||
<BreadcrumbItem key={crumb.name} text={crumb.name} href={crumb.link} />
|
||||
)
|
||||
}
|
||||
if (i === crumbs.length-1) {
|
||||
return (
|
||||
<BreadcrumbSeparator key={crumb.name}>
|
||||
<span>{crumb.name}</span>
|
||||
</BreadcrumbSeparator>
|
||||
)
|
||||
}
|
||||
return (
|
||||
crumbs.slice(0, crumbs.length - 1).map((crumb) => (
|
||||
< BreadcrumbSeparator key={crumb.name}>
|
||||
<BreadcrumbItem text={crumb.name} href={crumb.link} />
|
||||
</BreadcrumbSeparator>
|
||||
)
|
||||
})
|
||||
))}
|
||||
< BreadcrumbSeparator>
|
||||
<span className={s.currentItem}>{crumbs[crumbs.length - 1].name}</span>
|
||||
</BreadcrumbSeparator>
|
||||
</>
|
||||
}
|
||||
</section >
|
||||
)
|
||||
|
@ -0,0 +1,5 @@
|
||||
.breadcrumbItem {
|
||||
&:hover {
|
||||
color: var(--primary);
|
||||
}
|
||||
}
|
@ -1,5 +1,6 @@
|
||||
import React from 'react'
|
||||
import Link from 'next/link'
|
||||
import s from './BreadcrumbItem.module.scss'
|
||||
|
||||
interface BreadcrumbItemProps {
|
||||
text: string;
|
||||
@ -9,7 +10,7 @@ interface BreadcrumbItemProps {
|
||||
const BreadcrumbItem = ({ text, href }: BreadcrumbItemProps) => {
|
||||
return (
|
||||
<Link href={href}>
|
||||
<a>{text}</a>
|
||||
<a className={s.breadcrumbItem}>{text}</a>
|
||||
</Link>
|
||||
)
|
||||
}
|
||||
|
@ -5,7 +5,13 @@
|
||||
display: flex;
|
||||
justify-content: center;
|
||||
align-items: center;
|
||||
padding: 1.2rem 3.2rem;
|
||||
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;
|
||||
@ -63,7 +69,7 @@
|
||||
border: 1px solid var(--primary);
|
||||
&.loading {
|
||||
&::before {
|
||||
border-top-color: var(--primary);
|
||||
border-top-color: var(--text-active);
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -76,10 +82,19 @@
|
||||
}
|
||||
|
||||
&.large {
|
||||
padding: 1.6rem 4.8rem;
|
||||
padding: 1rem 1.5rem;
|
||||
&.onlyIcon {
|
||||
padding: 1rem;
|
||||
}
|
||||
@screen md {
|
||||
padding: 1.6rem 3.2rem;
|
||||
&.onlyIcon {
|
||||
padding: 1.6rem;
|
||||
}
|
||||
}
|
||||
@screen lg {
|
||||
padding: 1.6rem 4.8rem;
|
||||
}
|
||||
&.loading {
|
||||
&::before {
|
||||
width: 2.4rem;
|
||||
@ -97,10 +112,6 @@
|
||||
|
||||
.icon {
|
||||
margin: 0 1.6rem 0 0;
|
||||
}
|
||||
|
||||
.label,
|
||||
.icon {
|
||||
svg path {
|
||||
fill: currentColor;
|
||||
}
|
||||
|
@ -1,5 +0,0 @@
|
||||
.navigation_wrapper{
|
||||
@apply relative;
|
||||
min-height: theme("caroucel.arrow-height") ;
|
||||
|
||||
}
|
@ -0,0 +1,57 @@
|
||||
@import '../../../styles/utilities';
|
||||
.navigationWrapper {
|
||||
@apply relative;
|
||||
min-height: theme('caroucel.arrow-height');
|
||||
.isPadding {
|
||||
@apply spacing-horizontal;
|
||||
}
|
||||
:global(.customArrow) {
|
||||
width: 64px;
|
||||
height: 64px;
|
||||
&:focus {
|
||||
outline: none;
|
||||
}
|
||||
@apply absolute top-1/2 bg-background-arrow transform -translate-y-1/2 flex justify-center items-center transition duration-100;
|
||||
&:global(.leftArrow) {
|
||||
@apply hidden left-0;
|
||||
@screen md {
|
||||
@apply flex
|
||||
}
|
||||
}
|
||||
&:global(.rightArrow) {
|
||||
@apply hidden right-0;
|
||||
@screen md {
|
||||
@apply flex;
|
||||
}
|
||||
}
|
||||
&:global(.isDisabledArrow) {
|
||||
@apply hidden;
|
||||
}
|
||||
}
|
||||
:global {
|
||||
.dots {
|
||||
display: flex;
|
||||
padding: 1rem 0;
|
||||
justify-content: center;
|
||||
}
|
||||
|
||||
.dot {
|
||||
border: none;
|
||||
width: 1rem;
|
||||
height: 1rem;
|
||||
background: #c5c5c5;
|
||||
border-radius: 50%;
|
||||
margin: 0 0.5rem;
|
||||
padding: 0.5rem;
|
||||
cursor: pointer;
|
||||
}
|
||||
|
||||
.dot:focus {
|
||||
outline: none;
|
||||
}
|
||||
|
||||
.dot.active {
|
||||
background: #000;
|
||||
}
|
||||
}
|
||||
}
|
@ -1,25 +1,55 @@
|
||||
import { useKeenSlider } from 'keen-slider/react'
|
||||
import React from 'react'
|
||||
import React, { useEffect } from 'react'
|
||||
import 'keen-slider/keen-slider.min.css'
|
||||
import { CustomCarouselArrow } from './CustomArrow/CustomCarouselArrow';
|
||||
import s from "./CaroucelCommon.module.scss"
|
||||
interface CarouselCommonProps {
|
||||
children?: React.ReactNode
|
||||
data?: any[]
|
||||
Component: React.ComponentType<any>
|
||||
import { CustomCarouselArrow } from './CustomArrow/CustomCarouselArrow'
|
||||
import s from './CarouselCommon.module.scss'
|
||||
import { TOptionsEvents } from 'keen-slider'
|
||||
import classNames from 'classnames'
|
||||
import CustomDot from './CustomDot/CustomDot'
|
||||
export interface CarouselCommonProps<T> {
|
||||
data: T[]
|
||||
Component: React.ComponentType<T>
|
||||
isArrow?: Boolean
|
||||
isDot?: Boolean
|
||||
itemKey: String
|
||||
option: TOptionsEvents
|
||||
keenClassname?: string
|
||||
isPadding?: boolean
|
||||
}
|
||||
|
||||
const CarouselCommon = ({ data, Component,itemKey }: CarouselCommonProps) => {
|
||||
const CarouselCommon = <T,>({
|
||||
data,
|
||||
Component,
|
||||
itemKey,
|
||||
keenClassname,
|
||||
isPadding = false,
|
||||
isArrow = true,
|
||||
isDot = false,
|
||||
option: { slideChanged,slidesPerView, ...sliderOption },
|
||||
}: CarouselCommonProps<T>) => {
|
||||
const [currentSlide, setCurrentSlide] = React.useState(0)
|
||||
// const [dotActive, setDotActive] = React.useState<number>(0)
|
||||
const [dotArr, setDotArr] = React.useState<number[]>([])
|
||||
const [sliderRef, slider] = useKeenSlider<HTMLDivElement>({
|
||||
slidesPerView: 1,
|
||||
initial: 0,
|
||||
...sliderOption,
|
||||
slidesPerView,
|
||||
slideChanged(s) {
|
||||
setCurrentSlide(s.details().relativeSlide)
|
||||
},
|
||||
})
|
||||
|
||||
useEffect(() => {
|
||||
if(isDot && slider && data){
|
||||
let array:number[]
|
||||
let number = data.length - Math.floor(slider.details().slidesPerView - 1)
|
||||
if(number<1){
|
||||
number = 1
|
||||
}
|
||||
array = [...Array(number).keys()]
|
||||
setDotArr(array)
|
||||
}
|
||||
}, [isDot,slider,data])
|
||||
|
||||
const handleRightArrowClick = () => {
|
||||
slider.next()
|
||||
}
|
||||
@ -27,29 +57,45 @@ const CarouselCommon = ({ data, Component,itemKey }: CarouselCommonProps) => {
|
||||
const handleLeftArrowClick = () => {
|
||||
slider.prev()
|
||||
}
|
||||
|
||||
const onDotClick = (index:number) => {
|
||||
slider.moveToSlideRelative(index)
|
||||
}
|
||||
return (
|
||||
<div className={s.navigation_wrapper}>
|
||||
<div ref={sliderRef} className="keen-slider">
|
||||
<div className={s.navigationWrapper}>
|
||||
<div
|
||||
ref={sliderRef}
|
||||
className={classNames('keen-slider', keenClassname, {
|
||||
[s.isPadding]: isPadding,
|
||||
})}
|
||||
>
|
||||
{data?.map((props, index) => (
|
||||
<div className="keen-slider__slide" key={`${itemKey}-${index}`}>
|
||||
<Component {...props} />
|
||||
</div>
|
||||
))}
|
||||
</div>
|
||||
{slider && (
|
||||
{slider && isArrow && (
|
||||
<>
|
||||
<CustomCarouselArrow
|
||||
side="right"
|
||||
onClick={handleRightArrowClick}
|
||||
isDisabled={currentSlide === slider.details().size - 1}
|
||||
/>
|
||||
<CustomCarouselArrow
|
||||
side="left"
|
||||
onClick={handleLeftArrowClick}
|
||||
isDisabled={currentSlide === 0}
|
||||
/>
|
||||
</>
|
||||
)}
|
||||
{slider && isDot && (
|
||||
<div className="dots">
|
||||
{dotArr.map((index) => {
|
||||
return (
|
||||
<CustomDot key={`dot-${index}`} index={index} dotActive={currentSlide} onClick={onDotClick}/>
|
||||
)
|
||||
})}
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
)
|
||||
}
|
||||
|
@ -1,17 +1,2 @@
|
||||
.custom_arrow{
|
||||
width: 64px;
|
||||
height: 64px;
|
||||
&:focus{
|
||||
outline: none;
|
||||
}
|
||||
@apply absolute top-1/2 bg-background-arrow transform -translate-y-1/2 flex justify-center items-center transition duration-100;
|
||||
&.left{
|
||||
@apply left-0;
|
||||
}
|
||||
&.right{
|
||||
@apply right-0;
|
||||
}
|
||||
&.isDisabled{
|
||||
@apply hidden ;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
|
@ -2,11 +2,12 @@ import classNames from 'classnames'
|
||||
import React from 'react'
|
||||
import ArrowLeft from 'src/components/icons/ArrowLeft'
|
||||
import ArrowRight from 'src/components/icons/ArrowRight'
|
||||
import s from "./CustomCarouselArrow.module.scss"
|
||||
import "./CustomCarouselArrow.module.scss"
|
||||
|
||||
interface CustomCarouselArrowProps
|
||||
extends React.ButtonHTMLAttributes<HTMLButtonElement> {
|
||||
side: 'left' | 'right'
|
||||
isDisabled:Boolean
|
||||
isDisabled?:Boolean
|
||||
}
|
||||
|
||||
export const CustomCarouselArrow = ({
|
||||
@ -16,7 +17,7 @@ export const CustomCarouselArrow = ({
|
||||
return (
|
||||
<button
|
||||
{...props}
|
||||
className={classNames(`${s.custom_arrow}`, { [`${s[side]}`]: side,[`${s.isDisabled}`]:isDisabled })}
|
||||
className={classNames("customArrow", { [`${side}Arrow`]: side,"isDisabledArrow":isDisabled})}
|
||||
>
|
||||
{side==='left'?(<ArrowLeft/>):(<ArrowRight/>)}
|
||||
</button>
|
||||
|
21
src/components/common/CarouselCommon/CustomDot/CustomDot.tsx
Normal file
@ -0,0 +1,21 @@
|
||||
import React from 'react'
|
||||
|
||||
interface Props {
|
||||
index: number
|
||||
dotActive:number
|
||||
onClick: (index: number) => void
|
||||
}
|
||||
|
||||
const CustomDot = ({ index, onClick, dotActive }: Props) => {
|
||||
const handleOnClick = () => {
|
||||
onClick && onClick(index)
|
||||
}
|
||||
return (
|
||||
<button
|
||||
onClick={handleOnClick}
|
||||
className={'dot' + (dotActive === index ? ' active' : '')}
|
||||
/>
|
||||
)
|
||||
}
|
||||
|
||||
export default CustomDot
|
@ -1,3 +1,5 @@
|
||||
.subtitle {
|
||||
font-size: var(--font-size);
|
||||
line-height: var(--line-height);
|
||||
margin-top: .4rem;
|
||||
}
|
@ -2,7 +2,7 @@ import React from 'react'
|
||||
import s from './CollectionHeading.module.scss'
|
||||
import HeadingCommon from '../HeadingCommon/HeadingCommon'
|
||||
|
||||
interface CollectionHeadingProps {
|
||||
export interface CollectionHeadingProps {
|
||||
type?: 'default' | 'highlight' | 'light';
|
||||
title: string;
|
||||
subtitle: string;
|
||||
|
@ -0,0 +1,61 @@
|
||||
@import '../../../styles/utilities';
|
||||
.featuredProductCardWarpper{
|
||||
width: 59.8rem;
|
||||
height: 28.8rem;
|
||||
padding: 2.4rem;
|
||||
@apply bg-primary-light inline-flex justify-start items-center custom-border-radius ;
|
||||
.left{
|
||||
width: 24rem;
|
||||
height: 24rem;
|
||||
}
|
||||
.right{
|
||||
padding-left: 2.4rem;
|
||||
min-width: 27rem;
|
||||
max-width: 28.6rem;
|
||||
min-height: 16.8rem;
|
||||
@apply flex justify-between flex-col;
|
||||
.rightTop{
|
||||
min-height: 9.6rem;
|
||||
@apply flex justify-between flex-col;
|
||||
.title{
|
||||
@apply font-bold;
|
||||
font-size: 2rem;
|
||||
line-height: 2.8rem;
|
||||
letter-spacing: -0.01em;
|
||||
color: var(--text-active);
|
||||
}
|
||||
.subTitle{
|
||||
color: var(--text-base);
|
||||
font-size: 1.6rem;
|
||||
line-height: 2.4rem;
|
||||
}
|
||||
.priceWrapper{
|
||||
@apply flex justify-start;
|
||||
.price{
|
||||
@apply font-bold;
|
||||
font-size: 2rem;
|
||||
line-height: 2.8rem;
|
||||
letter-spacing: -0.01em;
|
||||
color: var(--text-active);
|
||||
}
|
||||
.originPrice{
|
||||
margin-left: 0.8rem;
|
||||
font-size: 2rem;
|
||||
line-height: 2.8rem;
|
||||
color: var(--text-label);
|
||||
text-decoration-line: line-through;
|
||||
}
|
||||
}
|
||||
}
|
||||
.buttonWarpper{
|
||||
@apply flex;
|
||||
.icon{
|
||||
width: 5.6rem;
|
||||
}
|
||||
.button{
|
||||
margin-left: 0.8rem;
|
||||
width: 20.6rem;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
@ -0,0 +1,46 @@
|
||||
import React from 'react'
|
||||
import { FeaturedProductProps } from 'src/utils/types.utils'
|
||||
import s from './FeaturedProductCard.module.scss'
|
||||
import { LANGUAGE } from '../../../utils/language.utils'
|
||||
import ButtonIconBuy from '../ButtonIconBuy/ButtonIconBuy'
|
||||
import ButtonCommon from '../ButtonCommon/ButtonCommon'
|
||||
interface FeaturedProductCardProps extends FeaturedProductProps {
|
||||
buttonText?: string
|
||||
}
|
||||
|
||||
const FeaturedProductCard = ({
|
||||
imageSrc,
|
||||
title,
|
||||
subTitle,
|
||||
price,
|
||||
originPrice,
|
||||
buttonText = LANGUAGE.BUTTON_LABEL.BUY_NOW,
|
||||
}: FeaturedProductCardProps) => {
|
||||
return (
|
||||
<div className={s.featuredProductCardWarpper}>
|
||||
<div className={s.left}>
|
||||
<img src={imageSrc} alt="image" />
|
||||
</div>
|
||||
<div className={s.right}>
|
||||
<div className={s.rightTop}>
|
||||
<div className={s.title}>{title}</div>
|
||||
<div className={s.subTitle}>{subTitle}</div>
|
||||
<div className={s.priceWrapper}>
|
||||
<div className={s.price}>{price} </div>
|
||||
<div className={s.originPrice}>{originPrice} </div>
|
||||
</div>
|
||||
</div>
|
||||
<div className={s.buttonWarpper}>
|
||||
<div className={s.icon}>
|
||||
<ButtonIconBuy />
|
||||
</div>
|
||||
<div className={s.button}>
|
||||
<ButtonCommon>{buttonText}</ButtonCommon>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
|
||||
export default FeaturedProductCard
|
@ -1,19 +1,20 @@
|
||||
import classNames from 'classnames'
|
||||
import React, { memo, useEffect, useState } from 'react'
|
||||
import { useModalCommon } from 'src/components/hooks'
|
||||
import { isMobile } from 'src/utils/funtion.utils'
|
||||
import ModalAuthenticate from '../ModalAuthenticate/ModalAuthenticate'
|
||||
import ModalCreateUserInfo from '../ModalCreateUserInfo/ModalCreateUserInfo'
|
||||
import HeaderHighLight from './components/HeaderHighLight/HeaderHighLight'
|
||||
import HeaderMenu from './components/HeaderMenu/HeaderMenu'
|
||||
import HeaderSubMenu from './components/HeaderSubMenu/HeaderSubMenu'
|
||||
import HeaderSubMenuMobile from './components/HeaderSubMenuMobile/HeaderSubMenuMobile'
|
||||
import s from './Header.module.scss'
|
||||
|
||||
interface Props {
|
||||
className?: string
|
||||
children?: any
|
||||
}
|
||||
|
||||
const Header = memo(({ }: Props) => {
|
||||
const Header = memo(() => {
|
||||
const [isFullHeader, setIsFullHeader] = useState<boolean>(true)
|
||||
const { visible: visibleModalAuthen, closeModal: closeModalAuthen, openModal: openModalAuthen } = useModalCommon({ initialValue: false })
|
||||
const { visible: visibleModalInfo, closeModal: closeModalInfo, openModal: openModalInfo } = useModalCommon({ initialValue: false })
|
||||
|
||||
useEffect(() => {
|
||||
window.addEventListener('scroll', handleScroll)
|
||||
@ -36,11 +37,15 @@ const Header = memo(({ }: Props) => {
|
||||
<header className={classNames({ [s.header]: true, [s.full]: isFullHeader })}>
|
||||
<HeaderHighLight isShow={isFullHeader} />
|
||||
<div className={s.menu}>
|
||||
<HeaderMenu isFull={isFullHeader} />
|
||||
<HeaderMenu isFull={isFullHeader}
|
||||
openModalAuthen={openModalAuthen}
|
||||
openModalInfo={openModalInfo} />
|
||||
<HeaderSubMenu isShow={isFullHeader} />
|
||||
</div>
|
||||
</header>
|
||||
<HeaderSubMenuMobile />
|
||||
<ModalAuthenticate visible={visibleModalAuthen} closeModal={closeModalAuthen} />
|
||||
<ModalCreateUserInfo demoVisible={visibleModalInfo} demoCloseModal={closeModalInfo}/>
|
||||
</>
|
||||
)
|
||||
})
|
||||
|
@ -1,13 +1,29 @@
|
||||
import classNames from 'classnames'
|
||||
import Link from 'next/link'
|
||||
import { memo } from 'react'
|
||||
import { memo, useMemo } from 'react'
|
||||
import InputSearch from 'src/components/common/InputSearch/InputSearch'
|
||||
import MenuDropdown from 'src/components/common/MenuDropdown/MenuDropdown'
|
||||
import { IconBuy, IconHeart, IconHistory, IconUser } from 'src/components/icons'
|
||||
import { ACCOUNT_TAB, QUERY_KEY, ROUTE } from 'src/utils/constanst.utils'
|
||||
import s from './HeaderMenu.module.scss'
|
||||
|
||||
const OPTION_MENU = [
|
||||
interface Props {
|
||||
children?: any,
|
||||
isFull: boolean,
|
||||
openModalAuthen: () => void,
|
||||
openModalInfo: () => void,
|
||||
}
|
||||
|
||||
const HeaderMenu = memo(({ isFull, openModalAuthen, openModalInfo }: Props) => {
|
||||
const optionMenu = useMemo(() => [
|
||||
{
|
||||
onClick: openModalAuthen,
|
||||
name: 'Login (Demo)',
|
||||
},
|
||||
{
|
||||
onClick: openModalInfo,
|
||||
name: 'Create User Info (Demo)',
|
||||
},
|
||||
{
|
||||
link: ROUTE.ACCOUNT,
|
||||
name: 'Account',
|
||||
@ -17,14 +33,8 @@ const OPTION_MENU = [
|
||||
name: 'Logout',
|
||||
},
|
||||
|
||||
]
|
||||
], [openModalAuthen])
|
||||
|
||||
interface Props {
|
||||
children?: any,
|
||||
isFull: boolean,
|
||||
}
|
||||
|
||||
const HeaderMenu = memo(({ isFull }: Props) => {
|
||||
return (
|
||||
<section className={classNames({ [s.headerMenu]: true, [s.full]: isFull })}>
|
||||
<div className={s.left}>
|
||||
@ -54,7 +64,7 @@ const HeaderMenu = memo(({ isFull }: Props) => {
|
||||
</Link>
|
||||
</li>
|
||||
<li>
|
||||
<MenuDropdown options={OPTION_MENU} isHasArrow={false}><IconUser /></MenuDropdown>
|
||||
<MenuDropdown options={optionMenu} isHasArrow={false}><IconUser /></MenuDropdown>
|
||||
</li>
|
||||
<li>
|
||||
<button>
|
||||
|
@ -7,6 +7,7 @@
|
||||
padding: 2rem 1rem;
|
||||
border-top: 1px solid var(--border-line);
|
||||
box-shadow: -5px 6px 10px rgba(0, 0, 0, 0.2);
|
||||
z-index: 9999;
|
||||
.menu {
|
||||
@apply grid grid-cols-4;
|
||||
li {
|
||||
|
@ -12,4 +12,6 @@
|
||||
&.center {
|
||||
@apply text-center;
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
|
@ -0,0 +1,4 @@
|
||||
.imgWithLink {
|
||||
@apply w-full h-full;
|
||||
object-fit: cover;
|
||||
}
|
16
src/components/common/ImgWithLink/ImgWithLink.tsx
Normal file
@ -0,0 +1,16 @@
|
||||
import React from 'react'
|
||||
import s from './ImgWithLink.module.scss'
|
||||
|
||||
export interface ImgWithLinkProps {
|
||||
src: string,
|
||||
alt?: string,
|
||||
}
|
||||
|
||||
const ImgWithLink = ({ src, alt }: ImgWithLinkProps) => {
|
||||
return (
|
||||
<img className={s.imgWithLink} src={src} alt={alt} />
|
||||
|
||||
)
|
||||
}
|
||||
|
||||
export default ImgWithLink
|
@ -1,9 +1,10 @@
|
||||
@import "../../../styles/utilities";
|
||||
|
||||
.inputWrap {
|
||||
.inputInner {
|
||||
@apply flex items-center relative;
|
||||
.icon {
|
||||
@apply absolute;
|
||||
@apply absolute flex justify-center items-center;
|
||||
content: "";
|
||||
left: 1.6rem;
|
||||
margin-right: 1.6rem;
|
||||
@ -14,6 +15,7 @@
|
||||
.icon + .inputCommon {
|
||||
padding-left: 4.8rem;
|
||||
}
|
||||
|
||||
.inputCommon {
|
||||
@apply block w-full transition-all duration-200 rounded;
|
||||
padding: 1.2rem 1.6rem;
|
||||
@ -48,4 +50,45 @@
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
&.preserve {
|
||||
@apply flex-row-reverse;
|
||||
.icon {
|
||||
left: unset;
|
||||
right: 1.6rem;
|
||||
margin-left: 1.6rem;
|
||||
margin-right: 0;
|
||||
svg path {
|
||||
fill: var(--text-label);
|
||||
}
|
||||
}
|
||||
.icon + .inputCommon {
|
||||
padding-left: 1.6rem;
|
||||
padding-right: 4.8rem;
|
||||
}
|
||||
}
|
||||
&.success {
|
||||
.icon {
|
||||
svg path {
|
||||
fill: var(--primary);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
&.error {
|
||||
.icon {
|
||||
svg path {
|
||||
fill: var(--negative);
|
||||
}
|
||||
}
|
||||
input {
|
||||
border-color: var(--negative) !important;
|
||||
}
|
||||
}
|
||||
}
|
||||
.errorMessage {
|
||||
@apply caption;
|
||||
color: var(--negative);
|
||||
margin-top: 0.4rem;
|
||||
}
|
||||
}
|
||||
|
@ -1,5 +1,6 @@
|
||||
import classNames from 'classnames';
|
||||
import React, { forwardRef, useImperativeHandle, useRef } from 'react';
|
||||
import React, { forwardRef, useImperativeHandle, useMemo, useRef, useState } from 'react';
|
||||
import { IconCheck, IconError, IconPassword, IconPasswordCross } from 'src/components/icons';
|
||||
import { KEY } from 'src/utils/constanst.utils';
|
||||
import s from './InputCommon.module.scss';
|
||||
|
||||
@ -10,18 +11,33 @@ interface Props {
|
||||
children?: React.ReactNode,
|
||||
value?: string | number,
|
||||
placeholder?: string,
|
||||
type?: 'text' | 'number' | 'email',
|
||||
type?: 'text' | 'number' | 'email' | 'password',
|
||||
styleType?: 'default' | 'custom',
|
||||
backgroundTransparent?: boolean,
|
||||
icon?: React.ReactNode,
|
||||
isIconSuffix?: boolean,
|
||||
isShowIconSuccess?: boolean,
|
||||
error?: string,
|
||||
onChange?: (value: string | number) => void,
|
||||
onEnter?: (value: string | number) => void,
|
||||
}
|
||||
|
||||
const InputCommon = forwardRef<Ref, Props>(({ value, placeholder, type, styleType = 'default', icon, backgroundTransparent = false,
|
||||
isIconSuffix, isShowIconSuccess, error,
|
||||
onChange, onEnter }: Props, ref) => {
|
||||
const inputElementRef = useRef<HTMLInputElement>(null);
|
||||
|
||||
const iconElement = useMemo(() => {
|
||||
if (error) {
|
||||
return <span className={s.icon}><IconError /> </span>
|
||||
} else if (isShowIconSuccess) {
|
||||
return <span className={s.icon}><IconCheck /> </span>
|
||||
} else if (icon) {
|
||||
return <span className={s.icon}>{icon} </span>
|
||||
}
|
||||
return <></>
|
||||
}, [icon, error, isShowIconSuccess])
|
||||
|
||||
useImperativeHandle(ref, () => ({
|
||||
focus: () => {
|
||||
inputElementRef.current?.focus();
|
||||
@ -44,10 +60,16 @@ const InputCommon = forwardRef<Ref, Props>(({ value, placeholder, type, styleTyp
|
||||
}
|
||||
|
||||
return (
|
||||
<div className={s.inputWrap}>
|
||||
{
|
||||
icon && <span className={s.icon}>{icon}</span>
|
||||
}
|
||||
<div className={classNames({
|
||||
[s.inputWrap]: true,
|
||||
})}>
|
||||
<div className={classNames({
|
||||
[s.inputInner]: true,
|
||||
[s.preserve]: isIconSuffix,
|
||||
[s.success]: isShowIconSuccess,
|
||||
[s.error]: !!error,
|
||||
})}>
|
||||
{iconElement}
|
||||
<input
|
||||
ref={inputElementRef}
|
||||
value={value}
|
||||
@ -62,6 +84,10 @@ const InputCommon = forwardRef<Ref, Props>(({ value, placeholder, type, styleTyp
|
||||
})}
|
||||
/>
|
||||
</div>
|
||||
{
|
||||
error && <div className={s.errorMessage}>{error}</div>
|
||||
}
|
||||
</div>
|
||||
)
|
||||
|
||||
})
|
||||
|
@ -0,0 +1,10 @@
|
||||
.iconPassword {
|
||||
all: unset;
|
||||
cursor: pointer;
|
||||
&:focus {
|
||||
outline: none;
|
||||
}
|
||||
&:focus-visible {
|
||||
outline: 2px solid var(--text-active);
|
||||
}
|
||||
}
|
40
src/components/common/InputPassword/InputPassword.tsx
Normal file
@ -0,0 +1,40 @@
|
||||
import React, { useState } from 'react';
|
||||
import { IconPassword, IconPasswordCross } from 'src/components/icons';
|
||||
import { Inputcommon } from '..';
|
||||
import s from './InputPassword.module.scss';
|
||||
|
||||
interface Props {
|
||||
children?: React.ReactNode,
|
||||
value?: string | number,
|
||||
placeholder?: string,
|
||||
styleType?: 'default' | 'custom',
|
||||
error?: string,
|
||||
onChange?: (value: string | number) => void,
|
||||
onEnter?: (value: string | number) => void,
|
||||
}
|
||||
|
||||
const InputPassword = ({ value, placeholder, styleType = 'default', error,
|
||||
onChange, onEnter }: Props) => {
|
||||
const [isShowPassword, setIsShowPassword] = useState<boolean>(false)
|
||||
const toggleShowPassword = () => {
|
||||
setIsShowPassword(!isShowPassword)
|
||||
}
|
||||
|
||||
return (
|
||||
<Inputcommon
|
||||
value={value}
|
||||
type={isShowPassword ? 'text' : 'password'}
|
||||
styleType={styleType}
|
||||
error={error}
|
||||
placeholder={placeholder}
|
||||
icon={<button className={s.iconPassword} onClick={toggleShowPassword}>
|
||||
{isShowPassword ? <IconPassword /> : <IconPasswordCross />}
|
||||
</button>}
|
||||
isIconSuffix={true}
|
||||
onChange={onChange}
|
||||
onEnter={onEnter}
|
||||
/>
|
||||
)
|
||||
}
|
||||
|
||||
export default InputPassword
|
@ -0,0 +1,23 @@
|
||||
@import "../../../../styles/utilities";
|
||||
|
||||
.infoProducts {
|
||||
@apply flex justify-between items-center spacing-horizontal;
|
||||
|
||||
.top {
|
||||
.sub {
|
||||
display: none;
|
||||
}
|
||||
}
|
||||
@screen lg {
|
||||
@apply block;
|
||||
margin-right: 4rem;
|
||||
padding: 0;
|
||||
.top {
|
||||
margin-bottom: 3.2rem;
|
||||
.sub {
|
||||
display: block;
|
||||
margin-top: 0.4rem;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
@ -0,0 +1,26 @@
|
||||
import React from 'react';
|
||||
import { ROUTE } from 'src/utils/constanst.utils';
|
||||
import HeadingCommon from '../../HeadingCommon/HeadingCommon';
|
||||
import ViewAllItem from '../../ViewAllItem/ViewAllItem';
|
||||
import s from './InfoProducts.module.scss'
|
||||
interface Props {
|
||||
title: string,
|
||||
subtitle?: string,
|
||||
}
|
||||
|
||||
const InfoProducts = ({ title, subtitle }: Props) => {
|
||||
return (
|
||||
<div className={s.infoProducts}>
|
||||
<div className={s.top}>
|
||||
<HeadingCommon>{title}</HeadingCommon>
|
||||
<div className={s.sub}>
|
||||
{subtitle}
|
||||
</div>
|
||||
</div>
|
||||
<ViewAllItem link={ROUTE.PRODUCTS} />
|
||||
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
export default InfoProducts;
|
@ -0,0 +1,48 @@
|
||||
@import "../../../styles/utilities";
|
||||
|
||||
.listProductWithInfo {
|
||||
background-color: var(--background);
|
||||
border-top: 1rem solid var(--gray);
|
||||
border-bottom: 1rem solid var(--gray);
|
||||
padding-top: 6rem;
|
||||
padding-bottom: 6rem;
|
||||
@screen lg {
|
||||
@apply flex spacing-horizontal-left;
|
||||
padding-top: 5.6rem;
|
||||
padding-bottom: 5.6rem;
|
||||
border: none;
|
||||
background-color: #f5f4f2;
|
||||
}
|
||||
.productsWrap {
|
||||
@apply spacing-horizontal-left;
|
||||
@screen lg {
|
||||
max-width: 75%;
|
||||
@apply custom-border-radius-lg bg-white;
|
||||
padding: 4rem .8rem;
|
||||
:global(.customArrow) {
|
||||
@screen lg {
|
||||
&:global(.leftArrow) {
|
||||
left: calc(-6.4rem + 3rem);
|
||||
}
|
||||
&:global(.rightArrow) {
|
||||
right: calc(-6.4rem + 3rem);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@screen xl {
|
||||
padding: 4rem 2.4rem;
|
||||
max-width: 80%;
|
||||
:global(.customArrow) {
|
||||
@screen lg {
|
||||
&:global(.leftArrow) {
|
||||
left: calc(-6.4rem + 1rem);
|
||||
}
|
||||
&:global(.rightArrow) {
|
||||
right: calc(-6.4rem + 1rem);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
@ -0,0 +1,51 @@
|
||||
import { TOptionsEvents } from 'keen-slider';
|
||||
import React from 'react';
|
||||
import CarouselCommon from '../CarouselCommon/CarouselCommon';
|
||||
import ProductCard, { ProductCardProps } from '../ProductCard/ProductCard';
|
||||
import InfoProducts from './InfoProducts/InfoProducts';
|
||||
import s from './ListProductWithInfo.module.scss';
|
||||
|
||||
interface Props {
|
||||
data: ProductCardProps[],
|
||||
title: string,
|
||||
subtitle?: string,
|
||||
}
|
||||
const OPTION_DEFAULT: TOptionsEvents = {
|
||||
slidesPerView: 2,
|
||||
mode: 'free',
|
||||
breakpoints: {
|
||||
'(min-width: 640px)': {
|
||||
slidesPerView: 3,
|
||||
},
|
||||
'(min-width: 768px)': {
|
||||
slidesPerView: 4,
|
||||
},
|
||||
'(min-width: 1024px)': {
|
||||
slidesPerView: 3,
|
||||
},
|
||||
'(min-width: 1280px)': {
|
||||
slidesPerView: 4.5,
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
const ListProductWithInfo = ({ data, title, subtitle }: Props) => {
|
||||
return (
|
||||
<div className={s.listProductWithInfo}>
|
||||
<InfoProducts
|
||||
title={title}
|
||||
subtitle={subtitle}
|
||||
/>
|
||||
<div className={s.productsWrap}>
|
||||
<CarouselCommon<ProductCardProps>
|
||||
data={data}
|
||||
Component={ProductCard}
|
||||
itemKey={title}
|
||||
option={OPTION_DEFAULT}
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
export default ListProductWithInfo;
|
@ -62,17 +62,25 @@
|
||||
.menuIner {
|
||||
@apply rounded list-none bg-white;
|
||||
border: 1px solid var(--text-active);
|
||||
margin-top: .4rem;
|
||||
margin-top: 0.4rem;
|
||||
li {
|
||||
@apply block w-full transition-all duration-200 cursor-pointer text-active;
|
||||
button {
|
||||
all: unset;
|
||||
color: currentColor;
|
||||
@apply text-left w-full;
|
||||
}
|
||||
button,
|
||||
a {
|
||||
padding: 0.8rem 1.6rem;
|
||||
&:hover {
|
||||
@apply bg-primary-lightest;
|
||||
color: var(--primary);
|
||||
}
|
||||
a {
|
||||
@apply block;
|
||||
}
|
||||
&:hover {
|
||||
@apply bg-gray;
|
||||
color: var(--primary);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -5,7 +5,7 @@ import s from './MenuDropdown.module.scss';
|
||||
|
||||
interface Props {
|
||||
children?: React.ReactNode,
|
||||
options: { link: string, name: string }[],
|
||||
options: { link?: string, name: string, onClick?: () => void }[],
|
||||
isHasArrow?: boolean,
|
||||
align?: 'left'
|
||||
}
|
||||
@ -26,11 +26,16 @@ const MenuDropdown = ({ options, children, isHasArrow = true, align }: Props) =>
|
||||
<ul className={s.menuIner}>
|
||||
{
|
||||
options.map(item => <li key={item.name}>
|
||||
<Link href={item.link}>
|
||||
{item.onClick ?
|
||||
<button onClick={item.onClick}>
|
||||
{item.name}
|
||||
</button>
|
||||
:
|
||||
<Link href={item.link || ''}>
|
||||
<a >
|
||||
{item.name}
|
||||
</a>
|
||||
</Link>
|
||||
</Link>}
|
||||
</li>)
|
||||
}
|
||||
</ul>
|
||||
|
@ -0,0 +1,10 @@
|
||||
.formAuthenticate {
|
||||
@apply overflow-hidden;
|
||||
.inner {
|
||||
@apply grid grid-cols-2 overflow-hidden transition-all duration-200;
|
||||
width: 200%;
|
||||
&.register {
|
||||
transform: translateX(-50%);
|
||||
}
|
||||
}
|
||||
}
|
@ -0,0 +1,36 @@
|
||||
import classNames from 'classnames'
|
||||
import React, { useState } from 'react'
|
||||
import ModalCommon from '../ModalCommon/ModalCommon'
|
||||
import FormLogin from './components/FormLogin/FormLogin'
|
||||
import FormRegister from './components/FormRegister/FormRegister'
|
||||
import s from './ModalAuthenticate.module.scss'
|
||||
|
||||
interface Props {
|
||||
visible: boolean,
|
||||
closeModal: () => void,
|
||||
}
|
||||
|
||||
const ModalAuthenticate = ({ visible, closeModal }: Props) => {
|
||||
const [isLogin, setIsLogin] = useState<boolean>(true)
|
||||
|
||||
const onSwitch = () => {
|
||||
setIsLogin(!isLogin)
|
||||
}
|
||||
|
||||
return (
|
||||
<ModalCommon visible={visible} onClose={closeModal} title={isLogin ? 'Sign In' : 'Create Account'}>
|
||||
<section className={s.formAuthenticate}>
|
||||
<div className={classNames({
|
||||
[s.inner]: true,
|
||||
[s.register]: !isLogin,
|
||||
})}>
|
||||
<FormLogin isHide={!isLogin} onSwitch={onSwitch} />
|
||||
<FormRegister isHide={isLogin} onSwitch={onSwitch} />
|
||||
</div>
|
||||
</section>
|
||||
</ModalCommon>
|
||||
|
||||
)
|
||||
}
|
||||
|
||||
export default ModalAuthenticate
|
@ -0,0 +1,30 @@
|
||||
@import '../../../../styles/utilities';
|
||||
|
||||
.formAuthen {
|
||||
@apply bg-white w-full u-form;
|
||||
.inner {
|
||||
@screen md {
|
||||
width: 60rem;
|
||||
margin: auto;
|
||||
}
|
||||
.others {
|
||||
@apply font-bold text-center;
|
||||
margin-top: 4rem;
|
||||
|
||||
span {
|
||||
@apply text-active;
|
||||
margin-right: 0.8rem;
|
||||
}
|
||||
button {
|
||||
all: unset;
|
||||
@apply text-primary cursor-pointer;
|
||||
&:focus-visible {
|
||||
outline: 2px solid #000;
|
||||
}
|
||||
&:focus {
|
||||
outline: none;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
@ -0,0 +1,9 @@
|
||||
.bottom {
|
||||
@apply flex justify-between items-center;
|
||||
margin: 4rem auto;
|
||||
.forgotPassword {
|
||||
@apply font-bold;
|
||||
color: var(--primary);
|
||||
}
|
||||
}
|
||||
|
@ -0,0 +1,52 @@
|
||||
import Link from 'next/link'
|
||||
import React, { useEffect, useRef } from 'react'
|
||||
import { ButtonCommon, Inputcommon } from 'src/components/common'
|
||||
import InputPassword from 'src/components/common/InputPassword/InputPassword'
|
||||
import { ROUTE } from 'src/utils/constanst.utils'
|
||||
import { CustomInputCommon } from 'src/utils/type.utils'
|
||||
import s from '../FormAuthen.module.scss'
|
||||
import SocialAuthen from '../SocialAuthen/SocialAuthen'
|
||||
import styles from './FormLogin.module.scss'
|
||||
|
||||
interface Props {
|
||||
isHide: boolean,
|
||||
onSwitch: () => void
|
||||
}
|
||||
|
||||
const FormLogin = ({ onSwitch, isHide }: Props) => {
|
||||
const emailRef = useRef<CustomInputCommon>(null)
|
||||
|
||||
useEffect(() => {
|
||||
if (!isHide) {
|
||||
emailRef.current?.focus()
|
||||
}
|
||||
}, [isHide])
|
||||
|
||||
return (
|
||||
<section className={s.formAuthen}>
|
||||
<div className={s.inner}>
|
||||
<div className={s.body}>
|
||||
<Inputcommon placeholder='Email Address' type='email' ref={emailRef} />
|
||||
{/* <Inputcommon placeholder='Email Address' type='email' ref={emailRef}
|
||||
isShowIconSuccess={true} isIconSuffix={true} /> */}
|
||||
<InputPassword placeholder='Password'/>
|
||||
</div>
|
||||
<div className={styles.bottom}>
|
||||
<Link href={ROUTE.FORGOT_PASSWORD}>
|
||||
<a href="" className={styles.forgotPassword}>
|
||||
Forgot Password?
|
||||
</a>
|
||||
</Link>
|
||||
<ButtonCommon size='large'>Sign in</ButtonCommon>
|
||||
</div>
|
||||
<SocialAuthen />
|
||||
<div className={s.others}>
|
||||
<span>Don't have an account?</span>
|
||||
<button onClick={onSwitch}>Create Account</button>
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
)
|
||||
}
|
||||
|
||||
export default FormLogin
|
@ -0,0 +1,15 @@
|
||||
@import "../../../../../styles/utilities";
|
||||
|
||||
.formRegister {
|
||||
.passwordNote {
|
||||
@apply text-center caption text-label;
|
||||
margin-top: 0.8rem;
|
||||
}
|
||||
.bottom {
|
||||
@apply flex justify-between items-center w-full;
|
||||
margin: 4rem auto;
|
||||
button {
|
||||
@apply w-full;
|
||||
}
|
||||
}
|
||||
}
|
@ -0,0 +1,49 @@
|
||||
import React, { useEffect, useRef } from 'react'
|
||||
import { ButtonCommon, Inputcommon } from 'src/components/common'
|
||||
import s from '../FormAuthen.module.scss'
|
||||
import styles from './FormRegister.module.scss'
|
||||
import SocialAuthen from '../SocialAuthen/SocialAuthen'
|
||||
import classNames from 'classnames'
|
||||
import { CustomInputCommon } from 'src/utils/type.utils'
|
||||
|
||||
interface Props {
|
||||
isHide: boolean,
|
||||
onSwitch: () => void
|
||||
}
|
||||
|
||||
const FormRegister = ({ onSwitch, isHide }: Props) => {
|
||||
const emailRef = useRef<CustomInputCommon>(null)
|
||||
|
||||
useEffect(() => {
|
||||
if (!isHide) {
|
||||
emailRef.current?.focus()
|
||||
}
|
||||
}, [isHide])
|
||||
|
||||
return (
|
||||
<section className={classNames({
|
||||
[s.formAuthen]: true,
|
||||
[styles.formRegister]: true,
|
||||
})}>
|
||||
<div className={s.inner}>
|
||||
<div className={s.body}>
|
||||
<Inputcommon placeholder='Email Address' type='email' ref={emailRef}/>
|
||||
<InputPassword placeholder='Password'/>
|
||||
<div className={styles.passwordNote}>
|
||||
Must contain 8 characters with at least 1 uppercase and 1 lowercase letter and either 1 number or 1 special character.
|
||||
</div>
|
||||
</div>
|
||||
<div className={styles.bottom}>
|
||||
<ButtonCommon size='large'>Create Account</ButtonCommon>
|
||||
</div>
|
||||
<SocialAuthen />
|
||||
<div className={s.others}>
|
||||
<span>Already an account?</span>
|
||||
<button onClick={onSwitch}>Sign In</button>
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
)
|
||||
}
|
||||
|
||||
export default FormRegister
|
@ -0,0 +1,36 @@
|
||||
@import "../../../../../styles/utilities";
|
||||
|
||||
.socialAuthen {
|
||||
.captionText {
|
||||
@apply relative text-center;
|
||||
margin-bottom: 4rem;
|
||||
span {
|
||||
@apply relative bg-white uppercase text-label caption;
|
||||
padding: 0 0.8rem;
|
||||
z-index: 10;
|
||||
}
|
||||
&::after {
|
||||
@apply absolute bg-line;
|
||||
content: "";
|
||||
width: 100%;
|
||||
height: 1px;
|
||||
top: 50%;
|
||||
left: 0;
|
||||
transform: translateY(-50%);
|
||||
}
|
||||
}
|
||||
.btns {
|
||||
@apply grid grid-cols-3;
|
||||
grid-gap: 1.6rem;
|
||||
.buttonWithIcon {
|
||||
@apply flex items-center;
|
||||
.label {
|
||||
@apply hidden;
|
||||
@screen md {
|
||||
@apply inline-block;
|
||||
margin-left: 0.8rem;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
@ -0,0 +1,37 @@
|
||||
import React from 'react'
|
||||
import ButtonCommon from 'src/components/common/ButtonCommon/ButtonCommon'
|
||||
import { IconApple, IconFacebookColor, IconGoogleColor } from 'src/components/icons'
|
||||
import s from './SocialAuthen.module.scss'
|
||||
|
||||
const SocialAuthen = () => {
|
||||
return (
|
||||
<section className={s.socialAuthen}>
|
||||
<div className={s.captionText}>
|
||||
<span>
|
||||
OR CONTINUE WITH
|
||||
</span>
|
||||
</div>
|
||||
<div className={s.btns}>
|
||||
<ButtonCommon type='light' size='large'>
|
||||
<span className={s.buttonWithIcon}>
|
||||
<IconFacebookColor /><span className={s.label}>Facebook</span>
|
||||
</span>
|
||||
</ButtonCommon>
|
||||
<ButtonCommon type='light' size='large'>
|
||||
<span className={s.buttonWithIcon}>
|
||||
<IconApple />
|
||||
<span className={s.label}>Apple</span>
|
||||
</span>
|
||||
</ButtonCommon>
|
||||
<ButtonCommon type='light' size='large'>
|
||||
<span className={s.buttonWithIcon}>
|
||||
<IconGoogleColor />
|
||||
<span className={s.label}>Google</span>
|
||||
</span>
|
||||
</ButtonCommon>
|
||||
</div>
|
||||
</section>
|
||||
)
|
||||
}
|
||||
|
||||
export default SocialAuthen
|
32
src/components/common/ModalCommon/ModalCommon.module.scss
Normal file
@ -0,0 +1,32 @@
|
||||
@import '../../../styles/utilities';
|
||||
|
||||
.background{
|
||||
@apply fixed inset-0 overflow-y-auto;
|
||||
background: rgba(20, 20, 20, 0.65);
|
||||
z-index: 10000;
|
||||
.warpper{
|
||||
@apply flex justify-center items-center min-h-screen;
|
||||
.modal{
|
||||
@apply inline-block align-bottom bg-white relative;
|
||||
max-width: 66.4rem;
|
||||
padding: 3.2rem;
|
||||
box-shadow: 0px 8px 16px rgba(0, 0, 0, 0.24);
|
||||
border-radius: 1.2rem;
|
||||
.top {
|
||||
margin-bottom: 4rem;
|
||||
}
|
||||
.title{
|
||||
@apply font-heading heading-3;
|
||||
padding: 0 1.6rem 0 0.8rem;
|
||||
}
|
||||
.close{
|
||||
@apply absolute;
|
||||
&:hover{
|
||||
cursor: pointer;
|
||||
}
|
||||
top:4.4rem;
|
||||
right: 4.4rem;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
40
src/components/common/ModalCommon/ModalCommon.tsx
Normal file
@ -0,0 +1,40 @@
|
||||
import React, { useRef } from 'react'
|
||||
import { Close } from 'src/components/icons'
|
||||
import { useOnClickOutside } from 'src/utils/useClickOutSide'
|
||||
import s from './ModalCommon.module.scss'
|
||||
export interface ModalCommonProps {
|
||||
onClose: () => void
|
||||
visible: boolean
|
||||
children: React.ReactNode
|
||||
title?: string
|
||||
maxWidth?:string
|
||||
}
|
||||
|
||||
const ModalCommon = ({ onClose, visible, children, title="Modal",maxWidth }: ModalCommonProps) => {
|
||||
const modalRef = useRef<HTMLDivElement>(null)
|
||||
const clickOutSide = () => {
|
||||
onClose && onClose()
|
||||
}
|
||||
useOnClickOutside(modalRef, clickOutSide)
|
||||
return (
|
||||
<>
|
||||
{visible && (
|
||||
<div className={s.background}>
|
||||
<div className={s.warpper}>
|
||||
<div className={s.modal} ref={modalRef} style={{maxWidth}}>
|
||||
<div className={s.top}>
|
||||
<div className={s.title}>{title}</div>
|
||||
<div className={s.close} onClick={clickOutSide}>
|
||||
<Close />
|
||||
</div>
|
||||
</div>
|
||||
{children}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
)}
|
||||
</>
|
||||
)
|
||||
}
|
||||
|
||||
export default ModalCommon
|
@ -0,0 +1,4 @@
|
||||
.footer{
|
||||
margin-top: 4rem;
|
||||
@apply flex justify-end items-center;
|
||||
}
|
34
src/components/common/ModalConfirm/ModalConfirm.tsx
Normal file
@ -0,0 +1,34 @@
|
||||
import React from 'react'
|
||||
import ButtonCommon from '../ButtonCommon/ButtonCommon'
|
||||
import ModalCommon, { ModalCommonProps } from '../ModalCommon/ModalCommon'
|
||||
import s from './ModalConfirm.module.scss'
|
||||
interface ModalConfirmProps extends ModalCommonProps {
|
||||
okText?: String
|
||||
cancelText?: String
|
||||
onOk?: () => void
|
||||
onCancel?: () => void
|
||||
}
|
||||
|
||||
const ModalConfirm = ({
|
||||
okText = 'Ok',
|
||||
cancelText = 'cancel',
|
||||
onOk,
|
||||
onCancel,
|
||||
children,
|
||||
title = 'Confirm',
|
||||
...props
|
||||
}: ModalConfirmProps) => {
|
||||
return (
|
||||
<ModalCommon {...props} title={title}>
|
||||
{children}
|
||||
<div className={s.footer}>
|
||||
<div className="mr-4">
|
||||
<ButtonCommon onClick={onCancel} type="light"> {cancelText}</ButtonCommon>
|
||||
</div>
|
||||
<ButtonCommon onClick={onOk}>{okText}</ButtonCommon>
|
||||
</div>
|
||||
</ModalCommon>
|
||||
)
|
||||
}
|
||||
|
||||
export default ModalConfirm
|
@ -0,0 +1,19 @@
|
||||
@import "../../../styles/utilities";
|
||||
|
||||
.formUserInfo {
|
||||
@apply u-form;
|
||||
.inner {
|
||||
@screen md {
|
||||
width: 60rem;
|
||||
margin: auto;
|
||||
}
|
||||
.bottom {
|
||||
@apply grid grid-cols-2;
|
||||
margin-top: 4rem;
|
||||
grid-gap: 1.6rem;
|
||||
> button {
|
||||
@apply w-full;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
@ -0,0 +1,46 @@
|
||||
import classNames from 'classnames';
|
||||
import Link from 'next/link';
|
||||
import React, { useRef } from 'react';
|
||||
import { useModalCommon } from 'src/components/hooks/useModalCommon';
|
||||
import { CustomInputCommon } from 'src/utils/type.utils';
|
||||
import { Inputcommon } from '..';
|
||||
import ButtonCommon from '../ButtonCommon/ButtonCommon';
|
||||
import ModalCommon from '../ModalCommon/ModalCommon';
|
||||
import s from './ModalCreateUserInfo.module.scss';
|
||||
|
||||
// todo: remove
|
||||
interface Props {
|
||||
demoVisible: boolean,
|
||||
demoCloseModal: () => void,
|
||||
}
|
||||
|
||||
|
||||
const ModalCreateUserInfo = ({ demoVisible: visible, demoCloseModal: closeModal }: Props) => {
|
||||
// const { visible, closeModal } = useModalCommon({ initialValue: false})
|
||||
const firstInputRef = useRef<CustomInputCommon>(null)
|
||||
|
||||
return (
|
||||
<ModalCommon visible={visible} onClose={closeModal} title='Enter your Information'>
|
||||
<div className={s.formUserInfo}>
|
||||
<div className={s.inner}>
|
||||
<div className={s.body}>
|
||||
<Inputcommon placeholder='Street Address' ref={firstInputRef} />
|
||||
<Inputcommon placeholder='City' />
|
||||
<div className={s.line}>
|
||||
{/* todo: select, not input */}
|
||||
<Inputcommon placeholder='State' />
|
||||
<Inputcommon placeholder='Zip code' />
|
||||
</div>
|
||||
<Inputcommon placeholder='Phone (delivery contact)' />
|
||||
</div>
|
||||
<div className={s.bottom}>
|
||||
<ButtonCommon size='large' onClick={closeModal} type='light'>Skip</ButtonCommon>
|
||||
<ButtonCommon size='large'>Submit</ButtonCommon>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</ModalCommon>
|
||||
);
|
||||
}
|
||||
|
||||
export default ModalCreateUserInfo;
|
4
src/components/common/ModalInfo/ModalInfo.module.scss
Normal file
@ -0,0 +1,4 @@
|
||||
.footer{
|
||||
margin-top: 4rem;
|
||||
@apply flex justify-end items-center;
|
||||
}
|
27
src/components/common/ModalInfo/ModalInfo.tsx
Normal file
@ -0,0 +1,27 @@
|
||||
import React from 'react'
|
||||
import ButtonCommon from '../ButtonCommon/ButtonCommon'
|
||||
import ModalCommon, { ModalCommonProps } from '../ModalCommon/ModalCommon'
|
||||
import s from './ModalInfo.module.scss'
|
||||
interface ModalInfoProps extends ModalCommonProps {
|
||||
okText?: String
|
||||
onOk?: () => void
|
||||
}
|
||||
|
||||
const ModalInfo = ({
|
||||
okText = 'Ok',
|
||||
onOk,
|
||||
children,
|
||||
title = 'Confirm',
|
||||
...props
|
||||
}: ModalInfoProps) => {
|
||||
return (
|
||||
<ModalCommon {...props} title={title}>
|
||||
{children}
|
||||
<div className={s.footer}>
|
||||
<ButtonCommon onClick={onOk}>{okText}</ButtonCommon>
|
||||
</div>
|
||||
</ModalCommon>
|
||||
)
|
||||
}
|
||||
|
||||
export default ModalInfo
|
65
src/components/common/ProductCard/ProductCard.module.scss
Normal file
@ -0,0 +1,65 @@
|
||||
.productCardWarpper{
|
||||
max-width: 20.8rem;
|
||||
min-height: 31.8rem;
|
||||
padding: 1.2rem 1.2rem 0 1.2rem;
|
||||
margin: auto;
|
||||
margin-bottom: 1px;
|
||||
@apply flex flex-col justify-between;
|
||||
&.notSell {
|
||||
@apply justify-center;
|
||||
}
|
||||
.cardTop{
|
||||
@apply relative;
|
||||
height: 13.8rem;
|
||||
width: 100%;
|
||||
.productImage{
|
||||
height: 100%;
|
||||
width: 100%;
|
||||
@apply flex justify-center items-center;
|
||||
img{
|
||||
@apply inline;
|
||||
}
|
||||
&:hover{
|
||||
cursor: pointer;
|
||||
}
|
||||
}
|
||||
.productLabel{
|
||||
@apply absolute left-0 bottom-0;
|
||||
}
|
||||
}
|
||||
.cardMid{
|
||||
min-height: 10.4rem;
|
||||
@apply flex flex-col justify-between;
|
||||
.cardMidTop{
|
||||
.productname{
|
||||
font-weight: bold;
|
||||
color: var(--text-active);
|
||||
&:hover{
|
||||
cursor: pointer;
|
||||
}
|
||||
}
|
||||
.productWeight{
|
||||
font-size: 1.2rem;
|
||||
line-height: 2rem;
|
||||
letter-spacing: 0.01em;
|
||||
color: var(--text-base);
|
||||
}
|
||||
}
|
||||
.cardMidBot{
|
||||
padding-top: 0.8rem;
|
||||
@apply flex justify-between items-center border-t border-solid border-line;
|
||||
.productPrice{
|
||||
@apply font-bold;
|
||||
font-size: 2rem;
|
||||
line-height: 2.8rem;
|
||||
letter-spacing: -0.01em;
|
||||
}
|
||||
}
|
||||
}
|
||||
.cardBot{
|
||||
min-height: 4rem;
|
||||
@apply flex justify-between items-center;
|
||||
.cardButton{
|
||||
}
|
||||
}
|
||||
}
|
69
src/components/common/ProductCard/ProductCard.tsx
Normal file
@ -0,0 +1,69 @@
|
||||
import Link from 'next/link'
|
||||
import React from 'react'
|
||||
import { ProductProps } from 'src/utils/types.utils'
|
||||
import ButtonCommon from '../ButtonCommon/ButtonCommon'
|
||||
import ButtonIconBuy from '../ButtonIconBuy/ButtonIconBuy'
|
||||
import ItemWishList from '../ItemWishList/ItemWishList'
|
||||
import LabelCommon from '../LabelCommon/LabelCommon'
|
||||
import s from './ProductCard.module.scss'
|
||||
import ProductNotSell from './ProductNotSell/ProductNotSell'
|
||||
|
||||
export interface ProductCardProps extends ProductProps {
|
||||
buttonText?: string
|
||||
}
|
||||
|
||||
const ProductCard = ({
|
||||
category,
|
||||
name,
|
||||
weight,
|
||||
price,
|
||||
buttonText = 'Buy Now',
|
||||
imageSrc,
|
||||
isNotSell,
|
||||
}: ProductCardProps) => {
|
||||
if (isNotSell) {
|
||||
return <div className={`${s.productCardWarpper} ${s.notSell}`}>
|
||||
<ProductNotSell name={name} imageSrc={imageSrc} />
|
||||
</div>
|
||||
|
||||
}
|
||||
|
||||
return (
|
||||
<div className={s.productCardWarpper}>
|
||||
<div className={s.cardTop}>
|
||||
<Link href="#">
|
||||
<div className={s.productImage}>
|
||||
<img src={imageSrc} alt="image" />
|
||||
</div>
|
||||
</Link>
|
||||
<div className={s.productLabel}>
|
||||
<LabelCommon shape="half">{category}</LabelCommon>
|
||||
</div>
|
||||
</div>
|
||||
<div className={s.cardMid}>
|
||||
<div className={s.cardMidTop}>
|
||||
<Link href="#">
|
||||
<div className={s.productname}>{name} </div>
|
||||
</Link>
|
||||
<div className={s.productWeight}>{weight}</div>
|
||||
</div>
|
||||
<div className={s.cardMidBot}>
|
||||
<div className={s.productPrice}>{price}</div>
|
||||
<div className={s.wishList}>
|
||||
<ItemWishList />
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div className={s.cardBot}>
|
||||
<div className={s.cardIcon}>
|
||||
<ButtonIconBuy />
|
||||
</div>
|
||||
<div className={s.cardButton}>
|
||||
<ButtonCommon type="light">{buttonText}</ButtonCommon>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
|
||||
export default ProductCard
|
@ -0,0 +1,27 @@
|
||||
@import "../../../../styles/utilities";
|
||||
|
||||
.imgWrap {
|
||||
img {
|
||||
opacity: 0.5;
|
||||
}
|
||||
}
|
||||
|
||||
.name {
|
||||
@apply text-label cursor-default font-bold;
|
||||
}
|
||||
|
||||
.info {
|
||||
@apply flex justify-center items-center custom-border-radius bg-info-light text-center;
|
||||
padding: .8rem 1.6rem;
|
||||
margin-top: 1.6rem;
|
||||
color: var(--info);
|
||||
svg {
|
||||
@apply u-icon;
|
||||
path {
|
||||
fill: currentColor;
|
||||
}
|
||||
}
|
||||
.text {
|
||||
margin-left: 0.8rem;
|
||||
}
|
||||
}
|
@ -0,0 +1,28 @@
|
||||
import React from 'react';
|
||||
import { IconInfo } from 'src/components/icons';
|
||||
import ImgWithLink from '../../ImgWithLink/ImgWithLink';
|
||||
import s from './ProductNotSell.module.scss';
|
||||
|
||||
export interface Props {
|
||||
name: string,
|
||||
imageSrc: string,
|
||||
}
|
||||
|
||||
const ProductNotSell = ({ name, imageSrc }: Props) => {
|
||||
return (
|
||||
<>
|
||||
<div className={s.imgWrap}>
|
||||
<ImgWithLink src={imageSrc} alt={name} />
|
||||
</div>
|
||||
<div className={s.name}>{name}</div>
|
||||
<div className={s.info}>
|
||||
<IconInfo />
|
||||
<div className={s.text}>
|
||||
Not Sell
|
||||
</div>
|
||||
</div>
|
||||
</>
|
||||
);
|
||||
};
|
||||
|
||||
export default ProductNotSell;
|
@ -0,0 +1,16 @@
|
||||
@import '../../../styles/utilities';
|
||||
.productCardWarpper {
|
||||
@apply spacing-horizontal;
|
||||
@screen xl {
|
||||
:global(.customArrow) {
|
||||
@screen lg {
|
||||
&:global(.leftArrow) {
|
||||
left: calc(-6.4rem - 2rem);
|
||||
}
|
||||
&:global(.rightArrow) {
|
||||
right: calc(-6.4rem - 2rem);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
44
src/components/common/ProductCarousel/ProductCarousel.tsx
Normal file
@ -0,0 +1,44 @@
|
||||
import { TOptionsEvents } from 'keen-slider'
|
||||
import React from 'react'
|
||||
import CarouselCommon, {
|
||||
CarouselCommonProps,
|
||||
} from '../CarouselCommon/CarouselCommon'
|
||||
import ProductCard, { ProductCardProps } from '../ProductCard/ProductCard'
|
||||
import s from "./ProductCarousel.module.scss"
|
||||
|
||||
interface ProductCarouselProps
|
||||
extends Omit<CarouselCommonProps<ProductCardProps>, 'Component'|"option"> {
|
||||
option?:TOptionsEvents
|
||||
}
|
||||
|
||||
const OPTION_DEFAULT: TOptionsEvents = {
|
||||
slidesPerView: 2,
|
||||
mode: 'free',
|
||||
breakpoints: {
|
||||
'(min-width: 640px)': {
|
||||
slidesPerView: 3,
|
||||
},
|
||||
'(min-width: 768px)': {
|
||||
slidesPerView: 4,
|
||||
},
|
||||
'(min-width: 1024px)': {
|
||||
slidesPerView: 4.5,
|
||||
},'(min-width: 1280px)': {
|
||||
slidesPerView: 5.5,
|
||||
},
|
||||
},
|
||||
}
|
||||
const ProductCarousel = ({ option, data, ...props }: ProductCarouselProps) => {
|
||||
return (
|
||||
<div className={s.productCardWarpper}>
|
||||
<CarouselCommon<ProductCardProps>
|
||||
data={data}
|
||||
Component={ProductCard}
|
||||
{...props}
|
||||
option={{ ...OPTION_DEFAULT, ...option }}
|
||||
/>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
|
||||
export default ProductCarousel
|
34
src/components/common/RecipeCard/RecipeCard.module.scss
Normal file
@ -0,0 +1,34 @@
|
||||
.recipeCardWarpper{
|
||||
max-width: 39.2rem;
|
||||
min-height: 34rem;
|
||||
@apply inline-flex flex-col justify-start;
|
||||
.image{
|
||||
width: 100%;
|
||||
max-height: 22rem;
|
||||
border-radius: 2.4rem;
|
||||
img {
|
||||
border-radius: 2.4rem;
|
||||
}
|
||||
&:hover{
|
||||
cursor: pointer;
|
||||
}
|
||||
}
|
||||
.title{
|
||||
padding: 1.6rem 0.8rem 0.4rem 0.8rem;
|
||||
@apply font-bold;
|
||||
font-size: 2rem;
|
||||
line-height: 2.8rem;
|
||||
letter-spacing: -0.01em;
|
||||
color: var(--text-active);
|
||||
&:hover{
|
||||
cursor: pointer;
|
||||
}
|
||||
}
|
||||
.description{
|
||||
padding: 0 0.8rem;
|
||||
@apply overflow-hidden overflow-ellipsis ;
|
||||
display: -webkit-box;
|
||||
-webkit-line-clamp: 3; /* number of lines to show */
|
||||
-webkit-box-orient: vertical;
|
||||
}
|
||||
}
|
23
src/components/common/RecipeCard/RecipeCard.tsx
Normal file
@ -0,0 +1,23 @@
|
||||
import Link from 'next/link'
|
||||
import React from 'react'
|
||||
import { RecipeProps } from 'src/utils/types.utils'
|
||||
import s from './RecipeCard.module.scss'
|
||||
export interface RecipeCardProps extends RecipeProps {}
|
||||
|
||||
const RecipeCard = ({ imageSrc, title, description }: RecipeCardProps) => {
|
||||
return (
|
||||
<div className={s.recipeCardWarpper}>
|
||||
<Link href="#">
|
||||
<div className={s.image}>
|
||||
<img src={imageSrc} alt="image recipe" />
|
||||
</div>
|
||||
</Link>
|
||||
<Link href="#">
|
||||
<div className={s.title}>{title}</div>
|
||||
</Link>
|
||||
<div className={s.description}>{description}</div>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
|
||||
export default RecipeCard
|
@ -0,0 +1,16 @@
|
||||
@import '../../../styles/utilities';
|
||||
.recipeCardWarpper {
|
||||
@apply spacing-horizontal;
|
||||
@screen xl {
|
||||
:global(.customArrow) {
|
||||
@screen lg {
|
||||
&:global(.leftArrow) {
|
||||
left: calc(-6.4rem - 2rem);
|
||||
}
|
||||
&:global(.rightArrow) {
|
||||
right: calc(-6.4rem - 2rem);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
46
src/components/common/RecipeCarousel/RecipeCarousel.tsx
Normal file
@ -0,0 +1,46 @@
|
||||
import { TOptionsEvents } from 'keen-slider'
|
||||
import React from 'react'
|
||||
import CarouselCommon, {
|
||||
CarouselCommonProps,
|
||||
} from '../CarouselCommon/CarouselCommon'
|
||||
import RecipeCard, { RecipeCardProps } from '../RecipeCard/RecipeCard'
|
||||
import s from "./RecipeCarousel.module.scss"
|
||||
|
||||
interface RecipeCarouselProps
|
||||
extends Omit<CarouselCommonProps<RecipeCardProps>, 'Component'|"option"> {
|
||||
option?:TOptionsEvents
|
||||
}
|
||||
|
||||
const OPTION_DEFAULT: TOptionsEvents = {
|
||||
slidesPerView: 1.25,
|
||||
mode: 'free',
|
||||
spacing:24,
|
||||
breakpoints: {
|
||||
'(min-width: 640px)': {
|
||||
slidesPerView: 2,
|
||||
},
|
||||
'(min-width: 1024px)': {
|
||||
slidesPerView: 2.5,
|
||||
},
|
||||
'(min-width: 1440px)': {
|
||||
slidesPerView: 3,
|
||||
},
|
||||
'(min-width: 1536px)': {
|
||||
slidesPerView: 3.5,
|
||||
},
|
||||
},
|
||||
}
|
||||
const RecipeCarousel = ({ option, data, ...props }: RecipeCarouselProps) => {
|
||||
return (
|
||||
<div className={s.recipeCardWarpper}>
|
||||
<CarouselCommon<RecipeCardProps>
|
||||
data={data}
|
||||
Component={RecipeCard}
|
||||
{...props}
|
||||
option={{ ...OPTION_DEFAULT, ...option }}
|
||||
/>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
|
||||
export default RecipeCarousel
|
23
src/components/common/RecipeDetail/RecipeDetail.tsx
Normal file
@ -0,0 +1,23 @@
|
||||
import React from 'react'
|
||||
import { ProductCardProps } from '../ProductCard/ProductCard'
|
||||
import RecipeDetailInfo from './components/RecipeDetailInfo/RecipeDetailInfo'
|
||||
import RecipeIngredient from './components/RecipeIngredient/RecipeIngredient'
|
||||
import s from './RecipeDetail.module.scss'
|
||||
|
||||
|
||||
interface Props {
|
||||
className?: string
|
||||
children?: any,
|
||||
ingredients: ProductCardProps[],
|
||||
}
|
||||
|
||||
const RecipeDetail = ({ ingredients }: Props) => {
|
||||
return (
|
||||
<section className={s.recipeDetail}>
|
||||
<RecipeDetailInfo />
|
||||
<RecipeIngredient data={ingredients} />
|
||||
</section >
|
||||
)
|
||||
}
|
||||
|
||||
export default RecipeDetail
|
@ -0,0 +1,19 @@
|
||||
.recipeBriefInfo {
|
||||
@apply flex;
|
||||
.item {
|
||||
@apply flex;
|
||||
&:not(:last-child) {
|
||||
margin-right: 2.4rem;
|
||||
}
|
||||
svg {
|
||||
width: 2rem;
|
||||
height: 2rem;
|
||||
path {
|
||||
fill: var(--text-label);
|
||||
}
|
||||
}
|
||||
.content {
|
||||
margin-left: 0.8rem;
|
||||
}
|
||||
}
|
||||
}
|
@ -0,0 +1,29 @@
|
||||
import React from 'react'
|
||||
import { IconLocation, IconPeople, IconTime } from 'src/components/icons'
|
||||
import s from './RecipeBriefInfo.module.scss'
|
||||
|
||||
interface Props {
|
||||
className?: string
|
||||
children?: any,
|
||||
}
|
||||
|
||||
const RecipeBriefInfo = ({ }: Props) => {
|
||||
return (
|
||||
<section className={s.recipeBriefInfo}>
|
||||
<div className={s.item}>
|
||||
<IconTime />
|
||||
<div className={s.content}>15 minutes</div>
|
||||
</div>
|
||||
<div className={s.item}>
|
||||
<IconPeople />
|
||||
<div className={s.content}>4 People</div>
|
||||
</div>
|
||||
<div className={s.item}>
|
||||
<IconLocation />
|
||||
<div className={s.content}>15 minutes</div>
|
||||
</div>
|
||||
</section >
|
||||
)
|
||||
}
|
||||
|
||||
export default RecipeBriefInfo
|
@ -0,0 +1,61 @@
|
||||
@import "../../../../../styles/utilities";
|
||||
|
||||
.recipeDetailInfo {
|
||||
@apply spacing-horizontal;
|
||||
margin: 5.6rem auto;
|
||||
@screen md {
|
||||
@apply flex;
|
||||
}
|
||||
.img {
|
||||
width: fit-content;
|
||||
margin-top: 0;
|
||||
|
||||
@screen sm-only {
|
||||
margin-bottom: 2rem;
|
||||
}
|
||||
@screen lg {
|
||||
max-width: 60rem;
|
||||
}
|
||||
img {
|
||||
@apply w-full;
|
||||
object-fit: contain;
|
||||
max-height: 64rem;
|
||||
border-radius: 2.4rem;
|
||||
@screen md {
|
||||
max-height: 90rem;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.recipeInfo {
|
||||
@screen md {
|
||||
max-width: 39rem;
|
||||
margin-left: 4.8rem;
|
||||
}
|
||||
@screen lg {
|
||||
margin-left: 11.2rem;
|
||||
}
|
||||
.top {
|
||||
margin-bottom: 4.8rem;
|
||||
.name {
|
||||
@apply heading-1 font-heading;
|
||||
margin-bottom: 1.6rem;
|
||||
}
|
||||
}
|
||||
.detail {
|
||||
.item {
|
||||
&:not(:last-child) {
|
||||
margin-bottom: 2.4rem;
|
||||
}
|
||||
.heading {
|
||||
@apply heading-3 font-heading;
|
||||
margin-bottom: 0.8rem;
|
||||
}
|
||||
.content {
|
||||
list-style: disc;
|
||||
margin-left: 2rem;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
@ -0,0 +1,59 @@
|
||||
import React from 'react'
|
||||
import RecipeBriefInfo from '../RecipeBriefInfo/RecipeBriefInfo'
|
||||
import s from './RecipeDetailInfo.module.scss'
|
||||
|
||||
|
||||
interface Props {
|
||||
className?: string
|
||||
children?: any
|
||||
}
|
||||
|
||||
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" />
|
||||
</div>
|
||||
<div className={s.recipeInfo}>
|
||||
<div className={s.top}>
|
||||
<h1 className={s.name}>
|
||||
Crispy Fried Calamari
|
||||
</h1>
|
||||
<RecipeBriefInfo />
|
||||
</div>
|
||||
<div className={s.detail}>
|
||||
<div className={s.item}>
|
||||
<h3 className={s.heading}>Ingredients</h3>
|
||||
<ul className={s.content}>
|
||||
<li>Canola oil for frying</li>
|
||||
<li>1 pound clean squid bodies cut in 1/4 inch rings and dried with a paper towel</li>
|
||||
<li>2 cups flour</li>
|
||||
<li>1/2 teaspoon kosher salt</li>
|
||||
<li>1/2 teaspoon garlic powder</li>
|
||||
<li>1/8 teaspoon coarse ground black pepper</li>
|
||||
<li>1 lemon cut into wedges</li>
|
||||
</ul>
|
||||
</div>
|
||||
|
||||
<div className={s.item}>
|
||||
<h3 className={s.heading}>Preparation</h3>
|
||||
<ul className={s.content}>
|
||||
<li>1In a large pot or dutch oven add three inches of oil and bring to 350 degrees.</li>
|
||||
<li>Add the flour, salt, garlic powder and pepper to a large bowl and stir to combine.</li>
|
||||
<li>Toss the squid pieces in the flour then into the hot oil.</li>
|
||||
<li>Fry the squid for 1-2 minutes. You want the color to stay pale like in the pictures.</li>
|
||||
<li>Remove to a cookie sheet to drain (do not add paper towels as it will steam the calamari and make it soft.)</li>
|
||||
<li>Serve with lemon wedges.</li>
|
||||
</ul>
|
||||
</div>
|
||||
<div className={s.item}>
|
||||
<h3 className={s.heading}>Link</h3>
|
||||
<a href="https://dinnerthendessert.com/crispy-fried-calamari" target="_blank" rel="noopener noreferrer">https://dinnerthendessert.com/crispy-fried-calamari</a>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</section >
|
||||
)
|
||||
}
|
||||
|
||||
export default RecipeDetailInfo
|
@ -0,0 +1,21 @@
|
||||
@import "../../../../../styles/utilities";
|
||||
|
||||
.recipeIngredient {
|
||||
padding: 6rem 0;
|
||||
@screen md {
|
||||
padding: 5.6rem 0;
|
||||
}
|
||||
.top {
|
||||
@apply flex justify-between items-center spacing-horizontal;
|
||||
}
|
||||
.bottom {
|
||||
@apply flex justify-center items-center spacing-horizontal;
|
||||
margin-top: 4rem;
|
||||
button {
|
||||
width: 100%;
|
||||
@screen md {
|
||||
width: 39rem;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
@ -0,0 +1,33 @@
|
||||
import React from 'react'
|
||||
import ButtonCommon from 'src/components/common/ButtonCommon/ButtonCommon'
|
||||
import HeadingCommon from 'src/components/common/HeadingCommon/HeadingCommon'
|
||||
import { ProductCardProps } from 'src/components/common/ProductCard/ProductCard'
|
||||
import ProductCarousel from 'src/components/common/ProductCarousel/ProductCarousel'
|
||||
import ViewAllItem from 'src/components/common/ViewAllItem/ViewAllItem'
|
||||
import { ROUTE } from 'src/utils/constanst.utils'
|
||||
import s from './RecipeIngredient.module.scss'
|
||||
|
||||
interface Props {
|
||||
className?: string
|
||||
children?: any,
|
||||
data: ProductCardProps[],
|
||||
}
|
||||
|
||||
const RecipeIngredient = ({ data }: Props) => {
|
||||
return (
|
||||
<section className={s.recipeIngredient}>
|
||||
<div className={s.top}>
|
||||
<HeadingCommon>Ingredients</HeadingCommon>
|
||||
<div>
|
||||
<ViewAllItem link={ROUTE.PRODUCTS} />
|
||||
</div>
|
||||
</div>
|
||||
<ProductCarousel data={data} itemKey="recipe-ingredient" />
|
||||
<div className={s.bottom}>
|
||||
<ButtonCommon type='ghost' size='large'>Buy all</ButtonCommon>
|
||||
</div>
|
||||
</section>
|
||||
)
|
||||
}
|
||||
|
||||
export default RecipeIngredient
|
@ -1,15 +0,0 @@
|
||||
import React, { MutableRefObject } from 'react'
|
||||
|
||||
interface ScrollTargetProps {
|
||||
refScrollUp: MutableRefObject<HTMLDivElement>;
|
||||
}
|
||||
|
||||
const ScrollTarget = ({ refScrollUp } : ScrollTargetProps) => {
|
||||
|
||||
return (
|
||||
<div ref={refScrollUp}></div>
|
||||
)
|
||||
|
||||
}
|
||||
|
||||
export default ScrollTarget
|
@ -5,11 +5,10 @@ import s from './ScrollToTop.module.scss'
|
||||
import ArrowUp from '../../icons/IconArrowUp'
|
||||
|
||||
interface ScrollToTopProps {
|
||||
target: MutableRefObject<HTMLDivElement>;
|
||||
visibilityHeight?: number;
|
||||
}
|
||||
|
||||
const ScrollToTop = ({ target, visibilityHeight=450 }: ScrollToTopProps) => {
|
||||
const ScrollToTop = ({ visibilityHeight=450 }: ScrollToTopProps) => {
|
||||
|
||||
const [scrollPosition, setSrollPosition] = useState(0);
|
||||
const [showScrollToTop, setShowScrollToTop] = useState("hide");
|
||||
@ -26,7 +25,7 @@ const ScrollToTop = ({ target, visibilityHeight=450 }: ScrollToTopProps) => {
|
||||
};
|
||||
|
||||
function handleScrollUp() {
|
||||
target.current.scrollIntoView({ behavior: "smooth" });
|
||||
window.scrollTo(0, 0);
|
||||
}
|
||||
|
||||
function addEventScroll() {
|
||||
@ -34,7 +33,7 @@ const ScrollToTop = ({ target, visibilityHeight=450 }: ScrollToTopProps) => {
|
||||
}
|
||||
|
||||
useEffect(() => {
|
||||
addEventScroll()
|
||||
addEventScroll();
|
||||
});
|
||||
|
||||
return (
|
||||
|
@ -1,32 +1,69 @@
|
||||
@import "../../../styles/utilities";
|
||||
|
||||
.select{
|
||||
@apply rounded-lg border-solid;
|
||||
display: flex;
|
||||
flex-direction: row;
|
||||
justify-content: center;
|
||||
align-items: center;
|
||||
padding: 1.2rem 1.6rem;
|
||||
background-color: var(--white);
|
||||
&.base{
|
||||
width: 18.6rem;
|
||||
height: 4.8rem;
|
||||
width: 20.6rem;
|
||||
.selectTrigger{
|
||||
width: 20.6rem;
|
||||
padding: 1.2rem 1.6rem;
|
||||
}
|
||||
}
|
||||
&.large{
|
||||
width: 34.25rem;
|
||||
height: 5.6rem;
|
||||
.selectTrigger{
|
||||
width: 34.25rem;
|
||||
padding: 1.6rem 1.6rem;
|
||||
}
|
||||
}
|
||||
&.default{
|
||||
@apply border;
|
||||
.selectTrigger{
|
||||
@apply border-solid border border-current;
|
||||
}
|
||||
}
|
||||
&.custom{
|
||||
.selectTrigger{
|
||||
@apply border-2;
|
||||
border-color: var(--border-line);
|
||||
color: var(--text-label);
|
||||
}
|
||||
}
|
||||
&.isActive{
|
||||
.selectOptionWrapper{
|
||||
@apply block;
|
||||
}
|
||||
}
|
||||
}
|
||||
.selectTrigger{
|
||||
@apply outline-none flex justify-between;
|
||||
color: var(--text-active);
|
||||
border-radius: 0.8rem;
|
||||
|
||||
}
|
||||
.selectOptionWrapper{
|
||||
@apply outline-none hidden z-10 absolute;
|
||||
border-radius: 0.8rem;
|
||||
background-color: var(--white);
|
||||
padding: 0.4rem 0rem 0.4rem 0rem;
|
||||
margin-top: 0.6rem;
|
||||
&.base{
|
||||
width: 20.6rem;
|
||||
}
|
||||
&.large{
|
||||
width: 34.25rem;
|
||||
}
|
||||
&.default{
|
||||
@apply border-solid border border-current;
|
||||
}
|
||||
&.custom{
|
||||
@apply border-2;
|
||||
border-color: var(--border-line);
|
||||
color: var(--text-label);
|
||||
}
|
||||
.option{
|
||||
&:hover{
|
||||
background-color: black;
|
||||
}
|
||||
&.active{
|
||||
@apply hidden;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
@ -1,26 +1,75 @@
|
||||
import s from './SelectCommon.module.scss'
|
||||
import classNames from 'classnames'
|
||||
import { useState, useRef, useEffect } from 'react'
|
||||
import { IconVectorDown } from 'src/components/icons'
|
||||
import SelectOption from './SelectOption/SelectOption'
|
||||
|
||||
interface Props {
|
||||
placeHolder? : string,
|
||||
children? : React.ReactNode,
|
||||
size?: 'base' | 'large',
|
||||
type?: 'default' | 'custom',
|
||||
option: {name: string}[],
|
||||
}
|
||||
|
||||
const SelectCommon = ({ type = 'default', size = 'base', option, placeHolder }: Props) => {
|
||||
const SelectCommon = ({ type = 'default', size = 'base', option, children }: Props) => {
|
||||
const [isActive, setActive] = useState(false)
|
||||
const [selectedName, setSelectedName] = useState(children)
|
||||
const ref = useRef<HTMLDivElement>(null)
|
||||
|
||||
useEffect(() => {
|
||||
const handleClick = (event: MouseEvent) => {
|
||||
const { target } = event;
|
||||
if (!ref?.current || ref?.current.contains(target as Node)) {
|
||||
return
|
||||
}
|
||||
else{
|
||||
setActive(false)
|
||||
}
|
||||
}
|
||||
document.addEventListener('click', handleClick)
|
||||
return () => {
|
||||
document.removeEventListener('click', handleClick)
|
||||
}
|
||||
}, [ref])
|
||||
|
||||
const changeActiveStatus = () => {
|
||||
setActive(!isActive)
|
||||
}
|
||||
|
||||
const changeSelectedName = (item:string) => {
|
||||
setSelectedName(item)
|
||||
}
|
||||
return(
|
||||
<select className={classNames({
|
||||
<>
|
||||
<div className={classNames({
|
||||
[s.select] : true,
|
||||
[s[size]] : !!size,
|
||||
[s[type]] : !!type,
|
||||
[s.isActive] : isActive,
|
||||
})}
|
||||
onClick = { changeActiveStatus }
|
||||
ref = {ref}
|
||||
>
|
||||
<div className={classNames({
|
||||
[s.selectTrigger] : true,
|
||||
|
||||
})}
|
||||
>{selectedName}<IconVectorDown /></div>
|
||||
|
||||
<div className={classNames({
|
||||
[s.selectOptionWrapper] : true,
|
||||
[s[type]] : !!type,
|
||||
[s[size]] : !!size,
|
||||
})}
|
||||
>
|
||||
<option disabled selected hidden>{placeHolder}</option>
|
||||
{
|
||||
option.map(item => <option className={s.option} value={item.name}> {item.name} </option>)
|
||||
option.map(item =>
|
||||
<SelectOption itemName={item.name} onClick={changeSelectedName} size={size} />
|
||||
)
|
||||
}
|
||||
</select>
|
||||
</div>
|
||||
</div>
|
||||
</>
|
||||
)
|
||||
}
|
||||
|
||||
|
@ -0,0 +1,17 @@
|
||||
@import "../../../../styles/utilities";
|
||||
|
||||
.selectOption {
|
||||
@apply outline-none;
|
||||
background-color: var(--white);
|
||||
&.base{
|
||||
width: 20.4rem;
|
||||
padding: 0.8rem 1.6rem;
|
||||
}
|
||||
&.large{
|
||||
width: 33.75rem;
|
||||
padding: 0.8rem 1.6rem;
|
||||
}
|
||||
&:hover{
|
||||
background-color: var(--gray);
|
||||
}
|
||||
}
|
@ -0,0 +1,25 @@
|
||||
import s from './SelectOption.module.scss'
|
||||
import classNames from 'classnames'
|
||||
|
||||
interface Props{
|
||||
onClick: (value: string) => void,
|
||||
itemName: string,
|
||||
size: 'base' | 'large',
|
||||
}
|
||||
|
||||
const SelectOption = ({onClick, itemName, size}: Props) => {
|
||||
|
||||
const changeName = () => {
|
||||
onClick(itemName)
|
||||
}
|
||||
return(
|
||||
<div className={classNames({
|
||||
[s.selectOption] : true,
|
||||
[s[size]] : !!size,
|
||||
})}
|
||||
onClick = {changeName}
|
||||
>{itemName}</div>
|
||||
)
|
||||
}
|
||||
|
||||
export default SelectOption
|
@ -4,7 +4,7 @@
|
||||
display: flex;
|
||||
.content {
|
||||
color: var(--primary);
|
||||
margin: 0.8rem 0.8rem 0.8rem 1.6rem;
|
||||
margin: 0.8rem 0.8rem 0.8rem 0;
|
||||
font-weight: bold;
|
||||
}
|
||||
.vector {
|
||||
|
@ -3,18 +3,22 @@ export { default as Layout } from './Layout/Layout'
|
||||
export { default as CarouselCommon } from './CarouselCommon/CarouselCommon'
|
||||
export { default as QuanittyInput } from './QuanittyInput/QuanittyInput'
|
||||
export { default as LabelCommon } from './LabelCommon/LabelCommon'
|
||||
export { default as ProductCard } from './ProductCard/ProductCard'
|
||||
export { default as ProductCarousel } from './ProductCarousel/ProductCarousel'
|
||||
export { default as FeaturedProductCard } from './FeaturedProductCard/FeaturedProductCard'
|
||||
export { default as RecipeCard } from './RecipeCard/RecipeCard'
|
||||
export { default as Head } from './Head/Head'
|
||||
export { default as ViewAllItem} from './ViewAllItem/ViewAllItem'
|
||||
export { default as ItemWishList} from './ItemWishList/ItemWishList'
|
||||
export { default as Logo} from './Logo/Logo'
|
||||
export { default as Inputcommon} from './InputCommon/InputCommon'
|
||||
export { default as InputPassword} from './InputPassword/InputPassword'
|
||||
export { default as CheckboxCommon} from './CheckboxCommon/CheckboxCommon'
|
||||
export { default as Author} from './Author/Author'
|
||||
export { default as DateTime} from './DateTime/DateTime'
|
||||
export { default as HeadingCommon } from './HeadingCommon/HeadingCommon'
|
||||
export { default as CollectionHeading } from './CollectionHeading/CollectionHeading'
|
||||
export { default as ScrollToTop } from './ScrollToTop/ScrollToTop'
|
||||
export { default as ScrollTarget } from './ScrollToTop/ScrollTarget'
|
||||
export { default as InputSearch} from './InputSearch/InputSearch'
|
||||
export { default as ButtonIconBuy} from './ButtonIconBuy/ButtonIconBuy'
|
||||
export { default as Banner} from './Banner/Banner'
|
||||
@ -23,5 +27,9 @@ export { default as MenuDropdown} from './MenuDropdown/MenuDropdown'
|
||||
export { default as NotiMessage} from './NotiMessage/NotiMessage'
|
||||
export { default as VideoPlayer} from './VideoPlayer/VideoPlayer'
|
||||
export { default as SelectCommon} from './SelectCommon/SelectCommon'
|
||||
export { default as MenuNavigation} from './MenuNavigation/MenuNavigation'
|
||||
export { default as MenuFilter} from './MenuFilter/MenuFilter'
|
||||
export { default as ModalCommon} from './ModalCommon/ModalCommon'
|
||||
export { default as ModalConfirm} from "./ModalConfirm/ModalConfirm"
|
||||
export { default as ModalInfo} from "./ModalInfo/ModalInfo"
|
||||
export { default as ModalCreateUserInfo} from './ModalCreateUserInfo/ModalCreateUserInfo'
|
||||
export { default as ImgWithLink} from './ImgWithLink/ImgWithLink'
|
||||
export { default as RecipeDetail} from './RecipeDetail/RecipeDetail'
|
||||
|
1
src/components/hooks/index.ts
Normal file
@ -0,0 +1 @@
|
||||
export { default as useModalCommon } from './useModalCommon'
|
25
src/components/hooks/useModalCommon.tsx
Normal file
@ -0,0 +1,25 @@
|
||||
import { useState } from 'react';
|
||||
|
||||
interface Props {
|
||||
initialValue?: boolean,
|
||||
}
|
||||
|
||||
const useModalCommon = ({ initialValue = false }: Props) => {
|
||||
const [visible, setVisible] = useState<boolean>(initialValue)
|
||||
|
||||
const openModal = (e?: any) => {
|
||||
e && e.stopPropagation()
|
||||
setVisible(true)
|
||||
}
|
||||
|
||||
const closeModal = (e?: any) => {
|
||||
e && e.stopPropagation()
|
||||
setVisible(false)
|
||||
}
|
||||
|
||||
return {
|
||||
visible, openModal, closeModal
|
||||
}
|
||||
};
|
||||
|
||||
export default useModalCommon
|
22
src/components/icons/Close.tsx
Normal file
@ -0,0 +1,22 @@
|
||||
import React from 'react'
|
||||
|
||||
interface Props {}
|
||||
|
||||
const Close = (props: Props) => {
|
||||
return (
|
||||
<svg
|
||||
width="18"
|
||||
height="18"
|
||||
viewBox="0 0 18 18"
|
||||
fill="none"
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
>
|
||||
<path
|
||||
d="M10.4099 9.00019L16.7099 2.71019C16.8982 2.52188 17.004 2.26649 17.004 2.00019C17.004 1.73388 16.8982 1.47849 16.7099 1.29019C16.5216 1.10188 16.2662 0.996094 15.9999 0.996094C15.7336 0.996094 15.4782 1.10188 15.2899 1.29019L8.99994 7.59019L2.70994 1.29019C2.52164 1.10188 2.26624 0.996094 1.99994 0.996094C1.73364 0.996094 1.47824 1.10188 1.28994 1.29019C1.10164 1.47849 0.995847 1.73388 0.995847 2.00019C0.995847 2.26649 1.10164 2.52188 1.28994 2.71019L7.58994 9.00019L1.28994 15.2902C1.19621 15.3831 1.12182 15.4937 1.07105 15.6156C1.02028 15.7375 0.994141 15.8682 0.994141 16.0002C0.994141 16.1322 1.02028 16.2629 1.07105 16.3848C1.12182 16.5066 1.19621 16.6172 1.28994 16.7102C1.3829 16.8039 1.4935 16.8783 1.61536 16.9291C1.73722 16.9798 1.86793 17.006 1.99994 17.006C2.13195 17.006 2.26266 16.9798 2.38452 16.9291C2.50638 16.8783 2.61698 16.8039 2.70994 16.7102L8.99994 10.4102L15.2899 16.7102C15.3829 16.8039 15.4935 16.8783 15.6154 16.9291C15.7372 16.9798 15.8679 17.006 15.9999 17.006C16.132 17.006 16.2627 16.9798 16.3845 16.9291C16.5064 16.8783 16.617 16.8039 16.7099 16.7102C16.8037 16.6172 16.8781 16.5066 16.9288 16.3848C16.9796 16.2629 17.0057 16.1322 17.0057 16.0002C17.0057 15.8682 16.9796 15.7375 16.9288 15.6156C16.8781 15.4937 16.8037 15.3831 16.7099 15.2902L10.4099 9.00019Z"
|
||||
fill="#141414"
|
||||
/>
|
||||
</svg>
|
||||
)
|
||||
}
|
||||
|
||||
export default Close
|
18
src/components/icons/IconApple.tsx
Normal file
@ -0,0 +1,18 @@
|
||||
import React from 'react'
|
||||
|
||||
const IconApple = () => {
|
||||
return (
|
||||
<svg width="25" height="24" viewBox="0 0 25 24" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||
<g clipPath="url(#clip0)">
|
||||
<path d="M22.292 18.7033C21.929 19.5418 21.4994 20.3136 21.0016 21.0232C20.3231 21.9906 19.7676 22.6602 19.3395 23.032C18.6758 23.6424 17.9647 23.955 17.2032 23.9728C16.6566 23.9728 15.9973 23.8172 15.23 23.5017C14.4601 23.1876 13.7525 23.032 13.1056 23.032C12.4271 23.032 11.6994 23.1876 10.9211 23.5017C10.1415 23.8172 9.51355 23.9817 9.03342 23.9979C8.30322 24.0291 7.57539 23.7076 6.8489 23.032C6.38521 22.6276 5.80523 21.9343 5.11043 20.9521C4.36498 19.9033 3.75211 18.687 3.27198 17.3004C2.75777 15.8026 2.5 14.3523 2.5 12.9482C2.5 11.3398 2.84754 9.95259 3.54367 8.79011C4.09076 7.85636 4.81859 7.11979 5.72953 6.57906C6.64046 6.03834 7.62473 5.76279 8.68469 5.74516C9.26467 5.74516 10.0252 5.92457 10.9704 6.27715C11.9129 6.63091 12.5181 6.81032 12.7834 6.81032C12.9817 6.81032 13.654 6.60054 14.7937 6.18233C15.8714 5.79449 16.781 5.63391 17.5262 5.69716C19.5454 5.86012 21.0624 6.6561 22.0712 8.09013C20.2654 9.18432 19.3721 10.7169 19.3898 12.6829C19.4061 14.2142 19.9617 15.4886 21.0535 16.5004C21.5483 16.97 22.1009 17.333 22.7156 17.5907C22.5823 17.9774 22.4416 18.3477 22.292 18.7033ZM17.661 0.480137C17.661 1.68041 17.2225 2.8011 16.3484 3.8384C15.2937 5.07155 14.0179 5.78412 12.6343 5.67168C12.6167 5.52769 12.6065 5.37614 12.6065 5.21688C12.6065 4.06462 13.1081 2.83147 13.9989 1.82321C14.4436 1.3127 15.0092 0.888228 15.6951 0.549615C16.3796 0.216055 17.0269 0.031589 17.6358 0C17.6536 0.160458 17.661 0.320926 17.661 0.480121V0.480137Z" fill="black" />
|
||||
</g>
|
||||
<defs>
|
||||
<clipPath id="clip0">
|
||||
<rect width="24" height="24" fill="white" transform="translate(0.5)" />
|
||||
</clipPath>
|
||||
</defs>
|
||||
</svg>
|
||||
)
|
||||
}
|
||||
|
||||
export default IconApple
|
11
src/components/icons/IconCheck.tsx
Normal file
@ -0,0 +1,11 @@
|
||||
import React from 'react'
|
||||
|
||||
const IconCheck = () => {
|
||||
return (
|
||||
<svg width="16" height="12" viewBox="0 0 16 12" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||
<path d="M14.7104 1.20986C14.6175 1.11613 14.5069 1.04174 14.385 0.990969C14.2632 0.940201 14.1324 0.914062 14.0004 0.914062C13.8684 0.914062 13.7377 0.940201 13.6159 0.990969C13.494 1.04174 13.3834 1.11613 13.2904 1.20986L5.84044 8.66986L2.71044 5.52986C2.61392 5.43662 2.49998 5.36331 2.37512 5.3141C2.25026 5.2649 2.11694 5.24077 1.98276 5.24309C1.84858 5.24541 1.71617 5.27414 1.59309 5.32763C1.47001 5.38113 1.35868 5.45834 1.26544 5.55486C1.1722 5.65138 1.09889 5.76532 1.04968 5.89018C1.00048 6.01503 0.976347 6.14836 0.978669 6.28254C0.98099 6.41672 1.00972 6.54913 1.06321 6.67221C1.1167 6.79529 1.19392 6.90662 1.29044 6.99986L5.13044 10.8399C5.2234 10.9336 5.334 11.008 5.45586 11.0588C5.57772 11.1095 5.70843 11.1357 5.84044 11.1357C5.97245 11.1357 6.10316 11.1095 6.22502 11.0588C6.34687 11.008 6.45748 10.9336 6.55044 10.8399L14.7104 2.67986C14.8119 2.58622 14.893 2.47257 14.9484 2.34607C15.0038 2.21957 15.0324 2.08296 15.0324 1.94486C15.0324 1.80676 15.0038 1.67015 14.9484 1.54365C14.893 1.41715 14.8119 1.3035 14.7104 1.20986Z" fill="#4EA674" />
|
||||
</svg>
|
||||
)
|
||||
}
|
||||
|
||||
export default IconCheck
|