feat: Made booker layout settings more user friendly (#9397)

* Made booker layout settings more user friendly

* Fixed tyeps and default values in appearance form for booker layout

* Enable all layouts by default
This commit is contained in:
Jeroen Reumkens 2023-06-08 14:32:17 +02:00 committed by GitHub
parent 7f13fc58b9
commit 93fc036d2c
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
5 changed files with 40 additions and 17 deletions

View File

@ -1,5 +1,6 @@
import { useState } from "react";
import { Controller, useForm } from "react-hook-form";
import type { z } from "zod";
import { BookerLayoutSelector } from "@calcom/features/settings/BookerLayoutSelector";
import ThemeLabel from "@calcom/features/settings/ThemeLabel";
@ -9,6 +10,7 @@ import { checkWCAGContrastColor } from "@calcom/lib/getBrandColours";
import { useHasPaidPlan } from "@calcom/lib/hooks/useHasPaidPlan";
import { useLocale } from "@calcom/lib/hooks/useLocale";
import { validateBookerLayouts } from "@calcom/lib/validateBookerLayouts";
import type { userMetadata } from "@calcom/prisma/zod-utils";
import { trpc } from "@calcom/trpc/react";
import {
Alert,
@ -64,7 +66,7 @@ const AppearanceView = () => {
brandColor: user?.brandColor || "#292929",
darkBrandColor: user?.darkBrandColor || "#fafafa",
hideBranding: user?.hideBranding,
defaultBookerLayouts: user?.defaultBookerLayouts,
metadata: user?.metadata as z.infer<typeof userMetadata>,
},
});
@ -99,7 +101,7 @@ const AppearanceView = () => {
<Form
form={formMethods}
handleSubmit={(values) => {
const layoutError = validateBookerLayouts(values.defaultBookerLayouts || null);
const layoutError = validateBookerLayouts(values?.metadata?.defaultBookerLayouts || null);
if (layoutError) throw new Error(t(layoutError));
mutation.mutate({

View File

@ -5,9 +5,8 @@ import StickyBox from "react-sticky-box";
import { shallow } from "zustand/shallow";
import classNames from "@calcom/lib/classNames";
import { useLocale } from "@calcom/lib/hooks/useLocale";
import useMediaQuery from "@calcom/lib/hooks/useMediaQuery";
import { BookerLayouts, bookerLayoutOptions } from "@calcom/prisma/zod-utils";
import { BookerLayouts, defaultBookerLayoutSettings } from "@calcom/prisma/zod-utils";
import { AvailableTimeSlots } from "./components/AvailableTimeSlots";
import { BookEventForm } from "./components/BookEventForm";
@ -35,7 +34,6 @@ const BookerComponent = ({
rescheduleBooking,
hideBranding = false,
}: BookerProps) => {
const { t } = useLocale();
const isMobile = useMediaQuery("(max-width: 768px)");
const isTablet = useMediaQuery("(max-width: 1024px)");
const timeslotsRef = useRef<HTMLDivElement>(null);
@ -51,10 +49,7 @@ const BookerComponent = ({
shallow
);
const extraDays = layout === BookerLayouts.COLUMN_VIEW ? (isTablet ? 2 : 4) : 0;
const bookerLayouts = event.data?.profile?.bookerLayouts || {
defaultLayout: BookerLayouts.MONTH_VIEW,
enabledLayouts: bookerLayoutOptions,
};
const bookerLayouts = event.data?.profile?.bookerLayouts || defaultBookerLayoutSettings;
const animationScope = useBookerResizeAnimation(layout, bookerState);

View File

@ -7,7 +7,7 @@ import { Controller, useFormContext } from "react-hook-form";
import { useFlagMap } from "@calcom/features/flags/context/provider";
import { classNames } from "@calcom/lib";
import { useLocale } from "@calcom/lib/hooks/useLocale";
import { BookerLayouts } from "@calcom/prisma/zod-utils";
import { BookerLayouts, defaultBookerLayoutSettings } from "@calcom/prisma/zod-utils";
import { bookerLayoutOptions, type BookerLayoutSettings } from "@calcom/prisma/zod-utils";
import useMeQuery from "@calcom/trpc/react/hooks/useMeQuery";
import { Label, Checkbox, Button } from "@calcom/ui";
@ -85,19 +85,28 @@ const BookerLayoutFields = ({ settings, onChange, showUserSettings }: BookerLayo
// Converts the settings array into a boolean object, which can be used as form values.
const toggleValues: BookerLayoutState = bookerLayoutOptions.reduce((layouts, layout) => {
layouts[layout] = !shownSettings?.enabledLayouts
? true
? defaultBookerLayoutSettings.enabledLayouts.indexOf(layout) > -1
: shownSettings.enabledLayouts.indexOf(layout) > -1;
return layouts;
}, {} as BookerLayoutState);
const onLayoutToggleChange = useCallback(
(changedLayout: BookerLayouts, checked: boolean) => {
const newEnabledLayouts = Object.keys(toggleValues).filter((layout) => {
if (changedLayout === layout) return checked === true;
return toggleValues[layout as BookerLayouts] === true;
}) as BookerLayouts[];
const isDefaultLayoutToggledOff = newEnabledLayouts.indexOf(defaultLayout) === -1;
const firstEnabledLayout = newEnabledLayouts[0];
onChange({
enabledLayouts: Object.keys(toggleValues).filter((layout) => {
if (changedLayout === layout) return checked === true;
return toggleValues[layout as BookerLayouts] === true;
}) as BookerLayouts[],
defaultLayout,
enabledLayouts: newEnabledLayouts,
// If default layout is toggled off, we set the default layout to the first enabled layout
// if there's none enabled, we set it to month view.
defaultLayout: isDefaultLayoutToggledOff
? firstEnabledLayout || BookerLayouts.MONTH_VIEW
: defaultLayout,
});
},
[defaultLayout, onChange, toggleValues]
@ -149,6 +158,7 @@ const BookerLayoutFields = ({ settings, onChange, showUserSettings }: BookerLayo
))}
</div>
<div
hidden={Object.values(toggleValues).filter((value) => value === true).length <= 1}
className={classNames(
"transition-opacity",
disableFields && "pointer-events-none opacity-40",
@ -162,7 +172,8 @@ const BookerLayoutFields = ({ settings, onChange, showUserSettings }: BookerLayo
onValueChange={(layout: BookerLayouts) => onDefaultLayoutChange(layout)}>
{bookerLayoutOptions.map((layout) => (
<RadioGroup.Item
className="aria-checked:bg-emphasis hover:bg-subtle focus:bg-subtle w-full rounded-[4px] p-1 text-sm transition-colors"
className="aria-checked:bg-emphasis hover:[&:not(:disabled)]:bg-subtle focus:[&:not(:disabled)]:bg-subtle w-full rounded-[4px] p-1 text-sm transition-colors disabled:cursor-not-allowed disabled:opacity-40"
disabled={toggleValues[layout] === false}
key={layout}
value={layout}>
{t(`bookerlayout_${layout}`)}

View File

@ -55,6 +55,12 @@ export const bookerLayouts = z
})
.nullable();
export const defaultBookerLayoutSettings = {
defaultLayout: BookerLayouts.MONTH_VIEW,
// if the user has no explicit layouts set (not in user profile and not in event settings), all layouts are enabled.
enabledLayouts: bookerLayoutOptions,
};
export type BookerLayoutSettings = z.infer<typeof bookerLayouts>;
export const RequiresConfirmationThresholdUnits: z.ZodType<UnitTypeLongPlural> = z.enum(["hours", "minutes"]);

View File

@ -4,10 +4,12 @@ import type { NextApiResponse, GetServerSidePropsContext } from "next";
import stripe from "@calcom/app-store/stripepayment/lib/server";
import { getPremiumPlanProductId } from "@calcom/app-store/stripepayment/lib/utils";
import hasKeyInMetadata from "@calcom/lib/hasKeyInMetadata";
import { getTranslation } from "@calcom/lib/server";
import { checkUsername } from "@calcom/lib/server/checkUsername";
import { resizeBase64Image } from "@calcom/lib/server/resizeBase64Image";
import slugify from "@calcom/lib/slugify";
import { updateWebUser as syncServicesUpdateWebUser } from "@calcom/lib/sync/SyncServiceManager";
import { validateBookerLayouts } from "@calcom/lib/validateBookerLayouts";
import { prisma } from "@calcom/prisma";
import { userMetadata } from "@calcom/prisma/zod-utils";
import type { TrpcSessionUser } from "@calcom/trpc/server/trpc";
@ -32,6 +34,13 @@ export const updateProfileHandler = async ({ ctx, input }: UpdateProfileOptions)
};
let isPremiumUsername = false;
const layoutError = validateBookerLayouts(input?.metadata?.defaultBookerLayouts || null);
if (layoutError) {
const t = await getTranslation("en", "common");
throw new TRPCError({ code: "BAD_REQUEST", message: t(layoutError) });
}
if (input.username) {
const username = slugify(input.username);
// Only validate if we're changing usernames