Skip to content

Latest commit

 

History

History
539 lines (464 loc) · 20.8 KB

File metadata and controls

539 lines (464 loc) · 20.8 KB

PROMPT: Xây dựng Website Cửa Hàng Bán Sách (BookStore)

Mục tiêu

Xây dựng một website cửa hàng bán sách hoàn chỉnh fullstack, chạy local, hỗ trợ đa ngôn ngữ (Tiếng Việt / English), bao gồm giao diện người dùng, trang quản trị admin, và tích hợp thanh toán QR Code.


Tech Stack

Layer Công nghệ
Frontend React 18+ (Vite)
Styling Tailwind CSS 3+
State Mgmt Zustand hoặc React Context
Routing React Router v6
i18n react-i18next
Backend Node.js + Express.js
ORM Prisma
Database PostgreSQL
Authentication JWT (Access Token + Refresh Token)
Payment QR Code (VietQR API / static QR)
Image Upload Multer (lưu local /uploads)
Validation Zod (shared giữa FE & BE)

Cấu trúc thư mục

BookStore/
├── client/                     # Frontend React
│   ├── public/
│   │   └── locales/            # i18n translation files
│   │       ├── vi/
│   │       │   └── translation.json
│   │       └── en/
│   │           └── translation.json
│   ├── src/
│   │   ├── assets/             # Hình ảnh, icons
│   │   ├── components/         # Shared components
│   │   │   ├── ui/             # Button, Input, Modal, Card, Badge, Pagination...
│   │   │   ├── layout/         # Header, Footer, Sidebar, AdminLayout
│   │   │   └── common/         # LanguageSwitcher, StarRating, SearchBar, CartIcon
│   │   ├── features/           # Feature-based modules
│   │   │   ├── auth/           # Login, Register, ForgotPassword
│   │   │   ├── books/          # BookList, BookDetail, BookCard, BookFilter
│   │   │   ├── cart/           # CartPage, CartItem, CartSummary
│   │   │   ├── checkout/       # CheckoutPage, QRPayment, OrderConfirm
│   │   │   ├── orders/         # OrderHistory, OrderDetail, OrderTracking
│   │   │   ├── reviews/        # ReviewForm, ReviewList, ReviewItem
│   │   │   ├── profile/        # UserProfile, EditProfile, AddressBook
│   │   │   └── admin/          # Dashboard, BookMgmt, OrderMgmt, UserMgmt, CategoryMgmt
│   │   ├── hooks/              # Custom hooks (useAuth, useCart, useBooks, useDebounce)
│   │   ├── lib/                # axios instance, helpers, constants
│   │   ├── store/              # Zustand stores (authStore, cartStore)
│   │   ├── routes/             # Route config, ProtectedRoute, AdminRoute
│   │   ├── i18n.ts             # i18next config
│   │   ├── App.tsx
│   │   └── main.tsx
│   ├── index.html
│   ├── tailwind.config.js
│   ├── vite.config.ts
│   ├── tsconfig.json
│   └── package.json
│
├── server/                     # Backend Node.js
│   ├── prisma/
│   │   ├── schema.prisma       # Database schema
│   │   └── seed.ts             # Seed data
│   ├── src/
│   │   ├── controllers/        # Route handlers
│   │   │   ├── auth.controller.ts
│   │   │   ├── book.controller.ts
│   │   │   ├── category.controller.ts
│   │   │   ├── cart.controller.ts
│   │   │   ├── order.controller.ts
│   │   │   ├── review.controller.ts
│   │   │   ├── user.controller.ts
│   │   │   └── payment.controller.ts
│   │   ├── routes/             # Express routes
│   │   │   ├── auth.routes.ts
│   │   │   ├── book.routes.ts
│   │   │   ├── category.routes.ts
│   │   │   ├── cart.routes.ts
│   │   │   ├── order.routes.ts
│   │   │   ├── review.routes.ts
│   │   │   ├── user.routes.ts
│   │   │   ├── payment.routes.ts
│   │   │   └── index.ts
│   │   ├── middlewares/        # Middleware
│   │   │   ├── auth.middleware.ts       # JWT verify
│   │   │   ├── admin.middleware.ts      # Admin role check
│   │   │   ├── upload.middleware.ts     # Multer config
│   │   │   ├── validate.middleware.ts   # Zod validation
│   │   │   └── error.middleware.ts      # Global error handler
│   │   ├── services/           # Business logic
│   │   │   ├── auth.service.ts
│   │   │   ├── book.service.ts
│   │   │   ├── cart.service.ts
│   │   │   ├── order.service.ts
│   │   │   ├── review.service.ts
│   │   │   └── payment.service.ts
│   │   ├── validators/         # Zod schemas
│   │   │   ├── auth.validator.ts
│   │   │   ├── book.validator.ts
│   │   │   ├── order.validator.ts
│   │   │   └── review.validator.ts
│   │   ├── utils/              # Helpers
│   │   │   ├── jwt.ts
│   │   │   ├── password.ts
│   │   │   ├── qrcode.ts       # QR code generation
│   │   │   └── pagination.ts
│   │   ├── types/              # TypeScript types
│   │   │   └── index.ts
│   │   └── app.ts              # Express app setup
│   ├── uploads/                # Uploaded images
│   ├── tsconfig.json
│   └── package.json
│
├── .env                        # Environment variables
├── .gitignore
├── package.json                # Root workspace (optional scripts)
├── PROMPT.md
└── README.md

