-
Notifications
You must be signed in to change notification settings - Fork 12k
Recurring event fixes #3030
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Recurring event fixes #3030
Changes from all commits
de1a383
304cb1a
8bb95b4
57c8c81
7e5b0f4
47e086e
82c5e12
0ce0aaa
6574299
f3f5e32
15df6f1
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -13,11 +13,12 @@ import dayjs from "dayjs"; | |
| import { useRouter } from "next/router"; | ||
| import { useState } from "react"; | ||
| import { useMutation } from "react-query"; | ||
| import { Frequency as RRuleFrequency } from "rrule"; | ||
|
|
||
| import { parseRecurringEvent } from "@calcom/lib"; | ||
| import classNames from "@calcom/lib/classNames"; | ||
| import { useLocale } from "@calcom/lib/hooks/useLocale"; | ||
| import showToast from "@calcom/lib/notification"; | ||
| import { Frequency } from "@calcom/prisma/zod-utils"; | ||
| import Button from "@calcom/ui/Button"; | ||
| import { Dialog, DialogClose, DialogContent, DialogFooter, DialogHeader } from "@calcom/ui/Dialog"; | ||
| import { Tooltip } from "@calcom/ui/Tooltip"; | ||
|
|
@@ -196,7 +197,7 @@ function BookingListItem(booking: BookingItemProps) { | |
| [recurringStrings] = parseRecurringDates( | ||
| { | ||
| startDate: booking.startTime, | ||
| recurringEvent: booking.eventType.recurringEvent, | ||
| recurringEvent: parseRecurringEvent(booking.eventType.recurringEvent), | ||
| recurringCount: booking.recurringCount, | ||
| }, | ||
| i18n | ||
|
|
@@ -307,14 +308,10 @@ function BookingListItem(booking: BookingItemProps) { | |
| <RefreshIcon className="mr-1 -mt-1 inline-block h-4 w-4 text-gray-400" /> | ||
| {`${t("every_for_freq", { | ||
| freq: t( | ||
| `${RRuleFrequency[booking.eventType.recurringEvent.freq] | ||
| .toString() | ||
| .toLowerCase()}` | ||
| `${Frequency[booking.eventType.recurringEvent.freq].toString().toLowerCase()}` | ||
| ), | ||
| })} ${booking.recurringCount} ${t( | ||
| `${RRuleFrequency[booking.eventType.recurringEvent.freq] | ||
| .toString() | ||
| .toLowerCase()}`, | ||
| `${Frequency[booking.eventType.recurringEvent.freq].toString().toLowerCase()}`, | ||
|
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. This piece of very long and repeated string throughout the app will be refactored in an util as part of #2750 |
||
| { count: booking.recurringCount } | ||
| )}`} | ||
| </p> | ||
|
|
||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -1,17 +1,16 @@ | ||
| import { Collapsible, CollapsibleContent } from "@radix-ui/react-collapsible"; | ||
| import type { FormValues } from "pages/event-types/[type]"; | ||
| import { useState } from "react"; | ||
| import { UseFormReturn } from "react-hook-form"; | ||
| import { Frequency as RRuleFrequency } from "rrule"; | ||
|
|
||
| import { useLocale } from "@calcom/lib/hooks/useLocale"; | ||
| import { Frequency } from "@calcom/prisma/zod-utils"; | ||
| import { RecurringEvent } from "@calcom/types/Calendar"; | ||
| import { Alert } from "@calcom/ui/Alert"; | ||
|
|
||
| import Select from "@components/ui/form/Select"; | ||
|
|
||
| type RecurringEventControllerProps = { | ||
| recurringEvent: RecurringEvent; | ||
| recurringEvent: RecurringEvent | null; | ||
| formMethods: UseFormReturn<FormValues>; | ||
| paymentEnabled: boolean; | ||
| onRecurringEventDefined: (value: boolean) => void; | ||
|
|
@@ -24,20 +23,13 @@ export default function RecurringEventController({ | |
| onRecurringEventDefined, | ||
| }: RecurringEventControllerProps) { | ||
| const { t } = useLocale(); | ||
|
|
||
| const [recurringEventDefined, setRecurringEventDefined] = useState(recurringEvent?.count !== undefined); | ||
|
|
||
| const [recurringEventInterval, setRecurringEventInterval] = useState(recurringEvent?.interval || 1); | ||
| const [recurringEventFrequency, setRecurringEventFrequency] = useState( | ||
| recurringEvent?.freq || RRuleFrequency.WEEKLY | ||
| ); | ||
| const [recurringEventCount, setRecurringEventCount] = useState(recurringEvent?.count || 12); | ||
|
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Simplifying the logic to just rely on a state object |
||
| const [recurringEventState, setRecurringEventState] = useState<RecurringEvent | null>(recurringEvent); | ||
|
|
||
| /* Just yearly-0, monthly-1 and weekly-2 */ | ||
| const recurringEventFreqOptions = Object.entries(RRuleFrequency) | ||
| const recurringEventFreqOptions = Object.entries(Frequency) | ||
| .filter(([key, value]) => isNaN(Number(key)) && Number(value) < 3) | ||
| .map(([key, value]) => ({ | ||
| label: t(`${key.toString().toLowerCase()}`, { count: recurringEventInterval }), | ||
| label: t(`${key.toString().toLowerCase()}`, { count: recurringEventState?.interval }), | ||
| value: value.toString(), | ||
| })); | ||
|
|
||
|
|
@@ -55,27 +47,23 @@ export default function RecurringEventController({ | |
| <div className="flex h-5 items-center"> | ||
| <input | ||
| onChange={(event) => { | ||
| setRecurringEventDefined(event?.target.checked); | ||
| onRecurringEventDefined(event?.target.checked); | ||
| if (!event?.target.checked) { | ||
| formMethods.setValue("recurringEvent", {}); | ||
| formMethods.setValue("recurringEvent", null); | ||
| setRecurringEventState(null); | ||
| } else { | ||
| formMethods.setValue( | ||
| "recurringEvent", | ||
| recurringEventDefined | ||
| ? recurringEvent | ||
| : { | ||
| interval: 1, | ||
| count: 12, | ||
| freq: RRuleFrequency.WEEKLY, | ||
| } | ||
| ); | ||
| const newVal = recurringEvent || { | ||
| interval: 1, | ||
| count: 12, | ||
| freq: Frequency.WEEKLY, | ||
| }; | ||
| formMethods.setValue("recurringEvent", newVal); | ||
| setRecurringEventState(newVal); | ||
| } | ||
| recurringEvent = formMethods.getValues("recurringEvent"); | ||
| }} | ||
| type="checkbox" | ||
| className="text-primary-600 h-4 w-4 rounded border-gray-300" | ||
| defaultChecked={recurringEventDefined} | ||
| defaultChecked={recurringEventState !== null} | ||
| data-testid="recurring-event-check" | ||
| id="recurringEvent" | ||
| /> | ||
|
|
@@ -86,36 +74,37 @@ export default function RecurringEventController({ | |
| </label> | ||
| </div> | ||
| </div> | ||
| <Collapsible | ||
| open={recurringEventDefined} | ||
| data-testid="recurring-event-collapsible" | ||
| onOpenChange={() => setRecurringEventDefined(!recurringEventDefined)}> | ||
| <CollapsibleContent className="mt-4 text-sm"> | ||
| {recurringEventState && ( | ||
| <div data-testid="recurring-event-collapsible" className="mt-4 text-sm"> | ||
| <div className="flex items-center"> | ||
| <p className="mr-2 text-neutral-900">{t("repeats_every")}</p> | ||
| <input | ||
| type="number" | ||
| min="1" | ||
| max="20" | ||
| className="block w-16 rounded-sm border-gray-300 shadow-sm [appearance:textfield] ltr:mr-2 rtl:ml-2 sm:text-sm" | ||
| defaultValue={recurringEvent?.interval || 1} | ||
| defaultValue={recurringEventState.interval} | ||
| onChange={(event) => { | ||
| setRecurringEventInterval(parseInt(event?.target.value)); | ||
| recurringEvent.interval = parseInt(event?.target.value); | ||
| formMethods.setValue("recurringEvent", recurringEvent); | ||
| const newVal = { | ||
| ...recurringEventState, | ||
| interval: parseInt(event?.target.value), | ||
| }; | ||
| formMethods.setValue("recurringEvent", newVal); | ||
|
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
|
||
| setRecurringEventState(newVal); | ||
| }} | ||
| /> | ||
| <Select | ||
| options={recurringEventFreqOptions} | ||
| value={recurringEventFreqOptions[recurringEventFrequency]} | ||
| value={recurringEventFreqOptions[recurringEventState.freq]} | ||
| isSearchable={false} | ||
| className="w-18 block min-w-0 rounded-sm sm:text-sm" | ||
| onChange={(e) => { | ||
| if (e?.value) { | ||
| setRecurringEventFrequency(parseInt(e?.value)); | ||
| recurringEvent.freq = parseInt(e?.value); | ||
| formMethods.setValue("recurringEvent", recurringEvent); | ||
| } | ||
| onChange={(event) => { | ||
| const newVal = { | ||
| ...recurringEventState, | ||
| freq: parseInt(event?.value || `${Frequency.WEEKLY}`), | ||
| }; | ||
| formMethods.setValue("recurringEvent", newVal); | ||
| setRecurringEventState(newVal); | ||
| }} | ||
| /> | ||
| </div> | ||
|
|
@@ -126,21 +115,24 @@ export default function RecurringEventController({ | |
| min="1" | ||
| max="20" | ||
| className="block w-16 rounded-sm border-gray-300 shadow-sm [appearance:textfield] ltr:mr-2 rtl:ml-2 sm:text-sm" | ||
| defaultValue={recurringEvent?.count || 12} | ||
| defaultValue={recurringEventState.count} | ||
| onChange={(event) => { | ||
| setRecurringEventCount(parseInt(event?.target.value)); | ||
| recurringEvent.count = parseInt(event?.target.value); | ||
| formMethods.setValue("recurringEvent", recurringEvent); | ||
| const newVal = { | ||
| ...recurringEventState, | ||
| count: parseInt(event?.target.value), | ||
| }; | ||
| formMethods.setValue("recurringEvent", newVal); | ||
| setRecurringEventState(newVal); | ||
| }} | ||
| /> | ||
| <p className="mr-2 text-neutral-900"> | ||
| {t(`${RRuleFrequency[recurringEventFrequency].toString().toLowerCase()}`, { | ||
| count: recurringEventCount, | ||
| {t(`${Frequency[recurringEventState.freq].toString().toLowerCase()}`, { | ||
| count: recurringEventState.count, | ||
| })} | ||
| </p> | ||
| </div> | ||
| </CollapsibleContent> | ||
| </Collapsible> | ||
| </div> | ||
| )} | ||
| </> | ||
| )} | ||
| </div> | ||
|
|
||
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Runtime and type safety for an unknown object