diff --git a/.env.example b/.env.example index daa0dc0a52..0a75c7390a 100644 --- a/.env.example +++ b/.env.example @@ -79,6 +79,9 @@ CALENDSO_ENCRYPTION_KEY= # Intercom Config NEXT_PUBLIC_INTERCOM_APP_ID= +# Secret to enable Intercom Identity Verification +INTERCOM_SECRET= + # Zendesk Config NEXT_PUBLIC_ZENDESK_KEY= @@ -160,9 +163,6 @@ CLOSECOM_API_KEY= # Sendgrid internal sync service SENDGRID_SYNC_API_KEY= -# Sentry -NEXT_PUBLIC_SENTRY_DSN= -SENTRY_IGNORE_API_RESOLUTION_ERROR= # Change your Brand NEXT_PUBLIC_APP_NAME="Cal.com" @@ -178,4 +178,4 @@ CSP_POLICY= # Vercel Edge Config EDGE_CONFIG= -NEXT_PUBLIC_MINUTES_TO_BOOK=5 # Minutes \ No newline at end of file +NEXT_PUBLIC_MINUTES_TO_BOOK=5 # Minutes diff --git a/.github/workflows/cron-stale-issue.yml b/.github/workflows/cron-stale-issue.yml index 900f4c0bd9..b8ee224654 100644 --- a/.github/workflows/cron-stale-issue.yml +++ b/.github/workflows/cron-stale-issue.yml @@ -2,6 +2,7 @@ name: Cron - mark stale for inactive issues permissions: issues: write + pull-requests: write on: # "Scheduled workflows run on the latest commit on the default or base branch." diff --git a/.github/workflows/nextjs-bundle-analysis.yml b/.github/workflows/nextjs-bundle-analysis.yml index e5586be406..1e5d821a01 100644 --- a/.github/workflows/nextjs-bundle-analysis.yml +++ b/.github/workflows/nextjs-bundle-analysis.yml @@ -24,7 +24,7 @@ jobs: - name: Analyze bundle run: | cd apps/web - npx -p nextjs-bundle-analysis report + npx -p nextjs-bundle-analysis@0.5.0 report - name: Upload bundle uses: actions/upload-artifact@v2 @@ -76,7 +76,7 @@ jobs: id: fc with: issue-number: ${{ github.event.number }} - body-includes: "" + body-includes: "" - name: Create Comment uses: peter-evans/create-or-update-comment@v1.4.4 diff --git a/.github/workflows/pr.yml b/.github/workflows/pr.yml index 6a9d968c05..16f119312b 100644 --- a/.github/workflows/pr.yml +++ b/.github/workflows/pr.yml @@ -2,7 +2,6 @@ name: PR Update on: pull_request_target: - types: [ready_for_review, review_requested] branches: - main paths-ignore: diff --git a/apps/storybook/.storybook/main.js b/apps/storybook/.storybook/main.js index 3b7382f1ed..9b0d21eada 100644 --- a/apps/storybook/.storybook/main.js +++ b/apps/storybook/.storybook/main.js @@ -4,6 +4,7 @@ module.exports = { stories: [ "../intro.stories.mdx", "../../../packages/ui/components/**/*.stories.mdx", + "../../../packages/atoms/**/*.stories.mdx", "../../../packages/features/**/*.stories.mdx", "../../../packages/ui/components/**/*.stories.@(js|jsx|ts|tsx)", ], @@ -70,4 +71,5 @@ module.exports = { return config; }, + typescript: { reactDocgen: 'react-docgen' } }; diff --git a/apps/web/.gitignore b/apps/web/.gitignore index e9c028b5aa..520a11d78c 100644 --- a/apps/web/.gitignore +++ b/apps/web/.gitignore @@ -68,5 +68,3 @@ public/embed # Copied app-store images public/app-store -# Sentry -.sentryclirc diff --git a/apps/web/components/I18nLanguageHandler.tsx b/apps/web/components/I18nLanguageHandler.tsx index 3072b3504c..f7c9ba2930 100644 --- a/apps/web/components/I18nLanguageHandler.tsx +++ b/apps/web/components/I18nLanguageHandler.tsx @@ -1,7 +1,7 @@ +import { isEmpty } from "lodash"; import { useTranslation } from "next-i18next"; import { useEffect } from "react"; -import { getDirFromLang } from "@calcom/lib/i18n"; import { trpc } from "@calcom/trpc/react"; export function useViewerI18n() { @@ -22,16 +22,18 @@ export function useViewerI18n() { */ const I18nLanguageHandler = (): null => { const { i18n } = useTranslation("common"); - const locale = useViewerI18n().data?.locale || "en"; + const locale = useViewerI18n().data?.locale || i18n.language; useEffect(() => { - if (locale && i18n.language && i18n.language !== locale) { - if (typeof i18n.changeLanguage === "function") i18n.changeLanguage(locale); + // bail early when i18n = {} + if (isEmpty(i18n)) return; + // if locale is ready and the i18n.language does != locale - changeLanguage + if (locale && i18n.language !== locale) { + i18n.changeLanguage(locale); } - - const dir = getDirFromLang(locale); + // set dir="rtl|ltr" + document.dir = i18n.dir(); document.documentElement.setAttribute("lang", locale); - document.documentElement.setAttribute("dir", dir); }, [locale, i18n]); return null; diff --git a/apps/web/components/ImageUploader.tsx b/apps/web/components/ImageUploader.tsx deleted file mode 100644 index d4c133154c..0000000000 --- a/apps/web/components/ImageUploader.tsx +++ /dev/null @@ -1,167 +0,0 @@ -import type { FormEvent } from "react"; -import { useCallback, useEffect, useState } from "react"; -import Cropper from "react-easy-crop"; - -import { useLocale } from "@calcom/lib/hooks/useLocale"; -import { Button, Dialog, DialogClose, DialogContent, DialogTrigger } from "@calcom/ui"; - -import type { Area } from "@lib/cropImage"; -import { getCroppedImg } from "@lib/cropImage"; -import { useFileReader } from "@lib/hooks/useFileReader"; - -import Slider from "@components/Slider"; - -type ImageUploaderProps = { - id: string; - buttonMsg: string; - handleAvatarChange: (imageSrc: string) => void; - imageSrc?: string; - target: string; -}; - -interface FileEvent extends FormEvent { - target: EventTarget & T; -} - -// This is separate to prevent loading the component until file upload -function CropContainer({ - onCropComplete, - imageSrc, -}: { - imageSrc: string; - onCropComplete: (croppedAreaPixels: Area) => void; -}) { - const { t } = useLocale(); - const [crop, setCrop] = useState({ x: 0, y: 0 }); - const [zoom, setZoom] = useState(1); - - const handleZoomSliderChange = (value: number) => { - value < 1 ? setZoom(1) : setZoom(value); - }; - - return ( -
-
- onCropComplete(croppedAreaPixels)} - onZoomChange={setZoom} - /> -
- -
- ); -} - -/** @deprecated Use `packages/ui/v2/core/ImageUploader.tsx` */ -export default function ImageUploader({ - target, - id, - buttonMsg, - handleAvatarChange, - ...props -}: ImageUploaderProps) { - const { t } = useLocale(); - const [imageSrc, setImageSrc] = useState(null); - const [croppedAreaPixels, setCroppedAreaPixels] = useState(null); - - const [{ result }, setFile] = useFileReader({ - method: "readAsDataURL", - }); - - useEffect(() => { - if (props.imageSrc) setImageSrc(props.imageSrc); - }, [props.imageSrc]); - - const onInputFile = (e: FileEvent) => { - if (!e.target.files?.length) { - return; - } - setFile(e.target.files[0]); - }; - - const showCroppedImage = useCallback( - async (croppedAreaPixels: Area | null) => { - try { - if (!croppedAreaPixels) return; - const croppedImage = await getCroppedImg( - result as string /* result is always string when using readAsDataUrl */, - croppedAreaPixels - ); - setImageSrc(croppedImage); - handleAvatarChange(croppedImage); - } catch (e) { - console.error(e); - } - }, - [result, handleAvatarChange] - ); - - return ( - !opened && setFile(null) // unset file on close - }> - -
- -
-
- -
-
- -
-
-
-
- {!result && ( -
- {!imageSrc && ( -

- {t("no_target", { target })} -

- )} - {imageSrc && ( - // eslint-disable-next-line @next/next/no-img-element - {target} - )} -
- )} - {result && } - -
-
-
- showCroppedImage(croppedAreaPixels)}>{t("save")} - {t("cancel")} -
-
-
- ); -} diff --git a/apps/web/components/NavTabs.tsx b/apps/web/components/NavTabs.tsx index f92e0fda40..fe66bc3558 100644 --- a/apps/web/components/NavTabs.tsx +++ b/apps/web/components/NavTabs.tsx @@ -1,4 +1,3 @@ -import { AdminRequired } from "components/ui/AdminRequired"; import { noop } from "lodash"; import type { LinkProps } from "next/link"; import Link from "next/link"; @@ -6,6 +5,7 @@ import { useRouter } from "next/router"; import type { FC, MouseEventHandler } from "react"; import { Fragment } from "react"; +import { PermissionContainer } from "@calcom/features/auth/PermissionContainer"; import { useLocale } from "@calcom/lib/hooks/useLocale"; import classNames from "@lib/classNames"; @@ -60,7 +60,7 @@ const NavTabs: FC = ({ tabs, linkProps, ...props }) => { } : noop; - const Component = tab.adminRequired ? AdminRequired : Fragment; + const Component = tab.adminRequired ? PermissionContainer : Fragment; const className = tab.className || ""; return ( diff --git a/apps/web/components/PageWrapper.tsx b/apps/web/components/PageWrapper.tsx new file mode 100644 index 0000000000..cbd9f19ece --- /dev/null +++ b/apps/web/components/PageWrapper.tsx @@ -0,0 +1,82 @@ +import { DefaultSeo } from "next-seo"; +import { Inter } from "next/font/google"; +import localFont from "next/font/local"; +import Script from "next/script"; + +import "@calcom/embed-core/src/embed-iframe"; +import LicenseRequired from "@calcom/features/ee/common/components/LicenseRequired"; + +import type { AppProps } from "@lib/app-providers"; +import AppProviders from "@lib/app-providers"; +import { seoConfig } from "@lib/config/next-seo.config"; + +import I18nLanguageHandler from "@components/I18nLanguageHandler"; + +export interface CalPageWrapper { + (props?: any): JSX.Element; + PageWrapper?: AppProps["Component"]["PageWrapper"]; +} + +const interFont = Inter({ subsets: ["latin"], variable: "--font-inter", preload: true, display: "swap" }); +const calFont = localFont({ + src: "../fonts/CalSans-SemiBold.woff2", + variable: "--font-cal", + preload: true, + display: "swap", +}); + +function PageWrapper(props: AppProps) { + const { Component, pageProps, err, router } = props; + let pageStatus = "200"; + + if (router.pathname === "/404") { + pageStatus = "404"; + } else if (router.pathname === "/500") { + pageStatus = "500"; + } + + // On client side don't let nonce creep into DOM + // It also avoids hydration warning that says that Client has the nonce value but server has "" because browser removes nonce attributes before DOM is built + // See https://github.com/kentcdodds/nonce-hydration-issues + // Set "" only if server had it set otherwise keep it undefined because server has to match with client to avoid hydration error + const nonce = typeof window !== "undefined" ? (pageProps.nonce ? "" : undefined) : pageProps.nonce; + const providerProps = { + ...props, + pageProps: { + ...props.pageProps, + nonce, + }, + }; + // Use the layout defined at the page level, if available + const getLayout = Component.getLayout ?? ((page) => page); + return ( + + + +