* refactor: move lighthouse audits to dataforseo * chore: remove obsolete audit settings modal * refactor: rename psi flows to lighthouse * save * refactor: simplify audit lighthouse storage flow * fix: separate lighthouse metrics from actionable audits * refactor: remove redundant audit project inputs * feat: redesign lighthouse issues screen with score gauges and table layout Replace flat score cards with circular SVG gauges, condense metrics into a compact grid, and switch issue list from cards to an expandable table with fixed column widths. * test: harden lighthouse regression coverage * fix: restore project-scoped audit inputs * refactor: simplify lighthouse payload handling * refactor: inline lighthouse server handlers * refactor: share audit workflow types * refactor: simplify lighthouse payload flows * save * refactor: drop project pagespeed api key * fix: restore lighthouse issues loading with resilient project context * fix: restore audit issues back navigation * refactor: simplify project context and lighthouse error handling * fix: tolerate DataForSEO lighthouse payload drift * refactor: route audit lighthouse through dataforseo client
102 lines
2.6 KiB
TypeScript
102 lines
2.6 KiB
TypeScript
import { AlertCircle, CheckCircle, Loader2 } from "lucide-react";
|
|
|
|
export const SUPPORT_URL = "https://everyapp.dev/support";
|
|
|
|
export function extractPathname(url: string): string {
|
|
try {
|
|
return new URL(url).pathname;
|
|
} catch {
|
|
return url;
|
|
}
|
|
}
|
|
|
|
export function extractHostname(url: string): string {
|
|
try {
|
|
return new URL(url).hostname;
|
|
} catch {
|
|
return url;
|
|
}
|
|
}
|
|
|
|
export function formatDate(dateStr: string): string {
|
|
return new Date(dateStr).toLocaleDateString("en-US", {
|
|
month: "short",
|
|
day: "numeric",
|
|
year: "numeric",
|
|
});
|
|
}
|
|
|
|
export function formatStartedAt(dateStr: string): string {
|
|
return new Date(dateStr).toLocaleString("en-US", {
|
|
month: "short",
|
|
day: "numeric",
|
|
hour: "numeric",
|
|
minute: "2-digit",
|
|
});
|
|
}
|
|
|
|
export function StatusBadge({ status }: { status: string }) {
|
|
if (status === "running") {
|
|
return (
|
|
<span className="badge badge-info badge-sm gap-1">
|
|
<Loader2 className="size-3 animate-spin" /> Running
|
|
</span>
|
|
);
|
|
}
|
|
|
|
if (status === "completed") {
|
|
return (
|
|
<span className="badge badge-outline badge-sm gap-1 text-success/80 border-success/30 bg-success/5">
|
|
<CheckCircle className="size-3" /> Done
|
|
</span>
|
|
);
|
|
}
|
|
|
|
return (
|
|
<span className="badge badge-error badge-sm gap-1">
|
|
<AlertCircle className="size-3" /> Failed
|
|
</span>
|
|
);
|
|
}
|
|
|
|
export function HttpStatusBadge({ code }: { code: number | null }) {
|
|
if (!code) return <span className="badge badge-ghost badge-sm">-</span>;
|
|
if (code >= 200 && code < 300) {
|
|
return <span className="badge badge-success badge-sm">{code}</span>;
|
|
}
|
|
if (code >= 300 && code < 400) {
|
|
return <span className="badge badge-warning badge-sm">{code}</span>;
|
|
}
|
|
return <span className="badge badge-error badge-sm">{code}</span>;
|
|
}
|
|
|
|
export function LighthouseScoreBadge({ score }: { score: number | null }) {
|
|
if (score == null) {
|
|
return <span className="text-xs text-base-content/40">-</span>;
|
|
}
|
|
const color =
|
|
score >= 90 ? "text-success" : score >= 50 ? "text-warning" : "text-error";
|
|
return <span className={`font-medium text-sm ${color}`}>{score}</span>;
|
|
}
|
|
|
|
export function StatCard({
|
|
label,
|
|
value,
|
|
className = "",
|
|
}: {
|
|
label: string;
|
|
value: string;
|
|
className?: string;
|
|
}) {
|
|
return (
|
|
<div className="card bg-base-100 border border-base-300">
|
|
<div className="card-body p-4">
|
|
<p className="text-xs uppercase tracking-wide text-base-content/60">
|
|
{label}
|
|
</p>
|
|
<p className={`text-2xl font-semibold ${className}`}>{value}</p>
|
|
</div>
|
|
</div>
|
|
);
|
|
}
|