File size: 3,955 Bytes
e026dc9
 
8158a5c
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
e026dc9
 
 
8158a5c
 
 
e026dc9
 
 
 
 
 
 
 
8158a5c
 
 
e026dc9
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
505ff55
 
 
 
 
 
 
 
 
 
 
 
 
 
 
e026dc9
 
 
505ff55
 
 
 
 
 
 
 
 
 
 
 
 
 
 
e026dc9
 
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
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 };
};