Database Schema (Prisma)

generator client {
  provider = "prisma-client-js"
}

datasource db {
  provider = "postgresql"
  url      = env("DATABASE_URL")
}

enum Role {
  USER
  ADMIN
}

enum OrderStatus {
  PENDING
  CONFIRMED
  SHIPPING
  DELIVERED
  CANCELLED
}

enum PaymentStatus {
  UNPAID
  PAID
  FAILED
  REFUNDED
}

model User {
  id            String    @id @default(cuid())
  email         String    @unique
  password      String
  fullName      String
  phone         String?
  avatar        String?
  role          Role      @default(USER)
  refreshToken  String?
  addresses     Address[]
  orders        Order[]
  reviews       Review[]
  cart          Cart?
  createdAt     DateTime  @default(now())
  updatedAt     DateTime  @updatedAt
}

model Address {
  id          String   @id @default(cuid())
  userId      String
  user        User     @relation(fields: [userId], references: [id], onDelete: Cascade)
  fullName    String
  phone       String
  street      String
  ward        String
  district    String
  city        String
  isDefault   Boolean  @default(false)
  orders      Order[]
  createdAt   DateTime @default(now())
}

model Category {
  id          String   @id @default(cuid())
  name        String   @unique
  nameEn      String?
  slug        String   @unique
  description String?
  image       String?
  books       Book[]
  createdAt   DateTime @default(now())
  updatedAt   DateTime @updatedAt
}

model Book {
  id            String      @id @default(cuid())
  title         String
  titleEn       String?
  slug          String      @unique
  author        String
  isbn          String?     @unique
  description   String?
  descriptionEn String?
  price         Decimal     @db.Decimal(12, 0)
  salePrice     Decimal?    @db.Decimal(12, 0)
  stock         Int         @default(0)
  images        String[]
  thumbnail     String?
  publisher     String?
  publishedYear Int?
  pages         Int?
  language      String?     @default("vi")
  categoryId    String
  category      Category    @relation(fields: [categoryId], references: [id])
  reviews       Review[]
  cartItems     CartItem[]
  orderItems    OrderItem[]
  avgRating     Float       @default(0)
  totalReviews  Int         @default(0)
  isFeatured    Boolean     @default(false)
  isActive      Boolean     @default(true)
  createdAt     DateTime    @default(now())
  updatedAt     DateTime    @updatedAt

  @@index([categoryId])
  @@index([title])
  @@index([author])
}

