lmao
This commit is contained in:
parent
03445188ea
commit
524580eb25
102 changed files with 36625 additions and 1295 deletions
|
|
@ -5,9 +5,13 @@ import HomePage from './components/home/HomePage';
|
|||
import SavedSearchesPage from './components/saved-searches/SavedSearchesPage';
|
||||
import LearnPage from './components/learn/LearnPage';
|
||||
import AccountPage from './components/account/AccountPage';
|
||||
import InvitePage from './components/invite/InvitePage';
|
||||
import SupportPage from './components/support/SupportPage';
|
||||
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 VerificationBanner from './components/ui/VerificationBanner';
|
||||
import type { FeatureMeta, FeatureGroup, POICategoriesResponse, POICategoryGroup } from './types';
|
||||
import { fetchWithRetry, apiUrl } from './lib/api';
|
||||
import { parseUrlState } from './lib/url-state';
|
||||
|
|
@ -23,11 +27,11 @@ declare global {
|
|||
}
|
||||
}
|
||||
|
||||
function pageToPath(page: Page): string {
|
||||
function pageToPath(page: Page, inviteCode?: string): string {
|
||||
switch (page) {
|
||||
case 'dashboard':
|
||||
return '/dashboard';
|
||||
case 'saved-searches':
|
||||
case 'saved-searches':
|
||||
return '/saved';
|
||||
case 'learn':
|
||||
return '/learn';
|
||||
|
|
@ -35,18 +39,27 @@ case 'saved-searches':
|
|||
return '/pricing';
|
||||
case 'account':
|
||||
return '/account';
|
||||
case 'invite':
|
||||
return `/invite/${inviteCode || ''}`;
|
||||
case 'support':
|
||||
return '/support';
|
||||
default:
|
||||
return '/';
|
||||
}
|
||||
}
|
||||
|
||||
function pathToPage(pathname: string): Page | null {
|
||||
if (pathname === '/dashboard') return 'dashboard';
|
||||
if (pathname === '/saved') return 'saved-searches';
|
||||
if (pathname === '/learn') return 'learn';
|
||||
if (pathname === '/pricing') return 'pricing';
|
||||
if (pathname === '/account') return 'account';
|
||||
if (pathname === '/') return 'home';
|
||||
function pathToPage(pathname: string): { page: Page; inviteCode?: string } | null {
|
||||
if (pathname === '/dashboard') return { page: 'dashboard' };
|
||||
if (pathname === '/saved') return { page: 'saved-searches' };
|
||||
if (pathname === '/learn') return { page: 'learn' };
|
||||
if (pathname === '/pricing') return { page: 'pricing' };
|
||||
if (pathname === '/account') return { page: 'account' };
|
||||
if (pathname === '/support') return { page: 'support' };
|
||||
if (pathname.startsWith('/invite/')) {
|
||||
const code = pathname.slice('/invite/'.length);
|
||||
return { page: 'invite', inviteCode: code };
|
||||
}
|
||||
if (pathname === '/') return { page: 'home' };
|
||||
return null;
|
||||
}
|
||||
|
||||
|
|
@ -73,12 +86,13 @@ export default function App() {
|
|||
|
||||
// UI state
|
||||
const [pendingInfoFeature, setPendingInfoFeature] = useState<string | null>(null);
|
||||
const [inviteCode, setInviteCode] = useState<string | null>(null);
|
||||
const [activePage, setActivePage] = useState<Page>(() => {
|
||||
if (isScreenshotMode) return 'dashboard';
|
||||
|
||||
// Derive page from URL pathname
|
||||
const fromPath = pathToPage(window.location.pathname);
|
||||
if (fromPath) return fromPath;
|
||||
if (fromPath) return fromPath.page;
|
||||
|
||||
// Restore from history state (e.g. popstate)
|
||||
if (window.history.state?.page) return window.history.state.page;
|
||||
|
|
@ -86,6 +100,14 @@ export default function App() {
|
|||
return 'home';
|
||||
});
|
||||
|
||||
// Initialize invite code from URL
|
||||
useEffect(() => {
|
||||
const fromPath = pathToPage(window.location.pathname);
|
||||
if (fromPath?.inviteCode) {
|
||||
setInviteCode(fromPath.inviteCode);
|
||||
}
|
||||
}, []);
|
||||
|
||||
const { theme, toggleTheme } = useTheme();
|
||||
const isMobile = useIsMobile();
|
||||
const {
|
||||
|
|
@ -94,13 +116,31 @@ export default function App() {
|
|||
error: authError,
|
||||
login,
|
||||
register,
|
||||
loginWithOAuth,
|
||||
logout,
|
||||
requestPasswordReset,
|
||||
requestVerification,
|
||||
refreshAuth,
|
||||
clearError,
|
||||
} = useAuth();
|
||||
const [showAuthModal, setShowAuthModal] = useState(false);
|
||||
const [authModalTab, setAuthModalTab] = useState<'login' | 'register'>('login');
|
||||
const [showLicenseSuccess, setShowLicenseSuccess] = useState(false);
|
||||
const [verificationDismissed, setVerificationDismissed] = useState(false);
|
||||
|
||||
// Handle license_success query param (redirect from Stripe)
|
||||
useEffect(() => {
|
||||
const params = new URLSearchParams(window.location.search);
|
||||
if (params.get('license_success') === '1') {
|
||||
params.delete('license_success');
|
||||
const newUrl = params.toString()
|
||||
? `${window.location.pathname}?${params.toString()}`
|
||||
: window.location.pathname;
|
||||
window.history.replaceState({}, '', newUrl);
|
||||
setShowLicenseSuccess(true);
|
||||
refreshAuth();
|
||||
}
|
||||
}, []); // eslint-disable-line react-hooks/exhaustive-deps
|
||||
|
||||
const savedSearches = useSavedSearches(user?.id ?? null);
|
||||
const [showSaveModal, setShowSaveModal] = useState(false);
|
||||
|
|
@ -148,11 +188,11 @@ export default function App() {
|
|||
if (infoFeature) {
|
||||
window.history.replaceState({ ...window.history.state, infoFeature }, '');
|
||||
}
|
||||
const path = pageToPath(page);
|
||||
const path = pageToPath(page, inviteCode ?? undefined);
|
||||
const url = hash ? `${path}#${hash}` : path;
|
||||
window.history.pushState({ page }, '', url);
|
||||
setActivePage(page);
|
||||
}, []);
|
||||
}, [inviteCode]);
|
||||
|
||||
useEffect(() => {
|
||||
if (!window.history.state?.page) {
|
||||
|
|
@ -170,8 +210,9 @@ export default function App() {
|
|||
}
|
||||
} else {
|
||||
// Fall back to deriving page from pathname
|
||||
const page = pathToPage(window.location.pathname);
|
||||
setActivePage(page || 'home');
|
||||
const parsed = pathToPage(window.location.pathname);
|
||||
setActivePage(parsed?.page || 'home');
|
||||
if (parsed?.inviteCode) setInviteCode(parsed.inviteCode);
|
||||
}
|
||||
};
|
||||
window.addEventListener('popstate', handlePopState);
|
||||
|
|
@ -232,14 +273,51 @@ export default function App() {
|
|||
onLogout={logout}
|
||||
isMobile={isMobile}
|
||||
/>
|
||||
{user && !user.verified && !verificationDismissed && (
|
||||
<VerificationBanner
|
||||
email={user.email}
|
||||
onRequestVerification={requestVerification}
|
||||
onDismiss={() => setVerificationDismissed(true)}
|
||||
/>
|
||||
)}
|
||||
{activePage === 'home' ? (
|
||||
<HomePage onOpenDashboard={() => navigateTo('dashboard')} onOpenPricing={() => navigateTo('pricing')} theme={theme} features={features} />
|
||||
) : activePage === 'pricing' ? (
|
||||
<PricingPage onOpenDashboard={() => navigateTo('dashboard')} />
|
||||
<PricingPage
|
||||
onOpenDashboard={() => navigateTo('dashboard')}
|
||||
user={user}
|
||||
onLoginClick={() => {
|
||||
setAuthModalTab('login');
|
||||
setShowAuthModal(true);
|
||||
}}
|
||||
onRegisterClick={() => {
|
||||
setAuthModalTab('register');
|
||||
setShowAuthModal(true);
|
||||
}}
|
||||
/>
|
||||
) : activePage === 'learn' ? (
|
||||
<LearnPage />
|
||||
) : activePage === 'account' && user ? (
|
||||
<AccountPage user={user} onRefreshAuth={refreshAuth} />
|
||||
<AccountPage user={user} onRefreshAuth={refreshAuth} onRequestVerification={requestVerification} />
|
||||
) : activePage === 'support' ? (
|
||||
<SupportPage />
|
||||
) : activePage === 'invite' && inviteCode ? (
|
||||
<InvitePage
|
||||
code={inviteCode}
|
||||
user={user}
|
||||
onLoginClick={() => {
|
||||
setAuthModalTab('login');
|
||||
setShowAuthModal(true);
|
||||
}}
|
||||
onRegisterClick={() => {
|
||||
setAuthModalTab('register');
|
||||
setShowAuthModal(true);
|
||||
}}
|
||||
onLicenseGranted={() => {
|
||||
setShowLicenseSuccess(true);
|
||||
refreshAuth();
|
||||
}}
|
||||
/>
|
||||
) : activePage === 'saved-searches' ? (
|
||||
<SavedSearchesPage
|
||||
searches={savedSearches.searches}
|
||||
|
|
@ -265,6 +343,15 @@ export default function App() {
|
|||
onExportStateChange={setExportState}
|
||||
isMobile={isMobile}
|
||||
initialTravelTime={urlState.travelTime}
|
||||
user={user}
|
||||
onLoginClick={() => {
|
||||
setAuthModalTab('login');
|
||||
setShowAuthModal(true);
|
||||
}}
|
||||
onRegisterClick={() => {
|
||||
setAuthModalTab('register');
|
||||
setShowAuthModal(true);
|
||||
}}
|
||||
/>
|
||||
)}
|
||||
{showAuthModal && (
|
||||
|
|
@ -272,6 +359,7 @@ export default function App() {
|
|||
onClose={() => setShowAuthModal(false)}
|
||||
onLogin={login}
|
||||
onRegister={register}
|
||||
onOAuthLogin={loginWithOAuth}
|
||||
onForgotPassword={requestPasswordReset}
|
||||
loading={authLoading}
|
||||
error={authError}
|
||||
|
|
@ -287,6 +375,9 @@ export default function App() {
|
|||
error={savedSearches.error}
|
||||
/>
|
||||
)}
|
||||
{showLicenseSuccess && (
|
||||
<LicenseSuccessModal onClose={() => setShowLicenseSuccess(false)} />
|
||||
)}
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue