Skip to content

Commit

Permalink
refactor(sa): updates to algolia starter (#91)
Browse files Browse the repository at this point in the history
  • Loading branch information
ddaoxuan authored Nov 1, 2024
1 parent f210b5e commit b21d0e7
Show file tree
Hide file tree
Showing 182 changed files with 154,568 additions and 8,919 deletions.
2 changes: 1 addition & 1 deletion starters/shopify-algolia/.eslintrc.js
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@ module.exports = {
React: true,
JSX: true,
},
extends: ["next", "prettier", "react-app", "react-app/jest", "plugin:storybook/recommended", "plugin:tailwindcss/recommended"],
extends: ["next", "prettier", "react-app", "react-app/jest", "plugin:tailwindcss/recommended"],
parserOptions: {
babelOptions: {
presets: [require.resolve("next/babel")],
Expand Down
30 changes: 0 additions & 30 deletions starters/shopify-algolia/.storybook/main.ts

This file was deleted.

17 changes: 0 additions & 17 deletions starters/shopify-algolia/.storybook/preview.ts

This file was deleted.

1 change: 0 additions & 1 deletion starters/shopify-algolia/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -32,7 +32,6 @@ $ yarn create commerce
- Highly Scalable SEO Redirects ([Bloom Filters](https://nextjs.org/docs/app/building-your-application/routing/redirecting#managing-redirects-at-scale-advanced))
- Easy migration - migrate your existing solution in minutes
- Playwright - write end-to-end tests like a pro
- Storybook - create, test, and showcase your components
- T3 Env - manage your environment variables with ease
- Patch-package - fix external dependencies without losing your mind
- Components coupling and cohesion graph - a tool for managing component relationships
Expand Down
13 changes: 0 additions & 13 deletions starters/shopify-algolia/app/access-denied/page.tsx

This file was deleted.

103 changes: 67 additions & 36 deletions starters/shopify-algolia/app/actions/cart.actions.ts
Original file line number Diff line number Diff line change
@@ -1,86 +1,117 @@
"use server"

import { revalidateTag, unstable_cache } from "next/cache"
import { revalidateTag } from "next/cache"
import { cookies } from "next/headers"
import { storefrontClient } from "clients/storefront"
import { COOKIE_CART_ID, TAGS } from "constants/index"
import { isDemoMode } from "utils/demo-utils"
import { createCart, createCartItem, deleteCartItem, getCart, getProduct, updateCartItem } from "lib/shopify"

export const getCart = unstable_cache(async (cartId: string) => storefrontClient.getCart(cartId), [TAGS.CART], { revalidate: 60 * 15, tags: [TAGS.CART] })
export async function getOrCreateCart() {
const cartId = cookies().get(COOKIE_CART_ID)?.value
const cart = cartId ? await getCart(cartId) : await createCart()

export async function addCartItem(prevState: any, variantId: string) {
if (isDemoMode()) return { ok: false, message: "Demo mode active. Filtering, searching, and adding to cart disabled." }
if (!variantId) return { ok: false }
if (!cartId) {
const newCartId = cart?.id
if (newCartId) {
cookies().set(COOKIE_CART_ID, newCartId)
revalidateTag(TAGS.CART)
}
}

let cartId = cookies().get(COOKIE_CART_ID)?.value
let cart
return { cartId: cart?.id, cart }
}

if (cartId) cart = await storefrontClient.getCart(cartId)
export async function getItemAvailability({
cartId,
variantId,
productId,
}: {
cartId: string | null | undefined
variantId: string | null | undefined
productId: string | null | undefined
}) {
if (!variantId) {
return { inCartQuantity: 0, inStockQuantity: 0 }
}

if (!cartId || !cart) {
cart = await storefrontClient.createCart([])
cartId = cart?.id
cartId && cookies().set(COOKIE_CART_ID, cartId)
if (!cartId) {
const product = await getProduct(productId!)
const inStockQuantity = product?.variants?.find((variant) => variant.id === variantId)?.quantityAvailable ?? Infinity
return {
inCartQuantity: 0,
inStockQuantity,
}
}

revalidateTag(TAGS.CART)
const cart = await getCart(cartId)
const cartItem = cart?.items?.find((item) => item.merchandise.id === variantId)

return {
inCartQuantity: cartItem?.quantity ?? 0,
inStockQuantity: cartItem?.merchandise.quantityAvailable ?? Infinity,
}
}

const itemAvailability = await getItemAvailability(cartId, variantId)
export async function addCartItem(prevState: any, variantId: string, productId: string) {
if (isDemoMode()) {
return {
ok: false,
message: "Demo mode active. Filtering, searching, and adding to cart disabled.",
}
}

if (!itemAvailability || itemAvailability.inCartQuantity >= itemAvailability.inStockQuantity)
if (!variantId) return { ok: false }

const { cartId } = await getOrCreateCart()

if (!cartId) return { ok: false }

const availability = await getItemAvailability({ cartId, variantId, productId })
if (!availability || availability.inCartQuantity >= availability.inStockQuantity) {
return {
ok: false,
message: "This product is out of stock",
}
}

await storefrontClient.createCartItem(cartId!, [{ merchandiseId: variantId, quantity: 1 }])
await createCartItem(cartId, [{ merchandiseId: variantId, quantity: 1 }])
revalidateTag(TAGS.CART)

return { ok: true }
}

export async function getItemAvailability(cartId: string | null | undefined, variantId: string | null | undefined) {
if (!cartId || !variantId) return { inCartQuantity: 0, inStockQuantity: Infinity }

const cart = await storefrontClient.getCart(cartId)
const cartItem = cart?.items?.find((item) => item.merchandise.id === variantId)

return { inCartQuantity: cartItem?.quantity ?? 0, inStockQuantity: cartItem?.merchandise.quantityAvailable ?? Infinity }
}

export async function removeCartItem(prevState: any, itemId: string) {
const cartId = cookies().get(COOKIE_CART_ID)?.value

if (!cartId) return { ok: false }

await storefrontClient.deleteCartItem(cartId!, [itemId])
await deleteCartItem(cartId, [itemId])
revalidateTag(TAGS.CART)

return { ok: true }
}

export async function updateItemQuantity(prevState: any, payload: { itemId: string; variantId: string; quantity: number }) {
export async function updateItemQuantity(prevState: any, payload: { itemId: string; variantId: string; quantity: number; productId: string }) {
const cartId = cookies().get(COOKIE_CART_ID)?.value

if (!cartId) return { ok: false }

const { itemId, variantId, quantity } = payload
const { itemId, variantId, quantity, productId } = payload

if (quantity === 0) {
await storefrontClient.deleteCartItem(cartId, [itemId])
await deleteCartItem(cartId, [itemId])
revalidateTag(TAGS.CART)
return { ok: true }
}

const itemAvailability = await getItemAvailability(cartId, variantId)
if (!itemAvailability || quantity > itemAvailability.inStockQuantity)
const itemAvailability = await getItemAvailability({ cartId, variantId, productId })
if (!itemAvailability || quantity > itemAvailability.inStockQuantity) {
return {
ok: false,
message: "This product is out of stock",
}
}

await storefrontClient.updateCartItem(cartId, [{ id: itemId, merchandiseId: variantId, quantity }])

await updateCartItem(cartId, [{ id: itemId, merchandiseId: variantId, quantity }])
revalidateTag(TAGS.CART)

return { ok: true }
}
26 changes: 0 additions & 26 deletions starters/shopify-algolia/app/actions/collection.actions.ts

This file was deleted.

8 changes: 0 additions & 8 deletions starters/shopify-algolia/app/actions/page.actions.ts

This file was deleted.

56 changes: 5 additions & 51 deletions starters/shopify-algolia/app/actions/product.actions.ts
Original file line number Diff line number Diff line change
@@ -1,14 +1,10 @@
"use server"

import { unstable_cache } from "next/cache"
import { searchClient } from "lib/algolia/client"
import { env } from "env.mjs"

import { algolia } from "clients/search"
import type { Review } from "lib/reviews/types"

import { getDemoProductReviews, getDemoSingleProduct, isDemoMode } from "utils/demo-utils"
import { unstable_cache } from "next/cache"
import type { CommerceProduct } from "types"
import { notifyOptIn } from "utils/opt-in"
import { isDemoMode } from "utils/demo-utils"

export const searchProducts = unstable_cache(
async (query: string, limit: number = 4) => {
Expand All @@ -18,59 +14,17 @@ export const searchProducts = unstable_cache(
hasMore: false,
}

const { hits, estimatedTotalHits } = await algolia.search<CommerceProduct>({
const { hits, estimatedTotalHits } = await searchClient.search<CommerceProduct>({
indexName: env.ALGOLIA_PRODUCTS_INDEX,
searchParams: {
query,
hitsPerPage: limit,
attributesToRetrieve: ["id", "handle", "title", "featuredImage", "images", "variants"],
},
})

return { hits, hasMore: estimatedTotalHits > limit }
},
["autocomplete-search"],
{ revalidate: 3600 }
)

export const getProduct = unstable_cache(
async (handle: string) => {
if (isDemoMode()) return getDemoSingleProduct(handle)

const { hits } = await algolia.search<CommerceProduct>({
indexName: env.ALGOLIA_PRODUCTS_INDEX,
searchParams: {
filters: algolia.filterBuilder().where("handle", handle).build(),
hitsPerPage: 1,
},
})

return hits.find(Boolean) || null
},
["product-by-handle"],
{ revalidate: 3600 }
)

export const getProductReviews = unstable_cache(
async (handle: string, { page = 1, limit = 10 } = { page: 1, limit: 10 }) => {
if (isDemoMode()) return getDemoProductReviews()

if (!env.ALGOLIA_REVIEWS_INDEX) {
notifyOptIn({ feature: "reviews", source: "product.actions.ts" })
return { reviews: [], total: 0 }
}

const { hits, nbHits } = await algolia.search<Review>({
indexName: env.ALGOLIA_REVIEWS_INDEX,
searchParams: {
filters: algolia.filterBuilder().where("product_handle", handle).and().where("published", "true").and().where("hidden", "false").build(),
hitsPerPage: limit,
page,
attributesToRetrieve: ["body", "rating", "verified", "reviewer", "published", "created_at", "hidden", "featured"],
},
})

return { reviews: hits.sort((a, b) => new Date(b.created_at).getTime() - new Date(a.created_at).getTime()), total: nbHits }
},
["product-reviews-by-handle"],
{ revalidate: 3600 }
)
4 changes: 2 additions & 2 deletions starters/shopify-algolia/app/actions/reviews.actions.ts
Original file line number Diff line number Diff line change
@@ -1,14 +1,14 @@
"use server"

import { reviewsClient } from "clients/reviews"
import { createProductReview } from "lib/reviews"
import type { ProductReviewBody } from "lib/reviews/types"

import { headers } from "next/headers"

export const submitReview = async (payload: Omit<ProductReviewBody, "ip_addr">) => {
try {
const ipAddress = headers().get("x-forwarded-for") || null
await reviewsClient.createProductReview({ ...payload, ip_addr: ipAddress })
await createProductReview({ ...payload, ip_addr: ipAddress })
} catch (err) {
throw new Error(err as string)
}
Expand Down
Loading

0 comments on commit b21d0e7

Please sign in to comment.