finish activate warranty logic

Signed-off-by: Chloe <pinkcloudvnn@gmail.com>
This commit is contained in:
Chloe 2024-06-26 09:44:58 +07:00
parent 2477fdf84e
commit d801de0cf1
No known key found for this signature in database
GPG Key ID: CFD53CE570D42DF5
13 changed files with 167 additions and 36 deletions

View File

@ -3,13 +3,16 @@ import ActivateWarranty from 'components/orders/activate-warranty';
import MobileOrderActions from 'components/orders/mobile-order-actions'; import MobileOrderActions from 'components/orders/mobile-order-actions';
import OrdersHeader from 'components/orders/orders-header'; import OrdersHeader from 'components/orders/orders-header';
import Price from 'components/price'; import Price from 'components/price';
import { getCustomerOrders } from 'lib/shopify'; import { getCustomerOrders, getOrdersMetafields } from 'lib/shopify';
import { toPrintDate } from 'lib/utils'; import { isBeforeToday, toPrintDate } from 'lib/utils';
import Image from 'next/image'; import Image from 'next/image';
import Link from 'next/link'; import Link from 'next/link';
export default async function AccountPage() { export default async function AccountPage() {
const orders = await getCustomerOrders(); const [orders, ordersMetafields] = await Promise.all([
getCustomerOrders(),
getOrdersMetafields()
]);
return ( return (
<div className="py-5 sm:py-10"> <div className="py-5 sm:py-10">
@ -51,7 +54,7 @@ export default async function AccountPage() {
)} )}
</dl> </dl>
<MobileOrderActions order={order} /> <MobileOrderActions order={order} orderMetafields={ordersMetafields[order.id]} />
<div className="hidden lg:col-span-2 lg:flex lg:items-center lg:justify-end lg:space-x-4"> <div className="hidden lg:col-span-2 lg:flex lg:items-center lg:justify-end lg:space-x-4">
<Link <Link
@ -61,7 +64,12 @@ export default async function AccountPage() {
<span>View Order</span> <span>View Order</span>
<span className="sr-only">{order.normalizedId}</span> <span className="sr-only">{order.normalizedId}</span>
</Link> </Link>
<ActivateWarranty order={order} /> {!isBeforeToday(ordersMetafields[order.id]?.warrantyActivationDeadline) && (
<ActivateWarranty
order={order}
orderMetafields={ordersMetafields[order.id]}
/>
)}
</div> </div>
</div> </div>
@ -76,7 +84,7 @@ export default async function AccountPage() {
src={item.image.url} src={item.image.url}
width={item.image.width} width={item.image.width}
height={item.image.height} 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" className="h-full w-full object-cover object-center"
/> />
) : ( ) : (

View File

@ -29,7 +29,7 @@ const createStagedUploadFiles = async (params: UploadInput) => {
return JSON.parse(JSON.stringify(stagedTargets[0])); return JSON.parse(JSON.stringify(stagedTargets[0]));
} catch (error) { } catch (error) {
console.log(error); console.log('createStagedUploadFiles action', error);
} }
}; };
@ -54,7 +54,7 @@ const onUploadFile = async ({
originalSource: resourceUrl originalSource: resourceUrl
}); });
} catch (error) { } 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; return result?.[0]?.id;
} }
} catch (error) { } catch (error) {
console.log(error); console.log('handleUploadFile action', error);
} }
}; };

View File

@ -1,8 +1,9 @@
'use server'; 'use server';
import { handleUploadFile } from 'components/form/file-input/actions'; import { handleUploadFile } from 'components/form/file-input/actions';
import { TAGS } from 'lib/constants';
import { updateOrderMetafields } from 'lib/shopify'; import { updateOrderMetafields } from 'lib/shopify';
import { revalidatePath } from 'next/cache'; import { revalidateTag } from 'next/cache';
export const activateWarranty = async (orderId: string, formData: FormData) => { export const activateWarranty = async (orderId: string, formData: FormData) => {
let odometerFileId = null; let odometerFileId = null;
@ -37,8 +38,9 @@ export const activateWarranty = async (orderId: string, formData: FormData) => {
orderId, orderId,
metafields: rawFormData metafields: rawFormData
}); });
revalidatePath('/account');
revalidateTag(TAGS.orderMetafields);
} catch (error) { } catch (error) {
console.log(error); console.log('activateWarranty action', error);
} }
}; };

View File

