Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat: search product control implementations #2547

Open
wants to merge 29 commits into
base: feat/quick-order
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from 13 commits
Commits
Show all changes
29 commits
Select commit Hold shift + click to select a range
37d34d4
feat: search product item control
ramondorosario-ct Nov 6, 2024
7255092
feat: rename file
ramondorosario-ct Nov 6, 2024
a3177b0
style: tokens
ramondorosario-ct Nov 6, 2024
28e054a
style: ident
ramondorosario-ct Nov 6, 2024
09f0b28
feat: add skuMatrixControl prop
ramondorosario-ct Nov 6, 2024
5a7bf2b
feat: change timeout time
ramondorosario-ct Nov 6, 2024
e02f24c
feat: search product item control
ramondorosario-ct Nov 6, 2024
2c85f5d
Merge branch 'feat/search-product-item-control' of github.com:cubos-v…
ramondorosario-ct Nov 6, 2024
5e39413
feat: add quantity and onChangeQuantity props
ramondorosario-ct Nov 6, 2024
18c5d5d
chore: file formater
ramondorosario-ct Nov 6, 2024
8207add
feat: search product control implementations
ramondorosario-ct Nov 6, 2024
f8573ff
feat: adjust control behavior on mobile version
ramondorosario-ct Nov 8, 2024
d5f06a0
Merge branch 'feat/search-product-item-control' into feat/search-prod…
ramondorosario-ct Nov 8, 2024
d774a38
fix: set faststore button in ui
HiagoMoreiraCubos Nov 18, 2024
e63111b
fix: indentation
HiagoMoreiraCubos Nov 19, 2024
ba7344b
fix: indentation on styles.scss
HiagoMoreiraCubos Nov 19, 2024
9d6d4c3
fix: adjust imports
HiagoMoreiraCubos Nov 21, 2024
a6d9d05
feat: apply pr suggestions
ramondorosario-ct Nov 22, 2024
a51fdbb
fix: ident
ramondorosario-ct Nov 22, 2024
2636032
fix: ident
ramondorosario-ct Nov 22, 2024
8f915ea
fix: product name display
ramondorosario-ct Jan 9, 2025
e0bb7f5
fix: stop propagation click
ramondorosario-ct Jan 13, 2025
45fbac5
fix: lint
ramondorosario-ct Jan 21, 2025
d278e4b
chore: fix lint
ramondorosario-ct Jan 21, 2025
70a220f
feat: apply pr sugestions
ramondorosario-ct Feb 3, 2025
22163be
feat: actions control in mobile and desktop version
ramondorosario-ct Feb 5, 2025
467630b
Merge branch 'feat/search-product-item-control' into feat/search-prod…
ramondorosario-ct Feb 5, 2025
20e4fb5
Merge remote-tracking branch 'upstream/feat/quick-order' into feat/se…
ramondorosario-ct Feb 5, 2025
66fdda6
feat: remove unused method
ramondorosario-ct Feb 5, 2025
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
import React, { forwardRef } from 'react'
import React, { forwardRef, useCallback } from 'react'
import { ProductPrice } from '../..'
import SearchProductItemControl from './SearchProductItemControl'

import type { PriceDefinition } from '../../typings/PriceDefinition'

Expand All @@ -12,23 +13,63 @@ export interface SearchProductItemContentProps {
* Specifies product's prices.
*/
price: PriceDefinition
/**
* Quick order condition.
*/
quickOrder?: {
enabled: boolean
availability: boolean
hasVariants: boolean
skuMatrixControl: React.ReactNode
quantity: number,
onChangeQuantity(value: number): void
buyProps?: {
onClick: (e: React.MouseEvent<HTMLButtonElement>) => void
'data-testid': string
'data-sku': string
'data-seller': string
}
}
}

