Files
goodgo-platform/docs/audits/FRONTEND_EXPLORATION.md
Ho Ngoc Hai 59272e9321 chore(docs): consolidate 22 audit files from root into docs/audits/
Root directory had accumulated audit/exploration markdown files cluttering
the project root. Moved all audit-related files to docs/audits/ with a
README.md index, and updated cross-references in K6_LOAD_TESTING_GUIDE.md
and README_FRONTEND_DOCS.md.

Co-Authored-By: Paperclip <noreply@paperclip.ing>
2026-04-10 23:16:00 +07:00

19 KiB

GoodGo Platform Frontend Exploration Report

apps/web (Next.js 14 with App Router)

Date: April 9, 2026
Status: Pre-i18n (No existing i18n setup detected)
Next.js Version: 14.2.0 | React: 18.3.0
Primary Language: Vietnamese (vi_VN)


📁 Directory Structure Overview

apps/web/
├── app/                          # Next.js App Router (main application)
│   ├── layout.tsx               # Root layout with metadata & providers
│   ├── globals.css              # Global Tailwind styles & theme variables
│   ├── middleware.ts            # Already exists (auth routing middleware)
│   ├── loading.tsx              # Root loading state
│   ├── error.tsx                # Root error boundary
│   ├── not-found.tsx            # 404 page
│   │
│   ├── (public)/                # Public route group
│   │   ├── layout.tsx           # Public layout with header/footer
│   │   ├── page.tsx             # Landing page (hero + featured listings)
│   │   ├── search/              # Search results page
│   │   │   ├── page.tsx
│   │   │   ├── layout.tsx
│   │   │   ├── error.tsx
│   │   │   ├── loading.tsx
│   │   │   └── __tests__/
│   │   └── listings/[id]/       # Listing detail page
│   │       └── page.tsx
│   │
│   ├── (auth)/                  # Auth route group
│   │   ├── layout.tsx
│   │   ├── login/page.tsx       # Login form
│   │   ├── register/page.tsx    # Registration form
│   │   ├── error.tsx
│   │   ├── loading.tsx
│   │   └── __tests__/
│   │
│   ├── (dashboard)/             # Protected dashboard route group
│   │   ├── layout.tsx           # Dashboard layout with sidebar nav
│   │   ├── dashboard/page.tsx   # Main dashboard
│   │   ├── listings/
│   │   │   ├── page.tsx         # User listings list
│   │   │   ├── new/page.tsx     # Create listing (multi-step form)
│   │   │   └── [id]/edit/page.tsx
│   │   ├── analytics/page.tsx
│   │   ├── profile/page.tsx     # User profile settings
│   │   ├── subscription/page.tsx # Subscription plans
│   │   ├── payments/page.tsx    # Payment history
│   │   ├── valuation/page.tsx   # AI property valuation
│   │   ├── error.tsx
│   │   ├── loading.tsx
│   │   └── __tests__/
│   │
│   ├── (admin)/                 # Admin route group
│   │   ├── layout.tsx
│   │   ├── admin/page.tsx       # Admin dashboard
│   │   ├── admin/users/page.tsx
│   │   ├── admin/kyc/page.tsx
│   │   ├── admin/moderation/page.tsx
│   │   ├── error.tsx
│   │   └── loading.tsx
│   │
│   ├── auth/                    # Auth callbacks
│   │   └── callback/
│   │       ├── google/page.tsx
│   │       └── zalo/page.tsx
│   │
│   ├── api/                     # API routes
│   │   └── health/route.ts
│   │
│   ├── robots.ts
│   └── sitemap.ts
│
├── components/                   # Reusable React components
│   ├── providers/               # Context providers
│   │   ├── auth-provider.tsx    # Auth context & store wrapper
│   │   ├── query-provider.tsx   # TanStack React Query provider
│   │   └── theme-provider.tsx   # Dark/light mode provider
│   │
│   ├── ui/                      # Unstyled base UI components
│   │   ├── button.tsx           # CVA-based button variants
│   │   ├── input.tsx
│   │   ├── label.tsx
│   │   ├── card.tsx
│   │   ├── dialog.tsx           # Modal dialog
│   │   ├── tabs.tsx
│   │   ├── select.tsx           # Custom select component
│   │   ├── badge.tsx
│   │   ├── textarea.tsx
│   │   ├── table.tsx
│   │   └── __tests__/           # Component tests
│   │
│   ├── auth/
│   │   └── oauth-buttons.tsx    # Google & Zalo OAuth buttons
│   │
│   ├── search/
│   │   ├── filter-bar.tsx       # Search filters (transaction, property, price range)
│   │   ├── property-card.tsx    # Property listing card
│   │   └── search-results.tsx   # Results container
│   │
│   ├── listings/
│   │   ├── listing-form-steps.tsx      # Multi-step create/edit form
│   │   ├── image-upload.tsx            # Image upload component
│   │   ├── image-gallery.tsx           # Image gallery viewer
│   │   └── listing-status-badge.tsx    # Status display badge
│   │
│   ├── map/
│   │   └── listing-map.tsx      # Mapbox GL integration
│   │
│   ├── 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/                         # Utilities and hooks
│   ├── utils.ts                # cn() - clsx + tailwind-merge
│   ├── auth-store.ts           # Zustand auth state management
│   ├── api-client.ts           # Axios/fetch wrapper
│   ├── query-client.ts         # TanStack React Query config
│   │
│   ├── hooks/
│   │   ├── use-listings.ts
│   │   ├── use-analytics.ts
│   │   ├── use-valuation.ts
│   │   ├── use-payments.ts
│   │   └── use-subscription.ts
│   │
│   ├── validations/            # Zod schemas
│   │   ├── auth.ts            # Login/register schemas
│   │   ├── listings.ts        # Multi-step listing schemas
│   │   └── valuation.ts
│   │
│   ├── *-api.ts               # API clients
│   │   ├── 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 tests (Vitest + React Testing Library)
│       ├── auth-store.spec.ts
│       ├── auth-validations.spec.ts
│       ├── listing-validations.spec.ts
│       └── utils.spec.ts
│
├── public/                     # Static assets
│   └── [images, icons, etc.]
│
├── .next/                      # Build output (generated)
├── node_modules/
│
└── Configuration Files:
    ├── package.json            # Dependencies & scripts
    ├── next.config.js          # Next.js config (Sentry, CSP, headers)
    ├── tailwind.config.ts      # Tailwind CSS config
    ├── postcss.config.js       # PostCSS config (Tailwind + Autoprefixer)
    ├── tsconfig.json           # TypeScript config (extends base)
    ├── vitest.config.ts        # Testing framework config
    ├── vitest.setup.ts         # Test setup
    ├── middleware.ts           # Auth routing middleware
    ├── sentry.*.config.ts      # Sentry error tracking (3 files)
    ├── instrumentation.ts      # Server instrumentation
    └── global.d.ts             # Global TypeScript definitions

