add order details page

This commit is contained in:
tedraykov
2024-06-20 13:23:02 +03:00
parent 3694fef9a6
commit 8749b8aaec
52 changed files with 2000 additions and 724 deletions

View File

@@ -1,15 +1,12 @@
'use server';
import { TAGS } from 'lib/shopify/customer/constants';
import { removeAllCookiesServerAction } from 'lib/shopify/customer/auth-helpers';
import { CUSTOMER_API_URL, ORIGIN_URL, removeAllCookiesServerAction } from 'lib/shopify/auth';
import { redirect } from 'next/navigation';
import { revalidateTag } from 'next/cache';
import { cookies } from 'next/headers';
import { SHOPIFY_ORIGIN, SHOPIFY_CUSTOMER_ACCOUNT_API_URL } from 'lib/shopify/customer/constants';
export async function doLogout() {
const origin = SHOPIFY_ORIGIN;
const customerAccountApiUrl = SHOPIFY_CUSTOMER_ACCOUNT_API_URL;
const origin = ORIGIN_URL;
const customerAccountApiUrl = CUSTOMER_API_URL;
let logoutUrl;
try {
const idToken = cookies().get('shop_id_token');
@@ -26,7 +23,6 @@ export async function doLogout() {
);
}
await removeAllCookiesServerAction();
revalidateTag(TAGS.customer);
} catch (e) {
console.log('Error', e);
//you can throw error here or return - return goes back to form b/c of state, throw will throw the error boundary

View File

@@ -2,26 +2,21 @@
//https://nextjs.org/docs/app/building-your-application/data-fetching/server-actions-and-mutations#forms
'use server';
import { TAGS } from 'lib/shopify/customer/constants';
import { redirect } from 'next/navigation';
import { revalidateTag } from 'next/cache';
import { cookies } from 'next/headers';
//import { getOrigin } from 'lib/shopify/customer'
import {
generateCodeVerifier,
generateCodeChallenge,
generateRandomString
} from 'lib/shopify/customer/auth-utils';
import {
SHOPIFY_CUSTOMER_ACCOUNT_API_URL,
SHOPIFY_CLIENT_ID,
SHOPIFY_ORIGIN
} from 'lib/shopify/customer/constants';
generateRandomString,
CUSTOMER_API_CLIENT_ID,
ORIGIN_URL,
CUSTOMER_API_URL
} from 'lib/shopify/auth';
export async function doLogin(prevState: any) {
const customerAccountApiUrl = SHOPIFY_CUSTOMER_ACCOUNT_API_URL;
const clientId = SHOPIFY_CLIENT_ID;
const origin = SHOPIFY_ORIGIN;
export async function doLogin(_: any) {
const customerAccountApiUrl = CUSTOMER_API_URL;
const clientId = CUSTOMER_API_CLIENT_ID;
const origin = ORIGIN_URL;
const loginUrl = new URL(`${customerAccountApiUrl}/auth/oauth/authorize`);
//console.log ("previous", prevState)
@@ -64,6 +59,5 @@ export async function doLogin(prevState: any) {
return 'Error logging in. Please try again';
}
revalidateTag(TAGS.customer);
redirect(`${loginUrl}`); // Navigate to the new post page
}

View File

@@ -1,5 +1,5 @@
import { ChevronRightIcon, EllipsisHorizontalIcon } from '@heroicons/react/16/solid';
import { cn } from 'lib/utils';
import { cn } from 'lib/shopify/utils';
import Link, { LinkProps } from 'next/link';
import { ComponentPropsWithoutRef, ReactNode, forwardRef } from 'react';

View File

@@ -1,5 +1,4 @@
import { getCollection, getMenu, getProduct } from 'lib/shopify';
import { findParentCollection } from 'lib/utils';
import { Fragment } from 'react';
import {
Breadcrumb,
@@ -9,6 +8,7 @@ import {
BreadcrumbPage,
BreadcrumbSeparator
} from './breadcrumb-list';
import { findParentCollection } from 'lib/shopify/utils';
type BreadcrumbProps = {
type: 'product' | 'collection';

View File

@@ -1,69 +1,77 @@
'use client';
import React from 'react';
import { Slot } from '@radix-ui/react-slot';
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';
const buttonVariants = tv({
base: [
// base
'relative inline-flex items-center justify-center rounded-md border px-3 py-1.5 text-center text-sm font-medium transition-all duration-100 ease-in-out',
// disabled
'disabled:pointer-events-none disabled:shadow-none'
],
slots: {
root: [
// base
'relative inline-flex items-center justify-center rounded-md',
// text
'text-center font-medium',
// transition
'transition-all duration-100 ease-in-out',
// disabled
'disabled:pointer-events-none disabled:shadow-none'
],
loading: 'pointer-events-none flex shrink-0 items-center justify-center gap-1.5'
},
variants: {
size: {
sm: 'text-xs px-2.5 py-1.5',
md: 'text-sm px-3 py-2',
lg: 'text-base px-4 py-2.5'
sm: {
root: 'text-xs px-2.5 py-1.5'
},
md: {
root: 'text-sm px-3 py-2'
},
lg: {
root: 'text-base px-4 py-2.5'
}
},
variant: {
primary: [
// border
'border-transparent',
// text color
'text-white',
// background color
'bg-tremor-brand',
// hover color
'hover:bg-tremor-brand-emphasis',
// disabled
'disabled:bg-gray-100',
'disabled:bg-tremor-brand-muted'
],
secondary: [
// border
'border-gray-300',
// text color
'text-gray-900',
// background color
' bg-white',
//hover color
'hover:bg-gray-50',
// disabled
'disabled:text-gray-400'
],
text: [
// border
'border-transparent',
// text color
'text-tremor-brand',
// background color
'bg-transparent',
// hover color
'disabled:text-gray-400'
],
destructive: [
// text color
'text-white',
// border
'border-transparent',
// background color
'bg-red-600',
// hover color
'hover:bg-red-700',
// disabled
'disabled:bg-red-300 disabled:text-white'
]
primary: {
root: [
// border
'border-transparent',
// text color
'text-white',
// background color
'bg-primary',
// hover color
'hover:bg-primary-empahsis',
// disabled
'disabled:bg-primary-muted'
]
},
secondary: {
root: [
// border
'border-gray-300',
// text color
'text-gray-900',
// background color
' bg-white',
//hover color
'hover:bg-gray-50',
// disabled
'disabled:text-gray-400'
]
},
text: {
root: [
// border
'border-transparent',
// text color
'text-tremor-brand',
// background color
'bg-transparent',
// hover color
'disabled:text-gray-400'
]
}
}
},
defaultVariants: {
@@ -72,45 +80,44 @@ const buttonVariants = tv({
}
});
interface ButtonProps
extends React.ComponentPropsWithoutRef<'button'>,
VariantProps<typeof buttonVariants> {
asChild?: boolean;
interface ButtonProps extends ButtonBaseProps, VariantProps<typeof buttonVariants> {
isLoading?: boolean;
loadingText?: string;
className?: string;
}
const Button = React.forwardRef<HTMLButtonElement, ButtonProps>(
(
{
asChild,
isLoading = false,
loadingText,
children,
className,
disabled,
isLoading,
loadingText = 'Loading',
size,
variant,
children,
...props
}: ButtonProps,
forwardedRef
) => {
const Component = asChild ? Slot : 'button';
const { loading, root } = buttonVariants({ variant, size });
return (
<Component
<ButtonBase
ref={forwardedRef}
className={clsx(buttonVariants({ variant }), className)}
className={clsx(root(), className)}
disabled={disabled || isLoading}
{...props}
>
{isLoading ? (
<span className="pointer-events-none flex shrink-0 items-center justify-center gap-1.5">
<span className="sr-only">{loadingText ? loadingText : 'Loading'}</span>
{loadingText ? loadingText : children}
<span className={loading()}>
<Spinner />
<span className="sr-only">{loadingText}</span>
<span>{loadingText}</span>
</span>
) : (
children
)}
</Component>
</ButtonBase>
);
}
);

View File

@@ -2,7 +2,7 @@ import { PlusIcon } from '@heroicons/react/16/solid';
import Price from 'components/price';
import { DEFAULT_OPTION } from 'lib/constants';
import { CartItem } from 'lib/shopify/types';
import { createUrl } from 'lib/utils';
import { createUrl } from 'lib/shopify/utils';
import Image from 'next/image';
import Link from 'next/link';
import { DeleteItemButton } from './delete-item-button';
@@ -65,8 +65,10 @@ const LineItem = ({ item, closeCart }: LineItemProps) => {
className="h-full w-full object-cover"
width={64}
height={64}
alt={item.merchandise.product.featuredImage.altText || item.merchandise.product.title}
src={item.merchandise.product.featuredImage.url}
alt={
item.merchandise.product?.featuredImage?.altText || item.merchandise.product.title
}
src={item.merchandise.product?.featuredImage?.url}
/>
</div>

View File

@@ -2,7 +2,7 @@
import { CheckIcon } from '@heroicons/react/24/outline';
import * as CheckboxPrimitive from '@radix-ui/react-checkbox';
import { cn } from 'lib/utils';
import { cn } from 'lib/shopify/utils';
import { forwardRef } from 'react';
const Checkbox = forwardRef<

54
components/divider.tsx Normal file
View File

@@ -0,0 +1,54 @@
import { tv } from 'tailwind-variants';
const divider = tv({
slots: {
root: '',
element: 'bg-gray-200'
},
variants: {
orientation: {
horizontal: {
root: 'w-full mx-auto flex justify-between items-center text-tremor-default text-tremor-content',
element: 'w-full h-[1px] '
},
vertical: {
root: 'flex justify-between items-stretch text-tremor-default text-tremor-content',
element: 'h-full w-[1px]'
}
},
hasSpacing: {
true: {},
false: {}
}
},
compoundVariants: [
{
orientation: 'horizontal',
hasSpacing: true,
class: {
root: 'my-6'
}
},
{
orientation: 'vertical',
hasSpacing: true,
class: {
root: 'mx-6'
}
}
]
});
type DividerProps = {
orientation?: 'horizontal' | 'vertical';
hasSpacing?: boolean;
};
export default function Divider({ orientation = 'horizontal', hasSpacing = true }: DividerProps) {
const { root, element } = divider({ orientation, hasSpacing });
return (
<div className={root()}>
<span className={element()} />
</div>
);
}

View File

@@ -3,7 +3,7 @@
import { Button } from '@headlessui/react';
import { MAKE_FILTER_ID, MODEL_FILTER_ID, PART_TYPES, YEAR_FILTER_ID } from 'lib/constants';
import { Menu, Metaobject } from 'lib/shopify/types';
import { createUrl, findParentCollection } from 'lib/utils';
import { createUrl, findParentCollection } from 'lib/shopify/utils';
import get from 'lodash.get';
import { useParams, useRouter, useSearchParams } from 'next/navigation';
import { useState } from 'react';

View File

@@ -1,7 +1,7 @@
'use client';
import { MagnifyingGlassIcon } from '@heroicons/react/24/outline';
import { createUrl } from 'lib/utils';
import { createUrl } from 'lib/shopify/utils';
import { useRouter, useSearchParams } from 'next/navigation';
export default function Search() {

View File

@@ -3,7 +3,7 @@ import { Disclosure, DisclosureButton, DisclosurePanel } from '@headlessui/react
import { ChevronDownIcon } from '@heroicons/react/24/outline';
import clsx from 'clsx';
import { Filter, FilterType } from 'lib/shopify/types';
import { createUrl } from 'lib/utils';
import { createUrl } from 'lib/shopify/utils';
import { usePathname, useRouter, useSearchParams } from 'next/navigation';
import PriceRange from './price-range';
import SelectedList from './selected-list';

View File

@@ -3,7 +3,7 @@
import Price from 'components/price';
import { useDebounce } from 'hooks';
import { Filter } from 'lib/shopify/types';
import { createUrl } from 'lib/utils';
import { createUrl } from 'lib/shopify/utils';
import get from 'lodash.get';
import { usePathname, useRouter, useSearchParams } from 'next/navigation';
import { useCallback, useEffect, useRef, useState } from 'react';

View File

@@ -2,7 +2,7 @@
import { XMarkIcon } from '@heroicons/react/16/solid';
import { Filter } from 'lib/shopify/types';
import { createUrl } from 'lib/utils';
import { createUrl } from 'lib/shopify/utils';
import { usePathname, useRouter, useSearchParams } from 'next/navigation';
const SelectedList = ({ filters }: { filters: Filter[] }) => {

View File

@@ -1,6 +1,6 @@
import clsx from 'clsx';
import { SortFilterItem } from 'lib/constants';
import { createUrl } from 'lib/utils';
import { createUrl } from 'lib/shopify/utils';
import Link from 'next/link';
import { usePathname, useSearchParams } from 'next/navigation';

View File

@@ -3,7 +3,7 @@
import { ArrowRightIcon } from '@heroicons/react/16/solid';
import { MAKE_FILTER_ID } from 'lib/constants';
import { Metaobject } from 'lib/shopify/types';
import { createUrl } from 'lib/utils';
import { createUrl } from 'lib/shopify/utils';
import { useRouter, useSearchParams } from 'next/navigation';
const ButtonGroup = ({ manufacturer }: { manufacturer: Metaobject }) => {

View File

@@ -9,7 +9,6 @@ type ManufacturersGridProps = {
};
const ManufacturersGrid = ({ manufacturers, variant = 'home' }: ManufacturersGridProps) => {
console.log('manufacturers', manufacturers);
const popularManufacturers = manufacturers.filter(
(manufacturer) => manufacturer.is_popular === 'true'
);

View File

@@ -3,11 +3,13 @@ import clsx from 'clsx';
const Price = ({
amount,
className,
as,
currencyCode = 'USD',
currencyCodeClassName,
showCurrency = false
}: {
amount: string;
as?: 'p' | 'span';
className?: string;
currencyCode: string;
currencyCodeClassName?: string;
@@ -21,9 +23,10 @@ const Price = ({
return <p className={className}>Included</p>;
}
const Component = as || 'p';
// Otherwise, format and display the price
return (
<p suppressHydrationWarning={true} className={className}>
<Component suppressHydrationWarning={true} className={className}>
{new Intl.NumberFormat(undefined, {
style: 'currency',
currency: currencyCode,
@@ -32,7 +35,7 @@ const Price = ({
{showCurrency && (
<span className={clsx('ml-1 inline', currencyCodeClassName)}>{currencyCode}</span>
)}
</p>
</Component>
);
};

View File

@@ -6,7 +6,7 @@ import Price from 'components/price';
import SideDialog from 'components/side-dialog';
import { CORE_VARIANT_ID_KEY, CORE_WAIVER } from 'lib/constants';
import { CoreChargeOption, ProductVariant } from 'lib/shopify/types';
import { cn, createUrl } from 'lib/utils';
import { cn, createUrl } from 'lib/shopify/utils';
import { usePathname, useRouter, useSearchParams } from 'next/navigation';
import { useState } from 'react';

View File

@@ -2,7 +2,7 @@
import { ArrowLeftIcon, ArrowRightIcon } from '@heroicons/react/24/outline';
import { TileImage } from 'components/grid/tile';
import { createUrl } from 'lib/utils';
import { createUrl } from 'lib/shopify/utils';
import Image from 'next/image';
import Link from 'next/link';
import { usePathname, useSearchParams } from 'next/navigation';

View File

@@ -6,7 +6,7 @@ import clsx from 'clsx';
import Price from 'components/price';
import { CORE_VARIANT_ID_KEY, CORE_WAIVER } from 'lib/constants';
import { CoreChargeOption, Money, ProductOption, ProductVariant } from 'lib/shopify/types';
import { createUrl } from 'lib/utils';
import { createUrl } from 'lib/shopify/utils';
import { usePathname, useRouter, useSearchParams } from 'next/navigation';
import { Fragment, useEffect, useState } from 'react';

View File

@@ -1,7 +1,7 @@
'use client';
import Price from 'components/price';
import { cn } from 'lib/utils';
import { cn } from 'lib/shopify/utils';
import { ReactNode, useState } from 'react';
const options = ['Included', 'Premium Labor', '+1 Year'] as const;

View File

@@ -20,21 +20,15 @@ function SubmitButton(props: any) {
<>
{props?.message && <div className="my-5">{props?.message}</div>}
<Button
onClick={(e: React.FormEvent<HTMLButtonElement>) => {
if (pending) e.preventDefault();
}}
type="submit"
aria-label="Log in"
aria-disabled={pending}
disabled={pending}
isLoading={pending}
loadingText="Signing In..."
className="w-full"
>
{pending ? (
<>
<span>Logging In...</span>
</>
) : (
<>
<span>Log-In</span>
</>
)}
Sign In
</Button>
</>
);

19
components/spinner.tsx Normal file
View File

@@ -0,0 +1,19 @@
import clsx from 'clsx';
import React from 'react';
export default function Spinner({ className }: { className?: string }) {
return (
<svg
xmlns="http://www.w3.org/2000/svg"
className={clsx('flex-1 animate-spin stroke-current stroke-[3]', className)}
fill="none"
viewBox="0 0 24 24"
>
<path
d="M12 22C17.5228 22 22 17.5228 22 12C22 6.47715 17.5228 2 12 2C6.47715 2 2 6.47715 2 12C2 17.5228 6.47715 22 12 22Z"
className="stroke-current opacity-25"
/>
<path d="M12 2C6.47715 2 2 6.47715 2 12C2 14.7255 3.09032 17.1962 4.85857 19" />
</svg>
);
}

View File

@@ -1,6 +1,6 @@
'use client';
import { cn } from 'lib/utils';
import { cn } from 'lib/shopify/utils';
import { ITooltip, Tooltip as ReactTooltip } from 'react-tooltip';
const Tooltip = ({ id, children, className }: ITooltip) => {

35
components/ui/badge.tsx Normal file
View File

@@ -0,0 +1,35 @@
import React from 'react';
import { VariantProps, tv } from 'tailwind-variants';
const badgeStyles = tv({
base: [
'absolute -right-2 -top-2 h-5 w-5',
'flex items-center justify-center rounded-full text-xs font-semibold'
],
variants: {
color: {
primary: 'bg-primary text-white',
secondary: 'bg-secondary text-white',
content: 'bg-content text-white'
}
},
defaultVariants: {
color: 'content'
}
});
interface BadgeProps extends VariantProps<typeof badgeStyles> {
content: string | number;
className?: string;
children: React.ReactNode;
}
export default function Badge({ className, color, children, content }: BadgeProps) {
return (
<span className="relative flex-none">
{children}
<span className={badgeStyles({ color, className })}>{content}</span>
</span>
);
}

38
components/ui/card.tsx Normal file
View File

@@ -0,0 +1,38 @@
import React from 'react';
import { Slot } from '@radix-ui/react-slot';
import { VariantProps, tv } from 'tailwind-variants';
const cardStyles = tv({
base: 'rounded p-6 text-left w-full',
variants: {
outlined: {
true: 'border bg-white',
false: {}
},
elevated: {
true: 'shadow-lg shadow-content/10 bg-white',
false: {}
}
},
defaultVariants: {
outlined: true
}
});
interface CardProps extends React.ComponentPropsWithoutRef<'div'>, VariantProps<typeof cardStyles> {
asChild?: boolean;
}
const Card = React.forwardRef<HTMLDivElement, CardProps>(
({ className, asChild, outlined, elevated, ...props }, forwardedRef) => {
const Component = asChild ? Slot : 'div';
return (
<Component ref={forwardedRef} className={cardStyles({ outlined, className })} {...props} />
);
}
);
Card.displayName = 'Card';
export { Card, type CardProps };

26
components/ui/heading.tsx Normal file
View File

@@ -0,0 +1,26 @@
import { VariantProps, tv } from 'tailwind-variants';
const heading = tv({
base: [''],
variants: {
size: {
sm: 'text-heading-sm',
md: 'text-heading-md',
lg: 'text-heading-lg'
}
},
defaultVariants: {
size: 'md'
}
});
interface HeadingProps extends VariantProps<typeof heading> {
className?: string;
children: React.ReactNode;
as?: 'h1' | 'h2' | 'h3' | 'h4' | 'h5' | 'h6' | 'span' | 'p';
}
export default function Heading({ children, className, size, as }: HeadingProps) {
const Component = as || 'h2';
return <Component className={heading({ size, className })}>{children}</Component>;
}

32
components/ui/label.tsx Normal file
View File

@@ -0,0 +1,32 @@
import { VariantProps, tv } from 'tailwind-variants';
const label = tv(
{
base: 'text-content',
variants: {
size: {
sm: 'text-label-sm',
md: 'text-label-md',
lg: 'text-label-lg'
}
},
defaultVariants: {
size: 'md'
}
},
{
twMerge: false
}
);
interface LabelProps extends VariantProps<typeof label> {
className?: string;
children: React.ReactNode;
as?: 'h2' | 'h3' | 'h4' | 'h5' | 'h6' | 'span' | 'p';
}
export default function Label({ children, className, size, as }: LabelProps) {
const Component = as || 'span';
return <Component className={label({ size, className })}>{children}</Component>;
}

32
components/ui/text.tsx Normal file
View File

@@ -0,0 +1,32 @@
import { VariantProps, tv } from 'tailwind-variants';
const text = tv(
{
base: '',
variants: {
size: {
sm: 'text-xs',
md: 'text-sm',
lg: 'text-md'
}
},
defaultVariants: {
size: 'md'
}
},
{
twMerge: false
}
);
interface TextProps extends VariantProps<typeof text> {
className?: string;
children: React.ReactNode;
as?: 'h2' | 'h3' | 'h4' | 'h5' | 'h6' | 'span' | 'p';
}
export default function Text({ children, className, size, as }: TextProps) {
const Component = as || 'p';
return <Component className={text({ size, className })}>{children}</Component>;
}