const SearchProductItemContent = forwardRef<
HTMLElement,
SearchProductItemContentProps
>(function SearchProductItemContent({ price, title, ...otherProps }, ref) {
>(function SearchProductItemContent(
{ price, title, quickOrder, ...otherProps },
ref
) {
const renderProductItemContent = useCallback(() => {
return (
<>
<p data-fs-search-product-item-title>{title}</p>
{price.value !== 0 && (
<ProductPrice
data-fs-search-product-item-prices
listPrice={price.listPrice}
value={price.value}
formatter={price.formatter}
/>
)}
</>
)
}, [quickOrder?.enabled])

ramondorosario-ct marked this conversation as resolved.
Show resolved Hide resolved
return (
<section ref={ref} data-fs-search-product-item-content {...otherProps}>
<p data-fs-search-product-item-title>{title}</p>
{!quickOrder?.enabled && renderProductItemContent()}

{price.value !== 0 && (
<ProductPrice
data-fs-search-product-item-prices
listPrice={price.listPrice}
value={price.value}
formatter={price.formatter}
/>
{quickOrder?.enabled && (
<SearchProductItemControl
availability={quickOrder.availability}
hasVariants={quickOrder.hasVariants}
skuMatrixControl={quickOrder.skuMatrixControl}
quantity={quickOrder.quantity}
onChangeQuantity={quickOrder.onChangeQuantity}
{...quickOrder.buyProps}
>
{renderProductItemContent()}
</SearchProductItemControl>
)}
</section>
)
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,124 @@
import React, { forwardRef, HTMLAttributes } from 'react'
import { Badge, Icon, IconButton, Input, Loader, QuantitySelector } from '../..'
type StatusButtonAddToCartType = 'default' | 'inProgress' | 'completed'

export interface SearchProductItemControlProps
extends Omit<HTMLAttributes<HTMLDivElement>, 'children' | 'onClick'> {
children: React.ReactNode
availability: boolean
hasVariants: boolean
skuMatrixControl: React.ReactNode
quantity: number
onClick?(e: React.MouseEvent<HTMLButtonElement>): void
onChangeQuantity(value: number): void
}

const SearchProductItemControl = forwardRef<
HTMLDivElement,
SearchProductItemControlProps
>(function SearchProductItemControl(
{
availability,
children,
hasVariants,
skuMatrixControl,
quantity,
onClick,
onChangeQuantity,
...otherProps
},
ref
) {
const [statusAddToCart, setStatusAddToCart] =
React.useState<StatusButtonAddToCartType>('default')
function stopPropagationClick(e: React.MouseEvent) {
e.preventDefault()
e.stopPropagation()
}
function handleAddToCart(event: React.MouseEvent<HTMLButtonElement>) {
if (onClick) {
setStatusAddToCart('inProgress')

setTimeout(() => {
setStatusAddToCart('completed')
onClick(event)
}, 1000)

setTimeout(() => {
setStatusAddToCart('default')
onChangeQuantity(1)
}, 2000)
}
}

const getIcon = React.useCallback(() => {
switch (statusAddToCart) {
case 'inProgress':
return <Loader />
case 'completed':
return <Icon name="Checked" width={24} height={24} />
default:
return <Icon name="ShoppingCart" width={24} height={24} />
}
}, [statusAddToCart])

const showSKUMatrixControl = availability && hasVariants;
const isMobile = window.innerWidth <= 768

return (
<div
ref={ref}
data-fs-search-product-item-control
onClick={stopPropagationClick}
{...otherProps}
>
<div data-fs-search-product-item-control-content>
{!availability && (
<Badge
data-fs-search-product-item-control-badge
variant="warning"
>
Out of Stock
</Badge>
)}
{children}
</div>
{availability && !hasVariants && (
<div
data-fs-search-product-item-control-actions
role="group"
onClick={stopPropagationClick}
>
{!isMobile && (
<QuantitySelector
disabled={statusAddToCart !== 'default'}
initial={quantity}
onChange={onChangeQuantity}
/>
)}

{isMobile && (
<Input
data-fs-product-item-control-input
type="number"
min={1}
value={quantity}
onChange={(e) => onChangeQuantity(e.target.valueAsNumber)}
/>
)}

<IconButton
variant="primary"
aria-label="Add product to cart"
onClick={handleAddToCart}
disabled={statusAddToCart === 'inProgress'}
icon={getIcon()}
/>
</div>
)}

{showSKUMatrixControl && skuMatrixControl}
</div>
)
})
export default SearchProductItemControl
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,8 @@ import { Image } from 'src/components/ui/Image'
import { useFormattedPrice } from 'src/sdk/product/useFormattedPrice'
import { useProductLink } from 'src/sdk/product/useProductLink'
import type { ProductSummary_ProductFragment } from '@generated/graphql'
import { useMemo, useState } from 'react'
import { useBuyButton } from 'src/sdk/cart/useBuyButton'

type SearchProductItemProps = {
/**
Expand All @@ -19,11 +21,16 @@ type SearchProductItemProps = {
* Index to generate product link.
*/
index: number
/**
* Enable Quick Order.
*/
quickOrder?: boolean
}

function SearchProductItem({
product,
index,
quickOrder,
...otherProps
}: SearchProductItemProps) {
const {
Expand All @@ -36,13 +43,31 @@ function SearchProductItem({
index,
})

const [quantity, setQuantity] = useState<number>(1)

const {
isVariantOf: { name },
id,
sku,
gtin,
name,
brand,
isVariantOf,
unitMultiplier,
lucasfp13 marked this conversation as resolved.
Show resolved Hide resolved
image: [img],
offers: {
lowPrice: spotPrice,
offers: [{ listPrice }],
offers: [
{
listPrice,
availability,
price,
listPriceWithTaxes,
seller,
priceWithTaxes,
},
],
},
additionalProperty,
} = product

const linkProps = {
Expand All @@ -54,6 +79,43 @@ function SearchProductItem({
...baseLinkProps,
}

const outOfStock = useMemo(
() => availability === 'https://schema.org/OutOfStock',
[availability]
)

const hasVariants = useMemo(
() =>
Boolean(
Object.keys(product.isVariantOf.skuVariants.allVariantsByName).length
),

[product]
)

const buyProps = useBuyButton(
{
id,
price,
priceWithTaxes,
listPrice,
listPriceWithTaxes,
seller,
quantity,
itemOffered: {
sku,
name,
gtin,
image: [img],
brand,
isVariantOf,
additionalProperty,
unitMultiplier,
},
},
false
)

return (
<UISearchProductItem linkProps={linkProps} {...otherProps}>
<UISearchProductItemImage>
Expand All @@ -66,6 +128,16 @@ function SearchProductItem({
listPrice: listPrice,
formatter: useFormattedPrice,
}}
quickOrder={{
enabled: quickOrder,
availability: !outOfStock,
hasVariants,
buyProps,
quantity,
onChangeQuantity: setQuantity,
// FIXME: Use SKU Matrix component
skuMatrixControl: <button>Select multiple</button>,
HiagoMoreiraCubos marked this conversation as resolved.
Show resolved Hide resolved
}}
></UISearchProductItemContent>
</UISearchProductItem>
)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -7,12 +7,14 @@
@import "@faststore/ui/src/components/atoms/Badge/styles.scss";
@import "@faststore/ui/src/components/atoms/Button/styles.scss";
@import "@faststore/ui/src/components/atoms/Icon/styles.scss";
@import "@faststore/ui/src/components/atoms/Loader/styles.scss";
@import "@faststore/ui/src/components/atoms/Input/styles.scss";
@import "@faststore/ui/src/components/atoms/Link/styles.scss";
@import "@faststore/ui/src/components/atoms/List/styles.scss";
@import "@faststore/ui/src/components/atoms/Logo/styles.scss";
@import "@faststore/ui/src/components/atoms/Price/styles.scss";
@import "@faststore/ui/src/components/molecules/LinkButton/styles.scss";
@import "@faststore/ui/src/components/molecules/QuantitySelector/styles.scss";
@import "@faststore/ui/src/components/molecules/NavbarLinks/styles.scss";
@import "@faststore/ui/src/components/molecules/ProductPrice/styles.scss";
@import "@faststore/ui/src/components/molecules/SearchAutoComplete/styles.scss";
Expand Down
7 changes: 5 additions & 2 deletions packages/core/src/sdk/cart/useBuyButton.ts
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@ import { useUI } from '@faststore/ui'
import { useSession } from '../session'
import { cartStore } from './index'

export const useBuyButton = (item: CartItem | null) => {
export const useBuyButton = (item: CartItem | null, shouldOpenCart = true) => {
const { openCart } = useUI()
const {
currency: { code },
Expand Down Expand Up @@ -49,7 +49,10 @@ export const useBuyButton = (item: CartItem | null) => {
})

cartStore.addItem(item)
openCart()

if (shouldOpenCart) {
openCart()
}
},
[code, item, openCart]
)
Expand Down
Loading
Loading