mirror of
https://github.com/vercel/commerce.git
synced 2025-07-22 20:26:49 +00:00
product page: initial layout
This commit is contained in:
@@ -2,9 +2,77 @@ import Image from 'next/image';
|
||||
|
||||
import xss from 'xss';
|
||||
|
||||
import { getProducts, getProduct } from 'lib/shopify';
|
||||
import { getProducts, getProduct } from 'commerce/shopify';
|
||||
|
||||
import styles from './styles.module.scss';
|
||||
import PurchaseInput from '/components/product/purchase-input.js';
|
||||
import { getTags, listTags } from '/util';
|
||||
|
||||
//TODO: NumberInput
|
||||
|
||||
const ImageScroll = ({ images }) => (
|
||||
<div className={styles.imageScroll}>
|
||||
<div className={styles.horizScroll}>
|
||||
{images?.length > 1 && (
|
||||
<p className={styles.scrollMessage}>Scroll to right ( → )</p>
|
||||
)}
|
||||
<div className={styles.imageContainer}>
|
||||
{images?.map(image => (
|
||||
<Image
|
||||
key={image?.url}
|
||||
src={image?.url}
|
||||
alt={image?.altText}
|
||||
width={image?.width}
|
||||
height={image?.height}
|
||||
/>
|
||||
))}
|
||||
<div>
|
||||
<div className={styles.spacer} />
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
|
||||
const ProductPane = async ({ product }) => {
|
||||
const tags = await getTags({ product });
|
||||
|
||||
return (
|
||||
<div className={styles.productPane}>
|
||||
{product?.handle ? (
|
||||
<div className={styles.topBottom}>
|
||||
<div className={styles.description}>
|
||||
<h1>{product?.title}</h1>
|
||||
{tags && tags.length > 0 && (
|
||||
<h2 className={styles.collections}>
|
||||
{listTags({ tags })}
|
||||
</h2>
|
||||
)}
|
||||
<div
|
||||
dangerouslySetInnerHTML={{
|
||||
__html: xss(product.descriptionHtml),
|
||||
}}
|
||||
/>
|
||||
</div>
|
||||
<PurchaseInput product={product} />
|
||||
</div>
|
||||
) : (
|
||||
<p>Product not found</p>
|
||||
)}
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
export default async function ProductPage({ params: { handle } }) {
|
||||
const product = await getProduct(handle);
|
||||
|
||||
return (
|
||||
<div className={styles.productPage}>
|
||||
<ImageScroll images={product.images} />
|
||||
<ProductPane {...{ product }} />
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
export async function generateStaticParams() {
|
||||
const products = await getProducts({
|
||||
@@ -15,37 +83,3 @@ export async function generateStaticParams() {
|
||||
|
||||
return products.map(product => ({ product: product.handle }));
|
||||
}
|
||||
|
||||
//TODO: NumberInput
|
||||
|
||||
export default async function ProductPage({ params: { handle } }) {
|
||||
const product = await getProduct(handle);
|
||||
|
||||
return (
|
||||
<>
|
||||
{product?.handle ? (
|
||||
<>
|
||||
<h1>{product?.title}</h1>
|
||||
<div
|
||||
dangerouslySetInnerHTML={{
|
||||
__html: xss(product.descriptionHtml),
|
||||
}}
|
||||
/>
|
||||
<PurchaseInput product={product} />
|
||||
</>
|
||||
) : (
|
||||
<p>Product not found</p>
|
||||
)}
|
||||
<p>Scroll to right ( → )</p>
|
||||
{product?.images?.map(image => (
|
||||
<Image
|
||||
key={image?.url}
|
||||
src={image?.url}
|
||||
alt={image?.altText}
|
||||
width={image?.width}
|
||||
height={image?.height}
|
||||
/>
|
||||
))}
|
||||
</>
|
||||
);
|
||||
}
|
||||
|
93
app/(page)/product/[handle]/styles.module.scss
Normal file
93
app/(page)/product/[handle]/styles.module.scss
Normal file
@@ -0,0 +1,93 @@
|
||||
@use 'styles/_spacing';
|
||||
@use 'styles/_typography';
|
||||
|
||||
$spacer-width: calc(100vw - 100vh);
|
||||
|
||||
.imageScroll {
|
||||
position: relative;
|
||||
|
||||
left: 0;
|
||||
top: 0;
|
||||
right: 0;
|
||||
bottom: 0;
|
||||
height: 0;
|
||||
overflow-y: visible;
|
||||
|
||||
.horizScroll {
|
||||
height: 100vh;
|
||||
overflow-x: scroll;
|
||||
position: relative;
|
||||
|
||||
.scrollMessage {
|
||||
@include typography.subheader;
|
||||
|
||||
position: absolute;
|
||||
|
||||
left: 30px;
|
||||
bottom: spacing.$page-bottom-baseline;
|
||||
|
||||
z-index: 3;
|
||||
}
|
||||
|
||||
.imageContainer {
|
||||
display: flex;
|
||||
flex-direction: row;
|
||||
|
||||
height: 100%;
|
||||
|
||||
> * {
|
||||
height: 100%;
|
||||
width: auto;
|
||||
}
|
||||
|
||||
img {
|
||||
z-index: 2;
|
||||
}
|
||||
|
||||
.spacer {
|
||||
padding-right: $spacer-width;
|
||||
height: 0;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.productPane {
|
||||
padding-left: calc(calc(100vw - $spacer-width) + spacing.$grid-column-gap);
|
||||
padding-right: spacing.$page-margin-x;
|
||||
padding-top: 59px;
|
||||
padding-bottom: spacing.$page-bottom-baseline;
|
||||
|
||||
height: 100vh;
|
||||
|
||||
.topBottom {
|
||||
* {
|
||||
z-index: 1;
|
||||
}
|
||||
|
||||
height: 100%;
|
||||
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
|
||||
.description {
|
||||
@include typography.body-content;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.productPage {
|
||||
position: absolute;
|
||||
|
||||
height: 100vh;
|
||||
width: 100vw;
|
||||
overflow: hidden;
|
||||
}
|
||||
|
||||
.productPage {
|
||||
position: absolute;
|
||||
|
||||
height: 100vh;
|
||||
width: 100vw;
|
||||
overflow: hidden;
|
||||
}
|
Reference in New Issue
Block a user