1.6 KB39 lines
Blame
1/** Time-ago formatter for Unix timestamps (seconds since epoch). */
2export function timeAgo(timestamp: number): string {
3 if (!timestamp) return "";
4 const seconds = Math.floor(Date.now() / 1000 - timestamp);
5 if (seconds < 60) return "just now";
6 if (seconds < 3600) return `${Math.floor(seconds / 60)}m ago`;
7 if (seconds < 86400) return `${Math.floor(seconds / 3600)}h ago`;
8 if (seconds < 2592000) return `${Math.floor(seconds / 86400)}d ago`;
9 return new Date(timestamp * 1000).toLocaleDateString();
10}
11
12/** Time-ago formatter for ISO date strings. */
13export function timeAgoFromDate(dateStr: string): string {
14 const seconds = Math.floor(
15 (Date.now() - new Date(dateStr).getTime()) / 1000
16 );
17 if (seconds < 60) return "just now";
18 if (seconds < 3600) return `${Math.floor(seconds / 60)}m ago`;
19 if (seconds < 86400) return `${Math.floor(seconds / 3600)}h ago`;
20 if (seconds < 2592000) return `${Math.floor(seconds / 86400)}d ago`;
21 return new Date(dateStr).toLocaleDateString();
22}
23
24/** Human-readable file size. */
25export function formatSize(bytes: number): string {
26 if (bytes < 1024) return `${bytes} B`;
27 if (bytes < 1024 * 1024) return `${(bytes / 1024).toFixed(1)} KB`;
28 return `${(bytes / (1024 * 1024)).toFixed(1)} MB`;
29}
30
31/** Encode brackets in a URL path so Next.js Link doesn't treat them as dynamic params. */
32export function encodePath(p: string): string {
33 return p.split("/").map(s => s.replace(/\[/g, "%5B").replace(/\]/g, "%5D")).join("/");
34}
35
36/** Grove API URL for server components (SSR fetches). */
37export const groveApiUrl =
38 process.env.GROVE_API_URL ?? "http://localhost:4000";
39