Press n or j to go to the next uncovered block, b, p or k for the previous block.
| 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 | 731x 16x 15x 15x 3x 6x 12x 12x | import { type ClassValue, clsx } from 'clsx'
import { toast } from 'sonner'
import { twMerge } from 'tailwind-merge'
export function cn(...inputs: ClassValue[]) {
return twMerge(clsx(inputs))
}
/**
* Handle API errors with quota-aware messaging.
* Returns true if the error was a quota error (handled), false otherwise.
*/
export function handleQuotaError(error: unknown, fallbackMessage: string): boolean {
const err = error as { status?: number; body?: { error?: string; resets_at?: string } }
if (err?.status === 429 && err?.body?.error === 'quota_exceeded') {
const resetsAt = err.body.resets_at
const resetTime = resetsAt
? new Date(resetsAt).toLocaleTimeString([], { hour: '2-digit', minute: '2-digit' })
: 'midnight'
toast.error(`Daily limit reached. Resets at ${resetTime}.`)
return true
}
toast.error(fallbackMessage)
return false
}
/**
* Format nutrition key into human readable label.
* Converts camelCase keys like 'carbohydrateContent' to 'Carbohydrate'.
* Also handles snake_case like 'saturated_fat' to 'Saturated Fat'.
*/
export function formatNutritionKey(key: string): string {
if (!key) return ''
// Remove "Content" suffix
let formatted = key.replace(/Content$/, '')
// Handle snake_case
if (formatted.includes('_')) {
return formatted
.split('_')
.map((w) => w.charAt(0).toUpperCase() + w.slice(1))
.join(' ')
}
// Handle camelCase - insert space before capitals
formatted = formatted.replace(/([a-z])([A-Z])/g, '$1 $2')
// Capitalize first letter
return formatted.charAt(0).toUpperCase() + formatted.slice(1)
}
|