web/lib/utils.tsblame
View source
9e346cc1/** Time-ago formatter for Unix timestamps (seconds since epoch). */
9e346cc2export function timeAgo(timestamp: number): string {
9e346cc3 if (!timestamp) return "";
9e346cc4 const seconds = Math.floor(Date.now() / 1000 - timestamp);
9e346cc5 if (seconds < 60) return "just now";
9e346cc6 if (seconds < 3600) return `${Math.floor(seconds / 60)}m ago`;
9e346cc7 if (seconds < 86400) return `${Math.floor(seconds / 3600)}h ago`;
9e346cc8 if (seconds < 2592000) return `${Math.floor(seconds / 86400)}d ago`;
9e346cc9 return new Date(timestamp * 1000).toLocaleDateString();
9e346cc10}
9e346cc11
9e346cc12/** Time-ago formatter for ISO date strings. */
9e346cc13export function timeAgoFromDate(dateStr: string): string {
9e346cc14 const seconds = Math.floor(
9e346cc15 (Date.now() - new Date(dateStr).getTime()) / 1000
9e346cc16 );
9e346cc17 if (seconds < 60) return "just now";
9e346cc18 if (seconds < 3600) return `${Math.floor(seconds / 60)}m ago`;
9e346cc19 if (seconds < 86400) return `${Math.floor(seconds / 3600)}h ago`;
9e346cc20 if (seconds < 2592000) return `${Math.floor(seconds / 86400)}d ago`;
9e346cc21 return new Date(dateStr).toLocaleDateString();
9e346cc22}
9e346cc23
9e346cc24/** Human-readable file size. */
9e346cc25export function formatSize(bytes: number): string {
9e346cc26 if (bytes < 1024) return `${bytes} B`;
9e346cc27 if (bytes < 1024 * 1024) return `${(bytes / 1024).toFixed(1)} KB`;
9e346cc28 return `${(bytes / (1024 * 1024)).toFixed(1)} MB`;
9e346cc29}
9e346cc30
d744b8231/** Encode brackets in a URL path so Next.js Link doesn't treat them as dynamic params. */
d744b8232export function encodePath(p: string): string {
d744b8233 return p.split("/").map(s => s.replace(/\[/g, "%5B").replace(/\]/g, "%5D")).join("/");
d744b8234}
d744b8235
f0bb19236/** Grove API URL for server components (SSR fetches). */
f0bb19237export const groveApiUrl =
f0bb19238 process.env.GROVE_API_URL ?? "http://localhost:4000";