added redux support for cart and products. Need to update cart based on redux state and add cache
This commit is contained in:
parent
c046213dca
commit
86dd53cb28
Binary file not shown.
@ -13,6 +13,7 @@
|
||||
"@radix-ui/react-slot": "^1.1.0",
|
||||
"@react-email/components": "^0.0.22",
|
||||
"@react-email/tailwind": "^0.0.19",
|
||||
"@reduxjs/toolkit": "^2.3.0",
|
||||
"class-variance-authority": "^0.7.0",
|
||||
"clsx": "^2.1.1",
|
||||
"dotenv": "^16.4.5",
|
||||
@ -25,6 +26,8 @@
|
||||
"react": "^18.3.1",
|
||||
"react-dom": "^18.3.1",
|
||||
"react-icons": "^5.3.0",
|
||||
"react-redux": "^9.1.2",
|
||||
"redux": "^5.0.1",
|
||||
"resend": "^3.5.0",
|
||||
"sass": "^1.80.5",
|
||||
"sharp": "^0.33.5",
|
||||
|
||||
18
frontend/src/app/StoreProvider.tsx
Normal file
18
frontend/src/app/StoreProvider.tsx
Normal file
@ -0,0 +1,18 @@
|
||||
'use client'
|
||||
import { useRef } from 'react'
|
||||
import { Provider } from 'react-redux'
|
||||
import { makeStore, AppStore } from '../lib/Store'
|
||||
|
||||
export default function StoreProvider({
|
||||
children
|
||||
}: {
|
||||
children: React.ReactNode
|
||||
}) {
|
||||
const storeRef = useRef<AppStore>()
|
||||
if (!storeRef.current) {
|
||||
// Create the store instance the first time this renders
|
||||
storeRef.current = makeStore()
|
||||
}
|
||||
|
||||
return <Provider store={storeRef.current}>{children}</Provider>
|
||||
}
|
||||
@ -3,6 +3,7 @@ import { Inter } from "next/font/google";
|
||||
import "./globals.css";
|
||||
import Navbar from "@/components/Navbar/Navbar";
|
||||
import Footer from "@/components/Footer/Footer";
|
||||
import StoreProvider from "./StoreProvider";
|
||||
|
||||
|
||||
const inter = Inter({ subsets: ["latin"] });
|
||||
@ -18,12 +19,14 @@ export default function RootLayout({
|
||||
children: React.ReactNode;
|
||||
}>) {
|
||||
return (
|
||||
<html lang="en">
|
||||
<body className={`${inter.className} bg-slate-700 w-full max-h-screen text-white`}>
|
||||
<Navbar />
|
||||
{children}
|
||||
<Footer />
|
||||
<StoreProvider>
|
||||
<html lang="en">
|
||||
<body className={`${inter.className} bg-slate-700 w-full max-h-screen text-white`}>
|
||||
<Navbar />
|
||||
{children}
|
||||
<Footer />
|
||||
</body>
|
||||
</html>
|
||||
</html>
|
||||
</StoreProvider>
|
||||
);
|
||||
}
|
||||
|
||||
40
frontend/src/app/redux/CartReducer.ts
Normal file
40
frontend/src/app/redux/CartReducer.ts
Normal file
@ -0,0 +1,40 @@
|
||||
import { createSlice } from "@reduxjs/toolkit";
|
||||
|
||||
export interface Product {
|
||||
id: number;
|
||||
title: string;
|
||||
price: number;
|
||||
quantity: number;
|
||||
desc: string;
|
||||
image: string;
|
||||
}
|
||||
|
||||
|
||||
const initialState = {
|
||||
products: [],
|
||||
}
|
||||
|
||||
export const cartSlice = createSlice({
|
||||
name: "cart",
|
||||
initialState,
|
||||
reducers: {
|
||||
addToCart: (state: any, action: any) => {
|
||||
const item = state.products.find((p:Product) => p.id === action.payload.id);
|
||||
if(item) {
|
||||
item.quantity += action.payload.quantity;
|
||||
} else {
|
||||
state.products.push(action.payload);
|
||||
}
|
||||
},
|
||||
removeFromCart: (state, action) => {
|
||||
state.products = state.products.filter((p:Product) => p.id !== action.payload);
|
||||
},
|
||||
resetCart: (state) => {
|
||||
state.products = [];
|
||||
}
|
||||
},
|
||||
});
|
||||
|
||||
export const { addToCart, removeFromCart, resetCart } = cartSlice.actions;
|
||||
|
||||
export default cartSlice.reducer;
|
||||
37
frontend/src/app/redux/slice.ts
Normal file
37
frontend/src/app/redux/slice.ts
Normal file
@ -0,0 +1,37 @@
|
||||
import { createSlice, PayloadAction } from '@reduxjs/toolkit'
|
||||
import type { RootState } from '@/lib/Store'
|
||||
|
||||
// Define a type for the slice state
|
||||
export interface CounterState {
|
||||
value: number
|
||||
}
|
||||
|
||||
// Define the initial state using that type
|
||||
const initialState: CounterState = {
|
||||
value: 0
|
||||
}
|
||||
|
||||
export const counterSlice = createSlice({
|
||||
name: 'counter',
|
||||
// `createSlice` will infer the state type from the `initialState` argument
|
||||
initialState,
|
||||
reducers: {
|
||||
increment: state => {
|
||||
state.value += 1
|
||||
},
|
||||
decrement: state => {
|
||||
state.value -= 1
|
||||
},
|
||||
// Use the PayloadAction type to declare the contents of `action.payload`
|
||||
incrementByAmount: (state, action: PayloadAction<number>) => {
|
||||
state.value += action.payload
|
||||
}
|
||||
}
|
||||
})
|
||||
|
||||
export const { increment, decrement, incrementByAmount } = counterSlice.actions
|
||||
|
||||
// Other code such as selectors can use the imported `RootState` type
|
||||
export const selectCount = (state: RootState) => state.counter.value
|
||||
|
||||
export default counterSlice.reducer
|
||||
@ -4,35 +4,43 @@ import Image from "next/image";
|
||||
import { MdDeleteOutline } from "react-icons/md";
|
||||
import { Button } from "@/components/ui/button";
|
||||
import "./CartTab.css";
|
||||
import { useSelector } from "react-redux";
|
||||
import type { RootState } from "@/lib/Store";
|
||||
|
||||
|
||||
import type { Product } from "@/app/redux/CartReducer";
|
||||
|
||||
const CartTab = () => {
|
||||
const cartItems = true;
|
||||
|
||||
const data = [
|
||||
{
|
||||
id: 1,
|
||||
title: "Product 1",
|
||||
oldPrice: 19,
|
||||
price: 12,
|
||||
isNew: true,
|
||||
desc: "Short sleeve t-shirt",
|
||||
image:
|
||||
"https://ih1.redbubble.net/image.2997405810.0746/ssrco,slim_fit_t_shirt,womens,101010:01c5ca27c6,front,square_product,600x600.jpg",
|
||||
},
|
||||
{
|
||||
id: 2,
|
||||
title: "Product 2",
|
||||
oldPrice: 19,
|
||||
price: 12,
|
||||
isNew: false,
|
||||
desc: "Short sleeve t-shirt",
|
||||
image:
|
||||
"https://ih1.redbubble.net/image.2997405810.0746/ssrco,slim_fit_t_shirt,flatlay,101010:01c5ca27c6,front,wide_portrait,750x1000-bg,f8f8f8.jpg",
|
||||
},
|
||||
];
|
||||
// get the products from the cart
|
||||
const products = useSelector((state: RootState) => state.cart.products);
|
||||
|
||||
// const data = [
|
||||
// {
|
||||
// id: 1,
|
||||
// title: "Product 1",
|
||||
// oldPrice: 19,
|
||||
// price: 12,
|
||||
// isNew: true,
|
||||
// desc: "Short sleeve t-shirt",
|
||||
// image:
|
||||
// "https://ih1.redbubble.net/image.2997405810.0746/ssrco,slim_fit_t_shirt,womens,101010:01c5ca27c6,front,square_product,600x600.jpg",
|
||||
// },
|
||||
// {
|
||||
// id: 2,
|
||||
// title: "Product 2",
|
||||
// oldPrice: 19,
|
||||
// price: 12,
|
||||
// isNew: false,
|
||||
// desc: "Short sleeve t-shirt",
|
||||
// image:
|
||||
// "https://ih1.redbubble.net/image.2997405810.0746/ssrco,slim_fit_t_shirt,flatlay,101010:01c5ca27c6,front,wide_portrait,750x1000-bg,f8f8f8.jpg",
|
||||
// },
|
||||
// ];
|
||||
return (
|
||||
<div className="cart absolute p-5 text-slate-600 top-28 right-0">
|
||||
{data?.map((item) => (
|
||||
{products?.map((item: Product) => (
|
||||
<div className="item flex gap-5 items-center mb-[30px] " key={item.id}>
|
||||
<div className="relative w-[80px] h-[100px]">
|
||||
<Image
|
||||
@ -46,7 +54,7 @@ const CartTab = () => {
|
||||
<div className="details">
|
||||
<h1>{item.title}</h1>
|
||||
<p className="text-sm">{item.desc?.substring(0, 100)}</p>
|
||||
<div className="price">1 x ${item.price}</div>
|
||||
<div className="price">{item.quantity} x ${item.price}</div>
|
||||
</div>
|
||||
<MdDeleteOutline className="delete-icon mt-16" />
|
||||
</div>
|
||||
|
||||
@ -9,6 +9,11 @@ import { MdFavoriteBorder } from "react-icons/md";
|
||||
import { FaBalanceScale } from "react-icons/fa";
|
||||
import { FaRegStar } from "react-icons/fa";
|
||||
import { FaStar } from "react-icons/fa";
|
||||
import { useSelector, useDispatch } from "react-redux";
|
||||
import { RootState } from "@/lib/Store";
|
||||
import { addToCart } from "@/app/redux/CartReducer";
|
||||
|
||||
|
||||
|
||||
const ProductPage = ({ productId }: any) => {
|
||||
// console.log(productId);
|
||||
@ -28,6 +33,9 @@ const ProductPage = ({ productId }: any) => {
|
||||
const [salePrice, setSalePrice] = useState(0);
|
||||
const [category, setCategory] = useState('');
|
||||
|
||||
// Redux
|
||||
const dispatch = useDispatch()
|
||||
|
||||
|
||||
useEffect(() => {
|
||||
const fetchData = async () => {
|
||||
@ -76,7 +84,6 @@ const ProductPage = ({ productId }: any) => {
|
||||
|
||||
</div>
|
||||
<div className="product-image">
|
||||
|
||||
</div>
|
||||
</div> */}
|
||||
<div className="mainImage">
|
||||
@ -103,18 +110,34 @@ const ProductPage = ({ productId }: any) => {
|
||||
<Button
|
||||
className="bg-slate-600 text-xl"
|
||||
onClick={() => setQuantity((prev) => (prev === 1 ? 1 : prev - 1))}
|
||||
// onClick={() => dispatch(decrement())}
|
||||
// aria-label="Decrement value"
|
||||
>
|
||||
-
|
||||
</Button>
|
||||
<div>{quantity}</div>
|
||||
{/* <div>{count}</div> */}
|
||||
<Button
|
||||
className="bg-slate-600 text-lg"
|
||||
onClick={() => setQuantity((prev) => prev + 1)}
|
||||
// aria-label="Increment value"
|
||||
// onClick={() => dispatch(increment())}
|
||||
>
|
||||
+
|
||||
</Button>
|
||||
</div>
|
||||
<Button className="add bg-[#F45B1E] font-bold">
|
||||
<Button className="add bg-[#F45B1E] font-bold"
|
||||
onClick={() => {
|
||||
dispatch(addToCart({
|
||||
id: productId,
|
||||
title: name,
|
||||
price: price,
|
||||
desc: desc,
|
||||
image: product,
|
||||
quantity: quantity
|
||||
}))
|
||||
}}
|
||||
>
|
||||
<MdOutlineAddShoppingCart /> ADD TO CART
|
||||
</Button>
|
||||
<div className="link">
|
||||
|
||||
16
frontend/src/lib/Store.ts
Normal file
16
frontend/src/lib/Store.ts
Normal file
@ -0,0 +1,16 @@
|
||||
import { configureStore } from '@reduxjs/toolkit'
|
||||
import counterReducer from '@/app/redux/slice'
|
||||
import cartReducer from '@/app/redux/CartReducer'
|
||||
export const makeStore = () => {
|
||||
return configureStore({
|
||||
reducer: {
|
||||
cart: cartReducer,
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
// Infer the type of makeStore
|
||||
export type AppStore = ReturnType<typeof makeStore>
|
||||
// Infer the `RootState` and `AppDispatch` types from the store itself
|
||||
export type RootState = ReturnType<AppStore['getState']>
|
||||
export type AppDispatch = AppStore['dispatch']
|
||||
7
frontend/src/lib/hooks.ts
Normal file
7
frontend/src/lib/hooks.ts
Normal file
@ -0,0 +1,7 @@
|
||||
import { useDispatch, useSelector, useStore } from 'react-redux'
|
||||
import type { AppDispatch, AppStore, RootState } from './Store'
|
||||
|
||||
// Use throughout your app instead of plain `useDispatch` and `useSelector`
|
||||
export const useAppDispatch = useDispatch.withTypes<AppDispatch>()
|
||||
export const useAppSelector = useSelector.withTypes<RootState>()
|
||||
export const useAppStore = useStore.withTypes<AppStore>()
|
||||
@ -21,6 +21,6 @@
|
||||
"@/*": ["./src/*"]
|
||||
}
|
||||
},
|
||||
"include": ["next-env.d.ts", "**/*.ts", "**/*.tsx", ".next/types/**/*.ts"],
|
||||
"include": ["next-env.d.ts", "**/*.ts", "**/*.tsx", ".next/types/**/*.ts", "src/app/StoreProvider.ts", "src/app/redux/store.ts"],
|
||||
"exclude": ["node_modules"]
|
||||
}
|
||||
|
||||
Loading…
Reference in New Issue
Block a user