mirror of
https://github.com/vercel/commerce.git
synced 2025-05-11 12:17:51 +00:00
start moving over
This commit is contained in:
parent
694c5c17ba
commit
2c6e46b4d9
@ -1,10 +1,11 @@
|
|||||||
import { getCollection, getCollectionProducts } from 'lib/shopify';
|
import { getCollection } from 'lib/shopify';
|
||||||
import { Metadata } from 'next';
|
import { Metadata } from 'next';
|
||||||
import { notFound } from 'next/navigation';
|
import { notFound } from 'next/navigation';
|
||||||
|
|
||||||
import Grid from 'components/grid';
|
import Grid from 'components/grid';
|
||||||
import ProductGridItems from 'components/layout/product-grid-items';
|
import ProductGridItems from 'components/layout/product-grid-items';
|
||||||
import { defaultSort, sorting } from 'lib/constants';
|
import { defaultSort, sorting } from 'lib/constants';
|
||||||
|
import { getCollectionProducts } from 'lib/fourthwall';
|
||||||
|
|
||||||
export async function generateMetadata({
|
export async function generateMetadata({
|
||||||
params
|
params
|
||||||
|
@ -1,4 +1,4 @@
|
|||||||
import { getCollectionProducts } from 'lib/shopify';
|
import { getCollectionProducts } from 'lib/fourthwall';
|
||||||
import Link from 'next/link';
|
import Link from 'next/link';
|
||||||
import { GridTileImage } from './grid/tile';
|
import { GridTileImage } from './grid/tile';
|
||||||
|
|
||||||
|
@ -1,5 +1,5 @@
|
|||||||
import { GridTileImage } from 'components/grid/tile';
|
import { GridTileImage } from 'components/grid/tile';
|
||||||
import { getCollectionProducts } from 'lib/shopify';
|
import { getCollectionProducts } from 'lib/fourthwall';
|
||||||
import type { Product } from 'lib/shopify/types';
|
import type { Product } from 'lib/shopify/types';
|
||||||
import Link from 'next/link';
|
import Link from 'next/link';
|
||||||
|
|
||||||
|
@ -2,7 +2,7 @@ import Link from 'next/link';
|
|||||||
|
|
||||||
import FooterMenu from 'components/layout/footer-menu';
|
import FooterMenu from 'components/layout/footer-menu';
|
||||||
import LogoSquare from 'components/logo-square';
|
import LogoSquare from 'components/logo-square';
|
||||||
import { getMenu } from 'lib/shopify';
|
import { getMenu } from 'lib/fourthwall';
|
||||||
import { Suspense } from 'react';
|
import { Suspense } from 'react';
|
||||||
|
|
||||||
const { COMPANY_NAME, SITE_NAME } = process.env;
|
const { COMPANY_NAME, SITE_NAME } = process.env;
|
||||||
|
@ -1,6 +1,6 @@
|
|||||||
import CartModal from 'components/cart/modal';
|
import CartModal from 'components/cart/modal';
|
||||||
import LogoSquare from 'components/logo-square';
|
import LogoSquare from 'components/logo-square';
|
||||||
import { getMenu } from 'lib/shopify';
|
import { getMenu } from 'lib/fourthwall';
|
||||||
import { Menu } from 'lib/shopify/types';
|
import { Menu } from 'lib/shopify/types';
|
||||||
import Link from 'next/link';
|
import Link from 'next/link';
|
||||||
import { Suspense } from 'react';
|
import { Suspense } from 'react';
|
||||||
|
96
lib/fourthwall/index.ts
Normal file
96
lib/fourthwall/index.ts
Normal file
@ -0,0 +1,96 @@
|
|||||||
|
import { Menu, Product } from "lib/shopify/types";
|
||||||
|
import { reshapeProducts } from "./reshape";
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Helpers
|
||||||
|
*/
|
||||||
|
async function fourthwallGet<T>(url: string, options: RequestInit = {}): Promise<{ status: number; body: T }> {
|
||||||
|
try {
|
||||||
|
const result = await fetch(
|
||||||
|
url,
|
||||||
|
{
|
||||||
|
method: 'GET',
|
||||||
|
...options,
|
||||||
|
headers: {
|
||||||
|
'Content-Type': 'application/json',
|
||||||
|
...options.headers
|
||||||
|
}
|
||||||
|
}
|
||||||
|
);
|
||||||
|
|
||||||
|
const body = await result.json();
|
||||||
|
|
||||||
|
return {
|
||||||
|
status: result.status,
|
||||||
|
body
|
||||||
|
};
|
||||||
|
} catch (e) {
|
||||||
|
throw {
|
||||||
|
error: e,
|
||||||
|
url
|
||||||
|
};
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
async function fourthwallPost<T>(url: string, data: any, options: RequestInit = {}): Promise<{ status: number; body: T }> {
|
||||||
|
try {
|
||||||
|
const result = await fetch(url, {
|
||||||
|
method: 'POST',
|
||||||
|
...options,
|
||||||
|
headers: {
|
||||||
|
'Content-Type': 'application/json',
|
||||||
|
...options.headers
|
||||||
|
},
|
||||||
|
body: JSON.stringify(data)
|
||||||
|
});
|
||||||
|
|
||||||
|
const body = await result.json();
|
||||||
|
|
||||||
|
return {
|
||||||
|
status: result.status,
|
||||||
|
body
|
||||||
|
};
|
||||||
|
} catch (e) {
|
||||||
|
throw {
|
||||||
|
error: e,
|
||||||
|
url,
|
||||||
|
data
|
||||||
|
};
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Calls
|
||||||
|
*/
|
||||||
|
|
||||||
|
export async function getCollectionProducts({
|
||||||
|
collection,
|
||||||
|
reverse,
|
||||||
|
sortKey
|
||||||
|
}: {
|
||||||
|
collection: string;
|
||||||
|
reverse?: boolean;
|
||||||
|
sortKey?: string;
|
||||||
|
}): Promise<Product[]> {
|
||||||
|
const res = await fourthwallGet<{results: any[]}>(`${process.env.FW_URL}/api/public/v1.0/collections/${collection}/products?secret=${process.env.FW_SECRET}`, {
|
||||||
|
headers: {
|
||||||
|
'X-ShopId': process.env.FW_SHOPID || ''
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
if (!res.body.results) {
|
||||||
|
console.log(`No collection found for \`${collection}\``);
|
||||||
|
return [];
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
return reshapeProducts(res.body.results);
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Stubbed out
|
||||||
|
*/
|
||||||
|
export async function getMenu(handle: string): Promise<Menu[]> {
|
||||||
|
return [];
|
||||||
|
}
|
95
lib/fourthwall/reshape.ts
Normal file
95
lib/fourthwall/reshape.ts
Normal file
@ -0,0 +1,95 @@
|
|||||||
|
import { Image, Money, Product, ProductVariant } from "lib/shopify/types";
|
||||||
|
import { FourthwallMoney, FourthwallProduct, FourthwallProductImage, FourthwallProductVariant } from "./types";
|
||||||
|
|
||||||
|
const DEFAULT_IMAGE: Image = {
|
||||||
|
url: '',
|
||||||
|
altText: '',
|
||||||
|
width: 0,
|
||||||
|
height: 0
|
||||||
|
}
|
||||||
|
|
||||||
|
export const reshapeProducts = (products: FourthwallProduct[]) => {
|
||||||
|
const reshapedProducts = [];
|
||||||
|
|
||||||
|
for (const product of products) {
|
||||||
|
if (product) {
|
||||||
|
const reshapedProduct = reshapeProduct(product);
|
||||||
|
|
||||||
|
if (reshapedProduct) {
|
||||||
|
reshapedProducts.push(reshapedProduct);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return reshapedProducts;
|
||||||
|
};
|
||||||
|
|
||||||
|
const reshapeProduct = (product: FourthwallProduct): Product | undefined => {
|
||||||
|
if (!product) {
|
||||||
|
return undefined;
|
||||||
|
}
|
||||||
|
|
||||||
|
const { images, variants, ...rest } = product;
|
||||||
|
|
||||||
|
const minPrice = Math.min(...variants.map((v) => v.unitPrice.value));
|
||||||
|
const maxPrice = Math.max(...variants.map((v) => v.unitPrice.value));
|
||||||
|
|
||||||
|
const currencyCode = variants[0]?.unitPrice.currencyCode || 'USD';
|
||||||
|
|
||||||
|
return {
|
||||||
|
...rest,
|
||||||
|
handle: product.slug,
|
||||||
|
title: product.name,
|
||||||
|
descriptionHtml: product.description,
|
||||||
|
description: product.description,
|
||||||
|
images: reshapeImages(images, product.name),
|
||||||
|
variants: reshapeVariants(variants),
|
||||||
|
// stubbed out
|
||||||
|
availableForSale: true,
|
||||||
|
priceRange: {
|
||||||
|
minVariantPrice: {
|
||||||
|
amount: minPrice.toString(),
|
||||||
|
currencyCode,
|
||||||
|
},
|
||||||
|
maxVariantPrice: {
|
||||||
|
amount: maxPrice.toString(),
|
||||||
|
currencyCode,
|
||||||
|
}
|
||||||
|
},
|
||||||
|
options: [],
|
||||||
|
featuredImage: reshapeImages(images, product.name)[0] || DEFAULT_IMAGE,
|
||||||
|
seo: {
|
||||||
|
title: product.name,
|
||||||
|
description: product.description,
|
||||||
|
},
|
||||||
|
tags: [],
|
||||||
|
updatedAt: new Date().toISOString(),
|
||||||
|
};
|
||||||
|
};
|
||||||
|
|
||||||
|
const reshapeImages = (images: FourthwallProductImage[], productTitle: string): Image[] => {
|
||||||
|
return images.map((image) => {
|
||||||
|
const filename = image.url.match(/.*\/(.*)\..*/)?.[1];
|
||||||
|
return {
|
||||||
|
...image,
|
||||||
|
altText: `${productTitle} - ${filename}`
|
||||||
|
};
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
|
const reshapeVariants = (variants: FourthwallProductVariant[]): ProductVariant[] => {
|
||||||
|
return variants.map((v) => ({
|
||||||
|
id: v.id,
|
||||||
|
title: v.name,
|
||||||
|
availableForSale: true,
|
||||||
|
selectedOptions: [],
|
||||||
|
price: reshapeMoney(v.unitPrice),
|
||||||
|
}))
|
||||||
|
}
|
||||||
|
|
||||||
|
const reshapeMoney = (money: FourthwallMoney): Money => {
|
||||||
|
return {
|
||||||
|
amount: money.value.toString(),
|
||||||
|
currencyCode: money.currencyCode
|
||||||
|
};
|
||||||
|
}
|
32
lib/fourthwall/types.ts
Normal file
32
lib/fourthwall/types.ts
Normal file
@ -0,0 +1,32 @@
|
|||||||
|
export type FourthwallMoney = {
|
||||||
|
value: number;
|
||||||
|
currencyCode: string;
|
||||||
|
}
|
||||||
|
|
||||||
|
export type FourthwallProduct = {
|
||||||
|
id: string;
|
||||||
|
name: string;
|
||||||
|
slug: string;
|
||||||
|
description: string;
|
||||||
|
|
||||||
|
images: FourthwallProductImage[];
|
||||||
|
variants: FourthwallProductVariant[];
|
||||||
|
};
|
||||||
|
|
||||||
|
export type FourthwallProductImage = {
|
||||||
|
id: string;
|
||||||
|
url: string;
|
||||||
|
width: number;
|
||||||
|
height: number;
|
||||||
|
};
|
||||||
|
|
||||||
|
export type FourthwallProductVariant = {
|
||||||
|
id: string;
|
||||||
|
name: string;
|
||||||
|
sku: string;
|
||||||
|
unitPrice: FourthwallMoney;
|
||||||
|
|
||||||
|
images: FourthwallProductImage[];
|
||||||
|
|
||||||
|
// other attr
|
||||||
|
};
|
@ -12,11 +12,9 @@ import {
|
|||||||
} from './mutations/cart';
|
} from './mutations/cart';
|
||||||
import { getCartQuery } from './queries/cart';
|
import { getCartQuery } from './queries/cart';
|
||||||
import {
|
import {
|
||||||
getCollectionProductsQuery,
|
|
||||||
getCollectionQuery,
|
getCollectionQuery,
|
||||||
getCollectionsQuery
|
getCollectionsQuery
|
||||||
} from './queries/collection';
|
} from './queries/collection';
|
||||||
import { getMenuQuery } from './queries/menu';
|
|
||||||
import { getPageQuery, getPagesQuery } from './queries/page';
|
import { getPageQuery, getPagesQuery } from './queries/page';
|
||||||
import {
|
import {
|
||||||
getProductQuery,
|
getProductQuery,
|
||||||
@ -28,7 +26,6 @@ import {
|
|||||||
Collection,
|
Collection,
|
||||||
Connection,
|
Connection,
|
||||||
Image,
|
Image,
|
||||||
Menu,
|
|
||||||
Page,
|
Page,
|
||||||
Product,
|
Product,
|
||||||
ShopifyAddToCartOperation,
|
ShopifyAddToCartOperation,
|
||||||
@ -36,10 +33,8 @@ import {
|
|||||||
ShopifyCartOperation,
|
ShopifyCartOperation,
|
||||||
ShopifyCollection,
|
ShopifyCollection,
|
||||||
ShopifyCollectionOperation,
|
ShopifyCollectionOperation,
|
||||||
ShopifyCollectionProductsOperation,
|
|
||||||
ShopifyCollectionsOperation,
|
ShopifyCollectionsOperation,
|
||||||
ShopifyCreateCartOperation,
|
ShopifyCreateCartOperation,
|
||||||
ShopifyMenuOperation,
|
|
||||||
ShopifyPageOperation,
|
ShopifyPageOperation,
|
||||||
ShopifyPagesOperation,
|
ShopifyPagesOperation,
|
||||||
ShopifyProduct,
|
ShopifyProduct,
|
||||||
@ -285,33 +280,6 @@ export async function getCollection(handle: string): Promise<Collection | undefi
|
|||||||
return reshapeCollection(res.body.data.collection);
|
return reshapeCollection(res.body.data.collection);
|
||||||
}
|
}
|
||||||
|
|
||||||
export async function getCollectionProducts({
|
|
||||||
collection,
|
|
||||||
reverse,
|
|
||||||
sortKey
|
|
||||||
}: {
|
|
||||||
collection: string;
|
|
||||||
reverse?: boolean;
|
|
||||||
sortKey?: string;
|
|
||||||
}): Promise<Product[]> {
|
|
||||||
const res = await shopifyFetch<ShopifyCollectionProductsOperation>({
|
|
||||||
query: getCollectionProductsQuery,
|
|
||||||
tags: [TAGS.collections, TAGS.products],
|
|
||||||
variables: {
|
|
||||||
handle: collection,
|
|
||||||
reverse,
|
|
||||||
sortKey: sortKey === 'CREATED_AT' ? 'CREATED' : sortKey
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
if (!res.body.data.collection) {
|
|
||||||
console.log(`No collection found for \`${collection}\``);
|
|
||||||
return [];
|
|
||||||
}
|
|
||||||
|
|
||||||
return reshapeProducts(removeEdgesAndNodes(res.body.data.collection.products));
|
|
||||||
}
|
|
||||||
|
|
||||||
export async function getCollections(): Promise<Collection[]> {
|
export async function getCollections(): Promise<Collection[]> {
|
||||||
const res = await shopifyFetch<ShopifyCollectionsOperation>({
|
const res = await shopifyFetch<ShopifyCollectionsOperation>({
|
||||||
query: getCollectionsQuery,
|
query: getCollectionsQuery,
|
||||||
@ -340,23 +308,6 @@ export async function getCollections(): Promise<Collection[]> {
|
|||||||
return collections;
|
return collections;
|
||||||
}
|
}
|
||||||
|
|
||||||
export async function getMenu(handle: string): Promise<Menu[]> {
|
|
||||||
const res = await shopifyFetch<ShopifyMenuOperation>({
|
|
||||||
query: getMenuQuery,
|
|
||||||
tags: [TAGS.collections],
|
|
||||||
variables: {
|
|
||||||
handle
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
return (
|
|
||||||
res.body?.data?.menu?.items.map((item: { title: string; url: string }) => ({
|
|
||||||
title: item.title,
|
|
||||||
path: item.url.replace(domain, '').replace('/collections', '/search').replace('/pages', '')
|
|
||||||
})) || []
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
export async function getPage(handle: string): Promise<Page> {
|
export async function getPage(handle: string): Promise<Page> {
|
||||||
const res = await shopifyFetch<ShopifyPageOperation>({
|
const res = await shopifyFetch<ShopifyPageOperation>({
|
||||||
query: getPageQuery,
|
query: getPageQuery,
|
||||||
|
Loading…
x
Reference in New Issue
Block a user