deploy
This commit is contained in:
parent
273d7a83ee
commit
084117cea8
48 changed files with 2283 additions and 890 deletions
|
|
@ -12,16 +12,16 @@ import {
|
|||
} from './lib/seoRoutes';
|
||||
import Header, { type Page } from './components/ui/Header';
|
||||
import type { FeatureMeta, FeatureGroup, POICategoriesResponse, POICategoryGroup } from './types';
|
||||
import { fetchWithRetry, apiUrl } from './lib/api';
|
||||
import { fetchWithRetry, apiUrl, logNonAbortError } from './lib/api';
|
||||
import { trackEvent } from './lib/analytics';
|
||||
import { parseUrlState } from './lib/url-state';
|
||||
import { INITIAL_VIEW_STATE } from './lib/consts';
|
||||
import { useTheme } from './hooks/useTheme';
|
||||
import { useIsMobile } from './hooks/useIsMobile';
|
||||
import { useAuth } from './hooks/useAuth';
|
||||
import { useLicense } from './hooks/useLicense';
|
||||
import { useTelemetry } from './hooks/useTelemetry';
|
||||
import { useSavedSearches } from './hooks/useSavedSearches';
|
||||
import { useSavedProperties } from './hooks/useSavedProperties';
|
||||
|
||||
declare global {
|
||||
interface Window {
|
||||
|
|
@ -39,9 +39,6 @@ const AccountPage = lazy(() => import('./components/account/AccountPage'));
|
|||
const SavedPage = lazy(() =>
|
||||
import('./components/account/AccountPage').then((module) => ({ default: module.SavedPage }))
|
||||
);
|
||||
const InvitesPage = lazy(() =>
|
||||
import('./components/account/AccountPage').then((module) => ({ default: module.InvitesPage }))
|
||||
);
|
||||
const InvitePage = lazy(() => import('./components/invite/InvitePage'));
|
||||
const MapPage = lazy(() => import('./components/map/MapPage'));
|
||||
const AuthModal = lazy(() => import('./components/ui/AuthModal'));
|
||||
|
|
@ -52,6 +49,49 @@ function PageFallback() {
|
|||
return <div className="flex-1 bg-warm-50 dark:bg-navy-950" />;
|
||||
}
|
||||
|
||||
interface RouteMatch {
|
||||
page: Page;
|
||||
inviteCode?: string;
|
||||
hash?: string;
|
||||
}
|
||||
|
||||
type PostAuthIntent = 'checkout';
|
||||
type LicenseSuccessStatus = 'hidden' | 'verifying' | 'success' | 'delayed';
|
||||
|
||||
const LICENSE_VERIFICATION_ATTEMPTS = 8;
|
||||
const LICENSE_VERIFICATION_DELAY_MS = 1500;
|
||||
|
||||
function hasFullAccess(user?: { subscription?: string; isAdmin?: boolean } | null): boolean {
|
||||
return user?.subscription === 'licensed' || user?.isAdmin === true;
|
||||
}
|
||||
|
||||
function delay(ms: number): Promise<void> {
|
||||
return new Promise((resolve) => window.setTimeout(resolve, ms));
|
||||
}
|
||||
|
||||
function normalizeHash(hash?: string | null): string {
|
||||
return hash?.replace(/^#/, '') ?? '';
|
||||
}
|
||||
|
||||
function currentRelativePath(): string {
|
||||
return `${window.location.pathname}${window.location.search}`;
|
||||
}
|
||||
|
||||
function isProtectedPage(page: Page): boolean {
|
||||
return page === 'account' || page === 'saved';
|
||||
}
|
||||
|
||||
function buildPageUrl(page: Page, inviteCode?: string, search = '', hash = ''): string {
|
||||
const normalizedHash = normalizeHash(hash);
|
||||
return `${pageToPath(page, inviteCode)}${search}${normalizedHash ? `#${normalizedHash}` : ''}`;
|
||||
}
|
||||
|
||||
function scrollToHash(hash: string) {
|
||||
window.requestAnimationFrame(() => {
|
||||
document.getElementById(hash)?.scrollIntoView({ block: 'start', behavior: 'smooth' });
|
||||
});
|
||||
}
|
||||
|
||||
function unavailableAuthAction(): never {
|
||||
throw new Error('Authentication actions are not available in this render mode');
|
||||
}
|
||||
|
|
@ -79,8 +119,6 @@ function pageToPath(page: Page, inviteCode?: string): string {
|
|||
return SEO_CONTENT_PATHS[page];
|
||||
case 'saved':
|
||||
return '/saved';
|
||||
case 'invites':
|
||||
return '/invites';
|
||||
case 'account':
|
||||
return '/account';
|
||||
case 'invite':
|
||||
|
|
@ -93,10 +131,10 @@ function pageToPath(page: Page, inviteCode?: string): string {
|
|||
}
|
||||
}
|
||||
|
||||
function pathToPage(pathname: string): { page: Page; inviteCode?: string } | null {
|
||||
function pathToPage(pathname: string): RouteMatch | null {
|
||||
if (pathname === '/dashboard') return { page: 'dashboard' };
|
||||
if (pathname === '/saved') return { page: 'saved' };
|
||||
if (pathname === '/invites') return { page: 'invites' };
|
||||
if (pathname === '/invites') return { page: 'account', hash: 'invites' };
|
||||
if (pathname === '/learn') return { page: 'learn' };
|
||||
if (pathname === '/pricing') return { page: 'pricing' };
|
||||
const seoLandingPage = getSeoLandingPage(pathname);
|
||||
|
|
@ -123,7 +161,14 @@ function isSeoContentPage(page: Page): page is SeoContentKey {
|
|||
|
||||
export default function App() {
|
||||
const urlState = useMemo(() => parseUrlState(), []);
|
||||
const initialRoute = useMemo(() => pathToPage(window.location.pathname), []);
|
||||
const [mapUrlState, setMapUrlState] = useState(urlState);
|
||||
const [dashboardRouteKey, setDashboardRouteKey] = useState(() =>
|
||||
window.location.pathname === '/dashboard' ? window.location.search : ''
|
||||
);
|
||||
const [dashboardParams, setDashboardParams] = useState(() =>
|
||||
window.location.pathname === '/dashboard' ? window.location.search.replace(/^\?/, '') : ''
|
||||
);
|
||||
const dashboardSearchRef = useRef(
|
||||
window.location.pathname === '/dashboard' ? window.location.search : ''
|
||||
);
|
||||
|
|
@ -147,15 +192,16 @@ export default function App() {
|
|||
const [initialLoading, setInitialLoading] = useState(true);
|
||||
const [pendingInfoFeature, setPendingInfoFeature] = useState<string | null>(null);
|
||||
const [inviteCode, setInviteCode] = useState<string | null>(() => {
|
||||
const fromPath = pathToPage(window.location.pathname);
|
||||
return fromPath?.inviteCode ?? null;
|
||||
return initialRoute?.inviteCode ?? null;
|
||||
});
|
||||
const [routeHash, setRouteHash] = useState(
|
||||
() => initialRoute?.hash ?? normalizeHash(window.location.hash)
|
||||
);
|
||||
const [activePage, setActivePage] = useState<Page>(() => {
|
||||
if (isScreenshotMode) return 'dashboard';
|
||||
|
||||
// Derive page from URL pathname
|
||||
const fromPath = pathToPage(window.location.pathname);
|
||||
if (fromPath) return fromPath.page;
|
||||
if (initialRoute) return initialRoute.page;
|
||||
|
||||
// Restore from history state (e.g. popstate)
|
||||
if (window.history.state?.page) return window.history.state.page;
|
||||
|
|
@ -182,26 +228,95 @@ export default function App() {
|
|||
refreshAuth,
|
||||
clearError,
|
||||
} = useAuth();
|
||||
const { startCheckout: startPostAuthCheckout } = useLicense();
|
||||
const [showAuthModal, setShowAuthModal] = useState(false);
|
||||
const [authModalTab, setAuthModalTab] = useState<'login' | 'register'>('login');
|
||||
const [showLicenseSuccess, setShowLicenseSuccess] = useState(false);
|
||||
const [postAuthIntent, setPostAuthIntent] = useState<PostAuthIntent | null>(null);
|
||||
const postAuthCheckoutReturnPathRef = useRef<string | null>(null);
|
||||
const authCompletedRef = useRef(false);
|
||||
const [licenseSuccessStatus, setLicenseSuccessStatus] = useState<LicenseSuccessStatus>('hidden');
|
||||
|
||||
const openAuthModal = useCallback(
|
||||
(
|
||||
tab: 'login' | 'register',
|
||||
intent: PostAuthIntent | null = null,
|
||||
checkoutReturnPath?: string
|
||||
) => {
|
||||
authCompletedRef.current = false;
|
||||
postAuthCheckoutReturnPathRef.current =
|
||||
intent === 'checkout' ? (checkoutReturnPath ?? currentRelativePath()) : null;
|
||||
setPostAuthIntent(intent);
|
||||
setAuthModalTab(tab);
|
||||
setShowAuthModal(true);
|
||||
clearError();
|
||||
},
|
||||
[clearError]
|
||||
);
|
||||
|
||||
const closeAuthModal = useCallback(() => {
|
||||
setShowAuthModal(false);
|
||||
const completed = authCompletedRef.current;
|
||||
if (!completed) {
|
||||
setPostAuthIntent(null);
|
||||
postAuthCheckoutReturnPathRef.current = null;
|
||||
if (isProtectedPage(activePageRef.current)) {
|
||||
window.history.replaceState({ page: 'home', hash: '' }, '', '/');
|
||||
setRouteHash('');
|
||||
setActivePage('home');
|
||||
}
|
||||
}
|
||||
authCompletedRef.current = false;
|
||||
}, []);
|
||||
|
||||
useEffect(() => {
|
||||
const params = new URLSearchParams(window.location.search);
|
||||
if (params.get('license_success') === '1') {
|
||||
const returnedFromCheckout = params.get('license_success') === '1';
|
||||
let cancelled = false;
|
||||
|
||||
if (returnedFromCheckout) {
|
||||
params.delete('license_success');
|
||||
const newUrl = params.toString()
|
||||
? `${window.location.pathname}?${params.toString()}`
|
||||
: window.location.pathname;
|
||||
window.history.replaceState({}, '', newUrl);
|
||||
trackEvent('Purchase');
|
||||
setShowLicenseSuccess(true);
|
||||
}
|
||||
// Always refresh auth on startup to pick up server-side subscription changes
|
||||
refreshAuth().catch(() => {});
|
||||
|
||||
async function refreshOnStartup() {
|
||||
if (!returnedFromCheckout) {
|
||||
// Always refresh auth on startup to pick up server-side subscription changes.
|
||||
refreshAuth().catch(() => {});
|
||||
return;
|
||||
}
|
||||
|
||||
setLicenseSuccessStatus('verifying');
|
||||
for (let attempt = 0; attempt < LICENSE_VERIFICATION_ATTEMPTS; attempt += 1) {
|
||||
try {
|
||||
const refreshedUser = await refreshAuth();
|
||||
if (cancelled) return;
|
||||
if (hasFullAccess(refreshedUser)) {
|
||||
trackEvent('Purchase');
|
||||
setLicenseSuccessStatus('success');
|
||||
return;
|
||||
}
|
||||
} catch (error) {
|
||||
logNonAbortError('Failed to verify license activation', error);
|
||||
break;
|
||||
}
|
||||
|
||||
await delay(LICENSE_VERIFICATION_DELAY_MS);
|
||||
if (cancelled) return;
|
||||
}
|
||||
|
||||
if (!cancelled) setLicenseSuccessStatus('delayed');
|
||||
}
|
||||
|
||||
refreshOnStartup();
|
||||
return () => {
|
||||
cancelled = true;
|
||||
};
|
||||
}, []); // eslint-disable-line react-hooks/exhaustive-deps
|
||||
|
||||
const savedSearches = useSavedSearches(user?.id ?? null);
|
||||
const savedProperties = useSavedProperties(user?.id ?? null);
|
||||
const [showSaveModal, setShowSaveModal] = useState(false);
|
||||
|
||||
useEffect(() => {
|
||||
|
|
@ -241,6 +356,7 @@ export default function App() {
|
|||
|
||||
const navigateTo = useCallback(
|
||||
(page: Page, hash?: string, infoFeature?: string) => {
|
||||
const targetHash = normalizeHash(hash);
|
||||
// Save dashboard search params before navigating away
|
||||
if (activePageRef.current === 'dashboard') {
|
||||
dashboardSearchRef.current = window.location.search;
|
||||
|
|
@ -248,38 +364,67 @@ export default function App() {
|
|||
if (infoFeature) {
|
||||
window.history.replaceState({ ...window.history.state, infoFeature }, '');
|
||||
}
|
||||
const path = pageToPath(page, inviteCode ?? undefined);
|
||||
// Restore dashboard search params when navigating back
|
||||
const search = page === 'dashboard' ? dashboardSearchRef.current : '';
|
||||
const url = hash ? `${path}${search}#${hash}` : `${path}${search}`;
|
||||
window.history.pushState({ page }, '', url);
|
||||
const url = buildPageUrl(page, inviteCode ?? undefined, search, targetHash);
|
||||
window.history.pushState({ page, hash: targetHash }, '', url);
|
||||
if (page === 'dashboard') {
|
||||
setMapUrlState(parseUrlState());
|
||||
setDashboardRouteKey(window.location.search);
|
||||
}
|
||||
setRouteHash(targetHash);
|
||||
setActivePage(page);
|
||||
if (targetHash) scrollToHash(targetHash);
|
||||
},
|
||||
[inviteCode]
|
||||
);
|
||||
|
||||
useEffect(() => {
|
||||
if (authLoading || !user || postAuthIntent !== 'checkout') return;
|
||||
|
||||
setPostAuthIntent(null);
|
||||
setShowAuthModal(false);
|
||||
const checkoutReturnPath = postAuthCheckoutReturnPathRef.current ?? undefined;
|
||||
postAuthCheckoutReturnPathRef.current = null;
|
||||
if (hasFullAccess(user)) {
|
||||
if (checkoutReturnPath?.startsWith('/dashboard')) {
|
||||
window.history.pushState({ page: 'dashboard', hash: '' }, '', checkoutReturnPath);
|
||||
setMapUrlState(parseUrlState());
|
||||
setDashboardRouteKey(window.location.search);
|
||||
setRouteHash('');
|
||||
setActivePage('dashboard');
|
||||
} else {
|
||||
navigateTo('dashboard');
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
startPostAuthCheckout(checkoutReturnPath).catch((error) => {
|
||||
logNonAbortError('Failed to resume checkout after auth', error);
|
||||
navigateTo('pricing');
|
||||
});
|
||||
}, [authLoading, navigateTo, postAuthIntent, startPostAuthCheckout, user]);
|
||||
|
||||
useEffect(() => {
|
||||
activePageRef.current = activePage;
|
||||
}, [activePage]);
|
||||
|
||||
useEffect(() => {
|
||||
if (!window.history.state?.page) {
|
||||
const hash = routeHash || normalizeHash(window.location.hash);
|
||||
window.history.replaceState(
|
||||
{ page: activePage },
|
||||
{ page: activePage, hash },
|
||||
'',
|
||||
pageToPath(activePage, inviteCode ?? undefined) +
|
||||
window.location.search +
|
||||
window.location.hash
|
||||
buildPageUrl(activePage, inviteCode ?? undefined, window.location.search, hash)
|
||||
);
|
||||
}
|
||||
const handlePopState = (e: PopStateEvent) => {
|
||||
let page: Page;
|
||||
const hash = normalizeHash(window.location.hash);
|
||||
if (e.state?.page) {
|
||||
page = e.state.page;
|
||||
setActivePage(page);
|
||||
setRouteHash(hash || e.state.hash || '');
|
||||
if (e.state.infoFeature) {
|
||||
setPendingInfoFeature(e.state.infoFeature);
|
||||
}
|
||||
|
|
@ -287,11 +432,13 @@ export default function App() {
|
|||
const parsed = pathToPage(window.location.pathname);
|
||||
page = parsed?.page || 'home';
|
||||
setActivePage(page);
|
||||
setRouteHash(parsed?.hash ?? hash);
|
||||
if (parsed?.inviteCode) setInviteCode(parsed.inviteCode);
|
||||
}
|
||||
// Re-parse URL state when returning to dashboard via back/forward
|
||||
if (page === 'dashboard') {
|
||||
setMapUrlState(parseUrlState());
|
||||
setDashboardRouteKey(window.location.search);
|
||||
}
|
||||
};
|
||||
window.addEventListener('popstate', handlePopState);
|
||||
|
|
@ -299,30 +446,22 @@ export default function App() {
|
|||
}, []); // eslint-disable-line react-hooks/exhaustive-deps
|
||||
|
||||
const { fetchSearches } = savedSearches;
|
||||
const { fetchProperties: fetchSavedProperties } = savedProperties;
|
||||
useEffect(() => {
|
||||
if (activePage === 'saved') {
|
||||
fetchSearches();
|
||||
fetchSavedProperties();
|
||||
}
|
||||
if (activePage === 'dashboard' && user) {
|
||||
fetchSavedProperties();
|
||||
}
|
||||
}, [activePage, fetchSearches, fetchSavedProperties, user]);
|
||||
}, [activePage, fetchSearches]);
|
||||
|
||||
const isAuthRequiredPage =
|
||||
activePage === 'account' || activePage === 'saved' || activePage === 'invites';
|
||||
const isAuthRequiredPage = activePage === 'account' || activePage === 'saved';
|
||||
useEffect(() => {
|
||||
if (authLoading) return;
|
||||
if (isAuthRequiredPage && !user) {
|
||||
setAuthModalTab('login');
|
||||
setShowAuthModal(true);
|
||||
navigateTo('home');
|
||||
openAuthModal('login');
|
||||
}
|
||||
if (activePage === 'pricing' && (user?.subscription === 'licensed' || user?.isAdmin)) {
|
||||
if (activePage === 'pricing' && hasFullAccess(user)) {
|
||||
navigateTo('dashboard');
|
||||
}
|
||||
}, [activePage, isAuthRequiredPage, user, authLoading, navigateTo]);
|
||||
}, [activePage, authLoading, isAuthRequiredPage, navigateTo, openAuthModal, user]);
|
||||
|
||||
const [exportState, setExportState] = useState<ExportState | null>(null);
|
||||
|
||||
|
|
@ -372,21 +511,17 @@ export default function App() {
|
|||
<div className="h-full flex flex-col">
|
||||
<Header
|
||||
activePage={activePage}
|
||||
activeHash={routeHash}
|
||||
onPageChange={navigateTo}
|
||||
theme={theme}
|
||||
onToggleTheme={toggleTheme}
|
||||
exportState={activePage === 'dashboard' ? exportState : null}
|
||||
dashboardParams={activePage === 'dashboard' ? dashboardParams : ''}
|
||||
onSaveSearch={activePage === 'dashboard' && user ? () => setShowSaveModal(true) : null}
|
||||
savingSearch={savedSearches.saving}
|
||||
user={user}
|
||||
onLoginClick={() => {
|
||||
setAuthModalTab('login');
|
||||
setShowAuthModal(true);
|
||||
}}
|
||||
onRegisterClick={() => {
|
||||
setAuthModalTab('register');
|
||||
setShowAuthModal(true);
|
||||
}}
|
||||
onLoginClick={() => openAuthModal('login')}
|
||||
onRegisterClick={() => openAuthModal('register')}
|
||||
onLogout={logout}
|
||||
isMobile={isMobile}
|
||||
/>
|
||||
|
|
@ -402,14 +537,8 @@ export default function App() {
|
|||
<PricingPage
|
||||
onOpenDashboard={() => navigateTo('dashboard')}
|
||||
user={user}
|
||||
onLoginClick={() => {
|
||||
setAuthModalTab('login');
|
||||
setShowAuthModal(true);
|
||||
}}
|
||||
onRegisterClick={() => {
|
||||
setAuthModalTab('register');
|
||||
setShowAuthModal(true);
|
||||
}}
|
||||
onLoginClick={() => openAuthModal('login', 'checkout', '/pricing')}
|
||||
onRegisterClick={() => openAuthModal('register', 'checkout', '/pricing')}
|
||||
/>
|
||||
) : activePage === 'learn' ? (
|
||||
<LearnPage />
|
||||
|
|
@ -427,38 +556,32 @@ export default function App() {
|
|||
onOpenSearch={(params) => {
|
||||
window.location.href = `/dashboard?${params}`;
|
||||
}}
|
||||
savedProperties={savedProperties.properties}
|
||||
propertiesLoading={savedProperties.loading}
|
||||
onDeleteProperty={savedProperties.deleteProperty}
|
||||
onUpdatePropertyNotes={savedProperties.updatePropertyNotes}
|
||||
onOpenProperty={(postcode) => {
|
||||
window.location.href = `/dashboard?pc=${encodeURIComponent(postcode)}`;
|
||||
}}
|
||||
/>
|
||||
) : activePage === 'invites' && user ? (
|
||||
<InvitesPage user={user} />
|
||||
) : activePage === 'account' && user ? (
|
||||
<AccountPage user={user} onRefreshAuth={refreshAuth} />
|
||||
<AccountPage
|
||||
user={user}
|
||||
onRefreshAuth={async () => {
|
||||
await refreshAuth();
|
||||
}}
|
||||
scrollTarget={routeHash}
|
||||
/>
|
||||
) : isAuthRequiredPage && !user ? (
|
||||
<PageFallback />
|
||||
) : activePage === 'invite' && inviteCode ? (
|
||||
<InvitePage
|
||||
code={inviteCode}
|
||||
user={user}
|
||||
theme={theme}
|
||||
onLoginClick={() => {
|
||||
setAuthModalTab('login');
|
||||
setShowAuthModal(true);
|
||||
}}
|
||||
onRegisterClick={() => {
|
||||
setAuthModalTab('register');
|
||||
setShowAuthModal(true);
|
||||
}}
|
||||
onLoginClick={() => openAuthModal('login')}
|
||||
onRegisterClick={() => openAuthModal('register')}
|
||||
onLicenseGranted={() => {
|
||||
setShowLicenseSuccess(true);
|
||||
setLicenseSuccessStatus('success');
|
||||
refreshAuth();
|
||||
}}
|
||||
/>
|
||||
) : (
|
||||
<MapPage
|
||||
key={dashboardRouteKey}
|
||||
features={features}
|
||||
poiCategoryGroups={poiCategoryGroups}
|
||||
initialFilters={mapUrlState.filters}
|
||||
|
|
@ -471,24 +594,19 @@ export default function App() {
|
|||
onClearPendingInfoFeature={() => setPendingInfoFeature(null)}
|
||||
onNavigateTo={navigateTo}
|
||||
onExportStateChange={setExportState}
|
||||
onDashboardParamsChange={setDashboardParams}
|
||||
isMobile={isMobile}
|
||||
initialTravelTime={mapUrlState.travelTime}
|
||||
initialPostcode={mapUrlState.postcode}
|
||||
shareCode={mapUrlState.share}
|
||||
user={user}
|
||||
onLoginClick={() => {
|
||||
setAuthModalTab('login');
|
||||
setShowAuthModal(true);
|
||||
}}
|
||||
onRegisterClick={() => {
|
||||
setAuthModalTab('register');
|
||||
setShowAuthModal(true);
|
||||
}}
|
||||
onSaveProperty={user ? savedProperties.saveProperty : undefined}
|
||||
onUnsaveProperty={user ? savedProperties.deleteProperty : undefined}
|
||||
isPropertySaved={user ? savedProperties.isPropertySaved : undefined}
|
||||
getSavedPropertyId={user ? savedProperties.getSavedPropertyId : undefined}
|
||||
deferTutorial={showLicenseSuccess}
|
||||
onLoginClick={() => openAuthModal('login')}
|
||||
onRegisterClick={() => openAuthModal('register')}
|
||||
onCheckoutLoginClick={(returnPath) => openAuthModal('login', 'checkout', returnPath)}
|
||||
onCheckoutRegisterClick={(returnPath) =>
|
||||
openAuthModal('register', 'checkout', returnPath)
|
||||
}
|
||||
deferTutorial={licenseSuccessStatus !== 'hidden'}
|
||||
onSaveSearch={user ? savedSearches.saveSearch : undefined}
|
||||
savingSearch={savedSearches.saving}
|
||||
/>
|
||||
|
|
@ -497,7 +615,10 @@ export default function App() {
|
|||
<Suspense fallback={null}>
|
||||
{showAuthModal && (
|
||||
<AuthModal
|
||||
onClose={() => setShowAuthModal(false)}
|
||||
onClose={closeAuthModal}
|
||||
onAuthenticated={() => {
|
||||
authCompletedRef.current = true;
|
||||
}}
|
||||
onLogin={login}
|
||||
onRegister={register}
|
||||
onOAuthLogin={loginWithOAuth}
|
||||
|
|
@ -511,7 +632,7 @@ export default function App() {
|
|||
{showSaveModal && (
|
||||
<SaveSearchModal
|
||||
onClose={() => setShowSaveModal(false)}
|
||||
onSave={savedSearches.saveSearch}
|
||||
onSave={(name) => savedSearches.saveSearch(name, dashboardParams)}
|
||||
onViewSearches={() => {
|
||||
setShowSaveModal(false);
|
||||
navigateTo('saved');
|
||||
|
|
@ -520,11 +641,13 @@ export default function App() {
|
|||
error={savedSearches.error}
|
||||
/>
|
||||
)}
|
||||
{showLicenseSuccess && (
|
||||
{licenseSuccessStatus !== 'hidden' && (
|
||||
<LicenseSuccessModal
|
||||
status={licenseSuccessStatus}
|
||||
onClose={() => {
|
||||
setShowLicenseSuccess(false);
|
||||
navigateTo('dashboard');
|
||||
const shouldOpenDashboard = licenseSuccessStatus === 'success';
|
||||
setLicenseSuccessStatus('hidden');
|
||||
if (shouldOpenDashboard) navigateTo('dashboard');
|
||||
}}
|
||||
/>
|
||||
)}
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue