added redux support for cart and products. Need to update cart based on redux state and add cache

This commit is contained in:
Jacob Delgado 2024-11-24 22:38:28 -08:00
parent c046213dca
commit 86dd53cb28
11 changed files with 188 additions and 33 deletions

Binary file not shown.

View File

@ -13,6 +13,7 @@
"@radix-ui/react-slot": "^1.1.0", "@radix-ui/react-slot": "^1.1.0",
"@react-email/components": "^0.0.22", "@react-email/components": "^0.0.22",
"@react-email/tailwind": "^0.0.19", "@react-email/tailwind": "^0.0.19",
"@reduxjs/toolkit": "^2.3.0",
"class-variance-authority": "^0.7.0", "class-variance-authority": "^0.7.0",
"clsx": "^2.1.1", "clsx": "^2.1.1",
"dotenv": "^16.4.5", "dotenv": "^16.4.5",
@ -25,6 +26,8 @@
"react": "^18.3.1", "react": "^18.3.1",
"react-dom": "^18.3.1", "react-dom": "^18.3.1",
"react-icons": "^5.3.0", "react-icons": "^5.3.0",
"react-redux": "^9.1.2",
"redux": "^5.0.1",
"resend": "^3.5.0", "resend": "^3.5.0",
"sass": "^1.80.5", "sass": "^1.80.5",
"sharp": "^0.33.5", "sharp": "^0.33.5",

View 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>
}

View File

@ -3,6 +3,7 @@ import { Inter } from "next/font/google";
import "./globals.css"; import "./globals.css";
import Navbar from "@/components/Navbar/Navbar"; import Navbar from "@/components/Navbar/Navbar";
import Footer from "@/components/Footer/Footer"; import Footer from "@/components/Footer/Footer";
import StoreProvider from "./StoreProvider";
const inter = Inter({ subsets: ["latin"] }); const inter = Inter({ subsets: ["latin"] });
@ -18,6 +19,7 @@ export default function RootLayout({
children: React.ReactNode; children: React.ReactNode;
}>) { }>) {
return ( return (
<StoreProvider>
<html lang="en"> <html lang="en">
<body className={`${inter.className} bg-slate-700 w-full max-h-screen text-white`}> <body className={`${inter.className} bg-slate-700 w-full max-h-screen text-white`}>
<Navbar /> <Navbar />
@ -25,5 +27,6 @@ export default function RootLayout({
<Footer /> <Footer />
</body> </body>
</html> </html>
</StoreProvider>
); );
} }

View 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;

View 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

View File

@ -4,35 +4,43 @@ import Image from "next/image";
import { MdDeleteOutline } from "react-icons/md"; import { MdDeleteOutline } from "react-icons/md";
import { Button } from "@/components/ui/button"; import { Button } from "@/components/ui/button";
import "./CartTab.css"; import "./CartTab.css";
import { useSelector } from "react-redux";
import type { RootState } from "@/lib/Store";
import type { Product } from "@/app/redux/CartReducer";
const CartTab = () => { const CartTab = () => {
const cartItems = true; const cartItems = true;
const data = [ // get the products from the cart
{ const products = useSelector((state: RootState) => state.cart.products);
id: 1,
title: "Product 1", // const data = [
oldPrice: 19, // {
price: 12, // id: 1,
isNew: true, // title: "Product 1",
desc: "Short sleeve t-shirt", // oldPrice: 19,
image: // price: 12,
"https://ih1.redbubble.net/image.2997405810.0746/ssrco,slim_fit_t_shirt,womens,101010:01c5ca27c6,front,square_product,600x600.jpg", // isNew: true,
}, // desc: "Short sleeve t-shirt",
{ // image:
id: 2, // "https://ih1.redbubble.net/image.2997405810.0746/ssrco,slim_fit_t_shirt,womens,101010:01c5ca27c6,front,square_product,600x600.jpg",
title: "Product 2", // },
oldPrice: 19, // {
price: 12, // id: 2,
isNew: false, // title: "Product 2",
desc: "Short sleeve t-shirt", // oldPrice: 19,
image: // price: 12,
"https://ih1.redbubble.net/image.2997405810.0746/ssrco,slim_fit_t_shirt,flatlay,101010:01c5ca27c6,front,wide_portrait,750x1000-bg,f8f8f8.jpg", // 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 ( return (
<div className="cart absolute p-5 text-slate-600 top-28 right-0"> <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="item flex gap-5 items-center mb-[30px] " key={item.id}>
<div className="relative w-[80px] h-[100px]"> <div className="relative w-[80px] h-[100px]">
<Image <Image
@ -46,7 +54,7 @@ const CartTab = () => {
<div className="details"> <div className="details">
<h1>{item.title}</h1> <h1>{item.title}</h1>
<p className="text-sm">{item.desc?.substring(0, 100)}</p> <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> </div>
<MdDeleteOutline className="delete-icon mt-16" /> <MdDeleteOutline className="delete-icon mt-16" />
</div> </div>

View File

@ -9,6 +9,11 @@ import { MdFavoriteBorder } from "react-icons/md";
import { FaBalanceScale } from "react-icons/fa"; import { FaBalanceScale } from "react-icons/fa";
import { FaRegStar } from "react-icons/fa"; import { FaRegStar } from "react-icons/fa";
import { FaStar } 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) => { const ProductPage = ({ productId }: any) => {
// console.log(productId); // console.log(productId);
@ -28,6 +33,9 @@ const ProductPage = ({ productId }: any) => {
const [salePrice, setSalePrice] = useState(0); const [salePrice, setSalePrice] = useState(0);
const [category, setCategory] = useState(''); const [category, setCategory] = useState('');
// Redux
const dispatch = useDispatch()
useEffect(() => { useEffect(() => {
const fetchData = async () => { const fetchData = async () => {
@ -76,7 +84,6 @@ const ProductPage = ({ productId }: any) => {
</div> </div>
<div className="product-image"> <div className="product-image">
</div> </div>
</div> */} </div> */}
<div className="mainImage"> <div className="mainImage">
@ -103,18 +110,34 @@ const ProductPage = ({ productId }: any) => {
<Button <Button
className="bg-slate-600 text-xl" className="bg-slate-600 text-xl"
onClick={() => setQuantity((prev) => (prev === 1 ? 1 : prev - 1))} onClick={() => setQuantity((prev) => (prev === 1 ? 1 : prev - 1))}
// onClick={() => dispatch(decrement())}
// aria-label="Decrement value"
> >
- -
</Button> </Button>
<div>{quantity}</div> <div>{quantity}</div>
{/* <div>{count}</div> */}
<Button <Button
className="bg-slate-600 text-lg" className="bg-slate-600 text-lg"
onClick={() => setQuantity((prev) => prev + 1)} onClick={() => setQuantity((prev) => prev + 1)}
// aria-label="Increment value"
// onClick={() => dispatch(increment())}
> >
+ +
</Button> </Button>
</div> </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 <MdOutlineAddShoppingCart /> ADD TO CART
</Button> </Button>
<div className="link"> <div className="link">

16
frontend/src/lib/Store.ts Normal file
View 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']

View 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>()

View File

@ -21,6 +21,6 @@
"@/*": ["./src/*"] "@/*": ["./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"] "exclude": ["node_modules"]
} }