Skip to content

Commit

Permalink
wip tada
Browse files Browse the repository at this point in the history
  • Loading branch information
deini committed Mar 4, 2024
1 parent 80c80ba commit 090970c
Show file tree
Hide file tree
Showing 18 changed files with 540 additions and 293 deletions.
1 change: 1 addition & 0 deletions .vscode/settings.example.json
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
{
"typescript.tsdk": "node_modules/typescript/lib",
"typescript.enablePromptUseWorkspaceTsdk": true,
"eslint.workingDirectories": [
{ "pattern": "apps/*/" },
{ "pattern": "packages/*/" }
Expand Down
33 changes: 27 additions & 6 deletions apps/core/app/(default)/product/[slug]/_components/breadcrumbs.tsx
Original file line number Diff line number Diff line change
@@ -1,23 +1,44 @@
import { getProduct } from '~/client/queries/get-product';
import { removeEdgesAndNodes } from '@bigcommerce/catalyst-client';

import { Link } from '~/components/link';
import { cn } from '~/lib/utils';
import { FragmentOf, graphql, readFragment } from '~/tada/graphql';

export const BreadcrumbsFragment = graphql(`
fragment BreadcrumbsFragment on CategoryConnection {
edges {
node {
breadcrumbs(depth: 5) {
edges {
node {
name
path
}
}
}
}
}
}
`);

interface Props {
productId: number;
data: FragmentOf<typeof BreadcrumbsFragment>;
}

export const BreadCrumbs = async ({ productId }: Props) => {
const product = await getProduct(productId);
const category = product?.categories?.[0];
export const BreadCrumbs = ({ data }: Props) => {
const fragmentData = readFragment(BreadcrumbsFragment, data);
const [category] = removeEdgesAndNodes(fragmentData);

if (!category) {
return null;
}

const breadcrumbs = removeEdgesAndNodes(category.breadcrumbs);

return (
<nav>
<ul className="m-0 flex flex-wrap items-center p-0 md:container md:mx-auto ">
{category.breadcrumbs.map((breadcrumb, i, arr) => {
{breadcrumbs.map((breadcrumb, i, arr) => {
const isLast = arr.length - 1 === i;

return (
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,66 @@
import { ProductFormFragment } from '~/components/product-form/fragment';
import { graphql } from '~/tada/graphql';

import { ProductSchemaFragment } from '../product-schema';
import { ReviewSummaryFragment } from '../review-summary';

export const ProductDetailsFragment = graphql(
`
fragment ProductDetailsFragment on Product {
entityId
name
sku
upc
minPurchaseQuantity
maxPurchaseQuantity
condition
weight {
value
unit
}
availabilityV2 {
description
}
brand {
name
}
prices {
priceRange {
min {
value
}
max {
value
}
}
retailPrice {
value
}
salePrice {
value
}
basePrice {
value
}
price {
value
}
}
customFields {
edges {
node {
entityId
name
value
}
}
}
reviewSummary {
...ReviewSummaryFragment
}
...ProductSchemaFragment
...ProductFormFragment
}
`,
[ReviewSummaryFragment, ProductSchemaFragment, ProductFormFragment],
);
Original file line number Diff line number Diff line change
@@ -0,0 +1,139 @@
import { removeEdgesAndNodes } from '@bigcommerce/catalyst-client';

import { ProductForm } from '~/components/product-form';
import { FragmentOf, readFragment } from '~/tada/graphql';

import { ProductSchema } from '../product-schema';
import { ReviewSummary } from '../review-summary';

import { ProductDetailsFragment } from './fragment';

const currencyFormatter = new Intl.NumberFormat('en-US', {
style: 'currency',
currency: 'USD',
});

interface Props {
data: FragmentOf<typeof ProductDetailsFragment>;
}

export const ProductDetails = ({ data }: Props) => {
const product = readFragment(ProductDetailsFragment, data);
const customFields = removeEdgesAndNodes(product.customFields);

const showPriceRange =
product.prices?.priceRange.min.value !== product.prices?.priceRange.max.value;

return (
<div>
{product.brand && (
<p className="mb-2 font-semibold uppercase text-gray-500">{product.brand.name}</p>
)}

<h1 className="mb-4 text-h2">{product.name}</h1>

<ReviewSummary data={product.reviewSummary} />

{product.prices && (
<div className="my-6 text-h4">
{showPriceRange ? (
<span>
{currencyFormatter.format(product.prices.priceRange.min.value)} -{' '}
{currencyFormatter.format(product.prices.priceRange.max.value)}
</span>
) : (
<>
{product.prices.retailPrice?.value !== undefined && (
<span>
MSRP:{' '}
<span className="line-through">
{currencyFormatter.format(product.prices.retailPrice.value)}
</span>
<br />
</span>
)}
{product.prices.salePrice?.value !== undefined &&
product.prices.basePrice?.value !== undefined ? (
<>
<span>
Was:{' '}
<span className="line-through">
{currencyFormatter.format(product.prices.basePrice.value)}
</span>
</span>
<br />
<span>Now: {currencyFormatter.format(product.prices.salePrice.value)}</span>
</>
) : (
product.prices.price.value && (
<span>{currencyFormatter.format(product.prices.price.value)}</span>
)
)}
</>
)}
</div>
)}

<ProductForm data={product} />

<div className="my-12">
<h2 className="mb-4 text-h5">Additional details</h2>
<div className="grid gap-3 sm:grid-cols-2">
{Boolean(product.sku) && (
<div>
<h3 className="text-base font-bold">SKU</h3>
<p>{product.sku}</p>
</div>
)}
{Boolean(product.upc) && (
<div>
<h3 className="text-base font-bold">UPC</h3>
<p>{product.upc}</p>
</div>
)}
{Boolean(product.minPurchaseQuantity) && (
<div>
<h3 className="text-base font-bold">Minimum purchase</h3>
<p>{product.minPurchaseQuantity}</p>
</div>
)}
{Boolean(product.maxPurchaseQuantity) && (
<div>
<h3 className="text-base font-bold">Maxiumum purchase</h3>
<p>{product.maxPurchaseQuantity}</p>
</div>
)}
{Boolean(product.availabilityV2.description) && (
<div>
<h3 className="text-base font-bold">Availability</h3>
<p>{product.availabilityV2.description}</p>
</div>
)}
{Boolean(product.condition) && (
<div>
<h3 className="text-base font-bold">Condition</h3>
<p>{product.condition}</p>
</div>
)}
{Boolean(product.weight) && (
<div>
<h3 className="text-base font-bold">Weight</h3>
<p>
{product.weight?.value} {product.weight?.unit}
</p>
</div>
)}
{Boolean(customFields) &&
customFields.map((customField) => (
<div key={customField.entityId}>
<h3 className="text-base font-bold">{customField.name}</h3>
<p>{customField.value}</p>
</div>
))}
</div>
</div>

<ProductSchema data={product} />
</div>
);
};
Original file line number Diff line number Diff line change
@@ -1,15 +1,39 @@
import { removeEdgesAndNodes } from '@bigcommerce/catalyst-client';
import { Product as ProductSchemaType, WithContext } from 'schema-dts';

import { getProductReviews } from '~/client/queries/get-product-reviews';
import { ExistingResultType } from '~/client/util';
import { FragmentOf, graphql, readFragment } from '~/tada/graphql';

export const ProductReviewSchemaFragment = graphql(`
fragment ProductReviewSchema on Product {
entityId
reviews(first: 5) {
edges {
node {
entityId
author {
name
}
createdAt {
utc
}
rating
title
text
}
}
}
}
`);

interface Props {
data: FragmentOf<typeof ProductReviewSchemaFragment>;
}

export const ProductReviewSchema = ({ data }: Props) => {
const fragmentData = readFragment(ProductReviewSchemaFragment, data);
const productId = fragmentData.entityId;
const reviews = removeEdgesAndNodes(fragmentData.reviews);

export const ProductReviewSchema = ({
reviews,
productId,
}: {
reviews: ExistingResultType<typeof getProductReviews>['reviews'];
productId: number;
}) => {
const productReviewSchema: WithContext<ProductSchemaType> = {
'@context': 'https://schema.org',
'@type': 'Product',
Expand Down
Original file line number Diff line number Diff line change
@@ -1,9 +1,53 @@
import { Product as ProductSchemaType, WithContext } from 'schema-dts';

import { getProduct } from '~/client/queries/get-product';
import { FragmentOf, graphql, readFragment } from '~/tada/graphql';

export const ProductSchema = ({ product }: { product: Awaited<ReturnType<typeof getProduct>> }) => {
if (product === null) return null;
export const ProductSchemaFragment = graphql(`
fragment ProductSchemaFragment on Product {
name
path
plainTextDescription
sku
gtin
mpn
brand {
name
path
}
reviewSummary {
numberOfReviews
averageRating
}
defaultImage {
url(width: 600)
}
prices {
price {
value
currencyCode
}
priceRange {
min {
value
}
max {
value
}
}
}
condition
availabilityV2 {
status
}
}
`);

interface Props {
data: FragmentOf<typeof ProductSchemaFragment>;
}

export const ProductSchema = ({ data }: Props) => {
const product = readFragment(ProductSchemaFragment, data);

/* TODO: use common default image when product has no images */
const image = product.defaultImage ? { image: product.defaultImage.url } : null;
Expand Down
Loading

0 comments on commit 090970c

Please sign in to comment.