import { type ReactElement, type SVGProps, useEffect, useState } from 'react'; import { APIProvider } from '@vis.gl/react-google-maps'; import { AlertCircle, Briefcase, LogIn, ShieldAlert, UserPlus } from 'lucide-react'; import { Layout } from './components/Layout'; import { SearchSetup } from './components/SearchSetup'; import { Dashboard } from './components/Dashboard'; import { MapView } from './components/MapView'; import type { AppUser } from '../shared/types'; import { getLocalSessionUser, signInWithLocalAuth, signOutWithLocalAuth, signUpWithLocalAuth } from './lib/auth'; import { hasApiConfig } from './lib/api'; const GOOGLE_MAPS_API_KEY = import.meta.env.VITE_GOOGLE_MAPS_PLATFORM_KEY ?? ''; const hasValidMapsKey = Boolean(GOOGLE_MAPS_API_KEY) && GOOGLE_MAPS_API_KEY !== 'YOUR_API_KEY'; export default function App() { const [user, setUser] = useState(null); const [loading, setLoading] = useState(true); const [activeTab, setActiveTab] = useState<'setup' | 'dashboard' | 'map'>('setup'); const [selectedJobIds, setSelectedJobIds] = useState([]); const [authError, setAuthError] = useState(null); const [authNotice, setAuthNotice] = useState(null); const [isAuthenticating, setIsAuthenticating] = useState(false); const [authMode, setAuthMode] = useState<'sign_in' | 'sign_up'>('sign_in'); const [email, setEmail] = useState(''); const [password, setPassword] = useState(''); const [displayName, setDisplayName] = useState(''); useEffect(() => { let isMounted = true; const loadSession = async () => { try { const sessionUser = await getLocalSessionUser(); if (!isMounted) { return; } setUser(sessionUser); } catch (error) { if (!isMounted) { return; } setAuthError(error instanceof Error ? error.message : 'Failed to load session.'); } finally { if (isMounted) { setLoading(false); } } }; void loadSession(); return () => { isMounted = false; }; }, []); const handleToggleJobSelection = (jobId: string) => { setSelectedJobIds((currentJobIds) => currentJobIds.includes(jobId) ? currentJobIds.filter((currentJobId) => currentJobId !== jobId) : [...currentJobIds, jobId], ); }; const handleSelectCreatedJob = (jobId: string) => { setSelectedJobIds((currentJobIds) => (currentJobIds.includes(jobId) ? currentJobIds : [...currentJobIds, jobId])); }; const handleShowSelectedOnMap = () => { if (selectedJobIds.length === 0) { return; } setActiveTab('map'); }; const handleClearSelectedJobs = () => { setSelectedJobIds([]); }; const handleLogin = async () => { setAuthError(null); setAuthNotice(null); setIsAuthenticating(true); try { if (authMode === 'sign_up') { const nextUser = await signUpWithLocalAuth({ email, password, displayName: displayName.trim() || undefined, }); setUser(nextUser); setAuthNotice('Account created and signed in.'); return; } const nextUser = await signInWithLocalAuth({ email, password, }); setUser(nextUser); } catch (error) { setAuthError(error instanceof Error ? error.message : 'Authentication failed.'); } finally { setIsAuthenticating(false); } }; const handleLogout = async () => { try { await signOutWithLocalAuth(); setUser(null); setSelectedJobIds([]); setAuthNotice(null); } catch (error) { setAuthError(error instanceof Error ? error.message : 'Failed to sign out.'); } }; if (loading) { return (
); } if (!hasApiConfig) { return ( } title="Local API Config Required" description="Add your local API base URL before running the app." steps={[ 'Start the local Fastify API server.', 'Add VITE_API_BASE_URL to your local environment.', 'Ensure the API can reach your local PostgreSQL database.', 'Restart the Vite dev server after updating env vars.', ]} footer="The app now uses a local API, PostgreSQL, and local session auth." /> ); } if (!hasValidMapsKey) { return ( } title="Google Maps API Key Required" description="Add a browser key for map rendering and a server key for the local search API." steps={[ 'Create a Google Maps Platform API key for the browser app.', 'Set VITE_GOOGLE_MAPS_PLATFORM_KEY locally for the frontend.', 'Set GOOGLE_MAPS_SERVER_KEY for the local API server.', 'Enable Geocoding API and Places API in Google Cloud.', ]} footer="The app will start once the browser key is available." /> ); } if (!user) { return (

Leads4less

Create a local account to access your lead workspace.

{authError && (

Authentication Error

{authError}

)} {authNotice && (
{authNotice}
)}
{ event.preventDefault(); void handleLogin(); }} > {authMode === 'sign_up' && (
setDisplayName(event.target.value)} placeholder="Your name" className="w-full rounded-xl border border-stone-200 bg-stone-50 px-4 py-3 text-sm outline-none transition-all focus:border-emerald-500 focus:ring-2 focus:ring-emerald-500" />
)}
setEmail(event.target.value)} placeholder="you@example.com" className="w-full rounded-xl border border-stone-200 bg-stone-50 px-4 py-3 text-sm outline-none transition-all focus:border-emerald-500 focus:ring-2 focus:ring-emerald-500" />
setPassword(event.target.value)} placeholder="At least 6 characters" className="w-full rounded-xl border border-stone-200 bg-stone-50 px-4 py-3 text-sm outline-none transition-all focus:border-emerald-500 focus:ring-2 focus:ring-emerald-500" />
); } return ( void handleLogout()} > {activeTab === 'setup' && ( )} {activeTab === 'dashboard' && } {activeTab === 'map' && } ); } function ConfigScreen(props: { icon: ReactElement; title: string; description: string; steps: string[]; footer: string; }) { const { icon, title, description, steps, footer } = props; return (
{icon}

{title}

{description}

Follow these steps:

    {steps.map((step) => (
  1. {step}
  2. ))}

{footer}

); } function MapIcon(props: SVGProps) { return ( ); }