feat: migrate app to local Fastify and Postgres stack
Replace Supabase auth and search runtime with a local Fastify API, PostgreSQL/PostGIS schema, and local session handling. Scaffold the worker and deep-research foundations while keeping the existing research, dashboard, and map flows running on the new backend.
This commit is contained in:
+32
-10
@@ -1,21 +1,22 @@
|
||||
import React, { useEffect, useMemo, useState } from 'react';
|
||||
import type { User } from '@supabase/supabase-js';
|
||||
import { Map, AdvancedMarker, InfoWindow, Pin, useMap } from '@vis.gl/react-google-maps';
|
||||
import { Globe, Loader2, MapPin, Navigation, Phone, Star } from 'lucide-react';
|
||||
import { listBusinesses, listBusinessesForJob } from '../lib/database';
|
||||
import { listBusinesses, listBusinessesForJobs } from '../lib/database';
|
||||
import type { Business } from '../types';
|
||||
import type { AppUser } from '../../shared/types';
|
||||
|
||||
interface MapViewProps {
|
||||
user: User;
|
||||
jobId?: string | null;
|
||||
user: AppUser;
|
||||
jobIds?: string[] | null;
|
||||
}
|
||||
|
||||
export function MapView({ user, jobId }: MapViewProps) {
|
||||
export function MapView({ user, jobIds }: MapViewProps) {
|
||||
const map = useMap();
|
||||
const [businesses, setBusinesses] = useState<Business[]>([]);
|
||||
const [selected, setSelected] = useState<Business | null>(null);
|
||||
const [loading, setLoading] = useState(true);
|
||||
const [error, setError] = useState<string | null>(null);
|
||||
const selectedJobCount = jobIds?.length ?? 0;
|
||||
|
||||
useEffect(() => {
|
||||
const loadBusinesses = async () => {
|
||||
@@ -23,7 +24,7 @@ export function MapView({ user, jobId }: MapViewProps) {
|
||||
setError(null);
|
||||
|
||||
try {
|
||||
const nextBusinesses = jobId ? await listBusinessesForJob(user.id, jobId) : await listBusinesses(user.id);
|
||||
const nextBusinesses = selectedJobCount > 0 ? await listBusinessesForJobs(user.id, jobIds ?? []) : await listBusinesses(user.id);
|
||||
setBusinesses(nextBusinesses);
|
||||
} catch (err) {
|
||||
setError(err instanceof Error ? err.message : 'Failed to load map leads.');
|
||||
@@ -33,7 +34,7 @@ export function MapView({ user, jobId }: MapViewProps) {
|
||||
};
|
||||
|
||||
void loadBusinesses();
|
||||
}, [jobId, user.id]);
|
||||
}, [jobIds, selectedJobCount, user.id]);
|
||||
|
||||
useEffect(() => {
|
||||
if (!selected) {
|
||||
@@ -96,6 +97,25 @@ export function MapView({ user, jobId }: MapViewProps) {
|
||||
);
|
||||
}
|
||||
|
||||
if (businesses.length === 0) {
|
||||
return (
|
||||
<div className="flex flex-1 items-center justify-center bg-stone-50 p-8">
|
||||
<div className="w-full max-w-lg rounded-3xl border border-stone-200 bg-white p-8 text-center shadow-sm">
|
||||
<div className="mx-auto flex h-14 w-14 items-center justify-center rounded-full bg-stone-100 text-stone-500">
|
||||
<MapPin className="h-6 w-6" />
|
||||
</div>
|
||||
<h2 className="mt-4 text-2xl font-bold text-stone-900">No leads to show on the map</h2>
|
||||
<p className="mt-3 text-sm text-stone-600">
|
||||
{selectedJobCount > 0
|
||||
? 'The selected research jobs do not have saved map results yet. Try completed jobs or run the research again.'
|
||||
: 'No saved leads are available yet. Run a research job to populate the map.'}
|
||||
</p>
|
||||
{error && <p className="mt-4 text-sm font-medium text-red-700">{error}</p>}
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
return (
|
||||
<div className="relative flex-1 bg-stone-100">
|
||||
{error && (
|
||||
@@ -198,10 +218,12 @@ export function MapView({ user, jobId }: MapViewProps) {
|
||||
<span className="text-stone-500">Selected Lead</span>
|
||||
<span className="max-w-[120px] truncate font-bold text-stone-900">{selected ? selected.name : 'None'}</span>
|
||||
</div>
|
||||
{jobId && (
|
||||
{selectedJobCount > 0 && (
|
||||
<div className="mt-2 border-t border-stone-200 pt-2">
|
||||
<p className="text-[10px] font-bold uppercase tracking-wider text-stone-400">Filtering by Job</p>
|
||||
<p className="text-xs font-medium text-emerald-700 truncate">Active filter applied</p>
|
||||
<p className="text-[10px] font-bold uppercase tracking-wider text-stone-400">Filtering by Selection</p>
|
||||
<p className="text-xs font-medium text-emerald-700 truncate">
|
||||
{selectedJobCount === 1 ? '1 selected research job' : `${selectedJobCount} selected research jobs`}
|
||||
</p>
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
|
||||
Reference in New Issue
Block a user