From fd16743edd014ab075f1d5f850bad88feac8357b Mon Sep 17 00:00:00 2001 From: Fabian Cook Date: Thu, 22 Jun 2023 23:22:21 +1200 Subject: [PATCH] Display offers with product images, #1, #21, closes #35 with speculative offer imports --- README.md | 4 ++- src/data/file/source.ts | 4 +-- src/data/file/types.ts | 2 +- src/data/offer/schema.ts | 8 +++++ src/data/offer/types.ts | 2 ++ src/package.readonly.ts | 14 ++++----- src/react/server/paths/product/list.tsx | 42 +++++++++++++++++-------- src/tests/remote.ts | 36 ++++++++++++++++++--- src/view/index.tsx | 1 + 9 files changed, 84 insertions(+), 29 deletions(-) diff --git a/README.md b/README.md index 41fe2bc..0aa1850 100644 --- a/README.md +++ b/README.md @@ -129,7 +129,7 @@ export interface Expiring { expiresAt?: string; } -export type BaseFileStoreType = "product" | "inventory" | "productFile" | "inventoryFile" | "offer" | "offerFile" | "metrics" +export type BaseFileStoreType = "product" | "inventory" | "productFile" | "inventoryFile" | "offer" | "offerFile" export type BaseFileRemoteSourceName = "discord" | BaseFileStoreType; export type RemoteFileSourceName = BaseFileRemoteSourceName | `${BaseFileRemoteSourceName}_${number}`; @@ -371,6 +371,8 @@ export type MaybeNumberString = `${number}` | string; export interface OfferPrice { price: MaybeNumberString; currency: string; + currencyCode?: string; + countryCode?: string; } export interface ProductOfferItem { diff --git a/src/data/file/source.ts b/src/data/file/source.ts index aa7a1a2..1b371b3 100644 --- a/src/data/file/source.ts +++ b/src/data/file/source.ts @@ -2,8 +2,8 @@ import {isNumberString, ok} from "../../is"; import {BaseFileRemoteSourceName, RemoteFileSourceName} from "../data"; import {index} from "cheerio/lib/api/traversing"; -export const TYPE_BASE_NAMES: BaseFileRemoteSourceName[] = ["product", "inventory"]; -const NAMES: BaseFileRemoteSourceName[] = ["discord", ...TYPE_BASE_NAMES, "productFile", "inventoryFile"]; +export const TYPE_BASE_NAMES: BaseFileRemoteSourceName[] = ["product", "inventory", "offer"]; +const NAMES: BaseFileRemoteSourceName[] = ["discord", ...TYPE_BASE_NAMES, "productFile", "inventoryFile", "offerFile"]; const NAMES_STRINGS: string[] = NAMES; export type RemoteSourceEnvName = "token" | "origin" | "store" | "name" | "prefix" | "url" | "cacheBust" | "enableFileSource"; diff --git a/src/data/file/types.ts b/src/data/file/types.ts index 293d7a5..2145415 100644 --- a/src/data/file/types.ts +++ b/src/data/file/types.ts @@ -1,6 +1,6 @@ import {Expiring} from "../expiring"; -export type BaseFileStoreType = "product" | "inventory" | "productFile" | "inventoryFile" | "offer" | "offerFile" | "metrics" +export type BaseFileStoreType = "product" | "inventory" | "productFile" | "inventoryFile" | "offer" | "offerFile" export type BaseFileRemoteSourceName = "discord" | BaseFileStoreType; export type RemoteFileSourceName = BaseFileRemoteSourceName | `${BaseFileRemoteSourceName}_${number}`; diff --git a/src/data/offer/schema.ts b/src/data/offer/schema.ts index 3e59228..b4e69d1 100644 --- a/src/data/offer/schema.ts +++ b/src/data/offer/schema.ts @@ -81,6 +81,14 @@ export const offerData = { currency: { type: "string", nullable: true + }, + currencyCode: { + type: "string", + nullable: true + }, + countryCode: { + type: "string", + nullable: true } }, required: ["status", "items"], diff --git a/src/data/offer/types.ts b/src/data/offer/types.ts index 86f529d..b72327f 100644 --- a/src/data/offer/types.ts +++ b/src/data/offer/types.ts @@ -6,6 +6,8 @@ export type MaybeNumberString = `${number}` | string; export interface OfferPrice { price: MaybeNumberString; currency: string; + currencyCode?: string; + countryCode?: string; } export interface ProductOfferItem { diff --git a/src/package.readonly.ts b/src/package.readonly.ts index fb61ee1..789499c 100644 --- a/src/package.readonly.ts +++ b/src/package.readonly.ts @@ -1,14 +1,14 @@ // File generated by scripts/pre-build.js -export const commit = "702a10693c876b80f9c570ac4204cbb901aefe10"; -export const commitShort = "702a106"; +export const commit = "ab89632e7ea893d6c516e091c4c27f944c123190"; +export const commitShort = "ab89632"; export const commitAuthor = "Fabian Cook"; export const commitEmail = "hello@fabiancook.dev"; -export const commitMessage = "1.0.0-alpha.43"; -export const commitAt = "2023-06-22T08:44:58.000Z"; -export const secondsBetweenCommitAndBuild = 3952.32; -export const minutesBetweenCommitAndBuild = 65.87; -export const timeBetweenCommitAndBuild = "65 minutes and 52 seconds"; +export const commitMessage = "1.0.0-alpha.44"; +export const commitAt = "2023-06-22T09:51:48.000Z"; +export const secondsBetweenCommitAndBuild = 5254.22; +export const minutesBetweenCommitAndBuild = 87.57; +export const timeBetweenCommitAndBuild = "87 minutes and 34 seconds"; // Variables to be replaced after tests export const secondsBetweenCommitAndTestCompletion = ""; export const minutesBetweenCommitAndTestCompletion = ""; diff --git a/src/react/server/paths/product/list.tsx b/src/react/server/paths/product/list.tsx index 5b641f5..dc0fba4 100644 --- a/src/react/server/paths/product/list.tsx +++ b/src/react/server/paths/product/list.tsx @@ -1,5 +1,5 @@ import {useData, useInput, useProducts} from "../../data"; -import {listProductFiles, File} from "../../../../data"; +import {listProductFiles, File, listOffers, Offer} from "../../../../data"; import {isAnonymous} from "../../../../authentication"; import {FastifyRequest} from "fastify"; import {ok} from "../../../../is"; @@ -13,6 +13,7 @@ export const cache = true; export interface ProductInfo { images600: File[] productImages: Record + offers: Offer[]; } type Params = { @@ -39,7 +40,10 @@ export async function handler(): Promise { } ) ) - return { images600, productImages }; + const offers = await listOffers({ + public: isAnonymous() + }) + return { images600, productImages, offers }; } const LINK_CLASS = "text-blue-600 hover:bg-white underline hover:underline-offset-2"; @@ -47,9 +51,10 @@ const LINK_CLASS = "text-blue-600 hover:bg-white underline hover:underline-offse export function ListProducts() { const products = useProducts(); const { isAnonymous } = useData(); - const { images600, productImages } = useInput(); + const { images600, productImages, offers } = useInput(); const sorted = useMemo(() => { return [...products] + .filter(product => !product.generic) .sort((a, b) => { if (productImages[a.productId] && !productImages[b.productId]) { return -1; @@ -66,19 +71,30 @@ export function ListProducts() { {!isAnonymous ? Create Product : undefined}
{sorted.map(product => { - const image = images600.find(file => file.productId === product.productId); + const images = images600.filter(file => file.productId === product.productId); + const productOffer = offers.find(offer => ( + offer.items.length === 1 && + offer.items.find(item => item.type === "product" && item.productId === product.productId) + )) return ( -
+
{ - image ? ( - {String(image.alt - ) : undefined + images.map( + (image, index, array) => ( + + ) + ) }
@@ -90,7 +106,7 @@ export function ListProducts() { aria-hidden="true" className="absolute inset-x-0 bottom-0 h-36 bg-gradient-to-t from-black opacity-50" /> -

$PRICE

+

{productOffer ? `${productOffer.currencyCode ?? "$"}${productOffer.price}` : ""}

diff --git a/src/tests/remote.ts b/src/tests/remote.ts index e6c6823..8276081 100644 --- a/src/tests/remote.ts +++ b/src/tests/remote.ts @@ -2,7 +2,16 @@ import * as dotenv from "dotenv"; dotenv.config(); -import {File, getRemoteSourceKey, getRemoteSources, Product, setProduct, TYPE_BASE_NAMES} from "../data"; +import { + File, + getRemoteSourceKey, + getRemoteSources, + Offer, + Product, + setOffer, + setProduct, + TYPE_BASE_NAMES +} from "../data"; import {importRemoteSource} from "../remote/import"; import {ok} from "../is"; @@ -16,16 +25,33 @@ const bases = sources.filter(source => !typed.includes(source)); const imported: File[] = []; for (const source of bases) { - const file = await importRemoteSource({ - source, - json: true, - async handler(input: Product[]) { + + let handler; + + if (source === "product") { + handler = async function handler(input: Product[]) { // console.log(input); for (const product of input) { await setProduct(product); } + console.log("Updated products"); return input; } + } else if (source === "offer") { + handler = async function handler(input: Offer[]) { + // console.log(input); + for (const offer of input) { + await setOffer(offer); + } + console.log("Updated offers"); + return input; + } + } + if (!handler) continue; + const file = await importRemoteSource({ + source, + json: true, + handler }); console.log(file); imported.push(file); diff --git a/src/view/index.tsx b/src/view/index.tsx index c845c47..583f64e 100644 --- a/src/view/index.tsx +++ b/src/view/index.tsx @@ -2,6 +2,7 @@ import { FastifyInstance, FastifyReply, FastifyRequest } from "fastify"; import OpenNetworkServer, {OpenNetworkServerProps, ReactData} from "../react/server"; import { renderToStaticMarkup } from "react-dom/server"; import { + listOffers, listOrganisations, listPartners, listProducts, } from "../data";