mirror of
https://github.com/vercel/commerce.git
synced 2025-06-28 01:11:24 +00:00
fix: build
This commit is contained in:
parent
1b2211ddea
commit
aac4af90ae
5
app/[...not_found]/page.tsx
Normal file
5
app/[...not_found]/page.tsx
Normal file
@ -0,0 +1,5 @@
|
||||
import { notFound } from 'next/navigation';
|
||||
|
||||
export default function NotFoundCatchAll() {
|
||||
notFound();
|
||||
}
|
@ -1,5 +1,5 @@
|
||||
import { authOptions } from "lib/auth/config";
|
||||
import NextAuth from "next-auth";
|
||||
import { authOptions } from 'lib/auth/config';
|
||||
import NextAuth from 'next-auth';
|
||||
|
||||
const handler = NextAuth(authOptions);
|
||||
export { handler as GET, handler as POST };
|
||||
|
@ -20,7 +20,10 @@ export async function POST(req: NextRequest) {
|
||||
const cart = await storeApi.addToCart({ id, quantity, variation });
|
||||
return NextResponse.json(cart, { status: 200 });
|
||||
} catch (error) {
|
||||
return NextResponse.json({ error: 'Failed to add item to cart', message: JSON.stringify(error) }, { status: 500 });
|
||||
return NextResponse.json(
|
||||
{ error: 'Failed to add item to cart', message: JSON.stringify(error) },
|
||||
{ status: 500 }
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
@ -35,7 +38,10 @@ export async function PUT(req: NextRequest) {
|
||||
return NextResponse.json(cart, { status: 200 });
|
||||
}
|
||||
} catch (error) {
|
||||
return NextResponse.json({ error: 'Failed to update cart item', message: JSON.stringify(error) }, { status: 500 });
|
||||
return NextResponse.json(
|
||||
{ error: 'Failed to update cart item', message: JSON.stringify(error) },
|
||||
{ status: 500 }
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
@ -45,6 +51,9 @@ export async function DELETE(req: NextRequest) {
|
||||
const cart = await storeApi.removeFromCart({ key });
|
||||
return NextResponse.json(cart, { status: 200 });
|
||||
} catch (error) {
|
||||
return NextResponse.json({ error: 'Failed to remove item from cart', message: JSON.stringify(error) }, { status: 500 });
|
||||
return NextResponse.json(
|
||||
{ error: 'Failed to remove item from cart', message: JSON.stringify(error) },
|
||||
{ status: 500 }
|
||||
);
|
||||
}
|
||||
}
|
||||
|
@ -1,5 +1,5 @@
|
||||
import { woocommerce } from "lib/woocomerce/woocommerce";
|
||||
import { NextRequest, NextResponse } from "next/server";
|
||||
import { woocommerce } from 'lib/woocomerce/woocommerce';
|
||||
import { NextRequest, NextResponse } from 'next/server';
|
||||
|
||||
export async function POST(req: NextRequest) {
|
||||
try {
|
||||
@ -9,4 +9,4 @@ export async function POST(req: NextRequest) {
|
||||
} catch (error) {
|
||||
return NextResponse.json({ error: 'Failed to add item to cart' }, { status: 500 });
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -74,7 +74,7 @@ export default function CheckoutPage() {
|
||||
</ul>
|
||||
|
||||
<h2 className="mt-2 text-2xl font-bold">Shipping info</h2>
|
||||
<form className="md:grid-cols-6 md:grid-rows-2 gap-4">
|
||||
<form className="gap-4 md:grid-cols-6 md:grid-rows-2">
|
||||
<div className="mt-4">
|
||||
<label
|
||||
htmlFor="address_1"
|
||||
|
@ -12,7 +12,7 @@ export default function LoginPage() {
|
||||
|
||||
const handleLogin = async (event: React.FormEvent) => {
|
||||
event.preventDefault();
|
||||
const res = await signIn('credentials', { username, password, redirect: false, });
|
||||
const res = await signIn('credentials', { username, password, redirect: false });
|
||||
if (res?.ok) {
|
||||
router.replace('/');
|
||||
} else {
|
||||
@ -23,7 +23,7 @@ export default function LoginPage() {
|
||||
return (
|
||||
<section className="mx-auto mt-4 grid max-w-screen-2xl justify-center gap-4 px-4 pb-4">
|
||||
<h1 className="text-2xl font-bold">Login</h1>
|
||||
<div className="flex flex-col h-screen w-full max-w-md">
|
||||
<div className="flex h-screen w-full max-w-md flex-col">
|
||||
{error && <p className="text-red-500">{error}</p>}
|
||||
<form onSubmit={handleLogin}>
|
||||
<div className="mt-4">
|
||||
@ -67,7 +67,7 @@ export default function LoginPage() {
|
||||
</button>
|
||||
</div>
|
||||
|
||||
<span className="block mt-6 text-center text-sm text-gray-600 dark:text-gray-300">
|
||||
<span className="mt-6 block text-center text-sm text-gray-600 dark:text-gray-300">
|
||||
Don't have an account?{' '}
|
||||
<a href="/signup" className="text-indigo-600 hover:underline">
|
||||
Sign up
|
||||
|
@ -101,9 +101,9 @@ export default async function ProductPage(props: { params: Promise<{ name: strin
|
||||
</Suspense>
|
||||
)}
|
||||
<Suspense fallback={null}>
|
||||
<ProductDescription product={product} variations={variations}/>
|
||||
<ProductDescription product={product} variations={variations} />
|
||||
</Suspense>
|
||||
<AddToCart product={product} variations={variations}/>
|
||||
<AddToCart product={product} variations={variations} />
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
@ -16,8 +16,7 @@ export default function SearchLayout({ children }: { children: React.ReactNode }
|
||||
<ChildrenWrapper>{children}</ChildrenWrapper>
|
||||
</Suspense>
|
||||
</div>
|
||||
<div className="order-none flex-none md:order-last md:w-[100px]">
|
||||
</div>
|
||||
<div className="order-none flex-none md:order-last md:w-[100px]"></div>
|
||||
</div>
|
||||
<Footer />
|
||||
</>
|
||||
|
@ -15,7 +15,11 @@ export default async function SearchPage(props: {
|
||||
const { sort, q: searchValue } = searchParams as { [key: string]: string };
|
||||
const { sortKey, order } = sorting.find((item) => item.slug === sort) || defaultSort;
|
||||
|
||||
const products = await woocommerce.get('products', { search: searchValue, orderby: sortKey, order });
|
||||
const products = await woocommerce.get('products', {
|
||||
search: searchValue,
|
||||
orderby: sortKey,
|
||||
order
|
||||
});
|
||||
const resultsText = products.length > 1 ? 'results' : 'result';
|
||||
|
||||
return (
|
||||
|
@ -4,21 +4,23 @@ import { useState } from 'react';
|
||||
import { z } from 'zod';
|
||||
|
||||
type FormData = {
|
||||
username: string;
|
||||
email: string;
|
||||
password: string;
|
||||
confirmPassword: string;
|
||||
}
|
||||
username: string;
|
||||
email: string;
|
||||
password: string;
|
||||
confirmPassword: string;
|
||||
};
|
||||
|
||||
const customerSchema = z.object({
|
||||
const customerSchema = z
|
||||
.object({
|
||||
username: z.string().min(3),
|
||||
email: z.string().email({ message: "Invalid email" }),
|
||||
email: z.string().email({ message: 'Invalid email' }),
|
||||
password: z.string(),
|
||||
confirmPassword: z.string(),
|
||||
}).refine((data) => data.password === data.confirmPassword, {
|
||||
confirmPassword: z.string()
|
||||
})
|
||||
.refine((data) => data.password === data.confirmPassword, {
|
||||
message: "Passwords don't match",
|
||||
path: ["confirmPassword"],
|
||||
});;
|
||||
path: ['confirmPassword']
|
||||
});
|
||||
|
||||
export default function SignUpPage() {
|
||||
const initialState = { username: '', email: '', password: '', confirmPassword: '' };
|
||||
@ -27,7 +29,7 @@ export default function SignUpPage() {
|
||||
|
||||
const handleChange = (e: React.ChangeEvent<HTMLInputElement>) => {
|
||||
setFormData((prev) => ({ ...prev, [e.target.name]: e.target.value }));
|
||||
}
|
||||
};
|
||||
|
||||
const handleSignup = async (event: React.FormEvent) => {
|
||||
event.preventDefault();
|
||||
@ -37,22 +39,22 @@ export default function SignUpPage() {
|
||||
await fetch('/api/customer', {
|
||||
method: 'POST',
|
||||
headers: {
|
||||
'Content-Type': 'application/json',
|
||||
'Content-Type': 'application/json'
|
||||
},
|
||||
body: JSON.stringify({
|
||||
username: formData.username,
|
||||
first_name: '',
|
||||
last_name: '',
|
||||
email: formData.email,
|
||||
password: formData.password
|
||||
}),
|
||||
username: formData.username,
|
||||
first_name: '',
|
||||
last_name: '',
|
||||
email: formData.email,
|
||||
password: formData.password
|
||||
})
|
||||
});
|
||||
} catch (error) {
|
||||
if (error instanceof z.ZodError) {
|
||||
const errorObj: FormData = initialState;
|
||||
error.errors.forEach((err) => {
|
||||
const key = err.path[0] as keyof FormData;
|
||||
errorObj[key] = err.message as string;
|
||||
const key = err.path[0] as keyof FormData;
|
||||
errorObj[key] = err.message as string;
|
||||
});
|
||||
console.log(errorObj);
|
||||
setError(errorObj);
|
||||
|
@ -10,7 +10,7 @@ export default function LogoutButton() {
|
||||
className="w-full rounded-md bg-indigo-500 p-3 text-white"
|
||||
onClick={() => {
|
||||
signOut({ redirect: false });
|
||||
router.replace('/')
|
||||
router.replace('/');
|
||||
}}
|
||||
>
|
||||
Logout
|
||||
|
@ -6,12 +6,16 @@ import { useProduct } from 'components/product/product-context';
|
||||
import { Product, ProductVariations } from 'lib/woocomerce/models/product';
|
||||
import { useCart } from './cart-context';
|
||||
|
||||
function SubmitButton({disabled = false}: {disabled: boolean}) {
|
||||
function SubmitButton({ disabled = false }: { disabled: boolean }) {
|
||||
const buttonClasses =
|
||||
'relative flex w-full items-center justify-center rounded-full bg-blue-600 p-4 tracking-wide text-white';
|
||||
|
||||
return (
|
||||
<button aria-label="Please select an option" disabled={disabled} className={clsx(buttonClasses, disabled ? 'opacity-50 cursor-not-allowed' : '')}>
|
||||
<button
|
||||
aria-label="Please select an option"
|
||||
disabled={disabled}
|
||||
className={clsx(buttonClasses, disabled ? 'cursor-not-allowed opacity-50' : '')}
|
||||
>
|
||||
<div className="absolute left-0 ml-4">
|
||||
<PlusIcon className="h-5" />
|
||||
</div>
|
||||
@ -20,12 +24,21 @@ function SubmitButton({disabled = false}: {disabled: boolean}) {
|
||||
);
|
||||
}
|
||||
|
||||
export function AddToCart({ product, variations }: { product: Product, variations?: ProductVariations[] }) {
|
||||
export function AddToCart({
|
||||
product,
|
||||
variations
|
||||
}: {
|
||||
product: Product;
|
||||
variations?: ProductVariations[];
|
||||
}) {
|
||||
const { setNewCart } = useCart();
|
||||
const { state } = useProduct();
|
||||
const productVariant = variations?.find((variation) => variation.id.toString() === state.variation);
|
||||
const variation = productVariant?.attributes.map((attr) => ({ attribute: attr.name, value: attr.option })) || [];
|
||||
|
||||
const productVariant = variations?.find(
|
||||
(variation) => variation.id.toString() === state.variation
|
||||
);
|
||||
const variation =
|
||||
productVariant?.attributes.map((attr) => ({ attribute: attr.name, value: attr.option })) || [];
|
||||
|
||||
return (
|
||||
<form
|
||||
action={async () => {
|
||||
@ -42,7 +55,7 @@ export function AddToCart({ product, variations }: { product: Product, variation
|
||||
}
|
||||
}}
|
||||
>
|
||||
<SubmitButton disabled={variations?.length && !state.variation ? true : false}/>
|
||||
<SubmitButton disabled={variations?.length && !state.variation ? true : false} />
|
||||
</form>
|
||||
);
|
||||
}
|
||||
|
@ -23,7 +23,7 @@ export function CartProvider({ children }: { children: React.ReactNode }) {
|
||||
} catch (err) {
|
||||
console.error(err);
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
useEffect(() => {
|
||||
fetchCart();
|
||||
|
@ -10,7 +10,6 @@ type Menu = {
|
||||
path: string;
|
||||
};
|
||||
|
||||
|
||||
export function FooterMenuItem({ item }: { item: Menu }) {
|
||||
const pathname = usePathname();
|
||||
const [active, setActive] = useState(pathname === item.path);
|
||||
|
@ -24,10 +24,10 @@ export default async function Footer() {
|
||||
title: 'Shop',
|
||||
path: '/shop'
|
||||
},
|
||||
...categories.map((category) => ({
|
||||
title: category.name,
|
||||
path: path.join('/collection', category.id.toString())
|
||||
}))
|
||||
...categories.map((category) => ({
|
||||
title: category.name,
|
||||
path: path.join('/collection', category.id.toString())
|
||||
}))
|
||||
] as Menu[];
|
||||
const currentYear = new Date().getFullYear();
|
||||
const copyrightDate = 2023 + (currentYear > 2023 ? `-${currentYear}` : '');
|
||||
|
@ -14,15 +14,15 @@ function FilterItemList({ list }: { list: ListItem[] }) {
|
||||
{list.map((item: ListItem, i) => (
|
||||
<FilterItem key={i} item={item} />
|
||||
))}
|
||||
<Slider
|
||||
className="max-w-md"
|
||||
defaultValue={[100, 500]}
|
||||
formatOptions={{style: "currency", currency: "USD"}}
|
||||
label="Price Range"
|
||||
maxValue={1000}
|
||||
minValue={0}
|
||||
step={50}
|
||||
/>
|
||||
<Slider
|
||||
className="max-w-md"
|
||||
defaultValue={[100, 500]}
|
||||
formatOptions={{ style: 'currency', currency: 'USD' }}
|
||||
label="Price Range"
|
||||
maxValue={1000}
|
||||
minValue={0}
|
||||
step={50}
|
||||
/>
|
||||
</>
|
||||
);
|
||||
}
|
||||
|
@ -7,6 +7,5 @@ type Props = {
|
||||
};
|
||||
|
||||
export const NextAuthProvider = ({ children }: Props) => {
|
||||
|
||||
return <SessionProvider>{children}</SessionProvider>;
|
||||
};
|
||||
|
@ -9,7 +9,7 @@ type ProductState = {
|
||||
image?: string;
|
||||
} & {
|
||||
variation?: string;
|
||||
}
|
||||
};
|
||||
|
||||
type ProductContextType = {
|
||||
state: ProductState;
|
||||
@ -55,7 +55,7 @@ export function ProductProvider({ children }: { children: React.ReactNode }) {
|
||||
const newState = { variation };
|
||||
setOptimisticState(newState);
|
||||
return { ...state, ...newState };
|
||||
}
|
||||
};
|
||||
|
||||
const value = useMemo(
|
||||
() => ({
|
||||
|
@ -4,15 +4,26 @@ import Prose from 'components/prose';
|
||||
import { Product, ProductVariations } from 'lib/woocomerce/models/product';
|
||||
import { useProduct } from './product-context';
|
||||
|
||||
export function ProductDescription({ product, variations }: { product: Product, variations?: ProductVariations[] }) {
|
||||
export function ProductDescription({
|
||||
product,
|
||||
variations
|
||||
}: {
|
||||
product: Product;
|
||||
variations?: ProductVariations[];
|
||||
}) {
|
||||
const { state } = useProduct();
|
||||
const productVariant = variations?.find((variation) => variation.id.toString() === state.variation);
|
||||
|
||||
const productVariant = variations?.find(
|
||||
(variation) => variation.id.toString() === state.variation
|
||||
);
|
||||
|
||||
return (
|
||||
<>
|
||||
<div className="mb-6 flex flex-col border-b pb-6 dark:border-neutral-700">
|
||||
<div className="mr-auto w-auto rounded-full bg-blue-600 p-2 text-sm text-white">
|
||||
<Price amount={productVariant ? productVariant.price : product.price} currencyCode="EUR" />
|
||||
<Price
|
||||
amount={productVariant ? productVariant.price : product.price}
|
||||
currencyCode="EUR"
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
{product.description ? (
|
||||
|
@ -5,7 +5,6 @@ import { useProduct, useUpdateURL } from 'components/product/product-context';
|
||||
import { Attribute } from 'lib/woocomerce/models/base';
|
||||
import { ProductVariations } from 'lib/woocomerce/models/product';
|
||||
|
||||
|
||||
type FilterVariation = {
|
||||
name: string | undefined;
|
||||
values: string[] | undefined;
|
||||
@ -13,7 +12,7 @@ type FilterVariation = {
|
||||
|
||||
export function VariantSelector({
|
||||
options,
|
||||
variations,
|
||||
variations
|
||||
}: {
|
||||
options: Partial<Attribute>[];
|
||||
variations: ProductVariations[];
|
||||
@ -21,9 +20,9 @@ export function VariantSelector({
|
||||
const { state, updateOption } = useProduct();
|
||||
const updateURL = useUpdateURL();
|
||||
|
||||
const combinations: FilterVariation[] = options?.map(attribute => ({
|
||||
const combinations: FilterVariation[] = options?.map((attribute) => ({
|
||||
name: attribute.name,
|
||||
values: attribute?.options?.map(option => option),
|
||||
values: attribute?.options?.map((option) => option)
|
||||
}));
|
||||
|
||||
return combinations.map((option) => (
|
||||
@ -41,12 +40,21 @@ export function VariantSelector({
|
||||
formAction={() => {
|
||||
if (!optionNameLowerCase) return;
|
||||
let newState = updateOption(optionNameLowerCase, value);
|
||||
const keys = Object.keys(newState).filter((key) => key !== 'id' && key !== 'image' && key !== 'variation');
|
||||
const variant = variations.find((variation) => {
|
||||
return variation?.attributes?.every((attr) => attr.name && keys.includes(attr.name) && newState[attr.name] === attr.option);
|
||||
})?.id?.toString();
|
||||
const keys = Object.keys(newState).filter(
|
||||
(key) => key !== 'id' && key !== 'image' && key !== 'variation'
|
||||
);
|
||||
const variant = variations
|
||||
.find((variation) => {
|
||||
return variation?.attributes?.every(
|
||||
(attr) =>
|
||||
attr.name &&
|
||||
keys.includes(attr.name) &&
|
||||
newState[attr.name] === attr.option
|
||||
);
|
||||
})
|
||||
?.id?.toString();
|
||||
if (variant) {
|
||||
newState = {...newState, variation: variant};
|
||||
newState = { ...newState, variation: variant };
|
||||
}
|
||||
updateURL(newState);
|
||||
}}
|
||||
@ -59,7 +67,7 @@ export function VariantSelector({
|
||||
'ring-1 ring-transparent transition duration-300 ease-in-out hover:ring-blue-600':
|
||||
!isActive,
|
||||
'relative z-10 cursor-not-allowed overflow-hidden bg-neutral-100 text-neutral-500 ring-1 ring-neutral-300 before:absolute before:inset-x-0 before:-z-10 before:h-px before:-rotate-45 before:bg-neutral-300 before:transition-transform dark:bg-neutral-900 dark:text-neutral-400 dark:ring-neutral-700 before:dark:bg-neutral-700':
|
||||
''
|
||||
''
|
||||
}
|
||||
)}
|
||||
>
|
||||
|
@ -42,7 +42,7 @@ export const authOptions = {
|
||||
console.debug('Set session token', token.user);
|
||||
session.user = token.user;
|
||||
return session;
|
||||
},
|
||||
}
|
||||
},
|
||||
events: {
|
||||
async signIn() {
|
||||
@ -53,4 +53,4 @@ export const authOptions = {
|
||||
storeApi._setAuthorizationToken('');
|
||||
}
|
||||
}
|
||||
} satisfies NextAuthOptions;
|
||||
} satisfies NextAuthOptions;
|
||||
|
@ -226,7 +226,9 @@ export default class WooCommerceRestApi<T extends WooRestApiOptions> {
|
||||
|
||||
const queryParams: string[] = [];
|
||||
for (const key in params) {
|
||||
queryParams.push(`${encodeURIComponent(key)}=${encodeURIComponent(params[key] as string | number | boolean)}`);
|
||||
queryParams.push(
|
||||
`${encodeURIComponent(key)}=${encodeURIComponent(params[key] as string | number | boolean)}`
|
||||
);
|
||||
}
|
||||
|
||||
if (queryParams.length > 0) {
|
||||
|
@ -15,12 +15,12 @@ export type OrderPayload = {
|
||||
payment_method: string;
|
||||
payment_data?: PaymentMethodData[];
|
||||
customer_note?: string;
|
||||
}
|
||||
};
|
||||
|
||||
export type PaymentMethodData = {
|
||||
key: string;
|
||||
value: string;
|
||||
}
|
||||
};
|
||||
|
||||
class WooCommerceStoreApiClient {
|
||||
private client: AxiosInstance;
|
||||
@ -53,7 +53,6 @@ class WooCommerceStoreApiClient {
|
||||
this.client.defaults.headers['cart-token'] = cartToken;
|
||||
}
|
||||
|
||||
|
||||
async getCart(params?: Record<string, string | number>): Promise<Cart> {
|
||||
return this.client.get<Cart>('/cart', { params }).then(async (response) => {
|
||||
this._seCartToken(response.headers['cart-token']);
|
||||
@ -94,6 +93,7 @@ class WooCommerceStoreApiClient {
|
||||
}
|
||||
|
||||
// Example usage.
|
||||
const baseURL = process.env.WOOCOMMERCE_STORE_API_URL ?? 'http://wordpress.localhost/wp-json/wc/store/v1';
|
||||
const baseURL =
|
||||
process.env.WOOCOMMERCE_STORE_API_URL ?? 'http://wordpress.localhost/wp-json/wc/store/v1';
|
||||
|
||||
export const storeApi = new WooCommerceStoreApiClient(baseURL);
|
||||
|
@ -2,10 +2,8 @@ import WooCommerceRestApi, { WooRestApiOptions } from './models/client';
|
||||
|
||||
const option: WooRestApiOptions = {
|
||||
url: process.env.WOOCOMMERCE_URL ?? 'http://wordpress.localhost',
|
||||
consumerKey:
|
||||
process.env.WOOCOMMERCE_CONSUMER_KEY ?? '',
|
||||
consumerSecret:
|
||||
process.env.WOOCOMMERCE_CONSUMER_SECRET ?? '',
|
||||
consumerKey: process.env.WOOCOMMERCE_CONSUMER_KEY ?? '',
|
||||
consumerSecret: process.env.WOOCOMMERCE_CONSUMER_SECRET ?? '',
|
||||
isHttps: false,
|
||||
version: 'wc/v3',
|
||||
queryStringAuth: false // Force Basic Authentication as query string true and using under
|
||||
|
2291
pnpm-lock.yaml
generated
2291
pnpm-lock.yaml
generated
File diff suppressed because it is too large
Load Diff
Loading…
x
Reference in New Issue
Block a user