Skip to content
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

fix: UI mismatch with form state #10651

Merged
merged 11 commits into from
Jan 24, 2024
10 changes: 6 additions & 4 deletions apps/web/components/eventtype/EventAdvancedTab.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -55,8 +55,8 @@ export const EventAdvancedTab = ({ eventType, team }: Pick<EventTypeSetupProps,
const { t } = useLocale();

const [showEventNameTip, setShowEventNameTip] = useState(false);
const [hashedLinkVisible, setHashedLinkVisible] = useState(!!eventType.hashedLink);
const [redirectUrlVisible, setRedirectUrlVisible] = useState(!!eventType.successRedirectUrl);
const [hashedLinkVisible, setHashedLinkVisible] = useState(!!formMethods.getValues("hashedLink"));
const [redirectUrlVisible, setRedirectUrlVisible] = useState(!!formMethods.getValues("successRedirectUrl"));
const [hashedUrl, setHashedUrl] = useState(eventType.hashedLink?.link);

const bookingFields: Prisma.JsonObject = {};
Expand All @@ -79,7 +79,9 @@ export const EventAdvancedTab = ({ eventType, team }: Pick<EventTypeSetupProps,
t,
};

const [requiresConfirmation, setRequiresConfirmation] = useState(eventType.requiresConfirmation);
const [requiresConfirmation, setRequiresConfirmation] = useState(
formMethods.getValues("requiresConfirmation")
);
const placeholderHashedLink = `${CAL_URL}/d/${hashedUrl}/${eventType.slug}`;
const seatsEnabled = formMethods.watch("seatsPerTimeSlotEnabled");
const noShowFeeEnabled = eventType.metadata?.apps?.stripe?.paymentOption === "HOLD";
Expand Down Expand Up @@ -203,7 +205,7 @@ export const EventAdvancedTab = ({ eventType, team }: Pick<EventTypeSetupProps,
<RequiresConfirmationController
eventType={eventType}
seatsEnabled={seatsEnabled}
metadata={eventType.metadata}
metadata={formMethods.getValues("metadata")}
Copy link
Member

@hariombalhara hariombalhara Dec 8, 2023

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I don't like the idea of sometimes getting the value from eventType and sometimes from formMethods. It makes the code complex for little gains.

Can we not just use formMethods everywhere. We could maybe do eventType = formMethods.getValues() and then we can keep on accessing values from eventType.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Going with formMethods only is what I will prefer too, will look into it.

Copy link
Contributor Author

@MehulZR MehulZR Dec 9, 2023

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

There are some cases when we need the original eventType prop alongside formMethods values.
For ex. having both the count of webhooks and other form values in the same component.

what we can do is instead of calling formMethods.getValues("some_property_name") everywhere, we just do formValues = formMethods.getValues() saving the execution calls and cleaning up things.
but formMethods.getValues isn't reactive (value change doesn't cause re-render & it doesn't subscribe to input changes), so for some cases we will still be using formMethods.watch().

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I have tested a bit with using the formValues strategy, as expected it is not reliable. We should use formMethods.getValues("some_prop")

requiresConfirmation={requiresConfirmation}
onRequiresConfirmation={setRequiresConfirmation}
/>
Expand Down
5 changes: 3 additions & 2 deletions apps/web/components/eventtype/EventLimitsTab.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -254,8 +254,9 @@ export const EventLimitsTab = ({ eventType }: Pick<EventTypeSetupProps, "eventTy
formMethods.setValue("slotInterval", val && (val.value || 0) > 0 ? val.value : null);
}}
defaultValue={
slotIntervalOptions.find((option) => option.value === eventType.slotInterval) ||
slotIntervalOptions[0]
slotIntervalOptions.find(
(option) => option.value === formMethods.getValues("slotInterval")
) || slotIntervalOptions[0]
}
options={slotIntervalOptions}
/>
Expand Down
4 changes: 2 additions & 2 deletions apps/web/components/eventtype/EventRecurringTab.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -10,8 +10,8 @@ export const EventRecurringTab = ({ eventType }: Pick<EventTypeSetupProps, "even
const requirePayment = paymentAppData.price > 0;

return (
<div className="">
<>
<RecurringEventController paymentEnabled={requirePayment} eventType={eventType} />
</div>
</>
);
};
10 changes: 7 additions & 3 deletions apps/web/components/eventtype/EventSetupTab.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -113,7 +113,9 @@ export const EventSetupTab = (
const { t } = useLocale();
const formMethods = useFormContext<FormValues>();
const { eventType, team, destinationCalendar } = props;
const [multipleDuration, setMultipleDuration] = useState(eventType.metadata?.multipleDuration);
const [multipleDuration, setMultipleDuration] = useState(
formMethods.getValues("metadata")?.multipleDuration
);
const orgBranding = useOrgBranding();
const seatsEnabled = formMethods.watch("seatsPerTimeSlotEnabled");

Expand Down Expand Up @@ -143,7 +145,7 @@ export const EventSetupTab = (
}>
>(multipleDurationOptions.filter((mdOpt) => multipleDuration?.includes(mdOpt.value)));
const [defaultDuration, setDefaultDuration] = useState(
selectedMultipleDuration.find((opt) => opt.value === eventType.length) ?? null
selectedMultipleDuration.find((opt) => opt.value === formMethods.getValues("length")) ?? null
);

const { isChildrenManagedEventType, isManagedEventType, shouldLockIndicator, shouldLockDisableProps } =
Expand Down Expand Up @@ -565,7 +567,7 @@ export const EventSetupTab = (
type="number"
{...lengthLockedProps}
label={t("duration")}
defaultValue={eventType.length ?? 15}
defaultValue={formMethods.getValues("length") ?? 15}
{...formMethods.register("length")}
addOnSuffix={<>{t("minutes")}</>}
min={1}
Expand All @@ -581,6 +583,8 @@ export const EventSetupTab = (
onCheckedChange={() => {
if (multipleDuration !== undefined) {
setMultipleDuration(undefined);
setSelectedMultipleDuration([]);
setDefaultDuration(null);
formMethods.setValue("metadata.multipleDuration", undefined);
formMethods.setValue("length", eventType.length);
} else {
Expand Down
47 changes: 33 additions & 14 deletions apps/web/components/eventtype/EventTypeSingleLayout.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -69,53 +69,63 @@ type Props = {
isUserOrganizationAdmin: boolean;
};

function getNavigation(props: {
type getNavigationProps = {
t: TFunction;
eventType: Props["eventType"];
length: number;
id: number;
multipleDuration?: EventTypeSetupProps["eventType"]["metadata"]["multipleDuration"];
enabledAppsNumber: number;
enabledWorkflowsNumber: number;
installedAppsNumber: number;
availability: AvailabilityOption | undefined;
}) {
const { eventType, t, enabledAppsNumber, installedAppsNumber, enabledWorkflowsNumber } = props;
const duration =
eventType.metadata?.multipleDuration?.map((duration) => ` ${duration}`) || eventType.length;
};

function getNavigation({
length,
id,
multipleDuration,
t,
enabledAppsNumber,
installedAppsNumber,
enabledWorkflowsNumber,
}: getNavigationProps) {
const duration = multipleDuration?.map((duration) => ` ${duration}`) || length;

return [
{
name: "event_setup_tab_title",
href: `/event-types/${eventType.id}?tabName=setup`,
href: `/event-types/${id}?tabName=setup`,
icon: LinkIcon,
info: `${duration} ${t("minute_timeUnit")}`, // TODO: Get this from props
},
{
name: "event_limit_tab_title",
href: `/event-types/${eventType.id}?tabName=limits`,
href: `/event-types/${id}?tabName=limits`,
icon: Clock,
info: `event_limit_tab_description`,
},
{
name: "event_advanced_tab_title",
href: `/event-types/${eventType.id}?tabName=advanced`,
href: `/event-types/${id}?tabName=advanced`,
icon: Sliders,
info: `event_advanced_tab_description`,
},
{
name: "recurring",
href: `/event-types/${eventType.id}?tabName=recurring`,
href: `/event-types/${id}?tabName=recurring`,
icon: Repeat,
info: `recurring_event_tab_description`,
},
{
name: "apps",
href: `/event-types/${eventType.id}?tabName=apps`,
href: `/event-types/${id}?tabName=apps`,
icon: Grid,
//TODO: Handle proper translation with count handling
info: `${installedAppsNumber} apps, ${enabledAppsNumber} ${t("active")}`,
},
{
name: "workflows",
href: `/event-types/${eventType.id}?tabName=workflows`,
href: `/event-types/${id}?tabName=workflows`,
icon: Zap,
info: `${enabledWorkflowsNumber} ${t("active")}`,
},
Expand Down Expand Up @@ -171,11 +181,17 @@ function EventTypeSingleLayout({
t("locked_fields_member_description")
);

const enabledWebhooks = formMethods.watch("enabledWebhooks");
const length = formMethods.watch("length");
const multipleDuration = formMethods.watch("metadata")?.multipleDuration;

MehulZR marked this conversation as resolved.
Show resolved Hide resolved
// Define tab navigation here
const EventTypeTabs = useMemo(() => {
const navigation = getNavigation({
t,
eventType,
length,
multipleDuration,
id: eventType.id,
enabledAppsNumber,
installedAppsNumber,
enabledWorkflowsNumber,
Expand Down Expand Up @@ -218,7 +234,7 @@ function EventTypeSingleLayout({
name: "webhooks",
href: `/event-types/${eventType.id}?tabName=webhooks`,
icon: TbWebhook,
info: `${eventType.webhooks.filter((webhook) => webhook.active).length} ${t("active")}`,
info: `${enabledWebhooks} ${t("active")}`,
});
}
return navigation;
Expand All @@ -232,6 +248,9 @@ function EventTypeSingleLayout({
isManagedEventType,
isChildrenManagedEventType,
team,
length,
multipleDuration,
enabledWebhooks,
formMethods,
]);

Expand Down
10 changes: 8 additions & 2 deletions apps/web/components/eventtype/EventWebhooksTab.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -2,8 +2,9 @@
import { Webhook as TbWebhook } from "lucide-react";
import { Trans } from "next-i18next";
import Link from "next/link";
import type { EventTypeSetupProps } from "pages/event-types/[type]";
import { useState } from "react";
import type { EventTypeSetupProps, FormValues } from "pages/event-types/[type]";
import { useEffect, useState } from "react";
import { useFormContext } from "react-hook-form";

import useLockedFieldsManager from "@calcom/features/ee/managed-event-types/hooks/useLockedFieldsManager";
import { WebhookForm } from "@calcom/features/webhooks/components";
Expand All @@ -23,6 +24,11 @@

const { data: webhooks } = trpc.viewer.webhook.list.useQuery({ eventTypeId: eventType.id });

const enabledWebhooks = webhooks?.filter((webhook) => webhook.active).length;
const formMethods = useFormContext<FormValues>();
useEffect(() => {
if (enabledWebhooks !== undefined) formMethods.setValue("enabledWebhooks", enabledWebhooks);
}, [enabledWebhooks]);

Check warning on line 31 in apps/web/components/eventtype/EventWebhooksTab.tsx

View workflow job for this annotation

GitHub Actions / ESLint Report Analysis

apps/web/components/eventtype/EventWebhooksTab.tsx#L31

[react-hooks/exhaustive-deps] React Hook useEffect has a missing dependency: 'formMethods'. Either include it or remove the dependency array.
MehulZR marked this conversation as resolved.
Show resolved Hide resolved
const { data: installedApps, isLoading } = trpc.viewer.integrations.useQuery({
variant: "other",
onlyInstalled: true,
Expand Down
5 changes: 2 additions & 3 deletions apps/web/components/eventtype/RecurringEventController.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -19,11 +19,10 @@ export default function RecurringEventController({
paymentEnabled,
}: RecurringEventControllerProps) {
const { t } = useLocale();
const formMethods = useFormContext<FormValues>();
const [recurringEventState, setRecurringEventState] = useState<RecurringEvent | null>(
eventType.recurringEvent
formMethods.getValues("recurringEvent")
);
const formMethods = useFormContext<FormValues>();

/* Just yearly-0, monthly-1 and weekly-2 */
const recurringEventFreqOptions = Object.entries(Frequency)
.filter(([key, value]) => isNaN(Number(key)) && Number(value) < 3)
Expand Down
9 changes: 7 additions & 2 deletions apps/web/pages/event-types/[type]/index.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -138,6 +138,7 @@ export type FormValues = {
availability?: AvailabilityOption;
bookerLayouts: BookerLayoutSettings;
multipleDurationEnabled: boolean;
enabledWebhooks: number;
};

export type CustomInputParsed = typeof customInputSchema._output;
Expand Down Expand Up @@ -265,9 +266,13 @@ const EventTypePage = (props: EventTypeSetupProps) => {
periodType: eventType.periodType,
periodCountCalendarDays: eventType.periodCountCalendarDays ? "1" : "0",
schedulingType: eventType.schedulingType,
requiresConfirmation: eventType.requiresConfirmation,
slotInterval: eventType.slotInterval,
minimumBookingNotice: eventType.minimumBookingNotice,
metadata,
hosts: eventType.hosts,
successRedirectUrl: eventType.successRedirectUrl,
enabledWebhooks: eventType.webhooks.filter((webhook) => webhook.active).length,
children: eventType.children.map((ch) => ({
...ch,
created: true,
Expand Down Expand Up @@ -481,7 +486,7 @@ const EventTypePage = (props: EventTypeSetupProps) => {
}

// eslint-disable-next-line @typescript-eslint/no-unused-vars
const { availability, ...rest } = input;
const { availability, enabledWebhooks, ...rest } = input;
updateMutation.mutate({
...rest,
length,
Expand Down Expand Up @@ -576,7 +581,7 @@ const EventTypePage = (props: EventTypeSetupProps) => {
}
}
// eslint-disable-next-line @typescript-eslint/no-unused-vars
const { availability, ...rest } = input;
const { availability, enabledWebhooks, ...rest } = input;
updateMutation.mutate({
...rest,
length,
Expand Down
Loading