Compare commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
0d03006653 | ||
|
|
d44c5fd8b0 | ||
|
|
74aa366f1e | ||
|
|
7ebea57602 | ||
|
|
26d675e162 | ||
|
|
e16d29e916 | ||
|
|
0e89fb9f6a | ||
|
|
43fb410a28 | ||
|
|
b8bbfe8a3f | ||
|
|
1f74fc394a | ||
|
|
9a12b5a0ac | ||
|
|
2ce2c1074b | ||
|
|
0ef80da260 | ||
|
|
1e50488cfe | ||
|
|
ae9f66c9ef | ||
|
|
798a4b0090 | ||
|
|
dcd9ff72b7 | ||
|
|
0b5a0186bc | ||
|
|
d9846dca4c | ||
|
|
2fd606c38e | ||
|
|
c066a0cba9 | ||
|
|
568cc22c60 | ||
|
|
b3243c53dc | ||
|
|
80ffcf8b58 | ||
|
+1 |
77bf14a797 | ||
|
|
b188368e8f | ||
|
|
e35631aee3 | ||
|
|
733a06a672 | ||
|
|
2de3e7fc59 | ||
|
|
3137a188a0 | ||
|
|
a2a4ff934f | ||
|
|
eda1d5f393 | ||
|
|
93a14b190e | ||
|
|
2a6dc340df | ||
|
|
9cff1e3439 | ||
|
|
bb9cbd6582 | ||
|
|
81721e7bb9 | ||
|
|
454311dd52 | ||
|
|
7df04c1d50 | ||
|
|
d9a8ab3864 | ||
|
|
e3a7fd006b | ||
|
|
38d8852819 | ||
|
|
c0f34a39d1 | ||
|
|
e09dd04e7f | ||
|
|
1c2c6a62e6 | ||
|
|
33271a88b1 | ||
|
|
4086a666ae | ||
|
|
b31b8cc6df | ||
|
|
6dce57440e | ||
|
|
22a030afab | ||
|
|
e04d81cf11 | ||
|
|
bd6888b758 | ||
|
|
ccc2bdd25e | ||
|
|
a36b46bd81 | ||
|
|
a94d51c4bd | ||
|
|
3854b9c58e | ||
|
|
ba6963573e | ||
|
|
596f723cff | ||
|
|
c408d71a53 | ||
|
|
d64400d66b | ||
|
|
16eede9bb0 | ||
|
|
ba5d448811 | ||
|
|
1070cc2892 | ||
|
|
df91425643 | ||
|
|
c98bafcd52 | ||
|
|
9441a79def | ||
|
|
994df45a19 | ||
|
|
c4662d79e1 | ||
|
|
50cd1fbd83 | ||
|
|
196549c124 | ||
|
|
91274d8ee0 | ||
|
|
fa026ee393 | ||
|
|
5793901b03 | ||
|
|
030f1c6990 | ||
|
|
b201067f86 | ||
|
|
cb96b12108 | ||
|
|
e7f557f7f9 | ||
|
|
e2921c3126 | ||
|
|
d8102615b1 | ||
|
|
1010e2894a | ||
|
|
c728174c14 | ||
|
|
3889b49fa1 | ||
|
|
461389fb0b | ||
|
|
a8186e3039 | ||
|
|
5e2db12396 | ||
|
|
6bc9d2fd94 | ||
|
|
37f9bbf3fa | ||
|
|
fb5b3bbcc6 | ||
|
|
d43ee154d0 |
+10
-1
@@ -11,6 +11,10 @@
|
||||
# - ZOOM
|
||||
# - GIPHY
|
||||
# - VITAL
|
||||
# - ZAPIER
|
||||
# - LARK
|
||||
# - WEB3
|
||||
# - SALESFORCE
|
||||
|
||||
# - APP STORE **********************************************************************************************
|
||||
# ⚠️ ⚠️ ⚠️ THESE WILL BE MIGRATED TO THE DATABASE TO PREVENT AWS's 4KB ENV QUOTA ⚠️ ⚠️ ⚠️
|
||||
@@ -88,7 +92,7 @@ VITAL_REGION="us"
|
||||
# @see https://github.com/calcom/cal.com/blob/main/packages/app-store/zapier/README.md
|
||||
ZAPIER_INVITE_LINK=""
|
||||
|
||||
# - LARK
|
||||
# - LARK
|
||||
# Needed to enable Lark Calendar integration and Login with Lark
|
||||
# @see <https://open.larksuite.com/document/ukTMukTMukTM/ukDNz4SO0MjL5QzM/g>
|
||||
LARK_OPEN_APP_ID=""
|
||||
@@ -100,4 +104,9 @@ LARK_OPEN_VERIFICATION_TOKEN=""
|
||||
# @see https://github.com/calcom/cal.com/blob/main/packages/app-store/rainbow/README.md
|
||||
ALCHEMY_API_KEY=""
|
||||
INFURA_API_KEY=""
|
||||
|
||||
# - SALESFORCE
|
||||
# Used for the Salesforce (Sales Cloud) app
|
||||
SALESFORCE_CONSUMER_KEY=""
|
||||
SALESFORCE_CONSUMER_SECRET=""
|
||||
# *********************************************************************************************************
|
||||
|
||||
+3
-3
@@ -77,7 +77,7 @@ NEXT_PUBLIC_HELPSCOUT_KEY=
|
||||
SEND_FEEDBACK_EMAIL=
|
||||
|
||||
# Sengrid
|
||||
# Used for email reminders in workflows
|
||||
# Used for email reminders in workflows and internal sync services
|
||||
SENDGRID_API_KEY=
|
||||
SENDGRID_EMAIL=
|
||||
|
||||
@@ -134,8 +134,8 @@ NEXT_PUBLIC_TEAM_IMPERSONATION=false
|
||||
# Close.com internal CRM
|
||||
CLOSECOM_API_KEY=
|
||||
|
||||
# Sendgrid internal email sender
|
||||
SENDGRID_API_KEY=
|
||||
# Sendgrid internal sync service
|
||||
SENDGRID_SYNC_API_KEY=
|
||||
|
||||
# Sentry
|
||||
NEXT_PUBLIC_SENTRY_DSN=
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
on:
|
||||
workflow_dispatch:
|
||||
push:
|
||||
# Pattern matched against refs/tags
|
||||
tags:
|
||||
@@ -8,4 +9,9 @@ jobs:
|
||||
release:
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- run: curl -X POST -d {} ${{ secrets.VERCEL_VERSIONED_DEPLOY_HOOK }}
|
||||
- uses: actions/checkout@v3
|
||||
with:
|
||||
ref: main # Always checkout main even for tagged releases
|
||||
fetch-depth: 0
|
||||
token: ${{ secrets.GH_ACCESS_TOKEN }}
|
||||
- run: git push origin +main:production
|
||||
|
||||
Vendored
+2
-1
@@ -6,5 +6,6 @@
|
||||
},
|
||||
"typescript.preferences.importModuleSpecifier": "non-relative",
|
||||
"spellright.language": ["en"],
|
||||
"spellright.documentTypes": ["markdown", "typescript"]
|
||||
"spellright.documentTypes": ["markdown", "typescript"],
|
||||
"tailwindCSS.experimental.classRegex": [["cva\\(([^)]*)\\)", "[\"'`]([^\"'`]*).*?[\"'`]"]]
|
||||
}
|
||||
|
||||
@@ -387,7 +387,7 @@ following
|
||||
4. Fill in any information you want in the "App info" tab
|
||||
5. Go to tab "Auth"
|
||||
6. Now copy the Client ID and Client Secret to your .env file into the `HUBSPOT_CLIENT_ID` and `HUBSPOT_CLIENT_SECRET` fields.
|
||||
7. Set the Redirect URL for OAuth `<Cal.com URL>/api/integrations/hubspotothercalendar/callback` replacing Cal.com URL with the URI at which your application runs.
|
||||
7. Set the Redirect URL for OAuth `<Cal.com URL>/api/integrations/hubspot/callback` replacing Cal.com URL with the URI at which your application runs.
|
||||
8. In the "Scopes" section at the bottom of the page, make sure you select "Read" and "Write" for scope called `crm.objects.contacts`
|
||||
9. Click the "Save" button at the bottom footer.
|
||||
10. You're good to go. Now you can see any booking in Cal.com created as a meeting in HubSpot for your contacts.
|
||||
|
||||
@@ -0,0 +1,35 @@
|
||||
/**
|
||||
* This is a Playwright script Checkly generated for you based on your Vercel project.
|
||||
* To learn more about Browser checks and Playwright visit: https://www.checklyhq.com/docs/browser-checks
|
||||
*/
|
||||
|
||||
// Create a Chromium browser
|
||||
const { chromium } = require("playwright");
|
||||
|
||||
// Checkly supports top level await, but we wrap your code in an async function so you can run it locally too.
|
||||
async function run() {
|
||||
const browser = await chromium.launch();
|
||||
const page = await browser.newPage();
|
||||
const targetUrl = process.env.ENVIRONMENT_URL || "https://cal.com";
|
||||
await page.goto(`${targetUrl}/rick/test-location-link`);
|
||||
|
||||
await page.waitForSelector('[data-testid="day"][data-disabled="false"]');
|
||||
await page.click('[data-testid="day"][data-disabled="false"]');
|
||||
|
||||
await page.waitForSelector('[data-testid="time"]');
|
||||
await page.click('[data-testid="time"]');
|
||||
|
||||
await page.waitForSelector("#name");
|
||||
await page.click("#name");
|
||||
|
||||
await page.type("#name", "Calcom");
|
||||
|
||||
await page.type('[name="email"]', "cal@cal.com");
|
||||
|
||||
await page.waitForSelector('[data-testid="confirm-book-button"]');
|
||||
await page.click('[data-testid="confirm-book-button"]');
|
||||
|
||||
await browser.close();
|
||||
}
|
||||
|
||||
run();
|
||||
@@ -50,6 +50,10 @@
|
||||
"description": "Sendgrid api key. Used for email reminders in workflows",
|
||||
"value": ""
|
||||
},
|
||||
"SENDGRID_SYNC_API_KEY": {
|
||||
"description": "Sendgrid internal sync service",
|
||||
"value": ""
|
||||
},
|
||||
"SENDGRID_EMAIL": {
|
||||
"description": "Sendgrid email. Used for email reminders in workflows",
|
||||
"value": ""
|
||||
|
||||
+1
-1
Submodule apps/api updated: 0f5017010f...055699f612
+1
-1
Submodule apps/console updated: 2219900e06...9174a0c261
@@ -19,7 +19,7 @@ export function VariantsTable({
|
||||
<div
|
||||
className={classNames(
|
||||
isDark &&
|
||||
"relative py-8 before:absolute before:left-0 before:top-0 before:block before:h-full before:w-screen before:bg-[#22252A]"
|
||||
"relative py-8 before:absolute before:left-0 before:top-0 before:block before:h-full before:w-screen before:bg-[#1C1C1C]"
|
||||
)}>
|
||||
<div className="z-1 relative mx-auto w-full max-w-[1200px] overflow-auto pr-8 pt-6">
|
||||
<table>
|
||||
|
||||
@@ -1,15 +0,0 @@
|
||||
declare module "@wojtekmaj/react-daterange-picker/dist/entry.nostyle" {
|
||||
import { CalendarProps } from "react-calendar";
|
||||
export type DateRangePickerCalendarProps = Omit<
|
||||
CalendarProps,
|
||||
"calendarClassName" | "onChange" | "value"
|
||||
> & {
|
||||
calendarClassName?: string;
|
||||
onChange: (value: [Date, Date]) => void;
|
||||
value: [Date, Date];
|
||||
clearIcon: JSX.Element | null;
|
||||
calendarIcon: JSX.Element | null;
|
||||
rangeDivider: JSX.Element | null;
|
||||
};
|
||||
export default function DateRangePicker(props: DateRangePickerCalendarProps): JSX.Element;
|
||||
}
|
||||
@@ -1,7 +1,7 @@
|
||||
import { useState } from "react";
|
||||
|
||||
import { useLocale } from "@calcom/lib/hooks/useLocale";
|
||||
import { Icon } from "@calcom/ui/Icon";
|
||||
import { Icon } from "@calcom/ui";
|
||||
|
||||
export default function AddToHomescreen() {
|
||||
const { t } = useLocale();
|
||||
|
||||
@@ -5,11 +5,20 @@ import { createRef, forwardRef, MutableRefObject, RefObject, useRef, useState }
|
||||
import { components, ControlProps } from "react-select";
|
||||
|
||||
import { useLocale } from "@calcom/lib/hooks/useLocale";
|
||||
import { Icon } from "@calcom/ui/Icon";
|
||||
import { Button } from "@calcom/ui/components";
|
||||
import { InputLeading, Label, TextArea, TextField } from "@calcom/ui/form/fields";
|
||||
import { HorizontalTabs, showToast, Switch } from "@calcom/ui/v2";
|
||||
import { Dialog, DialogClose, DialogContent } from "@calcom/ui/v2/core/Dialog";
|
||||
import {
|
||||
Button,
|
||||
Dialog,
|
||||
DialogClose,
|
||||
DialogContent,
|
||||
HorizontalTabs,
|
||||
Icon,
|
||||
InputLeading,
|
||||
Label,
|
||||
showToast,
|
||||
Switch,
|
||||
TextArea,
|
||||
TextField,
|
||||
} from "@calcom/ui";
|
||||
|
||||
import { EMBED_LIB_URL, WEBAPP_URL } from "@lib/config/constants";
|
||||
|
||||
@@ -610,7 +619,7 @@ const ChooseEmbedTypesDialogContent = () => {
|
||||
const { t } = useLocale();
|
||||
const router = useRouter();
|
||||
return (
|
||||
<DialogContent type="creation" useOwnActionButtons size="lg">
|
||||
<DialogContent type="creation" size="lg">
|
||||
<div className="mb-4">
|
||||
<h3 className="text-lg font-bold leading-6 text-gray-900" id="modal-title">
|
||||
{t("how_you_want_add_cal_site")}
|
||||
@@ -788,7 +797,7 @@ const EmbedTypeCodeAndPreviewDialogContent = ({
|
||||
];
|
||||
|
||||
return (
|
||||
<DialogContent size="xl" className="p-0.5" type="creation" useOwnActionButtons>
|
||||
<DialogContent size="xl" className="p-0.5" type="creation">
|
||||
<div className="flex">
|
||||
<div className="flex w-1/3 flex-col bg-gray-50 p-8">
|
||||
<h3 className="mb-2 flex text-xl font-bold leading-6 text-gray-900" id="modal-title">
|
||||
@@ -1086,9 +1095,7 @@ const EmbedTypeCodeAndPreviewDialogContent = ({
|
||||
{t("copy_code")}
|
||||
</Button>
|
||||
) : null}
|
||||
<DialogClose asChild>
|
||||
<Button color="secondary">{t("Close")}</Button>
|
||||
</DialogClose>
|
||||
<DialogClose />
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
|
||||
@@ -1,8 +1,7 @@
|
||||
import { FormEvent, useCallback, useEffect, useState } from "react";
|
||||
import Cropper from "react-easy-crop";
|
||||
|
||||
import Button from "@calcom/ui/Button";
|
||||
import { Dialog, DialogClose, DialogContent, DialogTrigger } from "@calcom/ui/Dialog";
|
||||
import { Button, Dialog, DialogClose, DialogContent, DialogTrigger } from "@calcom/ui";
|
||||
|
||||
import { Area, getCroppedImg } from "@lib/cropImage";
|
||||
import { useFileReader } from "@lib/hooks/useFileReader";
|
||||
@@ -157,12 +156,8 @@ export default function ImageUploader({
|
||||
</div>
|
||||
</div>
|
||||
<div className="mt-5 gap-x-2 sm:mt-4 sm:flex sm:flex-row-reverse">
|
||||
<DialogClose asChild>
|
||||
<Button onClick={() => showCroppedImage(croppedAreaPixels)}>{t("save")}</Button>
|
||||
</DialogClose>
|
||||
<DialogClose asChild>
|
||||
<Button color="secondary">{t("cancel")}</Button>
|
||||
</DialogClose>
|
||||
<DialogClose onClick={() => showCroppedImage(croppedAreaPixels)}>{t("save")}</DialogClose>
|
||||
<DialogClose color="secondary">{t("cancel")}</DialogClose>
|
||||
</div>
|
||||
</DialogContent>
|
||||
</Dialog>
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
/**
|
||||
* @deprecated Use custom Skeletons instead
|
||||
**/
|
||||
export { default } from "@calcom/ui/Loader";
|
||||
export { Loader as default } from "@calcom/ui";
|
||||
|
||||
@@ -1,8 +1,6 @@
|
||||
import React, { ComponentProps } from "react";
|
||||
|
||||
import ErrorBoundary from "@calcom/ui/ErrorBoundary";
|
||||
import { Icon } from "@calcom/ui/Icon";
|
||||
import Shell from "@calcom/ui/Shell";
|
||||
import { ErrorBoundary, Icon, Shell } from "@calcom/ui";
|
||||
|
||||
import NavTabs from "./NavTabs";
|
||||
|
||||
|
||||
@@ -1,13 +1,10 @@
|
||||
import React from "react";
|
||||
import { OptionProps } from "react-select";
|
||||
|
||||
import { InstallAppButton } from "@calcom/app-store/components";
|
||||
import { useLocale } from "@calcom/lib/hooks/useLocale";
|
||||
import { trpc } from "@calcom/trpc/react";
|
||||
import type { App } from "@calcom/types/App";
|
||||
import { Button } from "@calcom/ui";
|
||||
import { Icon } from "@calcom/ui/Icon";
|
||||
import { Select } from "@calcom/ui/v2";
|
||||
import { Button, Icon, Select } from "@calcom/ui";
|
||||
|
||||
import { QueryCell } from "@lib/QueryCell";
|
||||
|
||||
|
||||
@@ -9,11 +9,15 @@ import classNames from "@calcom/lib/classNames";
|
||||
import { useLocale } from "@calcom/lib/hooks/useLocale";
|
||||
import { trpc } from "@calcom/trpc/react";
|
||||
import { App as AppType } from "@calcom/types/App";
|
||||
import { Icon } from "@calcom/ui/Icon";
|
||||
import { Button } from "@calcom/ui/components";
|
||||
import { showToast, SkeletonText } from "@calcom/ui/v2";
|
||||
import { SkeletonButton, Shell } from "@calcom/ui/v2";
|
||||
import DisconnectIntegration from "@calcom/ui/v2/modules/integrations/DisconnectIntegration";
|
||||
import {
|
||||
Button,
|
||||
DisconnectIntegration,
|
||||
Icon,
|
||||
Shell,
|
||||
showToast,
|
||||
SkeletonButton,
|
||||
SkeletonText,
|
||||
} from "@calcom/ui";
|
||||
|
||||
import HeadSeo from "@components/seo/head-seo";
|
||||
|
||||
@@ -43,7 +47,8 @@ const Component = ({
|
||||
const router = useRouter();
|
||||
|
||||
const mutation = useAddAppMutation(null, {
|
||||
onSuccess: () => {
|
||||
onSuccess: (data) => {
|
||||
if (data.setupPending) return;
|
||||
showToast(t("app_successfully_installed"), "success");
|
||||
},
|
||||
onError: (error) => {
|
||||
@@ -122,6 +127,7 @@ const Component = ({
|
||||
render={({ useDefaultComponent, ...props }) => {
|
||||
if (useDefaultComponent) {
|
||||
props = {
|
||||
...props,
|
||||
onClick: () => {
|
||||
mutation.mutate({ type, variant, slug });
|
||||
},
|
||||
@@ -160,6 +166,7 @@ const Component = ({
|
||||
render={({ useDefaultComponent, ...props }) => {
|
||||
if (useDefaultComponent) {
|
||||
props = {
|
||||
...props,
|
||||
onClick: () => {
|
||||
mutation.mutate({ type, variant, slug });
|
||||
},
|
||||
|
||||
@@ -6,15 +6,18 @@ import { InstallAppButton } from "@calcom/app-store/components";
|
||||
import DestinationCalendarSelector from "@calcom/features/calendars/DestinationCalendarSelector";
|
||||
import { useLocale } from "@calcom/lib/hooks/useLocale";
|
||||
import { trpc } from "@calcom/trpc/react";
|
||||
import { Icon } from "@calcom/ui/Icon";
|
||||
import SkeletonLoader from "@calcom/ui/apps/SkeletonLoader";
|
||||
import { Button } from "@calcom/ui/components";
|
||||
import { Alert, EmptyScreen } from "@calcom/ui/v2";
|
||||
import { List } from "@calcom/ui/v2/core/List";
|
||||
import { ShellSubHeading } from "@calcom/ui/v2/core/Shell";
|
||||
import Switch from "@calcom/ui/v2/core/Switch";
|
||||
import showToast from "@calcom/ui/v2/core/notifications";
|
||||
import DisconnectIntegration from "@calcom/ui/v2/modules/integrations/DisconnectIntegration";
|
||||
import {
|
||||
Alert,
|
||||
Button,
|
||||
DisconnectIntegration,
|
||||
EmptyScreen,
|
||||
Icon,
|
||||
List,
|
||||
ShellSubHeading,
|
||||
showToast,
|
||||
SkeletonLoader,
|
||||
Switch,
|
||||
} from "@calcom/ui";
|
||||
|
||||
import { QueryCell } from "@lib/QueryCell";
|
||||
|
||||
|
||||
@@ -3,8 +3,7 @@ import { useRouter } from "next/router";
|
||||
import { ReactNode, useEffect, useState } from "react";
|
||||
|
||||
import { useLocale } from "@calcom/lib/hooks/useLocale";
|
||||
import { Icon } from "@calcom/ui";
|
||||
import { ListItem, ListItemText, ListItemTitle } from "@calcom/ui/v2/core/List";
|
||||
import { Icon, ListItem, ListItemText, ListItemTitle, showToast } from "@calcom/ui";
|
||||
|
||||
import classNames from "@lib/classNames";
|
||||
|
||||
@@ -27,6 +26,13 @@ function IntegrationListItem(props: {
|
||||
const [highlight, setHighlight] = useState(hl === props.slug);
|
||||
const title = props.name || props.title;
|
||||
|
||||
// The highlight is to show a newly installed app, coming from the app's
|
||||
// redirection after installation, so we proceed to show the corresponding
|
||||
// message
|
||||
if (highlight) {
|
||||
showToast(t("app_successfully_installed"), "success");
|
||||
}
|
||||
|
||||
useEffect(() => {
|
||||
const timer = setTimeout(() => setHighlight(false), 3000);
|
||||
return () => {
|
||||
|
||||
@@ -3,7 +3,7 @@ import useDigitInput from "react-digit-input";
|
||||
import { useFormContext } from "react-hook-form";
|
||||
|
||||
import { useLocale } from "@calcom/lib/hooks/useLocale";
|
||||
import { Label, Input } from "@calcom/ui/form/fields";
|
||||
import { Label, Input } from "@calcom/ui";
|
||||
|
||||
export default function TwoFactor({ center = true }) {
|
||||
const [value, onChange] = useState("");
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
import React from "react";
|
||||
|
||||
import { SkeletonText } from "@calcom/ui/v2";
|
||||
import { SkeletonText } from "@calcom/ui";
|
||||
|
||||
import classNames from "@lib/classNames";
|
||||
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
import { getEventLocationType, locationKeyToString } from "@calcom/app-store/locations";
|
||||
import { classNames } from "@calcom/lib";
|
||||
import Tooltip from "@calcom/ui/v2/core/Tooltip";
|
||||
import { Icon, Tooltip } from "@calcom/ui";
|
||||
|
||||
import { Props } from "./pages/AvailabilityPage";
|
||||
|
||||
@@ -15,14 +15,18 @@ export function AvailableEventLocations({ locations }: { locations: Props["event
|
||||
}
|
||||
return (
|
||||
<div key={location.type} className="flex flex-row items-center text-sm font-medium">
|
||||
<img
|
||||
src={eventLocationType.iconUrl}
|
||||
className={classNames(
|
||||
"mr-[10px] ml-[2px] h-4 w-4 opacity-70 dark:opacity-100 ",
|
||||
!eventLocationType.iconUrl?.includes("api") ? "dark:invert" : ""
|
||||
)}
|
||||
alt={`${eventLocationType.label} icon`}
|
||||
/>
|
||||
{eventLocationType.iconUrl === "/link.svg" ? (
|
||||
<Icon.FiLink className="dark:text-darkgray-600 mr-[10px] ml-[2px] h-4 w-4 opacity-70 dark:opacity-100 " />
|
||||
) : (
|
||||
<img
|
||||
src={eventLocationType.iconUrl}
|
||||
className={classNames(
|
||||
"mr-[10px] ml-[2px] h-4 w-4 opacity-70 dark:opacity-100 ",
|
||||
!eventLocationType.iconUrl?.includes("api") ? "dark:invert" : ""
|
||||
)}
|
||||
alt={`${eventLocationType.label} icon`}
|
||||
/>
|
||||
)}
|
||||
<Tooltip content={locationKeyToString(location)}>
|
||||
<p className="truncate">{locationKeyToString(location)}</p>
|
||||
</Tooltip>
|
||||
|
||||
@@ -7,8 +7,7 @@ import { useLocale } from "@calcom/lib/hooks/useLocale";
|
||||
import { TimeFormat } from "@calcom/lib/timeFormat";
|
||||
import { nameOfDay } from "@calcom/lib/weekday";
|
||||
import type { Slot } from "@calcom/trpc/server/routers/viewer/slots";
|
||||
import { SkeletonContainer, SkeletonText } from "@calcom/ui/v2";
|
||||
import { ToggleGroup } from "@calcom/ui/v2/core/form/ToggleGroup";
|
||||
import { SkeletonContainer, SkeletonText, ToggleGroup } from "@calcom/ui";
|
||||
|
||||
import classNames from "@lib/classNames";
|
||||
import { timeZone } from "@lib/clock";
|
||||
|
||||
@@ -1,9 +1,11 @@
|
||||
import { SchedulingType } from "@prisma/client";
|
||||
import { FC, ReactNode } from "react";
|
||||
import { FC, ReactNode, useEffect } from "react";
|
||||
|
||||
import { classNames } from "@calcom/lib";
|
||||
import { useLocale } from "@calcom/lib/hooks/useLocale";
|
||||
import { Icon } from "@calcom/ui/Icon";
|
||||
import { Icon, Badge } from "@calcom/ui";
|
||||
|
||||
import useRouterQuery from "@lib/hooks/useRouterQuery";
|
||||
|
||||
import { UserAvatars } from "@components/booking/UserAvatars";
|
||||
import EventTypeDescriptionSafeHTML from "@components/eventtype/EventTypeDescriptionSafeHTML";
|
||||
@@ -40,6 +42,18 @@ interface Props {
|
||||
const BookingDescription: FC<Props> = (props) => {
|
||||
const { profile, eventType, isBookingPage = false, children } = props;
|
||||
const { t } = useLocale();
|
||||
const { duration, setQuery: setDuration } = useRouterQuery("duration");
|
||||
useEffect(() => {
|
||||
if (eventType.metadata?.multipleDuration !== undefined) {
|
||||
if (!duration) {
|
||||
setDuration(eventType.length);
|
||||
} else {
|
||||
if (!eventType.metadata?.multipleDuration.includes(Number(duration))) {
|
||||
setDuration(eventType.length);
|
||||
}
|
||||
}
|
||||
}
|
||||
}, []);
|
||||
return (
|
||||
<>
|
||||
<UserAvatars
|
||||
@@ -55,7 +69,7 @@ const BookingDescription: FC<Props> = (props) => {
|
||||
<h1 className="font-cal dark:text-darkgray-900 mb-6 break-words text-2xl font-semibold text-gray-900">
|
||||
{eventType.title}
|
||||
</h1>
|
||||
<div className="dark:text-darkgray-600 flex flex-col space-y-3 text-sm font-medium text-gray-600">
|
||||
<div className="dark:text-darkgray-600 flex flex-col space-y-4 text-sm font-medium text-gray-600">
|
||||
{eventType?.description && (
|
||||
<div
|
||||
className={classNames(
|
||||
@@ -90,14 +104,46 @@ const BookingDescription: FC<Props> = (props) => {
|
||||
<AvailableEventLocations
|
||||
locations={eventType.locations as AvailabilityPageProps["eventType"]["locations"]}
|
||||
/>
|
||||
<p
|
||||
<div
|
||||
className={classNames(
|
||||
"text-sm font-medium",
|
||||
"flex flex-nowrap text-sm font-medium",
|
||||
isBookingPage && "dark:text-darkgray-600 text-gray-600"
|
||||
)}>
|
||||
<Icon.FiClock className="mr-[10px] -mt-1 ml-[2px] inline-block h-4 w-4" />
|
||||
{eventType.length} {t("minutes")}
|
||||
</p>
|
||||
<Icon.FiClock
|
||||
className={classNames(
|
||||
"min-h-4 min-w-4 mr-[10px] ml-[2px] inline-block",
|
||||
isBookingPage && "mt-[2px]"
|
||||
)}
|
||||
/>
|
||||
{eventType.metadata?.multipleDuration !== undefined ? (
|
||||
!isBookingPage ? (
|
||||
<ul className="-mt-1 flex flex-wrap gap-1">
|
||||
{eventType.metadata.multipleDuration.map((dur, idx) => (
|
||||
<li key={idx}>
|
||||
<Badge
|
||||
variant="gray"
|
||||
size="lg"
|
||||
className={classNames(
|
||||
duration === dur.toString()
|
||||
? "bg-darkgray-200 text-darkgray-900 dark:bg-darkmodebrand dark:!text-darkmodebrandcontrast"
|
||||
: "hover:bg-darkgray-200 dark:hover:bg-darkmodebrand hover:text-darkgray-900 dark:hover:text-darkmodebrandcontrast dark:bg-darkgray-200 bg-gray-200 text-gray-900 dark:text-white",
|
||||
"cursor-pointer"
|
||||
)}
|
||||
onClick={() => {
|
||||
setDuration(dur);
|
||||
}}>
|
||||
{dur} {t("minute_timeUnit")}
|
||||
</Badge>
|
||||
</li>
|
||||
))}
|
||||
</ul>
|
||||
) : (
|
||||
`${duration} ${t("minutes")}`
|
||||
)
|
||||
) : (
|
||||
`${eventType.length} ${t("minutes")}`
|
||||
)}
|
||||
</div>
|
||||
{children}
|
||||
</div>
|
||||
</>
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
import { BookingStatus } from "@prisma/client";
|
||||
import { useRouter } from "next/router";
|
||||
import { useState, useMemo } from "react";
|
||||
import { useState } from "react";
|
||||
|
||||
import { EventLocationType, getEventLocationType } from "@calcom/app-store/locations";
|
||||
import dayjs from "@calcom/dayjs";
|
||||
@@ -9,14 +9,20 @@ import { formatTime } from "@calcom/lib/date-fns";
|
||||
import { useLocale } from "@calcom/lib/hooks/useLocale";
|
||||
import { getEveryFreqFor } from "@calcom/lib/recurringStrings";
|
||||
import { RouterInputs, RouterOutputs, trpc } from "@calcom/trpc/react";
|
||||
import { Dialog, DialogClose, DialogContent, DialogFooter, DialogHeader } from "@calcom/ui/Dialog";
|
||||
import { Icon } from "@calcom/ui/Icon";
|
||||
import { Badge } from "@calcom/ui/components/badge";
|
||||
import { Button } from "@calcom/ui/components/button";
|
||||
import { TextArea } from "@calcom/ui/form/fields";
|
||||
import MeetingTimeInTimezones from "@calcom/ui/v2/core/MeetingTimeInTimezones";
|
||||
import Tooltip from "@calcom/ui/v2/core/Tooltip";
|
||||
import showToast from "@calcom/ui/v2/core/notifications";
|
||||
import {
|
||||
Badge,
|
||||
Button,
|
||||
Dialog,
|
||||
DialogClose,
|
||||
DialogContent,
|
||||
DialogFooter,
|
||||
DialogHeader,
|
||||
Icon,
|
||||
MeetingTimeInTimezones,
|
||||
showToast,
|
||||
TextArea,
|
||||
Tooltip,
|
||||
} from "@calcom/ui";
|
||||
|
||||
import useMeQuery from "@lib/hooks/useMeQuery";
|
||||
|
||||
@@ -231,9 +237,7 @@ function BookingListItem(booking: BookingItemProps) {
|
||||
/>
|
||||
|
||||
<DialogFooter>
|
||||
<DialogClose>
|
||||
<Button color="secondary">{t("cancel")}</Button>
|
||||
</DialogClose>
|
||||
<DialogClose />
|
||||
|
||||
<Button
|
||||
disabled={mutation.isLoading}
|
||||
|
||||
@@ -5,8 +5,7 @@ import { useLocale } from "@calcom/lib/hooks/useLocale";
|
||||
import useTheme from "@calcom/lib/hooks/useTheme";
|
||||
import { collectPageParameters, telemetryEventTypes, useTelemetry } from "@calcom/lib/telemetry";
|
||||
import type { RecurringEvent } from "@calcom/types/Calendar";
|
||||
import { Icon } from "@calcom/ui/Icon";
|
||||
import { Button, TextArea } from "@calcom/ui/components";
|
||||
import { Button, Icon, TextArea } from "@calcom/ui";
|
||||
|
||||
type Props = {
|
||||
booking: {
|
||||
@@ -38,7 +37,7 @@ export default function CancelBooking(props: Props) {
|
||||
return (
|
||||
<>
|
||||
{error && (
|
||||
<div>
|
||||
<div className="mt-8">
|
||||
<div className="mx-auto flex h-12 w-12 items-center justify-center rounded-full bg-red-100">
|
||||
<Icon.FiX className="h-6 w-6 text-red-600" />
|
||||
</div>
|
||||
@@ -90,11 +89,7 @@ export default function CancelBooking(props: Props) {
|
||||
});
|
||||
|
||||
if (res.status >= 200 && res.status < 300) {
|
||||
await router.push(
|
||||
`/cancel/success?name=${props.profile.name}&title=${booking?.title}&eventPage=${
|
||||
profile.slug
|
||||
}&team=${team ? 1 : 0}&allRemainingBookings=${allRemainingBookings}`
|
||||
);
|
||||
await router.replace(router.asPath);
|
||||
} else {
|
||||
setLoading(false);
|
||||
setError(
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
import React from "react";
|
||||
|
||||
import { SkeletonText } from "@calcom/ui/v2";
|
||||
import { SkeletonText } from "@calcom/ui";
|
||||
|
||||
function SkeletonLoader() {
|
||||
return (
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
import { FC, useEffect, useState } from "react";
|
||||
|
||||
import { useLocale } from "@calcom/lib/hooks/useLocale";
|
||||
import TimezoneSelect, { ITimezoneOption } from "@calcom/ui/v2/core/TimezoneSelect";
|
||||
import { ITimezoneOption, TimezoneSelect } from "@calcom/ui";
|
||||
|
||||
import { timeZone } from "../../lib/clock";
|
||||
|
||||
|
||||
@@ -7,7 +7,7 @@ export const UserAvatars = ({
|
||||
users,
|
||||
...props
|
||||
}: {
|
||||
profile: { image: string | null; name?: string | null };
|
||||
profile: { image: string | null; name?: string | null; username?: string | null };
|
||||
showMembers: boolean;
|
||||
users: { username: string | null; name?: string | null }[];
|
||||
} & Pick<AvatarGroupProps, "size" | "truncateAfter">) => {
|
||||
@@ -17,15 +17,26 @@ export const UserAvatars = ({
|
||||
border="border-2 dark:border-darkgray-100 border-white"
|
||||
items={
|
||||
[
|
||||
{ image: profile.image, alt: profile.name, title: profile.name },
|
||||
{
|
||||
image: profile.image,
|
||||
alt: profile.name,
|
||||
title: profile.name,
|
||||
href: profile.username ? `${CAL_URL}/${profile.username}` : undefined,
|
||||
},
|
||||
...(showMembers
|
||||
? users.map((user) => ({
|
||||
title: user.name,
|
||||
image: `${CAL_URL}/${user.username}/avatar.png`,
|
||||
alt: user.name || undefined,
|
||||
href: user.username ? `${CAL_URL}/${user.username}` : undefined,
|
||||
}))
|
||||
: []),
|
||||
].filter((item) => !!item.image) as { image: string; alt?: string; title?: string }[]
|
||||
].filter((item) => !!item.image) as {
|
||||
image: string;
|
||||
alt?: string;
|
||||
title?: string;
|
||||
href?: string;
|
||||
}[]
|
||||
}
|
||||
size={props.size}
|
||||
truncateAfter={props.truncateAfter}
|
||||
|
||||
@@ -27,13 +27,14 @@ import notEmpty from "@calcom/lib/notEmpty";
|
||||
import { getRecurringFreq } from "@calcom/lib/recurringStrings";
|
||||
import { collectPageParameters, telemetryEventTypes, useTelemetry } from "@calcom/lib/telemetry";
|
||||
import { detectBrowserTimeFormat, setIs24hClockInLocalStorage, TimeFormat } from "@calcom/lib/timeFormat";
|
||||
import { EventTypeMetaDataSchema } from "@calcom/prisma/zod-utils";
|
||||
import { trpc } from "@calcom/trpc/react";
|
||||
import { Icon } from "@calcom/ui/Icon";
|
||||
import DatePicker from "@calcom/ui/v2/modules/booker/DatePicker";
|
||||
import { Icon, DatePicker } from "@calcom/ui";
|
||||
|
||||
import { timeZone as localStorageTimeZone } from "@lib/clock";
|
||||
// import { timeZone } from "@lib/clock";
|
||||
import { useExposePlanGlobally } from "@lib/hooks/useExposePlanGlobally";
|
||||
import useRouterQuery from "@lib/hooks/useRouterQuery";
|
||||
import { isBrandingHidden } from "@lib/isBrandingHidden";
|
||||
|
||||
import Gates, { Gate, GateState } from "@components/Gates";
|
||||
@@ -71,6 +72,7 @@ const useSlots = ({
|
||||
endTime,
|
||||
usernameList,
|
||||
timeZone,
|
||||
duration,
|
||||
}: {
|
||||
eventTypeId: number;
|
||||
eventTypeSlug: string;
|
||||
@@ -78,6 +80,7 @@ const useSlots = ({
|
||||
endTime?: Dayjs;
|
||||
usernameList: string[];
|
||||
timeZone?: string;
|
||||
duration?: string;
|
||||
}) => {
|
||||
const { data, isLoading, isPaused } = trpc.viewer.public.slots.getSchedule.useQuery(
|
||||
{
|
||||
@@ -87,6 +90,7 @@ const useSlots = ({
|
||||
startTime: startTime?.toISOString() || "",
|
||||
endTime: endTime?.toISOString() || "",
|
||||
timeZone,
|
||||
duration,
|
||||
},
|
||||
{
|
||||
enabled: !!startTime && !!endTime,
|
||||
@@ -127,6 +131,7 @@ const SlotPicker = ({
|
||||
}) => {
|
||||
const [selectedDate, setSelectedDate] = useState<Dayjs>();
|
||||
const [browsingDate, setBrowsingDate] = useState<Dayjs>();
|
||||
const { duration } = useRouterQuery("duration");
|
||||
const { date, setQuery: setDate } = useRouterQuery("date");
|
||||
const { month, setQuery: setMonth } = useRouterQuery("month");
|
||||
const router = useRouter();
|
||||
@@ -152,7 +157,7 @@ const SlotPicker = ({
|
||||
setSelectedDate(dayjs.tz(date, timeZone));
|
||||
}
|
||||
}
|
||||
}, [router.isReady, month, date, timeZone]);
|
||||
}, [router.isReady, month, date, duration, timeZone]);
|
||||
|
||||
const { i18n, isLocaleReady } = useLocale();
|
||||
const { slots: _1 } = useSlots({
|
||||
@@ -162,6 +167,7 @@ const SlotPicker = ({
|
||||
startTime: selectedDate?.startOf("day"),
|
||||
endTime: selectedDate?.endOf("day"),
|
||||
timeZone,
|
||||
duration,
|
||||
});
|
||||
const { slots: _2, isLoading } = useSlots({
|
||||
eventTypeId: eventType.id,
|
||||
@@ -170,6 +176,7 @@ const SlotPicker = ({
|
||||
startTime: browsingDate?.startOf("month"),
|
||||
endTime: browsingDate?.endOf("month"),
|
||||
timeZone,
|
||||
duration,
|
||||
});
|
||||
|
||||
const slots = useMemo(() => ({ ..._2, ..._1 }), [_1, _2]);
|
||||
@@ -231,13 +238,13 @@ function TimezoneDropdown({
|
||||
return (
|
||||
<Popover.Root open={isTimeOptionsOpen} onOpenChange={setIsTimeOptionsOpen}>
|
||||
<Popover.Trigger className="min-w-32 dark:text-darkgray-600 radix-state-open:bg-gray-200 dark:radix-state-open:bg-darkgray-200 group relative mb-2 -ml-2 inline-block rounded-md px-2 py-2 text-left text-gray-600">
|
||||
<p className="text-sm font-medium">
|
||||
<Icon.FiGlobe className="mr-[10px] ml-[2px] -mt-[2px] inline-block h-4 w-4" />
|
||||
<p className="flex items-center text-sm font-medium">
|
||||
<Icon.FiGlobe className="min-h-4 min-w-4 mr-[10px] ml-[2px] -mt-[2px] inline-block" />
|
||||
{timeZone}
|
||||
{isTimeOptionsOpen ? (
|
||||
<Icon.FiChevronUp className="ml-1 inline-block h-4 w-4" />
|
||||
<Icon.FiChevronUp className="min-h-4 min-w-4 ml-1 inline-block" />
|
||||
) : (
|
||||
<Icon.FiChevronDown className="ml-1 inline-block h-4 w-4" />
|
||||
<Icon.FiChevronDown className="min-h-4 min-w-4 ml-1 inline-block" />
|
||||
)}
|
||||
</p>
|
||||
</Popover.Trigger>
|
||||
@@ -259,23 +266,6 @@ const dateQuerySchema = z.object({
|
||||
timeZone: z.string().optional().default(""),
|
||||
});
|
||||
|
||||
const useRouterQuery = <T extends string>(name: T) => {
|
||||
const router = useRouter();
|
||||
const existingQueryParams = router.asPath.split("?")[1];
|
||||
|
||||
const urlParams = new URLSearchParams(existingQueryParams);
|
||||
const query = Object.fromEntries(urlParams);
|
||||
|
||||
const setQuery = (newValue: string | number | null | undefined) => {
|
||||
router.replace({ query: { ...router.query, [name]: newValue } }, undefined, { shallow: true });
|
||||
router.replace({ query: { ...router.query, ...query, [name]: newValue } }, undefined, { shallow: true });
|
||||
};
|
||||
|
||||
return { [name]: query[name], setQuery } as {
|
||||
[K in T]: string | undefined;
|
||||
} & { setQuery: typeof setQuery };
|
||||
};
|
||||
|
||||
export type Props = AvailabilityTeamPageProps | AvailabilityPageProps | DynamicAvailabilityPageProps;
|
||||
|
||||
const AvailabilityPage = ({ profile, eventType, ...restProps }: Props) => {
|
||||
|
||||
@@ -5,7 +5,7 @@ import { isValidPhoneNumber } from "libphonenumber-js";
|
||||
import { useSession } from "next-auth/react";
|
||||
import Head from "next/head";
|
||||
import { useRouter } from "next/router";
|
||||
import { useEffect, useMemo, useState, useReducer } from "react";
|
||||
import { useEffect, useMemo, useReducer, useState } from "react";
|
||||
import { Controller, useForm, useWatch } from "react-hook-form";
|
||||
import { FormattedNumber, IntlProvider } from "react-intl";
|
||||
import { ReactMultiEmail } from "react-multi-email";
|
||||
@@ -14,11 +14,11 @@ import { z } from "zod";
|
||||
|
||||
import BookingPageTagManager from "@calcom/app-store/BookingPageTagManager";
|
||||
import {
|
||||
locationKeyToString,
|
||||
getEventLocationValue,
|
||||
getEventLocationType,
|
||||
EventLocationType,
|
||||
getEventLocationType,
|
||||
getEventLocationValue,
|
||||
getHumanReadableLocationValue,
|
||||
locationKeyToString,
|
||||
} from "@calcom/app-store/locations";
|
||||
import { createPaymentLink } from "@calcom/app-store/stripepayment/lib/client";
|
||||
import { getEventTypeAppData } from "@calcom/app-store/utils";
|
||||
@@ -37,17 +37,13 @@ import useTheme from "@calcom/lib/hooks/useTheme";
|
||||
import { HttpError } from "@calcom/lib/http-error";
|
||||
import { getEveryFreqFor } from "@calcom/lib/recurringStrings";
|
||||
import { collectPageParameters, telemetryEventTypes, useTelemetry } from "@calcom/lib/telemetry";
|
||||
import { Icon } from "@calcom/ui/Icon";
|
||||
import { Tooltip } from "@calcom/ui/Tooltip";
|
||||
import { Button } from "@calcom/ui/components";
|
||||
import AddressInput from "@calcom/ui/form/AddressInputLazy";
|
||||
import PhoneInput from "@calcom/ui/form/PhoneInputLazy";
|
||||
import { EmailInput, Form } from "@calcom/ui/form/fields";
|
||||
import { AddressInput, Button, EmailInput, Form, Icon, PhoneInput, Tooltip } from "@calcom/ui";
|
||||
|
||||
import { asStringOrNull } from "@lib/asStringOrNull";
|
||||
import { timeZone } from "@lib/clock";
|
||||
import { ensureArray } from "@lib/ensureArray";
|
||||
import useMeQuery from "@lib/hooks/useMeQuery";
|
||||
import useRouterQuery from "@lib/hooks/useRouterQuery";
|
||||
import createBooking from "@lib/mutations/bookings/create-booking";
|
||||
import createRecurringBooking from "@lib/mutations/bookings/create-recurring-booking";
|
||||
import { parseDate, parseRecurringDates } from "@lib/parseDate";
|
||||
@@ -90,9 +86,7 @@ const BookingPage = ({
|
||||
...restProps
|
||||
}: BookingPageProps) => {
|
||||
const { t, i18n } = useLocale();
|
||||
// Get user so we can determine 12/24 hour format preferences
|
||||
const query = useMeQuery();
|
||||
const user = query.data;
|
||||
const { duration: queryDuration } = useRouterQuery("duration");
|
||||
const isEmbed = useIsEmbed(restProps.isEmbed);
|
||||
const shouldAlignCentrallyInEmbed = useEmbedNonStylesConfig("align") !== "left";
|
||||
const shouldAlignCentrally = !isEmbed || shouldAlignCentrallyInEmbed;
|
||||
@@ -109,6 +103,12 @@ const BookingPage = ({
|
||||
);
|
||||
const stripeAppData = getStripeAppData(eventType);
|
||||
|
||||
// Define duration now that we support multiple duration eventTypes
|
||||
let duration = eventType.length;
|
||||
if (queryDuration && !isNaN(Number(queryDuration))) {
|
||||
duration = Number(queryDuration);
|
||||
}
|
||||
|
||||
useEffect(() => {
|
||||
if (top !== window) {
|
||||
//page_view will be collected automatically by _middleware.ts
|
||||
@@ -323,7 +323,7 @@ const BookingPage = ({
|
||||
const recurringBookings = recurringDates.map((recurringDate) => ({
|
||||
...booking,
|
||||
start: dayjs(recurringDate).format(),
|
||||
end: dayjs(recurringDate).add(eventType.length, "minute").format(),
|
||||
end: dayjs(recurringDate).add(duration, "minute").format(),
|
||||
eventTypeId: eventType.id,
|
||||
eventTypeSlug: eventType.slug,
|
||||
recurringEventId,
|
||||
@@ -356,7 +356,7 @@ const BookingPage = ({
|
||||
mutation.mutate({
|
||||
...booking,
|
||||
start: dayjs(date).format(),
|
||||
end: dayjs(date).add(eventType.length, "minute").format(),
|
||||
end: dayjs(date).add(duration, "minute").format(),
|
||||
eventTypeId: eventType.id,
|
||||
eventTypeSlug: eventType.slug,
|
||||
timeZone: timeZone(),
|
||||
@@ -627,7 +627,7 @@ const BookingPage = ({
|
||||
)}
|
||||
</>
|
||||
{/* TODO: Change name and id ="phone" to something generic */}
|
||||
{AttendeeInput && (
|
||||
{AttendeeInput && !disableInput && (
|
||||
<div className="mb-4">
|
||||
<label
|
||||
htmlFor={
|
||||
@@ -664,7 +664,6 @@ const BookingPage = ({
|
||||
: ""
|
||||
}
|
||||
required
|
||||
disabled={disableInput}
|
||||
/>
|
||||
</div>
|
||||
{bookingForm.formState.errors.phone && (
|
||||
@@ -845,7 +844,7 @@ const BookingPage = ({
|
||||
) : (
|
||||
<textarea
|
||||
{...bookingForm.register("notes")}
|
||||
required={!!eventType.metadata.additionalNotesRequired}
|
||||
required={!!eventType.metadata?.additionalNotesRequired}
|
||||
id="notes"
|
||||
name="notes"
|
||||
rows={3}
|
||||
@@ -860,7 +859,7 @@ const BookingPage = ({
|
||||
{!eventType.disableGuests && !guestToggle && (
|
||||
<Button
|
||||
type="button"
|
||||
color="minimalSecondary"
|
||||
color="minimal"
|
||||
size="icon"
|
||||
tooltip={t("additional_guests")}
|
||||
StartIcon={Icon.FiUserPlus}
|
||||
@@ -878,7 +877,6 @@ const BookingPage = ({
|
||||
</Button>
|
||||
<Button
|
||||
type="submit"
|
||||
className="dark:bg-darkmodebrand dark:text-darkmodebrandcontrast dark:hover:border-darkmodebrandcontrast mr-auto dark:border-transparent"
|
||||
data-testid={rescheduleUid ? "confirm-reschedule-button" : "confirm-book-button"}
|
||||
loading={mutation.isLoading || recurringMutation.isLoading}>
|
||||
{rescheduleUid ? t("reschedule") : t("confirm")}
|
||||
@@ -911,7 +909,7 @@ function ErrorMessage({ error }: { error: unknown }) {
|
||||
<div className="ltr:ml-3 rtl:mr-3">
|
||||
<p className="text-sm text-yellow-700">
|
||||
{rescheduleUid ? t("reschedule_fail") : t("booking_fail")}{" "}
|
||||
{error instanceof HttpError || error instanceof Error ? error.message : "Unknown error"}
|
||||
{error instanceof HttpError || error instanceof Error ? t(error.message) : "Unknown error"}
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@@ -1,74 +0,0 @@
|
||||
import * as DialogPrimitive from "@radix-ui/react-dialog";
|
||||
import React, { PropsWithChildren } from "react";
|
||||
|
||||
import { useLocale } from "@calcom/lib/hooks/useLocale";
|
||||
import { Button } from "@calcom/ui/Button";
|
||||
import { DialogClose, DialogContent } from "@calcom/ui/Dialog";
|
||||
import { Icon } from "@calcom/ui/Icon";
|
||||
|
||||
export type DeleteStripeDialogContentProps = {
|
||||
cancelAllBookingsBtnText?: string;
|
||||
removeBtnText?: string;
|
||||
cancelBtnText?: string;
|
||||
onConfirm?: (event: React.MouseEvent<HTMLElement, MouseEvent>) => void;
|
||||
onRemove?: (event: React.MouseEvent<HTMLElement, MouseEvent>) => void;
|
||||
title: string;
|
||||
variety?: "danger" | "warning" | "success";
|
||||
};
|
||||
|
||||
export default function DeleteStripeDialogContent(props: PropsWithChildren<DeleteStripeDialogContentProps>) {
|
||||
const { t } = useLocale();
|
||||
const {
|
||||
title,
|
||||
variety,
|
||||
cancelAllBookingsBtnText,
|
||||
removeBtnText,
|
||||
cancelBtnText = t("cancel"),
|
||||
onConfirm,
|
||||
onRemove,
|
||||
children,
|
||||
} = props;
|
||||
|
||||
return (
|
||||
<DialogContent>
|
||||
<div className="flex">
|
||||
{variety && (
|
||||
<div className="mt-0.5 ltr:mr-3">
|
||||
{variety === "danger" && (
|
||||
<div className="mx-auto rounded-full bg-red-100 p-2 text-center">
|
||||
<Icon.FiAlertTriangle className="h-5 w-5 text-red-600" />
|
||||
</div>
|
||||
)}
|
||||
{variety === "warning" && (
|
||||
<div className="mx-auto rounded-full bg-orange-100 p-2 text-center">
|
||||
<Icon.FiAlertTriangle className="h-5 w-5 text-orange-600" />
|
||||
</div>
|
||||
)}
|
||||
{variety === "success" && (
|
||||
<div className="mx-auto rounded-full bg-green-100 p-2 text-center">
|
||||
<Icon.FiCheck className="h-5 w-5 text-green-600" />
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
)}
|
||||
<div>
|
||||
<DialogPrimitive.Title className="font-cal text-xl text-gray-900">{title}</DialogPrimitive.Title>
|
||||
<DialogPrimitive.Description className="text-sm text-neutral-500">
|
||||
{children}
|
||||
</DialogPrimitive.Description>
|
||||
</div>
|
||||
</div>
|
||||
<div className="mt-5 flex flex-row-reverse gap-x-2 sm:mt-8">
|
||||
<DialogClose onClick={onConfirm} asChild>
|
||||
<Button color="alert">{cancelAllBookingsBtnText}</Button>
|
||||
</DialogClose>
|
||||
<DialogClose onClick={onRemove} asChild>
|
||||
<Button color="alert2">{removeBtnText}</Button>
|
||||
</DialogClose>
|
||||
<DialogClose asChild>
|
||||
<Button color="secondary">{cancelBtnText}</Button>
|
||||
</DialogClose>
|
||||
</div>
|
||||
</DialogContent>
|
||||
);
|
||||
}
|
||||
@@ -3,23 +3,21 @@ import { zodResolver } from "@hookform/resolvers/zod";
|
||||
import { isValidPhoneNumber } from "libphonenumber-js";
|
||||
import { useEffect } from "react";
|
||||
import { Controller, useForm, useWatch } from "react-hook-form";
|
||||
import { components } from "react-select";
|
||||
import { z } from "zod";
|
||||
|
||||
import {
|
||||
LocationType,
|
||||
getEventLocationType,
|
||||
EventLocationType,
|
||||
getEventLocationType,
|
||||
getHumanReadableLocationValue,
|
||||
getMessageForOrganizer,
|
||||
LocationObject,
|
||||
LocationType,
|
||||
} from "@calcom/app-store/locations";
|
||||
import { getMessageForOrganizer } from "@calcom/app-store/locations";
|
||||
import { getHumanReadableLocationValue } from "@calcom/app-store/locations";
|
||||
import { classNames } from "@calcom/lib";
|
||||
import { useLocale } from "@calcom/lib/hooks/useLocale";
|
||||
import { RouterOutputs, trpc } from "@calcom/trpc/react";
|
||||
import { Button } from "@calcom/ui";
|
||||
import { Dialog, DialogContent } from "@calcom/ui/Dialog";
|
||||
import { Icon } from "@calcom/ui/Icon";
|
||||
import PhoneInput from "@calcom/ui/form/PhoneInputLazy";
|
||||
import { Form } from "@calcom/ui/form/fields";
|
||||
import { Button, Dialog, DialogClose, DialogContent, DialogFooter, Form, Icon, PhoneInput } from "@calcom/ui";
|
||||
|
||||
import { QueryCell } from "@lib/QueryCell";
|
||||
|
||||
@@ -42,6 +40,7 @@ interface ISetLocationDialog {
|
||||
setShowLocationModal: React.Dispatch<React.SetStateAction<boolean>>;
|
||||
isOpenDialog: boolean;
|
||||
setSelectedLocation?: (param: OptionTypeBase | undefined) => void;
|
||||
setEditingLocationType?: (param: string) => void;
|
||||
}
|
||||
|
||||
const LocationInput = (props: {
|
||||
@@ -79,6 +78,7 @@ export const EditLocationDialog = (props: ISetLocationDialog) => {
|
||||
isOpenDialog,
|
||||
defaultValues,
|
||||
setSelectedLocation,
|
||||
setEditingLocationType,
|
||||
} = props;
|
||||
const { t } = useLocale();
|
||||
const locationsQuery = trpc.viewer.locationOptions.useQuery();
|
||||
@@ -212,13 +212,13 @@ export const EditLocationDialog = (props: ISetLocationDialog) => {
|
||||
|
||||
return (
|
||||
<Dialog open={isOpenDialog}>
|
||||
<DialogContent asChild>
|
||||
<div className="inline-block transform rounded-sm bg-white px-4 pt-5 pb-4 text-left align-bottom shadow-xl transition-all sm:my-8 sm:w-full sm:max-w-lg sm:p-6 sm:align-middle">
|
||||
<div className="mb-4 sm:flex sm:items-start">
|
||||
<div className="bg-secondary-100 mx-auto flex h-12 w-12 flex-shrink-0 items-center justify-center rounded-full sm:mx-0 sm:h-10 sm:w-10">
|
||||
<Icon.FiMapPin className="text-primary-600 h-6 w-6" />
|
||||
</div>
|
||||
<div className="mt-3 text-center sm:mt-0 sm:ml-4 sm:text-left">
|
||||
<DialogContent>
|
||||
<div className="flex flex-row space-x-3">
|
||||
<div className="bg-secondary-100 mx-auto flex h-12 w-12 flex-shrink-0 items-center justify-center rounded-full sm:mx-0 sm:h-10 sm:w-10">
|
||||
<Icon.FiMapPin className="text-primary-600 h-6 w-6" />
|
||||
</div>
|
||||
<div className="w-full">
|
||||
<div className="mt-3 text-center sm:mt-0 sm:text-left">
|
||||
<h3 className="text-lg font-medium leading-6 text-gray-900" id="modal-title">
|
||||
{t("edit_location")}
|
||||
</h3>
|
||||
@@ -226,118 +226,152 @@ export const EditLocationDialog = (props: ISetLocationDialog) => {
|
||||
<p className="text-sm text-gray-400">{t("this_input_will_shown_booking_this_event")}</p>
|
||||
)}
|
||||
</div>
|
||||
<div className="mt-3 text-center sm:mt-0 sm:ml-4 sm:text-left" />
|
||||
</div>
|
||||
{booking && (
|
||||
<>
|
||||
<p className="mt-6 mb-2 ml-1 text-sm font-bold text-black">{t("current_location")}:</p>
|
||||
<p className="mb-2 ml-1 text-sm text-black">
|
||||
{getHumanReadableLocationValue(booking.location, t)}
|
||||
</p>
|
||||
</>
|
||||
)}
|
||||
<Form
|
||||
form={locationFormMethods}
|
||||
handleSubmit={async (values) => {
|
||||
const { locationType: newLocation, displayLocationPublicly } = values;
|
||||
<div className="mt-3 text-center sm:mt-0 sm:text-left" />
|
||||
|
||||
let details = {};
|
||||
if (newLocation === LocationType.InPerson) {
|
||||
details = {
|
||||
address: values.locationAddress,
|
||||
};
|
||||
}
|
||||
const eventLocationType = getEventLocationType(newLocation);
|
||||
{booking && (
|
||||
<>
|
||||
<p className="mt-6 mb-2 ml-1 text-sm font-bold text-black">{t("current_location")}:</p>
|
||||
<p className="mb-2 ml-1 text-sm text-black">
|
||||
{getHumanReadableLocationValue(booking.location, t)}
|
||||
</p>
|
||||
</>
|
||||
)}
|
||||
<Form
|
||||
form={locationFormMethods}
|
||||
handleSubmit={async (values) => {
|
||||
const { locationType: newLocation, displayLocationPublicly } = values;
|
||||
|
||||
// TODO: There can be a property that tells if it is to be saved in `link`
|
||||
if (
|
||||
newLocation === LocationType.Link ||
|
||||
(!eventLocationType?.default && eventLocationType?.linkType === "static")
|
||||
) {
|
||||
details = { link: values.locationLink };
|
||||
}
|
||||
let details = {};
|
||||
if (newLocation === LocationType.InPerson) {
|
||||
details = {
|
||||
address: values.locationAddress,
|
||||
};
|
||||
}
|
||||
const eventLocationType = getEventLocationType(newLocation);
|
||||
|
||||
if (newLocation === LocationType.UserPhone) {
|
||||
details = { hostPhoneNumber: values.locationPhoneNumber };
|
||||
}
|
||||
// TODO: There can be a property that tells if it is to be saved in `link`
|
||||
if (
|
||||
newLocation === LocationType.Link ||
|
||||
(!eventLocationType?.default && eventLocationType?.linkType === "static")
|
||||
) {
|
||||
details = { link: values.locationLink };
|
||||
}
|
||||
|
||||
if (eventLocationType?.organizerInputType) {
|
||||
details = {
|
||||
...details,
|
||||
displayLocationPublicly,
|
||||
};
|
||||
}
|
||||
if (newLocation === LocationType.UserPhone) {
|
||||
details = { hostPhoneNumber: values.locationPhoneNumber };
|
||||
}
|
||||
|
||||
saveLocation(newLocation, details);
|
||||
setShowLocationModal(false);
|
||||
setSelectedLocation?.(undefined);
|
||||
locationFormMethods.unregister([
|
||||
"locationType",
|
||||
"locationLink",
|
||||
"locationAddress",
|
||||
"locationPhoneNumber",
|
||||
]);
|
||||
}}>
|
||||
<QueryCell
|
||||
query={locationsQuery}
|
||||
success={({ data: locationOptions }) => {
|
||||
if (!locationOptions.length) return null;
|
||||
return (
|
||||
<Controller
|
||||
name="locationType"
|
||||
control={locationFormMethods.control}
|
||||
render={() => (
|
||||
<Select
|
||||
maxMenuHeight={150}
|
||||
name="location"
|
||||
defaultValue={selection}
|
||||
options={
|
||||
booking
|
||||
? locationOptions.filter(
|
||||
(location) =>
|
||||
location.value !== "phone" && location.value !== "attendeeInPerson"
|
||||
)
|
||||
: locationOptions
|
||||
}
|
||||
isSearchable
|
||||
className="my-4 block w-full min-w-0 flex-1 rounded-sm border border-gray-300 text-sm"
|
||||
onChange={(val) => {
|
||||
if (val) {
|
||||
locationFormMethods.setValue("locationType", val.value);
|
||||
locationFormMethods.unregister([
|
||||
"locationLink",
|
||||
"locationAddress",
|
||||
"locationPhoneNumber",
|
||||
]);
|
||||
locationFormMethods.clearErrors([
|
||||
"locationLink",
|
||||
"locationPhoneNumber",
|
||||
"locationAddress",
|
||||
]);
|
||||
setSelectedLocation?.(val);
|
||||
}
|
||||
}}
|
||||
/>
|
||||
)}
|
||||
/>
|
||||
);
|
||||
}}
|
||||
/>
|
||||
{selectedLocation && LocationOptions}
|
||||
<div className="mt-4 flex justify-end space-x-2">
|
||||
<Button
|
||||
onClick={() => {
|
||||
setShowLocationModal(false);
|
||||
setSelectedLocation?.(undefined);
|
||||
locationFormMethods.unregister("locationType");
|
||||
if (eventLocationType?.organizerInputType) {
|
||||
details = {
|
||||
...details,
|
||||
displayLocationPublicly,
|
||||
};
|
||||
}
|
||||
|
||||
saveLocation(newLocation, details);
|
||||
setShowLocationModal(false);
|
||||
setSelectedLocation?.(undefined);
|
||||
locationFormMethods.unregister([
|
||||
"locationType",
|
||||
"locationLink",
|
||||
"locationAddress",
|
||||
"locationPhoneNumber",
|
||||
]);
|
||||
}}>
|
||||
<QueryCell
|
||||
query={locationsQuery}
|
||||
success={({ data: locationOptions }) => {
|
||||
if (!locationOptions.length) return null;
|
||||
if (booking) {
|
||||
locationOptions.forEach((location) => {
|
||||
if (location.label === "phone") {
|
||||
location.options.filter((l) => l.value !== "phone");
|
||||
} else if (location.label === "in person") {
|
||||
location.options.filter((l) => l.value !== "attendeeInPerson");
|
||||
}
|
||||
});
|
||||
}
|
||||
return (
|
||||
<Controller
|
||||
name="locationType"
|
||||
control={locationFormMethods.control}
|
||||
render={() => (
|
||||
<Select<{ label: string; value: string; icon?: string }>
|
||||
maxMenuHeight={150}
|
||||
name="location"
|
||||
defaultValue={selection}
|
||||
options={locationOptions}
|
||||
components={{
|
||||
Option: (props) => (
|
||||
<components.Option {...props}>
|
||||
<div className="flex items-center gap-3">
|
||||
{props.data.icon && (
|
||||
<img src={props.data.icon} alt="cover" className="h-3.5 w-3.5" />
|
||||
)}
|
||||
<span
|
||||
className={classNames(
|
||||
"text-sm font-medium",
|
||||
props.isSelected ? "text-white" : "text-gray-900"
|
||||
)}>
|
||||
{props.data.label}
|
||||
</span>
|
||||
</div>
|
||||
</components.Option>
|
||||
),
|
||||
}}
|
||||
formatOptionLabel={(e) => (
|
||||
<div className="flex items-center gap-3">
|
||||
{e.icon && <img src={e.icon} alt="app-icon" className="h-5 w-5" />}
|
||||
<span>{e.label}</span>
|
||||
</div>
|
||||
)}
|
||||
formatGroupLabel={(e) => (
|
||||
<p className="text-xs font-medium text-gray-600">{e.label}</p>
|
||||
)}
|
||||
isSearchable
|
||||
className="my-4 block w-full min-w-0 flex-1 rounded-sm border border-gray-300 text-sm"
|
||||
onChange={(val) => {
|
||||
if (val) {
|
||||
locationFormMethods.setValue("locationType", val.value);
|
||||
locationFormMethods.unregister([
|
||||
"locationLink",
|
||||
"locationAddress",
|
||||
"locationPhoneNumber",
|
||||
]);
|
||||
locationFormMethods.clearErrors([
|
||||
"locationLink",
|
||||
"locationPhoneNumber",
|
||||
"locationAddress",
|
||||
]);
|
||||
setSelectedLocation?.(val);
|
||||
}
|
||||
}}
|
||||
/>
|
||||
)}
|
||||
/>
|
||||
);
|
||||
}}
|
||||
type="button"
|
||||
color="secondary">
|
||||
{t("cancel")}
|
||||
</Button>
|
||||
<Button type="submit">{t("update")}</Button>
|
||||
</div>
|
||||
</Form>
|
||||
/>
|
||||
{selectedLocation && LocationOptions}
|
||||
<DialogFooter>
|
||||
<div className="mt-4 flex justify-end space-x-2">
|
||||
<Button
|
||||
onClick={() => {
|
||||
setShowLocationModal(false);
|
||||
setSelectedLocation?.(undefined);
|
||||
setEditingLocationType?.("");
|
||||
locationFormMethods.unregister("locationType");
|
||||
}}
|
||||
type="button"
|
||||
color="secondary">
|
||||
{t("cancel")}
|
||||
</Button>
|
||||
|
||||
<Button type="submit">{t("update")}</Button>
|
||||
</div>
|
||||
</DialogFooter>
|
||||
</Form>
|
||||
</div>
|
||||
</div>
|
||||
</DialogContent>
|
||||
</Dialog>
|
||||
|
||||
@@ -1,12 +1,18 @@
|
||||
import React, { useState, Dispatch, SetStateAction } from "react";
|
||||
import { Dispatch, SetStateAction, useState } from "react";
|
||||
|
||||
import { useLocale } from "@calcom/lib/hooks/useLocale";
|
||||
import { trpc } from "@calcom/trpc/react";
|
||||
import { Dialog, DialogClose, DialogContent, DialogFooter, DialogHeader } from "@calcom/ui/Dialog";
|
||||
import { Icon } from "@calcom/ui/Icon";
|
||||
import { Button } from "@calcom/ui/components/button";
|
||||
import { TextArea } from "@calcom/ui/form/fields";
|
||||
import showToast from "@calcom/ui/v2/core/notifications";
|
||||
import {
|
||||
Button,
|
||||
Dialog,
|
||||
DialogClose,
|
||||
DialogContent,
|
||||
DialogFooter,
|
||||
DialogHeader,
|
||||
Icon,
|
||||
showToast,
|
||||
TextArea,
|
||||
} from "@calcom/ui";
|
||||
|
||||
interface IRescheduleDialog {
|
||||
isOpenDialog: boolean;
|
||||
@@ -41,7 +47,6 @@ export const RescheduleDialog = (props: IRescheduleDialog) => {
|
||||
</div>
|
||||
<div className="pt-1">
|
||||
<DialogHeader title={t("send_reschedule_request")} />
|
||||
|
||||
<p className="-mt-8 text-sm text-gray-500">{t("reschedule_modal_description")}</p>
|
||||
<p className="mt-6 mb-2 text-sm font-bold text-black">
|
||||
{t("reason_for_reschedule_request")}
|
||||
@@ -56,9 +61,7 @@ export const RescheduleDialog = (props: IRescheduleDialog) => {
|
||||
/>
|
||||
|
||||
<DialogFooter>
|
||||
<DialogClose asChild>
|
||||
<Button color="secondary">{t("cancel")}</Button>
|
||||
</DialogClose>
|
||||
<DialogClose />
|
||||
<Button
|
||||
data-testid="send_request"
|
||||
disabled={isLoading}
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
import { FormValues } from "pages/event-types/[type]";
|
||||
import { Controller, useFormContext } from "react-hook-form";
|
||||
import { SingleValueProps, OptionProps, components } from "react-select";
|
||||
import { components, OptionProps, SingleValueProps } from "react-select";
|
||||
|
||||
import dayjs from "@calcom/dayjs";
|
||||
import classNames from "@calcom/lib/classNames";
|
||||
@@ -8,12 +8,7 @@ import { useLocale } from "@calcom/lib/hooks/useLocale";
|
||||
import { weekdayNames } from "@calcom/lib/weekday";
|
||||
import { trpc } from "@calcom/trpc/react";
|
||||
import useMeQuery from "@calcom/trpc/react/hooks/useMeQuery";
|
||||
import { Icon } from "@calcom/ui";
|
||||
import { Badge } from "@calcom/ui/components/badge";
|
||||
import { Button } from "@calcom/ui/components/button";
|
||||
import { SettingsToggle } from "@calcom/ui/v2";
|
||||
import Select from "@calcom/ui/v2/core/form/select";
|
||||
import { SkeletonText } from "@calcom/ui/v2/core/skeleton";
|
||||
import { Badge, Button, Icon, Select, SettingsToggle, SkeletonText } from "@calcom/ui";
|
||||
|
||||
import { SelectSkeletonLoader } from "@components/availability/SkeletonLoader";
|
||||
|
||||
|
||||
@@ -11,19 +11,25 @@ import { WEBAPP_URL } from "@calcom/lib/constants";
|
||||
import { useLocale } from "@calcom/lib/hooks/useLocale";
|
||||
import { createEventTypeInput } from "@calcom/prisma/zod/custom/eventtype";
|
||||
import { trpc } from "@calcom/trpc/react";
|
||||
import { Alert } from "@calcom/ui/Alert";
|
||||
import { Button } from "@calcom/ui/Button";
|
||||
import { Dialog, DialogClose, DialogContent } from "@calcom/ui/Dialog";
|
||||
import Dropdown, {
|
||||
import {
|
||||
Alert,
|
||||
Button,
|
||||
Dialog,
|
||||
DialogClose,
|
||||
DialogContent,
|
||||
Dropdown,
|
||||
DropdownMenuContent,
|
||||
DropdownMenuItem,
|
||||
DropdownMenuLabel,
|
||||
DropdownMenuSeparator,
|
||||
DropdownMenuTrigger,
|
||||
} from "@calcom/ui/Dropdown";
|
||||
import { Icon } from "@calcom/ui/Icon";
|
||||
import { Form, InputLeading, TextAreaField, TextField } from "@calcom/ui/form/fields";
|
||||
import showToast from "@calcom/ui/v2/core/notifications";
|
||||
Form,
|
||||
Icon,
|
||||
InputLeading,
|
||||
showToast,
|
||||
TextAreaField,
|
||||
TextField,
|
||||
} from "@calcom/ui";
|
||||
|
||||
import { HttpError } from "@lib/core/http/error";
|
||||
import { slugify } from "@lib/slugify";
|
||||
@@ -258,9 +264,7 @@ export default function CreateEventTypeButton(props: CreateEventTypeBtnProps) {
|
||||
<Button type="submit" loading={createMutation.isLoading}>
|
||||
{t("continue")}
|
||||
</Button>
|
||||
<DialogClose asChild>
|
||||
<Button color="secondary">{t("cancel")}</Button>
|
||||
</DialogClose>
|
||||
<DialogClose />
|
||||
</div>
|
||||
</Form>
|
||||
</DialogContent>
|
||||
|
||||
@@ -1,10 +1,8 @@
|
||||
import { EventTypeCustomInput, EventTypeCustomInputType } from "@prisma/client";
|
||||
import React, { FC } from "react";
|
||||
import { FC } from "react";
|
||||
import { Controller, SubmitHandler, useForm, useWatch } from "react-hook-form";
|
||||
|
||||
import { Button } from "@calcom/ui/components";
|
||||
import { TextField } from "@calcom/ui/components/form";
|
||||
import { Select } from "@calcom/ui/v2";
|
||||
import { Button, Select, TextField } from "@calcom/ui";
|
||||
|
||||
import { useLocale } from "@lib/hooks/useLocale";
|
||||
|
||||
|
||||
@@ -4,24 +4,21 @@ import { isValidPhoneNumber } from "libphonenumber-js";
|
||||
import { Trans } from "next-i18next";
|
||||
import { useEffect } from "react";
|
||||
import { Controller, useForm, useWatch } from "react-hook-form";
|
||||
import { components } from "react-select";
|
||||
import { z } from "zod";
|
||||
|
||||
import {
|
||||
LocationType,
|
||||
getEventLocationType,
|
||||
EventLocationType,
|
||||
getEventLocationType,
|
||||
getHumanReadableLocationValue,
|
||||
getMessageForOrganizer,
|
||||
LocationObject,
|
||||
LocationType,
|
||||
} from "@calcom/app-store/locations";
|
||||
import { classNames } from "@calcom/lib";
|
||||
import { useLocale } from "@calcom/lib/hooks/useLocale";
|
||||
import { RouterOutputs, trpc } from "@calcom/trpc/react";
|
||||
import { Icon } from "@calcom/ui/Icon";
|
||||
import { Button } from "@calcom/ui/components";
|
||||
import { Label, Form } from "@calcom/ui/components/form";
|
||||
import PhoneInput from "@calcom/ui/form/PhoneInputLazy";
|
||||
import { Dialog, DialogContent } from "@calcom/ui/v2";
|
||||
import { Select } from "@calcom/ui/v2/";
|
||||
import { Button, Dialog, DialogContent, Form, Icon, Label, PhoneInput, Select } from "@calcom/ui";
|
||||
|
||||
import { QueryCell } from "@lib/QueryCell";
|
||||
|
||||
@@ -214,7 +211,6 @@ export const EditLocationDialog = (props: ISetLocationDialog) => {
|
||||
<DialogContent
|
||||
type="creation"
|
||||
Icon={Icon.FiMapPin}
|
||||
useOwnActionButtons
|
||||
title={t("edit_location")}
|
||||
description={!booking ? t("this_input_will_shown_booking_this_event") : undefined}>
|
||||
{booking && (
|
||||
@@ -278,20 +274,48 @@ export const EditLocationDialog = (props: ISetLocationDialog) => {
|
||||
query={locationsQuery}
|
||||
success={({ data: locationOptions }) => {
|
||||
if (!locationOptions.length) return null;
|
||||
if (booking) {
|
||||
locationOptions.forEach((location) => {
|
||||
if (location.label === "phone") {
|
||||
location.options.filter((l) => l.value !== "phone");
|
||||
}
|
||||
});
|
||||
}
|
||||
return (
|
||||
<Controller
|
||||
name="locationType"
|
||||
control={locationFormMethods.control}
|
||||
render={() => (
|
||||
<Select
|
||||
<Select<{ label: string; value: string; icon?: string }>
|
||||
maxMenuHeight={150}
|
||||
name="location"
|
||||
defaultValue={selection}
|
||||
options={
|
||||
booking
|
||||
? locationOptions.filter((location) => location.value !== "phone")
|
||||
: locationOptions
|
||||
}
|
||||
options={locationOptions}
|
||||
components={{
|
||||
Option: (props) => (
|
||||
<components.Option {...props}>
|
||||
<div className="flex items-center gap-3">
|
||||
{props.data.icon && (
|
||||
<img src={props.data.icon} alt="cover" className="h-3.5 w-3.5" />
|
||||
)}
|
||||
<span
|
||||
className={classNames(
|
||||
"text-sm font-medium",
|
||||
props.isSelected ? "text-white" : "text-gray-900"
|
||||
)}>
|
||||
{props.data.label}
|
||||
</span>
|
||||
</div>
|
||||
</components.Option>
|
||||
),
|
||||
}}
|
||||
formatOptionLabel={(e) => (
|
||||
<div className="flex items-center gap-2.5">
|
||||
{e.icon && <img src={e.icon} alt="app-icon" className="h-5 w-5" />}
|
||||
<span>{e.label}</span>
|
||||
</div>
|
||||
)}
|
||||
formatGroupLabel={(e) => <p className="text-xs font-medium text-gray-600">{e.label}</p>}
|
||||
isSearchable={false}
|
||||
onChange={(val) => {
|
||||
if (val) {
|
||||
|
||||
@@ -10,9 +10,21 @@ import DestinationCalendarSelector from "@calcom/features/calendars/DestinationC
|
||||
import { CAL_URL } from "@calcom/lib/constants";
|
||||
import { useLocale } from "@calcom/lib/hooks/useLocale";
|
||||
import { trpc } from "@calcom/trpc/react";
|
||||
import { Icon } from "@calcom/ui";
|
||||
import { Checkbox, Button, TextField, Label } from "@calcom/ui/components";
|
||||
import { CustomInputItem, Dialog, DialogContent, SettingsToggle, showToast, Tooltip } from "@calcom/ui/v2";
|
||||
import {
|
||||
Button,
|
||||
Checkbox,
|
||||
CustomInputItem,
|
||||
Dialog,
|
||||
DialogClose,
|
||||
DialogContent,
|
||||
DialogFooter,
|
||||
Icon,
|
||||
Label,
|
||||
SettingsToggle,
|
||||
showToast,
|
||||
TextField,
|
||||
Tooltip,
|
||||
} from "@calcom/ui";
|
||||
|
||||
import CustomInputTypeForm from "@components/eventtype/CustomInputTypeForm";
|
||||
|
||||
@@ -98,7 +110,7 @@ export const EventAdvancedTab = ({ eventType, team }: Pick<EventTypeSetupInfered
|
||||
StartIcon={Icon.FiEdit}
|
||||
size="icon"
|
||||
color="minimal"
|
||||
className="hover:stroke-3 hover:bg-transparent hover:text-black"
|
||||
className="hover:stroke-3 min-w-fit px-0 hover:bg-transparent hover:text-black"
|
||||
onClick={() => setShowEventNameTip((old) => !old)}
|
||||
/>
|
||||
}
|
||||
@@ -342,11 +354,7 @@ export const EventAdvancedTab = ({ eventType, team }: Pick<EventTypeSetupInfered
|
||||
<DialogContent
|
||||
title={t("custom_event_name")}
|
||||
description={t("custom_event_name_description")}
|
||||
type="creation"
|
||||
actionText="Create"
|
||||
// Set event name back to what it was on close
|
||||
actionOnClose={() => formMethods.setValue("eventName", eventType.eventName ?? "")}
|
||||
actionOnClick={() => setShowEventNameTip(false)}>
|
||||
type="creation">
|
||||
<TextField
|
||||
label={t("event_name")}
|
||||
type="text"
|
||||
@@ -360,6 +368,12 @@ export const EventAdvancedTab = ({ eventType, team }: Pick<EventTypeSetupInfered
|
||||
<p>{`{HOST/ATTENDEE} = ${t("dynamically_display_attendee_or_organizer")}`}</p>
|
||||
<p>{`{LOCATION} = ${t("event_location")}`}</p>
|
||||
</div>
|
||||
<DialogFooter>
|
||||
<Button color="primary" onClick={() => setShowEventNameTip(false)}>
|
||||
{t("create")}
|
||||
</Button>
|
||||
<DialogClose onClick={() => formMethods.setValue("eventName", eventType.eventName ?? "")} />
|
||||
</DialogFooter>
|
||||
</DialogContent>
|
||||
</Dialog>
|
||||
)}
|
||||
@@ -373,7 +387,6 @@ export const EventAdvancedTab = ({ eventType, team }: Pick<EventTypeSetupInfered
|
||||
type="creation"
|
||||
Icon={Icon.FiPlus}
|
||||
title={t("add_new_custom_input_field")}
|
||||
useOwnActionButtons
|
||||
description={t("this_input_will_shown_booking_this_event")}>
|
||||
<CustomInputTypeForm
|
||||
selectedCustomInput={selectedCustomInput}
|
||||
|
||||
@@ -1,5 +1,4 @@
|
||||
import { EventTypeSetupInfered, FormValues } from "pages/event-types/[type]";
|
||||
import React from "react";
|
||||
import { useFormContext } from "react-hook-form";
|
||||
|
||||
import EventTypeAppContext, { GetAppData, SetAppData } from "@calcom/app-store/EventTypeAppContext";
|
||||
@@ -8,10 +7,7 @@ import { EventTypeAppCardComponentProps } from "@calcom/app-store/types";
|
||||
import { EventTypeAppsList } from "@calcom/app-store/utils";
|
||||
import { useLocale } from "@calcom/lib/hooks/useLocale";
|
||||
import { RouterOutputs, trpc } from "@calcom/trpc/react";
|
||||
import { Icon } from "@calcom/ui";
|
||||
import ErrorBoundary from "@calcom/ui/ErrorBoundary";
|
||||
import { Button } from "@calcom/ui/components";
|
||||
import { EmptyScreen } from "@calcom/ui/v2";
|
||||
import { Button, EmptyScreen, ErrorBoundary, Icon } from "@calcom/ui";
|
||||
|
||||
type EventType = Pick<EventTypeSetupInfered, "eventType">["eventType"] &
|
||||
EventTypeAppCardComponentProps["eventType"];
|
||||
|
||||
@@ -10,11 +10,7 @@ import findDurationType from "@calcom/lib/findDurationType";
|
||||
import { useLocale } from "@calcom/lib/hooks/useLocale";
|
||||
import { PeriodType } from "@calcom/prisma/client";
|
||||
import type { BookingLimit } from "@calcom/types/Calendar";
|
||||
import { Icon } from "@calcom/ui";
|
||||
import { Button } from "@calcom/ui/components";
|
||||
import { Input, InputField, Label } from "@calcom/ui/components/form";
|
||||
import { Select, SettingsToggle } from "@calcom/ui/v2";
|
||||
import DateRangePicker from "@calcom/ui/v2/core/form/date-range-picker/DateRangePicker";
|
||||
import { Button, DateRangePicker, Icon, Input, InputField, Label, Select, SettingsToggle } from "@calcom/ui";
|
||||
|
||||
export const EventLimitsTab = ({ eventType }: Pick<EventTypeSetupInfered, "eventType">) => {
|
||||
const { t } = useLocale();
|
||||
@@ -173,7 +169,7 @@ export const EventLimitsTab = ({ eventType }: Pick<EventTypeSetupInfered, "event
|
||||
</div>
|
||||
</div>
|
||||
<div className="flex flex-col space-y-4 pt-4 lg:flex-row lg:space-y-0 lg:space-x-4">
|
||||
<div className="flex w-full items-end">
|
||||
<div className="flex w-full items-end justify-end">
|
||||
<Controller
|
||||
name="minimumBookingNotice"
|
||||
control={formMethods.control}
|
||||
@@ -201,7 +197,7 @@ export const EventLimitsTab = ({ eventType }: Pick<EventTypeSetupInfered, "event
|
||||
label={t("minimum_booking_notice")}
|
||||
type="number"
|
||||
placeholder="120"
|
||||
className="mr-2 rounded-[4px]"
|
||||
className="mr-2 mb-0 h-[38px] rounded-[4px]"
|
||||
{...formMethods.register("minimumBookingNoticeInDurationType", {
|
||||
onChange: (event: React.ChangeEvent<HTMLInputElement>) =>
|
||||
onMinimumNoticeChange(event.target.value),
|
||||
@@ -211,7 +207,7 @@ export const EventLimitsTab = ({ eventType }: Pick<EventTypeSetupInfered, "event
|
||||
</div>
|
||||
<Select
|
||||
isSearchable={false}
|
||||
className="mb-2 ml-2 w-full capitalize md:min-w-[150px] md:max-w-[200px]"
|
||||
className="mb-0 ml-2 h-[38px] w-full capitalize md:min-w-[150px] md:max-w-[200px]"
|
||||
defaultValue={durationTypeOptions.find(
|
||||
(option) => option.value === minimumBookingNoticeType.current
|
||||
)}
|
||||
@@ -234,7 +230,7 @@ export const EventLimitsTab = ({ eventType }: Pick<EventTypeSetupInfered, "event
|
||||
label: t("slot_interval_default"),
|
||||
value: -1,
|
||||
},
|
||||
...[5, 10, 15, 20, 30, 45, 60].map((minutes) => ({
|
||||
...[5, 10, 15, 20, 30, 45, 60, 75, 90, 105, 120].map((minutes) => ({
|
||||
label: minutes + " " + t("minutes"),
|
||||
value: minutes,
|
||||
})),
|
||||
@@ -289,7 +285,7 @@ export const EventLimitsTab = ({ eventType }: Pick<EventTypeSetupInfered, "event
|
||||
<SettingsToggle
|
||||
title={t("limit_future_bookings")}
|
||||
description={t("limit_future_bookings_description")}
|
||||
checked={value !== "UNLIMITED"}
|
||||
checked={value && value !== "UNLIMITED"}
|
||||
onCheckedChange={(bool) => formMethods.setValue("periodType", bool ? "ROLLING" : "UNLIMITED")}>
|
||||
<RadioGroup.Root
|
||||
defaultValue={watchPeriodType}
|
||||
|
||||
@@ -4,19 +4,17 @@ import { isValidPhoneNumber } from "libphonenumber-js";
|
||||
import { EventTypeSetupInfered, FormValues } from "pages/event-types/[type]";
|
||||
import { useState } from "react";
|
||||
import { Controller, useForm, useFormContext } from "react-hook-form";
|
||||
import { MultiValue } from "react-select";
|
||||
import { z } from "zod";
|
||||
|
||||
import { getEventLocationType, EventLocationType } from "@calcom/app-store/locations";
|
||||
import { EventLocationType, getEventLocationType } from "@calcom/app-store/locations";
|
||||
import { CAL_URL } from "@calcom/lib/constants";
|
||||
import { useLocale } from "@calcom/lib/hooks/useLocale";
|
||||
import { Icon } from "@calcom/ui/Icon";
|
||||
import { Button } from "@calcom/ui/components";
|
||||
import { Label, TextField } from "@calcom/ui/components/form";
|
||||
import { Select, Skeleton } from "@calcom/ui/v2";
|
||||
import { Button, Icon, Label, Select, Skeleton, TextField, SettingsToggle } from "@calcom/ui";
|
||||
|
||||
import { slugify } from "@lib/slugify";
|
||||
|
||||
import { EditLocationDialog } from "@components/eventtype/EditLocationDialog";
|
||||
import { EditLocationDialog } from "@components/dialog/EditLocationDialog";
|
||||
|
||||
type OptionTypeBase = {
|
||||
label: string;
|
||||
@@ -31,7 +29,36 @@ export const EventSetupTab = (
|
||||
const formMethods = useFormContext<FormValues>();
|
||||
const { eventType, locationOptions, team } = props;
|
||||
const [showLocationModal, setShowLocationModal] = useState(false);
|
||||
const [editingLocationType, setEditingLocationType] = useState<string>("");
|
||||
const [selectedLocation, setSelectedLocation] = useState<OptionTypeBase | undefined>(undefined);
|
||||
const [multipleDuration, setMultipleDuration] = useState(eventType.metadata.multipleDuration);
|
||||
|
||||
const multipleDurationOptions = [
|
||||
{ value: 5, label: t("multiple_duration_mins", { count: 5 }) },
|
||||
{ value: 10, label: t("multiple_duration_mins", { count: 10 }) },
|
||||
{ value: 15, label: t("multiple_duration_mins", { count: 15 }) },
|
||||
{ value: 20, label: t("multiple_duration_mins", { count: 20 }) },
|
||||
{ value: 25, label: t("multiple_duration_mins", { count: 25 }) },
|
||||
{ value: 30, label: t("multiple_duration_mins", { count: 30 }) },
|
||||
{ value: 45, label: t("multiple_duration_mins", { count: 45 }) },
|
||||
{ value: 50, label: t("multiple_duration_mins", { count: 50 }) },
|
||||
{ value: 60, label: t("multiple_duration_mins", { count: 60 }) },
|
||||
{ value: 75, label: t("multiple_duration_mins", { count: 75 }) },
|
||||
{ value: 80, label: t("multiple_duration_mins", { count: 80 }) },
|
||||
{ value: 90, label: t("multiple_duration_mins", { count: 90 }) },
|
||||
{ value: 120, label: t("multiple_duration_mins", { count: 120 }) },
|
||||
{ value: 180, label: t("multiple_duration_mins", { count: 180 }) },
|
||||
];
|
||||
|
||||
const [selectedMultipleDuration, setSelectedMultipleDuration] = useState<
|
||||
MultiValue<{
|
||||
value: number;
|
||||
label: string;
|
||||
}>
|
||||
>(multipleDurationOptions.filter((mdOpt) => multipleDuration?.includes(mdOpt.value)));
|
||||
const [defaultDuration, setDefaultDuration] = useState(
|
||||
selectedMultipleDuration.find((opt) => opt.value === eventType.length) ?? null
|
||||
);
|
||||
|
||||
const openLocationModal = (type: EventLocationType["type"]) => {
|
||||
setSelectedLocation(locationOptions.find((option) => option.value === type));
|
||||
@@ -46,14 +73,23 @@ export const EventSetupTab = (
|
||||
);
|
||||
};
|
||||
|
||||
const addLocation = (newLocationType: EventLocationType["type"], details = {}) => {
|
||||
const existingIdx = formMethods.getValues("locations").findIndex((loc) => newLocationType === loc.type);
|
||||
const saveLocation = (newLocationType: EventLocationType["type"], details = {}) => {
|
||||
const locationType = editingLocationType !== "" ? editingLocationType : newLocationType;
|
||||
const existingIdx = formMethods.getValues("locations").findIndex((loc) => locationType === loc.type);
|
||||
if (existingIdx !== -1) {
|
||||
const copy = formMethods.getValues("locations");
|
||||
copy[existingIdx] = {
|
||||
...formMethods.getValues("locations")[existingIdx],
|
||||
...details,
|
||||
};
|
||||
if (editingLocationType !== "") {
|
||||
copy[existingIdx] = {
|
||||
...details,
|
||||
type: newLocationType,
|
||||
};
|
||||
} else {
|
||||
copy[existingIdx] = {
|
||||
...formMethods.getValues("locations")[existingIdx],
|
||||
...details,
|
||||
};
|
||||
}
|
||||
|
||||
formMethods.setValue("locations", copy);
|
||||
} else {
|
||||
formMethods.setValue(
|
||||
@@ -61,6 +97,8 @@ export const EventSetupTab = (
|
||||
formMethods.getValues("locations").concat({ type: newLocationType, ...details })
|
||||
);
|
||||
}
|
||||
|
||||
setEditingLocationType("");
|
||||
setShowLocationModal(false);
|
||||
};
|
||||
|
||||
@@ -118,7 +156,7 @@ export const EventSetupTab = (
|
||||
if (eventLocationType.organizerInputType) {
|
||||
openLocationModal(newLocationType);
|
||||
} else {
|
||||
addLocation(newLocationType);
|
||||
saveLocation(newLocationType);
|
||||
}
|
||||
}
|
||||
}}
|
||||
@@ -138,10 +176,10 @@ export const EventSetupTab = (
|
||||
<div key={index} className="flex flex-grow items-center">
|
||||
<img
|
||||
src={eventLocationType.iconUrl}
|
||||
className="h-6 w-6"
|
||||
className="h-4 w-4"
|
||||
alt={`${eventLocationType.label} logo`}
|
||||
/>
|
||||
<span className="truncate text-sm ltr:ml-2 rtl:mr-2">
|
||||
<span className="truncate text-sm ltr:ml-1 rtl:mr-1">
|
||||
{location[eventLocationType.defaultValueVariable] || eventLocationType.label}
|
||||
</span>
|
||||
</div>
|
||||
@@ -153,6 +191,7 @@ export const EventSetupTab = (
|
||||
locationFormMethods.unregister("locationLink");
|
||||
locationFormMethods.unregister("locationAddress");
|
||||
locationFormMethods.unregister("locationPhoneNumber");
|
||||
setEditingLocationType(location.type);
|
||||
openLocationModal(location.type);
|
||||
}}
|
||||
aria-label={t("edit")}
|
||||
@@ -209,17 +248,91 @@ export const EventSetupTab = (
|
||||
setValueAs: (v) => slugify(v),
|
||||
})}
|
||||
/>
|
||||
<TextField
|
||||
required
|
||||
name="length"
|
||||
type="number"
|
||||
label={t("duration")}
|
||||
addOnSuffix={<>{t("minutes")}</>}
|
||||
defaultValue={eventType.length ?? 15}
|
||||
onChange={(e) => {
|
||||
formMethods.setValue("length", Number(e.target.value));
|
||||
}}
|
||||
/>
|
||||
{multipleDuration ? (
|
||||
<div className="space-y-4">
|
||||
<div>
|
||||
<Skeleton as={Label} loadingClassName="w-16">
|
||||
{t("available_durations")}
|
||||
</Skeleton>
|
||||
<Select
|
||||
isMulti
|
||||
defaultValue={selectedMultipleDuration}
|
||||
name="metadata.multipleDuration"
|
||||
isSearchable={false}
|
||||
className="h-auto !min-h-[36px] text-sm"
|
||||
options={multipleDurationOptions}
|
||||
onChange={(options) => {
|
||||
const values = options
|
||||
.map((opt) => opt.value)
|
||||
.sort(function (a, b) {
|
||||
return a - b;
|
||||
});
|
||||
setMultipleDuration(values);
|
||||
setSelectedMultipleDuration(options);
|
||||
if (!options.find((opt) => opt.value === defaultDuration?.value)) {
|
||||
if (options.length > 0) {
|
||||
setDefaultDuration(options[0]);
|
||||
} else {
|
||||
setDefaultDuration(null);
|
||||
}
|
||||
}
|
||||
if (options.length === 1 && defaultDuration === null) {
|
||||
setDefaultDuration(options[0]);
|
||||
}
|
||||
formMethods.setValue("metadata.multipleDuration", values);
|
||||
}}
|
||||
/>
|
||||
</div>
|
||||
<div>
|
||||
<Skeleton as={Label} loadingClassName="w-16">
|
||||
{t("default_duration")}
|
||||
</Skeleton>
|
||||
<Select
|
||||
value={defaultDuration}
|
||||
isSearchable={false}
|
||||
name="length"
|
||||
className="text-sm"
|
||||
noOptionsMessage={() => t("default_duration_no_options")}
|
||||
options={selectedMultipleDuration}
|
||||
onChange={(option) => {
|
||||
setDefaultDuration(
|
||||
selectedMultipleDuration.find((opt) => opt.value === option?.value) ?? null
|
||||
);
|
||||
formMethods.setValue("length", Number(option?.value));
|
||||
}}
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
) : (
|
||||
<TextField
|
||||
required
|
||||
name="length"
|
||||
type="number"
|
||||
label={t("duration")}
|
||||
addOnSuffix={<>{t("minutes")}</>}
|
||||
defaultValue={eventType.length ?? 15}
|
||||
onChange={(e) => {
|
||||
formMethods.setValue("length", Number(e.target.value));
|
||||
}}
|
||||
/>
|
||||
)}
|
||||
<div className="!mt-4 [&_label]:my-1 [&_label]:font-normal">
|
||||
<SettingsToggle
|
||||
title={t("allow_booker_to_select_duration")}
|
||||
checked={multipleDuration !== undefined}
|
||||
onCheckedChange={() => {
|
||||
if (multipleDuration !== undefined) {
|
||||
setMultipleDuration(undefined);
|
||||
formMethods.setValue("metadata.multipleDuration", undefined);
|
||||
formMethods.setValue("length", eventType.length);
|
||||
} else {
|
||||
setMultipleDuration([]);
|
||||
formMethods.setValue("metadata.multipleDuration", []);
|
||||
formMethods.setValue("length", 0);
|
||||
}
|
||||
}}
|
||||
/>
|
||||
</div>
|
||||
<div>
|
||||
<Skeleton as={Label} loadingClassName="w-16">
|
||||
{t("location")}
|
||||
@@ -237,12 +350,13 @@ export const EventSetupTab = (
|
||||
<EditLocationDialog
|
||||
isOpenDialog={showLocationModal}
|
||||
setShowLocationModal={setShowLocationModal}
|
||||
saveLocation={addLocation}
|
||||
saveLocation={saveLocation}
|
||||
defaultValues={formMethods.getValues("locations")}
|
||||
selection={
|
||||
selectedLocation ? { value: selectedLocation.value, label: selectedLocation.label } : undefined
|
||||
}
|
||||
setSelectedLocation={setSelectedLocation}
|
||||
setEditingLocationType={setEditingLocationType}
|
||||
/>
|
||||
</div>
|
||||
);
|
||||
|
||||
@@ -5,10 +5,7 @@ import { Controller, useFormContext } from "react-hook-form";
|
||||
|
||||
import { WEBAPP_URL } from "@calcom/lib/constants";
|
||||
import { useLocale } from "@calcom/lib/hooks/useLocale";
|
||||
import { Icon } from "@calcom/ui";
|
||||
import { Button, Avatar } from "@calcom/ui/components";
|
||||
import { Label } from "@calcom/ui/components/form";
|
||||
import { Select, CheckedTeamSelect } from "@calcom/ui/v2";
|
||||
import { Avatar, Button, CheckedTeamSelect, Icon, Label, Select } from "@calcom/ui";
|
||||
|
||||
interface IMemberToValue {
|
||||
id: number | null;
|
||||
|
||||
@@ -10,20 +10,27 @@ import { CAL_URL } from "@calcom/lib/constants";
|
||||
import { useLocale } from "@calcom/lib/hooks/useLocale";
|
||||
import { HttpError } from "@calcom/lib/http-error";
|
||||
import { trpc, TRPCClientError } from "@calcom/trpc/react";
|
||||
import { Icon } from "@calcom/ui/Icon";
|
||||
import { Button, ButtonGroup, Label } from "@calcom/ui/components";
|
||||
import { HorizontalTabs, showToast, Switch, Tooltip, VerticalTabs } from "@calcom/ui/v2";
|
||||
import ConfirmationDialogContent from "@calcom/ui/v2/core/ConfirmationDialogContent";
|
||||
import { Dialog } from "@calcom/ui/v2/core/Dialog";
|
||||
import Divider from "@calcom/ui/v2/core/Divider";
|
||||
import Dropdown, {
|
||||
import {
|
||||
Button,
|
||||
ButtonGroup,
|
||||
ConfirmationDialogContent,
|
||||
Dialog,
|
||||
Divider,
|
||||
Dropdown,
|
||||
DropdownMenuContent,
|
||||
DropdownMenuItem,
|
||||
DropdownMenuTrigger,
|
||||
} from "@calcom/ui/v2/core/Dropdown";
|
||||
import Shell from "@calcom/ui/v2/core/Shell";
|
||||
import VerticalDivider from "@calcom/ui/v2/core/VerticalDivider";
|
||||
import { Skeleton } from "@calcom/ui/v2/core/skeleton";
|
||||
HorizontalTabs,
|
||||
Icon,
|
||||
Label,
|
||||
Shell,
|
||||
showToast,
|
||||
Skeleton,
|
||||
Switch,
|
||||
Tooltip,
|
||||
VerticalDivider,
|
||||
VerticalTabs,
|
||||
} from "@calcom/ui";
|
||||
|
||||
import { ClientSuspense } from "@components/ClientSuspense";
|
||||
import { EmbedButton, EmbedDialog } from "@components/Embed";
|
||||
@@ -202,7 +209,6 @@ function EventTypeSingleLayout({
|
||||
color="secondary"
|
||||
size="icon"
|
||||
StartIcon={Icon.FiLink}
|
||||
combined
|
||||
tooltip={t("copy_link")}
|
||||
onClick={() => {
|
||||
navigator.clipboard.writeText(permalink);
|
||||
|
||||
@@ -1 +1 @@
|
||||
export { default } from "@calcom/features/ee/workflows/components/v2/EventWorkflowsTab";
|
||||
export { default } from "@calcom/features/ee/workflows/components/EventWorkflowsTab";
|
||||
|
||||
@@ -5,8 +5,7 @@ import { useFormContext } from "react-hook-form";
|
||||
import { useLocale } from "@calcom/lib/hooks/useLocale";
|
||||
import { Frequency } from "@calcom/prisma/zod-utils";
|
||||
import type { RecurringEvent } from "@calcom/types/Calendar";
|
||||
import { Alert } from "@calcom/ui/Alert";
|
||||
import { Select, SettingsToggle } from "@calcom/ui/v2";
|
||||
import { Alert, Select, SettingsToggle } from "@calcom/ui";
|
||||
|
||||
type RecurringEventControllerProps = {
|
||||
recurringEvent: RecurringEvent | null;
|
||||
|
||||
@@ -1,5 +1,4 @@
|
||||
import { Icon } from "@calcom/ui/Icon";
|
||||
import { SkeletonAvatar, SkeletonContainer, SkeletonText } from "@calcom/ui/v2";
|
||||
import { Icon, SkeletonAvatar, SkeletonContainer, SkeletonText } from "@calcom/ui";
|
||||
|
||||
function SkeletonLoader() {
|
||||
return (
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
import { InstallAppButtonWithoutPlanCheck } from "@calcom/app-store/components";
|
||||
import { useLocale } from "@calcom/lib/hooks/useLocale";
|
||||
import type { App } from "@calcom/types/App";
|
||||
import { Button } from "@calcom/ui/components/button";
|
||||
import { Button } from "@calcom/ui";
|
||||
|
||||
interface ICalendarItem {
|
||||
title: string;
|
||||
|
||||
@@ -1,8 +1,7 @@
|
||||
import { useMutation } from "@tanstack/react-query";
|
||||
|
||||
import { trpc } from "@calcom/trpc/react";
|
||||
import { Switch } from "@calcom/ui/v2";
|
||||
import showToast from "@calcom/ui/v2/core/notifications";
|
||||
import { showToast, Switch } from "@calcom/ui";
|
||||
|
||||
import classNames from "@lib/classNames";
|
||||
|
||||
|
||||
@@ -3,8 +3,7 @@ import { ArrowRightIcon } from "@heroicons/react/solid";
|
||||
import classNames from "@calcom/lib/classNames";
|
||||
import { useLocale } from "@calcom/lib/hooks/useLocale";
|
||||
import { trpc } from "@calcom/trpc/react";
|
||||
import { List } from "@calcom/ui/List";
|
||||
import { SkeletonAvatar, SkeletonText, SkeletonButton } from "@calcom/ui/v2";
|
||||
import { SkeletonAvatar, SkeletonText, SkeletonButton, List } from "@calcom/ui";
|
||||
|
||||
import { CalendarItem } from "../components/CalendarItem";
|
||||
import { ConnectedCalendarItem } from "../components/ConnectedCalendarItem";
|
||||
|
||||
@@ -7,8 +7,7 @@ import { DEFAULT_SCHEDULE } from "@calcom/lib/availability";
|
||||
import { useLocale } from "@calcom/lib/hooks/useLocale";
|
||||
import { trpc, TRPCClientErrorLike } from "@calcom/trpc/react";
|
||||
import { AppRouter } from "@calcom/trpc/server/routers/_app";
|
||||
import { Button } from "@calcom/ui/components/button";
|
||||
import { Form } from "@calcom/ui/form/fields";
|
||||
import { Button, Form } from "@calcom/ui";
|
||||
|
||||
interface ISetupAvailabilityProps {
|
||||
nextStep: () => void;
|
||||
|
||||
@@ -6,10 +6,7 @@ import { useForm } from "react-hook-form";
|
||||
import { useLocale } from "@calcom/lib/hooks/useLocale";
|
||||
import { User } from "@calcom/prisma/client";
|
||||
import { trpc } from "@calcom/trpc/react";
|
||||
import { Button } from "@calcom/ui/components";
|
||||
import { TextArea } from "@calcom/ui/components/form";
|
||||
import { showToast } from "@calcom/ui/v2";
|
||||
import ImageUploader from "@calcom/ui/v2/core/ImageUploader";
|
||||
import { Button, ImageUploader, showToast, TextArea } from "@calcom/ui";
|
||||
|
||||
import { AvatarSSR } from "@components/ui/AvatarSSR";
|
||||
|
||||
|
||||
@@ -1,13 +1,12 @@
|
||||
import { ArrowRightIcon } from "@heroicons/react/outline";
|
||||
import { useRef, useState } from "react";
|
||||
import { useState } from "react";
|
||||
import { Controller, useForm } from "react-hook-form";
|
||||
|
||||
import dayjs from "@calcom/dayjs";
|
||||
import { useLocale } from "@calcom/lib/hooks/useLocale";
|
||||
import { User } from "@calcom/prisma/client";
|
||||
import { trpc } from "@calcom/trpc/react";
|
||||
import { Button } from "@calcom/ui/components/button";
|
||||
import TimezoneSelect from "@calcom/ui/form/TimezoneSelect";
|
||||
import { Button, TimezoneSelect } from "@calcom/ui";
|
||||
|
||||
import { UsernameAvailability } from "@components/ui/UsernameAvailability";
|
||||
|
||||
|
||||
@@ -1,293 +0,0 @@
|
||||
import { useMutation } from "@tanstack/react-query";
|
||||
import { Fragment } from "react";
|
||||
|
||||
import { InstallAppButton } from "@calcom/app-store/components";
|
||||
import { useLocale } from "@calcom/lib/hooks/useLocale";
|
||||
import { RouterOutputs, trpc } from "@calcom/trpc/react";
|
||||
import { Alert } from "@calcom/ui/Alert";
|
||||
import { List } from "@calcom/ui/List";
|
||||
import { ShellSubHeading } from "@calcom/ui/Shell";
|
||||
import Switch from "@calcom/ui/Switch";
|
||||
import SkeletonLoader from "@calcom/ui/apps/SkeletonLoader";
|
||||
import { Button } from "@calcom/ui/components/button";
|
||||
import showToast from "@calcom/ui/v2/core/notifications";
|
||||
|
||||
import { QueryCell } from "@lib/QueryCell";
|
||||
|
||||
import AdditionalCalendarSelector from "@components/AdditionalCalendarSelector";
|
||||
import DestinationCalendarSelector from "@components/DestinationCalendarSelector";
|
||||
|
||||
import DisconnectIntegration from "./DisconnectIntegration";
|
||||
import IntegrationListItem from "./IntegrationListItem";
|
||||
import SubHeadingTitleWithConnections from "./SubHeadingTitleWithConnections";
|
||||
|
||||
type Props = {
|
||||
onChanged: () => unknown | Promise<unknown>;
|
||||
fromOnboarding?: boolean;
|
||||
};
|
||||
|
||||
function CalendarSwitch(props: {
|
||||
type: string;
|
||||
externalId: string;
|
||||
title: string;
|
||||
defaultSelected: boolean;
|
||||
}) {
|
||||
const utils = trpc.useContext();
|
||||
|
||||
const mutation = useMutation<
|
||||
unknown,
|
||||
unknown,
|
||||
{
|
||||
isOn: boolean;
|
||||
}
|
||||
>(
|
||||
async ({ isOn }) => {
|
||||
const body = {
|
||||
integration: props.type,
|
||||
externalId: props.externalId,
|
||||
};
|
||||
if (isOn) {
|
||||
const res = await fetch("/api/availability/calendar", {
|
||||
method: "POST",
|
||||
headers: {
|
||||
"Content-Type": "application/json",
|
||||
},
|
||||
body: JSON.stringify(body),
|
||||
});
|
||||
if (!res.ok) {
|
||||
throw new Error("Something went wrong");
|
||||
}
|
||||
} else {
|
||||
const res = await fetch("/api/availability/calendar", {
|
||||
method: "DELETE",
|
||||
headers: {
|
||||
"Content-Type": "application/json",
|
||||
},
|
||||
body: JSON.stringify(body),
|
||||
});
|
||||
|
||||
if (!res.ok) {
|
||||
throw new Error("Something went wrong");
|
||||
}
|
||||
}
|
||||
},
|
||||
{
|
||||
async onSettled() {
|
||||
await utils.viewer.integrations.invalidate();
|
||||
},
|
||||
onError() {
|
||||
showToast(`Something went wrong when toggling "${props.title}""`, "error");
|
||||
},
|
||||
}
|
||||
);
|
||||
return (
|
||||
<div className="py-1">
|
||||
<Switch
|
||||
key={props.externalId}
|
||||
name="enabled"
|
||||
label={props.title}
|
||||
defaultChecked={props.defaultSelected}
|
||||
onCheckedChange={(isOn: boolean) => {
|
||||
mutation.mutate({ isOn });
|
||||
}}
|
||||
/>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
function CalendarList(props: Props) {
|
||||
const { t } = useLocale();
|
||||
const query = trpc.viewer.integrations.useQuery({ variant: "calendar", onlyInstalled: false });
|
||||
|
||||
return (
|
||||
<QueryCell
|
||||
query={query}
|
||||
success={({ data }) => (
|
||||
<List>
|
||||
{data.items.map((item) => (
|
||||
<IntegrationListItem
|
||||
name={item.name}
|
||||
slug={item.slug}
|
||||
key={item.title}
|
||||
title={item.title}
|
||||
logo={item.logo}
|
||||
description={item.description}
|
||||
actions={
|
||||
<InstallAppButton
|
||||
type={item.type}
|
||||
render={(buttonProps) => (
|
||||
<Button color="secondary" {...buttonProps}>
|
||||
{t("connect")}
|
||||
</Button>
|
||||
)}
|
||||
onChanged={() => props.onChanged()}
|
||||
/>
|
||||
}
|
||||
/>
|
||||
))}
|
||||
</List>
|
||||
)}
|
||||
/>
|
||||
);
|
||||
}
|
||||
|
||||
function ConnectedCalendarsList(props: Props) {
|
||||
const { t } = useLocale();
|
||||
const query = trpc.viewer.connectedCalendars.useQuery(undefined, {
|
||||
suspense: true,
|
||||
trpc: {},
|
||||
});
|
||||
const { fromOnboarding } = props;
|
||||
return (
|
||||
<QueryCell
|
||||
query={query}
|
||||
empty={() => null}
|
||||
success={({ data }) => {
|
||||
if (!data.connectedCalendars.length) {
|
||||
return null;
|
||||
}
|
||||
return (
|
||||
<List>
|
||||
{data.connectedCalendars.map((item) => (
|
||||
<Fragment key={item.credentialId}>
|
||||
{item.calendars ? (
|
||||
<IntegrationListItem
|
||||
slug={item.integration.slug}
|
||||
title={item.integration.title}
|
||||
logo={item.integration.logo}
|
||||
description={item.primary?.externalId || "No external Id"}
|
||||
actions={
|
||||
<DisconnectIntegration
|
||||
id={item.credentialId}
|
||||
externalId={item.primary?.externalId}
|
||||
render={(btnProps) => (
|
||||
<Button {...btnProps} color="secondary" data-testid="integration-connection-button">
|
||||
{t("disconnect")}
|
||||
</Button>
|
||||
)}
|
||||
onOpenChange={props.onChanged}
|
||||
/>
|
||||
}>
|
||||
{!fromOnboarding && (
|
||||
<>
|
||||
<p className="px-4 pt-4 text-sm text-neutral-500">{t("toggle_calendars_conflict")}</p>
|
||||
<ul className="space-y-2 p-4">
|
||||
{item.calendars.map((cal) => (
|
||||
<CalendarSwitch
|
||||
key={cal.externalId}
|
||||
externalId={cal.externalId}
|
||||
title={cal.name || "Nameless calendar"}
|
||||
type={item.integration.type}
|
||||
defaultSelected={cal.isSelected}
|
||||
/>
|
||||
))}
|
||||
</ul>
|
||||
</>
|
||||
)}
|
||||
</IntegrationListItem>
|
||||
) : (
|
||||
<Alert
|
||||
severity="warning"
|
||||
title={t("something_went_wrong")}
|
||||
message={t("calendar_error")}
|
||||
actions={
|
||||
<DisconnectIntegration
|
||||
id={item.credentialId}
|
||||
render={(btnProps) => (
|
||||
<Button {...btnProps} color="secondary" data-testid="integration-connection-button">
|
||||
Disconnect
|
||||
</Button>
|
||||
)}
|
||||
onOpenChange={() => props.onChanged()}
|
||||
/>
|
||||
}
|
||||
/>
|
||||
)}
|
||||
</Fragment>
|
||||
))}
|
||||
</List>
|
||||
);
|
||||
}}
|
||||
/>
|
||||
);
|
||||
}
|
||||
|
||||
export function CalendarListContainer(props: {
|
||||
heading?: boolean;
|
||||
items?: RouterOutputs["viewer"]["integrations"]["items"];
|
||||
fromOnboarding?: boolean;
|
||||
}) {
|
||||
const { t } = useLocale();
|
||||
const { heading = true, fromOnboarding } = props;
|
||||
const utils = trpc.useContext();
|
||||
const onChanged = () =>
|
||||
Promise.allSettled([
|
||||
utils.viewer.integrations.invalidate(
|
||||
{ variant: "calendar", onlyInstalled: true },
|
||||
{
|
||||
exact: true,
|
||||
}
|
||||
),
|
||||
utils.viewer.connectedCalendars.invalidate(),
|
||||
]);
|
||||
const query = trpc.viewer.connectedCalendars.useQuery();
|
||||
const installedCalendars = trpc.viewer.integrations.useQuery({ variant: "calendar", onlyInstalled: true });
|
||||
const mutation = trpc.viewer.setDestinationCalendar.useMutation();
|
||||
return (
|
||||
<QueryCell
|
||||
query={query}
|
||||
customLoader={<SkeletonLoader className="mt-10" />}
|
||||
success={({ data }) => {
|
||||
return (
|
||||
<>
|
||||
{(!!data.connectedCalendars.length || !!installedCalendars.data?.items.length) && (
|
||||
<>
|
||||
{heading && (
|
||||
<ShellSubHeading
|
||||
className="mt-10 mb-0"
|
||||
title={
|
||||
<SubHeadingTitleWithConnections
|
||||
title="Calendars"
|
||||
numConnections={data.connectedCalendars.length}
|
||||
/>
|
||||
}
|
||||
subtitle={t("configure_how_your_event_types_interact")}
|
||||
actions={
|
||||
<div className="flex flex-col xl:flex-row xl:space-x-5">
|
||||
<div className="sm:min-w-80 block max-w-full">
|
||||
<DestinationCalendarSelector
|
||||
onChange={mutation.mutate}
|
||||
isLoading={mutation.isLoading}
|
||||
value={data.destinationCalendar?.externalId}
|
||||
/>
|
||||
</div>
|
||||
|
||||
{!!data.connectedCalendars.length && (
|
||||
<div className="sm:min-w-80 inline max-w-full">
|
||||
<AdditionalCalendarSelector isLoading={mutation.isLoading} />
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
}
|
||||
/>
|
||||
)}
|
||||
<ConnectedCalendarsList onChanged={onChanged} fromOnboarding={fromOnboarding} />
|
||||
</>
|
||||
)}
|
||||
{fromOnboarding && (
|
||||
<>
|
||||
{!!query.data?.connectedCalendars.length && (
|
||||
<ShellSubHeading
|
||||
className="mt-4"
|
||||
title={<SubHeadingTitleWithConnections title={t("connect_additional_calendar")} />}
|
||||
/>
|
||||
)}
|
||||
<CalendarList onChanged={onChanged} />
|
||||
</>
|
||||
)}
|
||||
</>
|
||||
);
|
||||
}}
|
||||
/>
|
||||
);
|
||||
}
|
||||
@@ -1,57 +0,0 @@
|
||||
import { useState } from "react";
|
||||
|
||||
import { useLocale } from "@calcom/lib/hooks/useLocale";
|
||||
import { trpc } from "@calcom/trpc/react";
|
||||
import ConfirmationDialogContent from "@calcom/ui/ConfirmationDialogContent";
|
||||
import { Dialog } from "@calcom/ui/Dialog";
|
||||
import { ButtonBaseProps } from "@calcom/ui/components/button";
|
||||
import showToast from "@calcom/ui/v2/core/notifications";
|
||||
|
||||
export default function DisconnectIntegration(props: {
|
||||
/** Integration credential id */
|
||||
id: number;
|
||||
externalId?: string;
|
||||
render: (renderProps: ButtonBaseProps) => JSX.Element;
|
||||
onOpenChange: (isOpen: boolean) => unknown | Promise<unknown>;
|
||||
}) {
|
||||
const { id, externalId = "" } = props;
|
||||
const { t } = useLocale();
|
||||
const [modalOpen, setModalOpen] = useState(false);
|
||||
|
||||
const mutation = trpc.viewer.deleteCredential.useMutation({
|
||||
onSettled: async () => {
|
||||
await props.onOpenChange(modalOpen);
|
||||
},
|
||||
onSuccess: () => {
|
||||
showToast("Integration deleted successfully", "success");
|
||||
setModalOpen(false);
|
||||
},
|
||||
onError: () => {
|
||||
throw new Error("Something went wrong");
|
||||
},
|
||||
});
|
||||
|
||||
return (
|
||||
<>
|
||||
<Dialog open={modalOpen} onOpenChange={setModalOpen}>
|
||||
<ConfirmationDialogContent
|
||||
variety="danger"
|
||||
title={t("remove_app")}
|
||||
confirmBtnText={t("yes_remove_app")}
|
||||
cancelBtnText="Cancel"
|
||||
onConfirm={() => {
|
||||
mutation.mutate({ id, externalId });
|
||||
}}>
|
||||
{t("are_you_sure_you_want_to_remove_this_app")}
|
||||
</ConfirmationDialogContent>
|
||||
</Dialog>
|
||||
{props.render({
|
||||
onClick() {
|
||||
setModalOpen(true);
|
||||
},
|
||||
disabled: modalOpen,
|
||||
loading: mutation.isLoading,
|
||||
})}
|
||||
</>
|
||||
);
|
||||
}
|
||||
@@ -1,39 +0,0 @@
|
||||
import Link from "next/link";
|
||||
import { ReactNode } from "react";
|
||||
|
||||
import { ListItem, ListItemText, ListItemTitle } from "@calcom/ui/List";
|
||||
|
||||
import classNames from "@lib/classNames";
|
||||
|
||||
function IntegrationListItem(props: {
|
||||
imageSrc?: string;
|
||||
slug: string;
|
||||
name?: string;
|
||||
title?: string;
|
||||
description: string;
|
||||
actions?: ReactNode;
|
||||
children?: ReactNode;
|
||||
logo: string;
|
||||
}): JSX.Element {
|
||||
const title = props.name || props.title;
|
||||
return (
|
||||
<ListItem expanded={!!props.children} className={classNames("flex-col")}>
|
||||
<div className={classNames("flex w-full flex-1 items-center space-x-2 p-3 rtl:space-x-reverse")}>
|
||||
{
|
||||
// eslint-disable-next-line @next/next/no-img-element
|
||||
props.logo && <img className="h-10 w-10" src={props.logo} alt={title} />
|
||||
}
|
||||
<div className="flex-grow truncate pl-2">
|
||||
<ListItemTitle component="h3">
|
||||
<Link href={"/apps/" + props.slug}>{props.name || title}</Link>
|
||||
</ListItemTitle>
|
||||
<ListItemText component="p">{props.description}</ListItemText>
|
||||
</div>
|
||||
<div>{props.actions}</div>
|
||||
</div>
|
||||
{props.children && <div className="w-full border-t border-gray-200">{props.children}</div>}
|
||||
</ListItem>
|
||||
);
|
||||
}
|
||||
|
||||
export default IntegrationListItem;
|
||||
@@ -1,6 +1,6 @@
|
||||
import { ReactNode } from "react";
|
||||
|
||||
import { Badge } from "@calcom/ui/components/badge";
|
||||
import { Badge } from "@calcom/ui";
|
||||
|
||||
function pluralize(opts: { num: number; plural: string; singular: string }) {
|
||||
if (opts.num === 0) {
|
||||
|
||||
@@ -2,7 +2,7 @@ import { EventTypeCustomInput, EventTypeCustomInputType } from "@prisma/client";
|
||||
import React, { FC } from "react";
|
||||
import { Controller, SubmitHandler, useForm, useWatch } from "react-hook-form";
|
||||
|
||||
import Button from "@calcom/ui/Button";
|
||||
import { Button } from "@calcom/ui";
|
||||
|
||||
import { useLocale } from "@lib/hooks/useLocale";
|
||||
|
||||
|
||||
@@ -1,9 +1,8 @@
|
||||
import React, { SyntheticEvent, useState } from "react";
|
||||
import { SyntheticEvent, useState } from "react";
|
||||
|
||||
import { ErrorCode } from "@calcom/lib/auth";
|
||||
import { useLocale } from "@calcom/lib/hooks/useLocale";
|
||||
import Button from "@calcom/ui/Button";
|
||||
import showToast from "@calcom/ui/v2/core/notifications";
|
||||
import { Button, showToast } from "@calcom/ui";
|
||||
|
||||
const ChangePasswordSection = () => {
|
||||
const [oldPassword, setOldPassword] = useState("");
|
||||
|
||||
@@ -3,10 +3,7 @@ import { useForm } from "react-hook-form";
|
||||
|
||||
import { ErrorCode } from "@calcom/lib/auth";
|
||||
import { useLocale } from "@calcom/lib/hooks/useLocale";
|
||||
import Button from "@calcom/ui/Button";
|
||||
import { Dialog, DialogContent } from "@calcom/ui/Dialog";
|
||||
import { PasswordField } from "@calcom/ui/components/form";
|
||||
import { Form, Label } from "@calcom/ui/form/fields";
|
||||
import { Button, Dialog, DialogContent, Form, Label, PasswordField } from "@calcom/ui";
|
||||
|
||||
import TwoFactor from "@components/auth/TwoFactor";
|
||||
|
||||
|
||||
@@ -1,8 +1,6 @@
|
||||
import { useLocale } from "@calcom/lib/hooks/useLocale";
|
||||
import { trpc } from "@calcom/trpc/react";
|
||||
import Button from "@calcom/ui/Button";
|
||||
import { Badge } from "@calcom/ui/components/badge";
|
||||
import showToast from "@calcom/ui/v2/core/notifications";
|
||||
import { Badge, Button, showToast } from "@calcom/ui";
|
||||
|
||||
const DisableUserImpersonation = ({ disableImpersonation }: { disableImpersonation: boolean }) => {
|
||||
const utils = trpc.useContext();
|
||||
|
||||
@@ -3,9 +3,7 @@ import { useForm } from "react-hook-form";
|
||||
|
||||
import { ErrorCode } from "@calcom/lib/auth";
|
||||
import { useLocale } from "@calcom/lib/hooks/useLocale";
|
||||
import Button from "@calcom/ui/Button";
|
||||
import { Dialog, DialogContent } from "@calcom/ui/Dialog";
|
||||
import { Form } from "@calcom/ui/components/form";
|
||||
import { Button, Dialog, DialogContent, Form } from "@calcom/ui";
|
||||
|
||||
import TwoFactor from "@components/auth/TwoFactor";
|
||||
|
||||
|
||||
@@ -1,7 +1,6 @@
|
||||
import { useState } from "react";
|
||||
|
||||
import Button from "@calcom/ui/Button";
|
||||
import { Badge } from "@calcom/ui/components/badge";
|
||||
import { Badge, Button } from "@calcom/ui";
|
||||
|
||||
import { useLocale } from "@lib/hooks/useLocale";
|
||||
|
||||
|
||||
@@ -1,6 +1,4 @@
|
||||
import React from "react";
|
||||
|
||||
import { ShieldCheckIcon } from "@calcom/ui/Icon";
|
||||
import { ShieldCheckIcon } from "@calcom/ui";
|
||||
|
||||
const TwoFactorModalHeader = ({ title, description }: { title: string; description: string }) => {
|
||||
return (
|
||||
|
||||
@@ -2,10 +2,7 @@ import { useMutation } from "@tanstack/react-query";
|
||||
|
||||
import { useLocale } from "@calcom/lib/hooks/useLocale";
|
||||
import { trpc } from "@calcom/trpc/react";
|
||||
import { Icon } from "@calcom/ui";
|
||||
import { Badge } from "@calcom/ui/components/badge";
|
||||
import Switch from "@calcom/ui/v2/core/Switch";
|
||||
import showToast from "@calcom/ui/v2/core/notifications";
|
||||
import { Badge, Icon, showToast, Switch } from "@calcom/ui";
|
||||
|
||||
export function CalendarSwitch(props: {
|
||||
type: string;
|
||||
|
||||
@@ -3,10 +3,7 @@ import { useForm } from "react-hook-form";
|
||||
|
||||
import { ErrorCode } from "@calcom/lib/auth";
|
||||
import { useLocale } from "@calcom/lib/hooks/useLocale";
|
||||
import { Button } from "@calcom/ui/components/button";
|
||||
import { Form, Label } from "@calcom/ui/components/form";
|
||||
import { PasswordField } from "@calcom/ui/components/form";
|
||||
import { Dialog, DialogContent } from "@calcom/ui/v2/core/Dialog";
|
||||
import { Button, Dialog, DialogContent, Form, Label, PasswordField } from "@calcom/ui";
|
||||
|
||||
import TwoFactor from "@components/auth/TwoFactor";
|
||||
|
||||
@@ -75,11 +72,7 @@ const DisableTwoFactorAuthModal = ({
|
||||
|
||||
return (
|
||||
<Dialog open={open} onOpenChange={onOpenChange}>
|
||||
<DialogContent
|
||||
title={t("disable_2fa")}
|
||||
description={t("disable_2fa_recommendation")}
|
||||
type="creation"
|
||||
useOwnActionButtons>
|
||||
<DialogContent title={t("disable_2fa")} description={t("disable_2fa_recommendation")} type="creation">
|
||||
<Form form={form} handleSubmit={handleDisable}>
|
||||
<div className="mb-4">
|
||||
<PasswordField
|
||||
|
||||
@@ -3,9 +3,7 @@ import { useForm } from "react-hook-form";
|
||||
|
||||
import { ErrorCode } from "@calcom/lib/auth";
|
||||
import { useLocale } from "@calcom/lib/hooks/useLocale";
|
||||
import { Button } from "@calcom/ui/components/button";
|
||||
import { Form, TextField } from "@calcom/ui/components/form";
|
||||
import { Dialog, DialogContent } from "@calcom/ui/v2/core/Dialog";
|
||||
import { Button, Dialog, DialogContent, Form, TextField } from "@calcom/ui";
|
||||
|
||||
import TwoFactor from "@components/auth/TwoFactor";
|
||||
|
||||
@@ -132,11 +130,7 @@ const EnableTwoFactorModal = ({ onEnable, onCancel, open, onOpenChange }: Enable
|
||||
|
||||
return (
|
||||
<Dialog open={open} onOpenChange={onOpenChange}>
|
||||
<DialogContent
|
||||
title={t("enable_2fa")}
|
||||
description={setupDescriptions[step]}
|
||||
type="creation"
|
||||
useOwnActionButtons>
|
||||
<DialogContent title={t("enable_2fa")} description={setupDescriptions[step]} type="creation">
|
||||
<WithStep step={SetupStep.ConfirmPassword} current={step}>
|
||||
<form onSubmit={handleSetup}>
|
||||
<div className="mb-4">
|
||||
|
||||
@@ -4,11 +4,7 @@ import { useLocale } from "@calcom/lib/hooks/useLocale";
|
||||
import { objectKeys } from "@calcom/lib/objectKeys";
|
||||
import { TeamWithMembers } from "@calcom/lib/server/queries/teams";
|
||||
import { trpc } from "@calcom/trpc/react";
|
||||
import { Alert } from "@calcom/ui/Alert";
|
||||
import Button from "@calcom/ui/Button";
|
||||
import { Icon } from "@calcom/ui/Icon";
|
||||
import { TextField } from "@calcom/ui/form/fields";
|
||||
import showToast from "@calcom/ui/v2/core/notifications";
|
||||
import { Alert, Button, Icon, showToast, TextField } from "@calcom/ui";
|
||||
|
||||
import ImageUploader from "@components/ImageUploader";
|
||||
import SettingInputContainer from "@components/ui/SettingInputContainer";
|
||||
|
||||
@@ -1,14 +1,10 @@
|
||||
import { MembershipRole } from "@prisma/client";
|
||||
import Link from "next/link";
|
||||
import { useRouter } from "next/router";
|
||||
import React from "react";
|
||||
|
||||
import { TeamWithMembers } from "@calcom/lib/server/queries/teams";
|
||||
import { trpc } from "@calcom/trpc/react";
|
||||
import ConfirmationDialogContent from "@calcom/ui/ConfirmationDialogContent";
|
||||
import { Dialog, DialogTrigger } from "@calcom/ui/Dialog";
|
||||
import { Icon } from "@calcom/ui/Icon";
|
||||
import showToast from "@calcom/ui/v2/core/notifications";
|
||||
import { ConfirmationDialogContent, Dialog, DialogTrigger, Icon, showToast } from "@calcom/ui";
|
||||
|
||||
import { useLocale } from "@lib/hooks/useLocale";
|
||||
|
||||
|
||||
@@ -2,7 +2,7 @@ import Link from "next/link";
|
||||
import { TeamPageProps } from "pages/team/[slug]";
|
||||
|
||||
import { WEBAPP_URL } from "@calcom/lib/constants";
|
||||
import { Avatar } from "@calcom/ui/components";
|
||||
import { Avatar } from "@calcom/ui";
|
||||
|
||||
import { useLocale } from "@lib/hooks/useLocale";
|
||||
|
||||
|
||||
@@ -1,3 +1,4 @@
|
||||
import Link from "next/link";
|
||||
import React from "react";
|
||||
|
||||
import classNames from "@lib/classNames";
|
||||
@@ -12,6 +13,7 @@ export type AvatarGroupProps = {
|
||||
image: string;
|
||||
title?: string;
|
||||
alt?: string;
|
||||
href?: string;
|
||||
}[];
|
||||
className?: string;
|
||||
};
|
||||
@@ -21,15 +23,25 @@ export const AvatarGroup = function AvatarGroup(props: AvatarGroupProps) {
|
||||
<ul className={classNames(props.className)}>
|
||||
{props.items.slice(0, props.truncateAfter).map((item, idx) => {
|
||||
if (item.image != null) {
|
||||
const avatar = (
|
||||
<AvatarSSR
|
||||
className={props.border}
|
||||
imageSrc={item.image}
|
||||
title={item.title}
|
||||
alt={item.alt || ""}
|
||||
size={props.size}
|
||||
/>
|
||||
);
|
||||
|
||||
return (
|
||||
<li key={idx} className="-mr-2 inline-block">
|
||||
<AvatarSSR
|
||||
className={props.border}
|
||||
imageSrc={item.image}
|
||||
title={item.title}
|
||||
alt={item.alt || ""}
|
||||
size={props.size}
|
||||
/>
|
||||
{item.href ? (
|
||||
<Link href={item.href}>
|
||||
<a>{avatar}</a>
|
||||
</Link>
|
||||
) : (
|
||||
avatar
|
||||
)}
|
||||
</li>
|
||||
);
|
||||
}
|
||||
|
||||
@@ -15,6 +15,7 @@ export type AvatarProps = (
|
||||
className?: string;
|
||||
size?: number;
|
||||
title?: string;
|
||||
href?: string;
|
||||
alt: string;
|
||||
};
|
||||
|
||||
@@ -49,7 +50,7 @@ export function AvatarSSR(props: AvatarProps) {
|
||||
const avatar = imgSrc ? <img alt={alt} className={className} src={imgSrc} /> : null;
|
||||
return title ? (
|
||||
<Tooltip.Tooltip delayDuration={300}>
|
||||
<Tooltip.TooltipTrigger className="cursor-default">{avatar}</Tooltip.TooltipTrigger>
|
||||
<Tooltip.TooltipTrigger asChild>{avatar}</Tooltip.TooltipTrigger>
|
||||
<Tooltip.Content className="rounded-sm bg-black p-2 text-sm text-white">
|
||||
<Tooltip.Arrow />
|
||||
{title}
|
||||
|
||||
@@ -2,7 +2,7 @@ import classNames from "classnames";
|
||||
import React, { useState } from "react";
|
||||
import { ControllerRenderProps } from "react-hook-form";
|
||||
|
||||
import { Icon } from "@calcom/ui/Icon";
|
||||
import { Icon } from "@calcom/ui";
|
||||
|
||||
const EditableHeading = function EditableHeading({
|
||||
value,
|
||||
|
||||
@@ -1,5 +1,4 @@
|
||||
import { Icon } from "@calcom/ui/Icon";
|
||||
import { Tooltip } from "@calcom/ui/Tooltip";
|
||||
import { Icon, Tooltip } from "@calcom/ui";
|
||||
|
||||
export default function InfoBadge({ content }: { content: string }) {
|
||||
return (
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
import classNames from "classnames";
|
||||
import React, { PropsWithChildren } from "react";
|
||||
|
||||
import { Dialog, DialogContent } from "@calcom/ui/Dialog";
|
||||
import { Dialog, DialogContent } from "@calcom/ui";
|
||||
|
||||
export default function ModalContainer(
|
||||
props: PropsWithChildren<{
|
||||
|
||||
-41
@@ -1,41 +0,0 @@
|
||||
import { cleanup, screen, render } from "@testing-library/react";
|
||||
|
||||
import { trpc } from "@calcom/trpc/react";
|
||||
|
||||
import NoCalendarConnectedAlert from "..";
|
||||
|
||||
// TODO: useQuery mock is not working
|
||||
describe.skip("Testing NoCalendarConnectedAlert", () => {
|
||||
describe("Render test", () => {
|
||||
it("should render without crashing", () => {
|
||||
// Disabled as its asking for full trpc useQuery response
|
||||
// eslint-disable-next-line @typescript-eslint/ban-ts-comment
|
||||
// @ts-ignore
|
||||
jest.spyOn(trpc, "useQuery").mockReturnValue({
|
||||
isSuccess: true,
|
||||
isFetched: true,
|
||||
data: { connectedCalendars: [], destinationCalendar: undefined },
|
||||
});
|
||||
render(<NoCalendarConnectedAlert />);
|
||||
|
||||
const testProp = screen.getByText("missing_connected_calendar");
|
||||
expect(testProp).toBeTruthy();
|
||||
});
|
||||
|
||||
it("should not render", () => {
|
||||
// Disabled as its asking for full trpc useQuery response
|
||||
// eslint-disable-next-line @typescript-eslint/ban-ts-comment
|
||||
// @ts-ignore
|
||||
jest.spyOn(trpc, "useQuery").mockReturnValue({
|
||||
isSuccess: true,
|
||||
isFetched: true,
|
||||
data: { connectedCalendars: [1, 2], destinationCalendar: { a: "test" } },
|
||||
});
|
||||
render(<NoCalendarConnectedAlert />);
|
||||
|
||||
expect(screen.queryByText("missing_connected_calendar")).toBeNull();
|
||||
});
|
||||
|
||||
afterEach(cleanup);
|
||||
});
|
||||
});
|
||||
@@ -1,44 +0,0 @@
|
||||
import { useTranslation } from "next-i18next";
|
||||
import { Trans } from "next-i18next";
|
||||
|
||||
import { trpc } from "@calcom/trpc/react";
|
||||
// @TODO: alert v2
|
||||
import { Alert } from "@calcom/ui/Alert";
|
||||
|
||||
import { LinkText } from "../LinkText";
|
||||
|
||||
const NoCalendarConnectedAlert = () => {
|
||||
const { t } = useTranslation();
|
||||
|
||||
const query = trpc.viewer.connectedCalendars.useQuery();
|
||||
// We are not gonna show this alert till we fetch data from DB
|
||||
let defaultCalendarConnected = true;
|
||||
if (query.isSuccess && query.isFetched && query.data) {
|
||||
defaultCalendarConnected =
|
||||
query.data.connectedCalendars.length > 0 && query.data.destinationCalendar !== undefined;
|
||||
}
|
||||
return (
|
||||
<>
|
||||
{!defaultCalendarConnected && (
|
||||
<Alert
|
||||
severity="warning"
|
||||
className="mb-4"
|
||||
title={<>{t("missing_connected_calendar") as string}</>}
|
||||
message={
|
||||
<Trans i18nKey="connect_your_calendar_and_link">
|
||||
You can connect your calendar from
|
||||
<LinkText
|
||||
data-testid="no-calendar-connected-alert"
|
||||
href="/apps/categories/calendar"
|
||||
classNameChildren="underline">
|
||||
here
|
||||
</LinkText>
|
||||
.
|
||||
</Trans>
|
||||
}
|
||||
/>
|
||||
)}
|
||||
</>
|
||||
);
|
||||
};
|
||||
export default NoCalendarConnectedAlert;
|
||||
@@ -1,13 +1,14 @@
|
||||
import React, { FC } from "react";
|
||||
|
||||
import Dropdown, {
|
||||
import {
|
||||
Button,
|
||||
Dropdown,
|
||||
DropdownMenuContent,
|
||||
DropdownMenuItem,
|
||||
DropdownMenuTrigger,
|
||||
DropdownMenuPortal,
|
||||
} from "@calcom/ui/Dropdown";
|
||||
import { Icon } from "@calcom/ui/Icon";
|
||||
import { Button } from "@calcom/ui/components/button";
|
||||
DropdownMenuTrigger,
|
||||
Icon,
|
||||
} from "@calcom/ui";
|
||||
|
||||
import { SVGComponent } from "@lib/types/SVGComponent";
|
||||
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
import classNames from "classnames";
|
||||
import { debounce, noop } from "lodash";
|
||||
import { useRouter } from "next/router";
|
||||
import { RefCallback, useMemo, useEffect, useState } from "react";
|
||||
import { RefCallback, useEffect, useMemo, useState } from "react";
|
||||
|
||||
import { getPremiumPlanMode, getPremiumPlanPriceValue } from "@calcom/app-store/stripepayment/lib/utils";
|
||||
import { fetchUsername } from "@calcom/lib/fetchUsername";
|
||||
@@ -11,10 +11,17 @@ import { User } from "@calcom/prisma/client";
|
||||
import { TRPCClientErrorLike } from "@calcom/trpc/client";
|
||||
import { RouterOutputs, trpc } from "@calcom/trpc/react";
|
||||
import type { AppRouter } from "@calcom/trpc/server/routers/_app";
|
||||
import { Dialog, DialogClose, DialogContent, DialogHeader } from "@calcom/ui/Dialog";
|
||||
import { Icon, StarIconSolid } from "@calcom/ui/Icon";
|
||||
import { Button } from "@calcom/ui/components/button";
|
||||
import { Input, Label } from "@calcom/ui/components/form";
|
||||
import {
|
||||
Button,
|
||||
Dialog,
|
||||
DialogClose,
|
||||
DialogContent,
|
||||
DialogHeader,
|
||||
Icon,
|
||||
Input,
|
||||
Label,
|
||||
StarIconSolid,
|
||||
} from "@calcom/ui";
|
||||
|
||||
export enum UsernameChangeStatusEnum {
|
||||
NORMAL = "NORMAL",
|
||||
@@ -340,10 +347,8 @@ const PremiumTextfield = (props: ICustomUsernameProps) => {
|
||||
{t("save")}
|
||||
</Button>
|
||||
)}
|
||||
<DialogClose asChild>
|
||||
<Button color="secondary" onClick={() => setOpenDialogSaveUsername(false)}>
|
||||
{t("cancel")}
|
||||
</Button>
|
||||
<DialogClose color="secondary" onClick={() => setOpenDialogSaveUsername(false)}>
|
||||
{t("cancel")}
|
||||
</DialogClose>
|
||||
</div>
|
||||
</DialogContent>
|
||||
|
||||
@@ -7,9 +7,7 @@ import { useLocale } from "@calcom/lib/hooks/useLocale";
|
||||
import { TRPCClientErrorLike } from "@calcom/trpc/client";
|
||||
import { trpc } from "@calcom/trpc/react";
|
||||
import { AppRouter } from "@calcom/trpc/server/routers/_app";
|
||||
import { Dialog, DialogClose, DialogContent, DialogHeader } from "@calcom/ui/Dialog";
|
||||
import { Icon } from "@calcom/ui/Icon";
|
||||
import { Button, Input, Label } from "@calcom/ui/components";
|
||||
import { Button, Dialog, DialogClose, DialogContent, DialogHeader, Icon, Input, Label } from "@calcom/ui";
|
||||
|
||||
interface ICustomUsernameProps {
|
||||
currentUsername: string | undefined;
|
||||
@@ -196,10 +194,8 @@ const UsernameTextfield = (props: ICustomUsernameProps) => {
|
||||
{t("save")}
|
||||
</Button>
|
||||
|
||||
<DialogClose asChild>
|
||||
<Button color="secondary" onClick={() => setOpenDialogSaveUsername(false)}>
|
||||
{t("cancel")}
|
||||
</Button>
|
||||
<DialogClose color="secondary" onClick={() => setOpenDialogSaveUsername(false)}>
|
||||
{t("cancel")}
|
||||
</DialogClose>
|
||||
</div>
|
||||
</DialogContent>
|
||||
|
||||
@@ -3,7 +3,7 @@ import { useEffect } from "react";
|
||||
import { HexColorInput, HexColorPicker } from "react-colorful";
|
||||
|
||||
import { isValidHexCode, fallBackHex } from "@calcom/lib/CustomBranding";
|
||||
import { Swatch } from "@calcom/ui/v2";
|
||||
import { Swatch } from "@calcom/ui";
|
||||
|
||||
type Handler = (event: MouseEvent | Event) => void;
|
||||
function useEventListener<
|
||||
|
||||
@@ -2,7 +2,7 @@ import React from "react";
|
||||
import { Props } from "react-select";
|
||||
|
||||
import { useLocale } from "@calcom/lib/hooks/useLocale";
|
||||
import { Icon } from "@calcom/ui/Icon";
|
||||
import { Icon } from "@calcom/ui";
|
||||
|
||||
import Avatar from "@components/ui/Avatar";
|
||||
import Select from "@components/ui/form/Select";
|
||||
|
||||
@@ -3,7 +3,7 @@ import "react-calendar/dist/Calendar.css";
|
||||
import "react-date-picker/dist/DatePicker.css";
|
||||
import PrimitiveDatePicker from "react-date-picker/dist/entry.nostyle";
|
||||
|
||||
import { Icon } from "@calcom/ui/Icon";
|
||||
import { Icon } from "@calcom/ui";
|
||||
|
||||
import classNames from "@lib/classNames";
|
||||
|
||||
|
||||
@@ -1,28 +0,0 @@
|
||||
// @see: https://github.com/wojtekmaj/react-daterange-picker/issues/91
|
||||
import "@wojtekmaj/react-daterange-picker/dist/DateRangePicker.css";
|
||||
import PrimitiveDateRangePicker from "@wojtekmaj/react-daterange-picker/dist/entry.nostyle";
|
||||
import React from "react";
|
||||
import "react-calendar/dist/Calendar.css";
|
||||
|
||||
import { Icon } from "@calcom/ui/Icon";
|
||||
|
||||
type Props = {
|
||||
startDate: Date;
|
||||
endDate: Date;
|
||||
onDatesChange?: ((arg: { startDate: Date; endDate: Date }) => void) | undefined;
|
||||
};
|
||||
|
||||
export const DateRangePicker = ({ startDate, endDate, onDatesChange }: Props) => {
|
||||
return (
|
||||
<PrimitiveDateRangePicker
|
||||
className="rounded-sm border-gray-300 text-sm"
|
||||
clearIcon={null}
|
||||
calendarIcon={<Icon.FiCalendar className="h-5 w-5 text-gray-500" />}
|
||||
rangeDivider={<Icon.FiArrowRight className="h-4 w-4 text-gray-400 ltr:mr-2 rtl:ml-2" />}
|
||||
value={[startDate, endDate]}
|
||||
onChange={([startDate, endDate]) => {
|
||||
if (typeof onDatesChange === "function") onDatesChange({ startDate, endDate });
|
||||
}}
|
||||
/>
|
||||
);
|
||||
};
|
||||
@@ -1,7 +1,7 @@
|
||||
import { Collapsible, CollapsibleContent, CollapsibleTrigger } from "@radix-ui/react-collapsible";
|
||||
import React from "react";
|
||||
|
||||
import { Icon } from "@calcom/ui/Icon";
|
||||
import { Icon } from "@calcom/ui";
|
||||
|
||||
import classNames from "@lib/classNames";
|
||||
import { useLocale } from "@lib/hooks/useLocale";
|
||||
|
||||
@@ -12,8 +12,7 @@ import type { TRPCClientErrorLike } from "@calcom/trpc/client";
|
||||
import type { DecorateProcedure } from "@calcom/trpc/react/shared";
|
||||
import type { AnyQueryProcedure, inferProcedureInput, inferProcedureOutput } from "@calcom/trpc/server";
|
||||
import type { AppRouter } from "@calcom/trpc/server/routers/_app";
|
||||
import { Alert } from "@calcom/ui/Alert";
|
||||
import Loader from "@calcom/ui/Loader";
|
||||
import { Alert, Loader } from "@calcom/ui";
|
||||
|
||||
import type { UseTRPCQueryOptions } from "@trpc/react-query/shared";
|
||||
|
||||
|
||||
@@ -10,7 +10,7 @@ import { ComponentProps, ReactNode } from "react";
|
||||
import DynamicHelpscoutProvider from "@calcom/features/ee/support/lib/helpscout/providerDynamic";
|
||||
import DynamicIntercomProvider from "@calcom/features/ee/support/lib/intercom/providerDynamic";
|
||||
import { trpc } from "@calcom/trpc/react";
|
||||
import { MetaProvider } from "@calcom/ui/v2/core/Meta";
|
||||
import { MetaProvider } from "@calcom/ui";
|
||||
|
||||
import usePublicPage from "@lib/hooks/usePublicPage";
|
||||
|
||||
|
||||
@@ -0,0 +1,18 @@
|
||||
import { useRouter } from "next/router";
|
||||
|
||||
export default function useRouterQuery<T extends string>(name: T) {
|
||||
const router = useRouter();
|
||||
const existingQueryParams = router.asPath.split("?")[1];
|
||||
|
||||
const urlParams = new URLSearchParams(existingQueryParams);
|
||||
const query = Object.fromEntries(urlParams);
|
||||
|
||||
const setQuery = (newValue: string | number | null | undefined) => {
|
||||
router.replace({ query: { ...router.query, [name]: newValue } }, undefined, { shallow: true });
|
||||
router.replace({ query: { ...router.query, ...query, [name]: newValue } }, undefined, { shallow: true });
|
||||
};
|
||||
|
||||
return { [name]: query[name], setQuery } as {
|
||||
[K in T]: string | undefined;
|
||||
} & { setQuery: typeof setQuery };
|
||||
}
|
||||
+19
-8
@@ -1,7 +1,7 @@
|
||||
require("dotenv").config({ path: "../../.env" });
|
||||
const CopyWebpackPlugin = require("copy-webpack-plugin");
|
||||
const { withSentryConfig } = require("@sentry/nextjs");
|
||||
|
||||
const os = require("os");
|
||||
const withTM = require("next-transpile-modules")([
|
||||
"@calcom/app-store",
|
||||
"@calcom/core",
|
||||
@@ -80,6 +80,7 @@ plugins.push(withAxiom);
|
||||
/** @type {import("next").NextConfig} */
|
||||
const nextConfig = {
|
||||
i18n,
|
||||
productionBrowserSourceMaps: true,
|
||||
/* We already do type check on GH actions */
|
||||
typescript: {
|
||||
ignoreBuildErrors: !!process.env.CI,
|
||||
@@ -88,10 +89,8 @@ const nextConfig = {
|
||||
eslint: {
|
||||
ignoreDuringBuilds: !!process.env.CI,
|
||||
},
|
||||
experimental: {
|
||||
images: {
|
||||
unoptimized: true,
|
||||
},
|
||||
images: {
|
||||
unoptimized: true,
|
||||
},
|
||||
webpack: (config) => {
|
||||
config.plugins.push(
|
||||
@@ -100,6 +99,13 @@ const nextConfig = {
|
||||
{
|
||||
from: "../../packages/app-store/**/static/**",
|
||||
to({ context, absoluteFilename }) {
|
||||
// Adds compatibility for windows path
|
||||
if (os.platform() === "win32") {
|
||||
const absoluteFilenameWin = absoluteFilename.replaceAll("\\", "/");
|
||||
const contextWin = context.replaceAll("\\", "/");
|
||||
const appName = /app-store\/(.*)\/static/.exec(absoluteFilenameWin);
|
||||
return Promise.resolve(`${contextWin}/public/app-store/${appName[1]}/[name][ext]`);
|
||||
}
|
||||
const appName = /app-store\/(.*)\/static/.exec(absoluteFilename);
|
||||
return Promise.resolve(`${context}/public/app-store/${appName[1]}/[name][ext]`);
|
||||
},
|
||||
@@ -230,9 +236,6 @@ const nextConfig = {
|
||||
|
||||
return redirects;
|
||||
},
|
||||
sentry: {
|
||||
hideSourceMaps: true,
|
||||
},
|
||||
};
|
||||
|
||||
const sentryWebpackPluginOptions = {
|
||||
@@ -241,6 +244,14 @@ const sentryWebpackPluginOptions = {
|
||||
|
||||
const moduleExports = () => plugins.reduce((acc, next) => next(acc), nextConfig);
|
||||
|
||||
if (process.env.NEXT_PUBLIC_SENTRY_DSN) {
|
||||
nextConfig.sentry = {
|
||||
hideSourceMaps: true,
|
||||
// Prevents Sentry from running on this Edge function, where Sentry doesn't work yet (build whould crash the api route).
|
||||
excludeServerRoutes: [/\/api\/social\/og\/image\/?/],
|
||||
};
|
||||
}
|
||||
|
||||
// Sentry should be the last thing to export to catch everything right
|
||||
module.exports = process.env.NEXT_PUBLIC_SENTRY_DSN
|
||||
? withSentryConfig(moduleExports, sentryWebpackPluginOptions)
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "@calcom/web",
|
||||
"version": "2.2.0",
|
||||
"version": "2.3.3",
|
||||
"private": true,
|
||||
"scripts": {
|
||||
"analyze": "ANALYZE=true next build",
|
||||
@@ -19,11 +19,11 @@
|
||||
"check-changed-files": "ts-node scripts/ts-check-changed-files.ts"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=16.x",
|
||||
"node": "16.x || 18.x",
|
||||
"yarn": ">=1.19.0 < 2.0.0"
|
||||
},
|
||||
"dependencies": {
|
||||
"@boxyhq/saml-jackson": "1.3.1",
|
||||
"@boxyhq/saml-jackson": "1.3.6",
|
||||
"@calcom/app-store": "*",
|
||||
"@calcom/app-store-cli": "*",
|
||||
"@calcom/core": "*",
|
||||
@@ -56,13 +56,12 @@
|
||||
"@radix-ui/react-switch": "^1.0.0",
|
||||
"@radix-ui/react-toggle-group": "^1.0.0",
|
||||
"@radix-ui/react-tooltip": "^1.0.0",
|
||||
"@sentry/nextjs": "^7.17.3",
|
||||
"@sentry/nextjs": "^7.20.0",
|
||||
"@stripe/react-stripe-js": "^1.10.0",
|
||||
"@stripe/stripe-js": "^1.35.0",
|
||||
"@tanstack/react-query": "^4.3.9",
|
||||
"@vercel/edge-functions-ui": "^0.2.1",
|
||||
"@vercel/og": "^0.0.19",
|
||||
"@wojtekmaj/react-daterange-picker": "^3.3.1",
|
||||
"accept-language-parser": "^1.5.0",
|
||||
"async": "^3.2.4",
|
||||
"bcryptjs": "^2.4.3",
|
||||
@@ -73,7 +72,6 @@
|
||||
"handlebars": "^4.7.7",
|
||||
"ical.js": "^1.4.0",
|
||||
"ics": "^2.37.0",
|
||||
"jimp": "^0.16.1",
|
||||
"kbar": "^0.1.0-beta.36",
|
||||
"libphonenumber-js": "^1.10.12",
|
||||
"lodash": "^4.17.21",
|
||||
|
||||
@@ -4,7 +4,7 @@ import { useRouter } from "next/router";
|
||||
import React, { useEffect, useState } from "react";
|
||||
|
||||
import { DEVELOPER_DOCS, DOCS_URL, JOIN_SLACK, WEBSITE_URL } from "@calcom/lib/constants";
|
||||
import { Icon } from "@calcom/ui/Icon";
|
||||
import { Icon } from "@calcom/ui";
|
||||
|
||||
import { useLocale } from "@lib/hooks/useLocale";
|
||||
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
import Head from "next/head";
|
||||
|
||||
import { WEBSITE_URL } from "@calcom/lib/constants";
|
||||
import Button from "@calcom/ui/Button";
|
||||
import { Button } from "@calcom/ui";
|
||||
|
||||
export default function Error500() {
|
||||
return (
|
||||
|
||||
@@ -1,4 +1,3 @@
|
||||
import { UserPlan } from "@prisma/client";
|
||||
import classNames from "classnames";
|
||||
import { GetServerSidePropsContext } from "next";
|
||||
import dynamic from "next/dynamic";
|
||||
@@ -27,7 +26,7 @@ import { collectPageParameters, telemetryEventTypes, useTelemetry } from "@calco
|
||||
import prisma from "@calcom/prisma";
|
||||
import { baseEventTypeSelect } from "@calcom/prisma/selects";
|
||||
import { EventTypeMetaDataSchema } from "@calcom/prisma/zod-utils";
|
||||
import { BadgeCheckIcon, Icon } from "@calcom/ui/Icon";
|
||||
import { BadgeCheckIcon, EventTypeDescriptionLazy as EventTypeDescription, Icon } from "@calcom/ui";
|
||||
|
||||
import { useExposePlanGlobally } from "@lib/hooks/useExposePlanGlobally";
|
||||
import { inferSSRProps } from "@lib/types/inferSSRProps";
|
||||
@@ -38,7 +37,6 @@ import { AvatarSSR } from "@components/ui/AvatarSSR";
|
||||
|
||||
import { ssrInit } from "@server/lib/ssr";
|
||||
|
||||
const EventTypeDescription = dynamic(() => import("@calcom/ui/v2/modules/event-types/EventTypeDescription"));
|
||||
const HeadSeo = dynamic(() => import("@components/seo/head-seo"));
|
||||
export default function User(props: inferSSRProps<typeof getServerSideProps> & EmbedProps) {
|
||||
const { users, profile, eventTypes, isDynamicGroup, dynamicNames, dynamicUsernames, isSingleUser } = props;
|
||||
|
||||
@@ -224,7 +224,7 @@ async function getDynamicGroupPageProps(context: GetStaticPropsContext) {
|
||||
|
||||
const locations = eventType.locations ? (eventType.locations as LocationObject[]) : [];
|
||||
const eventTypeObject = Object.assign({}, eventType, {
|
||||
metadata: (eventType.metadata || {}) as JSONObject,
|
||||
metadata: EventTypeMetaDataSchema.parse(eventType.metadata || {}),
|
||||
recurringEvent: parseRecurringEvent(eventType.recurringEvent),
|
||||
locations: privacyFilteredLocations(locations),
|
||||
users: users.map((user) => {
|
||||
|
||||
@@ -12,6 +12,7 @@ import {
|
||||
import { useLocale } from "@calcom/lib/hooks/useLocale";
|
||||
import { bookEventTypeSelect } from "@calcom/prisma";
|
||||
import prisma from "@calcom/prisma";
|
||||
import { EventTypeMetaDataSchema } from "@calcom/prisma/zod-utils";
|
||||
|
||||
import { asStringOrNull, asStringOrThrow } from "@lib/asStringOrNull";
|
||||
import getBooking, { GetBookingType } from "@lib/getBooking";
|
||||
@@ -113,7 +114,7 @@ export async function getServerSideProps(context: GetServerSidePropsContext) {
|
||||
|
||||
const eventType = {
|
||||
...eventTypeRaw,
|
||||
metadata: (eventTypeRaw.metadata || {}) as JSONObject,
|
||||
metadata: EventTypeMetaDataSchema.parse(eventTypeRaw.metadata || {}),
|
||||
recurringEvent: parseRecurringEvent(eventTypeRaw.recurringEvent),
|
||||
};
|
||||
|
||||
|
||||
@@ -271,7 +271,7 @@ export default NextAuth({
|
||||
username: user.username,
|
||||
email: user.email,
|
||||
role: user.role,
|
||||
impersonatedByUID: user?.impersonatedByUID as number,
|
||||
impersonatedByUID: user?.impersonatedByUID,
|
||||
};
|
||||
}
|
||||
|
||||
@@ -331,16 +331,16 @@ export default NextAuth({
|
||||
async signIn(params) {
|
||||
const { user, account, profile } = params;
|
||||
|
||||
if (account.provider === "email") {
|
||||
if (account?.provider === "email") {
|
||||
return true;
|
||||
}
|
||||
// In this case we've already verified the credentials in the authorize
|
||||
// callback so we can sign the user in.
|
||||
if (account.type === "credentials") {
|
||||
if (account?.type === "credentials") {
|
||||
return true;
|
||||
}
|
||||
|
||||
if (account.type !== "oauth") {
|
||||
if (account?.type !== "oauth") {
|
||||
return false;
|
||||
}
|
||||
|
||||
@@ -352,12 +352,14 @@ export default NextAuth({
|
||||
return false;
|
||||
}
|
||||
|
||||
if (account.provider) {
|
||||
if (account?.provider) {
|
||||
let idP: IdentityProvider = IdentityProvider.GOOGLE;
|
||||
if (account.provider === "saml") {
|
||||
idP = IdentityProvider.SAML;
|
||||
}
|
||||
user.email_verified = user.email_verified || user.emailVerified || profile.email_verified;
|
||||
// eslint-disable-next-line @typescript-eslint/ban-ts-comment
|
||||
// @ts-ignore-error TODO validate email_verified key on profile
|
||||
user.email_verified = user.email_verified || !!user.emailVerified || profile.email_verified;
|
||||
|
||||
if (!user.email_verified) {
|
||||
return "/auth/error?error=unverified-email";
|
||||
@@ -440,7 +442,7 @@ export default NextAuth({
|
||||
emailVerified: new Date(Date.now()),
|
||||
name: user.name,
|
||||
identityProvider: idP,
|
||||
identityProviderId: user.id as string,
|
||||
identityProviderId: String(user.id),
|
||||
},
|
||||
});
|
||||
|
||||
@@ -463,7 +465,7 @@ export default NextAuth({
|
||||
name: user.name,
|
||||
email: user.email,
|
||||
identityProvider: idP,
|
||||
identityProviderId: user.id as string,
|
||||
identityProviderId: String(user.id),
|
||||
},
|
||||
});
|
||||
const linkAccountNewUserData = { ...account, userId: newUser.id };
|
||||
|
||||
@@ -6,8 +6,8 @@ import { defaultResponder } from "@calcom/lib/server";
|
||||
|
||||
async function handler(req: NextApiRequest & { userId?: number }) {
|
||||
const session = await getSession({ req });
|
||||
/* To mimic API behavior */
|
||||
req.userId = session?.user?.id;
|
||||
/* To mimic API behavior and comply with types */
|
||||
req.userId = session?.user?.id || -1;
|
||||
const booking = await handleNewBooking(req);
|
||||
return booking;
|
||||
}
|
||||
|
||||
@@ -6,8 +6,8 @@ import { defaultResponder, defaultHandler } from "@calcom/lib/server";
|
||||
|
||||
async function handler(req: NextApiRequest & { userId?: number }) {
|
||||
const session = await getSession({ req });
|
||||
/* To mimic API behavior */
|
||||
req.userId = session?.user?.id;
|
||||
/* To mimic API behavior and comply with types */
|
||||
req.userId = session?.user?.id || -1;
|
||||
return await handleCancelBooking(req);
|
||||
}
|
||||
|
||||
|
||||
@@ -6,9 +6,7 @@ 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 { SkeletonText } from "@calcom/ui/v2";
|
||||
import Shell from "@calcom/ui/v2/core/Shell";
|
||||
import AppCard from "@calcom/ui/v2/core/apps/AppCard";
|
||||
import { AppCard, Shell, SkeletonText } from "@calcom/ui";
|
||||
|
||||
export default function Apps({ apps }: InferGetStaticPropsType<typeof getStaticProps>) {
|
||||
const { t, isLocaleReady } = useLocale();
|
||||
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user