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:
@@ -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>
|
||||
|
||||
+1
-53
@@ -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,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";
|
||||
|
||||
Reference in New Issue
Block a user