From d0338dc71b043a9836a2b0857ed54d7121060cde Mon Sep 17 00:00:00 2001 From: Jeroen Reumkens Date: Tue, 1 Nov 2022 14:29:01 +0100 Subject: [PATCH] =?UTF-8?q?=F0=9F=A7=B9=20=20Cleaned=20up=20older=20v1=20c?= =?UTF-8?q?omponents=20for=20availability,=20settings=20and=20auth.=20(#52?= =?UTF-8?q?88)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * Cleaned up older v1 components for availability, settings and auth. * Updated path. --- .../availability/NewScheduleButton.tsx | 86 -- apps/web/components/availability/Schedule.tsx | 350 --------- .../availability/ScheduleListItem.tsx | 83 -- .../availability/SkeletonLoader.tsx | 23 +- .../web/components/booking/SkeletonLoader.tsx | 11 +- .../{v2 => }/settings/CalendarSwitch.tsx | 0 .../settings/DisableTwoFactorModal.tsx | 0 .../settings/EnableTwoFactorModal.tsx | 0 .../{v2 => }/settings/TwoFactorAuthAPI.ts | 0 apps/web/components/ui/AuthContainer.tsx | 20 +- .../v2/availability/SkeletonLoader.tsx | 51 -- .../components/v2/bookings/SkeletonLoader.tsx | 36 - .../v2/eventtype/AvailabilityTab.tsx | 2 +- apps/web/components/v2/ui/AuthContainer.tsx | 42 - apps/web/pages/apps/categories/[category].tsx | 1 - apps/web/pages/apps/installed/[category].tsx | 2 +- apps/web/pages/auth/forgot-password/[id].tsx | 2 +- apps/web/pages/auth/forgot-password/index.tsx | 2 +- apps/web/pages/auth/login.tsx | 2 +- apps/web/pages/auth/logout.tsx | 2 +- apps/web/pages/availability/[schedule].tsx | 2 +- apps/web/pages/availability/index.tsx | 2 +- apps/web/pages/bookings/[status].tsx | 2 +- apps/web/pages/old-getting-started.tsx | 741 ------------------ .../pages/settings/my-account/calendars.tsx | 2 +- .../settings/security/two-factor-auth.tsx | 4 +- 26 files changed, 40 insertions(+), 1428 deletions(-) delete mode 100644 apps/web/components/availability/NewScheduleButton.tsx delete mode 100644 apps/web/components/availability/Schedule.tsx delete mode 100644 apps/web/components/availability/ScheduleListItem.tsx rename apps/web/components/{v2 => }/settings/CalendarSwitch.tsx (100%) rename apps/web/components/{v2 => }/settings/DisableTwoFactorModal.tsx (100%) rename apps/web/components/{v2 => }/settings/EnableTwoFactorModal.tsx (100%) rename apps/web/components/{v2 => }/settings/TwoFactorAuthAPI.ts (100%) delete mode 100644 apps/web/components/v2/availability/SkeletonLoader.tsx delete mode 100644 apps/web/components/v2/bookings/SkeletonLoader.tsx delete mode 100644 apps/web/components/v2/ui/AuthContainer.tsx delete mode 100644 apps/web/pages/old-getting-started.tsx diff --git a/apps/web/components/availability/NewScheduleButton.tsx b/apps/web/components/availability/NewScheduleButton.tsx deleted file mode 100644 index be1dcc967d..0000000000 --- a/apps/web/components/availability/NewScheduleButton.tsx +++ /dev/null @@ -1,86 +0,0 @@ -import { useRouter } from "next/router"; -import { useForm } from "react-hook-form"; - -import { useLocale } from "@calcom/lib/hooks/useLocale"; -import { trpc } from "@calcom/trpc/react"; -import { Button } from "@calcom/ui"; -import { Dialog, DialogClose, DialogContent, DialogTrigger } from "@calcom/ui/Dialog"; -import { Icon } from "@calcom/ui/Icon"; -import { Form } from "@calcom/ui/form/fields"; -import showToast from "@calcom/ui/v2/core/notifications"; - -import { HttpError } from "@lib/core/http/error"; - -export function NewScheduleButton({ name = "new-schedule" }: { name?: string }) { - const router = useRouter(); - const { t } = useLocale(); - - const form = useForm<{ - name: string; - }>(); - const { register } = form; - - const createMutation = trpc.useMutation("viewer.availability.schedule.create", { - onSuccess: async ({ schedule }) => { - await router.push("/availability/" + schedule.id); - showToast(t("schedule_created_successfully", { scheduleName: schedule.name }), "success"); - }, - onError: (err) => { - if (err instanceof HttpError) { - const message = `${err.statusCode}: ${err.message}`; - showToast(message, "error"); - } - - if (err.data?.code === "UNAUTHORIZED") { - const message = `${err.data.code}: You are not able to create this event`; - showToast(message, "error"); - } - }, - }); - - return ( - - - - - -
- -
-
{ - createMutation.mutate(values); - }}> -
- -
- -
-
-
- - - - -
-
-
-
- ); -} diff --git a/apps/web/components/availability/Schedule.tsx b/apps/web/components/availability/Schedule.tsx deleted file mode 100644 index 53020d52ec..0000000000 --- a/apps/web/components/availability/Schedule.tsx +++ /dev/null @@ -1,350 +0,0 @@ -/** - * @deprecated - * use Component in "/packages/features/schedules/components/Schedule"; - **/ -import classNames from "classnames"; -import React, { useCallback, useEffect, useMemo, useState } from "react"; -import { Controller, useFieldArray, useFormContext } from "react-hook-form"; -import { GroupBase, Props } from "react-select"; - -import dayjs, { Dayjs, ConfigType } from "@calcom/dayjs"; -import { useLocale } from "@calcom/lib/hooks/useLocale"; -import Button from "@calcom/ui/Button"; -import Dropdown, { DropdownMenuContent, DropdownMenuTrigger } from "@calcom/ui/Dropdown"; -import { Icon } from "@calcom/ui/Icon"; -import { Tooltip } from "@calcom/ui/Tooltip"; - -import { defaultDayRange } from "@lib/availability"; -import { weekdayNames } from "@lib/core/i18n/weekday"; -import useMeQuery from "@lib/hooks/useMeQuery"; -import { TimeRange } from "@lib/types/schedule"; - -import Select from "@components/ui/form/Select"; - -/** Begin Time Increments For Select */ -const increment = 15; - -type Option = { - readonly label: string; - readonly value: number; -}; - -/** - * Creates an array of times on a 15 minute interval from - * 00:00:00 (Start of day) to - * 23:45:00 (End of day with enough time for 15 min booking) - */ -const useOptions = () => { - // Get user so we can determine 12/24 hour format preferences - const query = useMeQuery(); - const { timeFormat } = query.data || { timeFormat: null }; - - const [filteredOptions, setFilteredOptions] = useState([]); - - const options = useMemo(() => { - const end = dayjs().utc().endOf("day"); - let t: Dayjs = dayjs().utc().startOf("day"); - - const options: Option[] = []; - while (t.isBefore(end)) { - options.push({ - value: t.toDate().valueOf(), - label: dayjs(t) - .utc() - .format(timeFormat === 12 ? "h:mma" : "HH:mm"), - }); - t = t.add(increment, "minutes"); - } - return options; - }, [timeFormat]); - - const filter = useCallback( - ({ offset, limit, current }: { offset?: ConfigType; limit?: ConfigType; current?: ConfigType }) => { - if (current) { - const currentOption = options.find((option) => option.value === dayjs(current).toDate().valueOf()); - if (currentOption) setFilteredOptions([currentOption]); - } else - setFilteredOptions( - options.filter((option) => { - const time = dayjs(option.value); - return (!limit || time.isBefore(limit)) && (!offset || time.isAfter(offset)); - }) - ); - }, - [options] - ); - - return { options: filteredOptions, filter }; -}; - -type TimeRangeFieldProps = { - name: string; - className?: string; -}; - -const LazySelect = ({ - value, - min, - max, - ...props -}: Omit>, "value"> & { - value: ConfigType; - min?: ConfigType; - max?: ConfigType; -}) => { - // Lazy-loaded options, otherwise adding a field has a noticable redraw delay. - const { options, filter } = useOptions(); - - useEffect(() => { - filter({ current: value }); - }, [filter, value]); - - return ( - { - if (e.target.checked && !selected.includes(num)) { - setSelected(selected.concat([num])); - } else if (!e.target.checked && selected.includes(num)) { - setSelected(selected.slice(selected.indexOf(num), 1)); - } - }} - type="checkbox" - className="inline-block rounded-sm border-gray-300 text-neutral-900 focus:ring-neutral-500 disabled:text-neutral-400" - /> - - - ))} - -
- -
- - ); -}; - -/** - * @deprecated - * use Component in "/packages/features/schedules/components/Schedule"; - **/ -export const DayRanges = ({ - name, - defaultValue = [defaultDayRange], -}: { - name: string; - defaultValue?: TimeRange[]; -}) => { - const { setValue, watch } = useFormContext(); - // XXX: Hack to make copying times work; `fields` is out of date until save. - const watcher = watch(name); - const { t } = useLocale(); - const { fields, replace, append, remove } = useFieldArray({ - name, - }); - - useEffect(() => { - if (defaultValue.length && !fields.length) { - replace(defaultValue); - } - }, [replace, defaultValue, fields.length]); - - const handleAppend = () => { - // FIXME: Fix type-inference, can't get this to work. @see https://github.com/react-hook-form/react-hook-form/issues/4499 - const nextRangeStart = dayjs((fields[fields.length - 1] as unknown as TimeRange).end); - const nextRangeEnd = dayjs(nextRangeStart).add(1, "hour"); - - if (nextRangeEnd.isBefore(nextRangeStart.endOf("day"))) { - return append({ - start: nextRangeStart.toDate(), - end: nextRangeEnd.toDate(), - }); - } - }; - - return ( -
- {fields.map((field, index) => ( -
-
- -
- {index === 0 && ( -
- -
- )} -
- ))} -
- ); -}; - -const ScheduleBlock = ({ name, day, weekday }: ScheduleBlockProps) => { - const { t } = useLocale(); - - const form = useFormContext(); - const watchAvailable = form.watch(`${name}.${day}`, []); - - return ( -
- - {!!watchAvailable.length && ( -
- -
- )} -
- ); -}; - -/** - * @deprecated - * use Component in "/packages/features/schedules/components/Schedule"; - **/ -const Schedule = ({ name }: { name: string }) => { - const { i18n } = useLocale(); - return ( -
- {weekdayNames(i18n.language).map((weekday, num) => ( - - ))} -
- ); -}; - -export default Schedule; diff --git a/apps/web/components/availability/ScheduleListItem.tsx b/apps/web/components/availability/ScheduleListItem.tsx deleted file mode 100644 index b4e20d80e2..0000000000 --- a/apps/web/components/availability/ScheduleListItem.tsx +++ /dev/null @@ -1,83 +0,0 @@ -/** - * @deprecated modifications to this file should be v2 only - * Use `/packages/features/schedules/components/ScheduleListItem.tsx` instead - */ -import Link from "next/link"; -import { Fragment } from "react"; - -import { availabilityAsString } from "@calcom/lib/availability"; -import { useLocale } from "@calcom/lib/hooks/useLocale"; -import { Availability } from "@calcom/prisma/client"; -import { inferQueryOutput } from "@calcom/trpc/react"; -import { Button } from "@calcom/ui"; -import Dropdown, { DropdownMenuContent, DropdownMenuItem, DropdownMenuTrigger } from "@calcom/ui/Dropdown"; -import { Icon } from "@calcom/ui/Icon"; - -/** - * @deprecated modifications to this file should be v2 only - * Use `/packages/features/schedules/components/ScheduleListItem.tsx` instead - */ -export function ScheduleListItem({ - schedule, - deleteFunction, - isDeleting = false, -}: { - schedule: inferQueryOutput<"viewer.availability.list">["schedules"][number]; - deleteFunction: ({ scheduleId }: { scheduleId: number }) => void; - isDeleting: boolean; -}) { - const { t, i18n } = useLocale(); - - return ( -
  • -
    - - - - - - - - - - - - -
    -
  • - ); -} diff --git a/apps/web/components/availability/SkeletonLoader.tsx b/apps/web/components/availability/SkeletonLoader.tsx index c0fe6da745..d5c75a6b41 100644 --- a/apps/web/components/availability/SkeletonLoader.tsx +++ b/apps/web/components/availability/SkeletonLoader.tsx @@ -1,8 +1,9 @@ import React from "react"; -import { SkeletonText } from "@calcom/ui"; +import { SkeletonText } from "@calcom/ui/v2"; + +import classNames from "@lib/classNames"; -/** @deprecated Use `apps/web/components/v2/availability/SkeletonLoader.tsx` */ function SkeletonLoader() { return (
      @@ -20,26 +21,30 @@ function SkeletonItem() {
    • - - + +
      - +
    • ); } -export const AvailabilitySelectSkeletonLoader = () => { +export const SelectSkeletonLoader = ({ className }: { className?: string }) => { return ( -
    • +
    • - - + +
    • diff --git a/apps/web/components/booking/SkeletonLoader.tsx b/apps/web/components/booking/SkeletonLoader.tsx index 354e8777c8..e9cb916267 100644 --- a/apps/web/components/booking/SkeletonLoader.tsx +++ b/apps/web/components/booking/SkeletonLoader.tsx @@ -1,8 +1,7 @@ import React from "react"; -import { SkeletonText } from "@calcom/ui"; +import { SkeletonText } from "@calcom/ui/v2"; -/** @deprecated Use `apps/web/components/v2/bookings/SkeletonLoader.tsx` */ function SkeletonLoader() { return (
        @@ -21,15 +20,15 @@ function SkeletonItem() {
        - - + +
        - - + +
        diff --git a/apps/web/components/v2/settings/CalendarSwitch.tsx b/apps/web/components/settings/CalendarSwitch.tsx similarity index 100% rename from apps/web/components/v2/settings/CalendarSwitch.tsx rename to apps/web/components/settings/CalendarSwitch.tsx diff --git a/apps/web/components/v2/settings/DisableTwoFactorModal.tsx b/apps/web/components/settings/DisableTwoFactorModal.tsx similarity index 100% rename from apps/web/components/v2/settings/DisableTwoFactorModal.tsx rename to apps/web/components/settings/DisableTwoFactorModal.tsx diff --git a/apps/web/components/v2/settings/EnableTwoFactorModal.tsx b/apps/web/components/settings/EnableTwoFactorModal.tsx similarity index 100% rename from apps/web/components/v2/settings/EnableTwoFactorModal.tsx rename to apps/web/components/settings/EnableTwoFactorModal.tsx diff --git a/apps/web/components/v2/settings/TwoFactorAuthAPI.ts b/apps/web/components/settings/TwoFactorAuthAPI.ts similarity index 100% rename from apps/web/components/v2/settings/TwoFactorAuthAPI.ts rename to apps/web/components/settings/TwoFactorAuthAPI.ts diff --git a/apps/web/components/ui/AuthContainer.tsx b/apps/web/components/ui/AuthContainer.tsx index 03750a87a9..f34f51b7f7 100644 --- a/apps/web/components/ui/AuthContainer.tsx +++ b/apps/web/components/ui/AuthContainer.tsx @@ -17,27 +17,25 @@ interface Props { export default function AuthContainer(props: React.PropsWithChildren) { return ( -
        +
        + {props.showLogo && ( + // eslint-disable-next-line @next/next/no-img-element + Cal.com Logo + )}
        - {props.showLogo && ( - // eslint-disable-next-line @next/next/no-img-element - Cal.com Logo - )} - {props.heading && ( -

        {props.heading}

        - )} + {props.heading &&

        {props.heading}

        }
        {props.loading && (
        )} -
        -
        +
        +
        {props.children}
        -
        {props.footerText}
        +
        {props.footerText}
        ); diff --git a/apps/web/components/v2/availability/SkeletonLoader.tsx b/apps/web/components/v2/availability/SkeletonLoader.tsx deleted file mode 100644 index 7b984652df..0000000000 --- a/apps/web/components/v2/availability/SkeletonLoader.tsx +++ /dev/null @@ -1,51 +0,0 @@ -import classNames from "classnames"; -import React from "react"; - -import { SkeletonText } from "@calcom/ui/v2"; - -function SkeletonLoader() { - return ( -
          - - - -
        - ); -} - -export default SkeletonLoader; - -function SkeletonItem() { - return ( -
      • -
        -
        - - -
        -
        -
        -
        - -
        -
        -
      • - ); -} - -export const SelectSkeletonLoader = ({ className }: { className?: string }) => { - return ( -
      • -
        -
        - - -
        -
        -
      • - ); -}; diff --git a/apps/web/components/v2/bookings/SkeletonLoader.tsx b/apps/web/components/v2/bookings/SkeletonLoader.tsx deleted file mode 100644 index e9cb916267..0000000000 --- a/apps/web/components/v2/bookings/SkeletonLoader.tsx +++ /dev/null @@ -1,36 +0,0 @@ -import React from "react"; - -import { SkeletonText } from "@calcom/ui/v2"; - -function SkeletonLoader() { - return ( -
          - - - -
        - ); -} - -export default SkeletonLoader; - -function SkeletonItem() { - return ( -
      • -
        -
        -
        - - -
        -
        -
        -
        -
        - - -
        -
        -
      • - ); -} diff --git a/apps/web/components/v2/eventtype/AvailabilityTab.tsx b/apps/web/components/v2/eventtype/AvailabilityTab.tsx index aa130bbca8..c6fad16b1f 100644 --- a/apps/web/components/v2/eventtype/AvailabilityTab.tsx +++ b/apps/web/components/v2/eventtype/AvailabilityTab.tsx @@ -13,7 +13,7 @@ import Button from "@calcom/ui/v2/core/Button"; import Select from "@calcom/ui/v2/core/form/select"; import { SkeletonText } from "@calcom/ui/v2/core/skeleton"; -import { SelectSkeletonLoader } from "@components/v2/availability/SkeletonLoader"; +import { SelectSkeletonLoader } from "@components/availability/SkeletonLoader"; type AvailabilityOption = { label: string; diff --git a/apps/web/components/v2/ui/AuthContainer.tsx b/apps/web/components/v2/ui/AuthContainer.tsx deleted file mode 100644 index f34f51b7f7..0000000000 --- a/apps/web/components/v2/ui/AuthContainer.tsx +++ /dev/null @@ -1,42 +0,0 @@ -import classNames from "classnames"; -import React from "react"; - -import { LOGO } from "@calcom/lib/constants"; - -import Loader from "@components/Loader"; -import { HeadSeo } from "@components/seo/head-seo"; - -interface Props { - title: string; - description: string; - footerText?: React.ReactNode | string; - showLogo?: boolean; - heading?: string; - loading?: boolean; -} - -export default function AuthContainer(props: React.PropsWithChildren) { - return ( -
        - - {props.showLogo && ( - // eslint-disable-next-line @next/next/no-img-element - Cal.com Logo - )} -
        - {props.heading &&

        {props.heading}

        } -
        - {props.loading && ( -
        - -
        - )} -
        -
        - {props.children} -
        -
        {props.footerText}
        -
        -
        - ); -} diff --git a/apps/web/pages/apps/categories/[category].tsx b/apps/web/pages/apps/categories/[category].tsx index be7872d538..966ee1d35d 100644 --- a/apps/web/pages/apps/categories/[category].tsx +++ b/apps/web/pages/apps/categories/[category].tsx @@ -6,7 +6,6 @@ import { useRouter } from "next/router"; import { getAppRegistry } from "@calcom/app-store/_appRegistry"; import { useLocale } from "@calcom/lib/hooks/useLocale"; import prisma from "@calcom/prisma"; -import { Icon } from "@calcom/ui/Icon"; import { SkeletonText } from "@calcom/ui/v2"; import Shell from "@calcom/ui/v2/core/Shell"; import AppCard from "@calcom/ui/v2/core/apps/AppCard"; diff --git a/apps/web/pages/apps/installed/[category].tsx b/apps/web/pages/apps/installed/[category].tsx index 440a3bca7e..f3cffac435 100644 --- a/apps/web/pages/apps/installed/[category].tsx +++ b/apps/web/pages/apps/installed/[category].tsx @@ -21,7 +21,7 @@ import { QueryCell } from "@lib/QueryCell"; import { CalendarListContainer } from "@components/apps/CalendarListContainer"; import IntegrationListItem from "@components/apps/IntegrationListItem"; -import SkeletonLoader from "@components/v2/availability/SkeletonLoader"; +import SkeletonLoader from "@components/availability/SkeletonLoader"; function ConnectOrDisconnectIntegrationButton(props: { credentialIds: number[]; diff --git a/apps/web/pages/auth/forgot-password/[id].tsx b/apps/web/pages/auth/forgot-password/[id].tsx index d0d34297e6..6eb1191c91 100644 --- a/apps/web/pages/auth/forgot-password/[id].tsx +++ b/apps/web/pages/auth/forgot-password/[id].tsx @@ -12,7 +12,7 @@ import { Button, Input, TextField } from "@calcom/ui/v2"; import { useLocale } from "@lib/hooks/useLocale"; import { HeadSeo } from "@components/seo/head-seo"; -import AuthContainer from "@components/v2/ui/AuthContainer"; +import AuthContainer from "@components/ui/AuthContainer"; type Props = { id: string; diff --git a/apps/web/pages/auth/forgot-password/index.tsx b/apps/web/pages/auth/forgot-password/index.tsx index 51b23a208e..b35e4c5c57 100644 --- a/apps/web/pages/auth/forgot-password/index.tsx +++ b/apps/web/pages/auth/forgot-password/index.tsx @@ -10,7 +10,7 @@ import { EmailField, Button } from "@calcom/ui/v2"; import { getSession } from "@lib/auth"; import { useLocale } from "@lib/hooks/useLocale"; -import AuthContainer from "@components/v2/ui/AuthContainer"; +import AuthContainer from "@components/ui/AuthContainer"; export default function ForgotPassword({ csrfToken }: { csrfToken: string }) { const { t, i18n } = useLocale(); diff --git a/apps/web/pages/auth/login.tsx b/apps/web/pages/auth/login.tsx index 6480c328fa..74637779dc 100644 --- a/apps/web/pages/auth/login.tsx +++ b/apps/web/pages/auth/login.tsx @@ -23,7 +23,7 @@ import { inferSSRProps } from "@lib/types/inferSSRProps"; import AddToHomescreen from "@components/AddToHomescreen"; import TwoFactor from "@components/auth/TwoFactor"; -import AuthContainer from "@components/v2/ui/AuthContainer"; +import AuthContainer from "@components/ui/AuthContainer"; import { IS_GOOGLE_LOGIN_ENABLED } from "@server/lib/constants"; import { ssrInit } from "@server/lib/ssr"; diff --git a/apps/web/pages/auth/logout.tsx b/apps/web/pages/auth/logout.tsx index 5eac3b48c0..55d1935062 100644 --- a/apps/web/pages/auth/logout.tsx +++ b/apps/web/pages/auth/logout.tsx @@ -10,7 +10,7 @@ import Button from "@calcom/ui/v2/core/Button"; import { inferSSRProps } from "@lib/types/inferSSRProps"; -import AuthContainer from "@components/v2/ui/AuthContainer"; +import AuthContainer from "@components/ui/AuthContainer"; import { ssrInit } from "@server/lib/ssr"; diff --git a/apps/web/pages/availability/[schedule].tsx b/apps/web/pages/availability/[schedule].tsx index 5131a05d0b..b16781b9f3 100644 --- a/apps/web/pages/availability/[schedule].tsx +++ b/apps/web/pages/availability/[schedule].tsx @@ -23,8 +23,8 @@ import { Skeleton, SkeletonText } from "@calcom/ui/v2/core/skeleton"; import { HttpError } from "@lib/core/http/error"; +import { SelectSkeletonLoader } from "@components/availability/SkeletonLoader"; import EditableHeading from "@components/ui/EditableHeading"; -import { SelectSkeletonLoader } from "@components/v2/availability/SkeletonLoader"; const querySchema = z.object({ schedule: stringOrNumber, diff --git a/apps/web/pages/availability/index.tsx b/apps/web/pages/availability/index.tsx index f7595ab4ff..719f5b648c 100644 --- a/apps/web/pages/availability/index.tsx +++ b/apps/web/pages/availability/index.tsx @@ -10,7 +10,7 @@ import { EmptyScreen, showToast } from "@calcom/ui/v2"; import { withQuery } from "@lib/QueryCell"; import { HttpError } from "@lib/core/http/error"; -import SkeletonLoader from "@components/v2/availability/SkeletonLoader"; +import SkeletonLoader from "@components/availability/SkeletonLoader"; export function AvailabilityList({ schedules }: inferQueryOutput<"viewer.availability.list">) { const { t } = useLocale(); diff --git a/apps/web/pages/bookings/[status].tsx b/apps/web/pages/bookings/[status].tsx index d618059202..a1f943ccc1 100644 --- a/apps/web/pages/bookings/[status].tsx +++ b/apps/web/pages/bookings/[status].tsx @@ -15,7 +15,7 @@ import BookingLayout from "@calcom/ui/v2/core/layouts/BookingLayout"; import { useInViewObserver } from "@lib/hooks/useInViewObserver"; import BookingListItem from "@components/booking/BookingListItem"; -import SkeletonLoader from "@components/v2/bookings/SkeletonLoader"; +import SkeletonLoader from "@components/booking/SkeletonLoader"; type BookingListingStatus = inferQueryInput<"viewer.bookings">["status"]; type BookingOutput = inferQueryOutput<"viewer.bookings">["bookings"][0]; diff --git a/apps/web/pages/old-getting-started.tsx b/apps/web/pages/old-getting-started.tsx deleted file mode 100644 index c78eb8ba13..0000000000 --- a/apps/web/pages/old-getting-started.tsx +++ /dev/null @@ -1,741 +0,0 @@ -// @@DEPRECATED, use new getting-started.tsx instead -import { zodResolver } from "@hookform/resolvers/zod"; -import { Prisma } from "@prisma/client"; -import classnames from "classnames"; -import debounce from "lodash/debounce"; -import omit from "lodash/omit"; -import { NextPageContext } from "next"; -import { useSession } from "next-auth/react"; -import Head from "next/head"; -import { useRouter } from "next/router"; -import React, { useCallback, useEffect, useRef, useState } from "react"; -import { useForm } from "react-hook-form"; -import * as z from "zod"; - -import getApps from "@calcom/app-store/utils"; -import dayjs from "@calcom/dayjs"; -import { DEFAULT_SCHEDULE } from "@calcom/lib/availability"; -import { DOCS_URL } from "@calcom/lib/constants"; -import { fetchUsername } from "@calcom/lib/fetchUsername"; -import { useLocale } from "@calcom/lib/hooks/useLocale"; -import { collectPageParameters, telemetryEventTypes, useTelemetry } from "@calcom/lib/telemetry"; -import prisma from "@calcom/prisma"; -import { TRPCClientErrorLike } from "@calcom/trpc/client"; -import { trpc } from "@calcom/trpc/react"; -import type { AppRouter } from "@calcom/trpc/server/routers/_app"; -import { Alert } from "@calcom/ui/Alert"; -import Button from "@calcom/ui/Button"; -import { Icon } from "@calcom/ui/Icon"; -import TimezoneSelect from "@calcom/ui/form/TimezoneSelect"; -import { Form } from "@calcom/ui/form/fields"; -import showToast from "@calcom/ui/v2/core/notifications"; - -import { getSession } from "@lib/auth"; -import { inferSSRProps } from "@lib/types/inferSSRProps"; -import { Schedule as ScheduleType } from "@lib/types/schedule"; - -import { ClientSuspense } from "@components/ClientSuspense"; -import Loader from "@components/Loader"; -import Schedule from "@components/availability/Schedule"; -import { CalendarListContainer } from "@components/integrations/CalendarListContainer"; -import { UsernameAvailability } from "@components/ui/UsernameAvailability"; - -// Embed isn't applicable to onboarding, so ignore the rule -/* eslint-disable @calcom/eslint/avoid-web-storage */ - -export interface ScheduleFormValues { - schedule: ScheduleType; -} - -let mutationComplete: ((err: Error | null) => void) | null; - -export default function Onboarding(props: inferSSRProps) { - const { t } = useLocale(); - const { user } = props; - const router = useRouter(); - const utils = trpc.useContext(); - const telemetry = useTelemetry(); - const [hasErrors, setHasErrors] = useState(false); - const [errorMessage, setErrorMessage] = useState(""); - const { data: eventTypes } = trpc.useQuery(["viewer.eventTypes.list"]); - const onSuccessMutation = async () => { - showToast(t("your_user_profile_updated_successfully"), "success"); - setHasErrors(false); // dismiss any open errors - await utils.invalidateQueries(["viewer.me"]); - }; - - const onErrorMutation = (error: TRPCClientErrorLike) => { - setHasErrors(true); - setErrorMessage(error.message); - document?.getElementsByTagName("main")[0]?.scrollTo({ top: 0, behavior: "smooth" }); - }; - const mutation = trpc.useMutation("viewer.updateProfile", { - onSuccess: async () => { - setSubmitting(true); - setEnteredName(nameRef.current?.value || ""); - setInputUsernameValue(usernameRef.current?.value || ""); - if (mutationComplete) { - mutationComplete(null); - mutationComplete = null; - } - setSubmitting(false); - }, - onError: (err: TRPCClientErrorLike) => { - setError(new Error(err.message)); - if (mutationComplete) { - mutationComplete(new Error(err.message)); - } - setSubmitting(false); - }, - }); - - const DEFAULT_EVENT_TYPES = [ - { - title: t("15min_meeting"), - slug: "15min", - length: 15, - }, - { - title: t("30min_meeting"), - slug: "30min", - length: 30, - }, - { - title: t("secret_meeting"), - slug: "secret", - length: 15, - hidden: true, - }, - ]; - - const [isSubmitting, setSubmitting] = React.useState(false); - const [enteredName, setEnteredName] = React.useState(""); - const [currentUsername, setCurrentUsername] = useState(user.username || undefined); - const [inputUsernameValue, setInputUsernameValue] = useState(currentUsername); - - const { status } = useSession(); - const loading = status === "loading"; - const [ready, setReady] = useState(false); - const [selectedImport, setSelectedImport] = useState(""); - const [error, setError] = useState(null); - - const updateUser = useCallback( - async (data: Prisma.UserUpdateInput) => { - const res = await fetch(`/api/user/${user.id}`, { - method: "PATCH", - body: JSON.stringify({ data: { ...data } }), - headers: { - "Content-Type": "application/json", - }, - }); - - if (!res.ok) { - throw new Error((await res.json()).message); - } - const responseData = await res.json(); - return responseData.data; - }, - [user.id] - ); - - const createEventType = trpc.useMutation("viewer.eventTypes.create"); - - const createSchedule = trpc.useMutation("viewer.availability.schedule.create", { - onError: (error: TRPCClientErrorLike) => { - throw new Error(error.message); - }, - }); - - /** Name */ - const nameRef = useRef(null); - /** Username */ - const usernameRef = useRef(null!); - const bioRef = useRef(null); - /** End Name */ - /** TimeZone */ - const [selectedTimeZone, setSelectedTimeZone] = useState(dayjs.tz.guess()); - /** End TimeZone */ - - /** Onboarding Steps */ - const [currentStep, setCurrentStep] = useState(props.initialStep); - const handleConfirmStep = async () => { - try { - setSubmitting(true); - const onComplete = steps[currentStep]?.onComplete; - if (onComplete) { - await onComplete(); - } - incrementStep(); - setSubmitting(false); - } catch (error) { - setSubmitting(false); - setError(error as Error); - } - }; - - const debouncedHandleConfirmStep = debounce(handleConfirmStep, 850); - - const handleSkipStep = () => { - incrementStep(); - }; - - const incrementStep = () => { - const nextStep = currentStep + 1; - - if (nextStep >= steps.length) { - completeOnboarding(); - return; - } - setCurrentStep(nextStep); - }; - - const decrementStep = () => { - const previous = currentStep - 1; - - if (previous < 0) { - return; - } - setCurrentStep(previous); - }; - - const goToStep = (step: number) => { - setCurrentStep(step); - }; - - /** - * Complete Onboarding finalizes the onboarding flow for a new user. - * - * Here, 3 event types are pre-created for the user as well. - * Set to the availability the user enter during the onboarding. - * - * If a user skips through the Onboarding flow, - * then the default availability is applied. - */ - const completeOnboarding = async () => { - setSubmitting(true); - if (eventTypes?.length === 0) { - await Promise.all( - DEFAULT_EVENT_TYPES.map(async (event) => { - return createEventType.mutate(event); - }) - ); - } - await updateUser({ - completedOnboarding: true, - }); - - setSubmitting(false); - router.push("/event-types"); - }; - - const schema = z.object({ - token: z.string(), - }); - - const formMethods = useForm<{ - token: string; - }>({ resolver: zodResolver(schema), mode: "onSubmit" }); - - // Should update username on user when being redirected from sign up and doing google/saml - useEffect(() => { - async function validateAndSave(username: string) { - const { data } = await fetchUsername(username); - - // Only persist username if its available and not premium - // premium usernames are saved via stripe webhook - if (data.available && !data.premium) { - await updateUser({ - username, - }); - } - // Remove it from localStorage - window.localStorage.removeItem("username"); - return; - } - - // Looking for username on localStorage - const username = window.localStorage.getItem("username"); - if (username) { - validateAndSave(username); - } - }, [updateUser]); - - const availabilityForm = useForm({ defaultValues: { schedule: DEFAULT_SCHEDULE } }); - const steps = [ - { - id: t("welcome"), - title: t("welcome_to_calcom"), - description: t("welcome_instructions"), - Component: ( - <> - {/** @NOTE: Hiding temporarily - * @URL related: https://github.com/calcom/cal.com/issues/3941 - */} - {/* {selectedImport == "" && ( -
        - - -
        - )} */} - {/* {selectedImport && ( -
        -

        - {t("import_from")} {selectedImport === "calendly" ? "Calendly" : "SavvyCal"} -

        -

        - {t("you_will_need_to_generate")}. Find out how to do this{" "} - here. -

        -
        { - // track the number of imports. Without personal data/payload - telemetry.event(telemetryEventTypes.importSubmitted, { - ...collectPageParameters(), - selectedImport, - }); - setSubmitting(true); - const response = await fetch(`/api/import/${selectedImport}`, { - method: "POST", - body: JSON.stringify({ - token: values.token, - }), - headers: { - "Content-Type": "application/json", - }, - }); - if (response.status === 201) { - setSubmitting(false); - handleSkipStep(); - } else { - await response.json().catch((e) => { - console.log("Error: response.json invalid: " + e); - setSubmitting(false); - }); - } - })}> - {hasErrors && } - - { - formMethods.setValue("token", e.target.value); - }} - type="text" - name="token" - id="token" - placeholder={t("access_token")} - required - className="mt-1 block w-full rounded-sm border border-gray-300 px-3 py-2 focus:border-neutral-500 focus:outline-none focus:ring-neutral-500 sm:text-sm" - /> - - -
        - )} -
        - - */} -
        -
        -
        - {user.username !== "" && ( - - )} -
        -
        - - -
        - -
        -
        - -

        - {t("current_time")}:  - {dayjs().tz(selectedTimeZone).format("LT")} -

        -
        - setSelectedTimeZone(value)} - className="mt-1 block w-full rounded-md border-gray-300 focus:border-blue-500 focus:ring-blue-500 sm:text-sm" - /> -
        -
        -
        - - ), - hideConfirm: false, - confirmText: t("continue"), - showCancel: true, - cancelText: t("set_up_later"), - onComplete: async () => { - mutationComplete = null; - setError(null); - const mutationAsync = new Promise((resolve, reject) => { - mutationComplete = (err) => { - if (err) { - reject(err); - return; - } - resolve(null); - }; - }); - - const userUpdateData = { - name: nameRef.current?.value, - username: usernameRef.current?.value, - timeZone: selectedTimeZone, - }; - - mutation.mutate(userUpdateData); - - if (mutationComplete) { - await mutationAsync; - } - }, - }, - { - id: "connect-calendar", - title: t("connect_your_calendar"), - description: t("connect_your_calendar_instructions"), - Component: ( -
        - }> - - -
        - -
        -
        - ), - hideConfirm: true, - confirmText: t("continue"), - showCancel: true, - cancelText: t("continue_without_calendar"), - }, - { - id: "set-availability", - title: t("set_availability"), - description: t("set_availability_instructions"), - Component: ( - - className="mx-auto max-w-lg bg-white text-black dark:bg-opacity-5 dark:text-white" - form={availabilityForm} - handleSubmit={async (values) => { - try { - setSubmitting(true); - await createSchedule.mutate({ - name: t("default_schedule_name"), - ...values, - }); - debouncedHandleConfirmStep(); - setSubmitting(false); - } catch (error) { - if (error instanceof Error) { - setError(error); - } - } - }}> -
        - -
        - -
        -
        - - ), - hideConfirm: true, - showCancel: false, - }, - { - id: "profile", - title: t("nearly_there"), - description: t("nearly_there_instructions"), - Component: ( -
        -
        -
        - - -
        -
        - - -

        - {t("few_sentences_about_yourself")} -

        -
        -
        -
        - ), - hideConfirm: false, - confirmText: t("finish"), - showCancel: true, - cancelText: t("set_up_later"), - onComplete: async () => { - try { - setSubmitting(true); - await updateUser({ - bio: bioRef.current?.value, - }); - setSubmitting(false); - } catch (error) { - setError(error as Error); - setSubmitting(false); - } - }, - }, - ]; - /** End Onboarding Steps */ - - useEffect(() => { - setReady(true); - // eslint-disable-next-line react-hooks/exhaustive-deps - }, []); - - if (loading || !ready) { - return
        ; - } - - return ( -
        - - Cal.com - {t("getting_started")} - - - - {isSubmitting && ( -
        - -
        - )} -
        -
        -
        -
        -

        {steps[currentStep].title}

        -

        {steps[currentStep].description}

        -
        -
        -

        - Step {currentStep + 1} of {steps.length} -

        - - {error && } - -
        - {steps.map((s, index) => { - return index <= currentStep ? ( -
        goToStep(index)} - className={classnames( - "h-1 w-1/4 bg-white", - index < currentStep ? "cursor-pointer" : "" - )} - /> - ) : ( -
        - ); - })} -
        -
        -
        -
        - {steps[currentStep].Component} - - {!steps[currentStep].hideConfirm && ( -
        - -
        - )} -
        -
        -
        - - {currentStep !== 0 && ( - - )} -
        -
        -
        -
        -
        - ); -} - -export async function getServerSideProps(context: NextPageContext) { - const session = await getSession(context); - - if (!session?.user?.id) { - return { - redirect: { - permanent: false, - destination: "/auth/login", - }, - }; - } - const user = await prisma.user.findFirst({ - where: { - id: session.user.id, - }, - select: { - id: true, - startTime: true, - endTime: true, - username: true, - name: true, - email: true, - bio: true, - avatar: true, - timeZone: true, - identityProvider: true, - completedOnboarding: true, - weekStart: true, - hideBranding: true, - theme: true, - plan: true, - brandColor: true, - darkBrandColor: true, - metadata: true, - timeFormat: true, - allowDynamicBooking: true, - selectedCalendars: { - select: { - externalId: true, - integration: true, - }, - }, - credentials: { - where: { - userId: session.user.id, - }, - select: { - id: true, - type: true, - key: true, - userId: true, - appId: true, - }, - }, - schedules: { - where: { - userId: session.user.id, - }, - select: { - id: true, - }, - }, - }, - }); - if (!user) { - throw new Error(`Signed in as ${session.user.id} but cannot be found in db`); - } - - if (user.completedOnboarding) { - return { - redirect: { - permanent: false, - destination: "/event-types", - }, - }; - } - - const integrations = getApps(user.credentials) - .filter((item) => item.type.endsWith("_calendar")) - .map((item) => omit(item, "key")); - const { schedules } = user; - const hasConfigureCalendar = integrations.some((integration) => integration.credential !== null); - const hasSchedules = schedules && schedules.length > 0; - - return { - props: { - session, - user, - initialStep: hasSchedules ? (hasConfigureCalendar ? 2 : 3) : 0, - }, - }; -} diff --git a/apps/web/pages/settings/my-account/calendars.tsx b/apps/web/pages/settings/my-account/calendars.tsx index 968000dd4c..b3ff71a87d 100644 --- a/apps/web/pages/settings/my-account/calendars.tsx +++ b/apps/web/pages/settings/my-account/calendars.tsx @@ -18,7 +18,7 @@ import DisconnectIntegration from "@calcom/ui/v2/modules/integrations/Disconnect import { QueryCell } from "@lib/QueryCell"; -import { CalendarSwitch } from "@components/v2/settings/CalendarSwitch"; +import { CalendarSwitch } from "@components/settings/CalendarSwitch"; const SkeletonLoader = () => { return ( diff --git a/apps/web/pages/settings/security/two-factor-auth.tsx b/apps/web/pages/settings/security/two-factor-auth.tsx index 420c5483d4..ba81a3e293 100644 --- a/apps/web/pages/settings/security/two-factor-auth.tsx +++ b/apps/web/pages/settings/security/two-factor-auth.tsx @@ -8,8 +8,8 @@ import Meta from "@calcom/ui/v2/core/Meta"; import Switch from "@calcom/ui/v2/core/Switch"; import { getLayout } from "@calcom/ui/v2/core/layouts/SettingsLayout"; -import DisableTwoFactorModal from "@components/v2/settings/DisableTwoFactorModal"; -import EnableTwoFactorModal from "@components/v2/settings/EnableTwoFactorModal"; +import DisableTwoFactorModal from "@components/settings/DisableTwoFactorModal"; +import EnableTwoFactorModal from "@components/settings/EnableTwoFactorModal"; const TwoFactorAuthView = () => { const utils = trpc.useContext();