feat: Responsive admin UI, SEO pages, and click analytics
## Responsive Admin UI - Layout.tsx: Mobile sidebar drawer with hamburger menu - Dashboard.tsx: 2-col grid on mobile, responsive stats cards - OrchestratorDashboard.tsx: Responsive table with hidden columns - PagesTab.tsx: Responsive filters and table ## SEO Pages - New /admin/seo section with state landing pages - SEO page generation and management - State page content with dispensary/product counts ## Click Analytics - Product click tracking infrastructure - Click analytics dashboard ## Other Changes - Consumer features scaffolding (alerts, deals, favorites) - Health panel component - Workers dashboard improvements - Legacy DutchieAZ pages removed 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
This commit is contained in:
@@ -23,10 +23,14 @@ app.use('/images', express.static(LOCAL_IMAGES_PATH));
|
||||
const LOCAL_DOWNLOADS_PATH = process.env.LOCAL_DOWNLOADS_PATH || '/app/public/downloads';
|
||||
app.use('/downloads', express.static(LOCAL_DOWNLOADS_PATH));
|
||||
|
||||
// Simple health check for load balancers/K8s probes
|
||||
app.get('/health', (req, res) => {
|
||||
res.json({ status: 'ok', timestamp: new Date().toISOString() });
|
||||
});
|
||||
|
||||
// Comprehensive health endpoints for monitoring (no auth required)
|
||||
app.use('/api/health', healthRoutes);
|
||||
|
||||
// Endpoint to check server's outbound IP (for proxy whitelist setup)
|
||||
app.get('/outbound-ip', async (req, res) => {
|
||||
try {
|
||||
@@ -62,6 +66,8 @@ import usersRoutes from './routes/users';
|
||||
import staleProcessesRoutes from './routes/stale-processes';
|
||||
import orchestratorAdminRoutes from './routes/orchestrator-admin';
|
||||
import adminRoutes from './routes/admin';
|
||||
import healthRoutes from './routes/health';
|
||||
import workersRoutes from './routes/workers';
|
||||
import { dutchieAZRouter, startScheduler as startDutchieAZScheduler, initializeDefaultSchedules } from './dutchie-az';
|
||||
import { getPool } from './dutchie-az/db/connection';
|
||||
import { createAnalyticsRouter } from './dutchie-az/routes/analytics';
|
||||
@@ -77,6 +83,16 @@ import { createAnalyticsV2Router } from './routes/analytics-v2';
|
||||
import { createDiscoveryRoutes } from './discovery';
|
||||
import { createDutchieDiscoveryRoutes, promoteDiscoveryLocation } from './dutchie-az/discovery';
|
||||
|
||||
// Consumer API routes (findadispo.com, findagram.co)
|
||||
import consumerAuthRoutes from './routes/consumer-auth';
|
||||
import consumerFavoritesRoutes from './routes/consumer-favorites';
|
||||
import consumerAlertsRoutes from './routes/consumer-alerts';
|
||||
import consumerSavedSearchesRoutes from './routes/consumer-saved-searches';
|
||||
import consumerDealsRoutes from './routes/consumer-deals';
|
||||
import eventsRoutes from './routes/events';
|
||||
import clickAnalyticsRoutes from './routes/click-analytics';
|
||||
import seoRoutes from './routes/seo';
|
||||
|
||||
// Mark requests from trusted domains (cannaiq.co, findagram.co, findadispo.com)
|
||||
// These domains can access the API without authentication
|
||||
app.use(markTrustedDomains);
|
||||
@@ -96,6 +112,18 @@ app.use('/api/changes', changesRoutes);
|
||||
app.use('/api/categories', categoriesRoutes);
|
||||
app.use('/api/products', productsRoutes);
|
||||
app.use('/api/campaigns', campaignsRoutes);
|
||||
|
||||
// Multi-state API routes - national analytics and cross-state comparisons (NO AUTH)
|
||||
// IMPORTANT: Must be mounted BEFORE /api/analytics to avoid auth middleware blocking these routes
|
||||
try {
|
||||
const multiStateRoutes = createMultiStateRoutes(getPool());
|
||||
app.use('/api', multiStateRoutes);
|
||||
console.log('[MultiState] Routes registered at /api (analytics/national/*, states/*, etc.)');
|
||||
} catch (error) {
|
||||
console.warn('[MultiState] Failed to register routes (DB may not be configured):', error);
|
||||
}
|
||||
|
||||
// Legacy click analytics routes (requires auth)
|
||||
app.use('/api/analytics', analyticsRoutes);
|
||||
app.use('/api/settings', settingsRoutes);
|
||||
app.use('/api/proxies', proxiesRoutes);
|
||||
@@ -112,16 +140,29 @@ app.use('/api/stale-processes', staleProcessesRoutes);
|
||||
// Admin routes - operator actions (crawl triggers, health checks)
|
||||
app.use('/api/admin', adminRoutes);
|
||||
app.use('/api/admin/orchestrator', orchestratorAdminRoutes);
|
||||
// Vendor-agnostic AZ data pipeline routes (new public surface)
|
||||
|
||||
// SEO orchestrator routes
|
||||
app.use('/api/seo', seoRoutes);
|
||||
|
||||
// Provider-agnostic worker management routes (replaces /api/dutchie-az/admin/schedules)
|
||||
app.use('/api/workers', workersRoutes);
|
||||
// Monitor routes - aliased from workers for convenience
|
||||
app.use('/api/monitor', workersRoutes);
|
||||
console.log('[Workers] Routes registered at /api/workers and /api/monitor');
|
||||
|
||||
// Market data pipeline routes (provider-agnostic)
|
||||
app.use('/api/markets', dutchieAZRouter);
|
||||
// Legacy aliases (deprecated - remove after frontend migration)
|
||||
app.use('/api/az', dutchieAZRouter);
|
||||
// Legacy alias (kept temporarily for backward compatibility)
|
||||
app.use('/api/dutchie-az', dutchieAZRouter);
|
||||
|
||||
// Phase 3: Analytics Dashboards - price trends, penetration, category growth, etc.
|
||||
try {
|
||||
const analyticsRouter = createAnalyticsRouter(getPool());
|
||||
app.use('/api/markets/analytics', analyticsRouter);
|
||||
// Legacy alias for backwards compatibility
|
||||
app.use('/api/az/analytics', analyticsRouter);
|
||||
console.log('[Analytics] Routes registered at /api/az/analytics');
|
||||
console.log('[Analytics] Routes registered at /api/markets/analytics');
|
||||
} catch (error) {
|
||||
console.warn('[Analytics] Failed to register routes:', error);
|
||||
}
|
||||
@@ -139,15 +180,24 @@ try {
|
||||
// Uses dutchie_az data pipeline with per-dispensary API key auth
|
||||
app.use('/api/v1', publicApiRoutes);
|
||||
|
||||
// Multi-state API routes - national analytics and cross-state comparisons
|
||||
// Phase 4: Multi-State Expansion
|
||||
try {
|
||||
const multiStateRoutes = createMultiStateRoutes(getPool());
|
||||
app.use('/api', multiStateRoutes);
|
||||
console.log('[MultiState] Routes registered');
|
||||
} catch (error) {
|
||||
console.warn('[MultiState] Failed to register routes (DB may not be configured):', error);
|
||||
}
|
||||
// Consumer API - findadispo.com and findagram.co user features
|
||||
// Auth routes don't require authentication
|
||||
app.use('/api/consumer/auth', consumerAuthRoutes);
|
||||
// Protected consumer routes (favorites, alerts, saved searches)
|
||||
app.use('/api/consumer/favorites', consumerFavoritesRoutes);
|
||||
app.use('/api/consumer/alerts', consumerAlertsRoutes);
|
||||
app.use('/api/consumer/saved-searches', consumerSavedSearchesRoutes);
|
||||
// Deals endpoint - public, no auth required
|
||||
app.use('/api/v1/deals', consumerDealsRoutes);
|
||||
console.log('[Consumer] Routes registered at /api/consumer/*');
|
||||
|
||||
// Events API - product click tracking for analytics and campaigns
|
||||
app.use('/api/events', eventsRoutes);
|
||||
console.log('[Events] Routes registered at /api/events');
|
||||
|
||||
// Click Analytics API - brand and campaign engagement aggregations
|
||||
app.use('/api/analytics/clicks', clickAnalyticsRoutes);
|
||||
console.log('[ClickAnalytics] Routes registered at /api/analytics/clicks');
|
||||
|
||||
// States API routes - cannabis legalization status and targeting
|
||||
try {
|
||||
|
||||
Reference in New Issue
Block a user