Compare commits

...
Author SHA1 Message Date
Sonarly Claude Code 259f742189 fix: convert ISO datetime to plain date string before passing to DatePicker
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<string>` 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.
2026-03-26 14:56:24 +00:00
2 changed files with 8 additions and 3 deletions
@@ -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();
@@ -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();