📦 Package.json Dependencies

Production Dependencies:

{
  "@hookform/resolvers": "^5.2.2",      // Form validation resolver
  "@sentry/nextjs": "^10.47.0",         // Error tracking
  "@tanstack/react-query": "^5.96.2",   // Data fetching & caching
  "class-variance-authority": "^0.7.1", // Component variant utilities
  "clsx": "^2.1.1",                     // Class name utility
  "lucide-react": "^1.7.0",             // Icon library
  "mapbox-gl": "^3.21.0",               // Map library
  "next": "^14.2.0",                    // Framework
  "react": "^18.3.0",
  "react-dom": "^18.3.0",
  "react-hook-form": "^7.72.1",         // Form state management
  "recharts": "^3.8.1",                 // Chart library
  "tailwind-merge": "^3.5.0",           // Merge Tailwind conflicts
  "zod": "^4.3.6",                      // Schema validation
  "zustand": "^5.0.12"                  // Lightweight state management
}

Dev Dependencies (including testing):

{
  "@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"
}

🎯 Root Layout (app/layout.tsx)

Current Implementation:

  • HTML language: lang="vi" (Vietnamese hardcoded)
  • Metadata structure: Vietnamese title & description
  • OpenGraph: Locale set to vi_VN
  • Providers stacked: ThemeProvider → QueryProvider → AuthProvider
  • Accessibility: Includes skip-to-main-content link (already A11y compliant)
  • Theme color: #15803d (primary green)

Current Metadata:

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)

Current Auth Routing:

- 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

Key Entry Points to Update for i18n:

  • Locale prefix detection needed (e.g., /en/dashboard, /vi/dashboard)
  • Cookie/header locale detection

