tests pages

This commit is contained in:
Timur Suleymanov 2024-06-04 23:59:54 +05:00
parent d827e1232b
commit 83ae628672
19 changed files with 235 additions and 68 deletions

View File

@ -1,4 +1,4 @@
import spree from '@commerce/index';
import spree from '@commerce/client';
import DiseasesAndConditions from 'components/diseases/diseases-and-conditions';
import Specialities from 'components/diseases/specialities';
import { cache } from 'react';

View File

@ -1,7 +1,7 @@
import '@styles/globals.scss';
import Footer from 'components/layout/footer';
import Navbar from 'components/layout/navbar';
import { ReactNode } from 'react';
import './globals.css';
export default async function RootLayout({ children }: { children: ReactNode }) {
return (

View File

@ -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 (
<div>
<h1>Test Detail for ID:</h1>
{/* Render test details based on the ID */}
<button>Add to cart</button>
<h1 className="mb-4 text-4xl font-extrabold leading-none tracking-tight text-gray-900 md:text-5xl lg:text-6xl dark:text-white">
Test Detail for ID: {product.id}
</h1>
<p className="mb-4 text-lg text-gray-600 dark:text-gray-400">{product.attributes.name}</p>
<CartButton id={product.id} />
</div>
);
}

View File

@ -1,19 +1,9 @@
import spree from '@commerce/index';
import commerceApi from '@commerce/api';
import Alphabet from 'components/tests/alphabet';
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() {
const products = await getProducts();
const products = await commerceApi.getProducts();
return (
<div>

View File

@ -1,15 +1,25 @@
'use client';
import { addProductToCart } from '@commerce/api/cart';
import { useFormState, useFormStatus } from 'react-dom';
interface Props {
id: number;
id: number | string;
}
const CartButton: React.FC<Props> = ({ id }) => {
const handleClick = () => {
console.log('click', id);
};
const handleClick = async () => await addProductToCart(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;

View File

@ -1,10 +1,11 @@
import { ProductAttr } from '@spree/storefront-api-v2-sdk/dist/*';
import Link from 'next/link';
import CartButton from './cart-button';
export default function TestsTable({ products = [] }) {
export default function TestsTable({ products }: { products: ProductAttr[] }) {
return (
<div>
<table>
<table className="table w-full border-collapse border">
<thead>
<tr>
<th>Number</th>

View File

57
lib/spree/api/cart.ts Normal file
View 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
View 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
View 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;
};

View File

@ -1,9 +1,9 @@
const createAxiosFetcher = require('@spree/axios-fetcher/dist/server/index').default;
import { makeClient } from '@spree/storefront-api-v2-sdk/dist/client';
const spree = makeClient({
const spreeClient = makeClient({
host: 'http://localhost:3000',
createFetcher: createAxiosFetcher
});
export default spree;
export default spreeClient;

View File

@ -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
View 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
View 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');

View File

@ -1,3 +1,5 @@
const path = require('path');
/** @type {import('next').NextConfig} */
const baseConfig = {
eslint: {
@ -14,6 +16,9 @@ const baseConfig = {
}
]
},
sassOptions: {
includePaths: [path.join(__dirname, 'styles')]
},
async redirects() {
return [
{

View File

@ -29,10 +29,12 @@
"axios": "^1.7.1",
"clsx": "^2.1.0",
"geist": "^1.3.0",
"js-cookie": "^3.0.5",
"next": "14.2.2",
"next-i18next": "^15.3.0",
"react": "18.2.0",
"react-dom": "18.2.0"
"react-dom": "18.2.0",
"universal-cookie": "^7.1.4"
},
"devDependencies": {
"@tailwindcss/container-queries": "^0.1.1",
@ -50,6 +52,7 @@
"postcss": "^8.4.38",
"prettier": "3.2.5",
"prettier-plugin-tailwindcss": "^0.5.14",
"sass": "^1.77.4",
"tailwindcss": "^3.4.3",
"typescript": "5.4.5"
}

11
styles/_buttons.scss Normal file
View File

@ -0,0 +1,11 @@
.button {
padding: 10px 20px;
border: none;
border-radius: 5px;
cursor: pointer;
background-color: #0070f3;
}
nav {
display: block;
}

View File

@ -1,6 +1,7 @@
@tailwind base;
@tailwind components;
@tailwind utilities;
@import 'tailwindcss/components.css';
@import 'tailwindcss/utilities.css';
@import 'tailwindcss/base.css';
@import './buttons.scss';
@media (prefers-color-scheme: dark) {
html {

View File

@ -16,7 +16,8 @@
"incremental": true,
"baseUrl": ".",
"paths": {
"@commerce/*": ["lib/spree/*"]
"@commerce/*": ["lib/spree/*"],
"@styles/*": ["styles/*"]
},
"noUncheckedIndexedAccess": true,
"plugins": [