fix: Autodetection of time zone only updated default time zone (#15392)
* fix: Autodetection of time zone only updated default time zone * fix: description text --------- Co-authored-by: sean-brydon <55134778+sean-brydon@users.noreply.github.com>
This commit is contained in:
co-authored by
sean-brydon
parent
c458b297ae
commit
285d1ec4c9
@@ -5,7 +5,7 @@ import { InstallAppButton } from "@calcom/app-store/components";
|
||||
import { getEventLocationTypeFromApp, type EventLocationType } from "@calcom/app-store/locations";
|
||||
import type { CredentialOwner } from "@calcom/app-store/types";
|
||||
import { AppSetDefaultLinkDialog } from "@calcom/features/apps/components/AppSetDefaultLinkDialog";
|
||||
import { BulkEditDefaultForEventsModal } from "@calcom/features/eventtypes/components/BulkEditDefaultForEventsModal";
|
||||
import { BulkEditDefaultModal } from "@calcom/features/eventtypes/components/BulkEditDefaultModal";
|
||||
import { useLocale } from "@calcom/lib/hooks/useLocale";
|
||||
import type { AppCategories } from "@calcom/prisma/enums";
|
||||
import { trpc, type RouterOutputs } from "@calcom/trpc";
|
||||
@@ -153,6 +153,8 @@ export const AppList = ({ data, handleDisconnect, variant, listClassName }: AppL
|
||||
});
|
||||
|
||||
const { t } = useLocale();
|
||||
const [eventTypeIds, setEventTypeIds] = useState<number[]>([]);
|
||||
const { data: bulkEventTypes } = trpc.viewer.eventTypes.bulkEventFetch.useQuery();
|
||||
const updateLocationsMutation = trpc.viewer.eventTypes.bulkUpdateToDefaultLocation.useMutation({
|
||||
onSuccess: () => {
|
||||
utils.viewer.getUsersDefaultConferencingApp.invalidate();
|
||||
@@ -178,11 +180,20 @@ export const AppList = ({ data, handleDisconnect, variant, listClassName }: AppL
|
||||
)}
|
||||
|
||||
{bulkUpdateModal && (
|
||||
<BulkEditDefaultForEventsModal
|
||||
bulkUpdateFunction={updateLocationsMutation.mutate}
|
||||
<BulkEditDefaultModal
|
||||
handleSubmit={() => {
|
||||
updateLocationsMutation.mutate({
|
||||
eventTypeIds,
|
||||
});
|
||||
}}
|
||||
open={bulkUpdateModal}
|
||||
setOpen={setBulkUpdateModal}
|
||||
isPending={updateLocationsMutation.isPending}
|
||||
title={t("default_conferencing_bulk_title")}
|
||||
description={t("default_conferencing_bulk_description")}
|
||||
data={bulkEventTypes?.eventTypes}
|
||||
ids={eventTypeIds}
|
||||
setIds={setEventTypeIds}
|
||||
/>
|
||||
)}
|
||||
</>
|
||||
|
||||
@@ -3,7 +3,7 @@ import Link from "next/link";
|
||||
import { useRouter, usePathname } from "next/navigation";
|
||||
import { useCallback, useState } from "react";
|
||||
|
||||
import { BulkEditDefaultForEventsModal } from "@calcom/features/eventtypes/components/BulkEditDefaultForEventsModal";
|
||||
import { BulkEditDefaultModal } from "@calcom/features/eventtypes/components/BulkEditDefaultModal";
|
||||
import { NewScheduleButton, ScheduleListItem } from "@calcom/features/schedules";
|
||||
import Shell from "@calcom/features/shell/Shell";
|
||||
import { AvailabilitySliderTable } from "@calcom/features/timezone-buddy/components/AvailabilitySliderTable";
|
||||
@@ -24,12 +24,14 @@ import SkeletonLoader from "@components/availability/SkeletonLoader";
|
||||
export function AvailabilityList({ schedules }: RouterOutputs["viewer"]["availability"]["list"]) {
|
||||
const { t } = useLocale();
|
||||
const [bulkUpdateModal, setBulkUpdateModal] = useState(false);
|
||||
const [eventTypeIds, setEventTypeIds] = useState<number[]>([]);
|
||||
const utils = trpc.useUtils();
|
||||
|
||||
const meQuery = trpc.viewer.me.useQuery();
|
||||
|
||||
const router = useRouter();
|
||||
|
||||
const { data } = trpc.viewer.eventTypes.bulkEventFetch.useQuery();
|
||||
const deleteMutation = trpc.viewer.availability.schedule.delete.useMutation({
|
||||
onMutate: async ({ scheduleId }) => {
|
||||
await utils.viewer.availability.list.cancel();
|
||||
@@ -143,11 +145,20 @@ export function AvailabilityList({ schedules }: RouterOutputs["viewer"]["availab
|
||||
</Link>
|
||||
</div>
|
||||
{bulkUpdateModal && (
|
||||
<BulkEditDefaultForEventsModal
|
||||
<BulkEditDefaultModal
|
||||
isPending={bulkUpdateDefaultAvailabilityMutation.isPending}
|
||||
open={bulkUpdateModal}
|
||||
setOpen={setBulkUpdateModal}
|
||||
bulkUpdateFunction={bulkUpdateDefaultAvailabilityMutation.mutate}
|
||||
title={t("default_conferencing_bulk_title")}
|
||||
description={t("default_conferencing_bulk_description")}
|
||||
ids={eventTypeIds}
|
||||
setIds={setEventTypeIds}
|
||||
data={data?.eventTypes}
|
||||
handleSubmit={() => {
|
||||
bulkUpdateDefaultAvailabilityMutation.mutate({
|
||||
eventTypeIds,
|
||||
});
|
||||
}}
|
||||
/>
|
||||
)}
|
||||
</>
|
||||
|
||||
@@ -1916,12 +1916,14 @@
|
||||
"invalid_event_name_variables": "There is an invalid variable in your event name",
|
||||
"select_all": "Select All",
|
||||
"default_conferencing_bulk_title": "Bulk update existing event types",
|
||||
"default_timezone_bulk_title": "Bulk update existing availabilities",
|
||||
"members_default_schedule": "Member's default schedule",
|
||||
"set_by_admin": "Set by team admin",
|
||||
"members_default_location": "Member's default location",
|
||||
"members_default_schedule_description": "We will use each members default availability schedule. They will be able to edit or change it.",
|
||||
"requires_at_least_one_schedule": "You are required to have at least one schedule",
|
||||
"default_conferencing_bulk_description": "Update the locations for the selected event types",
|
||||
"default_timezone_bulk_description": "Update the timezone for selected availabilities.",
|
||||
"locked_for_members": "Locked for members",
|
||||
"unlocked_for_members": "Unlocked for members",
|
||||
"apps_locked_for_members_description": "Members will be able to see the active apps but will not be able to edit any app settings",
|
||||
|
||||
@@ -1,90 +0,0 @@
|
||||
import { zodResolver } from "@hookform/resolvers/zod";
|
||||
import { useForm } from "react-hook-form";
|
||||
import { z } from "zod";
|
||||
|
||||
import { useLocale } from "@calcom/lib/hooks/useLocale";
|
||||
import { trpc } from "@calcom/trpc/react";
|
||||
import { Dialog, DialogContent, Form, DialogFooter, DialogClose, Button, CheckboxField } from "@calcom/ui";
|
||||
|
||||
export const BulkUpdateEventSchema = z.object({
|
||||
eventTypeIds: z.array(z.number()),
|
||||
});
|
||||
|
||||
export function BulkEditDefaultForEventsModal(props: {
|
||||
open: boolean;
|
||||
setOpen: (open: boolean) => void;
|
||||
bulkUpdateFunction: ({ eventTypeIds }: { eventTypeIds: number[] }) => void;
|
||||
isPending: boolean;
|
||||
}) {
|
||||
const { t } = useLocale();
|
||||
const utils = trpc.useUtils();
|
||||
const { data, isFetching } = trpc.viewer.eventTypes.bulkEventFetch.useQuery();
|
||||
const form = useForm({
|
||||
resolver: zodResolver(BulkUpdateEventSchema),
|
||||
defaultValues: {
|
||||
eventTypeIds: data?.eventTypes.map((e) => e.id) ?? [],
|
||||
},
|
||||
});
|
||||
|
||||
const eventTypesSelected = form.watch("eventTypeIds");
|
||||
|
||||
if (isFetching || !open || !data?.eventTypes) return null;
|
||||
|
||||
return (
|
||||
<Dialog name="Bulk Default Location Update" open={props.open} onOpenChange={props.setOpen}>
|
||||
<DialogContent
|
||||
type="creation"
|
||||
title={t("default_conferencing_bulk_title")}
|
||||
description={t("default_conferencing_bulk_description")}
|
||||
enableOverflow>
|
||||
<Form
|
||||
form={form}
|
||||
handleSubmit={(values) => {
|
||||
props.bulkUpdateFunction(values);
|
||||
}}>
|
||||
<div className="flex flex-col space-y-2">
|
||||
{data.eventTypes.length > 0 && (
|
||||
<div className="flex items-center space-x-2 rounded-md px-3 pb-2.5 pt-1">
|
||||
<CheckboxField
|
||||
description={t("select_all")}
|
||||
descriptionAsLabel
|
||||
onChange={(e) => {
|
||||
form.setValue("eventTypeIds", e.target.checked ? data.eventTypes.map((e) => e.id) : []);
|
||||
}}
|
||||
checked={eventTypesSelected.length === data.eventTypes.length}
|
||||
/>
|
||||
</div>
|
||||
)}
|
||||
{data.eventTypes.map((eventType) => (
|
||||
<div key={eventType.id} className="bg-muted flex items-center space-x-2 rounded-md px-3 py-2.5">
|
||||
<CheckboxField
|
||||
description={eventType.title}
|
||||
descriptionAsLabel
|
||||
checked={eventTypesSelected.includes(eventType.id)}
|
||||
onChange={(e) => {
|
||||
form.setValue(
|
||||
"eventTypeIds",
|
||||
e.target.checked
|
||||
? [...eventTypesSelected, eventType.id]
|
||||
: eventTypesSelected.filter((id) => id !== eventType.id)
|
||||
);
|
||||
}}
|
||||
/>
|
||||
</div>
|
||||
))}
|
||||
</div>
|
||||
<DialogFooter showDivider className="mt-10">
|
||||
<DialogClose
|
||||
onClick={() => {
|
||||
utils.viewer.getUsersDefaultConferencingApp.invalidate();
|
||||
}}
|
||||
/>
|
||||
<Button type="submit" color="primary" loading={props.isPending}>
|
||||
{t("update")}
|
||||
</Button>
|
||||
</DialogFooter>
|
||||
</Form>
|
||||
</DialogContent>
|
||||
</Dialog>
|
||||
);
|
||||
}
|
||||
@@ -0,0 +1,63 @@
|
||||
import type { Dispatch, SetStateAction } from "react";
|
||||
|
||||
import { useLocale } from "@calcom/lib/hooks/useLocale";
|
||||
import { Dialog, DialogContent, DialogFooter, DialogClose, Button, CheckboxField } from "@calcom/ui";
|
||||
|
||||
export function BulkEditDefaultModal(props: {
|
||||
open: boolean;
|
||||
setOpen: (open: boolean) => void;
|
||||
ids: number[];
|
||||
setIds: Dispatch<SetStateAction<number[]>>;
|
||||
isPending: boolean;
|
||||
data?: {
|
||||
id: number;
|
||||
title: string;
|
||||
default?: boolean;
|
||||
}[];
|
||||
title: string;
|
||||
description: string;
|
||||
handleSubmit: () => void;
|
||||
}) {
|
||||
const { title, description, handleSubmit, data, ids, setIds, open } = props;
|
||||
const { t } = useLocale();
|
||||
if (!open || !data || !ids) return null;
|
||||
|
||||
return (
|
||||
<Dialog name="Bulk Default Update" open={props.open} onOpenChange={props.setOpen}>
|
||||
<DialogContent type="creation" title={title} description={description} enableOverflow>
|
||||
<div className="flex flex-col space-y-2">
|
||||
{data.length > 0 && (
|
||||
<div className="flex items-center space-x-2 rounded-md px-3 pb-2.5 pt-1">
|
||||
<CheckboxField
|
||||
description={t("select_all")}
|
||||
descriptionAsLabel
|
||||
onChange={(e) => {
|
||||
setIds(e.target.checked ? data.map((e) => e.id) : []);
|
||||
}}
|
||||
checked={ids.length === data.length}
|
||||
/>
|
||||
</div>
|
||||
)}
|
||||
{data.map((item) => (
|
||||
<div key={item.id} className="bg-muted flex items-center space-x-2 rounded-md px-3 py-2.5">
|
||||
<CheckboxField
|
||||
description={item.title}
|
||||
descriptionAsLabel
|
||||
checked={ids.includes(item.id)}
|
||||
onChange={(e) => {
|
||||
setIds(e.target.checked ? [...ids, item.id] : ids.filter((id) => id !== item.id));
|
||||
}}
|
||||
/>
|
||||
</div>
|
||||
))}
|
||||
</div>
|
||||
<DialogFooter showDivider className="mt-10">
|
||||
<DialogClose />
|
||||
<Button type="submit" color="primary" loading={props.isPending} onClick={handleSubmit}>
|
||||
{t("update")}
|
||||
</Button>
|
||||
</DialogFooter>
|
||||
</DialogContent>
|
||||
</Dialog>
|
||||
);
|
||||
}
|
||||
@@ -4,6 +4,7 @@ import { useSession } from "next-auth/react";
|
||||
import { useEffect, useState } from "react";
|
||||
|
||||
import dayjs from "@calcom/dayjs";
|
||||
import { BulkEditDefaultModal } from "@calcom/features/eventtypes/components/BulkEditDefaultModal";
|
||||
import { useLocale } from "@calcom/lib/hooks/useLocale";
|
||||
import { trpc } from "@calcom/trpc/react";
|
||||
import { Dialog, DialogClose, DialogContent, DialogFooter, showToast } from "@calcom/ui";
|
||||
@@ -16,7 +17,6 @@ const TimezoneChangeDialogContent = ({
|
||||
onAction: (action?: "update" | "cancel") => void;
|
||||
}) => {
|
||||
const { t } = useLocale();
|
||||
const utils = trpc.useUtils();
|
||||
const formattedCurrentTz = browserTimezone.replace("_", " ");
|
||||
|
||||
// save cookie to not show again
|
||||
@@ -28,28 +28,6 @@ const TimezoneChangeDialogContent = ({
|
||||
toast && showToast(t("we_wont_show_again"), "success");
|
||||
}
|
||||
|
||||
const onSuccessMutation = async () => {
|
||||
showToast(t("updated_timezone_to", { formattedCurrentTz }), "success");
|
||||
await utils.viewer.me.invalidate();
|
||||
};
|
||||
|
||||
const onErrorMutation = () => {
|
||||
showToast(t("couldnt_update_timezone"), "error");
|
||||
};
|
||||
|
||||
// update timezone in db
|
||||
const mutation = trpc.viewer.updateProfile.useMutation({
|
||||
onSuccess: onSuccessMutation,
|
||||
onError: onErrorMutation,
|
||||
});
|
||||
|
||||
function updateTimezone() {
|
||||
onAction("update");
|
||||
mutation.mutate({
|
||||
timeZone: browserTimezone,
|
||||
});
|
||||
}
|
||||
|
||||
return (
|
||||
<DialogContent
|
||||
title={t("update_timezone_question")}
|
||||
@@ -64,7 +42,7 @@ const TimezoneChangeDialogContent = ({
|
||||
<DialogClose onClick={() => onCancel([3, "months"], true)} color="secondary">
|
||||
{t("dont_update")}
|
||||
</DialogClose>
|
||||
<DialogClose onClick={() => updateTimezone()} color="primary">
|
||||
<DialogClose onClick={() => onAction("update")} color="primary">
|
||||
{t("update_timezone")}
|
||||
</DialogClose>
|
||||
</DialogFooter>
|
||||
@@ -104,9 +82,56 @@ export function useOpenTimezoneDialog() {
|
||||
|
||||
export default function TimezoneChangeDialog() {
|
||||
const { open, setOpen, browserTimezone } = useOpenTimezoneDialog();
|
||||
const { t } = useLocale();
|
||||
const utils = trpc.useUtils();
|
||||
|
||||
const [selectAvailability, setSelectAvailability] = useState(false);
|
||||
const [ids, setIds] = useState<number[]>([]);
|
||||
const { data: availability, isPending } = trpc.viewer.availability.list.useQuery();
|
||||
const formattedCurrentTz = browserTimezone.replace("_", " ");
|
||||
|
||||
const data = availability?.schedules?.map((a) => ({
|
||||
id: a.id,
|
||||
title: a.name,
|
||||
default: a.isDefault,
|
||||
}));
|
||||
|
||||
const mutation = trpc.viewer.updateProfile.useMutation({
|
||||
onSuccess: async () => {
|
||||
showToast(t("updated_timezone_to", { formattedCurrentTz }), "success");
|
||||
await utils.viewer.me.invalidate();
|
||||
setSelectAvailability(false);
|
||||
},
|
||||
onError: () => {
|
||||
showToast(t("couldnt_update_timezone"), "error");
|
||||
},
|
||||
});
|
||||
|
||||
return (
|
||||
<Dialog open={open} onOpenChange={setOpen}>
|
||||
<TimezoneChangeDialogContent browserTimezone={browserTimezone} onAction={() => setOpen(false)} />
|
||||
<TimezoneChangeDialogContent
|
||||
browserTimezone={browserTimezone}
|
||||
onAction={() => {
|
||||
setOpen(false);
|
||||
setSelectAvailability(true);
|
||||
}}
|
||||
/>
|
||||
<BulkEditDefaultModal
|
||||
title={t("default_timezone_bulk_title")}
|
||||
description={t("default_timezone_bulk_description")}
|
||||
ids={ids}
|
||||
isPending={isPending}
|
||||
open={selectAvailability}
|
||||
setOpen={setSelectAvailability}
|
||||
setIds={setIds}
|
||||
data={data}
|
||||
handleSubmit={() => {
|
||||
mutation.mutate({
|
||||
timeZone: browserTimezone,
|
||||
availabilityIds: ids,
|
||||
});
|
||||
}}
|
||||
/>
|
||||
</Dialog>
|
||||
);
|
||||
}
|
||||
|
||||
@@ -24,7 +24,6 @@ import type { TrpcSessionUser } from "@calcom/trpc/server/trpc";
|
||||
|
||||
import { TRPCError } from "@trpc/server";
|
||||
|
||||
import { getDefaultScheduleId } from "../viewer/availability/util";
|
||||
import { updateUserMetadataAllowedKeys, type TUpdateProfileInputSchema } from "./updateProfile.schema";
|
||||
|
||||
const log = logger.getSubLogger({ prefix: ["updateProfile"] });
|
||||
@@ -42,7 +41,7 @@ export const updateProfileHandler = async ({ ctx, input }: UpdateProfileOptions)
|
||||
const locale = input.locale || user.locale;
|
||||
const emailVerification = await getFeatureFlag(prisma, "email-verification");
|
||||
|
||||
const { travelSchedules, ...rest } = input;
|
||||
const { travelSchedules, availabilityIds, ...rest } = input;
|
||||
|
||||
const secondaryEmails = input?.secondaryEmails || [];
|
||||
delete input.secondaryEmails;
|
||||
@@ -257,24 +256,11 @@ export const updateProfileHandler = async ({ ctx, input }: UpdateProfileOptions)
|
||||
}
|
||||
|
||||
if (user.timeZone !== data.timeZone && updatedUser.schedules.length > 0) {
|
||||
// on timezone change update timezone of default schedule
|
||||
const defaultScheduleId = await getDefaultScheduleId(user.id, prisma);
|
||||
|
||||
if (!user.defaultScheduleId) {
|
||||
// set default schedule if not already set
|
||||
await prisma.user.update({
|
||||
where: {
|
||||
id: user.id,
|
||||
},
|
||||
data: {
|
||||
defaultScheduleId,
|
||||
},
|
||||
});
|
||||
}
|
||||
|
||||
await prisma.schedule.updateMany({
|
||||
where: {
|
||||
id: defaultScheduleId,
|
||||
id: {
|
||||
in: input.availabilityIds,
|
||||
},
|
||||
},
|
||||
data: {
|
||||
timeZone: data.timeZone,
|
||||
|
||||
@@ -15,6 +15,7 @@ export const ZUpdateProfileInputSchema = z.object({
|
||||
bio: z.string().optional(),
|
||||
avatarUrl: z.string().nullable().optional(),
|
||||
timeZone: z.string().optional(),
|
||||
availabilityIds: z.array(z.number()).optional(),
|
||||
weekStart: z.string().optional(),
|
||||
hideBranding: z.boolean().optional(),
|
||||
allowDynamicBooking: z.boolean().optional(),
|
||||
|
||||
Reference in New Issue
Block a user