Add packages/common for shared utilities
Create new @reviq/common package with environment-agnostic utilities: - Date formatting: formatDate, formatDateTime, formatLongDate, formatRelativeDate, formatRelativeTime - User utilities: getUserInitials, formatRole Consolidate date formatting from publisher-dashboard into shared package. All utilities include comprehensive test coverage with bun:test. Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
This commit is contained in:
128
packages/common/src/format-date.ts
Normal file
128
packages/common/src/format-date.ts
Normal file
@@ -0,0 +1,128 @@
|
||||
/**
|
||||
* Date formatting utilities for consistent display across the app.
|
||||
* Works in all JavaScript environments (browser, Node.js, Bun, etc.)
|
||||
*/
|
||||
|
||||
type DateInput = string | Date;
|
||||
|
||||
/**
|
||||
* Safely convert a date input to a Date object.
|
||||
*/
|
||||
function toDate(date: DateInput): Date {
|
||||
return typeof date === "string" ? new Date(date) : date;
|
||||
}
|
||||
|
||||
/**
|
||||
* Calculate the difference in days between two dates.
|
||||
*/
|
||||
function daysDiff(from: Date, to: Date): number {
|
||||
const diffMs = to.getTime() - from.getTime();
|
||||
return Math.floor(diffMs / (1000 * 60 * 60 * 24));
|
||||
}
|
||||
|
||||
/**
|
||||
* Format a date for display in tables and lists.
|
||||
* @example formatDate("2024-01-15") // "Jan 15, 2024"
|
||||
*/
|
||||
export function formatDate(date: DateInput): string {
|
||||
const d = toDate(date);
|
||||
return d.toLocaleDateString("en-US", {
|
||||
month: "short",
|
||||
day: "numeric",
|
||||
year: "numeric",
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Format a date with time for detailed views.
|
||||
* @example formatDateTime("2024-01-15T15:30:00") // "Jan 15, 2024, 3:30 PM"
|
||||
*/
|
||||
export function formatDateTime(date: DateInput): string {
|
||||
const d = toDate(date);
|
||||
return d.toLocaleDateString("en-US", {
|
||||
month: "short",
|
||||
day: "numeric",
|
||||
year: "numeric",
|
||||
hour: "numeric",
|
||||
minute: "2-digit",
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Format a date in long form.
|
||||
* @example formatLongDate("2024-01-15") // "January 15, 2024"
|
||||
*/
|
||||
export function formatLongDate(date: DateInput): string {
|
||||
const d = toDate(date);
|
||||
return d.toLocaleDateString("en-US", {
|
||||
month: "long",
|
||||
day: "numeric",
|
||||
year: "numeric",
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Options for relative date formatting.
|
||||
*/
|
||||
export interface FormatRelativeDateOptions {
|
||||
/**
|
||||
* Reference date to compare against. Defaults to current date.
|
||||
*/
|
||||
now?: Date;
|
||||
}
|
||||
|
||||
/**
|
||||
* Format a date as a relative time string.
|
||||
* @example
|
||||
* formatRelativeDate("2024-01-15") // "Today" (if today is Jan 15)
|
||||
* formatRelativeDate("2024-01-14") // "Yesterday" (if today is Jan 15)
|
||||
* formatRelativeDate("2024-01-10") // "5 days ago" (if today is Jan 15)
|
||||
* formatRelativeDate("2024-01-01") // "2 weeks ago" (if today is Jan 15)
|
||||
* formatRelativeDate("2023-06-15") // "Jun 15, 2023" (older dates)
|
||||
*/
|
||||
export function formatRelativeDate(
|
||||
date: DateInput,
|
||||
options?: FormatRelativeDateOptions,
|
||||
): string {
|
||||
const d = toDate(date);
|
||||
const now = options?.now ?? new Date();
|
||||
const diffDays = daysDiff(d, now);
|
||||
|
||||
if (diffDays === 0) {
|
||||
return "Today";
|
||||
}
|
||||
if (diffDays === 1) {
|
||||
return "Yesterday";
|
||||
}
|
||||
if (diffDays < 7) {
|
||||
return `${String(diffDays)} days ago`;
|
||||
}
|
||||
if (diffDays < 30) {
|
||||
const weeks = Math.floor(diffDays / 7);
|
||||
return weeks === 1 ? "1 week ago" : `${String(weeks)} weeks ago`;
|
||||
}
|
||||
|
||||
// For older dates, show the actual date
|
||||
return d.toLocaleDateString("en-US", {
|
||||
month: "short",
|
||||
day: "numeric",
|
||||
year: d.getFullYear() !== now.getFullYear() ? "numeric" : undefined,
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Format a date as a relative time string, with "Never" for null values.
|
||||
* Useful for displaying "last used" timestamps.
|
||||
* @example
|
||||
* formatRelativeTime("2024-01-15") // "Today"
|
||||
* formatRelativeTime(null) // "Never"
|
||||
*/
|
||||
export function formatRelativeTime(
|
||||
date: DateInput | null | undefined,
|
||||
options?: FormatRelativeDateOptions,
|
||||
): string {
|
||||
if (date === null || date === undefined) {
|
||||
return "Never";
|
||||
}
|
||||
return formatRelativeDate(date, options);
|
||||
}
|
||||
Reference in New Issue
Block a user