mirror of
https://github.com/NginxProxyManager/nginx-proxy-manager.git
synced 2026-01-21 19:25:43 +00:00
Fix frontend locale tests after date-fns changed intl formatting
and also attempt to format dates in locale
This commit is contained in:
@@ -3,6 +3,7 @@ import { Field, useFormikContext } from "formik";
|
|||||||
import type { ReactNode } from "react";
|
import type { ReactNode } from "react";
|
||||||
import Select, { type ActionMeta, components, type OptionProps } from "react-select";
|
import Select, { type ActionMeta, components, type OptionProps } from "react-select";
|
||||||
import type { AccessList } from "src/api/backend";
|
import type { AccessList } from "src/api/backend";
|
||||||
|
import { useLocaleState } from "src/context";
|
||||||
import { useAccessLists } from "src/hooks";
|
import { useAccessLists } from "src/hooks";
|
||||||
import { formatDateTime, intl, T } from "src/locale";
|
import { formatDateTime, intl, T } from "src/locale";
|
||||||
|
|
||||||
@@ -32,6 +33,7 @@ interface Props {
|
|||||||
label?: string;
|
label?: string;
|
||||||
}
|
}
|
||||||
export function AccessField({ name = "accessListId", label = "access-list", id = "accessListId" }: Props) {
|
export function AccessField({ name = "accessListId", label = "access-list", id = "accessListId" }: Props) {
|
||||||
|
const { locale } = useLocaleState();
|
||||||
const { isLoading, isError, error, data } = useAccessLists(["owner", "items", "clients"]);
|
const { isLoading, isError, error, data } = useAccessLists(["owner", "items", "clients"]);
|
||||||
const { setFieldValue } = useFormikContext();
|
const { setFieldValue } = useFormikContext();
|
||||||
|
|
||||||
@@ -48,7 +50,7 @@ export function AccessField({ name = "accessListId", label = "access-list", id =
|
|||||||
{
|
{
|
||||||
users: item?.items?.length,
|
users: item?.items?.length,
|
||||||
rules: item?.clients?.length,
|
rules: item?.clients?.length,
|
||||||
date: item?.createdOn ? formatDateTime(item?.createdOn) : "N/A",
|
date: item?.createdOn ? formatDateTime(item?.createdOn, locale) : "N/A",
|
||||||
},
|
},
|
||||||
),
|
),
|
||||||
icon: <IconLock size={14} className="text-lime" />,
|
icon: <IconLock size={14} className="text-lime" />,
|
||||||
|
|||||||
@@ -2,6 +2,7 @@ import { IconShield } from "@tabler/icons-react";
|
|||||||
import { Field, useFormikContext } from "formik";
|
import { Field, useFormikContext } from "formik";
|
||||||
import Select, { type ActionMeta, components, type OptionProps } from "react-select";
|
import Select, { type ActionMeta, components, type OptionProps } from "react-select";
|
||||||
import type { Certificate } from "src/api/backend";
|
import type { Certificate } from "src/api/backend";
|
||||||
|
import { useLocaleState } from "src/context";
|
||||||
import { useCertificates } from "src/hooks";
|
import { useCertificates } from "src/hooks";
|
||||||
import { formatDateTime, intl, T } from "src/locale";
|
import { formatDateTime, intl, T } from "src/locale";
|
||||||
|
|
||||||
@@ -41,6 +42,7 @@ export function SSLCertificateField({
|
|||||||
allowNew,
|
allowNew,
|
||||||
forHttp = true,
|
forHttp = true,
|
||||||
}: Props) {
|
}: Props) {
|
||||||
|
const { locale } = useLocaleState();
|
||||||
const { isLoading, isError, error, data } = useCertificates();
|
const { isLoading, isError, error, data } = useCertificates();
|
||||||
const { values, setFieldValue } = useFormikContext();
|
const { values, setFieldValue } = useFormikContext();
|
||||||
const v: any = values || {};
|
const v: any = values || {};
|
||||||
@@ -75,7 +77,7 @@ export function SSLCertificateField({
|
|||||||
data?.map((cert: Certificate) => ({
|
data?.map((cert: Certificate) => ({
|
||||||
value: cert.id,
|
value: cert.id,
|
||||||
label: cert.niceName,
|
label: cert.niceName,
|
||||||
subLabel: `${cert.provider === "letsencrypt" ? intl.formatMessage({ id: "lets-encrypt" }) : cert.provider} — ${intl.formatMessage({ id: "expires.on" }, { date: cert.expiresOn ? formatDateTime(cert.expiresOn) : "N/A" })}`,
|
subLabel: `${cert.provider === "letsencrypt" ? intl.formatMessage({ id: "lets-encrypt" }) : cert.provider} — ${intl.formatMessage({ id: "expires.on" }, { date: cert.expiresOn ? formatDateTime(cert.expiresOn, locale) : "N/A" })}`,
|
||||||
icon: <IconShield size={14} className="text-pink" />,
|
icon: <IconShield size={14} className="text-pink" />,
|
||||||
})) || [];
|
})) || [];
|
||||||
|
|
||||||
|
|||||||
@@ -1,5 +1,6 @@
|
|||||||
import cn from "classnames";
|
import cn from "classnames";
|
||||||
import { differenceInDays, isPast } from "date-fns";
|
import { differenceInDays, isPast } from "date-fns";
|
||||||
|
import { useLocaleState } from "src/context";
|
||||||
import { formatDateTime, parseDate } from "src/locale";
|
import { formatDateTime, parseDate } from "src/locale";
|
||||||
|
|
||||||
interface Props {
|
interface Props {
|
||||||
@@ -8,6 +9,7 @@ interface Props {
|
|||||||
highlistNearlyExpired?: boolean;
|
highlistNearlyExpired?: boolean;
|
||||||
}
|
}
|
||||||
export function DateFormatter({ value, highlightPast, highlistNearlyExpired }: Props) {
|
export function DateFormatter({ value, highlightPast, highlistNearlyExpired }: Props) {
|
||||||
|
const { locale } = useLocaleState();
|
||||||
const d = parseDate(value);
|
const d = parseDate(value);
|
||||||
const dateIsPast = d ? isPast(d) : false;
|
const dateIsPast = d ? isPast(d) : false;
|
||||||
const days = d ? differenceInDays(d, new Date()) : 0;
|
const days = d ? differenceInDays(d, new Date()) : 0;
|
||||||
@@ -15,5 +17,5 @@ export function DateFormatter({ value, highlightPast, highlistNearlyExpired }: P
|
|||||||
"text-danger": highlightPast && dateIsPast,
|
"text-danger": highlightPast && dateIsPast,
|
||||||
"text-warning": highlistNearlyExpired && !dateIsPast && days <= 30 && days >= 0,
|
"text-warning": highlistNearlyExpired && !dateIsPast && days <= 30 && days >= 0,
|
||||||
});
|
});
|
||||||
return <span className={cl}>{formatDateTime(value)}</span>;
|
return <span className={cl}>{formatDateTime(value, locale)}</span>;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,5 +1,6 @@
|
|||||||
import cn from "classnames";
|
import cn from "classnames";
|
||||||
import type { ReactNode } from "react";
|
import type { ReactNode } from "react";
|
||||||
|
import { useLocaleState } from "src/context";
|
||||||
import { formatDateTime, T } from "src/locale";
|
import { formatDateTime, T } from "src/locale";
|
||||||
|
|
||||||
interface Props {
|
interface Props {
|
||||||
@@ -37,7 +38,9 @@ const DomainLink = ({ domain, color }: { domain?: string; color?: string }) => {
|
|||||||
};
|
};
|
||||||
|
|
||||||
export function DomainsFormatter({ domains, createdOn, niceName, provider, color }: Props) {
|
export function DomainsFormatter({ domains, createdOn, niceName, provider, color }: Props) {
|
||||||
|
const { locale } = useLocaleState();
|
||||||
const elms: ReactNode[] = [];
|
const elms: ReactNode[] = [];
|
||||||
|
|
||||||
if ((!domains || domains.length === 0) && !niceName) {
|
if ((!domains || domains.length === 0) && !niceName) {
|
||||||
elms.push(
|
elms.push(
|
||||||
<span key="nice-name" className="badge bg-danger-lt me-2">
|
<span key="nice-name" className="badge bg-danger-lt me-2">
|
||||||
@@ -62,7 +65,7 @@ export function DomainsFormatter({ domains, createdOn, niceName, provider, color
|
|||||||
<div className="font-weight-medium">{...elms}</div>
|
<div className="font-weight-medium">{...elms}</div>
|
||||||
{createdOn ? (
|
{createdOn ? (
|
||||||
<div className="text-secondary mt-1">
|
<div className="text-secondary mt-1">
|
||||||
<T id="created-on" data={{ date: formatDateTime(createdOn) }} />
|
<T id="created-on" data={{ date: formatDateTime(createdOn, locale) }} />
|
||||||
</div>
|
</div>
|
||||||
) : null}
|
) : null}
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
@@ -1,6 +1,7 @@
|
|||||||
import { IconArrowsCross, IconBolt, IconBoltOff, IconDisc, IconLock, IconShield, IconUser } from "@tabler/icons-react";
|
import { IconArrowsCross, IconBolt, IconBoltOff, IconDisc, IconLock, IconShield, IconUser } from "@tabler/icons-react";
|
||||||
import cn from "classnames";
|
import cn from "classnames";
|
||||||
import type { AuditLog } from "src/api/backend";
|
import type { AuditLog } from "src/api/backend";
|
||||||
|
import { useLocaleState } from "src/context";
|
||||||
import { formatDateTime, T } from "src/locale";
|
import { formatDateTime, T } from "src/locale";
|
||||||
|
|
||||||
const getEventValue = (event: AuditLog) => {
|
const getEventValue = (event: AuditLog) => {
|
||||||
@@ -66,6 +67,7 @@ interface Props {
|
|||||||
row: AuditLog;
|
row: AuditLog;
|
||||||
}
|
}
|
||||||
export function EventFormatter({ row }: Props) {
|
export function EventFormatter({ row }: Props) {
|
||||||
|
const { locale } = useLocaleState();
|
||||||
return (
|
return (
|
||||||
<div className="flex-fill">
|
<div className="flex-fill">
|
||||||
<div className="font-weight-medium">
|
<div className="font-weight-medium">
|
||||||
@@ -73,7 +75,7 @@ export function EventFormatter({ row }: Props) {
|
|||||||
<T id={`object.event.${row.action}`} tData={{ object: row.objectType }} />
|
<T id={`object.event.${row.action}`} tData={{ object: row.objectType }} />
|
||||||
— <span className="badge">{getEventValue(row)}</span>
|
— <span className="badge">{getEventValue(row)}</span>
|
||||||
</div>
|
</div>
|
||||||
<div className="text-secondary mt-1">{formatDateTime(row.createdOn)}</div>
|
<div className="text-secondary mt-1">{formatDateTime(row.createdOn, locale)}</div>
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,3 +1,4 @@
|
|||||||
|
import { useLocaleState } from "src/context";
|
||||||
import { formatDateTime, T } from "src/locale";
|
import { formatDateTime, T } from "src/locale";
|
||||||
|
|
||||||
interface Props {
|
interface Props {
|
||||||
@@ -6,6 +7,7 @@ interface Props {
|
|||||||
disabled?: boolean;
|
disabled?: boolean;
|
||||||
}
|
}
|
||||||
export function ValueWithDateFormatter({ value, createdOn, disabled }: Props) {
|
export function ValueWithDateFormatter({ value, createdOn, disabled }: Props) {
|
||||||
|
const { locale } = useLocaleState();
|
||||||
return (
|
return (
|
||||||
<div className="flex-fill">
|
<div className="flex-fill">
|
||||||
<div className="font-weight-medium">
|
<div className="font-weight-medium">
|
||||||
@@ -13,7 +15,7 @@ export function ValueWithDateFormatter({ value, createdOn, disabled }: Props) {
|
|||||||
</div>
|
</div>
|
||||||
{createdOn ? (
|
{createdOn ? (
|
||||||
<div className={`text-secondary mt-1 ${disabled ? "text-red" : ""}`}>
|
<div className={`text-secondary mt-1 ${disabled ? "text-red" : ""}`}>
|
||||||
<T id={disabled ? "disabled" : "created-on"} data={{ date: formatDateTime(createdOn) }} />
|
<T id={disabled ? "disabled" : "created-on"} data={{ date: formatDateTime(createdOn, locale) }} />
|
||||||
</div>
|
</div>
|
||||||
) : null}
|
) : null}
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
@@ -39,19 +39,19 @@ describe("DateFormatter", () => {
|
|||||||
it("format date from iso date", () => {
|
it("format date from iso date", () => {
|
||||||
const value = "2024-01-01T00:00:00.000Z";
|
const value = "2024-01-01T00:00:00.000Z";
|
||||||
const text = formatDateTime(value);
|
const text = formatDateTime(value);
|
||||||
expect(text).toBe("Monday, 01/01/2024, 12:00:00 am");
|
expect(text).toBe("1 Jan 2024, 12:00:00 am");
|
||||||
});
|
});
|
||||||
|
|
||||||
it("format date from unix timestamp number", () => {
|
it("format date from unix timestamp number", () => {
|
||||||
const value = 1762476112;
|
const value = 1762476112;
|
||||||
const text = formatDateTime(value);
|
const text = formatDateTime(value);
|
||||||
expect(text).toBe("Friday, 07/11/2025, 12:41:52 am");
|
expect(text).toBe("7 Nov 2025, 12:41:52 am");
|
||||||
});
|
});
|
||||||
|
|
||||||
it("format date from unix timestamp string", () => {
|
it("format date from unix timestamp string", () => {
|
||||||
const value = "1762476112";
|
const value = "1762476112";
|
||||||
const text = formatDateTime(value);
|
const text = formatDateTime(value);
|
||||||
expect(text).toBe("Friday, 07/11/2025, 12:41:52 am");
|
expect(text).toBe("7 Nov 2025, 12:41:52 am");
|
||||||
});
|
});
|
||||||
|
|
||||||
it("catch bad format from string", () => {
|
it("catch bad format from string", () => {
|
||||||
|
|||||||
@@ -1,4 +1,9 @@
|
|||||||
import { fromUnixTime, intlFormat, parseISO } from "date-fns";
|
import {
|
||||||
|
fromUnixTime,
|
||||||
|
type IntlFormatFormatOptions,
|
||||||
|
intlFormat,
|
||||||
|
parseISO,
|
||||||
|
} from "date-fns";
|
||||||
|
|
||||||
const isUnixTimestamp = (value: unknown): boolean => {
|
const isUnixTimestamp = (value: unknown): boolean => {
|
||||||
if (typeof value !== "number" && typeof value !== "string") return false;
|
if (typeof value !== "number" && typeof value !== "string") return false;
|
||||||
@@ -20,20 +25,19 @@ const parseDate = (value: string | number): Date | null => {
|
|||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
const formatDateTime = (value: string | number): string => {
|
const formatDateTime = (value: string | number, locale = "en-US"): string => {
|
||||||
const d = parseDate(value);
|
const d = parseDate(value);
|
||||||
if (!d) return `${value}`;
|
if (!d) return `${value}`;
|
||||||
try {
|
try {
|
||||||
return intlFormat(d, {
|
return intlFormat(
|
||||||
weekday: "long",
|
d,
|
||||||
year: "numeric",
|
{
|
||||||
month: "numeric",
|
dateStyle: "medium",
|
||||||
day: "numeric",
|
timeStyle: "medium",
|
||||||
hour: "numeric",
|
hourCycle: "h12",
|
||||||
minute: "numeric",
|
} as IntlFormatFormatOptions,
|
||||||
second: "numeric",
|
{ locale },
|
||||||
hour12: true,
|
);
|
||||||
});
|
|
||||||
} catch {
|
} catch {
|
||||||
return `${value}`;
|
return `${value}`;
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user