seems alright

This commit is contained in:
Andras Schmelczer 2026-05-17 13:52:11 +01:00
parent ebe7bbb51d
commit eac1bd0d13
58 changed files with 23125 additions and 153505 deletions

View file

@ -236,6 +236,13 @@ export default function App() {
const authCompletedRef = useRef(false);
const [licenseSuccessStatus, setLicenseSuccessStatus] = useState<LicenseSuccessStatus>('hidden');
// Keep a ref to the latest refreshAuth so the mount-only startup effect always
// calls the current implementation without re-running when the callback identity changes.
const refreshAuthRef = useRef(refreshAuth);
useEffect(() => {
refreshAuthRef.current = refreshAuth;
}, [refreshAuth]);
const openAuthModal = useCallback(
(
tab: 'login' | 'register',
@ -284,14 +291,14 @@ export default function App() {
async function refreshOnStartup() {
if (!returnedFromCheckout) {
// Always refresh auth on startup to pick up server-side subscription changes.
refreshAuth().catch(() => {});
refreshAuthRef.current().catch(() => {});
return;
}
setLicenseSuccessStatus('verifying');
for (let attempt = 0; attempt < LICENSE_VERIFICATION_ATTEMPTS; attempt += 1) {
try {
const refreshedUser = await refreshAuth();
const refreshedUser = await refreshAuthRef.current();
if (cancelled) return;
if (hasFullAccess(refreshedUser)) {
trackEvent('Purchase');
@ -314,7 +321,9 @@ export default function App() {
return () => {
cancelled = true;
};
}, []); // eslint-disable-line react-hooks/exhaustive-deps
// Mount-only: this is a startup auth refresh / license verification handshake
// that must fire exactly once on initial load. refreshAuth is read via ref.
}, []);
const savedSearches = useSavedSearches(user?.id ?? null);
const [showSaveModal, setShowSaveModal] = useState(false);
@ -381,20 +390,17 @@ export default function App() {
[inviteCode]
);
const handleEditSearch = useCallback(
(id: string, name: string, params: string) => {
const search = params.startsWith('?') ? params : `?${params}`;
dashboardSearchRef.current = search;
const url = `/dashboard${search}`;
window.history.pushState({ page: 'dashboard', hash: '' }, '', url);
setMapUrlState(parseUrlState());
setDashboardRouteKey(search);
setRouteHash('');
setActivePage('dashboard');
setEditingSearch({ id, name });
},
[]
);
const handleEditSearch = useCallback((id: string, name: string, params: string) => {
const search = params.startsWith('?') ? params : `?${params}`;
dashboardSearchRef.current = search;
const url = `/dashboard${search}`;
window.history.pushState({ page: 'dashboard', hash: '' }, '', url);
setMapUrlState(parseUrlState());
setDashboardRouteKey(search);
setRouteHash('');
setActivePage('dashboard');
setEditingSearch({ id, name });
}, []);
const handleCancelEdit = useCallback(() => {
setEditingSearch(null);
@ -451,13 +457,25 @@ export default function App() {
activePageRef.current = activePage;
}, [activePage]);
// Refs for the initial history.replaceState seed below — the popstate effect runs
// mount-only, but it needs to read the *initial* page/hash/inviteCode values once.
const initialPageRef = useRef(activePage);
const initialRouteHashRef = useRef(routeHash);
const initialInviteCodeRef = useRef(inviteCode);
useEffect(() => {
if (!window.history.state?.page) {
const hash = routeHash || normalizeHash(window.location.hash);
const initialActivePage = initialPageRef.current;
const hash = initialRouteHashRef.current || normalizeHash(window.location.hash);
window.history.replaceState(
{ page: activePage, hash },
{ page: initialActivePage, hash },
'',
buildPageUrl(activePage, inviteCode ?? undefined, window.location.search, hash)
buildPageUrl(
initialActivePage,
initialInviteCodeRef.current ?? undefined,
window.location.search,
hash
)
);
}
const handlePopState = (e: PopStateEvent) => {
@ -487,7 +505,10 @@ export default function App() {
};
window.addEventListener('popstate', handlePopState);
return () => window.removeEventListener('popstate', handlePopState);
}, []); // eslint-disable-line react-hooks/exhaustive-deps
// Mount-only: registers a single popstate listener for the app lifetime and
// seeds initial history state. The handler uses only stable setters and module
// functions; initial-render values are read via refs above.
}, []);
const { fetchSearches } = savedSearches;
useEffect(() => {