Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
33 changes: 33 additions & 0 deletions components/Cart/CartBar.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,33 @@
/* eslint-disable jsx-a11y/anchor-is-valid */
import Link from 'next/link';
import { useCartState } from './CartContext';

export function CartBar() {
const cartState = useCartState();

return (
<Link
href="/cart"
className="block border-b-4 border-transparent p-6 hover:border-red-700"
>
<a className="inline-flex">
{cartState.items.length}
<svg
className="h-4 w-4"
fill="none"
viewBox="0 0 24 24"
stroke="currentColor"
xmlns="http://www.w3.org/2000/svg"
>
<path
strokeLinecap="round"
strokeLinejoin="round"
strokeWidth="2"
d="M16 11V7a4 4 0 00-8 0v4M5 9h14l1 12H4L5 9z"
/>
</svg>
<span className="sr-only">Cart</span>
</a>
</Link>
);
}
61 changes: 61 additions & 0 deletions components/Cart/CartContext.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,61 @@
import {
createContext,
ReactNode,
useContext,
useState,
} from 'react';

interface ICartItem {
price: number;
title: string;
count: number;
}

interface ICartState {
items: readonly ICartItem[];
addItemToCart: (item: ICartItem) => void;
}

export const CartStateContext = createContext<ICartState | null>(null);

export function CartStateContextProvider({
children,
}: {
children: ReactNode;
}) {
const [cartItems, setCartItems] = useState<ICartItem[]>([]);

return (
<CartStateContext.Provider
// eslint-disable-next-line react/jsx-no-constructed-context-values
value={{
items: cartItems,
addItemToCart: (item) => {
setCartItems((prevState) => {
const existingItem = prevState.find(
(elo) => elo.title === item.title,
);
if (!existingItem) {
return [...prevState, item];
}
// eslint-disable-next-line @typescript-eslint/no-shadow
return prevState.map((existingItem) => (existingItem.title === item.title
? { ...existingItem, count: existingItem.count + 1 }
: existingItem));
});
},
}}
>
{children}
</CartStateContext.Provider>
);
}

export function useCartState() {
const cartState = useContext(CartStateContext);
if (!cartState) {
throw new Error('You forgot CartStateContextProvider!');
}

return cartState;
}
25 changes: 2 additions & 23 deletions components/Header.tsx
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
/* eslint-disable jsx-a11y/anchor-is-valid */
import Link from 'next/link';
import { CartBar } from './Cart/CartBar';

export function Header() {
return (
Expand Down Expand Up @@ -69,29 +70,7 @@ export function Header() {
</nav>
<div className="ml-8 flex items-center">
<div className="flex items-center divide-x divide-gray-100 border-x border-gray-100">
<span>
<a
href="/cart"
className="block border-b-4 border-transparent p-6 hover:border-red-700"
>
<svg
className="h-4 w-4"
fill="none"
viewBox="0 0 24 24"
stroke="currentColor"
xmlns="http://www.w3.org/2000/svg"
>
<path
strokeLinecap="round"
strokeLinejoin="round"
strokeWidth="2"
d="M16 11V7a4 4 0 00-8 0v4M5 9h14l1 12H4L5 9z"
/>
</svg>

<span className="sr-only">Cart</span>
</a>
</span>
<CartBar />
<span>
<a
href="/account"
Expand Down
27 changes: 21 additions & 6 deletions components/Product.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@ import { NextSeo } from 'next-seo';
import { MDXRemote } from 'next-mdx-remote';
import { Raiting } from './Raiting';
import { MarkdownResult } from '../utils';
import { useCartState } from './Cart/CartContext';

interface IProductDetails {
id: number;
Expand Down Expand Up @@ -53,6 +54,7 @@ interface IProductListItemProps {
}

export function ProductListItem({ data }: IProductListItemProps) {
const cartState = useCartState();
return (
<>
<div className="bg-white p-4">
Expand All @@ -65,12 +67,25 @@ export function ProductListItem({ data }: IProductListItemProps) {
objectFit="contain"
/>
</div>
<Link href={`/products/${data.id}`}>
<a>
<h2 className="p-4 text-3xl font-bold">{data.title}</h2>
<p className="p-4">{data.description}</p>
</a>
</Link>
<div className="p-4">
<Link href={`/products/${data.id}`}>
<a>
<h2 className="p-4 text-3xl font-bold">{data.title}</h2>
<p className="p-4">{data.description}</p>
</a>
</Link>
<button
type="button"
onClick={() => cartState.addItemToCart({
price: 21.37,
title: data.title,
count: 1,
})}
className="inline-block rounded border border-indigo-600 bg-indigo-600 px-12 py-3 text-sm font-medium text-white hover:bg-transparent hover:text-indigo-600 focus:outline-none focus:ring active:text-indigo-500"
>
Add to Cart
</button>
</div>
</>
);
}
15 changes: 9 additions & 6 deletions pages/_app.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -4,17 +4,20 @@ import { QueryClient, QueryClientProvider } from '@tanstack/react-query';
import { DefaultSeo } from 'next-seo';
import SEO from '../next-seo.config';
import Layout from '../components/Layout';
import { CartStateContextProvider } from '../components/Cart/CartContext';

const queryClient = new QueryClient();

function MyApp({ Component, pageProps }: AppProps) {
return (
<Layout>
<DefaultSeo {...SEO} />
<QueryClientProvider client={queryClient}>
<Component {...pageProps} />
</QueryClientProvider>
</Layout>
<CartStateContextProvider>
<Layout>
<DefaultSeo {...SEO} />
<QueryClientProvider client={queryClient}>
<Component {...pageProps} />
</QueryClientProvider>
</Layout>
</CartStateContextProvider>
);
}

Expand Down
42 changes: 42 additions & 0 deletions pages/cart.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,42 @@
/* eslint-disable react/jsx-one-expression-per-line */
import { useCartState } from '../components/Cart/CartContext';

function CartContent() {
const cartState = useCartState();

return (
<div className="col-span-2">
<ul className="divide-y divide-gray-200">
{cartState.items.map((item) => (
<li key={`${item.title}`} className="py-4 flex justify-between">
<div>{item.count} x {item.title}</div>
<div>{item.price}
<button type="button" className="ml-4 text-red-500"> Delete </button>
</div>
</li>
))}
</ul>
</div>
);
}

function CartSummary() {
const cartState = useCartState();

return (
<div> Summary: {cartState.items.length}</div>
);
}

function CartPage() {
return (
<div className="max-w-5xl mx-auto p-4">
<div className="grid grid-cols-3 gap-8">
<CartContent />
<CartSummary />
</div>
</div>
);
}

export default CartPage;
17 changes: 1 addition & 16 deletions pages/products/[productId].tsx
Original file line number Diff line number Diff line change
Expand Up @@ -3,22 +3,7 @@ import { GetStaticPropsContext, InferGetStaticPropsType } from 'next';
import { serialize } from 'next-mdx-remote/serialize';
import Link from 'next/link';
import { ProductDetails } from '../../components/Product';
import { TInferGetStaticPathsType } from '../../types';
import { MarkdownResult } from '../../utils';

export interface IStoreApiResponse {
id: number;
title: string;
price: number;
description: string;
category: string;
image: string;
longDescription: MarkdownResult;
rating: {
rate: number;
count: number;
};
}
import { IStoreApiResponse, TInferGetStaticPathsType } from '../../types';

export async function getStaticPaths() {
const res = await fetch('https://naszsklep-api.vercel.app/api/products');
Expand Down