refactor: availabilitySettings atoms web wrapper and translations (#14167)

* refactor: availabilitySettings atoms web wrapper and translations

* fixup! refactor: availabilitySettings atoms web wrapper and translations
This commit is contained in:
Morgan
2024-03-21 15:26:33 +02:00
committed by GitHub
parent 0fc79f93de
commit e19e829ff7
5 changed files with 131 additions and 211 deletions
+1 -116
View File
@@ -1,124 +1,9 @@
import { useRouter } from "next/navigation";
import { useForm } from "react-hook-form";
import { withErrorFromUnknown } from "@calcom/lib/getClientErrorFromUnknown";
import { useCompatSearchParams } from "@calcom/lib/hooks/useCompatSearchParams";
import { useLocale } from "@calcom/lib/hooks/useLocale";
import { HttpError } from "@calcom/lib/http-error";
import { AvailabilitySettings } from "@calcom/platform-atoms";
import { trpc } from "@calcom/trpc/react";
import useMeQuery from "@calcom/trpc/react/hooks/useMeQuery";
import type { Schedule as ScheduleType, TimeRange } from "@calcom/types/schedule";
import { showToast } from "@calcom/ui";
import PageWrapper from "@components/PageWrapper";
type AvailabilityFormValues = {
name: string;
schedule: ScheduleType;
dateOverrides: { ranges: TimeRange[] }[];
timeZone: string;
isDefault: boolean;
};
export default function Availability() {
const searchParams = useCompatSearchParams();
const { t } = useLocale();
const router = useRouter();
const utils = trpc.useContext();
const me = useMeQuery();
const scheduleId = searchParams?.get("schedule") ? Number(searchParams.get("schedule")) : -1;
const fromEventType = searchParams?.get("fromEventType");
const { timeFormat } = me.data || { timeFormat: null };
const { data: schedule, isPending } = trpc.viewer.availability.schedule.get.useQuery(
{ scheduleId },
{
enabled: !!scheduleId,
}
);
const form = useForm<AvailabilityFormValues>({
values: schedule && {
...schedule,
schedule: schedule?.availability || [],
},
});
const updateMutation = trpc.viewer.availability.schedule.update.useMutation({
onSuccess: async ({ prevDefaultId, currentDefaultId, ...data }) => {
if (prevDefaultId && currentDefaultId) {
// check weather the default schedule has been changed by comparing previous default schedule id and current default schedule id.
if (prevDefaultId !== currentDefaultId) {
// if not equal, invalidate previous default schedule id and refetch previous default schedule id.
utils.viewer.availability.schedule.get.invalidate({ scheduleId: prevDefaultId });
utils.viewer.availability.schedule.get.refetch({ scheduleId: prevDefaultId });
}
}
utils.viewer.availability.schedule.get.invalidate({ scheduleId: data.schedule.id });
utils.viewer.availability.list.invalidate();
showToast(
t("availability_updated_successfully", {
scheduleName: data.schedule.name,
}),
"success"
);
},
onError: (err) => {
if (err instanceof HttpError) {
const message = `${err.statusCode}: ${err.message}`;
showToast(message, "error");
}
},
});
const deleteMutation = trpc.viewer.availability.schedule.delete.useMutation({
onError: withErrorFromUnknown((err) => {
showToast(err.message, "error");
}),
onSettled: () => {
utils.viewer.availability.list.invalidate();
},
onSuccess: () => {
showToast(t("schedule_deleted_successfully"), "success");
router.push("/availability");
},
});
return (
<AvailabilitySettings
schedule={
schedule
? {
name: schedule.name,
id: schedule.id,
isLastSchedule: schedule.isLastSchedule,
isDefault: schedule.isDefault,
workingHours: schedule.workingHours,
dateOverrides: schedule.dateOverrides,
timeZone: schedule.timeZone,
availability: schedule.availability || [],
schedule: schedule.schedule || [],
}
: undefined
}
isDeleting={deleteMutation.isPending}
isLoading={isPending}
isSaving={updateMutation.isPending}
timeFormat={timeFormat}
weekStart={me.data?.weekStart || "Sunday"}
backPath={fromEventType ? true : "/availability"}
handleDelete={() => {
scheduleId && deleteMutation.mutate({ scheduleId });
}}
handleSubmit={async ({ dateOverrides, ...values }) => {
scheduleId &&
updateMutation.mutate({
scheduleId,
dateOverrides: dateOverrides.flatMap((override) => override.ranges),
...values,
});
}}
/>
);
return <AvailabilitySettings />;
}
Availability.PageWrapper = PageWrapper;
@@ -51,24 +51,6 @@ export type Schedule = {
days: number[];
};
export type TranslationsType = {
save: string;
timezone: string;
availability: string;
set_to_default: string;
delete: string;
delete_schedule: string;
availability_settings: string;
launch_troubleshooter: string;
requires_at_least_one_schedule: string;
delete_schedule_description: string;
name: string;
something_doesnt_look_right: string;
add_an_override: string;
add_time_availability: string;
copy_times_to: string;
};
export type CustomClassNames = {
containerClassName?: string;
ctaClassName?: string;
@@ -107,7 +89,6 @@ type AvailabilitySettingsProps = {
backPath: string | boolean;
handleSubmit: (data: AvailabilityFormValues) => Promise<void>;
isPlatform?: boolean;
translations?: Partial<TranslationsType>;
customClassNames?: CustomClassNames;
};
@@ -252,7 +233,6 @@ export function AvailabilitySettings({
backPath,
handleSubmit,
isPlatform = false,
translations,
customClassNames,
}: AvailabilitySettingsProps) {
const [openSidebar, setOpenSidebar] = useState(false);
@@ -275,11 +255,7 @@ export function AvailabilitySettings({
<Shell
headerClassName={cn(customClassNames?.containerClassName)}
backPath={backPath}
title={
schedule?.name
? `${schedule.name} | ${translations?.availability ?? t("availability")}`
: translations?.availability ?? t("availability")
}
title={schedule?.name ? `${schedule.name} | t("availability")}` : t("availability")}
heading={
<Controller
control={form.control}
@@ -319,7 +295,7 @@ export function AvailabilitySettings({
className="mt-2 cursor-pointer self-center pe-2"
loadingClassName="me-4"
waitForTranslation={!isPlatform}>
{translations?.set_to_default ?? t("set_to_default")}
{t("set_to_default")}
</Skeleton>
<Controller
control={form.control}
@@ -361,9 +337,7 @@ export function AvailabilitySettings({
)}>
<div className="flex flex-row items-center pt-5">
<Button StartIcon={ArrowLeft} color="minimal" onClick={() => setOpenSidebar(false)} />
<p className="-ml-2">
{translations?.availability_settings ?? t("availability_settings")}
</p>
<p className="-ml-2">{t("availability_settings")}</p>
<DeleteDialogButton
buttonClassName="ml-16 inline"
disabled={schedule?.isLastSchedule}
@@ -377,7 +351,7 @@ export function AvailabilitySettings({
</div>
<div className="flex flex-col px-2 py-2">
<Skeleton as={Label} waitForTranslation={!isPlatform}>
{translations?.name ?? t("name")}
{t("name")}
</Skeleton>
<Controller
control={form.control}
@@ -396,7 +370,7 @@ export function AvailabilitySettings({
htmlFor="hiddenSwitch"
className="mt-2 cursor-pointer self-center pr-2 sm:inline"
waitForTranslation={!isPlatform}>
{translations?.set_to_default ?? t("set_to_default")}
{t("set_to_default")}
</Skeleton>
<Controller
control={form.control}
@@ -420,7 +394,7 @@ export function AvailabilitySettings({
htmlFor="timeZone-sm-viewport"
className="mb-0 inline-block leading-none"
waitForTranslation={!isPlatform}>
{translations?.timezone ?? t("timezone")}
{t("timezone")}
</Skeleton>
<Controller
control={form.control}
@@ -450,7 +424,7 @@ export function AvailabilitySettings({
as="h3"
className="mb-0 inline-block text-sm font-medium"
waitForTranslation={!isPlatform}>
{translations?.something_doesnt_look_right ?? t("something_doesnt_look_right")}
{t("something_doesnt_look_right")}
</Skeleton>
<div className="mt-3 flex">
<Skeleton
@@ -458,7 +432,7 @@ export function AvailabilitySettings({
href="/availability/troubleshoot"
color="secondary"
waitForTranslation={!isPlatform}>
{translations?.launch_troubleshooter ?? t("launch_troubleshooter")}
{t("launch_troubleshooter")}
</Skeleton>
</div>
</div>
@@ -472,7 +446,7 @@ export function AvailabilitySettings({
</SmallScreenSideBar>
<div className="border-default border-l-2" />
<Button className="ml-4 lg:ml-0" type="submit" form="availability-form" loading={isSaving}>
{translations?.save ?? t("save")}
{t("save")}
</Button>
<Button
className="ml-3 sm:hidden"
@@ -497,9 +471,9 @@ export function AvailabilitySettings({
{typeof weekStart === "string" && (
<Schedule
labels={{
addTime: translations?.add_time_availability ?? t("add_time_availability"),
copyTime: translations?.copy_times_to ?? t("copy_times_to"),
deleteTime: translations?.delete ?? t("delete"),
addTime: t("add_time_availability"),
copyTime: t("copy_times_to"),
deleteTime: t("delete"),
}}
className={
customClassNames?.scheduleClassNames ? { ...customClassNames.scheduleClassNames } : {}
@@ -534,7 +508,7 @@ export function AvailabilitySettings({
htmlFor="timeZone-lg-viewport"
className="mb-0 inline-block leading-none"
waitForTranslation={!isPlatform}>
{translations?.timezone ?? t("timezone")}
{t("timezone")}
</Skeleton>
<Controller
name="timeZone"
@@ -562,7 +536,7 @@ export function AvailabilitySettings({
as="h3"
className="mb-0 inline-block text-sm font-medium"
waitForTranslation={!isPlatform}>
{translations?.something_doesnt_look_right ?? t("something_doesnt_look_right")}
{t("something_doesnt_look_right")}
</Skeleton>
<div className="mt-3 flex">
<Skeleton
@@ -570,7 +544,7 @@ export function AvailabilitySettings({
href="/availability/troubleshoot"
color="secondary"
waitForTranslation={!isPlatform}>
{translations?.launch_troubleshooter ?? t("launch_troubleshooter")}
{t("launch_troubleshooter")}
</Skeleton>
</div>
</div>
@@ -8,7 +8,7 @@ import { useMe } from "../../hooks/useMe";
import useUpdateSchedule from "../../hooks/useUpdateSchedule";
import { useToast } from "../../src/components/ui/use-toast";
import type { Schedule } from "../AvailabilitySettings";
import type { CustomClassNames, TranslationsType } from "../AvailabilitySettings";
import type { CustomClassNames } from "../AvailabilitySettings";
import { AvailabilitySettings } from "../AvailabilitySettings";
import type { AvailabilityFormValues } from "../types";
@@ -17,7 +17,6 @@ type PlatformAvailabilitySettingsWrapperProps = {
labels?: {
tooltips: Partial<ScheduleLabelsType>;
};
translations?: Partial<TranslationsType>;
customClassNames?: Partial<CustomClassNames>;
onUpdateSuccess?: (res: ApiResponse<UpdateScheduleOutputType>) => void;
onUpdateError?: (err: ApiErrorResponse) => void;
@@ -25,27 +24,8 @@ type PlatformAvailabilitySettingsWrapperProps = {
onDeleteError?: (err: ApiErrorResponse) => void;
};
const defaultTranslations: TranslationsType = {
availability: "Availability",
set_to_default: "Set to default",
delete: "Delete",
delete_schedule: "Delete schedule",
timezone: "Timezone",
availability_settings: "Availability settings",
launch_troubleshooter: "Launch troubleshooter",
requires_at_least_one_schedule: "Requires at least one schedule",
delete_schedule_description: "Are you sure you want to delete this schedule?",
name: "Name",
something_doesnt_look_right: "Something doesn't look right",
add_an_override: "Add an override",
add_time_availability: "Add new time slot",
copy_times_to: "Copy times to …",
save: "Save",
} as const;
export const PlatformAvailabilitySettingsWrapper = ({
id,
translations = defaultTranslations,
customClassNames,
onDeleteError,
onDeleteSuccess,
@@ -144,39 +124,7 @@ export const PlatformAvailabilitySettingsWrapper = ({
isSaving={isSavingInProgress}
backPath=""
isPlatform={true}
translations={{ ...defaultTranslations, ...translations }}
customClassNames={customClassNames}
/>
);
};
[
[
{
start: "2024-03-21T09:00:00.000Z",
end: "2024-03-21T17:00:00.000Z",
},
{
start: "2024-03-21T17:00:00.000Z",
end: "2024-03-21T18:00:00.000Z",
},
{
start: "2024-03-21T18:00:00.000Z",
end: "2024-03-21T19:00:00.000Z",
},
], // thursday
[
{
start: "2024-03-21T09:00:00.000Z",
end: "2024-03-21T17:00:00.000Z",
},
{
start: "2024-03-21T17:00:00.000Z",
end: "2024-03-21T18:00:00.000Z",
},
{
start: "2024-03-21T18:00:00.000Z",
end: "2024-03-21T19:00:00.000Z",
},
], //friday
];
@@ -0,0 +1,113 @@
import { useRouter } from "next/navigation";
import { useForm } from "react-hook-form";
import { withErrorFromUnknown } from "@calcom/lib/getClientErrorFromUnknown";
import { useCompatSearchParams } from "@calcom/lib/hooks/useCompatSearchParams";
import { useLocale } from "@calcom/lib/hooks/useLocale";
import { HttpError } from "@calcom/lib/http-error";
import { trpc } from "@calcom/trpc/react";
import useMeQuery from "@calcom/trpc/react/hooks/useMeQuery";
import { showToast } from "@calcom/ui";
import { AvailabilitySettings } from "../AvailabilitySettings";
import type { AvailabilityFormValues } from "../types";
export const WebAvailabilitySettingsWrapper = () => {
const searchParams = useCompatSearchParams();
const { t } = useLocale();
const router = useRouter();
const utils = trpc.useContext();
const me = useMeQuery();
const scheduleId = searchParams?.get("schedule") ? Number(searchParams.get("schedule")) : -1;
const fromEventType = searchParams?.get("fromEventType");
const { timeFormat } = me.data || { timeFormat: null };
const { data: schedule, isPending } = trpc.viewer.availability.schedule.get.useQuery(
{ scheduleId },
{
enabled: !!scheduleId,
}
);
const form = useForm<AvailabilityFormValues>({
values: schedule && {
...schedule,
schedule: schedule?.availability || [],
},
});
const updateMutation = trpc.viewer.availability.schedule.update.useMutation({
onSuccess: async ({ prevDefaultId, currentDefaultId, ...data }) => {
if (prevDefaultId && currentDefaultId) {
// check weather the default schedule has been changed by comparing previous default schedule id and current default schedule id.
if (prevDefaultId !== currentDefaultId) {
// if not equal, invalidate previous default schedule id and refetch previous default schedule id.
utils.viewer.availability.schedule.get.invalidate({ scheduleId: prevDefaultId });
utils.viewer.availability.schedule.get.refetch({ scheduleId: prevDefaultId });
}
}
utils.viewer.availability.schedule.get.invalidate({ scheduleId: data.schedule.id });
utils.viewer.availability.list.invalidate();
showToast(
t("availability_updated_successfully", {
scheduleName: data.schedule.name,
}),
"success"
);
},
onError: (err) => {
if (err instanceof HttpError) {
const message = `${err.statusCode}: ${err.message}`;
showToast(message, "error");
}
},
});
const deleteMutation = trpc.viewer.availability.schedule.delete.useMutation({
onError: withErrorFromUnknown((err) => {
showToast(err.message, "error");
}),
onSettled: () => {
utils.viewer.availability.list.invalidate();
},
onSuccess: () => {
showToast(t("schedule_deleted_successfully"), "success");
router.push("/availability");
},
});
return (
<AvailabilitySettings
schedule={
schedule
? {
name: schedule.name,
id: schedule.id,
isLastSchedule: schedule.isLastSchedule,
isDefault: schedule.isDefault,
workingHours: schedule.workingHours,
dateOverrides: schedule.dateOverrides,
timeZone: schedule.timeZone,
availability: schedule.availability || [],
schedule: schedule.schedule || [],
}
: undefined
}
isDeleting={deleteMutation.isPending}
isLoading={isPending}
isSaving={updateMutation.isPending}
timeFormat={timeFormat}
weekStart={me.data?.weekStart || "Sunday"}
backPath={fromEventType ? true : "/availability"}
handleDelete={() => {
scheduleId && deleteMutation.mutate({ scheduleId });
}}
handleSubmit={async ({ dateOverrides, ...values }) => {
scheduleId &&
updateMutation.mutate({
scheduleId,
dateOverrides: dateOverrides.flatMap((override) => override.ranges),
...values,
});
}}
/>
);
};
+1 -1
View File
@@ -1,6 +1,6 @@
export { BookerWebWrapper as Booker } from "./booker";
export { WebAvailabilitySettingsWrapper as AvailabilitySettings } from "./availability/wrappers/WebAvailabilitySettingsWrapper";
export { CalProvider } from "./cal-provider/CalProvider";
export { AvailabilitySettings } from "./availability";
export { useIsPlatform } from "./hooks/useIsPlatform";
export { useAtomsContext } from "./hooks/useAtomsContext";
export { useHandleBookEvent } from "./hooks/useHandleBookEvent";