mirror of
https://github.com/rgrgogu/new_starr.git
synced 2026-09-27 00:12:54 +08:00
@@ -0,0 +1,87 @@
|
||||
/**
|
||||
* datetime.util.js
|
||||
*
|
||||
* Pure date/time formatting functions. All accept an optional options object
|
||||
* with { timezone, locale }:
|
||||
* timezone — 'local' (default) | 'UTC'
|
||||
* locale — defaults to navigator.language (browser OS setting)
|
||||
*
|
||||
* These are the raw functions. In React components, use the useDateFormat()
|
||||
* hook instead — it reads the user's timezone preference automatically.
|
||||
*/
|
||||
|
||||
function tzOpt(timezone) {
|
||||
return timezone === 'UTC' ? { timeZone: 'UTC' } : {};
|
||||
}
|
||||
|
||||
function loc(locale) {
|
||||
return locale ?? (typeof navigator !== 'undefined' ? navigator.language : 'en-US');
|
||||
}
|
||||
|
||||
/** "Jun 27, 2026" */
|
||||
export function fmtDate(value, { timezone = 'local', locale } = {}) {
|
||||
if (!value) return '—';
|
||||
return new Date(value).toLocaleDateString(loc(locale), {
|
||||
month: 'short', day: 'numeric', year: 'numeric',
|
||||
...tzOpt(timezone),
|
||||
});
|
||||
}
|
||||
|
||||
/** "Jun 27, 2026, 3:45 PM" */
|
||||
export function fmtDateTime(value, { timezone = 'local', locale } = {}) {
|
||||
if (!value) return '—';
|
||||
return new Date(value).toLocaleString(loc(locale), {
|
||||
month: 'short', day: 'numeric', year: 'numeric',
|
||||
hour: 'numeric', minute: '2-digit',
|
||||
...tzOpt(timezone),
|
||||
});
|
||||
}
|
||||
|
||||
/** "Jun 27" — no year, for compact table cells */
|
||||
export function fmtDateShort(value, { timezone = 'local', locale } = {}) {
|
||||
if (!value) return '—';
|
||||
return new Date(value).toLocaleDateString(loc(locale), {
|
||||
month: 'short', day: 'numeric',
|
||||
...tzOpt(timezone),
|
||||
});
|
||||
}
|
||||
|
||||
/** "3:45 PM" */
|
||||
export function fmtTime(value, { timezone = 'local', locale } = {}) {
|
||||
if (!value) return '—';
|
||||
return new Date(value).toLocaleTimeString(loc(locale), {
|
||||
hour: 'numeric', minute: '2-digit',
|
||||
...tzOpt(timezone),
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* "2026-06-27" — ISO date string for date-picker inputs.
|
||||
* Always uses local calendar date; timezone conversion does not apply here
|
||||
* because the value is used as a form input, not a display label.
|
||||
*/
|
||||
export function fmtISO(value) {
|
||||
if (!value) return '';
|
||||
const d = new Date(value);
|
||||
return [
|
||||
d.getFullYear(),
|
||||
String(d.getMonth() + 1).padStart(2, '0'),
|
||||
String(d.getDate()).padStart(2, '0'),
|
||||
].join('-');
|
||||
}
|
||||
|
||||
/** "₱1,234.00" / "$99.00" — currency formatting using browser locale */
|
||||
export function fmtCurrency(value, currency = 'PHP', { locale } = {}) {
|
||||
if (value === null || value === undefined) return '—';
|
||||
return new Intl.NumberFormat(loc(locale), {
|
||||
style: 'currency',
|
||||
currency,
|
||||
minimumFractionDigits: 2,
|
||||
}).format(Number(value));
|
||||
}
|
||||
|
||||
/** "1,234.00" — plain number with decimal places */
|
||||
export function fmtNumber(value, { locale, minimumFractionDigits = 2 } = {}) {
|
||||
if (value === null || value === undefined) return '—';
|
||||
return Number(value).toLocaleString(loc(locale), { minimumFractionDigits });
|
||||
}
|
||||
@@ -0,0 +1,24 @@
|
||||
import { getTierColor } from './tierColors';
|
||||
|
||||
/**
|
||||
* Resolves badge + panel colors for a tier slug using the live tierMap from the API.
|
||||
* The category's stored `color` key drives all styling — no rank-indexed arrays.
|
||||
*/
|
||||
export function resolveTierBadge(slug, tierMap = {}) {
|
||||
const info = tierMap[slug];
|
||||
const colorKey = info?.color ?? ((!slug || slug === 'free') ? 'green' : 'purple');
|
||||
const colors = getTierColor(colorKey);
|
||||
const rank = info?.rank ?? 0;
|
||||
const label = info?.name ?? (slug ? slug.charAt(0).toUpperCase() + slug.slice(1) : 'Free');
|
||||
return { rank, label, cls: colors.badge, panel: colors.panel, colorKey };
|
||||
}
|
||||
|
||||
/** Returns badge Tailwind class string for a stored color key. */
|
||||
export function tierBadgeClass(colorKey = 'green') {
|
||||
return getTierColor(colorKey).badge;
|
||||
}
|
||||
|
||||
/** Returns panel Tailwind classes { bg, border } for a stored color key. */
|
||||
export function tierPanelColors(colorKey = 'green') {
|
||||
return getTierColor(colorKey).panel;
|
||||
}
|
||||
@@ -0,0 +1,100 @@
|
||||
/**
|
||||
* Official Tailwind color palette for tier categories.
|
||||
*
|
||||
* Each key is stored in tier_categories.color (DB).
|
||||
* badge — classes applied to <Badge> components
|
||||
* panel — bg + border classes for upsell/info panels
|
||||
* swatch — hex for the color picker dot in the admin UI
|
||||
*/
|
||||
export const TIER_COLOR_MAP = {
|
||||
green: {
|
||||
label: "Green",
|
||||
swatch: "#22c55e",
|
||||
badge: "bg-green-500 text-white border-0",
|
||||
panel: { bg: "bg-green-50 dark:bg-green-950/30", border: "border-green-200 dark:border-green-800" },
|
||||
},
|
||||
purple: {
|
||||
label: "Purple",
|
||||
swatch: "#a855f7",
|
||||
badge: "bg-gradient-to-r from-fuchsia-600 to-purple-600 text-white border-0",
|
||||
panel: { bg: "bg-fuchsia-50 dark:bg-fuchsia-950/30", border: "border-fuchsia-200 dark:border-fuchsia-800" },
|
||||
},
|
||||
rose: {
|
||||
label: "Rose",
|
||||
swatch: "#f43f5e",
|
||||
badge: "bg-gradient-to-r from-rose-500 to-red-600 text-white border-0",
|
||||
panel: { bg: "bg-rose-50 dark:bg-rose-950/30", border: "border-rose-200 dark:border-rose-800" },
|
||||
},
|
||||
amber: {
|
||||
label: "Amber",
|
||||
swatch: "#f59e0b",
|
||||
badge: "bg-gradient-to-r from-amber-400 to-orange-500 text-white border-0",
|
||||
panel: { bg: "bg-amber-50 dark:bg-amber-950/30", border: "border-amber-200 dark:border-amber-800" },
|
||||
},
|
||||
sky: {
|
||||
label: "Sky",
|
||||
swatch: "#0ea5e9",
|
||||
badge: "bg-gradient-to-r from-sky-500 to-blue-600 text-white border-0",
|
||||
panel: { bg: "bg-sky-50 dark:bg-sky-950/30", border: "border-sky-200 dark:border-sky-800" },
|
||||
},
|
||||
indigo: {
|
||||
label: "Indigo",
|
||||
swatch: "#6366f1",
|
||||
badge: "bg-gradient-to-r from-indigo-500 to-violet-600 text-white border-0",
|
||||
panel: { bg: "bg-indigo-50 dark:bg-indigo-950/30", border: "border-indigo-200 dark:border-indigo-800" },
|
||||
},
|
||||
teal: {
|
||||
label: "Teal",
|
||||
swatch: "#14b8a6",
|
||||
badge: "bg-gradient-to-r from-teal-500 to-emerald-600 text-white border-0",
|
||||
panel: { bg: "bg-teal-50 dark:bg-teal-950/30", border: "border-teal-200 dark:border-teal-800" },
|
||||
},
|
||||
orange: {
|
||||
label: "Orange",
|
||||
swatch: "#f97316",
|
||||
badge: "bg-gradient-to-r from-orange-500 to-red-500 text-white border-0",
|
||||
panel: { bg: "bg-orange-50 dark:bg-orange-950/30", border: "border-orange-200 dark:border-orange-800" },
|
||||
},
|
||||
pink: {
|
||||
label: "Pink",
|
||||
swatch: "#ec4899",
|
||||
badge: "bg-gradient-to-r from-pink-500 to-rose-500 text-white border-0",
|
||||
panel: { bg: "bg-pink-50 dark:bg-pink-950/30", border: "border-pink-200 dark:border-pink-800" },
|
||||
},
|
||||
cyan: {
|
||||
label: "Cyan",
|
||||
swatch: "#06b6d4",
|
||||
badge: "bg-gradient-to-r from-cyan-500 to-sky-500 text-white border-0",
|
||||
panel: { bg: "bg-cyan-50 dark:bg-cyan-950/30", border: "border-cyan-200 dark:border-cyan-800" },
|
||||
},
|
||||
lime: {
|
||||
label: "Lime",
|
||||
swatch: "#84cc16",
|
||||
badge: "bg-gradient-to-r from-lime-500 to-green-600 text-white border-0",
|
||||
panel: { bg: "bg-lime-50 dark:bg-lime-950/30", border: "border-lime-200 dark:border-lime-800" },
|
||||
},
|
||||
slate: {
|
||||
label: "Slate",
|
||||
swatch: "#64748b",
|
||||
badge: "bg-gradient-to-r from-slate-600 to-slate-800 text-white border-0",
|
||||
panel: { bg: "bg-slate-50 dark:bg-slate-950/30", border: "border-slate-200 dark:border-slate-700" },
|
||||
},
|
||||
};
|
||||
|
||||
/** Ordered list for the color picker UI. */
|
||||
export const TIER_COLOR_OPTIONS = Object.entries(TIER_COLOR_MAP).map(([key, val]) => ({
|
||||
key,
|
||||
label: val.label,
|
||||
swatch: val.swatch,
|
||||
}));
|
||||
|
||||
/** Fallback when a stored color key is not in the map. */
|
||||
const FALLBACK = TIER_COLOR_MAP.purple;
|
||||
|
||||
/**
|
||||
* Returns the color definition for a stored color key.
|
||||
* Falls back to purple for unknown keys.
|
||||
*/
|
||||
export function getTierColor(colorKey) {
|
||||
return TIER_COLOR_MAP[colorKey] ?? FALLBACK;
|
||||
}
|
||||
Reference in New Issue
Block a user