Public Access
1
0

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:
pguerrerox
2026-03-27 13:56:54 +00:00
parent 0e4910805a
commit a1ba5ee093
44 changed files with 3756 additions and 1128 deletions
+32 -10
View File
@@ -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>