Skip to content

Commit

Permalink
add show more button (#128)
Browse files Browse the repository at this point in the history
* add show more button

* improve showmore component

* fmt

* remove console log

* use pageHref instead

* remove showMore

* fix type errors

* back to use ctx.invoke

* use app version

* remove fashion photos

* create better types
  • Loading branch information
guitavano authored Feb 6, 2024
1 parent 128ae19 commit 0a67570
Show file tree
Hide file tree
Showing 15 changed files with 216 additions and 36 deletions.
7 changes: 5 additions & 2 deletions apps/site.ts
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@ import { color as wake } from "apps/wake/mod.ts";
import { color as linx } from "apps/linx/mod.ts";
import { color as nuvemshop } from "apps/nuvemshop/mod.ts";
import { Section } from "deco/blocks/section.ts";
import { App } from "deco/mod.ts";
import type { App as A, AppContext as AC } from "deco/mod.ts";
import { rgb24 } from "std/fmt/colors.ts";
import manifest, { Manifest } from "../manifest.gen.ts";

Expand All @@ -31,6 +31,9 @@ export type Platform =

export let _platform: Platform = "custom";

export type App = ReturnType<typeof Site>;
export type AppContext = AC<App>;

const color = (platform: string) => {
switch (platform) {
case "vtex":
Expand All @@ -56,7 +59,7 @@ let firstRun = true;

export default function Site(
{ theme, ...state }: Props,
): App<Manifest, Props, [ReturnType<typeof commerce>]> {
): A<Manifest, Props, [ReturnType<typeof commerce>]> {
_platform = state.platform || state.commerce?.platform || "custom";

// Prevent console.logging twice
Expand Down
31 changes: 29 additions & 2 deletions components/product/ProductGallery.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,11 @@ import ProductCard, {
Layout as CardLayout,
} from "$store/components/product/ProductCard.tsx";
import { usePlatform } from "$store/sdk/usePlatform.tsx";
import { Product } from "apps/commerce/types.ts";
import { PageInfo, Product, ProductListingPage } from "apps/commerce/types.ts";
import ShowMore from "$store/islands/ShowMore.tsx";
import { Head } from "$fresh/runtime.ts";
import { Format } from "$store/components/search/SearchResult.tsx";
import { Resolved } from "deco/engine/core/resolver.ts";

export interface Columns {
mobile?: 1 | 2;
Expand All @@ -11,10 +15,13 @@ export interface Columns {

export interface Props {
products: Product[] | null;
pageInfo: PageInfo;
loaderProps: Resolved<ProductListingPage | null>;
offset: number;
layout?: {
card?: CardLayout;
columns?: Columns;
format?: Format;
};
}

Expand All @@ -30,7 +37,9 @@ const DESKTOP_COLUMNS = {
5: "sm:grid-cols-5",
};

function ProductGallery({ products, layout, offset }: Props) {
function ProductGallery(
{ products, pageInfo, layout, offset, loaderProps }: Props,
) {
const platform = usePlatform();
const mobile = MOBILE_COLUMNS[layout?.columns?.mobile ?? 2];
const desktop = DESKTOP_COLUMNS[layout?.columns?.desktop ?? 4];
Expand All @@ -46,6 +55,24 @@ function ProductGallery({ products, layout, offset }: Props) {
platform={platform}
/>
))}

<Head>
{pageInfo.nextPage && <link rel="next" href={pageInfo.nextPage} />}
{pageInfo.previousPage && (
<link rel="prev" href={pageInfo.previousPage} />
)}
</Head>

{(layout && layout?.format === "Show More") && (
<>
<ShowMore
pageInfo={pageInfo}
layout={layout}
platform={platform}
loaderProps={loaderProps}
/>
</>
)}
</div>
);
}
Expand Down
95 changes: 67 additions & 28 deletions components/search/SearchResult.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,11 @@ import { useOffer } from "$store/sdk/useOffer.ts";
import type { ProductListingPage } from "apps/commerce/types.ts";
import { mapProductToAnalyticsItem } from "apps/commerce/utils/productToAnalyticsItem.ts";
import ProductGallery, { Columns } from "../product/ProductGallery.tsx";
import { Resolved } from "deco/engine/core/resolver.ts";
import { AppContext } from "$store/apps/site.ts";
import type { SectionProps } from "deco/types.ts";

export type Format = "Show More" | "Pagination";

export interface Layout {
/**
Expand All @@ -18,11 +23,15 @@ export interface Layout {
* @description Number of products per line on grid
*/
columns?: Columns;
/**
* @description Format of the pagination
*/
format?: Format;
}

export interface Props {
/** @title Integration */
page: ProductListingPage | null;
page: Resolved<ProductListingPage | null>;
layout?: Layout;
cardLayout?: CardLayout;

Expand All @@ -43,9 +52,16 @@ function Result({
layout,
cardLayout,
startingPage = 0,
}: Omit<Props, "page"> & { page: ProductListingPage }) {
loaderProps,
}: Omit<Props, "page"> & {
page: ProductListingPage;
layout?: Layout;
loaderProps: Resolved<ProductListingPage | null>;
}) {
const { products, filters, breadcrumb, pageInfo, sortOptions } = page;
const perPage = pageInfo.recordPerPage || products.length;
const perPage = pageInfo?.recordPerPage || products.length;

const { format = "Show More" } = layout ?? {};

const id = useId();

Expand All @@ -72,34 +88,38 @@ function Result({
<ProductGallery
products={products}
offset={offset}
layout={{ card: cardLayout, columns: layout?.columns }}
layout={{ card: cardLayout, columns: layout?.columns, format }}
pageInfo={pageInfo}
loaderProps={loaderProps}
/>
</div>
</div>

<div class="flex justify-center my-4">
<div class="join">
<a
aria-label="previous page link"
rel="prev"
href={pageInfo.previousPage ?? "#"}
class="btn btn-ghost join-item"
>
<Icon id="ChevronLeft" size={24} strokeWidth={2} />
</a>
<span class="btn btn-ghost join-item">
Page {zeroIndexedOffsetPage + 1}
</span>
<a
aria-label="next page link"
rel="next"
href={pageInfo.nextPage ?? "#"}
class="btn btn-ghost join-item"
>
<Icon id="ChevronRight" size={24} strokeWidth={2} />
</a>
{format == "Pagination" && (
<div class="flex justify-center my-4">
<div class="join">
<a
aria-label="previous page link"
rel="prev"
href={pageInfo.previousPage ?? "#"}
class="btn btn-ghost join-item"
>
<Icon id="ChevronLeft" size={24} strokeWidth={2} />
</a>
<span class="btn btn-ghost join-item">
Page {zeroIndexedOffsetPage + 1}
</span>
<a
aria-label="next page link"
rel="next"
href={pageInfo.nextPage ?? "#"}
class="btn btn-ghost join-item"
>
<Icon id="ChevronRight" size={24} strokeWidth={2} />
</a>
</div>
</div>
</div>
)}
</div>
<SendEventOnView
id={id}
Expand All @@ -124,12 +144,31 @@ function Result({
);
}

function SearchResult({ page, ...props }: Props) {
function SearchResult(
{ page, loaderProps, ...props }: SectionProps<typeof loader>,
) {
if (!page) {
return <NotFound />;
}

return <Result {...props} page={page} />;
return <Result {...props} page={page} loaderProps={loaderProps} />;
}

export const loader = async (props: Props, _req: Request, ctx: AppContext) => {
const page = await ctx.invoke(
// deno-lint-ignore no-explicit-any
props.page.__resolveType as any,
// deno-lint-ignore no-explicit-any
props.page as any,
) as ProductListingPage | null;

return {
...props,
page,
loaderProps: {
...props.page,
},
};
};

export default SearchResult;
2 changes: 2 additions & 0 deletions components/search/Sort.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@ import { ProductListingPage } from "apps/commerce/types.ts";
import type { JSX } from "preact";

const SORT_QUERY_PARAM = "sort";
const PAGE_QUERY_PARAM = "page";

const useSort = () =>
useMemo(() => {
Expand All @@ -18,6 +19,7 @@ const applySort = (e: JSX.TargetedEvent<HTMLSelectElement, Event>) => {
globalThis.window.location.search,
);

urlSearchParams.delete(PAGE_QUERY_PARAM);
urlSearchParams.set(SORT_QUERY_PARAM, e.currentTarget.value);
globalThis.window.location.search = urlSearchParams.toString();
};
Expand Down
22 changes: 21 additions & 1 deletion components/wishlist/WishlistGallery.tsx
Original file line number Diff line number Diff line change
@@ -1,10 +1,13 @@
import SearchResult, {
Props as SearchResultProps,
} from "$store/components/search/SearchResult.tsx";
import { ProductListingPage } from "apps/commerce/types.ts";
import { AppContext } from "$store/apps/site.ts";
import { SectionProps } from "deco/mod.ts";

export type Props = SearchResultProps;

function WishlistGallery(props: Props) {
function WishlistGallery(props: SectionProps<typeof loader>) {
const isEmpty = !props.page || props.page.products.length === 0;

if (isEmpty) {
Expand All @@ -24,4 +27,21 @@ function WishlistGallery(props: Props) {
return <SearchResult {...props} />;
}

export const loader = async (props: Props, _req: Request, ctx: AppContext) => {
const page = await ctx.invoke(
// deno-lint-ignore no-explicit-any
props.page.__resolveType as any,
// deno-lint-ignore no-explicit-any
props.page as any,
) as ProductListingPage | null;

return {
...props,
page,
loaderProps: {
...props.page,
},
};
};

export default WishlistGallery;
1 change: 1 addition & 0 deletions deno.json
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
{
"imports": {
"$store/": "./",
"deco/": "https://denopkg.com/deco-cx/[email protected]/",
"deco/": "https://denopkg.com/deco-cx/[email protected]/",
"apps/": "https://denopkg.com/deco-cx/[email protected]/",
"$fresh/": "https://deno.land/x/[email protected]/",
Expand Down
2 changes: 2 additions & 0 deletions fresh.gen.ts
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,7 @@ import * as $OutOfStock from "./islands/OutOfStock.tsx";
import * as $ProductImageZoom from "./islands/ProductImageZoom.tsx";
import * as $SearchControls from "./islands/SearchControls.tsx";
import * as $ShippingSimulation from "./islands/ShippingSimulation.tsx";
import * as $ShowMore from "./islands/ShowMore.tsx";
import * as $SliderJS from "./islands/SliderJS.tsx";
import * as $WishlistButton_vtex from "./islands/WishlistButton/vtex.tsx";
import * as $WishlistButton_wake from "./islands/WishlistButton/wake.tsx";
Expand Down Expand Up @@ -53,6 +54,7 @@ const manifest = {
"./islands/ProductImageZoom.tsx": $ProductImageZoom,
"./islands/SearchControls.tsx": $SearchControls,
"./islands/ShippingSimulation.tsx": $ShippingSimulation,
"./islands/ShowMore.tsx": $ShowMore,
"./islands/SliderJS.tsx": $SliderJS,
"./islands/WishlistButton/vtex.tsx": $WishlistButton_vtex,
"./islands/WishlistButton/wake.tsx": $WishlistButton_wake,
Expand Down
80 changes: 80 additions & 0 deletions islands/ShowMore.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,80 @@
import { useSignal } from "@preact/signals";
import { invoke } from "$store/runtime.ts";
import { PageInfo, Product, ProductListingPage } from "apps/commerce/types.ts";
import ProductCard from "$store/components/product/ProductCard.tsx";
import { Layout as CardLayout } from "$store/components/product/ProductCard.tsx";
import { Columns } from "$store/components/product/ProductGallery.tsx";
import Spinner from "$store/components/ui/Spinner.tsx";
import { usePlatform } from "$store/sdk/usePlatform.tsx";
import { Resolved } from "deco/engine/core/resolver.ts";

export interface Props {
pageInfo: PageInfo;
layout?: {
card?: CardLayout;
columns?: Columns;
};
platform: ReturnType<typeof usePlatform>;
loaderProps: Resolved<ProductListingPage | null>;
}

export default function ShowMore(
{ pageInfo, layout, platform, loaderProps }: Props,
) {
const products = useSignal<Array<Product | null>>([]);
const nextPage = useSignal(pageInfo.nextPage);
const loading = useSignal(false);

const handleLoadMore = async () => {
loading.value = true;

const url = new URL(
window.location.origin + window.location.pathname + nextPage.value,
);

// Figure out a better way to type this loader
// deno-lint-ignore no-explicit-any
const invokePayload: any = {
key: loaderProps.__resolveType,
props: {
...loaderProps,
__resolveType: undefined,
pageHref: url.href,
},
};

const page = await invoke(invokePayload) as ProductListingPage | null;

loading.value = false;

if (page) {
window.history.pushState({}, "", nextPage.value);
nextPage.value = page.pageInfo.nextPage;
products.value = [...products.value, ...page.products];
}
};

return (
<>
{products.value.map((product, index) => {
if (!product) return null;
return (
<ProductCard
product={product}
preload={index === 0}
layout={layout?.card}
platform={platform}
/>
);
})}
{nextPage.value && (
<button
onClick={handleLoadMore}
class="btn w-auto mx-auto col-span-full"
>
{loading.value ? <Spinner /> : "Show More"}
</button>
)}
</>
);
}
Loading

0 comments on commit 0a67570

Please sign in to comment.