tt
This commit is contained in:
@@ -9,11 +9,17 @@ import CategoryManagement from './pages/admin/Category/CategoryManagement'
|
||||
import ProductManagement from './pages/admin/Product/ProductManagement'
|
||||
import CreateProductForm from './pages/admin/Product/components/CreateProductForm'
|
||||
import Cart from './pages/home/cart/Cart'
|
||||
import HomeContent from './pages/home/HomeContent'
|
||||
import Collection from './pages/home/collections/Collection'
|
||||
import ProductDetail from './pages/home/product/ProductDetail'
|
||||
|
||||
export default function RouterSetup() {
|
||||
return <Routes>
|
||||
<Route path='/' element={<Home />}>
|
||||
<Route path='cart' element={<Cart />}></Route>
|
||||
<Route path='/' element={<HomeContent />}></Route>
|
||||
<Route path='cart' element={<Cart />}></Route>
|
||||
<Route path='collections/:categoryId' element={<Collection />}></Route>
|
||||
<Route path='product/:productId' element={<ProductDetail />}></Route>
|
||||
</Route>
|
||||
<Route path='/auth' element={<Auth />}></Route>
|
||||
<Route path='admin' element={
|
||||
|
||||
@@ -1,9 +1,33 @@
|
||||
import axios from "axios";
|
||||
|
||||
export interface AddToCartDTO {
|
||||
userId: string;
|
||||
productId: string;
|
||||
}
|
||||
|
||||
export const CartApi = {
|
||||
getCartByUserId: async (userId: string) => {
|
||||
let result = await axios.get(`${import.meta.env.VITE_SV_HOST}/carts?userId=${userId}`)
|
||||
return result.data
|
||||
},
|
||||
addToCart: async (data: AddToCartDTO) => {
|
||||
let resultExisted = await axios.get(`${import.meta.env.VITE_SV_HOST}/carts?productId=${data.productId}`)
|
||||
console.log("resultExisted", resultExisted.data)
|
||||
if (!resultExisted.data[0]) {
|
||||
/* thêm mới */
|
||||
await axios.post(`${import.meta.env.VITE_SV_HOST}/carts`, {
|
||||
...data,
|
||||
quantity: 1
|
||||
})
|
||||
|
||||
return
|
||||
}
|
||||
|
||||
/* cập nhật */
|
||||
let resultUpdate = await axios.patch(`${import.meta.env.VITE_SV_HOST}/carts/${resultExisted.data[0].id}`, {
|
||||
quantity: resultExisted.data[0].quantity + 1
|
||||
})
|
||||
|
||||
console.log("resultUpdate", resultUpdate.data)
|
||||
}
|
||||
}
|
||||
@@ -19,5 +19,13 @@ export const ProductApi = {
|
||||
isActive: true
|
||||
})
|
||||
return result.data
|
||||
}
|
||||
},
|
||||
findListWithCategoryId: async (categoryId: string) => {
|
||||
let result = await axios.get(`${import.meta.env.VITE_SV_HOST}/products?categoryId=${categoryId}`)
|
||||
return result.data
|
||||
},
|
||||
findProductById: async (productId: string) => {
|
||||
let result = await axios.get(`${import.meta.env.VITE_SV_HOST}/products/` + productId)
|
||||
return result.data
|
||||
},
|
||||
}
|
||||
31
src/pages/home/HomeContent.tsx
Normal file
31
src/pages/home/HomeContent.tsx
Normal file
@@ -0,0 +1,31 @@
|
||||
import React, { useEffect, useState } from 'react'
|
||||
import type { Category } from '../../types/category.type'
|
||||
import { Apis } from '../../apis'
|
||||
import { Link } from 'react-router'
|
||||
|
||||
export default function HomeContent() {
|
||||
const [categories, setCategories] = useState<Category[]>([])
|
||||
|
||||
useEffect(() => {
|
||||
Apis.category.getAll()
|
||||
.then(res => {
|
||||
setCategories(res)
|
||||
})
|
||||
}, [])
|
||||
return (
|
||||
<div>
|
||||
HomeContent
|
||||
<ul>
|
||||
{
|
||||
categories.map(item => {
|
||||
return (
|
||||
<li key={item.id}>
|
||||
<Link to={`/collections/${item.id}`}>{item.title}</Link>
|
||||
</li>
|
||||
)
|
||||
})
|
||||
}
|
||||
</ul>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
48
src/pages/home/collections/Collection.tsx
Normal file
48
src/pages/home/collections/Collection.tsx
Normal file
@@ -0,0 +1,48 @@
|
||||
import React, { useEffect, useState } from 'react'
|
||||
import { useParams } from 'react-router'
|
||||
import { Apis } from '../../../apis'
|
||||
import type { Product } from '../../../types/product.type'
|
||||
|
||||
export default function Collection() {
|
||||
const { categoryId } = useParams()
|
||||
const [productList, setProductList] = useState<Product[]>([])
|
||||
|
||||
|
||||
useEffect(() => {
|
||||
Apis.product.findListWithCategoryId((categoryId))
|
||||
.then(res => {
|
||||
setProductList(res)
|
||||
})
|
||||
}, [categoryId])
|
||||
return (
|
||||
<div className="p-4">
|
||||
<h2 className="text-2xl font-bold mb-4">
|
||||
Danh sách sản phẩm (Danh mục: {categoryId})
|
||||
</h2>
|
||||
|
||||
<div className="grid grid-cols-2 md:grid-cols-3 lg:grid-cols-4 gap-4">
|
||||
{productList.map((item) => (
|
||||
<div
|
||||
|
||||
onClick={() => {
|
||||
window.location.href = "/product/" + item.id
|
||||
}}
|
||||
key={item.id}
|
||||
className="border rounded-xl p-3 shadow hover:shadow-lg transition bg-white"
|
||||
>
|
||||
<img
|
||||
src={item.iconUrl}
|
||||
alt={item.name}
|
||||
className="w-full h-40 object-cover rounded-md mb-3"
|
||||
/>
|
||||
<h3 className="text-lg font-semibold">{item.name}</h3>
|
||||
<p className="text-gray-600">Giá: {item.price.toLocaleString()}₫</p>
|
||||
<p className={`text-sm mt-1 ${item.isActive ? 'text-green-600' : 'text-red-500'}`}>
|
||||
{item.isActive ? 'Đang bán' : 'Ngừng bán'}
|
||||
</p>
|
||||
</div>
|
||||
))}
|
||||
</div>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
75
src/pages/home/product/ProductDetail.tsx
Normal file
75
src/pages/home/product/ProductDetail.tsx
Normal file
@@ -0,0 +1,75 @@
|
||||
import React, { useEffect, useState } from 'react'
|
||||
import { useParams } from 'react-router'
|
||||
import { Apis } from '../../../apis'
|
||||
import type { Product } from '../../../types/product.type'
|
||||
import { Button } from 'antd'
|
||||
import { useSelector } from 'react-redux'
|
||||
import type { StoreType } from '../../../stores'
|
||||
|
||||
export default function ProductDetail() {
|
||||
const { productId } = useParams()
|
||||
const [product, setProduct] = useState<Product | null>(null)
|
||||
const userStore = useSelector((store: StoreType) => store.user)
|
||||
|
||||
useEffect(() => {
|
||||
if (!productId) return
|
||||
Apis.product.findProductById(productId)
|
||||
.then(res => {
|
||||
setProduct(res)
|
||||
})
|
||||
}, [productId])
|
||||
|
||||
if (!product) {
|
||||
return (
|
||||
<div className="flex items-center justify-center min-h-screen">
|
||||
<div className="animate-spin rounded-full h-12 w-12 border-t-4 border-blue-500"></div>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
|
||||
return (
|
||||
<div className="max-w-4xl mx-auto p-6 mt-10 bg-white shadow-lg rounded-2xl">
|
||||
<Button onClick={() => {
|
||||
Apis.cart.addToCart({
|
||||
productId: product.id,
|
||||
userId: userStore.data?.id
|
||||
})
|
||||
}}>Add To Cart</Button>
|
||||
<div className="flex flex-col md:flex-row gap-6">
|
||||
{/* Hình ảnh sản phẩm */}
|
||||
<div className="w-full md:w-1/3 flex justify-center">
|
||||
<img
|
||||
src={product.iconUrl}
|
||||
alt={product.name}
|
||||
className="rounded-xl shadow-md w-full h-auto object-cover"
|
||||
/>
|
||||
</div>
|
||||
|
||||
{/* Thông tin sản phẩm */}
|
||||
<div className="flex-1 space-y-4">
|
||||
<h1 className="text-3xl font-bold text-gray-800">{product.name}</h1>
|
||||
<p className="text-2xl font-semibold text-blue-600">
|
||||
{product.price.toLocaleString()} ₫
|
||||
</p>
|
||||
<span
|
||||
className={`inline-block px-3 py-1 text-sm font-medium rounded-full ${
|
||||
product.isActive
|
||||
? 'bg-green-100 text-green-700'
|
||||
: 'bg-red-100 text-red-700'
|
||||
}`}
|
||||
>
|
||||
{product.isActive ? 'Đang bán' : 'Ngừng kinh doanh'}
|
||||
</span>
|
||||
|
||||
<div className="pt-4 border-t border-gray-200">
|
||||
<h2 className="text-lg font-semibold text-gray-700 mb-2">Mô tả sản phẩm</h2>
|
||||
<div
|
||||
className="prose max-w-none text-gray-600"
|
||||
dangerouslySetInnerHTML={{ __html: product.des }}
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
Reference in New Issue
Block a user