mirror of
https://github.com/vercel/commerce.git
synced 2025-07-23 04:36:49 +00:00
Monorepo with Turborepo (#651)
* Moved everything * Figuring out how to make imports work * Updated exports * Added missing exports * Added @vercel/commerce-local to `site` * Updated commerce config * Updated exports and commerce config * Updated commerce hoc * Fixed exports in local * Added publish config * Updated imports in site * It's actually working * Don't use debugger in dev for better speeds * Improved DX when editing packages * Set up eslint with husky * Updated prettier config * Added prettier setup to every package * Moved bigcommerce * Moved Bigcommerce to src and package updates * Updated setup of bigcommerce * Moved definitions script * Moved commercejs * Move to src * Fixed types in commercejs * Moved kibocommerce * Moved kibocommerce to src * Added package/tsconfig to kibocommerce * Fixed imports and other things * Moved ordercloud * Moved ordercloud to src * Fixed imports * Added missing prettier files * Moved Saleor * Moved Saleor to src * Fixed imports * Replaced all imports to @commerce * Added prettierignore/rc to all providers * Moved shopify to src * Build shopify in packages * Moved Spree * Moved spree to src * Updated spree * Moved swell * Moved swell to src * Fixed type imports in swell * Moved Vendure to packages * Moved vendure to src * Fixed imports in vendure * Added codegen to saleor * Updated codegen setup for shopify * Added codegen to vendure * Added codegen to kibocommerce * Added all packages to site's deps * Updated codegen setup in bigcommerce * Minor fixes * Updated providers' names in site * Updated packages based on Bel's changes * Updated turbo to latest * Fixed ts complains * Set npm engine in root * New lockfile install * remove engines * Regen lockfile * Switched from npm to yarn * Updated typesVersions in all packages * Moved dep * Updated SWR to the just released 1.2.0 * Removed "isolatedModules" from packages * Updated list of providers and default * Updated swell declaration * Removed next import from kibocommerce * Added COMMERCE_PROVIDER log * Added another log * Updated turbo config * Updated docs * Removed test logs Co-authored-by: Jared Palmer <jared@jaredpalmer.com>
This commit is contained in:
24
site/components/common/Avatar/Avatar.tsx
Normal file
24
site/components/common/Avatar/Avatar.tsx
Normal file
@@ -0,0 +1,24 @@
|
||||
import { FC, useRef, useEffect } from 'react'
|
||||
import { useUserAvatar } from '@lib/hooks/useUserAvatar'
|
||||
|
||||
interface Props {
|
||||
className?: string
|
||||
children?: any
|
||||
}
|
||||
|
||||
const Avatar: FC<Props> = ({}) => {
|
||||
let ref = useRef() as React.MutableRefObject<HTMLInputElement>
|
||||
let { userAvatar } = useUserAvatar()
|
||||
|
||||
return (
|
||||
<div
|
||||
ref={ref}
|
||||
style={{ backgroundImage: userAvatar }}
|
||||
className="inline-block h-8 w-8 rounded-full border-2 border-primary hover:border-secondary focus:border-secondary transition-colors ease-linear"
|
||||
>
|
||||
{/* Add an image - We're generating a gradient as placeholder <img></img> */}
|
||||
</div>
|
||||
)
|
||||
}
|
||||
|
||||
export default Avatar
|
1
site/components/common/Avatar/index.ts
Normal file
1
site/components/common/Avatar/index.ts
Normal file
@@ -0,0 +1 @@
|
||||
export { default } from './Avatar'
|
6
site/components/common/FeatureBar/FeatureBar.module.css
Normal file
6
site/components/common/FeatureBar/FeatureBar.module.css
Normal file
@@ -0,0 +1,6 @@
|
||||
.root {
|
||||
@apply text-center p-6 bg-primary text-sm flex-row
|
||||
justify-center items-center font-medium fixed bottom-0
|
||||
w-full z-30 transition-all duration-300 ease-out
|
||||
md:flex md:text-left;
|
||||
}
|
39
site/components/common/FeatureBar/FeatureBar.tsx
Normal file
39
site/components/common/FeatureBar/FeatureBar.tsx
Normal file
@@ -0,0 +1,39 @@
|
||||
import cn from 'classnames'
|
||||
import s from './FeatureBar.module.css'
|
||||
|
||||
interface FeatureBarProps {
|
||||
className?: string
|
||||
title: string
|
||||
description?: string
|
||||
hide?: boolean
|
||||
action?: React.ReactNode
|
||||
}
|
||||
|
||||
const FeatureBar: React.FC<FeatureBarProps> = ({
|
||||
title,
|
||||
description,
|
||||
className,
|
||||
action,
|
||||
hide,
|
||||
}) => {
|
||||
const rootClassName = cn(
|
||||
s.root,
|
||||
{
|
||||
transform: true,
|
||||
'translate-y-0 opacity-100': !hide,
|
||||
'translate-y-full opacity-0': hide,
|
||||
},
|
||||
className
|
||||
)
|
||||
return (
|
||||
<div className={rootClassName}>
|
||||
<span className="block md:inline">{title}</span>
|
||||
<span className="block mb-6 md:inline md:mb-0 md:ml-2">
|
||||
{description}
|
||||
</span>
|
||||
{action && action}
|
||||
</div>
|
||||
)
|
||||
}
|
||||
|
||||
export default FeatureBar
|
1
site/components/common/FeatureBar/index.ts
Normal file
1
site/components/common/FeatureBar/index.ts
Normal file
@@ -0,0 +1 @@
|
||||
export { default } from './FeatureBar'
|
13
site/components/common/Footer/Footer.module.css
Normal file
13
site/components/common/Footer/Footer.module.css
Normal file
@@ -0,0 +1,13 @@
|
||||
.root {
|
||||
@apply border-t border-accent-2;
|
||||
}
|
||||
|
||||
.link {
|
||||
& > svg {
|
||||
@apply transform duration-75 ease-linear;
|
||||
}
|
||||
|
||||
&:hover > svg {
|
||||
@apply scale-110;
|
||||
}
|
||||
}
|
117
site/components/common/Footer/Footer.tsx
Normal file
117
site/components/common/Footer/Footer.tsx
Normal file
@@ -0,0 +1,117 @@
|
||||
import { FC } from 'react'
|
||||
import cn from 'classnames'
|
||||
import Link from 'next/link'
|
||||
import { useRouter } from 'next/router'
|
||||
import type { Page } from '@commerce/types/page'
|
||||
import getSlug from '@lib/get-slug'
|
||||
import { Github, Vercel } from '@components/icons'
|
||||
import { Logo, Container } from '@components/ui'
|
||||
import { I18nWidget } from '@components/common'
|
||||
import s from './Footer.module.css'
|
||||
|
||||
interface Props {
|
||||
className?: string
|
||||
children?: any
|
||||
pages?: Page[]
|
||||
}
|
||||
|
||||
const links = [
|
||||
{
|
||||
name: 'Home',
|
||||
url: '/',
|
||||
},
|
||||
]
|
||||
|
||||
const Footer: FC<Props> = ({ className, pages }) => {
|
||||
const { sitePages } = usePages(pages)
|
||||
const rootClassName = cn(s.root, className)
|
||||
|
||||
return (
|
||||
<footer className={rootClassName}>
|
||||
<Container>
|
||||
<div className="grid grid-cols-1 lg:grid-cols-12 gap-8 border-b border-accent-2 py-12 text-primary bg-primary transition-colors duration-150">
|
||||
<div className="col-span-1 lg:col-span-2">
|
||||
<Link href="/">
|
||||
<a className="flex flex-initial items-center font-bold md:mr-24">
|
||||
<span className="rounded-full border border-accent-6 mr-2">
|
||||
<Logo />
|
||||
</span>
|
||||
<span>ACME</span>
|
||||
</a>
|
||||
</Link>
|
||||
</div>
|
||||
<div className="col-span-1 lg:col-span-8">
|
||||
<div className="grid md:grid-rows-4 md:grid-cols-3 md:grid-flow-col">
|
||||
{[...links, ...sitePages].map((page) => (
|
||||
<span key={page.url} className="py-3 md:py-0 md:pb-4">
|
||||
<Link href={page.url!}>
|
||||
<a className="text-accent-9 hover:text-accent-6 transition ease-in-out duration-150">
|
||||
{page.name}
|
||||
</a>
|
||||
</Link>
|
||||
</span>
|
||||
))}
|
||||
</div>
|
||||
</div>
|
||||
<div className="col-span-1 lg:col-span-2 flex items-start lg:justify-end text-primary">
|
||||
<div className="flex space-x-6 items-center h-10">
|
||||
<a
|
||||
className={s.link}
|
||||
aria-label="Github Repository"
|
||||
href="https://github.com/vercel/commerce"
|
||||
>
|
||||
<Github />
|
||||
</a>
|
||||
<I18nWidget />
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div className="pt-6 pb-10 flex flex-col md:flex-row justify-between items-center space-y-4 text-accent-6 text-sm">
|
||||
<div>
|
||||
<span>© 2020 ACME, Inc. All rights reserved.</span>
|
||||
</div>
|
||||
<div className="flex items-center text-primary text-sm">
|
||||
<span className="text-primary">Created by</span>
|
||||
<a
|
||||
rel="noopener noreferrer"
|
||||
href="https://vercel.com"
|
||||
aria-label="Vercel.com Link"
|
||||
target="_blank"
|
||||
className="text-primary"
|
||||
>
|
||||
<Vercel
|
||||
className="inline-block h-6 ml-3 text-primary"
|
||||
alt="Vercel.com Logo"
|
||||
/>
|
||||
</a>
|
||||
</div>
|
||||
</div>
|
||||
</Container>
|
||||
</footer>
|
||||
)
|
||||
}
|
||||
|
||||
function usePages(pages?: Page[]) {
|
||||
const { locale } = useRouter()
|
||||
const sitePages: Page[] = []
|
||||
|
||||
if (pages) {
|
||||
pages.forEach((page) => {
|
||||
const slug = page.url && getSlug(page.url)
|
||||
if (!slug) return
|
||||
if (locale && !slug.startsWith(`${locale}/`)) return
|
||||
sitePages.push(page)
|
||||
})
|
||||
}
|
||||
|
||||
return {
|
||||
sitePages: sitePages.sort(bySortOrder),
|
||||
}
|
||||
}
|
||||
|
||||
// Sort pages by the sort order assigned in the BC dashboard
|
||||
function bySortOrder(a: Page, b: Page) {
|
||||
return (a.sort_order ?? 0) - (b.sort_order ?? 0)
|
||||
}
|
||||
|
||||
export default Footer
|
1
site/components/common/Footer/index.ts
Normal file
1
site/components/common/Footer/index.ts
Normal file
@@ -0,0 +1 @@
|
||||
export { default } from './Footer'
|
18
site/components/common/Head/Head.tsx
Normal file
18
site/components/common/Head/Head.tsx
Normal file
@@ -0,0 +1,18 @@
|
||||
import { FC } from 'react'
|
||||
import NextHead from 'next/head'
|
||||
import { DefaultSeo } from 'next-seo'
|
||||
import config from '@config/seo.json'
|
||||
|
||||
const Head: FC = () => {
|
||||
return (
|
||||
<>
|
||||
<DefaultSeo {...config} />
|
||||
<NextHead>
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1" />
|
||||
<link rel="manifest" href="/site.webmanifest" key="site-manifest" />
|
||||
</NextHead>
|
||||
</>
|
||||
)
|
||||
}
|
||||
|
||||
export default Head
|
1
site/components/common/Head/index.ts
Normal file
1
site/components/common/Head/index.ts
Normal file
@@ -0,0 +1 @@
|
||||
export { default } from './Head'
|
@@ -0,0 +1,23 @@
|
||||
.root {
|
||||
@apply py-12 flex flex-col w-full px-6;
|
||||
|
||||
@screen md {
|
||||
@apply flex-row;
|
||||
}
|
||||
|
||||
& .asideWrapper {
|
||||
@apply pr-3 w-full relative;
|
||||
|
||||
@screen md {
|
||||
@apply w-48;
|
||||
}
|
||||
}
|
||||
|
||||
& .aside {
|
||||
@apply flex flex-row w-full justify-around mb-12;
|
||||
|
||||
@screen md {
|
||||
@apply mb-0 block sticky top-32;
|
||||
}
|
||||
}
|
||||
}
|
@@ -0,0 +1,73 @@
|
||||
import { FC } from 'react'
|
||||
import Link from 'next/link'
|
||||
import type { Product } from '@commerce/types/product'
|
||||
import { Grid } from '@components/ui'
|
||||
import { ProductCard } from '@components/product'
|
||||
import s from './HomeAllProductsGrid.module.css'
|
||||
import { getCategoryPath, getDesignerPath } from '@lib/search'
|
||||
|
||||
interface Props {
|
||||
categories?: any
|
||||
brands?: any
|
||||
products?: Product[]
|
||||
}
|
||||
|
||||
const HomeAllProductsGrid: FC<Props> = ({
|
||||
categories,
|
||||
brands,
|
||||
products = [],
|
||||
}) => {
|
||||
return (
|
||||
<div className={s.root}>
|
||||
<div className={s.asideWrapper}>
|
||||
<div className={s.aside}>
|
||||
<ul className="mb-10">
|
||||
<li className="py-1 text-base font-bold tracking-wide">
|
||||
<Link href={getCategoryPath('')}>
|
||||
<a>All Categories</a>
|
||||
</Link>
|
||||
</li>
|
||||
{categories.map((cat: any) => (
|
||||
<li key={cat.path} className="py-1 text-accent-8 text-base">
|
||||
<Link href={getCategoryPath(cat.path)}>
|
||||
<a>{cat.name}</a>
|
||||
</Link>
|
||||
</li>
|
||||
))}
|
||||
</ul>
|
||||
<ul className="">
|
||||
<li className="py-1 text-base font-bold tracking-wide">
|
||||
<Link href={getDesignerPath('')}>
|
||||
<a>All Designers</a>
|
||||
</Link>
|
||||
</li>
|
||||
{brands.flatMap(({ node }: any) => (
|
||||
<li key={node.path} className="py-1 text-accent-8 text-base">
|
||||
<Link href={getDesignerPath(node.path)}>
|
||||
<a>{node.name}</a>
|
||||
</Link>
|
||||
</li>
|
||||
))}
|
||||
</ul>
|
||||
</div>
|
||||
</div>
|
||||
<div className="flex-1">
|
||||
<Grid layout="normal">
|
||||
{products.map((product) => (
|
||||
<ProductCard
|
||||
key={product.path}
|
||||
product={product}
|
||||
variant="simple"
|
||||
imgProps={{
|
||||
width: 480,
|
||||
height: 480,
|
||||
}}
|
||||
/>
|
||||
))}
|
||||
</Grid>
|
||||
</div>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
|
||||
export default HomeAllProductsGrid
|
1
site/components/common/HomeAllProductsGrid/index.ts
Normal file
1
site/components/common/HomeAllProductsGrid/index.ts
Normal file
@@ -0,0 +1 @@
|
||||
export { default } from './HomeAllProductsGrid'
|
48
site/components/common/I18nWidget/I18nWidget.module.css
Normal file
48
site/components/common/I18nWidget/I18nWidget.module.css
Normal file
@@ -0,0 +1,48 @@
|
||||
.root {
|
||||
@apply relative;
|
||||
}
|
||||
|
||||
.button {
|
||||
@apply h-10 px-2 rounded-md border border-accent-2 flex items-center justify-center transition-colors ease-linear;
|
||||
}
|
||||
|
||||
.button:hover {
|
||||
@apply border-accent-3 shadow-sm;
|
||||
}
|
||||
|
||||
.button:focus {
|
||||
@apply outline-none;
|
||||
}
|
||||
|
||||
.dropdownMenu {
|
||||
@apply fixed right-0 top-12 mt-2 origin-top-right outline-none bg-primary z-40 w-full h-full;
|
||||
}
|
||||
|
||||
.item {
|
||||
@apply flex cursor-pointer px-6 py-3 transition ease-in-out duration-150 text-primary leading-6 font-medium items-center;
|
||||
text-transform: capitalize;
|
||||
}
|
||||
|
||||
.item:hover {
|
||||
@apply bg-accent-1;
|
||||
}
|
||||
|
||||
.icon {
|
||||
transition: transform 0.2s ease;
|
||||
}
|
||||
|
||||
.icon.active {
|
||||
transform: rotate(180deg);
|
||||
}
|
||||
|
||||
@screen lg {
|
||||
.dropdownMenu {
|
||||
@apply absolute border border-accent-1 shadow-lg w-56 h-auto;
|
||||
}
|
||||
}
|
||||
|
||||
@screen md {
|
||||
.closeButton {
|
||||
@apply hidden;
|
||||
}
|
||||
}
|
101
site/components/common/I18nWidget/I18nWidget.tsx
Normal file
101
site/components/common/I18nWidget/I18nWidget.tsx
Normal file
@@ -0,0 +1,101 @@
|
||||
import cn from 'classnames'
|
||||
import Link from 'next/link'
|
||||
import { FC, useState } from 'react'
|
||||
import { useRouter } from 'next/router'
|
||||
import s from './I18nWidget.module.css'
|
||||
import { Cross, ChevronUp } from '@components/icons'
|
||||
import ClickOutside from '@lib/click-outside'
|
||||
interface LOCALE_DATA {
|
||||
name: string
|
||||
img: {
|
||||
filename: string
|
||||
alt: string
|
||||
}
|
||||
}
|
||||
|
||||
const LOCALES_MAP: Record<string, LOCALE_DATA> = {
|
||||
es: {
|
||||
name: 'Español',
|
||||
img: {
|
||||
filename: 'flag-es-co.svg',
|
||||
alt: 'Bandera Colombiana',
|
||||
},
|
||||
},
|
||||
'en-US': {
|
||||
name: 'English',
|
||||
img: {
|
||||
filename: 'flag-en-us.svg',
|
||||
alt: 'US Flag',
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
const I18nWidget: FC = () => {
|
||||
const [display, setDisplay] = useState(false)
|
||||
const {
|
||||
locale,
|
||||
locales,
|
||||
defaultLocale = 'en-US',
|
||||
asPath: currentPath,
|
||||
} = useRouter()
|
||||
|
||||
const options = locales?.filter((val) => val !== locale)
|
||||
const currentLocale = locale || defaultLocale
|
||||
|
||||
return (
|
||||
<ClickOutside active={display} onClick={() => setDisplay(false)}>
|
||||
<nav className={s.root}>
|
||||
<div
|
||||
className="flex items-center relative"
|
||||
onClick={() => setDisplay(!display)}
|
||||
>
|
||||
<button className={s.button} aria-label="Language selector">
|
||||
<img
|
||||
width="20"
|
||||
height="20"
|
||||
className="block mr-2 w-5"
|
||||
src={`/${LOCALES_MAP[currentLocale].img.filename}`}
|
||||
alt={LOCALES_MAP[currentLocale].img.alt}
|
||||
/>
|
||||
{options && (
|
||||
<span className="cursor-pointer">
|
||||
<ChevronUp className={cn(s.icon, { [s.active]: display })} />
|
||||
</span>
|
||||
)}
|
||||
</button>
|
||||
</div>
|
||||
<div className="absolute top-0 right-0">
|
||||
{options?.length && display ? (
|
||||
<div className={s.dropdownMenu}>
|
||||
<div className="flex flex-row justify-end px-6">
|
||||
<button
|
||||
onClick={() => setDisplay(false)}
|
||||
aria-label="Close panel"
|
||||
className={s.closeButton}
|
||||
>
|
||||
<Cross className="h-6 w-6" />
|
||||
</button>
|
||||
</div>
|
||||
<ul>
|
||||
{options.map((locale) => (
|
||||
<li key={locale}>
|
||||
<Link href={currentPath} locale={locale}>
|
||||
<a
|
||||
className={cn(s.item)}
|
||||
onClick={() => setDisplay(false)}
|
||||
>
|
||||
{LOCALES_MAP[locale].name}
|
||||
</a>
|
||||
</Link>
|
||||
</li>
|
||||
))}
|
||||
</ul>
|
||||
</div>
|
||||
) : null}
|
||||
</div>
|
||||
</nav>
|
||||
</ClickOutside>
|
||||
)
|
||||
}
|
||||
|
||||
export default I18nWidget
|
1
site/components/common/I18nWidget/index.ts
Normal file
1
site/components/common/I18nWidget/index.ts
Normal file
@@ -0,0 +1 @@
|
||||
export { default } from './I18nWidget'
|
4
site/components/common/Layout/Layout.module.css
Normal file
4
site/components/common/Layout/Layout.module.css
Normal file
@@ -0,0 +1,4 @@
|
||||
.root {
|
||||
@apply h-full bg-primary mx-auto transition-colors duration-150;
|
||||
max-width: 2460px;
|
||||
}
|
150
site/components/common/Layout/Layout.tsx
Normal file
150
site/components/common/Layout/Layout.tsx
Normal file
@@ -0,0 +1,150 @@
|
||||
import cn from 'classnames'
|
||||
import React, { FC } from 'react'
|
||||
import dynamic from 'next/dynamic'
|
||||
import { useRouter } from 'next/router'
|
||||
import { CommerceProvider } from '@framework'
|
||||
import { useUI } from '@components/ui/context'
|
||||
import type { Page } from '@commerce/types/page'
|
||||
import { Navbar, Footer } from '@components/common'
|
||||
import type { Category } from '@commerce/types/site'
|
||||
import ShippingView from '@components/checkout/ShippingView'
|
||||
import CartSidebarView from '@components/cart/CartSidebarView'
|
||||
import { useAcceptCookies } from '@lib/hooks/useAcceptCookies'
|
||||
import { Sidebar, Button, LoadingDots } from '@components/ui'
|
||||
import PaymentMethodView from '@components/checkout/PaymentMethodView'
|
||||
import CheckoutSidebarView from '@components/checkout/CheckoutSidebarView'
|
||||
import { CheckoutProvider } from '@components/checkout/context'
|
||||
import MenuSidebarView, { Link } from '../UserNav/MenuSidebarView'
|
||||
|
||||
import LoginView from '@components/auth/LoginView'
|
||||
import s from './Layout.module.css'
|
||||
|
||||
const Loading = () => (
|
||||
<div className="w-80 h-80 flex items-center text-center justify-center p-3">
|
||||
<LoadingDots />
|
||||
</div>
|
||||
)
|
||||
|
||||
const dynamicProps = {
|
||||
loading: Loading,
|
||||
}
|
||||
|
||||
const SignUpView = dynamic(
|
||||
() => import('@components/auth/SignUpView'),
|
||||
{
|
||||
...dynamicProps
|
||||
}
|
||||
)
|
||||
|
||||
const ForgotPassword = dynamic(
|
||||
() => import('@components/auth/ForgotPassword'),
|
||||
{
|
||||
...dynamicProps
|
||||
}
|
||||
)
|
||||
|
||||
const FeatureBar = dynamic(
|
||||
() => import('@components/common/FeatureBar'),
|
||||
{
|
||||
...dynamicProps
|
||||
}
|
||||
)
|
||||
|
||||
const Modal = dynamic(
|
||||
() => import('@components/ui/Modal'),
|
||||
{
|
||||
...dynamicProps,
|
||||
ssr: false
|
||||
}
|
||||
)
|
||||
|
||||
interface Props {
|
||||
pageProps: {
|
||||
pages?: Page[]
|
||||
categories: Category[]
|
||||
}
|
||||
}
|
||||
|
||||
const ModalView: FC<{ modalView: string; closeModal(): any }> = ({
|
||||
modalView,
|
||||
closeModal,
|
||||
}) => {
|
||||
return (
|
||||
<Modal onClose={closeModal}>
|
||||
{modalView === 'LOGIN_VIEW' && <LoginView />}
|
||||
{modalView === 'SIGNUP_VIEW' && <SignUpView />}
|
||||
{modalView === 'FORGOT_VIEW' && <ForgotPassword />}
|
||||
</Modal>
|
||||
)
|
||||
}
|
||||
|
||||
const ModalUI: FC = () => {
|
||||
const { displayModal, closeModal, modalView } = useUI()
|
||||
return displayModal ? (
|
||||
<ModalView modalView={modalView} closeModal={closeModal} />
|
||||
) : null
|
||||
}
|
||||
|
||||
const SidebarView: FC<{
|
||||
sidebarView: string
|
||||
closeSidebar(): any
|
||||
links: Link[]
|
||||
}> = ({ sidebarView, closeSidebar, links }) => {
|
||||
return (
|
||||
<Sidebar onClose={closeSidebar}>
|
||||
{sidebarView === 'MOBILEMENU_VIEW' && <MenuSidebarView links={links} />}
|
||||
{sidebarView === 'CART_VIEW' && <CartSidebarView />}
|
||||
{sidebarView === 'CHECKOUT_VIEW' && <CheckoutSidebarView />}
|
||||
{sidebarView === 'PAYMENT_VIEW' && <PaymentMethodView />}
|
||||
{sidebarView === 'SHIPPING_VIEW' && <ShippingView />}
|
||||
</Sidebar>
|
||||
)
|
||||
}
|
||||
|
||||
const SidebarUI: FC<{ links: any }> = ({ links }) => {
|
||||
const { displaySidebar, closeSidebar, sidebarView } = useUI()
|
||||
return displaySidebar ? (
|
||||
<SidebarView
|
||||
sidebarView={sidebarView}
|
||||
closeSidebar={closeSidebar}
|
||||
links={links}
|
||||
/>
|
||||
) : null
|
||||
}
|
||||
|
||||
const Layout: FC<Props> = ({
|
||||
children,
|
||||
pageProps: { categories = [], ...pageProps },
|
||||
}) => {
|
||||
const { acceptedCookies, onAcceptCookies } = useAcceptCookies()
|
||||
const { locale = 'en-US' } = useRouter()
|
||||
const navBarlinks = categories.slice(0, 2).map((c) => ({
|
||||
label: c.name,
|
||||
href: `/search/${c.slug}`,
|
||||
}))
|
||||
|
||||
return (
|
||||
<CommerceProvider locale={locale}>
|
||||
<div className={cn(s.root)}>
|
||||
<Navbar links={navBarlinks} />
|
||||
<main className="fit">{children}</main>
|
||||
<Footer pages={pageProps.pages} />
|
||||
<ModalUI />
|
||||
<CheckoutProvider>
|
||||
<SidebarUI links={navBarlinks} />
|
||||
</CheckoutProvider>
|
||||
<FeatureBar
|
||||
title="This site uses cookies to improve your experience. By clicking, you agree to our Privacy Policy."
|
||||
hide={acceptedCookies}
|
||||
action={
|
||||
<Button className="mx-5" onClick={() => onAcceptCookies()}>
|
||||
Accept cookies
|
||||
</Button>
|
||||
}
|
||||
/>
|
||||
</div>
|
||||
</CommerceProvider>
|
||||
)
|
||||
}
|
||||
|
||||
export default Layout
|
1
site/components/common/Layout/index.ts
Normal file
1
site/components/common/Layout/index.ts
Normal file
@@ -0,0 +1 @@
|
||||
export { default } from './Layout'
|
35
site/components/common/Navbar/Navbar.module.css
Normal file
35
site/components/common/Navbar/Navbar.module.css
Normal file
@@ -0,0 +1,35 @@
|
||||
.root {
|
||||
@apply sticky top-0 bg-primary z-40 transition-all duration-150;
|
||||
min-height: 74px;
|
||||
}
|
||||
|
||||
.nav {
|
||||
@apply relative flex flex-row justify-between py-4 md:py-4;
|
||||
}
|
||||
|
||||
.navMenu {
|
||||
@apply hidden ml-6 space-x-4 lg:block;
|
||||
}
|
||||
|
||||
.link {
|
||||
@apply inline-flex items-center leading-6
|
||||
transition ease-in-out duration-75 cursor-pointer
|
||||
text-accent-5;
|
||||
}
|
||||
|
||||
.link:hover {
|
||||
@apply text-accent-9;
|
||||
}
|
||||
|
||||
.link:focus {
|
||||
@apply outline-none text-accent-8;
|
||||
}
|
||||
|
||||
.logo {
|
||||
@apply cursor-pointer rounded-full border transform duration-100 ease-in-out;
|
||||
|
||||
&:hover {
|
||||
@apply shadow-md;
|
||||
transform: scale(1.05);
|
||||
}
|
||||
}
|
56
site/components/common/Navbar/Navbar.tsx
Normal file
56
site/components/common/Navbar/Navbar.tsx
Normal file
@@ -0,0 +1,56 @@
|
||||
import { FC } from 'react'
|
||||
import Link from 'next/link'
|
||||
import s from './Navbar.module.css'
|
||||
import NavbarRoot from './NavbarRoot'
|
||||
import { Logo, Container } from '@components/ui'
|
||||
import { Searchbar, UserNav } from '@components/common'
|
||||
|
||||
interface Link {
|
||||
href: string
|
||||
label: string
|
||||
}
|
||||
|
||||
interface NavbarProps {
|
||||
links?: Link[]
|
||||
}
|
||||
|
||||
const Navbar: FC<NavbarProps> = ({ links }) => (
|
||||
<NavbarRoot>
|
||||
<Container>
|
||||
<div className={s.nav}>
|
||||
<div className="flex items-center flex-1">
|
||||
<Link href="/">
|
||||
<a className={s.logo} aria-label="Logo">
|
||||
<Logo />
|
||||
</a>
|
||||
</Link>
|
||||
<nav className={s.navMenu}>
|
||||
<Link href="/search">
|
||||
<a className={s.link}>All</a>
|
||||
</Link>
|
||||
{links?.map((l) => (
|
||||
<Link href={l.href} key={l.href}>
|
||||
<a className={s.link}>{l.label}</a>
|
||||
</Link>
|
||||
))}
|
||||
</nav>
|
||||
</div>
|
||||
{process.env.COMMERCE_SEARCH_ENABLED && (
|
||||
<div className="justify-center flex-1 hidden lg:flex">
|
||||
<Searchbar />
|
||||
</div>
|
||||
)}
|
||||
<div className="flex items-center justify-end flex-1 space-x-8">
|
||||
<UserNav />
|
||||
</div>
|
||||
</div>
|
||||
{process.env.COMMERCE_SEARCH_ENABLED && (
|
||||
<div className="flex pb-4 lg:px-6 lg:hidden">
|
||||
<Searchbar id="mobile-search" />
|
||||
</div>
|
||||
)}
|
||||
</Container>
|
||||
</NavbarRoot>
|
||||
)
|
||||
|
||||
export default Navbar
|
33
site/components/common/Navbar/NavbarRoot.tsx
Normal file
33
site/components/common/Navbar/NavbarRoot.tsx
Normal file
@@ -0,0 +1,33 @@
|
||||
import { FC, useState, useEffect } from 'react'
|
||||
import throttle from 'lodash.throttle'
|
||||
import cn from 'classnames'
|
||||
import s from './Navbar.module.css'
|
||||
|
||||
const NavbarRoot: FC = ({ children }) => {
|
||||
const [hasScrolled, setHasScrolled] = useState(false)
|
||||
|
||||
useEffect(() => {
|
||||
const handleScroll = throttle(() => {
|
||||
const offset = 0
|
||||
const { scrollTop } = document.documentElement
|
||||
const scrolled = scrollTop > offset
|
||||
|
||||
if (hasScrolled !== scrolled) {
|
||||
setHasScrolled(scrolled)
|
||||
}
|
||||
}, 200)
|
||||
|
||||
document.addEventListener('scroll', handleScroll)
|
||||
return () => {
|
||||
document.removeEventListener('scroll', handleScroll)
|
||||
}
|
||||
}, [hasScrolled])
|
||||
|
||||
return (
|
||||
<div className={cn(s.root, { 'shadow-magical': hasScrolled })}>
|
||||
{children}
|
||||
</div>
|
||||
)
|
||||
}
|
||||
|
||||
export default NavbarRoot
|
1
site/components/common/Navbar/index.ts
Normal file
1
site/components/common/Navbar/index.ts
Normal file
@@ -0,0 +1 @@
|
||||
export { default } from './Navbar'
|
29
site/components/common/Searchbar/Searchbar.module.css
Normal file
29
site/components/common/Searchbar/Searchbar.module.css
Normal file
@@ -0,0 +1,29 @@
|
||||
.root {
|
||||
@apply relative text-sm bg-accent-0 text-base w-full transition-colors duration-150 border border-accent-2;
|
||||
}
|
||||
|
||||
.input {
|
||||
@apply bg-transparent px-3 py-2 appearance-none w-full transition duration-150 ease-in-out pr-10;
|
||||
}
|
||||
|
||||
.input::placeholder {
|
||||
@apply text-accent-3;
|
||||
}
|
||||
|
||||
.input:focus {
|
||||
@apply outline-none shadow-outline-normal;
|
||||
}
|
||||
|
||||
.iconContainer {
|
||||
@apply absolute inset-y-0 right-0 pr-3 flex items-center pointer-events-none;
|
||||
}
|
||||
|
||||
.icon {
|
||||
@apply h-5 w-5;
|
||||
}
|
||||
|
||||
@screen sm {
|
||||
.input {
|
||||
min-width: 300px;
|
||||
}
|
||||
}
|
60
site/components/common/Searchbar/Searchbar.tsx
Normal file
60
site/components/common/Searchbar/Searchbar.tsx
Normal file
@@ -0,0 +1,60 @@
|
||||
import { FC, memo, useEffect } from 'react'
|
||||
import cn from 'classnames'
|
||||
import s from './Searchbar.module.css'
|
||||
import { useRouter } from 'next/router'
|
||||
|
||||
interface Props {
|
||||
className?: string
|
||||
id?: string
|
||||
}
|
||||
|
||||
const Searchbar: FC<Props> = ({ className, id = 'search' }) => {
|
||||
const router = useRouter()
|
||||
|
||||
useEffect(() => {
|
||||
router.prefetch('/search')
|
||||
}, [router])
|
||||
|
||||
const handleKeyUp = (e: React.KeyboardEvent<HTMLInputElement>) => {
|
||||
e.preventDefault()
|
||||
|
||||
if (e.key === 'Enter') {
|
||||
const q = e.currentTarget.value
|
||||
|
||||
router.push(
|
||||
{
|
||||
pathname: `/search`,
|
||||
query: q ? { q } : {},
|
||||
},
|
||||
undefined,
|
||||
{ shallow: true }
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
return (
|
||||
<div className={cn(s.root, className)}>
|
||||
<label className="hidden" htmlFor={id}>
|
||||
Search
|
||||
</label>
|
||||
<input
|
||||
id={id}
|
||||
className={s.input}
|
||||
placeholder="Search for products..."
|
||||
defaultValue={router.query.q}
|
||||
onKeyUp={handleKeyUp}
|
||||
/>
|
||||
<div className={s.iconContainer}>
|
||||
<svg className={s.icon} fill="currentColor" viewBox="0 0 20 20">
|
||||
<path
|
||||
fillRule="evenodd"
|
||||
clipRule="evenodd"
|
||||
d="M8 4a4 4 0 100 8 4 4 0 000-8zM2 8a6 6 0 1110.89 3.476l4.817 4.817a1 1 0 01-1.414 1.414l-4.816-4.816A6 6 0 012 8z"
|
||||
/>
|
||||
</svg>
|
||||
</div>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
|
||||
export default memo(Searchbar)
|
1
site/components/common/Searchbar/index.ts
Normal file
1
site/components/common/Searchbar/index.ts
Normal file
@@ -0,0 +1 @@
|
||||
export { default } from './Searchbar'
|
@@ -0,0 +1,20 @@
|
||||
.root {
|
||||
@apply relative h-full flex flex-col;
|
||||
}
|
||||
|
||||
.header {
|
||||
@apply sticky top-0 pl-4 py-4 pr-6
|
||||
flex items-center justify-between
|
||||
bg-accent-0 box-border w-full z-10;
|
||||
min-height: 66px;
|
||||
}
|
||||
|
||||
.container {
|
||||
@apply flex flex-col flex-1 box-border;
|
||||
}
|
||||
|
||||
@screen lg {
|
||||
.header {
|
||||
min-height: 74px;
|
||||
}
|
||||
}
|
50
site/components/common/SidebarLayout/SidebarLayout.tsx
Normal file
50
site/components/common/SidebarLayout/SidebarLayout.tsx
Normal file
@@ -0,0 +1,50 @@
|
||||
import React, { FC } from 'react'
|
||||
import { Cross, ChevronLeft } from '@components/icons'
|
||||
import { UserNav } from '@components/common'
|
||||
import cn from 'classnames'
|
||||
import s from './SidebarLayout.module.css'
|
||||
|
||||
type ComponentProps = { className?: string } & (
|
||||
| { handleClose: () => any; handleBack?: never }
|
||||
| { handleBack: () => any; handleClose?: never }
|
||||
)
|
||||
|
||||
const SidebarLayout: FC<ComponentProps> = ({
|
||||
children,
|
||||
className,
|
||||
handleClose,
|
||||
handleBack,
|
||||
}) => {
|
||||
return (
|
||||
<div className={cn(s.root, className)}>
|
||||
<header className={s.header}>
|
||||
{handleClose && (
|
||||
<button
|
||||
onClick={handleClose}
|
||||
aria-label="Close"
|
||||
className="hover:text-accent-5 transition ease-in-out duration-150 flex items-center focus:outline-none mr-6"
|
||||
>
|
||||
<Cross className="h-6 w-6 hover:text-accent-3" />
|
||||
<span className="ml-2 text-accent-7 text-sm ">Close</span>
|
||||
</button>
|
||||
)}
|
||||
{handleBack && (
|
||||
<button
|
||||
onClick={handleBack}
|
||||
aria-label="Go back"
|
||||
className="hover:text-accent-5 transition ease-in-out duration-150 flex items-center focus:outline-none"
|
||||
>
|
||||
<ChevronLeft className="h-6 w-6 hover:text-accent-3" />
|
||||
<span className="ml-2 text-accent-7 text-xs">Back</span>
|
||||
</button>
|
||||
)}
|
||||
<span className={s.nav}>
|
||||
<UserNav />
|
||||
</span>
|
||||
</header>
|
||||
<div className={s.container}>{children}</div>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
|
||||
export default SidebarLayout
|
1
site/components/common/SidebarLayout/index.ts
Normal file
1
site/components/common/SidebarLayout/index.ts
Normal file
@@ -0,0 +1 @@
|
||||
export { default } from './SidebarLayout'
|
26
site/components/common/UserNav/DropdownMenu.module.css
Normal file
26
site/components/common/UserNav/DropdownMenu.module.css
Normal file
@@ -0,0 +1,26 @@
|
||||
@screen lg {
|
||||
.dropdownMenu {
|
||||
@apply absolute top-10 border border-accent-1 shadow-lg w-56 h-auto;
|
||||
}
|
||||
}
|
||||
|
||||
.dropdownMenu {
|
||||
@apply fixed right-0 mt-2 origin-top-right outline-none bg-primary z-40 w-full h-full;
|
||||
}
|
||||
|
||||
.link {
|
||||
@apply text-primary flex cursor-pointer px-6 py-3 flex transition ease-in-out duration-150 leading-6 font-medium items-center;
|
||||
text-transform: capitalize;
|
||||
}
|
||||
|
||||
.link:hover {
|
||||
@apply bg-accent-1;
|
||||
}
|
||||
|
||||
.link.active {
|
||||
@apply font-bold bg-accent-2;
|
||||
}
|
||||
|
||||
.off {
|
||||
@apply hidden;
|
||||
}
|
125
site/components/common/UserNav/DropdownMenu.tsx
Normal file
125
site/components/common/UserNav/DropdownMenu.tsx
Normal file
@@ -0,0 +1,125 @@
|
||||
import cn from 'classnames'
|
||||
import Link from 'next/link'
|
||||
import { FC, useRef, useState, useEffect } from 'react'
|
||||
import { useTheme } from 'next-themes'
|
||||
import { useRouter } from 'next/router'
|
||||
import s from './DropdownMenu.module.css'
|
||||
import { Avatar } from '@components/common'
|
||||
import { Moon, Sun } from '@components/icons'
|
||||
import { useUI } from '@components/ui/context'
|
||||
import ClickOutside from '@lib/click-outside'
|
||||
import useLogout from '@framework/auth/use-logout'
|
||||
|
||||
import {
|
||||
disableBodyScroll,
|
||||
enableBodyScroll,
|
||||
clearAllBodyScrollLocks,
|
||||
} from 'body-scroll-lock'
|
||||
|
||||
interface DropdownMenuProps {
|
||||
open?: boolean
|
||||
}
|
||||
|
||||
const LINKS = [
|
||||
{
|
||||
name: 'My Orders',
|
||||
href: '/orders',
|
||||
},
|
||||
{
|
||||
name: 'My Profile',
|
||||
href: '/profile',
|
||||
},
|
||||
{
|
||||
name: 'My Cart',
|
||||
href: '/cart',
|
||||
},
|
||||
]
|
||||
|
||||
const DropdownMenu: FC<DropdownMenuProps> = ({ open = false }) => {
|
||||
const logout = useLogout()
|
||||
const { pathname } = useRouter()
|
||||
const { theme, setTheme } = useTheme()
|
||||
const [display, setDisplay] = useState(false)
|
||||
const { closeSidebarIfPresent } = useUI()
|
||||
const ref = useRef() as React.MutableRefObject<HTMLUListElement>
|
||||
|
||||
useEffect(() => {
|
||||
if (ref.current) {
|
||||
if (display) {
|
||||
disableBodyScroll(ref.current)
|
||||
} else {
|
||||
enableBodyScroll(ref.current)
|
||||
}
|
||||
}
|
||||
return () => {
|
||||
clearAllBodyScrollLocks()
|
||||
}
|
||||
}, [display])
|
||||
|
||||
return (
|
||||
<ClickOutside active={display} onClick={() => setDisplay(false)}>
|
||||
<div>
|
||||
<button
|
||||
className={s.avatarButton}
|
||||
onClick={() => setDisplay(!display)}
|
||||
aria-label="Menu"
|
||||
>
|
||||
<Avatar />
|
||||
</button>
|
||||
{display && (
|
||||
<ul className={s.dropdownMenu} ref={ref}>
|
||||
{LINKS.map(({ name, href }) => (
|
||||
<li key={href}>
|
||||
<div>
|
||||
<Link href={href}>
|
||||
<a
|
||||
className={cn(s.link, {
|
||||
[s.active]: pathname === href,
|
||||
})}
|
||||
onClick={() => {
|
||||
setDisplay(false)
|
||||
closeSidebarIfPresent()
|
||||
}}
|
||||
>
|
||||
{name}
|
||||
</a>
|
||||
</Link>
|
||||
</div>
|
||||
</li>
|
||||
))}
|
||||
<li>
|
||||
<a
|
||||
className={cn(s.link, 'justify-between')}
|
||||
onClick={() => {
|
||||
theme === 'dark' ? setTheme('light') : setTheme('dark')
|
||||
setDisplay(false)
|
||||
}}
|
||||
>
|
||||
<div>
|
||||
Theme: <strong>{theme}</strong>{' '}
|
||||
</div>
|
||||
<div className="ml-3">
|
||||
{theme == 'dark' ? (
|
||||
<Moon width={20} height={20} />
|
||||
) : (
|
||||
<Sun width="20" height={20} />
|
||||
)}
|
||||
</div>
|
||||
</a>
|
||||
</li>
|
||||
<li>
|
||||
<a
|
||||
className={cn(s.link, 'border-t border-accent-2 mt-4')}
|
||||
onClick={() => logout()}
|
||||
>
|
||||
Logout
|
||||
</a>
|
||||
</li>
|
||||
</ul>
|
||||
)}
|
||||
</div>
|
||||
</ClickOutside>
|
||||
)
|
||||
}
|
||||
|
||||
export default DropdownMenu
|
@@ -0,0 +1,7 @@
|
||||
.root {
|
||||
@apply px-4 sm:px-6 sm:w-full flex-1 z-20;
|
||||
}
|
||||
|
||||
.item {
|
||||
@apply text-2xl font-bold;
|
||||
}
|
@@ -0,0 +1,41 @@
|
||||
import Link from 'next/link'
|
||||
import s from './MenuSidebarView.module.css'
|
||||
import { FC } from 'react'
|
||||
import { useUI } from '@components/ui/context'
|
||||
import SidebarLayout from '@components/common/SidebarLayout'
|
||||
import { Link as LinkProps} from '.'
|
||||
|
||||
|
||||
interface MenuProps {
|
||||
links?: LinkProps[]
|
||||
}
|
||||
|
||||
const MenuSidebarView: FC<MenuProps> = (props) => {
|
||||
const { closeSidebar } = useUI()
|
||||
const handleClose = () => closeSidebar()
|
||||
|
||||
return (
|
||||
<SidebarLayout handleClose={handleClose}>
|
||||
<div className={s.root}>
|
||||
<nav>
|
||||
<ul>
|
||||
<li className={s.item}>
|
||||
<Link href="/search">
|
||||
<a>All</a>
|
||||
</Link>
|
||||
</li>
|
||||
{props.links?.map((l: any) => (
|
||||
<li key={l.href} className={s.item}>
|
||||
<Link href={l.href}>
|
||||
<a>{l.label}</a>
|
||||
</Link>
|
||||
</li>
|
||||
))}
|
||||
</ul>
|
||||
</nav>
|
||||
</div>
|
||||
</SidebarLayout>
|
||||
)
|
||||
}
|
||||
|
||||
export default MenuSidebarView
|
6
site/components/common/UserNav/MenuSidebarView/index.ts
Normal file
6
site/components/common/UserNav/MenuSidebarView/index.ts
Normal file
@@ -0,0 +1,6 @@
|
||||
export { default } from './MenuSidebarView'
|
||||
|
||||
export interface Link {
|
||||
href: string
|
||||
label: string
|
||||
}
|
45
site/components/common/UserNav/UserNav.module.css
Normal file
45
site/components/common/UserNav/UserNav.module.css
Normal file
@@ -0,0 +1,45 @@
|
||||
.root {
|
||||
@apply relative flex items-center;
|
||||
}
|
||||
|
||||
.list {
|
||||
@apply flex flex-row items-center justify-items-end h-full;
|
||||
}
|
||||
|
||||
.item {
|
||||
@apply ml-6 cursor-pointer relative transition ease-in-out duration-100 flex items-center outline-none text-primary;
|
||||
|
||||
&:hover {
|
||||
@apply text-accent-6 transition scale-110 duration-100;
|
||||
}
|
||||
|
||||
&:first-child {
|
||||
@apply ml-0;
|
||||
}
|
||||
|
||||
&:focus,
|
||||
&:active {
|
||||
@apply outline-none;
|
||||
}
|
||||
}
|
||||
|
||||
.bagCount {
|
||||
@apply border border-accent-1 bg-secondary text-secondary absolute rounded-full right-3 top-3 flex items-center justify-center font-bold text-xs;
|
||||
padding-left: 2.5px;
|
||||
padding-right: 2.5px;
|
||||
min-width: 1.25rem;
|
||||
min-height: 1.25rem;
|
||||
}
|
||||
|
||||
.avatarButton {
|
||||
@apply inline-flex justify-center rounded-full;
|
||||
}
|
||||
|
||||
.mobileMenu {
|
||||
@apply flex lg:hidden ml-6
|
||||
}
|
||||
|
||||
.avatarButton:focus,
|
||||
.mobileMenu:focus {
|
||||
@apply outline-none;
|
||||
}
|
91
site/components/common/UserNav/UserNav.tsx
Normal file
91
site/components/common/UserNav/UserNav.tsx
Normal file
@@ -0,0 +1,91 @@
|
||||
import { FC } from 'react'
|
||||
import Link from 'next/link'
|
||||
import cn from 'classnames'
|
||||
import type { LineItem } from '@commerce/types/cart'
|
||||
import useCart from '@framework/cart/use-cart'
|
||||
import useCustomer from '@framework/customer/use-customer'
|
||||
import { Avatar } from '@components/common'
|
||||
import { Heart, Bag } from '@components/icons'
|
||||
import { useUI } from '@components/ui/context'
|
||||
import Button from '@components/ui/Button'
|
||||
import DropdownMenu from './DropdownMenu'
|
||||
import s from './UserNav.module.css'
|
||||
import Menu from '@components/icons/Menu'
|
||||
|
||||
interface Props {
|
||||
className?: string
|
||||
}
|
||||
|
||||
const countItem = (count: number, item: LineItem) => count + item.quantity
|
||||
|
||||
const UserNav: FC<Props> = ({ className }) => {
|
||||
const { data } = useCart()
|
||||
const { data: customer } = useCustomer()
|
||||
const { toggleSidebar, closeSidebarIfPresent, openModal, setSidebarView } =
|
||||
useUI()
|
||||
const itemsCount = data?.lineItems.reduce(countItem, 0) ?? 0
|
||||
|
||||
return (
|
||||
<nav className={cn(s.root, className)}>
|
||||
<ul className={s.list}>
|
||||
{process.env.COMMERCE_CART_ENABLED && (
|
||||
<li className={s.item}>
|
||||
<Button
|
||||
className={s.item}
|
||||
variant="naked"
|
||||
onClick={() => {
|
||||
setSidebarView('CART_VIEW')
|
||||
toggleSidebar()
|
||||
}}
|
||||
aria-label={`Cart items: ${itemsCount}`}
|
||||
>
|
||||
<Bag />
|
||||
{itemsCount > 0 && (
|
||||
<span className={s.bagCount}>{itemsCount}</span>
|
||||
)}
|
||||
</Button>
|
||||
</li>
|
||||
)}
|
||||
{process.env.COMMERCE_WISHLIST_ENABLED && (
|
||||
<li className={s.item}>
|
||||
<Link href="/wishlist">
|
||||
<a onClick={closeSidebarIfPresent} aria-label="Wishlist">
|
||||
<Heart />
|
||||
</a>
|
||||
</Link>
|
||||
</li>
|
||||
)}
|
||||
{process.env.COMMERCE_CUSTOMERAUTH_ENABLED && (
|
||||
<li className={s.item}>
|
||||
{customer ? (
|
||||
<DropdownMenu />
|
||||
) : (
|
||||
<button
|
||||
className={s.avatarButton}
|
||||
aria-label="Menu"
|
||||
onClick={() => openModal()}
|
||||
>
|
||||
<Avatar />
|
||||
</button>
|
||||
)}
|
||||
</li>
|
||||
)}
|
||||
<li className={s.mobileMenu}>
|
||||
<Button
|
||||
className={s.item}
|
||||
variant="naked"
|
||||
onClick={() => {
|
||||
setSidebarView('MOBILEMENU_VIEW')
|
||||
toggleSidebar()
|
||||
}}
|
||||
aria-label="Menu"
|
||||
>
|
||||
<Menu />
|
||||
</Button>
|
||||
</li>
|
||||
</ul>
|
||||
</nav>
|
||||
)
|
||||
}
|
||||
|
||||
export default UserNav
|
1
site/components/common/UserNav/index.ts
Normal file
1
site/components/common/UserNav/index.ts
Normal file
@@ -0,0 +1 @@
|
||||
export { default } from './UserNav'
|
9
site/components/common/index.ts
Normal file
9
site/components/common/index.ts
Normal file
@@ -0,0 +1,9 @@
|
||||
export { default as Avatar } from './Avatar'
|
||||
export { default as FeatureBar } from './FeatureBar'
|
||||
export { default as Footer } from './Footer'
|
||||
export { default as Layout } from './Layout'
|
||||
export { default as Navbar } from './Navbar'
|
||||
export { default as Searchbar } from './Searchbar'
|
||||
export { default as UserNav } from './UserNav'
|
||||
export { default as Head } from './Head'
|
||||
export { default as I18nWidget } from './I18nWidget'
|
Reference in New Issue
Block a user