Add account hierarchy framework with migrations, RLS policies, and UI components
This commit is contained in:
@@ -14,13 +14,16 @@
|
||||
"./utils": "./src/utils.ts",
|
||||
"./events": "./src/events/index.tsx",
|
||||
"./registry": "./src/registry/index.ts",
|
||||
"./env": "./src/env/index.ts"
|
||||
"./env": "./src/env/index.ts",
|
||||
"./dates": "./src/dates/index.ts",
|
||||
"./formatters": "./src/dates/index.ts"
|
||||
},
|
||||
"scripts": {
|
||||
"clean": "git clean -xdf .turbo node_modules",
|
||||
"typecheck": "tsc --noEmit"
|
||||
},
|
||||
"dependencies": {
|
||||
"date-fns": "catalog:",
|
||||
"next-runtime-env": "catalog:",
|
||||
"pino": "catalog:"
|
||||
},
|
||||
|
||||
149
packages/shared/src/dates/index.ts
Normal file
149
packages/shared/src/dates/index.ts
Normal file
@@ -0,0 +1,149 @@
|
||||
import { type Locale, format, isValid, parseISO } from 'date-fns';
|
||||
import { de, enUS } from 'date-fns/locale';
|
||||
|
||||
const LOCALES: Record<string, Locale> = {
|
||||
de,
|
||||
'de-DE': de,
|
||||
en: enUS,
|
||||
'en-US': enUS,
|
||||
};
|
||||
|
||||
function getLocale(locale?: string): Locale {
|
||||
if (locale && locale in LOCALES) {
|
||||
return LOCALES[locale]!;
|
||||
}
|
||||
|
||||
return de;
|
||||
}
|
||||
|
||||
function safeParse(value: string | Date | null | undefined): Date | null {
|
||||
if (!value) return null;
|
||||
|
||||
if (value instanceof Date) return value;
|
||||
|
||||
try {
|
||||
return parseISO(value);
|
||||
} catch {
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Format a date for display in the UI (e.g. "31.03.2026").
|
||||
* Returns '—' for null/invalid values.
|
||||
*/
|
||||
export function formatDate(
|
||||
value: string | Date | null | undefined,
|
||||
locale?: string,
|
||||
): string {
|
||||
const date = safeParse(value);
|
||||
|
||||
if (!date || !isValid(date)) return '—';
|
||||
|
||||
return format(date, 'P', { locale: getLocale(locale) });
|
||||
}
|
||||
|
||||
/**
|
||||
* Format a date with long month (e.g. "31. März 2026").
|
||||
*/
|
||||
export function formatDateLong(
|
||||
value: string | Date | null | undefined,
|
||||
locale?: string,
|
||||
): string {
|
||||
const date = safeParse(value);
|
||||
|
||||
if (!date || !isValid(date)) return '—';
|
||||
|
||||
return format(date, 'PP', { locale: getLocale(locale) });
|
||||
}
|
||||
|
||||
/**
|
||||
* Format a date with weekday and long month (e.g. "Montag, 31. März 2026").
|
||||
*/
|
||||
export function formatDateFull(
|
||||
value: string | Date | null | undefined,
|
||||
locale?: string,
|
||||
): string {
|
||||
const date = safeParse(value);
|
||||
|
||||
if (!date || !isValid(date)) return '—';
|
||||
|
||||
return format(date, 'EEEE, PP', { locale: getLocale(locale) });
|
||||
}
|
||||
|
||||
/**
|
||||
* Format a date with time (e.g. "31.03.2026, 14:30").
|
||||
*/
|
||||
export function formatDateTime(
|
||||
value: string | Date | null | undefined,
|
||||
locale?: string,
|
||||
): string {
|
||||
const date = safeParse(value);
|
||||
|
||||
if (!date || !isValid(date)) return '—';
|
||||
|
||||
return format(date, 'Pp', { locale: getLocale(locale) });
|
||||
}
|
||||
|
||||
/**
|
||||
* Format a date for HTML date input (YYYY-MM-DD).
|
||||
*/
|
||||
export function formatDateInput(
|
||||
value: string | Date | null | undefined,
|
||||
): string {
|
||||
const date = safeParse(value);
|
||||
|
||||
if (!date || !isValid(date)) return '';
|
||||
|
||||
return format(date, 'yyyy-MM-dd');
|
||||
}
|
||||
|
||||
/**
|
||||
* Format just the month (short), e.g. "Mär" or "Mar".
|
||||
*/
|
||||
export function formatMonthShort(
|
||||
value: string | Date | null | undefined,
|
||||
locale?: string,
|
||||
): string {
|
||||
const date = safeParse(value);
|
||||
|
||||
if (!date || !isValid(date)) return '—';
|
||||
|
||||
return format(date, 'MMM', { locale: getLocale(locale) });
|
||||
}
|
||||
|
||||
/**
|
||||
* Format a number for display using locale (e.g. 1.234,56).
|
||||
*/
|
||||
export function formatNumber(
|
||||
value: number | string | null | undefined,
|
||||
locale = 'de-DE',
|
||||
): string {
|
||||
if (value == null) return '—';
|
||||
|
||||
return Number(value).toLocaleString(locale);
|
||||
}
|
||||
|
||||
/**
|
||||
* Format a currency amount (e.g. "1.234,56 €").
|
||||
*/
|
||||
export function formatCurrencyAmount(
|
||||
value: number | string | null | undefined,
|
||||
locale = 'de-DE',
|
||||
currency = 'EUR',
|
||||
): string {
|
||||
if (value == null) return '—';
|
||||
|
||||
return Number(value).toLocaleString(locale, {
|
||||
style: 'currency',
|
||||
currency,
|
||||
minimumFractionDigits: 2,
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Get today's date as YYYY-MM-DD string (UTC).
|
||||
*/
|
||||
export function todayISO(): string {
|
||||
return new Date().toISOString().split('T')[0]!;
|
||||
}
|
||||
Reference in New Issue
Block a user