Skip to content

Commit

Permalink
Admin-powered product details
Browse files Browse the repository at this point in the history
tlgimenes committed Oct 14, 2023
1 parent f1cd9dc commit 340fe3d
Showing 21 changed files with 772 additions and 238 deletions.
50 changes: 50 additions & 0 deletions components/product/Gallery/FrontBack.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,50 @@
import { ProductDetailsPage } from "apps/commerce/types.ts";
import Image from "apps/website/components/Image.tsx";

export interface Props {
/** @title Integration */
page: ProductDetailsPage | null;

layout: {
width: number;
height: number;
};
}

/**
* @title Product Image Front Back
* @description Renders two images side by side both on mobile and on desktop. On mobile, the overflow is reached causing a scrollbar to be rendered.
*/
function GalleryFrontBack(props: Props) {
if (props.page === null) {
throw new Error("Missing Product Details Page Info");
}

const {
page: { product: { image: images = [] } },
layout: { width, height },
} = props;
const aspectRatio = `${width} / ${height}`;

return (
<ul class="carousel carousel-center gap-6">
{[images[0], images[1] ?? images[0]].map((img, index) => (
<li class="carousel-item min-w-[100vw] sm:min-w-[24vw]">
<Image
sizes="(max-width: 640px) 100vw, 24vw"
style={{ aspectRatio }}
src={img.url!}
alt={img.alternateName}
width={width}
height={height}
// Preload LCP image for better web vitals
preload={index === 0}
loading={index === 0 ? "eager" : "lazy"}
/>
</li>
))}
</ul>
);
}

export default GalleryFrontBack;
108 changes: 108 additions & 0 deletions components/product/Gallery/ImageSlider.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,108 @@
import Icon from "$store/components/ui/Icon.tsx";
import Slider from "$store/components/ui/Slider.tsx";
import ProductImageZoom from "$store/islands/ProductImageZoom.tsx";
import SliderJS from "$store/islands/SliderJS.tsx";
import { useId } from "$store/sdk/useId.ts";
import { ProductDetailsPage } from "apps/commerce/types.ts";
import Image from "apps/website/components/Image.tsx";

export interface Props {
/** @title Integration */
page: ProductDetailsPage | null;

layout: {
width: number;
height: number;
};
}

/**
* @title Product Image Slider
* @description Creates a three columned grid on destkop, one for the dots preview, one for the image slider and the other for product info
* On mobile, there's one single column with 3 rows. Note that the orders are different from desktop to mobile, that's why
* we rearrange each cell with col-start- directives
*/
export default function GallerySlider(props: Props) {
const id = useId();

if (props.page === null) {
throw new Error("Missing Product Details Page Info");
}

const {
page: { product: { image: images = [] } },
layout: { width, height },
} = props;
const aspectRatio = `${width} / ${height}`;

return (
<div id={id} class="grid grid-flow-row sm:grid-flow-col">
{/* Image Slider */}
<div class="relative order-1 sm:order-2">
<Slider class="carousel carousel-center gap-6 w-screen sm:w-[40vw]">
{images.map((img, index) => (
<Slider.Item
index={index}
class="carousel-item w-full"
>
<Image
class="w-full"
sizes="(max-width: 640px) 100vw, 40vw"
style={{ aspectRatio }}
src={img.url!}
alt={img.alternateName}
width={width}
height={height}
// Preload LCP image for better web vitals
preload={index === 0}
loading={index === 0 ? "eager" : "lazy"}
/>
</Slider.Item>
))}
</Slider>

<Slider.PrevButton
class="no-animation absolute left-2 top-1/2 btn btn-circle btn-outline"
disabled
>
<Icon size={24} id="ChevronLeft" strokeWidth={3} />
</Slider.PrevButton>

<Slider.NextButton
class="no-animation absolute right-2 top-1/2 btn btn-circle btn-outline"
disabled={images.length < 2}
>
<Icon size={24} id="ChevronRight" strokeWidth={3} />
</Slider.NextButton>

<div class="absolute top-2 right-2 bg-base-100 rounded-full">
<ProductImageZoom
images={images}
width={700}
height={Math.trunc(700 * height / width)}
/>
</div>
</div>

{/* Dots */}
<ul class="carousel carousel-center gap-1 px-4 sm:px-0 sm:flex-col order-2 sm:order-1">
{images.map((img, index) => (
<li class="carousel-item min-w-[63px] sm:min-w-[100px]">
<Slider.Dot index={index}>
<Image
style={{ aspectRatio }}
class="group-disabled:border-base-300 border rounded "
width={63}
height={87.5}
src={img.url!}
alt={img.alternateName}
/>
</Slider.Dot>
</li>
))}
</ul>

<SliderJS rootId={id} />
</div>
);
}
Original file line number Diff line number Diff line change
@@ -1,71 +1,39 @@
import { SendEventOnLoad } from "$store/components/Analytics.tsx";
import Breadcrumb from "$store/components/ui/Breadcrumb.tsx";
import Button from "$store/components/ui/Button.tsx";
import Icon from "$store/components/ui/Icon.tsx";
import Slider from "$store/components/ui/Slider.tsx";
import AddToCartButtonLinx from "$store/islands/AddToCartButton/linx.tsx";
import AddToCartButtonShopify from "$store/islands/AddToCartButton/shopify.tsx";
import AddToCartButtonVNDA from "$store/islands/AddToCartButton/vnda.tsx";
import AddToCartButtonVTEX from "$store/islands/AddToCartButton/vtex.tsx";
import AddToCartButtonWake from "$store/islands/AddToCartButton/wake.tsx";
import AddToCartButtonLinx from "$store/islands/AddToCartButton/linx.tsx";
import AddToCartButtonShopify from "$store/islands/AddToCartButton/shopify.tsx";
import OutOfStock from "$store/islands/OutOfStock.tsx";
import ProductImageZoom from "$store/islands/ProductImageZoom.tsx";
import ShippingSimulation from "$store/islands/ShippingSimulation.tsx";
import SliderJS from "$store/islands/SliderJS.tsx";
import WishlistButton from "$store/islands/WishlistButton.tsx";
import { formatPrice } from "$store/sdk/format.ts";
import { useId } from "$store/sdk/useId.ts";
import { useOffer } from "$store/sdk/useOffer.ts";
import { usePlatform } from "$store/sdk/usePlatform.tsx";
import type { ProductDetailsPage } from "apps/commerce/types.ts";
import { ProductDetailsPage } from "apps/commerce/types.ts";
import { mapProductToAnalyticsItem } from "apps/commerce/utils/productToAnalyticsItem.ts";
import Image from "apps/website/components/Image.tsx";
import ProductSelector from "./ProductVariantSelector.tsx";

