R5.4 ships the upgraded AVM UI behind the `avm_v2` A/B flag. When the
flag is on, the dashboard exposes:
- Tab switch between single valuation and multi-property compare
- Waterfall drivers chart (ValueDriversChart) alongside the existing
horizontal bar breakdown
- Mapbox comparables map with similarity-coloured markers and an
optional highlighted subject pin
- Confidence interval + range bar and PDF export remain available
- Valuation history chart surface unchanged (still lazy-loaded)
Flag plumbing (useAvmV2Flag):
- NEXT_PUBLIC_FEATURE_AVM_V2=1 enables by default
- `?avm_v2=1|0` URL param forces + persists to localStorage
- safe localStorage handling (no throw when storage is blocked)
Tests: comparables-map, value-drivers-chart, use-avm-v2-flag specs
added. Pre-existing "Yếu tố chính" assertion in valuation-results.spec
updated to match the current copy ("Yếu tố ảnh hưởng giá") so the
valuation suite is green (7 files, 52 tests).
Co-Authored-By: Paperclip <noreply@paperclip.ing>
56 lines
1.3 KiB
TypeScript
56 lines
1.3 KiB
TypeScript
'use client';
|
|
|
|
import { useEffect, useState } from 'react';
|
|
|
|
const LOCAL_STORAGE_KEY = 'goodgo:avm_v2';
|
|
const QUERY_PARAM = 'avm_v2';
|
|
|
|
function readEnvDefault(): boolean {
|
|
const raw = process.env['NEXT_PUBLIC_FEATURE_AVM_V2'];
|
|
if (!raw) return false;
|
|
return raw === '1' || raw.toLowerCase() === 'true';
|
|
}
|
|
|
|
function readOverride(): boolean | null {
|
|
if (typeof window === 'undefined') return null;
|
|
|
|
const params = new URLSearchParams(window.location.search);
|
|
const qp = params.get(QUERY_PARAM);
|
|
if (qp === '1' || qp === 'true') {
|
|
try {
|
|
window.localStorage.setItem(LOCAL_STORAGE_KEY, '1');
|
|
} catch {
|
|
// localStorage may be blocked — ignore
|
|
}
|
|
return true;
|
|
}
|
|
if (qp === '0' || qp === 'false') {
|
|
try {
|
|
window.localStorage.setItem(LOCAL_STORAGE_KEY, '0');
|
|
} catch {
|
|
// localStorage may be blocked — ignore
|
|
}
|
|
return false;
|
|
}
|
|
|
|
try {
|
|
const stored = window.localStorage.getItem(LOCAL_STORAGE_KEY);
|
|
if (stored === '1') return true;
|
|
if (stored === '0') return false;
|
|
} catch {
|
|
// ignore
|
|
}
|
|
return null;
|
|
}
|
|
|
|
export function useAvmV2Flag(): boolean {
|
|
const [enabled, setEnabled] = useState<boolean>(readEnvDefault());
|
|
|
|
useEffect(() => {
|
|
const override = readOverride();
|
|
setEnabled(override ?? readEnvDefault());
|
|
}, []);
|
|
|
|
return enabled;
|
|
}
|