22 KiB
Báo Cáo Khám Phá Frontend Nền Tảng GoodGo
apps/web (Next.js 15 với App Router)
Ngày: 9 tháng 4, 2026
Trạng thái: Chưa có i18n (Không phát hiện cấu hình i18n nào)
Phiên bản Next.js: 15.5.14 | React: 18.3.0
Ngôn ngữ chính: Tiếng Việt (vi_VN)
📁 Tổng Quan Cấu Trúc Thư Mục
apps/web/
├── app/ # Next.js App Router (ứng dụng chính)
│ ├── layout.tsx # Layout gốc với metadata & providers
│ ├── globals.css # Style Tailwind toàn cục & biến theme
│ ├── middleware.ts # Đã có sẵn (middleware định tuyến xác thực)
│ ├── loading.tsx # Trạng thái tải gốc
│ ├── error.tsx # Xử lý lỗi gốc
│ ├── not-found.tsx # Trang 404
│ │
│ ├── (public)/ # Nhóm route công khai
│ │ ├── layout.tsx # Layout công khai với header/footer
│ │ ├── page.tsx # Trang đích (hero + danh sách nổi bật)
│ │ ├── search/ # Trang kết quả tìm kiếm
│ │ │ ├── page.tsx
│ │ │ ├── layout.tsx
│ │ │ ├── error.tsx
│ │ │ ├── loading.tsx
│ │ │ └── __tests__/
│ │ └── listings/[id]/ # Trang chi tiết tin đăng
│ │ └── page.tsx
│ │
│ ├── (auth)/ # Nhóm route xác thực
│ │ ├── layout.tsx
│ │ ├── login/page.tsx # Form đăng nhập
│ │ ├── register/page.tsx # Form đăng ký
│ │ ├── error.tsx
│ │ ├── loading.tsx
│ │ └── __tests__/
│ │
│ ├── (dashboard)/ # Nhóm route dashboard được bảo vệ
│ │ ├── layout.tsx # Layout dashboard với thanh điều hướng bên
│ │ ├── dashboard/page.tsx # Dashboard chính
│ │ ├── listings/
│ │ │ ├── page.tsx # Danh sách tin đăng của người dùng
│ │ │ ├── new/page.tsx # Tạo tin đăng (form nhiều bước)
│ │ │ └── [id]/edit/page.tsx
│ │ ├── analytics/page.tsx
│ │ ├── profile/page.tsx # Cài đặt hồ sơ người dùng
│ │ ├── subscription/page.tsx # Gói đăng ký
│ │ ├── payments/page.tsx # Lịch sử thanh toán
│ │ ├── valuation/page.tsx # Định giá bất động sản bằng AI
│ │ ├── error.tsx
│ │ ├── loading.tsx
│ │ └── __tests__/
│ │
│ ├── (admin)/ # Nhóm route quản trị
│ │ ├── layout.tsx
│ │ ├── admin/page.tsx # Dashboard quản trị
│ │ ├── admin/users/page.tsx
│ │ ├── admin/kyc/page.tsx
│ │ ├── admin/moderation/page.tsx
│ │ ├── error.tsx
│ │ └── loading.tsx
│ │
│ ├── auth/ # Callback xác thực
│ │ └── callback/
│ │ ├── google/page.tsx
│ │ └── zalo/page.tsx
│ │
│ ├── api/ # Route API
│ │ └── health/route.ts
│ │
│ ├── robots.ts
│ └── sitemap.ts
│
├── components/ # Các component React tái sử dụng
│ ├── providers/ # Context providers
│ │ ├── auth-provider.tsx # Context xác thực & wrapper store
│ │ ├── query-provider.tsx # Provider TanStack React Query
│ │ └── theme-provider.tsx # Provider chế độ tối/sáng
│ │
│ ├── ui/ # Các component UI cơ sở không có style
│ │ ├── button.tsx # Biến thể button dựa trên CVA
│ │ ├── input.tsx
│ │ ├── label.tsx
│ │ ├── card.tsx
│ │ ├── dialog.tsx # Hộp thoại modal
│ │ ├── tabs.tsx
│ │ ├── select.tsx # Component select tùy chỉnh
│ │ ├── badge.tsx
│ │ ├── textarea.tsx
│ │ ├── table.tsx
│ │ └── __tests__/ # Test component
│ │
│ ├── auth/
│ │ └── oauth-buttons.tsx # Nút OAuth Google & Zalo
│ │
│ ├── search/
│ │ ├── filter-bar.tsx # Bộ lọc tìm kiếm (loại giao dịch, bất động sản, khoảng giá)
│ │ ├── property-card.tsx # Thẻ danh sách bất động sản
│ │ └── search-results.tsx # Container kết quả tìm kiếm
│ │
│ ├── listings/
│ │ ├── listing-form-steps.tsx # Form tạo/chỉnh sửa nhiều bước
│ │ ├── image-upload.tsx # Component tải ảnh lên
│ │ ├── image-gallery.tsx # Trình xem thư viện ảnh
│ │ └── listing-status-badge.tsx # Badge hiển thị trạng thái
│ │
│ ├── map/
│ │ └── listing-map.tsx # Tích hợp Mapbox GL
│ │
│ ├── valuation/
│ │ ├── valuation-form.tsx
│ │ ├── valuation-results.tsx
│ │ ├── valuation-history.tsx
│ │ └── ai-estimate-button.tsx
│ │
│ └── charts/
│ ├── price-trend-chart.tsx
│ ├── agent-performance.tsx
│ └── district-heatmap.tsx
│
├── lib/ # Tiện ích và hooks
│ ├── utils.ts # cn() - clsx + tailwind-merge
│ ├── auth-store.ts # Quản lý trạng thái xác thực Zustand
│ ├── api-client.ts # Wrapper Axios/fetch
│ ├── query-client.ts # Cấu hình TanStack React Query
│ │
│ ├── hooks/
│ │ ├── use-listings.ts
│ │ ├── use-analytics.ts
│ │ ├── use-valuation.ts
│ │ ├── use-payments.ts
│ │ └── use-subscription.ts
│ │
│ ├── validations/ # Schema Zod
│ │ ├── auth.ts # Schema đăng nhập/đăng ký
│ │ ├── listings.ts # Schema tin đăng nhiều bước
│ │ └── valuation.ts
│ │
│ ├── *-api.ts # Các API client
│ │ ├── auth-api.ts
│ │ ├── listings-api.ts
│ │ ├── profile-api.ts
│ │ ├── payment-api.ts
│ │ ├── subscription-api.ts
│ │ ├── analytics-api.ts
│ │ ├── valuation-api.ts
│ │ └── admin-api.ts
│ │
│ └── __tests__/ # Unit test (Vitest + React Testing Library)
│ ├── auth-store.spec.ts
│ ├── auth-validations.spec.ts
│ ├── listing-validations.spec.ts
│ └── utils.spec.ts
│
├── public/ # Tài nguyên tĩnh
│ └── [images, icons, etc.]
│
├── .next/ # Đầu ra build (được tạo tự động)
├── node_modules/
│
└── Tệp Cấu Hình:
├── package.json # Phụ thuộc & scripts
├── next.config.js # Cấu hình Next.js (Sentry, CSP, headers)
├── tailwind.config.ts # Cấu hình Tailwind CSS
├── postcss.config.js # Cấu hình PostCSS (Tailwind + Autoprefixer)
├── tsconfig.json # Cấu hình TypeScript (kế thừa base)
├── vitest.config.ts # Cấu hình framework kiểm thử
├── vitest.setup.ts # Thiết lập kiểm thử
├── middleware.ts # Middleware định tuyến xác thực
├── sentry.*.config.ts # Theo dõi lỗi Sentry (3 tệp)
├── instrumentation.ts # Instrumentation phía server
└── global.d.ts # Định nghĩa TypeScript toàn cục
📦 Phụ Thuộc Package.json
Phụ Thuộc Production:
{
"@hookform/resolvers": "^5.2.2", // Resolver xác thực form
"@sentry/nextjs": "^10.47.0", // Theo dõi lỗi
"@tanstack/react-query": "^5.96.2", // Lấy dữ liệu & bộ nhớ đệm
"class-variance-authority": "^0.7.1", // Tiện ích biến thể component
"clsx": "^2.1.1", // Tiện ích tên class
"lucide-react": "^1.7.0", // Thư viện icon
"mapbox-gl": "^3.21.0", // Thư viện bản đồ
"next": "^14.2.0", // Framework
"react": "^18.3.0",
"react-dom": "^18.3.0",
"react-hook-form": "^7.72.1", // Quản lý trạng thái form
"recharts": "^3.8.1", // Thư viện biểu đồ
"tailwind-merge": "^3.5.0", // Giải quyết xung đột Tailwind
"zod": "^4.3.6", // Xác thực schema
"zustand": "^5.0.12" // Quản lý trạng thái nhẹ
}
Phụ Thuộc Dev (bao gồm kiểm thử):
{
"@testing-library/jest-dom": "^6.9.1",
"@testing-library/react": "^16.3.2",
"@testing-library/user-event": "^14.6.1",
"@vitejs/plugin-react": "^4.7.0",
"vitest": "^4.1.3",
"tailwindcss": "^3.4.0",
"tailwindcss-animate": "^1.0.7",
"msw": "^2.13.2", // Mock Service Worker
"typescript": "^6.0.2"
}
🎯 Layout Gốc (app/layout.tsx)
Triển Khai Hiện Tại:
- Ngôn ngữ HTML:
lang="vi"(Tiếng Việt được mã hóa cứng) - Cấu trúc Metadata: Tiêu đề & mô tả bằng tiếng Việt
- OpenGraph: Locale đặt là
vi_VN - Providers xếp chồng:
ThemeProvider → QueryProvider → AuthProvider - Khả năng tiếp cận: Bao gồm liên kết bỏ qua đến nội dung chính (đã tuân thủ A11y)
- Màu theme:
#15803d(xanh lá chính)
Metadata Hiện Tại:
title: 'GoodGo — Nền tảng Bất động sản Việt Nam'
description: 'GoodGo — nền tảng bất động sản thông minh tại Việt Nam...'
openGraph: { locale: 'vi_VN', ... }
🔐 Middleware (middleware.ts)
Định Tuyến Xác Thực Hiện Tại:
- Public paths: /login, /register, /search, /auth/callback, / (root)
- Protected paths: Anything else requires 'goodgo_authenticated' cookie
- Auth-only paths: /login, /register (redirects to /dashboard if authenticated)
- Redirect param: Adds ?redirect=[original-path] on unauthorized access
Các Điểm Nhập Quan Trọng Cần Cập Nhật Cho i18n:
- Cần phát hiện tiền tố locale (ví dụ:
/en/dashboard,/vi/dashboard) - Phát hiện locale qua cookie/header
🎨 Cấu Hình Tailwind
Thiết Lập Theme (tailwind.config.ts):
- Dark mode: 'class' based
- Content paths: ./app/**, ./components/**, ./lib/**
- Colors: HSL-based CSS variables (--primary, --secondary, etc.)
- Border radius: Customizable via --radius CSS variable
- Animation plugin: tailwindcss-animate
Style Toàn Cục (app/globals.css):
- Biến CSS: Bảng màu chế độ sáng + chế độ tối
- Màu chính: HSL(142.1, 76.2%, 36.3%) — xanh lá
- Tất cả component: Dùng @apply border-border để nhất quán
- Nền gốc: Áp dụng biến HSL
🗣️ Nội Dung Văn Bản & Các Điểm i18n
Vị Trí Văn Bản Tiếng Việt Được Mã Hóa Cứng:
Layout & Điều Hướng:
app/(public)/layout.tsx— Nav header: "Trang chủ", "Tìm kiếm", "Đăng nhập", "Đăng ký"app/(dashboard)/layout.tsx— Các mục nav dashboard (8 mục + nhãn chuyển đổi theme)- Footer trong layout công khai — Tiêu đề phần, liên kết
Trang:
app/(public)/page.tsx— Trang đích (hero, thanh tìm kiếm, quận huyện, thống kê, CTA)app/(auth)/login/page.tsx— Nhãn form, thông báo lỗi (đối tượng OAUTH_ERROR_MESSAGES)app/(auth)/register/page.tsx— Cấu trúc form tương tự
Component:
components/search/filter-bar.tsx— Nhãn bộ lọc (PRICE_RANGES), tên thành phốcomponents/search/property-card.tsx— Badge thông tin bất động sản, nhãn hướngcomponents/listings/listing-form-steps.tsx— Nhãn form, thông báo xác thựccomponents/ui/label.tsx— Nhãn form trong toàn bộ ứng dụng
Thông Báo Lỗi API & Xác Thực Zod:
lib/validations/listings.ts— Thông báo lỗi Zod (tiếng Việt)lib/validations/auth.ts— Thông báo xác thực xác thựccomponents/auth/oauth-buttons.tsx— Văn bản nút ("Google", "Zalo")
🧩 Các Component Quan Trọng Cần Dịch
Form (Xác thực form + nhãn):
-
Form Đăng Nhập (
app/(auth)/login/page.tsx)- Nhãn nhập số điện thoại, nhãn mật khẩu, lỗi
- Nhãn nút OAuth
- Văn bản liên kết: "Chưa có tài khoản? Đăng ký"
-
Form Đăng Ký (
app/(auth)/register/page.tsx)- Cấu trúc tương tự form đăng nhập
-
Tạo Tin Đăng (
components/listings/listing-form-steps.tsx)- Form nhiều bước với nhãn cho:
- Loại giao dịch (Bán/Cho thuê)
- Loại bất động sản (Căn hộ/Nhà riêng/v.v.)
- Vị trí (địa chỉ, phường, quận, thành phố)
- Chi tiết (diện tích, phòng ngủ, phòng tắm, hướng)
- Giá cả
- Form nhiều bước với nhãn cho:
-
Bộ Lọc Tìm Kiếm (
components/search/filter-bar.tsx)- Select Giao dịch/Loại BĐS/Giá/Diện tích
- Tùy chọn thành phố (13 thành phố Việt Nam)
Component UI:
- Nút: Nhãn văn bản ("Đăng nhập", "Tìm kiếm", "Gửi", v.v.)
- Badge: Nhãn cho loại bất động sản, trạng thái, hướng
- Nhãn input: Trên tất cả các form
- Thông báo lỗi: Văn bản cảnh báo
Điều Hướng:
- Header công khai: 4 mục nav chính + menu người dùng
- Nav dashboard: 8 phần chính + chuyển đổi theme
- Footer: 4 cột liên kết + bản quyền
♿ Khả Năng Tiếp Cận (Trạng Thái Hiện Tại - WCAG 2.1 AA)
Đã Triển Khai ✅:
- Liên kết bỏ qua đến nội dung chính (ẩn, hiện khi focus)
- HTML ngữ nghĩa:
<header>,<nav>,<main>,<footer> aria-labeltrên các mục điều hướngaria-labeltrên thẻ bất động sản (cho trình đọc màn hình)role="alert"trên thông báo lỗiaria-invalidtrên các input form- Nhãn form liên kết với
htmlFor - Văn bản alt trên ảnh bất động sản
aria-hidden="true"trên các phần tử trang trí
Lỗ Hổng Khả Năng Tiếp Cận Cần Khắc Phục 🔧:
- Độ tương phản màu sắc: Cần kiểm tra theo chuẩn WCAG AA
- Chỉ báo focus: Đảm bảo trạng thái focus hiển thị rõ trên tất cả phần tử tương tác
- Dialog/Modal: Cần quản lý focus đúng cách trong dialog
- Form: Đảm bảo nhóm trường với
<fieldset>khi cần - Xử lý lỗi: Một số thông báo lỗi thiếu nhãn rõ ràng
- Trạng thái tải: Spinner cần
aria-busyhoặcaria-label - Bảng: Bảng dữ liệu cần header đúng cách (
<th>,scope) - Liên kết: Liên kết "Xem tất cả" nên có ngữ cảnh hoặc aria-label
- Nút chỉ có icon: Cần
aria-labelphù hợp - Văn bản thay thế: Đảm bảo tất cả icon có ý nghĩa đều có nhãn mô tả
Các Điểm Triển Khai ARIA:
- Điều hướng dropdown (nếu phức tạp)
- Giao diện Tab (recharts/biểu đồ)
- Component tải tệp
- Input ngày/giờ (nếu được thêm vào)
🔗 Thiết Lập Locale Hiện Tại
Trạng thái: CHƯA CÓ i18n
- Không có package
next-intl - Không có tệp dịch (JSON/YAML)
- Không có route theo locale (
/en/*,/vi/*) - Không có middleware i18n
- Ngôn ngữ được mã hóa cứng thành tiếng Việt ở khắp nơi
Nơi i18n Sẽ Được Tích Hợp:
- Middleware: Phát hiện locale từ URL, cookie, hoặc header
Accept-Language - Wrapper layout: Cấu trúc thư mục
[locale]/layout.tsx - Message providers: Provider
next-intltrong layout gốc - Phản hồi API: Có thể cần quốc tế hóa thông báo lỗi từ backend
🔒 Cấu Hình Bảo Mật
Security Headers Cấu Hình Next.js:
X-Content-Type-Options: nosniffX-Frame-Options: DENYX-XSS-Protection: 1; mode=blockContent-Security-Policyvới các domain Mapbox được đưa vào danh sách trắngPermissions-Policyhạn chế camera/microphone/geolocation
Middleware Xác Thực:
- Kiểm tra xác thực dựa trên cookie (
goodgo_authenticated) - Các route được bảo vệ chuyển hướng đến
/login?redirect=... - Bảo vệ route công khai trong middleware
📊 Cấu Trúc Dữ Liệu & Enum Quan Trọng
Loại Giao Dịch:
const TRANSACTION_TYPES = [
{ value: 'SALE', label: 'Bán' },
{ value: 'RENT', label: 'Cho thuê' },
];
Loại Bất Động Sản:
const PROPERTY_TYPES = [
{ value: 'APARTMENT', label: 'Căn hộ' },
{ value: 'HOUSE', label: 'Nhà riêng' },
{ value: 'VILLA', label: 'Biệt thự' },
{ value: 'LAND', label: 'Đất nền' },
{ value: 'OFFICE', label: 'Văn phòng' },
{ value: 'SHOPHOUSE', label: 'Shophouse' },
];
Trạng Thái Tin Đăng:
const LISTING_STATUSES = {
DRAFT, PENDING_REVIEW, ACTIVE, RESERVED, SOLD, RENTED, EXPIRED, REJECTED
};
Hướng:
const DIRECTIONS = [
{ value: 'NORTH', label: 'Bắc' },
{ value: 'SOUTH', label: 'Nam' },
{ value: 'EAST', label: 'Đông' },
{ value: 'WEST', label: 'Tây' },
// ... và các tổ hợp đường chéo
];
Thành Phố (13 tổng):
Hồ Chí Minh, Hà Nội, Đà Nẵng, Nha Trang, Cần Thơ, Hải Phòng,
Bình Dương, Đồng Nai, Long An, Bà Rịa - Vũng Tàu, [+ more]
🧪 Thiết Lập Kiểm Thử
Framework Kiểm Thử: Vitest + React Testing Library
- Tệp cấu hình:
vitest.config.ts - Tệp thiết lập:
vitest.setup.ts - Tệp test: Đặt cạnh source (
__tests__folders)
Phạm Vi Test Hiện Tại:
- Test component cho thư viện UI
- Test auth store
- Test schema xác thực
- Test hàm tiện ích
Thực Hành Tốt Nhất Khi Kiểm Thử i18n:
- Mock provider i18n trong thiết lập test
- Kiểm thử cả hai biến thể locale
- Xác minh các bản dịch hiển thị đúng
🚀 Mức Độ Sẵn Sàng Triển Khai
Sẵn Sàng Cho Triển Khai i18n:
✅ Thông báo xác thực tập trung (schema Zod)
✅ Văn bản UI dựa trên enum/hằng số (TRANSACTION_TYPES, PROPERTY_TYPES, v.v.)
✅ Thư viện component với các pattern nhất quán
✅ TypeScript để đảm bảo type safety
✅ Hỗ trợ middleware cho định tuyến theo locale
Cần Tái Cấu Trúc Nhỏ:
⚠️ Trích xuất một số chuỗi được mã hóa cứng ra khỏi component
⚠️ Chuyển thông báo lỗi form sang tệp message
⚠️ Tập trung hóa metadata trang cho i18n
📝 Tóm Tắt: Các Điểm Triển Khai i18n & A11y
| Khu Vực | Trạng Thái Hiện Tại | Cần Làm |
|---|---|---|
| Hỗ Trợ Locale | Mã hóa cứng thành tiếng Việt | Triển khai next-intl với định tuyến |
| Khóa Dịch | Rải rác trong code | Tập trung vào tệp message |
| Thông Báo Xác Thực | Trong schema Zod (tiếng Việt) | Trích xuất sang message i18n |
| Văn Bản Component | Chuỗi mã hóa cứng | Dùng hook i18n |
| Metadata/SEO | Tiếng Việt mã hóa cứng | Tạo cho từng locale |
| Độ Tương Phản Màu | Có thể đạt AA | Kiểm tra và xác minh |
| Quản Lý Focus | Một phần (nút/liên kết ổn) | Thêm vào modal & dropdown |
| Nhãn ARIA | Phạm vi tốt | Hoàn thiện nhãn còn thiếu |
| Thông Báo Lỗi | Hầu hết có aria-invalid | Thêm ngữ cảnh cho một số trường hợp |
| Trạng Thái Tải | Spinner tồn tại | Thêm aria-busy, nhãn tốt hơn |
| Bảng | Cấu trúc cơ bản | Thêm ngữ nghĩa header đúng cách |
🗂️ Tóm Tắt Số Lượng Tệp
- Route ứng dụng: ~15 tệp trang
- Component: ~35 tệp component
- Tiện ích lib: ~20 tệp (hooks, API, xác thực)
- Test: ~15 tệp test
- Tệp cấu hình: ~8 tệp cấu hình
- Tổng tệp TypeScript/TSX: ~90+
Các Bước Tiếp Theo Để Triển Khai
- Cài đặt i18n: Thêm
next-intlvào dependencies - Tạo tệp message: Thiết lập
messages/en.jsonvàmessages/vi.json - Tái cấu trúc middleware: Thêm phát hiện locale & định tuyến
- Cập nhật layout gốc: Bọc với provider i18n
- Cập nhật tất cả component: Thay thế chuỗi mã hóa cứng bằng
useTranslations() - Kiểm thử cả hai locale: Đảm bảo tất cả trang hiển thị đúng
- Kiểm tra A11y: Dùng axe DevTools để xác định các vấn đề còn lại
- Quản lý focus: Thêm focus trapping trong modal
- Kiểm thử: Cập nhật thiết lập test cho mock i18n
Báo Cáo Được Tạo: 9 tháng 4, 2026
Phạm Vi Khám Phá: Toàn diện
Độ Tin Cậy: Cao