🎨 Tailwind Configuration

Theme Setup (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

Global Styles (app/globals.css):

  • CSS Variables: Light mode + dark mode color schemes
  • Primary color: HSL(142.1, 76.2%, 36.3%) — green
  • All components: Use @apply border-border for consistency
  • Root background: HSL variables applied

🗣️ Text Content & i18n Points

Hardcoded Vietnamese Text Locations:

Layout & Navigation:

  • app/(public)/layout.tsx — Header nav: "Trang chủ", "Tìm kiếm", "Đăng nhập", "Đăng ký"
  • app/(dashboard)/layout.tsx — Dashboard nav items (8 items + theme toggle label)
  • Footer in public layout — Section headings, links

Pages:

  • app/(public)/page.tsx — Landing page (hero, search bar, districts, stats, CTA)
  • app/(auth)/login/page.tsx — Form labels, error messages (OAUTH_ERROR_MESSAGES object)
  • app/(auth)/register/page.tsx — Similar form structure

Components:

  • components/search/filter-bar.tsx — Filter labels (PRICE_RANGES), city names
  • components/search/property-card.tsx — Property info badges, direction labels
  • components/listings/listing-form-steps.tsx — Form labels, validation messages
  • components/ui/label.tsx — Form labels across app

API Error Messages & Zod Validation:

  • lib/validations/listings.ts — Zod error messages (Vietnamese)
  • lib/validations/auth.ts — Auth validation messages
  • components/auth/oauth-buttons.tsx — Button text ("Google", "Zalo")

🧩 Key Components Requiring Translation

Forms (Form validation + labels):

  1. Login Form (app/(auth)/login/page.tsx)

    • Phone input label, password label, errors
    • OAuth button labels
    • Link text: "Chưa có tài khoản? Đăng ký"
  2. Register Form (app/(auth)/register/page.tsx)

    • Similar structure to login
  3. Listing Creation (components/listings/listing-form-steps.tsx)

    • Multi-step form with labels for:
      • Transaction type (Bán/Cho thuê)
      • Property type (Căn hộ/Nhà riêng/etc)
      • Location (address, ward, district, city)
      • Details (area, bedrooms, bathrooms, direction)
      • Pricing
  4. Search Filter (components/search/filter-bar.tsx)

    • Transaction/Property/Price/Area selects
    • City options (13 Vietnamese cities)

UI Components:

  • Buttons: Text labels ("Đăng nhập", "Tìm kiếm", "Gửi", etc.)
  • Badge: Labels for property types, statuses, directions
  • Input labels: Across all forms
  • Error messages: Alert text

Navigation:

  • Public header: 4 main nav items + user menu
  • Dashboard nav: 8 main sections + theme toggle
  • Footer: 4 columns of links + copyright

Accessibility (Current State - WCAG 2.1 AA)

Already Implemented :

  • Skip-to-main-content link (hidden, appears on focus)
  • Semantic HTML: <header>, <nav>, <main>, <footer>
  • aria-label on navigation items
  • aria-label on property cards (for screen readers)
  • role="alert" on error messages
  • aria-invalid on form inputs
  • Form labels linked with htmlFor
  • Image alt text on property images
  • aria-hidden="true" on decorative elements

Accessibility Gaps to Fix 🔧:

  1. Color contrast: Need to verify against WCAG AA standards
  2. Focus indicators: Ensure visible focus states on all interactive elements
  3. Dialog/Modal: Need proper focus management in dialogs
  4. Forms: Ensure field grouping with <fieldset> where applicable
  5. Error handling: Some error messages lack clear labels
  6. Loading states: Spinner needs aria-busy or aria-label
  7. Tables: Data tables need proper headers (<th>, scope)
  8. Links: "Xem tất cả" links should have context or aria-labels
  9. Icon-only buttons: Need proper aria-label
  10. Text alternatives: Ensure all meaningful icons have descriptive labels

ARIA Implementation Points:

  • Dropdown navigation (if complex)
  • Tab interfaces (recharts/charts)
  • File upload components
  • Date/time inputs (if added)

🔗 Current Locale Setup

Status: NO EXISTING i18n

  • No next-intl package
  • No translation files (JSON/YAML)
  • No locale routes (/en/*, /vi/*)
  • No i18n middleware
  • Language is hardcoded to Vietnamese everywhere

Where i18n Will Be Integrated:

  1. Middleware: Detect locale from URL, cookie, or Accept-Language header
  2. Layout wrapper: [locale]/layout.tsx folder structure
  3. Message providers: next-intl Provider in root layout
  4. API responses: May need to internationalize error messages from backend

🔒 Security Configuration

Next.js Config Security Headers:

  • X-Content-Type-Options: nosniff
  • X-Frame-Options: DENY
  • X-XSS-Protection: 1; mode=block
  • Content-Security-Policy with Mapbox domains whitelisted
  • Permissions-Policy restricting camera/microphone/geolocation

Auth Middleware:

  • Cookie-based auth check (goodgo_authenticated)
  • Protected routes redirect to /login?redirect=...
  • Public route protection in middleware

📊 Key Data Structures & Enums

Transaction Types:

const TRANSACTION_TYPES = [
  { value: 'SALE', label: 'Bán' },
  { value: 'RENT', label: 'Cho thuê' },
];

Property Types:

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' },
];

Listing Statuses:

const LISTING_STATUSES = {
  DRAFT, PENDING_REVIEW, ACTIVE, RESERVED, SOLD, RENTED, EXPIRED, REJECTED
};

Directions:

const DIRECTIONS = [
  { value: 'NORTH', label: 'Bắc' },
  { value: 'SOUTH', label: 'Nam' },
  { value: 'EAST', label: 'Đông' },
  { value: 'WEST', label: 'Tây' },
  // ... and diagonal combinations
];

Cities (13 total):

Hồ Chí Minh,  Nội, Đà Nẵng, Nha Trang, Cần Thơ, Hải Phòng,
Bình Dương, Đồng Nai, Long An,  Rịa - Vũng Tàu, [+ more]

🧪 Testing Setup

Test Framework: Vitest + React Testing Library

  • Config file: vitest.config.ts
  • Setup file: vitest.setup.ts
  • Test files: Located alongside source (__tests__ folders)

Current Test Coverage:

  • Component tests for UI library
  • Auth store tests
  • Validation schema tests
  • Utility function tests

Testing Best Practices for i18n:

  • Mock i18n provider in test setup
  • Test both locale variants
  • Verify translations render correctly

🚀 Implementation Readiness

Ready for i18n Implementation:

Centralized validation messages (Zod schemas)
Enum/constant-based UI text (TRANSACTION_TYPES, PROPERTY_TYPES, etc.)
Component library with consistent patterns
TypeScript for type safety
Middleware support for locale routing

Minor Refactoring Needed:

⚠️ Extract some hardcoded strings from components
⚠️ Move form error messages to message files
⚠️ Centralize page metadata for i18n


📝 Summary: i18n & A11y Implementation Points

Area Current State Needs Work
Locale Support Hardcoded to Vietnamese Implement next-intl with routing
Translation Keys Scattered throughout code Centralize in message files
Validation Messages In Zod schemas (Vietnamese) Extract to i18n messages
Component Text Hardcoded strings Use i18n hook
Metadata/SEO Hardcoded Vietnamese Generate for each locale
Color Contrast Likely AA compliant Audit and verify
Focus Management Partial (buttons/links ok) Add to modals & dropdowns
ARIA Labels Good coverage Complete missing labels
Error Messages Most have aria-invalid Add more context to some
Loading States Spinner exists Add aria-busy, better labels
Tables Basic structure Add proper header semantics

🗂️ File Count Summary

  • App routes: ~15 page files
  • Components: ~35 component files
  • Lib utilities: ~20 files (hooks, APIs, validations)
  • Tests: ~15 test files
  • Config files: ~8 configuration files
  • Total TypeScript/TSX files: ~90+

Next Steps for Implementation

  1. Install i18n: Add next-intl to dependencies
  2. Create message files: Set up messages/en.json and messages/vi.json
  3. Refactor middleware: Add locale detection & routing
  4. Update root layout: Wrap with i18n provider
  5. Update all components: Replace hardcoded strings with useTranslations()
  6. Test both locales: Ensure all pages render correctly
  7. A11y audit: Use axe DevTools to identify remaining issues
  8. Focus management: Add focus trapping in modals
  9. Testing: Update test setup for i18n mocking

Report Generated: April 9, 2026
Exploration Scope: Thorough
Confidence: High