model Review {
  id        String   @id @default(cuid())
  rating    Int      // 1-5
  comment   String?
  userId    String
  user      User     @relation(fields: [userId], references: [id], onDelete: Cascade)
  bookId    String
  book      Book     @relation(fields: [bookId], references: [id], onDelete: Cascade)
  createdAt DateTime @default(now())
  updatedAt DateTime @updatedAt

  @@unique([userId, bookId])
}

model Cart {
  id        String     @id @default(cuid())
  userId    String     @unique
  user      User       @relation(fields: [userId], references: [id], onDelete: Cascade)
  items     CartItem[]
  createdAt DateTime   @default(now())
  updatedAt DateTime   @updatedAt
}

model CartItem {
  id       String @id @default(cuid())
  cartId   String
  cart     Cart   @relation(fields: [cartId], references: [id], onDelete: Cascade)
  bookId   String
  book     Book   @relation(fields: [bookId], references: [id])
  quantity Int    @default(1)

  @@unique([cartId, bookId])
}

model Order {
  id              String        @id @default(cuid())
  orderCode       String        @unique // Mã đơn hàng: ORD-20260311-XXXX
  userId          String
  user            User          @relation(fields: [userId], references: [id])
  addressId       String
  address         Address       @relation(fields: [addressId], references: [id])
  items           OrderItem[]
  subtotal        Decimal       @db.Decimal(12, 0)
  shippingFee     Decimal       @db.Decimal(12, 0) @default(0)
  discount        Decimal       @db.Decimal(12, 0) @default(0)
  total           Decimal       @db.Decimal(12, 0)
  status          OrderStatus   @default(PENDING)
  paymentStatus   PaymentStatus @default(UNPAID)
  paymentMethod   String        @default("QR_CODE")
  note            String?
  createdAt       DateTime      @default(now())
  updatedAt       DateTime      @updatedAt

  @@index([userId])
  @@index([orderCode])
}

model OrderItem {
  id       String  @id @default(cuid())
  orderId  String
  order    Order   @relation(fields: [orderId], references: [id], onDelete: Cascade)
  bookId   String
  book     Book    @relation(fields: [bookId], references: [id])
  title    String  // Snapshot tên sách tại thời điểm đặt
  price    Decimal @db.Decimal(12, 0) // Snapshot giá tại thời điểm đặt
  quantity Int
}

API Endpoints

Auth (/api/auth)

Method Endpoint Mô tả Auth
POST /register Đăng ký tài khoản No
POST /login Đăng nhập, trả JWT No
POST /refresh-token Làm mới access token No
POST /logout Đăng xuất, xóa refresh Yes
GET /me Lấy thông tin user hiện tại Yes

Books (/api/books)

Method Endpoint Mô tả Auth
GET / Danh sách sách (pagination, filter, search) No
GET /featured Sách nổi bật No
GET /:slug Chi tiết sách No
POST / Thêm sách mới Admin
PUT /:id Cập nhật sách Admin
DELETE /:id Xóa sách Admin

Categories (/api/categories)

Method Endpoint Mô tả Auth
GET / Danh sách thể loại No
POST / Thêm thể loại Admin
PUT /:id Sửa thể loại Admin
DELETE /:id Xóa thể loại Admin

Cart (/api/cart)

Method Endpoint Mô tả Auth
GET / Lấy giỏ hàng hiện tại Yes
POST /items Thêm sách vào giỏ Yes
PUT /items/:itemId Cập nhật số lượng Yes
DELETE /items/:itemId Xóa sách khỏi giỏ Yes
DELETE / Xóa toàn bộ giỏ hàng Yes

Orders (/api/orders)

Method Endpoint Mô tả Auth
POST / Tạo đơn hàng từ giỏ hàng Yes
GET /my-orders Lịch sử đơn hàng của user Yes
GET /:id Chi tiết đơn hàng Yes
PUT /:id/cancel Hủy đơn hàng Yes
GET / Tất cả đơn hàng (admin) Admin
PUT /:id/status Cập nhật trạng thái đơn Admin

Reviews (/api/reviews)

Method Endpoint Mô tả Auth
GET /book/:bookId Danh sách review của sách No
POST /book/:bookId Viết review Yes
PUT /:id Sửa review Yes
DELETE /:id Xóa review Yes

