mirror of
https://github.com/vercel/commerce.git
synced 2025-05-12 12:47:50 +00:00
Merge branch 'main' of github.com:Car-Part-Planet/storefront
This commit is contained in:
commit
d12c264a1c
@ -14,6 +14,9 @@ import { toPrintDate } from 'lib/utils';
|
|||||||
import Image from 'next/image';
|
import Image from 'next/image';
|
||||||
import Link from 'next/link';
|
import Link from 'next/link';
|
||||||
import ActivateWarranty from 'components/orders/activate-warranty';
|
import ActivateWarranty from 'components/orders/activate-warranty';
|
||||||
|
import OrderStatuses from 'components/orders/order-statuses';
|
||||||
|
import Divider from 'components/divider';
|
||||||
|
import { CoreReturn } from 'components/orders/core-return';
|
||||||
|
|
||||||
function Unfulfilled({ order }: { order: Order }) {
|
function Unfulfilled({ order }: { order: Order }) {
|
||||||
// Build a map of line item IDs to quantities fulfilled
|
// Build a map of line item IDs to quantities fulfilled
|
||||||
@ -222,10 +225,13 @@ export default async function OrderPage({ params }: { params: { id: string } })
|
|||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div className="flex items-start gap-2">
|
<div className="flex items-start gap-2">
|
||||||
|
<OrderStatuses order={order} className="hidden flex-wrap gap-2 lg:flex" />
|
||||||
<OrderConfirmation order={order} />
|
<OrderConfirmation order={order} />
|
||||||
<ActivateWarranty order={order} />
|
<ActivateWarranty order={order} />
|
||||||
|
<CoreReturn order={order} />
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
<OrderStatuses order={order} className="my-6 flex flex-wrap gap-2 lg:hidden" />
|
||||||
<div className="flex items-start gap-6">
|
<div className="flex items-start gap-6">
|
||||||
<div className="flex flex-1 flex-col gap-6">
|
<div className="flex flex-1 flex-col gap-6">
|
||||||
<Fulfillments order={order} />
|
<Fulfillments order={order} />
|
||||||
|
@ -1,7 +1,9 @@
|
|||||||
import { InformationCircleIcon } from '@heroicons/react/24/outline';
|
import { InformationCircleIcon } from '@heroicons/react/24/outline';
|
||||||
|
import Divider from 'components/divider';
|
||||||
import ActivateWarranty from 'components/orders/activate-warranty';
|
import ActivateWarranty from 'components/orders/activate-warranty';
|
||||||
import MobileOrderActions from 'components/orders/mobile-order-actions';
|
import MobileOrderActions from 'components/orders/mobile-order-actions';
|
||||||
import OrderConfirmation from 'components/orders/order-confirmation';
|
import OrderConfirmation from 'components/orders/order-confirmation';
|
||||||
|
import OrderStatuses from 'components/orders/order-statuses';
|
||||||
import OrdersHeader from 'components/orders/orders-header';
|
import OrdersHeader from 'components/orders/orders-header';
|
||||||
import Price from 'components/price';
|
import Price from 'components/price';
|
||||||
import { Button } from 'components/ui';
|
import { Button } from 'components/ui';
|
||||||
@ -28,7 +30,7 @@ export default async function AccountPage() {
|
|||||||
<h3 className="sr-only">
|
<h3 className="sr-only">
|
||||||
Order placed on <time dateTime={order.createdAt}>{order.createdAt}</time>
|
Order placed on <time dateTime={order.createdAt}>{order.createdAt}</time>
|
||||||
</h3>
|
</h3>
|
||||||
<div className="flex items-center border-b border-gray-200 p-4 sm:grid sm:grid-cols-4 sm:gap-x-6 sm:p-6">
|
<div className="flex items-center p-4 sm:grid sm:grid-cols-4 sm:gap-x-6 sm:p-6">
|
||||||
<dl className="grid flex-1 grid-cols-2 gap-x-6 text-sm sm:col-span-3 sm:grid-cols-3 lg:col-span-2">
|
<dl className="grid flex-1 grid-cols-2 gap-x-6 text-sm sm:col-span-3 sm:grid-cols-3 lg:col-span-2">
|
||||||
<div>
|
<div>
|
||||||
<dt className="font-medium text-gray-900">Order</dt>
|
<dt className="font-medium text-gray-900">Order</dt>
|
||||||
@ -66,6 +68,10 @@ export default async function AccountPage() {
|
|||||||
<OrderConfirmation order={order} />
|
<OrderConfirmation order={order} />
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
<div className="p-4 pt-0 sm:p-6 sm:pt-0">
|
||||||
|
<OrderStatuses order={order} className="flex flex-wrap gap-4" />
|
||||||
|
</div>
|
||||||
|
<Divider hasSpacing={false} />
|
||||||
|
|
||||||
<h4 className="sr-only">Items</h4>
|
<h4 className="sr-only">Items</h4>
|
||||||
<ul role="list" className="divide-y divide-gray-200">
|
<ul role="list" className="divide-y divide-gray-200">
|
||||||
|
@ -12,7 +12,7 @@ const divider = tv({
|
|||||||
element: 'w-full h-[1px] '
|
element: 'w-full h-[1px] '
|
||||||
},
|
},
|
||||||
vertical: {
|
vertical: {
|
||||||
root: 'flex justify-between items-stretch text-tremor-default text-tremor-content',
|
root: 'flex justify-between items-stretch text-tremor-default text-tremor-content h-full',
|
||||||
element: 'h-full w-[1px]'
|
element: 'h-full w-[1px]'
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
@ -42,12 +42,17 @@ const divider = tv({
|
|||||||
type DividerProps = {
|
type DividerProps = {
|
||||||
orientation?: 'horizontal' | 'vertical';
|
orientation?: 'horizontal' | 'vertical';
|
||||||
hasSpacing?: boolean;
|
hasSpacing?: boolean;
|
||||||
|
className?: string;
|
||||||
};
|
};
|
||||||
export default function Divider({ orientation = 'horizontal', hasSpacing = true }: DividerProps) {
|
export default function Divider({
|
||||||
|
orientation = 'horizontal',
|
||||||
|
hasSpacing = true,
|
||||||
|
className
|
||||||
|
}: DividerProps) {
|
||||||
const { root, element } = divider({ orientation, hasSpacing });
|
const { root, element } = divider({ orientation, hasSpacing });
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className={root()}>
|
<div className={root({ className })}>
|
||||||
<span className={element()} />
|
<span className={element()} />
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
|
18
components/form/combobox-button.tsx
Normal file
18
components/form/combobox-button.tsx
Normal file
@ -0,0 +1,18 @@
|
|||||||
|
import { ComboboxButtonProps, ComboboxButton as HeadlessComboboxButton } from '@headlessui/react';
|
||||||
|
import { ChevronDownIcon } from '@heroicons/react/16/solid';
|
||||||
|
import clsx from 'clsx';
|
||||||
|
|
||||||
|
export default function ComboboxButton({ className, ...props }: ComboboxButtonProps) {
|
||||||
|
return (
|
||||||
|
<HeadlessComboboxButton
|
||||||
|
className={clsx(
|
||||||
|
'group absolute inset-y-0 right-0 px-2.5',
|
||||||
|
'data-[disabled]:cursor-not-allowed data-[disabled]:opacity-50',
|
||||||
|
className
|
||||||
|
)}
|
||||||
|
{...props}
|
||||||
|
>
|
||||||
|
<ChevronDownIcon className="fill-black/60 group-data-[hover]:fill-black size-5" />
|
||||||
|
</HeadlessComboboxButton>
|
||||||
|
);
|
||||||
|
}
|
21
components/form/combobox-input.tsx
Normal file
21
components/form/combobox-input.tsx
Normal file
@ -0,0 +1,21 @@
|
|||||||
|
import { ComboboxInputProps } from '@headlessui/react';
|
||||||
|
import clsx from 'clsx';
|
||||||
|
|
||||||
|
export default function ComboboxInput({ className, ...props }: ComboboxInputProps) {
|
||||||
|
return (
|
||||||
|
<ComboboxInput
|
||||||
|
className={clsx(
|
||||||
|
'w-full rounded border border-gray-200',
|
||||||
|
'py-1.5 pl-3 pr-8 text-sm',
|
||||||
|
'ring-2 ring-transparent',
|
||||||
|
'focus:outline-none focus-visible:outline-none',
|
||||||
|
'data-[disabled]:cursor-not-allowed data-[autofocus]:border-0',
|
||||||
|
'data-[focus]:border-transparent data-[disabled]:opacity-50',
|
||||||
|
'data-[focus]:ring-2 data-[autofocus]:ring-secondary',
|
||||||
|
'data-[focus]:ring-secondary data-[focus]:ring-offset-0',
|
||||||
|
className
|
||||||
|
)}
|
||||||
|
{...props}
|
||||||
|
/>
|
||||||
|
);
|
||||||
|
}
|
16
components/form/combobox-option.tsx
Normal file
16
components/form/combobox-option.tsx
Normal file
@ -0,0 +1,16 @@
|
|||||||
|
import { ComboboxOption as HeadlessCombobox, ComboboxOptionProps } from '@headlessui/react';
|
||||||
|
import clsx from 'clsx';
|
||||||
|
|
||||||
|
export default function ComboboxOption({ className, ...props }: ComboboxOptionProps) {
|
||||||
|
return (
|
||||||
|
<HeadlessCombobox
|
||||||
|
className={clsx(
|
||||||
|
'flex cursor-default select-none items-center gap-2',
|
||||||
|
'rounded-lg px-3 py-1.5 text-sm/6',
|
||||||
|
'data-[focus]:bg-secondary/10',
|
||||||
|
className
|
||||||
|
)}
|
||||||
|
{...props}
|
||||||
|
/>
|
||||||
|
);
|
||||||
|
}
|
18
components/form/combobox-options.tsx
Normal file
18
components/form/combobox-options.tsx
Normal file
@ -0,0 +1,18 @@
|
|||||||
|
import {
|
||||||
|
ComboboxOptionsProps,
|
||||||
|
ComboboxOptions as HeadlessComboboxOptions
|
||||||
|
} from '@headlessui/react';
|
||||||
|
import clsx from 'clsx';
|
||||||
|
|
||||||
|
export default function ComboboxOption({ className, ...props }: ComboboxOptionsProps) {
|
||||||
|
return (
|
||||||
|
<HeadlessComboboxOptions
|
||||||
|
className={clsx(
|
||||||
|
'z-10 w-[var(--input-width)] rounded-xl',
|
||||||
|
'border border-gray-200 bg-white p-1 [--anchor-gap:6px] empty:hidden',
|
||||||
|
className
|
||||||
|
)}
|
||||||
|
{...props}
|
||||||
|
/>
|
||||||
|
);
|
||||||
|
}
|
@ -164,3 +164,63 @@ export const confirmOrder = async ({ order, content, formData }: ConfirmOrderOpt
|
|||||||
console.log('activateWarranty action', error);
|
console.log('activateWarranty action', error);
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
|
export async function returnCore() {
|
||||||
|
// const rawFormData = [
|
||||||
|
// getMetafieldValue(
|
||||||
|
// 'coreReturnZip',
|
||||||
|
// {
|
||||||
|
// key: '',
|
||||||
|
// value: formData.get('name') as string | null,
|
||||||
|
// 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: order.id,
|
||||||
|
// metafields: rawFormData
|
||||||
|
// });
|
||||||
|
|
||||||
|
revalidateTag(TAGS.orderMetafields);
|
||||||
|
} catch (error) {
|
||||||
|
console.log('activateWarranty action', error);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
@ -4,7 +4,6 @@ import { Order, WarrantyStatus } from 'lib/shopify/types';
|
|||||||
import { isBeforeToday } from 'lib/utils';
|
import { isBeforeToday } from 'lib/utils';
|
||||||
import { useState } from 'react';
|
import { useState } from 'react';
|
||||||
import ActivateWarrantyModal from './activate-warranty-modal';
|
import ActivateWarrantyModal from './activate-warranty-modal';
|
||||||
import WarrantyActivatedBadge from './warranty-activated-badge';
|
|
||||||
import { Button } from 'components/ui';
|
import { Button } from 'components/ui';
|
||||||
|
|
||||||
type ActivateWarrantyModalProps = {
|
type ActivateWarrantyModalProps = {
|
||||||
@ -13,7 +12,7 @@ type ActivateWarrantyModalProps = {
|
|||||||
|
|
||||||
const ActivateWarranty = ({ order }: ActivateWarrantyModalProps) => {
|
const ActivateWarranty = ({ order }: ActivateWarrantyModalProps) => {
|
||||||
const [isOpen, setIsOpen] = useState(false);
|
const [isOpen, setIsOpen] = useState(false);
|
||||||
const isWarrantyActivated = order?.warrantyStatus?.value === WarrantyStatus.Activated;
|
const isActivated = order?.warrantyStatus?.value === WarrantyStatus.Activated;
|
||||||
const isPassDeadline = isBeforeToday(order?.warrantyActivationDeadline?.value);
|
const isPassDeadline = isBeforeToday(order?.warrantyActivationDeadline?.value);
|
||||||
const isOrderConfirmed = order?.orderConfirmation?.value;
|
const isOrderConfirmed = order?.orderConfirmation?.value;
|
||||||
|
|
||||||
@ -21,11 +20,7 @@ const ActivateWarranty = ({ order }: ActivateWarrantyModalProps) => {
|
|||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (isWarrantyActivated) {
|
if (isPassDeadline || isActivated) {
|
||||||
return <WarrantyActivatedBadge />;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (isPassDeadline) {
|
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
94
components/orders/core-return-modal.tsx
Normal file
94
components/orders/core-return-modal.tsx
Normal file
@ -0,0 +1,94 @@
|
|||||||
|
import { Dialog, DialogBackdrop, DialogPanel, Fieldset, Legend } from '@headlessui/react';
|
||||||
|
import { Order } from 'lib/shopify/types';
|
||||||
|
import { Button, Heading, Input } from 'components/ui';
|
||||||
|
import StatesCombobox from 'components/states-combobox';
|
||||||
|
import { useTransition } from 'react';
|
||||||
|
// import { returnCore } from './actions';
|
||||||
|
|
||||||
|
export function CoreReturnModal({
|
||||||
|
isOpen,
|
||||||
|
onClose,
|
||||||
|
order
|
||||||
|
}: {
|
||||||
|
isOpen: boolean;
|
||||||
|
onClose: () => void;
|
||||||
|
order: Order;
|
||||||
|
}) {
|
||||||
|
const [submitting, startTransition] = useTransition();
|
||||||
|
|
||||||
|
async function submitCoreReturn(formData: FormData) {
|
||||||
|
startTransition(async () => {
|
||||||
|
// returnCore(order, formData);
|
||||||
|
console.log(formData);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
return (
|
||||||
|
<Dialog open={isOpen} onClose={onClose} className="relative z-50">
|
||||||
|
<DialogBackdrop
|
||||||
|
transition
|
||||||
|
className="bg-black/30 fixed inset-0 duration-300 ease-out data-[closed]:opacity-0"
|
||||||
|
/>
|
||||||
|
<div className="fixed inset-0 w-screen overflow-y-auto p-4">
|
||||||
|
<div className="flex min-h-full items-center justify-center">
|
||||||
|
<DialogPanel
|
||||||
|
transition
|
||||||
|
className="w-full max-w-xl space-y-4 rounded bg-white p-5 duration-300 ease-out data-[closed]:scale-95 data-[closed]:opacity-0"
|
||||||
|
>
|
||||||
|
<div className="flex justify-between">
|
||||||
|
<Heading>Core Return</Heading>
|
||||||
|
<Heading size="sm" className="text-content">
|
||||||
|
Order {order.name}
|
||||||
|
</Heading>
|
||||||
|
</div>
|
||||||
|
<form action={submitCoreReturn} className="flex flex-col gap-4">
|
||||||
|
<Fieldset className="grid grid-cols-2 gap-4" disabled={submitting}>
|
||||||
|
<Heading as={Legend} size="sm" className="col-span-2">
|
||||||
|
Core Pickup Address
|
||||||
|
</Heading>
|
||||||
|
<Input name="name" label="Name" required className="col-span-2" />
|
||||||
|
<Input
|
||||||
|
defaultValue={order.customer?.emailAddress}
|
||||||
|
label="Email"
|
||||||
|
name="email"
|
||||||
|
type="email"
|
||||||
|
required
|
||||||
|
/>
|
||||||
|
<Input
|
||||||
|
defaultValue={order.shippingAddress.phone}
|
||||||
|
label="Phone"
|
||||||
|
name="phone"
|
||||||
|
type="tel"
|
||||||
|
required
|
||||||
|
/>
|
||||||
|
<Input
|
||||||
|
defaultValue={order.shippingAddress.address1}
|
||||||
|
label="Address"
|
||||||
|
name="address"
|
||||||
|
required
|
||||||
|
className="col-span-2"
|
||||||
|
/>
|
||||||
|
<Input
|
||||||
|
defaultValue={order.shippingAddress.city}
|
||||||
|
label="City"
|
||||||
|
name="city"
|
||||||
|
required
|
||||||
|
/>
|
||||||
|
<Input defaultValue={order.shippingAddress.zip} label="Zip" name="zip" required />
|
||||||
|
<StatesCombobox defaultStateCode={order.shippingAddress.provinceCode} />
|
||||||
|
</Fieldset>
|
||||||
|
<div className="flex justify-end gap-2">
|
||||||
|
<Button variant="text" onClick={onClose}>
|
||||||
|
Close
|
||||||
|
</Button>
|
||||||
|
<Button color="primary" variant="solid" disabled={submitting}>
|
||||||
|
Submit
|
||||||
|
</Button>
|
||||||
|
</div>
|
||||||
|
</form>
|
||||||
|
</DialogPanel>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</Dialog>
|
||||||
|
);
|
||||||
|
}
|
8
components/orders/core-return-status.tsx
Normal file
8
components/orders/core-return-status.tsx
Normal file
@ -0,0 +1,8 @@
|
|||||||
|
import { Chip } from 'components/ui';
|
||||||
|
import { Order } from 'lib/shopify/types';
|
||||||
|
|
||||||
|
export default function CoreReturnStatus({ order }: { order: Order }) {
|
||||||
|
if (!order.coreReturnStatus?.value) return null;
|
||||||
|
|
||||||
|
return <Chip level="warn">Core Return: {order.coreReturnStatus.value}</Chip>;
|
||||||
|
}
|
15
components/orders/core-return.tsx
Normal file
15
components/orders/core-return.tsx
Normal file
@ -0,0 +1,15 @@
|
|||||||
|
'use client';
|
||||||
|
import { Button } from 'components/ui';
|
||||||
|
import { Order } from 'lib/shopify/types';
|
||||||
|
import { useState } from 'react';
|
||||||
|
import { CoreReturnModal } from './core-return-modal';
|
||||||
|
|
||||||
|
export function CoreReturn({ order }: { order: Order }) {
|
||||||
|
const [isOpen, setIsOpen] = useState(false);
|
||||||
|
return (
|
||||||
|
<>
|
||||||
|
<Button onClick={() => setIsOpen(true)}>Core Return</Button>
|
||||||
|
<CoreReturnModal order={order} isOpen={isOpen} onClose={() => setIsOpen(false)} />
|
||||||
|
</>
|
||||||
|
);
|
||||||
|
}
|
@ -8,7 +8,7 @@ import Markdown from 'markdown-to-jsx';
|
|||||||
import { Order, OrderConfirmationContent } from 'lib/shopify/types';
|
import { Order, OrderConfirmationContent } from 'lib/shopify/types';
|
||||||
import { FormEventHandler, useEffect, useRef, useState, useTransition } from 'react';
|
import { FormEventHandler, useEffect, useRef, useState, useTransition } from 'react';
|
||||||
import { confirmOrder, fetchOrderConfirmationContent } from 'components/orders/actions';
|
import { confirmOrder, fetchOrderConfirmationContent } from 'components/orders/actions';
|
||||||
import { Button, Heading, Text, Label, Input, Skeleton } from 'components/ui';
|
import { Button, Heading, Text, Label, Input } from 'components/ui';
|
||||||
import LoadingDots from 'components/loading-dots';
|
import LoadingDots from 'components/loading-dots';
|
||||||
|
|
||||||
function OrderConfirmationDetails({
|
function OrderConfirmationDetails({
|
||||||
@ -228,7 +228,7 @@ export default function OrderConfirmationModal({
|
|||||||
<Dialog open={isOpen} onClose={onClose} className="relative z-50">
|
<Dialog open={isOpen} onClose={onClose} className="relative z-50">
|
||||||
<DialogBackdrop
|
<DialogBackdrop
|
||||||
transition
|
transition
|
||||||
className="fixed inset-0 bg-black/30 duration-300 ease-out data-[closed]:opacity-0"
|
className="bg-black/30 fixed inset-0 duration-300 ease-out data-[closed]:opacity-0"
|
||||||
/>
|
/>
|
||||||
<div className="fixed inset-0 w-screen overflow-y-auto p-4">
|
<div className="fixed inset-0 w-screen overflow-y-auto p-4">
|
||||||
<div className="flex min-h-full items-center justify-center">
|
<div className="flex min-h-full items-center justify-center">
|
||||||
|
@ -156,7 +156,9 @@ export default function OrderConfirmationPdf({
|
|||||||
<Text style={styles.label}>Payment</Text>
|
<Text style={styles.label}>Payment</Text>
|
||||||
<View>
|
<View>
|
||||||
<Text style={styles.p}>
|
<Text style={styles.p}>
|
||||||
Ending with {order.transactions[0]!.paymentDetails.last4} -
|
{order.transactions[0]?.paymentDetails
|
||||||
|
? `Ending with ${order.transactions[0]!.paymentDetails.last4} - `
|
||||||
|
: 'Manual - '}
|
||||||
<PDFPrice
|
<PDFPrice
|
||||||
amount={order.transactions[0]!.transactionAmount.amount}
|
amount={order.transactions[0]!.transactionAmount.amount}
|
||||||
currencyCode={order.transactions[0]!.transactionAmount.currencyCode}
|
currencyCode={order.transactions[0]!.transactionAmount.currencyCode}
|
||||||
|
8
components/orders/order-confirmation-status.tsx
Normal file
8
components/orders/order-confirmation-status.tsx
Normal file
@ -0,0 +1,8 @@
|
|||||||
|
import { Chip } from 'components/ui';
|
||||||
|
import { Order } from 'lib/shopify/types';
|
||||||
|
|
||||||
|
export default function OrderConfirmedStatus({ order }: { order: Order }) {
|
||||||
|
if (order.orderConfirmation?.value) return null;
|
||||||
|
|
||||||
|
return <Chip level="error">Order Not Confirmed</Chip>;
|
||||||
|
}
|
14
components/orders/order-statuses.tsx
Normal file
14
components/orders/order-statuses.tsx
Normal file
@ -0,0 +1,14 @@
|
|||||||
|
import { Order } from 'lib/shopify/types';
|
||||||
|
import CoreReturnStatus from './core-return-status';
|
||||||
|
import WarrantyActivatedStatus from './warranty-activated-status';
|
||||||
|
import OrderConfirmedStatus from './order-confirmation-status';
|
||||||
|
|
||||||
|
export default function OrderStatuses({ order, className }: { order: Order; className?: string }) {
|
||||||
|
return (
|
||||||
|
<div className={className}>
|
||||||
|
<OrderConfirmedStatus order={order} />
|
||||||
|
<CoreReturnStatus order={order} />
|
||||||
|
<WarrantyActivatedStatus order={order} />
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
}
|
@ -20,7 +20,9 @@ export default function PaymentsDetails({ order, hideIcon }: { order: Order; hid
|
|||||||
|
|
||||||
<div>
|
<div>
|
||||||
<Text>
|
<Text>
|
||||||
Ending with {transaction.paymentDetails.last4} -
|
{transaction?.paymentDetails
|
||||||
|
? `Ending with ${transaction.paymentDetails.last4} - `
|
||||||
|
: 'Manual - '}
|
||||||
<Price
|
<Price
|
||||||
as="span"
|
as="span"
|
||||||
amount={transaction.transactionAmount.amount}
|
amount={transaction.transactionAmount.amount}
|
||||||
|
@ -1,12 +0,0 @@
|
|||||||
import { CheckCircleIcon } from '@heroicons/react/24/solid';
|
|
||||||
|
|
||||||
const WarrantyActivatedBadge = () => {
|
|
||||||
return (
|
|
||||||
<span className="inline-flex h-fit items-center gap-x-2 rounded-md bg-green-50 px-2.5 py-2 text-sm font-medium text-green-700 ring-1 ring-inset ring-green-600/20">
|
|
||||||
<CheckCircleIcon className="h-5 w-5 text-green-500" aria-hidden="true" />
|
|
||||||
Warranty Activated
|
|
||||||
</span>
|
|
||||||
);
|
|
||||||
};
|
|
||||||
|
|
||||||
export default WarrantyActivatedBadge;
|
|
21
components/orders/warranty-activated-status.tsx
Normal file
21
components/orders/warranty-activated-status.tsx
Normal file
@ -0,0 +1,21 @@
|
|||||||
|
import Chip, { ChipProps } from 'components/ui/chip';
|
||||||
|
import { Order, WarrantyStatus } from 'lib/shopify/types';
|
||||||
|
|
||||||
|
const WarrantyActivatedStatus = ({ order }: { order: Order }) => {
|
||||||
|
const warrantyStatus = order?.warrantyStatus?.value;
|
||||||
|
const isOrderConfirmed = order?.orderConfirmation?.value;
|
||||||
|
|
||||||
|
if (!isOrderConfirmed || !warrantyStatus) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
let level: ChipProps['level'] = 'success';
|
||||||
|
|
||||||
|
if (warrantyStatus === WarrantyStatus.NotActivated) {
|
||||||
|
level = 'warn';
|
||||||
|
}
|
||||||
|
|
||||||
|
return <Chip level={level}>Warranty: {warrantyStatus}</Chip>;
|
||||||
|
};
|
||||||
|
|
||||||
|
export default WarrantyActivatedStatus;
|
73
components/states-combobox.tsx
Normal file
73
components/states-combobox.tsx
Normal file
@ -0,0 +1,73 @@
|
|||||||
|
import Combobox from './ui/combobox';
|
||||||
|
|
||||||
|
const states = [
|
||||||
|
{ name: 'Alabama', code: 'AL' },
|
||||||
|
{ name: 'Alaska', code: 'AK' },
|
||||||
|
{ name: 'Arizona', code: 'AZ' },
|
||||||
|
{ name: 'Arkansas', code: 'AR' },
|
||||||
|
{ name: 'California', code: 'CA' },
|
||||||
|
{ name: 'Colorado', code: 'CO' },
|
||||||
|
{ name: 'Connecticut', code: 'CT' },
|
||||||
|
{ name: 'Delaware', code: 'DE' },
|
||||||
|
{ name: 'Florida', code: 'FL' },
|
||||||
|
{ name: 'Georgia', code: 'GA' },
|
||||||
|
{ name: 'Hawaii', code: 'HI' },
|
||||||
|
{ name: 'Idaho', code: 'ID' },
|
||||||
|
{ name: 'Illinois', code: 'IL' },
|
||||||
|
{ name: 'Indiana', code: 'IN' },
|
||||||
|
{ name: 'Iowa', code: 'IA' },
|
||||||
|
{ name: 'Kansas', code: 'KS' },
|
||||||
|
{ name: 'Kentucky', code: 'KY' },
|
||||||
|
{ name: 'Louisiana', code: 'LA' },
|
||||||
|
{ name: 'Maine', code: 'ME' },
|
||||||
|
{ name: 'Maryland', code: 'MD' },
|
||||||
|
{ name: 'Massachusetts', code: 'MA' },
|
||||||
|
{ name: 'Michigan', code: 'MI' },
|
||||||
|
{ name: 'Minnesota', code: 'MN' },
|
||||||
|
{ name: 'Mississippi', code: 'MS' },
|
||||||
|
{ name: 'Missouri', code: 'MO' },
|
||||||
|
{ name: 'Montana', code: 'MT' },
|
||||||
|
{ name: 'Nebraska', code: 'NE' },
|
||||||
|
{ name: 'Nevada', code: 'NV' },
|
||||||
|
{ name: 'New Hampshire', code: 'NH' },
|
||||||
|
{ name: 'New Jersey', code: 'NJ' },
|
||||||
|
{ name: 'New Mexico', code: 'NM' },
|
||||||
|
{ name: 'New York', code: 'NY' },
|
||||||
|
{ name: 'North Carolina', code: 'NC' },
|
||||||
|
{ name: 'North Dakota', code: 'ND' },
|
||||||
|
{ name: 'Ohio', code: 'OH' },
|
||||||
|
{ name: 'Oklahoma', code: 'OK' },
|
||||||
|
{ name: 'Oregon', code: 'OR' },
|
||||||
|
{ name: 'Pennsylvania', code: 'PA' },
|
||||||
|
{ name: 'Rhode Island', code: 'RI' },
|
||||||
|
{ name: 'South Carolina', code: 'SC' },
|
||||||
|
{ name: 'South Dakota', code: 'SD' },
|
||||||
|
{ name: 'Tennessee', code: 'TN' },
|
||||||
|
{ name: 'Texas', code: 'TX' },
|
||||||
|
{ name: 'Utah', code: 'UT' },
|
||||||
|
{ name: 'Vermont', code: 'VT' },
|
||||||
|
{ name: 'Virginia', code: 'VA' },
|
||||||
|
{ name: 'Washington', code: 'WA' },
|
||||||
|
{ name: 'West Virginia', code: 'WV' },
|
||||||
|
{ name: 'Wisconsin', code: 'WI' },
|
||||||
|
{ name: 'Wyoming', code: 'WY' }
|
||||||
|
];
|
||||||
|
|
||||||
|
function findState(code: string) {
|
||||||
|
return states.find((state) => state.code === code);
|
||||||
|
}
|
||||||
|
|
||||||
|
export default function StatesCombobox({ defaultStateCode }: { defaultStateCode: string }) {
|
||||||
|
return (
|
||||||
|
<Combobox
|
||||||
|
defaultValue={findState(defaultStateCode)}
|
||||||
|
label="State"
|
||||||
|
name="state"
|
||||||
|
required
|
||||||
|
options={states}
|
||||||
|
displayKey="name"
|
||||||
|
by="code"
|
||||||
|
className="col-span-2"
|
||||||
|
/>
|
||||||
|
);
|
||||||
|
}
|
@ -7,7 +7,7 @@ const cardStyles = tv({
|
|||||||
base: 'rounded p-6 text-left w-full',
|
base: 'rounded p-6 text-left w-full',
|
||||||
variants: {
|
variants: {
|
||||||
outlined: {
|
outlined: {
|
||||||
true: 'border bg-white',
|
true: 'border bg-white shadow-sm',
|
||||||
false: ''
|
false: ''
|
||||||
},
|
},
|
||||||
elevated: {
|
elevated: {
|
||||||
|
63
components/ui/chip.tsx
Normal file
63
components/ui/chip.tsx
Normal file
@ -0,0 +1,63 @@
|
|||||||
|
import { CheckCircleIcon, ExclamationCircleIcon, XCircleIcon } from '@heroicons/react/24/solid';
|
||||||
|
import clsx from 'clsx';
|
||||||
|
import { VariantProps, tv } from 'tailwind-variants';
|
||||||
|
|
||||||
|
const chip = tv({
|
||||||
|
slots: {
|
||||||
|
root: 'inline-flex items-center gap-x-2 rounded-md px-2.5 py-2 text-sm font-medium ring-1 ring-inset',
|
||||||
|
leadingIcon: 'h-5 w-5'
|
||||||
|
},
|
||||||
|
variants: {
|
||||||
|
level: {
|
||||||
|
success: {
|
||||||
|
root: 'bg-green-50 text-green-700 ring-green-600/20'
|
||||||
|
},
|
||||||
|
warn: {
|
||||||
|
root: 'bg-yellow-50 text-yellow-700 ring-yellow-600/20'
|
||||||
|
},
|
||||||
|
info: {
|
||||||
|
root: 'bg-content/5 text-content-emphasis ring-content/20'
|
||||||
|
},
|
||||||
|
error: {
|
||||||
|
root: 'bg-red-50 text-red-700 ring-red-600/20'
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
export interface LevelLeadingProps extends VariantProps<typeof chip> {
|
||||||
|
className?: string;
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface ChipProps extends VariantProps<typeof chip> {
|
||||||
|
children: React.ReactNode;
|
||||||
|
className?: string;
|
||||||
|
}
|
||||||
|
|
||||||
|
function LevelLeadingIcon({ level, className }: LevelLeadingProps) {
|
||||||
|
if (level === 'success') {
|
||||||
|
return <CheckCircleIcon className={clsx(className, 'text-green-500')} aria-hidden="true" />;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (level === 'warn') {
|
||||||
|
return (
|
||||||
|
<ExclamationCircleIcon className={clsx(className, 'text-yellow-500')} aria-hidden="true" />
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (level === 'error') {
|
||||||
|
return <XCircleIcon className={clsx(className, 'text-red-500')} aria-hidden="true" />;
|
||||||
|
}
|
||||||
|
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
export default function Chip({ children, level, className }: ChipProps) {
|
||||||
|
const { root, leadingIcon } = chip();
|
||||||
|
return (
|
||||||
|
<span className={root({ level, className })}>
|
||||||
|
<LevelLeadingIcon level={level} className={leadingIcon()} />
|
||||||
|
{children}
|
||||||
|
</span>
|
||||||
|
);
|
||||||
|
}
|
163
components/ui/combobox.tsx
Normal file
163
components/ui/combobox.tsx
Normal file
@ -0,0 +1,163 @@
|
|||||||
|
'use client';
|
||||||
|
|
||||||
|
import {
|
||||||
|
Combobox as HeadlessCombobox,
|
||||||
|
ComboboxProps as HeadlessComboboxProps,
|
||||||
|
ComboboxButton,
|
||||||
|
ComboboxInput,
|
||||||
|
ComboboxOption,
|
||||||
|
ComboboxOptions,
|
||||||
|
Field,
|
||||||
|
Label
|
||||||
|
} from '@headlessui/react';
|
||||||
|
import { AnchorProps } from '@headlessui/react/dist/internal/floating';
|
||||||
|
import { ChevronDownIcon } from '@heroicons/react/16/solid';
|
||||||
|
import { focusInput } from 'lib/utils';
|
||||||
|
import get from 'lodash.get';
|
||||||
|
import { useCallback, useState } from 'react';
|
||||||
|
import { tv } from 'tailwind-variants';
|
||||||
|
|
||||||
|
const combobox = tv({
|
||||||
|
slots: {
|
||||||
|
root: '',
|
||||||
|
label: [
|
||||||
|
'text-sm leading-none',
|
||||||
|
'text-content-strong font-medium',
|
||||||
|
'data-[disabled]:text-gray-400'
|
||||||
|
],
|
||||||
|
input: [
|
||||||
|
// base
|
||||||
|
'w-full relative block rounded-md border-0 shadow-sm outline-none transition sm:text-sm sm:leading-6',
|
||||||
|
'mt-2 px-2.5 py-1.5',
|
||||||
|
// border color
|
||||||
|
'border-gray-300',
|
||||||
|
// text color
|
||||||
|
'text-gray-900',
|
||||||
|
// ring
|
||||||
|
'ring-1 ring-inset ring-gray-300',
|
||||||
|
// placeholder color
|
||||||
|
'placeholder-gray-400',
|
||||||
|
// background color
|
||||||
|
'bg-white',
|
||||||
|
// disabled
|
||||||
|
'data-[disabled]:border-gray-300 data-[disabled]:bg-gray-100 data-[disabled]:text-gray-400',
|
||||||
|
// focus
|
||||||
|
focusInput,
|
||||||
|
// invalid
|
||||||
|
'data-[invalid]:ring-2 data-[invalid]:ring-red-200 data-[invalid]:border-red-500'
|
||||||
|
],
|
||||||
|
button: [
|
||||||
|
'group absolute inset-y-0 right-0 px-2.5 data-[disabled]:cursor-not-allowed data-[disabled]:opacity-50'
|
||||||
|
],
|
||||||
|
options: [
|
||||||
|
'z-10 w-[var(--input-width)] rounded-xl border border-gray-200 bg-white p-1 [--anchor-gap:6px] empty:hidden'
|
||||||
|
],
|
||||||
|
option: [
|
||||||
|
'flex cursor-default select-none items-center gap-2 rounded-lg px-3 py-1.5 text-sm/6 data-[focus]:bg-secondary/10'
|
||||||
|
]
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
interface ComboboxProps<T extends Record<string, unknown>, TMultiple extends boolean | undefined>
|
||||||
|
extends HeadlessComboboxProps<T, TMultiple> {
|
||||||
|
options: T[];
|
||||||
|
label: string;
|
||||||
|
labelHidden?: boolean;
|
||||||
|
autoFocus?: boolean;
|
||||||
|
displayKey: keyof T;
|
||||||
|
required?: boolean;
|
||||||
|
className?: string;
|
||||||
|
anchor?: AnchorProps;
|
||||||
|
}
|
||||||
|
|
||||||
|
const Combobox = <T extends Record<string, unknown>, TMultiple extends boolean | undefined>({
|
||||||
|
options,
|
||||||
|
value,
|
||||||
|
onChange,
|
||||||
|
label,
|
||||||
|
labelHidden,
|
||||||
|
displayKey,
|
||||||
|
disabled,
|
||||||
|
autoFocus,
|
||||||
|
by,
|
||||||
|
className,
|
||||||
|
required,
|
||||||
|
anchor,
|
||||||
|
...props
|
||||||
|
}: ComboboxProps<T, TMultiple>) => {
|
||||||
|
const {
|
||||||
|
root,
|
||||||
|
label: labelStyles,
|
||||||
|
input,
|
||||||
|
button,
|
||||||
|
options: optionsStyles,
|
||||||
|
option: optionStyles
|
||||||
|
} = combobox();
|
||||||
|
|
||||||
|
const [query, setQuery] = useState('');
|
||||||
|
const getDisplayValue = useCallback(
|
||||||
|
(option: T | null) => {
|
||||||
|
if (!option) return '';
|
||||||
|
|
||||||
|
if (typeof option[displayKey] === 'string') {
|
||||||
|
return option[displayKey] as string;
|
||||||
|
}
|
||||||
|
|
||||||
|
return get(option, `${displayKey as string}.value`) as string;
|
||||||
|
},
|
||||||
|
[displayKey]
|
||||||
|
);
|
||||||
|
|
||||||
|
const filteredOptions =
|
||||||
|
query === ''
|
||||||
|
? options
|
||||||
|
: options.filter((option) => {
|
||||||
|
return getDisplayValue(option).toLocaleLowerCase().includes(query.toLowerCase());
|
||||||
|
});
|
||||||
|
|
||||||
|
return (
|
||||||
|
<Field disabled={disabled} className={root({ className })}>
|
||||||
|
{!labelHidden && (
|
||||||
|
<Label className={labelStyles()}>
|
||||||
|
{label}
|
||||||
|
{required && <span className="text-red-500"> *</span>}
|
||||||
|
</Label>
|
||||||
|
)}
|
||||||
|
<HeadlessCombobox
|
||||||
|
value={value}
|
||||||
|
onChange={onChange}
|
||||||
|
onClose={() => setQuery('')}
|
||||||
|
disabled={disabled}
|
||||||
|
by={by}
|
||||||
|
{...props}
|
||||||
|
>
|
||||||
|
<div className="relative">
|
||||||
|
<ComboboxInput
|
||||||
|
aria-label={label}
|
||||||
|
displayValue={getDisplayValue}
|
||||||
|
placeholder={`Select ${label}`}
|
||||||
|
onChange={(event) => setQuery(event.target.value)}
|
||||||
|
className={input()}
|
||||||
|
autoFocus={autoFocus}
|
||||||
|
/>
|
||||||
|
<ComboboxButton className={button()}>
|
||||||
|
<ChevronDownIcon className="fill-black/60 group-data-[hover]:fill-black size-5" />
|
||||||
|
</ComboboxButton>
|
||||||
|
</div>
|
||||||
|
<ComboboxOptions anchor="bottom" className={optionsStyles()}>
|
||||||
|
{filteredOptions.map((option) => (
|
||||||
|
<ComboboxOption
|
||||||
|
key={option[by as keyof T] as string}
|
||||||
|
value={option}
|
||||||
|
className={optionStyles()}
|
||||||
|
>
|
||||||
|
{getDisplayValue(option)}
|
||||||
|
</ComboboxOption>
|
||||||
|
))}
|
||||||
|
</ComboboxOptions>
|
||||||
|
</HeadlessCombobox>
|
||||||
|
</Field>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
export default Combobox;
|
@ -22,7 +22,7 @@ const heading = tv(
|
|||||||
export interface HeadingProps extends VariantProps<typeof heading> {
|
export interface HeadingProps extends VariantProps<typeof heading> {
|
||||||
className?: string;
|
className?: string;
|
||||||
children: React.ReactNode;
|
children: React.ReactNode;
|
||||||
as?: 'h1' | 'h2' | 'h3' | 'h4' | 'h5' | 'h6' | 'span' | 'p';
|
as?: React.ElementType;
|
||||||
}
|
}
|
||||||
|
|
||||||
export default function Heading({ children, className, size, as }: HeadingProps) {
|
export default function Heading({ children, className, size, as }: HeadingProps) {
|
||||||
|
@ -12,3 +12,5 @@ export * from './skeleton';
|
|||||||
export { default as Skeleton } from './skeleton';
|
export { default as Skeleton } from './skeleton';
|
||||||
export * from './text';
|
export * from './text';
|
||||||
export { default as Text } from './text';
|
export { default as Text } from './text';
|
||||||
|
export { default as Chip } from './chip';
|
||||||
|
export * from './chip';
|
||||||
|
@ -70,7 +70,12 @@ function Input({
|
|||||||
const { root, label: labelStyles, input } = inputStyles({ hasError });
|
const { root, label: labelStyles, input } = inputStyles({ hasError });
|
||||||
return (
|
return (
|
||||||
<Field disabled={disabled} className={root({ className })}>
|
<Field disabled={disabled} className={root({ className })}>
|
||||||
{label && <Label className={labelStyles({ className: labelClassName })}>{label}</Label>}
|
{label && (
|
||||||
|
<Label className={labelStyles({ className: labelClassName })}>
|
||||||
|
{label}
|
||||||
|
{props.required && <span className="text-red-500"> *</span>}
|
||||||
|
</Label>
|
||||||
|
)}
|
||||||
<HeadlessInput
|
<HeadlessInput
|
||||||
disabled={disabled}
|
disabled={disabled}
|
||||||
className={input({ className: inputClassName })}
|
className={input({ className: inputClassName })}
|
||||||
|
@ -8,10 +8,14 @@ const text = tv(
|
|||||||
sm: 'text-xs',
|
sm: 'text-xs',
|
||||||
md: 'text-sm',
|
md: 'text-sm',
|
||||||
lg: 'text-md'
|
lg: 'text-md'
|
||||||
|
},
|
||||||
|
bold: {
|
||||||
|
true: 'font-medium'
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
defaultVariants: {
|
defaultVariants: {
|
||||||
size: 'md'
|
size: 'md',
|
||||||
|
bold: false
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
@ -25,8 +29,8 @@ export interface TextProps extends VariantProps<typeof text> {
|
|||||||
as?: 'h2' | 'h3' | 'h4' | 'h5' | 'h6' | 'span' | 'p';
|
as?: 'h2' | 'h3' | 'h4' | 'h5' | 'h6' | 'span' | 'p';
|
||||||
}
|
}
|
||||||
|
|
||||||
export default function Text({ children, className, size, as }: TextProps) {
|
export default function Text({ children, className, size, as, bold }: TextProps) {
|
||||||
const Component = as || 'p';
|
const Component = as || 'p';
|
||||||
|
|
||||||
return <Component className={text({ size, className })}>{children}</Component>;
|
return <Component className={text({ size, bold, className })}>{children}</Component>;
|
||||||
}
|
}
|
||||||
|
@ -203,7 +203,6 @@ export async function refreshToken({ request, origin }: { request: NextRequest;
|
|||||||
return { success: false, message: `no_refresh_token` };
|
return { success: false, message: `no_refresh_token` };
|
||||||
}
|
}
|
||||||
const data = await response.json();
|
const data = await response.json();
|
||||||
console.log('data response from initial fetch to refresh', data);
|
|
||||||
const { access_token, expires_in, refresh_token } = data;
|
const { access_token, expires_in, refresh_token } = data;
|
||||||
|
|
||||||
const customerAccessToken = await exchangeAccessToken(
|
const customerAccessToken = await exchangeAccessToken(
|
||||||
@ -238,10 +237,7 @@ export async function checkExpires({
|
|||||||
let isExpired = false;
|
let isExpired = false;
|
||||||
if (parseInt(expiresAt, 10) - 1000 < new Date().getTime()) {
|
if (parseInt(expiresAt, 10) - 1000 < new Date().getTime()) {
|
||||||
isExpired = true;
|
isExpired = true;
|
||||||
console.log('Isexpired is true, we are running refresh token!');
|
|
||||||
const refresh = await refreshToken({ request, origin });
|
const refresh = await refreshToken({ request, origin });
|
||||||
console.log('refresh', refresh);
|
|
||||||
//this will return success: true or success: false - depending on result of refresh
|
|
||||||
return { ranRefresh: isExpired, refresh };
|
return { ranRefresh: isExpired, refresh };
|
||||||
}
|
}
|
||||||
return { ranRefresh: isExpired, success: true };
|
return { ranRefresh: isExpired, success: true };
|
||||||
@ -364,8 +360,6 @@ export async function isLoggedIn(request: NextRequest, origin: string) {
|
|||||||
//return { success: false, message: `no_refresh_token` }
|
//return { success: false, message: `no_refresh_token` }
|
||||||
} else {
|
} else {
|
||||||
const refreshData = isExpired?.refresh?.data;
|
const refreshData = isExpired?.refresh?.data;
|
||||||
//console.log ("refresh data", refreshData)
|
|
||||||
console.log('We used the refresh token, so now going to reset the token and cookies');
|
|
||||||
const newCustomerAccessToken = refreshData?.customerAccessToken;
|
const newCustomerAccessToken = refreshData?.customerAccessToken;
|
||||||
const expires_in = refreshData?.expires_in;
|
const expires_in = refreshData?.expires_in;
|
||||||
//const test_expires_in = 180 //to test to see if it expires in 60 seconds!
|
//const test_expires_in = 180 //to test to see if it expires in 60 seconds!
|
||||||
@ -468,7 +462,6 @@ export async function authorize(request: NextRequest, origin: string) {
|
|||||||
//sets an expires time 2 minutes before expiration which we can use in refresh strategy
|
//sets an expires time 2 minutes before expiration which we can use in refresh strategy
|
||||||
//const test_expires_in = 180 //to test to see if it expires in 60 seconds!
|
//const test_expires_in = 180 //to test to see if it expires in 60 seconds!
|
||||||
const expiresAt = new Date(new Date().getTime() + (expires_in! - 120) * 1000).getTime() + '';
|
const expiresAt = new Date(new Date().getTime() + (expires_in! - 120) * 1000).getTime() + '';
|
||||||
console.log('expires at', expiresAt);
|
|
||||||
|
|
||||||
return await createAllCookies({
|
return await createAllCookies({
|
||||||
response: authResponse,
|
response: authResponse,
|
||||||
|
@ -50,6 +50,33 @@ const orderMetafieldsFragment = /* GraphQL */ `
|
|||||||
orderConfirmation: metafield(namespace: "custom", key: "customer_confirmation") {
|
orderConfirmation: metafield(namespace: "custom", key: "customer_confirmation") {
|
||||||
value
|
value
|
||||||
}
|
}
|
||||||
|
coreReturnStatus: metafield(namespace: "custom", key: "core_status") {
|
||||||
|
value
|
||||||
|
}
|
||||||
|
coreReturnDeadline: metafield(namespace: "custom", key: "core_return_deadline") {
|
||||||
|
value
|
||||||
|
}
|
||||||
|
coreReturnName: metafield(namespace: "custom", key: "core_return_name") {
|
||||||
|
value
|
||||||
|
}
|
||||||
|
coreReturnEmail: metafield(namespace: "custom", key: "core_return_email") {
|
||||||
|
value
|
||||||
|
}
|
||||||
|
coreReturnPhone: metafield(namespace: "custom", key: "core_return_phone") {
|
||||||
|
value
|
||||||
|
}
|
||||||
|
coreReturnAddress: metafield(namespace: "custom", key: "core_return_address") {
|
||||||
|
value
|
||||||
|
}
|
||||||
|
coreReturnCity: metafield(namespace: "custom", key: "core_return_city") {
|
||||||
|
value
|
||||||
|
}
|
||||||
|
coreReturnState: metafield(namespace: "custom", key: "core_return_state") {
|
||||||
|
value
|
||||||
|
}
|
||||||
|
coreReturnZip: metafield(namespace: "custom", key: "core_return_zip") {
|
||||||
|
value
|
||||||
|
}
|
||||||
}
|
}
|
||||||
`;
|
`;
|
||||||
|
|
||||||
|
@ -136,6 +136,9 @@ const userAgent = '*';
|
|||||||
const placeholderProductImage =
|
const placeholderProductImage =
|
||||||
'https://cdn.shopify.com/shopifycloud/customer-account-web/production/assets/8bc6556601c510713d76.svg';
|
'https://cdn.shopify.com/shopifycloud/customer-account-web/production/assets/8bc6556601c510713d76.svg';
|
||||||
|
|
||||||
|
const placeholderPaymentIcon =
|
||||||
|
'https://cdn.shopify.com/shopifycloud/customer-account-web/production/assets/7bea2f.svg';
|
||||||
|
|
||||||
const key = process.env.SHOPIFY_STOREFRONT_ACCESS_TOKEN!;
|
const key = process.env.SHOPIFY_STOREFRONT_ACCESS_TOKEN!;
|
||||||
const adminAccessToken = process.env.SHOPIFY_ADMIN_API_ACCESS_TOKEN!;
|
const adminAccessToken = process.env.SHOPIFY_ADMIN_API_ACCESS_TOKEN!;
|
||||||
|
|
||||||
@ -601,15 +604,17 @@ function reshapeOrder(shopifyOrder: ShopifyOrder): Order {
|
|||||||
const orderTransactions: Transaction[] = shopifyOrder.transactions?.map((transaction) => ({
|
const orderTransactions: Transaction[] = shopifyOrder.transactions?.map((transaction) => ({
|
||||||
processedAt: transaction.processedAt,
|
processedAt: transaction.processedAt,
|
||||||
paymentIcon: {
|
paymentIcon: {
|
||||||
url: transaction.paymentIcon.url,
|
url: transaction.paymentIcon?.url || placeholderPaymentIcon,
|
||||||
altText: transaction.paymentIcon.altText,
|
altText: transaction.paymentIcon?.altText || 'Payment Icon',
|
||||||
width: 100,
|
width: 100,
|
||||||
height: 100
|
height: 100
|
||||||
},
|
},
|
||||||
paymentDetails: {
|
paymentDetails: transaction.paymentDetails
|
||||||
|
? {
|
||||||
last4: transaction.paymentDetails.last4,
|
last4: transaction.paymentDetails.last4,
|
||||||
cardBrand: transaction.paymentDetails.cardBrand
|
cardBrand: transaction.paymentDetails.cardBrand
|
||||||
},
|
}
|
||||||
|
: undefined,
|
||||||
transactionAmount: reshapeMoney(transaction.transactionAmount.presentmentMoney)!
|
transactionAmount: reshapeMoney(transaction.transactionAmount.presentmentMoney)!
|
||||||
}));
|
}));
|
||||||
|
|
||||||
@ -646,7 +651,17 @@ function reshapeOrder(shopifyOrder: ShopifyOrder): Order {
|
|||||||
warrantyActivationOdometer: shopifyOrder.warrantyActivationOdometer,
|
warrantyActivationOdometer: shopifyOrder.warrantyActivationOdometer,
|
||||||
warrantyActivationSelfInstall: shopifyOrder.warrantyActivationSelfInstall,
|
warrantyActivationSelfInstall: shopifyOrder.warrantyActivationSelfInstall,
|
||||||
warrantyActivationVIN: shopifyOrder.warrantyActivationVIN,
|
warrantyActivationVIN: shopifyOrder.warrantyActivationVIN,
|
||||||
orderConfirmation: shopifyOrder.orderConfirmation
|
orderConfirmation: shopifyOrder.orderConfirmation,
|
||||||
|
coreReturnStatus: shopifyOrder.coreReturnStatus,
|
||||||
|
coreReturnDeadline: shopifyOrder.coreReturnDeadline,
|
||||||
|
coreReturnName: shopifyOrder.coreReturnName,
|
||||||
|
coreReturnAddress: shopifyOrder.coreReturnAddress,
|
||||||
|
coreReturnEmail: shopifyOrder.coreReturnEmail,
|
||||||
|
coreReturnPhone: shopifyOrder.coreReturnPhone,
|
||||||
|
coreReturnCity: shopifyOrder.coreReturnCity,
|
||||||
|
coreReturnState: shopifyOrder.coreReturnState,
|
||||||
|
coreReturnZip: shopifyOrder.coreReturnZip,
|
||||||
|
coreReturnDescription: shopifyOrder.coreReturnDescription
|
||||||
};
|
};
|
||||||
|
|
||||||
if (shopifyOrder.customer) {
|
if (shopifyOrder.customer) {
|
||||||
|
@ -115,7 +115,7 @@ export type Fulfillment = {
|
|||||||
export type Transaction = {
|
export type Transaction = {
|
||||||
processedAt: string;
|
processedAt: string;
|
||||||
paymentIcon: Image;
|
paymentIcon: Image;
|
||||||
paymentDetails: {
|
paymentDetails?: {
|
||||||
last4: string;
|
last4: string;
|
||||||
cardBrand: string;
|
cardBrand: string;
|
||||||
};
|
};
|
||||||
@ -206,8 +206,8 @@ type ShopifyShippingLine = {
|
|||||||
type ShopifyOrderTransaction = {
|
type ShopifyOrderTransaction = {
|
||||||
id: string;
|
id: string;
|
||||||
processedAt: string;
|
processedAt: string;
|
||||||
paymentIcon: ShopifyPaymentIconImage;
|
paymentIcon: ShopifyPaymentIconImage | null;
|
||||||
paymentDetails: ShopifyCardPaymentDetails;
|
paymentDetails: ShopifyCardPaymentDetails | null;
|
||||||
transactionAmount: ShopifyMoneyBag;
|
transactionAmount: ShopifyMoneyBag;
|
||||||
giftCardDetails: ShopifyGiftCardDetails | null;
|
giftCardDetails: ShopifyGiftCardDetails | null;
|
||||||
status: string;
|
status: string;
|
||||||
@ -901,6 +901,14 @@ export enum WarrantyStatus {
|
|||||||
LimitedActivated = 'Limited Activation'
|
LimitedActivated = 'Limited Activation'
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export enum CoreReturnStatus {
|
||||||
|
CoreNeeded = 'Core Needed',
|
||||||
|
PickupRequested = 'Pickup Requested',
|
||||||
|
BOLCreated = 'BOL Created',
|
||||||
|
CoreReceived = 'Core Received',
|
||||||
|
CoreRefunded = 'Core Refunded'
|
||||||
|
}
|
||||||
|
|
||||||
export type ShopifyOrderMetafield = {
|
export type ShopifyOrderMetafield = {
|
||||||
orderConfirmation: ShopifyMetafield | null;
|
orderConfirmation: ShopifyMetafield | null;
|
||||||
warrantyStatus: ShopifyMetafield | null;
|
warrantyStatus: ShopifyMetafield | null;
|
||||||
@ -910,6 +918,16 @@ export type ShopifyOrderMetafield = {
|
|||||||
warrantyActivationSelfInstall: ShopifyMetafield | null;
|
warrantyActivationSelfInstall: ShopifyMetafield | null;
|
||||||
warrantyActivationVIN: ShopifyMetafield | null;
|
warrantyActivationVIN: ShopifyMetafield | null;
|
||||||
warrantyActivationMileage: ShopifyMetafield | null;
|
warrantyActivationMileage: ShopifyMetafield | null;
|
||||||
|
coreReturnStatus: ShopifyMetafield | null;
|
||||||
|
coreReturnDeadline: ShopifyMetafield | null;
|
||||||
|
coreReturnName: ShopifyMetafield | null;
|
||||||
|
coreReturnAddress: ShopifyMetafield | null;
|
||||||
|
coreReturnEmail: ShopifyMetafield | null;
|
||||||
|
coreReturnPhone: ShopifyMetafield | null;
|
||||||
|
coreReturnCity: ShopifyMetafield | null;
|
||||||
|
coreReturnState: ShopifyMetafield | null;
|
||||||
|
coreReturnZip: ShopifyMetafield | null;
|
||||||
|
coreReturnDescription: ShopifyMetafield | null;
|
||||||
};
|
};
|
||||||
|
|
||||||
export type File = {
|
export type File = {
|
||||||
|
@ -8,11 +8,7 @@ export function cx(...args: ClassValue[]) {
|
|||||||
}
|
}
|
||||||
export const focusInput = [
|
export const focusInput = [
|
||||||
// base
|
// base
|
||||||
'focus:ring-2',
|
'focus:ring-2 focus:ring-offset-4'
|
||||||
// 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 = [
|
export const hasErrorInput = [
|
||||||
|
Loading…
x
Reference in New Issue
Block a user