Compare commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
0c9b9bc296 | ||
|
|
552bfcc7db | ||
|
|
986f9dca8d | ||
|
|
3af5a7404e | ||
|
|
10d3c6ecaa | ||
|
|
a0c37ea4b7 | ||
|
|
798999ad1a | ||
|
|
d40e306baa | ||
|
|
e558e61f6b | ||
|
|
210eafcd6e | ||
|
|
c05e7d75a5 | ||
|
|
d0ebf3319a | ||
|
|
9eb4187644 | ||
|
|
958cebc82c | ||
|
|
0c36dcac4c | ||
|
|
fe996b4e41 | ||
|
|
c1c95f711b | ||
|
|
7cb7006c54 | ||
|
|
e1403bfbeb | ||
|
|
c38dd2d072 | ||
|
|
f9d9e74d6b | ||
|
|
2ab597addc | ||
|
|
33e394d5ed | ||
|
|
4577de3b17 | ||
|
|
658b529840 | ||
|
|
4aed769da4 | ||
|
|
6c753290aa | ||
|
|
59c91607db | ||
|
|
b0530d59af | ||
|
|
b1ff829745 | ||
|
|
d202b536b5 | ||
|
|
2ac5ef10ba | ||
|
|
8a2ab68e9a | ||
|
|
877220caa0 | ||
|
|
0686c08de3 | ||
|
|
d3f1f8b906 | ||
|
|
33a69d3ef6 | ||
|
|
d91b451d7b | ||
|
|
9e17149e5e | ||
|
|
de61b032b7 | ||
|
|
bdbf9bd04d | ||
|
|
ccd589ef38 | ||
|
|
b4c3753566 | ||
|
|
2fdadb98ac | ||
|
|
a33847a878 | ||
|
|
88cf535c8e | ||
|
|
ea0b59d7c0 | ||
|
|
39b93cdb5b | ||
|
|
e27fc05d5f | ||
|
|
20c010fef1 | ||
|
|
1a7911e9e2 | ||
|
|
cb09b5bc5f | ||
|
|
a99031bdd2 | ||
|
|
ada8a98362 | ||
|
|
e176e81bae | ||
|
|
b64dd66c2c | ||
|
|
7fd162601b | ||
|
|
c1f346c935 | ||
|
|
36d47bd2d8 | ||
|
|
26cdb6521f | ||
|
|
014f1986de | ||
|
|
3ef328421f | ||
|
|
c4fe69d826 | ||
|
|
6fd64b2209 | ||
|
|
585e36793a | ||
|
|
09b9370e13 | ||
|
|
a76a4996b4 | ||
|
|
b28d60b6dc | ||
|
|
a716eda14c | ||
|
|
99f1524183 | ||
|
|
7ce6454915 | ||
|
|
93cb21f55a | ||
|
|
0aca769d06 | ||
|
|
c0bda6257e | ||
|
|
ae22246f05 | ||
|
|
e2931d9fa2 | ||
|
|
e6a8a31b53 | ||
|
|
ee86c751f7 | ||
|
|
073b212c05 | ||
|
|
cd8e2124c4 | ||
|
|
9dd36bd20e | ||
|
|
b1404356ff | ||
|
|
f467bb7957 | ||
|
|
597b14cf4c | ||
|
|
2bdd6df755 | ||
|
|
ad958f10bd | ||
|
|
e5eb7c3906 | ||
|
|
667d42d5da | ||
|
|
a23a9ad15a | ||
|
|
4482ffc66b | ||
|
|
493efabd2e | ||
|
|
a0355e276d | ||
|
|
b5c993e153 | ||
|
|
da3ee3f1fe | ||
|
|
d9fc595444 | ||
|
|
06d13e0f6e | ||
|
|
7dc3266133 | ||
|
|
2c96444058 | ||
|
|
39c48e7f79 | ||
|
|
b6e1989f17 | ||
|
|
45b5b2a376 | ||
|
|
05726ec94c | ||
|
|
6340ff43d8 | ||
|
|
e64b26c7e1 | ||
|
|
c829a7de82 | ||
|
|
0205bfb066 | ||
|
|
5fd7d63388 | ||
|
|
e1ea2cb532 | ||
|
|
88d151aa35 | ||
|
|
4e7c93a322 | ||
|
|
281c7628c1 | ||
|
|
1b24bc14cd | ||
|
|
be3d2fca05 | ||
|
|
327e11e7c7 | ||
|
|
bd999345f4 | ||
|
|
081776b4f9 | ||
|
|
8c328ce5d7 | ||
|
|
0fa2a6aea3 | ||
|
|
440251f9c3 | ||
|
|
7f6847f7c7 | ||
|
|
9f1fa83a97 | ||
|
|
f00b112792 | ||
|
|
5339a489f5 | ||
|
|
f7df204e0b | ||
|
|
4975aba5e0 | ||
|
|
29d61ad67b | ||
|
|
6e555e64fa | ||
|
|
c748bbc7e6 | ||
|
|
0681359bf3 | ||
|
|
4c9d4d7972 | ||
|
|
67f8de42b3 | ||
|
|
87584132b5 | ||
|
|
5b943742e0 | ||
|
|
fe155b0b97 | ||
|
|
6639588c2d | ||
|
|
7f9e8ca7a6 | ||
|
|
e478a46358 | ||
|
|
7c9012738a | ||
|
|
5c763389f7 | ||
|
|
7fde30edf9 | ||
|
|
0e22e92cab | ||
|
|
17f48614ff | ||
|
|
2f33b57eb7 | ||
|
|
6e5c3a88ee |
No files matched your search
+5
-3
@@ -79,6 +79,9 @@ CALENDSO_ENCRYPTION_KEY=
|
|||||||
# Intercom Config
|
# Intercom Config
|
||||||
NEXT_PUBLIC_INTERCOM_APP_ID=
|
NEXT_PUBLIC_INTERCOM_APP_ID=
|
||||||
|
|
||||||
|
# Secret to enable Intercom Identity Verification
|
||||||
|
INTERCOM_SECRET=
|
||||||
|
|
||||||
# Zendesk Config
|
# Zendesk Config
|
||||||
NEXT_PUBLIC_ZENDESK_KEY=
|
NEXT_PUBLIC_ZENDESK_KEY=
|
||||||
|
|
||||||
@@ -160,9 +163,6 @@ CLOSECOM_API_KEY=
|
|||||||
# Sendgrid internal sync service
|
# Sendgrid internal sync service
|
||||||
SENDGRID_SYNC_API_KEY=
|
SENDGRID_SYNC_API_KEY=
|
||||||
|
|
||||||
# Sentry
|
|
||||||
NEXT_PUBLIC_SENTRY_DSN=
|
|
||||||
SENTRY_IGNORE_API_RESOLUTION_ERROR=
|
|
||||||
|
|
||||||
# Change your Brand
|
# Change your Brand
|
||||||
NEXT_PUBLIC_APP_NAME="Cal.com"
|
NEXT_PUBLIC_APP_NAME="Cal.com"
|
||||||
@@ -177,3 +177,5 @@ CSP_POLICY=
|
|||||||
|
|
||||||
# Vercel Edge Config
|
# Vercel Edge Config
|
||||||
EDGE_CONFIG=
|
EDGE_CONFIG=
|
||||||
|
|
||||||
|
NEXT_PUBLIC_MINUTES_TO_BOOK=5 # Minutes
|
||||||
+2
-1
@@ -5,7 +5,8 @@
|
|||||||
# the repo. Unless a later match takes precedence,
|
# the repo. Unless a later match takes precedence,
|
||||||
# @global-owner1 and @global-owner2 will be requested for
|
# @global-owner1 and @global-owner2 will be requested for
|
||||||
# review when someone opens a pull request.
|
# review when someone opens a pull request.
|
||||||
* @calcom/reviewers
|
# ⬇️ Removed so we can manually trigger checks
|
||||||
|
# * @calcom/reviewers
|
||||||
|
|
||||||
.github/ @calcom/core
|
.github/ @calcom/core
|
||||||
.husky/ @calcom/core
|
.husky/ @calcom/core
|
||||||
|
|||||||
@@ -2,6 +2,7 @@ name: Cron - mark stale for inactive issues
|
|||||||
|
|
||||||
permissions:
|
permissions:
|
||||||
issues: write
|
issues: write
|
||||||
|
pull-requests: write
|
||||||
|
|
||||||
on:
|
on:
|
||||||
# "Scheduled workflows run on the latest commit on the default or base branch."
|
# "Scheduled workflows run on the latest commit on the default or base branch."
|
||||||
@@ -20,4 +21,4 @@ jobs:
|
|||||||
days-before-pr-stale: 14
|
days-before-pr-stale: 14
|
||||||
days-before-pr-close: 7
|
days-before-pr-close: 7
|
||||||
stale-pr-message: "This PR is being marked as stale due to inactivity."
|
stale-pr-message: "This PR is being marked as stale due to inactivity."
|
||||||
close-pr-message: "This PR is being closed due to inactivity. Please reopen if work is intended to be continued.
|
close-pr-message: "This PR is being closed due to inactivity. Please reopen if work is intended to be continued."
|
||||||
@@ -24,7 +24,7 @@ jobs:
|
|||||||
- name: Analyze bundle
|
- name: Analyze bundle
|
||||||
run: |
|
run: |
|
||||||
cd apps/web
|
cd apps/web
|
||||||
npx -p nextjs-bundle-analysis report
|
npx -p nextjs-bundle-analysis@0.5.0 report
|
||||||
|
|
||||||
- name: Upload bundle
|
- name: Upload bundle
|
||||||
uses: actions/upload-artifact@v2
|
uses: actions/upload-artifact@v2
|
||||||
@@ -76,7 +76,7 @@ jobs:
|
|||||||
id: fc
|
id: fc
|
||||||
with:
|
with:
|
||||||
issue-number: ${{ github.event.number }}
|
issue-number: ${{ github.event.number }}
|
||||||
body-includes: "<!-- __NEXTJS_BUNDLE -->"
|
body-includes: "<!-- __NEXTJS_BUNDLE_@calcom/web -->"
|
||||||
|
|
||||||
- name: Create Comment
|
- name: Create Comment
|
||||||
uses: peter-evans/create-or-update-comment@v1.4.4
|
uses: peter-evans/create-or-update-comment@v1.4.4
|
||||||
|
|||||||
@@ -68,5 +68,3 @@ public/embed
|
|||||||
|
|
||||||
# Copied app-store images
|
# Copied app-store images
|
||||||
public/app-store
|
public/app-store
|
||||||
# Sentry
|
|
||||||
.sentryclirc
|
|
||||||
@@ -103,59 +103,6 @@ paths:
|
|||||||
summary: Reset a user's password
|
summary: Reset a user's password
|
||||||
tags:
|
tags:
|
||||||
- Authentication
|
- Authentication
|
||||||
"/api/availability/{user}?dateFrom={dateFrom}&dateTo={dateTo}":
|
|
||||||
get:
|
|
||||||
description: "Gets the busy times for a particular user, by username."
|
|
||||||
summary: Gets the busy times for a user
|
|
||||||
tags:
|
|
||||||
- Availability
|
|
||||||
responses:
|
|
||||||
"200":
|
|
||||||
description: OK
|
|
||||||
content:
|
|
||||||
application/json:
|
|
||||||
schema:
|
|
||||||
type: array
|
|
||||||
description: ""
|
|
||||||
minItems: 1
|
|
||||||
uniqueItems: true
|
|
||||||
x-examples:
|
|
||||||
example-1:
|
|
||||||
- start: "Fri, 03 Sep 2021 17:00:00 GMT"
|
|
||||||
end: "Fri, 03 Sep 2021 17:40:00 GMT"
|
|
||||||
items:
|
|
||||||
type: object
|
|
||||||
properties:
|
|
||||||
start:
|
|
||||||
type: string
|
|
||||||
minLength: 1
|
|
||||||
end:
|
|
||||||
type: string
|
|
||||||
minLength: 1
|
|
||||||
required:
|
|
||||||
- start
|
|
||||||
- end
|
|
||||||
"500":
|
|
||||||
description: Internal Server Error
|
|
||||||
parameters:
|
|
||||||
- schema:
|
|
||||||
type: string
|
|
||||||
name: user
|
|
||||||
in: path
|
|
||||||
required: true
|
|
||||||
description: The username of who you want to check availability for
|
|
||||||
- schema:
|
|
||||||
type: string
|
|
||||||
name: dateFrom
|
|
||||||
in: path
|
|
||||||
required: true
|
|
||||||
description: The timestamp of which time you want to get busy times from
|
|
||||||
- schema:
|
|
||||||
type: string
|
|
||||||
name: dateTo
|
|
||||||
in: path
|
|
||||||
required: true
|
|
||||||
description: The timestamp of which time you want to get busy times until
|
|
||||||
/api/availability/calendar:
|
/api/availability/calendar:
|
||||||
get:
|
get:
|
||||||
description: Gets the user's selected calendars.
|
description: Gets the user's selected calendars.
|
||||||
|
|||||||
@@ -1,167 +0,0 @@
|
|||||||
import type { FormEvent } from "react";
|
|
||||||
import { useCallback, useEffect, useState } from "react";
|
|
||||||
import Cropper from "react-easy-crop";
|
|
||||||
|
|
||||||
import { useLocale } from "@calcom/lib/hooks/useLocale";
|
|
||||||
import { Button, Dialog, DialogClose, DialogContent, DialogTrigger } from "@calcom/ui";
|
|
||||||
|
|
||||||
import type { Area } from "@lib/cropImage";
|
|
||||||
import { getCroppedImg } from "@lib/cropImage";
|
|
||||||
import { useFileReader } from "@lib/hooks/useFileReader";
|
|
||||||
|
|
||||||
import Slider from "@components/Slider";
|
|
||||||
|
|
||||||
type ImageUploaderProps = {
|
|
||||||
id: string;
|
|
||||||
buttonMsg: string;
|
|
||||||
handleAvatarChange: (imageSrc: string) => void;
|
|
||||||
imageSrc?: string;
|
|
||||||
target: string;
|
|
||||||
};
|
|
||||||
|
|
||||||
interface FileEvent<T = Element> extends FormEvent<T> {
|
|
||||||
target: EventTarget & T;
|
|
||||||
}
|
|
||||||
|
|
||||||
// This is separate to prevent loading the component until file upload
|
|
||||||
function CropContainer({
|
|
||||||
onCropComplete,
|
|
||||||
imageSrc,
|
|
||||||
}: {
|
|
||||||
imageSrc: string;
|
|
||||||
onCropComplete: (croppedAreaPixels: Area) => void;
|
|
||||||
}) {
|
|
||||||
const { t } = useLocale();
|
|
||||||
const [crop, setCrop] = useState({ x: 0, y: 0 });
|
|
||||||
const [zoom, setZoom] = useState(1);
|
|
||||||
|
|
||||||
const handleZoomSliderChange = (value: number) => {
|
|
||||||
value < 1 ? setZoom(1) : setZoom(value);
|
|
||||||
};
|
|
||||||
|
|
||||||
return (
|
|
||||||
<div className="crop-container h-40 max-h-40 w-40 rounded-full">
|
|
||||||
<div className="relative h-40 w-40 rounded-full">
|
|
||||||
<Cropper
|
|
||||||
image={imageSrc}
|
|
||||||
crop={crop}
|
|
||||||
zoom={zoom}
|
|
||||||
aspect={1}
|
|
||||||
onCropChange={setCrop}
|
|
||||||
onCropComplete={(croppedArea, croppedAreaPixels) => onCropComplete(croppedAreaPixels)}
|
|
||||||
onZoomChange={setZoom}
|
|
||||||
/>
|
|
||||||
</div>
|
|
||||||
<Slider
|
|
||||||
value={zoom}
|
|
||||||
min={1}
|
|
||||||
max={3}
|
|
||||||
step={0.1}
|
|
||||||
label={t("slide_zoom_drag_instructions")}
|
|
||||||
changeHandler={handleZoomSliderChange}
|
|
||||||
/>
|
|
||||||
</div>
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
/** @deprecated Use `packages/ui/v2/core/ImageUploader.tsx` */
|
|
||||||
export default function ImageUploader({
|
|
||||||
target,
|
|
||||||
id,
|
|
||||||
buttonMsg,
|
|
||||||
handleAvatarChange,
|
|
||||||
...props
|
|
||||||
}: ImageUploaderProps) {
|
|
||||||
const { t } = useLocale();
|
|
||||||
const [imageSrc, setImageSrc] = useState<string | null>(null);
|
|
||||||
const [croppedAreaPixels, setCroppedAreaPixels] = useState<Area | null>(null);
|
|
||||||
|
|
||||||
const [{ result }, setFile] = useFileReader({
|
|
||||||
method: "readAsDataURL",
|
|
||||||
});
|
|
||||||
|
|
||||||
useEffect(() => {
|
|
||||||
if (props.imageSrc) setImageSrc(props.imageSrc);
|
|
||||||
}, [props.imageSrc]);
|
|
||||||
|
|
||||||
const onInputFile = (e: FileEvent<HTMLInputElement>) => {
|
|
||||||
if (!e.target.files?.length) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
setFile(e.target.files[0]);
|
|
||||||
};
|
|
||||||
|
|
||||||
const showCroppedImage = useCallback(
|
|
||||||
async (croppedAreaPixels: Area | null) => {
|
|
||||||
try {
|
|
||||||
if (!croppedAreaPixels) return;
|
|
||||||
const croppedImage = await getCroppedImg(
|
|
||||||
result as string /* result is always string when using readAsDataUrl */,
|
|
||||||
croppedAreaPixels
|
|
||||||
);
|
|
||||||
setImageSrc(croppedImage);
|
|
||||||
handleAvatarChange(croppedImage);
|
|
||||||
} catch (e) {
|
|
||||||
console.error(e);
|
|
||||||
}
|
|
||||||
},
|
|
||||||
[result, handleAvatarChange]
|
|
||||||
);
|
|
||||||
|
|
||||||
return (
|
|
||||||
<Dialog
|
|
||||||
onOpenChange={
|
|
||||||
(opened) => !opened && setFile(null) // unset file on close
|
|
||||||
}>
|
|
||||||
<DialogTrigger asChild>
|
|
||||||
<div className="flex items-center">
|
|
||||||
<Button color="secondary" type="button" className="py-1 text-xs">
|
|
||||||
{buttonMsg}
|
|
||||||
</Button>
|
|
||||||
</div>
|
|
||||||
</DialogTrigger>
|
|
||||||
<DialogContent>
|
|
||||||
<div className="mb-4 sm:flex sm:items-start">
|
|
||||||
<div className="mt-3 text-center sm:mt-0 sm:text-left">
|
|
||||||
<h3 className="font-cal text-emphasis text-lg leading-6" id="modal-title">
|
|
||||||
{t("upload_target", { target })}
|
|
||||||
</h3>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
<div className="mb-4">
|
|
||||||
<div className="cropper mt-6 flex flex-col items-center justify-center p-8">
|
|
||||||
{!result && (
|
|
||||||
<div className="bg-muted flex h-20 max-h-20 w-20 items-center justify-start rounded-full">
|
|
||||||
{!imageSrc && (
|
|
||||||
<p className="text-inverted w-full text-center text-sm sm:text-xs">
|
|
||||||
{t("no_target", { target })}
|
|
||||||
</p>
|
|
||||||
)}
|
|
||||||
{imageSrc && (
|
|
||||||
// eslint-disable-next-line @next/next/no-img-element
|
|
||||||
<img className="h-20 w-20 rounded-full" src={imageSrc} alt={target} />
|
|
||||||
)}
|
|
||||||
</div>
|
|
||||||
)}
|
|
||||||
{result && <CropContainer imageSrc={result as string} onCropComplete={setCroppedAreaPixels} />}
|
|
||||||
<label className="border-default bg-default hover:bg-mutedover:text-emphasis dark:hover:bg-inverted text-default dark:text-inverted focus:ring-empthasis mt-8 rounded-sm border px-3 py-1 text-xs font-medium leading-4 focus:outline-none focus:ring-2 focus:ring-offset-1 dark:border-gray-800 dark:bg-transparent">
|
|
||||||
<input
|
|
||||||
onInput={onInputFile}
|
|
||||||
type="file"
|
|
||||||
name={id}
|
|
||||||
placeholder={t("upload_image")}
|
|
||||||
className="pointer-events-none absolute mt-4 opacity-0"
|
|
||||||
accept="image/*"
|
|
||||||
/>
|
|
||||||
{t("choose_a_file")}
|
|
||||||
</label>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
<div className="mt-5 gap-x-2 sm:mt-4 sm:flex sm:flex-row-reverse">
|
|
||||||
<DialogClose onClick={() => showCroppedImage(croppedAreaPixels)}>{t("save")}</DialogClose>
|
|
||||||
<DialogClose color="secondary">{t("cancel")}</DialogClose>
|
|
||||||
</div>
|
|
||||||
</DialogContent>
|
|
||||||
</Dialog>
|
|
||||||
);
|
|
||||||
}
|
|
||||||
@@ -1,4 +1,3 @@
|
|||||||
import { AdminRequired } from "components/ui/AdminRequired";
|
|
||||||
import { noop } from "lodash";
|
import { noop } from "lodash";
|
||||||
import type { LinkProps } from "next/link";
|
import type { LinkProps } from "next/link";
|
||||||
import Link from "next/link";
|
import Link from "next/link";
|
||||||
@@ -6,6 +5,7 @@ import { useRouter } from "next/router";
|
|||||||
import type { FC, MouseEventHandler } from "react";
|
import type { FC, MouseEventHandler } from "react";
|
||||||
import { Fragment } from "react";
|
import { Fragment } from "react";
|
||||||
|
|
||||||
|
import { PermissionContainer } from "@calcom/features/auth/PermissionContainer";
|
||||||
import { useLocale } from "@calcom/lib/hooks/useLocale";
|
import { useLocale } from "@calcom/lib/hooks/useLocale";
|
||||||
|
|
||||||
import classNames from "@lib/classNames";
|
import classNames from "@lib/classNames";
|
||||||
@@ -60,7 +60,7 @@ const NavTabs: FC<NavTabProps> = ({ tabs, linkProps, ...props }) => {
|
|||||||
}
|
}
|
||||||
: noop;
|
: noop;
|
||||||
|
|
||||||
const Component = tab.adminRequired ? AdminRequired : Fragment;
|
const Component = tab.adminRequired ? PermissionContainer : Fragment;
|
||||||
const className = tab.className || "";
|
const className = tab.className || "";
|
||||||
return (
|
return (
|
||||||
<Component key={tab.name}>
|
<Component key={tab.name}>
|
||||||
|
|||||||
@@ -0,0 +1,82 @@
|
|||||||
|
import { DefaultSeo } from "next-seo";
|
||||||
|
import { Inter } from "next/font/google";
|
||||||
|
import localFont from "next/font/local";
|
||||||
|
import Script from "next/script";
|
||||||
|
|
||||||
|
import "@calcom/embed-core/src/embed-iframe";
|
||||||
|
import LicenseRequired from "@calcom/features/ee/common/components/LicenseRequired";
|
||||||
|
|
||||||
|
import type { AppProps } from "@lib/app-providers";
|
||||||
|
import AppProviders from "@lib/app-providers";
|
||||||
|
import { seoConfig } from "@lib/config/next-seo.config";
|
||||||
|
|
||||||
|
import I18nLanguageHandler from "@components/I18nLanguageHandler";
|
||||||
|
|
||||||
|
export interface CalPageWrapper {
|
||||||
|
(props?: any): JSX.Element;
|
||||||
|
PageWrapper?: AppProps["Component"]["PageWrapper"];
|
||||||
|
}
|
||||||
|
|
||||||
|
const interFont = Inter({ subsets: ["latin"], variable: "--font-inter", preload: true, display: "swap" });
|
||||||
|
const calFont = localFont({
|
||||||
|
src: "../fonts/CalSans-SemiBold.woff2",
|
||||||
|
variable: "--font-cal",
|
||||||
|
preload: true,
|
||||||
|
display: "swap",
|
||||||
|
});
|
||||||
|
|
||||||
|
function PageWrapper(props: AppProps) {
|
||||||
|
const { Component, pageProps, err, router } = props;
|
||||||
|
let pageStatus = "200";
|
||||||
|
|
||||||
|
if (router.pathname === "/404") {
|
||||||
|
pageStatus = "404";
|
||||||
|
} else if (router.pathname === "/500") {
|
||||||
|
pageStatus = "500";
|
||||||
|
}
|
||||||
|
|
||||||
|
// On client side don't let nonce creep into DOM
|
||||||
|
// It also avoids hydration warning that says that Client has the nonce value but server has "" because browser removes nonce attributes before DOM is built
|
||||||
|
// See https://github.com/kentcdodds/nonce-hydration-issues
|
||||||
|
// Set "" only if server had it set otherwise keep it undefined because server has to match with client to avoid hydration error
|
||||||
|
const nonce = typeof window !== "undefined" ? (pageProps.nonce ? "" : undefined) : pageProps.nonce;
|
||||||
|
const providerProps = {
|
||||||
|
...props,
|
||||||
|
pageProps: {
|
||||||
|
...props.pageProps,
|
||||||
|
nonce,
|
||||||
|
},
|
||||||
|
};
|
||||||
|
// Use the layout defined at the page level, if available
|
||||||
|
const getLayout = Component.getLayout ?? ((page) => page);
|
||||||
|
return (
|
||||||
|
<AppProviders {...providerProps}>
|
||||||
|
<DefaultSeo {...seoConfig.defaultNextSeo} />
|
||||||
|
<I18nLanguageHandler />
|
||||||
|
<Script
|
||||||
|
nonce={nonce}
|
||||||
|
id="page-status"
|
||||||
|
dangerouslySetInnerHTML={{ __html: `window.CalComPageStatus = '${pageStatus}'` }}
|
||||||
|
/>
|
||||||
|
<style jsx global>{`
|
||||||
|
:root {
|
||||||
|
--font-inter: ${interFont.style.fontFamily};
|
||||||
|
--font-cal: ${calFont.style.fontFamily};
|
||||||
|
}
|
||||||
|
`}</style>
|
||||||
|
|
||||||
|
{getLayout(
|
||||||
|
Component.requiresLicense ? (
|
||||||
|
<LicenseRequired>
|
||||||
|
<Component {...pageProps} err={err} />
|
||||||
|
</LicenseRequired>
|
||||||
|
) : (
|
||||||
|
<Component {...pageProps} err={err} />
|
||||||
|
),
|
||||||
|
router
|
||||||
|
)}
|
||||||
|
</AppProviders>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
export default PageWrapper;
|
||||||
@@ -46,7 +46,7 @@ export default function SettingsShell({
|
|||||||
...rest
|
...rest
|
||||||
}: { children: React.ReactNode } & ComponentProps<typeof Shell>) {
|
}: { children: React.ReactNode } & ComponentProps<typeof Shell>) {
|
||||||
return (
|
return (
|
||||||
<Shell {...rest}>
|
<Shell {...rest} hideHeadingOnMobile>
|
||||||
<div className="sm:mx-auto">
|
<div className="sm:mx-auto">
|
||||||
<NavTabs tabs={tabs} />
|
<NavTabs tabs={tabs} />
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
@@ -1,27 +0,0 @@
|
|||||||
import * as SliderPrimitive from "@radix-ui/react-slider";
|
|
||||||
import React from "react";
|
|
||||||
|
|
||||||
const Slider = ({
|
|
||||||
value,
|
|
||||||
label,
|
|
||||||
changeHandler,
|
|
||||||
...props
|
|
||||||
}: Omit<SliderPrimitive.SliderProps, "value"> & {
|
|
||||||
value: number;
|
|
||||||
label: string;
|
|
||||||
changeHandler: (value: number) => void;
|
|
||||||
}) => (
|
|
||||||
<SliderPrimitive.Root
|
|
||||||
className="slider mt-2"
|
|
||||||
value={[value]}
|
|
||||||
aria-label={label}
|
|
||||||
onValueChange={(value: number[]) => changeHandler(value[0] ?? value)}
|
|
||||||
{...props}>
|
|
||||||
<SliderPrimitive.Track className="slider-track">
|
|
||||||
<SliderPrimitive.Range className="slider-range" />
|
|
||||||
</SliderPrimitive.Track>
|
|
||||||
<SliderPrimitive.Thumb className="slider-thumb" />
|
|
||||||
</SliderPrimitive.Root>
|
|
||||||
);
|
|
||||||
|
|
||||||
export default Slider;
|
|
||||||
@@ -6,7 +6,7 @@ import React, { useState } from "react";
|
|||||||
import useAddAppMutation from "@calcom/app-store/_utils/useAddAppMutation";
|
import useAddAppMutation from "@calcom/app-store/_utils/useAddAppMutation";
|
||||||
import { InstallAppButton, AppDependencyComponent } from "@calcom/app-store/components";
|
import { InstallAppButton, AppDependencyComponent } from "@calcom/app-store/components";
|
||||||
import DisconnectIntegration from "@calcom/features/apps/components/DisconnectIntegration";
|
import DisconnectIntegration from "@calcom/features/apps/components/DisconnectIntegration";
|
||||||
import LicenseRequired from "@calcom/features/ee/common/components/v2/LicenseRequired";
|
import LicenseRequired from "@calcom/features/ee/common/components/LicenseRequired";
|
||||||
import Shell from "@calcom/features/shell/Shell";
|
import Shell from "@calcom/features/shell/Shell";
|
||||||
import classNames from "@calcom/lib/classNames";
|
import classNames from "@calcom/lib/classNames";
|
||||||
import { APP_NAME, COMPANY_NAME, SUPPORT_MAIL_ADDRESS } from "@calcom/lib/constants";
|
import { APP_NAME, COMPANY_NAME, SUPPORT_MAIL_ADDRESS } from "@calcom/lib/constants";
|
||||||
@@ -41,7 +41,7 @@ const Component = ({
|
|||||||
isTemplate,
|
isTemplate,
|
||||||
dependencies,
|
dependencies,
|
||||||
}: Parameters<typeof App>[0]) => {
|
}: Parameters<typeof App>[0]) => {
|
||||||
const { t } = useLocale();
|
const { t, i18n } = useLocale();
|
||||||
const hasDescriptionItems = descriptionItems && descriptionItems.length > 0;
|
const hasDescriptionItems = descriptionItems && descriptionItems.length > 0;
|
||||||
const router = useRouter();
|
const router = useRouter();
|
||||||
|
|
||||||
@@ -238,7 +238,7 @@ const Component = ({
|
|||||||
</span>
|
</span>
|
||||||
)}
|
)}
|
||||||
|
|
||||||
<div className="prose-sm prose prose-headings:text-emphasis prose-code:text-default prose-strong:text-default text-default mt-8">
|
<div className="prose-sm prose prose-a:text-default prose-headings:text-emphasis prose-code:text-default prose-strong:text-default text-default mt-8">
|
||||||
{body}
|
{body}
|
||||||
</div>
|
</div>
|
||||||
<h4 className="text-emphasis mt-8 font-semibold ">{t("pricing")}</h4>
|
<h4 className="text-emphasis mt-8 font-semibold ">{t("pricing")}</h4>
|
||||||
@@ -247,7 +247,7 @@ const Component = ({
|
|||||||
t("free_to_use_apps")
|
t("free_to_use_apps")
|
||||||
) : (
|
) : (
|
||||||
<>
|
<>
|
||||||
{Intl.NumberFormat("en-US", {
|
{Intl.NumberFormat(i18n.language, {
|
||||||
style: "currency",
|
style: "currency",
|
||||||
currency: "USD",
|
currency: "USD",
|
||||||
useGrouping: false,
|
useGrouping: false,
|
||||||
@@ -366,7 +366,7 @@ export default function App(props: {
|
|||||||
dependencies?: string[];
|
dependencies?: string[];
|
||||||
}) {
|
}) {
|
||||||
return (
|
return (
|
||||||
<Shell smallHeading isPublic heading={<ShellHeading />} backPath="/apps" withoutSeo>
|
<Shell smallHeading isPublic hideHeadingOnMobile heading={<ShellHeading />} backPath="/apps" withoutSeo>
|
||||||
<HeadSeo
|
<HeadSeo
|
||||||
title={props.name}
|
title={props.name}
|
||||||
description={props.description}
|
description={props.description}
|
||||||
|
|||||||
@@ -264,7 +264,7 @@ export function CalendarListContainer(props: { heading?: boolean; fromOnboarding
|
|||||||
{!!data.connectedCalendars.length || !!installedCalendars.data?.items.length ? (
|
{!!data.connectedCalendars.length || !!installedCalendars.data?.items.length ? (
|
||||||
<>
|
<>
|
||||||
{heading && (
|
{heading && (
|
||||||
<div className="flex flex-col gap-6 rounded-md border p-7">
|
<div className="border-default flex flex-col gap-6 rounded-md border p-7">
|
||||||
<ShellSubHeading
|
<ShellSubHeading
|
||||||
title={t("calendar")}
|
title={t("calendar")}
|
||||||
subtitle={t("installed_app_calendar_description")}
|
subtitle={t("installed_app_calendar_description")}
|
||||||
|
|||||||
@@ -22,7 +22,7 @@ export default function AppsLayout({ children, actions, emptyStore, ...rest }: A
|
|||||||
if (session.status === "loading") return <></>;
|
if (session.status === "loading") return <></>;
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Shell {...rest} actions={actions?.("block")}>
|
<Shell {...rest} actions={actions?.("block")} hideHeadingOnMobile>
|
||||||
<div className="flex flex-col xl:flex-row">
|
<div className="flex flex-col xl:flex-row">
|
||||||
<main className="w-full">
|
<main className="w-full">
|
||||||
{emptyStore ? (
|
{emptyStore ? (
|
||||||
|
|||||||
@@ -57,7 +57,7 @@ export default function InstalledAppsLayout({
|
|||||||
}
|
}
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Shell {...rest}>
|
<Shell {...rest} hideHeadingOnMobile>
|
||||||
<AppCategoryNavigation baseURL="/apps/installed" containerClassname="min-w-0 w-full">
|
<AppCategoryNavigation baseURL="/apps/installed" containerClassname="min-w-0 w-full">
|
||||||
{children}
|
{children}
|
||||||
</AppCategoryNavigation>
|
</AppCategoryNavigation>
|
||||||
|
|||||||
@@ -41,7 +41,7 @@ export function AvailableEventLocations({ locations }: { locations: Props["event
|
|||||||
return (
|
return (
|
||||||
<div key={`${location.type}-${index}`} className="flex flex-row items-center text-sm font-medium">
|
<div key={`${location.type}-${index}`} className="flex flex-row items-center text-sm font-medium">
|
||||||
{eventLocationType.iconUrl === "/link.svg" ? (
|
{eventLocationType.iconUrl === "/link.svg" ? (
|
||||||
<Link className="text-default min-h-4 min-w-4 ml-[2px] opacity-70 ltr:mr-[10px] rtl:ml-[10px] " />
|
<Link className="text-default ml-[2px] h-4 w-4 ltr:mr-[10px] rtl:ml-[10px] " />
|
||||||
) : (
|
) : (
|
||||||
<img
|
<img
|
||||||
src={eventLocationType.iconUrl}
|
src={eventLocationType.iconUrl}
|
||||||
|
|||||||
@@ -10,6 +10,7 @@ import { useLocale } from "@calcom/lib/hooks/useLocale";
|
|||||||
import useMediaQuery from "@calcom/lib/hooks/useMediaQuery";
|
import useMediaQuery from "@calcom/lib/hooks/useMediaQuery";
|
||||||
import { TimeFormat } from "@calcom/lib/timeFormat";
|
import { TimeFormat } from "@calcom/lib/timeFormat";
|
||||||
import { nameOfDay } from "@calcom/lib/weekday";
|
import { nameOfDay } from "@calcom/lib/weekday";
|
||||||
|
import { trpc } from "@calcom/trpc/react";
|
||||||
import type { Slot } from "@calcom/trpc/server/routers/viewer/slots";
|
import type { Slot } from "@calcom/trpc/server/routers/viewer/slots";
|
||||||
import { SkeletonContainer, SkeletonText, ToggleGroup } from "@calcom/ui";
|
import { SkeletonContainer, SkeletonText, ToggleGroup } from "@calcom/ui";
|
||||||
|
|
||||||
@@ -28,6 +29,7 @@ type AvailableTimesProps = {
|
|||||||
slots?: Slot[];
|
slots?: Slot[];
|
||||||
isLoading: boolean;
|
isLoading: boolean;
|
||||||
ethSignature?: string;
|
ethSignature?: string;
|
||||||
|
duration: number;
|
||||||
};
|
};
|
||||||
|
|
||||||
const AvailableTimes: FC<AvailableTimesProps> = ({
|
const AvailableTimes: FC<AvailableTimesProps> = ({
|
||||||
@@ -42,7 +44,9 @@ const AvailableTimes: FC<AvailableTimesProps> = ({
|
|||||||
seatsPerTimeSlot,
|
seatsPerTimeSlot,
|
||||||
bookingAttendees,
|
bookingAttendees,
|
||||||
ethSignature,
|
ethSignature,
|
||||||
|
duration,
|
||||||
}) => {
|
}) => {
|
||||||
|
const reserveSlotMutation = trpc.viewer.public.slots.reserveSlot.useMutation();
|
||||||
const [slotPickerRef] = useAutoAnimate<HTMLDivElement>();
|
const [slotPickerRef] = useAutoAnimate<HTMLDivElement>();
|
||||||
const { t, i18n } = useLocale();
|
const { t, i18n } = useLocale();
|
||||||
const router = useRouter();
|
const router = useRouter();
|
||||||
@@ -63,6 +67,14 @@ const AvailableTimes: FC<AvailableTimesProps> = ({
|
|||||||
[isMobile]
|
[isMobile]
|
||||||
);
|
);
|
||||||
|
|
||||||
|
const reserveSlot = (slot: Slot) => {
|
||||||
|
reserveSlotMutation.mutate({
|
||||||
|
slotUtcStartDate: slot.time,
|
||||||
|
eventTypeId,
|
||||||
|
slotUtcEndDate: dayjs(slot.time).utc().add(duration, "minutes").format(),
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div ref={slotPickerRef}>
|
<div ref={slotPickerRef}>
|
||||||
{!!date ? (
|
{!!date ? (
|
||||||
@@ -150,6 +162,7 @@ const AvailableTimes: FC<AvailableTimesProps> = ({
|
|||||||
" bg-default dark:bg-muted border-default hover:bg-subtle hover:border-brand-default text-emphasis mb-2 block rounded-md border py-2 text-sm font-medium",
|
" bg-default dark:bg-muted border-default hover:bg-subtle hover:border-brand-default text-emphasis mb-2 block rounded-md border py-2 text-sm font-medium",
|
||||||
brand === "#fff" || brand === "#ffffff" ? "" : ""
|
brand === "#fff" || brand === "#ffffff" ? "" : ""
|
||||||
)}
|
)}
|
||||||
|
onClick={() => reserveSlot(slot)}
|
||||||
data-testid="time">
|
data-testid="time">
|
||||||
{dayjs(slot.time).tz(timeZone()).format(timeFormat)}
|
{dayjs(slot.time).tz(timeZone()).format(timeFormat)}
|
||||||
{!!seatsPerTimeSlot && (
|
{!!seatsPerTimeSlot && (
|
||||||
|
|||||||
@@ -119,7 +119,7 @@ const BookingDescription: FC<Props> = (props) => {
|
|||||||
)}>
|
)}>
|
||||||
<Clock
|
<Clock
|
||||||
className={classNames(
|
className={classNames(
|
||||||
"min-h-4 min-w-4 ml-[2px] inline-block ltr:mr-[10px] rtl:ml-[10px]",
|
"ml-[2px] inline-block h-4 w-4 ltr:mr-[10px] rtl:ml-[10px]",
|
||||||
isBookingPage && "mt-[2px]"
|
isBookingPage && "mt-[2px]"
|
||||||
)}
|
)}
|
||||||
/>
|
/>
|
||||||
|
|||||||
@@ -1,5 +1,5 @@
|
|||||||
|
import { i18n } from "next-i18next";
|
||||||
import type { TFunction } from "next-i18next";
|
import type { TFunction } from "next-i18next";
|
||||||
import { FormattedNumber, IntlProvider } from "react-intl";
|
|
||||||
|
|
||||||
import getPaymentAppData from "@calcom/lib/getPaymentAppData";
|
import getPaymentAppData from "@calcom/lib/getPaymentAppData";
|
||||||
import { CreditCard } from "@calcom/ui/components/icon";
|
import { CreditCard } from "@calcom/ui/components/icon";
|
||||||
@@ -7,28 +7,29 @@ import { CreditCard } from "@calcom/ui/components/icon";
|
|||||||
const BookingDescriptionPayment = (props: {
|
const BookingDescriptionPayment = (props: {
|
||||||
eventType: Parameters<typeof getPaymentAppData>[0];
|
eventType: Parameters<typeof getPaymentAppData>[0];
|
||||||
t: TFunction;
|
t: TFunction;
|
||||||
|
i18n: typeof i18n;
|
||||||
}) => {
|
}) => {
|
||||||
const paymentAppData = getPaymentAppData(props.eventType);
|
const paymentAppData = getPaymentAppData(props.eventType);
|
||||||
if (!paymentAppData || paymentAppData.price <= 0) return null;
|
if (!paymentAppData || paymentAppData.price <= 0) return null;
|
||||||
|
|
||||||
|
const params = {
|
||||||
|
amount: paymentAppData.price / 100.0,
|
||||||
|
formatParams: { amount: { currency: paymentAppData.currency } },
|
||||||
|
};
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<p className="text-bookinglight -ml-2 px-2 text-sm ">
|
<p className="text-bookinglight -ml-2 px-2 text-sm ">
|
||||||
<CreditCard className="ml-[2px] -mt-1 inline-block h-4 w-4 ltr:mr-[10px] rtl:ml-[10px]" />
|
<CreditCard className="ml-[2px] -mt-1 inline-block h-4 w-4 ltr:mr-[10px] rtl:ml-[10px]" />
|
||||||
{paymentAppData.paymentOption === "HOLD" ? (
|
{paymentAppData.paymentOption === "HOLD" ? (
|
||||||
<>
|
<>{props.t("no_show_fee_amount", params)}</>
|
||||||
{props.t("no_show_fee_amount", {
|
|
||||||
amount: paymentAppData.price / 100.0,
|
|
||||||
formatParams: { amount: { currency: paymentAppData.currency } },
|
|
||||||
})}
|
|
||||||
</>
|
|
||||||
) : (
|
) : (
|
||||||
<IntlProvider locale="en">
|
<>
|
||||||
<FormattedNumber
|
{/* If undefined this will default to the browser locale */}
|
||||||
value={paymentAppData.price / 100.0}
|
{new Intl.NumberFormat(i18n?.language, {
|
||||||
style="currency"
|
style: "currency",
|
||||||
currency={paymentAppData.currency?.toUpperCase()}
|
currency: paymentAppData.currency,
|
||||||
/>
|
}).format(paymentAppData.price / 100)}
|
||||||
</IntlProvider>
|
</>
|
||||||
)}
|
)}
|
||||||
</p>
|
</p>
|
||||||
);
|
);
|
||||||
|
|||||||
@@ -256,10 +256,14 @@ function BookingListItem(booking: BookingItemProps) {
|
|||||||
},
|
},
|
||||||
});
|
});
|
||||||
};
|
};
|
||||||
|
|
||||||
|
const title = booking.title;
|
||||||
|
// To be used after we run query on legacy bookings
|
||||||
|
// const showRecordingsButtons = booking.isRecorded && isPast && isConfirmed;
|
||||||
|
|
||||||
const showRecordingsButtons =
|
const showRecordingsButtons =
|
||||||
(booking.location === "integrations:daily" || booking?.location?.trim() === "") && isPast && isConfirmed;
|
(booking.location === "integrations:daily" || booking?.location?.trim() === "") && isPast && isConfirmed;
|
||||||
|
|
||||||
const title = booking.title;
|
|
||||||
return (
|
return (
|
||||||
<>
|
<>
|
||||||
<RescheduleDialog
|
<RescheduleDialog
|
||||||
|
|||||||
@@ -2,7 +2,6 @@ import { useRouter } from "next/router";
|
|||||||
import { useCallback, useState } from "react";
|
import { useCallback, useState } from "react";
|
||||||
|
|
||||||
import { useLocale } from "@calcom/lib/hooks/useLocale";
|
import { useLocale } from "@calcom/lib/hooks/useLocale";
|
||||||
import useTheme from "@calcom/lib/hooks/useTheme";
|
|
||||||
import { collectPageParameters, telemetryEventTypes, useTelemetry } from "@calcom/lib/telemetry";
|
import { collectPageParameters, telemetryEventTypes, useTelemetry } from "@calcom/lib/telemetry";
|
||||||
import type { RecurringEvent } from "@calcom/types/Calendar";
|
import type { RecurringEvent } from "@calcom/types/Calendar";
|
||||||
import { Button, TextArea } from "@calcom/ui";
|
import { Button, TextArea } from "@calcom/ui";
|
||||||
@@ -34,7 +33,6 @@ export default function CancelBooking(props: Props) {
|
|||||||
const [loading, setLoading] = useState(false);
|
const [loading, setLoading] = useState(false);
|
||||||
const telemetry = useTelemetry();
|
const telemetry = useTelemetry();
|
||||||
const [error, setError] = useState<string | null>(booking ? null : t("booking_already_cancelled"));
|
const [error, setError] = useState<string | null>(booking ? null : t("booking_already_cancelled"));
|
||||||
useTheme(props.theme);
|
|
||||||
|
|
||||||
const cancelBookingRef = useCallback((node: HTMLTextAreaElement) => {
|
const cancelBookingRef = useCallback((node: HTMLTextAreaElement) => {
|
||||||
if (node !== null) {
|
if (node !== null) {
|
||||||
|
|||||||
@@ -194,6 +194,7 @@ export const SlotPicker = ({
|
|||||||
bookingAttendees={bookingAttendees}
|
bookingAttendees={bookingAttendees}
|
||||||
recurringCount={recurringEventCount}
|
recurringCount={recurringEventCount}
|
||||||
ethSignature={ethSignature}
|
ethSignature={ethSignature}
|
||||||
|
duration={parseInt(duration)}
|
||||||
/>
|
/>
|
||||||
</>
|
</>
|
||||||
);
|
);
|
||||||
|
|||||||
@@ -1,7 +1,6 @@
|
|||||||
import dynamic from "next/dynamic";
|
import dynamic from "next/dynamic";
|
||||||
import { useRouter } from "next/router";
|
import { useRouter } from "next/router";
|
||||||
import { useEffect, useMemo, useReducer, useState } from "react";
|
import { useEffect, useMemo, useReducer, useState } from "react";
|
||||||
import { FormattedNumber, IntlProvider } from "react-intl";
|
|
||||||
import { z } from "zod";
|
import { z } from "zod";
|
||||||
|
|
||||||
import BookingPageTagManager from "@calcom/app-store/BookingPageTagManager";
|
import BookingPageTagManager from "@calcom/app-store/BookingPageTagManager";
|
||||||
@@ -21,10 +20,9 @@ import { useLocale } from "@calcom/lib/hooks/useLocale";
|
|||||||
import useTheme from "@calcom/lib/hooks/useTheme";
|
import useTheme from "@calcom/lib/hooks/useTheme";
|
||||||
import notEmpty from "@calcom/lib/notEmpty";
|
import notEmpty from "@calcom/lib/notEmpty";
|
||||||
import { getRecurringFreq } from "@calcom/lib/recurringStrings";
|
import { getRecurringFreq } from "@calcom/lib/recurringStrings";
|
||||||
import { collectPageParameters, telemetryEventTypes, useTelemetry } from "@calcom/lib/telemetry";
|
|
||||||
import { detectBrowserTimeFormat, setIs24hClockInLocalStorage, TimeFormat } from "@calcom/lib/timeFormat";
|
import { detectBrowserTimeFormat, setIs24hClockInLocalStorage, TimeFormat } from "@calcom/lib/timeFormat";
|
||||||
import { trpc } from "@calcom/trpc";
|
import { trpc } from "@calcom/trpc";
|
||||||
import { HeadSeo, useCalcomTheme } from "@calcom/ui";
|
import { HeadSeo, NumberInput, useCalcomTheme } from "@calcom/ui";
|
||||||
import { CreditCard, User, RefreshCcw } from "@calcom/ui/components/icon";
|
import { CreditCard, User, RefreshCcw } from "@calcom/ui/components/icon";
|
||||||
|
|
||||||
import { timeZone as localStorageTimeZone } from "@lib/clock";
|
import { timeZone as localStorageTimeZone } from "@lib/clock";
|
||||||
@@ -76,7 +74,7 @@ const AvailabilityPage = ({ profile, eventType, ...restProps }: Props) => {
|
|||||||
brandColor: profile.brandColor,
|
brandColor: profile.brandColor,
|
||||||
darkBrandColor: profile.darkBrandColor,
|
darkBrandColor: profile.darkBrandColor,
|
||||||
});
|
});
|
||||||
const { t } = useLocale();
|
const { t, i18n } = useLocale();
|
||||||
const availabilityDatePickerEmbedStyles = useEmbedStyles("availabilityDatePicker");
|
const availabilityDatePickerEmbedStyles = useEmbedStyles("availabilityDatePicker");
|
||||||
//TODO: Plan to remove shouldAlignCentrallyInEmbed config
|
//TODO: Plan to remove shouldAlignCentrallyInEmbed config
|
||||||
const shouldAlignCentrallyInEmbed = useEmbedNonStylesConfig("align") !== "left";
|
const shouldAlignCentrallyInEmbed = useEmbedNonStylesConfig("align") !== "left";
|
||||||
@@ -105,8 +103,9 @@ const AvailabilityPage = ({ profile, eventType, ...restProps }: Props) => {
|
|||||||
|
|
||||||
const [recurringEventCount, setRecurringEventCount] = useState(eventType.recurringEvent?.count);
|
const [recurringEventCount, setRecurringEventCount] = useState(eventType.recurringEvent?.count);
|
||||||
|
|
||||||
|
/*
|
||||||
const telemetry = useTelemetry();
|
const telemetry = useTelemetry();
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
if (top !== window) {
|
if (top !== window) {
|
||||||
//page_view will be collected automatically by _middleware.ts
|
//page_view will be collected automatically by _middleware.ts
|
||||||
telemetry.event(
|
telemetry.event(
|
||||||
@@ -114,7 +113,7 @@ const AvailabilityPage = ({ profile, eventType, ...restProps }: Props) => {
|
|||||||
collectPageParameters("/availability", { isTeamBooking: document.URL.includes("team/") })
|
collectPageParameters("/availability", { isTeamBooking: document.URL.includes("team/") })
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
}, [telemetry]);
|
}, [telemetry]); */
|
||||||
const embedUiConfig = useEmbedUiConfig();
|
const embedUiConfig = useEmbedUiConfig();
|
||||||
// get dynamic user list here
|
// get dynamic user list here
|
||||||
const userList = eventType.users ? eventType.users.map((user) => user.username).filter(notEmpty) : [];
|
const userList = eventType.users ? eventType.users.map((user) => user.username).filter(notEmpty) : [];
|
||||||
@@ -124,16 +123,7 @@ const AvailabilityPage = ({ profile, eventType, ...restProps }: Props) => {
|
|||||||
[timeZone]
|
[timeZone]
|
||||||
);
|
);
|
||||||
const paymentAppData = getPaymentAppData(eventType);
|
const paymentAppData = getPaymentAppData(eventType);
|
||||||
const paymentAmount = () => {
|
|
||||||
return;
|
|
||||||
<IntlProvider locale="en">
|
|
||||||
<FormattedNumber
|
|
||||||
value={paymentAppData.price / 100.0}
|
|
||||||
style="currency"
|
|
||||||
currency={paymentAppData.currency?.toUpperCase()}
|
|
||||||
/>
|
|
||||||
</IntlProvider>;
|
|
||||||
};
|
|
||||||
const rainbowAppData = getEventTypeAppData(eventType, "rainbow") || {};
|
const rainbowAppData = getEventTypeAppData(eventType, "rainbow") || {};
|
||||||
const rawSlug = profile.slug ? profile.slug.split("/") : [];
|
const rawSlug = profile.slug ? profile.slug.split("/") : [];
|
||||||
if (rawSlug.length > 1) rawSlug.pop(); //team events have team name as slug, but user events have [user]/[type] as slug.
|
if (rawSlug.length > 1) rawSlug.pop(); //team events have team name as slug, but user events have [user]/[type] as slug.
|
||||||
@@ -191,7 +181,7 @@ const AvailabilityPage = ({ profile, eventType, ...restProps }: Props) => {
|
|||||||
style={availabilityDatePickerEmbedStyles}
|
style={availabilityDatePickerEmbedStyles}
|
||||||
className={classNames(
|
className={classNames(
|
||||||
isBackgroundTransparent ? "" : "bg-default dark:bg-muted pb-4 md:pb-0",
|
isBackgroundTransparent ? "" : "bg-default dark:bg-muted pb-4 md:pb-0",
|
||||||
"border-subtle md:rounded-md md:border",
|
"border-booker md:border-booker-width md:rounded-md",
|
||||||
isEmbed && "mx-auto"
|
isEmbed && "mx-auto"
|
||||||
)}>
|
)}>
|
||||||
<div className="md:flex">
|
<div className="md:flex">
|
||||||
@@ -226,16 +216,18 @@ const AvailabilityPage = ({ profile, eventType, ...restProps }: Props) => {
|
|||||||
<p className="mb-1 -ml-2 inline px-2 py-1">
|
<p className="mb-1 -ml-2 inline px-2 py-1">
|
||||||
{getRecurringFreq({ t, recurringEvent: eventType.recurringEvent })}
|
{getRecurringFreq({ t, recurringEvent: eventType.recurringEvent })}
|
||||||
</p>
|
</p>
|
||||||
<input
|
|
||||||
type="number"
|
<NumberInput
|
||||||
|
defaultValue={eventType.recurringEvent.count}
|
||||||
min="1"
|
min="1"
|
||||||
max={eventType.recurringEvent.count}
|
max={eventType.recurringEvent.count}
|
||||||
className="w-15 border-default bg-default dark:border-empthasis h-7 rounded-sm text-sm font-medium [appearance:textfield] ltr:mr-2 rtl:ml-2"
|
isFullWidth={false}
|
||||||
defaultValue={eventType.recurringEvent.count}
|
className="me-2 inline w-16"
|
||||||
onChange={(event) => {
|
onChange={(event) => {
|
||||||
setRecurringEventCount(parseInt(event?.target.value));
|
setRecurringEventCount(parseInt(event?.target.value));
|
||||||
}}
|
}}
|
||||||
/>
|
/>
|
||||||
|
|
||||||
<p className="inline">
|
<p className="inline">
|
||||||
{t("occurrence", {
|
{t("occurrence", {
|
||||||
count: recurringEventCount,
|
count: recurringEventCount,
|
||||||
@@ -255,13 +247,12 @@ const AvailabilityPage = ({ profile, eventType, ...restProps }: Props) => {
|
|||||||
})}
|
})}
|
||||||
</>
|
</>
|
||||||
) : (
|
) : (
|
||||||
<IntlProvider locale="en">
|
<>
|
||||||
<FormattedNumber
|
{new Intl.NumberFormat(i18n.language, {
|
||||||
value={paymentAppData.price / 100.0}
|
style: "currency",
|
||||||
style="currency"
|
currency: paymentAppData.currency,
|
||||||
currency={paymentAppData.currency?.toUpperCase()}
|
}).format(paymentAppData.price / 100)}
|
||||||
/>
|
</>
|
||||||
</IntlProvider>
|
|
||||||
)}
|
)}
|
||||||
</p>
|
</p>
|
||||||
)}
|
)}
|
||||||
|
|||||||
@@ -32,15 +32,16 @@ import getLocationOptionsForSelect from "@calcom/features/bookings/lib/getLocati
|
|||||||
import { FormBuilderField } from "@calcom/features/form-builder/FormBuilder";
|
import { FormBuilderField } from "@calcom/features/form-builder/FormBuilder";
|
||||||
import { bookingSuccessRedirect } from "@calcom/lib/bookingSuccessRedirect";
|
import { bookingSuccessRedirect } from "@calcom/lib/bookingSuccessRedirect";
|
||||||
import classNames from "@calcom/lib/classNames";
|
import classNames from "@calcom/lib/classNames";
|
||||||
import { APP_NAME } from "@calcom/lib/constants";
|
import { APP_NAME, MINUTES_TO_BOOK } from "@calcom/lib/constants";
|
||||||
import useGetBrandingColours from "@calcom/lib/getBrandColours";
|
import useGetBrandingColours from "@calcom/lib/getBrandColours";
|
||||||
import { useLocale } from "@calcom/lib/hooks/useLocale";
|
import { useLocale } from "@calcom/lib/hooks/useLocale";
|
||||||
import useTheme from "@calcom/lib/hooks/useTheme";
|
import useTheme from "@calcom/lib/hooks/useTheme";
|
||||||
import { useTypedQuery } from "@calcom/lib/hooks/useTypedQuery";
|
import { useTypedQuery } from "@calcom/lib/hooks/useTypedQuery";
|
||||||
import { HttpError } from "@calcom/lib/http-error";
|
import { HttpError } from "@calcom/lib/http-error";
|
||||||
import { getEveryFreqFor } from "@calcom/lib/recurringStrings";
|
import { getEveryFreqFor } from "@calcom/lib/recurringStrings";
|
||||||
import { collectPageParameters, telemetryEventTypes, useTelemetry } from "@calcom/lib/telemetry";
|
import { telemetryEventTypes, useTelemetry } from "@calcom/lib/telemetry";
|
||||||
import { TimeFormat } from "@calcom/lib/timeFormat";
|
import { TimeFormat } from "@calcom/lib/timeFormat";
|
||||||
|
import { trpc } from "@calcom/trpc";
|
||||||
import { Button, Form, Tooltip, useCalcomTheme } from "@calcom/ui";
|
import { Button, Form, Tooltip, useCalcomTheme } from "@calcom/ui";
|
||||||
import { AlertTriangle, Calendar, RefreshCw, User } from "@calcom/ui/components/icon";
|
import { AlertTriangle, Calendar, RefreshCw, User } from "@calcom/ui/components/icon";
|
||||||
|
|
||||||
@@ -210,8 +211,11 @@ const BookingPage = ({
|
|||||||
hashedLink,
|
hashedLink,
|
||||||
...restProps
|
...restProps
|
||||||
}: BookingPageProps) => {
|
}: BookingPageProps) => {
|
||||||
|
const removeSelectedSlotMarkMutation = trpc.viewer.public.slots.removeSelectedSlotMark.useMutation();
|
||||||
|
const reserveSlotMutation = trpc.viewer.public.slots.reserveSlot.useMutation();
|
||||||
const { t, i18n } = useLocale();
|
const { t, i18n } = useLocale();
|
||||||
const { duration: queryDuration } = useRouterQuery("duration");
|
const { duration: queryDuration } = useRouterQuery("duration");
|
||||||
|
const { date: queryDate } = useRouterQuery("date");
|
||||||
const isEmbed = useIsEmbed(restProps.isEmbed);
|
const isEmbed = useIsEmbed(restProps.isEmbed);
|
||||||
const embedUiConfig = useEmbedUiConfig();
|
const embedUiConfig = useEmbedUiConfig();
|
||||||
const shouldAlignCentrallyInEmbed = useEmbedNonStylesConfig("align") !== "left";
|
const shouldAlignCentrallyInEmbed = useEmbedNonStylesConfig("align") !== "left";
|
||||||
@@ -227,6 +231,15 @@ const BookingPage = ({
|
|||||||
}),
|
}),
|
||||||
{}
|
{}
|
||||||
);
|
);
|
||||||
|
const reserveSlot = () => {
|
||||||
|
if (queryDuration) {
|
||||||
|
reserveSlotMutation.mutate({
|
||||||
|
eventTypeId: eventType.id,
|
||||||
|
slotUtcStartDate: dayjs(queryDate).utc().format(),
|
||||||
|
slotUtcEndDate: dayjs(queryDate).utc().add(parseInt(queryDuration), "minutes").format(),
|
||||||
|
});
|
||||||
|
}
|
||||||
|
};
|
||||||
// Define duration now that we support multiple duration eventTypes
|
// Define duration now that we support multiple duration eventTypes
|
||||||
let duration = eventType.length;
|
let duration = eventType.length;
|
||||||
if (
|
if (
|
||||||
@@ -239,13 +252,19 @@ const BookingPage = ({
|
|||||||
}
|
}
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
if (top !== window) {
|
/* if (top !== window) {
|
||||||
//page_view will be collected automatically by _middleware.ts
|
//page_view will be collected automatically by _middleware.ts
|
||||||
telemetry.event(
|
telemetry.event(
|
||||||
telemetryEventTypes.embedView,
|
telemetryEventTypes.embedView,
|
||||||
collectPageParameters("/book", { isTeamBooking: document.URL.includes("team/") })
|
collectPageParameters("/book", { isTeamBooking: document.URL.includes("team/") })
|
||||||
);
|
);
|
||||||
}
|
} */
|
||||||
|
reserveSlot();
|
||||||
|
const interval = setInterval(reserveSlot, parseInt(MINUTES_TO_BOOK) * 60 * 1000 - 2000);
|
||||||
|
return () => {
|
||||||
|
clearInterval(interval);
|
||||||
|
removeSelectedSlotMarkMutation.mutate();
|
||||||
|
};
|
||||||
// eslint-disable-next-line react-hooks/exhaustive-deps
|
// eslint-disable-next-line react-hooks/exhaustive-deps
|
||||||
}, []);
|
}, []);
|
||||||
|
|
||||||
@@ -528,14 +547,14 @@ const BookingPage = ({
|
|||||||
<div
|
<div
|
||||||
className={classNames(
|
className={classNames(
|
||||||
"main",
|
"main",
|
||||||
isBackgroundTransparent ? "" : "dark:bg-darkgray-100 bg-default dark:border",
|
isBackgroundTransparent ? "" : "bg-default dark:bg-muted",
|
||||||
"border-subtle rounded-md sm:border"
|
"border-booker sm:border-booker-width rounded-md"
|
||||||
)}>
|
)}>
|
||||||
<div className="sm:flex">
|
<div className="sm:flex">
|
||||||
{showEventTypeDetails && (
|
{showEventTypeDetails && (
|
||||||
<div className="sm:border-subtle text-default flex flex-col px-6 pt-6 pb-0 sm:w-1/2 sm:border-r sm:pb-6">
|
<div className="sm:border-subtle text-default flex flex-col px-6 pt-6 pb-0 sm:w-1/2 sm:border-r sm:pb-6">
|
||||||
<BookingDescription isBookingPage profile={profile} eventType={eventType}>
|
<BookingDescription isBookingPage profile={profile} eventType={eventType}>
|
||||||
<BookingDescriptionPayment eventType={eventType} t={t} />
|
<BookingDescriptionPayment eventType={eventType} t={t} i18n={i18n} />
|
||||||
{!rescheduleUid && eventType.recurringEvent?.freq && recurringEventCount && (
|
{!rescheduleUid && eventType.recurringEvent?.freq && recurringEventCount && (
|
||||||
<div className="dark:text-inverted text-default items-start text-sm font-medium">
|
<div className="dark:text-inverted text-default items-start text-sm font-medium">
|
||||||
<RefreshCw className="ml-[2px] inline-block h-4 w-4 ltr:mr-[10px] rtl:ml-[10px]" />
|
<RefreshCw className="ml-[2px] inline-block h-4 w-4 ltr:mr-[10px] rtl:ml-[10px]" />
|
||||||
@@ -649,9 +668,9 @@ const BookingPage = ({
|
|||||||
</Button>
|
</Button>
|
||||||
</div>
|
</div>
|
||||||
</Form>
|
</Form>
|
||||||
{(mutation.isError || recurringMutation.isError) && (
|
{mutation.isError || recurringMutation.isError ? (
|
||||||
<ErrorMessage error={mutation.error || recurringMutation.error} />
|
<ErrorMessage error={mutation.error || recurringMutation.error} />
|
||||||
)}
|
) : null}
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
@@ -1,7 +1,5 @@
|
|||||||
import { Trans } from "next-i18next";
|
|
||||||
import { useState } from "react";
|
import { useState } from "react";
|
||||||
import type { Dispatch, SetStateAction } from "react";
|
import type { Dispatch, SetStateAction } from "react";
|
||||||
import { IntlProvider, FormattedNumber } from "react-intl";
|
|
||||||
|
|
||||||
import { useLocale } from "@calcom/lib/hooks/useLocale";
|
import { useLocale } from "@calcom/lib/hooks/useLocale";
|
||||||
import { trpc } from "@calcom/trpc/react";
|
import { trpc } from "@calcom/trpc/react";
|
||||||
@@ -40,6 +38,11 @@ export const ChargeCardDialog = (props: IRescheduleDialog) => {
|
|||||||
},
|
},
|
||||||
});
|
});
|
||||||
|
|
||||||
|
const currencyStringParams = {
|
||||||
|
amount: props.paymentAmount / 100.0,
|
||||||
|
formatParams: { amount: { currency: props.paymentCurrency } },
|
||||||
|
};
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Dialog open={isOpenDialog} onOpenChange={setIsOpenDialog}>
|
<Dialog open={isOpenDialog} onOpenChange={setIsOpenDialog}>
|
||||||
<DialogContent>
|
<DialogContent>
|
||||||
@@ -49,19 +52,7 @@ export const ChargeCardDialog = (props: IRescheduleDialog) => {
|
|||||||
</div>
|
</div>
|
||||||
<div className="pt-1">
|
<div className="pt-1">
|
||||||
<DialogHeader title={t("charge_card")} />
|
<DialogHeader title={t("charge_card")} />
|
||||||
<Trans i18nKey="charge_card_dialog_body">
|
<p>{t("charge_card_dialog_body", currencyStringParams)}</p>
|
||||||
<p className="text-sm text-gray-500">
|
|
||||||
You are about to charge the attendee{" "}
|
|
||||||
<IntlProvider locale="en">
|
|
||||||
<FormattedNumber
|
|
||||||
value={props.paymentAmount / 100.0}
|
|
||||||
style="currency"
|
|
||||||
currency={props.paymentCurrency?.toUpperCase()}
|
|
||||||
/>
|
|
||||||
</IntlProvider>
|
|
||||||
. Are you sure you want to continue?
|
|
||||||
</p>
|
|
||||||
</Trans>
|
|
||||||
|
|
||||||
{chargeError && (
|
{chargeError && (
|
||||||
<div className="mt-4 flex text-red-500">
|
<div className="mt-4 flex text-red-500">
|
||||||
@@ -80,16 +71,7 @@ export const ChargeCardDialog = (props: IRescheduleDialog) => {
|
|||||||
bookingId,
|
bookingId,
|
||||||
})
|
})
|
||||||
}>
|
}>
|
||||||
<Trans i18nKey="charge_card_confirm">
|
{t("charge_attendee", currencyStringParams)}
|
||||||
Charge attendee{" "}
|
|
||||||
<IntlProvider locale="en">
|
|
||||||
<FormattedNumber
|
|
||||||
value={props.paymentAmount / 100.0}
|
|
||||||
style="currency"
|
|
||||||
currency={props.paymentCurrency?.toUpperCase()}
|
|
||||||
/>
|
|
||||||
</IntlProvider>
|
|
||||||
</Trans>
|
|
||||||
</Button>
|
</Button>
|
||||||
</DialogFooter>
|
</DialogFooter>
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
@@ -174,7 +174,6 @@ export const EditLocationDialog = (props: ISetLocationDialog) => {
|
|||||||
id="locationInput"
|
id="locationInput"
|
||||||
placeholder={t(eventLocationType.organizerInputPlaceholder || "")}
|
placeholder={t(eventLocationType.organizerInputPlaceholder || "")}
|
||||||
required
|
required
|
||||||
className="border-default block w-full rounded-sm text-sm"
|
|
||||||
// eslint-disable-next-line @typescript-eslint/no-non-null-assertion
|
// eslint-disable-next-line @typescript-eslint/no-non-null-assertion
|
||||||
defaultValue={
|
defaultValue={
|
||||||
defaultLocation ? defaultLocation[eventLocationType.defaultValueVariable] : undefined
|
defaultLocation ? defaultLocation[eventLocationType.defaultValueVariable] : undefined
|
||||||
|
|||||||
@@ -1,21 +1,23 @@
|
|||||||
import { SchedulingType } from "@prisma/client";
|
import { SchedulingType } from "@prisma/client";
|
||||||
import type { FormValues, EventTypeSetup } from "pages/event-types/[type]";
|
import type { EventTypeSetup, FormValues } from "pages/event-types/[type]";
|
||||||
|
import { useState } from "react";
|
||||||
import { Controller, useFormContext } from "react-hook-form";
|
import { Controller, useFormContext } from "react-hook-form";
|
||||||
import type { OptionProps, SingleValueProps } from "react-select";
|
import type { OptionProps, SingleValueProps } from "react-select";
|
||||||
import { components } from "react-select";
|
import { components } from "react-select";
|
||||||
|
|
||||||
import dayjs from "@calcom/dayjs";
|
import dayjs from "@calcom/dayjs";
|
||||||
import useLockedFieldsManager from "@calcom/features/ee/managed-event-types/hooks/useLockedFieldsManager";
|
import useLockedFieldsManager from "@calcom/features/ee/managed-event-types/hooks/useLockedFieldsManager";
|
||||||
import { NewScheduleButton } from "@calcom/features/schedules";
|
|
||||||
import classNames from "@calcom/lib/classNames";
|
import classNames from "@calcom/lib/classNames";
|
||||||
import { useLocale } from "@calcom/lib/hooks/useLocale";
|
import { useLocale } from "@calcom/lib/hooks/useLocale";
|
||||||
import { weekdayNames } from "@calcom/lib/weekday";
|
import { weekdayNames } from "@calcom/lib/weekday";
|
||||||
import { trpc } from "@calcom/trpc/react";
|
import { trpc } from "@calcom/trpc/react";
|
||||||
import useMeQuery from "@calcom/trpc/react/hooks/useMeQuery";
|
import useMeQuery from "@calcom/trpc/react/hooks/useMeQuery";
|
||||||
import { Badge, Button, Select, SettingsToggle, SkeletonText, EmptyScreen } from "@calcom/ui";
|
import { Badge, Button, Select, SettingsToggle, SkeletonText } from "@calcom/ui";
|
||||||
import { ExternalLink, Globe, Clock } from "@calcom/ui/components/icon";
|
import { ExternalLink, Globe } from "@calcom/ui/components/icon";
|
||||||
|
|
||||||
type AvailabilityOption = {
|
import { SelectSkeletonLoader } from "@components/availability/SkeletonLoader";
|
||||||
|
|
||||||
|
export type AvailabilityOption = {
|
||||||
label: string;
|
label: string;
|
||||||
value: number;
|
value: number;
|
||||||
isDefault: boolean;
|
isDefault: boolean;
|
||||||
@@ -62,40 +64,12 @@ const SingleValue = ({ ...props }: SingleValueProps<AvailabilityOption>) => {
|
|||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|
||||||
const AvailabilitySelect = ({
|
|
||||||
className = "",
|
|
||||||
options,
|
|
||||||
value,
|
|
||||||
...props
|
|
||||||
}: {
|
|
||||||
className?: string;
|
|
||||||
name: string;
|
|
||||||
value: AvailabilityOption | undefined;
|
|
||||||
options: AvailabilityOption[];
|
|
||||||
isDisabled?: boolean;
|
|
||||||
onBlur: () => void;
|
|
||||||
onChange: (value: AvailabilityOption | null) => void;
|
|
||||||
}) => {
|
|
||||||
const { t } = useLocale();
|
|
||||||
return (
|
|
||||||
<Select
|
|
||||||
placeholder={t("select")}
|
|
||||||
options={options}
|
|
||||||
isDisabled={props.isDisabled}
|
|
||||||
isSearchable={false}
|
|
||||||
onChange={props.onChange}
|
|
||||||
className={classNames("block w-full min-w-0 flex-1 rounded-sm text-sm", className)}
|
|
||||||
defaultValue={value}
|
|
||||||
components={{ Option, SingleValue }}
|
|
||||||
isMulti={false}
|
|
||||||
/>
|
|
||||||
);
|
|
||||||
};
|
|
||||||
|
|
||||||
const format = (date: Date, hour12: boolean) =>
|
const format = (date: Date, hour12: boolean) =>
|
||||||
Intl.DateTimeFormat(undefined, { hour: "numeric", minute: "numeric", hour12 }).format(
|
Intl.DateTimeFormat(undefined, {
|
||||||
new Date(dayjs.utc(date).format("YYYY-MM-DDTHH:mm:ss"))
|
hour: "numeric",
|
||||||
);
|
minute: "numeric",
|
||||||
|
hourCycle: hour12 ? "h12" : "h24",
|
||||||
|
}).format(new Date(dayjs.utc(date).format("YYYY-MM-DDTHH:mm:ss")));
|
||||||
|
|
||||||
const EventTypeScheduleDetails = ({
|
const EventTypeScheduleDetails = ({
|
||||||
isManagedEventType,
|
isManagedEventType,
|
||||||
@@ -188,61 +162,57 @@ const EventTypeSchedule = ({ eventType }: { eventType: EventTypeSetup }) => {
|
|||||||
);
|
);
|
||||||
const { watch } = useFormContext<FormValues>();
|
const { watch } = useFormContext<FormValues>();
|
||||||
const watchSchedule = watch("schedule");
|
const watchSchedule = watch("schedule");
|
||||||
|
const formMethods = useFormContext<FormValues>();
|
||||||
|
const [options, setOptions] = useState<AvailabilityOption[]>([]);
|
||||||
|
|
||||||
const { data, isLoading } = trpc.viewer.availability.list.useQuery();
|
const { isLoading } = trpc.viewer.availability.list.useQuery(undefined, {
|
||||||
|
onSuccess: ({ schedules }) => {
|
||||||
|
const options = schedules.map((schedule) => ({
|
||||||
|
value: schedule.id,
|
||||||
|
label: schedule.name,
|
||||||
|
isDefault: schedule.isDefault,
|
||||||
|
isManaged: false,
|
||||||
|
}));
|
||||||
|
|
||||||
if (!data?.schedules.length && !isLoading)
|
// We are showing a managed event for a team admin, so adding the option to let members choose their schedule
|
||||||
return (
|
if (isManagedEventType) {
|
||||||
<EmptyScreen
|
options.push({
|
||||||
Icon={Clock}
|
value: 0,
|
||||||
headline={t("new_schedule_heading")}
|
label: t("members_default_schedule"),
|
||||||
description={t("new_schedule_description")}
|
isDefault: false,
|
||||||
buttonRaw={<NewScheduleButton fromEventType />}
|
isManaged: false,
|
||||||
border={false}
|
});
|
||||||
/>
|
}
|
||||||
);
|
|
||||||
|
|
||||||
const schedules = data?.schedules || [];
|
// We are showing a managed event for a member and team owner selected their own schedule, so adding
|
||||||
|
// the managed schedule option
|
||||||
|
if (
|
||||||
|
isChildrenManagedEventType &&
|
||||||
|
watchSchedule &&
|
||||||
|
!schedules.find((schedule) => schedule.id === watchSchedule)
|
||||||
|
) {
|
||||||
|
options.push({
|
||||||
|
value: watchSchedule,
|
||||||
|
label: eventType.scheduleName ?? t("default_schedule_name"),
|
||||||
|
isDefault: false,
|
||||||
|
isManaged: false,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
const options = schedules.map((schedule) => ({
|
setOptions(options);
|
||||||
value: schedule.id,
|
|
||||||
label: schedule.name,
|
|
||||||
isDefault: schedule.isDefault,
|
|
||||||
isManaged: false,
|
|
||||||
}));
|
|
||||||
|
|
||||||
// We are showing a managed event for a team admin, so adding the option to let members choose their schedule
|
const scheduleId = formMethods.getValues("schedule");
|
||||||
if (isManagedEventType) {
|
const value = options.find((option) =>
|
||||||
options.push({
|
scheduleId
|
||||||
value: 0,
|
? option.value === scheduleId
|
||||||
label: t("members_default_schedule"),
|
: option.value === schedules.find((schedule) => schedule.isDefault)?.id
|
||||||
isDefault: false,
|
);
|
||||||
isManaged: false,
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
// We are showing a managed event for a member and team owner selected their own schedule, so adding
|
formMethods.setValue("availability", value);
|
||||||
// the managed schedule option
|
},
|
||||||
if (
|
});
|
||||||
isChildrenManagedEventType &&
|
|
||||||
watchSchedule &&
|
|
||||||
!schedules.find((schedule) => schedule.id === watchSchedule)
|
|
||||||
) {
|
|
||||||
options.push({
|
|
||||||
value: watchSchedule,
|
|
||||||
label: eventType.scheduleName ?? t("default_schedule_name"),
|
|
||||||
isDefault: false,
|
|
||||||
isManaged: false,
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
const value = options.find((option) =>
|
const availabilityValue = formMethods.watch("availability");
|
||||||
watchSchedule
|
|
||||||
? option.value === watchSchedule
|
|
||||||
: isManagedEventType
|
|
||||||
? option.value === 0
|
|
||||||
: option.value === schedules.find((schedule) => schedule.isDefault)?.id
|
|
||||||
);
|
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className="space-y-4">
|
<div className="space-y-4">
|
||||||
@@ -251,27 +221,33 @@ const EventTypeSchedule = ({ eventType }: { eventType: EventTypeSetup }) => {
|
|||||||
{t("availability")}
|
{t("availability")}
|
||||||
{shouldLockIndicator("availability")}
|
{shouldLockIndicator("availability")}
|
||||||
</label>
|
</label>
|
||||||
<Controller
|
{isLoading && <SelectSkeletonLoader />}
|
||||||
name="schedule"
|
{!isLoading && (
|
||||||
render={({ field }) => (
|
<Controller
|
||||||
<>
|
name="schedule"
|
||||||
<AvailabilitySelect
|
render={({ field }) => {
|
||||||
value={value}
|
return (
|
||||||
options={options}
|
<Select
|
||||||
onBlur={field.onBlur}
|
placeholder={t("select")}
|
||||||
isDisabled={shouldLockDisableProps("schedule").disabled}
|
options={options}
|
||||||
name={field.name}
|
isSearchable={false}
|
||||||
onChange={(selected) => {
|
onChange={(selected) => {
|
||||||
field.onChange(selected?.value || null);
|
field.onChange(selected?.value || null);
|
||||||
}}
|
if (selected?.value) formMethods.setValue("availability", selected);
|
||||||
/>
|
}}
|
||||||
</>
|
className="block w-full min-w-0 flex-1 rounded-sm text-sm"
|
||||||
)}
|
value={availabilityValue}
|
||||||
/>
|
components={{ Option, SingleValue }}
|
||||||
|
isMulti={false}
|
||||||
|
/>
|
||||||
|
);
|
||||||
|
}}
|
||||||
|
/>
|
||||||
|
)}
|
||||||
</div>
|
</div>
|
||||||
{value?.value !== 0 ? (
|
{availabilityValue?.value !== 0 ? (
|
||||||
<EventTypeScheduleDetails
|
<EventTypeScheduleDetails
|
||||||
selectedScheduleValue={value}
|
selectedScheduleValue={availabilityValue}
|
||||||
isManagedEventType={isManagedEventType || isChildrenManagedEventType}
|
isManagedEventType={isManagedEventType || isChildrenManagedEventType}
|
||||||
/>
|
/>
|
||||||
) : (
|
) : (
|
||||||
|
|||||||
@@ -12,6 +12,7 @@ import { z } from "zod";
|
|||||||
import type { EventLocationType } from "@calcom/app-store/locations";
|
import type { EventLocationType } from "@calcom/app-store/locations";
|
||||||
import { getEventLocationType, MeetLocationType, LocationType } from "@calcom/app-store/locations";
|
import { getEventLocationType, MeetLocationType, LocationType } from "@calcom/app-store/locations";
|
||||||
import useLockedFieldsManager from "@calcom/features/ee/managed-event-types/hooks/useLockedFieldsManager";
|
import useLockedFieldsManager from "@calcom/features/ee/managed-event-types/hooks/useLockedFieldsManager";
|
||||||
|
import cx from "@calcom/lib/classNames";
|
||||||
import { CAL_URL } from "@calcom/lib/constants";
|
import { CAL_URL } from "@calcom/lib/constants";
|
||||||
import { useLocale } from "@calcom/lib/hooks/useLocale";
|
import { useLocale } from "@calcom/lib/hooks/useLocale";
|
||||||
import { md } from "@calcom/lib/markdownIt";
|
import { md } from "@calcom/lib/markdownIt";
|
||||||
@@ -80,6 +81,7 @@ const DescriptionEditor = (props: DescriptionEditorProps) => {
|
|||||||
const [mounted, setIsMounted] = useState(false);
|
const [mounted, setIsMounted] = useState(false);
|
||||||
const { t } = useLocale();
|
const { t } = useLocale();
|
||||||
const { description } = props;
|
const { description } = props;
|
||||||
|
const [firstRender, setFirstRender] = useState(true);
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
setIsMounted(true);
|
setIsMounted(true);
|
||||||
}, []);
|
}, []);
|
||||||
@@ -91,6 +93,8 @@ const DescriptionEditor = (props: DescriptionEditorProps) => {
|
|||||||
excludedToolbarItems={["blockType"]}
|
excludedToolbarItems={["blockType"]}
|
||||||
placeholder={t("quick_video_meeting")}
|
placeholder={t("quick_video_meeting")}
|
||||||
editable={props.editable}
|
editable={props.editable}
|
||||||
|
firstRender={firstRender}
|
||||||
|
setFirstRender={setFirstRender}
|
||||||
/>
|
/>
|
||||||
) : (
|
) : (
|
||||||
<SkeletonContainer>
|
<SkeletonContainer>
|
||||||
@@ -277,7 +281,13 @@ export const EventSetupTab = (
|
|||||||
<div className="flex items-center">
|
<div className="flex items-center">
|
||||||
<img
|
<img
|
||||||
src={eventLocationType.iconUrl}
|
src={eventLocationType.iconUrl}
|
||||||
className="h-4 w-4"
|
className={cx(
|
||||||
|
"h-4 w-4",
|
||||||
|
// invert all the icons except app icons
|
||||||
|
eventLocationType.iconUrl &&
|
||||||
|
!eventLocationType.iconUrl.startsWith("/api") &&
|
||||||
|
"dark:invert"
|
||||||
|
)}
|
||||||
alt={`${eventLocationType.label} logo`}
|
alt={`${eventLocationType.label} logo`}
|
||||||
/>
|
/>
|
||||||
<span className="line-clamp-1 ms-1 text-sm">{eventLabel}</span>
|
<span className="line-clamp-1 ms-1 text-sm">{eventLabel}</span>
|
||||||
|
|||||||
@@ -51,6 +51,7 @@ import {
|
|||||||
} from "@calcom/ui/components/icon";
|
} from "@calcom/ui/components/icon";
|
||||||
|
|
||||||
import { EmbedButton, EmbedDialog } from "@components/Embed";
|
import { EmbedButton, EmbedDialog } from "@components/Embed";
|
||||||
|
import type { AvailabilityOption } from "@components/eventtype/EventAvailabilityTab";
|
||||||
|
|
||||||
type Props = {
|
type Props = {
|
||||||
children: React.ReactNode;
|
children: React.ReactNode;
|
||||||
@@ -63,6 +64,7 @@ type Props = {
|
|||||||
enabledWorkflowsNumber: number;
|
enabledWorkflowsNumber: number;
|
||||||
formMethods: UseFormReturn<FormValues>;
|
formMethods: UseFormReturn<FormValues>;
|
||||||
isUpdateMutationLoading?: boolean;
|
isUpdateMutationLoading?: boolean;
|
||||||
|
availability?: AvailabilityOption;
|
||||||
};
|
};
|
||||||
|
|
||||||
function getNavigation(props: {
|
function getNavigation(props: {
|
||||||
@@ -71,8 +73,10 @@ function getNavigation(props: {
|
|||||||
enabledAppsNumber: number;
|
enabledAppsNumber: number;
|
||||||
enabledWorkflowsNumber: number;
|
enabledWorkflowsNumber: number;
|
||||||
installedAppsNumber: number;
|
installedAppsNumber: number;
|
||||||
|
availability: AvailabilityOption | undefined;
|
||||||
}) {
|
}) {
|
||||||
const { eventType, t, enabledAppsNumber, installedAppsNumber, enabledWorkflowsNumber } = props;
|
const { eventType, t, enabledAppsNumber, installedAppsNumber, enabledWorkflowsNumber, availability } =
|
||||||
|
props;
|
||||||
const duration =
|
const duration =
|
||||||
eventType.metadata?.multipleDuration?.map((duration) => ` ${duration}`) || eventType.length;
|
eventType.metadata?.multipleDuration?.map((duration) => ` ${duration}`) || eventType.length;
|
||||||
|
|
||||||
@@ -128,6 +132,7 @@ function EventTypeSingleLayout({
|
|||||||
enabledWorkflowsNumber,
|
enabledWorkflowsNumber,
|
||||||
isUpdateMutationLoading,
|
isUpdateMutationLoading,
|
||||||
formMethods,
|
formMethods,
|
||||||
|
availability,
|
||||||
}: Props) {
|
}: Props) {
|
||||||
const utils = trpc.useContext();
|
const utils = trpc.useContext();
|
||||||
const { t } = useLocale();
|
const { t } = useLocale();
|
||||||
@@ -171,6 +176,7 @@ function EventTypeSingleLayout({
|
|||||||
enabledAppsNumber,
|
enabledAppsNumber,
|
||||||
installedAppsNumber,
|
installedAppsNumber,
|
||||||
enabledWorkflowsNumber,
|
enabledWorkflowsNumber,
|
||||||
|
availability,
|
||||||
});
|
});
|
||||||
navigation.splice(1, 0, {
|
navigation.splice(1, 0, {
|
||||||
name: "availability",
|
name: "availability",
|
||||||
@@ -214,7 +220,7 @@ function EventTypeSingleLayout({
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
return navigation;
|
return navigation;
|
||||||
}, [t, eventType, installedAppsNumber, enabledAppsNumber, enabledWorkflowsNumber, team]);
|
}, [t, eventType, installedAppsNumber, enabledAppsNumber, enabledWorkflowsNumber, team, availability]);
|
||||||
|
|
||||||
const permalink = `${CAL_URL}/${team ? `team/${team.slug}` : eventType.users[0].username}/${
|
const permalink = `${CAL_URL}/${team ? `team/${team.slug}` : eventType.users[0].username}/${
|
||||||
eventType.slug
|
eventType.slug
|
||||||
@@ -374,6 +380,8 @@ function EventTypeSingleLayout({
|
|||||||
tabs={EventTypeTabs}
|
tabs={EventTypeTabs}
|
||||||
sticky
|
sticky
|
||||||
linkProps={{ shallow: true }}
|
linkProps={{ shallow: true }}
|
||||||
|
itemClassname="items-start"
|
||||||
|
iconClassName="md:mt-px"
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
<div className="p-2 md:mx-0 md:p-0 xl:hidden">
|
<div className="p-2 md:mx-0 md:p-0 xl:hidden">
|
||||||
|
|||||||
@@ -35,6 +35,7 @@ const UserProfile = (props: IUserProfileProps) => {
|
|||||||
const router = useRouter();
|
const router = useRouter();
|
||||||
const createEventType = trpc.viewer.eventTypes.create.useMutation();
|
const createEventType = trpc.viewer.eventTypes.create.useMutation();
|
||||||
const telemetry = useTelemetry();
|
const telemetry = useTelemetry();
|
||||||
|
const [firstRender, setFirstRender] = useState(true);
|
||||||
|
|
||||||
const mutation = trpc.viewer.updateProfile.useMutation({
|
const mutation = trpc.viewer.updateProfile.useMutation({
|
||||||
onSuccess: async (_data, context) => {
|
onSuccess: async (_data, context) => {
|
||||||
@@ -147,6 +148,8 @@ const UserProfile = (props: IUserProfileProps) => {
|
|||||||
getText={() => md.render(getValues("bio") || user?.bio || "")}
|
getText={() => md.render(getValues("bio") || user?.bio || "")}
|
||||||
setText={(value: string) => setValue("bio", turndown(value))}
|
setText={(value: string) => setValue("bio", turndown(value))}
|
||||||
excludedToolbarItems={["blockType"]}
|
excludedToolbarItems={["blockType"]}
|
||||||
|
firstRender={firstRender}
|
||||||
|
setFirstRender={setFirstRender}
|
||||||
/>
|
/>
|
||||||
<p className="dark:text-inverted text-default mt-2 font-sans text-sm font-normal">
|
<p className="dark:text-inverted text-default mt-2 font-sans text-sm font-normal">
|
||||||
{t("few_sentences_about_yourself")}
|
{t("few_sentences_about_yourself")}
|
||||||
|
|||||||
@@ -75,8 +75,8 @@ export function CalendarSwitch(props: {
|
|||||||
/>
|
/>
|
||||||
{props.defaultSelected && (
|
{props.defaultSelected && (
|
||||||
<Badge variant="gray">
|
<Badge variant="gray">
|
||||||
<div className="flex">
|
<div className="flex items-center">
|
||||||
<ArrowLeft className="text-default mr-1" /> {t("adding_events_to")}
|
<ArrowLeft className="text-default mr-1 h-4 w-4" /> {t("adding_events_to")}
|
||||||
</div>
|
</div>
|
||||||
</Badge>
|
</Badge>
|
||||||
)}
|
)}
|
||||||
|
|||||||
@@ -1,17 +0,0 @@
|
|||||||
import { useSession } from "next-auth/react";
|
|
||||||
import type { FC } from "react";
|
|
||||||
import { Fragment } from "react";
|
|
||||||
|
|
||||||
type AdminRequiredProps = {
|
|
||||||
as?: keyof JSX.IntrinsicElements;
|
|
||||||
children?: React.ReactNode;
|
|
||||||
};
|
|
||||||
|
|
||||||
/** @deprecated use PermssionContainer instead. Will delete once V2 goes live */
|
|
||||||
export const AdminRequired: FC<AdminRequiredProps> = ({ children, as, ...rest }) => {
|
|
||||||
const session = useSession();
|
|
||||||
|
|
||||||
if (session.data?.user.role !== "ADMIN") return null;
|
|
||||||
const Component = as ?? Fragment;
|
|
||||||
return <Component {...rest}>{children}</Component>;
|
|
||||||
};
|
|
||||||
@@ -1,7 +1,6 @@
|
|||||||
import classNames from "classnames";
|
import classNames from "classnames";
|
||||||
|
|
||||||
import { APP_NAME, LOGO } from "@calcom/lib/constants";
|
import { HeadSeo, Logo } from "@calcom/ui";
|
||||||
import { HeadSeo } from "@calcom/ui";
|
|
||||||
|
|
||||||
import Loader from "@components/Loader";
|
import Loader from "@components/Loader";
|
||||||
|
|
||||||
@@ -18,10 +17,7 @@ export default function AuthContainer(props: React.PropsWithChildren<Props>) {
|
|||||||
return (
|
return (
|
||||||
<div className="flex min-h-screen flex-col justify-center bg-[#f3f4f6] py-12 sm:px-6 lg:px-8">
|
<div className="flex min-h-screen flex-col justify-center bg-[#f3f4f6] py-12 sm:px-6 lg:px-8">
|
||||||
<HeadSeo title={props.title} description={props.description} />
|
<HeadSeo title={props.title} description={props.description} />
|
||||||
{props.showLogo && (
|
{props.showLogo && <Logo small inline={false} className="mx-auto mb-auto" />}
|
||||||
// eslint-disable-next-line @next/next/no-img-element
|
|
||||||
<img className="mb-auto h-4" src={LOGO} alt={`${APP_NAME} Logo`} />
|
|
||||||
)}
|
|
||||||
|
|
||||||
<div className={classNames(props.showLogo ? "text-center" : "", "sm:mx-auto sm:w-full sm:max-w-md")}>
|
<div className={classNames(props.showLogo ? "text-center" : "", "sm:mx-auto sm:w-full sm:max-w-md")}>
|
||||||
{props.heading && <h2 className="font-cal text-emphasis text-center text-3xl">{props.heading}</h2>}
|
{props.heading && <h2 className="font-cal text-emphasis text-center text-3xl">{props.heading}</h2>}
|
||||||
|
|||||||
@@ -16,8 +16,10 @@ const EditableHeading = function EditableHeading({
|
|||||||
const [isEditing, setIsEditing] = useState(false);
|
const [isEditing, setIsEditing] = useState(false);
|
||||||
const enableEditing = () => setIsEditing(true);
|
const enableEditing = () => setIsEditing(true);
|
||||||
return (
|
return (
|
||||||
<div className="group relative cursor-pointer" onClick={enableEditing}>
|
<div
|
||||||
<div className="flex items-center">
|
className="group pointer-events-none relative truncate sm:pointer-events-auto"
|
||||||
|
onClick={enableEditing}>
|
||||||
|
<div className="flex cursor-pointer items-center">
|
||||||
<label className="min-w-8 relative inline-block">
|
<label className="min-w-8 relative inline-block">
|
||||||
<span className="whitespace-pre text-xl tracking-normal text-transparent">{value} </span>
|
<span className="whitespace-pre text-xl tracking-normal text-transparent">{value} </span>
|
||||||
{!isEditing && isReady && (
|
{!isEditing && isReady && (
|
||||||
@@ -29,7 +31,7 @@ const EditableHeading = function EditableHeading({
|
|||||||
value={value}
|
value={value}
|
||||||
required
|
required
|
||||||
className={classNames(
|
className={classNames(
|
||||||
"text-emphasis hover:text-default focus:text-emphasis absolute top-0 left-0 w-full cursor-pointer border-none bg-transparent p-0 align-top text-xl focus:outline-none focus:ring-0"
|
"text-emphasis hover:text-default focus:text-emphasis absolute top-0 left-0 w-full cursor-pointer truncate border-none bg-transparent p-0 align-top text-xl focus:outline-none focus:ring-0"
|
||||||
)}
|
)}
|
||||||
onFocus={(e) => {
|
onFocus={(e) => {
|
||||||
setIsEditing(true);
|
setIsEditing(true);
|
||||||
|
|||||||
@@ -79,7 +79,7 @@ const UsernameTextfield = (props: ICustomUsernameProps) => {
|
|||||||
|
|
||||||
const ActionButtons = () => {
|
const ActionButtons = () => {
|
||||||
return usernameIsAvailable && currentUsername !== inputUsernameValue ? (
|
return usernameIsAvailable && currentUsername !== inputUsernameValue ? (
|
||||||
<div className="rlt:space-x-reverse ms-2 me-2 mt-px flex flex-row space-x-2 rtl:space-x-reverse">
|
<div className="ms-2 me-2 mt-px flex flex-row space-x-2">
|
||||||
<Button
|
<Button
|
||||||
type="button"
|
type="button"
|
||||||
onClick={() => setOpenDialogSaveUsername(true)}
|
onClick={() => setOpenDialogSaveUsername(true)}
|
||||||
@@ -123,7 +123,7 @@ const UsernameTextfield = (props: ICustomUsernameProps) => {
|
|||||||
autoCapitalize="none"
|
autoCapitalize="none"
|
||||||
autoCorrect="none"
|
autoCorrect="none"
|
||||||
className={classNames(
|
className={classNames(
|
||||||
"mb-0 mt-0 rounded-md ltr:rounded-l-none rtl:rounded-r-none",
|
"mb-0 mt-0 rounded-md rounded-l-none",
|
||||||
markAsError
|
markAsError
|
||||||
? "focus:shadow-0 focus:ring-shadow-0 border-red-500 focus:border-red-500 focus:outline-none focus:ring-0"
|
? "focus:shadow-0 focus:ring-shadow-0 border-red-500 focus:border-red-500 focus:outline-none focus:ring-0"
|
||||||
: ""
|
: ""
|
||||||
|
|||||||
@@ -49,7 +49,7 @@ const CheckboxField = forwardRef<HTMLInputElement, Props>(
|
|||||||
{...rest}
|
{...rest}
|
||||||
ref={ref}
|
ref={ref}
|
||||||
type="checkbox"
|
type="checkbox"
|
||||||
className="text-primary-600 focus:ring-primary-500 border-default h-4 w-4 rounded"
|
className="text-primary-600 focus:ring-primary-500 border-default bg-default h-4 w-4 rounded"
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
<span className="ms-3 text-sm">{description}</span>
|
<span className="ms-3 text-sm">{description}</span>
|
||||||
|
|||||||
@@ -3,6 +3,7 @@ import { components } from "react-select";
|
|||||||
|
|
||||||
import type { EventLocationType } from "@calcom/app-store/locations";
|
import type { EventLocationType } from "@calcom/app-store/locations";
|
||||||
import { classNames } from "@calcom/lib";
|
import { classNames } from "@calcom/lib";
|
||||||
|
import cx from "@calcom/lib/classNames";
|
||||||
import { Select } from "@calcom/ui";
|
import { Select } from "@calcom/ui";
|
||||||
|
|
||||||
export type LocationOption = {
|
export type LocationOption = {
|
||||||
@@ -19,10 +20,14 @@ export type GroupOptionType = GroupBase<LocationOption>;
|
|||||||
const OptionWithIcon = ({ icon, label }: { icon?: string; label: string }) => {
|
const OptionWithIcon = ({ icon, label }: { icon?: string; label: string }) => {
|
||||||
return (
|
return (
|
||||||
<div className="flex items-center gap-3">
|
<div className="flex items-center gap-3">
|
||||||
{/* TODO: figure out a way to invert icons when in dark mode. We can't just
|
{icon && (
|
||||||
dark:invert due to google meet cal etc all breaking when we do this
|
<img
|
||||||
*/}
|
src={icon}
|
||||||
{icon && <img src={icon} alt="cover" className="h-3.5 w-3.5 dark:hidden" />}
|
alt="cover"
|
||||||
|
// invert all the icons except app icons
|
||||||
|
className={cx("h-3.5 w-3.5", icon && !icon.startsWith("/api") && "dark:invert")}
|
||||||
|
/>
|
||||||
|
)}
|
||||||
<span className={classNames("text-sm font-medium")}>{label}</span>
|
<span className={classNames("text-sm font-medium")}>{label}</span>
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
|
|||||||
@@ -27,8 +27,10 @@ const I18nextAdapter = appWithTranslation<NextJsAppProps<SSRConfig> & { children
|
|||||||
export type AppProps = Omit<NextAppProps<WithNonceProps & Record<string, unknown>>, "Component"> & {
|
export type AppProps = Omit<NextAppProps<WithNonceProps & Record<string, unknown>>, "Component"> & {
|
||||||
Component: NextAppProps["Component"] & {
|
Component: NextAppProps["Component"] & {
|
||||||
requiresLicense?: boolean;
|
requiresLicense?: boolean;
|
||||||
isThemeSupported?: boolean | ((arg: { router: NextRouter }) => boolean);
|
isThemeSupported?: boolean;
|
||||||
|
isBookingPage?: boolean | ((arg: { router: NextRouter }) => boolean);
|
||||||
getLayout?: (page: React.ReactElement, router: NextRouter) => ReactNode;
|
getLayout?: (page: React.ReactElement, router: NextRouter) => ReactNode;
|
||||||
|
PageWrapper?: (props: AppProps) => JSX.Element;
|
||||||
};
|
};
|
||||||
|
|
||||||
/** Will be defined only is there was an error */
|
/** Will be defined only is there was an error */
|
||||||
@@ -61,45 +63,66 @@ const CustomI18nextProvider = (props: AppPropsWithChildren) => {
|
|||||||
return <I18nextAdapter {...passedProps} />;
|
return <I18nextAdapter {...passedProps} />;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
const enum ThemeSupport {
|
||||||
|
// e.g. Login Page
|
||||||
|
None = "none",
|
||||||
|
// Entire App except Booking Pages
|
||||||
|
App = "systemOnly",
|
||||||
|
// Booking Pages(including Routing Forms)
|
||||||
|
Booking = "userConfigured",
|
||||||
|
}
|
||||||
|
|
||||||
const CalcomThemeProvider = (
|
const CalcomThemeProvider = (
|
||||||
props: PropsWithChildren<
|
props: PropsWithChildren<
|
||||||
WithNonceProps & { isThemeSupported?: boolean | ((arg: { router: NextRouter }) => boolean) }
|
WithNonceProps & {
|
||||||
|
isBookingPage?: boolean | ((arg: { router: NextRouter }) => boolean);
|
||||||
|
isThemeSupported?: boolean;
|
||||||
|
}
|
||||||
>
|
>
|
||||||
) => {
|
) => {
|
||||||
// We now support the inverse of how we handled it in the past. Setting this to false will disable theme.
|
// We now support the inverse of how we handled it in the past. Setting this to false will disable theme.
|
||||||
// undefined or true means we use system theme
|
// undefined or true means we use system theme
|
||||||
const router = useRouter();
|
const router = useRouter();
|
||||||
const isThemeSupported = (() => {
|
const isBookingPage = (() => {
|
||||||
if (typeof props.isThemeSupported === "function") {
|
if (typeof props.isBookingPage === "function") {
|
||||||
return props.isThemeSupported({ router: router });
|
return props.isBookingPage({ router: router });
|
||||||
}
|
}
|
||||||
if (typeof props.isThemeSupported === "undefined") {
|
|
||||||
return true;
|
return props.isBookingPage;
|
||||||
}
|
|
||||||
return props.isThemeSupported;
|
|
||||||
})();
|
})();
|
||||||
|
|
||||||
const forcedTheme = !isThemeSupported ? "light" : undefined;
|
const themeSupport = isBookingPage
|
||||||
|
? ThemeSupport.Booking
|
||||||
|
: // if isThemeSupported is explicitly false, we don't use theme there
|
||||||
|
props.isThemeSupported === false
|
||||||
|
? ThemeSupport.None
|
||||||
|
: ThemeSupport.App;
|
||||||
|
|
||||||
|
const forcedTheme = themeSupport === ThemeSupport.None ? "light" : undefined;
|
||||||
// Use namespace of embed to ensure same namespaced embed are displayed with same theme. This allows different embeds on the same website to be themed differently
|
// Use namespace of embed to ensure same namespaced embed are displayed with same theme. This allows different embeds on the same website to be themed differently
|
||||||
// One such example is our Embeds Demo and Testing page at http://localhost:3100
|
// One such example is our Embeds Demo and Testing page at http://localhost:3100
|
||||||
// Having `getEmbedNamespace` defined on window before react initializes the app, ensures that embedNamespace is available on the first mount and can be used as part of storageKey
|
// Having `getEmbedNamespace` defined on window before react initializes the app, ensures that embedNamespace is available on the first mount and can be used as part of storageKey
|
||||||
const embedNamespace = typeof window !== "undefined" ? window.getEmbedNamespace() : null;
|
const embedNamespace = typeof window !== "undefined" ? window.getEmbedNamespace() : null;
|
||||||
const isEmbedMode = typeof embedNamespace === "string";
|
const isEmbedMode = typeof embedNamespace === "string";
|
||||||
// If embedNamespace is not defined, we use the default storageKey -> The default storage key changs based on if we force light mode or not
|
|
||||||
// This is done to ensure that the default theme is light when we force light mode and as soon as you navigate to a page that is dark we dont need a hard refresh to change
|
|
||||||
const storageKey = isEmbedMode
|
const storageKey = isEmbedMode
|
||||||
? `embed-theme-${embedNamespace}`
|
? `embed-theme-${embedNamespace}`
|
||||||
: !isThemeSupported
|
: themeSupport === ThemeSupport.App
|
||||||
? "cal-light"
|
? "app-theme"
|
||||||
: "theme";
|
: themeSupport === ThemeSupport.Booking
|
||||||
|
? "booking-theme"
|
||||||
|
: undefined;
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<ThemeProvider
|
<ThemeProvider
|
||||||
nonce={props.nonce}
|
nonce={props.nonce}
|
||||||
enableColorScheme={false}
|
enableColorScheme={false}
|
||||||
enableSystem={isThemeSupported}
|
enableSystem={themeSupport !== ThemeSupport.None}
|
||||||
forcedTheme={forcedTheme}
|
forcedTheme={forcedTheme}
|
||||||
storageKey={storageKey}
|
storageKey={storageKey}
|
||||||
|
// next-themes doesn't listen to changes on storageKey. So we need to force a re-render when storageKey changes
|
||||||
|
// This is how login to dashboard soft navigation changes theme from light to dark
|
||||||
|
key={storageKey}
|
||||||
attribute="class">
|
attribute="class">
|
||||||
{/* Embed Mode can be detected reliably only on client side here as there can be static generated pages as well which can't determine if it's embed mode at backend */}
|
{/* Embed Mode can be detected reliably only on client side here as there can be static generated pages as well which can't determine if it's embed mode at backend */}
|
||||||
{/* color-scheme makes background:transparent not work in iframe which is required by embed. */}
|
{/* color-scheme makes background:transparent not work in iframe which is required by embed. */}
|
||||||
@@ -132,9 +155,11 @@ const AppProviders = (props: AppPropsWithChildren) => {
|
|||||||
<SessionProvider session={session || undefined}>
|
<SessionProvider session={session || undefined}>
|
||||||
<CustomI18nextProvider {...props}>
|
<CustomI18nextProvider {...props}>
|
||||||
<TooltipProvider>
|
<TooltipProvider>
|
||||||
|
{/* color-scheme makes background:transparent not work which is required by embed. We need to ensure next-theme adds color-scheme to `body` instead of `html`(https://github.com/pacocoursey/next-themes/blob/main/src/index.tsx#L74). Once that's done we can enable color-scheme support */}
|
||||||
<CalcomThemeProvider
|
<CalcomThemeProvider
|
||||||
nonce={props.pageProps.nonce}
|
nonce={props.pageProps.nonce}
|
||||||
isThemeSupported={props.Component.isThemeSupported}>
|
isThemeSupported={props.Component.isThemeSupported}
|
||||||
|
isBookingPage={props.Component.isBookingPage}>
|
||||||
<FeatureFlagsProvider>
|
<FeatureFlagsProvider>
|
||||||
<MetaProvider>{props.children}</MetaProvider>
|
<MetaProvider>{props.children}</MetaProvider>
|
||||||
</FeatureFlagsProvider>
|
</FeatureFlagsProvider>
|
||||||
|
|||||||
+1
-4
@@ -13,9 +13,6 @@ function getCspPolicy(nonce: string) {
|
|||||||
// We can remove 'unsafe-inline' from style-src when we add nonces to all style tags
|
// We can remove 'unsafe-inline' from style-src when we add nonces to all style tags
|
||||||
// Maybe see how @next-safe/middleware does it if it's supported.
|
// Maybe see how @next-safe/middleware does it if it's supported.
|
||||||
const useNonStrictPolicy = CSP_POLICY === "non-strict";
|
const useNonStrictPolicy = CSP_POLICY === "non-strict";
|
||||||
const SENTRY_ENDPOINT = process.env.NEXT_PUBLIC_SENTRY_DSN
|
|
||||||
? new URL(process.env.NEXT_PUBLIC_SENTRY_DSN, "http://base_url").origin
|
|
||||||
: "";
|
|
||||||
|
|
||||||
// We add WEBAPP_URL to img-src because of booking pages, which end up loading images from app.cal.com on cal.com
|
// We add WEBAPP_URL to img-src because of booking pages, which end up loading images from app.cal.com on cal.com
|
||||||
// FIXME: Write a layer to extract out EventType Analytics tracking endpoints and add them to img-src or connect-src as needed. e.g. fathom, Google Analytics and others
|
// FIXME: Write a layer to extract out EventType Analytics tracking endpoints and add them to img-src or connect-src as needed. e.g. fathom, Google Analytics and others
|
||||||
@@ -36,7 +33,7 @@ function getCspPolicy(nonce: string) {
|
|||||||
} app.cal.com;
|
} app.cal.com;
|
||||||
font-src 'self';
|
font-src 'self';
|
||||||
img-src 'self' ${WEBAPP_URL} https://www.gravatar.com https://img.youtube.com https://eu.ui-avatars.com/api/ data:;
|
img-src 'self' ${WEBAPP_URL} https://www.gravatar.com https://img.youtube.com https://eu.ui-avatars.com/api/ data:;
|
||||||
connect-src 'self' ${SENTRY_ENDPOINT}
|
connect-src 'self'
|
||||||
`;
|
`;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
+1
-19
@@ -1,6 +1,5 @@
|
|||||||
require("dotenv").config({ path: "../../.env" });
|
require("dotenv").config({ path: "../../.env" });
|
||||||
const CopyWebpackPlugin = require("copy-webpack-plugin");
|
const CopyWebpackPlugin = require("copy-webpack-plugin");
|
||||||
const { withSentryConfig } = require("@sentry/nextjs");
|
|
||||||
const os = require("os");
|
const os = require("os");
|
||||||
|
|
||||||
const { withAxiom } = require("next-axiom");
|
const { withAxiom } = require("next-axiom");
|
||||||
@@ -318,21 +317,4 @@ const nextConfig = {
|
|||||||
},
|
},
|
||||||
};
|
};
|
||||||
|
|
||||||
const sentryWebpackPluginOptions = {
|
module.exports = () => plugins.reduce((acc, next) => next(acc), nextConfig);
|
||||||
silent: true, // Suppresses all logs
|
|
||||||
};
|
|
||||||
|
|
||||||
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)
|
|
||||||
: moduleExports;
|
|
||||||
@@ -1,6 +1,6 @@
|
|||||||
{
|
{
|
||||||
"name": "@calcom/web",
|
"name": "@calcom/web",
|
||||||
"version": "2.7.16",
|
"version": "2.8.7",
|
||||||
"private": true,
|
"private": true,
|
||||||
"scripts": {
|
"scripts": {
|
||||||
"analyze": "ANALYZE=true next build",
|
"analyze": "ANALYZE=true next build",
|
||||||
@@ -57,7 +57,6 @@
|
|||||||
"@radix-ui/react-switch": "^1.0.0",
|
"@radix-ui/react-switch": "^1.0.0",
|
||||||
"@radix-ui/react-toggle-group": "^1.0.0",
|
"@radix-ui/react-toggle-group": "^1.0.0",
|
||||||
"@radix-ui/react-tooltip": "^1.0.0",
|
"@radix-ui/react-tooltip": "^1.0.0",
|
||||||
"@sentry/nextjs": "^7.20.0",
|
|
||||||
"@stripe/react-stripe-js": "^1.10.0",
|
"@stripe/react-stripe-js": "^1.10.0",
|
||||||
"@stripe/stripe-js": "^1.35.0",
|
"@stripe/stripe-js": "^1.35.0",
|
||||||
"@tanstack/react-query": "^4.3.9",
|
"@tanstack/react-query": "^4.3.9",
|
||||||
@@ -116,8 +115,6 @@
|
|||||||
"react-select": "^5.7.0",
|
"react-select": "^5.7.0",
|
||||||
"react-timezone-select": "^1.4.0",
|
"react-timezone-select": "^1.4.0",
|
||||||
"react-use-intercom": "1.5.1",
|
"react-use-intercom": "1.5.1",
|
||||||
"react-virtualized-auto-sizer": "^1.0.6",
|
|
||||||
"react-window": "^1.8.7",
|
|
||||||
"remark": "^14.0.2",
|
"remark": "^14.0.2",
|
||||||
"rrule": "^2.7.1",
|
"rrule": "^2.7.1",
|
||||||
"sanitize-html": "^2.10.0",
|
"sanitize-html": "^2.10.0",
|
||||||
@@ -156,8 +153,6 @@
|
|||||||
"@types/qrcode": "^1.4.3",
|
"@types/qrcode": "^1.4.3",
|
||||||
"@types/react": "18.0.26",
|
"@types/react": "18.0.26",
|
||||||
"@types/react-phone-number-input": "^3.0.14",
|
"@types/react-phone-number-input": "^3.0.14",
|
||||||
"@types/react-virtualized-auto-sizer": "^1.0.1",
|
|
||||||
"@types/react-window": "^1.8.5",
|
|
||||||
"@types/sanitize-html": "^2.9.0",
|
"@types/sanitize-html": "^2.9.0",
|
||||||
"@types/stripe": "^8.0.417",
|
"@types/stripe": "^8.0.417",
|
||||||
"@types/uuid": "8.3.1",
|
"@types/uuid": "8.3.1",
|
||||||
|
|||||||
@@ -8,6 +8,8 @@ import { useLocale } from "@calcom/lib/hooks/useLocale";
|
|||||||
import { HeadSeo } from "@calcom/ui";
|
import { HeadSeo } from "@calcom/ui";
|
||||||
import { BookOpen, Check, ChevronRight, FileText } from "@calcom/ui/components/icon";
|
import { BookOpen, Check, ChevronRight, FileText } from "@calcom/ui/components/icon";
|
||||||
|
|
||||||
|
import PageWrapper from "@components/PageWrapper";
|
||||||
|
|
||||||
import { ssgInit } from "@server/lib/ssg";
|
import { ssgInit } from "@server/lib/ssg";
|
||||||
|
|
||||||
export default function Custom404() {
|
export default function Custom404() {
|
||||||
@@ -366,6 +368,8 @@ export default function Custom404() {
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
Custom404.PageWrapper = PageWrapper;
|
||||||
|
|
||||||
export const getStaticProps = async (context: GetStaticPropsContext) => {
|
export const getStaticProps = async (context: GetStaticPropsContext) => {
|
||||||
const ssr = await ssgInit(context);
|
const ssr = await ssgInit(context);
|
||||||
|
|
||||||
|
|||||||
@@ -6,6 +6,8 @@ import { useLocale } from "@calcom/lib/hooks/useLocale";
|
|||||||
import { Button, showToast } from "@calcom/ui";
|
import { Button, showToast } from "@calcom/ui";
|
||||||
import { Copy } from "@calcom/ui/components/icon";
|
import { Copy } from "@calcom/ui/components/icon";
|
||||||
|
|
||||||
|
import PageWrapper from "@components/PageWrapper";
|
||||||
|
|
||||||
export default function Error500() {
|
export default function Error500() {
|
||||||
const { t } = useLocale();
|
const { t } = useLocale();
|
||||||
const router = useRouter();
|
const router = useRouter();
|
||||||
@@ -52,3 +54,5 @@ export default function Error500() {
|
|||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
Error500.PageWrapper = PageWrapper;
|
||||||
@@ -3,7 +3,6 @@ import classNames from "classnames";
|
|||||||
import type { GetServerSidePropsContext } from "next";
|
import type { GetServerSidePropsContext } from "next";
|
||||||
import Link from "next/link";
|
import Link from "next/link";
|
||||||
import { useRouter } from "next/router";
|
import { useRouter } from "next/router";
|
||||||
import { useEffect } from "react";
|
|
||||||
import { Toaster } from "react-hot-toast";
|
import { Toaster } from "react-hot-toast";
|
||||||
|
|
||||||
import {
|
import {
|
||||||
@@ -24,7 +23,6 @@ import defaultEvents, {
|
|||||||
import { useLocale } from "@calcom/lib/hooks/useLocale";
|
import { useLocale } from "@calcom/lib/hooks/useLocale";
|
||||||
import useTheme from "@calcom/lib/hooks/useTheme";
|
import useTheme from "@calcom/lib/hooks/useTheme";
|
||||||
import { markdownToSafeHTML } from "@calcom/lib/markdownToSafeHTML";
|
import { markdownToSafeHTML } from "@calcom/lib/markdownToSafeHTML";
|
||||||
import { collectPageParameters, telemetryEventTypes, useTelemetry } from "@calcom/lib/telemetry";
|
|
||||||
import prisma from "@calcom/prisma";
|
import prisma from "@calcom/prisma";
|
||||||
import { baseEventTypeSelect } from "@calcom/prisma/selects";
|
import { baseEventTypeSelect } from "@calcom/prisma/selects";
|
||||||
import { EventTypeMetaDataSchema } from "@calcom/prisma/zod-utils";
|
import { EventTypeMetaDataSchema } from "@calcom/prisma/zod-utils";
|
||||||
@@ -34,6 +32,8 @@ import { ArrowRight } from "@calcom/ui/components/icon";
|
|||||||
import type { inferSSRProps } from "@lib/types/inferSSRProps";
|
import type { inferSSRProps } from "@lib/types/inferSSRProps";
|
||||||
import type { EmbedProps } from "@lib/withEmbedSsr";
|
import type { EmbedProps } from "@lib/withEmbedSsr";
|
||||||
|
|
||||||
|
import PageWrapper from "@components/PageWrapper";
|
||||||
|
|
||||||
import { ssrInit } from "@server/lib/ssr";
|
import { ssrInit } from "@server/lib/ssr";
|
||||||
|
|
||||||
export default function User(props: inferSSRProps<typeof getServerSideProps> & EmbedProps) {
|
export default function User(props: inferSSRProps<typeof getServerSideProps> & EmbedProps) {
|
||||||
@@ -93,14 +93,15 @@ export default function User(props: inferSSRProps<typeof getServerSideProps> & E
|
|||||||
const query = { ...router.query };
|
const query = { ...router.query };
|
||||||
delete query.user; // So it doesn't display in the Link (and make tests fail)
|
delete query.user; // So it doesn't display in the Link (and make tests fail)
|
||||||
const nameOrUsername = user.name || user.username || "";
|
const nameOrUsername = user.name || user.username || "";
|
||||||
const telemetry = useTelemetry();
|
|
||||||
|
|
||||||
useEffect(() => {
|
/*
|
||||||
|
const telemetry = useTelemetry();
|
||||||
|
useEffect(() => {
|
||||||
if (top !== window) {
|
if (top !== window) {
|
||||||
//page_view will be collected automatically by _middleware.ts
|
//page_view will be collected automatically by _middleware.ts
|
||||||
telemetry.event(telemetryEventTypes.embedView, collectPageParameters("/[user]"));
|
telemetry.event(telemetryEventTypes.embedView, collectPageParameters("/[user]"));
|
||||||
}
|
}
|
||||||
}, [telemetry, router.asPath]);
|
}, [telemetry, router.asPath]); */
|
||||||
const isEventListEmpty = eventTypes.length === 0;
|
const isEventListEmpty = eventTypes.length === 0;
|
||||||
return (
|
return (
|
||||||
<>
|
<>
|
||||||
@@ -122,7 +123,7 @@ export default function User(props: inferSSRProps<typeof getServerSideProps> & E
|
|||||||
<main
|
<main
|
||||||
className={classNames(
|
className={classNames(
|
||||||
shouldAlignCentrally ? "mx-auto" : "",
|
shouldAlignCentrally ? "mx-auto" : "",
|
||||||
isEmbed ? " border-bookinglightest bg-default rounded-md border" : "",
|
isEmbed ? " border-booker border-booker-width bg-default rounded-md border" : "",
|
||||||
"max-w-3xl py-24 px-4"
|
"max-w-3xl py-24 px-4"
|
||||||
)}>
|
)}>
|
||||||
{isSingleUser && ( // When we deal with a single user, not dynamic group
|
{isSingleUser && ( // When we deal with a single user, not dynamic group
|
||||||
@@ -196,6 +197,9 @@ export default function User(props: inferSSRProps<typeof getServerSideProps> & E
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
User.isBookingPage = true;
|
||||||
|
User.PageWrapper = PageWrapper;
|
||||||
|
|
||||||
const getEventTypesWithHiddenFromDB = async (userId: number) => {
|
const getEventTypesWithHiddenFromDB = async (userId: number) => {
|
||||||
return (
|
return (
|
||||||
await prisma.eventType.findMany({
|
await prisma.eventType.findMany({
|
||||||
|
|||||||
@@ -12,6 +12,7 @@ import { isBrandingHidden } from "@lib/isBrandingHidden";
|
|||||||
import type { inferSSRProps } from "@lib/types/inferSSRProps";
|
import type { inferSSRProps } from "@lib/types/inferSSRProps";
|
||||||
import type { EmbedProps } from "@lib/withEmbedSsr";
|
import type { EmbedProps } from "@lib/withEmbedSsr";
|
||||||
|
|
||||||
|
import PageWrapper from "@components/PageWrapper";
|
||||||
import AvailabilityPage from "@components/booking/pages/AvailabilityPage";
|
import AvailabilityPage from "@components/booking/pages/AvailabilityPage";
|
||||||
|
|
||||||
export type AvailabilityPageProps = inferSSRProps<typeof getStaticProps> & EmbedProps;
|
export type AvailabilityPageProps = inferSSRProps<typeof getStaticProps> & EmbedProps;
|
||||||
@@ -54,6 +55,9 @@ export default function Type(props: AvailabilityPageProps) {
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
Type.isBookingPage = true;
|
||||||
|
Type.PageWrapper = PageWrapper;
|
||||||
|
|
||||||
const paramsSchema = z.object({ type: z.string(), user: z.string() });
|
const paramsSchema = z.object({ type: z.string(), user: z.string() });
|
||||||
async function getUserPageProps(context: GetStaticPropsContext) {
|
async function getUserPageProps(context: GetStaticPropsContext) {
|
||||||
// load server side dependencies
|
// load server side dependencies
|
||||||
|
|||||||
@@ -26,6 +26,7 @@ import {
|
|||||||
|
|
||||||
import type { inferSSRProps } from "@lib/types/inferSSRProps";
|
import type { inferSSRProps } from "@lib/types/inferSSRProps";
|
||||||
|
|
||||||
|
import PageWrapper from "@components/PageWrapper";
|
||||||
import BookingPage from "@components/booking/pages/BookingPage";
|
import BookingPage from "@components/booking/pages/BookingPage";
|
||||||
|
|
||||||
import { ssrInit } from "@server/lib/ssr";
|
import { ssrInit } from "@server/lib/ssr";
|
||||||
@@ -69,6 +70,9 @@ export default function Book(props: BookPageProps) {
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
Book.isBookingPage = true;
|
||||||
|
Book.PageWrapper = PageWrapper;
|
||||||
|
|
||||||
const querySchema = z.object({
|
const querySchema = z.object({
|
||||||
bookingUid: z.string().optional(),
|
bookingUid: z.string().optional(),
|
||||||
count: z.coerce.number().optional(),
|
count: z.coerce.number().optional(),
|
||||||
|
|||||||
@@ -18,7 +18,7 @@ export const getStaticProps: GetStaticProps<
|
|||||||
{ user: string }
|
{ user: string }
|
||||||
> = async (context) => {
|
> = async (context) => {
|
||||||
const { user: username, month } = paramsSchema.parse(context.params);
|
const { user: username, month } = paramsSchema.parse(context.params);
|
||||||
const user = await prisma.user.findUnique({
|
const userWithCredentials = await prisma.user.findUnique({
|
||||||
where: {
|
where: {
|
||||||
username,
|
username,
|
||||||
},
|
},
|
||||||
@@ -34,14 +34,15 @@ export const getStaticProps: GetStaticProps<
|
|||||||
).startOf("day");
|
).startOf("day");
|
||||||
const endDate = startDate.endOf("month");
|
const endDate = startDate.endOf("month");
|
||||||
try {
|
try {
|
||||||
const results = user?.credentials
|
const results = userWithCredentials?.credentials
|
||||||
? await getCachedResults(
|
? await getCachedResults(
|
||||||
user?.credentials,
|
userWithCredentials?.credentials,
|
||||||
startDate.format(),
|
startDate.format(),
|
||||||
endDate.format(),
|
endDate.format(),
|
||||||
user?.selectedCalendars
|
userWithCredentials?.selectedCalendars
|
||||||
)
|
)
|
||||||
: [];
|
: [];
|
||||||
|
|
||||||
return {
|
return {
|
||||||
props: { results, date: new Date().toISOString() },
|
props: { results, date: new Date().toISOString() },
|
||||||
revalidate: 1,
|
revalidate: 1,
|
||||||
|
|||||||
+3
-72
@@ -1,82 +1,13 @@
|
|||||||
import { DefaultSeo } from "next-seo";
|
|
||||||
import { Inter } from "next/font/google";
|
|
||||||
import localFont from "next/font/local";
|
|
||||||
import Head from "next/head";
|
|
||||||
import Script from "next/script";
|
|
||||||
|
|
||||||
import "@calcom/embed-core/src/embed-iframe";
|
|
||||||
import LicenseRequired from "@calcom/features/ee/common/components/LicenseRequired";
|
|
||||||
import { trpc } from "@calcom/trpc/react";
|
import { trpc } from "@calcom/trpc/react";
|
||||||
|
|
||||||
import type { AppProps } from "@lib/app-providers";
|
import type { AppProps } from "@lib/app-providers";
|
||||||
import AppProviders from "@lib/app-providers";
|
|
||||||
import { seoConfig } from "@lib/config/next-seo.config";
|
|
||||||
|
|
||||||
import I18nLanguageHandler from "@components/I18nLanguageHandler";
|
|
||||||
|
|
||||||
import "../styles/globals.css";
|
import "../styles/globals.css";
|
||||||
|
|
||||||
const interFont = Inter({ subsets: ["latin"], variable: "--font-inter", preload: true, display: "swap" });
|
|
||||||
const calFont = localFont({
|
|
||||||
src: "../fonts/CalSans-SemiBold.woff2",
|
|
||||||
variable: "--font-cal",
|
|
||||||
preload: true,
|
|
||||||
display: "swap",
|
|
||||||
});
|
|
||||||
|
|
||||||
function MyApp(props: AppProps) {
|
function MyApp(props: AppProps) {
|
||||||
const { Component, pageProps, err, router } = props;
|
const { Component, pageProps } = props;
|
||||||
let pageStatus = "200";
|
if (Component.PageWrapper !== undefined) return Component.PageWrapper(props);
|
||||||
if (router.pathname === "/404") {
|
return <Component {...pageProps} />;
|
||||||
pageStatus = "404";
|
|
||||||
} else if (router.pathname === "/500") {
|
|
||||||
pageStatus = "500";
|
|
||||||
}
|
|
||||||
|
|
||||||
// On client side don't let nonce creep into DOM
|
|
||||||
// It also avoids hydration warning that says that Client has the nonce value but server has "" because browser removes nonce attributes before DOM is built
|
|
||||||
// See https://github.com/kentcdodds/nonce-hydration-issues
|
|
||||||
// Set "" only if server had it set otherwise keep it undefined because server has to match with client to avoid hydration error
|
|
||||||
const nonce = typeof window !== "undefined" ? (pageProps.nonce ? "" : undefined) : pageProps.nonce;
|
|
||||||
const providerProps = {
|
|
||||||
...props,
|
|
||||||
pageProps: {
|
|
||||||
...props.pageProps,
|
|
||||||
nonce,
|
|
||||||
},
|
|
||||||
};
|
|
||||||
// Use the layout defined at the page level, if available
|
|
||||||
const getLayout = Component.getLayout ?? ((page) => page);
|
|
||||||
return (
|
|
||||||
<AppProviders {...providerProps}>
|
|
||||||
<DefaultSeo {...seoConfig.defaultNextSeo} />
|
|
||||||
<I18nLanguageHandler />
|
|
||||||
<Script
|
|
||||||
nonce={nonce}
|
|
||||||
id="page-status"
|
|
||||||
dangerouslySetInnerHTML={{ __html: `window.CalComPageStatus = '${pageStatus}'` }}
|
|
||||||
/>
|
|
||||||
<style jsx global>{`
|
|
||||||
:root {
|
|
||||||
--font-inter: ${interFont.style.fontFamily};
|
|
||||||
--font-cal: ${calFont.style.fontFamily};
|
|
||||||
}
|
|
||||||
`}</style>
|
|
||||||
<Head>
|
|
||||||
<meta name="viewport" content="width=device-width, initial-scale=1, maximum-scale=1" />
|
|
||||||
</Head>
|
|
||||||
{getLayout(
|
|
||||||
Component.requiresLicense ? (
|
|
||||||
<LicenseRequired>
|
|
||||||
<Component {...pageProps} err={err} />
|
|
||||||
</LicenseRequired>
|
|
||||||
) : (
|
|
||||||
<Component {...pageProps} err={err} />
|
|
||||||
),
|
|
||||||
router
|
|
||||||
)}
|
|
||||||
</AppProviders>
|
|
||||||
);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
export default trpc.withTRPC(MyApp);
|
export default trpc.withTRPC(MyApp);
|
||||||
@@ -1,6 +1,6 @@
|
|||||||
|
import type { NextPageContext } from "next";
|
||||||
import type { DocumentContext, DocumentProps } from "next/document";
|
import type { DocumentContext, DocumentProps } from "next/document";
|
||||||
import Document, { Head, Html, Main, NextScript } from "next/document";
|
import Document, { Head, Html, Main, NextScript } from "next/document";
|
||||||
import Script from "next/script";
|
|
||||||
import { z } from "zod";
|
import { z } from "zod";
|
||||||
|
|
||||||
import { getDirFromLang } from "@calcom/lib/i18n";
|
import { getDirFromLang } from "@calcom/lib/i18n";
|
||||||
@@ -8,15 +8,22 @@ import { getDirFromLang } from "@calcom/lib/i18n";
|
|||||||
import { csp } from "@lib/csp";
|
import { csp } from "@lib/csp";
|
||||||
|
|
||||||
type Props = Record<string, unknown> & DocumentProps;
|
type Props = Record<string, unknown> & DocumentProps;
|
||||||
|
function setHeader(ctx: NextPageContext, name: string, value: string) {
|
||||||
|
try {
|
||||||
|
ctx.res?.setHeader(name, value);
|
||||||
|
} catch (e) {
|
||||||
|
// Getting "Error [ERR_HTTP_HEADERS_SENT]: Cannot set headers after they are sent to the client" when revalidate calendar chache
|
||||||
|
console.log(`Error setting header ${name}=${value} for ${ctx.asPath || "unknown asPath"}`, e);
|
||||||
|
}
|
||||||
|
}
|
||||||
class MyDocument extends Document<Props> {
|
class MyDocument extends Document<Props> {
|
||||||
static async getInitialProps(ctx: DocumentContext) {
|
static async getInitialProps(ctx: DocumentContext) {
|
||||||
const { nonce } = csp(ctx.req || null, ctx.res || null);
|
const { nonce } = csp(ctx.req || null, ctx.res || null);
|
||||||
if (!process.env.CSP_POLICY) {
|
if (!process.env.CSP_POLICY) {
|
||||||
ctx.res?.setHeader("x-csp", "not-opted-in");
|
setHeader(ctx, "x-csp", "not-opted-in");
|
||||||
} else if (!ctx.res?.getHeader("x-csp")) {
|
} else if (!ctx.res?.getHeader("x-csp")) {
|
||||||
// If x-csp not set by gSSP, then it's initialPropsOnly
|
// If x-csp not set by gSSP, then it's initialPropsOnly
|
||||||
ctx.res?.setHeader("x-csp", "initialPropsOnly");
|
setHeader(ctx, "x-csp", "initialPropsOnly");
|
||||||
}
|
}
|
||||||
const asPath = ctx.asPath || "";
|
const asPath = ctx.asPath || "";
|
||||||
// Use a dummy URL as default so that URL parsing works for relative URLs as well. We care about searchParams and pathname only
|
// Use a dummy URL as default so that URL parsing works for relative URLs as well. We care about searchParams and pathname only
|
||||||
@@ -42,7 +49,6 @@ class MyDocument extends Document<Props> {
|
|||||||
<link rel="mask-icon" href="/safari-pinned-tab.svg" color="#000000" />
|
<link rel="mask-icon" href="/safari-pinned-tab.svg" color="#000000" />
|
||||||
<meta name="msapplication-TileColor" content="#ff0000" />
|
<meta name="msapplication-TileColor" content="#ff0000" />
|
||||||
<meta name="theme-color" content="var(--cal-bg)" />
|
<meta name="theme-color" content="var(--cal-bg)" />
|
||||||
<Script src="/embed-init-iframe.js" strategy="beforeInteractive" />
|
|
||||||
</Head>
|
</Head>
|
||||||
|
|
||||||
<body
|
<body
|
||||||
|
|||||||
@@ -2,7 +2,6 @@
|
|||||||
* Typescript class based component for custom-error
|
* Typescript class based component for custom-error
|
||||||
* @link https://nextjs.org/docs/advanced-features/custom-error-page
|
* @link https://nextjs.org/docs/advanced-features/custom-error-page
|
||||||
*/
|
*/
|
||||||
import * as Sentry from "@sentry/nextjs";
|
|
||||||
import type { NextPage, NextPageContext } from "next";
|
import type { NextPage, NextPageContext } from "next";
|
||||||
import type { ErrorProps } from "next/error";
|
import type { ErrorProps } from "next/error";
|
||||||
import NextError from "next/error";
|
import NextError from "next/error";
|
||||||
@@ -50,7 +49,6 @@ const CustomError: NextPage<CustomErrorProps> = (props) => {
|
|||||||
*/
|
*/
|
||||||
CustomError.getInitialProps = async (ctx: AugmentedNextPageContext) => {
|
CustomError.getInitialProps = async (ctx: AugmentedNextPageContext) => {
|
||||||
const { res, err, asPath } = ctx;
|
const { res, err, asPath } = ctx;
|
||||||
await Sentry.captureUnderscoreErrorException(ctx);
|
|
||||||
const errorInitialProps = (await NextError.getInitialProps({
|
const errorInitialProps = (await NextError.getInitialProps({
|
||||||
res,
|
res,
|
||||||
err,
|
err,
|
||||||
|
|||||||
@@ -1,29 +0,0 @@
|
|||||||
import type { NextApiRequest } from "next";
|
|
||||||
import { z } from "zod";
|
|
||||||
|
|
||||||
import { getUserAvailability } from "@calcom/core/getUserAvailability";
|
|
||||||
import { defaultResponder } from "@calcom/lib/server";
|
|
||||||
import { stringOrNumber } from "@calcom/prisma/zod-utils";
|
|
||||||
|
|
||||||
const availabilitySchema = z.object({
|
|
||||||
user: z.string(),
|
|
||||||
dateFrom: z.string(),
|
|
||||||
dateTo: z.string(),
|
|
||||||
eventTypeId: stringOrNumber.optional(),
|
|
||||||
});
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @deprecated Use TRCP's viewer.availability.user
|
|
||||||
*/
|
|
||||||
|
|
||||||
async function handler(req: NextApiRequest) {
|
|
||||||
const { user: username, eventTypeId, dateTo, dateFrom } = availabilitySchema.parse(req.query);
|
|
||||||
return getUserAvailability({
|
|
||||||
username,
|
|
||||||
dateFrom,
|
|
||||||
dateTo,
|
|
||||||
eventTypeId,
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
export default defaultResponder(handler);
|
|
||||||
@@ -14,7 +14,7 @@ export default async function handler(req: NextApiRequest, res: NextApiResponse)
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
const user = await prisma.user.findUnique({
|
const userWithCredentials = await prisma.user.findUnique({
|
||||||
where: {
|
where: {
|
||||||
id: session.user.id,
|
id: session.user.id,
|
||||||
},
|
},
|
||||||
@@ -25,11 +25,11 @@ export default async function handler(req: NextApiRequest, res: NextApiResponse)
|
|||||||
selectedCalendars: true,
|
selectedCalendars: true,
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
|
if (!userWithCredentials) {
|
||||||
if (!user) {
|
|
||||||
res.status(401).json({ message: "Not authenticated" });
|
res.status(401).json({ message: "Not authenticated" });
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
const { credentials, ...user } = userWithCredentials;
|
||||||
|
|
||||||
if (req.method === "POST") {
|
if (req.method === "POST") {
|
||||||
await prisma.selectedCalendar.upsert({
|
await prisma.selectedCalendar.upsert({
|
||||||
@@ -80,7 +80,7 @@ export default async function handler(req: NextApiRequest, res: NextApiResponse)
|
|||||||
});
|
});
|
||||||
|
|
||||||
// get user's credentials + their connected integrations
|
// get user's credentials + their connected integrations
|
||||||
const calendarCredentials = getCalendarCredentials(user.credentials);
|
const calendarCredentials = getCalendarCredentials(credentials);
|
||||||
// get all the connected integrations' calendars (from third party)
|
// get all the connected integrations' calendars (from third party)
|
||||||
const { connectedCalendars } = await getConnectedCalendars(calendarCredentials, user.selectedCalendars);
|
const { connectedCalendars } = await getConnectedCalendars(calendarCredentials, user.selectedCalendars);
|
||||||
const calendars = connectedCalendars.flatMap((c) => c.calendars).filter(notEmpty);
|
const calendars = connectedCalendars.flatMap((c) => c.calendars).filter(notEmpty);
|
||||||
|
|||||||
@@ -0,0 +1,28 @@
|
|||||||
|
import type { NextApiRequest, NextApiResponse } from "next";
|
||||||
|
import crypto from "node:crypto";
|
||||||
|
|
||||||
|
import { getServerSession } from "@calcom/features/auth/lib/getServerSession";
|
||||||
|
import { defaultHandler } from "@calcom/lib/server";
|
||||||
|
|
||||||
|
async function handler(req: NextApiRequest, res: NextApiResponse) {
|
||||||
|
const session = await getServerSession({ req, res });
|
||||||
|
const secret = process.env.INTERCOM_SECRET;
|
||||||
|
|
||||||
|
if (!session) {
|
||||||
|
return res.status(401).json({ message: "user not authenticated" });
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!secret) {
|
||||||
|
return res.status(400).json({ message: "Intercom Identity Verification secret not set" });
|
||||||
|
}
|
||||||
|
|
||||||
|
const hmac = crypto.createHmac("sha256", secret);
|
||||||
|
hmac.update(String(session.user.id));
|
||||||
|
const hash = hmac.digest("hex");
|
||||||
|
|
||||||
|
return res.status(200).json({ hash });
|
||||||
|
}
|
||||||
|
|
||||||
|
export default defaultHandler({
|
||||||
|
GET: Promise.resolve({ default: handler }),
|
||||||
|
});
|
||||||
@@ -0,0 +1,80 @@
|
|||||||
|
import type { NextApiRequest, NextApiResponse } from "next";
|
||||||
|
import { z } from "zod";
|
||||||
|
|
||||||
|
import { IS_SELF_HOSTED, LOGO, LOGO_ICON, WEBAPP_URL } from "@calcom/lib/constants";
|
||||||
|
|
||||||
|
function removePort(url: string) {
|
||||||
|
return url.replace(/:\d+$/, "");
|
||||||
|
}
|
||||||
|
|
||||||
|
function extractSubdomainAndDomain(hostname: string) {
|
||||||
|
const hostParts = removePort(hostname).split(".");
|
||||||
|
|
||||||
|
const subdomainParts = hostParts.slice(0, hostParts.length - 2);
|
||||||
|
const domain = hostParts.slice(hostParts.length - 2).join(".");
|
||||||
|
|
||||||
|
return [subdomainParts[0], domain];
|
||||||
|
}
|
||||||
|
|
||||||
|
const logoApiSchema = z.object({
|
||||||
|
icon: z.coerce.boolean().optional(),
|
||||||
|
});
|
||||||
|
|
||||||
|
const SYSTEM_SUBDOMAINS = ["console", "app", "www"];
|
||||||
|
|
||||||
|
async function getTeamLogos(subdomain: string) {
|
||||||
|
if (
|
||||||
|
// if not cal.com
|
||||||
|
IS_SELF_HOSTED ||
|
||||||
|
// missing subdomain (empty string)
|
||||||
|
!subdomain ||
|
||||||
|
// in SYSTEM_SUBDOMAINS list
|
||||||
|
SYSTEM_SUBDOMAINS.includes(subdomain)
|
||||||
|
) {
|
||||||
|
return {
|
||||||
|
appLogo: `${WEBAPP_URL}${LOGO}`,
|
||||||
|
appIconLogo: `${WEBAPP_URL}${LOGO_ICON}`,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
// load from DB
|
||||||
|
const { default: prisma } = await import("@calcom/prisma");
|
||||||
|
const team = await prisma.team.findUniqueOrThrow({
|
||||||
|
where: {
|
||||||
|
slug: subdomain,
|
||||||
|
},
|
||||||
|
select: {
|
||||||
|
appLogo: true,
|
||||||
|
appIconLogo: true,
|
||||||
|
},
|
||||||
|
});
|
||||||
|
// try to use team logos, otherwise default to LOGO/LOGO_ICON regardless
|
||||||
|
return {
|
||||||
|
appLogo: team.appLogo || `${WEBAPP_URL}${LOGO}`,
|
||||||
|
appIconLogo: team.appIconLogo || `${WEBAPP_URL}${LOGO_ICON}`,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* This API endpoint is used to serve the logo associated with a team if no logo is found we serve our default logo
|
||||||
|
*/
|
||||||
|
export default async function handler(req: NextApiRequest, res: NextApiResponse) {
|
||||||
|
const { query } = req;
|
||||||
|
const parsedQuery = logoApiSchema.parse(query);
|
||||||
|
|
||||||
|
const hostname = req?.headers["host"];
|
||||||
|
if (!hostname) throw new Error("No hostname");
|
||||||
|
const domains = extractSubdomainAndDomain(hostname);
|
||||||
|
if (!domains) throw new Error("No domains");
|
||||||
|
|
||||||
|
const [subdomain] = domains;
|
||||||
|
const { appLogo, appIconLogo } = await getTeamLogos(subdomain);
|
||||||
|
|
||||||
|
const filteredLogo = parsedQuery?.icon ? appIconLogo : appLogo;
|
||||||
|
|
||||||
|
const response = await fetch(filteredLogo);
|
||||||
|
const arrayBuffer = await response.arrayBuffer();
|
||||||
|
const buffer = Buffer.from(arrayBuffer);
|
||||||
|
res.setHeader("Content-Type", response.headers.get("content-type") as string);
|
||||||
|
res.setHeader("Cache-Control", "s-maxage=86400");
|
||||||
|
res.send(buffer);
|
||||||
|
}
|
||||||
@@ -0,0 +1,146 @@
|
|||||||
|
import type { NextApiRequest, NextApiResponse } from "next";
|
||||||
|
import { z } from "zod";
|
||||||
|
|
||||||
|
import { DailyLocationType } from "@calcom/app-store/locations";
|
||||||
|
import { getDownloadLinkOfCalVideoByRecordingId } from "@calcom/core/videoClient";
|
||||||
|
import { sendDailyVideoRecordingEmails } from "@calcom/emails";
|
||||||
|
import { getServerSession } from "@calcom/features/auth/lib/getServerSession";
|
||||||
|
import { IS_SELF_HOSTED } from "@calcom/lib/constants";
|
||||||
|
import { defaultHandler } from "@calcom/lib/server";
|
||||||
|
import { getTranslation } from "@calcom/lib/server/i18n";
|
||||||
|
import prisma, { bookingMinimalSelect } from "@calcom/prisma";
|
||||||
|
import type { CalendarEvent } from "@calcom/types/Calendar";
|
||||||
|
|
||||||
|
const schema = z.object({
|
||||||
|
recordingId: z.string(),
|
||||||
|
bookingUID: z.string(),
|
||||||
|
});
|
||||||
|
|
||||||
|
const downloadLinkSchema = z.object({
|
||||||
|
download_link: z.string(),
|
||||||
|
});
|
||||||
|
|
||||||
|
async function handler(req: NextApiRequest, res: NextApiResponse) {
|
||||||
|
if (!process.env.SENDGRID_API_KEY || !process.env.SENDGRID_EMAIL) {
|
||||||
|
return res.status(405).json({ message: "No SendGrid API key or email" });
|
||||||
|
}
|
||||||
|
const response = schema.safeParse(JSON.parse(req.body));
|
||||||
|
|
||||||
|
if (!response.success) {
|
||||||
|
return res.status(400).send({
|
||||||
|
message: "Invalid Payload",
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
const { recordingId, bookingUID } = response.data;
|
||||||
|
const session = await getServerSession({ req, res });
|
||||||
|
|
||||||
|
if (!session?.user) {
|
||||||
|
return res.status(401).send({
|
||||||
|
message: "User not logged in",
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
try {
|
||||||
|
const booking = await prisma.booking.findFirst({
|
||||||
|
where: {
|
||||||
|
uid: bookingUID,
|
||||||
|
},
|
||||||
|
select: {
|
||||||
|
...bookingMinimalSelect,
|
||||||
|
uid: true,
|
||||||
|
location: true,
|
||||||
|
isRecorded: true,
|
||||||
|
user: {
|
||||||
|
select: {
|
||||||
|
id: true,
|
||||||
|
timeZone: true,
|
||||||
|
email: true,
|
||||||
|
name: true,
|
||||||
|
locale: true,
|
||||||
|
destinationCalendar: true,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
|
if (!booking || booking.location !== DailyLocationType) {
|
||||||
|
return res.status(404).send({
|
||||||
|
message: `Booking of uid ${bookingUID} does not exist or does not contain daily video as location`,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
const t = await getTranslation(booking?.user?.locale ?? "en", "common");
|
||||||
|
const attendeesListPromises = booking.attendees.map(async (attendee) => {
|
||||||
|
return {
|
||||||
|
id: attendee.id,
|
||||||
|
name: attendee.name,
|
||||||
|
email: attendee.email,
|
||||||
|
timeZone: attendee.timeZone,
|
||||||
|
language: {
|
||||||
|
translate: await getTranslation(attendee.locale ?? "en", "common"),
|
||||||
|
locale: attendee.locale ?? "en",
|
||||||
|
},
|
||||||
|
};
|
||||||
|
});
|
||||||
|
|
||||||
|
const attendeesList = await Promise.all(attendeesListPromises);
|
||||||
|
|
||||||
|
const isUserAttendeeOrOrganiser =
|
||||||
|
booking?.user?.id === session.user.id ||
|
||||||
|
attendeesList.find((attendee) => attendee.id === session.user.id);
|
||||||
|
|
||||||
|
if (!isUserAttendeeOrOrganiser) {
|
||||||
|
return res.status(403).send({
|
||||||
|
message: "Unauthorised",
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
await prisma.booking.update({
|
||||||
|
where: {
|
||||||
|
uid: booking.uid,
|
||||||
|
},
|
||||||
|
data: {
|
||||||
|
isRecorded: true,
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
|
const isSendingEmailsAllowed = IS_SELF_HOSTED || session?.user?.belongsToActiveTeam;
|
||||||
|
|
||||||
|
// send emails to all attendees only when user has team plan
|
||||||
|
if (isSendingEmailsAllowed) {
|
||||||
|
const response = await getDownloadLinkOfCalVideoByRecordingId(recordingId);
|
||||||
|
|
||||||
|
const downloadLinkResponse = downloadLinkSchema.parse(response);
|
||||||
|
const downloadLink = downloadLinkResponse.download_link;
|
||||||
|
|
||||||
|
const evt: CalendarEvent = {
|
||||||
|
type: booking.title,
|
||||||
|
title: booking.title,
|
||||||
|
description: booking.description || undefined,
|
||||||
|
startTime: booking.startTime.toISOString(),
|
||||||
|
endTime: booking.endTime.toISOString(),
|
||||||
|
organizer: {
|
||||||
|
email: booking.user?.email || "Email-less",
|
||||||
|
name: booking.user?.name || "Nameless",
|
||||||
|
timeZone: booking.user?.timeZone || "Europe/London",
|
||||||
|
language: { translate: t, locale: booking?.user?.locale ?? "en" },
|
||||||
|
},
|
||||||
|
attendees: attendeesList,
|
||||||
|
uid: booking.uid,
|
||||||
|
};
|
||||||
|
|
||||||
|
await sendDailyVideoRecordingEmails(evt, downloadLink);
|
||||||
|
return res.status(200).json({ message: "Success" });
|
||||||
|
}
|
||||||
|
|
||||||
|
return res.status(403).json({ message: "User does not have team plan to send out emails" });
|
||||||
|
} catch (err) {
|
||||||
|
console.warn("something_went_wrong", err);
|
||||||
|
return res.status(500).json({ message: "something went wrong" });
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export default defaultHandler({
|
||||||
|
POST: Promise.resolve({ default: handler }),
|
||||||
|
});
|
||||||
@@ -9,13 +9,16 @@ import type { AppGetServerSideProps } from "@calcom/types/AppGetServerSideProps"
|
|||||||
|
|
||||||
import type { AppProps } from "@lib/app-providers";
|
import type { AppProps } from "@lib/app-providers";
|
||||||
|
|
||||||
|
import PageWrapper from "@components/PageWrapper";
|
||||||
|
|
||||||
import { ssrInit } from "@server/lib/ssr";
|
import { ssrInit } from "@server/lib/ssr";
|
||||||
|
|
||||||
type AppPageType = {
|
type AppPageType = {
|
||||||
getServerSideProps: AppGetServerSideProps;
|
getServerSideProps: AppGetServerSideProps;
|
||||||
// A component than can accept any properties
|
// A component than can accept any properties
|
||||||
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
||||||
default: ((props: any) => JSX.Element) & Pick<AppProps["Component"], "isThemeSupported" | "getLayout">;
|
default: ((props: any) => JSX.Element) &
|
||||||
|
Pick<AppProps["Component"], "isBookingPage" | "getLayout" | "PageWrapper">;
|
||||||
};
|
};
|
||||||
|
|
||||||
type Found = {
|
type Found = {
|
||||||
@@ -70,17 +73,17 @@ const AppPage: AppPageType["default"] = function AppPage(props) {
|
|||||||
return <route.Component {...componentProps} />;
|
return <route.Component {...componentProps} />;
|
||||||
};
|
};
|
||||||
|
|
||||||
AppPage.isThemeSupported = ({ router }) => {
|
AppPage.isBookingPage = ({ router }) => {
|
||||||
const route = getRoute(router.query.slug as string, router.query.pages as string[]);
|
const route = getRoute(router.query.slug as string, router.query.pages as string[]);
|
||||||
if (route.notFound) {
|
if (route.notFound) {
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
const isThemeSupported = route.Component.isThemeSupported;
|
const isBookingPage = route.Component.isBookingPage;
|
||||||
if (typeof isThemeSupported === "function") {
|
if (typeof isBookingPage === "function") {
|
||||||
return isThemeSupported({ router });
|
return isBookingPage({ router });
|
||||||
}
|
}
|
||||||
|
|
||||||
return !!isThemeSupported;
|
return !!isBookingPage;
|
||||||
};
|
};
|
||||||
|
|
||||||
AppPage.getLayout = (page, router) => {
|
AppPage.getLayout = (page, router) => {
|
||||||
@@ -94,6 +97,8 @@ AppPage.getLayout = (page, router) => {
|
|||||||
return route.Component.getLayout(page, router);
|
return route.Component.getLayout(page, router);
|
||||||
};
|
};
|
||||||
|
|
||||||
|
AppPage.PageWrapper = PageWrapper;
|
||||||
|
|
||||||
export default AppPage;
|
export default AppPage;
|
||||||
|
|
||||||
export async function getServerSideProps(
|
export async function getServerSideProps(
|
||||||
|
|||||||
@@ -10,6 +10,7 @@ import prisma from "@calcom/prisma";
|
|||||||
|
|
||||||
import type { inferSSRProps } from "@lib/types/inferSSRProps";
|
import type { inferSSRProps } from "@lib/types/inferSSRProps";
|
||||||
|
|
||||||
|
import PageWrapper from "@components/PageWrapper";
|
||||||
import App from "@components/apps/App";
|
import App from "@components/apps/App";
|
||||||
|
|
||||||
const md = new MarkdownIt("default", { html: true, breaks: true });
|
const md = new MarkdownIt("default", { html: true, breaks: true });
|
||||||
@@ -122,4 +123,6 @@ export const getStaticProps = async (ctx: GetStaticPropsContext) => {
|
|||||||
};
|
};
|
||||||
};
|
};
|
||||||
|
|
||||||
|
SingleAppPage.PageWrapper = PageWrapper;
|
||||||
|
|
||||||
export default SingleAppPage;
|
export default SingleAppPage;
|
||||||
@@ -5,6 +5,8 @@ import { useRouter } from "next/router";
|
|||||||
import { AppSetupPage } from "@calcom/app-store/_pages/setup";
|
import { AppSetupPage } from "@calcom/app-store/_pages/setup";
|
||||||
import { getStaticProps } from "@calcom/app-store/_pages/setup/_getStaticProps";
|
import { getStaticProps } from "@calcom/app-store/_pages/setup/_getStaticProps";
|
||||||
|
|
||||||
|
import PageWrapper from "@components/PageWrapper";
|
||||||
|
|
||||||
export default function SetupInformation(props: InferGetStaticPropsType<typeof getStaticProps>) {
|
export default function SetupInformation(props: InferGetStaticPropsType<typeof getStaticProps>) {
|
||||||
const router = useRouter();
|
const router = useRouter();
|
||||||
const slug = router.query.slug as string;
|
const slug = router.query.slug as string;
|
||||||
@@ -26,6 +28,8 @@ export default function SetupInformation(props: InferGetStaticPropsType<typeof g
|
|||||||
return <AppSetupPage slug={slug} {...props} />;
|
return <AppSetupPage slug={slug} {...props} />;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
SetupInformation.PageWrapper = PageWrapper;
|
||||||
|
|
||||||
export const getStaticPaths: GetStaticPaths = async () => {
|
export const getStaticPaths: GetStaticPaths = async () => {
|
||||||
return {
|
return {
|
||||||
paths: [],
|
paths: [],
|
||||||
|
|||||||
@@ -9,6 +9,8 @@ import { useLocale } from "@calcom/lib/hooks/useLocale";
|
|||||||
import prisma from "@calcom/prisma";
|
import prisma from "@calcom/prisma";
|
||||||
import { AppCard, SkeletonText } from "@calcom/ui";
|
import { AppCard, SkeletonText } from "@calcom/ui";
|
||||||
|
|
||||||
|
import PageWrapper from "@components/PageWrapper";
|
||||||
|
|
||||||
export default function Apps({ apps }: InferGetStaticPropsType<typeof getStaticProps>) {
|
export default function Apps({ apps }: InferGetStaticPropsType<typeof getStaticProps>) {
|
||||||
const { t, isLocaleReady } = useLocale();
|
const { t, isLocaleReady } = useLocale();
|
||||||
const router = useRouter();
|
const router = useRouter();
|
||||||
@@ -47,6 +49,8 @@ export default function Apps({ apps }: InferGetStaticPropsType<typeof getStaticP
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
Apps.PageWrapper = PageWrapper;
|
||||||
|
|
||||||
export const getStaticPaths = async () => {
|
export const getStaticPaths = async () => {
|
||||||
const paths = Object.keys(AppCategories);
|
const paths = Object.keys(AppCategories);
|
||||||
|
|
||||||
|
|||||||
@@ -7,11 +7,13 @@ import { useLocale } from "@calcom/lib/hooks/useLocale";
|
|||||||
import { SkeletonText } from "@calcom/ui";
|
import { SkeletonText } from "@calcom/ui";
|
||||||
import { ArrowLeft, ArrowRight } from "@calcom/ui/components/icon";
|
import { ArrowLeft, ArrowRight } from "@calcom/ui/components/icon";
|
||||||
|
|
||||||
|
import PageWrapper from "@components/PageWrapper";
|
||||||
|
|
||||||
export default function Apps({ categories }: InferGetStaticPropsType<typeof getStaticProps>) {
|
export default function Apps({ categories }: InferGetStaticPropsType<typeof getStaticProps>) {
|
||||||
const { t, isLocaleReady } = useLocale();
|
const { t, isLocaleReady } = useLocale();
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Shell isPublic large>
|
<Shell isPublic large hideHeadingOnMobile>
|
||||||
<div className="text-md flex items-center gap-1 px-4 pb-3 pt-3 font-normal md:px-8 lg:px-0 lg:pt-0">
|
<div className="text-md flex items-center gap-1 px-4 pb-3 pt-3 font-normal md:px-8 lg:px-0 lg:pt-0">
|
||||||
<Link
|
<Link
|
||||||
href="/apps"
|
href="/apps"
|
||||||
@@ -43,6 +45,8 @@ export default function Apps({ categories }: InferGetStaticPropsType<typeof getS
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
Apps.PageWrapper = PageWrapper;
|
||||||
|
|
||||||
export const getStaticProps = async () => {
|
export const getStaticProps = async () => {
|
||||||
const appStore = await getAppRegistry();
|
const appStore = await getAppRegistry();
|
||||||
const categories = appStore.reduce((c, app) => {
|
const categories = appStore.reduce((c, app) => {
|
||||||
|
|||||||
@@ -12,6 +12,7 @@ import type { HorizontalTabItemProps } from "@calcom/ui";
|
|||||||
import { AllApps, AppStoreCategories, HorizontalTabs, TextField, PopularAppsSlider } from "@calcom/ui";
|
import { AllApps, AppStoreCategories, HorizontalTabs, TextField, PopularAppsSlider } from "@calcom/ui";
|
||||||
import { Search } from "@calcom/ui/components/icon";
|
import { Search } from "@calcom/ui/components/icon";
|
||||||
|
|
||||||
|
import PageWrapper from "@components/PageWrapper";
|
||||||
import AppsLayout from "@components/apps/layouts/AppsLayout";
|
import AppsLayout from "@components/apps/layouts/AppsLayout";
|
||||||
|
|
||||||
import { ssrInit } from "@server/lib/ssr";
|
import { ssrInit } from "@server/lib/ssr";
|
||||||
@@ -85,6 +86,8 @@ export default function Apps({ categories, appStore }: inferSSRProps<typeof getS
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
Apps.PageWrapper = PageWrapper;
|
||||||
|
|
||||||
export const getServerSideProps = async (context: GetServerSidePropsContext) => {
|
export const getServerSideProps = async (context: GetServerSidePropsContext) => {
|
||||||
const { req, res } = context;
|
const { req, res } = context;
|
||||||
|
|
||||||
|
|||||||
@@ -44,6 +44,7 @@ import {
|
|||||||
import { QueryCell } from "@lib/QueryCell";
|
import { QueryCell } from "@lib/QueryCell";
|
||||||
|
|
||||||
import AppListCard from "@components/AppListCard";
|
import AppListCard from "@components/AppListCard";
|
||||||
|
import PageWrapper from "@components/PageWrapper";
|
||||||
import { CalendarListContainer } from "@components/apps/CalendarListContainer";
|
import { CalendarListContainer } from "@components/apps/CalendarListContainer";
|
||||||
import InstalledAppsLayout from "@components/apps/layouts/InstalledAppsLayout";
|
import InstalledAppsLayout from "@components/apps/layouts/InstalledAppsLayout";
|
||||||
|
|
||||||
@@ -372,3 +373,5 @@ export async function getServerSideProps(ctx: AppGetServerSidePropsContext) {
|
|||||||
},
|
},
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
InstalledApps.PageWrapper = PageWrapper;
|
||||||
@@ -7,6 +7,7 @@ import { useLocale } from "@calcom/lib/hooks/useLocale";
|
|||||||
import { Button, SkeletonText } from "@calcom/ui";
|
import { Button, SkeletonText } from "@calcom/ui";
|
||||||
import { X } from "@calcom/ui/components/icon";
|
import { X } from "@calcom/ui/components/icon";
|
||||||
|
|
||||||
|
import PageWrapper from "@components/PageWrapper";
|
||||||
import AuthContainer from "@components/ui/AuthContainer";
|
import AuthContainer from "@components/ui/AuthContainer";
|
||||||
|
|
||||||
import { ssgInit } from "@server/lib/ssg";
|
import { ssgInit } from "@server/lib/ssg";
|
||||||
@@ -49,6 +50,8 @@ export default function Error() {
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
Error.PageWrapper = PageWrapper;
|
||||||
|
|
||||||
export const getStaticProps = async (context: GetStaticPropsContext) => {
|
export const getStaticProps = async (context: GetStaticPropsContext) => {
|
||||||
const ssr = await ssgInit(context);
|
const ssr = await ssgInit(context);
|
||||||
|
|
||||||
|
|||||||
@@ -12,6 +12,7 @@ import { useLocale } from "@calcom/lib/hooks/useLocale";
|
|||||||
import prisma from "@calcom/prisma";
|
import prisma from "@calcom/prisma";
|
||||||
import { Button, TextField } from "@calcom/ui";
|
import { Button, TextField } from "@calcom/ui";
|
||||||
|
|
||||||
|
import PageWrapper from "@components/PageWrapper";
|
||||||
import AuthContainer from "@components/ui/AuthContainer";
|
import AuthContainer from "@components/ui/AuthContainer";
|
||||||
|
|
||||||
type Props = {
|
type Props = {
|
||||||
@@ -174,7 +175,7 @@ export default function Page({ resetPasswordRequest, csrfToken }: Props) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
Page.isThemeSupported = false;
|
Page.isThemeSupported = false;
|
||||||
|
Page.PageWrapper = PageWrapper;
|
||||||
export async function getServerSideProps(context: GetServerSidePropsContext) {
|
export async function getServerSideProps(context: GetServerSidePropsContext) {
|
||||||
const id = context.params?.id as string;
|
const id = context.params?.id as string;
|
||||||
|
|
||||||
|
|||||||
@@ -11,6 +11,7 @@ import { getServerSession } from "@calcom/features/auth/lib/getServerSession";
|
|||||||
import { useLocale } from "@calcom/lib/hooks/useLocale";
|
import { useLocale } from "@calcom/lib/hooks/useLocale";
|
||||||
import { Button, EmailField } from "@calcom/ui";
|
import { Button, EmailField } from "@calcom/ui";
|
||||||
|
|
||||||
|
import PageWrapper from "@components/PageWrapper";
|
||||||
import AuthContainer from "@components/ui/AuthContainer";
|
import AuthContainer from "@components/ui/AuthContainer";
|
||||||
|
|
||||||
export default function ForgotPassword({ csrfToken }: { csrfToken: string }) {
|
export default function ForgotPassword({ csrfToken }: { csrfToken: string }) {
|
||||||
@@ -144,6 +145,7 @@ export default function ForgotPassword({ csrfToken }: { csrfToken: string }) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
ForgotPassword.isThemeSupported = false;
|
ForgotPassword.isThemeSupported = false;
|
||||||
|
ForgotPassword.PageWrapper = PageWrapper;
|
||||||
|
|
||||||
export const getServerSideProps = async (context: GetServerSidePropsContext) => {
|
export const getServerSideProps = async (context: GetServerSidePropsContext) => {
|
||||||
const { req, res } = context;
|
const { req, res } = context;
|
||||||
|
|||||||
@@ -26,6 +26,7 @@ import type { WithNonceProps } from "@lib/withNonce";
|
|||||||
import withNonce from "@lib/withNonce";
|
import withNonce from "@lib/withNonce";
|
||||||
|
|
||||||
import AddToHomescreen from "@components/AddToHomescreen";
|
import AddToHomescreen from "@components/AddToHomescreen";
|
||||||
|
import PageWrapper from "@components/PageWrapper";
|
||||||
import TwoFactor from "@components/auth/TwoFactor";
|
import TwoFactor from "@components/auth/TwoFactor";
|
||||||
import AuthContainer from "@components/ui/AuthContainer";
|
import AuthContainer from "@components/ui/AuthContainer";
|
||||||
|
|
||||||
@@ -164,14 +165,6 @@ export default function Login({
|
|||||||
{...register("email")}
|
{...register("email")}
|
||||||
/>
|
/>
|
||||||
<div className="relative">
|
<div className="relative">
|
||||||
<div className="absolute -top-[6px] z-10 ltr:right-0 rtl:left-0">
|
|
||||||
<Link
|
|
||||||
href="/auth/forgot-password"
|
|
||||||
tabIndex={-1}
|
|
||||||
className="text-default text-sm font-medium">
|
|
||||||
{t("forgot")}
|
|
||||||
</Link>
|
|
||||||
</div>
|
|
||||||
<PasswordField
|
<PasswordField
|
||||||
id="password"
|
id="password"
|
||||||
autoComplete="off"
|
autoComplete="off"
|
||||||
@@ -179,6 +172,14 @@ export default function Login({
|
|||||||
className="mb-0"
|
className="mb-0"
|
||||||
{...register("password")}
|
{...register("password")}
|
||||||
/>
|
/>
|
||||||
|
<div className="absolute -top-1.5 ltr:right-0 rtl:left-0">
|
||||||
|
<Link
|
||||||
|
href="/auth/forgot-password"
|
||||||
|
tabIndex={-1}
|
||||||
|
className="text-default text-sm font-medium">
|
||||||
|
{t("forgot")}
|
||||||
|
</Link>
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
@@ -302,5 +303,6 @@ const _getServerSideProps = async function getServerSideProps(context: GetServer
|
|||||||
};
|
};
|
||||||
|
|
||||||
Login.isThemeSupported = false;
|
Login.isThemeSupported = false;
|
||||||
|
Login.PageWrapper = PageWrapper;
|
||||||
|
|
||||||
export const getServerSideProps = withNonce(_getServerSideProps);
|
export const getServerSideProps = withNonce(_getServerSideProps);
|
||||||
@@ -10,6 +10,7 @@ import { Check } from "@calcom/ui/components/icon";
|
|||||||
|
|
||||||
import type { inferSSRProps } from "@lib/types/inferSSRProps";
|
import type { inferSSRProps } from "@lib/types/inferSSRProps";
|
||||||
|
|
||||||
|
import PageWrapper from "@components/PageWrapper";
|
||||||
import AuthContainer from "@components/ui/AuthContainer";
|
import AuthContainer from "@components/ui/AuthContainer";
|
||||||
|
|
||||||
import { ssrInit } from "@server/lib/ssr";
|
import { ssrInit } from "@server/lib/ssr";
|
||||||
@@ -51,7 +52,7 @@ export function Logout(props: Props) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
Logout.isThemeSupported = false;
|
Logout.isThemeSupported = false;
|
||||||
|
Logout.PageWrapper = PageWrapper;
|
||||||
export default Logout;
|
export default Logout;
|
||||||
|
|
||||||
export async function getServerSideProps(context: GetServerSidePropsContext) {
|
export async function getServerSideProps(context: GetServerSidePropsContext) {
|
||||||
|
|||||||
@@ -2,6 +2,8 @@ import { signIn } from "next-auth/react";
|
|||||||
import { useRouter } from "next/router";
|
import { useRouter } from "next/router";
|
||||||
import { useEffect } from "react";
|
import { useEffect } from "react";
|
||||||
|
|
||||||
|
import PageWrapper from "@components/PageWrapper";
|
||||||
|
|
||||||
// To handle the IdP initiated login flow callback
|
// To handle the IdP initiated login flow callback
|
||||||
export default function Page() {
|
export default function Page() {
|
||||||
const router = useRouter();
|
const router = useRouter();
|
||||||
@@ -21,3 +23,4 @@ export default function Page() {
|
|||||||
|
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
|
Page.PageWrapper = PageWrapper;
|
||||||
@@ -11,6 +11,7 @@ import prisma from "@calcom/prisma";
|
|||||||
import type { inferSSRProps } from "@calcom/types/inferSSRProps";
|
import type { inferSSRProps } from "@calcom/types/inferSSRProps";
|
||||||
import { Meta, WizardForm } from "@calcom/ui";
|
import { Meta, WizardForm } from "@calcom/ui";
|
||||||
|
|
||||||
|
import PageWrapper from "@components/PageWrapper";
|
||||||
import { AdminUserContainer as AdminUser } from "@components/setup/AdminUser";
|
import { AdminUserContainer as AdminUser } from "@components/setup/AdminUser";
|
||||||
import ChooseLicense from "@components/setup/ChooseLicense";
|
import ChooseLicense from "@components/setup/ChooseLicense";
|
||||||
import EnterpriseLicense from "@components/setup/EnterpriseLicense";
|
import EnterpriseLicense from "@components/setup/EnterpriseLicense";
|
||||||
@@ -136,7 +137,7 @@ export function Setup(props: inferSSRProps<typeof getServerSideProps>) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
Setup.isThemeSupported = false;
|
Setup.isThemeSupported = false;
|
||||||
|
Setup.PageWrapper = PageWrapper;
|
||||||
export default Setup;
|
export default Setup;
|
||||||
|
|
||||||
export const getServerSideProps = async (context: GetServerSidePropsContext) => {
|
export const getServerSideProps = async (context: GetServerSidePropsContext) => {
|
||||||
|
|||||||
@@ -4,6 +4,8 @@ import { getProviders, signIn, getCsrfToken } from "next-auth/react";
|
|||||||
import { getServerSession } from "@calcom/features/auth/lib/getServerSession";
|
import { getServerSession } from "@calcom/features/auth/lib/getServerSession";
|
||||||
import { Button } from "@calcom/ui";
|
import { Button } from "@calcom/ui";
|
||||||
|
|
||||||
|
import PageWrapper from "@components/PageWrapper";
|
||||||
|
|
||||||
type Provider = {
|
type Provider = {
|
||||||
name: string;
|
name: string;
|
||||||
id: string;
|
id: string;
|
||||||
@@ -23,6 +25,8 @@ function signin({ providers }: { providers: Provider[] }) {
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
signin.PageWrapper = PageWrapper;
|
||||||
|
|
||||||
export default signin;
|
export default signin;
|
||||||
|
|
||||||
export async function getServerSideProps(context: GetServerSidePropsContext) {
|
export async function getServerSideProps(context: GetServerSidePropsContext) {
|
||||||
|
|||||||
@@ -19,6 +19,8 @@ import prisma from "@calcom/prisma";
|
|||||||
import { asStringOrNull } from "@lib/asStringOrNull";
|
import { asStringOrNull } from "@lib/asStringOrNull";
|
||||||
import type { inferSSRProps } from "@lib/types/inferSSRProps";
|
import type { inferSSRProps } from "@lib/types/inferSSRProps";
|
||||||
|
|
||||||
|
import PageWrapper from "@components/PageWrapper";
|
||||||
|
|
||||||
import { ssrInit } from "@server/lib/ssr";
|
import { ssrInit } from "@server/lib/ssr";
|
||||||
|
|
||||||
export type SSOProviderPageProps = inferSSRProps<typeof getServerSideProps>;
|
export type SSOProviderPageProps = inferSSRProps<typeof getServerSideProps>;
|
||||||
@@ -49,6 +51,8 @@ export default function Provider(props: SSOProviderPageProps) {
|
|||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
Provider.PageWrapper = PageWrapper;
|
||||||
|
|
||||||
export const getServerSideProps = async (context: GetServerSidePropsContext) => {
|
export const getServerSideProps = async (context: GetServerSidePropsContext) => {
|
||||||
// get query params and typecast them to string
|
// get query params and typecast them to string
|
||||||
// (would be even better to assert them instead of typecasting)
|
// (would be even better to assert them instead of typecasting)
|
||||||
|
|||||||
@@ -1,33 +1,41 @@
|
|||||||
import { signIn } from "next-auth/react";
|
import { signIn } from "next-auth/react";
|
||||||
import { useRouter } from "next/router";
|
import { useRouter } from "next/router";
|
||||||
|
import { useEffect } from "react";
|
||||||
|
|
||||||
import { samlProductID, samlTenantID } from "@calcom/features/ee/sso/lib/saml";
|
import { samlProductID, samlTenantID } from "@calcom/features/ee/sso/lib/saml";
|
||||||
import { HOSTED_CAL_FEATURES } from "@calcom/lib/constants";
|
import { HOSTED_CAL_FEATURES } from "@calcom/lib/constants";
|
||||||
|
|
||||||
import type { inferSSRProps } from "@lib/types/inferSSRProps";
|
import type { inferSSRProps } from "@lib/types/inferSSRProps";
|
||||||
|
|
||||||
|
import PageWrapper from "@components/PageWrapper";
|
||||||
|
|
||||||
// This page is used to initiate the SAML authentication flow by redirecting to the SAML provider.
|
// This page is used to initiate the SAML authentication flow by redirecting to the SAML provider.
|
||||||
// Accessible only on self-hosted Cal.com instances.
|
// Accessible only on self-hosted Cal.com instances.
|
||||||
export default function Page({ samlTenantID, samlProductID }: inferSSRProps<typeof getServerSideProps>) {
|
export default function Page({ samlTenantID, samlProductID }: inferSSRProps<typeof getServerSideProps>) {
|
||||||
const router = useRouter();
|
const router = useRouter();
|
||||||
|
|
||||||
if (HOSTED_CAL_FEATURES) {
|
useEffect(() => {
|
||||||
router.push("/auth/login");
|
if (HOSTED_CAL_FEATURES) {
|
||||||
return;
|
router.push("/auth/login");
|
||||||
}
|
}
|
||||||
|
}, []);
|
||||||
|
|
||||||
// Initiate SAML authentication flow
|
useEffect(() => {
|
||||||
signIn(
|
// Initiate SAML authentication flow
|
||||||
"saml",
|
signIn(
|
||||||
{
|
"saml",
|
||||||
callbackUrl: "/",
|
{
|
||||||
},
|
callbackUrl: "/",
|
||||||
{ tenant: samlTenantID, product: samlProductID }
|
},
|
||||||
);
|
{ tenant: samlTenantID, product: samlProductID }
|
||||||
|
);
|
||||||
|
}, []);
|
||||||
|
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
Page.PageWrapper = PageWrapper;
|
||||||
|
|
||||||
export async function getServerSideProps() {
|
export async function getServerSideProps() {
|
||||||
return {
|
return {
|
||||||
props: {
|
props: {
|
||||||
|
|||||||
@@ -11,6 +11,7 @@ import { trpc } from "@calcom/trpc/react";
|
|||||||
import { Button, showToast } from "@calcom/ui";
|
import { Button, showToast } from "@calcom/ui";
|
||||||
|
|
||||||
import Loader from "@components/Loader";
|
import Loader from "@components/Loader";
|
||||||
|
import PageWrapper from "@components/PageWrapper";
|
||||||
|
|
||||||
async function sendVerificationLogin(email: string, username: string) {
|
async function sendVerificationLogin(email: string, username: string) {
|
||||||
await signIn("email", {
|
await signIn("email", {
|
||||||
@@ -177,3 +178,5 @@ export default function Verify() {
|
|||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
Verify.PageWrapper = PageWrapper;
|
||||||
@@ -1,4 +1,5 @@
|
|||||||
import { useRouter } from "next/router";
|
import { useRouter } from "next/router";
|
||||||
|
import { useState } from "react";
|
||||||
import { Controller, useFieldArray, useForm } from "react-hook-form";
|
import { Controller, useFieldArray, useForm } from "react-hook-form";
|
||||||
import { z } from "zod";
|
import { z } from "zod";
|
||||||
|
|
||||||
@@ -24,13 +25,19 @@ import {
|
|||||||
Tooltip,
|
Tooltip,
|
||||||
Dialog,
|
Dialog,
|
||||||
DialogTrigger,
|
DialogTrigger,
|
||||||
|
DropdownMenuSeparator,
|
||||||
|
Dropdown,
|
||||||
|
DropdownMenuContent,
|
||||||
|
DropdownItem,
|
||||||
|
DropdownMenuTrigger,
|
||||||
ConfirmationDialogContent,
|
ConfirmationDialogContent,
|
||||||
VerticalDivider,
|
VerticalDivider,
|
||||||
} from "@calcom/ui";
|
} from "@calcom/ui";
|
||||||
import { Info, Plus, Trash } from "@calcom/ui/components/icon";
|
import { Info, Plus, Trash, MoreHorizontal } from "@calcom/ui/components/icon";
|
||||||
|
|
||||||
import { HttpError } from "@lib/core/http/error";
|
import { HttpError } from "@lib/core/http/error";
|
||||||
|
|
||||||
|
import PageWrapper from "@components/PageWrapper";
|
||||||
import { SelectSkeletonLoader } from "@components/availability/SkeletonLoader";
|
import { SelectSkeletonLoader } from "@components/availability/SkeletonLoader";
|
||||||
import EditableHeading from "@components/ui/EditableHeading";
|
import EditableHeading from "@components/ui/EditableHeading";
|
||||||
|
|
||||||
@@ -96,6 +103,7 @@ export default function Availability() {
|
|||||||
|
|
||||||
const { fromEventType } = router.query;
|
const { fromEventType } = router.query;
|
||||||
const { timeFormat } = me.data || { timeFormat: null };
|
const { timeFormat } = me.data || { timeFormat: null };
|
||||||
|
const [deleteDialogOpen, setDeleteDialogOpen] = useState(false);
|
||||||
const { data: schedule, isLoading } = trpc.viewer.availability.schedule.get.useQuery(
|
const { data: schedule, isLoading } = trpc.viewer.availability.schedule.get.useQuery(
|
||||||
{ scheduleId },
|
{ scheduleId },
|
||||||
{
|
{
|
||||||
@@ -181,11 +189,8 @@ export default function Availability() {
|
|||||||
}
|
}
|
||||||
CTA={
|
CTA={
|
||||||
<div className="flex items-center justify-end">
|
<div className="flex items-center justify-end">
|
||||||
<div className="sm:hover:bg-subtle flex items-center rounded-md px-2">
|
<div className="hidden items-center rounded-md px-2 sm:flex sm:hover:bg-gray-100">
|
||||||
<Skeleton
|
<Skeleton as={Label} htmlFor="hiddenSwitch" className="mt-2 cursor-pointer self-center pr-2 ">
|
||||||
as={Label}
|
|
||||||
htmlFor="hiddenSwitch"
|
|
||||||
className="mt-2 hidden cursor-pointer self-center pr-2 sm:inline">
|
|
||||||
{t("set_to_default")}
|
{t("set_to_default")}
|
||||||
</Skeleton>
|
</Skeleton>
|
||||||
<Switch
|
<Switch
|
||||||
@@ -198,7 +203,7 @@ export default function Availability() {
|
|||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<VerticalDivider />
|
<VerticalDivider className="hidden sm:inline" />
|
||||||
<Dialog>
|
<Dialog>
|
||||||
<DialogTrigger asChild>
|
<DialogTrigger asChild>
|
||||||
<Button
|
<Button
|
||||||
@@ -206,6 +211,7 @@ export default function Availability() {
|
|||||||
variant="icon"
|
variant="icon"
|
||||||
color="destructive"
|
color="destructive"
|
||||||
aria-label={t("delete")}
|
aria-label={t("delete")}
|
||||||
|
className="hidden sm:inline"
|
||||||
disabled={schedule?.isLastSchedule}
|
disabled={schedule?.isLastSchedule}
|
||||||
tooltip={t("requires_at_least_one_schedule")}
|
tooltip={t("requires_at_least_one_schedule")}
|
||||||
/>
|
/>
|
||||||
@@ -222,8 +228,51 @@ export default function Availability() {
|
|||||||
{t("delete_schedule_description")}
|
{t("delete_schedule_description")}
|
||||||
</ConfirmationDialogContent>
|
</ConfirmationDialogContent>
|
||||||
</Dialog>
|
</Dialog>
|
||||||
|
<VerticalDivider className="hidden sm:inline" />
|
||||||
<VerticalDivider />
|
<Dropdown>
|
||||||
|
<DropdownMenuTrigger asChild>
|
||||||
|
<Button className="sm:hidden" StartIcon={MoreHorizontal} variant="icon" color="secondary" />
|
||||||
|
</DropdownMenuTrigger>
|
||||||
|
<DropdownMenuContent style={{ minWidth: "200px" }}>
|
||||||
|
<DropdownItem
|
||||||
|
type="button"
|
||||||
|
color="destructive"
|
||||||
|
StartIcon={Trash}
|
||||||
|
onClick={() => setDeleteDialogOpen(true)}>
|
||||||
|
{t("delete")}
|
||||||
|
</DropdownItem>
|
||||||
|
<Dialog open={deleteDialogOpen} onOpenChange={setDeleteDialogOpen}>
|
||||||
|
<ConfirmationDialogContent
|
||||||
|
isLoading={deleteMutation.isLoading}
|
||||||
|
variety="danger"
|
||||||
|
title={t("delete_schedule")}
|
||||||
|
confirmBtnText={t("delete")}
|
||||||
|
loadingText={t("delete")}
|
||||||
|
onConfirm={() => {
|
||||||
|
schedule !== undefined && deleteMutation.mutate({ scheduleId: schedule.id });
|
||||||
|
}}>
|
||||||
|
{t("delete_schedule_description")}
|
||||||
|
</ConfirmationDialogContent>
|
||||||
|
</Dialog>
|
||||||
|
<DropdownMenuSeparator />
|
||||||
|
<div className="flex h-9 flex-row items-center justify-between py-2 px-4 hover:bg-gray-100">
|
||||||
|
<Skeleton
|
||||||
|
as={Label}
|
||||||
|
htmlFor="hiddenSwitch"
|
||||||
|
className="mt-2 cursor-pointer self-center pr-2 sm:inline">
|
||||||
|
{t("set_to_default")}
|
||||||
|
</Skeleton>
|
||||||
|
<Switch
|
||||||
|
id="hiddenSwitch"
|
||||||
|
disabled={isLoading || schedule?.isDefault}
|
||||||
|
checked={form.watch("isDefault")}
|
||||||
|
onCheckedChange={(e) => {
|
||||||
|
form.setValue("isDefault", e);
|
||||||
|
}}
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
</DropdownMenuContent>
|
||||||
|
</Dropdown>
|
||||||
|
|
||||||
<div className="border-default border-l-2" />
|
<div className="border-default border-l-2" />
|
||||||
<Button className="ml-4 lg:ml-0" type="submit" form="availability-form">
|
<Button className="ml-4 lg:ml-0" type="submit" form="availability-form">
|
||||||
@@ -301,3 +350,5 @@ export default function Availability() {
|
|||||||
</Shell>
|
</Shell>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
Availability.PageWrapper = PageWrapper;
|
||||||
@@ -11,6 +11,7 @@ import { Clock } from "@calcom/ui/components/icon";
|
|||||||
import { withQuery } from "@lib/QueryCell";
|
import { withQuery } from "@lib/QueryCell";
|
||||||
import { HttpError } from "@lib/core/http/error";
|
import { HttpError } from "@lib/core/http/error";
|
||||||
|
|
||||||
|
import PageWrapper from "@components/PageWrapper";
|
||||||
import SkeletonLoader from "@components/availability/SkeletonLoader";
|
import SkeletonLoader from "@components/availability/SkeletonLoader";
|
||||||
|
|
||||||
export function AvailabilityList({ schedules }: RouterOutputs["viewer"]["availability"]["list"]) {
|
export function AvailabilityList({ schedules }: RouterOutputs["viewer"]["availability"]["list"]) {
|
||||||
@@ -110,9 +111,15 @@ export default function AvailabilityPage() {
|
|||||||
const { t } = useLocale();
|
const { t } = useLocale();
|
||||||
return (
|
return (
|
||||||
<div>
|
<div>
|
||||||
<Shell heading={t("availability")} subtitle={t("configure_availability")} CTA={<NewScheduleButton />}>
|
<Shell
|
||||||
|
heading={t("availability")}
|
||||||
|
hideHeadingOnMobile
|
||||||
|
subtitle={t("configure_availability")}
|
||||||
|
CTA={<NewScheduleButton />}>
|
||||||
<WithQuery success={({ data }) => <AvailabilityList {...data} />} customLoader={<SkeletonLoader />} />
|
<WithQuery success={({ data }) => <AvailabilityList {...data} />} customLoader={<SkeletonLoader />} />
|
||||||
</Shell>
|
</Shell>
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
AvailabilityPage.PageWrapper = PageWrapper;
|
||||||
@@ -7,6 +7,8 @@ import { SkeletonText } from "@calcom/ui";
|
|||||||
|
|
||||||
import useRouterQuery from "@lib/hooks/useRouterQuery";
|
import useRouterQuery from "@lib/hooks/useRouterQuery";
|
||||||
|
|
||||||
|
import PageWrapper from "@components/PageWrapper";
|
||||||
|
|
||||||
type User = RouterOutputs["viewer"]["me"];
|
type User = RouterOutputs["viewer"]["me"];
|
||||||
|
|
||||||
export interface IBusySlot {
|
export interface IBusySlot {
|
||||||
@@ -120,12 +122,13 @@ export default function Troubleshoot() {
|
|||||||
const { t } = useLocale();
|
const { t } = useLocale();
|
||||||
return (
|
return (
|
||||||
<div>
|
<div>
|
||||||
<Shell heading={t("troubleshoot")} subtitle={t("troubleshoot_description")}>
|
<Shell heading={t("troubleshoot")} hideHeadingOnMobile subtitle={t("troubleshoot_description")}>
|
||||||
{!isLoading && data && <AvailabilityView user={data} />}
|
{!isLoading && data && <AvailabilityView user={data} />}
|
||||||
</Shell>
|
</Shell>
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
Troubleshoot.PageWrapper = PageWrapper;
|
||||||
|
|
||||||
function convertMinsToHrsMins(mins: number) {
|
function convertMinsToHrsMins(mins: number) {
|
||||||
const h = Math.floor(mins / 60);
|
const h = Math.floor(mins / 60);
|
||||||
|
|||||||
@@ -23,6 +23,7 @@ import {
|
|||||||
useIsBackgroundTransparent,
|
useIsBackgroundTransparent,
|
||||||
useIsEmbed,
|
useIsEmbed,
|
||||||
} from "@calcom/embed-core/embed-iframe";
|
} from "@calcom/embed-core/embed-iframe";
|
||||||
|
import { getServerSession } from "@calcom/features/auth/lib/getServerSession";
|
||||||
import {
|
import {
|
||||||
SystemField,
|
SystemField,
|
||||||
getBookingFieldsWithSystemFields,
|
getBookingFieldsWithSystemFields,
|
||||||
@@ -41,7 +42,6 @@ import { useLocale } from "@calcom/lib/hooks/useLocale";
|
|||||||
import useTheme from "@calcom/lib/hooks/useTheme";
|
import useTheme from "@calcom/lib/hooks/useTheme";
|
||||||
import { getEveryFreqFor } from "@calcom/lib/recurringStrings";
|
import { getEveryFreqFor } from "@calcom/lib/recurringStrings";
|
||||||
import { maybeGetBookingUidFromSeat } from "@calcom/lib/server/maybeGetBookingUidFromSeat";
|
import { maybeGetBookingUidFromSeat } from "@calcom/lib/server/maybeGetBookingUidFromSeat";
|
||||||
import { collectPageParameters, telemetryEventTypes, useTelemetry } from "@calcom/lib/telemetry";
|
|
||||||
import { getIs24hClockFromLocalStorage, isBrowserLocale24h } from "@calcom/lib/timeFormat";
|
import { getIs24hClockFromLocalStorage, isBrowserLocale24h } from "@calcom/lib/timeFormat";
|
||||||
import { localStorage } from "@calcom/lib/webstorage";
|
import { localStorage } from "@calcom/lib/webstorage";
|
||||||
import prisma from "@calcom/prisma";
|
import prisma from "@calcom/prisma";
|
||||||
@@ -54,6 +54,7 @@ import { X, ExternalLink, ChevronLeft, Check, Calendar } from "@calcom/ui/compon
|
|||||||
import { timeZone } from "@lib/clock";
|
import { timeZone } from "@lib/clock";
|
||||||
import type { inferSSRProps } from "@lib/types/inferSSRProps";
|
import type { inferSSRProps } from "@lib/types/inferSSRProps";
|
||||||
|
|
||||||
|
import PageWrapper from "@components/PageWrapper";
|
||||||
import CancelBooking from "@components/booking/CancelBooking";
|
import CancelBooking from "@components/booking/CancelBooking";
|
||||||
import EventReservationSchema from "@components/schemas/EventReservationSchema";
|
import EventReservationSchema from "@components/schemas/EventReservationSchema";
|
||||||
|
|
||||||
@@ -106,10 +107,10 @@ export default function Success(props: SuccessProps) {
|
|||||||
seatReferenceUid,
|
seatReferenceUid,
|
||||||
} = querySchema.parse(router.query);
|
} = querySchema.parse(router.query);
|
||||||
|
|
||||||
const tz =
|
const attendeeTimeZone = props?.bookingInfo?.attendees.find(
|
||||||
(isSuccessBookingPage
|
(attendee) => attendee.email === email
|
||||||
? props.bookingInfo.attendees.find((attendee) => attendee.email === email)?.timeZone
|
)?.timeZone;
|
||||||
: props.bookingInfo.eventType?.timeZone || props.bookingInfo.user?.timeZone) || timeZone();
|
const tz = isSuccessBookingPage && attendeeTimeZone ? attendeeTimeZone : props.tz ? props.tz : timeZone();
|
||||||
|
|
||||||
const location = props.bookingInfo.location as ReturnType<typeof getEventLocationValue>;
|
const location = props.bookingInfo.location as ReturnType<typeof getEventLocationValue>;
|
||||||
|
|
||||||
@@ -177,7 +178,7 @@ export default function Success(props: SuccessProps) {
|
|||||||
// - Event Type has require confirmation option enabled always
|
// - Event Type has require confirmation option enabled always
|
||||||
// - EventType has conditionally enabled confirmation option based on how far the booking is scheduled.
|
// - EventType has conditionally enabled confirmation option based on how far the booking is scheduled.
|
||||||
// - It's a paid event and payment is pending.
|
// - It's a paid event and payment is pending.
|
||||||
const needsConfirmation = bookingInfo.status === BookingStatus.PENDING;
|
const needsConfirmation = bookingInfo.status === BookingStatus.PENDING && eventType.requiresConfirmation;
|
||||||
const userIsOwner = !!(session?.user?.id && eventType.owner?.id === session.user.id);
|
const userIsOwner = !!(session?.user?.id && eventType.owner?.id === session.user.id);
|
||||||
|
|
||||||
const isCancelled =
|
const isCancelled =
|
||||||
@@ -186,13 +187,13 @@ export default function Success(props: SuccessProps) {
|
|||||||
(!!seatReferenceUid &&
|
(!!seatReferenceUid &&
|
||||||
!bookingInfo.seatsReferences.some((reference) => reference.referenceUid === seatReferenceUid));
|
!bookingInfo.seatsReferences.some((reference) => reference.referenceUid === seatReferenceUid));
|
||||||
|
|
||||||
const telemetry = useTelemetry();
|
// const telemetry = useTelemetry();
|
||||||
useEffect(() => {
|
/* useEffect(() => {
|
||||||
if (top !== window) {
|
if (top !== window) {
|
||||||
//page_view will be collected automatically by _middleware.ts
|
//page_view will be collected automatically by _middleware.ts
|
||||||
telemetry.event(telemetryEventTypes.embedView, collectPageParameters("/booking"));
|
telemetry.event(telemetryEventTypes.embedView, collectPageParameters("/booking"));
|
||||||
}
|
}
|
||||||
}, [telemetry]);
|
}, [telemetry]); */
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
const users = eventType.users;
|
const users = eventType.users;
|
||||||
@@ -269,7 +270,9 @@ export default function Success(props: SuccessProps) {
|
|||||||
return t("emailed_you_and_attendees" + titleSuffix);
|
return t("emailed_you_and_attendees" + titleSuffix);
|
||||||
}
|
}
|
||||||
|
|
||||||
useTheme(isSuccessBookingPage ? props.profile.theme : "light");
|
// This is a weird case where the same route can be opened in booking flow as a success page or as a booking detail page from the app
|
||||||
|
// As Booking Page it has to support configured theme, but as booking detail page it should not do any change. Let Shell.tsx handle it.
|
||||||
|
useTheme(isSuccessBookingPage ? props.profile.theme : undefined);
|
||||||
useBrandColors({
|
useBrandColors({
|
||||||
brandColor: props.profile.brandColor,
|
brandColor: props.profile.brandColor,
|
||||||
darkBrandColor: props.profile.darkBrandColor,
|
darkBrandColor: props.profile.darkBrandColor,
|
||||||
@@ -325,7 +328,7 @@ export default function Success(props: SuccessProps) {
|
|||||||
<div
|
<div
|
||||||
className={classNames(
|
className={classNames(
|
||||||
"main inline-block transform overflow-hidden rounded-lg border sm:my-8 sm:max-w-xl",
|
"main inline-block transform overflow-hidden rounded-lg border sm:my-8 sm:max-w-xl",
|
||||||
isBackgroundTransparent ? "" : " bg-default dark:bg-muted border-subtle",
|
!isBackgroundTransparent && " bg-default dark:bg-muted border-booker border-booker-width",
|
||||||
"px-8 pt-5 pb-4 text-left align-bottom transition-all sm:w-full sm:py-8 sm:align-middle"
|
"px-8 pt-5 pb-4 text-left align-bottom transition-all sm:w-full sm:py-8 sm:align-middle"
|
||||||
)}
|
)}
|
||||||
role="dialog"
|
role="dialog"
|
||||||
@@ -706,6 +709,9 @@ export default function Success(props: SuccessProps) {
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
Success.isBookingPage = true;
|
||||||
|
Success.PageWrapper = PageWrapper;
|
||||||
|
|
||||||
type RecurringBookingsProps = {
|
type RecurringBookingsProps = {
|
||||||
eventType: SuccessProps["eventType"];
|
eventType: SuccessProps["eventType"];
|
||||||
recurringBookings: SuccessProps["recurringBookings"];
|
recurringBookings: SuccessProps["recurringBookings"];
|
||||||
@@ -914,7 +920,16 @@ const handleSeatsEventTypeOnBooking = (
|
|||||||
|
|
||||||
export async function getServerSideProps(context: GetServerSidePropsContext) {
|
export async function getServerSideProps(context: GetServerSidePropsContext) {
|
||||||
const ssr = await ssrInit(context);
|
const ssr = await ssrInit(context);
|
||||||
|
const session = await getServerSession(context);
|
||||||
|
let tz: string | null = null;
|
||||||
|
|
||||||
|
if (session) {
|
||||||
|
const user = await ssr.viewer.me.fetch();
|
||||||
|
tz = user.timeZone;
|
||||||
|
}
|
||||||
|
|
||||||
const parsedQuery = querySchema.safeParse(context.query);
|
const parsedQuery = querySchema.safeParse(context.query);
|
||||||
|
|
||||||
if (!parsedQuery.success) return { notFound: true };
|
if (!parsedQuery.success) return { notFound: true };
|
||||||
const { uid, email, eventTypeSlug, cancel, isSuccessBookingPage } = parsedQuery.data;
|
const { uid, email, eventTypeSlug, cancel, isSuccessBookingPage } = parsedQuery.data;
|
||||||
|
|
||||||
@@ -1046,6 +1061,7 @@ export async function getServerSideProps(context: GetServerSidePropsContext) {
|
|||||||
dynamicEventName: bookingInfo?.eventType?.eventName || "",
|
dynamicEventName: bookingInfo?.eventType?.eventName || "",
|
||||||
bookingInfo,
|
bookingInfo,
|
||||||
paymentStatus: payment,
|
paymentStatus: payment,
|
||||||
|
...(tz && { tz }),
|
||||||
},
|
},
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -16,6 +16,7 @@ import { Calendar } from "@calcom/ui/components/icon";
|
|||||||
|
|
||||||
import { useInViewObserver } from "@lib/hooks/useInViewObserver";
|
import { useInViewObserver } from "@lib/hooks/useInViewObserver";
|
||||||
|
|
||||||
|
import PageWrapper from "@components/PageWrapper";
|
||||||
import BookingListItem from "@components/booking/BookingListItem";
|
import BookingListItem from "@components/booking/BookingListItem";
|
||||||
import SkeletonLoader from "@components/booking/SkeletonLoader";
|
import SkeletonLoader from "@components/booking/SkeletonLoader";
|
||||||
|
|
||||||
@@ -194,6 +195,8 @@ export default function Bookings() {
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
Bookings.PageWrapper = PageWrapper;
|
||||||
|
|
||||||
export const getStaticProps: GetStaticProps = async (ctx) => {
|
export const getStaticProps: GetStaticProps = async (ctx) => {
|
||||||
const params = querySchema.safeParse(ctx.params);
|
const params = querySchema.safeParse(ctx.params);
|
||||||
const ssg = await ssgInit(ctx);
|
const ssg = await ssgInit(ctx);
|
||||||
|
|||||||
@@ -14,6 +14,7 @@ import { EventTypeMetaDataSchema } from "@calcom/prisma/zod-utils";
|
|||||||
import type { inferSSRProps } from "@lib/types/inferSSRProps";
|
import type { inferSSRProps } from "@lib/types/inferSSRProps";
|
||||||
import type { EmbedProps } from "@lib/withEmbedSsr";
|
import type { EmbedProps } from "@lib/withEmbedSsr";
|
||||||
|
|
||||||
|
import PageWrapper from "@components/PageWrapper";
|
||||||
import AvailabilityPage from "@components/booking/pages/AvailabilityPage";
|
import AvailabilityPage from "@components/booking/pages/AvailabilityPage";
|
||||||
|
|
||||||
import { ssrInit } from "@server/lib/ssr";
|
import { ssrInit } from "@server/lib/ssr";
|
||||||
@@ -24,6 +25,9 @@ export default function Type(props: DynamicAvailabilityPageProps) {
|
|||||||
return <AvailabilityPage {...props} />;
|
return <AvailabilityPage {...props} />;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
Type.isBookingPage = true;
|
||||||
|
Type.PageWrapper = PageWrapper;
|
||||||
|
|
||||||
const querySchema = z.object({
|
const querySchema = z.object({
|
||||||
link: z.string().optional().default(""),
|
link: z.string().optional().default(""),
|
||||||
slug: z.string().optional().default(""),
|
slug: z.string().optional().default(""),
|
||||||
|
|||||||
@@ -9,6 +9,7 @@ import { customInputSchema, eventTypeBookingFields, EventTypeMetaDataSchema } fr
|
|||||||
import { asStringOrNull, asStringOrThrow } from "@lib/asStringOrNull";
|
import { asStringOrNull, asStringOrThrow } from "@lib/asStringOrNull";
|
||||||
import type { inferSSRProps } from "@lib/types/inferSSRProps";
|
import type { inferSSRProps } from "@lib/types/inferSSRProps";
|
||||||
|
|
||||||
|
import PageWrapper from "@components/PageWrapper";
|
||||||
import BookingPage from "@components/booking/pages/BookingPage";
|
import BookingPage from "@components/booking/pages/BookingPage";
|
||||||
|
|
||||||
import { ssrInit } from "@server/lib/ssr";
|
import { ssrInit } from "@server/lib/ssr";
|
||||||
@@ -19,6 +20,9 @@ export default function Book(props: HashLinkPageProps) {
|
|||||||
return <BookingPage {...props} />;
|
return <BookingPage {...props} />;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
Book.isBookingPage = true;
|
||||||
|
Book.PageWrapper = PageWrapper;
|
||||||
|
|
||||||
export async function getServerSideProps(context: GetServerSidePropsContext) {
|
export async function getServerSideProps(context: GetServerSidePropsContext) {
|
||||||
const ssr = await ssrInit(context);
|
const ssr = await ssrInit(context);
|
||||||
const link = asStringOrThrow(context.query.link as string);
|
const link = asStringOrThrow(context.query.link as string);
|
||||||
|
|||||||
@@ -1,8 +1,7 @@
|
|||||||
/* eslint-disable @typescript-eslint/no-empty-function */
|
/* eslint-disable @typescript-eslint/no-empty-function */
|
||||||
import { useAutoAnimate } from "@formkit/auto-animate/react";
|
import { useAutoAnimate } from "@formkit/auto-animate/react";
|
||||||
import { zodResolver } from "@hookform/resolvers/zod";
|
import { zodResolver } from "@hookform/resolvers/zod";
|
||||||
import type { PeriodType } from "@prisma/client";
|
import type { PeriodType, SchedulingType } from "@prisma/client";
|
||||||
import type { SchedulingType } from "@prisma/client";
|
|
||||||
import type { GetServerSidePropsContext } from "next";
|
import type { GetServerSidePropsContext } from "next";
|
||||||
import { Trans } from "next-i18next";
|
import { Trans } from "next-i18next";
|
||||||
import { useEffect, useState } from "react";
|
import { useEffect, useState } from "react";
|
||||||
@@ -18,10 +17,10 @@ import { CAL_URL } from "@calcom/lib/constants";
|
|||||||
import { useLocale } from "@calcom/lib/hooks/useLocale";
|
import { useLocale } from "@calcom/lib/hooks/useLocale";
|
||||||
import { useTypedQuery } from "@calcom/lib/hooks/useTypedQuery";
|
import { useTypedQuery } from "@calcom/lib/hooks/useTypedQuery";
|
||||||
import { HttpError } from "@calcom/lib/http-error";
|
import { HttpError } from "@calcom/lib/http-error";
|
||||||
import { useTelemetry, telemetryEventTypes } from "@calcom/lib/telemetry";
|
import { telemetryEventTypes, useTelemetry } from "@calcom/lib/telemetry";
|
||||||
import type { Prisma } from "@calcom/prisma/client";
|
import type { Prisma } from "@calcom/prisma/client";
|
||||||
import { eventTypeBookingFields } from "@calcom/prisma/zod-utils";
|
|
||||||
import type { customInputSchema, EventTypeMetaDataSchema } from "@calcom/prisma/zod-utils";
|
import type { customInputSchema, EventTypeMetaDataSchema } from "@calcom/prisma/zod-utils";
|
||||||
|
import { eventTypeBookingFields } from "@calcom/prisma/zod-utils";
|
||||||
import type { RouterOutputs } from "@calcom/trpc/react";
|
import type { RouterOutputs } from "@calcom/trpc/react";
|
||||||
import { trpc } from "@calcom/trpc/react";
|
import { trpc } from "@calcom/trpc/react";
|
||||||
import type { IntervalLimit, RecurringEvent } from "@calcom/types/Calendar";
|
import type { IntervalLimit, RecurringEvent } from "@calcom/types/Calendar";
|
||||||
@@ -30,9 +29,11 @@ import { ConfirmationDialogContent, Dialog, Form, showToast } from "@calcom/ui";
|
|||||||
import { asStringOrThrow } from "@lib/asStringOrNull";
|
import { asStringOrThrow } from "@lib/asStringOrNull";
|
||||||
import type { inferSSRProps } from "@lib/types/inferSSRProps";
|
import type { inferSSRProps } from "@lib/types/inferSSRProps";
|
||||||
|
|
||||||
|
import PageWrapper from "@components/PageWrapper";
|
||||||
// These can't really be moved into calcom/ui due to the fact they use infered getserverside props typings
|
// These can't really be moved into calcom/ui due to the fact they use infered getserverside props typings
|
||||||
import { EventAdvancedTab } from "@components/eventtype/EventAdvancedTab";
|
import { EventAdvancedTab } from "@components/eventtype/EventAdvancedTab";
|
||||||
import { EventAppsTab } from "@components/eventtype/EventAppsTab";
|
import { EventAppsTab } from "@components/eventtype/EventAppsTab";
|
||||||
|
import type { AvailabilityOption } from "@components/eventtype/EventAvailabilityTab";
|
||||||
import { EventAvailabilityTab } from "@components/eventtype/EventAvailabilityTab";
|
import { EventAvailabilityTab } from "@components/eventtype/EventAvailabilityTab";
|
||||||
import { EventLimitsTab } from "@components/eventtype/EventLimitsTab";
|
import { EventLimitsTab } from "@components/eventtype/EventLimitsTab";
|
||||||
import { EventRecurringTab } from "@components/eventtype/EventRecurringTab";
|
import { EventRecurringTab } from "@components/eventtype/EventRecurringTab";
|
||||||
@@ -93,6 +94,7 @@ export type FormValues = {
|
|||||||
children: ChildrenEventType[];
|
children: ChildrenEventType[];
|
||||||
hosts: { userId: number; isFixed: boolean }[];
|
hosts: { userId: number; isFixed: boolean }[];
|
||||||
bookingFields: z.infer<typeof eventTypeBookingFields>;
|
bookingFields: z.infer<typeof eventTypeBookingFields>;
|
||||||
|
availability?: AvailabilityOption;
|
||||||
};
|
};
|
||||||
|
|
||||||
export type CustomInputParsed = typeof customInputSchema._output;
|
export type CustomInputParsed = typeof customInputSchema._output;
|
||||||
@@ -261,6 +263,7 @@ const EventTypePage = (props: EventTypeSetupProps) => {
|
|||||||
}, [defaultValues]);
|
}, [defaultValues]);
|
||||||
|
|
||||||
const appsMetadata = formMethods.getValues("metadata")?.apps;
|
const appsMetadata = formMethods.getValues("metadata")?.apps;
|
||||||
|
const availability = formMethods.watch("availability");
|
||||||
const numberOfInstalledApps = eventTypeApps?.filter((app) => app.isInstalled).length || 0;
|
const numberOfInstalledApps = eventTypeApps?.filter((app) => app.isInstalled).length || 0;
|
||||||
let numberOfActiveApps = 0;
|
let numberOfActiveApps = 0;
|
||||||
|
|
||||||
@@ -377,6 +380,7 @@ const EventTypePage = (props: EventTypeSetupProps) => {
|
|||||||
enabledWorkflowsNumber={eventType.workflows.length}
|
enabledWorkflowsNumber={eventType.workflows.length}
|
||||||
eventType={eventType}
|
eventType={eventType}
|
||||||
team={team}
|
team={team}
|
||||||
|
availability={availability}
|
||||||
isUpdateMutationLoading={updateMutation.isLoading}
|
isUpdateMutationLoading={updateMutation.isLoading}
|
||||||
formMethods={formMethods}
|
formMethods={formMethods}
|
||||||
disableBorder={tabName === "apps" || tabName === "workflows" || tabName === "webhooks"}
|
disableBorder={tabName === "apps" || tabName === "workflows" || tabName === "webhooks"}
|
||||||
@@ -384,13 +388,66 @@ const EventTypePage = (props: EventTypeSetupProps) => {
|
|||||||
<Form
|
<Form
|
||||||
form={formMethods}
|
form={formMethods}
|
||||||
id="event-type-form"
|
id="event-type-form"
|
||||||
handleSubmit={async (values: FormValues) => {
|
handleSubmit={async (values) => {
|
||||||
if (!values.children.length) return handleSubmit(values);
|
const {
|
||||||
const existingSlugEventTypes = values.children.filter((ch) =>
|
periodDates,
|
||||||
ch.owner.eventTypeSlugs.includes(slug)
|
periodCountCalendarDays,
|
||||||
);
|
beforeBufferTime,
|
||||||
if (!existingSlugEventTypes.length) return handleSubmit(values);
|
afterBufferTime,
|
||||||
setSlugExistsChildrenDialogOpen(existingSlugEventTypes);
|
seatsPerTimeSlot,
|
||||||
|
seatsShowAttendees,
|
||||||
|
bookingLimits,
|
||||||
|
durationLimits,
|
||||||
|
recurringEvent,
|
||||||
|
locations,
|
||||||
|
metadata,
|
||||||
|
customInputs,
|
||||||
|
// We don't need to send send these values to the backend
|
||||||
|
// eslint-disable-next-line @typescript-eslint/no-unused-vars
|
||||||
|
seatsPerTimeSlotEnabled,
|
||||||
|
// eslint-disable-next-line @typescript-eslint/no-unused-vars
|
||||||
|
minimumBookingNoticeInDurationType,
|
||||||
|
availability,
|
||||||
|
...input
|
||||||
|
} = values;
|
||||||
|
|
||||||
|
if (bookingLimits) {
|
||||||
|
const isValid = validateIntervalLimitOrder(bookingLimits);
|
||||||
|
if (!isValid) throw new Error(t("event_setup_booking_limits_error"));
|
||||||
|
}
|
||||||
|
|
||||||
|
if (durationLimits) {
|
||||||
|
const isValid = validateIntervalLimitOrder(durationLimits);
|
||||||
|
if (!isValid) throw new Error(t("event_setup_duration_limits_error"));
|
||||||
|
}
|
||||||
|
|
||||||
|
if (metadata?.multipleDuration !== undefined) {
|
||||||
|
if (metadata?.multipleDuration.length < 1) {
|
||||||
|
throw new Error(t("event_setup_multiple_duration_error"));
|
||||||
|
} else {
|
||||||
|
if (!input.length && !metadata?.multipleDuration?.includes(input.length)) {
|
||||||
|
throw new Error(t("event_setup_multiple_duration_default_error"));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
updateMutation.mutate({
|
||||||
|
...input,
|
||||||
|
locations,
|
||||||
|
recurringEvent,
|
||||||
|
periodStartDate: periodDates.startDate,
|
||||||
|
periodEndDate: periodDates.endDate,
|
||||||
|
periodCountCalendarDays: periodCountCalendarDays === "1",
|
||||||
|
id: eventType.id,
|
||||||
|
beforeEventBuffer: beforeBufferTime,
|
||||||
|
afterEventBuffer: afterBufferTime,
|
||||||
|
bookingLimits,
|
||||||
|
durationLimits,
|
||||||
|
seatsPerTimeSlot,
|
||||||
|
seatsShowAttendees,
|
||||||
|
metadata,
|
||||||
|
customInputs,
|
||||||
|
});
|
||||||
}}>
|
}}>
|
||||||
<div ref={animationParentRef}>{tabMap[tabName]}</div>
|
<div ref={animationParentRef}>{tabMap[tabName]}</div>
|
||||||
</Form>
|
</Form>
|
||||||
@@ -477,4 +534,6 @@ export const getServerSideProps = async (context: GetServerSidePropsContext) =>
|
|||||||
};
|
};
|
||||||
};
|
};
|
||||||
|
|
||||||
|
EventTypePageWrapper.PageWrapper = PageWrapper;
|
||||||
|
|
||||||
export default EventTypePageWrapper;
|
export default EventTypePageWrapper;
|
||||||
@@ -61,6 +61,7 @@ import { withQuery } from "@lib/QueryCell";
|
|||||||
import { HttpError } from "@lib/core/http/error";
|
import { HttpError } from "@lib/core/http/error";
|
||||||
|
|
||||||
import { EmbedButton, EmbedDialog } from "@components/Embed";
|
import { EmbedButton, EmbedDialog } from "@components/Embed";
|
||||||
|
import PageWrapper from "@components/PageWrapper";
|
||||||
import SkeletonLoader from "@components/eventtype/SkeletonLoader";
|
import SkeletonLoader from "@components/eventtype/SkeletonLoader";
|
||||||
|
|
||||||
type EventTypeGroups = RouterOutputs["viewer"]["eventTypes"]["getByViewer"]["eventTypeGroups"];
|
type EventTypeGroups = RouterOutputs["viewer"]["eventTypes"]["getByViewer"]["eventTypeGroups"];
|
||||||
@@ -767,6 +768,7 @@ const EventTypesPage = () => {
|
|||||||
<Shell
|
<Shell
|
||||||
withoutSeo
|
withoutSeo
|
||||||
heading={t("event_types_page_title")}
|
heading={t("event_types_page_title")}
|
||||||
|
hideHeadingOnMobile
|
||||||
subtitle={t("event_types_page_subtitle")}
|
subtitle={t("event_types_page_subtitle")}
|
||||||
CTA={<CTA />}>
|
CTA={<CTA />}>
|
||||||
<WithQuery
|
<WithQuery
|
||||||
@@ -817,4 +819,6 @@ const EventTypesPage = () => {
|
|||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|
||||||
|
EventTypesPage.PageWrapper = PageWrapper;
|
||||||
|
|
||||||
export default EventTypesPage;
|
export default EventTypesPage;
|
||||||
@@ -13,6 +13,7 @@ import { Button, StepCard, Steps } from "@calcom/ui";
|
|||||||
|
|
||||||
import type { inferSSRProps } from "@lib/types/inferSSRProps";
|
import type { inferSSRProps } from "@lib/types/inferSSRProps";
|
||||||
|
|
||||||
|
import PageWrapper from "@components/PageWrapper";
|
||||||
import { ConnectedCalendars } from "@components/getting-started/steps-views/ConnectCalendars";
|
import { ConnectedCalendars } from "@components/getting-started/steps-views/ConnectCalendars";
|
||||||
import { SetupAvailability } from "@components/getting-started/steps-views/SetupAvailability";
|
import { SetupAvailability } from "@components/getting-started/steps-views/SetupAvailability";
|
||||||
import UserProfile from "@components/getting-started/steps-views/UserProfile";
|
import UserProfile from "@components/getting-started/steps-views/UserProfile";
|
||||||
@@ -203,5 +204,6 @@ export const getServerSideProps = async (context: GetServerSidePropsContext) =>
|
|||||||
};
|
};
|
||||||
|
|
||||||
OnboardingPage.isThemeSupported = false;
|
OnboardingPage.isThemeSupported = false;
|
||||||
|
OnboardingPage.PageWrapper = PageWrapper;
|
||||||
|
|
||||||
export default OnboardingPage;
|
export default OnboardingPage;
|
||||||
@@ -17,6 +17,8 @@ import { trpc } from "@calcom/trpc";
|
|||||||
import { Button, ButtonGroup } from "@calcom/ui";
|
import { Button, ButtonGroup } from "@calcom/ui";
|
||||||
import { RefreshCcw, UserPlus, Users } from "@calcom/ui/components/icon";
|
import { RefreshCcw, UserPlus, Users } from "@calcom/ui/components/icon";
|
||||||
|
|
||||||
|
import PageWrapper from "@components/PageWrapper";
|
||||||
|
|
||||||
const Heading = () => {
|
const Heading = () => {
|
||||||
const { t } = useLocale();
|
const { t } = useLocale();
|
||||||
|
|
||||||
@@ -53,7 +55,7 @@ export default function InsightsPage() {
|
|||||||
|
|
||||||
return (
|
return (
|
||||||
<div>
|
<div>
|
||||||
<Shell>
|
<Shell hideHeadingOnMobile>
|
||||||
<UpgradeTip
|
<UpgradeTip
|
||||||
title={t("make_informed_decisions")}
|
title={t("make_informed_decisions")}
|
||||||
description={t("make_informed_decisions_description")}
|
description={t("make_informed_decisions_description")}
|
||||||
@@ -111,6 +113,8 @@ export default function InsightsPage() {
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
InsightsPage.PageWrapper = PageWrapper;
|
||||||
|
|
||||||
// If feature flag is disabled, return not found on getServerSideProps
|
// If feature flag is disabled, return not found on getServerSideProps
|
||||||
export const getServerSideProps = async () => {
|
export const getServerSideProps = async () => {
|
||||||
const prisma = await import("@calcom/prisma").then((mod) => mod.default);
|
const prisma = await import("@calcom/prisma").then((mod) => mod.default);
|
||||||
|
|||||||
@@ -4,6 +4,8 @@ import { APP_NAME, WEBSITE_URL } from "@calcom/lib/constants";
|
|||||||
import { useLocale } from "@calcom/lib/hooks/useLocale";
|
import { useLocale } from "@calcom/lib/hooks/useLocale";
|
||||||
import { Button } from "@calcom/ui";
|
import { Button } from "@calcom/ui";
|
||||||
|
|
||||||
|
import PageWrapper from "@components/PageWrapper";
|
||||||
|
|
||||||
export default function MaintenancePage() {
|
export default function MaintenancePage() {
|
||||||
const { t, isLocaleReady } = useLocale();
|
const { t, isLocaleReady } = useLocale();
|
||||||
if (!isLocaleReady) return null;
|
if (!isLocaleReady) return null;
|
||||||
@@ -25,3 +27,5 @@ export default function MaintenancePage() {
|
|||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
MaintenancePage.PageWrapper = PageWrapper;
|
||||||
@@ -1,10 +1,12 @@
|
|||||||
import Shell, { MobileNavigationMoreItems } from "@calcom/features/shell/Shell";
|
import Shell, { MobileNavigationMoreItems } from "@calcom/features/shell/Shell";
|
||||||
import { useLocale } from "@calcom/lib/hooks/useLocale";
|
import { useLocale } from "@calcom/lib/hooks/useLocale";
|
||||||
|
|
||||||
|
import PageWrapper from "@components/PageWrapper";
|
||||||
|
|
||||||
export default function MorePage() {
|
export default function MorePage() {
|
||||||
const { t } = useLocale();
|
const { t } = useLocale();
|
||||||
return (
|
return (
|
||||||
<Shell>
|
<Shell hideHeadingOnMobile>
|
||||||
<div className="max-w-screen-lg">
|
<div className="max-w-screen-lg">
|
||||||
<MobileNavigationMoreItems />
|
<MobileNavigationMoreItems />
|
||||||
<p className="text-subtle mt-6 text-xs leading-tight md:hidden">{t("more_page_footer")}</p>
|
<p className="text-subtle mt-6 text-xs leading-tight md:hidden">{t("more_page_footer")}</p>
|
||||||
@@ -12,3 +14,4 @@ export default function MorePage() {
|
|||||||
</Shell>
|
</Shell>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
MorePage.PageWrapper = PageWrapper;
|
||||||
@@ -2,8 +2,10 @@ import PaymentPage from "@calcom/features/ee/payments/components/PaymentPage";
|
|||||||
import { getServerSideProps } from "@calcom/features/ee/payments/pages/payment";
|
import { getServerSideProps } from "@calcom/features/ee/payments/pages/payment";
|
||||||
import type { inferSSRProps } from "@calcom/types/inferSSRProps";
|
import type { inferSSRProps } from "@calcom/types/inferSSRProps";
|
||||||
|
|
||||||
|
import PageWrapper from "@components/PageWrapper";
|
||||||
|
|
||||||
export default function Payment(props: inferSSRProps<typeof getServerSideProps>) {
|
export default function Payment(props: inferSSRProps<typeof getServerSideProps>) {
|
||||||
return <PaymentPage {...props} />;
|
return <PaymentPage {...props} />;
|
||||||
}
|
}
|
||||||
|
Payment.PageWrapper = PageWrapper;
|
||||||
export { getServerSideProps };
|
export { getServerSideProps };
|
||||||
@@ -2,6 +2,7 @@ import AdminAppsList from "@calcom/features/apps/AdminAppsList";
|
|||||||
import { useLocale } from "@calcom/lib/hooks/useLocale";
|
import { useLocale } from "@calcom/lib/hooks/useLocale";
|
||||||
import { Meta } from "@calcom/ui";
|
import { Meta } from "@calcom/ui";
|
||||||
|
|
||||||
|
import PageWrapper from "@components/PageWrapper";
|
||||||
import { getLayout } from "@components/auth/layouts/AdminLayout";
|
import { getLayout } from "@components/auth/layouts/AdminLayout";
|
||||||
|
|
||||||
function AdminAppsView() {
|
function AdminAppsView() {
|
||||||
@@ -15,5 +16,6 @@ function AdminAppsView() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
AdminAppsView.getLayout = getLayout;
|
AdminAppsView.getLayout = getLayout;
|
||||||
|
AdminAppsView.PageWrapper = PageWrapper;
|
||||||
|
|
||||||
export default AdminAppsView;
|
export default AdminAppsView;
|
||||||
@@ -1,9 +1,11 @@
|
|||||||
import { FlagListingView } from "@calcom/features/flags/pages/flag-listing-view";
|
import { FlagListingView } from "@calcom/features/flags/pages/flag-listing-view";
|
||||||
|
|
||||||
|
import PageWrapper from "@components/PageWrapper";
|
||||||
import { getLayout } from "@components/auth/layouts/AdminLayout";
|
import { getLayout } from "@components/auth/layouts/AdminLayout";
|
||||||
|
|
||||||
const FlagsPage = () => <FlagListingView />;
|
const FlagsPage = () => <FlagListingView />;
|
||||||
|
|
||||||
FlagsPage.getLayout = getLayout;
|
FlagsPage.getLayout = getLayout;
|
||||||
|
FlagsPage.PageWrapper = PageWrapper;
|
||||||
|
|
||||||
export default FlagsPage;
|
export default FlagsPage;
|
||||||
@@ -4,6 +4,7 @@ import { useRef } from "react";
|
|||||||
import { useLocale } from "@calcom/lib/hooks/useLocale";
|
import { useLocale } from "@calcom/lib/hooks/useLocale";
|
||||||
import { Button, Meta, TextField } from "@calcom/ui";
|
import { Button, Meta, TextField } from "@calcom/ui";
|
||||||
|
|
||||||
|
import PageWrapper from "@components/PageWrapper";
|
||||||
import { getLayout } from "@components/auth/layouts/AdminLayout";
|
import { getLayout } from "@components/auth/layouts/AdminLayout";
|
||||||
|
|
||||||
function AdminView() {
|
function AdminView() {
|
||||||
@@ -37,5 +38,6 @@ function AdminView() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
AdminView.getLayout = getLayout;
|
AdminView.getLayout = getLayout;
|
||||||
|
AdminView.PageWrapper = PageWrapper;
|
||||||
|
|
||||||
export default AdminView;
|
export default AdminView;
|
||||||
@@ -1,5 +1,6 @@
|
|||||||
import { Meta } from "@calcom/ui";
|
import { Meta } from "@calcom/ui";
|
||||||
|
|
||||||
|
import PageWrapper from "@components/PageWrapper";
|
||||||
import { getLayout } from "@components/auth/layouts/AdminLayout";
|
import { getLayout } from "@components/auth/layouts/AdminLayout";
|
||||||
|
|
||||||
function AdminAppsView() {
|
function AdminAppsView() {
|
||||||
@@ -12,5 +13,6 @@ function AdminAppsView() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
AdminAppsView.getLayout = getLayout;
|
AdminAppsView.getLayout = getLayout;
|
||||||
|
AdminAppsView.PageWrapper = PageWrapper;
|
||||||
|
|
||||||
export default AdminAppsView;
|
export default AdminAppsView;
|
||||||
@@ -0,0 +1,9 @@
|
|||||||
|
import UsersEditView from "@calcom/features/ee/users/pages/users-edit-view";
|
||||||
|
|
||||||
|
import type { CalPageWrapper } from "@components/PageWrapper";
|
||||||
|
import PageWrapper from "@components/PageWrapper";
|
||||||
|
|
||||||
|
const Page = UsersEditView as CalPageWrapper;
|
||||||
|
Page.PageWrapper = PageWrapper;
|
||||||
|
|
||||||
|
export default Page;
|
||||||
@@ -0,0 +1,9 @@
|
|||||||
|
import UsersAddView from "@calcom/features/ee/users/pages/users-add-view";
|
||||||
|
|
||||||
|
import type { CalPageWrapper } from "@components/PageWrapper";
|
||||||
|
import PageWrapper from "@components/PageWrapper";
|
||||||
|
|
||||||
|
const Page = UsersAddView as CalPageWrapper;
|
||||||
|
Page.PageWrapper = PageWrapper;
|
||||||
|
|
||||||
|
export default Page;
|
||||||
@@ -0,0 +1,9 @@
|
|||||||
|
import UsersListingView from "@calcom/features/ee/users/pages/users-listing-view";
|
||||||
|
|
||||||
|
import type { CalPageWrapper } from "@components/PageWrapper";
|
||||||
|
import PageWrapper from "@components/PageWrapper";
|
||||||
|
|
||||||
|
const Page = UsersListingView as CalPageWrapper;
|
||||||
|
Page.PageWrapper = PageWrapper;
|
||||||
|
|
||||||
|
export default Page;
|
||||||
@@ -8,6 +8,8 @@ import { useLocale } from "@calcom/lib/hooks/useLocale";
|
|||||||
import { Button, Meta } from "@calcom/ui";
|
import { Button, Meta } from "@calcom/ui";
|
||||||
import { ExternalLink } from "@calcom/ui/components/icon";
|
import { ExternalLink } from "@calcom/ui/components/icon";
|
||||||
|
|
||||||
|
import PageWrapper from "@components/PageWrapper";
|
||||||
|
|
||||||
interface CtaRowProps {
|
interface CtaRowProps {
|
||||||
title: string;
|
title: string;
|
||||||
description: string;
|
description: string;
|
||||||
@@ -37,8 +39,8 @@ const BillingView = () => {
|
|||||||
const returnTo = router.asPath;
|
const returnTo = router.asPath;
|
||||||
const billingHref = `/api/integrations/stripepayment/portal?returnTo=${WEBAPP_URL}${returnTo}`;
|
const billingHref = `/api/integrations/stripepayment/portal?returnTo=${WEBAPP_URL}${returnTo}`;
|
||||||
|
|
||||||
const onContactSupportClick = () => {
|
const onContactSupportClick = async () => {
|
||||||
open();
|
await open();
|
||||||
};
|
};
|
||||||
|
|
||||||
return (
|
return (
|
||||||
@@ -64,5 +66,6 @@ const BillingView = () => {
|
|||||||
};
|
};
|
||||||
|
|
||||||
BillingView.getLayout = getLayout;
|
BillingView.getLayout = getLayout;
|
||||||
|
BillingView.PageWrapper = PageWrapper;
|
||||||
|
|
||||||
export default BillingView;
|
export default BillingView;
|
||||||
@@ -1,7 +1,7 @@
|
|||||||
import { useState } from "react";
|
import { useState } from "react";
|
||||||
|
|
||||||
import type { TApiKeys } from "@calcom/ee/api-keys/components/ApiKeyListItem";
|
import type { TApiKeys } from "@calcom/ee/api-keys/components/ApiKeyListItem";
|
||||||
import LicenseRequired from "@calcom/ee/common/components/v2/LicenseRequired";
|
import LicenseRequired from "@calcom/ee/common/components/LicenseRequired";
|
||||||
import ApiKeyDialogForm from "@calcom/features/ee/api-keys/components/ApiKeyDialogForm";
|
import ApiKeyDialogForm from "@calcom/features/ee/api-keys/components/ApiKeyDialogForm";
|
||||||
import ApiKeyListItem from "@calcom/features/ee/api-keys/components/ApiKeyListItem";
|
import ApiKeyListItem from "@calcom/features/ee/api-keys/components/ApiKeyListItem";
|
||||||
import { getLayout } from "@calcom/features/settings/layouts/SettingsLayout";
|
import { getLayout } from "@calcom/features/settings/layouts/SettingsLayout";
|
||||||
@@ -18,6 +18,8 @@ import {
|
|||||||
} from "@calcom/ui";
|
} from "@calcom/ui";
|
||||||
import { Link as LinkIcon, Plus } from "@calcom/ui/components/icon";
|
import { Link as LinkIcon, Plus } from "@calcom/ui/components/icon";
|
||||||
|
|
||||||
|
import PageWrapper from "@components/PageWrapper";
|
||||||
|
|
||||||
const ApiKeysView = () => {
|
const ApiKeysView = () => {
|
||||||
const { t } = useLocale();
|
const { t } = useLocale();
|
||||||
|
|
||||||
@@ -92,5 +94,6 @@ const ApiKeysView = () => {
|
|||||||
};
|
};
|
||||||
|
|
||||||
ApiKeysView.getLayout = getLayout;
|
ApiKeysView.getLayout = getLayout;
|
||||||
|
ApiKeysView.PageWrapper = PageWrapper;
|
||||||
|
|
||||||
export default ApiKeysView;
|
export default ApiKeysView;
|
||||||
@@ -1 +1,9 @@
|
|||||||
export { default } from "@calcom/features/webhooks/pages/webhook-edit-view";
|
import WebhookEditView from "@calcom/features/webhooks/pages/webhook-edit-view";
|
||||||
|
|
||||||
|
import type { CalPageWrapper } from "@components/PageWrapper";
|
||||||
|
import PageWrapper from "@components/PageWrapper";
|
||||||
|
|
||||||
|
const Page = WebhookEditView as CalPageWrapper;
|
||||||
|
Page.PageWrapper = PageWrapper;
|
||||||
|
|
||||||
|
export default Page;
|
||||||
@@ -1 +1,9 @@
|
|||||||
export { default } from "@calcom/features/webhooks/pages/webhooks-view";
|
import WeebhooksView from "@calcom/features/webhooks/pages/webhooks-view";
|
||||||
|
|
||||||
|
import type { CalPageWrapper } from "@components/PageWrapper";
|
||||||
|
import PageWrapper from "@components/PageWrapper";
|
||||||
|
|
||||||
|
const Page = WeebhooksView as CalPageWrapper;
|
||||||
|
Page.PageWrapper = PageWrapper;
|
||||||
|
|
||||||
|
export default Page;
|
||||||
@@ -1 +1,9 @@
|
|||||||
export { default } from "@calcom/features/webhooks/pages/webhook-new-view";
|
import WeebhookNewView from "@calcom/features/webhooks/pages/webhook-new-view";
|
||||||
|
|
||||||
|
import type { CalPageWrapper } from "@components/PageWrapper";
|
||||||
|
import PageWrapper from "@components/PageWrapper";
|
||||||
|
|
||||||
|
const Page = WeebhookNewView as CalPageWrapper;
|
||||||
|
Page.PageWrapper = PageWrapper;
|
||||||
|
|
||||||
|
export default Page;
|
||||||
@@ -22,6 +22,8 @@ import {
|
|||||||
UpgradeTeamsBadge,
|
UpgradeTeamsBadge,
|
||||||
} from "@calcom/ui";
|
} from "@calcom/ui";
|
||||||
|
|
||||||
|
import PageWrapper from "@components/PageWrapper";
|
||||||
|
|
||||||
const SkeletonLoader = ({ title, description }: { title: string; description: string }) => {
|
const SkeletonLoader = ({ title, description }: { title: string; description: string }) => {
|
||||||
return (
|
return (
|
||||||
<SkeletonContainer>
|
<SkeletonContainer>
|
||||||
@@ -50,6 +52,7 @@ const AppearanceView = () => {
|
|||||||
const utils = trpc.useContext();
|
const utils = trpc.useContext();
|
||||||
const { data: user, isLoading } = trpc.viewer.me.useQuery();
|
const { data: user, isLoading } = trpc.viewer.me.useQuery();
|
||||||
const [darkModeError, setDarkModeError] = useState(false);
|
const [darkModeError, setDarkModeError] = useState(false);
|
||||||
|
const [lightModeError, setLightModeError] = useState(false);
|
||||||
|
|
||||||
const { isLoading: isTeamPlanStatusLoading, hasPaidPlan } = useHasPaidPlan();
|
const { isLoading: isTeamPlanStatusLoading, hasPaidPlan } = useHasPaidPlan();
|
||||||
|
|
||||||
@@ -143,7 +146,14 @@ const AppearanceView = () => {
|
|||||||
<p className="text-default mb-2 block text-sm font-medium">{t("light_brand_color")}</p>
|
<p className="text-default mb-2 block text-sm font-medium">{t("light_brand_color")}</p>
|
||||||
<ColorPicker
|
<ColorPicker
|
||||||
defaultValue={user.brandColor}
|
defaultValue={user.brandColor}
|
||||||
onChange={(value) => formMethods.setValue("brandColor", value, { shouldDirty: true })}
|
onChange={(value) => {
|
||||||
|
if (!checkWCAGContrastColor("#ffffff", value)) {
|
||||||
|
setLightModeError(true);
|
||||||
|
} else {
|
||||||
|
setLightModeError(false);
|
||||||
|
}
|
||||||
|
formMethods.setValue("brandColor", value, { shouldDirty: true });
|
||||||
|
}}
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
)}
|
)}
|
||||||
@@ -178,6 +188,14 @@ const AppearanceView = () => {
|
|||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
) : null}
|
) : null}
|
||||||
|
{lightModeError ? (
|
||||||
|
<div className="mt-4">
|
||||||
|
<Alert
|
||||||
|
severity="warning"
|
||||||
|
message="Light Theme color doesn't pass contrast check. We recommend you change this colour so your buttons will be more visible."
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
) : null}
|
||||||
{/* TODO future PR to preview brandColors */}
|
{/* TODO future PR to preview brandColors */}
|
||||||
{/* <Button
|
{/* <Button
|
||||||
color="secondary"
|
color="secondary"
|
||||||
@@ -230,5 +248,6 @@ const AppearanceView = () => {
|
|||||||
};
|
};
|
||||||
|
|
||||||
AppearanceView.getLayout = getLayout;
|
AppearanceView.getLayout = getLayout;
|
||||||
|
AppearanceView.PageWrapper = PageWrapper;
|
||||||
|
|
||||||
export default AppearanceView;
|
export default AppearanceView;
|
||||||
Loaded 100 of 331 files, more files were not shown because too many files have changed in this diff.
Show more
Reference in new issue
Block a user