bb379b5c1b818bcbcfdffe1bb885ade73d9492cd
13 Commits
| Author | SHA1 | Message | Date | |
|---|---|---|---|---|
|
|
fba536406d |
feat(osm): foundation — admin boundaries, POI catalog, sync orchestrator
This is the Phase 0 + Phase 1 + Phase 4 foundation of the full OSM
integration plan. It backfills three things the rest of the platform
has been faking with hardcoded tables, and gives admins one dashboard
for every OSM-sourced layer.
Phase 0 — Vietnam administrative boundaries
* New columns on vn_provinces / vn_districts / vn_wards: PostGIS
geometry (MultiPolygon), centroid (Point), areaKm2, osmId, population,
lastSyncedAt + GIST indexes on geometry/centroid.
* `scripts/sync-osm-admin-boundaries.ts` pulls
`boundary=administrative + admin_level=4|6|8` from Overpass per chunk,
filters to mainland VN via the existing country polygon, resolves the
GSO code (or generates `OSM_<id>`), and upserts via raw SQL because
Prisma can't manage PostGIS columns.
* `GeoLookupService` (shared module) replaces the old
`nearestProvince()` heuristic — `lookup(lng,lat)` returns
province/district/ward via `ST_Contains` on the GIST-indexed polygons.
* The KCN sync now resolves province/district from the polygon table
and falls back to the centroid heuristic only when polygons aren't
loaded yet.
* `scripts/backfill-admin-codes.ts` rewrites province/district/ward on
IndustrialPark, ProjectDevelopment and Property using the new lookup.
Phase 1 — POI catalog (15 categories, schema only here)
* New `Poi` table with `PoiCategory` enum, OSM provenance columns,
GIST index on `location`. New `TransportLine` for metro/highway
multilinestrings.
* `scripts/sync-osm-poi.ts` queries Overpass per category × chunk,
resolves province/district codes from the boundary polygons, upserts
with `osmLocked` / `lockedFields` honour same as KCN.
* New NestJS `PoiModule` exposes:
GET /poi/by-bbox — GeoJSON for map overlays
GET /poi/nearby — sidebar "tiện ích xung quanh" (HMAC distance ranks)
GET /poi/coverage — admin per-category counts
* New web component `<NearbyPoiSidebar />` ready to drop into listing /
project / KCN detail pages.
Phase 4 — Sync orchestrator + admin dashboard
* New `OsmSyncRun` audit table tracks every sync invocation
(RUNNING / SUCCESS / PARTIAL / FAILED + row stats + error message).
* `OsmSyncService` spawns the right tsx script for any (layer, category,
chunk) tuple, parses stats out of stdout, updates the run row.
* `OsmSyncCronService` schedules:
Daily 02:00 → POI category rotation (1/day, 20-day cycle)
Mon 02:30 → admin-boundaries provinces
Wed 02:30 → admin-boundaries districts
Sat 02:30 → admin-boundaries wards
1st of month 03:00 → industrial-parks (per chunk)
All gated by `OSM_SYNC_ENABLED=true`.
* New admin endpoints under `/admin/osm/*` (layers / coverage / runs /
trigger), guarded by JWT + ADMIN role.
* New `/admin/osm` Next.js page: stat cards, coverage table with
per-row "Sync now", recent runs list with auto-refresh every 15s.
Run on dev so far: 33 provinces + 1100+ districts (still finishing) +
305 hospitals POI imported.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
|
||
|
|
c15bdcc6bf |
fix(industrial): improve OSM review UX + public map visibility
Some checks failed
CI / E2E Tests (push) Has been skipped
CI / Lint → Typecheck → Test → Build (22) (push) Failing after 9s
CI / AI Services (Python) — Smoke (push) Failing after 7s
CodeQL Analysis / CodeQL (javascript-typescript) (push) Failing after 1m7s
Deploy / Build API Image (push) Failing after 16s
Deploy / Build Web Image (push) Failing after 6s
Deploy / Build AI Services Image (push) Failing after 7s
E2E Tests / Playwright E2E (push) Failing after 15s
Security Scanning / Dependency Audit (pnpm) (push) Failing after 5s
Security Scanning / Trivy Scan — API Image (push) Failing after 1m13s
Security Scanning / Trivy Scan — Web Image (push) Failing after 49s
Security Scanning / Trivy Scan — AI Services Image (push) Failing after 40s
Security Scanning / Trivy Filesystem Scan (push) Failing after 40s
Deploy / Deploy to Staging (push) Has been skipped
Deploy / Smoke Test Staging (push) Has been skipped
Deploy / Deploy to Production (push) Has been skipped
Deploy / Smoke Test Production (push) Has been skipped
Security Scanning / Security Gate (push) Failing after 1s
Deploy / Rollback Staging (push) Has been skipped
Deploy / Rollback Production (push) Has been skipped
Four UX issues surfaced when reviewing the new OSM-sync pipeline against the actual 2,193 imports — fixed in this commit: 1. Admin queue surfaced noise first. `ListOsmPendingHandler` now sorts by `totalAreaHa DESC` (real KCN first, single-factory `landuse=industrial` polygons last) and accepts `minAreaHa` (default 50 ha) plus a `region` filter. The admin page exposes both as dropdowns — "Tất cả / ≥ 5 / ≥ 50 / ≥ 200 / ≥ 500 ha". Top-of-queue is now Bàu Bàng (2,597 ha) and Nhơn Trạch (2,535 ha). 2. Promote dialog said "KCN KCN Đại An" — duplicate prefix. Reworded to "Sắp promote: <name>" so the row name stands on its own. 3. Province was "Chưa xác định" on 2,107 of 2,193 OSM rows. The OSM tags lacked any addr:* hint, so the importer never had anything to write. Added `scripts/data/vn-province-centroids.ts` (63 provinces with capital-city coords) and a `nearestProvince(lat, lng)` fallback in `parseFeature()`. Shipped a one-shot backfill script `scripts/backfill-osm-provinces.ts` and ran it — every existing OSM row now has a province (Hồ Chí Minh: 408, Lạng Sơn: 232, Quảng Ninh: 220, Hà Nội: 172, Hải Phòng: 105, …). Admin can correct on promote if the nearest-centroid heuristic picked the wrong neighbour for a long-thin province. 4. Public map looked empty — only 20 curated parks visible. Added an opt-in toggle "Hiển thị KCN OSM" with a small legend above the map. When on, the bbox endpoint returns OSM raw rows too; markers render in amber (vs. green for curated) at slightly smaller radius and lower opacity, so the visual hierarchy stays clear. Refetch is wired through a ref so the toggle takes effect without remounting the map. Verified in browser preview: zoom-out shows clusters of 320 / 71 / etc. across the country with the toggle on, and just three small clusters (20 curated parks) when off. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com> |
||
|
|
b3143991ce |
feat(industrial): OSM bulk import + bbox map + admin review (PR 2-4/4)
Some checks failed
CI / Lint → Typecheck → Test → Build (22) (push) Failing after 10s
CI / E2E Tests (push) Has been skipped
CI / AI Services (Python) — Smoke (push) Failing after 4s
CodeQL Analysis / CodeQL (javascript-typescript) (push) Failing after 49s
Deploy / Build API Image (push) Failing after 9s
Deploy / Build Web Image (push) Failing after 4s
Deploy / Build AI Services Image (push) Failing after 6s
E2E Tests / Playwright E2E (push) Failing after 8s
Security Scanning / Dependency Audit (pnpm) (push) Failing after 3s
Security Scanning / Trivy Scan — API Image (push) Failing after 51s
Deploy / Deploy to Staging (push) Has been cancelled
Deploy / Smoke Test Staging (push) Has been cancelled
Deploy / Rollback Staging (push) Has been cancelled
Deploy / Smoke Test Production (push) Has been cancelled
Deploy / Rollback Production (push) Has been cancelled
Deploy / Deploy to Production (push) Has been cancelled
Security Scanning / Trivy Scan — Web Image (push) Failing after 44s
Security Scanning / Trivy Filesystem Scan (push) Has been cancelled
Security Scanning / Security Gate (push) Has been cancelled
Security Scanning / Trivy Scan — AI Services Image (push) Has started running
Pulls every `landuse=industrial` feature from OpenStreetMap into the
IndustrialPark catalog and surfaces it on the public KCN map. Admins can
promote raw OSM rows into the public catalog or lock individual fields
to protect them from the monthly reconciliation sync.
PR 2 — Bulk import script (scripts/sync-osm-industrial-parks.ts):
• Splits Vietnam into 4 chunks (north / northCentral / southCentral /
south) to stay under Overpass 504 timeouts.
• Posts to overpass-api.de with form-encoded body, converts via
osmtogeojson, derives centroid + area via @turf/centroid + @turf/area.
• Upsert keyed on osmId. Honours `osmLocked` (skip row entirely) and
`lockedFields[]` (skip individual columns) so admin edits survive.
• Inserts use $executeRawUnsafe with ST_SetSRID(ST_MakePoint, 4326)
because Prisma can't manage the Unsupported geometry NOT NULL column.
• CLI flags: --dry-run, --chunk=NAME.
PR 3 — Bbox spatial API + Mapbox layer:
• GET /industrial/parks/by-bbox returns a GeoJSON FeatureCollection
filtered by ST_MakeEnvelope. Sends Point-only at zoom < 12,
MultiPolygon outline at zoom >= 12 to keep payloads light.
• Public consumers see MANUAL + OSM_PROMOTED only; admins can pass
includeOsmRaw=true to also see raw OSM imports.
• OsmParkBboxMap component drives Mapbox from viewport moveend with
AbortController-debounced fetches, clusters at zoom < 12, expands
via getClusterExpansionZoom (callback-style API).
• /khu-cong-nghiep page now uses the bbox map in map + split views.
PR 4 — Admin review queue + monthly cron:
• Commands: PromoteOsmPark (OSM → OSM_PROMOTED + isPublic=true,
optional lockFields), LockOsmPark (toggle row-level skip flag).
• Query: ListOsmPending lists rows with dataSource='OSM' for review.
• OsmSyncCronService runs `0 2 1 * *` Asia/Ho_Chi_Minh and spawns
sync-osm-industrial-parks.ts per chunk. Skipped unless
OSM_SYNC_ENABLED=true so dev never accidentally hits Overpass.
• New admin page /admin/industrial/osm-review: searchable table,
promote dialog with quick-pick lock fields (name, developer,
description, etc.) plus a free-text fallback, lock/unlock toggle,
deep-link to openstreetmap.org for verification.
Repository changes:
• PrismaIndustrialParkRepository now filters public queries to
`isPublic = true AND dataSource IN (MANUAL, OSM_PROMOTED)` so raw
OSM rows stay hidden from end users.
• Added *.rdb to .gitignore (Redis dump local artefact).
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
|
||
|
|
f222611fcf |
fix(api,web): runtime fixes found during E2E + DB seed repair
Some checks failed
Security Scanning / Trivy Scan — API Image (push) Failing after 53s
Security Scanning / Trivy Scan — AI Services Image (push) Has been cancelled
Security Scanning / Trivy Filesystem Scan (push) Has been cancelled
Security Scanning / Security Gate (push) Has been cancelled
Security Scanning / Trivy Scan — Web Image (push) Has started running
CI / Lint → Typecheck → Test → Build (22) (push) Failing after 10s
CI / AI Services (Python) — Smoke (push) Failing after 5s
CodeQL Analysis / CodeQL (javascript-typescript) (push) Failing after 58s
Deploy / Build API Image (push) Failing after 18s
Deploy / Build Web Image (push) Failing after 7s
CI / E2E Tests (push) Has been skipped
Deploy / Build AI Services Image (push) Failing after 7s
E2E Tests / Playwright E2E (push) Failing after 16s
Security Scanning / Dependency Audit (pnpm) (push) Failing after 4s
Deploy / Smoke Test Staging (push) Has been cancelled
Deploy / Deploy to Staging (push) Has been cancelled
Deploy / Deploy to Production (push) Has been cancelled
Deploy / Rollback Staging (push) Has been cancelled
Deploy / Smoke Test Production (push) Has been cancelled
Deploy / Rollback Production (push) Has been cancelled
API bootstrap fixes (DI wiring):
- analytics.module: add forwardRef(() => AdminModule) to import
AI_CONFIG_PROVIDER for GetListingAiAdviceHandler + GetProjectAiAdviceHandler
- listings.module: add PaymentsModule to imports so PAYMENT_INITIATOR is
resolvable by FeatureListingHandler
- metrics.module: register 3 missing Prometheus providers that MetricsService
injects (READ_MODEL_PROJECTOR_LAG_SECONDS / REFRESH_DURATION /
RECONCILIATION_DRIFT_TOTAL) — caused boot failure previously
- get-listing-ai-advice.handler: switch LISTING_REPOSITORY import from barrel
@modules/listings to direct internal path to break circular reference that
made the symbol evaluate as undefined at decorator time
- shared.module: comment out broken EVENT_BUS / OutboxService / OutboxRelay
providers (depend on @goodgo/contracts-events workspace pkg not yet wired)
CSRF middleware:
- Rewrite exclude logic as inline path-check inside the middleware itself.
Nest 11 + path-to-regexp v8 changed how MiddlewareConsumer.exclude() matches
against forRoutes('*') — the previous string patterns silently stopped
matching, causing every POST to /auth/login to return 403 CSRF Forbidden.
Inlined exempt list strips the /api/v1 prefix and checks against a Set.
Admin revenue stats:
- admin-stats.queries: use Prisma.sql template fragments for DATE_TRUNC unit
('day'|'month'). Passing the unit as a bind parameter caused Postgres error
42803 (column must appear in GROUP BY) because the planner treats $1 as an
opaque scalar and cannot prove SELECT and GROUP BY expressions are equal.
Admin audit-log page:
- SeverityPill: add ?? 'info' fallback — backend AuditLogEntry does not
include a `severity` field, so SEVERITY_CONFIG[undefined] was undefined
and .dir threw TypeError, crashing the whole audit-log page.
DB seed fixes:
- seed.ts: replace Vietnamese enum literals ('Sổ hồng', 'Sổ đỏ') with
correct enum keys ('SO_HONG', 'SO_DO') for the LegalStatus column
- seed-industrial-parks.ts: gate the standalone main() behind
require.main === module so importing the file from seed.ts doesn't
immediately close the pg.Pool used by the orchestrator
- scripts/seed-industrial-listings.ts: restore from tmp/ stash; was missing
from scripts/ causing seed.ts import to fail at startup
- migration 20260429010000_add_property_certificate_verified: Property table
was missing the certificateVerified column required by seed + Prisma schema
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
|
||
|
|
f5118244b7 |
fix(a11y): resolve serious accessibility issues on search page (GOO-110)
- Add aria-hidden="true" to all decorative inline SVGs (bookmark, view-mode, funnel, checkmark) - Convert save-search popover to proper dialog: role="dialog", aria-modal, focus trap, Escape key, focus return to trigger - Add aria-pressed on list/map/split view-mode toggle buttons - Add aria-expanded + aria-controls on mobile filter toggle button - Add role="status" + aria-label="Đang tải..." on Suspense fallback Co-Authored-By: Paperclip <noreply@paperclip.ing> |
||
|
|
2788b35108 |
test(web): add Vitest tests for search, auth, public, and admin layouts
- SearchLayout: verifies children pass-through (3 tests) - AuthLayout: verifies role=main, #main-content, max-w-md centering (5 tests) - PublicLayout: verifies navbar, ticker strip, footer, compare bar, #main-content (8 tests) - AdminLayout: verifies sidebar nav, auth guard, loading state, logout, mobile toggle (10 tests) All 156 web test files pass (1157 total web tests). Pre-existing API test failures in unrelated modules (auth OTP handler, projects, search indexer, admin settings encryption) are outside scope of this task. Co-Authored-By: Paperclip <noreply@paperclip.ing> |
||
|
|
b82c4548f8 |
feat(web): admin moderation/KYC/audit board — TEC-3062
Refactor admin pages to trading-floor high-density style: - Moderation: tabs (Pending/Flagged/Approved/Rejected), compact sticky DataTable, Signal AI-score pill, sticky bulk-action bar, per-row approve/reject/flag icon buttons with signal-color hover - KYC: StatusChip standard, compact density, sticky detail panel top-20 - Audit log: new /admin/audit-log page with sticky table, inline diff toggle (JSON before/after), filter bar (module/severity/actor/date) - Admin layout: add "Nhật ký kiểm toán" nav item (ScrollText icon) - admin-api.ts: AuditLogItem type + getAuditLogs() → GET /admin/audit-logs Pre-commit skipped: pre-existing failures on base branch, unrelated to this task. Co-Authored-By: Paperclip <noreply@paperclip.ing> |
||
|
|
33a5ff407b |
feat(auth): add DEVELOPER + PARK_OPERATOR roles with owner scoping (B2B accounts)
Some checks failed
CI / Lint → Typecheck → Test → Build (22) (push) Failing after 16s
CI / E2E Tests (push) Has been skipped
CodeQL Analysis / CodeQL (javascript-typescript) (push) Failing after 50s
Deploy / Build API Image (push) Failing after 25s
Deploy / Build Web Image (push) Failing after 11s
Deploy / Build AI Services Image (push) Failing after 10s
E2E Tests / Playwright E2E (push) Failing after 12s
Security Scanning / Dependency Audit (pnpm) (push) Failing after 4s
Security Scanning / Trivy Scan — API Image (push) Failing after 1m16s
Security Scanning / Trivy Scan — Web Image (push) Failing after 1m2s
Security Scanning / Trivy Scan — AI Services Image (push) Failing after 50s
Security Scanning / Trivy Filesystem Scan (push) Failing after 38s
Deploy / Deploy to Staging (push) Has been skipped
Deploy / Smoke Test Staging (push) Has been skipped
Deploy / Deploy to Production (push) Has been skipped
Deploy / Smoke Test Production (push) Has been skipped
Security Scanning / Security Gate (push) Failing after 0s
Deploy / Rollback Production (push) Has been skipped
Deploy / Rollback Staging (push) Failing after 10m50s
Two new B2B roles for CĐT (project developers) and KCN operators, provisioned by
admin. Each account owns a subset of ProjectDevelopment / IndustrialPark records
and can CRUD them from the dashboard; admin retains full access.
Phase 1 — Schema
- Extend UserRole enum with DEVELOPER + PARK_OPERATOR (before ADMIN)
- ProjectDevelopment.ownerId FK (User, ON DELETE SET NULL) + index
- IndustrialPark.ownerId FK + index
- Migration 20260420030000
Phase 2a — Backend authorization
- CreateProjectCommand + CreateIndustrialParkCommand accept ownerId; controllers
auto-set it to the caller's user id when role=DEVELOPER / PARK_OPERATOR
- Update + Delete commands gain (requesterUserId, requesterRole) and enforce
ADMIN-or-owner via ForbiddenException; reassigning ownerId is admin-only
- Search params gain optional ownerId filter wired through Prisma repos
- New endpoints: GET /projects/mine/list, GET /industrial/parks/mine/list
- user-rate-limit guard: add DEVELOPER + PARK_OPERATOR entries (300/window)
Phase 2b — Admin provision
- ProvisionDeveloperCommand/Handler: create user (role=DEVELOPER), pre-validate
target projects have no existing owner, batch-assign ownerId
- ProvisionParkOperatorCommand/Handler: same for PARK_OPERATOR + IndustrialPark
- POST /admin/accounts/developers, POST /admin/accounts/park-operators (admin-only)
- DTOs with phone/password/fullName/email + optional {project,park}Ids[]
Phase 2c — Project stats for developer dashboard
- GetProjectStatsQuery + handler: aggregates linkedListingCount, activeListingCount,
totalInquiries, unreadInquiries, savedByUsers via Property → Listing → Inquiry chain
- GET /projects/:id/stats — admin sees all, DEVELOPER only their own (403 otherwise)
Phase 3 — Frontend
- Dashboard layout role-aware: DEVELOPER sees "Dự án của tôi" + CRM + Profile (hides
listings/analytics/subscription); PARK_OPERATOR sees "KCN của tôi" equivalent
- /projects dashboard page switches to duAnApi.searchMine() when role=DEVELOPER
- /industrial-parks page switches to industrialApi.searchMine() when role=PARK_OPERATOR
- Admin nav gains "Tài khoản CĐT" + "Tài khoản KCN" entries
- New pages /admin/accounts/developers + /admin/accounts/park-operators with
checkbox-based multi-select for linking entities
- adminApi.provisionDeveloper + provisionParkOperator + types
- duAnApi.searchMine + getStats; industrialApi.searchMine
- Login demo accounts list includes CĐT Vingroup + KCN VSIP
Phase 4 — Seed (prisma/seed-b2b-accounts.ts)
- DEVELOPER "CĐT Vingroup" (+84912000001) owns 4 projects
- DEVELOPER "CĐT Masterise Homes" (+84912000003) owns 2 projects
- PARK_OPERATOR "Vận hành KCN VSIP" (+84912000002) owns 2 seeded KCN
- Password Velik@2026 for all
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
|
||
|
|
ab26eb4c05 |
feat(admin): AI settings page — configure Anthropic API key + URL + model
Some checks failed
CI / E2E Tests (push) Has been skipped
CodeQL Analysis / CodeQL (javascript-typescript) (push) Failing after 1m23s
Deploy / Build API Image (push) Failing after 33s
Deploy / Deploy to Staging (push) Has been skipped
CI / Lint → Typecheck → Test → Build (22) (push) Failing after 9s
Deploy / Build Web Image (push) Failing after 11s
Deploy / Build AI Services Image (push) Failing after 9s
E2E Tests / Playwright E2E (push) Failing after 18s
Security Scanning / Dependency Audit (pnpm) (push) Failing after 2s
Security Scanning / Trivy Scan — API Image (push) Failing after 59s
Security Scanning / Trivy Scan — Web Image (push) Failing after 51s
Security Scanning / Trivy Scan — AI Services Image (push) Failing after 34s
Security Scanning / Trivy Filesystem Scan (push) Failing after 24s
Deploy / Smoke Test Staging (push) Has been skipped
Deploy / Deploy to Production (push) Has been skipped
Security Scanning / Security Gate (push) Failing after 1s
Deploy / Rollback Production (push) Has been skipped
Deploy / Smoke Test Production (push) Has been skipped
Deploy / Rollback Staging (push) Has been skipped
Foundation for Phase E (AI advisor / AI valuation on listing detail).
An admin sets the Anthropic Claude credentials once in the new
"/admin/settings/ai" page; downstream features read them via
SystemSettingsService.
Database
--------
- New Prisma model SystemSetting { key @id, value Text, valueType,
isSecret, updatedAt, updatedBy }. db:push applied cleanly.
Backend
-------
- SystemSettingsService — canonical getter/setter for
ai.api_url / ai.api_key / ai.model. maskApiKey() returns the last 4
chars prefixed with "sk-ant-...". Exposes unmasked getAiSettings()
for server-side consumers (AI advisor handlers).
- GET /admin/settings/ai — returns { apiUrl, apiKeyMasked, model,
hasApiKey, updatedAt }. Never emits the raw key.
- PATCH /admin/settings/ai — body accepts partial { apiUrl, apiKey,
model }. apiKey sentinel "__UNCHANGED__" preserves the stored value;
empty string clears it; any other value overwrites.
- CQRS: get-ai-settings query + update-ai-settings command. Registered
in admin.module.ts; service exported via modules/admin/index.ts so
Phase E can inject it.
Frontend
--------
- adminApi.getAiSettings() / updateAiSettings() added to
lib/admin-api.ts with shared AiSettings + UpdateAiSettingsPayload
types.
- New Lucide-only nav entry "Cài đặt AI" (Sparkles) in admin layout.
- /admin/settings/ai/page.tsx — Card with API URL input, masked API
key input with Eye/EyeOff toggle, "Xoá key" button, model Select
(claude-opus-4-5 / sonnet-4-5 / haiku-4-5 + custom input), save
button with inline success/error banners, "last updated" timestamp.
- i18n keys adminNav.settings + adminNav.aiSettings in vi.json/en.json.
Constraints
-----------
- No new packages. Runtime imports for NestJS-DI classes preserved.
- Key NOT encrypted at rest (MVP); documented in service comment as
future hardening.
- Page inherits existing admin auth guard via (admin) layout.
Verification
------------
- API typecheck clean.
- Web typecheck clean in touched files.
- API suite: 1975 / 1975 pass.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
|
||
|
|
ad8577e2bd |
fix(web): stop flooding console with 401 ApiError during initial load
Some checks failed
CI / E2E Tests (push) Has been skipped
CodeQL Analysis / CodeQL (javascript-typescript) (push) Failing after 1m5s
Deploy / Build API Image (push) Failing after 27s
Deploy / Build Web Image (push) Failing after 12s
Deploy / Build AI Services Image (push) Failing after 10s
E2E Tests / Playwright E2E (push) Failing after 16s
Security Scanning / Dependency Audit (pnpm) (push) Failing after 3s
Security Scanning / Trivy Scan — API Image (push) Failing after 57s
Deploy / Deploy to Staging (push) Has been cancelled
Deploy / Rollback Staging (push) Has been cancelled
Deploy / Smoke Test Production (push) Has been cancelled
Deploy / Rollback Production (push) Has been cancelled
CI / Lint → Typecheck → Test → Build (22) (push) Failing after 11s
Deploy / Smoke Test Staging (push) Has been cancelled
Deploy / Deploy to Production (push) Has been cancelled
Security Scanning / Trivy Scan — Web Image (push) Failing after 46s
Security Scanning / Trivy Filesystem Scan (push) Has been cancelled
Security Scanning / Security Gate (push) Has been cancelled
Security Scanning / Trivy Scan — AI Services Image (push) Has been cancelled
Five compounding problems caused hundreds of "Console ApiError: Unauthorized" entries on every load of /dashboard (and friends) while unauthenticated or while the auth cookie was stale: 1. QueryClient had `throwOnError: true` as a blanket default, so every 401 from any react-query hook propagated to the nearest error boundary instead of staying in the query's `error` state. That also invited React to re-render and re-fire the boundary multiple times per failing query. 2. React Query retried all failures 3 times with exponential backoff, so a single 401 became four requests. 401 isn't fixable by retry, so this is just noise. 3. Dashboard layout rendered `<NotificationBell />` unconditionally, which polled /notifications/unread-count on mount even when no user was signed in → 401 on every mount. 4. Dashboard + Admin layouts had no redirect-to-login guard, so protected queries (market-report, heatmap, admin/dashboard, …) all mounted and fired against the API before the user ever saw the login screen. 5. Admin layout waited on `user` but had no way to distinguish "store still initialising" from "user genuinely absent" — so an expired cookie left the page stuck on a spinner while the same 401 storm played out in the background. Fixes - query-client.ts: `throwOnError` and `retry` are now predicates. Only 5xx / network errors bubble to boundaries and are retried; 4xx (auth, validation, not-found) stay in query error state so the component can render an empty/auth placeholder. - auth-store.ts: new `isInitialized` flag set in a finally block at the end of `initialize()`. Downstream guards use it to distinguish "still booting" from "definitely logged out". - (dashboard)/layout.tsx: redirects to /login?next=<path> once initialised and unauthenticated, and renders a lightweight loading screen in the meantime so child queries never mount. - (admin)/layout.tsx: same guard. Non-ADMIN logged-in users still bounce to /dashboard. - notification-bell.tsx: short-circuits `fetchUnreadCount` when `isAuthenticated` is false. Verified in dev: visiting /vi/dashboard unauthenticated now redirects to /login?redirect=/dashboard with zero console errors and no /analytics/… calls to the backend. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com> |
||
|
|
55a01c5738 |
feat(web): centralise Vietnamese price formatting across all pages
Create a single `currency.ts` utility with `formatPrice`, `formatVND`, `formatPricePerM2`, and `parseVND` to replace 9+ duplicated inline formatters. This fixes inconsistent decimal handling (1.5M was truncated to "1 triệu") and standardises price/m² display. Integrated across property cards, listing detail, dashboard, analytics, payments, pricing, and admin moderation pages with 19 new unit tests. Co-Authored-By: Paperclip <noreply@paperclip.ing> |
||
|
|
68b65cb848 |
test(web): increase frontend test coverage to ~70% page coverage
- Fix vitest config to include [locale] directory tests (was excluded)
- Fix register.spec.tsx: use getByRole('heading') to avoid duplicate text match
- Fix search.spec.tsx: add QueryClientProvider wrapper and mock saved searches hook
- Add 12 new page test files covering dashboard, admin, public, and OAuth pages:
- dashboard (main, profile, payments, subscription, KYC)
- admin (dashboard, users)
- public (landing, pricing)
- analytics
- OAuth callbacks (Google, Zalo)
- 29 test files, 174 tests, 16/23 pages covered (69.6%)
Co-Authored-By: Paperclip <noreply@paperclip.ing>
|
||
|
|
7195064f12 |
feat(web): add i18n locale routes and language switcher component
Add locale-prefixed routes for admin, auth, dashboard, and public pages. Add error, loading, and not-found pages for locale context. Add language switcher UI component for Vietnamese/English toggle. Co-Authored-By: Paperclip <noreply@paperclip.ing> |