Jake Vanderwerf
2026-01-01 52733beffd7f1c48012b371d4ad8e7d937afd924
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
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
// formatters.js - Utility functions for formatting data
 
/**
 * Format a date in relative time (e.g., "2 days ago")
 * @param {string} dateStr - ISO date string
 * @returns {string} - Formatted relative time
 */
export function formatTimeAgo(dateStr) {
    const date = new Date(dateStr);
    const now = new Date();
    const seconds = Math.floor((now - date) / 1000);
    const minutes = Math.floor(seconds / 60);
    const hours = Math.floor(minutes / 60);
    const days = Math.floor(hours / 24);
    const weeks = Math.floor(days / 7);
    const months = Math.floor(days / 30);
 
    // Within the last 24 hours - show hours ago
    if (hours < 24) {
        if (hours === 0) {
            return minutes === 0 ? 'Just now' : `${minutes} ${minutes === 1 ? 'minute' : 'minutes'} ago`;
        }
        return `${hours} ${hours === 1 ? 'hour' : 'hours'} ago`;
    }
 
    // Within the last 7 days - show days ago
    if (days < 7) {
        return `${days} ${days === 1 ? 'day' : 'days'} ago`;
    }
 
    // Within the last 4 weeks - show week-based format
    if (weeks < 4) {
        if (weeks === 1) return 'Last week';
        return 'A few weeks ago';
    }
 
    // Within the last 2 months - show "Last month"
    if (months < 2) {
        return 'Last month';
    }
 
    // For anything older, show the full date
    return date.toLocaleDateString('en-US', {
        year: 'numeric',
        month: 'long',
        day: 'numeric'
    });
}
 
/**
 * Format a number with comma separator (e.g., 1,234)
 * @param {number} num - Number to format
 * @returns {string} - Formatted number
 */
export function formatNumber(num) {
    return num.toString().replace(/\B(?=(\d{3})+(?!\d))/g, ",");
}
 
/**
 * Format a price with currency symbol
 * @param {number} price - Price to format
 * @param {string} currency - Currency code (default: 'CAD')
 * @returns {string} - Formatted price
 */
export function formatPrice(price, currency = 'CAD') {
    return new Intl.NumberFormat('en-CA', {
        style: 'currency',
        currency: currency
    }).format(price);
}
 
/**
 * Escape HTML special characters to prevent XSS
 * @param {string} text - Text to escape
 * @returns {string} - Escaped text
 */
export function escapeHtml(text) {
    if (!text) return '';
 
    return text
        .replace(/&/g, "&amp;")
        .replace(/</g, "&lt;")
        .replace(/>/g, "&gt;")
        .replace(/"/g, "&quot;")
        .replace(/'/g, "&#039;");
}
 
/**
 * Truncate text to a specific length with ellipsis
 * @param {string} text - Text to truncate
 * @param {number} length - Maximum length
 * @returns {string} - Truncated text
 */
export function truncateText(text, length = 100) {
    if (!text || text.length <= length) return text;
    return text.substring(0, length) + '...';
}
 
/**
 * Format a date range (e.g., "Jan 1 - Jan 5, 2023")
 * @param {string} startDate - Start date ISO string
 * @param {string} endDate - End date ISO string
 * @returns {string} - Formatted date range
 */
export function formatDateRange(startDate, endDate) {
    const start = new Date(startDate);
    const end = new Date(endDate);
 
    // If same day, just show one date
    if (start.toDateString() === end.toDateString()) {
        return start.toLocaleDateString('en-US', {
            year: 'numeric',
            month: 'short',
            day: 'numeric'
        });
    }
 
    // If same month and year, show range with month once
    if (start.getMonth() === end.getMonth() && start.getFullYear() === end.getFullYear()) {
        return `${start.toLocaleDateString('en-US', { month: 'short', day: 'numeric' })} - ${end.getDate()}, ${end.getFullYear()}`;
    }
 
    // If same year, show full range with year once
    if (start.getFullYear() === end.getFullYear()) {
        return `${start.toLocaleDateString('en-US', { month: 'short', day: 'numeric' })} - ${end.toLocaleDateString('en-US', { month: 'short', day: 'numeric' })}, ${end.getFullYear()}`;
    }
 
    // Different years, show full dates
    return `${start.toLocaleDateString('en-US', { month: 'short', day: 'numeric', year: 'numeric' })} - ${end.toLocaleDateString('en-US', { month: 'short', day: 'numeric', year: 'numeric' })}`;
}
 
/**
 * Debounce function to limit frequent calls
 * @param {Function} func - Function to debounce
 * @param {number} wait - Wait time in milliseconds
 * @returns {Function} - Debounced function
 */
export function debounce(func, wait = 300) {
    let timeout;
    return function(...args) {
        clearTimeout(timeout);
        timeout = setTimeout(() => func.apply(this, args), wait);
    };
}
 
/**
 * Throttle function to limit call frequency
 * @param {Function} func - Function to throttle
 * @param {number} limit - Time limit in milliseconds
 * @returns {Function} - Throttled function
 */
export function throttle(func, limit = 300) {
    let inThrottle;
    return function(...args) {
        if (!inThrottle) {
            func.apply(this, args);
            inThrottle = true;
            setTimeout(() => inThrottle = false, limit);
        }
    };
}