export interface Props {
/** @title Integration */
interface Props {
page: ProductDetailsPage | null;

layout?: {
/**
* @title Product Image
* @description How the main product image will be displayed
* @default slider
*/
image?: "front-back" | "slider";
layout: {
/**
* @title Product Name
* @description How product title will be displayed. Concat to concatenate product and sku names.
* @default product
*/
name?: "concat" | "productGroup" | "product";
};

id?: string
}

const WIDTH = 360;
const HEIGHT = 360;
const ASPECT_RATIO = `${WIDTH} / ${HEIGHT}`;
function ProductInfo({ page, layout }: Props) {
const platform = usePlatform();

/**
* Rendered when a not found is returned by any of the loaders run on this page
*/
function NotFound() {
return (
<div class="w-full flex justify-center items-center py-28">
<div class="flex flex-col items-center justify-center gap-6">
<span class="font-medium text-2xl">Página não encontrada</span>
<a href="/">
<Button>Voltar à página inicial</Button>
</a>
</div>
</div>
);
}
if (page === null) {
throw new Error("Missing Product Details Page Info");
}

function ProductInfo({ page, layout, id }: { page: ProductDetailsPage } & Props) {
const platform = usePlatform();
const {
breadcrumbList,
product,
@@ -90,7 +58,7 @@ function ProductInfo({ page, layout, id }: { page: ProductDetailsPage } & Props)
const discount = price && listPrice ? listPrice - price : 0;

return (
<>
<div class="flex flex-col">
{/* Breadcrumb */}
<Breadcrumb
itemListElement={breadcrumbList?.itemListElement.slice(0, -1)}
@@ -132,7 +100,7 @@ function ProductInfo({ page, layout, id }: { page: ProductDetailsPage } & Props)
</div>
{/* Sku Selector */}
<div class="mt-4 sm:mt-6">
<ProductSelector product={product} id={id} />
<ProductSelector product={product} />
</div>
{/* Add to Cart and Favorites button */}
<div class="mt-4 sm:mt-10 flex flex-col gap-2">
@@ -239,145 +207,8 @@ function ProductInfo({ page, layout, id }: { page: ProductDetailsPage } & Props)
},
}}
/>
</>
);
}

function Details(props: { page: ProductDetailsPage } & Props) {
const id = useId();
const { page: { product: { image: images = [] } }, layout } = props;
const variant = layout?.image ?? "slider";

/**
* Product slider variant
*
* Creates a three columned grid on destkop, one for the dots preview, one for the image slider and the other for product info
* On mobile, there's one single column with 3 rows. Note that the orders are different from desktop to mobile, that's why
* we rearrange each cell with col-start- directives
*/
if (variant === "slider") {
return (
<>
<div
id={id}
class="grid grid-cols-1 gap-4 sm:grid-cols-[max-content_40vw_40vw] sm:grid-rows-1 sm:justify-center"
>
{/* Image Slider */}
<div class="relative sm:col-start-2 sm:col-span-1 sm:row-start-1">
<Slider class="carousel carousel-center gap-6 w-screen sm:w-[40vw]">
{images.map((img, index) => (
<Slider.Item
index={index}
class="carousel-item w-full"
>
<Image
class="w-full"
sizes="(max-width: 640px) 100vw, 40vw"
style={{ aspectRatio: ASPECT_RATIO }}
src={img.url!}
alt={img.alternateName}
width={WIDTH}
height={HEIGHT}
// Preload LCP image for better web vitals
preload={index === 0}
loading={index === 0 ? "eager" : "lazy"}
/>
</Slider.Item>
))}
</Slider>

<Slider.PrevButton
class="no-animation absolute left-2 top-1/2 btn btn-circle btn-outline"
disabled
>
<Icon size={24} id="ChevronLeft" strokeWidth={3} />
</Slider.PrevButton>

<Slider.NextButton
class="no-animation absolute right-2 top-1/2 btn btn-circle btn-outline"
disabled={images.length < 2}
>
<Icon size={24} id="ChevronRight" strokeWidth={3} />
</Slider.NextButton>

<div class="absolute top-2 right-2 bg-base-100 rounded-full">
<ProductImageZoom
images={images}
width={700}
height={Math.trunc(700 * HEIGHT / WIDTH)}
/>
</div>
</div>

{/* Dots */}
<ul class="flex gap-2 sm:justify-start overflow-auto px-4 sm:px-0 sm:flex-col sm:col-start-1 sm:col-span-1 sm:row-start-1">
{images.map((img, index) => (
<li class="min-w-[63px] sm:min-w-[100px]">
<Slider.Dot index={index}>
<Image
style={{ aspectRatio: ASPECT_RATIO }}
class="group-disabled:border-base-300 border rounded "
width={63}
height={87.5}
src={img.url!}
alt={img.alternateName}
/>
</Slider.Dot>
</li>
))}
</ul>

{/* Product Info */}
<div class="px-4 sm:pr-0 sm:pl-6 sm:col-start-3 sm:col-span-1 sm:row-start-1">
<ProductInfo {...props} />
</div>
</div>
<SliderJS rootId={id}></SliderJS>
</>
);
}

/**
* Product front-back variant.
*
* Renders two images side by side both on mobile and on desktop. On mobile, the overflow is
* reached causing a scrollbar to be rendered.
*/
return (
<div class="grid grid-cols-1 gap-4 sm:grid-cols-[50vw_25vw] sm:grid-rows-1 sm:justify-center">
{/* Image slider */}
<ul class="carousel carousel-center gap-6">
{[images[0], images[1] ?? images[0]].map((img, index) => (
<li class="carousel-item min-w-[100vw] sm:min-w-[24vw]">
<Image
sizes="(max-width: 640px) 100vw, 24vw"
style={{ aspectRatio: ASPECT_RATIO }}
src={img.url!}
alt={img.alternateName}
width={WIDTH}
height={HEIGHT}
// Preload LCP image for better web vitals
preload={index === 0}
loading={index === 0 ? "eager" : "lazy"}
/>
</li>
))}
</ul>

{/* Product Info */}
<div class="px-4 sm:pr-0 sm:pl-6">
<ProductInfo {...props} />
</div>
</div>
);
}

function ProductDetails({ page, layout, id }: Props) {
return (
<div class="container py-0 sm:py-10">
{page ? <Details page={page} layout={layout} id={id} /> : <NotFound />}
</div>
);
}

export default ProductDetails;
export default ProductInfo;
5 changes: 2 additions & 3 deletions components/product/ProductVariantSelector.tsx
Original file line number Diff line number Diff line change
@@ -5,10 +5,9 @@ import { usePartial } from "apps/website/hooks/usePartial.ts";

interface Props {
product: Product;
id?: string;
}

function VariantSelector({ product, id }: Props) {
function VariantSelector({ product }: Props) {
const { url, isVariantOf } = product;
const hasVariant = isVariantOf?.hasVariant ?? [];
const possibilities = useVariantPossibilities(hasVariant, product);
@@ -20,7 +19,7 @@ function VariantSelector({ product, id }: Props) {
<span class="text-sm">{name}</span>
<ul class="flex flex-row gap-3">
{Object.entries(possibilities[name]).map(([value, link]) => {
const partial = usePartial({ id, href: link });
const partial = usePartial({ href: link });

return (
<li>
247 changes: 247 additions & 0 deletions constants.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,247 @@
// TODO: Support Preact's VNode
// deno-lint-ignore no-explicit-any
export type VNode = any;

export const grid = {
gap: {
mobile: {
"1": "gap-1",
"2": "gap-2",
"4": "gap-4",
"8": "gap-8",
"12": "gap-12",
"16": "gap-16",
},
desktop: {
"1": "sm:gap-1",
"2": "sm:gap-2",
"4": "sm:gap-4",
"8": "sm:gap-8",
"12": "sm:gap-12",
"16": "sm:gap-16",
},
},
cols: {
mobile: {
"1": "grid-cols-1",
"2": "grid-cols-2",
"3": "grid-cols-3",
"4": "grid-cols-4",
"5": "grid-cols-5",
"6": "grid-cols-6",
"7": "grid-cols-7",
"8": "grid-cols-8",
"9": "grid-cols-9",
"10": "grid-cols-10",
"11": "grid-cols-11",
"12": "grid-cols-12",
"none": "grid-cols-none",
},
desktop: {
"1": "sm:grid-cols-1",
"2": "sm:grid-cols-2",
"3": "sm:grid-cols-3",
"4": "sm:grid-cols-4",
"5": "sm:grid-cols-5",
"6": "sm:grid-cols-6",
"7": "sm:grid-cols-7",
"8": "sm:grid-cols-8",
"9": "sm:grid-cols-9",
"10": "sm:grid-cols-10",
"11": "sm:grid-cols-11",
"12": "sm:grid-cols-12",
"none": "sm:grid-cols-none",
},
},
rows: {
mobile: {
"1": "grid-cols-1",
"2": "grid-cols-2",
"3": "grid-cols-3",
"4": "grid-cols-4",
"5": "grid-cols-5",
"6": "grid-cols-6",
"none": "grid-cols-none",
},
desktop: {
"1": "sm:grid-cols-1",
"2": "sm:grid-cols-2",
"3": "sm:grid-cols-3",
"4": "sm:grid-cols-4",
"5": "sm:grid-cols-5",
"6": "sm:grid-cols-6",
"none": "sm:grid-cols-none",
},
},
flow: {
mobile: {
"col": "grid-flow-col",
"row": "grid-flow-row",
"dense": "grid-flow-dense",
"col-dense": "grid-flow-col-dense",
"row-dense": "grid-flow-row-dense",
},
desktop: {
"col": "sm:grid-flow-col",
"row": "sm:grid-flow-row",
"dense": "sm:grid-flow-dense",
"col-dense": "sm:grid-flow-col-dense",
"row-dense": "sm:grid-flow-row-dense",
},
},
placeItems: {
mobile: {
"center": "place-items-center",
"start": "place-items-start",
"end": "place-items-end",
"baseline": "place-items-baseline",
"stretch": "place-items-stretch",
},
desktop: {
"center": "sm:place-items-center",
"start": "sm:place-items-start",
"end": "sm:place-items-end",
"baseline": "sm:place-items-baseline",
"stretch": "sm:place-items-stretch",
},
},
rowStart: {
mobile: {
"1": "row-start-1",
"2": "row-start-2",
"3": "row-start-3",
"4": "row-start-4",
"5": "row-start-5",
"6": "row-start-6",
"7": "row-start-7",
"auto": "row-start-auto",
},
desktop: {
"1": "sm:row-start-1",
"2": "sm:row-start-2",
"3": "sm:row-start-3",
"4": "sm:row-start-4",
"5": "sm:row-start-5",
"6": "sm:row-start-6",
"7": "sm:row-start-7",
"auto": "sm:row-start-auto",
},
},
rowSpan: {
mobile: {
"1": "row-span-1",
"2": "row-span-2",
"3": "row-span-3",
"4": "row-span-4",
"5": "row-span-5",
"6": "row-span-6",
"full": "row-span-full",
},
desktop: {
"1": "sm:row-span-1",
"2": "sm:row-span-2",
"3": "sm:row-span-3",
"4": "sm:row-span-4",
"5": "sm:row-span-5",
"6": "sm:row-span-6",
"full": "sm:row-span-full",
},
},
colStart: {
mobile: {
"1": "col-start-1",
"2": "col-start-2",
"3": "col-start-3",
"4": "col-start-4",
"5": "col-start-5",
"6": "col-start-6",
"7": "col-start-7",
"auto": "col-start-auto",
},
desktop: {
"1": "sm:col-start-1",
"2": "sm:col-start-2",
"3": "sm:col-start-3",
"4": "sm:col-start-4",
"5": "sm:col-start-5",
"6": "sm:col-start-6",
"7": "sm:col-start-7",
"auto": "sm:col-start-auto",
},
},
colSpan: {
mobile: {
"1": "col-span-1",
"2": "col-span-2",
"3": "col-span-3",
"4": "col-span-4",
"5": "col-span-5",
"6": "col-span-6",
"full": "col-span-full",
},
desktop: {
"1": "sm:col-span-1",
"2": "sm:col-span-2",
"3": "sm:col-span-3",
"4": "sm:col-span-4",
"5": "sm:col-span-5",
"6": "sm:col-span-6",
"full": "sm:col-span-full",
},
},
};

export const flex = {
gap: {
mobile: {
"1": "gap-1",
"2": "gap-2",
"4": "gap-4",
"8": "gap-8",
"12": "gap-12",
"16": "gap-16",
},
desktop: {
"1": "sm:gap-1",
"2": "sm:gap-2",
"4": "sm:gap-4",
"8": "sm:gap-8",
"12": "sm:gap-12",
"16": "sm:gap-16",
},
},
direction: {
mobile: {
"row": "flex-row",
"col": "flex-col",
},
desktop: {
"row": "sm:flex-row",
"col": "sm:flex-col",
},
},
justify: {
mobile: {
"center": "justify-center",
"start": "justify-start",
"end": "justify-end",
},
desktop: {
"center": "sm:justify-center",
"start": "sm:justify-start",
"end": "sm:justify-end",
},
},
wrap: {
mobile: {
"wrap": "flex-wrap",
"nowrap": "flex-nowrap",
"wrap-reverse": "flex-wrap-reverse",
},
desktop: {
"wrap": "sm:flex-wrap",
"nowrap": "sm:flex-nowrap",
"wrap-reverse": "flex-wrap-reverse",
},
},
};
2 changes: 1 addition & 1 deletion deno.json
Original file line number Diff line number Diff line change
@@ -2,7 +2,7 @@
"imports": {
"$store/": "./",
"deco/": "https://denopkg.com/deco-cx/deco@1.41.9/",
"apps/": "https://denopkg.com/deco-cx/apps@0.13.21/",
"apps/": "https://denopkg.com/deco-cx/apps@0.14.1/",
"$fresh/": "https://denopkg.com/deco-cx/fresh@1.4.4/",
"preact": "https://esm.sh/preact@10.15.1",
"preact/": "https://esm.sh/preact@10.15.1/",
16 changes: 16 additions & 0 deletions loaders/List/Sections.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
import type { Section } from "deco/blocks/section.ts";
import type { VNode } from "../../constants.tsx";

interface Props {
sections: Section[] | null;
}

function Sections({ sections }: Props): VNode[] | null {
if (sections === null) {
return null;
}

return sections.map(({ Component, props }) => <Component {...props} />);
}

export default Sections;
121 changes: 71 additions & 50 deletions manifest.gen.ts
Original file line number Diff line number Diff line change
@@ -3,64 +3,85 @@
// This file is automatically updated during development when running `dev.ts`.

import * as $$$0 from "./loaders/Layouts/ProductCard.tsx";
import * as $$$$$$0 from "./sections/Footer/Footer.tsx";
import * as $$$$$$1 from "./sections/Category/CategoryBanner.tsx";
import * as $$$$$$2 from "./sections/Category/CategoryList.tsx";
import * as $$$$$$3 from "./sections/Images/ShoppableBanner.tsx";
import * as $$$$$$4 from "./sections/Images/BannerGrid.tsx";
import * as $$$$$$5 from "./sections/Images/ImageGallery.tsx";
import * as $$$$$$6 from "./sections/Images/Carousel.tsx";
import * as $$$$$$7 from "./sections/Content/Testimonials.tsx";
import * as $$$$$$8 from "./sections/Content/Logos.tsx";
import * as $$$$$$9 from "./sections/Content/Faq.tsx";
import * as $$$$$$10 from "./sections/Content/Benefits.tsx";
import * as $$$$$$11 from "./sections/Product/Wishlist.tsx";
import * as $$$$$$12 from "./sections/Product/SearchResult.tsx";
import * as $$$$$$13 from "./sections/Product/ProductShelf.tsx";
import * as $$$$$$14 from "./sections/Product/ProductShelfTabbed.tsx";
import * as $$$$$$15 from "./sections/Product/ProductDetails.tsx";
import * as $$$$$$16 from "./sections/Miscellaneous/CampaignTimer.tsx";
import * as $$$$$$17 from "./sections/Miscellaneous/CookieConsent.tsx";
import * as $$$$$$18 from "./sections/Social/WhatsApp.tsx";
import * as $$$$$$19 from "./sections/Social/InstagramPosts.tsx";
import * as $$$$$$20 from "./sections/Theme/Theme.tsx";
import * as $$$$$$21 from "./sections/Links/LinkTree.tsx";
import * as $$$$$$22 from "./sections/Links/Shortcuts.tsx";
import * as $$$$$$23 from "./sections/Newsletter/Newsletter.tsx";
import * as $$$$$$24 from "./sections/Header/Header.tsx";
import * as $$$1 from "./loaders/List/Sections.tsx";
import * as $$$$$$0 from "./sections/Gallery.tsx";
import * as $$$$$$1 from "./sections/Footer/Footer.tsx";
import * as $$$$$$2 from "./sections/Category/CategoryBanner.tsx";
import * as $$$$$$3 from "./sections/Category/CategoryList.tsx";
import * as $$$$$$4 from "./sections/Images/ShoppableBanner.tsx";
import * as $$$$$$5 from "./sections/Images/BannerGrid.tsx";
import * as $$$$$$6 from "./sections/Images/ImageGallery.tsx";
import * as $$$$$$7 from "./sections/Images/Carousel.tsx";
import * as $$$$$$8 from "./sections/Layout/Container.tsx";
import * as $$$$$$9 from "./sections/Layout/Grid.tsx";
import * as $$$$$$10 from "./sections/Layout/Flex.tsx";
import * as $$$$$$11 from "./sections/Layout/GridItem.tsx";
import * as $$$$$$12 from "./sections/Content/Testimonials.tsx";
import * as $$$$$$13 from "./sections/Content/Logos.tsx";
import * as $$$$$$14 from "./sections/Content/Faq.tsx";
import * as $$$$$$15 from "./sections/Content/Benefits.tsx";
import * as $$$$$$16 from "./sections/Product/Wishlist.tsx";
import * as $$$$$$17 from "./sections/Product/NotFoundChallenge.tsx";
import * as $$$$$$18 from "./sections/Product/SearchResult.tsx";
import * as $$$$$$19 from "./sections/Product/ProductInfo.tsx";
import * as $$$$$$20 from "./sections/Product/ProductShelf.tsx";
import * as $$$$$$21 from "./sections/Product/ImageGallerySlider.tsx";
import * as $$$$$$22 from "./sections/Product/ImageGalleryFrontBack.tsx";
import * as $$$$$$23 from "./sections/Product/ProductShelfTabbed.tsx";
import * as $$$$$$24 from "./sections/Product/NotFound.tsx";
import * as $$$$$$25 from "./sections/Miscellaneous/CampaignTimer.tsx";
import * as $$$$$$26 from "./sections/Miscellaneous/CookieConsent.tsx";
import * as $$$$$$27 from "./sections/Social/WhatsApp.tsx";
import * as $$$$$$28 from "./sections/Social/InstagramPosts.tsx";
import * as $$$$$$29 from "./sections/Theme/Theme.tsx";
import * as $$$$$$30 from "./sections/Links/LinkTree.tsx";
import * as $$$$$$31 from "./sections/Links/Shortcuts.tsx";
import * as $$$$$$32 from "./sections/Newsletter/Newsletter.tsx";
import * as $$$$$$33 from "./sections/Header/Header.tsx";
import * as $$$$$$$$$$$0 from "./apps/decohub.ts";
import * as $$$$$$$$$$$1 from "./apps/site.ts";

const manifest = {
"loaders": {
"deco-sites/storefront/loaders/Layouts/ProductCard.tsx": $$$0,
"deco-sites/storefront/loaders/List/Sections.tsx": $$$1,
},
"sections": {
"deco-sites/storefront/sections/Category/CategoryBanner.tsx": $$$$$$1,
"deco-sites/storefront/sections/Category/CategoryList.tsx": $$$$$$2,
"deco-sites/storefront/sections/Content/Benefits.tsx": $$$$$$10,
"deco-sites/storefront/sections/Content/Faq.tsx": $$$$$$9,
"deco-sites/storefront/sections/Content/Logos.tsx": $$$$$$8,
"deco-sites/storefront/sections/Content/Testimonials.tsx": $$$$$$7,
"deco-sites/storefront/sections/Footer/Footer.tsx": $$$$$$0,
"deco-sites/storefront/sections/Header/Header.tsx": $$$$$$24,
"deco-sites/storefront/sections/Images/BannerGrid.tsx": $$$$$$4,
"deco-sites/storefront/sections/Images/Carousel.tsx": $$$$$$6,
"deco-sites/storefront/sections/Images/ImageGallery.tsx": $$$$$$5,
"deco-sites/storefront/sections/Images/ShoppableBanner.tsx": $$$$$$3,
"deco-sites/storefront/sections/Links/LinkTree.tsx": $$$$$$21,
"deco-sites/storefront/sections/Links/Shortcuts.tsx": $$$$$$22,
"deco-sites/storefront/sections/Miscellaneous/CampaignTimer.tsx": $$$$$$16,
"deco-sites/storefront/sections/Miscellaneous/CookieConsent.tsx": $$$$$$17,
"deco-sites/storefront/sections/Newsletter/Newsletter.tsx": $$$$$$23,
"deco-sites/storefront/sections/Product/ProductDetails.tsx": $$$$$$15,
"deco-sites/storefront/sections/Product/ProductShelf.tsx": $$$$$$13,
"deco-sites/storefront/sections/Product/ProductShelfTabbed.tsx": $$$$$$14,
"deco-sites/storefront/sections/Product/SearchResult.tsx": $$$$$$12,
"deco-sites/storefront/sections/Product/Wishlist.tsx": $$$$$$11,
"deco-sites/storefront/sections/Social/InstagramPosts.tsx": $$$$$$19,
"deco-sites/storefront/sections/Social/WhatsApp.tsx": $$$$$$18,
"deco-sites/storefront/sections/Theme/Theme.tsx": $$$$$$20,
"deco-sites/storefront/sections/Category/CategoryBanner.tsx": $$$$$$2,
"deco-sites/storefront/sections/Category/CategoryList.tsx": $$$$$$3,
"deco-sites/storefront/sections/Content/Benefits.tsx": $$$$$$15,
"deco-sites/storefront/sections/Content/Faq.tsx": $$$$$$14,
"deco-sites/storefront/sections/Content/Logos.tsx": $$$$$$13,
"deco-sites/storefront/sections/Content/Testimonials.tsx": $$$$$$12,
"deco-sites/storefront/sections/Footer/Footer.tsx": $$$$$$1,
"deco-sites/storefront/sections/Gallery.tsx": $$$$$$0,
"deco-sites/storefront/sections/Header/Header.tsx": $$$$$$33,
"deco-sites/storefront/sections/Images/BannerGrid.tsx": $$$$$$5,
"deco-sites/storefront/sections/Images/Carousel.tsx": $$$$$$7,
"deco-sites/storefront/sections/Images/ImageGallery.tsx": $$$$$$6,
"deco-sites/storefront/sections/Images/ShoppableBanner.tsx": $$$$$$4,
"deco-sites/storefront/sections/Layout/Container.tsx": $$$$$$8,
"deco-sites/storefront/sections/Layout/Flex.tsx": $$$$$$10,
"deco-sites/storefront/sections/Layout/Grid.tsx": $$$$$$9,
"deco-sites/storefront/sections/Layout/GridItem.tsx": $$$$$$11,
"deco-sites/storefront/sections/Links/LinkTree.tsx": $$$$$$30,
"deco-sites/storefront/sections/Links/Shortcuts.tsx": $$$$$$31,
"deco-sites/storefront/sections/Miscellaneous/CampaignTimer.tsx": $$$$$$25,
"deco-sites/storefront/sections/Miscellaneous/CookieConsent.tsx": $$$$$$26,
"deco-sites/storefront/sections/Newsletter/Newsletter.tsx": $$$$$$32,
"deco-sites/storefront/sections/Product/ImageGalleryFrontBack.tsx":
$$$$$$22,
"deco-sites/storefront/sections/Product/ImageGallerySlider.tsx": $$$$$$21,
"deco-sites/storefront/sections/Product/NotFound.tsx": $$$$$$24,
"deco-sites/storefront/sections/Product/NotFoundChallenge.tsx": $$$$$$17,
"deco-sites/storefront/sections/Product/ProductInfo.tsx": $$$$$$19,
"deco-sites/storefront/sections/Product/ProductShelf.tsx": $$$$$$20,
"deco-sites/storefront/sections/Product/ProductShelfTabbed.tsx": $$$$$$23,
"deco-sites/storefront/sections/Product/SearchResult.tsx": $$$$$$18,
"deco-sites/storefront/sections/Product/Wishlist.tsx": $$$$$$16,
"deco-sites/storefront/sections/Social/InstagramPosts.tsx": $$$$$$28,
"deco-sites/storefront/sections/Social/WhatsApp.tsx": $$$$$$27,
"deco-sites/storefront/sections/Theme/Theme.tsx": $$$$$$29,
},
"apps": {
"deco-sites/storefront/apps/decohub.ts": $$$$$$$$$$$0,
3 changes: 3 additions & 0 deletions sdk/clx.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
/** filter out nullable values, join and minify class names */
export const clx = (...args: (string | null | undefined | false)[]) =>
args.filter(Boolean).join(" ").replace(/\s\s+/g, " ");
15 changes: 15 additions & 0 deletions sections/Gallery.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
import { Section } from "deco/blocks/section.ts";

interface Props {
children: Section;
}

function Gallery({ children: { Component, props } }: Props) {
return (
<>
<Component {...props} />
</>
);
}

export default Gallery;
15 changes: 15 additions & 0 deletions sections/Layout/Container.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
import { Section } from "deco/blocks/section.ts";

interface Props {
children: Section;
}

function Container({ children }: Props) {
return (
<div class="container">
<children.Component {...children.props} />
</div>
);
}

export default Container;
58 changes: 58 additions & 0 deletions sections/Layout/Flex.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,58 @@
import { clx } from "$store/sdk/clx.ts";
import { Section } from "deco/blocks/section.ts";
import { flex, VNode } from "../../constants.tsx";

interface Props {
children: VNode[] | null;
layout?: {
gap?: {
/** @default 2 */
mobile?: "1" | "2" | "4" | "8" | "12" | "16";
/** @default 4 */
desktop?: "1" | "2" | "4" | "8" | "12" | "16";
};
direction?: {
/** @default row */
mobile?: "col" | "row";
/** @default row */
desktop?: "col" | "row";
};
justify?: {
/** @default center */
mobile?: "center" | "start" | "end";
/** @default center */
desktop?: "center" | "start" | "end";
};
wrap?: {
/** @default wrap */
mobile?: "wrap" | "nowrap" | "wrap-reverse";
/** @default wrap */
desktop?: "wrap" | "nowrap" | "wrap-reverse";
};
};
}

function Section({ layout, children }: Props) {
return (
<div
class={clx(
"flex",
layout?.gap?.mobile && flex.gap.mobile[layout.gap.mobile],
layout?.gap?.desktop && flex.gap.desktop[layout.gap.desktop],
layout?.direction?.mobile &&
flex.direction.mobile[layout.direction.mobile],
layout?.direction?.desktop &&
flex.direction.desktop[layout.direction.desktop],
layout?.justify?.mobile && flex.justify.mobile[layout.justify.mobile],
layout?.justify?.desktop &&
flex.justify.desktop[layout.justify.desktop],
layout?.wrap?.mobile && flex.wrap.mobile[layout.wrap.mobile],
layout?.wrap?.desktop && flex.wrap.desktop[layout.wrap.desktop],
)}
>
{children}
</div>
);
}

export default Section;
85 changes: 85 additions & 0 deletions sections/Layout/Grid.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,85 @@
import { Section } from "deco/blocks/section.ts";
import { grid, VNode } from "../../constants.tsx";
import { clx } from "../../sdk/clx.ts";

interface Props {
children: VNode[] | null;
layout?: {
gap?: {
/** @default 2 */
mobile?: "1" | "2" | "4" | "8" | "12" | "16";
/** @default 4 */
desktop?: "1" | "2" | "4" | "8" | "12" | "16";
};
cols?: {
mobile?:
| "1"
| "2"
| "3"
| "4"
| "5"
| "6"
| "7"
| "8"
| "9"
| "10"
| "11"
| "12"
| "none";
desktop?:
| "1"
| "2"
| "3"
| "4"
| "5"
| "6"
| "7"
| "8"
| "9"
| "10"
| "11"
| "12"
| "none";
};
rows?: {
mobile?: "1" | "2" | "3" | "4" | "5" | "6" | "none";
desktop?: "1" | "2" | "3" | "4" | "5" | "6" | "none";
};
flow?: {
/** @default row */
mobile?: "col" | "row" | "dense" | "col-dense" | "row-dense";
/** @default row */
desktop?: "col" | "row" | "dense" | "col-dense" | "row-dense";
};
placeItems?: {
/** @default center */
mobile?: "center" | "start" | "end" | "baseline" | "stretch";
/** @default center */
desktop?: "center" | "start" | "end" | "baseline" | "stretch";
};
};
}

function Section({ layout, children }: Props) {
return (
<div
class={clx(
"grid",
layout?.gap?.mobile && grid.gap.mobile[layout.gap.mobile],
layout?.gap?.desktop && grid.gap.desktop[layout.gap.desktop],
layout?.cols?.mobile && grid.cols.mobile[layout.cols.mobile],
layout?.cols?.desktop && grid.cols.desktop[layout.cols.desktop],
layout?.flow?.mobile && grid.flow.mobile[layout.flow.mobile],
layout?.flow?.desktop && grid.flow.desktop[layout.flow.desktop],
layout?.placeItems?.mobile &&
grid.placeItems.mobile[layout.placeItems.mobile],
layout?.placeItems?.desktop &&
grid.placeItems.desktop[layout.placeItems.desktop],
)}
>
{children}
</div>
);
}

export default Section;
44 changes: 44 additions & 0 deletions sections/Layout/GridItem.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,44 @@
import { Section } from "deco/blocks/section.ts";
import { clx } from "../../sdk/clx.ts";
import { grid } from "../../constants.tsx";

interface Props {
children: Section;
layout?: {
rowStart?: {
mobile?: "1" | "2" | "3" | "4" | "5" | "6" | "7" | "auto";
desktop?: "1" | "2" | "3" | "4" | "5" | "6" | "7" | "auto";
};
rowSpan?: {
mobile?: "1" | "2" | "3" | "4" | "5" | "6" | "full";
desktop?: "1" | "2" | "3" | "4" | "5" | "6" | "full";
};
colStart?: {
mobile?: "1" | "2" | "3" | "4" | "5" | "6" | "7" | "auto";
desktop?: "1" | "2" | "3" | "4" | "5" | "6" | "7" | "auto";
};
colSpan?: {
mobile?: "1" | "2" | "3" | "4" | "5" | "6" | "full";
desktop?: "1" | "2" | "3" | "4" | "5" | "6" | "full";
};
};
}

function GridItem({ children, layout }: Props) {
return (
<div class={clx(
layout?.rowStart?.mobile && grid.rowStart.mobile[layout.rowStart.mobile],
layout?.rowStart?.desktop && grid.rowStart.desktop[layout.rowStart.desktop],
layout?.rowSpan?.mobile && grid.rowSpan.mobile[layout.rowSpan.mobile],
layout?.rowSpan?.desktop && grid.rowSpan.desktop[layout.rowSpan.desktop],
layout?.colStart?.mobile && grid.colStart.mobile[layout.colStart.mobile],
layout?.colStart?.desktop && grid.colStart.desktop[layout.colStart.desktop],
layout?.colSpan?.mobile && grid.colSpan.mobile[layout.colSpan.mobile],
layout?.colSpan?.desktop && grid.colSpan.desktop[layout.colSpan.desktop],
)}>
<children.Component {...children.props} />
</div>
);
}

export default GridItem;
1 change: 1 addition & 0 deletions sections/Product/ImageGalleryFrontBack.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
export { default } from "../../components/product/Gallery/FrontBack.tsx";
1 change: 1 addition & 0 deletions sections/Product/ImageGallerySlider.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
export { default } from "../../components/product/Gallery/ImageSlider.tsx";
17 changes: 17 additions & 0 deletions sections/Product/NotFound.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
/**
* Rendered when a not found is returned by any of the loaders run on this page
*/
function NotFound() {
return (
<div class="w-full flex justify-center items-center py-28">
<div class="flex flex-col items-center justify-center gap-6">
<span class="font-medium text-2xl">Página não encontrada</span>
<a href="/" class="btn no-animation">
Voltar à página inicial
</a>
</div>
</div>
);
}

export default NotFound;
23 changes: 23 additions & 0 deletions sections/Product/NotFoundChallenge.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
import type { Section } from "deco/blocks/section.ts";
import type { ProductDetailsPage } from "apps/commerce/types.ts";

export interface Props {
/** @title Integration */
page: ProductDetailsPage | null;

/** @title On Product Found */
children: Section;

/** @title On Product Not Found */
fallback: Section;
}

function NotFoundChallenge({ page, children, fallback }: Props) {
if (page === null) {
return <fallback.Component {...fallback.props} />;
}

return <children.Component {...children.props} />;
}

export default NotFoundChallenge;
1 change: 0 additions & 1 deletion sections/Product/ProductDetails.tsx

This file was deleted.

1 change: 1 addition & 0 deletions sections/Product/ProductInfo.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
export { default } from "../../components/product/ProductInfo.tsx";
2 changes: 1 addition & 1 deletion static/tailwind.css

Large diffs are not rendered by default.

1 comment on commit 340fe3d

@deno-deploy
Copy link
Contributor

@deno-deploy deno-deploy bot commented on 340fe3d Oct 14, 2023

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

An internal server error occurred.

Please sign in to comment.