Skip to content

Commit

Permalink
Merge pull request #9 from ildecimo/develop
Browse files Browse the repository at this point in the history
dev -> main
  • Loading branch information
maxdyy committed Aug 11, 2023
2 parents 3424596 + d9a7145 commit 69cca45
Show file tree
Hide file tree
Showing 17 changed files with 306 additions and 113 deletions.
43 changes: 0 additions & 43 deletions public/images/example.svg

This file was deleted.

Binary file added public/images/product-placeholder.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
File renamed without changes.
25 changes: 22 additions & 3 deletions src/app/page.tsx
Original file line number Diff line number Diff line change
@@ -1,5 +1,24 @@
import HomePage from '../components/Home';
import { authorize } from '~/lib/authorize';
import * as db from '~/lib/db';

export default function Page() {
return <HomePage />;
import { fetchAllProducts } from '~/server/bigcommerce-api';

import ProductList from '~/components/ProductList';

export default async function Page() {
const authorized = authorize();

if (!authorized) {
throw new Error('Token is not valid. Try to re-open the app.');
}

const accessToken = await db.getStoreToken(authorized.storeHash);

if (!accessToken) {
throw new Error('Access token not found. Try to re-install the app.');
}

const products = await fetchAllProducts(accessToken, authorized.storeHash);

return <ProductList products={products} />;
}
7 changes: 7 additions & 0 deletions src/app/product/[productId]/loading.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
'use client';

import Loader from '~/components/Loader';

export default function Loading() {
return <Loader minHeight="90vh" />;
}
File renamed without changes.
43 changes: 0 additions & 43 deletions src/components/Home.tsx

This file was deleted.

16 changes: 16 additions & 0 deletions src/components/NextLink.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
import Link from 'next/link';

interface NextLinkProps {
href: string;
children: React.ReactNode | string;
}

export const NextLink = ({ href, children, ...props }: NextLinkProps) => (
<Link
className="inline-flex items-center whitespace-nowrap text-blue-700 hover:text-blue-900"
href={href}
{...props}
>
{children}
</Link>
);
122 changes: 122 additions & 0 deletions src/components/ProductList.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,122 @@
'use client';

import { type SimpleProduct } from 'types';

import { Badge, Table } from '@bigcommerce/big-design';
import Image from 'next/image';

import { Breadcrumbs } from '~/components/Breadcrumbs';
import { NextLink } from '~/components/NextLink';
import { StarRating } from '~/components/StarRating';

import { ArrowLongRightIcon } from '@heroicons/react/20/solid';
import { convertToUDS } from '~/utils/utils';

interface ProductListProps {
products: SimpleProduct[];
}

interface ReviewsAverageProps {
product: SimpleProduct;
}

const ReviewsAverage = ({ product }: ReviewsAverageProps) => {
const { reviews_count, reviews_rating_sum } = product;

if (reviews_count === 0) {
return (
<Badge label="no reviews" variant="warning">
No Reviews
</Badge>
);
}

const average = reviews_rating_sum / reviews_count;

return <StarRating rating={average} />;
};

const ProductList = ({ products }: ProductListProps) => {
return (
<div>
<Breadcrumbs>
<Breadcrumbs.Text>All Products</Breadcrumbs.Text>
</Breadcrumbs>
<div className="mt-10">
<Table
stickyHeader
columns={[
{
header: 'Image',
hash: 'image',
render: (product) => (
<div className="relative h-16 w-16 overflow-hidden rounded-md">
<Image
alt={product.name}
className="object-cover object-center"
fill
src={
product.thumbnailImage ||
'/images/product-placeholder.png'
}
/>
</div>
),
},
{
header: 'Name',
hash: 'name',
render: (product) => product.name,
},
{
header: 'Price',
hash: 'price',
render: (product) => convertToUDS(product.price),
},
{
header: 'Total Sold',
hash: 'totalSold',
render: (product) => product.total_sold,
},
{
header: 'Visible',
hash: 'visible',
render: (product) =>
product.is_visible ? (
<Badge label="yes" variant="success">
Yes
</Badge>
) : (
<Badge label="no" variant="danger">
No
</Badge>
),
},
{
header: 'Approved Reviews',
hash: 'approvedReviews',
render: (product) => product.reviews_count,
},
{
header: 'Approved Rating',
hash: 'approvedRating',
render: (product) => <ReviewsAverage product={product} />,
},
{
header: 'Action',
hash: 'action',
render: (product) => (
<NextLink href={`/product/${product.id}`}>
Explore <ArrowLongRightIcon className="ml-1 h-4 w-4" />
</NextLink>
),
},
]}
items={products}
/>
</div>
</div>
);
};

export default ProductList;
22 changes: 11 additions & 11 deletions src/components/ProductReviewList.tsx
Original file line number Diff line number Diff line change
@@ -1,14 +1,17 @@
'use client';
import { type Product, type Review } from 'types';

import { Table } from '@bigcommerce/big-design';
import { BoltIcon } from '@heroicons/react/20/solid';
import Link from 'next/link';
import { useMemo } from 'react';
import { type Product, type Review } from 'types';

import { Breadcrumbs } from '~/components/Breadcrumbs';
import { Card } from '~/components/Card';
import { NextLink } from '~/components/NextLink';
import { ReviewStatusBadge } from '~/components/ReviewStatusBadge';
import { StarRating } from '~/components/StarRating';

import { convertToDateString } from '~/utils/utils';
import { Breadcrumbs } from './Breadcrumbs';
import { Card } from './Card';
import { ReviewStatusBadge } from './ReviewStatusBadge';
import { StarRating } from './StarRating';

interface ProductReviewListProps {
product: Product;
Expand Down Expand Up @@ -128,12 +131,9 @@ export const ProductReviewList = ({
header: 'Action',
hash: 'action',
render: ({ id }) => (
<Link
className="inline-flex items-center whitespace-nowrap text-blue-700 hover:text-blue-900"
href={`/productReview/${product.id}/review/${id}`}
>
<NextLink href={`/product/${product.id}/review/${id}`}>
AI Explore <BoltIcon className="ml-1 h-4 w-4" />
</Link>
</NextLink>
),
},
]}
Expand Down
2 changes: 1 addition & 1 deletion src/components/ReviewDetail.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -22,7 +22,7 @@ export const ReviewDetail = ({ product, review }: ReviewDetailProps) => {
<Breadcrumbs>
<Breadcrumbs.Link href="/">All Products</Breadcrumbs.Link>
<Breadcrumbs.Divider />
<Breadcrumbs.Link href={`/productReview/${product.id}`}>
<Breadcrumbs.Link href={`/product/${product.id}`}>
{product.name}
</Breadcrumbs.Link>
<Breadcrumbs.Divider />
Expand Down
2 changes: 1 addition & 1 deletion src/lib/appExtensions.ts
Original file line number Diff line number Diff line change
Expand Up @@ -165,7 +165,7 @@ const createReviewsAppExtensionMutation = () => ({
input: {
context: 'PANEL',
model: 'PRODUCTS',
url: '/productReview/${id}',
url: '/product/${id}',
label: {
defaultValue: 'Product Review AI',
locales: [
Expand Down
88 changes: 82 additions & 6 deletions src/server/bigcommerce-api/client.ts
Original file line number Diff line number Diff line change
Expand Up @@ -11,13 +11,50 @@ const productSchema = z.object({
width: z.number(),
depth: z.number(),
videos: z.array(z.object({ description: z.string() })),
images: z.array(z.object({ description: z.string(), url_standard: z.string(), is_thumbnail: z.boolean() })),
images: z.array(
z.object({
description: z.string(),
url_standard: z.string(),
is_thumbnail: z.boolean(),
})
),
custom_fields: z.array(z.object({ name: z.string(), value: z.string() })),
brand_id: z.number().optional(),
categories: z.array(z.number()),
}),
});

const productsSchema = z.object({
data: z.array(
z.object({
id: z.number(),
name: z.string(),
type: z.string(),
condition: z.string(),
weight: z.number(),
height: z.number(),
width: z.number(),
depth: z.number(),
reviews_rating_sum: z.number(),
reviews_count: z.number(),
total_sold: z.number(),
is_visible: z.boolean(),
price: z.number(),
videos: z.array(z.object({ description: z.string() })),
images: z.array(
z.object({
description: z.string(),
url_standard: z.string(),
is_thumbnail: z.boolean(),
})
),
custom_fields: z.array(z.object({ name: z.string(), value: z.string() })),
brand_id: z.number().optional(),
categories: z.array(z.number()),
})
),
});

const categorySchema = z.object({
data: z.array(z.object({ name: z.string() })),
});
Expand All @@ -37,12 +74,12 @@ const reviewObjectSchema = z.object({
id: z.number(),
date_created: z.string(),
date_modified: z.string(),
})
});

const reviewSchema = z.object({
data: reviewObjectSchema,
meta: z.object({})
})
meta: z.object({}),
});

const reviewsSchema = z.object({
data: z.array(reviewObjectSchema),
Expand Down Expand Up @@ -107,7 +144,8 @@ export async function fetchProduct(
...restAttr,
videosDescriptions: videos.map(({ description }) => description).join(','),
imagesDescriptions: images.map(({ description }) => description).join(','),
thumbnailImage: images.find(({ is_thumbnail }) => is_thumbnail)?.url_standard,
thumbnailImage: images.find(({ is_thumbnail }) => is_thumbnail)
?.url_standard,
};
}

Expand Down Expand Up @@ -208,5 +246,43 @@ export async function fetchProductReview(
throw new Error('Failed to parse review');
}

return parsedReview.data.data
return parsedReview.data.data;
}

export async function fetchProducts(accessToken: string, storeHash: string) {
const params = new URLSearchParams({
include: 'videos,images,custom_fields',
}).toString();

const response = await fetchFromBigCommerceApi(
`/catalog/products?${params}`,
accessToken,
storeHash
);

if (!response.ok) {
throw new Error('Failed to fetch products');
}

const parsedProducts = productsSchema.safeParse(await response.json());

if (!parsedProducts.success) {
throw new Error('Failed to parse products');
}

const cleanProducts = parsedProducts.data.data.map(
({ videos, images, ...restAttr }) => ({
...restAttr,
videosDescriptions: videos
.map(({ description }) => description)
.join(','),
imagesDescriptions: images
.map(({ description }) => description)
.join(','),
thumbnailImage: images.find(({ is_thumbnail }) => is_thumbnail)
?.url_standard,
})
);

return cleanProducts;
}
Loading

0 comments on commit 69cca45

Please sign in to comment.