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:
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") ?? []}
|
||||
/>
|
||||
|
||||
Reference in new issue
Block a user