tt
This commit is contained in:
2
db.json
2
db.json
@@ -79,7 +79,7 @@
|
|||||||
"id": "c06c",
|
"id": "c06c",
|
||||||
"productId": "d317",
|
"productId": "d317",
|
||||||
"userId": "1",
|
"userId": "1",
|
||||||
"quantity": 2
|
"quantity": 48
|
||||||
}
|
}
|
||||||
]
|
]
|
||||||
}
|
}
|
||||||
@@ -21,7 +21,7 @@ export default function App() {
|
|||||||
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}, [userStore.data, userStore.loading])
|
}, [userStore.data, userStore.loading, userStore.reloadCart])
|
||||||
return (
|
return (
|
||||||
<>
|
<>
|
||||||
{
|
{
|
||||||
|
|||||||
@@ -14,12 +14,12 @@ export const CartApi = {
|
|||||||
let resultExisted = await axios.get(`${import.meta.env.VITE_SV_HOST}/carts?productId=${data.productId}`)
|
let resultExisted = await axios.get(`${import.meta.env.VITE_SV_HOST}/carts?productId=${data.productId}`)
|
||||||
if (!resultExisted.data[0]) {
|
if (!resultExisted.data[0]) {
|
||||||
/* thêm mới */
|
/* thêm mới */
|
||||||
await axios.post(`${import.meta.env.VITE_SV_HOST}/carts`, {
|
let addRes = await axios.post(`${import.meta.env.VITE_SV_HOST}/carts`, {
|
||||||
...data,
|
...data,
|
||||||
quantity: 1
|
quantity: 1
|
||||||
})
|
})
|
||||||
|
|
||||||
return
|
return addRes.data
|
||||||
}
|
}
|
||||||
|
|
||||||
/* cập nhật */
|
/* cập nhật */
|
||||||
@@ -27,6 +27,6 @@ export const CartApi = {
|
|||||||
quantity: resultExisted.data[0].quantity + 1
|
quantity: resultExisted.data[0].quantity + 1
|
||||||
})
|
})
|
||||||
|
|
||||||
return true
|
return resultUpdate.data
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -1,4 +1,4 @@
|
|||||||
import React from 'react'
|
import React, { useEffect, useState } from 'react'
|
||||||
import { useSelector } from 'react-redux'
|
import { useSelector } from 'react-redux'
|
||||||
import type { StoreType } from '../../../stores'
|
import type { StoreType } from '../../../stores'
|
||||||
import type { CartItem } from '../../../types/cart.type'
|
import type { CartItem } from '../../../types/cart.type'
|
||||||
@@ -7,33 +7,98 @@ import type { Product } from '../../../types/product.type'
|
|||||||
|
|
||||||
export default function Cart() {
|
export default function Cart() {
|
||||||
const userStore = useSelector((store: StoreType) => store.user)
|
const userStore = useSelector((store: StoreType) => store.user)
|
||||||
|
const [products, setProducts] = useState<Product[]>([])
|
||||||
|
|
||||||
//
|
// Lấy thông tin product cho từng item trong giỏ
|
||||||
//đây là api lấy product id let data = Apis.product.findProductById(productId)
|
useEffect(() => {
|
||||||
// data có interface là Product
|
async function fetchProducts() {
|
||||||
// cho bạn interface product luôn
|
let temsArr: Product[] = []
|
||||||
/*
|
for (const item of userStore.cart || []) {
|
||||||
export interface Product {
|
const res = await Apis.product.findProductById(item.productId)
|
||||||
id: string;
|
temsArr.push(res)
|
||||||
categoryId: string;
|
|
||||||
price: number;
|
|
||||||
iconUrl: string;
|
|
||||||
isActive: boolean;
|
|
||||||
name: string;
|
|
||||||
des: string;
|
|
||||||
}
|
}
|
||||||
|
setProducts(temsArr)
|
||||||
|
}
|
||||||
|
fetchProducts()
|
||||||
|
}, [userStore.cart])
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
// Tính tổng tiền
|
||||||
|
const total = userStore.cart?.reduce((sum, item) => {
|
||||||
|
const p = products[item.productId]
|
||||||
|
return sum + (p ? p.price * item.quantity : 0)
|
||||||
|
}, 0) || 0
|
||||||
|
|
||||||
*/
|
|
||||||
return (
|
return (
|
||||||
<div>
|
<div className="max-w-4xl mx-auto p-6">
|
||||||
Cart Page
|
<h1 className="text-2xl font-bold mb-6 text-center">🛒 Giỏ hàng của bạn</h1>
|
||||||
<ul>
|
|
||||||
{
|
{userStore.cart?.length === 0 ? (
|
||||||
userStore.cart?.map((item: CartItem) => {
|
<p className="text-center text-gray-500">Giỏ hàng trống.</p>
|
||||||
return <li key={item.id}>{item.productId} - {item.quantity}</li>
|
) : (
|
||||||
})
|
<div className="bg-white shadow-md rounded-2xl p-4">
|
||||||
}
|
<ul className="divide-y divide-gray-200">
|
||||||
|
{userStore.cart.map((item: CartItem) => {
|
||||||
|
const product = products.find(pro => pro.id == item.productId)
|
||||||
|
return (
|
||||||
|
<li
|
||||||
|
key={item.id}
|
||||||
|
className="flex items-center gap-4 py-4"
|
||||||
|
>
|
||||||
|
{/* Ảnh sản phẩm */}
|
||||||
|
<div className="w-16 h-16 flex-shrink-0 overflow-hidden rounded-lg border">
|
||||||
|
{product ? (
|
||||||
|
<img
|
||||||
|
src={product.iconUrl}
|
||||||
|
alt={product.name}
|
||||||
|
className="w-full h-full object-cover"
|
||||||
|
/>
|
||||||
|
) : (
|
||||||
|
<div className="w-full h-full bg-gray-200 animate-pulse" />
|
||||||
|
)}
|
||||||
|
</div>
|
||||||
|
|
||||||
|
{/* Thông tin */}
|
||||||
|
<div className="flex-1">
|
||||||
|
<h2 className="font-semibold text-gray-800">
|
||||||
|
{product ? product.name : 'Đang tải...'}
|
||||||
|
</h2>
|
||||||
|
<p className="text-sm text-gray-500">
|
||||||
|
{product ? product.price.toLocaleString() + ' đ' : ''}
|
||||||
|
</p>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
{/* Số lượng & tổng */}
|
||||||
|
<div className="text-right">
|
||||||
|
<p className="text-sm text-gray-700">SL: {item.quantity}</p>
|
||||||
|
{product && (
|
||||||
|
<p className="font-semibold text-gray-800">
|
||||||
|
{(product.price * item.quantity).toLocaleString()} đ
|
||||||
|
</p>
|
||||||
|
)}
|
||||||
|
</div>
|
||||||
|
</li>
|
||||||
|
)
|
||||||
|
})}
|
||||||
</ul>
|
</ul>
|
||||||
|
|
||||||
|
{/* Tổng tiền */}
|
||||||
|
<div className="mt-6 border-t pt-4 text-right">
|
||||||
|
<p className="text-lg font-semibold">
|
||||||
|
Tổng cộng:{' '}
|
||||||
|
<span className="text-blue-600">{total.toLocaleString()} đ</span>
|
||||||
|
</p>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
{/* Nút hành động */}
|
||||||
|
<div className="mt-6 text-right">
|
||||||
|
<button className="px-6 py-2 bg-blue-600 hover:bg-blue-700 text-white rounded-xl transition">
|
||||||
|
Thanh toán
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
</div>
|
</div>
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -3,13 +3,15 @@ import { useParams } from 'react-router'
|
|||||||
import { Apis } from '../../../apis'
|
import { Apis } from '../../../apis'
|
||||||
import type { Product } from '../../../types/product.type'
|
import type { Product } from '../../../types/product.type'
|
||||||
import { Button } from 'antd'
|
import { Button } from 'antd'
|
||||||
import { useSelector } from 'react-redux'
|
import { useDispatch, useSelector } from 'react-redux'
|
||||||
import type { StoreType } from '../../../stores'
|
import type { StoreType } from '../../../stores'
|
||||||
|
import { userAction } from '../../../stores/slices/user.slice'
|
||||||
|
|
||||||
export default function ProductDetail() {
|
export default function ProductDetail() {
|
||||||
const { productId } = useParams()
|
const { productId } = useParams()
|
||||||
const [product, setProduct] = useState<Product | null>(null)
|
const [product, setProduct] = useState<Product | null>(null)
|
||||||
const userStore = useSelector((store: StoreType) => store.user)
|
const userStore = useSelector((store: StoreType) => store.user)
|
||||||
|
const dipatch = useDispatch()
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
if (!productId) return
|
if (!productId) return
|
||||||
@@ -35,7 +37,9 @@ export default function ProductDetail() {
|
|||||||
productId: product.id,
|
productId: product.id,
|
||||||
userId: userStore.data?.id
|
userId: userStore.data?.id
|
||||||
})
|
})
|
||||||
alert("thành công")
|
console.log("result", result)
|
||||||
|
|
||||||
|
dipatch(userAction.changeLoad())
|
||||||
} catch (err) {
|
} catch (err) {
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -7,13 +7,15 @@ import type { CartItem } from "../../types/cart.type";
|
|||||||
interface UserState {
|
interface UserState {
|
||||||
data: User | null,
|
data: User | null,
|
||||||
loading: boolean;
|
loading: boolean;
|
||||||
cart: CartItem[]
|
cart: CartItem[],
|
||||||
|
reloadCart: boolean
|
||||||
}
|
}
|
||||||
|
|
||||||
const InitUserState: UserState = {
|
const InitUserState: UserState = {
|
||||||
data: null,
|
data: null,
|
||||||
loading: false,
|
loading: false,
|
||||||
cart: []
|
cart: [],
|
||||||
|
reloadCart: false
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
@@ -23,7 +25,27 @@ const userSlice = createSlice({
|
|||||||
reducers: {
|
reducers: {
|
||||||
initCartData: (state, action) =>{
|
initCartData: (state, action) =>{
|
||||||
state.cart = action.payload
|
state.cart = action.payload
|
||||||
|
},
|
||||||
|
changeLoad: (state) => {
|
||||||
|
state.reloadCart = !state.reloadCart
|
||||||
}
|
}
|
||||||
|
// addToCart: (state, action) => {
|
||||||
|
// let existedItem = state.cart.find(item => item.productId == action.payload.productId)
|
||||||
|
|
||||||
|
// if(existedItem) {
|
||||||
|
// state.cart = state.cart.map(item => {
|
||||||
|
// if(item.id == existedItem.id) {
|
||||||
|
// return {
|
||||||
|
// ...existedItem,
|
||||||
|
// quantity: action.payload.quantity
|
||||||
|
// }
|
||||||
|
// }
|
||||||
|
// return item
|
||||||
|
// })
|
||||||
|
// }else {
|
||||||
|
// state.cart.push(action.payload)
|
||||||
|
// }
|
||||||
|
// }
|
||||||
},
|
},
|
||||||
extraReducers: (bd) => {
|
extraReducers: (bd) => {
|
||||||
bd.addCase(fetchUserData.pending, (state, action) => {
|
bd.addCase(fetchUserData.pending, (state, action) => {
|
||||||
|
|||||||
Reference in New Issue
Block a user