mirror of
https://github.com/vercel/commerce.git
synced 2025-07-22 20:26:49 +00:00
Merge branch 'master' into storefront-data-hooks-imports
This commit is contained in:
@@ -1,9 +1,7 @@
|
||||
import { FC, useEffect, useState, useCallback } from 'react'
|
||||
import { validate } from 'email-validator'
|
||||
import { Info } from '@components/icons'
|
||||
import { useUI } from '@components/ui/context'
|
||||
import { Logo, Button, Input } from '@components/ui'
|
||||
import useSignup from '@bigcommerce/storefront-data-hooks/use-signup'
|
||||
|
||||
interface Props {}
|
||||
|
||||
@@ -15,27 +13,15 @@ const ForgotPassword: FC<Props> = () => {
|
||||
const [dirty, setDirty] = useState(false)
|
||||
const [disabled, setDisabled] = useState(false)
|
||||
|
||||
const signup = useSignup()
|
||||
const { setModalView, closeModal } = useUI()
|
||||
|
||||
const handleSignup = async () => {
|
||||
const handleResetPassword = async (e: React.SyntheticEvent<EventTarget>) => {
|
||||
e.preventDefault()
|
||||
|
||||
if (!dirty && !disabled) {
|
||||
setDirty(true)
|
||||
handleValidation()
|
||||
}
|
||||
|
||||
// try {
|
||||
// setLoading(true)
|
||||
// setMessage('')
|
||||
// await signup({
|
||||
// email,
|
||||
// })
|
||||
// setLoading(false)
|
||||
// closeModal()
|
||||
// } catch ({ errors }) {
|
||||
// setMessage(errors[0].message)
|
||||
// setLoading(false)
|
||||
// }
|
||||
}
|
||||
|
||||
const handleValidation = useCallback(() => {
|
||||
@@ -50,7 +36,10 @@ const ForgotPassword: FC<Props> = () => {
|
||||
}, [handleValidation])
|
||||
|
||||
return (
|
||||
<div className="w-80 flex flex-col justify-between p-3">
|
||||
<form
|
||||
onSubmit={handleResetPassword}
|
||||
className="w-80 flex flex-col justify-between p-3"
|
||||
>
|
||||
<div className="flex justify-center pb-12 ">
|
||||
<Logo width="64px" height="64px" />
|
||||
</div>
|
||||
@@ -63,7 +52,7 @@ const ForgotPassword: FC<Props> = () => {
|
||||
<div className="pt-2 w-full flex flex-col">
|
||||
<Button
|
||||
variant="slim"
|
||||
onClick={() => handleSignup()}
|
||||
type="submit"
|
||||
loading={loading}
|
||||
disabled={disabled}
|
||||
>
|
||||
@@ -82,7 +71,7 @@ const ForgotPassword: FC<Props> = () => {
|
||||
</a>
|
||||
</span>
|
||||
</div>
|
||||
</div>
|
||||
</form>
|
||||
)
|
||||
}
|
||||
|
||||
|
@@ -18,7 +18,9 @@ const LoginView: FC<Props> = () => {
|
||||
|
||||
const login = useLogin()
|
||||
|
||||
const handleLogin = async () => {
|
||||
const handleLogin = async (e: React.SyntheticEvent<EventTarget>) => {
|
||||
e.preventDefault()
|
||||
|
||||
if (!dirty && !disabled) {
|
||||
setDirty(true)
|
||||
handleValidation()
|
||||
@@ -54,7 +56,10 @@ const LoginView: FC<Props> = () => {
|
||||
}, [handleValidation])
|
||||
|
||||
return (
|
||||
<div className="w-80 flex flex-col justify-between p-3">
|
||||
<form
|
||||
onSubmit={handleLogin}
|
||||
className="w-80 flex flex-col justify-between p-3"
|
||||
>
|
||||
<div className="flex justify-center pb-12 ">
|
||||
<Logo width="64px" height="64px" />
|
||||
</div>
|
||||
@@ -75,7 +80,7 @@ const LoginView: FC<Props> = () => {
|
||||
|
||||
<Button
|
||||
variant="slim"
|
||||
onClick={() => handleLogin()}
|
||||
type="submit"
|
||||
loading={loading}
|
||||
disabled={disabled}
|
||||
>
|
||||
@@ -92,7 +97,7 @@ const LoginView: FC<Props> = () => {
|
||||
</a>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</form>
|
||||
)
|
||||
}
|
||||
|
||||
|
@@ -21,7 +21,9 @@ const SignUpView: FC<Props> = () => {
|
||||
const signup = useSignup()
|
||||
const { setModalView, closeModal } = useUI()
|
||||
|
||||
const handleSignup = async () => {
|
||||
const handleSignup = async (e: React.SyntheticEvent<EventTarget>) => {
|
||||
e.preventDefault()
|
||||
|
||||
if (!dirty && !disabled) {
|
||||
setDirty(true)
|
||||
handleValidation()
|
||||
@@ -59,7 +61,10 @@ const SignUpView: FC<Props> = () => {
|
||||
}, [handleValidation])
|
||||
|
||||
return (
|
||||
<div className="w-80 flex flex-col justify-between p-3">
|
||||
<form
|
||||
onSubmit={handleSignup}
|
||||
className="w-80 flex flex-col justify-between p-3"
|
||||
>
|
||||
<div className="flex justify-center pb-12 ">
|
||||
<Logo width="64px" height="64px" />
|
||||
</div>
|
||||
@@ -83,7 +88,7 @@ const SignUpView: FC<Props> = () => {
|
||||
<div className="pt-2 w-full flex flex-col">
|
||||
<Button
|
||||
variant="slim"
|
||||
onClick={() => handleSignup()}
|
||||
type="submit"
|
||||
loading={loading}
|
||||
disabled={disabled}
|
||||
>
|
||||
@@ -102,7 +107,7 @@ const SignUpView: FC<Props> = () => {
|
||||
</a>
|
||||
</span>
|
||||
</div>
|
||||
</div>
|
||||
</form>
|
||||
)
|
||||
}
|
||||
|
||||
|
@@ -2,7 +2,7 @@ import { FC } from 'react'
|
||||
import cn from 'classnames'
|
||||
import { UserNav } from '@components/core'
|
||||
import { Button } from '@components/ui'
|
||||
import { ArrowLeft, Bag, Cross, Check } from '@components/icons'
|
||||
import { Bag, Cross, Check } from '@components/icons'
|
||||
import { useUI } from '@components/ui/context'
|
||||
import useCart from '@bigcommerce/storefront-data-hooks/cart/use-cart'
|
||||
import usePrice from '@bigcommerce/storefront-data-hooks/use-price'
|
||||
@@ -47,11 +47,11 @@ const CartSidebarView: FC = () => {
|
||||
aria-label="Close panel"
|
||||
className="hover:text-gray-500 transition ease-in-out duration-150"
|
||||
>
|
||||
<ArrowLeft className="h-6 w-6" />
|
||||
<Cross className="h-6 w-6" />
|
||||
</button>
|
||||
</div>
|
||||
<div className="space-y-1">
|
||||
<UserNav />
|
||||
<UserNav className="" />
|
||||
</div>
|
||||
</div>
|
||||
</header>
|
||||
|
9
components/core/Footer/Footer.module.css
Normal file
9
components/core/Footer/Footer.module.css
Normal file
@@ -0,0 +1,9 @@
|
||||
.link {
|
||||
& > svg {
|
||||
@apply transform duration-75 ease-linear;
|
||||
}
|
||||
|
||||
&:hover > svg {
|
||||
@apply scale-110;
|
||||
}
|
||||
}
|
@@ -7,7 +7,7 @@ import getSlug from '@utils/get-slug'
|
||||
import { Github } from '@components/icons'
|
||||
import { Logo, Container } from '@components/ui'
|
||||
import { I18nWidget } from '@components/core'
|
||||
|
||||
import s from './Footer.module.css'
|
||||
interface Props {
|
||||
className?: string
|
||||
children?: any
|
||||
@@ -83,7 +83,9 @@ const Footer: FC<Props> = ({ className, pages }) => {
|
||||
</div>
|
||||
<div className="col-span-1 lg:col-span-6 flex items-start lg:justify-end text-primary">
|
||||
<div className="flex space-x-6 items-center h-10">
|
||||
<Github />
|
||||
<a href="https://github.com/vercel/commerce" className={s.link}>
|
||||
<Github />
|
||||
</a>
|
||||
<I18nWidget />
|
||||
</div>
|
||||
</div>
|
||||
|
@@ -6,21 +6,52 @@ import { Menu } from '@headlessui/react'
|
||||
import { DoubleChevron } from '@components/icons'
|
||||
import s from './I18nWidget.module.css'
|
||||
|
||||
const LOCALES_MAP: Record<string, string> = {
|
||||
es: 'Español',
|
||||
'en-US': 'English',
|
||||
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 { locale, locales, defaultLocale = 'en-US' } = useRouter()
|
||||
const {
|
||||
locale,
|
||||
locales,
|
||||
defaultLocale = 'en-US',
|
||||
asPath: currentPath,
|
||||
} = useRouter()
|
||||
const options = locales?.filter((val) => val !== locale)
|
||||
|
||||
const currentLocale = locale || defaultLocale
|
||||
|
||||
return (
|
||||
<nav className={s.root}>
|
||||
<Menu>
|
||||
<Menu.Button className={s.button} aria-label="Language selector">
|
||||
<img className="mr-2" src="/flag-us.png" alt="US Flag" />
|
||||
<span className="mr-2">{LOCALES_MAP[locale || defaultLocale]}</span>
|
||||
<img
|
||||
className="block mr-2 w-5"
|
||||
src={`/${LOCALES_MAP[currentLocale].img.filename}`}
|
||||
alt={LOCALES_MAP[currentLocale].img.alt}
|
||||
/>
|
||||
<span className="mr-2">{LOCALES_MAP[currentLocale].name}</span>
|
||||
{options && (
|
||||
<span>
|
||||
<DoubleChevron />
|
||||
@@ -33,9 +64,9 @@ const I18nWidget: FC = () => {
|
||||
{options.map((locale) => (
|
||||
<Menu.Item key={locale}>
|
||||
{({ active }) => (
|
||||
<Link href="/" locale={locale}>
|
||||
<Link href={currentPath} locale={locale}>
|
||||
<a className={cn(s.item, { [s.active]: active })}>
|
||||
{LOCALES_MAP[locale]}
|
||||
{LOCALES_MAP[locale].name}
|
||||
</a>
|
||||
</Link>
|
||||
)}
|
||||
|
@@ -4,6 +4,7 @@ import { useTheme } from 'next-themes'
|
||||
import cn from 'classnames'
|
||||
import s from './DropdownMenu.module.css'
|
||||
import { Moon, Sun } from '@components/icons'
|
||||
import { useUI } from '@components/ui/context'
|
||||
import { Menu, Transition } from '@headlessui/react'
|
||||
import useLogout from '@bigcommerce/storefront-data-hooks/use-logout'
|
||||
import { useRouter } from 'next/router'
|
||||
@@ -32,6 +33,8 @@ const DropdownMenu: FC<DropdownMenuProps> = ({ open = false }) => {
|
||||
const logout = useLogout()
|
||||
const { pathname } = useRouter()
|
||||
|
||||
const { closeSidebarIfPresent } = useUI()
|
||||
|
||||
return (
|
||||
<Transition
|
||||
show={open}
|
||||
@@ -51,6 +54,7 @@ const DropdownMenu: FC<DropdownMenuProps> = ({ open = false }) => {
|
||||
className={cn(s.link, {
|
||||
[s.active]: pathname === href,
|
||||
})}
|
||||
onClick={closeSidebarIfPresent}
|
||||
>
|
||||
{name}
|
||||
</a>
|
||||
|
@@ -21,21 +21,18 @@ const UserNav: FC<Props> = ({ className, children, ...props }) => {
|
||||
const { data } = useCart()
|
||||
const { data: customer } = useCustomer()
|
||||
|
||||
const { openSidebar, closeSidebar, displaySidebar, openModal } = useUI()
|
||||
const { toggleSidebar, closeSidebarIfPresent, openModal } = useUI()
|
||||
const itemsCount = Object.values(data?.line_items ?? {}).reduce(countItems, 0)
|
||||
return (
|
||||
<nav className={cn(s.root, className)}>
|
||||
<div className={s.mainContainer}>
|
||||
<ul className={s.list}>
|
||||
<li
|
||||
className={s.item}
|
||||
onClick={(e) => (displaySidebar ? closeSidebar() : openSidebar())}
|
||||
>
|
||||
<li className={s.item} onClick={toggleSidebar}>
|
||||
<Bag />
|
||||
{itemsCount > 0 && <span className={s.bagCount}>{itemsCount}</span>}
|
||||
</li>
|
||||
<Link href="/wishlist">
|
||||
<li className={s.item}>
|
||||
<li className={s.item} onClick={closeSidebarIfPresent}>
|
||||
<Heart />
|
||||
</li>
|
||||
</Link>
|
||||
|
@@ -31,56 +31,56 @@ const ProductCard: FC<Props> = ({
|
||||
currencyCode: p.prices?.price?.currencyCode!,
|
||||
})
|
||||
|
||||
if (variant === 'slim') {
|
||||
return (
|
||||
<div className="relative overflow-hidden box-border">
|
||||
<div className="absolute inset-0 flex items-center justify-end mr-8 z-20">
|
||||
<span className="bg-black text-white inline-block p-3 font-bold text-xl break-words">
|
||||
{p.name}
|
||||
</span>
|
||||
</div>
|
||||
<EnhancedImage
|
||||
src={p.images.edges?.[0]?.node.urlOriginal!}
|
||||
alt={p.name}
|
||||
width={imgWidth}
|
||||
height={imgHeight}
|
||||
priority={priority}
|
||||
quality="90"
|
||||
/>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
|
||||
return (
|
||||
<Link href={`/product${p.path}`}>
|
||||
<a
|
||||
className={cn(s.root, { [s.simple]: variant === 'simple' }, className)}
|
||||
>
|
||||
<div className={s.squareBg} />
|
||||
<div className="flex flex-row justify-between box-border w-full z-20 absolute">
|
||||
<div className="absolute top-0 left-0 pr-16 max-w-full">
|
||||
<h3 className={s.productTitle}>
|
||||
<span>{p.name}</span>
|
||||
</h3>
|
||||
<span className={s.productPrice}>{price}</span>
|
||||
{variant === 'slim' ? (
|
||||
<div className="relative overflow-hidden box-border">
|
||||
<div className="absolute inset-0 flex items-center justify-end mr-8 z-20">
|
||||
<span className="bg-black text-white inline-block p-3 font-bold text-xl break-words">
|
||||
{p.name}
|
||||
</span>
|
||||
</div>
|
||||
<EnhancedImage
|
||||
src={p.images.edges?.[0]?.node.urlOriginal!}
|
||||
alt={p.name}
|
||||
width={imgWidth}
|
||||
height={imgHeight}
|
||||
priority={priority}
|
||||
quality="90"
|
||||
/>
|
||||
</div>
|
||||
<WishlistButton
|
||||
className={s.wishlistButton}
|
||||
productId={p.entityId}
|
||||
variant={p.variants.edges?.[0]!}
|
||||
/>
|
||||
</div>
|
||||
<div className={s.imageContainer}>
|
||||
<EnhancedImage
|
||||
alt={p.name}
|
||||
className={cn('w-full object-cover', s['product-image'])}
|
||||
src={src}
|
||||
width={imgWidth}
|
||||
height={imgHeight}
|
||||
priority={priority}
|
||||
quality="90"
|
||||
/>
|
||||
</div>
|
||||
) : (
|
||||
<>
|
||||
<div className={s.squareBg} />
|
||||
<div className="flex flex-row justify-between box-border w-full z-20 absolute">
|
||||
<div className="absolute top-0 left-0 pr-16 max-w-full">
|
||||
<h3 className={s.productTitle}>
|
||||
<span>{p.name}</span>
|
||||
</h3>
|
||||
<span className={s.productPrice}>{price}</span>
|
||||
</div>
|
||||
<WishlistButton
|
||||
className={s.wishlistButton}
|
||||
productId={p.entityId}
|
||||
variant={p.variants.edges?.[0]!}
|
||||
/>
|
||||
</div>
|
||||
<div className={s.imageContainer}>
|
||||
<EnhancedImage
|
||||
alt={p.name}
|
||||
className={cn('w-full object-cover', s['product-image'])}
|
||||
src={src}
|
||||
width={imgWidth}
|
||||
height={imgHeight}
|
||||
priority={priority}
|
||||
quality="90"
|
||||
/>
|
||||
</div>
|
||||
</>
|
||||
)}
|
||||
</a>
|
||||
</Link>
|
||||
)
|
||||
|
@@ -13,6 +13,7 @@ import { HTMLContent } from '@components/core'
|
||||
import useAddItem from '@bigcommerce/storefront-data-hooks/cart/use-add-item'
|
||||
import type { ProductNode } from '@bigcommerce/storefront-data-hooks/api/operations/get-product'
|
||||
import { getProductOptions } from '../helpers'
|
||||
import WishlistButton from '@components/wishlist/WishlistButton'
|
||||
|
||||
interface Props {
|
||||
className?: string
|
||||
@@ -143,9 +144,11 @@ const ProductView: FC<Props> = ({ product, className }) => {
|
||||
</div>
|
||||
|
||||
{/* TODO make it work */}
|
||||
<div className={s.wishlistButton}>
|
||||
<Heart />
|
||||
</div>
|
||||
<WishlistButton
|
||||
className={s.wishlistButton}
|
||||
productId={product.entityId}
|
||||
variant={product.variants.edges?.[0]!}
|
||||
/>
|
||||
</div>
|
||||
</Container>
|
||||
)
|
||||
|
@@ -131,6 +131,12 @@ export const UIProvider: FC = (props) => {
|
||||
|
||||
const openSidebar = () => dispatch({ type: 'OPEN_SIDEBAR' })
|
||||
const closeSidebar = () => dispatch({ type: 'CLOSE_SIDEBAR' })
|
||||
const toggleSidebar = () =>
|
||||
state.displaySidebar
|
||||
? dispatch({ type: 'CLOSE_SIDEBAR' })
|
||||
: dispatch({ type: 'OPEN_SIDEBAR' })
|
||||
const closeSidebarIfPresent = () =>
|
||||
state.displaySidebar && dispatch({ type: 'CLOSE_SIDEBAR' })
|
||||
|
||||
const openDropdown = () => dispatch({ type: 'OPEN_DROPDOWN' })
|
||||
const closeDropdown = () => dispatch({ type: 'CLOSE_DROPDOWN' })
|
||||
@@ -149,6 +155,8 @@ export const UIProvider: FC = (props) => {
|
||||
...state,
|
||||
openSidebar,
|
||||
closeSidebar,
|
||||
toggleSidebar,
|
||||
closeSidebarIfPresent,
|
||||
openDropdown,
|
||||
closeDropdown,
|
||||
openModal,
|
||||
|
@@ -62,9 +62,10 @@ const WishlistButton: FC<Props> = ({
|
||||
|
||||
return (
|
||||
<button
|
||||
{...props}
|
||||
aria-label="Add to wishlist"
|
||||
className={cn({ 'opacity-50': loading }, className)}
|
||||
onClick={handleWishlistChange}
|
||||
{...props}
|
||||
>
|
||||
<Heart fill={itemInWishlist ? 'var(--pink)' : 'none'} />
|
||||
</button>
|
||||
|
Reference in New Issue
Block a user