From 9e8fa124b5851a78ac1f544e940e84a8175e1875 Mon Sep 17 00:00:00 2001 From: Baptiste Arnaud Date: Tue, 5 Sep 2023 10:34:56 +0200 Subject: [PATCH] :zap: (dateInput) Add format option and improve parsing Use date-fns for custom format and make sure dates are timezone independants Closes #533, closes #592 --- .../src/features/account/UserProvider.tsx | 1 - .../date/components/DateInputSettings.tsx | 61 +++++++++++-------- .../features/blocks/inputs/date/date.spec.ts | 12 +++- apps/docs/docs/editor/blocks/inputs/date.mdx | 14 +++++ apps/viewer/package.json | 7 ++- .../blocks/inputs/date/parseDateReply.ts | 35 +++++++---- apps/viewer/src/features/chat/chat.spec.ts | 1 - packages/embeds/js/package.json | 2 +- .../inputs/date/components/DateForm.tsx | 9 +-- .../inputs/date/utils/parseReadableDate.ts | 27 -------- packages/embeds/nextjs/package.json | 2 +- packages/embeds/react/package.json | 2 +- .../schemas/features/blocks/inputs/date.ts | 1 + pnpm-lock.yaml | 12 ++-- 14 files changed, 100 insertions(+), 86 deletions(-) delete mode 100644 packages/embeds/js/src/features/blocks/inputs/date/utils/parseReadableDate.ts diff --git a/apps/builder/src/features/account/UserProvider.tsx b/apps/builder/src/features/account/UserProvider.tsx index f02f27989..47bc533e5 100644 --- a/apps/builder/src/features/account/UserProvider.tsx +++ b/apps/builder/src/features/account/UserProvider.tsx @@ -44,7 +44,6 @@ export const UserProvider = ({ children }: { children: ReactNode }) => { | 'dark' | null if (currentColorScheme === user.preferredAppAppearance) return - console.log('SET') setColorMode(user.preferredAppAppearance) }, [setColorMode, user?.preferredAppAppearance]) diff --git a/apps/builder/src/features/blocks/inputs/date/components/DateInputSettings.tsx b/apps/builder/src/features/blocks/inputs/date/components/DateInputSettings.tsx index b3b22d68a..fa9a7f0ae 100644 --- a/apps/builder/src/features/blocks/inputs/date/components/DateInputSettings.tsx +++ b/apps/builder/src/features/blocks/inputs/date/components/DateInputSettings.tsx @@ -1,3 +1,4 @@ +import { SwitchWithRelatedSettings } from '@/components/SwitchWithRelatedSettings' import { TextInput } from '@/components/inputs' import { SwitchWithLabel } from '@/components/inputs/SwitchWithLabel' import { VariableSearchInput } from '@/components/inputs/VariableSearchInput' @@ -11,49 +12,57 @@ type Props = { } export const DateInputSettings = ({ options, onOptionsChange }: Props) => { - const handleFromChange = (from: string) => + const updateFromLabel = (from: string) => onOptionsChange({ ...options, labels: { ...options?.labels, from } }) - const handleToChange = (to: string) => + const updateToLabel = (to: string) => onOptionsChange({ ...options, labels: { ...options?.labels, to } }) - const handleButtonLabelChange = (button: string) => + const updateButtonLabel = (button: string) => onOptionsChange({ ...options, labels: { ...options?.labels, button } }) - const handleIsRangeChange = (isRange: boolean) => + const updateIsRange = (isRange: boolean) => onOptionsChange({ ...options, isRange }) - const handleHasTimeChange = (hasTime: boolean) => + const updateHasTime = (hasTime: boolean) => onOptionsChange({ ...options, hasTime }) - const handleVariableChange = (variable?: Variable) => + const updateVariable = (variable?: Variable) => onOptionsChange({ ...options, variableId: variable?.id }) + const updateFormat = (format: string) => { + if (format === '') return onOptionsChange({ ...options, format: undefined }) + onOptionsChange({ ...options, format }) + } return ( - + onCheckChange={updateIsRange} + > + + + - {options.isRange && ( - <> - - - - )} + @@ -61,7 +70,7 @@ export const DateInputSettings = ({ options, onOptionsChange }: Props) => { diff --git a/apps/builder/src/features/blocks/inputs/date/date.spec.ts b/apps/builder/src/features/blocks/inputs/date/date.spec.ts index fab9b90fb..deda473d2 100644 --- a/apps/builder/src/features/blocks/inputs/date/date.spec.ts +++ b/apps/builder/src/features/blocks/inputs/date/date.spec.ts @@ -49,7 +49,17 @@ test.describe('Date input block', () => { await page.locator('[data-testid="to-date"]').fill('2022-01-01T09:00') await page.getByRole('button', { name: 'Go' }).click() await expect( - page.locator('text="01/01/2021, 11:00 AM to 01/01/2022, 09:00 AM"') + page.locator('text="01/01/2021 11:00 to 01/01/2022 09:00"') + ).toBeVisible() + + await page.click(`text=Pick a date...`) + await page.getByPlaceholder('dd/MM/yyyy HH:mm').fill('dd.MM HH:mm') + await page.click('text=Restart') + await page.locator('[data-testid="from-date"]').fill('2023-01-01T11:00') + await page.locator('[data-testid="to-date"]').fill('2023-02-01T09:00') + await page.getByRole('button', { name: 'Go' }).click() + await expect( + page.locator('text="01.01 11:00 to 01.02 09:00"') ).toBeVisible() }) }) diff --git a/apps/docs/docs/editor/blocks/inputs/date.mdx b/apps/docs/docs/editor/blocks/inputs/date.mdx index a110002c8..c2d19b2b3 100644 --- a/apps/docs/docs/editor/blocks/inputs/date.mdx +++ b/apps/docs/docs/editor/blocks/inputs/date.mdx @@ -35,3 +35,17 @@ The input will use the native date picker depending on the device and browser us style={{ maxWidth: '500px' }} alt="Date native picker" /> + +## Format + +The `Format` setting lets you customize the picked date format. Under the hood, it is done using the [date-fns](https://date-fns.org/) library. You can use any of the [formatting tokens](https://date-fns.org/docs/format) supported by the library. + +Here are some examples: + +```text +yyyy-MM-dd +yyyy-MM-dd HH:mm:ss +dd/MM/yy +dd/MM/yyyy HH:mm:ss +d.MM.yy +``` diff --git a/apps/viewer/package.json b/apps/viewer/package.json index 49a6c766e..80b7cc028 100644 --- a/apps/viewer/package.json +++ b/apps/viewer/package.json @@ -16,11 +16,12 @@ "@trpc/server": "10.34.0", "@typebot.io/nextjs": "workspace:*", "@typebot.io/prisma": "workspace:*", - "ai": "2.1.32", "@udecode/plate-common": "^21.1.5", + "ai": "2.1.32", "bot-engine": "workspace:*", - "chrono-node": "^2.6.4", + "chrono-node": "2.6.6", "cors": "2.8.5", + "date-fns": "^2.30.0", "eventsource-parser": "^1.0.0", "google-spreadsheet": "4.0.2", "got": "12.6.0", @@ -54,10 +55,10 @@ "@types/react": "18.2.15", "@types/sanitize-html": "2.9.0", "dotenv-cli": "^7.2.1", - "next-runtime-env": "^1.6.2", "eslint": "8.44.0", "eslint-config-custom": "workspace:*", "google-auth-library": "8.9.0", + "next-runtime-env": "^1.6.2", "node-fetch": "3.3.1", "papaparse": "5.4.1", "superjson": "1.12.4", diff --git a/apps/viewer/src/features/blocks/inputs/date/parseDateReply.ts b/apps/viewer/src/features/blocks/inputs/date/parseDateReply.ts index 87849781f..c3e14da8c 100644 --- a/apps/viewer/src/features/blocks/inputs/date/parseDateReply.ts +++ b/apps/viewer/src/features/blocks/inputs/date/parseDateReply.ts @@ -1,6 +1,7 @@ import { ParsedReply } from '@/features/chat/types' import { DateInputBlock } from '@typebot.io/schemas' import { parse as chronoParse } from 'chrono-node' +import { format } from 'date-fns' export const parseDateReply = ( reply: string, @@ -8,21 +9,29 @@ export const parseDateReply = ( ): ParsedReply => { const parsedDate = chronoParse(reply) if (parsedDate.length === 0) return { status: 'fail' } - const formatOptions: Intl.DateTimeFormatOptions = { - day: '2-digit', - month: '2-digit', - year: 'numeric', - hour: block.options.hasTime ? '2-digit' : undefined, - minute: block.options.hasTime ? '2-digit' : undefined, - } - const startDate = parsedDate[0].start - .date() - .toLocaleString(undefined, formatOptions) - const endDate = parsedDate[0].end - ?.date() - .toLocaleString(undefined, formatOptions) + const formatString = + block.options.format ?? + (block.options.hasTime ? 'dd/MM/yyyy HH:mm' : 'dd/MM/yyyy') + + const detectedStartDate = parseDateWithNeutralTimezone( + parsedDate[0].start.date() + ) + const startDate = format(detectedStartDate, formatString) + + const detectedEndDate = parsedDate[0].end?.date() + ? parseDateWithNeutralTimezone(parsedDate[0].end?.date()) + : undefined + const endDate = detectedEndDate + ? format(detectedEndDate, formatString) + : undefined + + if (block.options.isRange && !endDate) return { status: 'fail' } + return { status: 'success', reply: block.options.isRange ? `${startDate} to ${endDate}` : startDate, } } + +const parseDateWithNeutralTimezone = (date: Date) => + new Date(date.valueOf() + date.getTimezoneOffset() * 60 * 1000) diff --git a/apps/viewer/src/features/chat/chat.spec.ts b/apps/viewer/src/features/chat/chat.spec.ts index 83c9434fb..99f034c7e 100644 --- a/apps/viewer/src/features/chat/chat.spec.ts +++ b/apps/viewer/src/features/chat/chat.spec.ts @@ -142,7 +142,6 @@ test('API chat execution should work on published bot', async ({ request }) => { data: { message: '8', sessionId: chatSessionId }, }) ).json() - console.log(messages, input) expect(messages[0].content.richText).toStrictEqual([ { children: [{ text: "I'm gonna shoot multiple inputs now..." }], diff --git a/packages/embeds/js/package.json b/packages/embeds/js/package.json index 7643ec0f6..33c57e108 100644 --- a/packages/embeds/js/package.json +++ b/packages/embeds/js/package.json @@ -1,6 +1,6 @@ { "name": "@typebot.io/js", - "version": "0.1.23", + "version": "0.1.24", "description": "Javascript library to display typebots on your website", "type": "module", "main": "dist/index.js", diff --git a/packages/embeds/js/src/features/blocks/inputs/date/components/DateForm.tsx b/packages/embeds/js/src/features/blocks/inputs/date/components/DateForm.tsx index 265694bdf..adb29dcf1 100644 --- a/packages/embeds/js/src/features/blocks/inputs/date/components/DateForm.tsx +++ b/packages/embeds/js/src/features/blocks/inputs/date/components/DateForm.tsx @@ -2,7 +2,6 @@ import { SendButton } from '@/components/SendButton' import { InputSubmitContent } from '@/types' import type { DateInputOptions } from '@typebot.io/schemas' import { createSignal } from 'solid-js' -import { parseReadableDate } from '../utils/parseReadableDate' type Props = { onSubmit: (inputValue: InputSubmitContent) => void @@ -24,11 +23,9 @@ export const DateForm = (props: Props) => { if (inputValues().from === '' && inputValues().to === '') return e.preventDefault() props.onSubmit({ - value: parseReadableDate({ - ...inputValues(), - hasTime: props.options?.hasTime, - isRange: props.options?.isRange, - }), + value: `${inputValues().from}${ + props.options?.isRange ? ` to ${inputValues().to}` : '' + }`, }) }} > diff --git a/packages/embeds/js/src/features/blocks/inputs/date/utils/parseReadableDate.ts b/packages/embeds/js/src/features/blocks/inputs/date/utils/parseReadableDate.ts deleted file mode 100644 index fc04dd293..000000000 --- a/packages/embeds/js/src/features/blocks/inputs/date/utils/parseReadableDate.ts +++ /dev/null @@ -1,27 +0,0 @@ -export const parseReadableDate = ({ - from, - to, - hasTime, - isRange, -}: { - from: string - to: string - hasTime?: boolean - isRange?: boolean -}) => { - const currentLocale = window.navigator.language - const formatOptions: Intl.DateTimeFormatOptions = { - day: '2-digit', - month: '2-digit', - year: 'numeric', - hour: hasTime ? '2-digit' : undefined, - minute: hasTime ? '2-digit' : undefined, - } - const fromReadable = new Date( - hasTime ? from : from.replace(/-/g, '/') - ).toLocaleString(currentLocale, formatOptions) - const toReadable = new Date( - hasTime ? to : to.replace(/-/g, '/') - ).toLocaleString(currentLocale, formatOptions) - return `${fromReadable}${isRange ? ` to ${toReadable}` : ''}` -} diff --git a/packages/embeds/nextjs/package.json b/packages/embeds/nextjs/package.json index 3ee9b0afe..ee22b346b 100644 --- a/packages/embeds/nextjs/package.json +++ b/packages/embeds/nextjs/package.json @@ -1,6 +1,6 @@ { "name": "@typebot.io/nextjs", - "version": "0.1.23", + "version": "0.1.24", "description": "Convenient library to display typebots on your Next.js website", "main": "dist/index.js", "types": "dist/index.d.ts", diff --git a/packages/embeds/react/package.json b/packages/embeds/react/package.json index a7c84f32e..99e513343 100644 --- a/packages/embeds/react/package.json +++ b/packages/embeds/react/package.json @@ -1,6 +1,6 @@ { "name": "@typebot.io/react", - "version": "0.1.23", + "version": "0.1.24", "description": "Convenient library to display typebots on your React app", "main": "dist/index.js", "types": "dist/index.d.ts", diff --git a/packages/schemas/features/blocks/inputs/date.ts b/packages/schemas/features/blocks/inputs/date.ts index b56377b89..01a7e713b 100644 --- a/packages/schemas/features/blocks/inputs/date.ts +++ b/packages/schemas/features/blocks/inputs/date.ts @@ -12,6 +12,7 @@ export const dateInputOptionsSchema = optionBaseSchema.merge( }), hasTime: z.boolean(), isRange: z.boolean(), + format: z.string().optional(), }) ) diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index 8fd935342..cc2aef155 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -543,11 +543,14 @@ importers: specifier: workspace:* version: link:../../packages/deprecated/bot-engine chrono-node: - specifier: ^2.6.4 - version: 2.6.4 + specifier: 2.6.6 + version: 2.6.6 cors: specifier: 2.8.5 version: 2.8.5 + date-fns: + specifier: ^2.30.0 + version: 2.30.0 eventsource-parser: specifier: ^1.0.0 version: 1.0.0 @@ -11206,8 +11209,8 @@ packages: resolution: {integrity: sha512-p3KULyQg4S7NIHixdwbGX+nFHkoBiA4YQmyWtjb8XngSKV124nJmRysgAeujbUVb15vh+RvFUfCPqU7rXk+hZg==} engines: {node: '>=6.0'} - /chrono-node@2.6.4: - resolution: {integrity: sha512-weCpfagfISvUMleIIqCi12AL9iQYn1ybX/6RB9qolynvHNvYlfdJete51uyB8TmwDTgEeKFEq0I5p/SHhOfhsw==} + /chrono-node@2.6.6: + resolution: {integrity: sha512-RObSvo49wRL/ek6U4lMuZjmCi//gLM2GsHBMauIw/50fBbP6To3F99vn88IRL9w4qC39tFRnJZc6uiGrOi1oGw==} engines: {node: ^12.20.0 || ^14.13.1 || >=16.0.0} dependencies: dayjs: 1.11.9 @@ -12063,7 +12066,6 @@ packages: engines: {node: '>=0.11'} dependencies: '@babel/runtime': 7.22.15 - dev: true /dayjs@1.11.9: resolution: {integrity: sha512-QvzAURSbQ0pKdIye2txOzNaHmxtUBXerpY0FJsFXUMKbIZeFm5ht1LS/jFsrncjnmtv8HsG0W2g6c0zUjZWmpA==}