mirror of
https://github.com/vercel/commerce.git
synced 2025-05-19 07:56:59 +00:00
conversions
This commit is contained in:
parent
2dd8d59ae7
commit
914d75dc8d
142
components/agility-modules/Cart.tsx
Normal file
142
components/agility-modules/Cart.tsx
Normal file
@ -0,0 +1,142 @@
|
|||||||
|
import React, { FC } from 'react'
|
||||||
|
|
||||||
|
import { ProductCard } from '@components/product'
|
||||||
|
import { Grid, Marquee, Hero } from '@components/ui'
|
||||||
|
import useCart from '@framework/cart/use-cart'
|
||||||
|
import usePrice from '@framework/use-price'
|
||||||
|
import { Button } from '@components/ui'
|
||||||
|
import { Bag, Cross, Check } from '@components/icons'
|
||||||
|
import { CartItem } from '@components/cart'
|
||||||
|
import { Text } from '@components/ui'
|
||||||
|
|
||||||
|
interface Fields {
|
||||||
|
}
|
||||||
|
|
||||||
|
interface Props {
|
||||||
|
fields: Fields,
|
||||||
|
customData: any
|
||||||
|
}
|
||||||
|
|
||||||
|
const Cart: FC<Props> = ({ fields, customData }) => {
|
||||||
|
const { data, isEmpty } = useCart()
|
||||||
|
const { price: subTotal } = usePrice(
|
||||||
|
data && {
|
||||||
|
amount: data.base_amount,
|
||||||
|
currencyCode: data.currency.code,
|
||||||
|
}
|
||||||
|
)
|
||||||
|
const { price: total } = usePrice(
|
||||||
|
data && {
|
||||||
|
amount: data.cart_amount,
|
||||||
|
currencyCode: data.currency.code,
|
||||||
|
}
|
||||||
|
)
|
||||||
|
|
||||||
|
const items = data?.line_items.physical_items ?? []
|
||||||
|
|
||||||
|
const error = null
|
||||||
|
const success = null
|
||||||
|
|
||||||
|
return (
|
||||||
|
<div className="grid lg:grid-cols-12">
|
||||||
|
<div className="lg:col-span-8">
|
||||||
|
{isEmpty ? (
|
||||||
|
<div className="flex-1 px-12 py-24 flex flex-col justify-center items-center ">
|
||||||
|
<span className="border border-dashed border-secondary flex items-center justify-center w-16 h-16 bg-primary p-12 rounded-lg text-primary">
|
||||||
|
<Bag className="absolute" />
|
||||||
|
</span>
|
||||||
|
<h2 className="pt-6 text-2xl font-bold tracking-wide text-center">
|
||||||
|
Your cart is empty
|
||||||
|
</h2>
|
||||||
|
<p className="text-accents-6 px-10 text-center pt-2">
|
||||||
|
Biscuit oat cake wafer icing ice cream tiramisu pudding cupcake.
|
||||||
|
</p>
|
||||||
|
</div>
|
||||||
|
) : error ? (
|
||||||
|
<div className="flex-1 px-4 flex flex-col justify-center items-center">
|
||||||
|
<span className="border border-white rounded-full flex items-center justify-center w-16 h-16">
|
||||||
|
<Cross width={24} height={24} />
|
||||||
|
</span>
|
||||||
|
<h2 className="pt-6 text-xl font-light text-center">
|
||||||
|
We couldn’t process the purchase. Please check your card
|
||||||
|
information and try again.
|
||||||
|
</h2>
|
||||||
|
</div>
|
||||||
|
) : success ? (
|
||||||
|
<div className="flex-1 px-4 flex flex-col justify-center items-center">
|
||||||
|
<span className="border border-white rounded-full flex items-center justify-center w-16 h-16">
|
||||||
|
<Check />
|
||||||
|
</span>
|
||||||
|
<h2 className="pt-6 text-xl font-light text-center">
|
||||||
|
Thank you for your order.
|
||||||
|
</h2>
|
||||||
|
</div>
|
||||||
|
) : (
|
||||||
|
<div className="px-4 sm:px-6 flex-1">
|
||||||
|
<Text variant="pageHeading">My Cart</Text>
|
||||||
|
<Text variant="sectionHeading">Review your Order</Text>
|
||||||
|
<ul className="py-6 space-y-6 sm:py-0 sm:space-y-0 sm:divide-y sm:divide-accents-2 border-b border-accents-2">
|
||||||
|
{items.map((item) => (
|
||||||
|
<CartItem
|
||||||
|
key={item.id}
|
||||||
|
item={item}
|
||||||
|
currencyCode={data?.currency.code!}
|
||||||
|
/>
|
||||||
|
))}
|
||||||
|
</ul>
|
||||||
|
<div className="my-6">
|
||||||
|
<Text>
|
||||||
|
Before you leave, take a look at these items. We picked them
|
||||||
|
just for you
|
||||||
|
</Text>
|
||||||
|
<div className="flex py-6 space-x-6">
|
||||||
|
{[1, 2, 3, 4, 5, 6].map((x) => (
|
||||||
|
<div className="border border-accents-3 w-full h-24 bg-accents-2 bg-opacity-50 transform cursor-pointer hover:scale-110 duration-75" />
|
||||||
|
))}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
|
</div>
|
||||||
|
<div className="lg:col-span-4">
|
||||||
|
<div className="flex-shrink-0 px-4 py-24 sm:px-6">
|
||||||
|
<div className="border-t border-accents-2">
|
||||||
|
<ul className="py-3">
|
||||||
|
<li className="flex justify-between py-1">
|
||||||
|
<span>Subtotal</span>
|
||||||
|
<span>{subTotal}</span>
|
||||||
|
</li>
|
||||||
|
<li className="flex justify-between py-1">
|
||||||
|
<span>Taxes</span>
|
||||||
|
<span>Calculated at checkout</span>
|
||||||
|
</li>
|
||||||
|
<li className="flex justify-between py-1">
|
||||||
|
<span>Estimated Shipping</span>
|
||||||
|
<span className="font-bold tracking-wide">FREE</span>
|
||||||
|
</li>
|
||||||
|
</ul>
|
||||||
|
<div className="flex justify-between border-t border-accents-2 py-3 font-bold mb-10">
|
||||||
|
<span>Total</span>
|
||||||
|
<span>{total}</span>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div className="flex flex-row justify-end">
|
||||||
|
<div className="w-full lg:w-72">
|
||||||
|
{isEmpty ? (
|
||||||
|
<Button href="/" Component="a" width="100%">
|
||||||
|
Continue Shopping
|
||||||
|
</Button>
|
||||||
|
) : (
|
||||||
|
<Button href="/checkout" Component="a" width="100%">
|
||||||
|
Proceed to Checkout
|
||||||
|
</Button>
|
||||||
|
)}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
export default Cart
|
28
components/agility-modules/HomeAllProductsGrid.tsx
Normal file
28
components/agility-modules/HomeAllProductsGrid.tsx
Normal file
@ -0,0 +1,28 @@
|
|||||||
|
import React, { FC } from 'react'
|
||||||
|
import HomeAllProductsGrid from '@components/common/HomeAllProductsGrid'
|
||||||
|
|
||||||
|
interface Fields {
|
||||||
|
}
|
||||||
|
|
||||||
|
interface Props {
|
||||||
|
fields: Fields,
|
||||||
|
customData: any
|
||||||
|
}
|
||||||
|
|
||||||
|
const HomeAllProductsGridModule:FC<Props> = ({fields, customData}) => {
|
||||||
|
|
||||||
|
const categories = customData.categories
|
||||||
|
const newestProducts = customData.newestProducts
|
||||||
|
const brands = customData.brands
|
||||||
|
|
||||||
|
return (
|
||||||
|
<HomeAllProductsGrid
|
||||||
|
categories={categories}
|
||||||
|
brands={brands}
|
||||||
|
newestProducts={newestProducts}
|
||||||
|
/>
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
export default HomeAllProductsGridModule
|
||||||
|
|
34
components/agility-modules/Orders.tsx
Normal file
34
components/agility-modules/Orders.tsx
Normal file
@ -0,0 +1,34 @@
|
|||||||
|
import React, { FC } from 'react'
|
||||||
|
|
||||||
|
import { Container, Text } from '@components/ui'
|
||||||
|
import { Bag } from '@components/icons'
|
||||||
|
|
||||||
|
|
||||||
|
interface Fields {
|
||||||
|
}
|
||||||
|
|
||||||
|
interface Props {
|
||||||
|
fields: Fields,
|
||||||
|
customData: any
|
||||||
|
}
|
||||||
|
|
||||||
|
const Orders: FC<Props> = ({ fields, customData }) => {
|
||||||
|
return (
|
||||||
|
<Container>
|
||||||
|
<Text variant="pageHeading">My Orders</Text>
|
||||||
|
<div className="flex-1 p-24 flex flex-col justify-center items-center ">
|
||||||
|
<span className="border border-dashed border-secondary rounded-full flex items-center justify-center w-16 h-16 p-12 bg-primary text-primary">
|
||||||
|
<Bag className="absolute" />
|
||||||
|
</span>
|
||||||
|
<h2 className="pt-6 text-2xl font-bold tracking-wide text-center">
|
||||||
|
No orders found
|
||||||
|
</h2>
|
||||||
|
<p className="text-accents-6 px-10 text-center pt-2">
|
||||||
|
Orders coming soon!
|
||||||
|
</p>
|
||||||
|
</div>
|
||||||
|
</Container>
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
export default Orders
|
450
components/agility-modules/ProductSearch.tsx
Normal file
450
components/agility-modules/ProductSearch.tsx
Normal file
@ -0,0 +1,450 @@
|
|||||||
|
import React, { FC } from 'react'
|
||||||
|
import cn from 'classnames'
|
||||||
|
import Link from 'next/link'
|
||||||
|
import { useState } from 'react'
|
||||||
|
import { useRouter } from 'next/router'
|
||||||
|
import useSearch from '@framework/products/use-search'
|
||||||
|
import { ProductCard } from '@components/product'
|
||||||
|
import { Container, Grid, Skeleton } from '@components/ui'
|
||||||
|
|
||||||
|
import rangeMap from '@lib/range-map'
|
||||||
|
import getSlug from '@lib/get-slug'
|
||||||
|
import {
|
||||||
|
filterQuery,
|
||||||
|
getCategoryPath,
|
||||||
|
getDesignerPath,
|
||||||
|
useSearchMeta,
|
||||||
|
} from '@lib/search'
|
||||||
|
|
||||||
|
const SORT = Object.entries({
|
||||||
|
'latest-desc': 'Latest arrivals',
|
||||||
|
'trending-desc': 'Trending',
|
||||||
|
'price-asc': 'Price: Low to high',
|
||||||
|
'price-desc': 'Price: High to low',
|
||||||
|
})
|
||||||
|
|
||||||
|
interface Fields {
|
||||||
|
}
|
||||||
|
|
||||||
|
interface Props {
|
||||||
|
fields: Fields,
|
||||||
|
customData: any
|
||||||
|
}
|
||||||
|
|
||||||
|
const ProductSearch: FC<Props> = ({ fields, customData }) => {
|
||||||
|
|
||||||
|
const categories:[any] = customData.categories
|
||||||
|
const brands:[any] = customData.brands
|
||||||
|
|
||||||
|
const [activeFilter, setActiveFilter] = useState('')
|
||||||
|
const [toggleFilter, setToggleFilter] = useState(false)
|
||||||
|
|
||||||
|
const router = useRouter()
|
||||||
|
const { asPath } = router
|
||||||
|
const { q, sort } = router.query
|
||||||
|
// `q` can be included but because categories and designers can't be searched
|
||||||
|
// in the same way of products, it's better to ignore the search input if one
|
||||||
|
// of those is selected
|
||||||
|
const query = filterQuery({ sort })
|
||||||
|
|
||||||
|
const { pathname, category, brand } = useSearchMeta(asPath)
|
||||||
|
const activeCategory = categories.find(
|
||||||
|
(cat) => getSlug(cat.path) === category
|
||||||
|
)
|
||||||
|
const activeBrand = brands.find(
|
||||||
|
(b) => getSlug(b.node.path) === `brands/${brand}`
|
||||||
|
)?.node
|
||||||
|
|
||||||
|
const { data } = useSearch({
|
||||||
|
search: typeof q === 'string' ? q : '',
|
||||||
|
categoryId: activeCategory?.entityId,
|
||||||
|
brandId: activeBrand?.entityId,
|
||||||
|
sort: typeof sort === 'string' ? sort : '',
|
||||||
|
})
|
||||||
|
|
||||||
|
const handleClick = (event: any, filter: string) => {
|
||||||
|
if (filter !== activeFilter) {
|
||||||
|
setToggleFilter(true)
|
||||||
|
} else {
|
||||||
|
setToggleFilter(!toggleFilter)
|
||||||
|
}
|
||||||
|
|
||||||
|
setActiveFilter(filter)
|
||||||
|
}
|
||||||
|
|
||||||
|
return (
|
||||||
|
<Container>
|
||||||
|
<div className="grid grid-cols-1 lg:grid-cols-12 gap-4 mt-3 mb-20">
|
||||||
|
<div className="col-span-8 lg:col-span-2 order-1 lg:order-none">
|
||||||
|
{/* Categories */}
|
||||||
|
<div className="relative inline-block w-full">
|
||||||
|
<div className="lg:hidden">
|
||||||
|
<span className="rounded-md shadow-sm">
|
||||||
|
<button
|
||||||
|
type="button"
|
||||||
|
onClick={(e) => handleClick(e, 'categories')}
|
||||||
|
className="flex justify-between w-full rounded-sm border border-gray-300 px-4 py-3 bg-white text-sm leading-5 font-medium text-gray-700 hover:text-gray-500 focus:outline-none focus:border-blue-300 focus:shadow-outline-blue active:bg-gray-50 active:text-gray-800 transition ease-in-out duration-150"
|
||||||
|
id="options-menu"
|
||||||
|
aria-haspopup="true"
|
||||||
|
aria-expanded="true"
|
||||||
|
>
|
||||||
|
{activeCategory?.name
|
||||||
|
? `Category: ${activeCategory?.name}`
|
||||||
|
: 'All Categories'}
|
||||||
|
<svg
|
||||||
|
className="-mr-1 ml-2 h-5 w-5"
|
||||||
|
xmlns="http://www.w3.org/2000/svg"
|
||||||
|
viewBox="0 0 20 20"
|
||||||
|
fill="currentColor"
|
||||||
|
>
|
||||||
|
<path
|
||||||
|
fillRule="evenodd"
|
||||||
|
d="M5.293 7.293a1 1 0 011.414 0L10 10.586l3.293-3.293a1 1 0 111.414 1.414l-4 4a1 1 0 01-1.414 0l-4-4a1 1 0 010-1.414z"
|
||||||
|
clipRule="evenodd"
|
||||||
|
/>
|
||||||
|
</svg>
|
||||||
|
</button>
|
||||||
|
</span>
|
||||||
|
</div>
|
||||||
|
<div
|
||||||
|
className={`origin-top-left absolute lg:relative left-0 mt-2 w-full rounded-md shadow-lg lg:shadow-none z-10 mb-10 lg:block ${
|
||||||
|
activeFilter !== 'categories' || toggleFilter !== true
|
||||||
|
? 'hidden'
|
||||||
|
: ''
|
||||||
|
}`}
|
||||||
|
>
|
||||||
|
<div className="rounded-sm bg-white shadow-xs lg:bg-none lg:shadow-none">
|
||||||
|
<div
|
||||||
|
role="menu"
|
||||||
|
aria-orientation="vertical"
|
||||||
|
aria-labelledby="options-menu"
|
||||||
|
>
|
||||||
|
<ul>
|
||||||
|
<li
|
||||||
|
className={cn(
|
||||||
|
'block text-sm leading-5 text-gray-700 lg:text-base lg:no-underline lg:font-bold lg:tracking-wide hover:bg-gray-100 lg:hover:bg-transparent hover:text-gray-900 focus:outline-none focus:bg-gray-100 focus:text-gray-900',
|
||||||
|
{
|
||||||
|
underline: !activeCategory?.name,
|
||||||
|
}
|
||||||
|
)}
|
||||||
|
>
|
||||||
|
<Link
|
||||||
|
href={{ pathname: getCategoryPath('', brand), query }}
|
||||||
|
>
|
||||||
|
<a
|
||||||
|
onClick={(e) => handleClick(e, 'categories')}
|
||||||
|
className={
|
||||||
|
'block lg:inline-block px-4 py-2 lg:p-0 lg:my-2 lg:mx-4'
|
||||||
|
}
|
||||||
|
>
|
||||||
|
All Categories
|
||||||
|
</a>
|
||||||
|
</Link>
|
||||||
|
</li>
|
||||||
|
{categories.map((cat) => (
|
||||||
|
<li
|
||||||
|
key={cat.path}
|
||||||
|
className={cn(
|
||||||
|
'block text-sm leading-5 text-gray-700 hover:bg-gray-100 lg:hover:bg-transparent hover:text-gray-900 focus:outline-none focus:bg-gray-100 focus:text-gray-900',
|
||||||
|
{
|
||||||
|
underline:
|
||||||
|
activeCategory?.entityId === cat.entityId,
|
||||||
|
}
|
||||||
|
)}
|
||||||
|
>
|
||||||
|
<Link
|
||||||
|
href={{
|
||||||
|
pathname: getCategoryPath(cat.path, brand),
|
||||||
|
query,
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
<a
|
||||||
|
onClick={(e) => handleClick(e, 'categories')}
|
||||||
|
className={
|
||||||
|
'block lg:inline-block px-4 py-2 lg:p-0 lg:my-2 lg:mx-4'
|
||||||
|
}
|
||||||
|
>
|
||||||
|
{cat.name}
|
||||||
|
</a>
|
||||||
|
</Link>
|
||||||
|
</li>
|
||||||
|
))}
|
||||||
|
</ul>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
{/* Designs */}
|
||||||
|
<div className="relative inline-block w-full">
|
||||||
|
<div className="lg:hidden mt-3">
|
||||||
|
<span className="rounded-md shadow-sm">
|
||||||
|
<button
|
||||||
|
type="button"
|
||||||
|
onClick={(e) => handleClick(e, 'brands')}
|
||||||
|
className="flex justify-between w-full rounded-sm border border-gray-300 px-4 py-3 bg-white text-sm leading-5 font-medium text-gray-900 hover:text-gray-500 focus:outline-none focus:border-blue-300 focus:shadow-outline-blue active:bg-gray-50 active:text-gray-800 transition ease-in-out duration-150"
|
||||||
|
id="options-menu"
|
||||||
|
aria-haspopup="true"
|
||||||
|
aria-expanded="true"
|
||||||
|
>
|
||||||
|
{activeBrand?.name
|
||||||
|
? `Design: ${activeBrand?.name}`
|
||||||
|
: 'All Designs'}
|
||||||
|
<svg
|
||||||
|
className="-mr-1 ml-2 h-5 w-5"
|
||||||
|
xmlns="http://www.w3.org/2000/svg"
|
||||||
|
viewBox="0 0 20 20"
|
||||||
|
fill="currentColor"
|
||||||
|
>
|
||||||
|
<path
|
||||||
|
fillRule="evenodd"
|
||||||
|
d="M5.293 7.293a1 1 0 011.414 0L10 10.586l3.293-3.293a1 1 0 111.414 1.414l-4 4a1 1 0 01-1.414 0l-4-4a1 1 0 010-1.414z"
|
||||||
|
clipRule="evenodd"
|
||||||
|
/>
|
||||||
|
</svg>
|
||||||
|
</button>
|
||||||
|
</span>
|
||||||
|
</div>
|
||||||
|
<div
|
||||||
|
className={`origin-top-left absolute lg:relative left-0 mt-2 w-full rounded-md shadow-lg lg:shadow-none z-10 mb-10 lg:block ${
|
||||||
|
activeFilter !== 'brands' || toggleFilter !== true
|
||||||
|
? 'hidden'
|
||||||
|
: ''
|
||||||
|
}`}
|
||||||
|
>
|
||||||
|
<div className="rounded-sm bg-white shadow-xs lg:bg-none lg:shadow-none">
|
||||||
|
<div
|
||||||
|
role="menu"
|
||||||
|
aria-orientation="vertical"
|
||||||
|
aria-labelledby="options-menu"
|
||||||
|
>
|
||||||
|
<ul>
|
||||||
|
<li
|
||||||
|
className={cn(
|
||||||
|
'block text-sm leading-5 text-gray-700 lg:text-base lg:no-underline lg:font-bold lg:tracking-wide hover:bg-gray-100 lg:hover:bg-transparent hover:text-gray-900 focus:outline-none focus:bg-gray-100 focus:text-gray-900',
|
||||||
|
{
|
||||||
|
underline: !activeBrand?.name,
|
||||||
|
}
|
||||||
|
)}
|
||||||
|
>
|
||||||
|
<Link
|
||||||
|
href={{
|
||||||
|
pathname: getDesignerPath('', category),
|
||||||
|
query,
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
<a
|
||||||
|
onClick={(e) => handleClick(e, 'brands')}
|
||||||
|
className={
|
||||||
|
'block lg:inline-block px-4 py-2 lg:p-0 lg:my-2 lg:mx-4'
|
||||||
|
}
|
||||||
|
>
|
||||||
|
All Designers
|
||||||
|
</a>
|
||||||
|
</Link>
|
||||||
|
</li>
|
||||||
|
{brands.flatMap(({ node }) => (
|
||||||
|
<li
|
||||||
|
key={node.path}
|
||||||
|
className={cn(
|
||||||
|
'block text-sm leading-5 text-gray-700 hover:bg-gray-100 lg:hover:bg-transparent hover:text-gray-900 focus:outline-none focus:bg-gray-100 focus:text-gray-900',
|
||||||
|
{
|
||||||
|
underline: activeBrand?.entityId === node.entityId,
|
||||||
|
}
|
||||||
|
)}
|
||||||
|
>
|
||||||
|
<Link
|
||||||
|
href={{
|
||||||
|
pathname: getDesignerPath(node.path, category),
|
||||||
|
query,
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
<a
|
||||||
|
onClick={(e) => handleClick(e, 'brands')}
|
||||||
|
className={
|
||||||
|
'block lg:inline-block px-4 py-2 lg:p-0 lg:my-2 lg:mx-4'
|
||||||
|
}
|
||||||
|
>
|
||||||
|
{node.name}
|
||||||
|
</a>
|
||||||
|
</Link>
|
||||||
|
</li>
|
||||||
|
))}
|
||||||
|
</ul>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
{/* Products */}
|
||||||
|
<div className="col-span-8 order-3 lg:order-none">
|
||||||
|
{(q || activeCategory || activeBrand) && (
|
||||||
|
<div className="mb-12 transition ease-in duration-75">
|
||||||
|
{data ? (
|
||||||
|
<>
|
||||||
|
<span
|
||||||
|
className={cn('animated', {
|
||||||
|
fadeIn: data.found,
|
||||||
|
hidden: !data.found,
|
||||||
|
})}
|
||||||
|
>
|
||||||
|
Showing {data.products.length} results{' '}
|
||||||
|
{q && (
|
||||||
|
<>
|
||||||
|
for "<strong>{q}</strong>"
|
||||||
|
</>
|
||||||
|
)}
|
||||||
|
</span>
|
||||||
|
<span
|
||||||
|
className={cn('animated', {
|
||||||
|
fadeIn: !data.found,
|
||||||
|
hidden: data.found,
|
||||||
|
})}
|
||||||
|
>
|
||||||
|
{q ? (
|
||||||
|
<>
|
||||||
|
There are no products that match "<strong>{q}</strong>"
|
||||||
|
</>
|
||||||
|
) : (
|
||||||
|
<>
|
||||||
|
There are no products that match the selected category &
|
||||||
|
designer
|
||||||
|
</>
|
||||||
|
)}
|
||||||
|
</span>
|
||||||
|
</>
|
||||||
|
) : q ? (
|
||||||
|
<>
|
||||||
|
Searching for: "<strong>{q}</strong>"
|
||||||
|
</>
|
||||||
|
) : (
|
||||||
|
<>Searching...</>
|
||||||
|
)}
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
|
|
||||||
|
{data ? (
|
||||||
|
<Grid layout="normal">
|
||||||
|
{data.products.map(({ node }) => (
|
||||||
|
<ProductCard
|
||||||
|
variant="simple"
|
||||||
|
key={node.path}
|
||||||
|
className="animated fadeIn"
|
||||||
|
product={node}
|
||||||
|
imgWidth={480}
|
||||||
|
imgHeight={480}
|
||||||
|
/>
|
||||||
|
))}
|
||||||
|
</Grid>
|
||||||
|
) : (
|
||||||
|
<Grid layout="normal">
|
||||||
|
{rangeMap(12, (i) => (
|
||||||
|
<Skeleton
|
||||||
|
key={i}
|
||||||
|
className="w-full animated fadeIn"
|
||||||
|
height={325}
|
||||||
|
/>
|
||||||
|
))}
|
||||||
|
</Grid>
|
||||||
|
)}
|
||||||
|
</div>
|
||||||
|
|
||||||
|
{/* Sort */}
|
||||||
|
<div className="col-span-8 lg:col-span-2 order-2 lg:order-none">
|
||||||
|
<div className="relative inline-block w-full">
|
||||||
|
<div className="lg:hidden">
|
||||||
|
<span className="rounded-md shadow-sm">
|
||||||
|
<button
|
||||||
|
type="button"
|
||||||
|
onClick={(e) => handleClick(e, 'sort')}
|
||||||
|
className="flex justify-between w-full rounded-sm border border-gray-300 px-4 py-3 bg-white text-sm leading-5 font-medium text-gray-700 hover:text-gray-500 focus:outline-none focus:border-blue-300 focus:shadow-outline-blue active:bg-gray-50 active:text-gray-800 transition ease-in-out duration-150"
|
||||||
|
id="options-menu"
|
||||||
|
aria-haspopup="true"
|
||||||
|
aria-expanded="true"
|
||||||
|
>
|
||||||
|
{sort ? `Sort: ${sort}` : 'Relevance'}
|
||||||
|
<svg
|
||||||
|
className="-mr-1 ml-2 h-5 w-5"
|
||||||
|
xmlns="http://www.w3.org/2000/svg"
|
||||||
|
viewBox="0 0 20 20"
|
||||||
|
fill="currentColor"
|
||||||
|
>
|
||||||
|
<path
|
||||||
|
fillRule="evenodd"
|
||||||
|
d="M5.293 7.293a1 1 0 011.414 0L10 10.586l3.293-3.293a1 1 0 111.414 1.414l-4 4a1 1 0 01-1.414 0l-4-4a1 1 0 010-1.414z"
|
||||||
|
clipRule="evenodd"
|
||||||
|
/>
|
||||||
|
</svg>
|
||||||
|
</button>
|
||||||
|
</span>
|
||||||
|
</div>
|
||||||
|
<div
|
||||||
|
className={`origin-top-left absolute lg:relative left-0 mt-2 w-full rounded-md shadow-lg lg:shadow-none z-10 mb-10 lg:block ${
|
||||||
|
activeFilter !== 'sort' || toggleFilter !== true ? 'hidden' : ''
|
||||||
|
}`}
|
||||||
|
>
|
||||||
|
<div className="rounded-sm bg-white shadow-xs lg:bg-none lg:shadow-none">
|
||||||
|
<div
|
||||||
|
role="menu"
|
||||||
|
aria-orientation="vertical"
|
||||||
|
aria-labelledby="options-menu"
|
||||||
|
>
|
||||||
|
<ul>
|
||||||
|
<li
|
||||||
|
className={cn(
|
||||||
|
'block text-sm leading-5 text-gray-700 lg:text-base lg:no-underline lg:font-bold lg:tracking-wide hover:bg-gray-100 lg:hover:bg-transparent hover:text-gray-900 focus:outline-none focus:bg-gray-100 focus:text-gray-900',
|
||||||
|
{
|
||||||
|
underline: !sort,
|
||||||
|
}
|
||||||
|
)}
|
||||||
|
>
|
||||||
|
<Link href={{ pathname, query: filterQuery({ q }) }}>
|
||||||
|
<a
|
||||||
|
onClick={(e) => handleClick(e, 'sort')}
|
||||||
|
className={
|
||||||
|
'block lg:inline-block px-4 py-2 lg:p-0 lg:my-2 lg:mx-4'
|
||||||
|
}
|
||||||
|
>
|
||||||
|
Relevance
|
||||||
|
</a>
|
||||||
|
</Link>
|
||||||
|
</li>
|
||||||
|
{SORT.map(([key, text]) => (
|
||||||
|
<li
|
||||||
|
key={key}
|
||||||
|
className={cn(
|
||||||
|
'block text-sm leading-5 text-gray-700 hover:bg-gray-100 lg:hover:bg-transparent hover:text-gray-900 focus:outline-none focus:bg-gray-100 focus:text-gray-900',
|
||||||
|
{
|
||||||
|
underline: sort === key,
|
||||||
|
}
|
||||||
|
)}
|
||||||
|
>
|
||||||
|
<Link
|
||||||
|
href={{
|
||||||
|
pathname,
|
||||||
|
query: filterQuery({ q, sort: key }),
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
<a
|
||||||
|
onClick={(e) => handleClick(e, 'sort')}
|
||||||
|
className={
|
||||||
|
'block lg:inline-block px-4 py-2 lg:p-0 lg:my-2 lg:mx-4'
|
||||||
|
}
|
||||||
|
>
|
||||||
|
{text}
|
||||||
|
</a>
|
||||||
|
</Link>
|
||||||
|
</li>
|
||||||
|
))}
|
||||||
|
</ul>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</Container>
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
export default ProductSearch
|
48
components/agility-modules/Profile.tsx
Normal file
48
components/agility-modules/Profile.tsx
Normal file
@ -0,0 +1,48 @@
|
|||||||
|
import React, { FC } from 'react'
|
||||||
|
|
||||||
|
import useCustomer from '@framework/use-customer'
|
||||||
|
import { Container, Text } from '@components/ui'
|
||||||
|
|
||||||
|
interface Fields {
|
||||||
|
heading: string,
|
||||||
|
fullNameLabel: string,
|
||||||
|
emailLabel: string,
|
||||||
|
notLoggedInMessage: string
|
||||||
|
}
|
||||||
|
|
||||||
|
interface Props {
|
||||||
|
fields: Fields
|
||||||
|
}
|
||||||
|
|
||||||
|
const ProfileModule:FC<Props> = ({fields}) => {
|
||||||
|
|
||||||
|
const { data } = useCustomer()
|
||||||
|
return (
|
||||||
|
<Container>
|
||||||
|
<Text variant="pageHeading">{fields.heading}</Text>
|
||||||
|
{data && (
|
||||||
|
<div className="grid lg:grid-cols-12">
|
||||||
|
<div className="lg:col-span-8 pr-4">
|
||||||
|
<div>
|
||||||
|
<Text variant="sectionHeading">{fields.fullNameLabel}</Text>
|
||||||
|
<span>
|
||||||
|
{data.firstName} {data.lastName}
|
||||||
|
</span>
|
||||||
|
</div>
|
||||||
|
<div className="mt-5">
|
||||||
|
<Text variant="sectionHeading">{fields.emailLabel}</Text>
|
||||||
|
<span>{data.email}</span>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
|
|
||||||
|
{ !data &&
|
||||||
|
<Text variant="body">{fields.notLoggedInMessage}</Text>
|
||||||
|
}
|
||||||
|
</Container>
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
export default ProfileModule
|
||||||
|
|
53
components/agility-modules/Wishlist.tsx
Normal file
53
components/agility-modules/Wishlist.tsx
Normal file
@ -0,0 +1,53 @@
|
|||||||
|
import React, { FC } from 'react'
|
||||||
|
|
||||||
|
import useWishlist from '@framework/wishlist/use-wishlist'
|
||||||
|
|
||||||
|
import { Heart } from '@components/icons'
|
||||||
|
import { Text, Container } from '@components/ui'
|
||||||
|
import { WishlistCard } from '@components/wishlist'
|
||||||
|
|
||||||
|
|
||||||
|
interface Fields {
|
||||||
|
heading: string,
|
||||||
|
emptyMessage: string,
|
||||||
|
addItemsMessage?: string
|
||||||
|
}
|
||||||
|
|
||||||
|
interface Props {
|
||||||
|
fields: Fields,
|
||||||
|
customData: any
|
||||||
|
}
|
||||||
|
|
||||||
|
const Wishlist: FC<Props> = ({ fields, customData }) => {
|
||||||
|
const { data, isEmpty } = useWishlist({ includeProducts: true })
|
||||||
|
|
||||||
|
return (
|
||||||
|
<Container>
|
||||||
|
<div className="mt-3 mb-20">
|
||||||
|
<Text variant="pageHeading">{fields.heading}</Text>
|
||||||
|
<div className="group flex flex-col">
|
||||||
|
{isEmpty ? (
|
||||||
|
<div className="flex-1 px-12 py-24 flex flex-col justify-center items-center ">
|
||||||
|
<span className="border border-dashed border-secondary flex items-center justify-center w-16 h-16 bg-primary p-12 rounded-lg text-primary">
|
||||||
|
<Heart className="absolute" />
|
||||||
|
</span>
|
||||||
|
<h2 className="pt-6 text-2xl font-bold tracking-wide text-center">
|
||||||
|
{fields.emptyMessage}
|
||||||
|
</h2>
|
||||||
|
<p className="text-accents-6 px-10 text-center pt-2">
|
||||||
|
{fields.addItemsMessage}
|
||||||
|
</p>
|
||||||
|
</div>
|
||||||
|
) : (
|
||||||
|
data &&
|
||||||
|
data.items?.map((item) => (
|
||||||
|
<WishlistCard key={item.id} item={item} />
|
||||||
|
))
|
||||||
|
)}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</Container>
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
export default Wishlist
|
@ -1,27 +1,39 @@
|
|||||||
import {FC} from "react"
|
import { FC } from "react"
|
||||||
import * as AgilityTypes from "@agility/types"
|
import * as AgilityTypes from "@agility/types"
|
||||||
import RichTextArea from "./RichTextArea"
|
import RichTextArea from "./RichTextArea"
|
||||||
import BestsellingProducts from "./BestsellingProducts"
|
import BestsellingProducts from "./BestsellingProducts"
|
||||||
import ProductDetails from "./ProductDetails"
|
import ProductDetails from "./ProductDetails"
|
||||||
import FeaturedProducts from "./FeaturedProducts"
|
import FeaturedProducts from "./FeaturedProducts"
|
||||||
import ProductListing from "./ProductListing"
|
import ProductListing from "./ProductListing"
|
||||||
|
import ProductSearch from "./ProductSearch"
|
||||||
import Hero from "./Hero"
|
import Hero from "./Hero"
|
||||||
|
import HomeAllProductsGrid from "./HomeAllProductsGrid"
|
||||||
|
import Cart from "./Cart"
|
||||||
|
import Orders from "./Orders"
|
||||||
|
import Profile from "./Profile"
|
||||||
|
import Wishlist from "./Wishlist"
|
||||||
|
|
||||||
|
|
||||||
const allModules =[
|
const allModules = [
|
||||||
{ name: "RichTextArea", module:RichTextArea },
|
{ name: "RichTextArea", module: RichTextArea },
|
||||||
{ name: "BestsellingProducts", module: BestsellingProducts },
|
{ name: "BestsellingProducts", module: BestsellingProducts },
|
||||||
{ name: "FeaturedProducts", module: FeaturedProducts},
|
{ name: "FeaturedProducts", module: FeaturedProducts },
|
||||||
{ name: "ProductListing", module: ProductListing},
|
{ name: "ProductListing", module: ProductListing },
|
||||||
{ name: "Hero", module: Hero},
|
{ name: "ProductSearch", module: ProductSearch },
|
||||||
{ name: "ProductDetails", module: ProductDetails }
|
{ name: "Hero", module: Hero },
|
||||||
|
{ name: "ProductDetails", module: ProductDetails },
|
||||||
|
{ name: "HomeAllProductsGrid", module: HomeAllProductsGrid },
|
||||||
|
{ name: "Cart", module: Cart },
|
||||||
|
{ name: "Orders", module: Orders },
|
||||||
|
{ name: "Profile", module: Profile},
|
||||||
|
{ name: "Wishlist", module: Wishlist}
|
||||||
]
|
]
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Find the component for a module.
|
* Find the component for a module by name.
|
||||||
* @param moduleName
|
* @param moduleName
|
||||||
*/
|
*/
|
||||||
const getModule = (moduleName:string):any | null => {
|
const getModule = (moduleName: string): any | null => {
|
||||||
const obj = allModules.find(m => m.name.toLowerCase() === moduleName.toLowerCase())
|
const obj = allModules.find(m => m.name.toLowerCase() === moduleName.toLowerCase())
|
||||||
if (!obj) return null
|
if (!obj) return null
|
||||||
return obj.module
|
return obj.module
|
||||||
|
@ -45,9 +45,9 @@ const Footer: FC<Props> = ({ className, pages }) => {
|
|||||||
</Link>
|
</Link>
|
||||||
</li>
|
</li>
|
||||||
<li className="py-3 md:py-0 md:pb-4">
|
<li className="py-3 md:py-0 md:pb-4">
|
||||||
<Link href="/">
|
<Link href="/about">
|
||||||
<a className="text-primary hover:text-accents-6 transition ease-in-out duration-150">
|
<a className="text-primary hover:text-accents-6 transition ease-in-out duration-150">
|
||||||
Careers
|
About
|
||||||
</a>
|
</a>
|
||||||
</Link>
|
</Link>
|
||||||
</li>
|
</li>
|
||||||
|
@ -2,10 +2,10 @@ import s from './LoadingDots.module.css'
|
|||||||
|
|
||||||
const LoadingDots: React.FC = () => {
|
const LoadingDots: React.FC = () => {
|
||||||
return (
|
return (
|
||||||
<span className={s.root}>
|
<span className={s.root} >
|
||||||
<span></span>
|
<i></i>
|
||||||
<span></span>
|
<i></i>
|
||||||
<span></span>
|
<i></i>
|
||||||
</span>
|
</span>
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
38
framework/module-data/HomeAllProductsGridData.ts
Normal file
38
framework/module-data/HomeAllProductsGridData.ts
Normal file
@ -0,0 +1,38 @@
|
|||||||
|
import { getConfig } from '@framework/api'
|
||||||
|
import getSiteInfo from '@framework/api/operations/get-site-info'
|
||||||
|
import getAllProducts from '@framework/api/operations/get-all-products'
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
import rangeMap from '@lib/range-map'
|
||||||
|
|
||||||
|
const nonNullable = (v: any) => v
|
||||||
|
|
||||||
|
const HomeAllProductsGridData = async function ({ item, agility, languageCode, channelName, pageInSitemap, dynamicPageItem }: any) {
|
||||||
|
//TODO: pass the locale and preview mode as props...
|
||||||
|
|
||||||
|
const locale = "en-US"
|
||||||
|
const preview = false
|
||||||
|
|
||||||
|
|
||||||
|
const config = getConfig({ locale })
|
||||||
|
const { categories, brands } = await getSiteInfo({ config, preview })
|
||||||
|
|
||||||
|
// Get Best Newest Products
|
||||||
|
const { products: newestProducts } = await getAllProducts({
|
||||||
|
variables: { field: 'newestProducts', first: 12 },
|
||||||
|
config,
|
||||||
|
preview,
|
||||||
|
})
|
||||||
|
|
||||||
|
|
||||||
|
return {
|
||||||
|
newestProducts: newestProducts,
|
||||||
|
categories,
|
||||||
|
brands
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
export default HomeAllProductsGridData
|
21
framework/module-data/ProductSearchData.ts
Normal file
21
framework/module-data/ProductSearchData.ts
Normal file
@ -0,0 +1,21 @@
|
|||||||
|
import { getConfig } from '@framework/api'
|
||||||
|
import getSiteInfo from '@framework/api/operations/get-site-info'
|
||||||
|
|
||||||
|
const ProductSearchData = async function ({ item, agility, languageCode, channelName, pageInSitemap, dynamicPageItem }: any) {
|
||||||
|
//TODO: pass the locale and preview mode as props...
|
||||||
|
|
||||||
|
const locale = "en-US"
|
||||||
|
const preview = false
|
||||||
|
|
||||||
|
const config = getConfig({ locale })
|
||||||
|
const { categories, brands } = await getSiteInfo({ config, preview })
|
||||||
|
|
||||||
|
return {
|
||||||
|
categories,
|
||||||
|
brands
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
export default ProductSearchData
|
@ -1,16 +1,17 @@
|
|||||||
import {FC} from "react"
|
|
||||||
import * as AgilityTypes from "@agility/types"
|
|
||||||
|
|
||||||
import BestsellingProductsData from "./BestsellingProductsData"
|
import BestsellingProductsData from "./BestsellingProductsData"
|
||||||
import FeaturedProductsData from "./FeaturedProductsData"
|
import FeaturedProductsData from "./FeaturedProductsData"
|
||||||
|
import HomeAllProductsGridData from "./HomeAllProductsGridData"
|
||||||
|
import ProductSearchData from "./ProductSearchData"
|
||||||
|
|
||||||
const allModules:any =[
|
const allModules:any =[
|
||||||
{ name: "BestsellingProducts", init: BestsellingProductsData },
|
{ name: "BestsellingProducts", init: BestsellingProductsData },
|
||||||
{ name: "FeaturedProducts", init: FeaturedProductsData}
|
{ name: "FeaturedProducts", init: FeaturedProductsData},
|
||||||
|
{ name: "HomeAllProductsGrid", init: HomeAllProductsGridData},
|
||||||
|
{ name: "ProductSearch", init: ProductSearchData}
|
||||||
]
|
]
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Find the data method for a module.
|
* Find the data method for a module by module reference name.
|
||||||
* @param moduleName
|
* @param moduleName
|
||||||
*/
|
*/
|
||||||
const getInitMethod = (moduleName:string):any => {
|
const getInitMethod = (moduleName:string):any => {
|
||||||
|
@ -106,9 +106,9 @@ export default function Search({
|
|||||||
fill="currentColor"
|
fill="currentColor"
|
||||||
>
|
>
|
||||||
<path
|
<path
|
||||||
fill-rule="evenodd"
|
fillRule="evenodd"
|
||||||
d="M5.293 7.293a1 1 0 011.414 0L10 10.586l3.293-3.293a1 1 0 111.414 1.414l-4 4a1 1 0 01-1.414 0l-4-4a1 1 0 010-1.414z"
|
d="M5.293 7.293a1 1 0 011.414 0L10 10.586l3.293-3.293a1 1 0 111.414 1.414l-4 4a1 1 0 01-1.414 0l-4-4a1 1 0 010-1.414z"
|
||||||
clip-rule="evenodd"
|
clipRule="evenodd"
|
||||||
/>
|
/>
|
||||||
</svg>
|
</svg>
|
||||||
</button>
|
</button>
|
||||||
@ -205,9 +205,9 @@ export default function Search({
|
|||||||
fill="currentColor"
|
fill="currentColor"
|
||||||
>
|
>
|
||||||
<path
|
<path
|
||||||
fill-rule="evenodd"
|
fillRule="evenodd"
|
||||||
d="M5.293 7.293a1 1 0 011.414 0L10 10.586l3.293-3.293a1 1 0 111.414 1.414l-4 4a1 1 0 01-1.414 0l-4-4a1 1 0 010-1.414z"
|
d="M5.293 7.293a1 1 0 011.414 0L10 10.586l3.293-3.293a1 1 0 111.414 1.414l-4 4a1 1 0 01-1.414 0l-4-4a1 1 0 010-1.414z"
|
||||||
clip-rule="evenodd"
|
clipRule="evenodd"
|
||||||
/>
|
/>
|
||||||
</svg>
|
</svg>
|
||||||
</button>
|
</button>
|
||||||
@ -378,9 +378,9 @@ export default function Search({
|
|||||||
fill="currentColor"
|
fill="currentColor"
|
||||||
>
|
>
|
||||||
<path
|
<path
|
||||||
fill-rule="evenodd"
|
fillRule="evenodd"
|
||||||
d="M5.293 7.293a1 1 0 011.414 0L10 10.586l3.293-3.293a1 1 0 111.414 1.414l-4 4a1 1 0 01-1.414 0l-4-4a1 1 0 010-1.414z"
|
d="M5.293 7.293a1 1 0 011.414 0L10 10.586l3.293-3.293a1 1 0 111.414 1.414l-4 4a1 1 0 01-1.414 0l-4-4a1 1 0 010-1.414z"
|
||||||
clip-rule="evenodd"
|
clipRule="evenodd"
|
||||||
/>
|
/>
|
||||||
</svg>
|
</svg>
|
||||||
</button>
|
</button>
|
||||||
|
@ -1,6 +1,6 @@
|
|||||||
<svg width="24" height="24" viewBox="0 0 24 24" fill="none" xmlns="http://www.w3.org/2000/svg">
|
<svg width="24" height="24" viewBox="0 0 24 24" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||||
<mask id="mask0" mask-type="alpha" maskUnits="userSpaceOnUse" x="0" y="0" width="24" height="24">
|
<mask id="mask0" mask-type="alpha" maskUnits="userSpaceOnUse" x="0" y="0" width="24" height="24">
|
||||||
<path fill-rule="evenodd" clip-rule="evenodd" d="M21.5144 6C19.5212 2.84625 16.0064 0.75 11.9999 0.75C7.99335 0.75 4.47848 2.84625 2.48535 6H2.4855C1.38787 7.737 0.75 9.79313 0.75 12C0.75 14.2069 1.38787 16.263 2.4855 18H2.48535C4.47848 21.1537 7.99335 23.25 11.9999 23.25C16.0064 23.25 19.5212 21.1537 21.5144 18H21.5145C22.6121 16.263 23.25 14.2069 23.25 12C23.25 9.79313 22.6121 7.737 21.5145 6H21.5144Z" fill="#B4D7EE"/>
|
<path fillRule="evenodd" clipRule="evenodd" d="M21.5144 6C19.5212 2.84625 16.0064 0.75 11.9999 0.75C7.99335 0.75 4.47848 2.84625 2.48535 6H2.4855C1.38787 7.737 0.75 9.79313 0.75 12C0.75 14.2069 1.38787 16.263 2.4855 18H2.48535C4.47848 21.1537 7.99335 23.25 11.9999 23.25C16.0064 23.25 19.5212 21.1537 21.5144 18H21.5145C22.6121 16.263 23.25 14.2069 23.25 12C23.25 9.79313 22.6121 7.737 21.5145 6H21.5144Z" fill="#B4D7EE"/>
|
||||||
</mask>
|
</mask>
|
||||||
<g mask="url(#mask0)">
|
<g mask="url(#mask0)">
|
||||||
<rect x="-2" y="-1" width="27" height="14" fill="#FCD116"/>
|
<rect x="-2" y="-1" width="27" height="14" fill="#FCD116"/>
|
||||||
|
Before Width: | Height: | Size: 840 B After Width: | Height: | Size: 838 B |
Loading…
x
Reference in New Issue
Block a user