improve
All checks were successful
Build and publish Docker image / build-and-push (push) Successful in 2m18s
CI / Check (push) Successful in 14m12s

This commit is contained in:
Andras Schmelczer 2026-06-26 21:08:08 +01:00
parent f03edb86c1
commit e9e47fd811
10 changed files with 141 additions and 24 deletions

View file

@ -7,7 +7,7 @@ import { useMapData } from '../../hooks/useMapData';
import { usePOIData } from '../../hooks/usePOIData';
import { useActualListings } from '../../hooks/useActualListings';
import { buildTravelParam } from '../../lib/travel-params';
import { useFilters } from '../../hooks/useFilters';
import { useFilters, countEffectiveFilters } from '../../hooks/useFilters';
import { useHexagonSelection } from '../../hooks/useHexagonSelection';
import { usePaneResize } from '../../hooks/usePaneResize';
import { useCollapsibleGroups } from '../../hooks/useCollapsibleGroups';
@ -159,17 +159,49 @@ export default function MapPage({
user?.isAdmin === true;
const effectiveFilterCap = isLoggedIn ? REGISTERED_MAX_FILTERS : DEMO_MAX_FILTERS;
const filterLimit = filtersUnlimited ? null : effectiveFilterCap;
// On a shared link, non-paying users may view and adjust the shared filters but
// not add new ones — so a richer shared search can't become a way around the cap.
const lockFilterAdds = !filtersUnlimited && !!shareCode;
// Which upsell the modal frames: a shared-link block, or a plain filter-cap hit.
const upgradeReason: 'filters' | 'shared' = lockFilterAdds ? 'shared' : 'filters';
// On a shared link, anonymous visitors may view and adjust the shared filters but
// not add new ones. Keyed off login state (not paid status) so the "create a free
// account to build your own" upsell isn't a dead-end — once registered, the user can
// add filters up to their normal cap right there on the shared search. The
// `!filtersUnlimited` term also excludes the non-interactive screenshot/OG render
// (treated as unlimited above), which must show the shared filters in full.
const lockFilterAdds = !isLoggedIn && !filtersUnlimited && !!shareCode;
// A shared/bookmarked link can encode more filters than the viewer's tier allows.
// The initial set is then trimmed to the cap (the server 400s the full request), so
// the viewer would silently see a broader result than was shared. Count the effective
// filters (folded + known to the backend, matching what the server counts) so stale
// or unknown keys don't raise a false alarm; recomputed once features finish loading.
const initialEffectiveFilterCount = useMemo(
() =>
countEffectiveFilters(initialFilters, features) + (initialTravelTime?.entries?.length ?? 0),
[initialFilters, features, initialTravelTime]
);
// Gate on features being loaded: until then countEffectiveFilters can't drop unknown
// keys and would over-count (firing the popup for a set that actually fits the cap).
const sharedSearchTrimmed =
features.length > 0 && !filtersUnlimited && initialEffectiveFilterCount > effectiveFilterCap;
// Which upsell the modal frames: a shared-link context (lock, or a trimmed shared
// search), or a plain filter-cap hit.
const upgradeReason: 'filters' | 'shared' =
lockFilterAdds || sharedSearchTrimmed ? 'shared' : 'filters';
const [filterLimitHit, setFilterLimitHit] = useState(false);
const handleFilterLimitReached = useCallback(() => {
trackEvent('Upgrade Modal Shown');
setFilterLimitHit(true);
}, []);
// Pop the upgrade modal once when the shared/bookmarked search carried more filters
// than the viewer's tier allows — so trimmed filters are surfaced, not silently
// dropped. Fires when the signal first turns true (which may be after features finish
// loading), and only once so dismissing it sticks.
const overCapPopupShownRef = useRef(false);
useEffect(() => {
if (sharedSearchTrimmed && !overCapPopupShownRef.current) {
overCapPopupShownRef.current = true;
handleFilterLimitReached();
}
}, [sharedSearchTrimmed, handleFilterLimitReached]);
// Travel-time destinations count toward the same cap as feature filters (each one
// restricts which properties match). Cap an over-budget initial travel set (from a
// bookmarked/shared URL), preferring feature filters, so the first data request