This commit is contained in:
Luis Alvarez
2020-10-25 13:42:11 -05:00
29 changed files with 230 additions and 224 deletions

View File

@@ -10,8 +10,9 @@
.productImage {
position: absolute;
top: 0;
left: -10px;
top: 15px;
transform: scale(1.9);
width: 100%;
height: 100%;
left: 30% !important;
top: 30% !important;
}

View File

@@ -1,10 +1,11 @@
import { ChangeEvent, useEffect, useState } from 'react'
import s from './CartItem.module.css'
import Image from 'next/image'
import Link from 'next/link'
import { ChangeEvent, useEffect, useState } from 'react'
import { Trash, Plus, Minus } from '@components/icon'
import usePrice from '@lib/bigcommerce/use-price'
import useUpdateItem from '@lib/bigcommerce/cart/use-update-item'
import useRemoveItem from '@lib/bigcommerce/cart/use-remove-item'
import s from './CartItem.module.css'
const CartItem = ({
item,
@@ -55,18 +56,26 @@ const CartItem = ({
}, [item.quantity])
return (
<li className="flex flex-row space-x-8 py-6">
<div className="w-12 h-12 bg-violet relative overflow-hidden">
<li className="flex flex-row space-x-8 py-8">
<div className="w-16 h-16 bg-violet relative overflow-hidden">
<Image
className={s.productImage}
src={item.image_url}
width={60}
height={60}
width={150}
height={150}
alt="Product Image"
// The cart item image is already optimized and very small in size
unoptimized
/>
</div>
<div className="flex-1 flex flex-col justify-between text-base">
<span className="font-bold mb-3">{item.name}</span>
<div className="flex-1 flex flex-col text-base">
{/** TODO: Replace this. No `path` found at Cart */}
<Link href={`/product/${item.url.split('/')[3]}`}>
<span className="font-bold mb-5 text-lg cursor-pointer">
{item.name}
</span>
</Link>
<div className="flex items-center">
<button type="button" onClick={() => increaseQuantity(-1)}>
<Minus width={18} height={18} />

View File

@@ -0,0 +1,42 @@
import { FC } from 'react'
import { useInView } from 'react-intersection-observer'
import Image from 'next/image'
type Props = Omit<
JSX.IntrinsicElements['img'],
'src' | 'srcSet' | 'ref' | 'width' | 'height' | 'loading'
> & {
src: string
quality?: string
priority?: boolean
loading?: readonly ['lazy', 'eager', undefined]
unoptimized?: boolean
} & (
| {
width: number | string
height: number | string
unsized?: false
}
| {
width?: number | string
height?: number | string
unsized: true
}
)
const EnhancedImage: FC<Props & JSX.IntrinsicElements['img']> = ({
...props
}) => {
const [ref, inView] = useInView({
triggerOnce: true,
rootMargin: '220px 0px',
})
return (
<div ref={ref}>
<Image {...props} />
</div>
)
}
export default EnhancedImage

View File

@@ -0,0 +1 @@
export { default } from './EnhancedImage'

View File

@@ -38,21 +38,21 @@ const Footer: FC<Props> = ({ className, pages }) => {
<ul className="flex flex-initial flex-col md:flex-1">
<li className="py-3 md:py-0 md:pb-4">
<Link href="/">
<a className="text-gray-400 hover:text-white transition ease-in-out duration-150">
<a className="text-accent-3 hover:text-white transition ease-in-out duration-150">
Home
</a>
</Link>
</li>
<li className="py-3 md:py-0 md:pb-4">
<Link href="/">
<a className="text-gray-400 hover:text-white transition ease-in-out duration-150">
<a className="text-accent-3 hover:text-white transition ease-in-out duration-150">
Careers
</a>
</Link>
</li>
<li className="py-3 md:py-0 md:pb-4">
<Link href="/blog">
<a className="text-gray-400 hover:text-white transition ease-in-out duration-150">
<a className="text-accent-3 hover:text-white transition ease-in-out duration-150">
Blog
</a>
</Link>
@@ -60,7 +60,7 @@ const Footer: FC<Props> = ({ className, pages }) => {
{sitePages.map((page) => (
<li key={page.url} className="py-3 md:py-0 md:pb-4">
<Link href={page.url!}>
<a className="text-gray-400 hover:text-white transition ease-in-out duration-150">
<a className="text-accent-3 hover:text-white transition ease-in-out duration-150">
{page.name}
</a>
</Link>
@@ -73,7 +73,7 @@ const Footer: FC<Props> = ({ className, pages }) => {
{legalPages.map((page) => (
<li key={page.url} className="py-3 md:py-0 md:pb-4">
<Link href={page.url!}>
<a className="text-gray-400 hover:text-white transition ease-in-out duration-150">
<a className="text-accent-3 hover:text-white transition ease-in-out duration-150">
{page.name}
</a>
</Link>
@@ -92,9 +92,9 @@ const Footer: FC<Props> = ({ className, pages }) => {
<div>
<span>&copy; 2020 ACME, Inc. All rights reserved.</span>
</div>
<div className="flex items-center text-accents-4">
<span>Crafted by</span>
<a href="https://vercel.com">
<div className="flex items-center">
<span className="text-accent-3">Crafted by</span>
<a href="https://vercel.com" aria-label="Vercel.com Link">
<img
src="/vercel.png"
alt="Vercel.com Logo"

View File

@@ -8,8 +8,8 @@ const I18nWidget: FC = () => {
return (
<nav className={s.root}>
<Menu>
<Menu.Button className={s.button}>
<img className="" src="/flag-us.png" />
<Menu.Button className={s.button} aria-label="Language selector">
<img className="" src="/flag-us.png" alt="US Flag" />
<span>English</span>
<span className="">
<DoubleChevron />

View File

@@ -15,7 +15,7 @@ const Navbar: FC<Props> = ({ className }) => {
<div className="flex justify-between align-center flex-row py-4 md:py-6 relative">
<div className="flex flex-1 items-center">
<Link href="/">
<a className="cursor-pointer">
<a className="cursor-pointer" aria-label="Logo">
<Logo />
</a>
</Link>
@@ -42,7 +42,7 @@ const Navbar: FC<Props> = ({ className }) => {
</div>
<div className="flex pb-4 lg:px-6 lg:hidden">
<Searchbar />
<Searchbar id="mobileSearch" />
</div>
</div>
)

View File

@@ -5,9 +5,10 @@ import { useRouter } from 'next/router'
interface Props {
className?: string
id?: string
}
const Searchbar: FC<Props> = ({ className }) => {
const Searchbar: FC<Props> = ({ className, id = 'search' }) => {
const router = useRouter()
useEffect(() => {
@@ -21,27 +22,30 @@ const Searchbar: FC<Props> = ({ className }) => {
className
)}
>
<input
className={s.input}
placeholder="Search for products..."
defaultValue={router.query.q}
onKeyUp={(e) => {
e.preventDefault()
<label htmlFor={id}>
<input
id={id}
className={s.input}
placeholder="Search for products..."
defaultValue={router.query.q}
onKeyUp={(e) => {
e.preventDefault()
if (e.key === 'Enter') {
const q = e.currentTarget.value
if (e.key === 'Enter') {
const q = e.currentTarget.value
router.push(
{
pathname: `/search`,
query: q ? { q } : {},
},
undefined,
{ shallow: true }
)
}
}}
/>
router.push(
{
pathname: `/search`,
query: q ? { q } : {},
},
undefined,
{ shallow: true }
)
}
}}
/>
</label>
<div className={s.iconContainer}>
<svg className={s.icon} fill="currentColor" viewBox="0 0 20 20">
<path

View File

@@ -1,6 +1,6 @@
import React, { FC } from 'react'
import { Switch } from '@headlessui/react'
import { HiSun, HiMoon } from 'react-icons/hi'
import { Moon, Sun } from '@components/icon'
interface Props {
className?: string
checked: boolean
@@ -35,7 +35,7 @@ const Toggle: FC<Props> = ({ className, checked, onChange }) => {
: 'opacity-100 ease-in duration-150'
} absolute inset-0 h-full w-full flex items-center justify-center transition-opacity`}
>
<HiSun className="h-3 w-3 text-gray-400" />
<Sun className="h-3 w-3 text-accent-3" />
</span>
<span
className={`${
@@ -44,7 +44,7 @@ const Toggle: FC<Props> = ({ className, checked, onChange }) => {
: 'opacity-0 ease-out duration-150'
} opacity-0 ease-out duration-150 absolute inset-0 h-full w-full flex items-center justify-center transition-opacity`}
>
<HiMoon className="h-3 w-3 text-yellow-400" />
<Moon className="h-3 w-3 text-yellow-400" />
</span>
</span>
</span>

View File

@@ -42,7 +42,7 @@ const UserNav: FC<Props> = ({ className, children, ...props }) => {
<Menu>
{({ open }) => (
<>
<Menu.Button className={s.avatarButton}>
<Menu.Button className={s.avatarButton} aria-label="Menu">
<Avatar />
</Menu.Button>
<DropdownMenu open={open} />

View File

@@ -9,3 +9,4 @@ export { default as Toggle } from './Toggle'
export { default as Head } from './Head'
export { default as HTMLContent } from './HTMLContent'
export { default as I18nWidget } from './I18nWidget'
export { default as EnhancedImage } from './EnhancedImage'

View File

@@ -1,10 +1,10 @@
import { FC, ReactNode, Component } from 'react'
import React, { FC, ReactNode, Component } from 'react'
import cn from 'classnames'
import Image from 'next/image'
import Link from 'next/link'
import type { ProductNode } from '@lib/bigcommerce/api/operations/get-all-products'
import { Heart } from '@components/icon'
import s from './ProductCard.module.css'
import Link from 'next/link'
import { Heart } from '@components/icon'
import { EnhancedImage } from '@components/core'
import type { ProductNode } from '@lib/bigcommerce/api/operations/get-all-products'
interface Props {
className?: string
@@ -24,7 +24,7 @@ const ProductCard: FC<Props> = ({
imgHeight,
priority,
}) => {
const src = p.images.edges?.[0]?.node.urlOriginal!
const src = p.images.edges?.[0]?.node.urlLarge!
if (variant === 'slim') {
return (
@@ -34,8 +34,9 @@ const ProductCard: FC<Props> = ({
{p.name}
</span>
</div>
<Image
src={src}
<EnhancedImage
src={p.images.edges?.[0]?.node.urlSmall!}
alt={p.name}
width={imgWidth}
height={imgHeight}
priority={priority}
@@ -63,7 +64,8 @@ const ProductCard: FC<Props> = ({
</div>
</div>
<div className={cn(s.imageContainer)}>
<Image
<EnhancedImage
alt={p.name}
className={cn('w-full object-cover', s['product-image'])}
src={src}
width={imgWidth}

View File

@@ -19,10 +19,15 @@ const ProductSlider: FC = ({ children }) => {
return (
<div className={s.root}>
<button className={cn(s.leftControl, s.control)} onClick={slider?.prev} />
<button
className={cn(s.leftControl, s.control)}
onClick={slider?.prev}
aria-label="Previous Product Image"
/>
<button
className={cn(s.rightControl, s.control)}
onClick={slider?.next}
aria-label="Next Product Image"
/>
<div
ref={ref}
@@ -50,6 +55,7 @@ const ProductSlider: FC = ({ children }) => {
{[...Array(slider.details().size).keys()].map((idx) => {
return (
<button
aria-label="Position indicator"
key={idx}
className={cn(s.positionIndicator, {
[s.positionIndicatorActive]: currentSlide === idx,

View File

@@ -53,10 +53,10 @@
}
.sidebar {
@apply flex flex-col col-span-1 mx-auto max-w-8xl px-6 w-full;
@apply flex flex-col col-span-1 mx-auto max-w-8xl px-6 w-full h-full;
@screen lg {
@apply col-span-6 pt-20;
@apply col-span-6 py-24 justify-between;
}
}

View File

@@ -6,8 +6,9 @@ import { NextSeo } from 'next-seo'
import s from './ProductView.module.css'
import { Heart } from '@components/icon'
import { useUI } from '@components/ui/context'
import { Button, Container } from '@components/ui'
import { Swatch, ProductSlider } from '@components/product'
import { Button, Container } from '@components/ui'
import { HTMLContent } from '@components/core'
import useAddItem from '@lib/bigcommerce/cart/use-add-item'
import type { ProductNode } from '@lib/bigcommerce/api/operations/get-product'
@@ -79,6 +80,7 @@ const ProductView: FC<Props> = ({ product, className }) => {
{product.images.edges?.map((image, i) => (
<div key={image?.node.urlXL} className={s.imageContainer}>
<Image
alt={product.name}
className={s.img}
src={image?.node.urlXL!}
width={1050}
@@ -110,7 +112,6 @@ const ProductView: FC<Props> = ({ product, className }) => {
label={v.label}
onClick={() => {
setChoices((choices) => {
console.log(choices)
return {
...choices,
[opt.displayName]: v.label,
@@ -123,21 +124,22 @@ const ProductView: FC<Props> = ({ product, className }) => {
</div>
</div>
))}
<div className="pb-12">
<div
className="pb-14 break-words w-full"
dangerouslySetInnerHTML={{ __html: product.description }}
/>
<Button
type="button"
className={s.button}
onClick={addToCart}
loading={loading}
>
Add to Cart
</Button>
<div className="pb-14 break-words w-full max-w-xl">
<HTMLContent html={product.description} />
</div>
</section>
<div>
<Button
aria-label="Add to Cart"
type="button"
className={s.button}
onClick={addToCart}
loading={loading}
>
Add to Cart
</Button>
</div>
</div>
{/* TODO make it work */}

View File

@@ -39,6 +39,7 @@ const Swatch: FC<Props & ButtonProps> = ({
<Button
className={rootClassName}
style={color ? { backgroundColor: color } : {}}
aria-label="Variant Swatch"
{...props}
>
{variant === 'color' && active && (

View File

@@ -1,7 +1,6 @@
import type { ProductNode } from '@lib/bigcommerce/api/operations/get-product'
export function getProductOptions(product: ProductNode) {
// console.log(product)
const options = product.productOptions.edges?.map(({ node }: any) => ({
displayName: node.displayName.toLowerCase(),
values: node.values.edges?.map(({ node }: any) => node),

View File

@@ -2,7 +2,7 @@ import React, { FC } from 'react'
import { Container } from '@components/ui'
import { RightArrow } from '@components/icon'
import s from './Hero.module.css'
import Link from 'next/link'
interface Props {
className?: string
headline: string
@@ -21,10 +21,12 @@ const Hero: FC<Props> = ({ headline, description }) => {
<p className="mt-5 text-xl leading-7 text-accent-2 text-white">
{description}
</p>
<a className="text-white pt-3 font-bold hover:underline flex flex-row cursor-pointer w-max-content">
<span>Read it here</span>
<RightArrow width="20" heigh="20" className="ml-1" />
</a>
<Link href="/blog">
<a className="text-white pt-3 font-bold hover:underline flex flex-row cursor-pointer w-max-content">
Read it here
<RightArrow width="20" heigh="20" className="ml-1" />
</a>
</Link>
</div>
</div>
</Container>

View File

@@ -9,3 +9,7 @@
.pageHeading {
@apply pt-1 pb-4 text-2xl leading-7 font-bold text-base tracking-wide;
}
.sectionHeading {
@apply pt-1 pb-4 text-base leading-7 text-base tracking-wide uppercase border-b border-accents-2;
}

View File

@@ -13,7 +13,7 @@ interface Props {
children: React.ReactNode | any
}
type Variant = 'heading' | 'body' | 'pageHeading'
type Variant = 'heading' | 'body' | 'pageHeading' | 'sectionHeading'
const Text: FunctionComponent<Props> = ({
style,
@@ -27,6 +27,7 @@ const Text: FunctionComponent<Props> = ({
body: 'p',
heading: 'h1',
pageHeading: 'h1',
sectionHeading: 'h2',
}
const Component:
@@ -43,6 +44,7 @@ const Text: FunctionComponent<Props> = ({
[s.body]: variant === 'body',
[s.heading]: variant === 'heading',
[s.pageHeading]: variant === 'pageHeading',
[s.sectionHeading]: variant === 'sectionHeading',
},
className
)}