seems fine
This commit is contained in:
parent
48983e3b4b
commit
7a1696541f
37 changed files with 4999 additions and 1242 deletions
|
|
@ -1,14 +1,16 @@
|
|||
import { useState, useEffect, useCallback, useMemo, useRef } from 'react';
|
||||
import MapPage, { type ExportState } from './components/map/MapPage';
|
||||
import PricingPage from './components/pricing/PricingPage';
|
||||
import HomePage from './components/home/HomePage';
|
||||
import LearnPage from './components/learn/LearnPage';
|
||||
import AccountPage, { SavedPage, InvitesPage } from './components/account/AccountPage';
|
||||
import InvitePage from './components/invite/InvitePage';
|
||||
import { lazy, Suspense, useState, useEffect, useCallback, useMemo, useRef } from 'react';
|
||||
import type { ExportState } from './components/map/MapPage';
|
||||
import {
|
||||
getSeoContentPage,
|
||||
getSeoLandingPage,
|
||||
isSeoContentKey,
|
||||
isSeoLandingKey,
|
||||
SEO_CONTENT_PATHS,
|
||||
SEO_LANDING_PATHS,
|
||||
type SeoContentKey,
|
||||
type SeoLandingKey,
|
||||
} from './lib/seoRoutes';
|
||||
import Header, { type Page } from './components/ui/Header';
|
||||
import AuthModal from './components/ui/AuthModal';
|
||||
import SaveSearchModal from './components/ui/SaveSearchModal';
|
||||
import LicenseSuccessModal from './components/ui/LicenseSuccessModal';
|
||||
import type { FeatureMeta, FeatureGroup, POICategoriesResponse, POICategoryGroup } from './types';
|
||||
import { fetchWithRetry, apiUrl } from './lib/api';
|
||||
import { trackEvent } from './lib/analytics';
|
||||
|
|
@ -28,6 +30,28 @@ declare global {
|
|||
}
|
||||
}
|
||||
|
||||
const HomePage = lazy(() => import('./components/home/HomePage'));
|
||||
const PricingPage = lazy(() => import('./components/pricing/PricingPage'));
|
||||
const LearnPage = lazy(() => import('./components/learn/LearnPage'));
|
||||
const SeoLandingPage = lazy(() => import('./components/landing/SeoLandingPage'));
|
||||
const SeoContentPage = lazy(() => import('./components/landing/SeoContentPage'));
|
||||
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'));
|
||||
const SaveSearchModal = lazy(() => import('./components/ui/SaveSearchModal'));
|
||||
const LicenseSuccessModal = lazy(() => import('./components/ui/LicenseSuccessModal'));
|
||||
|
||||
function PageFallback() {
|
||||
return <div className="flex-1 bg-warm-50 dark:bg-navy-950" />;
|
||||
}
|
||||
|
||||
function pageToPath(page: Page, inviteCode?: string): string {
|
||||
switch (page) {
|
||||
case 'dashboard':
|
||||
|
|
@ -36,6 +60,19 @@ function pageToPath(page: Page, inviteCode?: string): string {
|
|||
return '/learn';
|
||||
case 'pricing':
|
||||
return '/pricing';
|
||||
case 'property-price-map':
|
||||
case 'postcode-property-search':
|
||||
case 'commute-property-search':
|
||||
case 'school-property-search':
|
||||
case 'postcode-checker':
|
||||
return SEO_LANDING_PATHS[page];
|
||||
case 'birmingham-property-search':
|
||||
case 'manchester-property-search':
|
||||
case 'bristol-property-search':
|
||||
case 'data-sources':
|
||||
case 'methodology':
|
||||
case 'privacy-security':
|
||||
return SEO_CONTENT_PATHS[page];
|
||||
case 'saved':
|
||||
return '/saved';
|
||||
case 'invites':
|
||||
|
|
@ -55,6 +92,10 @@ function pathToPage(pathname: string): { page: Page; inviteCode?: string } | nul
|
|||
if (pathname === '/invites') return { page: 'invites' };
|
||||
if (pathname === '/learn') return { page: 'learn' };
|
||||
if (pathname === '/pricing') return { page: 'pricing' };
|
||||
const seoLandingPage = getSeoLandingPage(pathname);
|
||||
if (seoLandingPage) return { page: seoLandingPage };
|
||||
const seoContentPage = getSeoContentPage(pathname);
|
||||
if (seoContentPage) return { page: seoContentPage };
|
||||
if (pathname === '/account') return { page: 'account' };
|
||||
if (pathname === '/support') return { page: 'learn' };
|
||||
if (pathname.startsWith('/invite/')) {
|
||||
|
|
@ -65,6 +106,14 @@ function pathToPage(pathname: string): { page: Page; inviteCode?: string } | nul
|
|||
return null;
|
||||
}
|
||||
|
||||
function isSeoLandingPage(page: Page): page is SeoLandingKey {
|
||||
return isSeoLandingKey(page);
|
||||
}
|
||||
|
||||
function isSeoContentPage(page: Page): page is SeoContentKey {
|
||||
return isSeoContentKey(page);
|
||||
}
|
||||
|
||||
export default function App() {
|
||||
const urlState = useMemo(() => parseUrlState(), []);
|
||||
const [mapUrlState, setMapUrlState] = useState(urlState);
|
||||
|
|
@ -271,37 +320,41 @@ export default function App() {
|
|||
|
||||
if ((isScreenshotMode || isOgMode) && inviteCode) {
|
||||
return (
|
||||
<InvitePage
|
||||
code={inviteCode}
|
||||
user={null}
|
||||
theme={theme}
|
||||
screenshotMode
|
||||
onLoginClick={() => {}}
|
||||
onRegisterClick={() => {}}
|
||||
onLicenseGranted={() => {}}
|
||||
/>
|
||||
<Suspense fallback={<PageFallback />}>
|
||||
<InvitePage
|
||||
code={inviteCode}
|
||||
user={null}
|
||||
theme={theme}
|
||||
screenshotMode
|
||||
onLoginClick={() => {}}
|
||||
onRegisterClick={() => {}}
|
||||
onLicenseGranted={() => {}}
|
||||
/>
|
||||
</Suspense>
|
||||
);
|
||||
}
|
||||
|
||||
if (isScreenshotMode) {
|
||||
return (
|
||||
<MapPage
|
||||
features={features}
|
||||
poiCategoryGroups={poiCategoryGroups}
|
||||
initialFilters={urlState.filters || {}}
|
||||
initialViewState={initialViewState}
|
||||
initialPOICategories={urlState.poiCategories || new Set()}
|
||||
initialTab={urlState.tab || 'area'}
|
||||
initialLoading={initialLoading}
|
||||
theme={theme}
|
||||
pendingInfoFeature={null}
|
||||
onClearPendingInfoFeature={() => {}}
|
||||
onNavigateTo={() => {}}
|
||||
screenshotMode
|
||||
ogMode={isOgMode}
|
||||
initialTravelTime={urlState.travelTime}
|
||||
shareCode={urlState.share}
|
||||
/>
|
||||
<Suspense fallback={<PageFallback />}>
|
||||
<MapPage
|
||||
features={features}
|
||||
poiCategoryGroups={poiCategoryGroups}
|
||||
initialFilters={urlState.filters || {}}
|
||||
initialViewState={initialViewState}
|
||||
initialPOICategories={urlState.poiCategories || new Set()}
|
||||
initialTab={urlState.tab || 'area'}
|
||||
initialLoading={initialLoading}
|
||||
theme={theme}
|
||||
pendingInfoFeature={null}
|
||||
onClearPendingInfoFeature={() => {}}
|
||||
onNavigateTo={() => {}}
|
||||
screenshotMode
|
||||
ogMode={isOgMode}
|
||||
initialTravelTime={urlState.travelTime}
|
||||
shareCode={urlState.share}
|
||||
/>
|
||||
</Suspense>
|
||||
);
|
||||
}
|
||||
|
||||
|
|
@ -328,137 +381,145 @@ export default function App() {
|
|||
onLogout={logout}
|
||||
isMobile={isMobile}
|
||||
/>
|
||||
{activePage === 'home' ? (
|
||||
<HomePage
|
||||
onOpenDashboard={() => navigateTo('dashboard')}
|
||||
onOpenPricing={() => navigateTo('pricing')}
|
||||
theme={theme}
|
||||
hidePricing={user?.subscription === 'licensed' || user?.isAdmin}
|
||||
/>
|
||||
) : activePage === 'pricing' && !(user?.subscription === 'licensed' || user?.isAdmin) ? (
|
||||
<PricingPage
|
||||
onOpenDashboard={() => navigateTo('dashboard')}
|
||||
user={user}
|
||||
onLoginClick={() => {
|
||||
setAuthModalTab('login');
|
||||
setShowAuthModal(true);
|
||||
}}
|
||||
onRegisterClick={() => {
|
||||
setAuthModalTab('register');
|
||||
setShowAuthModal(true);
|
||||
}}
|
||||
/>
|
||||
) : activePage === 'learn' ? (
|
||||
<LearnPage />
|
||||
) : activePage === 'saved' && user ? (
|
||||
<SavedPage
|
||||
searches={savedSearches.searches}
|
||||
searchesLoading={savedSearches.loading}
|
||||
onDeleteSearch={savedSearches.deleteSearch}
|
||||
onUpdateSearchNotes={savedSearches.updateSearchNotes}
|
||||
onUpdateSearchName={savedSearches.updateSearchName}
|
||||
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} />
|
||||
) : activePage === 'invite' && inviteCode ? (
|
||||
<InvitePage
|
||||
code={inviteCode}
|
||||
user={user}
|
||||
theme={theme}
|
||||
onLoginClick={() => {
|
||||
setAuthModalTab('login');
|
||||
setShowAuthModal(true);
|
||||
}}
|
||||
onRegisterClick={() => {
|
||||
setAuthModalTab('register');
|
||||
setShowAuthModal(true);
|
||||
}}
|
||||
onLicenseGranted={() => {
|
||||
setShowLicenseSuccess(true);
|
||||
refreshAuth();
|
||||
}}
|
||||
/>
|
||||
) : (
|
||||
<MapPage
|
||||
features={features}
|
||||
poiCategoryGroups={poiCategoryGroups}
|
||||
initialFilters={mapUrlState.filters || {}}
|
||||
initialViewState={initialViewState}
|
||||
initialPOICategories={mapUrlState.poiCategories || new Set()}
|
||||
initialTab={mapUrlState.tab || 'area'}
|
||||
initialLoading={initialLoading}
|
||||
theme={theme}
|
||||
pendingInfoFeature={pendingInfoFeature}
|
||||
onClearPendingInfoFeature={() => setPendingInfoFeature(null)}
|
||||
onNavigateTo={navigateTo}
|
||||
onExportStateChange={setExportState}
|
||||
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}
|
||||
onSaveSearch={user ? savedSearches.saveSearch : undefined}
|
||||
savingSearch={savedSearches.saving}
|
||||
/>
|
||||
)}
|
||||
{showAuthModal && (
|
||||
<AuthModal
|
||||
onClose={() => setShowAuthModal(false)}
|
||||
onLogin={login}
|
||||
onRegister={register}
|
||||
onOAuthLogin={loginWithOAuth}
|
||||
onForgotPassword={requestPasswordReset}
|
||||
loading={authLoading}
|
||||
error={authError}
|
||||
onClearError={clearError}
|
||||
initialTab={authModalTab}
|
||||
/>
|
||||
)}
|
||||
{showSaveModal && (
|
||||
<SaveSearchModal
|
||||
onClose={() => setShowSaveModal(false)}
|
||||
onSave={savedSearches.saveSearch}
|
||||
onViewSearches={() => {
|
||||
setShowSaveModal(false);
|
||||
navigateTo('saved');
|
||||
}}
|
||||
saving={savedSearches.saving}
|
||||
error={savedSearches.error}
|
||||
/>
|
||||
)}
|
||||
{showLicenseSuccess && (
|
||||
<LicenseSuccessModal
|
||||
onClose={() => {
|
||||
setShowLicenseSuccess(false);
|
||||
navigateTo('dashboard');
|
||||
}}
|
||||
/>
|
||||
)}
|
||||
<Suspense fallback={<PageFallback />}>
|
||||
{activePage === 'home' ? (
|
||||
<HomePage
|
||||
onOpenDashboard={() => navigateTo('dashboard')}
|
||||
onOpenPricing={() => navigateTo('pricing')}
|
||||
theme={theme}
|
||||
hidePricing={user?.subscription === 'licensed' || user?.isAdmin}
|
||||
/>
|
||||
) : activePage === 'pricing' && !(user?.subscription === 'licensed' || user?.isAdmin) ? (
|
||||
<PricingPage
|
||||
onOpenDashboard={() => navigateTo('dashboard')}
|
||||
user={user}
|
||||
onLoginClick={() => {
|
||||
setAuthModalTab('login');
|
||||
setShowAuthModal(true);
|
||||
}}
|
||||
onRegisterClick={() => {
|
||||
setAuthModalTab('register');
|
||||
setShowAuthModal(true);
|
||||
}}
|
||||
/>
|
||||
) : activePage === 'learn' ? (
|
||||
<LearnPage />
|
||||
) : isSeoLandingPage(activePage) ? (
|
||||
<SeoLandingPage pageKey={activePage} onOpenDashboard={() => navigateTo('dashboard')} />
|
||||
) : isSeoContentPage(activePage) ? (
|
||||
<SeoContentPage pageKey={activePage} onOpenDashboard={() => navigateTo('dashboard')} />
|
||||
) : activePage === 'saved' && user ? (
|
||||
<SavedPage
|
||||
searches={savedSearches.searches}
|
||||
searchesLoading={savedSearches.loading}
|
||||
onDeleteSearch={savedSearches.deleteSearch}
|
||||
onUpdateSearchNotes={savedSearches.updateSearchNotes}
|
||||
onUpdateSearchName={savedSearches.updateSearchName}
|
||||
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} />
|
||||
) : activePage === 'invite' && inviteCode ? (
|
||||
<InvitePage
|
||||
code={inviteCode}
|
||||
user={user}
|
||||
theme={theme}
|
||||
onLoginClick={() => {
|
||||
setAuthModalTab('login');
|
||||
setShowAuthModal(true);
|
||||
}}
|
||||
onRegisterClick={() => {
|
||||
setAuthModalTab('register');
|
||||
setShowAuthModal(true);
|
||||
}}
|
||||
onLicenseGranted={() => {
|
||||
setShowLicenseSuccess(true);
|
||||
refreshAuth();
|
||||
}}
|
||||
/>
|
||||
) : (
|
||||
<MapPage
|
||||
features={features}
|
||||
poiCategoryGroups={poiCategoryGroups}
|
||||
initialFilters={mapUrlState.filters || {}}
|
||||
initialViewState={initialViewState}
|
||||
initialPOICategories={mapUrlState.poiCategories || new Set()}
|
||||
initialTab={mapUrlState.tab || 'area'}
|
||||
initialLoading={initialLoading}
|
||||
theme={theme}
|
||||
pendingInfoFeature={pendingInfoFeature}
|
||||
onClearPendingInfoFeature={() => setPendingInfoFeature(null)}
|
||||
onNavigateTo={navigateTo}
|
||||
onExportStateChange={setExportState}
|
||||
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}
|
||||
onSaveSearch={user ? savedSearches.saveSearch : undefined}
|
||||
savingSearch={savedSearches.saving}
|
||||
/>
|
||||
)}
|
||||
</Suspense>
|
||||
<Suspense fallback={null}>
|
||||
{showAuthModal && (
|
||||
<AuthModal
|
||||
onClose={() => setShowAuthModal(false)}
|
||||
onLogin={login}
|
||||
onRegister={register}
|
||||
onOAuthLogin={loginWithOAuth}
|
||||
onForgotPassword={requestPasswordReset}
|
||||
loading={authLoading}
|
||||
error={authError}
|
||||
onClearError={clearError}
|
||||
initialTab={authModalTab}
|
||||
/>
|
||||
)}
|
||||
{showSaveModal && (
|
||||
<SaveSearchModal
|
||||
onClose={() => setShowSaveModal(false)}
|
||||
onSave={savedSearches.saveSearch}
|
||||
onViewSearches={() => {
|
||||
setShowSaveModal(false);
|
||||
navigateTo('saved');
|
||||
}}
|
||||
saving={savedSearches.saving}
|
||||
error={savedSearches.error}
|
||||
/>
|
||||
)}
|
||||
{showLicenseSuccess && (
|
||||
<LicenseSuccessModal
|
||||
onClose={() => {
|
||||
setShowLicenseSuccess(false);
|
||||
navigateTo('dashboard');
|
||||
}}
|
||||
/>
|
||||
)}
|
||||
</Suspense>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue