From f4f6edcd9a50d9ecc9863b411b8e9a27cc01bd58 Mon Sep 17 00:00:00 2001 From: tedraykov Date: Fri, 28 Jun 2024 18:21:44 +0300 Subject: [PATCH 1/3] Order Confirmation --- app/account/(orderId)/orders/[id]/page.tsx | 43 +- app/account/(orders)/page.tsx | 29 +- app/api/orders/confirmation/route.ts | 7 + components/orders/actions.tsx | 107 ++ components/orders/activate-warranty.tsx | 16 +- components/orders/mobile-order-actions.tsx | 51 +- .../orders/order-confirmation-modal.tsx | 293 +++++ components/orders/order-confirmation-pdf.tsx | 298 +++++ components/orders/order-confirmation.tsx | 24 + components/orders/order-summary.tsx | 4 +- components/orders/payment-details.tsx | 36 + components/profile/popover.tsx | 2 +- components/spinner.tsx | 19 - components/ui/badge.tsx | 2 +- components/{ => ui}/button.tsx | 72 +- components/ui/card.tsx | 6 +- components/ui/checkbox.tsx | 28 + components/ui/heading.tsx | 27 +- components/ui/index.ts | 19 + components/ui/input-label.tsx | 32 + components/ui/input.tsx | 70 ++ components/ui/label.tsx | 2 +- components/ui/loading-dots.tsx | 15 + components/ui/text.tsx | 2 +- lib/shopify/auth.ts | 1 + lib/shopify/fragments/address.ts | 17 + lib/shopify/fragments/order-metafields.ts | 56 + lib/shopify/fragments/order-transaction.ts | 38 + lib/shopify/fragments/order.ts | 89 +- lib/shopify/fragments/price.ts | 8 + lib/shopify/index.ts | 138 +-- lib/shopify/queries/metaobject.ts | 8 + lib/shopify/queries/order.ts | 75 +- lib/shopify/queries/orders.ts | 14 - lib/shopify/types.ts | 38 +- lib/styles.ts | 2 +- lib/utils.ts | 35 +- package.json | 5 + pnpm-lock.yaml | 1021 +++++++++++++++++ 39 files changed, 2386 insertions(+), 363 deletions(-) create mode 100644 app/api/orders/confirmation/route.ts create mode 100644 components/orders/actions.tsx create mode 100644 components/orders/order-confirmation-modal.tsx create mode 100644 components/orders/order-confirmation-pdf.tsx create mode 100644 components/orders/order-confirmation.tsx create mode 100644 components/orders/payment-details.tsx delete mode 100644 components/spinner.tsx rename components/{ => ui}/button.tsx (63%) create mode 100644 components/ui/checkbox.tsx create mode 100644 components/ui/index.ts create mode 100644 components/ui/input-label.tsx create mode 100644 components/ui/input.tsx create mode 100644 components/ui/loading-dots.tsx create mode 100644 lib/shopify/fragments/address.ts create mode 100644 lib/shopify/fragments/order-metafields.ts create mode 100644 lib/shopify/fragments/order-transaction.ts create mode 100644 lib/shopify/fragments/price.ts diff --git a/app/account/(orderId)/orders/[id]/page.tsx b/app/account/(orderId)/orders/[id]/page.tsx index 8868b2f2a..9d889d95d 100644 --- a/app/account/(orderId)/orders/[id]/page.tsx +++ b/app/account/(orderId)/orders/[id]/page.tsx @@ -1,18 +1,19 @@ import { ArrowLeftIcon, CheckCircleIcon, TruckIcon } from '@heroicons/react/24/outline'; -import ActivateWarranty from 'components/orders/activate-warranty'; +import OrderConfirmation from 'components/orders/order-confirmation'; +import PaymentsDetails from 'components/orders/payment-details'; import OrderSummary from 'components/orders/order-summary'; import OrderSummaryMobile from 'components/orders/order-summary-mobile'; -import Price from 'components/price'; import Badge from 'components/ui/badge'; -import { Card } from 'components/ui/card'; +import { Card } from 'components/ui'; import Heading from 'components/ui/heading'; import Label from 'components/ui/label'; import Text from 'components/ui/text'; -import { getCustomerOrder, getOrderMetafields } from 'lib/shopify'; +import { getCustomerOrder } from 'lib/shopify'; import { Fulfillment, Order } from 'lib/shopify/types'; import { toPrintDate } from 'lib/utils'; import Image from 'next/image'; import Link from 'next/link'; +import ActivateWarranty from 'components/orders/activate-warranty'; function Unfulfilled({ order }: { order: Order }) { // Build a map of line item IDs to quantities fulfilled @@ -144,30 +145,6 @@ function Fulfillments({ order }: { order: Order }) { ); } -function PaymentsDetails({ order }: { order: Order }) { - return ( - <> - {order.transactions.map((transaction, index) => ( -
- {/* eslint-disable-next-line @next/next/no-img-element */} - {transaction.paymentIcon.altText} -
- - Ending with {transaction.paymentDetails.last4} - - - - -
-
- ))} - - ); -} - function OrderDetails({ order }: { order: Order }) { return ( @@ -228,10 +205,7 @@ function OrderDetails({ order }: { order: Order }) { } export default async function OrderPage({ params }: { params: { id: string } }) { - const [order, orderMetafields] = await Promise.all([ - getCustomerOrder(params.id), - getOrderMetafields(params.id) - ]); + const order = await getCustomerOrder(params.id); return ( <> @@ -247,7 +221,10 @@ export default async function OrderPage({ params }: { params: { id: string } }) - +
+ + +
diff --git a/app/account/(orders)/page.tsx b/app/account/(orders)/page.tsx index 03fa41fd7..c3286a12f 100644 --- a/app/account/(orders)/page.tsx +++ b/app/account/(orders)/page.tsx @@ -1,18 +1,17 @@ import { InformationCircleIcon } from '@heroicons/react/24/outline'; +import OrderConfirmation from 'components/orders/order-confirmation'; 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, getOrdersMetafields } from 'lib/shopify'; -import { toPrintDate } from 'lib/utils'; +import { getCustomerOrders } from 'lib/shopify'; +import { isBeforeToday, toPrintDate } from 'lib/utils'; import Image from 'next/image'; import Link from 'next/link'; +import { Button } from 'components/ui'; export default async function AccountPage() { - const [orders, ordersMetafields] = await Promise.all([ - getCustomerOrders(), - getOrdersMetafields() - ]); + const orders = await getCustomerOrders(); return (
@@ -54,17 +53,19 @@ export default async function AccountPage() { )} - +
- - View Order - {order.normalizedId} + + - + {!isBeforeToday(order?.warrantyActivationDeadline) && ( + + )} + {!order.orderConfirmation && }
diff --git a/app/api/orders/confirmation/route.ts b/app/api/orders/confirmation/route.ts new file mode 100644 index 000000000..7fe51f09e --- /dev/null +++ b/app/api/orders/confirmation/route.ts @@ -0,0 +1,7 @@ +import { getOrderConfirmationContent } from 'lib/shopify'; + +export async function GET() { + const data = await getOrderConfirmationContent(); + + return Response.json({ ...data }); +} diff --git a/components/orders/actions.tsx b/components/orders/actions.tsx new file mode 100644 index 000000000..343aeeb54 --- /dev/null +++ b/components/orders/actions.tsx @@ -0,0 +1,107 @@ +'use server'; + +import { renderToBuffer } from '@react-pdf/renderer'; +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 { revalidateTag } from 'next/cache'; + +export const activateWarranty = async (orderId: string, formData: FormData) => { + 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 }); + } + + 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' + } + ]; + + try { + await updateOrderMetafields({ + orderId, + metafields: rawFormData + }); + + revalidateTag(TAGS.orderMetafields); + } catch (error) { + console.log('activateWarranty action', error); + } +}; + +async function generateOrderConfirmationPDF( + order: Order, + content: OrderConfirmationContent, + signature1: string, + signature2: string, + signDate: string +) { + return renderToBuffer( + + ); +} + +type ConfirmOrderOptions = { + order: Order; + content: OrderConfirmationContent; + formData: FormData; +}; + +export const confirmOrder = async ({ order, content, formData }: ConfirmOrderOptions) => { + const signature1 = formData.get('signature1') as string; + const signature2 = formData.get('signature2') as string; + const signDate = formData.get('date') as string; + + const pdfBuffer = await generateOrderConfirmationPDF( + order, + content, + signature1, + signature2, + signDate + ); + + const fileName = `${new Date().getTime()}-${order.name}-signaturePdf.pdf`; + const file = new File([pdfBuffer], fileName, { type: 'application/pdf' }); + + const confirmationPDFId = await handleUploadFile({ file }); + + const rawFormData = [ + { key: 'customer_confirmation', value: confirmationPDFId, type: 'file_reference' } + ]; + + try { + await updateOrderMetafields({ + orderId: order.id, + metafields: rawFormData + }); + + revalidateTag(TAGS.orderMetafields); + } catch (error) { + console.log('activateWarranty action', error); + } +}; diff --git a/components/orders/activate-warranty.tsx b/components/orders/activate-warranty.tsx index b98c4cac2..35a7b9b8f 100644 --- a/components/orders/activate-warranty.tsx +++ b/components/orders/activate-warranty.tsx @@ -1,6 +1,6 @@ 'use client'; -import { Order, ShopifyOrderMetafield, WarrantyStatus } from 'lib/shopify/types'; +import { Order, WarrantyStatus } from 'lib/shopify/types'; import { isBeforeToday } from 'lib/utils'; import { useState } from 'react'; import ActivateWarrantyModal from './activate-warranty-modal'; @@ -8,13 +8,12 @@ import WarrantyActivatedBadge from './warranty-activated-badge'; type ActivateWarrantyModalProps = { order: Order; - orderMetafields?: ShopifyOrderMetafield; }; -const ActivateWarranty = ({ order, orderMetafields }: ActivateWarrantyModalProps) => { +const ActivateWarranty = ({ order }: ActivateWarrantyModalProps) => { const [isOpen, setIsOpen] = useState(false); - const isWarrantyActivated = orderMetafields?.warrantyStatus?.value === WarrantyStatus.Activated; - const isPassDeadline = isBeforeToday(orderMetafields?.warrantyActivationDeadline?.value); + const isWarrantyActivated = order?.warrantyStatus === WarrantyStatus.Activated; + const isPassDeadline = isBeforeToday(order?.warrantyActivationDeadline?.value); if (isWarrantyActivated) { return ; @@ -32,12 +31,7 @@ const ActivateWarranty = ({ order, orderMetafields }: ActivateWarrantyModalProps > Activate Warranty - setIsOpen(false)} - orderId={order.id} - orderMetafields={orderMetafields} - /> + setIsOpen(false)} orderId={order.id} /> ); }; diff --git a/components/orders/mobile-order-actions.tsx b/components/orders/mobile-order-actions.tsx index e49b01463..7ddfdb232 100644 --- a/components/orders/mobile-order-actions.tsx +++ b/components/orders/mobile-order-actions.tsx @@ -1,24 +1,24 @@ 'use client'; +import dynamic from 'next/dynamic'; import { Button, Menu, MenuButton, MenuItem, MenuItems } from '@headlessui/react'; import { EllipsisVerticalIcon } from '@heroicons/react/24/solid'; import clsx from 'clsx'; -import { Order, ShopifyOrderMetafield, WarrantyStatus } from 'lib/shopify/types'; +import { Order, WarrantyStatus } from 'lib/shopify/types'; import { isBeforeToday } from 'lib/utils'; import Link from 'next/link'; import { useState } from 'react'; import ActivateWarrantyModal from './activate-warranty-modal'; -const MobileOrderActions = ({ - order, - orderMetafields -}: { - order: Order; - orderMetafields?: ShopifyOrderMetafield; -}) => { - const [isOpen, setIsOpen] = useState(false); - const isWarrantyActivated = orderMetafields?.warrantyStatus?.value === WarrantyStatus.Activated; - const isPassDeadline = isBeforeToday(orderMetafields?.warrantyActivationDeadline?.value); +const OrderConfirmationModal = dynamic(() => import('./order-confirmation-modal')); + +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; return ( <> @@ -56,22 +56,43 @@ const MobileOrderActions = ({ focus ? 'bg-gray-100 text-gray-900' : 'text-gray-700', 'flex w-full px-4 py-2 text-sm' )} - onClick={() => setIsOpen(true)} + onClick={() => setIsWarrantyOpen(true)} > Activate Warranty )} )} + {!isOrderConfirmed && ( + + {({ focus }) => ( + + )} + + )}
setIsOpen(false)} + isOpen={isWarrantyOpen} + onClose={() => setIsWarrantyOpen(false)} orderId={order.id} - orderMetafields={orderMetafields} /> + {!isOrderConfirmed && ( + setIsOrderConfirmationOpen(false)} + order={order} + /> + )} ); }; diff --git a/components/orders/order-confirmation-modal.tsx b/components/orders/order-confirmation-modal.tsx new file mode 100644 index 000000000..af6099122 --- /dev/null +++ b/components/orders/order-confirmation-modal.tsx @@ -0,0 +1,293 @@ +import Image from 'next/image'; +import { Dialog, DialogBackdrop, DialogPanel } from '@headlessui/react'; +import Text from 'components/ui/text'; +import { Input } from 'components/ui/input'; +import { InputLabel } from 'components/ui/input-label'; +import Skeleton from 'components/ui/skeleton'; +import { toPrintDate } from 'lib/utils'; +import Label from 'components/ui/label'; +import Heading from 'components/ui/heading'; +import PaymentsDetails from './payment-details'; +import Price from 'components/price'; +import Divider from 'components/divider'; +import Markdown from 'markdown-to-jsx'; +import { Order, OrderConfirmationContent } from 'lib/shopify/types'; +import { FormEventHandler, useEffect, useRef, useState, useTransition } from 'react'; +import { confirmOrder } from 'components/orders/actions'; +import { Button } from 'components/ui'; + +function OrderConfirmationDetails({ + content, + order +}: { + content: OrderConfirmationContent; + order: Order; +}) { + return ( +
+
+ {content?.logo?.altText +
+ + ORDER INFORMATION: + +
+ Order number: {order.name} + Email: {order.customer?.emailAddress} + Date: {toPrintDate(order.processedAt)} +
+
+
+ +
+ + {order.shippingAddress!.firstName} {order.shippingAddress!.lastName} + + {order.shippingAddress!.address1} + {order.shippingAddress!.address2 && {order.shippingAddress!.address2}} + + {order.shippingAddress!.city} {order.shippingAddress!.provinceCode}{' '} + {order.shippingAddress!.zip} + + {order.shippingAddress!.country} +
+
+
+ +
+ + {order.billingAddress!.firstName} {order.billingAddress!.lastName} + + {order.billingAddress!.address1} + {order.billingAddress!.address2 && {order.billingAddress!.address2}} + + {order.billingAddress!.city} {order.billingAddress!.provinceCode}{' '} + {order.billingAddress!.zip} + + {order.billingAddress!.country} +
+
+
+
+ + +
+
+ Products + + + + + + + + + + + {order.lineItems.map((lineItem, index) => ( + + + + + + ))} + +
+ + + + + +
+ {lineItem.title} + + {lineItem.quantity} + + +
+ +
+
+ Subtotal + +
+
+ Shipping + {order.shippingMethod.price.amount !== '0.0' ? ( + + ) : ( + Free + )} +
+
+ + Total + + +
+
+
+ + {content?.body || ''} + +
+ ); +} +export default function OrderConfirmationModal({ + order, + isOpen, + onClose +}: { + order: Order; + isOpen: boolean; + onClose: () => void; +}) { + const [loading, setLoading] = useState(true); + const [orderConfirmationContent, setOrderConfirmationContent] = + useState(); + const [, startTransition] = useTransition(); + const formRef = useRef(null); + + useEffect(() => { + const fetchOrderConfirmationContent = async () => { + const res = await fetch('/api/orders/confirmation'); + const data = await res.json(); + + setOrderConfirmationContent(data); + + setLoading(false); + }; + + // If the order has already been confirmed, don't fetch the content + if (order.orderConfirmation) return; + + fetchOrderConfirmationContent(); + // eslint-disable-next-line react-hooks/exhaustive-deps + }, []); + + const handleSubmit: FormEventHandler = (event) => { + event.preventDefault(); + const form = formRef.current; + if (!form) return; + const formData = new FormData(form); + + startTransition(async () => { + await confirmOrder({ + order, + content: orderConfirmationContent!, + formData + }); + form.reset(); + }); + }; + + if (!loading && !orderConfirmationContent) return null; + + return ( + + +
+
+ + {loading ? ( + + ) : ( + + )} +
+
+ Today's date + +
+
+ Print your name to sign + +
+
+ + Credit card holder's electronic signature + + +
+
+ + +
+
+
+
+
+
+ ); +} diff --git a/components/orders/order-confirmation-pdf.tsx b/components/orders/order-confirmation-pdf.tsx new file mode 100644 index 000000000..88f53fd6e --- /dev/null +++ b/components/orders/order-confirmation-pdf.tsx @@ -0,0 +1,298 @@ +import Markdown from 'markdown-to-jsx'; +import { Document, Image, Page, Text, StyleSheet, View, Link } from '@react-pdf/renderer'; +import { Order, OrderConfirmationContent } from 'lib/shopify/types'; +import { toPrintDate } from 'lib/utils'; + +const PDFPrice = ({ + style, + amount, + currencyCode = 'USD' +}: { + style?: any; + amount: string; + currencyCode: string; +}) => { + const price = parseFloat(amount); + + // Return 'Included' if price is 0 + if (price === 0) { + return Free; + } + + return ( + + {new Intl.NumberFormat(undefined, { + style: 'currency', + currency: currencyCode, + currencyDisplay: 'narrowSymbol' + }).format(price)} + + ); +}; + +export default function OrderConfirmationPdf({ + content, + order, + signature1, + signature2, + date +}: { + content: OrderConfirmationContent; + order: Order; + signature1: string; + signature2: string; + date: string; +}) { + const styles = StyleSheet.create({ + logo: { + width: 300, + marginHorizontal: 'auto', + marginBottom: 24 + }, + page: { + padding: 48, + paddingVertical: 64 + }, + h1: { + fontSize: 18, + fontWeight: 700, + marginBottom: 12, + color: content.color + }, + h2: { + fontSize: 14, + fontWeight: 700, + marginBottom: 12, + color: content.color + }, + h3: { + fontSize: 12, + fontWeight: 700, + marginBottom: 12, + color: content.color + }, + p: { + fontSize: 10, + marginBottom: 12 + }, + span: { + fontSize: 10 + }, + strong: { + fontWeight: 700, + fontSize: 10 + }, + a: { + color: content.color, + fontSize: 10, + textDecoration: 'underline' + }, + label: { + fontSize: 10, + fontWeight: 'bold', + color: '#555' + }, + tableRow: { + display: 'flex', + flexDirection: 'row', + gap: 8 + }, + tableCell: { + textAlign: 'left', + fontSize: 10, + paddingVertical: 12 + } + }); + + return ( + + + + {/* eslint-disable-next-line jsx-a11y/alt-text */} + + ORDER INFORMATION: + + Order number: {order.name} + Email: {order.customer?.emailAddress} + Date: {toPrintDate(order.processedAt)} + + + + Shipping Address + + + {order.shippingAddress!.firstName} {order.shippingAddress!.lastName} + + {order.shippingAddress!.address1} + {order.shippingAddress!.address2 && ( + {order.shippingAddress!.address2} + )} + + {order.shippingAddress!.city} {order.shippingAddress!.provinceCode}{' '} + {order.shippingAddress!.zip} + + {order.shippingAddress!.country} + + + + Billing Address + + + {order.billingAddress!.firstName} {order.billingAddress!.lastName} + + {order.billingAddress!.address1} + {order.billingAddress!.address2 && ( + {order.billingAddress!.address2} + )} + + {order.billingAddress!.city} {order.billingAddress!.provinceCode}{' '} + {order.billingAddress!.zip} + + {order.billingAddress!.country} + + + + + Payment + + + Ending with {order.transactions[0]!.paymentDetails.last4} - + + + + + + + Products + Quantity + Price + + + {order.lineItems.map((lineItem, index) => ( + + {lineItem.title} + {lineItem.quantity} + + + ))} + + + + Subtotal + + + + Shipping + + + + Total + + + + + + + + + {content.body} + + + Date: + {toPrintDate(date)} + + + Print your name to sign: + {signature1} + + + + Credit card holder's electronic signature + + {signature2} + + + + ); +} diff --git a/components/orders/order-confirmation.tsx b/components/orders/order-confirmation.tsx new file mode 100644 index 000000000..3924b2455 --- /dev/null +++ b/components/orders/order-confirmation.tsx @@ -0,0 +1,24 @@ +'use client'; +import { Button } from 'components/ui'; +import { Order } from 'lib/shopify/types'; +import dynamic from 'next/dynamic'; +import { useState } from 'react'; + +const OrderConfirmationModal = dynamic(() => import('./order-confirmation-modal')); + +export default function OrderConfirmation({ order }: { order: Order }) { + const [isOpen, setIsOpen] = useState(false); + + if (order.orderConfirmation) return null; + return ( + <> + + + {isOpen && ( + setIsOpen(false)} order={order} /> + )} + + ); +} diff --git a/components/orders/order-summary.tsx b/components/orders/order-summary.tsx index 4510d0562..3a4f51481 100644 --- a/components/orders/order-summary.tsx +++ b/components/orders/order-summary.tsx @@ -54,8 +54,8 @@ export default function OrderSummary({ order }: { order: Order }) { Subtotal
diff --git a/components/orders/payment-details.tsx b/components/orders/payment-details.tsx new file mode 100644 index 000000000..0284757c5 --- /dev/null +++ b/components/orders/payment-details.tsx @@ -0,0 +1,36 @@ +import Text from 'components/ui/text'; +import Label from 'components/ui/label'; +import { Order } from 'lib/shopify/types'; +import Price from 'components/price'; +import { toPrintDate } from 'lib/utils'; + +export default function PaymentsDetails({ order, hideIcon }: { order: Order; hideIcon?: boolean }) { + return ( + <> + {order.transactions.map((transaction, index) => ( +
+ {!hideIcon && ( + // eslint-disable-next-line @next/next/no-img-element + {transaction.paymentIcon.altText} + )} + +
+ + Ending with {transaction.paymentDetails.last4} - + + + +
+
+ ))} + + ); +} diff --git a/components/profile/popover.tsx b/components/profile/popover.tsx index 85c4b4c19..c8b605e2b 100644 --- a/components/profile/popover.tsx +++ b/components/profile/popover.tsx @@ -1,7 +1,7 @@ 'use client'; import { CloseButton, Popover, PopoverButton, PopoverPanel, Transition } from '@headlessui/react'; import { ArrowRightIcon } from '@heroicons/react/16/solid'; -import { Button } from 'components/button'; +import { Button } from 'components/ui'; import useAuth from 'hooks/use-auth'; import { Menu } from 'lib/shopify/types'; import Link from 'next/link'; diff --git a/components/spinner.tsx b/components/spinner.tsx deleted file mode 100644 index a80fa722b..000000000 --- a/components/spinner.tsx +++ /dev/null @@ -1,19 +0,0 @@ -import clsx from 'clsx'; -import React from 'react'; - -export default function Spinner({ className }: { className?: string }) { - return ( - - - - - ); -} diff --git a/components/ui/badge.tsx b/components/ui/badge.tsx index a70e349f1..0780b53d9 100644 --- a/components/ui/badge.tsx +++ b/components/ui/badge.tsx @@ -19,7 +19,7 @@ const badgeStyles = tv({ } }); -interface BadgeProps extends VariantProps { +export interface BadgeProps extends VariantProps { content: string | number; className?: string; children: React.ReactNode; diff --git a/components/button.tsx b/components/ui/button.tsx similarity index 63% rename from components/button.tsx rename to components/ui/button.tsx index 0750b652f..4e6a646e7 100644 --- a/components/button.tsx +++ b/components/ui/button.tsx @@ -3,7 +3,8 @@ import React from 'react'; import { Button as ButtonBase, ButtonProps as ButtonBaseProps } from '@headlessui/react'; import { tv, type VariantProps } from 'tailwind-variants'; import clsx from 'clsx'; -import Spinner from './spinner'; +import LoadingDots from './loading-dots'; +import { focusInput } from 'lib/utils'; const buttonVariants = tv({ slots: { @@ -15,7 +16,9 @@ const buttonVariants = tv({ // transition 'transition-all duration-100 ease-in-out', // disabled - 'disabled:pointer-events-none disabled:shadow-none' + 'disabled:pointer-events-none disabled:shadow-none', + 'shadow-sm', + focusInput ], loading: 'pointer-events-none flex shrink-0 items-center justify-center gap-1.5' }, @@ -36,7 +39,9 @@ const buttonVariants = tv({ content: {} }, variant: { - solid: {}, + solid: { + root: 'border border-transparent' + }, outlined: { root: 'border bg-white' }, @@ -49,20 +54,35 @@ const buttonVariants = tv({ variant: 'solid', class: { root: [ - // border - 'border-transparent', // text color 'text-white', // background color 'bg-primary', // hover color - 'hover:bg-primary-empahsis', + 'hover:bg-primary-emphasis', // disabled 'disabled:bg-primary-muted', 'pressed:bg-primary-emphasis/80' ] } }, + { + color: 'content', + variant: 'solid', + class: { + root: [ + // text color + 'text-white', + // background color + 'bg-content', + // hover color + 'hover:bg-content-emphasis', + // disabled + 'disabled:bg-content-muted', + 'pressed:bg-content-emphasis/80' + ] + } + }, { color: 'primary', variant: 'outlined', @@ -75,25 +95,46 @@ const buttonVariants = tv({ // background color 'bg-white', // hover color - 'hover:bg-primary/10', + 'hover:bg-primary/5', // disabled 'disabled:border-primary-muted disabled:text-primary-muted' ] } + }, + { + color: 'content', + variant: 'outlined', + class: { + root: [ + // border + 'border-content-subtle', + // text color + 'text-content-emphasis', + // background color + 'bg-white', + // hover color + 'hover:bg-content/5', + // disabled + 'disabled:border-content-muted disabled:text-content-muted' + ] + } } ], defaultVariants: { - variant: 'solid', - color: 'primary', + variant: 'outlined', + color: 'content', size: 'md' } }); -interface ButtonProps extends Omit, VariantProps { +export interface ButtonProps + extends Omit, + VariantProps { isLoading?: boolean; loadingText?: string; className?: string; disabled?: boolean; + as?: React.ElementType; } const Button = React.forwardRef( @@ -105,14 +146,19 @@ const Button = React.forwardRef( isLoading, loadingText = 'Loading', size, + color, variant, + as, ...props }: ButtonProps, forwardedRef ) => { - const { loading, root } = buttonVariants({ variant, size }); + const { loading, root } = buttonVariants({ variant, size, color }); + + const Component = as || 'button'; return ( ( > {isLoading ? ( - + {loadingText} {loadingText} @@ -134,4 +180,4 @@ const Button = React.forwardRef( Button.displayName = 'Button'; -export { Button, buttonVariants, type ButtonProps }; +export default Button; diff --git a/components/ui/card.tsx b/components/ui/card.tsx index 31d263482..bb65e3345 100644 --- a/components/ui/card.tsx +++ b/components/ui/card.tsx @@ -21,7 +21,9 @@ const cardStyles = tv({ } }); -interface CardProps extends React.ComponentPropsWithoutRef<'div'>, VariantProps { +export interface CardProps + extends React.ComponentPropsWithoutRef<'div'>, + VariantProps { asChild?: boolean; } @@ -41,4 +43,4 @@ const Card = React.forwardRef( Card.displayName = 'Card'; -export { Card, type CardProps }; +export default Card; diff --git a/components/ui/checkbox.tsx b/components/ui/checkbox.tsx new file mode 100644 index 000000000..197cb3b1d --- /dev/null +++ b/components/ui/checkbox.tsx @@ -0,0 +1,28 @@ +'use client'; + +import { CheckIcon } from '@heroicons/react/24/outline'; +import * as CheckboxPrimitive from '@radix-ui/react-checkbox'; +import { cn } from 'lib/utils'; +import { forwardRef } from 'react'; + +const Checkbox = forwardRef< + React.ElementRef, + React.ComponentPropsWithoutRef +>(({ className, ...props }, ref) => ( + + + + + +)); + +Checkbox.displayName = CheckboxPrimitive.Root.displayName; + +export { Checkbox }; diff --git a/components/ui/heading.tsx b/components/ui/heading.tsx index a73dee028..79ca13f5d 100644 --- a/components/ui/heading.tsx +++ b/components/ui/heading.tsx @@ -1,20 +1,25 @@ import { VariantProps, tv } from 'tailwind-variants'; -const heading = tv({ - base: [''], - variants: { - size: { - sm: 'text-heading-sm', - md: 'text-heading-md', - lg: 'text-heading-lg' +const heading = tv( + { + base: [''], + variants: { + size: { + sm: 'text-heading-sm', + md: 'text-heading-md', + lg: 'text-heading-lg' + } + }, + defaultVariants: { + size: 'md' } }, - defaultVariants: { - size: 'md' + { + twMerge: false } -}); +); -interface HeadingProps extends VariantProps { +export interface HeadingProps extends VariantProps { className?: string; children: React.ReactNode; as?: 'h1' | 'h2' | 'h3' | 'h4' | 'h5' | 'h6' | 'span' | 'p'; diff --git a/components/ui/index.ts b/components/ui/index.ts new file mode 100644 index 000000000..10f29b318 --- /dev/null +++ b/components/ui/index.ts @@ -0,0 +1,19 @@ +export { default as Badge } from './badge'; +export * from './badge'; +export { default as Button } from './button'; +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'; +export { default as Input } from './input'; +export * from './input'; +export { default as Label } from './label'; +export * from './label'; +export { default as Skeleton } from './skeleton'; +export * from './skeleton'; +export { default as Text } from './text'; +export * from './text'; diff --git a/components/ui/input-label.tsx b/components/ui/input-label.tsx new file mode 100644 index 000000000..0b2defc50 --- /dev/null +++ b/components/ui/input-label.tsx @@ -0,0 +1,32 @@ +import React from 'react'; +import * as LabelPrimitives from '@radix-ui/react-label'; + +import { cx } from 'lib/utils'; + +interface InputLabelProps extends React.ComponentPropsWithoutRef { + disabled?: boolean; +} + +const InputLabel = React.forwardRef, InputLabelProps>( + ({ className, disabled, ...props }, forwardedRef) => ( + + ) +); +InputLabel.displayName = 'InputLabel'; + +export { InputLabel }; diff --git a/components/ui/input.tsx b/components/ui/input.tsx new file mode 100644 index 000000000..bb774baa1 --- /dev/null +++ b/components/ui/input.tsx @@ -0,0 +1,70 @@ +import React from 'react'; +import { tv, type VariantProps } from 'tailwind-variants'; + +import { cx, focusInput, hasErrorInput } from 'lib/utils'; + +const inputStyles = tv({ + base: [ + // base + 'relative block w-full appearance-none rounded-md border px-2.5 py-1.5 shadow-sm outline-none transition sm:text-sm', + // border color + 'border-gray-300 dark:border-gray-800', + // text color + 'text-gray-900 dark:text-gray-50', + // placeholder color + 'placeholder-gray-400 dark:placeholder-gray-500', + // background color + 'bg-white dark:bg-gray-950', + // disabled + 'disabled:border-gray-300 disabled:bg-gray-100 disabled:text-gray-400', + 'disabled:dark:border-gray-700 disabled:dark:bg-gray-800 disabled:dark:text-gray-500', + // file + [ + 'file:-my-1.5 file:-ml-2.5 file:h-[36px] file:cursor-pointer file:rounded-l-md file:rounded-r-none file:border-0 file:px-3 file:py-1.5 file:outline-none focus:outline-none disabled:pointer-events-none file:disabled:pointer-events-none', + 'file:border-solid file:border-gray-300 file:bg-gray-50 file:text-gray-500 file:hover:bg-gray-100 file:dark:border-gray-800 file:dark:bg-gray-950 file:hover:dark:bg-gray-900/20 file:disabled:dark:border-gray-700', + 'file:[border-inline-end-width:1px] file:[margin-inline-end:0.75rem]', + 'file:disabled:bg-gray-100 file:disabled:text-gray-500 file:disabled:dark:bg-gray-800' + ], + // focus + focusInput, + // invalid + 'aria-[invalid=true]:dark:ring-red-400/20 aria-[invalid=true]:ring-2 aria-[invalid=true]:ring-red-200 aria-[invalid=true]:border-red-500 invalid:ring-2 invalid:ring-red-200 invalid:border-red-500' + ], + variants: { + hasError: { + true: hasErrorInput + }, + // number input + enableStepper: { + true: '[appearance:textfield] [&::-webkit-inner-spin-button]:appearance-none [&::-webkit-outer-spin-button]:appearance-none' + } + } +}); + +interface InputProps + extends React.InputHTMLAttributes, + VariantProps { + inputClassName?: string; +} + +const Input = React.forwardRef( + ( + { className, inputClassName, hasError, enableStepper, type, ...props }: InputProps, + forwardedRef + ) => { + return ( +
+ +
+ ); + } +); + +Input.displayName = 'Input'; + +export { Input, inputStyles, type InputProps }; diff --git a/components/ui/label.tsx b/components/ui/label.tsx index 1dec14f18..2178321ec 100644 --- a/components/ui/label.tsx +++ b/components/ui/label.tsx @@ -19,7 +19,7 @@ const label = tv( } ); -interface LabelProps extends VariantProps { +export interface LabelProps extends VariantProps { className?: string; children: React.ReactNode; as?: 'h2' | 'h3' | 'h4' | 'h5' | 'h6' | 'span' | 'p'; diff --git a/components/ui/loading-dots.tsx b/components/ui/loading-dots.tsx new file mode 100644 index 000000000..0a3fb676e --- /dev/null +++ b/components/ui/loading-dots.tsx @@ -0,0 +1,15 @@ +import clsx from 'clsx'; + +const dots = 'mx-[1px] inline-block h-1 w-1 animate-blink rounded-md'; + +const LoadingDots = ({ className }: { className?: string }) => { + return ( + + + + + + ); +}; + +export default LoadingDots; diff --git a/components/ui/text.tsx b/components/ui/text.tsx index 7ce0a09b7..ac30a0c8e 100644 --- a/components/ui/text.tsx +++ b/components/ui/text.tsx @@ -19,7 +19,7 @@ const text = tv( } ); -interface TextProps extends VariantProps { +export interface TextProps extends VariantProps { className?: string; children: React.ReactNode; as?: 'h2' | 'h3' | 'h4' | 'h5' | 'h6' | 'span' | 'p'; diff --git a/lib/shopify/auth.ts b/lib/shopify/auth.ts index f0a82a52e..4070d6fba 100644 --- a/lib/shopify/auth.ts +++ b/lib/shopify/auth.ts @@ -390,6 +390,7 @@ export async function isLoggedIn(request: NextRequest, origin: string) { } newHeaders.set('x-shop-customer-token', `${customerTokenValue}`); + console.log('Customer Token', customerTokenValue); return NextResponse.next({ request: { // New request headers diff --git a/lib/shopify/fragments/address.ts b/lib/shopify/fragments/address.ts new file mode 100644 index 000000000..cf9af22f0 --- /dev/null +++ b/lib/shopify/fragments/address.ts @@ -0,0 +1,17 @@ +const addressFragment = /* GraphQL */ ` + fragment Address on CustomerAddress { + id + address1 + address2 + firstName + lastName + provinceCode: zoneCode + city + zip + countryCodeV2: territoryCode + company + phone: phoneNumber + } +`; + +export default addressFragment; diff --git a/lib/shopify/fragments/order-metafields.ts b/lib/shopify/fragments/order-metafields.ts new file mode 100644 index 000000000..2488b66d7 --- /dev/null +++ b/lib/shopify/fragments/order-metafields.ts @@ -0,0 +1,56 @@ +const orderMetafieldsFragment = /* GraphQL */ ` + fragment OrderMetafields on Order { + warrantyStatus: metafield(namespace: "custom", key: "warranty_status") { + value + id + key + } + warrantyActivationDeadline: metafield( + namespace: "custom" + key: "warranty_activation_deadline" + ) { + value + id + key + } + warrantyActivationOdometer: metafield( + namespace: "custom" + key: "warranty_activation_odometer" + ) { + value + id + key + } + warrantyActivationInstallation: metafield( + namespace: "custom" + key: "warranty_activation_installation" + ) { + value + id + key + } + warrantyActivationSelfInstall: metafield( + namespace: "custom" + key: "warranty_activation_self_install" + ) { + value + id + key + } + warrantyActivationVIN: metafield(namespace: "custom", key: "warranty_activation_vin") { + value + id + key + } + warrantyActivationMileage: metafield(namespace: "custom", key: "warranty_activation_mileage") { + value + id + key + } + orderConfirmation: metafield(namespace: "custom", key: "customer_confirmation") { + value + } + } +`; + +export default orderMetafieldsFragment; diff --git a/lib/shopify/fragments/order-transaction.ts b/lib/shopify/fragments/order-transaction.ts new file mode 100644 index 000000000..4047a7af4 --- /dev/null +++ b/lib/shopify/fragments/order-transaction.ts @@ -0,0 +1,38 @@ +const orderTransactionFragment = /* GraphQL */ ` + fragment OrderTransaction on OrderTransaction { + id + processedAt + paymentIcon { + id + url + altText + } + paymentDetails { + ... on CardPaymentDetails { + last4 + cardBrand + } + } + transactionAmount { + presentmentMoney { + ...Price + } + } + giftCardDetails { + last4 + balance { + ...Price + } + } + status + kind + transactionParentId + type + typeDetails { + name + message + } + } +`; + +export default orderTransactionFragment; diff --git a/lib/shopify/fragments/order.ts b/lib/shopify/fragments/order.ts index d15409862..f5fcf13e4 100644 --- a/lib/shopify/fragments/order.ts +++ b/lib/shopify/fragments/order.ts @@ -1,4 +1,8 @@ +import addressFragment from './address'; import lineItemFragment from './line-item'; +import orderMetafieldsFragment from './order-metafields'; +import orderTrasactionFragment from './order-transaction'; +import priceFragment from './price'; const orderCard = /* GraphQL */ ` fragment OrderCard on Order { @@ -16,69 +20,44 @@ const orderCard = /* GraphQL */ ` } } totalPrice { - amount - currencyCode + ...Price + } + subtotal { + ...Price + } + totalShipping { + ...Price + } + totalTax { + ...Price + } + shippingLine { + title + originalPrice { + ...Price + } } lineItems(first: 20) { nodes { ...LineItem } } + shippingAddress { + ...Address + } + billingAddress { + ...Address + } + transactions { + ...OrderTransaction + } + ...OrderMetafields } ${lineItemFragment} -`; - -export const orderMetafields = /* GraphQL */ ` - fragment OrderMetafield on Order { - id - warrantyStatus: metafield(namespace: "custom", key: "warranty_status") { - value - id - key - } - warrantyActivationDeadline: metafield( - namespace: "custom" - key: "warranty_activation_deadline" - ) { - value - id - key - } - warrantyActivationOdometer: metafield( - namespace: "custom" - key: "warranty_activation_odometer" - ) { - value - id - key - } - warrantyActivationInstallation: metafield( - namespace: "custom" - key: "warranty_activation_installation" - ) { - value - id - key - } - warrantyActivationSelfInstall: metafield( - namespace: "custom" - key: "warranty_activation_self_install" - ) { - value - id - key - } - warrantyActivationVIN: metafield(namespace: "custom", key: "warranty_activation_vin") { - value - id - key - } - warrantyActivationMileage: metafield(namespace: "custom", key: "warranty_activation_mileage") { - value - id - key - } - } + ${addressFragment} + ${priceFragment} + ${orderTrasactionFragment} + ${orderMetafieldsFragment} `; export default orderCard; diff --git a/lib/shopify/fragments/price.ts b/lib/shopify/fragments/price.ts new file mode 100644 index 000000000..6b003a6dd --- /dev/null +++ b/lib/shopify/fragments/price.ts @@ -0,0 +1,8 @@ +const priceFragment = /* GraphQL */ ` + fragment Price on MoneyV2 { + amount + currencyCode + } +`; + +export default priceFragment; diff --git a/lib/shopify/index.ts b/lib/shopify/index.ts index 005d70a76..e1bbd7d60 100644 --- a/lib/shopify/index.ts +++ b/lib/shopify/index.ts @@ -37,9 +37,8 @@ import { import { getCustomerQuery } from './queries/customer'; import { getMenuQuery } from './queries/menu'; import { getMetaobjectQuery, getMetaobjectsQuery } from './queries/metaobject'; -import { getFileQuery, getImageQuery, getMetaobjectsByIdsQuery } from './queries/node'; -import { getCustomerOrderQuery, getOrderMetafieldsQuery } from './queries/order'; -import { getCustomerOrderMetafieldsQuery, getCustomerOrdersQuery } from './queries/orders'; +import { getImageQuery, getMetaobjectsByIdsQuery } from './queries/node'; +import { getCustomerOrdersQuery } from './queries/orders'; import { getPageQuery, getPagesQuery } from './queries/page'; import { getProductQuery, @@ -53,7 +52,6 @@ import { Collection, Connection, Customer, - File, FileCreateInput, Filter, Fulfillment, @@ -64,6 +62,7 @@ import { Metaobject, Money, Order, + OrderConfirmationContent, Page, PageInfo, Product, @@ -86,10 +85,10 @@ import { ShopifyImageOperation, ShopifyMenuOperation, ShopifyMetaobject, + ShopifyMetaobjectOperation, ShopifyMetaobjectsOperation, ShopifyMoneyV2, ShopifyOrder, - ShopifyOrderMetafield, ShopifyPage, ShopifyPageOperation, ShopifyPagesOperation, @@ -109,6 +108,7 @@ import { UploadInput, WarrantyStatus } from './types'; +import getCustomerOrderQuery from './queries/order'; const domain = process.env.SHOPIFY_STORE_DOMAIN ? ensureStartsWith(process.env.SHOPIFY_STORE_DOMAIN, 'https://') @@ -185,7 +185,7 @@ export async function shopifyFetch({ } } -async function adminFetch({ +async function shopifyAdminFetch({ headers, query, variables, @@ -531,8 +531,7 @@ function reshapeOrders(orders: ShopifyOrder[]): any[] | Promise { } function reshapeOrder(shopifyOrder: ShopifyOrder): Order { - const reshapeAddress = (address?: ShopifyAddress): Address | undefined => { - if (!address) return undefined; + const reshapeAddress = (address: ShopifyAddress): Address => { return { address1: address.address1, address2: address.address2, @@ -547,8 +546,7 @@ function reshapeOrder(shopifyOrder: ShopifyOrder): Order { }; }; - const reshapeMoney = (money?: ShopifyMoneyV2): Money | undefined => { - if (!money) return undefined; + const reshapeMoney = (money: ShopifyMoneyV2): Money => { return { amount: money.amount || '0.00', currencyCode: money.currencyCode || 'USD' @@ -619,23 +617,42 @@ function reshapeOrder(shopifyOrder: ShopifyOrder): Order { totalShipping: reshapeMoney(shopifyOrder.totalShipping), totalTax: reshapeMoney(shopifyOrder.totalTax), totalPrice: reshapeMoney(shopifyOrder.totalPrice), - createdAt: shopifyOrder.createdAt + createdAt: shopifyOrder.createdAt, + shippingMethod: { + name: shopifyOrder.shippingLine?.title, + price: reshapeMoney(shopifyOrder.shippingLine.originalPrice)! + } }; if (shopifyOrder.customer) { order.customer = reshapeCustomer(shopifyOrder.customer); } - if (shopifyOrder.shippingLine) { - order.shippingMethod = { - name: shopifyOrder.shippingLine?.title, - price: reshapeMoney(shopifyOrder.shippingLine.originalPrice)! - }; + 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; } +export function reshapeOrderConfirmationPdf( + metaobject: ShopifyMetaobject +): OrderConfirmationContent { + return { + body: metaobject.fields.find((field) => field.key === 'body')?.value || '', + logo: metaobject.fields.find((field) => field.key === 'logo')?.reference.image!, + color: metaobject.fields.find((field) => field.key === 'color')?.value || '#000000' + }; +} + export async function createCart(): Promise { const res = await shopifyFetch({ query: createCartMutation, @@ -895,10 +912,7 @@ export async function getMetaobject({ id?: string; handle?: { handle: string; type: string }; }) { - const res = await shopifyFetch<{ - data: { metaobject: ShopifyMetaobject }; - variables: { id?: string; handle?: { handle: string; type: string } }; - }>({ + const res = await shopifyFetch({ query: getMetaobjectQuery, variables: { id, handle } }); @@ -906,6 +920,15 @@ export async function getMetaobject({ return res.body.data.metaobject ? reshapeMetaobjects([res.body.data.metaobject])[0] : null; } +export async function getOrderConfirmationContent(): Promise { + const res = await shopifyFetch({ + query: getMetaobjectQuery, + variables: { handle: { handle: 'order-confirmation-pdf', type: 'order_confirmation_pdf' } } + }); + + return reshapeOrderConfirmationPdf(res.body.data.metaobject); +} + export async function getPage(handle: string): Promise { const res = await shopifyFetch({ query: getPageQuery, @@ -1064,7 +1087,7 @@ export const getImage = async (id: string): Promise => { }; export const stageUploadFile = async (params: UploadInput) => { - const res = await adminFetch({ + const res = await shopifyAdminFetch({ query: createStageUploads, variables: { input: [params] } }); @@ -1080,7 +1103,7 @@ export const uploadFile = async ({ url, formData }: { url: string; formData: For }; export const createFile = async (params: FileCreateInput) => { - const res = await adminFetch({ + const res = await shopifyAdminFetch({ query: createFileMutation, variables: { files: [params] } }); @@ -1103,7 +1126,7 @@ export const updateOrderMetafields = async ({ validMetafields.find(({ key }) => (Array.isArray(field) ? field.includes(key) : key === field)) ); - const response = await adminFetch({ + const response = await shopifyAdminFetch({ query: updateOrderMetafieldsMutation, variables: { input: { @@ -1124,72 +1147,3 @@ export const updateOrderMetafields = async ({ return response.body.data.orderUpdate.order.id; }; - -export const getOrdersMetafields = async (): Promise<{ [key: string]: ShopifyOrderMetafield }> => { - 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]: order - }), - {} as { [key: string]: ShopifyOrderMetafield } - ); -}; - -export const getOrderMetafields = async (orderId: string): Promise => { - const res = await adminFetch<{ - data: { - order: { - id: string; - } & ShopifyOrderMetafield; - }; - variables: { - id: string; - }; - }>({ - query: getOrderMetafieldsQuery, - variables: { id: `gid://shopify/Order/${orderId}` }, - tags: [TAGS.orderMetafields] - }); - - const order = res.body.data.order; - - return order; -}; - -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 70270821f..5beca0fc8 100644 --- a/lib/shopify/queries/metaobject.ts +++ b/lib/shopify/queries/metaobject.ts @@ -30,6 +30,14 @@ export const getMetaobjectQuery = /* GraphQL */ ` ... on Metaobject { id } + ... on MediaImage { + image { + url + altText + height + width + } + } } key value diff --git a/lib/shopify/queries/order.ts b/lib/shopify/queries/order.ts index 58b47a282..3680aaa23 100644 --- a/lib/shopify/queries/order.ts +++ b/lib/shopify/queries/order.ts @@ -1,8 +1,11 @@ +import addressFragment from '../fragments/address'; import lineItemFragment from '../fragments/line-item'; -import { orderMetafields } from '../fragments/order'; +import orderMetafieldsFragment from '../fragments/order-metafields'; +import orderTrasactionFragment from '../fragments/order-transaction'; +import priceFragment from '../fragments/price'; // NOTE: https://shopify.dev/docs/api/customer/latest/queries/customer -export const getCustomerOrderQuery = /* GraphQL */ ` +const getCustomerOrderQuery = /* GraphQL */ ` query getCustomerOrderQuery($orderId: ID!) { customer { emailAddress { @@ -95,60 +98,7 @@ export const getCustomerOrderQuery = /* GraphQL */ ` ...Price } } - } - - fragment OrderTransaction on OrderTransaction { - id - processedAt - paymentIcon { - id - url - altText - } - paymentDetails { - ... on CardPaymentDetails { - last4 - cardBrand - } - } - transactionAmount { - presentmentMoney { - ...Price - } - } - giftCardDetails { - last4 - balance { - ...Price - } - } - status - kind - transactionParentId - type - typeDetails { - name - message - } - } - - fragment Price on MoneyV2 { - amount - currencyCode - } - - fragment Address on CustomerAddress { - id - address1 - address2 - firstName - lastName - provinceCode: zoneCode - city - zip - countryCodeV2: territoryCode - company - phone: phoneNumber + ...OrderMetafields } fragment Fulfillment on Fulfillment { @@ -220,13 +170,10 @@ export const getCustomerOrderQuery = /* GraphQL */ ` } } ${lineItemFragment} + ${addressFragment} + ${priceFragment} + ${orderTrasactionFragment} + ${orderMetafieldsFragment} `; -export const getOrderMetafieldsQuery = /* GraphQL */ ` - query getOrderMetafields($id: ID!) { - order(id: $id) { - ...OrderMetafield - } - } - ${orderMetafields} -`; +export default getCustomerOrderQuery; diff --git a/lib/shopify/queries/orders.ts b/lib/shopify/queries/orders.ts index 27a840428..1c55a2b9a 100644 --- a/lib/shopify/queries/orders.ts +++ b/lib/shopify/queries/orders.ts @@ -1,5 +1,4 @@ import customerDetailsFragment from '../fragments/customer-details'; -import { orderMetafields } from '../fragments/order'; const customerFragment = `#graphql `; @@ -14,16 +13,3 @@ 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 { - ...OrderMetafield - } - } - } - } - ${orderMetafields} -`; diff --git a/lib/shopify/types.ts b/lib/shopify/types.ts index 049c7b715..c40765285 100644 --- a/lib/shopify/types.ts +++ b/lib/shopify/types.ts @@ -141,17 +141,20 @@ export type Order = { fulfillments: Fulfillment[]; transactions: Transaction[]; lineItems: LineItem[]; - shippingAddress?: Address; - billingAddress?: Address; + shippingAddress: Address; + billingAddress: Address; /** the price of all line items, excluding taxes and surcharges */ - subtotal?: Money; - totalShipping?: Money; - totalTax?: Money; - totalPrice?: Money; - shippingMethod?: { + subtotal: Money; + totalShipping: Money; + totalTax: Money; + totalPrice: Money; + shippingMethod: { name: string; price: Money; }; + warrantyStatus?: WarrantyStatus | null; + warrantyActivationDeadline?: Date | null; + orderConfirmation?: string | null; }; export type ShopifyOrder = { @@ -181,6 +184,9 @@ export type ShopifyOrder = { requiresShipping: boolean; shippingLine: ShopifyShippingLine; note: string | null; + warrantyStatus?: ShopifyMetafield; + warrantyActivationDeadline?: ShopifyMetafield; + orderConfirmation?: ShopifyMetafield; }; type ShopifyShippingLine = { @@ -372,16 +378,30 @@ export type ShopifyMetaobject = { value: string; reference: { id: string; + image?: Image; }; }>; }; +export type ShopifyMetafield = { + id: string; + namespace: string; + key: string; + value: string; +}; + export type Metaobject = { id: string; type: string; [key: string]: string; }; +export type OrderConfirmationContent = { + logo: Image; + body: string; + color: string; +}; + export type TransmissionType = 'Automatic' | 'Manual'; export type Product = Omit< @@ -675,8 +695,8 @@ export type ShopifyPagesOperation = { }; export type ShopifyMetaobjectOperation = { - data: { nodes: ShopifyMetaobject[] }; - variables: { ids: string[] }; + data: { metaobject: ShopifyMetaobject }; + variables: { id?: string; handle?: { handle: string; type: string } }; }; export type ShopifyProductOperation = { diff --git a/lib/styles.ts b/lib/styles.ts index 60c08db14..7dd7612e5 100644 --- a/lib/styles.ts +++ b/lib/styles.ts @@ -5,7 +5,7 @@ export const colors = { muted: '#E6CCB7' }, content: { - subtle: '#9ca3af', // gray-400 + subtle: '#d1d5db', // gray-300 DEFAULT: '#6b7280', // gray-500 emphasis: '#374151', // gray-700 strong: '#111827', // gray-900 diff --git a/lib/utils.ts b/lib/utils.ts index 850264821..72685f7f6 100644 --- a/lib/utils.ts +++ b/lib/utils.ts @@ -3,6 +3,34 @@ import { ReadonlyURLSearchParams } from 'next/navigation'; import { twMerge } from 'tailwind-merge'; import { Menu } from './shopify/types'; +export function cx(...args: ClassValue[]) { + return twMerge(clsx(...args)); +} +export const focusInput = [ + // base + 'focus:ring-2', + // ring color + 'focus:ring-blue-200 focus:dark:ring-blue-700/30', + // border color + 'focus:border-blue-500 focus:dark:border-blue-700' +]; + +export const hasErrorInput = [ + // base + 'ring-2', + // border color + 'border-red-500 dark:border-red-700', + // ring color + 'ring-red-200 dark:ring-red-700/30' +]; + +export const focusRing = [ + // base + 'outline outline-offset-2 outline-0 focus-visible:outline-2', + // outline color + 'outline-blue-500 dark:outline-blue-500' +]; + export const createUrl = (pathname: string, params: URLSearchParams | ReadonlyURLSearchParams) => { const paramsString = params.toString(); const queryString = `${paramsString.length ? '?' : ''}${paramsString}`; @@ -96,13 +124,12 @@ export function toPrintDate(date: string) { }); } -export const isBeforeToday = (date?: string | null) => { +export const isBeforeToday = (date?: Date | 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); + date.setHours(0, 0, 0, 0); - return compareDate <= today; + return date <= today; }; diff --git a/package.json b/package.json index 5251cbf2b..f9ef027d5 100644 --- a/package.json +++ b/package.json @@ -25,12 +25,17 @@ "@headlessui/react": "^2.1.0", "@heroicons/react": "^2.1.3", "@hookform/resolvers": "^3.6.0", + "@radix-ui/react-checkbox": "^1.0.4", + "@radix-ui/react-label": "^2.1.0", + "@radix-ui/react-popover": "^1.1.1", "@radix-ui/react-slot": "^1.0.2", + "@react-pdf/renderer": "^3.4.4", "clsx": "^2.1.0", "geist": "^1.3.0", "lodash.get": "^4.4.2", "lodash.kebabcase": "^4.1.1", "lodash.startcase": "^4.4.0", + "markdown-to-jsx": "^7.4.7", "next": "14.2.4", "react": "18.2.0", "react-dom": "18.2.0", diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index eedcb4fc1..601c223d1 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -17,9 +17,21 @@ importers: '@hookform/resolvers': specifier: ^3.6.0 version: 3.6.0(react-hook-form@7.51.5(react@18.2.0)) + '@radix-ui/react-checkbox': + specifier: ^1.0.4 + version: 1.1.1(@types/react-dom@18.2.22)(@types/react@18.2.72)(react-dom@18.2.0(react@18.2.0))(react@18.2.0) + '@radix-ui/react-label': + specifier: ^2.1.0 + version: 2.1.0(@types/react-dom@18.2.22)(@types/react@18.2.72)(react-dom@18.2.0(react@18.2.0))(react@18.2.0) + '@radix-ui/react-popover': + specifier: ^1.1.1 + version: 1.1.1(@types/react-dom@18.2.22)(@types/react@18.2.72)(react-dom@18.2.0(react@18.2.0))(react@18.2.0) '@radix-ui/react-slot': specifier: ^1.0.2 version: 1.0.2(@types/react@18.2.72)(react@18.2.0) + '@react-pdf/renderer': + specifier: ^3.4.4 + version: 3.4.4(react@18.2.0) clsx: specifier: ^2.1.0 version: 2.1.0 @@ -35,6 +47,9 @@ importers: lodash.startcase: specifier: ^4.4.0 version: 4.4.0 + markdown-to-jsx: + specifier: ^7.4.7 + version: 7.4.7(react@18.2.0) next: specifier: 14.2.4 version: 14.2.4(react-dom@18.2.0(react@18.2.0))(react@18.2.0) @@ -318,6 +333,35 @@ packages: resolution: {integrity: sha512-+1VkjdD0QBLPodGrJUeqarH8VAIvQODIbwh9XpP5Syisf7YoQgsJKPNFoqqLQlu+VQ/tVSshMR6loPMn8U+dPg==} engines: {node: '>=14'} + '@radix-ui/primitive@1.1.0': + resolution: {integrity: sha512-4Z8dn6Upk0qk4P74xBhZ6Hd/w0mPEzOOLxy4xiPXOXqjF7jZS0VAKk7/x/H6FyY2zCkYJqePf1G5KmkmNJ4RBA==} + + '@radix-ui/react-arrow@1.1.0': + resolution: {integrity: sha512-FmlW1rCg7hBpEBwFbjHwCW6AmWLQM6g/v0Sn8XbP9NvmSZ2San1FpQeyPtufzOMSIx7Y4dzjlHoifhp+7NkZhw==} + peerDependencies: + '@types/react': '*' + '@types/react-dom': '*' + react: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc + react-dom: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc + peerDependenciesMeta: + '@types/react': + optional: true + '@types/react-dom': + optional: true + + '@radix-ui/react-checkbox@1.1.1': + resolution: {integrity: sha512-0i/EKJ222Afa1FE0C6pNJxDq1itzcl3HChE9DwskA4th4KRse8ojx8a1nVcOjwJdbpDLcz7uol77yYnQNMHdKw==} + peerDependencies: + '@types/react': '*' + '@types/react-dom': '*' + react: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc + react-dom: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc + peerDependenciesMeta: + '@types/react': + optional: true + '@types/react-dom': + optional: true + '@radix-ui/react-compose-refs@1.0.1': resolution: {integrity: sha512-fDSBgd44FKHa1FRMU59qBMPFcl2PZE+2nmqunj+BWFyYYjnhIDWL2ItDs3rrbJDQOtzt5nIebLCQc4QRfz6LJw==} peerDependencies: @@ -327,6 +371,146 @@ packages: '@types/react': optional: true + '@radix-ui/react-compose-refs@1.1.0': + resolution: {integrity: sha512-b4inOtiaOnYf9KWyO3jAeeCG6FeyfY6ldiEPanbUjWd+xIk5wZeHa8yVwmrJ2vderhu/BQvzCrJI0lHd+wIiqw==} + peerDependencies: + '@types/react': '*' + react: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc + peerDependenciesMeta: + '@types/react': + optional: true + + '@radix-ui/react-context@1.1.0': + resolution: {integrity: sha512-OKrckBy+sMEgYM/sMmqmErVn0kZqrHPJze+Ql3DzYsDDp0hl0L62nx/2122/Bvps1qz645jlcu2tD9lrRSdf8A==} + peerDependencies: + '@types/react': '*' + react: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc + peerDependenciesMeta: + '@types/react': + optional: true + + '@radix-ui/react-dismissable-layer@1.1.0': + resolution: {integrity: sha512-/UovfmmXGptwGcBQawLzvn2jOfM0t4z3/uKffoBlj724+n3FvBbZ7M0aaBOmkp6pqFYpO4yx8tSVJjx3Fl2jig==} + peerDependencies: + '@types/react': '*' + '@types/react-dom': '*' + react: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc + react-dom: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc + peerDependenciesMeta: + '@types/react': + optional: true + '@types/react-dom': + optional: true + + '@radix-ui/react-focus-guards@1.1.0': + resolution: {integrity: sha512-w6XZNUPVv6xCpZUqb/yN9DL6auvpGX3C/ee6Hdi16v2UUy25HV2Q5bcflsiDyT/g5RwbPQ/GIT1vLkeRb+ITBw==} + peerDependencies: + '@types/react': '*' + react: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc + peerDependenciesMeta: + '@types/react': + optional: true + + '@radix-ui/react-focus-scope@1.1.0': + resolution: {integrity: sha512-200UD8zylvEyL8Bx+z76RJnASR2gRMuxlgFCPAe/Q/679a/r0eK3MBVYMb7vZODZcffZBdob1EGnky78xmVvcA==} + peerDependencies: + '@types/react': '*' + '@types/react-dom': '*' + react: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc + react-dom: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc + peerDependenciesMeta: + '@types/react': + optional: true + '@types/react-dom': + optional: true + + '@radix-ui/react-id@1.1.0': + resolution: {integrity: sha512-EJUrI8yYh7WOjNOqpoJaf1jlFIH2LvtgAl+YcFqNCa+4hj64ZXmPkAKOFs/ukjz3byN6bdb/AVUqHkI8/uWWMA==} + peerDependencies: + '@types/react': '*' + react: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc + peerDependenciesMeta: + '@types/react': + optional: true + + '@radix-ui/react-label@2.1.0': + resolution: {integrity: sha512-peLblDlFw/ngk3UWq0VnYaOLy6agTZZ+MUO/WhVfm14vJGML+xH4FAl2XQGLqdefjNb7ApRg6Yn7U42ZhmYXdw==} + peerDependencies: + '@types/react': '*' + '@types/react-dom': '*' + react: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc + react-dom: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc + peerDependenciesMeta: + '@types/react': + optional: true + '@types/react-dom': + optional: true + + '@radix-ui/react-popover@1.1.1': + resolution: {integrity: sha512-3y1A3isulwnWhvTTwmIreiB8CF4L+qRjZnK1wYLO7pplddzXKby/GnZ2M7OZY3qgnl6p9AodUIHRYGXNah8Y7g==} + peerDependencies: + '@types/react': '*' + '@types/react-dom': '*' + react: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc + react-dom: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc + peerDependenciesMeta: + '@types/react': + optional: true + '@types/react-dom': + optional: true + + '@radix-ui/react-popper@1.2.0': + resolution: {integrity: sha512-ZnRMshKF43aBxVWPWvbj21+7TQCvhuULWJ4gNIKYpRlQt5xGRhLx66tMp8pya2UkGHTSlhpXwmjqltDYHhw7Vg==} + peerDependencies: + '@types/react': '*' + '@types/react-dom': '*' + react: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc + react-dom: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc + peerDependenciesMeta: + '@types/react': + optional: true + '@types/react-dom': + optional: true + + '@radix-ui/react-portal@1.1.1': + resolution: {integrity: sha512-A3UtLk85UtqhzFqtoC8Q0KvR2GbXF3mtPgACSazajqq6A41mEQgo53iPzY4i6BwDxlIFqWIhiQ2G729n+2aw/g==} + peerDependencies: + '@types/react': '*' + '@types/react-dom': '*' + react: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc + react-dom: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc + peerDependenciesMeta: + '@types/react': + optional: true + '@types/react-dom': + optional: true + + '@radix-ui/react-presence@1.1.0': + resolution: {integrity: sha512-Gq6wuRN/asf9H/E/VzdKoUtT8GC9PQc9z40/vEr0VCJ4u5XvvhWIrSsCB6vD2/cH7ugTdSfYq9fLJCcM00acrQ==} + peerDependencies: + '@types/react': '*' + '@types/react-dom': '*' + react: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc + react-dom: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc + peerDependenciesMeta: + '@types/react': + optional: true + '@types/react-dom': + optional: true + + '@radix-ui/react-primitive@2.0.0': + resolution: {integrity: sha512-ZSpFm0/uHa8zTvKBDjLFWLo8dkr4MBsiDLz0g3gMUwqgLHz9rTaRRGYDgvZPtBJgYCBKXkS9fzmoySgr8CO6Cw==} + peerDependencies: + '@types/react': '*' + '@types/react-dom': '*' + react: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc + react-dom: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc + peerDependenciesMeta: + '@types/react': + optional: true + '@types/react-dom': + optional: true + '@radix-ui/react-slot@1.0.2': resolution: {integrity: sha512-YeTpuq4deV+6DusvVUW4ivBgnkHwECUu0BiN43L5UCDFgdhsRUWAghhTF5MbvNTPzmiFOx90asDSUjWuCNapwg==} peerDependencies: @@ -336,6 +520,81 @@ packages: '@types/react': optional: true + '@radix-ui/react-slot@1.1.0': + resolution: {integrity: sha512-FUCf5XMfmW4dtYl69pdS4DbxKy8nj4M7SafBgPllysxmdachynNflAdp/gCsnYWNDnge6tI9onzMp5ARYc1KNw==} + peerDependencies: + '@types/react': '*' + react: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc + peerDependenciesMeta: + '@types/react': + optional: true + + '@radix-ui/react-use-callback-ref@1.1.0': + resolution: {integrity: sha512-CasTfvsy+frcFkbXtSJ2Zu9JHpN8TYKxkgJGWbjiZhFivxaeW7rMeZt7QELGVLaYVfFMsKHjb7Ak0nMEe+2Vfw==} + peerDependencies: + '@types/react': '*' + react: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc + peerDependenciesMeta: + '@types/react': + optional: true + + '@radix-ui/react-use-controllable-state@1.1.0': + resolution: {integrity: sha512-MtfMVJiSr2NjzS0Aa90NPTnvTSg6C/JLCV7ma0W6+OMV78vd8OyRpID+Ng9LxzsPbLeuBnWBA1Nq30AtBIDChw==} + peerDependencies: + '@types/react': '*' + react: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc + peerDependenciesMeta: + '@types/react': + optional: true + + '@radix-ui/react-use-escape-keydown@1.1.0': + resolution: {integrity: sha512-L7vwWlR1kTTQ3oh7g1O0CBF3YCyyTj8NmhLR+phShpyA50HCfBFKVJTpshm9PzLiKmehsrQzTYTpX9HvmC9rhw==} + peerDependencies: + '@types/react': '*' + react: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc + peerDependenciesMeta: + '@types/react': + optional: true + + '@radix-ui/react-use-layout-effect@1.1.0': + resolution: {integrity: sha512-+FPE0rOdziWSrH9athwI1R0HDVbWlEhd+FR+aSDk4uWGmSJ9Z54sdZVDQPZAinJhJXwfT+qnj969mCsT2gfm5w==} + peerDependencies: + '@types/react': '*' + react: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc + peerDependenciesMeta: + '@types/react': + optional: true + + '@radix-ui/react-use-previous@1.1.0': + resolution: {integrity: sha512-Z/e78qg2YFnnXcW88A4JmTtm4ADckLno6F7OXotmkQfeuCVaKuYzqAATPhVzl3delXE7CxIV8shofPn3jPc5Og==} + peerDependencies: + '@types/react': '*' + react: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc + peerDependenciesMeta: + '@types/react': + optional: true + + '@radix-ui/react-use-rect@1.1.0': + resolution: {integrity: sha512-0Fmkebhr6PiseyZlYAOtLS+nb7jLmpqTrJyv61Pe68MKYW6OWdRE2kI70TaYY27u7H0lajqM3hSMMLFq18Z7nQ==} + peerDependencies: + '@types/react': '*' + react: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc + peerDependenciesMeta: + '@types/react': + optional: true + + '@radix-ui/react-use-size@1.1.0': + resolution: {integrity: sha512-XW3/vWuIXHa+2Uwcc2ABSfcCledmXhhQPlGbfcRXbiUQI5Icjcg19BGCZVKKInYbvUCut/ufbbLLPFC5cbb1hw==} + peerDependencies: + '@types/react': '*' + react: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc + peerDependenciesMeta: + '@types/react': + optional: true + + '@radix-ui/rect@1.1.0': + resolution: {integrity: sha512-A9+lCBZoaMJlVKcRBz2YByCG+Cp2t6nAnMnNba+XiWxnj6r4JUFqfsgwocMBZU9LPtdxC6wB56ySYpc7LQIoJg==} + '@react-aria/focus@3.17.1': resolution: {integrity: sha512-FLTySoSNqX++u0nWZJPPN5etXY0WBxaIe/YuL/GTEeuqUIuC/2bJSaw5hlsM6T2yjy6Y/VAxBcKSdAFUlU6njQ==} peerDependencies: @@ -357,6 +616,44 @@ packages: peerDependencies: react: ^16.8.0 || ^17.0.0-rc.1 || ^18.0.0 + '@react-pdf/fns@2.2.1': + resolution: {integrity: sha512-s78aDg0vDYaijU5lLOCsUD+qinQbfOvcNeaoX9AiE7+kZzzCo6B/nX+l48cmt9OosJmvZvE9DWR9cLhrhOi2pA==} + + '@react-pdf/font@2.5.1': + resolution: {integrity: sha512-Hyb2zBb92Glc3lvhmJfy4dO2Mj29KB26Uk12Ua9EhKAdiuCTLBqgP8Oe1cGwrvDI7xA4OOcwvBMdYh0vhOUHzA==} + + '@react-pdf/image@2.3.6': + resolution: {integrity: sha512-7iZDYZrZlJqNzS6huNl2XdMcLFUo68e6mOdzQeJ63d5eApdthhSHBnkGzHfLhH5t8DCpZNtClmklzuLL63ADfw==} + + '@react-pdf/layout@3.12.1': + resolution: {integrity: sha512-BxSeykDxvADlpe4OGtQ7NH46QXq3uImAYsTHOPLCwbXMniQ1O3uCBx7H+HthxkCNshgYVPp9qS3KyvQv/oIZwg==} + + '@react-pdf/pdfkit@3.1.10': + resolution: {integrity: sha512-P/qPBtCFo2HDJD0i6NfbmoBRrsOVO8CIogYsefwG4fklTo50zNgnMM5U1WLckTuX8Qt1ThiQuokmTG5arheblA==} + + '@react-pdf/png-js@2.3.1': + resolution: {integrity: sha512-pEZ18I4t1vAUS4lmhvXPmXYP4PHeblpWP/pAlMMRkEyP7tdAeHUN7taQl9sf9OPq7YITMY3lWpYpJU6t4CZgZg==} + + '@react-pdf/primitives@3.1.1': + resolution: {integrity: sha512-miwjxLwTnO3IjoqkTVeTI+9CdyDggwekmSLhVCw+a/7FoQc+gF3J2dSKwsHvAcVFM0gvU8mzCeTofgw0zPDq0w==} + + '@react-pdf/render@3.4.4': + resolution: {integrity: sha512-CfGxWmVgrY3JgmB1iMnz2W6Ck+8pisZeFt8vGlxP+JfT+0onr208pQvGSV5KwA9LGhAdABxqc/+y17V3vtKdFA==} + + '@react-pdf/renderer@3.4.4': + resolution: {integrity: sha512-j1TWMHHXDeHdoQE3xjhBh0MZ2rn7wHIlP/uglr/EJZXqnPbfg6bfLzRJCM6bs+XJV3d8+zLQjHf6sF/fWcBDfg==} + peerDependencies: + react: ^16.8.0 || ^17.0.0 || ^18.0.0 + + '@react-pdf/stylesheet@4.2.5': + resolution: {integrity: sha512-XnmapeCW+hDuNdVwpuvO04WKv71wAs8aH+saIq29Bo2fp1SxznHTcQArTZtK6Wgr/E9BHXeB2iAPpUZuI6G+xA==} + + '@react-pdf/textkit@4.4.1': + resolution: {integrity: sha512-Jl9wdTqIvJ5pX+vAGz0EOhP7ut5Two9H6CzTKo/YYPeD79cM2yTXF3JzTERBC28y7LR0Waq9D2LHQjI+b/EYUQ==} + + '@react-pdf/types@2.5.0': + resolution: {integrity: sha512-XsVRkt0hQ60I4e3leAVt+aZR3KJCaJd179BfJHAv4F4x6Vq3yqkry8lcbUWKGKDw1j3/8sW4FsgGR41SFvsG9A==} + '@react-stately/utils@3.10.1': resolution: {integrity: sha512-VS/EHRyicef25zDZcM/ClpzYMC5i2YGN6uegOeQawmgfGjb02yaCX0F0zR69Pod9m2Hr3wunTbtpgVXvYbZItg==} peerDependencies: @@ -373,6 +670,12 @@ packages: '@swc/counter@0.1.3': resolution: {integrity: sha512-e2BR4lsJkkRlKZ/qCHPw9ZaSxc0MVUd7gtbtaB7aMvHeJVYe8sOB8DBZkP2DtISHGSku9sCK6T6cnY0CtXrOCQ==} + '@swc/helpers@0.4.14': + resolution: {integrity: sha512-4C7nX/dvpzB7za4Ql9K81xK3HPxCpHMgwTZVyf+9JQ6VUbn9jjZVN7/Nkdz/Ugzs2CSjqnL/UPXroiVBVHUWUw==} + + '@swc/helpers@0.4.36': + resolution: {integrity: sha512-5lxnyLEYFskErRPenYItLRSge5DjrJngYKdVjRSrWfza9G6KkgHEXi0vUZiyUeMU5JfXH1YnvXZzSp8ul88o2Q==} + '@swc/helpers@0.5.5': resolution: {integrity: sha512-KGYxvIOXcceOAbEk4bi/dVLEK9z8sZ0uBB3Il5b1rhfClSpcX0yfRO0KmTkqR2cnQDymwLB+25ZyMzICg/cm/A==} @@ -472,6 +775,9 @@ packages: '@vercel/git-hooks@1.0.0': resolution: {integrity: sha512-OxDFAAdyiJ/H0b8zR9rFCu3BIb78LekBXOphOYG3snV4ULhKFX387pBPpqZ9HLiRTejBWBxYEahkw79tuIgdAA==} + abs-svg-path@0.1.1: + resolution: {integrity: sha512-d8XPSGjfyzlXC3Xx891DJRyZfqk5JU0BJrDQcsWomFIV1/BIzPW5HDH5iDdWpqWaav0YVIEzT1RHTwWr0FFshA==} + acorn-jsx@5.3.2: resolution: {integrity: sha512-rq9s+JNhf0IChjtDXxllJ7g41oZk5SlXtp0LHwyA5cejwn7vKmKp4pPri6YEePv2PU65sAsegbXtIinmDFDXgQ==} peerDependencies: @@ -522,6 +828,10 @@ packages: argparse@2.0.1: resolution: {integrity: sha512-8+9WqebbFzpX9OR+Wa6O29asIogeRMzcGtAINdpMHHyAg10f05aSFVBbcEqGf/PXw1EjAZ+q2/bEBg3DvurK3Q==} + aria-hidden@1.2.4: + resolution: {integrity: sha512-y+CcFFwelSXpLZk/7fMB2mUbGtX9lKycf1MWJ7CaTIERyitVlyQx6C+sxcROU2BAJ24OiZyK+8wj2i8AlBoS3A==} + engines: {node: '>=10'} + aria-query@5.3.0: resolution: {integrity: sha512-b0P0sZPKtyu8HkeRAfCq0IfURZK+SuwMjY1UXGBU27wpAiTwQAIlq56IbIO+ytk/JjS1fMR14ee5WBBfKi5J6A==} @@ -587,6 +897,12 @@ packages: balanced-match@1.0.2: resolution: {integrity: sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw==} + base64-js@1.5.1: + resolution: {integrity: sha512-AKpaYlHn8t4SVbOHCy+b5+KKgvR4vrsD8vbvrbiQJps7fKDTkjkDry6ji0rUJjC0kzbNePLwzxq8iypo41qeWA==} + + bidi-js@1.0.3: + resolution: {integrity: sha512-RKshQI1R3YQ+n9YJz2QQ147P66ELpa1FQEg20Dk8oW9t2KgLbpDLLp9aGZ7y8WHSshDknG0bknqGw5/tyCs5tw==} + binary-extensions@2.3.0: resolution: {integrity: sha512-Ceh+7ox5qe7LJuLHoY0feh3pHuUDHAcRUeyL2VYghZwfpkNIy/+8Ocg0a3UuSoYzavmylwuLWQOf3hl0jjMMIw==} engines: {node: '>=8'} @@ -601,6 +917,12 @@ packages: resolution: {integrity: sha512-b8um+L1RzM3WDSzvhm6gIz1yfTbBt6YTlcEKAvsmqCZZFw46z626lVj9j1yEPW33H5H+lBQpZMP1k8l+78Ha0A==} engines: {node: '>=8'} + brotli@1.3.3: + resolution: {integrity: sha512-oTKjJdShmDuGW94SyyaoQvAjf30dZaHnjJ8uAF+u2/vGJkJbJPJAT1gDiOJP5v1Zb6f9KEyW/1HpuaWIXtGHPg==} + + browserify-zlib@0.2.0: + resolution: {integrity: sha512-Z942RysHXmJrhqk88FmKBVq/v5tqmSkDz7p54G/MGyjMnCFFnC79XWNbg+Vta8W6Wb2qtSZTSxIGkJrRpCFEiA==} + browserslist@4.23.0: resolution: {integrity: sha512-QW8HiM1shhT2GuzkvklfjcKDiWFXHOeFCIA/huJPwHsslwcydgk7X+z2zXpEijP98UCY7HbubZt5J2Zgvf0CaQ==} engines: {node: ^6 || ^7 || ^8 || ^9 || ^10 || ^11 || ^12 || >=13.7} @@ -667,6 +989,10 @@ packages: client-only@0.0.1: resolution: {integrity: sha512-IV3Ou0jSMzZrd3pZ48nLkT9DA7Ag1pnPzaiQhpW7c3RbcqqzvzzVu+L8gfqMp/8IM2MQtSiqaCxrrcfu8I8rMA==} + clone@2.1.2: + resolution: {integrity: sha512-3Pe/CF1Nn94hyhIYpjtiLhdCoEoz0DqQ+988E9gmeEdQZlojxnOb74wctFyuwWQHzqyf9X7C7MG8juUpqBJT8w==} + engines: {node: '>=0.8'} + clsx@2.1.0: resolution: {integrity: sha512-m3iNNWpd9rl3jvvcBnu70ylMdrXt8Vlq4HYadnU5fwcOtvkSQWPmj7amUcDT2qYI7risszBjI5AUIUox9D16pg==} engines: {node: '>=6'} @@ -684,6 +1010,9 @@ packages: color-name@1.1.4: resolution: {integrity: sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==} + color-string@1.9.1: + resolution: {integrity: sha512-shrVawQFojnZv6xM40anx4CkoDP+fZsw/ZerEMsW/pyzsRbElpsL/DBVW7q3ExxwusdNXI3lXpuhEZkzs8p5Eg==} + colorette@2.0.20: resolution: {integrity: sha512-IfEDxwoWIjkeXL1eXcDiow4UbKjhLdq6/EuSVR9GMN7KVH3r9gQ83e73hsz1Nd1T3ijd5xv1wcWRYO+D6kCI2w==} @@ -701,10 +1030,16 @@ packages: core-js-compat@3.36.1: resolution: {integrity: sha512-Dk997v9ZCt3X/npqzyGdTlq6t7lDBhZwGvV94PKzDArjp7BTRm7WlDAXYd/OWdeFHO8OChQYRJNJvUCqCbrtKA==} + cross-fetch@3.1.8: + resolution: {integrity: sha512-cvA+JwZoU0Xq+h6WkMvAUqPEYy92Obet6UdKLfW60qn99ftItKjB5T+BkyWOFWe2pUyfQ+IJHmpOTznqk1M6Kg==} + cross-spawn@7.0.3: resolution: {integrity: sha512-iRDPJKUPVEND7dHPO8rkbOnPpyDygcDFtWjpeWNCgy8WP2rXcxXL8TskReQl6OrB2G7+UJrags1q15Fudc7G6w==} engines: {node: '>= 8'} + crypto-js@4.2.0: + resolution: {integrity: sha512-KALDyEYgpY+Rlob/iriUtjV6d5Eq+Y191A5g4UqLAi8CyGP9N1+FdVbkc1SxKc2r4YAYqG8JzO2KGL+AizD70Q==} + cssesc@3.0.0: resolution: {integrity: sha512-/Tb/JcjK111nNScGob5MNtsntNM1aCNUDipB/TkwZFhyDrrE47SOx/18wF2bbjgc3ZzCSKW1T5nt5EbFoAz/Vg==} engines: {node: '>=4'} @@ -760,6 +1095,12 @@ packages: resolution: {integrity: sha512-0je+qPKHEMohvfRTCEo3CrPG6cAzAYgmzKyxRiYSSDkS6eGJdyVJm7WaYA5ECaAD9wLB2T4EEeymA5aFVcYXCA==} engines: {node: '>=6'} + detect-node-es@1.1.0: + resolution: {integrity: sha512-ypdmJU/TbBby2Dxibuv7ZLW3Bs1QEmM7nHjEANfohJLvE0XVujisn1qPJcZxg+qDucsr+bP6fLD1rPS3AhJ7EQ==} + + dfa@1.2.0: + resolution: {integrity: sha512-ED3jP8saaweFTjeGX8HQPjeC1YYyZs98jGNZx6IiBvxW7JG5v492kamAQB3m2wop07CvU/RQmzcKr6bgcC5D/Q==} + didyoumean@1.2.2: resolution: {integrity: sha512-gxtyfqMg7GKyhQmb056K7M3xszy/myH8w+B4RT+QXBQsvAOdc3XymqDDPHx1BgPgsdAA5SIifona89YtRATDzw==} @@ -959,6 +1300,10 @@ packages: eventemitter3@5.0.1: resolution: {integrity: sha512-GWkBvjiSZK87ELrYOSESUYeVIc9mvLLf/nXalMOS5dYrgZq9o5OVkbZAVM06CVxYsCwH9BDZFPlQTlPA1j4ahA==} + events@3.3.0: + resolution: {integrity: sha512-mQw+2fkQbALzQ7V0MY0IqdnXNOeTtP4r0lN9z7AAawCXgqea7bDii20AYrIBrFd/Hx0M2Ocz6S111CaFkUcb0Q==} + engines: {node: '>=0.8.x'} + execa@8.0.1: resolution: {integrity: sha512-VyhnebXciFV2DESc+p6B+y0LjSm0krU4OgJN44qFAhBY0TJ+1V61tYD2+wHusZ6F9n5K+vl8k0sTy7PEfV4qpg==} engines: {node: '>=16.17'} @@ -1002,6 +1347,9 @@ packages: flatted@3.3.1: resolution: {integrity: sha512-X8cqMLLie7KsNUDSdzeN8FYK9rEt4Dt67OsG/DNGnYTSDBG4uFAJFBnUeiV+zCVAvwFy56IjM9sH51jVaEhNxw==} + fontkit@2.0.2: + resolution: {integrity: sha512-jc4k5Yr8iov8QfS6u8w2CnHWVmbOGtdBtOXMze5Y+QD966Rx6PEVWXSEGwXlsDlKtu1G12cJjcsybnqhSk/+LA==} + for-each@0.3.3: resolution: {integrity: sha512-jqYfLp7mo9vIyQf8ykW2v7A+2N4QjeCeI5+Dz9XraiO1ign81wjiH7Fb9vSOWvQfNtmSa4H2RoQTrrXivdUZmw==} @@ -1043,6 +1391,10 @@ packages: resolution: {integrity: sha512-5uYhsJH8VJBTv7oslg4BznJYhDoRI6waYCxMmCdnTrcCrHA/fCFKoTFz2JKKE0HdDFUF7/oQuhzumXJK7paBRQ==} engines: {node: '>= 0.4'} + get-nonce@1.0.1: + resolution: {integrity: sha512-FJhYRoDaiatfEkUK8HKlicmu/3SGFD51q3itKDGoSTysQJBnfOcxU5GxnhE1E6soB76MbT0MBtnKJuXyAx+96Q==} + engines: {node: '>=6'} + get-stream@8.0.1: resolution: {integrity: sha512-VaUJspBffn/LMCJVoMvSAdmscJyS1auj5Zulnn5UoYcY531UWmdwhRWkcGKnGU93m5HSXP9LP2usOryrBtQowA==} engines: {node: '>=16'} @@ -1124,10 +1476,19 @@ packages: hosted-git-info@2.8.9: resolution: {integrity: sha512-mxIDAb9Lsm6DoOJ7xH+5+X4y1LU/4Hi50L9C5sIswK3JzULS4bwk1FvjdBgvYR4bzT4tuUQiC15FE2f5HbLvYw==} + hsl-to-hex@1.0.0: + resolution: {integrity: sha512-K6GVpucS5wFf44X0h2bLVRDsycgJmf9FF2elg+CrqD8GcFU8c6vYhgXn8NjUkFCwj+xDFb70qgLbTUm6sxwPmA==} + + hsl-to-rgb-for-reals@1.1.1: + resolution: {integrity: sha512-LgOWAkrN0rFaQpfdWBQlv/VhkOxb5AsBjk6NQVx4yEzWS923T07X0M1Y0VNko2H52HeSpZrZNNMJ0aFqsdVzQg==} + human-signals@5.0.0: resolution: {integrity: sha512-AXcZb6vzzrFAUE61HnN4mpLqd/cSIwNQjtNWR0euPm6y0iqx3G4gOXaIDdtdDwZmhwe82LA6+zinmW4UBWVePQ==} engines: {node: '>=16.17.0'} + hyphen@1.10.4: + resolution: {integrity: sha512-SejXzIpv9gOVdDWXd4suM1fdF1k2dxZGvuTdkOVLoazYfK7O4DykIQbdrvuyG+EaTNlXAGhMndtKrhykgbt0gg==} + ignore@5.3.1: resolution: {integrity: sha512-5Fytz/IraMjqpwfd34ke28PTVMjZjJG2MPn5t7OE4eUCUNf8BAa7b5WUS9/Qvr6mwOQS7Mk6vdsMno5he+T8Xw==} engines: {node: '>= 4'} @@ -1154,6 +1515,9 @@ packages: resolution: {integrity: sha512-NGnrKwXzSms2qUUih/ILZ5JBqNTSa1+ZmP6flaIp6KmSElgE9qdndzS3cqjrDovwFdmwsGsLdeFgB6suw+1e9g==} engines: {node: '>= 0.4'} + invariant@2.2.4: + resolution: {integrity: sha512-phJfQVBuaJM5raOpJjSfkiD6BpbCE4Ns//LaXl6wGYtUBY83nWS6Rf9tXm2e8VaK60JEjYldbPif/A2B1C2gNA==} + is-array-buffer@3.0.4: resolution: {integrity: sha512-wcjaerHw0ydZwfhiKbXJWLDY8A7yV7KhjQOpb83hGgGfId/aQa4TOvwyzn2PuswW2gPCYEL/nEAiSVpdOj1lXw==} engines: {node: '>= 0.4'} @@ -1161,6 +1525,9 @@ packages: is-arrayish@0.2.1: resolution: {integrity: sha512-zz06S8t0ozoDXMG+ube26zeCTNXcKIPJZJi8hBrF4idCLms4CG9QtK7qBl1boi5ODzFpjswb5JPmHCbMpjaYzg==} + is-arrayish@0.3.2: + resolution: {integrity: sha512-eVRqCvVlZbuw3GrM63ovNSNAeA1K16kaR/LRY/92w0zxQ5/1YzwblUX652i4Xs9RwAGjW9d9y6X88t8OaAJfWQ==} + is-async-function@2.0.0: resolution: {integrity: sha512-Y1JXKrfykRJGdlDwdKlLpLyMIiWqWvuSd17TvZk68PLAOGOoF4Xyav1z0Xhoi+gCYjZVeC5SI+hYFOfvXmGRCA==} engines: {node: '>= 0.4'} @@ -1270,6 +1637,9 @@ packages: resolution: {integrity: sha512-uZ25/bUAlUY5fR4OKT4rZQEBrzQWYV9ZJYGGsUmEJ6thodVJ1HX64ePQ6Z0qPWP+m+Uq6e9UugrE38jeYsDSMw==} engines: {node: '>= 0.4'} + is-url@1.2.4: + resolution: {integrity: sha512-ITvGim8FhRiYe4IQ5uHSkj7pVaPDrCTkNd3yq3cV7iZAcJdHTUMPMEHcqSOy9xZ9qFenQCvi+2wjH9a1nXqHww==} + is-weakmap@2.0.2: resolution: {integrity: sha512-K5pXYOm9wqY1RgjpL3YTkF39tni1XajUIkawTLUo9EZEVUFga5gSQJF8nNS7ZwJQ02y+1YCNYcMh+HIf1ZqE+w==} engines: {node: '>= 0.4'} @@ -1294,6 +1664,9 @@ packages: resolution: {integrity: sha512-N3yCS/NegsOBokc8GAdM8UcmfsKiSS8cipheD/nivzr700H+nsMOxJjQnvwOcRYVuFkdH0wGUvW2WbXGmrZGbQ==} engines: {node: '>=14'} + jay-peg@1.0.2: + resolution: {integrity: sha512-fyV3NVvv6pTys/3BTapBUGAWAuU9rM2gRcgijZHzptd5KKL+s+S7hESFN+wOsbDH1MzFwdlRAXi0aGxS6uiMKg==} + jiti@1.21.0: resolution: {integrity: sha512-gFqAIbuKyyso/3G2qhiO2OM6shY6EPP/R0+mkDbyspxKazh8BXDC5FiFsUjlczgdNz/vfra0da2y+aHrusLG/Q==} hasBin: true @@ -1414,6 +1787,15 @@ packages: resolution: {integrity: sha512-Jo6dJ04CmSjuznwJSS3pUeWmd/H0ffTlkXXgwZi+eq1UCmqQwCh+eLsYOYCwY991i2Fah4h1BEMCx4qThGbsiA==} engines: {node: '>=10'} + markdown-to-jsx@7.4.7: + resolution: {integrity: sha512-0+ls1IQZdU6cwM1yu0ZjjiVWYtkbExSyUIFU2ZeDIFuZM1W42Mh4OlJ4nb4apX4H8smxDHRdFaoIVJGwfv5hkg==} + engines: {node: '>= 10'} + peerDependencies: + react: '>= 0.14.0' + + media-engine@1.0.3: + resolution: {integrity: sha512-aa5tG6sDoK+k70B9iEX1NeyfT8ObCKhNDs6lJVpwF6r8vhUfuKMslIcirq6HIUYuuUYLefcEQOn9bSBOvawtwg==} + merge-stream@2.0.0: resolution: {integrity: sha512-abv/qOcuPfk3URPfDzmZU1LKmuw8kT+0nIHvKrKgFrwifol/doWcdA4ZqsWQ8ENrFKkd67Mfpo/LovbIUsbt3w==} @@ -1490,6 +1872,15 @@ packages: sass: optional: true + node-fetch@2.7.0: + resolution: {integrity: sha512-c4FRfUm/dbcWZ7U+1Wq0AwCyFL+3nt2bEw05wfxSz+DWpWsitgmSgYmy2dQdWyKC1694ELPqMs/YzUSNozLt8A==} + engines: {node: 4.x || >=6.0.0} + peerDependencies: + encoding: ^0.1.0 + peerDependenciesMeta: + encoding: + optional: true + node-releases@2.0.14: resolution: {integrity: sha512-y10wOWt8yZpqXmOgRo77WaHEmhYQYGNA6y421PKsKYWEK8aW+cqAphborZDhqfyKrbZEN92CN1X2KbafY2s7Yw==} @@ -1504,6 +1895,9 @@ packages: resolution: {integrity: sha512-bdok/XvKII3nUpklnV6P2hxtMNrCboOjAcyBuQnWEhO665FwrSNRxU+AqpsyvO6LgGYPspN+lu5CLtw4jPRKNA==} engines: {node: '>=0.10.0'} + normalize-svg-path@1.1.0: + resolution: {integrity: sha512-r9KHKG2UUeB5LoTouwDzBy2VxXlHsiM6fyLQvnJa0S5hrhzqElH/CH7TUGhT1fVvIYBIKf3OpY4YJ4CK+iaqHg==} + npm-run-path@5.3.0: resolution: {integrity: sha512-ppwTtiJZq0O/ai0z7yfudtBpWIoxM8yE6nHi1X47eFR2EWORqfbu6CnPlNsjeN683eT0qG6H/Pyf9fCcvjnnnQ==} engines: {node: ^12.20.0 || ^14.13.1 || >=16.0.0} @@ -1582,6 +1976,12 @@ packages: resolution: {integrity: sha512-R4nPAVTAU0B9D35/Gk3uJf/7XYbQcyohSKdvAxIRSNghFl4e71hVoGnBNQz9cWaXxO2I10KTC+3jMdvvoKw6dQ==} engines: {node: '>=6'} + pako@0.2.9: + resolution: {integrity: sha512-NUcwaKxUxWrZLpDG+z/xZaCgQITkA/Dv4V/T6bw7VON6l1Xz/VnrBqrYjZQ12TamKHzITTfOEIYUj48y2KXImA==} + + pako@1.0.11: + resolution: {integrity: sha512-4hLB8Py4zZce5s4yd9XzopqwVv/yGNhV1Bl8NTmCq1763HeK2+EwVTv+leGeL13Dnh2wfbqowVPXCIO0z4taYw==} + parent-module@1.0.1: resolution: {integrity: sha512-GQ2EWRpQV8/o+Aw8YqtfZZPfNRWZYkbidE9k5rpl/hC3vtHHBfGm2Ifi6qWV+coDGkrUKZAxE3Lot5kcsRlh+g==} engines: {node: '>=6'} @@ -1590,6 +1990,9 @@ packages: resolution: {integrity: sha512-ayCKvm/phCGxOkYRSCM82iDwct8/EonSEgCSxWxD7ve6jHggsFl4fZVQBPRNgQoKiuV/odhFrGzQXZwbifC8Rg==} engines: {node: '>=8'} + parse-svg-path@0.1.2: + resolution: {integrity: sha512-JyPSBnkTJ0AI8GGJLfMXvKq42cj5c006fnLz6fXy6zfoVjJizi8BNTpu8on8ziI1cKy9d9DGNuY17Ce7wuejpQ==} + path-exists@4.0.0: resolution: {integrity: sha512-ak9Qy5Q7jYb2Wwcey5Fpvg2KoAc/ZIhLSLOSBmRmygPsGwkVVt0fZa0qrtMz+m6tJTAHfZQ8FnmB4MG4LWy7/w==} engines: {node: '>=8'} @@ -1765,6 +2168,9 @@ packages: queue-microtask@1.2.3: resolution: {integrity: sha512-NuaNSa6flKT5JaSYQzJok04JzTL1CA6aGhv5rfLW3PgqA+M2ChpZQnAC8h8i4ZFkBS8X5RqkDBHA7r4hej3K9A==} + queue@6.0.2: + resolution: {integrity: sha512-iHZWu+q3IdFZFX36ro/lKBkSvfkztY5Y7HMiPlOUjhupPcG2JMfst2KKEpu5XndviX/3UhFbRngUPNKtgvtZiA==} + react-dom@18.2.0: resolution: {integrity: sha512-6IMTriUmvsjHUjNtEDudZfuDQUoWXVxKHhlEGSk81n4YFS+r/Kl99wXiwlVXtPBtJenozv2P+hxDsw9eA7Xo6g==} peerDependencies: @@ -1779,6 +2185,36 @@ packages: react-is@16.13.1: resolution: {integrity: sha512-24e6ynE2H+OKt4kqsOvNd8kBpV65zoxbA4BVsEOB3ARVWQki/DHzaUoC5KuON/BiccDaCCTZBuOcfZs70kR8bQ==} + react-remove-scroll-bar@2.3.6: + resolution: {integrity: sha512-DtSYaao4mBmX+HDo5YWYdBWQwYIQQshUV/dVxFxK+KM26Wjwp1gZ6rv6OC3oujI6Bfu6Xyg3TwK533AQutsn/g==} + engines: {node: '>=10'} + peerDependencies: + '@types/react': ^16.8.0 || ^17.0.0 || ^18.0.0 + react: ^16.8.0 || ^17.0.0 || ^18.0.0 + peerDependenciesMeta: + '@types/react': + optional: true + + react-remove-scroll@2.5.7: + resolution: {integrity: sha512-FnrTWO4L7/Bhhf3CYBNArEG/yROV0tKmTv7/3h9QCFvH6sndeFf1wPqOcbFVu5VAulS5dV1wGT3GZZ/1GawqiA==} + engines: {node: '>=10'} + peerDependencies: + '@types/react': ^16.8.0 || ^17.0.0 || ^18.0.0 + react: ^16.8.0 || ^17.0.0 || ^18.0.0 + peerDependenciesMeta: + '@types/react': + optional: true + + react-style-singleton@2.2.1: + resolution: {integrity: sha512-ZWj0fHEMyWkHzKYUr2Bs/4zU6XLmq9HsgBURm7g5pAVfyn49DgUiNgY2d4lXRlYSiCif9YBGpQleewkcqddc7g==} + engines: {node: '>=10'} + peerDependencies: + '@types/react': ^16.8.0 || ^17.0.0 || ^18.0.0 + react: ^16.8.0 || ^17.0.0 || ^18.0.0 + peerDependenciesMeta: + '@types/react': + optional: true + react-tooltip@5.26.3: resolution: {integrity: sha512-MpYAws8CEHUd/RC4GaDCdoceph/T4KHM5vS5Dbk8FOmLMvvIht2ymP2htWdrke7K6lqPO8rz8+bnwWUIXeDlzg==} peerDependencies: @@ -1823,6 +2259,10 @@ packages: resolution: {integrity: sha512-qx+xQGZVsy55CH0a1hiVwHmqjLryfh7wQyF5HO07XJ9f7dQMY/gPQHhlyDkIzJKC+x2fUCpCcUODUUUFrm7SHA==} hasBin: true + require-from-string@2.0.2: + resolution: {integrity: sha512-Xf0nWe6RseziFMu+Ap9biiUbmplq6S9/p+7w7YXP/JBHhrUDDUhwa+vANyubuqfZWTveU//DYVGsDG7RKL/vEw==} + engines: {node: '>=0.10.0'} + resolve-from@4.0.0: resolution: {integrity: sha512-pb/MYmXstAkysRFx8piNI1tGFNQIFA3vkE3Gq4EuA1dF6gHp/+vgZqsCGJapvy8N3Q+4o7FwvquPJcnZ7RYy4g==} engines: {node: '>=4'} @@ -1842,6 +2282,9 @@ packages: resolution: {integrity: sha512-I9fPXU9geO9bHOt9pHHOhOkYerIMsmVaWB0rA2AI9ERh/+x/i7MV5HKBNrg+ljO5eoPVgCcnFuRjJ9uH6I/3eg==} engines: {node: ^12.20.0 || ^14.13.1 || >=16.0.0} + restructure@3.0.2: + resolution: {integrity: sha512-gSfoiOEA0VPE6Tukkrr7I0RBdE0s7H1eFCDBk05l1KIQT1UIKNc5JZy6jdyW6eYH3aR3g5b3PuL77rq0hvwtAw==} + reusify@1.0.4: resolution: {integrity: sha512-U9nH88a3fc/ekCF1l0/UP1IosiuIjyTh7hBvXVMHYgVcfGvt897Xguj2UOLDeI5BG2m7/uwyaLVT6fbtCwTyzw==} engines: {iojs: '>=1.0.0', node: '>=0.10.0'} @@ -1860,10 +2303,16 @@ packages: resolution: {integrity: sha512-vj6RsCsWBCf19jIeHEfkRMw8DPiBb+DMXklQ/1SGDHOMlHdPUkZXFQ2YdplS23zESTijAcurb1aSgJA3AgMu1Q==} engines: {node: '>=0.4'} + safe-buffer@5.2.1: + resolution: {integrity: sha512-rp3So07KcdmmKbGvgaNxQSJr7bGVSVk5S9Eq1F+ppbRo70+YeaDxkw5Dd8NPN+GD6bjnYm2VuPuCXmpuYvmCXQ==} + safe-regex-test@1.0.3: resolution: {integrity: sha512-CdASjNJPvRa7roO6Ra/gLYBTzYzzPyyBXxIMdGW3USQLyjWEls2RgW5UBTXaQVp+OrpeCK3bLem8smtmheoRuw==} engines: {node: '>= 0.4'} + scheduler@0.17.0: + resolution: {integrity: sha512-7rro8Io3tnCPuY4la/NuI5F2yfESpnfZyT6TtkXnSWVkcu0BCDJ+8gk5ozUaFaxpIyNuWAPXrH0yFcSi28fnDA==} + scheduler@0.23.0: resolution: {integrity: sha512-CtuThmgHNg7zIZWAXi3AsyIzA3n4xx7aNyjwC2VJldO2LMVDhFK+63xGqq6CsJH4rTAt6/M+N4GhZiDYPx9eUw==} @@ -1907,6 +2356,9 @@ packages: resolution: {integrity: sha512-bzyZ1e88w9O1iNJbKnOlvYTrWPDl46O1bG0D3XInv+9tkPrxrN8jUUTiFlDkkmKWgn1M6CfIA13SuGqOa9Korw==} engines: {node: '>=14'} + simple-swizzle@0.2.2: + resolution: {integrity: sha512-JA//kQgZtbuY83m+xT+tXJkmJncGMTFT+C+g2h2R9uxkYIrE2yy9sgmcLhCnw57/WSD+Eh3J97FPEDFnbXnDUg==} + slash@3.0.0: resolution: {integrity: sha512-g9Q1haeby36OSStwb4ntCGGGaKsaVSjQ68fBxoQcutl5fS1vuY18H3wSt3jFyFtrkx+Kz0V1G85A4MyAdDMi2Q==} engines: {node: '>=8'} @@ -1970,6 +2422,9 @@ packages: resolution: {integrity: sha512-UXSH262CSZY1tfu3G3Secr6uGLCFVPMhIqHjlgCUtCCcgihYc/xKs9djMTMUOb2j1mVSeU8EU6NWc/iQKU6Gfg==} engines: {node: '>= 0.4'} + string_decoder@1.3.0: + resolution: {integrity: sha512-hkRX8U1WjJFd8LsDJ2yQ/wWWxaopEsABU1XfkM8A+j0+85JAGppt16cr1Whg6KIbb4okU6Mql6BOj+uup/wKeA==} + strip-ansi@6.0.1: resolution: {integrity: sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A==} engines: {node: '>=8'} @@ -2024,6 +2479,9 @@ packages: resolution: {integrity: sha512-ot0WnXS9fgdkgIcePe6RHNk1WA8+muPa6cSjeR3V8K27q9BB1rTE3R1p7Hv0z1ZyAc8s6Vvv8DIyWf681MAt0w==} engines: {node: '>= 0.4'} + svg-arc-to-cubic-bezier@3.2.0: + resolution: {integrity: sha512-djbJ/vZKZO+gPoSDThGNpKDO+o+bAeA4XQKovvkNCqnIS2t+S4qnLAGQhyyrulhCFRl1WWzAp0wUDV8PpTVU3g==} + tabbable@6.2.0: resolution: {integrity: sha512-Cat63mxsVJlzYvN51JmVXIgNoUokrIaT2zLclCXjRd8boZ0004U4KCs/sToJ75C6sdlByWxpYnb5Boif1VSFew==} @@ -2055,10 +2513,16 @@ packages: thenify@3.3.1: resolution: {integrity: sha512-RVZSIV5IG10Hk3enotrhvz0T9em6cyHBLkH/YAZuKqd8hRkKhSfCGIcP2KUY0EPxndzANBmNllzWPwak+bheSw==} + tiny-inflate@1.0.3: + resolution: {integrity: sha512-pkY1fj1cKHb2seWDy0B16HeWyczlJA9/WW3u3c4z/NiWDsO3DOU5D7nhTLE9CF0yXv/QZFY7sEJmj24dK+Rrqw==} + to-regex-range@5.0.1: resolution: {integrity: sha512-65P7iz6X5yEr1cwcgvQxbbIw7Uk3gOy5dIdtZ4rDveLqhrdJP+Li/Hx6tyK0NEb+2GCyneCMJiGqrADCSNk8sQ==} engines: {node: '>=8.0'} + tr46@0.0.3: + resolution: {integrity: sha512-N3WMsuqV66lT30CrXNbEjx4GEwlow3v6rr4mCcv6prnfwhS01rkgyFdjPNBYd9br7LpXV1+Emh01fHnq2Gdgrw==} + ts-api-utils@1.3.0: resolution: {integrity: sha512-UQMIo7pb8WRomKR1/+MFVLTroIvDVtMX3K6OUir8ynLyzB8Jeriont2bTAtmNPa1ekAgN7YPDyf6V+ygrdU+eQ==} engines: {node: '>=16'} @@ -2117,6 +2581,12 @@ packages: undici-types@5.26.5: resolution: {integrity: sha512-JlCMO+ehdEIKqlFxk6IfVoAUVmgz7cU7zD/h9XZ0qzeosSHmUJVOzSQvvYSYWXkFXC+IfLKSIffhv0sVZup6pA==} + unicode-properties@1.4.1: + resolution: {integrity: sha512-CLjCCLQ6UuMxWnbIylkisbRj31qxHPAurvena/0iwSVbQ2G1VY5/HjV0IRabOEbDHlzZlRdCrD4NhB0JtU40Pg==} + + unicode-trie@2.0.0: + resolution: {integrity: sha512-x7bc76x0bm4prf1VLg79uhAzKw8DVboClSN5VxJuQ+LKDOVEW9CdH+VY7SP+vX7xCYQqzzgQpFqz15zeLvAtZQ==} + update-browserslist-db@1.0.13: resolution: {integrity: sha512-xebP81SNcPuNpPP3uzeW1NYXxI3rxyJzF3pD6sH4jE7o/IX+WtSpwnVU+qIsDPyk0d3hmFQ7mjqc6AtV604hbg==} hasBin: true @@ -2126,12 +2596,42 @@ packages: uri-js@4.4.1: resolution: {integrity: sha512-7rKUyy33Q1yc98pQ1DAmLtwX109F7TIfWlW1Ydo8Wl1ii1SeHieeh0HHfPeL2fMXK6z0s8ecKs9frCuLJvndBg==} + use-callback-ref@1.3.2: + resolution: {integrity: sha512-elOQwe6Q8gqZgDA8mrh44qRTQqpIHDcZ3hXTLjBe1i4ph8XpNJnO+aQf3NaG+lriLopI4HMx9VjQLfPQ6vhnoA==} + engines: {node: '>=10'} + peerDependencies: + '@types/react': ^16.8.0 || ^17.0.0 || ^18.0.0 + react: ^16.8.0 || ^17.0.0 || ^18.0.0 + peerDependenciesMeta: + '@types/react': + optional: true + + use-sidecar@1.1.2: + resolution: {integrity: sha512-epTbsLuzZ7lPClpz2TyryBfztm7m+28DlEv2ZCQ3MDr5ssiwyOwGH/e5F9CkfWjJ1t4clvI58yF822/GUkjjhw==} + engines: {node: '>=10'} + peerDependencies: + '@types/react': ^16.9.0 || ^17.0.0 || ^18.0.0 + react: ^16.8.0 || ^17.0.0 || ^18.0.0 + peerDependenciesMeta: + '@types/react': + optional: true + util-deprecate@1.0.2: resolution: {integrity: sha512-EPD5q1uXyFxJpCrLnCc1nHnq3gOa6DZBocAIiI2TaSCA7VCJ1UJDMagCzIkXNsUYfD1daK//LTEQ8xiIbrHtcw==} validate-npm-package-license@3.0.4: resolution: {integrity: sha512-DpKm2Ui/xN7/HQKCtpZxoRWBhZ9Z0kqtygG8XCgNQ8ZlDnxuQmWhj566j8fN4Cu3/JmbhsDo7fcAJq4s9h27Ew==} + vite-compatible-readable-stream@3.6.1: + resolution: {integrity: sha512-t20zYkrSf868+j/p31cRIGN28Phrjm3nRSLR2fyc2tiWi4cZGVdv68yNlwnIINTkMTmPoMiSlc0OadaO7DXZaQ==} + engines: {node: '>= 6'} + + webidl-conversions@3.0.1: + resolution: {integrity: sha512-2JAn3z8AR6rjK8Sm8orRC0h/bcl/DqL7tRPdGZ4I1CjdF+EaMLmYxBHyXuKL849eucPFhvBoxMsflfOb8kxaeQ==} + + whatwg-url@5.0.0: + resolution: {integrity: sha512-saE57nupxk6v3HY35+jzBwYa0rKSy0XR8JSxZPwgLr7ys0IBzhGviA1/TUGJLmSVqs8pb9AnvICXEuOHLprYTw==} + which-boxed-primitive@1.0.2: resolution: {integrity: sha512-bwZdv0AKLpplFY2KZRX6TvyuN7ojjr7lwkg6ml0roIy9YeuSr7JS372qlNW18UQYzgYK9ziGcerWqZOmEn9VNg==} @@ -2183,6 +2683,9 @@ packages: resolution: {integrity: sha512-rVksvsnNCdJ/ohGc6xgPwyN8eheCxsiLM8mxuE/t/mOVqJewPuO1miLpTHQiRgTKCLexL4MeAFVagts7HmNZ2Q==} engines: {node: '>=10'} + yoga-layout@2.0.1: + resolution: {integrity: sha512-tT/oChyDXelLo2A+UVnlW9GU7CsvFMaEnd9kVFsaiCQonFAXd3xrHhkLYu+suwwosrAEQ746xBU+HvYtm1Zs2Q==} + zod@3.23.8: resolution: {integrity: sha512-XBx9AXhXktjUqnepgTiE5flcKIYWi/rme0Eaj+5Y0lftuGBq+jyRu/md4WnuxqgP1ubdpNCsYEYPxrzVHD8d6g==} @@ -2361,6 +2864,33 @@ snapshots: '@pkgjs/parseargs@0.11.0': optional: true + '@radix-ui/primitive@1.1.0': {} + + '@radix-ui/react-arrow@1.1.0(@types/react-dom@18.2.22)(@types/react@18.2.72)(react-dom@18.2.0(react@18.2.0))(react@18.2.0)': + dependencies: + '@radix-ui/react-primitive': 2.0.0(@types/react-dom@18.2.22)(@types/react@18.2.72)(react-dom@18.2.0(react@18.2.0))(react@18.2.0) + react: 18.2.0 + react-dom: 18.2.0(react@18.2.0) + optionalDependencies: + '@types/react': 18.2.72 + '@types/react-dom': 18.2.22 + + '@radix-ui/react-checkbox@1.1.1(@types/react-dom@18.2.22)(@types/react@18.2.72)(react-dom@18.2.0(react@18.2.0))(react@18.2.0)': + dependencies: + '@radix-ui/primitive': 1.1.0 + '@radix-ui/react-compose-refs': 1.1.0(@types/react@18.2.72)(react@18.2.0) + '@radix-ui/react-context': 1.1.0(@types/react@18.2.72)(react@18.2.0) + '@radix-ui/react-presence': 1.1.0(@types/react-dom@18.2.22)(@types/react@18.2.72)(react-dom@18.2.0(react@18.2.0))(react@18.2.0) + '@radix-ui/react-primitive': 2.0.0(@types/react-dom@18.2.22)(@types/react@18.2.72)(react-dom@18.2.0(react@18.2.0))(react@18.2.0) + '@radix-ui/react-use-controllable-state': 1.1.0(@types/react@18.2.72)(react@18.2.0) + '@radix-ui/react-use-previous': 1.1.0(@types/react@18.2.72)(react@18.2.0) + '@radix-ui/react-use-size': 1.1.0(@types/react@18.2.72)(react@18.2.0) + react: 18.2.0 + react-dom: 18.2.0(react@18.2.0) + optionalDependencies: + '@types/react': 18.2.72 + '@types/react-dom': 18.2.22 + '@radix-ui/react-compose-refs@1.0.1(@types/react@18.2.72)(react@18.2.0)': dependencies: '@babel/runtime': 7.24.1 @@ -2368,6 +2898,134 @@ snapshots: optionalDependencies: '@types/react': 18.2.72 + '@radix-ui/react-compose-refs@1.1.0(@types/react@18.2.72)(react@18.2.0)': + dependencies: + react: 18.2.0 + optionalDependencies: + '@types/react': 18.2.72 + + '@radix-ui/react-context@1.1.0(@types/react@18.2.72)(react@18.2.0)': + dependencies: + react: 18.2.0 + optionalDependencies: + '@types/react': 18.2.72 + + '@radix-ui/react-dismissable-layer@1.1.0(@types/react-dom@18.2.22)(@types/react@18.2.72)(react-dom@18.2.0(react@18.2.0))(react@18.2.0)': + dependencies: + '@radix-ui/primitive': 1.1.0 + '@radix-ui/react-compose-refs': 1.1.0(@types/react@18.2.72)(react@18.2.0) + '@radix-ui/react-primitive': 2.0.0(@types/react-dom@18.2.22)(@types/react@18.2.72)(react-dom@18.2.0(react@18.2.0))(react@18.2.0) + '@radix-ui/react-use-callback-ref': 1.1.0(@types/react@18.2.72)(react@18.2.0) + '@radix-ui/react-use-escape-keydown': 1.1.0(@types/react@18.2.72)(react@18.2.0) + react: 18.2.0 + react-dom: 18.2.0(react@18.2.0) + optionalDependencies: + '@types/react': 18.2.72 + '@types/react-dom': 18.2.22 + + '@radix-ui/react-focus-guards@1.1.0(@types/react@18.2.72)(react@18.2.0)': + dependencies: + react: 18.2.0 + optionalDependencies: + '@types/react': 18.2.72 + + '@radix-ui/react-focus-scope@1.1.0(@types/react-dom@18.2.22)(@types/react@18.2.72)(react-dom@18.2.0(react@18.2.0))(react@18.2.0)': + dependencies: + '@radix-ui/react-compose-refs': 1.1.0(@types/react@18.2.72)(react@18.2.0) + '@radix-ui/react-primitive': 2.0.0(@types/react-dom@18.2.22)(@types/react@18.2.72)(react-dom@18.2.0(react@18.2.0))(react@18.2.0) + '@radix-ui/react-use-callback-ref': 1.1.0(@types/react@18.2.72)(react@18.2.0) + react: 18.2.0 + react-dom: 18.2.0(react@18.2.0) + optionalDependencies: + '@types/react': 18.2.72 + '@types/react-dom': 18.2.22 + + '@radix-ui/react-id@1.1.0(@types/react@18.2.72)(react@18.2.0)': + dependencies: + '@radix-ui/react-use-layout-effect': 1.1.0(@types/react@18.2.72)(react@18.2.0) + react: 18.2.0 + optionalDependencies: + '@types/react': 18.2.72 + + '@radix-ui/react-label@2.1.0(@types/react-dom@18.2.22)(@types/react@18.2.72)(react-dom@18.2.0(react@18.2.0))(react@18.2.0)': + dependencies: + '@radix-ui/react-primitive': 2.0.0(@types/react-dom@18.2.22)(@types/react@18.2.72)(react-dom@18.2.0(react@18.2.0))(react@18.2.0) + react: 18.2.0 + react-dom: 18.2.0(react@18.2.0) + optionalDependencies: + '@types/react': 18.2.72 + '@types/react-dom': 18.2.22 + + '@radix-ui/react-popover@1.1.1(@types/react-dom@18.2.22)(@types/react@18.2.72)(react-dom@18.2.0(react@18.2.0))(react@18.2.0)': + dependencies: + '@radix-ui/primitive': 1.1.0 + '@radix-ui/react-compose-refs': 1.1.0(@types/react@18.2.72)(react@18.2.0) + '@radix-ui/react-context': 1.1.0(@types/react@18.2.72)(react@18.2.0) + '@radix-ui/react-dismissable-layer': 1.1.0(@types/react-dom@18.2.22)(@types/react@18.2.72)(react-dom@18.2.0(react@18.2.0))(react@18.2.0) + '@radix-ui/react-focus-guards': 1.1.0(@types/react@18.2.72)(react@18.2.0) + '@radix-ui/react-focus-scope': 1.1.0(@types/react-dom@18.2.22)(@types/react@18.2.72)(react-dom@18.2.0(react@18.2.0))(react@18.2.0) + '@radix-ui/react-id': 1.1.0(@types/react@18.2.72)(react@18.2.0) + '@radix-ui/react-popper': 1.2.0(@types/react-dom@18.2.22)(@types/react@18.2.72)(react-dom@18.2.0(react@18.2.0))(react@18.2.0) + '@radix-ui/react-portal': 1.1.1(@types/react-dom@18.2.22)(@types/react@18.2.72)(react-dom@18.2.0(react@18.2.0))(react@18.2.0) + '@radix-ui/react-presence': 1.1.0(@types/react-dom@18.2.22)(@types/react@18.2.72)(react-dom@18.2.0(react@18.2.0))(react@18.2.0) + '@radix-ui/react-primitive': 2.0.0(@types/react-dom@18.2.22)(@types/react@18.2.72)(react-dom@18.2.0(react@18.2.0))(react@18.2.0) + '@radix-ui/react-slot': 1.1.0(@types/react@18.2.72)(react@18.2.0) + '@radix-ui/react-use-controllable-state': 1.1.0(@types/react@18.2.72)(react@18.2.0) + aria-hidden: 1.2.4 + react: 18.2.0 + react-dom: 18.2.0(react@18.2.0) + react-remove-scroll: 2.5.7(@types/react@18.2.72)(react@18.2.0) + optionalDependencies: + '@types/react': 18.2.72 + '@types/react-dom': 18.2.22 + + '@radix-ui/react-popper@1.2.0(@types/react-dom@18.2.22)(@types/react@18.2.72)(react-dom@18.2.0(react@18.2.0))(react@18.2.0)': + dependencies: + '@floating-ui/react-dom': 2.1.0(react-dom@18.2.0(react@18.2.0))(react@18.2.0) + '@radix-ui/react-arrow': 1.1.0(@types/react-dom@18.2.22)(@types/react@18.2.72)(react-dom@18.2.0(react@18.2.0))(react@18.2.0) + '@radix-ui/react-compose-refs': 1.1.0(@types/react@18.2.72)(react@18.2.0) + '@radix-ui/react-context': 1.1.0(@types/react@18.2.72)(react@18.2.0) + '@radix-ui/react-primitive': 2.0.0(@types/react-dom@18.2.22)(@types/react@18.2.72)(react-dom@18.2.0(react@18.2.0))(react@18.2.0) + '@radix-ui/react-use-callback-ref': 1.1.0(@types/react@18.2.72)(react@18.2.0) + '@radix-ui/react-use-layout-effect': 1.1.0(@types/react@18.2.72)(react@18.2.0) + '@radix-ui/react-use-rect': 1.1.0(@types/react@18.2.72)(react@18.2.0) + '@radix-ui/react-use-size': 1.1.0(@types/react@18.2.72)(react@18.2.0) + '@radix-ui/rect': 1.1.0 + react: 18.2.0 + react-dom: 18.2.0(react@18.2.0) + optionalDependencies: + '@types/react': 18.2.72 + '@types/react-dom': 18.2.22 + + '@radix-ui/react-portal@1.1.1(@types/react-dom@18.2.22)(@types/react@18.2.72)(react-dom@18.2.0(react@18.2.0))(react@18.2.0)': + dependencies: + '@radix-ui/react-primitive': 2.0.0(@types/react-dom@18.2.22)(@types/react@18.2.72)(react-dom@18.2.0(react@18.2.0))(react@18.2.0) + '@radix-ui/react-use-layout-effect': 1.1.0(@types/react@18.2.72)(react@18.2.0) + react: 18.2.0 + react-dom: 18.2.0(react@18.2.0) + optionalDependencies: + '@types/react': 18.2.72 + '@types/react-dom': 18.2.22 + + '@radix-ui/react-presence@1.1.0(@types/react-dom@18.2.22)(@types/react@18.2.72)(react-dom@18.2.0(react@18.2.0))(react@18.2.0)': + dependencies: + '@radix-ui/react-compose-refs': 1.1.0(@types/react@18.2.72)(react@18.2.0) + '@radix-ui/react-use-layout-effect': 1.1.0(@types/react@18.2.72)(react@18.2.0) + react: 18.2.0 + react-dom: 18.2.0(react@18.2.0) + optionalDependencies: + '@types/react': 18.2.72 + '@types/react-dom': 18.2.22 + + '@radix-ui/react-primitive@2.0.0(@types/react-dom@18.2.22)(@types/react@18.2.72)(react-dom@18.2.0(react@18.2.0))(react@18.2.0)': + dependencies: + '@radix-ui/react-slot': 1.1.0(@types/react@18.2.72)(react@18.2.0) + react: 18.2.0 + react-dom: 18.2.0(react@18.2.0) + optionalDependencies: + '@types/react': 18.2.72 + '@types/react-dom': 18.2.22 + '@radix-ui/react-slot@1.0.2(@types/react@18.2.72)(react@18.2.0)': dependencies: '@babel/runtime': 7.24.1 @@ -2376,6 +3034,61 @@ snapshots: optionalDependencies: '@types/react': 18.2.72 + '@radix-ui/react-slot@1.1.0(@types/react@18.2.72)(react@18.2.0)': + dependencies: + '@radix-ui/react-compose-refs': 1.1.0(@types/react@18.2.72)(react@18.2.0) + react: 18.2.0 + optionalDependencies: + '@types/react': 18.2.72 + + '@radix-ui/react-use-callback-ref@1.1.0(@types/react@18.2.72)(react@18.2.0)': + dependencies: + react: 18.2.0 + optionalDependencies: + '@types/react': 18.2.72 + + '@radix-ui/react-use-controllable-state@1.1.0(@types/react@18.2.72)(react@18.2.0)': + dependencies: + '@radix-ui/react-use-callback-ref': 1.1.0(@types/react@18.2.72)(react@18.2.0) + react: 18.2.0 + optionalDependencies: + '@types/react': 18.2.72 + + '@radix-ui/react-use-escape-keydown@1.1.0(@types/react@18.2.72)(react@18.2.0)': + dependencies: + '@radix-ui/react-use-callback-ref': 1.1.0(@types/react@18.2.72)(react@18.2.0) + react: 18.2.0 + optionalDependencies: + '@types/react': 18.2.72 + + '@radix-ui/react-use-layout-effect@1.1.0(@types/react@18.2.72)(react@18.2.0)': + dependencies: + react: 18.2.0 + optionalDependencies: + '@types/react': 18.2.72 + + '@radix-ui/react-use-previous@1.1.0(@types/react@18.2.72)(react@18.2.0)': + dependencies: + react: 18.2.0 + optionalDependencies: + '@types/react': 18.2.72 + + '@radix-ui/react-use-rect@1.1.0(@types/react@18.2.72)(react@18.2.0)': + dependencies: + '@radix-ui/rect': 1.1.0 + react: 18.2.0 + optionalDependencies: + '@types/react': 18.2.72 + + '@radix-ui/react-use-size@1.1.0(@types/react@18.2.72)(react@18.2.0)': + dependencies: + '@radix-ui/react-use-layout-effect': 1.1.0(@types/react@18.2.72)(react@18.2.0) + react: 18.2.0 + optionalDependencies: + '@types/react': 18.2.72 + + '@radix-ui/rect@1.1.0': {} + '@react-aria/focus@3.17.1(react@18.2.0)': dependencies: '@react-aria/interactions': 3.21.3(react@18.2.0) @@ -2407,6 +3120,113 @@ snapshots: clsx: 2.1.0 react: 18.2.0 + '@react-pdf/fns@2.2.1': + dependencies: + '@babel/runtime': 7.24.1 + + '@react-pdf/font@2.5.1': + dependencies: + '@babel/runtime': 7.24.1 + '@react-pdf/types': 2.5.0 + cross-fetch: 3.1.8 + fontkit: 2.0.2 + is-url: 1.2.4 + transitivePeerDependencies: + - encoding + + '@react-pdf/image@2.3.6': + dependencies: + '@babel/runtime': 7.24.1 + '@react-pdf/png-js': 2.3.1 + cross-fetch: 3.1.8 + jay-peg: 1.0.2 + transitivePeerDependencies: + - encoding + + '@react-pdf/layout@3.12.1': + dependencies: + '@babel/runtime': 7.24.1 + '@react-pdf/fns': 2.2.1 + '@react-pdf/image': 2.3.6 + '@react-pdf/pdfkit': 3.1.10 + '@react-pdf/primitives': 3.1.1 + '@react-pdf/stylesheet': 4.2.5 + '@react-pdf/textkit': 4.4.1 + '@react-pdf/types': 2.5.0 + cross-fetch: 3.1.8 + emoji-regex: 10.3.0 + queue: 6.0.2 + yoga-layout: 2.0.1 + transitivePeerDependencies: + - encoding + + '@react-pdf/pdfkit@3.1.10': + dependencies: + '@babel/runtime': 7.24.1 + '@react-pdf/png-js': 2.3.1 + browserify-zlib: 0.2.0 + crypto-js: 4.2.0 + fontkit: 2.0.2 + jay-peg: 1.0.2 + vite-compatible-readable-stream: 3.6.1 + + '@react-pdf/png-js@2.3.1': + dependencies: + browserify-zlib: 0.2.0 + + '@react-pdf/primitives@3.1.1': {} + + '@react-pdf/render@3.4.4': + dependencies: + '@babel/runtime': 7.24.1 + '@react-pdf/fns': 2.2.1 + '@react-pdf/primitives': 3.1.1 + '@react-pdf/textkit': 4.4.1 + '@react-pdf/types': 2.5.0 + abs-svg-path: 0.1.1 + color-string: 1.9.1 + normalize-svg-path: 1.1.0 + parse-svg-path: 0.1.2 + svg-arc-to-cubic-bezier: 3.2.0 + + '@react-pdf/renderer@3.4.4(react@18.2.0)': + dependencies: + '@babel/runtime': 7.24.1 + '@react-pdf/font': 2.5.1 + '@react-pdf/layout': 3.12.1 + '@react-pdf/pdfkit': 3.1.10 + '@react-pdf/primitives': 3.1.1 + '@react-pdf/render': 3.4.4 + '@react-pdf/types': 2.5.0 + events: 3.3.0 + object-assign: 4.1.1 + prop-types: 15.8.1 + queue: 6.0.2 + react: 18.2.0 + scheduler: 0.17.0 + transitivePeerDependencies: + - encoding + + '@react-pdf/stylesheet@4.2.5': + dependencies: + '@babel/runtime': 7.24.1 + '@react-pdf/fns': 2.2.1 + '@react-pdf/types': 2.5.0 + color-string: 1.9.1 + hsl-to-hex: 1.0.0 + media-engine: 1.0.3 + postcss-value-parser: 4.2.0 + + '@react-pdf/textkit@4.4.1': + dependencies: + '@babel/runtime': 7.24.1 + '@react-pdf/fns': 2.2.1 + bidi-js: 1.0.3 + hyphen: 1.10.4 + unicode-properties: 1.4.1 + + '@react-pdf/types@2.5.0': {} + '@react-stately/utils@3.10.1(react@18.2.0)': dependencies: '@swc/helpers': 0.5.5 @@ -2420,6 +3240,15 @@ snapshots: '@swc/counter@0.1.3': {} + '@swc/helpers@0.4.14': + dependencies: + tslib: 2.6.2 + + '@swc/helpers@0.4.36': + dependencies: + legacy-swc-helpers: '@swc/helpers@0.4.14' + tslib: 2.6.2 + '@swc/helpers@0.5.5': dependencies: '@swc/counter': 0.1.3 @@ -2531,6 +3360,8 @@ snapshots: '@vercel/git-hooks@1.0.0': {} + abs-svg-path@0.1.1: {} + acorn-jsx@5.3.2(acorn@8.11.3): dependencies: acorn: 8.11.3 @@ -2571,6 +3402,10 @@ snapshots: argparse@2.0.1: {} + aria-hidden@1.2.4: + dependencies: + tslib: 2.6.2 + aria-query@5.3.0: dependencies: dequal: 2.0.3 @@ -2673,6 +3508,12 @@ snapshots: balanced-match@1.0.2: {} + base64-js@1.5.1: {} + + bidi-js@1.0.3: + dependencies: + require-from-string: 2.0.2 + binary-extensions@2.3.0: {} brace-expansion@1.1.11: @@ -2688,6 +3529,14 @@ snapshots: dependencies: fill-range: 7.0.1 + brotli@1.3.3: + dependencies: + base64-js: 1.5.1 + + browserify-zlib@0.2.0: + dependencies: + pako: 1.0.11 + browserslist@4.23.0: dependencies: caniuse-lite: 1.0.30001600 @@ -2759,6 +3608,8 @@ snapshots: client-only@0.0.1: {} + clone@2.1.2: {} + clsx@2.1.0: {} color-convert@1.9.3: @@ -2773,6 +3624,11 @@ snapshots: color-name@1.1.4: {} + color-string@1.9.1: + dependencies: + color-name: 1.1.4 + simple-swizzle: 0.2.2 + colorette@2.0.20: {} commander@11.1.0: {} @@ -2785,12 +3641,20 @@ snapshots: dependencies: browserslist: 4.23.0 + cross-fetch@3.1.8: + dependencies: + node-fetch: 2.7.0 + transitivePeerDependencies: + - encoding + cross-spawn@7.0.3: dependencies: path-key: 3.1.1 shebang-command: 2.0.0 which: 2.0.2 + crypto-js@4.2.0: {} + cssesc@3.0.0: {} csstype@3.1.3: {} @@ -2839,6 +3703,10 @@ snapshots: dequal@2.0.3: {} + detect-node-es@1.1.0: {} + + dfa@1.2.0: {} + didyoumean@1.2.2: {} dir-glob@3.0.1: @@ -3195,6 +4063,8 @@ snapshots: eventemitter3@5.0.1: {} + events@3.3.0: {} + execa@8.0.1: dependencies: cross-spawn: 7.0.3 @@ -3251,6 +4121,18 @@ snapshots: flatted@3.3.1: {} + fontkit@2.0.2: + dependencies: + '@swc/helpers': 0.4.36 + brotli: 1.3.3 + clone: 2.1.2 + dfa: 1.2.0 + fast-deep-equal: 3.1.3 + restructure: 3.0.2 + tiny-inflate: 1.0.3 + unicode-properties: 1.4.1 + unicode-trie: 2.0.0 + for-each@0.3.3: dependencies: is-callable: 1.2.7 @@ -3292,6 +4174,8 @@ snapshots: has-symbols: 1.0.3 hasown: 2.0.2 + get-nonce@1.0.1: {} + get-stream@8.0.1: {} get-symbol-description@1.0.2: @@ -3378,8 +4262,16 @@ snapshots: hosted-git-info@2.8.9: {} + hsl-to-hex@1.0.0: + dependencies: + hsl-to-rgb-for-reals: 1.1.1 + + hsl-to-rgb-for-reals@1.1.1: {} + human-signals@5.0.0: {} + hyphen@1.10.4: {} + ignore@5.3.1: {} import-fresh@3.3.0: @@ -3404,6 +4296,10 @@ snapshots: hasown: 2.0.2 side-channel: 1.0.6 + invariant@2.2.4: + dependencies: + loose-envify: 1.4.0 + is-array-buffer@3.0.4: dependencies: call-bind: 1.0.7 @@ -3411,6 +4307,8 @@ snapshots: is-arrayish@0.2.1: {} + is-arrayish@0.3.2: {} + is-async-function@2.0.0: dependencies: has-tostringtag: 1.0.2 @@ -3505,6 +4403,8 @@ snapshots: dependencies: which-typed-array: 1.1.15 + is-url@1.2.4: {} + is-weakmap@2.0.2: {} is-weakref@1.0.2: @@ -3534,6 +4434,10 @@ snapshots: optionalDependencies: '@pkgjs/parseargs': 0.11.0 + jay-peg@1.0.2: + dependencies: + restructure: 3.0.2 + jiti@1.21.0: {} js-tokens@4.0.0: {} @@ -3650,6 +4554,12 @@ snapshots: dependencies: yallist: 4.0.0 + markdown-to-jsx@7.4.7(react@18.2.0): + dependencies: + react: 18.2.0 + + media-engine@1.0.3: {} + merge-stream@2.0.0: {} merge2@1.4.1: {} @@ -3718,6 +4628,10 @@ snapshots: - '@babel/core' - babel-plugin-macros + node-fetch@2.7.0: + dependencies: + whatwg-url: 5.0.0 + node-releases@2.0.14: {} normalize-package-data@2.5.0: @@ -3731,6 +4645,10 @@ snapshots: normalize-range@0.1.2: {} + normalize-svg-path@1.1.0: + dependencies: + svg-arc-to-cubic-bezier: 3.2.0 + npm-run-path@5.3.0: dependencies: path-key: 4.0.0 @@ -3820,6 +4738,10 @@ snapshots: p-try@2.2.0: {} + pako@0.2.9: {} + + pako@1.0.11: {} + parent-module@1.0.1: dependencies: callsites: 3.1.0 @@ -3831,6 +4753,8 @@ snapshots: json-parse-even-better-errors: 2.3.1 lines-and-columns: 1.2.4 + parse-svg-path@0.1.2: {} + path-exists@4.0.0: {} path-is-absolute@1.0.1: {} @@ -3928,6 +4852,10 @@ snapshots: queue-microtask@1.2.3: {} + queue@6.0.2: + dependencies: + inherits: 2.0.4 + react-dom@18.2.0(react@18.2.0): dependencies: loose-envify: 1.4.0 @@ -3940,6 +4868,34 @@ snapshots: react-is@16.13.1: {} + react-remove-scroll-bar@2.3.6(@types/react@18.2.72)(react@18.2.0): + dependencies: + react: 18.2.0 + react-style-singleton: 2.2.1(@types/react@18.2.72)(react@18.2.0) + tslib: 2.6.2 + optionalDependencies: + '@types/react': 18.2.72 + + react-remove-scroll@2.5.7(@types/react@18.2.72)(react@18.2.0): + dependencies: + react: 18.2.0 + react-remove-scroll-bar: 2.3.6(@types/react@18.2.72)(react@18.2.0) + react-style-singleton: 2.2.1(@types/react@18.2.72)(react@18.2.0) + tslib: 2.6.2 + use-callback-ref: 1.3.2(@types/react@18.2.72)(react@18.2.0) + use-sidecar: 1.1.2(@types/react@18.2.72)(react@18.2.0) + optionalDependencies: + '@types/react': 18.2.72 + + react-style-singleton@2.2.1(@types/react@18.2.72)(react@18.2.0): + dependencies: + get-nonce: 1.0.1 + invariant: 2.2.4 + react: 18.2.0 + tslib: 2.6.2 + optionalDependencies: + '@types/react': 18.2.72 + react-tooltip@5.26.3(react-dom@18.2.0(react@18.2.0))(react@18.2.0): dependencies: '@floating-ui/dom': 1.6.3 @@ -3997,6 +4953,8 @@ snapshots: dependencies: jsesc: 0.5.0 + require-from-string@2.0.2: {} + resolve-from@4.0.0: {} resolve-pkg-maps@1.0.0: {} @@ -4018,6 +4976,8 @@ snapshots: onetime: 5.1.2 signal-exit: 3.0.7 + restructure@3.0.2: {} + reusify@1.0.4: {} rfdc@1.3.1: {} @@ -4037,12 +4997,19 @@ snapshots: has-symbols: 1.0.3 isarray: 2.0.5 + safe-buffer@5.2.1: {} + safe-regex-test@1.0.3: dependencies: call-bind: 1.0.7 es-errors: 1.3.0 is-regex: 1.1.4 + scheduler@0.17.0: + dependencies: + loose-envify: 1.4.0 + object-assign: 4.1.1 + scheduler@0.23.0: dependencies: loose-envify: 1.4.0 @@ -4088,6 +5055,10 @@ snapshots: signal-exit@4.1.0: {} + simple-swizzle@0.2.2: + dependencies: + is-arrayish: 0.3.2 + slash@3.0.0: {} slice-ansi@5.0.0: @@ -4172,6 +5143,10 @@ snapshots: define-properties: 1.2.1 es-object-atoms: 1.0.0 + string_decoder@1.3.0: + dependencies: + safe-buffer: 5.2.1 + strip-ansi@6.0.1: dependencies: ansi-regex: 5.0.1 @@ -4215,6 +5190,8 @@ snapshots: supports-preserve-symlinks-flag@1.0.0: {} + svg-arc-to-cubic-bezier@3.2.0: {} + tabbable@6.2.0: {} tailwind-merge@2.2.2: @@ -4265,10 +5242,14 @@ snapshots: dependencies: any-promise: 1.3.0 + tiny-inflate@1.0.3: {} + to-regex-range@5.0.1: dependencies: is-number: 7.0.0 + tr46@0.0.3: {} + ts-api-utils@1.3.0(typescript@5.4.3): dependencies: typescript: 5.4.3 @@ -4337,6 +5318,16 @@ snapshots: undici-types@5.26.5: {} + unicode-properties@1.4.1: + dependencies: + base64-js: 1.5.1 + unicode-trie: 2.0.0 + + unicode-trie@2.0.0: + dependencies: + pako: 0.2.9 + tiny-inflate: 1.0.3 + update-browserslist-db@1.0.13(browserslist@4.23.0): dependencies: browserslist: 4.23.0 @@ -4347,6 +5338,21 @@ snapshots: dependencies: punycode: 2.3.1 + use-callback-ref@1.3.2(@types/react@18.2.72)(react@18.2.0): + dependencies: + react: 18.2.0 + tslib: 2.6.2 + optionalDependencies: + '@types/react': 18.2.72 + + use-sidecar@1.1.2(@types/react@18.2.72)(react@18.2.0): + dependencies: + detect-node-es: 1.1.0 + react: 18.2.0 + tslib: 2.6.2 + optionalDependencies: + '@types/react': 18.2.72 + util-deprecate@1.0.2: {} validate-npm-package-license@3.0.4: @@ -4354,6 +5360,19 @@ snapshots: spdx-correct: 3.2.0 spdx-expression-parse: 3.0.1 + vite-compatible-readable-stream@3.6.1: + dependencies: + inherits: 2.0.4 + string_decoder: 1.3.0 + util-deprecate: 1.0.2 + + webidl-conversions@3.0.1: {} + + whatwg-url@5.0.0: + dependencies: + tr46: 0.0.3 + webidl-conversions: 3.0.1 + which-boxed-primitive@1.0.2: dependencies: is-bigint: 1.0.4 @@ -4424,4 +5443,6 @@ snapshots: yocto-queue@0.1.0: {} + yoga-layout@2.0.1: {} + zod@3.23.8: {} From 64a666f34ab589b902b922456c63ddd1532226c8 Mon Sep 17 00:00:00 2001 From: tedraykov Date: Mon, 1 Jul 2024 22:42:02 +0300 Subject: [PATCH 2/3] polish order confirmation in orders list and order details pages --- app/account/(orders)/page.tsx | 2 +- components/filters/hompage-filters.tsx | 8 +- components/orders/actions.ts | 95 ------------------- components/orders/actions.tsx | 86 +++++++++++++---- components/orders/activate-warranty-modal.tsx | 24 ++--- components/orders/activate-warranty.tsx | 12 +-- components/orders/mobile-order-actions.tsx | 8 +- .../orders/order-confirmation-modal.tsx | 23 +++-- components/ui/button.tsx | 25 ++++- components/ui/checkbox.tsx | 2 +- components/ui/index.ts | 1 - components/ui/input-label.tsx | 5 +- components/ui/input.tsx | 4 +- lib/shopify/fragments/order-card.ts | 0 lib/shopify/index.ts | 70 ++++++++++---- lib/shopify/queries/metaobject.ts | 8 +- lib/shopify/types.ts | 34 +++---- lib/utils.ts | 7 +- 18 files changed, 206 insertions(+), 208 deletions(-) delete mode 100644 components/orders/actions.ts create mode 100644 lib/shopify/fragments/order-card.ts 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; }; From 02746834ecede2d845179e6d99e45c8c3b888165 Mon Sep 17 00:00:00 2001 From: tedraykov Date: Mon, 1 Jul 2024 22:52:39 +0300 Subject: [PATCH 3/3] dynamically import warranty and order confirmation in orders page --- app/account/(orders)/page.tsx | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/app/account/(orders)/page.tsx b/app/account/(orders)/page.tsx index 32ac1f27b..3e833f54f 100644 --- a/app/account/(orders)/page.tsx +++ b/app/account/(orders)/page.tsx @@ -1,6 +1,4 @@ import { InformationCircleIcon } from '@heroicons/react/24/outline'; -import OrderConfirmation from 'components/orders/order-confirmation'; -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'; @@ -9,6 +7,10 @@ import { isBeforeToday, toPrintDate } from 'lib/utils'; import Image from 'next/image'; import Link from 'next/link'; import { Button } from 'components/ui'; +import dynamic from 'next/dynamic'; + +const OrderConfirmation = dynamic(() => import('components/orders/order-confirmation')); +const ActivateWarranty = dynamic(() => import('components/orders/activate-warranty')); export default async function AccountPage() { const orders = await getCustomerOrders();