diff --git a/app/account/(orders)/page.tsx b/app/account/(orders)/page.tsx index c3286a12f..32ac1f27b 100644 --- a/app/account/(orders)/page.tsx +++ b/app/account/(orders)/page.tsx @@ -62,7 +62,7 @@ export default async function AccountPage() { {order.normalizedId} - {!isBeforeToday(order?.warrantyActivationDeadline) && ( + {!isBeforeToday(order?.warrantyActivationDeadline?.value) && ( )} {!order.orderConfirmation && } diff --git a/components/filters/hompage-filters.tsx b/components/filters/hompage-filters.tsx index 3c6e7a38d..9cf777784 100644 --- a/components/filters/hompage-filters.tsx +++ b/components/filters/hompage-filters.tsx @@ -1,10 +1,10 @@ -import { getMenu, getMetaobjects } from 'lib/shopify'; +import { getAllMetaobjects, getMenu } from 'lib/shopify'; import FiltersList from './filters-list'; const HomePageFilters = async () => { - const yearsData = getMetaobjects('make_model_year_composite'); - const modelsData = getMetaobjects('make_model_composite'); - const makesData = getMetaobjects('make'); + const yearsData = getAllMetaobjects('make_model_year_composite'); + const modelsData = getAllMetaobjects('make_model_composite'); + const makesData = getAllMetaobjects('make'); const [years, models, makes] = await Promise.all([yearsData, modelsData, makesData]); const menu = await getMenu('main-menu'); diff --git a/components/orders/actions.ts b/components/orders/actions.ts deleted file mode 100644 index 4fad38bfe..000000000 --- a/components/orders/actions.ts +++ /dev/null @@ -1,95 +0,0 @@ -'use server'; - -import { handleUploadFile } from 'components/form/file-input/actions'; -import { TAGS } from 'lib/constants'; -import { updateOrderMetafields } from 'lib/shopify'; -import { ShopifyOrderMetafield, UpdateOrderMetafieldInput } from 'lib/shopify/types'; -import { revalidateTag } from 'next/cache'; - -const getMetafieldValue = ( - key: keyof ShopifyOrderMetafield, - newValue: { value?: string | null; type: string; key: string }, - orderMetafields?: ShopifyOrderMetafield -): UpdateOrderMetafieldInput => { - return orderMetafields?.[key]?.id - ? { id: orderMetafields[key]?.id!, value: newValue.value, key: newValue.key } - : { ...newValue, namespace: 'custom' }; -}; - -export const activateWarranty = async ( - orderId: string, - formData: FormData, - orderMetafields?: ShopifyOrderMetafield -) => { - let odometerFileId = null; - let installationFileId = null; - const odometerFile = formData.get('warranty_activation_odometer'); - const installationFile = formData.get('warranty_activation_installation'); - if (odometerFile) { - odometerFileId = await handleUploadFile({ file: odometerFile as File }); - } - - if (installationFile) { - installationFileId = await handleUploadFile({ file: installationFile as File }); - } - console.log(formData.get('warranty_activation_self_install')); - // https://shopify.dev/docs/api/admin-graphql/2024-01/mutations/orderUpdate - const rawFormData = [ - getMetafieldValue( - 'warrantyActivationOdometer', - { - key: 'warranty_activation_odometer', - value: odometerFileId, - type: 'file_reference' - }, - orderMetafields - ), - getMetafieldValue( - 'warrantyActivationInstallation', - { - key: 'warranty_activation_installation', - value: installationFileId, - type: 'file_reference' - }, - orderMetafields - ), - getMetafieldValue( - 'warrantyActivationSelfInstall', - { - key: 'warranty_activation_self_install', - value: formData.get('warranty_activation_self_install') === 'on' ? 'true' : 'false', - type: 'boolean' - }, - orderMetafields - ), - getMetafieldValue( - 'warrantyActivationMileage', - { - key: 'warranty_activation_mileage', - value: formData.get('warranty_activation_mileage') as string | null, - type: 'number_integer' - }, - orderMetafields - ), - getMetafieldValue( - 'warrantyActivationVIN', - { - key: 'warranty_activation_vin', - value: formData.get('warranty_activation_vin') as string | null, - type: 'single_line_text_field' - }, - orderMetafields - ) - ]; - - try { - await updateOrderMetafields({ - orderId, - metafields: rawFormData - }); - - revalidateTag(TAGS.orderMetafields); - } catch (error) { - console.log('activateWarranty action', error); - } -}; diff --git a/components/orders/actions.tsx b/components/orders/actions.tsx index 343aeeb54..becf68b23 100644 --- a/components/orders/actions.tsx +++ b/components/orders/actions.tsx @@ -5,10 +5,25 @@ import OrderConfirmationPdf from 'components/orders/order-confirmation-pdf'; import { handleUploadFile } from 'components/form/file-input/actions'; import { TAGS } from 'lib/constants'; import { updateOrderMetafields } from 'lib/shopify'; -import { Order, OrderConfirmationContent } from 'lib/shopify/types'; +import { + Order, + OrderConfirmationContent, + ShopifyOrderMetafield, + UpdateOrderMetafieldInput +} from 'lib/shopify/types'; import { revalidateTag } from 'next/cache'; -export const activateWarranty = async (orderId: string, formData: FormData) => { +const getMetafieldValue = ( + key: keyof ShopifyOrderMetafield, + newValue: { value?: string | null; type: string; key: string }, + orderMetafields?: ShopifyOrderMetafield +): UpdateOrderMetafieldInput => { + return orderMetafields?.[key]?.id + ? { id: orderMetafields[key]?.id!, value: newValue.value, key: newValue.key } + : { ...newValue, namespace: 'custom' }; +}; + +export const activateWarranty = async (order: Order, formData: FormData) => { let odometerFileId = null; let installationFileId = null; const odometerFile = formData.get('warranty_activation_odometer'); @@ -21,24 +36,58 @@ export const activateWarranty = async (orderId: string, formData: FormData) => { installationFileId = await handleUploadFile({ file: installationFile as File }); } + // https://shopify.dev/docs/api/admin-graphql/2024-01/mutations/orderUpdate const rawFormData = [ - { key: 'warranty_activation_odometer', value: odometerFileId, type: 'file_reference' }, - { key: 'warranty_activation_installation', value: installationFileId, type: 'file_reference' }, - { - key: 'warranty_activation_mileage', - value: formData.get('warranty_activation_mileage') as string | null, - type: 'number_integer' - }, - { - key: 'warranty_activation_vin', - value: formData.get('warranty_activation_vin') as string | null, - type: 'single_line_text_field' - } + getMetafieldValue( + 'warrantyActivationOdometer', + { + key: 'warranty_activation_odometer', + value: odometerFileId, + type: 'file_reference' + }, + order + ), + getMetafieldValue( + 'warrantyActivationInstallation', + { + key: 'warranty_activation_installation', + value: installationFileId, + type: 'file_reference' + }, + order + ), + getMetafieldValue( + 'warrantyActivationSelfInstall', + { + key: 'warranty_activation_self_install', + value: formData.get('warranty_activation_self_install') === 'on' ? 'true' : 'false', + type: 'boolean' + }, + order + ), + getMetafieldValue( + 'warrantyActivationMileage', + { + key: 'warranty_activation_mileage', + value: formData.get('warranty_activation_mileage') as string | null, + type: 'number_integer' + }, + order + ), + getMetafieldValue( + 'warrantyActivationVIN', + { + key: 'warranty_activation_vin', + value: formData.get('warranty_activation_vin') as string | null, + type: 'single_line_text_field' + }, + order + ) ]; try { await updateOrderMetafields({ - orderId, + orderId: order.id, metafields: rawFormData }); @@ -91,7 +140,12 @@ export const confirmOrder = async ({ order, content, formData }: ConfirmOrderOpt const confirmationPDFId = await handleUploadFile({ file }); const rawFormData = [ - { key: 'customer_confirmation', value: confirmationPDFId, type: 'file_reference' } + { + key: 'customer_confirmation', + value: confirmationPDFId, + type: 'file_reference', + namespace: 'custom' + } ]; try { diff --git a/components/orders/activate-warranty-modal.tsx b/components/orders/activate-warranty-modal.tsx index a06264afe..39e74ab25 100644 --- a/components/orders/activate-warranty-modal.tsx +++ b/components/orders/activate-warranty-modal.tsx @@ -6,23 +6,17 @@ import CheckboxField from 'components/form/checkbox-field'; import FileInput from 'components/form/file-input'; import Input from 'components/form/input-field'; import LoadingDots from 'components/loading-dots'; -import { ShopifyOrderMetafield } from 'lib/shopify/types'; +import { Order } from 'lib/shopify/types'; import { FormEventHandler, useRef, useTransition } from 'react'; import { activateWarranty } from './actions'; type ActivateWarrantyModalProps = { isOpen: boolean; onClose: () => void; - orderId: string; - orderMetafields?: ShopifyOrderMetafield; + order: Order; }; -function ActivateWarrantyModal({ - onClose, - isOpen, - orderId, - orderMetafields -}: ActivateWarrantyModalProps) { +function ActivateWarrantyModal({ onClose, isOpen, order }: ActivateWarrantyModalProps) { const [pending, startTransition] = useTransition(); const formRef = useRef(null); @@ -33,7 +27,7 @@ function ActivateWarrantyModal({ const formData = new FormData(form); startTransition(async () => { - await activateWarranty(orderId, formData, orderMetafields); + await activateWarranty(order, formData); form.reset(); onClose(); }); @@ -59,28 +53,28 @@ function ActivateWarrantyModal({
diff --git a/components/orders/activate-warranty.tsx b/components/orders/activate-warranty.tsx index 35a7b9b8f..42afa2989 100644 --- a/components/orders/activate-warranty.tsx +++ b/components/orders/activate-warranty.tsx @@ -5,6 +5,7 @@ import { isBeforeToday } from 'lib/utils'; import { useState } from 'react'; import ActivateWarrantyModal from './activate-warranty-modal'; import WarrantyActivatedBadge from './warranty-activated-badge'; +import { Button } from 'components/ui'; type ActivateWarrantyModalProps = { order: Order; @@ -12,7 +13,7 @@ type ActivateWarrantyModalProps = { const ActivateWarranty = ({ order }: ActivateWarrantyModalProps) => { const [isOpen, setIsOpen] = useState(false); - const isWarrantyActivated = order?.warrantyStatus === WarrantyStatus.Activated; + const isWarrantyActivated = order?.warrantyStatus?.value === WarrantyStatus.Activated; const isPassDeadline = isBeforeToday(order?.warrantyActivationDeadline?.value); if (isWarrantyActivated) { @@ -25,13 +26,8 @@ const ActivateWarranty = ({ order }: ActivateWarrantyModalProps) => { return ( <> - - setIsOpen(false)} orderId={order.id} /> + + setIsOpen(false)} order={order} /> ); }; diff --git a/components/orders/mobile-order-actions.tsx b/components/orders/mobile-order-actions.tsx index 7ddfdb232..6df4f57c3 100644 --- a/components/orders/mobile-order-actions.tsx +++ b/components/orders/mobile-order-actions.tsx @@ -16,9 +16,9 @@ const MobileOrderActions = ({ order }: { order: Order }) => { const [isWarrantyOpen, setIsWarrantyOpen] = useState(false); const [isOrderConfirmaionOpen, setIsOrderConfirmationOpen] = useState(false); - const isWarrantyActivated = order?.warrantyStatus === WarrantyStatus.Activated; - const isPassDeadline = isBeforeToday(order?.warrantyActivationDeadline); - const isOrderConfirmed = order?.orderConfirmation; + const isWarrantyActivated = order?.warrantyStatus?.value === WarrantyStatus.Activated; + const isPassDeadline = isBeforeToday(order?.warrantyActivationDeadline?.value); + const isOrderConfirmed = order?.orderConfirmation?.value; return ( <> @@ -84,7 +84,7 @@ const MobileOrderActions = ({ order }: { order: Order }) => { setIsWarrantyOpen(false)} - orderId={order.id} + order={order} /> {!isOrderConfirmed && ( = (event) => { event.preventDefault(); + setLoading(true); const form = formRef.current; if (!form) return; const formData = new FormData(form); @@ -276,11 +271,15 @@ export default function OrderConfirmationModal({
-
- - +
diff --git a/components/ui/button.tsx b/components/ui/button.tsx index 4e6a646e7..29e55f81c 100644 --- a/components/ui/button.tsx +++ b/components/ui/button.tsx @@ -17,7 +17,6 @@ const buttonVariants = tv({ 'transition-all duration-100 ease-in-out', // disabled 'disabled:pointer-events-none disabled:shadow-none', - 'shadow-sm', focusInput ], loading: 'pointer-events-none flex shrink-0 items-center justify-center gap-1.5' @@ -40,12 +39,14 @@ const buttonVariants = tv({ }, variant: { solid: { - root: 'border border-transparent' + root: 'border border-transparent shadow-sm' }, outlined: { - root: 'border bg-white' + root: 'border bg-white shadow-sm' }, - text: {} + text: { + root: 'border border-transparent' + } } }, compoundVariants: [ @@ -118,6 +119,22 @@ const buttonVariants = tv({ 'disabled:border-content-muted disabled:text-content-muted' ] } + }, + { + color: 'content', + variant: 'text', + class: { + root: [ + // text color + 'text-content-emphasis', + // background color + 'bg-transparent', + // hover color + 'hover:bg-content/5', + // disabled + 'disabled:text-content-muted' + ] + } } ], defaultVariants: { diff --git a/components/ui/checkbox.tsx b/components/ui/checkbox.tsx index 197cb3b1d..48d1c8ed2 100644 --- a/components/ui/checkbox.tsx +++ b/components/ui/checkbox.tsx @@ -25,4 +25,4 @@ const Checkbox = forwardRef< Checkbox.displayName = CheckboxPrimitive.Root.displayName; -export { Checkbox }; +export default Checkbox; diff --git a/components/ui/index.ts b/components/ui/index.ts index 10f29b318..01c268397 100644 --- a/components/ui/index.ts +++ b/components/ui/index.ts @@ -5,7 +5,6 @@ export * from './button'; export { default as Card } from './card'; export * from './card'; export { default as Checkbox } from './checkbox'; -export * from './checkbox'; export { default as Heading } from './heading'; export { default as InputLabel } from './input-label'; export * from './input-label'; diff --git a/components/ui/input-label.tsx b/components/ui/input-label.tsx index 0b2defc50..669aaf2f0 100644 --- a/components/ui/input-label.tsx +++ b/components/ui/input-label.tsx @@ -3,7 +3,8 @@ import * as LabelPrimitives from '@radix-ui/react-label'; import { cx } from 'lib/utils'; -interface InputLabelProps extends React.ComponentPropsWithoutRef { +export interface InputLabelProps + extends React.ComponentPropsWithoutRef { disabled?: boolean; } @@ -29,4 +30,4 @@ const InputLabel = React.forwardRef, VariantProps { inputClassName?: string; @@ -67,4 +67,4 @@ const Input = React.forwardRef( Input.displayName = 'Input'; -export { Input, inputStyles, type InputProps }; +export default Input; diff --git a/lib/shopify/fragments/order-card.ts b/lib/shopify/fragments/order-card.ts new file mode 100644 index 000000000..e69de29bb diff --git a/lib/shopify/index.ts b/lib/shopify/index.ts index e1bbd7d60..daab9ebed 100644 --- a/lib/shopify/index.ts +++ b/lib/shopify/index.ts @@ -37,7 +37,7 @@ import { import { getCustomerQuery } from './queries/customer'; import { getMenuQuery } from './queries/menu'; import { getMetaobjectQuery, getMetaobjectsQuery } from './queries/metaobject'; -import { getImageQuery, getMetaobjectsByIdsQuery } from './queries/node'; +import { getFileQuery, getImageQuery, getMetaobjectsByIdsQuery } from './queries/node'; import { getCustomerOrdersQuery } from './queries/orders'; import { getPageQuery, getPagesQuery } from './queries/page'; import { @@ -52,6 +52,7 @@ import { Collection, Connection, Customer, + File, FileCreateInput, Filter, Fulfillment, @@ -313,7 +314,7 @@ export async function shopifyCustomerFetch({ } } -const removeEdgesAndNodes = (array: Connection) => { +const removeEdgesAndNodes = (array: Connection) => { return array.edges.map((edge) => edge?.node); }; @@ -439,7 +440,7 @@ const reshapeImages = (images: Connection, productTitle: string) => { const flattened = removeEdgesAndNodes(images); return flattened.map((image) => { - const filename = image.url.match(/.*\/(.*)\..*/)[1]; + const filename = (image.url.match(/.*\/(.*)\..*/) || [])[1]; return { ...image, altText: image.altText || `${productTitle} - ${filename}` @@ -621,25 +622,21 @@ function reshapeOrder(shopifyOrder: ShopifyOrder): Order { shippingMethod: { name: shopifyOrder.shippingLine?.title, price: reshapeMoney(shopifyOrder.shippingLine.originalPrice)! - } + }, + warrantyActivationDeadline: shopifyOrder.warrantyActivationDeadline, + warrantyStatus: shopifyOrder.warrantyStatus, + warrantyActivationInstallation: shopifyOrder.warrantyActivationInstallation, + warrantyActivationMileage: shopifyOrder.warrantyActivationMileage, + warrantyActivationOdometer: shopifyOrder.warrantyActivationOdometer, + warrantyActivationSelfInstall: shopifyOrder.warrantyActivationSelfInstall, + warrantyActivationVIN: shopifyOrder.warrantyActivationVIN, + orderConfirmation: shopifyOrder.orderConfirmation }; if (shopifyOrder.customer) { order.customer = reshapeCustomer(shopifyOrder.customer); } - if (shopifyOrder.warrantyStatus) { - order.warrantyStatus = shopifyOrder.warrantyStatus.value as WarrantyStatus; - } - - if (shopifyOrder.warrantyActivationDeadline) { - order.warrantyActivationDeadline = new Date(shopifyOrder.warrantyActivationDeadline.value); - } - - if (shopifyOrder.orderConfirmation) { - order.orderConfirmation = shopifyOrder.orderConfirmation.value; - } - return order; } @@ -891,6 +888,31 @@ export async function getMetaobjects(type: string) { return reshapeMetaobjects(removeEdgesAndNodes(res.body.data.metaobjects)); } +export async function getAllMetaobjects(type: string) { + const allMetaobjects: Metaobject[] = []; + let hasNextPage = true; + let after: string | undefined; + + while (hasNextPage) { + const res = await shopifyFetch({ + query: getMetaobjectsQuery, + tags: [TAGS.collections, TAGS.products], + variables: { type, after } + }); + + const metaobjects = reshapeMetaobjects(removeEdgesAndNodes(res.body.data.metaobjects)); + + for (const metaobject of metaobjects) { + allMetaobjects.push(metaobject); + } + + hasNextPage = res.body.data.metaobjects.pageInfo?.hasNextPage || false; + after = res.body.data.metaobjects.pageInfo?.endCursor; + } + + return allMetaobjects; +} + export async function getMetaobjectsByIds(ids: string[]) { if (!ids.length) return []; @@ -1147,3 +1169,19 @@ export const updateOrderMetafields = async ({ return response.body.data.orderUpdate.order.id; }; + +export const getFile = async (id: string) => { + const res = await shopifyFetch<{ + data: { + node: File; + }; + variables: { + id: string; + }; + }>({ + query: getFileQuery, + variables: { id } + }); + + return res.body.data.node; +}; diff --git a/lib/shopify/queries/metaobject.ts b/lib/shopify/queries/metaobject.ts index 5beca0fc8..2c63e3696 100644 --- a/lib/shopify/queries/metaobject.ts +++ b/lib/shopify/queries/metaobject.ts @@ -1,6 +1,6 @@ export const getMetaobjectsQuery = /* GraphQL */ ` - query getMetaobjects($type: String!) { - metaobjects(type: $type, first: 200) { + query getMetaobjects($type: String!, $after: String) { + metaobjects(type: $type, first: 200, after: $after) { edges { node { id @@ -16,6 +16,10 @@ export const getMetaobjectsQuery = /* GraphQL */ ` } } } + pageInfo { + hasNextPage + endCursor + } } } `; diff --git a/lib/shopify/types.ts b/lib/shopify/types.ts index c40765285..5c63c5669 100644 --- a/lib/shopify/types.ts +++ b/lib/shopify/types.ts @@ -3,6 +3,7 @@ export type Maybe = T | null; export type Connection = { edges: Array>; + pageInfo?: PageInfo; }; export type Edge = { @@ -152,10 +153,7 @@ export type Order = { name: string; price: Money; }; - warrantyStatus?: WarrantyStatus | null; - warrantyActivationDeadline?: Date | null; - orderConfirmation?: string | null; -}; +} & ShopifyOrderMetafield; export type ShopifyOrder = { id: string; @@ -184,10 +182,7 @@ export type ShopifyOrder = { requiresShipping: boolean; shippingLine: ShopifyShippingLine; note: string | null; - warrantyStatus?: ShopifyMetafield; - warrantyActivationDeadline?: ShopifyMetafield; - orderConfirmation?: ShopifyMetafield; -}; +} & ShopifyOrderMetafield; type ShopifyShippingLine = { title: string; @@ -685,7 +680,7 @@ export type ShopifyImageOperation = { export type ShopifyMetaobjectsOperation = { data: { metaobjects: Connection }; - variables: { type: string }; + variables: { type: string; after?: string }; }; export type ShopifyPagesOperation = { @@ -878,20 +873,15 @@ export enum WarrantyStatus { LimitedActivated = 'Limited Activation' } -export type OrderMetafieldValue = { - value: T; - id: string; - key: string; -}; - export type ShopifyOrderMetafield = { - warrantyStatus: OrderMetafieldValue | null; - warrantyActivationDeadline: OrderMetafieldValue | null; - warrantyActivationOdometer: OrderMetafieldValue | null; - warrantyActivationInstallation: OrderMetafieldValue | null; - warrantyActivationSelfInstall: OrderMetafieldValue | null; - warrantyActivationVIN: OrderMetafieldValue | null; - warrantyActivationMileage: OrderMetafieldValue | null; + orderConfirmation: ShopifyMetafield | null; + warrantyStatus: ShopifyMetafield | null; + warrantyActivationDeadline: ShopifyMetafield | null; + warrantyActivationOdometer: ShopifyMetafield | null; + warrantyActivationInstallation: ShopifyMetafield | null; + warrantyActivationSelfInstall: ShopifyMetafield | null; + warrantyActivationVIN: ShopifyMetafield | null; + warrantyActivationMileage: ShopifyMetafield | null; }; export type File = { diff --git a/lib/utils.ts b/lib/utils.ts index 72685f7f6..9d5aee336 100644 --- a/lib/utils.ts +++ b/lib/utils.ts @@ -124,12 +124,13 @@ export function toPrintDate(date: string) { }); } -export const isBeforeToday = (date?: Date | null) => { +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); - date.setHours(0, 0, 0, 0); + compareDate.setHours(0, 0, 0, 0); - return date <= today; + return compareDate <= today; };