feat: PocketVeto v1.0.0 — initial public release
Self-hosted US Congress monitoring platform with AI policy briefs, bill/member/topic follows, ntfy + RSS + email notifications, alignment scoring, collections, and draft-letter generator. Authored by: Jack Levy
This commit is contained in:
67
frontend/lib/utils.ts
Normal file
67
frontend/lib/utils.ts
Normal file
@@ -0,0 +1,67 @@
|
||||
import { clsx, type ClassValue } from "clsx";
|
||||
import { twMerge } from "tailwind-merge";
|
||||
|
||||
export function cn(...inputs: ClassValue[]) {
|
||||
return twMerge(clsx(inputs));
|
||||
}
|
||||
|
||||
export function formatDate(date?: string | null): string {
|
||||
if (!date) return "—";
|
||||
return new Date(date).toLocaleDateString("en-US", {
|
||||
year: "numeric",
|
||||
month: "short",
|
||||
day: "numeric",
|
||||
});
|
||||
}
|
||||
|
||||
export function billLabel(billType: string, billNumber: number): string {
|
||||
const labels: Record<string, string> = {
|
||||
hr: "H.R.",
|
||||
s: "S.",
|
||||
hjres: "H.J.Res.",
|
||||
sjres: "S.J.Res.",
|
||||
hconres: "H.Con.Res.",
|
||||
sconres: "S.Con.Res.",
|
||||
hres: "H.Res.",
|
||||
sres: "S.Res.",
|
||||
};
|
||||
return `${labels[billType?.toLowerCase()] ?? billType?.toUpperCase()} ${billNumber}`;
|
||||
}
|
||||
|
||||
export function partyColor(party?: string): string {
|
||||
if (!party) return "text-muted-foreground";
|
||||
const p = party.toLowerCase();
|
||||
if (p.includes("democrat") || p === "d") return "text-blue-500";
|
||||
if (p.includes("republican") || p === "r") return "text-red-500";
|
||||
return "text-yellow-500";
|
||||
}
|
||||
|
||||
export function partyBadgeColor(party?: string): string {
|
||||
if (!party) return "bg-muted text-muted-foreground";
|
||||
const p = party.toLowerCase();
|
||||
if (p.includes("democrat") || p === "d") return "bg-blue-600 text-white";
|
||||
if (p.includes("republican") || p === "r") return "bg-red-600 text-white";
|
||||
return "bg-slate-500 text-white";
|
||||
}
|
||||
|
||||
export function congressLabel(congress: number): string {
|
||||
const lastTwo = congress % 100;
|
||||
if (lastTwo >= 11 && lastTwo <= 13) return `${congress}th Congress`;
|
||||
const suffixes: Record<number, string> = { 1: "st", 2: "nd", 3: "rd" };
|
||||
return `${congress}${suffixes[congress % 10] ?? "th"} Congress`;
|
||||
}
|
||||
|
||||
export function chamberBadgeColor(chamber?: string): string {
|
||||
if (!chamber) return "bg-muted text-muted-foreground";
|
||||
const c = chamber.toLowerCase();
|
||||
if (c === "senate") return "bg-amber-100 text-amber-700 dark:bg-amber-900/30 dark:text-amber-400";
|
||||
if (c.startsWith("house")) return "bg-slate-100 text-slate-600 dark:bg-slate-700/50 dark:text-slate-300";
|
||||
return "bg-muted text-muted-foreground";
|
||||
}
|
||||
|
||||
export function trendColor(score?: number): string {
|
||||
if (!score) return "text-muted-foreground";
|
||||
if (score >= 70) return "text-red-500";
|
||||
if (score >= 40) return "text-yellow-500";
|
||||
return "text-green-500";
|
||||
}
|
||||
Reference in New Issue
Block a user