Payment (/api/payment)

Method Endpoint Mô tả Auth
POST /qr-code/:orderId Tạo QR code thanh toán Yes
POST /confirm/:orderId Xác nhận đã thanh toán (admin hoặc webhook) Admin

Users (/api/users) — Admin

Method Endpoint Mô tả Auth
GET / Danh sách users Admin
PUT /:id Cập nhật user Admin
DELETE /:id Xóa user Admin

Trang Frontend

Public Pages

  1. Trang chủ (/) — Banner, sách nổi bật, danh mục, sách mới
  2. Danh sách sách (/books) — Grid sách + sidebar filter (thể loại, giá, rating) + search bar + pagination
  3. Chi tiết sách (/books/:slug) — Ảnh, thông tin, giá, nút thêm giỏ hàng, danh sách review
  4. Đăng nhập (/login)
  5. Đăng ký (/register)

Protected Pages (User)

  1. Giỏ hàng (/cart) — Danh sách sản phẩm, tăng/giảm số lượng, tổng tiền
  2. Thanh toán (/checkout) — Chọn địa chỉ, xác nhận đơn, hiển thị QR Code thanh toán
  3. Lịch sử đơn hàng (/orders) — Danh sách đơn + trạng thái
  4. Chi tiết đơn hàng (/orders/:id)
  5. Trang cá nhân (/profile) — Thông tin, đổi mật khẩu, quản lý địa chỉ

Admin Pages (/admin/...)

  1. Dashboard (/admin) — Thống kê: tổng đơn hàng, doanh thu, số sách, số user (cards + chart đơn giản)
  2. Quản lý sách (/admin/books) — Bảng CRUD, upload ảnh, form thêm/sửa
  3. Quản lý thể loại (/admin/categories) — CRUD thể loại
  4. Quản lý đơn hàng (/admin/orders) — Bảng đơn hàng, cập nhật trạng thái, xác nhận thanh toán
  5. Quản lý người dùng (/admin/users) — Danh sách user, đổi role

Tính năng chi tiết

Authentication & Authorization

  • Đăng ký: email, password (hash bcrypt), fullName, phone
  • Đăng nhập: trả về accessToken (15 phút) + refreshToken (7 ngày, lưu DB + httpOnly cookie)
  • Middleware phân quyền: USER / ADMIN
  • Auto refresh token khi accessToken hết hạn (axios interceptor ở FE)

Quản lý sách (CRUD)

  • Admin tạo/sửa/xóa sách với form: title, author, ISBN, mô tả, giá gốc, giá khuyến mãi, số lượng tồn, thể loại, upload nhiều ảnh
  • Hiển thị danh sách dạng bảng với pagination, search, filter theo trạng thái

Tìm kiếm & Lọc

  • Search bar toàn cục: tìm theo tên sách, tác giả (debounce 300ms)
  • Filter sidebar: thể loại (checkbox), khoảng giá (range slider), đánh giá (star filter)
  • Sort: mới nhất, giá tăng/giảm, bán chạy, đánh giá cao

Giỏ hàng

  • Thêm sách vào giỏ (kiểm tra tồn kho)
  • Tăng/giảm số lượng, xóa item
  • Hiển thị tổng tiền, số lượng
  • Icon giỏ hàng trên header có badge số lượng
  • Giỏ hàng lưu server-side (user đã đăng nhập)

Thanh toán QR Code

  • User đặt hàng → hệ thống tạo đơn với trạng thái PENDING + UNPAID
  • Sinh QR Code bằng VietQR API format: https://img.vietqr.io/image/{bankId}-{accountNo}-{template}.png?amount={total}&addInfo={orderCode}
  • Hiển thị QR Code để user quét chuyển khoản
  • Admin xác nhận thanh toán thủ công → cập nhật paymentStatus = PAID
  • Khi thanh toán xong → trừ tồn kho

