Initial commit: Lead Scraper - Google Maps

This commit is contained in:
Mambo
2026-02-11 01:48:37 +01:00
commit 831d63b7e8
13 changed files with 2406 additions and 0 deletions

121
views/index.ejs Normal file
View File

@@ -0,0 +1,121 @@
<!DOCTYPE html>
<html lang="en" class="bg-dark">
<head><%- include('partials/head') %></head>
<body class="bg-dark text-gray-200 flex min-h-screen">
<% const activePage = 'search'; %>
<%- include('partials/sidebar') %>
<main class="flex-1 ml-56">
<!-- Search Bar -->
<div class="bg-sidebar/50 border-b border-white/5 p-4">
<form id="searchForm" class="flex items-end gap-3 max-w-5xl">
<div class="flex-1">
<label class="block text-xs text-gray-500 mb-1">Keyword</label>
<input type="text" name="keyword" placeholder="e.g. Restaurants, Plumbers..."
class="w-full bg-card border border-white/10 rounded-lg px-3 py-2 text-sm text-white placeholder-gray-500 focus:outline-none focus:border-accent" required>
</div>
<div class="flex-1">
<label class="block text-xs text-gray-500 mb-1">City</label>
<input type="text" name="city" placeholder="e.g. Austin"
class="w-full bg-card border border-white/10 rounded-lg px-3 py-2 text-sm text-white placeholder-gray-500 focus:outline-none focus:border-accent" required>
</div>
<div class="w-40">
<label class="block text-xs text-gray-500 mb-1">State</label>
<input type="text" name="state" placeholder="e.g. Texas"
class="w-full bg-card border border-white/10 rounded-lg px-3 py-2 text-sm text-white placeholder-gray-500 focus:outline-none focus:border-accent">
</div>
<div class="w-24">
<label class="block text-xs text-gray-500 mb-1">Max</label>
<input type="number" name="maxResults" value="20" min="1" max="100"
class="w-full bg-card border border-white/10 rounded-lg px-3 py-2 text-sm text-white focus:outline-none focus:border-accent">
</div>
<button type="submit" id="searchBtn"
class="bg-accent hover:bg-accent-hover text-white px-5 py-2 rounded-lg text-sm font-medium transition-colors flex items-center gap-2">
<svg class="w-4 h-4" fill="none" stroke="currentColor" viewBox="0 0 24 24"><path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M21 21l-6-6m2-5a7 7 0 11-14 0 7 7 0 0114 0z"/></svg>
Search
</button>
</form>
</div>
<!-- Content Area -->
<div class="p-4 relative" id="contentArea">
<!-- Loading State -->
<div id="loadingState" class="hidden">
<div class="flex flex-col items-center justify-center py-24 text-gray-500">
<div class="flex gap-1 mb-4">
<div class="w-3 h-3 bg-accent rounded-full pulse-dot"></div>
<div class="w-3 h-3 bg-accent rounded-full pulse-dot"></div>
<div class="w-3 h-3 bg-accent rounded-full pulse-dot"></div>
</div>
<p class="text-sm font-medium">Scraping Google Maps...</p>
<p class="text-xs mt-1">This usually takes 30-120 seconds</p>
</div>
</div>
<!-- Error State -->
<div id="errorState" class="hidden">
<div class="flex flex-col items-center justify-center py-24">
<div class="bg-red-500/10 border border-red-500/20 rounded-lg p-6 max-w-md text-center">
<svg class="w-8 h-8 text-red-400 mx-auto mb-3" fill="none" stroke="currentColor" viewBox="0 0 24 24"><path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M12 9v2m0 4h.01m-6.938 4h13.856c1.54 0 2.502-1.667 1.732-2.5L13.732 4c-.77-.833-1.964-.833-2.732 0L4.082 16.5c-.77.833.192 2.5 1.732 2.5z"/></svg>
<p class="text-red-400 text-sm font-medium" id="errorMsg"></p>
</div>
</div>
</div>
<!-- Empty State -->
<div id="emptyState">
<div class="flex flex-col items-center justify-center py-24 text-gray-500">
<svg class="w-16 h-16 mb-4 text-gray-600" fill="none" stroke="currentColor" viewBox="0 0 24 24"><path stroke-linecap="round" stroke-linejoin="round" stroke-width="1" d="M17.657 16.657L13.414 20.9a1.998 1.998 0 01-2.827 0l-4.244-4.243a8 8 0 1111.314 0z"/><path stroke-linecap="round" stroke-linejoin="round" stroke-width="1" d="M15 11a3 3 0 11-6 0 3 3 0 016 0z"/></svg>
<p class="text-sm font-medium">Search Google Maps for leads</p>
<p class="text-xs mt-1">Enter a keyword and city to get started</p>
</div>
</div>
<!-- Results Table -->
<div id="resultsArea" class="hidden">
<div class="flex items-center justify-between mb-3">
<p class="text-sm text-gray-400"><span id="resultsQuery"></span> — <span id="resultsCount"></span> results</p>
</div>
<div class="overflow-x-auto rounded-lg border border-white/5">
<table class="w-full text-sm">
<thead>
<tr class="bg-card text-gray-400 text-left text-xs uppercase tracking-wider">
<th class="px-4 py-3 w-8"></th>
<th class="px-4 py-3">Business</th>
<th class="px-4 py-3">Phone</th>
<th class="px-4 py-3">Email</th>
<th class="px-4 py-3">Website</th>
<th class="px-4 py-3">Rating</th>
<th class="px-4 py-3">Reviews</th>
</tr>
</thead>
<tbody id="resultsBody" class="divide-y divide-white/5"></tbody>
</table>
</div>
</div>
<!-- Detail Panel -->
<div id="detailPanel" class="fixed top-0 right-0 w-96 h-full bg-sidebar border-l border-white/10 z-50 hidden overflow-y-auto scrollbar-thin">
</div>
</div>
<!-- Recent Searches (shown on empty state) -->
<% if (searches && searches.length > 0) { %>
<div id="recentSearches" class="px-4 pb-4">
<h3 class="text-xs text-gray-500 uppercase tracking-wider mb-2">Recent Searches</h3>
<div class="grid grid-cols-1 md:grid-cols-2 lg:grid-cols-3 gap-2">
<% searches.slice(0, 6).forEach(s => { %>
<button onclick="loadSearch('<%= s.key %>')"
class="bg-card hover:bg-card-hover border border-white/5 rounded-lg p-3 text-left transition-colors">
<p class="text-sm text-white font-medium truncate"><%= s.query %></p>
<p class="text-xs text-gray-500 mt-1"><%= s.resultsCount %> results · <%= new Date(s.timestamp).toLocaleDateString() %></p>
</button>
<% }) %>
</div>
</div>
<% } %>
</main>
<script src="/js/app.js"></script>
</body>
</html>