Root cause of recurring dev server hangs:
/api/v1/website-analytics threw CodedError('UMAMI_NOT_CONFIGURED') which
rendered as HTTP 409. React Query default-retries on 4xx (we set retry=1
globally), so every page render fired the umami queries → 409 →
retry → 409. Each request queried system_settings to resolve umami
credentials. Six analytics widgets on the /website-analytics page +
two on the dashboard glance tile × 2 (initial + retry) = 16 system_settings
queries on first paint. Combined with React Query refetching on mount,
the postgres pool (max=20) saturated and the server appeared hung.
Fix: return 200 with `{ data: null, notConfigured: true }` instead of
4xx. Not-configured is a steady empty state, not a transient error —
no retry loop. Updated WebsiteGlanceTile (hides itself) and
WebsiteAnalyticsShell (renders configure-umami CTA) to check the new
notConfigured flag.
Also includes from in-flight work: package.json dev script binds
0.0.0.0 so iPhone on LAN can reach the dev server, and BrandedAuthShell
uses fixed/inset-0 + flex to lock the login surface to the viewport so
iOS Safari doesn't rubber-band-scroll the card.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
79 lines
3.2 KiB
TypeScript
79 lines
3.2 KiB
TypeScript
'use client';
|
|
|
|
/**
|
|
* Compact "Website at a glance" tile for the main sales dashboard. Shows
|
|
* pageviews today + active visitors right now + a deep-link to the full
|
|
* /website-analytics page. Soft-fails (renders nothing) when Umami isn't
|
|
* configured for this port - so the dashboard doesn't get cluttered with
|
|
* a "configure Umami" prompt that the user already saw on the dedicated
|
|
* page.
|
|
*/
|
|
|
|
import Link from 'next/link';
|
|
import { Globe, ArrowRight } from 'lucide-react';
|
|
|
|
import { useUIStore } from '@/stores/ui-store';
|
|
import { Card } from '@/components/ui/card';
|
|
import { Skeleton } from '@/components/ui/skeleton';
|
|
import {
|
|
useUmamiActive,
|
|
useUmamiStats,
|
|
} from '@/components/website-analytics/use-website-analytics';
|
|
|
|
export function WebsiteGlanceTile() {
|
|
const portSlug = useUIStore((s) => s.currentPortSlug);
|
|
const stats = useUmamiStats('today');
|
|
const active = useUmamiActive('today');
|
|
|
|
// Hide the tile entirely if Umami isn't configured - this dashboard is
|
|
// for sales, not for prompting the operator into integration setup.
|
|
// The API surfaces `notConfigured: true` on a 200 response so React
|
|
// Query doesn't retry-loop (a prior 409-throw caused server hangs).
|
|
if (stats.data?.notConfigured || active.data?.notConfigured) {
|
|
return null;
|
|
}
|
|
|
|
const today = stats.data?.data?.pageviews?.value ?? 0;
|
|
const activeNow = active.data?.data?.visitors ?? 0;
|
|
const loading = stats.isLoading || active.isLoading;
|
|
|
|
return (
|
|
<Link
|
|
/* eslint-disable-next-line @typescript-eslint/no-explicit-any */
|
|
href={portSlug ? (`/${portSlug}/website-analytics` as any) : ('/' as any)}
|
|
className="block group"
|
|
>
|
|
<Card className="relative overflow-hidden p-3 sm:p-5 transition-shadow hover:shadow-md">
|
|
<div className="absolute inset-x-0 top-0 h-1 bg-mint" aria-hidden />
|
|
<div className="flex items-start justify-between gap-3">
|
|
<div className="min-w-0 flex-1">
|
|
<div className="flex items-center gap-1.5 text-[10px] font-medium uppercase tracking-wide text-muted-foreground sm:text-xs">
|
|
<Globe className="h-3 w-3" aria-hidden />
|
|
Website today
|
|
</div>
|
|
{loading ? (
|
|
<Skeleton className="mt-2 h-7 w-20" />
|
|
) : (
|
|
<div className="mt-1 flex items-baseline gap-2 text-lg font-semibold tabular-nums sm:mt-2 sm:text-2xl">
|
|
{today.toLocaleString()}
|
|
<span className="text-xs font-normal text-muted-foreground">pageviews</span>
|
|
</div>
|
|
)}
|
|
<div className="mt-1 flex items-center gap-1.5 text-xs text-muted-foreground">
|
|
<span className="relative flex h-1.5 w-1.5">
|
|
<span className="absolute inline-flex h-full w-full animate-ping rounded-full bg-emerald-400 opacity-75" />
|
|
<span className="relative inline-flex h-1.5 w-1.5 rounded-full bg-emerald-500" />
|
|
</span>
|
|
{activeNow} active right now
|
|
</div>
|
|
</div>
|
|
<ArrowRight
|
|
className="h-4 w-4 shrink-0 text-muted-foreground transition-transform group-hover:translate-x-0.5"
|
|
aria-hidden
|
|
/>
|
|
</div>
|
|
</Card>
|
|
</Link>
|
|
);
|
|
}
|