@ -1,15 +1,23 @@
'use client'; 'use client';
import { Order } from 'lib/shopify/types'; import { Order, OrderMetafield, WarrantyStatus } from 'lib/shopify/types';
import { useState } from 'react'; import { useState } from 'react';
import ActivateWarrantyModal from './activate-warranty-modal'; import ActivateWarrantyModal from './activate-warranty-modal';
import WarrantyActivatedBadge from './warranty-activated-badge';
type ActivateWarrantyModalProps = { type ActivateWarrantyModalProps = {
order: Order; order: Order;
orderMetafields?: OrderMetafield;
}; };
const ActivateWarranty = ({ order }: ActivateWarrantyModalProps) => { const ActivateWarranty = ({ order, orderMetafields }: ActivateWarrantyModalProps) => {
const [isOpen, setIsOpen] = useState(false); const [isOpen, setIsOpen] = useState(false);
const isWarrantyActivated = orderMetafields?.warrantyStatus === WarrantyStatus.Activated;
if (isWarrantyActivated) {
return <WarrantyActivatedBadge />;
}
return ( return (
<> <>
<button <button

View File

@ -3,13 +3,22 @@
import { Button, Menu, MenuButton, MenuItem, MenuItems } from '@headlessui/react'; import { Button, Menu, MenuButton, MenuItem, MenuItems } from '@headlessui/react';
import { EllipsisVerticalIcon } from '@heroicons/react/24/solid'; import { EllipsisVerticalIcon } from '@heroicons/react/24/solid';
import clsx from 'clsx'; import clsx from 'clsx';
import { Order } from 'lib/shopify/types'; import { Order, OrderMetafield, WarrantyStatus } from 'lib/shopify/types';
import { isBeforeToday } from 'lib/utils';
import Link from 'next/link'; import Link from 'next/link';
import { useState } from 'react'; import { useState } from 'react';
import ActivateWarrantyModal from './activate-warranty-modal'; import ActivateWarrantyModal from './activate-warranty-modal';
const MobileOrderActions = ({ order }: { order: Order }) => { const MobileOrderActions = ({
order,
orderMetafields
}: {
order: Order;
orderMetafields?: OrderMetafield;
}) => {
const [isOpen, setIsOpen] = useState(false); const [isOpen, setIsOpen] = useState(false);
const isWarrantyActivated = orderMetafields?.warrantyStatus === WarrantyStatus.Activated;
const isPassDeadline = isBeforeToday(orderMetafields?.warrantyActivationDeadline);
return ( return (
<> <>
@ -39,6 +48,7 @@ const MobileOrderActions = ({ order }: { order: Order }) => {
</Link> </Link>
)} )}
</MenuItem> </MenuItem>
{!isPassDeadline && !isWarrantyActivated && (
<MenuItem> <MenuItem>
{({ focus }) => ( {({ focus }) => (
<Button <Button
@ -52,6 +62,7 @@ const MobileOrderActions = ({ order }: { order: Order }) => {
</Button> </Button>
)} )}
</MenuItem> </MenuItem>
)}
</div> </div>
</MenuItems> </MenuItems>
</Menu> </Menu>

View File

@ -0,0 +1,12 @@
import { CheckCircleIcon } from '@heroicons/react/24/solid';
const WarrantyActivatedBadge = () => {
return (
<span className="inline-flex items-center gap-x-2 rounded-md bg-green-50 px-2.5 py-2 text-sm font-medium text-green-700 ring-1 ring-inset ring-green-600/20">
<CheckCircleIcon className="h-5 w-5 text-green-500" aria-hidden="true" />
Warranty Activated
</span>
);
};
export default WarrantyActivatedBadge;

View File

@ -0,0 +1,5 @@
const WarrantyHeaderAction = () => {
return <div>WarrantyHeaderAction</div>;
};
export default WarrantyHeaderAction;

View File

@ -29,7 +29,8 @@ export const TAGS = {
collections: 'collections', collections: 'collections',
products: 'products', products: 'products',
cart: 'cart', cart: 'cart',
pages: 'pages' pages: 'pages',
orderMetafields: 'orderMetafields'
}; };
export const HIDDEN_PRODUCT_TAG = 'nextjs-frontend-hidden'; export const HIDDEN_PRODUCT_TAG = 'nextjs-frontend-hidden';

View File

@ -39,7 +39,7 @@ import { getMenuQuery } from './queries/menu';
import { getMetaobjectQuery, getMetaobjectsQuery } from './queries/metaobject'; import { getMetaobjectQuery, getMetaobjectsQuery } from './queries/metaobject';
import { getImageQuery, getMetaobjectsByIdsQuery } from './queries/node'; import { getImageQuery, getMetaobjectsByIdsQuery } from './queries/node';
import { getCustomerOrderQuery } from './queries/order'; import { getCustomerOrderQuery } from './queries/order';
import { getCustomerOrdersQuery } from './queries/orders'; import { getCustomerOrderMetafieldsQuery, getCustomerOrdersQuery } from './queries/orders';
import { getPageQuery, getPagesQuery } from './queries/page'; import { getPageQuery, getPagesQuery } from './queries/page';
import { import {
getProductQuery, getProductQuery,
@ -63,6 +63,7 @@ import {
Metaobject, Metaobject,
Money, Money,
Order, Order,
OrderMetafield,
Page, Page,
PageInfo, PageInfo,
Product, Product,
@ -88,6 +89,7 @@ import {
ShopifyMetaobjectsOperation, ShopifyMetaobjectsOperation,
ShopifyMoneyV2, ShopifyMoneyV2,
ShopifyOrder, ShopifyOrder,
ShopifyOrderMetafield,
ShopifyPage, ShopifyPage,
ShopifyPageOperation, ShopifyPageOperation,
ShopifyPagesOperation, ShopifyPagesOperation,
@ -185,11 +187,13 @@ export async function shopifyFetch<T>({
async function adminFetch<T>({ async function adminFetch<T>({
headers, headers,
query, query,
variables variables,
tags
}: { }: {
headers?: HeadersInit; headers?: HeadersInit;
query: string; query: string;
variables?: ExtractVariables<T>; variables?: ExtractVariables<T>;
tags?: string[];
}): Promise<{ status: number; body: T } | never> { }): Promise<{ status: number; body: T } | never> {
try { try {
const result = await fetch(adminEndpoint, { const result = await fetch(adminEndpoint, {
@ -202,7 +206,9 @@ async function adminFetch<T>({
body: JSON.stringify({ body: JSON.stringify({
...(query && { query }), ...(query && { query }),
...(variables && { variables }) ...(variables && { variables })
}) }),
...(tags && { next: { tags } }),
cache: 'no-store'
}); });
const body = await result.json(); const body = await result.json();
@ -505,7 +511,8 @@ function reshapeCustomer(customer: ShopifyCustomer): Customer {
firstName: customer.firstName, firstName: customer.firstName,
lastName: customer.lastName, lastName: customer.lastName,
displayName: customer.displayName, 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) totalPrice: reshapeMoney(item.totalPrice)
})) || []; })) || [];
console.log(shopifyOrder);
const order: Order = { const order: Order = {
id: shopifyOrder.id, id: shopifyOrder.id,
normalizedId: shopifyOrder.id.replace('gid://shopify/Order/', ''), normalizedId: shopifyOrder.id.replace('gid://shopify/Order/', ''),
@ -1120,3 +1126,38 @@ export const updateOrderMetafields = async ({
return response.body.data.orderUpdate.order.id; 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 }
);
};

View File

@ -2,6 +2,7 @@
export const getCustomerQuery = /* GraphQL */ ` export const getCustomerQuery = /* GraphQL */ `
query customer { query customer {
customer { customer {
id
emailAddress { emailAddress {
emailAddress emailAddress
} }

View File

@ -13,3 +13,24 @@ export const getCustomerOrdersQuery = `#graphql
${customerFragment} ${customerFragment}
${customerDetailsFragment} ${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
}
}
}
}
}
`;

View File

@ -52,6 +52,7 @@ export type Customer = {
firstName?: string; firstName?: string;
lastName?: string; lastName?: string;
displayName?: string; displayName?: string;
id: string;
}; };
export type Image = { export type Image = {
@ -856,3 +857,13 @@ export enum WarrantyStatus {
NotActivated = 'Not Activated', NotActivated = 'Not Activated',
LimitedActivated = 'Limited Activation' LimitedActivated = 'Limited Activation'
} }
export type ShopifyOrderMetafield = {
warrantyStatus: { value: WarrantyStatus } | null;
warrantyActivationDeadline: { value: string } | null;
};
export type OrderMetafield = {
warrantyStatus: WarrantyStatus | null;
warrantyActivationDeadline: string | null;
};

View File

@ -19,7 +19,6 @@ export const validateEnvironmentVariables = () => {
'SHOPIFY_STOREFRONT_ACCESS_TOKEN', 'SHOPIFY_STOREFRONT_ACCESS_TOKEN',
'SHOPIFY_CUSTOMER_ACCOUNT_API_CLIENT_ID', 'SHOPIFY_CUSTOMER_ACCOUNT_API_CLIENT_ID',
'SHOPIFY_CUSTOMER_ACCOUNT_API_URL', 'SHOPIFY_CUSTOMER_ACCOUNT_API_URL',
'SHOPIFY_CUSTOMER_API_VERSION',
'SHOPIFY_ORIGIN_URL', 'SHOPIFY_ORIGIN_URL',
'SHOPIFY_ADMIN_API_ACCESS_TOKEN' 'SHOPIFY_ADMIN_API_ACCESS_TOKEN'
]; ];
@ -96,3 +95,14 @@ export function toPrintDate(date: string) {
day: 'numeric' 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;
};