Đánh giá sách

  • User đã mua sách mới được review (1 review / sách / user)
  • Rating 1-5 sao + comment
  • Hiển thị danh sách review + average rating trên trang chi tiết
  • Cập nhật avgRating và totalReviews trên bảng Book sau mỗi review

Quản lý đơn hàng

  • User: xem danh sách đơn, chi tiết, hủy đơn (chỉ khi PENDING)
  • Admin: xem tất cả đơn, filter theo trạng thái, cập nhật trạng thái (PENDING → CONFIRMED → SHIPPING → DELIVERED)

Dashboard Admin

  • Cards: Tổng đơn hàng, Doanh thu tháng, Số sách, Số user mới
  • Biểu đồ doanh thu 7 ngày gần nhất (dùng recharts hoặc chart.js)
  • Đơn hàng gần đây (5 đơn mới nhất)

Đa ngôn ngữ (i18n)

  • Hỗ trợ Tiếng Việt (mặc định) và English
  • Nút chuyển đổi ngôn ngữ trên Header (icon cờ hoặc dropdown)
  • Dịch toàn bộ UI labels, messages, button text, placeholder
  • Nội dung sách: dùng trường titleEn, descriptionEn, nameEn (category) — hiển thị theo ngôn ngữ đang chọn, fallback về tiếng Việt nếu không có bản dịch

UI/UX Guidelines

  • Color scheme: Tông ấm — primary: amber/brown (#92400e, #d97706), neutral: warm gray, accent: emerald cho success
  • Typography: Font sans-serif (Inter hoặc Be Vietnam Pro cho tiếng Việt)
  • Layout: Header cố định, max-width 1280px cho content, responsive mobile-first
  • Components: Rounded corners (rounded-lg), subtle shadows, hover transitions
  • Toast notifications: Dùng react-hot-toast cho feedback (thêm giỏ hàng, đặt hàng thành công...)
  • Loading: Skeleton loader cho danh sách sách, spinner cho actions
  • Empty states: Hiển thị illustration + message khi giỏ hàng trống, chưa có đơn hàng...

Seed Data

Tạo file seed với:

  • 2 users: 1 admin (admin@bookstore.com / admin123), 1 user (user@bookstore.com / user123)
  • 8 categories: Văn học, Kinh tế, Kỹ năng sống, Thiếu nhi, Khoa học, Công nghệ, Lịch sử, Tâm lý
  • 20+ sách mẫu phân bổ đều các thể loại (dữ liệu tiếng Việt thực tế, có cả titleEn)
  • Một số review mẫu

Environment Variables (.env)

# Database
DATABASE_URL="postgresql://postgres:password@localhost:5432/bookstore"

# JWT
JWT_ACCESS_SECRET="your-access-secret-key"
JWT_REFRESH_SECRET="your-refresh-secret-key"
JWT_ACCESS_EXPIRES_IN="15m"
JWT_REFRESH_EXPIRES_IN="7d"

# Server
PORT=5000
CLIENT_URL="http://localhost:5173"

# VietQR (thanh toán QR)
VIETQR_BANK_ID="970422"
VIETQR_ACCOUNT_NO="0123456789"
VIETQR_ACCOUNT_NAME="BOOKSTORE"
VIETQR_TEMPLATE="compact2"

Hướng dẫn chạy Local

# 1. Cài đặt dependencies
cd server && npm install
cd ../client && npm install

# 2. Setup database
cd server
npx prisma migrate dev --name init
npx prisma db seed

# 3. Chạy Backend (port 5000)
cd server && npm run dev

# 4. Chạy Frontend (port 5173)
cd client && npm run dev

Yêu cầu kỹ thuật

  • TypeScript cho cả FE và BE
  • ESLint + Prettier đã config
  • Error handling thống nhất: { success: boolean, data?: any, message?: string, errors?: any[] }
  • Pagination response: { data: [], meta: { page, limit, total, totalPages } }
  • API prefix: /api/v1/...
  • CORS config cho localhost
  • Tất cả giá tiền dùng đơn vị VNĐ, format: xxx.xxx đ
  • Image upload: max 5MB, chấp nhận jpg/png/webp