Skip to content

Commit

Permalink
feat: added pagination
Browse files Browse the repository at this point in the history
fix: only cache 200 responses
  • Loading branch information
jacob-ebey committed Jan 12, 2022
1 parent 1cdfc23 commit 0abc6a5
Show file tree
Hide file tree
Showing 6 changed files with 118 additions and 52 deletions.
30 changes: 28 additions & 2 deletions app/containers/cdp/cdp.component.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -86,8 +86,16 @@ function ThreeProductGridItem({ product }: { product: CDPProduct }) {
}

export default function CDP() {
let { category, sort, categories, search, sortByOptions, products } =
useLoaderData<LoaderData>();
let {
category,
sort,
categories,
search,
sortByOptions,
products,
hasNextPage,
nextPageCursor,
} = useLoaderData<LoaderData>();
let submit = useSubmit();
let location = useLocation();

Expand All @@ -114,6 +122,7 @@ export default function CDP() {
prefetch="intent"
to={(() => {
let params = new URLSearchParams(location.search);
params.delete("cursor");
params.delete("q");
params.set("category", cat.slug);
params.sort();
Expand Down Expand Up @@ -142,6 +151,7 @@ export default function CDP() {
prefetch="intent"
to={(() => {
let params = new URLSearchParams(location.search);
params.delete("cursor");
params.set("sort", sortBy.value);
return location.pathname + "?" + params.toString();
})()}
Expand All @@ -157,6 +167,7 @@ export default function CDP() {
action={(() => {
let params = new URLSearchParams(location.search);
params.delete("category");
params.delete("cursor");
params.delete("q");
let search = params.toString();
search = search ? "?" + search : "";
Expand Down Expand Up @@ -190,6 +201,7 @@ export default function CDP() {
className="px-4 pb-2 border-b border-zinc-700 lg:hidden"
action={(() => {
let params = new URLSearchParams(location.search);
params.delete("cursor");
params.delete("sort");
let search = params.toString();
search = search ? "?" + search : "";
Expand Down Expand Up @@ -230,6 +242,20 @@ export default function CDP() {
<ThreeProductGridItem key={product.id} product={product} />
))}
</ul>
{hasNextPage && nextPageCursor && (
<p className="mt-8">
<Link
className="text-lg font-semibold focus:underline hover:underline"
to={(() => {
let params = new URLSearchParams(location.search);
params.set("cursor", nextPageCursor);
return location.pathname + "?" + params.toString();
})()}
>
Load more
</Link>
</p>
)}
</article>
</main>
);
Expand Down
11 changes: 8 additions & 3 deletions app/containers/cdp/cdp.server.ts
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,8 @@ export type LoaderData = {
search?: string;
sortByOptions: SortByOption[];
products: CDPProduct[];
hasNextPage: boolean;
nextPageCursor?: string;
};

export let loader: LoaderFunction = async ({ request, params }) => {
Expand All @@ -35,11 +37,12 @@ export let loader: LoaderFunction = async ({ request, params }) => {
let category = url.searchParams.get("category") || undefined;
let sort = url.searchParams.get("sort") || undefined;
let search = url.searchParams.get("q") || undefined;
let cursor = url.searchParams.get("cursor") || undefined;

let [categories, sortByOptions, products, wishlist] = await Promise.all([
let [categories, sortByOptions, productsPage, wishlist] = await Promise.all([
commerce.getCategories(lang, 250),
commerce.getSortByOptions(lang),
commerce.getProducts(lang, category, sort, search),
commerce.getProducts(lang, category, sort, search, cursor),
session.getWishlist(),
]);

Expand All @@ -53,7 +56,9 @@ export let loader: LoaderFunction = async ({ request, params }) => {
categories,
search,
sortByOptions,
products: products.map((product) => ({
hasNextPage: productsPage.hasNextPage,
nextPageCursor: productsPage.nextPageCursor,
products: productsPage.products.map((product) => ({
favorited: wishlistHasProduct.has(product.id),
formattedPrice: product.formattedPrice,
id: product.id,
Expand Down
1 change: 0 additions & 1 deletion app/containers/wishlist/wishlist.server.ts
Original file line number Diff line number Diff line change
Expand Up @@ -44,7 +44,6 @@ export let action: ActionFunction = async ({ request, params }) => {
let productId = formData.get("productId");
let variantId = formData.get("variantId");
let quantityStr = formData.get("quantity");
console.log({ productId, variantId, quantityStr });
if (!productId || !variantId || !quantityStr) {
break;
}
Expand Down
12 changes: 10 additions & 2 deletions app/models/ecommerce-provider.server.ts
Original file line number Diff line number Diff line change
Expand Up @@ -77,6 +77,12 @@ export interface SelectedProductOption {
value: string;
}

export interface ProductsResult {
hasNextPage: boolean;
nextPageCursor?: string;
products: Product[];
}

export interface EcommerceProvider {
getCartInfo(
locale: Language,
Expand All @@ -96,8 +102,10 @@ export interface EcommerceProvider {
language: Language,
category?: string,
sort?: string,
search?: string
): Promise<Product[]>;
search?: string,
cursor?: string,
perPage?: number
): Promise<ProductsResult>;
getSortByOptions(language: Language): Promise<SortByOption[]>;
getWishlistInfo(
locale: Language,
Expand Down
63 changes: 41 additions & 22 deletions app/models/ecommerce-providers/shopify.server.ts
Original file line number Diff line number Diff line change
Expand Up @@ -26,7 +26,7 @@ export function createShopifyProvider({
storefrontAccessToken,
}: ShopifyProviderOptions): EcommerceProvider {
let href = `https://${shop}.myshopify.com/api/2021-10/graphql.json`;
function query(locale: string, query: string, variables?: any) {
async function query(locale: string, query: string, variables?: any) {
let request = new Request(href, {
method: "POST",
headers: {
Expand Down Expand Up @@ -62,7 +62,7 @@ export function createShopifyProvider({
let itemsMap = new Map(items.map((item) => [item.variantId, item]));
let fullItems: FullCartItem[] = [];
for (let item of json.data.nodes) {
let itemInput = itemsMap.get(item.id);
let itemInput = !!item && itemsMap.get(item.id);
if (!itemInput) {
continue;
}
Expand Down Expand Up @@ -94,7 +94,9 @@ export function createShopifyProvider({

let formattedSubTotal = formatPrice({
amount: subtotal.toDecimalPlaces(2).toString(),
currencyCode: json.data.nodes[0].priceV2.currencyCode,
currencyCode: json.data.nodes.find(
(n: any) => !!n?.priceV2?.currencyCode
).priceV2.currencyCode,
});

let translations = getTranslations(locale, ["Calculated at checkout"]);
Expand Down Expand Up @@ -254,7 +256,7 @@ export function createShopifyProvider({
})),
};
},
async getProducts(locale, category, sort, search) {
async getProducts(locale, category, sort, search, cursor, perPage = 30) {
let q = "";
if (search) {
q += `product_type:${search} OR title:${search} OR tag:${search} `;
Expand Down Expand Up @@ -294,31 +296,39 @@ export function createShopifyProvider({
category ? getCollectionProductsQuery : getAllProductsQuery,
{
...sortVariables,
first: 250,
first: perPage,
query: q,
collection: category,
cursor,
}
);

let edges = category
? json.data.collections.edges[0]?.node.products.edges
: json.data.products.edges;
let productsInfo = category
? json.data.collections.edges[0]?.node.products
: json.data.products;

let { edges, pageInfo } = productsInfo;

let nextPageCursor: string | undefined = undefined;
let products =
edges?.map(
({
cursor,
node: { id, handle, title, images, priceRange, variants },
}: any): Product => ({
formattedPrice: formatPrice(priceRange.minVariantPrice),
id,
defaultVariantId: variants.edges[0].node.id,
image: images.edges[0].node.originalSrc,
slug: handle,
title,
})
}: any): Product => {
nextPageCursor = cursor;
return {
formattedPrice: formatPrice(priceRange.minVariantPrice),
id,
defaultVariantId: variants.edges[0].node.id,
image: images.edges[0].node.originalSrc,
slug: handle,
title,
};
}
) || [];

return products;
return { hasNextPage: pageInfo.hasNextPage, nextPageCursor, products };
},
async getSortByOptions(locale) {
let translations = getTranslations(locale, [
Expand Down Expand Up @@ -358,7 +368,7 @@ export function createShopifyProvider({
let itemsMap = new Map(items.map((item) => [item.variantId, item]));
let fullItems: FullWishlistItem[] = [];
for (let item of json.data.nodes) {
let itemInput = itemsMap.get(item.id);
let itemInput = !!item && itemsMap.get(item.id);
if (!itemInput) {
continue;
}
Expand Down Expand Up @@ -490,6 +500,7 @@ let productConnectionFragment = /* GraphQL */ `
hasPreviousPage
}
edges {
cursor
node {
id
title
Expand Down Expand Up @@ -529,16 +540,18 @@ let productConnectionFragment = /* GraphQL */ `

let getAllProductsQuery = /* GraphQL */ `
query getAllProducts(
$first: Int = 250
$first: Int = 20
$query: String = ""
$sortKey: ProductSortKeys = RELEVANCE
$reverse: Boolean = false
$cursor: String
) {
products(
first: $first
sortKey: $sortKey
reverse: $reverse
query: $query
after: $cursor
) {
...productConnection
}
Expand All @@ -549,15 +562,21 @@ let getAllProductsQuery = /* GraphQL */ `
let getCollectionProductsQuery = /* GraphQL */ `
query getProductsFromCollection(
$collection: String
$first: Int = 250
$first: Int = 20
$sortKey: ProductCollectionSortKeys = RELEVANCE
$reverse: Boolean = false
$cursor: String
) {
collections(first: 1, query: $collection) {
edges {
node {
handle
products(first: $first, sortKey: $sortKey, reverse: $reverse) {
products(
first: $first
sortKey: $sortKey
reverse: $reverse
after: $cursor
) {
...productConnection
}
}
Expand Down Expand Up @@ -618,7 +637,7 @@ let getProductQuery = /* GraphQL */ `
}
}
}
images(first: 250) {
images(first: 20) {
pageInfo {
hasNextPage
hasPreviousPage
Expand Down
53 changes: 31 additions & 22 deletions app/models/request-response-caches/swr-redis-cache.server.ts
Original file line number Diff line number Diff line change
Expand Up @@ -64,6 +64,11 @@ export let createSwrRedisCache = ({
let cachedResponseJson = JSON.parse(
cachedResponseString
) as CachedResponse;

if (cachedResponseJson.status !== 200) {
return null;
}

let cachedResponse = new Response(cachedResponseJson.body, {
status: cachedResponseJson.status,
statusText: cachedResponseJson.statusText,
Expand All @@ -77,15 +82,17 @@ export let createSwrRedisCache = ({

(async () => {
let responseToCache = await fetch(request.clone());
let toCache: CachedResponse = {
status: responseToCache.status,
statusText: responseToCache.statusText,
headers: Array.from(responseToCache.headers),
body: await responseToCache.text(),
};

await redisClient.set(responseKey, JSON.stringify(toCache));
await redisClient.setEx(stillGoodKey, maxAgeSeconds, "true");
if (responseToCache.status === 200) {
let toCache: CachedResponse = {
status: responseToCache.status,
statusText: responseToCache.statusText,
headers: Array.from(responseToCache.headers),
body: await responseToCache.text(),
};

await redisClient.set(responseKey, JSON.stringify(toCache));
await redisClient.setEx(stillGoodKey, maxAgeSeconds, "true");
}
})().catch((error) => {
console.error("Failed to revalidate", error);
});
Expand All @@ -100,19 +107,21 @@ export let createSwrRedisCache = ({
let responseToCache = response.clone();
response.headers.set("X-SWR-Cache", "miss");

(async () => {
let toCache: CachedResponse = {
status: responseToCache.status,
statusText: responseToCache.statusText,
headers: Array.from(responseToCache.headers),
body: await responseToCache.text(),
};

await redisClient.set(responseKey, JSON.stringify(toCache));
await redisClient.setEx(stillGoodKey, maxAgeSeconds, "true");
})().catch((error) => {
console.error("Failed to seed cache", error);
});
if (responseToCache.status === 200) {
(async () => {
let toCache: CachedResponse = {
status: responseToCache.status,
statusText: responseToCache.statusText,
headers: Array.from(responseToCache.headers),
body: await responseToCache.text(),
};

await redisClient.set(responseKey, JSON.stringify(toCache));
await redisClient.setEx(stillGoodKey, maxAgeSeconds, "true");
})().catch((error) => {
console.error("Failed to seed cache", error);
});
}
}

return response;
Expand Down

0 comments on commit 0abc6a5

Please sign in to comment.