From 259f742189a8fc23dde4f9cb48d07ba95f741b6d Mon Sep 17 00:00:00 2001 From: Sonarly Claude Code Date: Thu, 26 Mar 2026 14:56:24 +0000 Subject: [PATCH] fix: convert ISO datetime to plain date string before passing to DatePicker MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit https://sonarly.com/issue/18717?type=bug The DatePicker component crashes with a RangeError when it receives a full ISO datetime string (e.g. `2025-10-02T17:32:55.570Z`) because `Temporal.PlainDate.from()` only accepts date-only strings like `2025-10-02`. Fix: Replaced `Temporal.PlainDate.from(plainDateString)` with the team's existing `parseToPlainDateOrThrow(plainDateString)` utility from `twenty-shared/utils` at two sites: 1. **`DatePicker.tsx` (line 359)** — The primary crash site from the Sentry error. When a user clicks a date cell on the opportunities page, the raw field value from the record store is a full ISO datetime string (e.g. `2025-10-02T17:32:55.570Z`). `Temporal.PlainDate.from()` cannot parse this format and throws `RangeError: Cannot parse`. `parseToPlainDateOrThrow` first attempts `Temporal.Instant.from().toZonedDateTimeISO('UTC').toPlainDate()` (handles ISO datetime), then falls back to `Temporal.PlainDate.from()` (handles plain dates). 2. **`DatePickerWithoutCalendar.tsx` (line 326)** — Same defensive fix for the calendar top bar's date picker, which accepts `Nullable` and could receive a datetime string through the same data path. Both changes import `parseToPlainDateOrThrow` alongside the existing imports from `twenty-shared/utils`. The return type remains `Temporal.PlainDate`, so all downstream code is unaffected. --- .../components/internal/date/components/DatePicker.tsx | 3 ++- .../date/components/DatePickerWithoutCalendar.tsx | 8 ++++++-- 2 files changed, 8 insertions(+), 3 deletions(-) diff --git a/packages/twenty-front/src/modules/ui/input/components/internal/date/components/DatePicker.tsx b/packages/twenty-front/src/modules/ui/input/components/internal/date/components/DatePicker.tsx index 08e824802c6..4b12b878977 100644 --- a/packages/twenty-front/src/modules/ui/input/components/internal/date/components/DatePicker.tsx +++ b/packages/twenty-front/src/modules/ui/input/components/internal/date/components/DatePicker.tsx @@ -21,6 +21,7 @@ import { Temporal } from 'temporal-polyfill'; import { type Nullable } from 'twenty-shared/types'; import { isDefined, + parseToPlainDateOrThrow, turnJSDateToPlainDate, type RelativeDateFilter, } from 'twenty-shared/utils'; @@ -356,7 +357,7 @@ export const DatePicker = ({ }: DatePickerProps) => { const { theme } = useContext(ThemeContext); const plainDate = isDefined(plainDateString) - ? Temporal.PlainDate.from(plainDateString) + ? parseToPlainDateOrThrow(plainDateString) : Temporal.Now.plainDateISO(); const { userTimezone } = useUserTimezone(); diff --git a/packages/twenty-front/src/modules/ui/input/components/internal/date/components/DatePickerWithoutCalendar.tsx b/packages/twenty-front/src/modules/ui/input/components/internal/date/components/DatePickerWithoutCalendar.tsx index fa13744512d..7dcf4727756 100644 --- a/packages/twenty-front/src/modules/ui/input/components/internal/date/components/DatePickerWithoutCalendar.tsx +++ b/packages/twenty-front/src/modules/ui/input/components/internal/date/components/DatePickerWithoutCalendar.tsx @@ -15,7 +15,11 @@ import 'react-datepicker/dist/react-datepicker.css'; import { useAtomStateValue } from '@/ui/utilities/state/jotai/hooks/useAtomStateValue'; import { Temporal } from 'temporal-polyfill'; import { type Nullable } from 'twenty-shared/types'; -import { isDefined, turnJSDateToPlainDate } from 'twenty-shared/utils'; +import { + isDefined, + parseToPlainDateOrThrow, + turnJSDateToPlainDate, +} from 'twenty-shared/utils'; import { ThemeContext, themeCssVariables } from 'twenty-ui/theme-constants'; export const MONTH_AND_YEAR_DROPDOWN_MONTH_SELECT_ID = @@ -323,7 +327,7 @@ export const DatePickerWithoutCalendar = ({ onClose, }: DatePickerWithoutCalendarProps) => { const { theme } = useContext(ThemeContext); - const plainDate = isDefined(date) ? Temporal.PlainDate.from(date) : null; + const plainDate = isDefined(date) ? parseToPlainDateOrThrow(date) : null; const { closeDropdown: closeDropdownMonthSelect } = useCloseDropdown(); const { closeDropdown: closeDropdownYearSelect } = useCloseDropdown();