fix(coss-ui): convert /settings/my-account/general to coss-ui (#26053)

* fix(coss-ui): convert /settings/my-account/general to coss-ui

* update guide

* fix: add menuPosition fixed to TimezoneSelect in TravelScheduleModal

Co-Authored-By: eunjae@cal.com <hey@eunjae.dev>

* fix: use menuPortalTarget to render TimezoneSelect dropdown above dialog footer

Co-Authored-By: eunjae@cal.com <hey@eunjae.dev>

* fix: use Object.assign for type-safe styles in TimezoneSelect

Co-Authored-By: eunjae@cal.com <hey@eunjae.dev>

* revert comment

* fix: guard document access for SSR in TravelScheduleModal

Addresses Cubic AI review feedback: menuPortalTarget={document.body} will
throw during server rendering where document is undefined. Added typeof
check to guard against SSR.

Co-Authored-By: unknown <>

* change dialog backdrop from blur to dim

* fix: add menuPlacement auto to open dropdown upward on mobile

Co-Authored-By: eunjae@cal.com <hey@eunjae.dev>

* fix: use menuPlacement top on mobile for TimezoneSelect dropdown

Co-Authored-By: eunjae@cal.com <hey@eunjae.dev>

* fix: make timezone button stack vertically on mobile to prevent overflow

Co-Authored-By: eunjae@cal.com <hey@eunjae.dev>

* fix: make timezone select and button 50/50 width on larger viewports

Co-Authored-By: eunjae@cal.com <hey@eunjae.dev>

* fix date range picker issue on mobile

* prevent travel schedule dialog dismissal with date picker

---------

Co-authored-by: Devin AI <158243242+devin-ai-integration[bot]@users.noreply.github.com>
This commit is contained in:
authored and GitHub committed 2026-01-23 11:19:00 +00:00
1 parent 052e99f7ba
commit 25fa46fd34
7 files changed
+1195 -83

No files matched your search

@@ -1,20 +1,29 @@
import { useState } from "react";
import type { UseFormSetValue } from "react-hook-form";
import dayjs from "@calcom/dayjs";
import { useTimePreferences } from "@calcom/features/bookings/lib/timePreferences";
import { Dialog } from "@calcom/features/components/controlled-dialog";
import { TimezoneSelect } from "@calcom/features/components/timezone-select";
import { useLocale } from "@calcom/lib/hooks/useLocale";
import { Button } from "@calcom/ui/components/button";
import { DialogContent, DialogFooter, DialogClose } from "@calcom/ui/components/dialog";
import { Label, SettingsToggle, DateRangePicker, DatePicker } from "@calcom/ui/components/form";
import { DatePicker, SettingsToggle } from "@calcom/ui/components/form";
import { DatePickerWithRange as DateRangePicker } from "@calcom/ui/components/form/date-range-picker/DateRangePicker";
import { Button } from "@coss/ui/components/button";
import {
Dialog,
DialogClose,
DialogDescription,
DialogFooter,
DialogHeader,
DialogPanel,
DialogPopup,
DialogTitle,
} from "@coss/ui/components/dialog";
import { Label } from "@coss/ui/components/label";
import { useIsMobile } from "@coss/ui/hooks/use-mobile";
import { useState } from "react";
import type { UseFormSetValue } from "react-hook-form";
import type { FormValues } from "~/settings/my-account/general-view";
interface TravelScheduleModalProps {
open: boolean;
onOpenChange: () => void;
onOpenChange: (open: boolean) => void;
setValue: UseFormSetValue<FormValues>;
existingSchedules: FormValues["travelSchedules"];
}
@@ -27,12 +36,14 @@ const TravelScheduleModal = ({
}: TravelScheduleModalProps) => {
const { t } = useLocale();
const { timezone: preferredTimezone } = useTimePreferences();
const isMobile = useIsMobile();
const [startDate, setStartDate] = useState<Date>(new Date());
const [endDate, setEndDate] = useState<Date | undefined>(new Date());
const [selectedTimeZone, setSelectedTimeZone] = useState(preferredTimezone);
const [isNoEndDate, setIsNoEndDate] = useState(false);
const [isDateRangeOpen, setIsDateRangeOpen] = useState(false);
const [errorMessage, setErrorMessage] = useState("");
const isOverlapping = (newSchedule: { startDate: Date; endDate?: Date }) => {
@@ -64,6 +75,7 @@ const TravelScheduleModal = ({
setEndDate(new Date());
setSelectedTimeZone(preferredTimezone);
setIsNoEndDate(false);
setIsDateRangeOpen(false);
};
const createNewSchedule = () => {
@@ -75,7 +87,7 @@ const TravelScheduleModal = ({
if (!isOverlapping(newSchedule)) {
setValue("travelSchedules", existingSchedules.concat(newSchedule), { shouldDirty: true });
onOpenChange();
onOpenChange(false);
resetValues();
} else {
setErrorMessage(t("overlaps_with_existing_schedule"));
@@ -83,65 +95,86 @@ const TravelScheduleModal = ({
};
return (
<Dialog open={open} onOpenChange={onOpenChange}>
<DialogContent
title={t("travel_schedule")}
description={t("travel_schedule_description")}
type="creation">
<div>
{!isNoEndDate ? (
<>
<Label className="mt-2">{t("time_range")}</Label>
<DateRangePicker
dates={{
startDate,
endDate,
}}
onDatesChange={({ startDate: newStartDate, endDate: newEndDate }) => {
// If newStartDate does become undefined - we resort back to to-todays date
setStartDate(newStartDate ?? new Date());
setEndDate(newEndDate);
<Dialog
open={open}
disablePointerDismissal={isDateRangeOpen}
onOpenChange={(nextOpen) => {
onOpenChange(nextOpen);
if (!nextOpen) {
setIsDateRangeOpen(false);
}
}}>
<DialogPopup>
<DialogHeader>
<DialogTitle>{t("travel_schedule")}</DialogTitle>
<DialogDescription>{t("travel_schedule_description")}</DialogDescription>
</DialogHeader>
<DialogPanel>
<div>
{!isNoEndDate ? (
<>
<Label className="mt-2">{t("time_range")}</Label>
<DateRangePicker
dates={{
startDate,
endDate,
}}
popoverModal={isMobile}
onPopoverOpenChange={setIsDateRangeOpen}
onDatesChange={({ startDate: newStartDate, endDate: newEndDate }) => {
// If newStartDate does become undefined - we resort back to to-todays date
setStartDate(newStartDate ?? new Date());
setEndDate(newEndDate);
setErrorMessage("");
}}
/>
</>
) : (
<>
<Label className="mt-2">{t("date")}</Label>
<DatePicker
minDate={new Date()}
date={startDate}
className="w-56"
onDatesChange={(newDate) => {
setStartDate(newDate);
setErrorMessage("");
}}
/>
</>
)}
<div className="text-error mt-1 text-sm">{errorMessage}</div>
<div className="mt-3">
<SettingsToggle
labelClassName="mt-1 font-normal"
title={t("schedule_tz_without_end_date")}
checked={isNoEndDate}
onCheckedChange={(checked) => {
setEndDate(!checked ? startDate : undefined);
setIsNoEndDate(checked);
if (checked) {
setIsDateRangeOpen(false);
}
setErrorMessage("");
}}
/>
</>
) : (
<>
<Label className="mt-2">{t("date")}</Label>
<DatePicker
minDate={new Date()}
date={startDate}
className="w-56"
onDatesChange={(newDate) => {
setStartDate(newDate);
setErrorMessage("");
}}
/>
</>
)}
<div className="text-error mt-1 text-sm">{errorMessage}</div>
<div className="mt-3">
<SettingsToggle
labelClassName="mt-1 font-normal"
title={t("schedule_tz_without_end_date")}
checked={isNoEndDate}
onCheckedChange={(e) => {
setEndDate(!e ? startDate : undefined);
setIsNoEndDate(e);
setErrorMessage("");
}}
</div>
<Label className="mt-6">{t("timezone")}</Label>
<TimezoneSelect
id="timeZone"
value={selectedTimeZone}
onChange={({ value }) => setSelectedTimeZone(value)}
menuPortalTarget={typeof document === "undefined" ? undefined : document.body}
menuPlacement={isMobile ? "top" : "auto"}
styles={{ menuPortal: (base) => Object.assign({}, base, { zIndex: 9999 }) }}
className="mb-11 mt-2 w-full rounded-md text-sm"
/>
</div>
<Label className="mt-6">{t("timezone")}</Label>
<TimezoneSelect
id="timeZone"
value={selectedTimeZone}
onChange={({ value }) => setSelectedTimeZone(value)}
className="mb-11 mt-2 w-full rounded-md text-sm"
/>
</div>
<DialogFooter showDivider className="relative">
<DialogClose />
</DialogPanel>
<DialogFooter>
<DialogClose render={<Button variant="ghost" />}>{t("cancel")}</DialogClose>
<Button
disabled={isNoEndDate ? !startDate : !startDate || !endDate}
onClick={() => {
@@ -150,7 +183,7 @@ const TravelScheduleModal = ({
{t("add")}
</Button>
</DialogFooter>
</DialogContent>
</DialogPopup>
</Dialog>
);
};
@@ -2,22 +2,21 @@
import SettingsHeader from "@calcom/features/settings/appDir/SettingsHeader";
import { useLocale } from "@calcom/lib/hooks/useLocale";
import { SkeletonButton, SkeletonContainer, SkeletonText } from "@calcom/ui/components/skeleton";
import { Skeleton } from "@coss/ui/components/skeleton";
export const SkeletonLoader = () => {
const { t } = useLocale();
return (
<SettingsHeader title={t("general")} description={t("general_description")} borderInShellHeader={true}>
<SkeletonContainer>
<div className="space-y-6">
<div className="border-subtle stack-y-6 rounded-b-xl border border-t-0 px-4 py-8 sm:px-6">
<SkeletonText className="h-8 w-full" />
<SkeletonText className="h-8 w-full" />
<SkeletonText className="h-8 w-full" />
<SkeletonText className="h-8 w-full" />
<SkeletonButton className="ml-auto h-8 w-20 rounded-md p-5" />
<Skeleton className="h-8 w-full" />
<Skeleton className="h-8 w-full" />
<Skeleton className="h-8 w-full" />
<Skeleton className="h-8 w-full" />
<Skeleton className="ml-auto h-8 w-20 rounded-md p-5" />
</div>
</SkeletonContainer>
</div>
</SettingsHeader>
);
};
@@ -204,8 +204,8 @@ const GeneralView = ({ user, travelSchedules }: GeneralViewProps) => {
<Label className="text-emphasis mt-6">
<>{t("timezone")}</>
</Label>
<div className="flex items-end gap-2">
<div className="flex-1">
<div className="flex flex-col gap-2 sm:flex-row sm:items-end">
<div className="w-full sm:w-1/2">
<TimezoneSelect
id="timezone"
value={value}
@@ -219,6 +219,7 @@ const GeneralView = ({ user, travelSchedules }: GeneralViewProps) => {
</div>
{!watchedTzSchedules.length && (
<Button
className="w-full sm:w-1/2"
color="secondary"
StartIcon="calendar"
onClick={() => setIsTZScheduleOpen(true)}
@@ -413,7 +414,7 @@ const GeneralView = ({ user, travelSchedules }: GeneralViewProps) => {
/>
<TravelScheduleModal
open={isTZScheduleOpen}
onOpenChange={() => setIsTZScheduleOpen(false)}
onOpenChange={setIsTZScheduleOpen}
setValue={formMethods.setValue}
existingSchedules={formMethods.getValues("travelSchedules") ?? []}
/>