mirror of
https://github.com/vercel/commerce.git
synced 2025-06-16 04:11:22 +00:00
tests pages
This commit is contained in:
parent
d827e1232b
commit
83ae628672
@ -1,4 +1,4 @@
|
|||||||
import spree from '@commerce/index';
|
import spree from '@commerce/client';
|
||||||
import DiseasesAndConditions from 'components/diseases/diseases-and-conditions';
|
import DiseasesAndConditions from 'components/diseases/diseases-and-conditions';
|
||||||
import Specialities from 'components/diseases/specialities';
|
import Specialities from 'components/diseases/specialities';
|
||||||
import { cache } from 'react';
|
import { cache } from 'react';
|
||||||
|
@ -1,7 +1,7 @@
|
|||||||
|
import '@styles/globals.scss';
|
||||||
import Footer from 'components/layout/footer';
|
import Footer from 'components/layout/footer';
|
||||||
import Navbar from 'components/layout/navbar';
|
import Navbar from 'components/layout/navbar';
|
||||||
import { ReactNode } from 'react';
|
import { ReactNode } from 'react';
|
||||||
import './globals.css';
|
|
||||||
|
|
||||||
export default async function RootLayout({ children }: { children: ReactNode }) {
|
export default async function RootLayout({ children }: { children: ReactNode }) {
|
||||||
return (
|
return (
|
||||||
|
@ -1,10 +1,20 @@
|
|||||||
export default async function TestPage() {
|
import commerceApi from '@commerce/api';
|
||||||
|
import CartButton from 'components/tests/cart-button';
|
||||||
|
import { notFound } from 'next/navigation';
|
||||||
|
|
||||||
|
export default async function TestPage({ params }: { params: any }) {
|
||||||
|
const { id } = params;
|
||||||
|
const product = await commerceApi.getProduct(id);
|
||||||
|
|
||||||
|
if (!product) return notFound();
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div>
|
<div>
|
||||||
<h1>Test Detail for ID:</h1>
|
<h1 className="mb-4 text-4xl font-extrabold leading-none tracking-tight text-gray-900 md:text-5xl lg:text-6xl dark:text-white">
|
||||||
{/* Render test details based on the ID */}
|
Test Detail for ID: {product.id}
|
||||||
|
</h1>
|
||||||
<button>Add to cart</button>
|
<p className="mb-4 text-lg text-gray-600 dark:text-gray-400">{product.attributes.name}</p>
|
||||||
|
<CartButton id={product.id} />
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
@ -1,19 +1,9 @@
|
|||||||
import spree from '@commerce/index';
|
import commerceApi from '@commerce/api';
|
||||||
import Alphabet from 'components/tests/alphabet';
|
import Alphabet from 'components/tests/alphabet';
|
||||||
import TestsTable from 'components/tests/tests-table';
|
import TestsTable from 'components/tests/tests-table';
|
||||||
|
|
||||||
async function getProducts() {
|
|
||||||
const res = await spree.products.list({});
|
|
||||||
|
|
||||||
if (!res.isSuccess()) {
|
|
||||||
throw new Error('Failed to fetch data');
|
|
||||||
}
|
|
||||||
|
|
||||||
return res.success().data;
|
|
||||||
}
|
|
||||||
|
|
||||||
export default async function TestsPage() {
|
export default async function TestsPage() {
|
||||||
const products = await getProducts();
|
const products = await commerceApi.getProducts();
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div>
|
<div>
|
||||||
|
@ -1,15 +1,25 @@
|
|||||||
'use client';
|
'use client';
|
||||||
|
|
||||||
|
import { addProductToCart } from '@commerce/api/cart';
|
||||||
|
import { useFormState, useFormStatus } from 'react-dom';
|
||||||
|
|
||||||
interface Props {
|
interface Props {
|
||||||
id: number;
|
id: number | string;
|
||||||
}
|
}
|
||||||
|
|
||||||
const CartButton: React.FC<Props> = ({ id }) => {
|
const CartButton: React.FC<Props> = ({ id }) => {
|
||||||
const handleClick = () => {
|
const handleClick = async () => await addProductToCart(id);
|
||||||
console.log('click', id);
|
|
||||||
};
|
|
||||||
|
|
||||||
return <button onClick={handleClick}>Add To Cart 1</button>;
|
const [, formAction] = useFormState(handleClick, null);
|
||||||
|
const { pending } = useFormStatus();
|
||||||
|
|
||||||
|
return (
|
||||||
|
<form action={formAction}>
|
||||||
|
<button className="button" disabled={pending}>
|
||||||
|
Add To Cart
|
||||||
|
</button>
|
||||||
|
</form>
|
||||||
|
);
|
||||||
};
|
};
|
||||||
|
|
||||||
export default CartButton;
|
export default CartButton;
|
||||||
|
@ -1,10 +1,11 @@
|
|||||||
|
import { ProductAttr } from '@spree/storefront-api-v2-sdk/dist/*';
|
||||||
import Link from 'next/link';
|
import Link from 'next/link';
|
||||||
import CartButton from './cart-button';
|
import CartButton from './cart-button';
|
||||||
|
|
||||||
export default function TestsTable({ products = [] }) {
|
export default function TestsTable({ products }: { products: ProductAttr[] }) {
|
||||||
return (
|
return (
|
||||||
<div>
|
<div>
|
||||||
<table>
|
<table className="table w-full border-collapse border">
|
||||||
<thead>
|
<thead>
|
||||||
<tr>
|
<tr>
|
||||||
<th>Number</th>
|
<th>Number</th>
|
||||||
|
57
lib/spree/api/cart.ts
Normal file
57
lib/spree/api/cart.ts
Normal file
@ -0,0 +1,57 @@
|
|||||||
|
import spree from '@commerce/client';
|
||||||
|
import { IToken, ProductId } from '@commerce/types';
|
||||||
|
import { ensureIToken, setCartToken } from '@commerce/utils';
|
||||||
|
import { FetchError, IOrder, extractSuccess } from '@spree/storefront-api-v2-sdk';
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Checks if user authenticated, creates an empty cart (for user or guest)
|
||||||
|
* and returns cart @type {Cart}
|
||||||
|
*/
|
||||||
|
const createEmptyCart = async () => {
|
||||||
|
const token: IToken | undefined = ensureIToken();
|
||||||
|
const cartResponse = await extractSuccess(spree.cart.create(token));
|
||||||
|
return cartResponse;
|
||||||
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Checks if token is present,
|
||||||
|
* if not creates an empty cart and returns cart @type {Cart}
|
||||||
|
*/
|
||||||
|
export const addProductToCart = async (productId: ProductId): Promise<IOrder | null> => {
|
||||||
|
let cartResponse: IOrder | null;
|
||||||
|
|
||||||
|
let token = ensureIToken();
|
||||||
|
|
||||||
|
if (!token) {
|
||||||
|
const cartCreateResponse = await createEmptyCart();
|
||||||
|
setCartToken(cartCreateResponse.data.attributes.token);
|
||||||
|
token = ensureIToken();
|
||||||
|
}
|
||||||
|
|
||||||
|
const addItemParams = {
|
||||||
|
...token,
|
||||||
|
variant_id: productId,
|
||||||
|
quantity: 1,
|
||||||
|
include: [
|
||||||
|
'line_items',
|
||||||
|
'line_items.variant',
|
||||||
|
'line_items.variant.product',
|
||||||
|
'line_items.variant.product.images',
|
||||||
|
'line_items.variant.images',
|
||||||
|
'line_items.variant.option_values',
|
||||||
|
'line_items.variant.product.option_types'
|
||||||
|
].join(',')
|
||||||
|
};
|
||||||
|
|
||||||
|
try {
|
||||||
|
cartResponse = await extractSuccess(spree.cart.addItem(addItemParams));
|
||||||
|
} catch (addItemError: unknown) {
|
||||||
|
if ((addItemError as FetchError).status === 404) {
|
||||||
|
throw new Error('No response from server');
|
||||||
|
}
|
||||||
|
|
||||||
|
cartResponse = null;
|
||||||
|
}
|
||||||
|
|
||||||
|
return cartResponse;
|
||||||
|
};
|
5
lib/spree/api/index.ts
Normal file
5
lib/spree/api/index.ts
Normal file
@ -0,0 +1,5 @@
|
|||||||
|
import * as cart from './cart';
|
||||||
|
import { getProduct, getProducts } from './products';
|
||||||
|
|
||||||
|
const api = { cart, getProduct, getProducts };
|
||||||
|
export default api;
|
39
lib/spree/api/products.ts
Normal file
39
lib/spree/api/products.ts
Normal file
@ -0,0 +1,39 @@
|
|||||||
|
import commerce from '@commerce/client';
|
||||||
|
import { ProductAttr, extractSuccess } from '@spree/storefront-api-v2-sdk';
|
||||||
|
|
||||||
|
export const getProduct = async (id: string): Promise<ProductAttr | null> => {
|
||||||
|
let product = null;
|
||||||
|
|
||||||
|
const params = {
|
||||||
|
id,
|
||||||
|
include: 'option_types'
|
||||||
|
};
|
||||||
|
|
||||||
|
try {
|
||||||
|
const response = await extractSuccess(commerce.products.show(params));
|
||||||
|
product = response.data;
|
||||||
|
} catch (error) {
|
||||||
|
console.log('Error fetching product', error);
|
||||||
|
}
|
||||||
|
|
||||||
|
return product;
|
||||||
|
};
|
||||||
|
|
||||||
|
export const getProducts = async ({ taxons = [] } = {}): Promise<ProductAttr[]> => {
|
||||||
|
const filter = { filter: { taxons: taxons.join(',') }, sort: '-updated_at' };
|
||||||
|
|
||||||
|
const params = {
|
||||||
|
methodPath: 'products.list',
|
||||||
|
arguments: [
|
||||||
|
{
|
||||||
|
include: 'option_types',
|
||||||
|
per_page: 100,
|
||||||
|
...filter
|
||||||
|
}
|
||||||
|
]
|
||||||
|
};
|
||||||
|
|
||||||
|
const successResponse = await extractSuccess(commerce.products.list(params));
|
||||||
|
|
||||||
|
return successResponse.data;
|
||||||
|
};
|
@ -1,9 +1,9 @@
|
|||||||
const createAxiosFetcher = require('@spree/axios-fetcher/dist/server/index').default;
|
const createAxiosFetcher = require('@spree/axios-fetcher/dist/server/index').default;
|
||||||
import { makeClient } from '@spree/storefront-api-v2-sdk/dist/client';
|
import { makeClient } from '@spree/storefront-api-v2-sdk/dist/client';
|
||||||
|
|
||||||
const spree = makeClient({
|
const spreeClient = makeClient({
|
||||||
host: 'http://localhost:3000',
|
host: 'http://localhost:3000',
|
||||||
createFetcher: createAxiosFetcher
|
createFetcher: createAxiosFetcher
|
||||||
});
|
});
|
||||||
|
|
||||||
export default spree;
|
export default spreeClient;
|
@ -1,35 +0,0 @@
|
|||||||
import fetcher from './fetcher';
|
|
||||||
import { handler as useCart } from './cart/use-cart';
|
|
||||||
import { handler as useAddItem } from './cart/use-add-item';
|
|
||||||
import { handler as useUpdateItem } from './cart/use-update-item';
|
|
||||||
import { handler as useRemoveItem } from './cart/use-remove-item';
|
|
||||||
import { handler as useCustomer } from './customer/use-customer';
|
|
||||||
import { handler as useSearch } from './product/use-search';
|
|
||||||
import { handler as useLogin } from './auth/use-login';
|
|
||||||
import { handler as useLogout } from './auth/use-logout';
|
|
||||||
import { handler as useSignup } from './auth/use-signup';
|
|
||||||
import { handler as useCheckout } from './checkout/use-checkout';
|
|
||||||
import { handler as useWishlist } from './wishlist/use-wishlist';
|
|
||||||
import { handler as useWishlistAddItem } from './wishlist/use-add-item';
|
|
||||||
import { handler as useWishlistRemoveItem } from './wishlist/use-remove-item';
|
|
||||||
import { requireConfigValue } from './isomorphic-config';
|
|
||||||
|
|
||||||
const spreeProvider = {
|
|
||||||
locale: requireConfigValue('defaultLocale') as string,
|
|
||||||
cartCookie: requireConfigValue('cartCookieName') as string,
|
|
||||||
fetcher,
|
|
||||||
cart: { useCart, useAddItem, useUpdateItem, useRemoveItem },
|
|
||||||
customer: { useCustomer },
|
|
||||||
products: { useSearch },
|
|
||||||
auth: { useLogin, useLogout, useSignup },
|
|
||||||
checkout: { useCheckout },
|
|
||||||
wishlist: {
|
|
||||||
useWishlist,
|
|
||||||
useAddItem: useWishlistAddItem,
|
|
||||||
useRemoveItem: useWishlistRemoveItem
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
export { spreeProvider };
|
|
||||||
|
|
||||||
export type SpreeProvider = typeof spreeProvider;
|
|
21
lib/spree/types.ts
Normal file
21
lib/spree/types.ts
Normal file
@ -0,0 +1,21 @@
|
|||||||
|
export interface IOAuthToken {
|
||||||
|
access_token: string;
|
||||||
|
token_type: 'Bearer';
|
||||||
|
expires_in: number;
|
||||||
|
refresh_token: string;
|
||||||
|
created_at: number;
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface IToken {
|
||||||
|
order_token?: string;
|
||||||
|
bearer_token?: string;
|
||||||
|
}
|
||||||
|
|
||||||
|
export type ProductId = string | number;
|
||||||
|
|
||||||
|
export interface Product {
|
||||||
|
type: string;
|
||||||
|
id: string;
|
||||||
|
attributes: Object;
|
||||||
|
relationships: Object;
|
||||||
|
}
|
48
lib/spree/utils.ts
Normal file
48
lib/spree/utils.ts
Normal file
@ -0,0 +1,48 @@
|
|||||||
|
import { IOAuthToken } from '@spree/storefront-api-v2-sdk/dist/*';
|
||||||
|
import Cookies from 'js-cookie';
|
||||||
|
import { IToken } from './types';
|
||||||
|
|
||||||
|
export const getCartToken = () => Cookies.get('cartToken');
|
||||||
|
|
||||||
|
export const setCartToken = (cartToken: string) => {
|
||||||
|
const date = new Date();
|
||||||
|
const expires = date.setTime(date.getTime() + 30 * 24 * 60 * 60 * 1000);
|
||||||
|
const cookieOptions = { expires };
|
||||||
|
|
||||||
|
Cookies.set('cartToken', cartToken, cookieOptions);
|
||||||
|
};
|
||||||
|
|
||||||
|
export const removeCartToken = () => Cookies.remove('cartToken');
|
||||||
|
|
||||||
|
export const ensureIToken = (): IToken | undefined => {
|
||||||
|
const userTokenResponse = ensureUserTokenResponse();
|
||||||
|
|
||||||
|
if (userTokenResponse) return { bearer_token: userTokenResponse.access_token };
|
||||||
|
|
||||||
|
const cartToken = getCartToken();
|
||||||
|
|
||||||
|
if (cartToken) return { order_token: cartToken };
|
||||||
|
|
||||||
|
return undefined;
|
||||||
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Retrieves the saved user token response. If the response fails json parsing,
|
||||||
|
* removes the saved token and returns @type {undefined} instead.
|
||||||
|
*/
|
||||||
|
export const ensureUserTokenResponse = (): IOAuthToken | undefined => {};
|
||||||
|
|
||||||
|
export const getUserTokenResponse = (): IOAuthToken | undefined => {
|
||||||
|
const stringifiedToken = Cookies.get('userToken');
|
||||||
|
|
||||||
|
if (!stringifiedToken) return undefined;
|
||||||
|
|
||||||
|
try {
|
||||||
|
const token = JSON.parse(stringifiedToken);
|
||||||
|
return token;
|
||||||
|
} catch (parseError) {
|
||||||
|
throw 'Could not parse stored user token response.';
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
export const removeUserTokenResponse = () => Cookies.remove('userCookieName');
|
@ -1,3 +1,5 @@
|
|||||||
|
const path = require('path');
|
||||||
|
|
||||||
/** @type {import('next').NextConfig} */
|
/** @type {import('next').NextConfig} */
|
||||||
const baseConfig = {
|
const baseConfig = {
|
||||||
eslint: {
|
eslint: {
|
||||||
@ -14,6 +16,9 @@ const baseConfig = {
|
|||||||
}
|
}
|
||||||
]
|
]
|
||||||
},
|
},
|
||||||
|
sassOptions: {
|
||||||
|
includePaths: [path.join(__dirname, 'styles')]
|
||||||
|
},
|
||||||
async redirects() {
|
async redirects() {
|
||||||
return [
|
return [
|
||||||
{
|
{
|
||||||
|
@ -29,10 +29,12 @@
|
|||||||
"axios": "^1.7.1",
|
"axios": "^1.7.1",
|
||||||
"clsx": "^2.1.0",
|
"clsx": "^2.1.0",
|
||||||
"geist": "^1.3.0",
|
"geist": "^1.3.0",
|
||||||
|
"js-cookie": "^3.0.5",
|
||||||
"next": "14.2.2",
|
"next": "14.2.2",
|
||||||
"next-i18next": "^15.3.0",
|
"next-i18next": "^15.3.0",
|
||||||
"react": "18.2.0",
|
"react": "18.2.0",
|
||||||
"react-dom": "18.2.0"
|
"react-dom": "18.2.0",
|
||||||
|
"universal-cookie": "^7.1.4"
|
||||||
},
|
},
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
"@tailwindcss/container-queries": "^0.1.1",
|
"@tailwindcss/container-queries": "^0.1.1",
|
||||||
@ -50,6 +52,7 @@
|
|||||||
"postcss": "^8.4.38",
|
"postcss": "^8.4.38",
|
||||||
"prettier": "3.2.5",
|
"prettier": "3.2.5",
|
||||||
"prettier-plugin-tailwindcss": "^0.5.14",
|
"prettier-plugin-tailwindcss": "^0.5.14",
|
||||||
|
"sass": "^1.77.4",
|
||||||
"tailwindcss": "^3.4.3",
|
"tailwindcss": "^3.4.3",
|
||||||
"typescript": "5.4.5"
|
"typescript": "5.4.5"
|
||||||
}
|
}
|
||||||
|
11
styles/_buttons.scss
Normal file
11
styles/_buttons.scss
Normal file
@ -0,0 +1,11 @@
|
|||||||
|
.button {
|
||||||
|
padding: 10px 20px;
|
||||||
|
border: none;
|
||||||
|
border-radius: 5px;
|
||||||
|
cursor: pointer;
|
||||||
|
background-color: #0070f3;
|
||||||
|
}
|
||||||
|
|
||||||
|
nav {
|
||||||
|
display: block;
|
||||||
|
}
|
@ -1,6 +1,7 @@
|
|||||||
@tailwind base;
|
@import 'tailwindcss/components.css';
|
||||||
@tailwind components;
|
@import 'tailwindcss/utilities.css';
|
||||||
@tailwind utilities;
|
@import 'tailwindcss/base.css';
|
||||||
|
@import './buttons.scss';
|
||||||
|
|
||||||
@media (prefers-color-scheme: dark) {
|
@media (prefers-color-scheme: dark) {
|
||||||
html {
|
html {
|
@ -16,7 +16,8 @@
|
|||||||
"incremental": true,
|
"incremental": true,
|
||||||
"baseUrl": ".",
|
"baseUrl": ".",
|
||||||
"paths": {
|
"paths": {
|
||||||
"@commerce/*": ["lib/spree/*"]
|
"@commerce/*": ["lib/spree/*"],
|
||||||
|
"@styles/*": ["styles/*"]
|
||||||
},
|
},
|
||||||
"noUncheckedIndexedAccess": true,
|
"noUncheckedIndexedAccess": true,
|
||||||
"plugins": [
|
"plugins": [
|
||||||
|
Loading…
x
Reference in New Issue
Block a user