Remove Luxon from codebase (#14448)
Fixes #14444 We shouldn't have 2 libraries to manage dates, one is enough
This commit is contained in:
@@ -46,7 +46,6 @@
|
||||
"lodash.pickby": "^4.6.0",
|
||||
"lodash.snakecase": "^4.1.1",
|
||||
"lodash.upperfirst": "^4.3.1",
|
||||
"luxon": "^3.3.0",
|
||||
"microdiff": "^1.3.2",
|
||||
"moize": "^6.1.6",
|
||||
"patch-package": "^8.0.0",
|
||||
@@ -142,7 +141,6 @@
|
||||
"@types/lodash.pickby": "^4.6.9",
|
||||
"@types/lodash.snakecase": "^4.1.7",
|
||||
"@types/lodash.upperfirst": "^4.3.7",
|
||||
"@types/luxon": "^3.3.0",
|
||||
"@types/mailparser": "^3.4.6",
|
||||
"@types/ms": "^0.7.31",
|
||||
"@types/node": "^24.0.0",
|
||||
|
||||
+3
-2
@@ -10,7 +10,6 @@ import { DropdownContent } from '@/ui/layout/dropdown/components/DropdownContent
|
||||
import { ScrollWrapper } from '@/ui/utilities/scroll/components/ScrollWrapper';
|
||||
import { useRecoilComponentValue } from '@/ui/utilities/state/component-state/hooks/useRecoilComponentValue';
|
||||
import styled from '@emotion/styled';
|
||||
import { id } from 'date-fns/locale';
|
||||
import { isDefined } from 'twenty-shared/utils';
|
||||
|
||||
const StyledContainer = styled.div`
|
||||
@@ -37,7 +36,9 @@ export const AdvancedFilterRootRecordFilterGroup = () => {
|
||||
}
|
||||
|
||||
return (
|
||||
<ScrollWrapper componentInstanceId={`scroll-wrapper-dropdown-menu-${id}`}>
|
||||
<ScrollWrapper
|
||||
componentInstanceId={`scroll-wrapper-dropdown-menu-${rootRecordFilterGroup.id}`}
|
||||
>
|
||||
<DropdownContent widthInPixels={ADVANCED_FILTER_DROPDOWN_CONTENT_WIDTH}>
|
||||
<StyledContainer>
|
||||
{childRecordFiltersAndRecordFilterGroups.map(
|
||||
|
||||
+3
-1
@@ -114,7 +114,9 @@ export const FormDateTimeFieldInput = ({
|
||||
);
|
||||
|
||||
const draftValueAsDate =
|
||||
isDefined(draftValue.value) && isNonEmptyString(draftValue.value)
|
||||
isDefined(draftValue.value) &&
|
||||
isNonEmptyString(draftValue.value) &&
|
||||
draftValue.type === 'static'
|
||||
? new Date(draftValue.value)
|
||||
: null;
|
||||
|
||||
|
||||
+28
-33
@@ -10,7 +10,6 @@ import {
|
||||
waitForElementToBeRemoved,
|
||||
within,
|
||||
} from '@storybook/test';
|
||||
import { DateTime } from 'luxon';
|
||||
import { I18nFrontDecorator } from '~/testing/decorators/I18nFrontDecorator';
|
||||
import { WorkflowStepDecorator } from '~/testing/decorators/WorkflowStepDecorator';
|
||||
import { MOCKED_STEP_ID } from '~/testing/mock-data/workflow';
|
||||
@@ -208,23 +207,21 @@ export const DefaultsToMinValueWhenTypingReallyOldDate: Story = {
|
||||
}),
|
||||
);
|
||||
|
||||
const expectedDate = DateTime.fromJSDate(MIN_DATE)
|
||||
.toLocal()
|
||||
.set({
|
||||
day: MIN_DATE.getUTCDate(),
|
||||
month: MIN_DATE.getUTCMonth() + 1,
|
||||
year: MIN_DATE.getUTCFullYear(),
|
||||
hour: 0,
|
||||
minute: 0,
|
||||
second: 0,
|
||||
millisecond: 0,
|
||||
});
|
||||
const expectedDate = new Date(
|
||||
MIN_DATE.getUTCFullYear(),
|
||||
MIN_DATE.getUTCMonth(),
|
||||
MIN_DATE.getUTCDate(),
|
||||
0,
|
||||
0,
|
||||
0,
|
||||
0,
|
||||
);
|
||||
|
||||
const selectedDay = within(datePicker).getByRole('option', {
|
||||
selected: true,
|
||||
name: (accessibleName) => {
|
||||
// The name looks like "Choose Sunday, December 31st, 1899"
|
||||
return accessibleName.includes(expectedDate.toFormat('yyyy'));
|
||||
return accessibleName.includes(expectedDate.getFullYear().toString());
|
||||
},
|
||||
});
|
||||
expect(selectedDay).toBeVisible();
|
||||
@@ -264,23 +261,23 @@ export const DefaultsToMaxValueWhenTypingReallyFarDate: Story = {
|
||||
);
|
||||
}),
|
||||
waitFor(() => {
|
||||
const expectedDate = DateTime.fromJSDate(MAX_DATE)
|
||||
.toLocal()
|
||||
.set({
|
||||
day: MAX_DATE.getUTCDate(),
|
||||
month: MAX_DATE.getUTCMonth() + 1,
|
||||
year: MAX_DATE.getUTCFullYear(),
|
||||
hour: 0,
|
||||
minute: 0,
|
||||
second: 0,
|
||||
millisecond: 0,
|
||||
});
|
||||
const expectedDate = new Date(
|
||||
MAX_DATE.getUTCFullYear(),
|
||||
MAX_DATE.getUTCMonth(),
|
||||
MAX_DATE.getUTCDate(),
|
||||
0,
|
||||
0,
|
||||
0,
|
||||
0,
|
||||
);
|
||||
|
||||
const selectedDay = within(datePicker).getByRole('option', {
|
||||
selected: true,
|
||||
name: (accessibleName) => {
|
||||
// The name looks like "Choose Thursday, December 30th, 2100"
|
||||
return accessibleName.includes(expectedDate.toFormat('yyyy'));
|
||||
return accessibleName.includes(
|
||||
expectedDate.getFullYear().toString(),
|
||||
);
|
||||
},
|
||||
});
|
||||
expect(selectedDay).toBeVisible();
|
||||
@@ -315,13 +312,7 @@ export const SwitchesToStandaloneVariable: Story = {
|
||||
const variableTag = await canvas.findByText('Creation date');
|
||||
expect(variableTag).toBeVisible();
|
||||
|
||||
const removeVariableButton = canvasElement.querySelector(
|
||||
'button .tabler-icon-x',
|
||||
);
|
||||
|
||||
if (!removeVariableButton) {
|
||||
throw new Error('Remove variable button not found');
|
||||
}
|
||||
const removeVariableButton = canvas.getByLabelText('Remove variable');
|
||||
|
||||
await Promise.all([
|
||||
userEvent.click(removeVariableButton),
|
||||
@@ -342,8 +333,12 @@ export const ClickingOutsideDoesNotResetInputState: Story = {
|
||||
onChange: fn(),
|
||||
},
|
||||
play: async ({ canvasElement, args }) => {
|
||||
if (!args.defaultValue) {
|
||||
throw new Error('This test requires a defaultValue');
|
||||
}
|
||||
|
||||
const defaultValueAsDisplayString = parseDateToString({
|
||||
date: new Date(args.defaultValue!),
|
||||
date: new Date(args.defaultValue),
|
||||
isDateTimeInput: false,
|
||||
userTimezone: undefined,
|
||||
});
|
||||
|
||||
+30
-33
@@ -11,7 +11,6 @@ import {
|
||||
waitForElementToBeRemoved,
|
||||
within,
|
||||
} from '@storybook/test';
|
||||
import { DateTime } from 'luxon';
|
||||
import { I18nFrontDecorator } from '~/testing/decorators/I18nFrontDecorator';
|
||||
import { WorkflowStepDecorator } from '~/testing/decorators/WorkflowStepDecorator';
|
||||
import { MOCKED_STEP_ID } from '~/testing/mock-data/workflow';
|
||||
@@ -257,23 +256,23 @@ export const DefaultsToMinValueWhenTypingReallyOldDate: Story = {
|
||||
);
|
||||
}),
|
||||
waitFor(() => {
|
||||
const expectedDate = DateTime.fromJSDate(MIN_DATE)
|
||||
.toLocal()
|
||||
.set({
|
||||
day: MIN_DATE.getUTCDate(),
|
||||
month: MIN_DATE.getUTCMonth() + 1,
|
||||
year: MIN_DATE.getUTCFullYear(),
|
||||
hour: 0,
|
||||
minute: 0,
|
||||
second: 0,
|
||||
millisecond: 0,
|
||||
});
|
||||
const expectedDate = new Date(
|
||||
MIN_DATE.getUTCFullYear(),
|
||||
MIN_DATE.getUTCMonth(),
|
||||
MIN_DATE.getUTCDate(),
|
||||
0,
|
||||
0,
|
||||
0,
|
||||
0,
|
||||
);
|
||||
|
||||
const selectedDay = within(datePicker).getByRole('option', {
|
||||
selected: true,
|
||||
name: (accessibleName) => {
|
||||
// The name looks like "Choose Sunday, December 31st, 1899"
|
||||
return accessibleName.includes(expectedDate.toFormat('yyyy'));
|
||||
return accessibleName.includes(
|
||||
expectedDate.getFullYear().toString(),
|
||||
);
|
||||
},
|
||||
});
|
||||
expect(selectedDay).toBeVisible();
|
||||
@@ -315,23 +314,23 @@ export const DefaultsToMaxValueWhenTypingReallyFarDate: Story = {
|
||||
);
|
||||
}),
|
||||
waitFor(() => {
|
||||
const expectedDate = DateTime.fromJSDate(MAX_DATE)
|
||||
.toLocal()
|
||||
.set({
|
||||
day: MAX_DATE.getUTCDate(),
|
||||
month: MAX_DATE.getUTCMonth() + 1,
|
||||
year: MAX_DATE.getUTCFullYear(),
|
||||
hour: 0,
|
||||
minute: 0,
|
||||
second: 0,
|
||||
millisecond: 0,
|
||||
});
|
||||
const expectedDate = new Date(
|
||||
MAX_DATE.getUTCFullYear(),
|
||||
MAX_DATE.getUTCMonth(),
|
||||
MAX_DATE.getUTCDate(),
|
||||
0,
|
||||
0,
|
||||
0,
|
||||
0,
|
||||
);
|
||||
|
||||
const selectedDay = within(datePicker).getByRole('option', {
|
||||
selected: true,
|
||||
name: (accessibleName) => {
|
||||
// The name looks like "Choose Thursday, December 30th, 2100"
|
||||
return accessibleName.includes(expectedDate.toFormat('yyyy'));
|
||||
return accessibleName.includes(
|
||||
expectedDate.getFullYear().toString(),
|
||||
);
|
||||
},
|
||||
});
|
||||
expect(selectedDay).toBeVisible();
|
||||
@@ -366,13 +365,7 @@ export const SwitchesToStandaloneVariable: Story = {
|
||||
const variableTag = await canvas.findByText('Creation date');
|
||||
expect(variableTag).toBeVisible();
|
||||
|
||||
const removeVariableButton = canvasElement.querySelector(
|
||||
'button .tabler-icon-x',
|
||||
);
|
||||
|
||||
if (!removeVariableButton) {
|
||||
throw new Error('Remove variable button not found');
|
||||
}
|
||||
const removeVariableButton = canvas.getByLabelText('Remove variable');
|
||||
|
||||
await Promise.all([
|
||||
userEvent.click(removeVariableButton),
|
||||
@@ -393,8 +386,12 @@ export const ClickingOutsideDoesNotResetInputState: Story = {
|
||||
onChange: fn(),
|
||||
},
|
||||
play: async ({ canvasElement, args }) => {
|
||||
if (!args.defaultValue) {
|
||||
throw new Error('This test requires a defaultValue');
|
||||
}
|
||||
|
||||
const defaultValueAsDisplayString = parseDateToString({
|
||||
date: new Date(args.defaultValue!),
|
||||
date: new Date(args.defaultValue),
|
||||
isDateTimeInput: true,
|
||||
userTimezone: undefined,
|
||||
});
|
||||
|
||||
+11
-7
@@ -1,4 +1,4 @@
|
||||
import { DateTime } from 'luxon';
|
||||
import { parseISO, isEqual, isAfter, isBefore } from 'date-fns';
|
||||
|
||||
import { type DateFilter } from '@/object-record//graphql/types/RecordGqlOperationFilter';
|
||||
|
||||
@@ -11,10 +11,10 @@ export const isMatchingDateFilter = ({
|
||||
}) => {
|
||||
switch (true) {
|
||||
case dateFilter.eq !== undefined: {
|
||||
return DateTime.fromISO(value).equals(DateTime.fromISO(dateFilter.eq));
|
||||
return isEqual(parseISO(value), parseISO(dateFilter.eq));
|
||||
}
|
||||
case dateFilter.neq !== undefined: {
|
||||
return !DateTime.fromISO(value).equals(DateTime.fromISO(dateFilter.neq));
|
||||
return !isEqual(parseISO(value), parseISO(dateFilter.neq));
|
||||
}
|
||||
case dateFilter.in !== undefined: {
|
||||
return dateFilter.in.includes(value);
|
||||
@@ -27,16 +27,20 @@ export const isMatchingDateFilter = ({
|
||||
}
|
||||
}
|
||||
case dateFilter.gt !== undefined: {
|
||||
return DateTime.fromISO(value) > DateTime.fromISO(dateFilter.gt);
|
||||
return isAfter(parseISO(value), parseISO(dateFilter.gt));
|
||||
}
|
||||
case dateFilter.gte !== undefined: {
|
||||
return DateTime.fromISO(value) >= DateTime.fromISO(dateFilter.gte);
|
||||
const valueDate = parseISO(value);
|
||||
const filterDate = parseISO(dateFilter.gte);
|
||||
return isAfter(valueDate, filterDate) || isEqual(valueDate, filterDate);
|
||||
}
|
||||
case dateFilter.lt !== undefined: {
|
||||
return DateTime.fromISO(value) < DateTime.fromISO(dateFilter.lt);
|
||||
return isBefore(parseISO(value), parseISO(dateFilter.lt));
|
||||
}
|
||||
case dateFilter.lte !== undefined: {
|
||||
return DateTime.fromISO(value) <= DateTime.fromISO(dateFilter.lte);
|
||||
const valueDate = parseISO(value);
|
||||
const filterDate = parseISO(dateFilter.lte);
|
||||
return isBefore(valueDate, filterDate) || isEqual(valueDate, filterDate);
|
||||
}
|
||||
default: {
|
||||
throw new Error(
|
||||
|
||||
+5
-13
@@ -1,4 +1,4 @@
|
||||
import { DateTime } from 'luxon';
|
||||
import { formatISO, parseISO, subDays, subHours } from 'date-fns';
|
||||
|
||||
import { type BlocklistItem } from '@/accounts/types/BlocklistItem';
|
||||
|
||||
@@ -8,9 +8,7 @@ export const mockedBlocklist: BlocklistItem[] = [
|
||||
handle: 'test1@twenty.com',
|
||||
workspaceMemberId: '1',
|
||||
createdAt:
|
||||
DateTime.fromISO('2023-04-26T10:12:42.33625+00:00')
|
||||
.minus({ hours: 2 })
|
||||
.toISO() ?? '',
|
||||
formatISO(subHours(parseISO('2023-04-26T10:12:42.33625+00:00'), 2)) ?? '',
|
||||
__typename: 'BlocklistItem',
|
||||
},
|
||||
{
|
||||
@@ -18,9 +16,7 @@ export const mockedBlocklist: BlocklistItem[] = [
|
||||
handle: 'test2@twenty.com',
|
||||
workspaceMemberId: '1',
|
||||
createdAt:
|
||||
DateTime.fromISO('2023-04-26T10:12:42.33625+00:00')
|
||||
.minus({ days: 2 })
|
||||
.toISO() ?? '',
|
||||
formatISO(subDays(parseISO('2023-04-26T10:12:42.33625+00:00'), 2)) ?? '',
|
||||
__typename: 'BlocklistItem',
|
||||
},
|
||||
{
|
||||
@@ -28,9 +24,7 @@ export const mockedBlocklist: BlocklistItem[] = [
|
||||
handle: 'test3@twenty.com',
|
||||
workspaceMemberId: '1',
|
||||
createdAt:
|
||||
DateTime.fromISO('2023-04-26T10:12:42.33625+00:00')
|
||||
.minus({ days: 3 })
|
||||
.toISO() ?? '',
|
||||
formatISO(subDays(parseISO('2023-04-26T10:12:42.33625+00:00'), 3)) ?? '',
|
||||
__typename: 'BlocklistItem',
|
||||
},
|
||||
{
|
||||
@@ -38,9 +32,7 @@ export const mockedBlocklist: BlocklistItem[] = [
|
||||
handle: '@twenty.com',
|
||||
workspaceMemberId: '1',
|
||||
createdAt:
|
||||
DateTime.fromISO('2023-04-26T10:12:42.33625+00:00')
|
||||
.minus({ days: 4 })
|
||||
.toISO() ?? '',
|
||||
formatISO(subDays(parseISO('2023-04-26T10:12:42.33625+00:00'), 4)) ?? '',
|
||||
__typename: 'BlocklistItem',
|
||||
},
|
||||
];
|
||||
|
||||
+5
-5
@@ -1,4 +1,4 @@
|
||||
import { DateTime } from 'luxon';
|
||||
import { differenceInDays, parseISO, addDays } from 'date-fns';
|
||||
|
||||
export const computeNewExpirationDate = (
|
||||
expiresAt: string | null | undefined,
|
||||
@@ -7,8 +7,8 @@ export const computeNewExpirationDate = (
|
||||
if (!expiresAt) {
|
||||
return null;
|
||||
}
|
||||
const days = DateTime.fromISO(expiresAt).diff(DateTime.fromISO(createdAt), [
|
||||
'days',
|
||||
]).days;
|
||||
return DateTime.utc().plus({ days }).toISO();
|
||||
const expirationDate = parseISO(expiresAt);
|
||||
const creationDate = parseISO(createdAt);
|
||||
const days = differenceInDays(expirationDate, creationDate);
|
||||
return addDays(new Date(), days).toISOString();
|
||||
};
|
||||
|
||||
@@ -1,16 +1,15 @@
|
||||
import { t } from '@lingui/core/macro';
|
||||
import { isNonEmptyString } from '@sniptt/guards';
|
||||
import { DateTime } from 'luxon';
|
||||
import { differenceInYears, parseISO } from 'date-fns';
|
||||
|
||||
import { NEVER_EXPIRE_DELTA_IN_YEARS } from '@/settings/developers/constants/NeverExpireDeltaInYears';
|
||||
import { beautifyDateDiff } from '~/utils/date-utils';
|
||||
|
||||
export const doesNeverExpire = (expiresAt: string) => {
|
||||
const dateDiff = DateTime.fromISO(expiresAt).diff(DateTime.now(), [
|
||||
'years',
|
||||
'days',
|
||||
]);
|
||||
return dateDiff.years > NEVER_EXPIRE_DELTA_IN_YEARS / 10;
|
||||
const expirationDate = parseISO(expiresAt);
|
||||
const now = new Date();
|
||||
const yearsDiff = differenceInYears(expirationDate, now);
|
||||
return yearsDiff > NEVER_EXPIRE_DELTA_IN_YEARS / 10;
|
||||
};
|
||||
|
||||
export const isExpired = (expiresAt: string | null) => {
|
||||
|
||||
+9
-12
@@ -1,5 +1,4 @@
|
||||
import styled from '@emotion/styled';
|
||||
import { DateTime } from 'luxon';
|
||||
import { useRecoilValue } from 'recoil';
|
||||
|
||||
import { currentWorkspaceMemberState } from '@/auth/states/currentWorkspaceMemberState';
|
||||
@@ -60,17 +59,15 @@ export const AbsoluteDatePickerHeader = ({
|
||||
const currentWorkspaceMember = useRecoilValue(currentWorkspaceMemberState);
|
||||
const userLocale = currentWorkspaceMember?.locale ?? SOURCE_LOCALE;
|
||||
|
||||
const endOfDayDateTimeInLocalTimezone = DateTime.now().set({
|
||||
day: date.getDate(),
|
||||
month: date.getMonth() + 1,
|
||||
year: date.getFullYear(),
|
||||
hour: 23,
|
||||
minute: 59,
|
||||
second: 59,
|
||||
millisecond: 999,
|
||||
});
|
||||
|
||||
const endOfDayInLocalTimezone = endOfDayDateTimeInLocalTimezone.toJSDate();
|
||||
const endOfDayInLocalTimezone = new Date(
|
||||
date.getFullYear(),
|
||||
date.getMonth(),
|
||||
date.getDate(),
|
||||
23,
|
||||
59,
|
||||
59,
|
||||
999,
|
||||
);
|
||||
|
||||
return (
|
||||
<>
|
||||
|
||||
+29
-61
@@ -1,6 +1,6 @@
|
||||
import styled from '@emotion/styled';
|
||||
import { DateTime } from 'luxon';
|
||||
import { lazy, Suspense, useContext, type ComponentType } from 'react';
|
||||
import { addMonths, setDate, setMonth, setYear, subMonths } from 'date-fns';
|
||||
import { lazy, Suspense, type ComponentType } from 'react';
|
||||
import type { ReactDatePickerProps as ReactDatePickerLibProps } from 'react-datepicker';
|
||||
import Skeleton, { SkeletonTheme } from 'react-loading-skeleton';
|
||||
|
||||
@@ -13,7 +13,6 @@ import { DateTimeInput } from '@/ui/input/components/internal/date/components/Da
|
||||
import { RelativeDatePickerHeader } from '@/ui/input/components/internal/date/components/RelativeDatePickerHeader';
|
||||
import { getHighlightedDates } from '@/ui/input/components/internal/date/utils/getHighlightedDates';
|
||||
import { useCloseDropdown } from '@/ui/layout/dropdown/hooks/useCloseDropdown';
|
||||
import { UserContext } from '@/users/contexts/UserContext';
|
||||
import { useTheme } from '@emotion/react';
|
||||
import { t } from '@lingui/core/macro';
|
||||
import 'react-datepicker/dist/react-datepicker.css';
|
||||
@@ -352,7 +351,6 @@ export const DateTimePicker = ({
|
||||
}: DateTimePickerProps) => {
|
||||
const internalDate = date ?? new Date();
|
||||
|
||||
const { timeZone } = useContext(UserContext);
|
||||
const theme = useTheme();
|
||||
|
||||
const { closeDropdown: closeDropdownMonthSelect } = useCloseDropdown();
|
||||
@@ -380,90 +378,60 @@ export const DateTimePicker = ({
|
||||
};
|
||||
|
||||
const handleAddMonth = () => {
|
||||
const dateParsed = DateTime.fromJSDate(internalDate, { zone: timeZone })
|
||||
.plus({ months: 1 })
|
||||
.toJSDate();
|
||||
|
||||
const dateParsed = addMonths(internalDate, 1);
|
||||
onChange?.(dateParsed);
|
||||
};
|
||||
|
||||
const handleSubtractMonth = () => {
|
||||
const dateParsed = DateTime.fromJSDate(internalDate, { zone: timeZone })
|
||||
.minus({ months: 1 })
|
||||
.toJSDate();
|
||||
|
||||
const dateParsed = subMonths(internalDate, 1);
|
||||
onChange?.(dateParsed);
|
||||
};
|
||||
|
||||
const handleChangeYear = (year: number) => {
|
||||
const dateParsed = DateTime.fromJSDate(internalDate, { zone: timeZone })
|
||||
.set({ year: year })
|
||||
.toJSDate();
|
||||
|
||||
const dateParsed = setYear(internalDate, year);
|
||||
onChange?.(dateParsed);
|
||||
};
|
||||
|
||||
const handleDateChange = (date: Date) => {
|
||||
const dateParsed = DateTime.fromJSDate(internalDate, {
|
||||
zone: isDateTimeInput ? timeZone : 'local',
|
||||
})
|
||||
.set({
|
||||
day: date.getDate(),
|
||||
month: date.getMonth() + 1,
|
||||
year: date.getFullYear(),
|
||||
})
|
||||
.toJSDate();
|
||||
let dateParsed = setYear(internalDate, date.getFullYear());
|
||||
dateParsed = setMonth(dateParsed, date.getMonth());
|
||||
dateParsed = setDate(dateParsed, date.getDate());
|
||||
|
||||
onChange?.(dateParsed);
|
||||
};
|
||||
|
||||
const handleDateSelect = (date: Date) => {
|
||||
const dateParsed = DateTime.fromJSDate(internalDate, {
|
||||
zone: isDateTimeInput ? timeZone : 'local',
|
||||
})
|
||||
.set({
|
||||
day: date.getDate(),
|
||||
month: date.getMonth() + 1,
|
||||
year: date.getFullYear(),
|
||||
})
|
||||
.toJSDate();
|
||||
let dateParsed = setYear(internalDate, date.getFullYear());
|
||||
dateParsed = setMonth(dateParsed, date.getMonth());
|
||||
dateParsed = setDate(dateParsed, date.getDate());
|
||||
|
||||
handleClose?.(dateParsed);
|
||||
};
|
||||
|
||||
const dateWithoutTime = DateTime.fromJSDate(internalDate)
|
||||
.toLocal()
|
||||
.set({
|
||||
day: internalDate.getUTCDate(),
|
||||
month: internalDate.getUTCMonth() + 1,
|
||||
year: internalDate.getUTCFullYear(),
|
||||
hour: 0,
|
||||
minute: 0,
|
||||
second: 0,
|
||||
millisecond: 0,
|
||||
})
|
||||
.toJSDate();
|
||||
|
||||
const dateParsed = DateTime.fromJSDate(internalDate, {
|
||||
zone: isDateTimeInput ? timeZone : 'local',
|
||||
});
|
||||
const dateWithoutTime = new Date(
|
||||
internalDate.getUTCFullYear(),
|
||||
internalDate.getUTCMonth(),
|
||||
internalDate.getUTCDate(),
|
||||
0,
|
||||
0,
|
||||
0,
|
||||
0,
|
||||
);
|
||||
|
||||
// We have to force a end of day on the computer local timezone with the given date
|
||||
// Because JS Date API cannot hold a timezone other than the local one
|
||||
// And if we don't do that workaround we will have problems when changing the date
|
||||
// Because the shown date will have 1 day more or less than the real date
|
||||
// Leading to bugs where we select 1st of January and it shows 31st of December for example
|
||||
const endOfDayDateTimeInLocalTimezone = DateTime.now().set({
|
||||
day: dateParsed.get('day'),
|
||||
month: dateParsed.get('month'),
|
||||
year: dateParsed.get('year'),
|
||||
hour: 23,
|
||||
minute: 59,
|
||||
second: 59,
|
||||
millisecond: 999,
|
||||
});
|
||||
|
||||
const endOfDayInLocalTimezone = endOfDayDateTimeInLocalTimezone.toJSDate();
|
||||
const endOfDayInLocalTimezone = new Date(
|
||||
internalDate.getFullYear(),
|
||||
internalDate.getMonth(),
|
||||
internalDate.getDate(),
|
||||
23,
|
||||
59,
|
||||
59,
|
||||
999,
|
||||
);
|
||||
|
||||
const dateToUse = isDateTimeInput ? endOfDayInLocalTimezone : dateWithoutTime;
|
||||
|
||||
|
||||
+13
-20
@@ -1,5 +1,7 @@
|
||||
import { DateFormat } from '@/localization/constants/DateFormat';
|
||||
import { DateTime } from 'luxon';
|
||||
import { format } from 'date-fns';
|
||||
import { formatInTimeZone } from 'date-fns-tz';
|
||||
import { isDefined } from 'twenty-shared/utils';
|
||||
import { getDateFormatString } from '~/utils/date-utils';
|
||||
|
||||
type ParseDateToStringArgs = {
|
||||
@@ -17,23 +19,14 @@ export const parseDateToString = ({
|
||||
}: ParseDateToStringArgs) => {
|
||||
const parsingFormat = getDateFormatString(dateFormat, isDateTimeInput);
|
||||
|
||||
const dateParsed = DateTime.fromJSDate(date, { zone: userTimezone });
|
||||
|
||||
const dateWithoutTime = DateTime.fromJSDate(date)
|
||||
.toLocal()
|
||||
.set({
|
||||
day: date.getUTCDate(),
|
||||
month: date.getUTCMonth() + 1,
|
||||
year: date.getUTCFullYear(),
|
||||
hour: 0,
|
||||
minute: 0,
|
||||
second: 0,
|
||||
millisecond: 0,
|
||||
});
|
||||
|
||||
const formattedDate = isDateTimeInput
|
||||
? dateParsed.setZone(userTimezone).toFormat(parsingFormat)
|
||||
: dateWithoutTime.toFormat(parsingFormat);
|
||||
|
||||
return formattedDate;
|
||||
if (isDateTimeInput && isDefined(userTimezone)) {
|
||||
return formatInTimeZone(date, userTimezone, parsingFormat);
|
||||
} else if (isDateTimeInput) {
|
||||
return format(date, parsingFormat);
|
||||
} else {
|
||||
const dateWithoutTime = new Date(
|
||||
Date.UTC(date.getUTCFullYear(), date.getUTCMonth(), date.getUTCDate()),
|
||||
);
|
||||
return format(dateWithoutTime, parsingFormat);
|
||||
}
|
||||
};
|
||||
|
||||
+10
-9
@@ -1,5 +1,7 @@
|
||||
import { type DateFormat } from '@/localization/constants/DateFormat';
|
||||
import { DateTime } from 'luxon';
|
||||
import { isValid, parse } from 'date-fns';
|
||||
import { zonedTimeToUtc } from 'date-fns-tz';
|
||||
import { isDefined } from 'twenty-shared/utils';
|
||||
import { getDateFormatString } from '~/utils/date-utils';
|
||||
|
||||
type ParseStringToDateArgs = {
|
||||
@@ -16,18 +18,17 @@ export const parseStringToDate = ({
|
||||
dateFormat,
|
||||
}: ParseStringToDateArgs) => {
|
||||
const parsingFormat = getDateFormatString(dateFormat, isDateTimeInput);
|
||||
const referenceDate = new Date();
|
||||
|
||||
const parsedDate = isDateTimeInput
|
||||
? DateTime.fromFormat(dateAsString, parsingFormat, { zone: userTimezone })
|
||||
: DateTime.fromFormat(dateAsString, parsingFormat, { zone: 'utc' });
|
||||
const parsedDate = parse(dateAsString, parsingFormat, referenceDate);
|
||||
|
||||
const isValid = parsedDate.isValid;
|
||||
|
||||
if (!isValid) {
|
||||
if (!isValid(parsedDate)) {
|
||||
return null;
|
||||
}
|
||||
|
||||
const jsDate = parsedDate.toJSDate();
|
||||
if (isDateTimeInput && isDefined(userTimezone)) {
|
||||
return zonedTimeToUtc(parsedDate, userTimezone);
|
||||
}
|
||||
|
||||
return jsDate;
|
||||
return parsedDate;
|
||||
};
|
||||
|
||||
+5
-4
@@ -1,4 +1,4 @@
|
||||
import { DateTime } from 'luxon';
|
||||
import { addDays } from 'date-fns';
|
||||
import { useState } from 'react';
|
||||
|
||||
import { currentWorkspaceState } from '@/auth/states/currentWorkspaceState';
|
||||
@@ -72,9 +72,10 @@ export const SettingsDevelopersApiKeysNew = () => {
|
||||
);
|
||||
|
||||
const handleSave = async () => {
|
||||
const expiresAt = DateTime.now()
|
||||
.plus({ days: formValues.expirationDate ?? 30 })
|
||||
.toString();
|
||||
const expiresAt = addDays(
|
||||
new Date(),
|
||||
formValues.expirationDate ?? 30,
|
||||
).toISOString();
|
||||
|
||||
const roleIdToUse = formValues.roleId;
|
||||
|
||||
|
||||
@@ -1,7 +1,6 @@
|
||||
import { i18n } from '@lingui/core';
|
||||
import { formatDistanceToNow } from 'date-fns';
|
||||
import { addDays, format, formatDistanceToNow, subDays } from 'date-fns';
|
||||
import { fr } from 'date-fns/locale';
|
||||
import { DateTime } from 'luxon';
|
||||
import { SOURCE_LOCALE } from 'twenty-shared/translations';
|
||||
import { messages as enMessages } from '~/locales/generated/en';
|
||||
import { messages as frMessages } from '~/locales/generated/fr-FR';
|
||||
@@ -19,10 +18,6 @@ import { logError } from '../logError';
|
||||
i18n.load(SOURCE_LOCALE, enMessages);
|
||||
i18n.activate(SOURCE_LOCALE);
|
||||
|
||||
const getLuxonLocale = () => {
|
||||
return SOURCE_LOCALE === 'en' ? 'en-US' : SOURCE_LOCALE;
|
||||
};
|
||||
|
||||
jest.mock('~/utils/logError');
|
||||
jest.useFakeTimers().setSystemTime(new Date('2024-01-01T00:00:00.000Z'));
|
||||
|
||||
@@ -30,20 +25,16 @@ describe('beautifyExactDateTime', () => {
|
||||
it('should return the date in the correct format with time', () => {
|
||||
const mockDate = '2023-01-01T12:13:24';
|
||||
const actualDate = new Date(mockDate);
|
||||
const expected = DateTime.fromJSDate(actualDate)
|
||||
.setLocale(getLuxonLocale())
|
||||
.toFormat('DD · T');
|
||||
const expected = format(actualDate, 'MMM d, yyyy · HH:mm');
|
||||
|
||||
const result = beautifyExactDateTime(mockDate);
|
||||
expect(result).toEqual(expected);
|
||||
});
|
||||
it('should return the time in the correct format for a datetime that is today', () => {
|
||||
const todayString = DateTime.local().toISODate();
|
||||
const todayString = '2024-01-01'; // Using the mocked date
|
||||
const mockDate = `${todayString}T12:13:24`;
|
||||
const actualDate = new Date(mockDate);
|
||||
const expected = DateTime.fromJSDate(actualDate)
|
||||
.setLocale(getLuxonLocale())
|
||||
.toFormat('T');
|
||||
const expected = format(actualDate, 'HH:mm');
|
||||
|
||||
const result = beautifyExactDateTime(mockDate);
|
||||
expect(result).toEqual(expected);
|
||||
@@ -54,15 +45,13 @@ describe('beautifyExactDate', () => {
|
||||
it('should return the past date in the correct format without time', () => {
|
||||
const mockDate = '2023-01-01T12:13:24';
|
||||
const actualDate = new Date(mockDate);
|
||||
const expected = DateTime.fromJSDate(actualDate)
|
||||
.setLocale(getLuxonLocale())
|
||||
.toFormat('DD');
|
||||
const expected = format(actualDate, 'MMM d, yyyy');
|
||||
|
||||
const result = beautifyExactDate(mockDate);
|
||||
expect(result).toEqual(expected);
|
||||
});
|
||||
it('should return "Today" if the date is today', () => {
|
||||
const todayString = DateTime.local().toISODate();
|
||||
const todayString = '2024-01-01'; // Using the mocked date
|
||||
const mockDate = `${todayString}T12:13:24`;
|
||||
const expected = 'Today';
|
||||
|
||||
@@ -162,25 +151,25 @@ describe('hasDatePassed', () => {
|
||||
});
|
||||
|
||||
it('should return true when passed past date', () => {
|
||||
const now = DateTime.local();
|
||||
const pastDate = now.minus({ day: 1 });
|
||||
const now = new Date();
|
||||
const pastDate = subDays(now, 1);
|
||||
|
||||
const result = hasDatePassed(pastDate.toJSDate());
|
||||
const result = hasDatePassed(pastDate);
|
||||
expect(result).toEqual(true);
|
||||
});
|
||||
|
||||
it('should return false when passed future date', () => {
|
||||
const now = DateTime.local();
|
||||
const futureDate = now.plus({ days: 1 });
|
||||
const now = new Date();
|
||||
const futureDate = addDays(now, 1);
|
||||
|
||||
const result = hasDatePassed(futureDate.toJSDate());
|
||||
const result = hasDatePassed(futureDate);
|
||||
expect(result).toEqual(false);
|
||||
});
|
||||
|
||||
it('should return false when passed current date', () => {
|
||||
const now = DateTime.local();
|
||||
const now = new Date();
|
||||
|
||||
const result = hasDatePassed(now.toJSDate());
|
||||
const result = hasDatePassed(now);
|
||||
expect(result).toEqual(false);
|
||||
});
|
||||
});
|
||||
|
||||
@@ -1,14 +1,18 @@
|
||||
import { isDate, isNumber, isString } from '@sniptt/guards';
|
||||
import {
|
||||
differenceInCalendarDays,
|
||||
differenceInDays,
|
||||
differenceInYears,
|
||||
format,
|
||||
formatDistance,
|
||||
formatDistanceToNow,
|
||||
isToday,
|
||||
isValid,
|
||||
parseISO,
|
||||
type Locale,
|
||||
} from 'date-fns';
|
||||
import { DateTime } from 'luxon';
|
||||
|
||||
import { DateFormat } from '@/localization/constants/DateFormat';
|
||||
import { SOURCE_LOCALE } from 'twenty-shared/translations';
|
||||
import { isDefined } from 'twenty-shared/utils';
|
||||
|
||||
import { CustomError } from '@/error-handler/CustomError';
|
||||
@@ -16,14 +20,10 @@ import { i18n } from '@lingui/core';
|
||||
import { plural, t } from '@lingui/core/macro';
|
||||
import { logError } from './logError';
|
||||
|
||||
const getLuxonLocale = () => {
|
||||
return SOURCE_LOCALE === 'en' ? 'en-US' : SOURCE_LOCALE;
|
||||
};
|
||||
export const parseDate = (dateToParse: Date | string | number): Date => {
|
||||
if (dateToParse === 'now') return new Date();
|
||||
|
||||
export const parseDate = (dateToParse: Date | string | number) => {
|
||||
if (dateToParse === 'now') return DateTime.fromJSDate(new Date());
|
||||
|
||||
let formattedDate: DateTime | null = null;
|
||||
let formattedDate: Date | null = null;
|
||||
|
||||
if (!dateToParse) {
|
||||
throw new CustomError(
|
||||
@@ -31,11 +31,11 @@ export const parseDate = (dateToParse: Date | string | number) => {
|
||||
'INVALID_DATE_FORMAT',
|
||||
);
|
||||
} else if (isString(dateToParse)) {
|
||||
formattedDate = DateTime.fromISO(dateToParse);
|
||||
formattedDate = parseISO(dateToParse);
|
||||
} else if (isDate(dateToParse)) {
|
||||
formattedDate = DateTime.fromJSDate(dateToParse);
|
||||
formattedDate = dateToParse;
|
||||
} else if (isNumber(dateToParse)) {
|
||||
formattedDate = DateTime.fromMillis(dateToParse);
|
||||
formattedDate = new Date(dateToParse);
|
||||
}
|
||||
|
||||
if (!formattedDate) {
|
||||
@@ -45,26 +45,23 @@ export const parseDate = (dateToParse: Date | string | number) => {
|
||||
);
|
||||
}
|
||||
|
||||
if (!formattedDate.isValid) {
|
||||
if (!isValid(formattedDate)) {
|
||||
throw new CustomError(
|
||||
`Invalid date passed to formatPastDate: "${dateToParse}"`,
|
||||
'INVALID_DATE_FORMAT',
|
||||
);
|
||||
}
|
||||
|
||||
return formattedDate.setLocale(getLuxonLocale());
|
||||
return formattedDate;
|
||||
};
|
||||
|
||||
const isSameDay = (a: DateTime, b: DateTime): boolean =>
|
||||
a.hasSame(b, 'day') && a.hasSame(b, 'month') && a.hasSame(b, 'year');
|
||||
|
||||
export const formatDate = (
|
||||
dateToFormat: Date | string | number,
|
||||
format: string,
|
||||
formatString: string,
|
||||
) => {
|
||||
try {
|
||||
const parsedDate = parseDate(dateToFormat);
|
||||
return parsedDate.toFormat(format);
|
||||
return format(parsedDate, formatString);
|
||||
} catch (error) {
|
||||
logError(error);
|
||||
return '';
|
||||
@@ -74,17 +71,19 @@ export const formatDate = (
|
||||
export const beautifyExactDateTime = (
|
||||
dateToBeautify: Date | string | number,
|
||||
) => {
|
||||
const isToday = isSameDay(parseDate(dateToBeautify), DateTime.local());
|
||||
const dateFormat = isToday ? 'T' : 'DD · T';
|
||||
const parsedDate = parseDate(dateToBeautify);
|
||||
const isTodayDate = isToday(parsedDate);
|
||||
const dateFormat = isTodayDate ? 'HH:mm' : 'MMM d, yyyy · HH:mm';
|
||||
return formatDate(dateToBeautify, dateFormat);
|
||||
};
|
||||
|
||||
export const beautifyExactDate = (dateToBeautify: Date | string | number) => {
|
||||
const isToday = isSameDay(parseDate(dateToBeautify), DateTime.local());
|
||||
if (isToday) {
|
||||
const parsedDate = parseDate(dateToBeautify);
|
||||
const isTodayDate = isToday(parsedDate);
|
||||
if (isTodayDate) {
|
||||
return t`Today`;
|
||||
}
|
||||
return formatDate(dateToBeautify, 'DD');
|
||||
return formatDate(dateToBeautify, 'MMM d, yyyy');
|
||||
};
|
||||
|
||||
export const beautifyPastDateRelativeToNow = (
|
||||
@@ -95,7 +94,7 @@ export const beautifyPastDateRelativeToNow = (
|
||||
const parsedDate = parseDate(pastDate);
|
||||
const now = new Date();
|
||||
const diffInSeconds = Math.abs(
|
||||
(now.getTime() - parsedDate.toJSDate().getTime()) / 1000,
|
||||
(now.getTime() - parsedDate.getTime()) / 1000,
|
||||
);
|
||||
|
||||
// For very recent times (less than 30 seconds), show "now"
|
||||
@@ -103,7 +102,7 @@ export const beautifyPastDateRelativeToNow = (
|
||||
return t`now`;
|
||||
}
|
||||
|
||||
return formatDistanceToNow(parsedDate.toJSDate(), {
|
||||
return formatDistanceToNow(parsedDate, {
|
||||
addSuffix: true,
|
||||
locale,
|
||||
includeSeconds: true,
|
||||
@@ -118,12 +117,7 @@ export const hasDatePassed = (date: Date | string | number) => {
|
||||
try {
|
||||
const parsedDate = parseDate(date);
|
||||
|
||||
return (
|
||||
differenceInCalendarDays(
|
||||
DateTime.local().toJSDate(),
|
||||
parsedDate.toJSDate(),
|
||||
) >= 1
|
||||
);
|
||||
return differenceInCalendarDays(new Date(), parsedDate) >= 1;
|
||||
} catch (error) {
|
||||
logError(error);
|
||||
return false;
|
||||
@@ -144,13 +138,16 @@ export const beautifyDateDiff = (
|
||||
}
|
||||
|
||||
// Manual implementation for complex cases or when locale is not available
|
||||
const dateDiff = DateTime.fromISO(date).diff(
|
||||
dateToCompareWith ? DateTime.fromISO(dateToCompareWith) : DateTime.now(),
|
||||
['years', 'days'],
|
||||
);
|
||||
const fromDate = parseISO(date);
|
||||
const toDate = dateToCompareWith ? parseISO(dateToCompareWith) : new Date();
|
||||
|
||||
const years = Math.floor(dateDiff.years);
|
||||
const days = Math.floor(dateDiff.days);
|
||||
const years = differenceInYears(fromDate, toDate);
|
||||
// Calculate remaining days after accounting for full years
|
||||
const startDateForDayCalculation = new Date(toDate);
|
||||
startDateForDayCalculation.setFullYear(
|
||||
startDateForDayCalculation.getFullYear() + years,
|
||||
);
|
||||
const days = differenceInDays(fromDate, startDateForDayCalculation);
|
||||
|
||||
let result = '';
|
||||
|
||||
@@ -179,7 +176,7 @@ export const beautifyDateDiff = (
|
||||
};
|
||||
|
||||
export const formatToHumanReadableDate = (date: Date | string) => {
|
||||
const parsedJSDate = parseDate(date).toJSDate();
|
||||
const parsedJSDate = parseDate(date);
|
||||
|
||||
return i18n.date(parsedJSDate, { dateStyle: 'medium' });
|
||||
};
|
||||
|
||||
@@ -4,7 +4,7 @@ export const formatToHumanReadableMonth = (
|
||||
date: Date | string,
|
||||
timeZone: string,
|
||||
) => {
|
||||
const parsedJSDate = parseDate(date).toJSDate();
|
||||
const parsedJSDate = parseDate(date);
|
||||
|
||||
return new Intl.DateTimeFormat(undefined, {
|
||||
month: 'short',
|
||||
@@ -16,7 +16,7 @@ export const formatToHumanReadableDay = (
|
||||
date: Date | string,
|
||||
timeZone: string,
|
||||
) => {
|
||||
const parsedJSDate = parseDate(date).toJSDate();
|
||||
const parsedJSDate = parseDate(date);
|
||||
|
||||
return new Intl.DateTimeFormat(undefined, {
|
||||
day: 'numeric',
|
||||
@@ -28,7 +28,7 @@ export const formatToHumanReadableTime = (
|
||||
date: Date | string,
|
||||
timeZone: string,
|
||||
) => {
|
||||
const parsedJSDate = parseDate(date).toJSDate();
|
||||
const parsedJSDate = parseDate(date);
|
||||
|
||||
return new Intl.DateTimeFormat(undefined, {
|
||||
hour: 'numeric',
|
||||
|
||||
@@ -16,5 +16,5 @@ export const sortFieldMetadataItem = (
|
||||
if (!dateA) return 1;
|
||||
if (!dateB) return -1;
|
||||
|
||||
return dateB.diff(dateA).milliseconds > 0 ? -1 : 1;
|
||||
return dateB.getTime() - dateA.getTime() > 0 ? -1 : 1;
|
||||
};
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
import { DateFormat } from '@/localization/constants/DateFormat';
|
||||
import { FieldDateDisplayFormat } from '@/object-record/record-field/ui/types/FieldMetadata';
|
||||
import { enUS } from 'date-fns/locale';
|
||||
import { DateTime } from 'luxon';
|
||||
import { subDays } from 'date-fns';
|
||||
import { formatDateString } from '~/utils/string/formatDateString';
|
||||
|
||||
describe('formatDateString', () => {
|
||||
@@ -31,7 +31,7 @@ describe('formatDateString', () => {
|
||||
});
|
||||
|
||||
it('should format date as relative when displayFormat is set to RELATIVE', () => {
|
||||
const mockDate = DateTime.now().minus({ days: 2 }).toISO();
|
||||
const mockDate = subDays(new Date(), 2).toISOString();
|
||||
const mockRelativeDate = '2 days ago';
|
||||
|
||||
const result = formatDateString({
|
||||
|
||||
@@ -1,8 +1,8 @@
|
||||
import { DateFormat } from '@/localization/constants/DateFormat';
|
||||
import { TimeFormat } from '@/localization/constants/TimeFormat';
|
||||
import { FieldDateDisplayFormat } from '@/object-record/record-field/ui/types/FieldMetadata';
|
||||
import { subDays } from 'date-fns';
|
||||
import { enUS } from 'date-fns/locale';
|
||||
import { DateTime } from 'luxon';
|
||||
import { formatDateTimeString } from '~/utils/string/formatDateTimeString';
|
||||
|
||||
describe('formatDateTimeString', () => {
|
||||
@@ -33,7 +33,7 @@ describe('formatDateTimeString', () => {
|
||||
});
|
||||
|
||||
it('should format date as relative when displayFormat is RELATIVE', () => {
|
||||
const mockDate = DateTime.now().minus({ days: 2 }).toISO();
|
||||
const mockDate = subDays(new Date(), 2).toISOString();
|
||||
const mockRelativeDate = '2 days ago';
|
||||
|
||||
const result = formatDateTimeString({
|
||||
|
||||
@@ -20557,13 +20557,6 @@ __metadata:
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
"@types/luxon@npm:^3.3.0":
|
||||
version: 3.4.2
|
||||
resolution: "@types/luxon@npm:3.4.2"
|
||||
checksum: 10c0/d835467de3daf7e17ba78b50bb5a14efd94272439ca067990d71332a54b311544459c69623eddd243b511b28d70194c9591a9ee8cf9c038962c965f991affd7e
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
"@types/luxon@npm:~3.3.0":
|
||||
version: 3.3.8
|
||||
resolution: "@types/luxon@npm:3.3.8"
|
||||
@@ -39460,7 +39453,7 @@ __metadata:
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
"luxon@npm:^3.2.1, luxon@npm:^3.3.0, luxon@npm:^3.6.1":
|
||||
"luxon@npm:^3.2.1, luxon@npm:^3.6.1":
|
||||
version: 3.6.1
|
||||
resolution: "luxon@npm:3.6.1"
|
||||
checksum: 10c0/906d57a9dc4d1de9383f2e9223e378c298607c1b4d17b6657b836a3cd120feb1c1de3b5d06d846a3417e1ca764de8476e8c23b3cd4083b5cdb870adcb06a99d5
|
||||
@@ -52062,7 +52055,6 @@ __metadata:
|
||||
"@types/lodash.pickby": "npm:^4.6.9"
|
||||
"@types/lodash.snakecase": "npm:^4.1.7"
|
||||
"@types/lodash.upperfirst": "npm:^4.3.7"
|
||||
"@types/luxon": "npm:^3.3.0"
|
||||
"@types/mailparser": "npm:^3.4.6"
|
||||
"@types/ms": "npm:^0.7.31"
|
||||
"@types/node": "npm:^24.0.0"
|
||||
@@ -52140,7 +52132,6 @@ __metadata:
|
||||
lodash.pickby: "npm:^4.6.0"
|
||||
lodash.snakecase: "npm:^4.1.1"
|
||||
lodash.upperfirst: "npm:^4.3.1"
|
||||
luxon: "npm:^3.3.0"
|
||||
microdiff: "npm:^1.3.2"
|
||||
moize: "npm:^6.1.6"
|
||||
msw: "npm:^2.0.11"
|
||||
|
||||
Reference in New Issue
Block a user