sushilideaclan01's picture
removed the containers and made some optimizations
505ff55
import { format, formatDistanceToNow } from "date-fns";
/**
* Parse a date string, ensuring UTC dates are properly converted to local time.
* Handles both UTC dates (with 'Z' suffix) and dates without timezone info.
*
* @param dateString - ISO date string, potentially without timezone info
* @returns Date object in local timezone
*/
const parseDate = (dateString: string): Date => {
if (!dateString || typeof dateString !== 'string') {
throw new Error('Invalid date string');
}
// If the string already has 'Z' or timezone offset, new Date() will handle it correctly
const hasTimezone = dateString.includes('Z') ||
dateString.includes('+') ||
/[+-]\d{2}:\d{2}$/.test(dateString);
if (!hasTimezone && dateString.includes('T')) {
// Date string without timezone - assume UTC and add 'Z'
// Format: "2026-01-15T06:55:00" -> "2026-01-15T06:55:00Z"
// Format: "2026-01-15T06:55:00.123" -> "2026-01-15T06:55:00.123Z"
const datePart = dateString.split('.')[0]; // Remove milliseconds if present
if (datePart.length >= 19) { // Ensure we have at least "YYYY-MM-DDTHH:mm:ss"
dateString = dateString.replace(datePart, datePart + 'Z');
}
}
const date = new Date(dateString);
// Validate the date is valid
if (isNaN(date.getTime())) {
throw new Error(`Invalid date: ${dateString}`);
}
return date;
};
export const formatDate = (dateString: string | null | undefined): string => {
if (!dateString) return "N/A";
try {
const date = parseDate(dateString);
// Format in user's local timezone
return format(date, "MMM d, yyyy 'at' h:mm a");
} catch {
return dateString;
}
};
export const formatRelativeDate = (dateString: string | null | undefined): string => {
if (!dateString) return "N/A";
try {
const date = parseDate(dateString);
// Format relative time in user's local timezone
return formatDistanceToNow(date, { addSuffix: true });
} catch {
return dateString;
}
};
export const formatNiche = (niche: string): string => {
return niche
.split("_")
.map((word) => word.charAt(0).toUpperCase() + word.slice(1))
.join(" ");
};
export const formatGenerationMethod = (method: string | null | undefined): string => {
if (!method) return "Original";
if (method === "angle_concept_matrix") return "Matrix";
return method
.split("_")
.map((word) => word.charAt(0).toUpperCase() + word.slice(1))
.join(" ");
};
export const truncateText = (text: string, maxLength: number): string => {
if (text.length <= maxLength) return text;
return text.slice(0, maxLength) + "...";
};
const getApiBase = () => process.env.NEXT_PUBLIC_API_URL || "http://localhost:8000";
const isAbsoluteUrl = (url: string) =>
typeof url === "string" && (url.startsWith("http://") || url.startsWith("https://"));
export const getImageUrl = (
imageUrl: string | null | undefined,
filename: string | null | undefined,
r2Url?: string | null
): string | null => {
const best = r2Url ?? imageUrl;
if (best && isAbsoluteUrl(best)) return best;
if (filename) return `${getApiBase()}/images/${filename}`;
if (imageUrl && typeof imageUrl === "string" && imageUrl.startsWith("/"))
return `${getApiBase()}${imageUrl}`;
return null;
};
export const getImageUrlFallback = (
imageUrl: string | null | undefined,
filename: string | null | undefined,
r2Url?: string | null
): { primary: string | null; fallback: string | null } => {
const apiBase = getApiBase();
const best = r2Url ?? imageUrl;
const primary =
best && isAbsoluteUrl(best) ? best : null;
const fromFilename = filename ? `${apiBase}/images/${filename}` : null;
const fromRelative =
imageUrl && typeof imageUrl === "string" && imageUrl.startsWith("/")
? `${apiBase}${imageUrl}`
: null;
const fallback = fromFilename ?? fromRelative ?? null;
return { primary, fallback };
};