diff --git a/app/api/revalidate/route.ts b/app/api/revalidate/route.ts deleted file mode 100644 index 47af2a4a4a..0000000000 --- a/app/api/revalidate/route.ts +++ /dev/null @@ -1,8 +0,0 @@ -import { revalidate } from 'lib/shopify'; -import { NextRequest, NextResponse } from 'next/server'; - -export const runtime = 'edge'; - -export async function POST(req: NextRequest): Promise { - return revalidate(req); -} diff --git a/components/carousel.tsx b/components/carousel.tsx index 5e297f3f6f..5cb848f189 100644 --- a/components/carousel.tsx +++ b/components/carousel.tsx @@ -4,7 +4,7 @@ import { GridTileImage } from './grid/tile'; export async function Carousel() { // Collections that start with `hidden-*` are hidden from the search page. - const products = await getCollectionProducts({ collection: 'hidden-homepage-carousel' }); + const products = await getCollectionProducts({}); if (!products?.length) return null; diff --git a/components/grid/three-items.tsx b/components/grid/three-items.tsx index 75948bdfd5..5e0da03c20 100644 --- a/components/grid/three-items.tsx +++ b/components/grid/three-items.tsx @@ -39,9 +39,7 @@ function ThreeItemGridItem({ export async function ThreeItemGrid() { // Collections that start with `hidden-*` are hidden from the search page. - const homepageItems = await getCollectionProducts({ - collection: 'hidden-homepage-featured-items' - }); + const homepageItems = await getCollectionProducts({}); if (!homepageItems[0] || !homepageItems[1] || !homepageItems[2]) return null; diff --git a/lib/constants.ts b/lib/constants.ts index c09b78eafe..f3f15c475e 100644 --- a/lib/constants.ts +++ b/lib/constants.ts @@ -25,6 +25,10 @@ export const TAGS = { products: 'products' }; +export const DEFAULT_OPTION = 'Default Title'; + +export const HIDDEN_PRODUCT_TAG = 'nextjs-frontend-hidden'; + export const SYLIUS_API_ENDPOINT = '/api/v2/shop'; export const REST_METHODS = { diff --git a/lib/sylius/index.ts b/lib/sylius/index.ts index 8e8844918c..b619b102f2 100644 --- a/lib/sylius/index.ts +++ b/lib/sylius/index.ts @@ -1,4 +1,8 @@ import { REST_METHODS, SYLIUS_API_ENDPOINT } from 'lib/constants'; +import { normalizeCollection } from './normalizer/collection-normalizer'; +import { normalizeProduct } from './normalizer/product-normalizer'; +import { SyliusProduct, SyliusTaxon } from './sylius-types/product-types'; +import { AddToCartPayload, GetCollectionProductsPayload, GetProductsPayload } from './types'; const DOMAIN = `${process.env.SYLIUS_STORE_DOMAIN}`; const ENDPOINT = `${DOMAIN}${SYLIUS_API_ENDPOINT}`; @@ -12,7 +16,8 @@ export default async function syliusRequest( const options: RequestInit = { method, headers: { - 'Content-Type': 'application/json' + 'Content-Type': 'application/json', + Accept: 'application/json' } }; @@ -45,11 +50,100 @@ export const getPages = () => []; export const getPage = () => {}; // Products -export const getProducts = () => []; -export const getProduct = () => {}; -export const getProductRecommendations = () => []; -export const getCollection = () => {}; -export const getCollectionProducts = () => []; +export const getProducts = async (payload: GetProductsPayload) => { + const url = new URL(`${ENDPOINT}/products`); + if (payload.query) { + url.searchParams.set('translations.name', payload.query); + } + const orderBy = payload.reverse ? 'desc' : 'asc'; + + if (payload.sortKey) { + switch (payload.sortKey) { + case 'RELEVANCE': + break; + case 'BEST_SELLING': + break; + case 'CREATED_AT': + url.searchParams.set('order[createdAt]', orderBy); + break; + case 'PRICE': + url.searchParams.set('order[price]', orderBy); + break; + default: + break; + } + } + + const data = await syliusRequest(REST_METHODS.GET, '/products' + url.search); + const syliusProducts = data.body; + const products = syliusProducts.map((syliusProduct: SyliusProduct) => + normalizeProduct(syliusProduct) + ); + return products; +}; + +export const getProduct = async (slug: string) => { + const data = await syliusRequest(REST_METHODS.GET, '/products-by-slug/' + slug); + + const syliusProduct = data.body; + const product = normalizeProduct(syliusProduct); + + return product; +}; +export const getProductRecommendations = () => { + return []; +}; +export const getCollections = async () => { + const data = await syliusRequest(REST_METHODS.GET, '/taxons'); + + const syliusTaxons = data.body; + const collections = syliusTaxons.map((syliusTaxon: SyliusTaxon) => + normalizeCollection(syliusTaxon) + ); + + return collections; +}; + +export const getCollection = async (taxonCode: string) => { + const data = await syliusRequest(REST_METHODS.GET, '/taxons/' + taxonCode); + + const syliusTaxon = data.body; + const collection = normalizeCollection(syliusTaxon); + + return collection; +}; + +export const getCollectionProducts = async (payload: GetCollectionProductsPayload) => { + const url = new URL(`${ENDPOINT}/products`); + if (payload.collection) { + url.searchParams.set('productTaxons.taxon.code', payload.collection); + } + const orderBy = payload.reverse ? 'desc' : 'asc'; + + if (payload.sortKey) { + switch (payload.sortKey) { + case 'RELEVANCE': + break; + case 'BEST_SELLING': + break; + case 'CREATED_AT': + url.searchParams.set('order[createdAt]', orderBy); + break; + case 'PRICE': + url.searchParams.set('order[price]', orderBy); + break; + default: + break; + } + } + + const data = await syliusRequest(REST_METHODS.GET, '/products' + url.search); + const syliusProducts = data.body; + const products = syliusProducts.map((syliusProduct: SyliusProduct) => + normalizeProduct(syliusProduct) + ); + return products; +}; // Cart export const createCart = async () => { @@ -70,9 +164,7 @@ export const updateCart = () => {}; // Site export const getMenu = () => [ { - title: 'Home', - path: '/' + title: 'All', + path: '/search' } ]; - -type AddToCartPayload = { merchandiseId: string; quantity: number }; diff --git a/lib/sylius/normalizer/collection-normalizer.ts b/lib/sylius/normalizer/collection-normalizer.ts new file mode 100644 index 0000000000..107164c50d --- /dev/null +++ b/lib/sylius/normalizer/collection-normalizer.ts @@ -0,0 +1,14 @@ +import { SyliusTaxon } from '../sylius-types/product-types'; +import { Collection } from '../types'; + +export const normalizeCollection = (syliusTaxon: SyliusTaxon): Collection => { + return { + seo: { + title: syliusTaxon.name, + description: syliusTaxon.description + }, + code: syliusTaxon.code, + title: syliusTaxon.name, + path: `/search/${syliusTaxon.code}` + }; +}; diff --git a/lib/sylius/normalizer/product-normalizer.ts b/lib/sylius/normalizer/product-normalizer.ts index c46406b5c3..fbf0e112f8 100644 --- a/lib/sylius/normalizer/product-normalizer.ts +++ b/lib/sylius/normalizer/product-normalizer.ts @@ -7,6 +7,10 @@ import { import { Image, Money, Product, ProductOption, ProductVariant } from '../types'; export const normalizeProduct = (product: SyliusProduct): Product => ({ + seo: { + title: product.name, + description: product.shortDescription + }, variants: product.variants.map((variant) => normalizeProductVariant(variant)), images: product.images.map((image) => normalizeProductImage(image)), id: product.id.toString(), diff --git a/lib/sylius/types.ts b/lib/sylius/types.ts index 9331d960d7..ddd6a11986 100644 --- a/lib/sylius/types.ts +++ b/lib/sylius/types.ts @@ -33,6 +33,10 @@ export interface Money { } export interface Product { + seo?: { + title?: string; + description?: string; + }; variants: ProductVariant[]; images: Image[]; id: string; @@ -79,3 +83,27 @@ export interface Menu { title: string; path: string; } + +export interface Collection { + seo?: { + title?: string; + description?: string; + }; + code: string; + title: string; + path: string; +} + +export interface GetCollectionProductsPayload { + collection?: string; + sortKey?: 'RELEVANCE' | 'BEST_SELLING' | 'CREATED_AT' | 'PRICE'; + reverse?: boolean; +} + +export interface GetProductsPayload { + sortKey?: 'RELEVANCE' | 'BEST_SELLING' | 'CREATED_AT' | 'PRICE'; + reverse?: boolean; + query?: string; +} + +export type AddToCartPayload = { merchandiseId: string; quantity: number }; diff --git a/lib/utils.ts b/lib/utils.ts index e69de29bb2..02ff6fe1b7 100644 --- a/lib/utils.ts +++ b/lib/utils.ts @@ -0,0 +1,11 @@ +import { ReadonlyURLSearchParams } from 'next/navigation'; + +export const createUrl = (pathname: string, params: URLSearchParams | ReadonlyURLSearchParams) => { + const paramsString = params.toString(); + const queryString = `${paramsString.length ? '?' : ''}${paramsString}`; + + return `${pathname}${queryString}`; +}; + +export const ensureStartsWith = (stringToCheck: string, startsWith: string) => + stringToCheck.startsWith(startsWith) ? stringToCheck : `${startsWith}${stringToCheck}`; diff --git a/next.config.js b/next.config.js index 53a8515e2a..dfd81e4c55 100644 --- a/next.config.js +++ b/next.config.js @@ -11,9 +11,9 @@ module.exports = { formats: ['image/avif', 'image/webp'], remotePatterns: [ { - protocol: 'https', - hostname: 'cdn.shopify.com', - pathname: '/s/files/**' + protocol: 'http', + hostname: 'localhost', + pathname: '/media/**' } ] },