From d801de0cf1cc06ab351e4581e47938bb84facda3 Mon Sep 17 00:00:00 2001 From: Chloe Date: Wed, 26 Jun 2024 09:44:58 +0700 Subject: [PATCH] finish activate warranty logic Signed-off-by: Chloe --- app/account/page.tsx | 20 +++++--- components/form/file-input/actions.ts | 6 +-- components/orders/actions.ts | 8 +-- components/orders/activate-warranty.tsx | 12 ++++- components/orders/mobile-order-actions.tsx | 41 +++++++++------ .../orders/warranty-activated-badge.tsx | 12 +++++ components/orders/warranty-header-action.tsx | 5 ++ lib/constants.ts | 3 +- lib/shopify/index.ts | 51 +++++++++++++++++-- lib/shopify/queries/customer.ts | 1 + lib/shopify/queries/orders.ts | 21 ++++++++ lib/shopify/types.ts | 11 ++++ lib/utils.ts | 12 ++++- 13 files changed, 167 insertions(+), 36 deletions(-) create mode 100644 components/orders/warranty-activated-badge.tsx create mode 100644 components/orders/warranty-header-action.tsx diff --git a/app/account/page.tsx b/app/account/page.tsx index fe2482c40..160c5feb8 100644 --- a/app/account/page.tsx +++ b/app/account/page.tsx @@ -3,13 +3,16 @@ import ActivateWarranty from 'components/orders/activate-warranty'; import MobileOrderActions from 'components/orders/mobile-order-actions'; import OrdersHeader from 'components/orders/orders-header'; import Price from 'components/price'; -import { getCustomerOrders } from 'lib/shopify'; -import { toPrintDate } from 'lib/utils'; +import { getCustomerOrders, getOrdersMetafields } from 'lib/shopify'; +import { isBeforeToday, toPrintDate } from 'lib/utils'; import Image from 'next/image'; import Link from 'next/link'; export default async function AccountPage() { - const orders = await getCustomerOrders(); + const [orders, ordersMetafields] = await Promise.all([ + getCustomerOrders(), + getOrdersMetafields() + ]); return (
@@ -51,7 +54,7 @@ export default async function AccountPage() { )} - +
View Order {order.normalizedId} - + {!isBeforeToday(ordersMetafields[order.id]?.warrantyActivationDeadline) && ( + + )}
@@ -76,7 +84,7 @@ export default async function AccountPage() { src={item.image.url} width={item.image.width} height={item.image.height} - alt={item.image.altText || item.title} + alt={item.image.altText || item.title || 'Product Image'} className="h-full w-full object-cover object-center" /> ) : ( diff --git a/components/form/file-input/actions.ts b/components/form/file-input/actions.ts index 55581992d..3f8e3a914 100644 --- a/components/form/file-input/actions.ts +++ b/components/form/file-input/actions.ts @@ -29,7 +29,7 @@ const createStagedUploadFiles = async (params: UploadInput) => { return JSON.parse(JSON.stringify(stagedTargets[0])); } catch (error) { - console.log(error); + console.log('createStagedUploadFiles action', error); } }; @@ -54,7 +54,7 @@ const onUploadFile = async ({ originalSource: resourceUrl }); } catch (error) { - console.log(error); + console.log('onUploadFile action', error); } }; @@ -81,6 +81,6 @@ export const handleUploadFile = async ({ file }: { file: File }) => { return result?.[0]?.id; } } catch (error) { - console.log(error); + console.log('handleUploadFile action', error); } }; diff --git a/components/orders/actions.ts b/components/orders/actions.ts index 3b392d1a0..c5158103c 100644 --- a/components/orders/actions.ts +++ b/components/orders/actions.ts @@ -1,8 +1,9 @@ 'use server'; import { handleUploadFile } from 'components/form/file-input/actions'; +import { TAGS } from 'lib/constants'; import { updateOrderMetafields } from 'lib/shopify'; -import { revalidatePath } from 'next/cache'; +import { revalidateTag } from 'next/cache'; export const activateWarranty = async (orderId: string, formData: FormData) => { let odometerFileId = null; @@ -37,8 +38,9 @@ export const activateWarranty = async (orderId: string, formData: FormData) => { orderId, metafields: rawFormData }); - revalidatePath('/account'); + + revalidateTag(TAGS.orderMetafields); } catch (error) { - console.log(error); + console.log('activateWarranty action', error); } }; diff --git a/components/orders/activate-warranty.tsx b/components/orders/activate-warranty.tsx index 7777cd52e..68325d27b 100644 --- a/components/orders/activate-warranty.tsx +++ b/components/orders/activate-warranty.tsx @@ -1,15 +1,23 @@ 'use client'; -import { Order } from 'lib/shopify/types'; +import { Order, OrderMetafield, WarrantyStatus } from 'lib/shopify/types'; import { useState } from 'react'; import ActivateWarrantyModal from './activate-warranty-modal'; +import WarrantyActivatedBadge from './warranty-activated-badge'; type ActivateWarrantyModalProps = { order: Order; + orderMetafields?: OrderMetafield; }; -const ActivateWarranty = ({ order }: ActivateWarrantyModalProps) => { +const ActivateWarranty = ({ order, orderMetafields }: ActivateWarrantyModalProps) => { const [isOpen, setIsOpen] = useState(false); + const isWarrantyActivated = orderMetafields?.warrantyStatus === WarrantyStatus.Activated; + + if (isWarrantyActivated) { + return ; + } + return ( <> - )} - + {!isPassDeadline && !isWarrantyActivated && ( + + {({ focus }) => ( + + )} + + )} diff --git a/components/orders/warranty-activated-badge.tsx b/components/orders/warranty-activated-badge.tsx new file mode 100644 index 000000000..69a3658fd --- /dev/null +++ b/components/orders/warranty-activated-badge.tsx @@ -0,0 +1,12 @@ +import { CheckCircleIcon } from '@heroicons/react/24/solid'; + +const WarrantyActivatedBadge = () => { + return ( + + + ); +}; + +export default WarrantyActivatedBadge; diff --git a/components/orders/warranty-header-action.tsx b/components/orders/warranty-header-action.tsx new file mode 100644 index 000000000..5bb301dfa --- /dev/null +++ b/components/orders/warranty-header-action.tsx @@ -0,0 +1,5 @@ +const WarrantyHeaderAction = () => { + return
WarrantyHeaderAction
; +}; + +export default WarrantyHeaderAction; diff --git a/lib/constants.ts b/lib/constants.ts index c3c8d0a82..793f90552 100644 --- a/lib/constants.ts +++ b/lib/constants.ts @@ -29,7 +29,8 @@ export const TAGS = { collections: 'collections', products: 'products', cart: 'cart', - pages: 'pages' + pages: 'pages', + orderMetafields: 'orderMetafields' }; export const HIDDEN_PRODUCT_TAG = 'nextjs-frontend-hidden'; diff --git a/lib/shopify/index.ts b/lib/shopify/index.ts index dbd69e2fb..1351b1491 100644 --- a/lib/shopify/index.ts +++ b/lib/shopify/index.ts @@ -39,7 +39,7 @@ import { getMenuQuery } from './queries/menu'; import { getMetaobjectQuery, getMetaobjectsQuery } from './queries/metaobject'; import { getImageQuery, getMetaobjectsByIdsQuery } from './queries/node'; import { getCustomerOrderQuery } from './queries/order'; -import { getCustomerOrdersQuery } from './queries/orders'; +import { getCustomerOrderMetafieldsQuery, getCustomerOrdersQuery } from './queries/orders'; import { getPageQuery, getPagesQuery } from './queries/page'; import { getProductQuery, @@ -63,6 +63,7 @@ import { Metaobject, Money, Order, + OrderMetafield, Page, PageInfo, Product, @@ -88,6 +89,7 @@ import { ShopifyMetaobjectsOperation, ShopifyMoneyV2, ShopifyOrder, + ShopifyOrderMetafield, ShopifyPage, ShopifyPageOperation, ShopifyPagesOperation, @@ -185,11 +187,13 @@ export async function shopifyFetch({ async function adminFetch({ headers, query, - variables + variables, + tags }: { headers?: HeadersInit; query: string; variables?: ExtractVariables; + tags?: string[]; }): Promise<{ status: number; body: T } | never> { try { const result = await fetch(adminEndpoint, { @@ -202,7 +206,9 @@ async function adminFetch({ body: JSON.stringify({ ...(query && { query }), ...(variables && { variables }) - }) + }), + ...(tags && { next: { tags } }), + cache: 'no-store' }); const body = await result.json(); @@ -505,7 +511,8 @@ function reshapeCustomer(customer: ShopifyCustomer): Customer { firstName: customer.firstName, lastName: customer.lastName, displayName: customer.displayName, - emailAddress: customer.emailAddress.emailAddress + emailAddress: customer.emailAddress.emailAddress, + id: customer.id }; } @@ -597,7 +604,6 @@ function reshapeOrder(shopifyOrder: ShopifyOrder): Order { totalPrice: reshapeMoney(item.totalPrice) })) || []; - console.log(shopifyOrder); const order: Order = { id: shopifyOrder.id, normalizedId: shopifyOrder.id.replace('gid://shopify/Order/', ''), @@ -1120,3 +1126,38 @@ export const updateOrderMetafields = async ({ return response.body.data.orderUpdate.order.id; }; + +export const getOrdersMetafields = async (): Promise<{ [key: string]: OrderMetafield }> => { + const customer = await getCustomer(); + const res = await adminFetch<{ + data: { + customer: { + orders: { + nodes: Array< + { + id: string; + } & ShopifyOrderMetafield + >; + }; + }; + }; + variables: { + id: string; + }; + }>({ + query: getCustomerOrderMetafieldsQuery, + variables: { id: customer.id }, + tags: [TAGS.orderMetafields] + }); + + return res.body.data.customer.orders.nodes.reduce( + (acc, order) => ({ + ...acc, + [order.id]: { + warrantyStatus: order.warrantyStatus?.value ?? null, + warrantyActivationDeadline: order.warrantyActivationDeadline?.value ?? null + } + }), + {} as { [key: string]: OrderMetafield } + ); +}; diff --git a/lib/shopify/queries/customer.ts b/lib/shopify/queries/customer.ts index 4f9f2bb24..079be1990 100644 --- a/lib/shopify/queries/customer.ts +++ b/lib/shopify/queries/customer.ts @@ -2,6 +2,7 @@ export const getCustomerQuery = /* GraphQL */ ` query customer { customer { + id emailAddress { emailAddress } diff --git a/lib/shopify/queries/orders.ts b/lib/shopify/queries/orders.ts index 1c55a2b9a..0b1cbe027 100644 --- a/lib/shopify/queries/orders.ts +++ b/lib/shopify/queries/orders.ts @@ -13,3 +13,24 @@ export const getCustomerOrdersQuery = `#graphql ${customerFragment} ${customerDetailsFragment} `; + +export const getCustomerOrderMetafieldsQuery = /* GraphQL */ ` + query getCustomerOrderMetafields($id: ID!) { + customer(id: $id) { + orders(first: 20, sortKey: PROCESSED_AT, reverse: true) { + nodes { + id + warrantyStatus: metafield(namespace: "custom", key: "warranty_status") { + value + } + warrantyActivationDeadline: metafield( + namespace: "custom" + key: "warranty_activation_deadline" + ) { + value + } + } + } + } + } +`; diff --git a/lib/shopify/types.ts b/lib/shopify/types.ts index 4965f259f..2980d1a4c 100644 --- a/lib/shopify/types.ts +++ b/lib/shopify/types.ts @@ -52,6 +52,7 @@ export type Customer = { firstName?: string; lastName?: string; displayName?: string; + id: string; }; export type Image = { @@ -856,3 +857,13 @@ export enum WarrantyStatus { NotActivated = 'Not Activated', LimitedActivated = 'Limited Activation' } + +export type ShopifyOrderMetafield = { + warrantyStatus: { value: WarrantyStatus } | null; + warrantyActivationDeadline: { value: string } | null; +}; + +export type OrderMetafield = { + warrantyStatus: WarrantyStatus | null; + warrantyActivationDeadline: string | null; +}; diff --git a/lib/utils.ts b/lib/utils.ts index 766ec9773..850264821 100644 --- a/lib/utils.ts +++ b/lib/utils.ts @@ -19,7 +19,6 @@ export const validateEnvironmentVariables = () => { 'SHOPIFY_STOREFRONT_ACCESS_TOKEN', 'SHOPIFY_CUSTOMER_ACCOUNT_API_CLIENT_ID', 'SHOPIFY_CUSTOMER_ACCOUNT_API_URL', - 'SHOPIFY_CUSTOMER_API_VERSION', 'SHOPIFY_ORIGIN_URL', 'SHOPIFY_ADMIN_API_ACCESS_TOKEN' ]; @@ -96,3 +95,14 @@ export function toPrintDate(date: string) { day: 'numeric' }); } + +export const isBeforeToday = (date?: string | null) => { + if (!date) return false; + const today = new Date(); + const compareDate = new Date(date); + + today.setHours(0, 0, 0, 0); + compareDate.setHours(0, 0, 0, 0); + + return compareDate <= today; +};