Compare commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
b83ee2d57d | ||
|
|
5f39bbe730 | ||
|
|
cf3d8cb4bb | ||
|
|
fbe0018d01 | ||
|
|
6586ed4d87 | ||
|
|
81e57afcf4 | ||
|
|
5cc530593b | ||
|
|
4111f78431 | ||
|
|
def80ea4cf | ||
|
|
2fc4676f6d | ||
|
|
318bafaab8 | ||
|
|
fc10c29037 | ||
|
|
ccb7b1f9ac | ||
|
|
c6c6544f50 | ||
|
|
8a6b93a747 | ||
|
|
ff6c6ccec0 | ||
|
|
c8db177f7d | ||
|
|
8871e1eb21 | ||
|
|
92b2d57871 | ||
|
|
77623c72c5 | ||
|
|
3239976e18 | ||
|
|
611939ffe8 | ||
|
|
1b8ecbe449 | ||
|
|
cd4882429d | ||
|
|
4fd2d80637 | ||
|
|
a343d934fe | ||
|
|
933cc739bd | ||
|
|
c7c75ff4d3 | ||
|
|
41f1920a7a | ||
|
|
e4836ea8f3 | ||
|
|
bfa30aa504 | ||
|
|
b36043b637 | ||
|
|
75026289fd | ||
|
|
80649c9f1a | ||
|
|
7dd75afb63 | ||
|
|
f66e4aeeed | ||
|
|
8225e61710 | ||
|
|
175fc4fcf3 | ||
|
|
f20a5df322 | ||
|
|
62544931b2 | ||
|
|
f6ad1d6ed5 | ||
|
|
d22b1072b5 | ||
|
|
4b356dd937 | ||
|
|
1f51199a7b | ||
|
|
b80a631998 | ||
|
|
bef4422080 | ||
|
|
cfd1a72c64 | ||
|
|
23b3a6661c | ||
|
|
b8b6c48d7d | ||
|
|
81655f9988 | ||
|
|
ff74822c48 | ||
|
|
8ec178debd | ||
|
|
f881061d8d | ||
|
|
2525559d6b | ||
|
|
38fb7a3f77 | ||
|
|
08956e54c5 | ||
|
|
ea65a2a27e | ||
|
|
0d45cfb7ea | ||
|
|
a4fc25e260 | ||
|
|
796c932311 | ||
|
|
4137f63ec7 | ||
|
|
2147cc82a5 | ||
|
|
40e646e9a4 | ||
|
|
cb2225259c | ||
|
|
372cd94d9f | ||
|
|
c7c282f30f | ||
|
|
959488910f | ||
|
|
0ca0cc3265 | ||
|
|
69bc12187e | ||
|
|
5e772530b2 | ||
|
|
3088b1174a | ||
|
|
fadf12b2bd | ||
|
|
c81f30b092 | ||
|
|
476119d627 | ||
|
|
040f03a9b5 | ||
|
|
b552bf6956 | ||
|
|
82d3c4370d | ||
|
|
492b4d3e97 | ||
|
|
0ae319d206 | ||
|
|
749311be2a | ||
|
|
3cce4e5009 | ||
|
|
341f82ccb9 | ||
|
|
e028a339f5 | ||
|
|
c55e432bc3 | ||
|
|
f47c3cd538 | ||
|
|
4b70a20941 | ||
|
|
c873b83965 | ||
|
|
ff859737ec | ||
|
|
607ef71f9f | ||
|
|
24dd4002fa | ||
|
|
078c9ddca5 | ||
|
|
b44e5a7aae | ||
|
|
4374da08e2 | ||
|
|
98b531e6b5 | ||
|
|
74414db760 | ||
|
|
bdf3e34ea1 | ||
|
|
738e0f3e50 | ||
|
|
16ae19abdb | ||
|
|
a2e18ae9ba | ||
|
|
e2cfd0d1d1 | ||
|
|
067b77faf1 | ||
|
|
740c01ac29 | ||
|
|
97cfd1ffa7 | ||
|
|
a7b2cfc90f | ||
|
|
d067b0782a | ||
|
|
203a36e2b5 | ||
|
|
f5a38c3f89 | ||
|
|
f179c23cc5 | ||
|
|
9f83885e8c | ||
|
|
8ffb5529e6 | ||
|
|
2e686d03fa | ||
|
|
cc3be0a6ac | ||
|
|
ec3a3268c9 | ||
|
|
77eed5611e | ||
|
|
a827d1defb | ||
|
|
b08a62d30b | ||
|
|
d6fb0df64f | ||
|
|
03548d8381 | ||
|
|
36d8554fd5 | ||
|
|
6fc9e9315d | ||
|
|
6eb8119a82 | ||
|
|
f4c613e6d1 | ||
|
|
082507aea5 | ||
|
|
90c1270e3d | ||
|
|
94917dbb1e | ||
|
|
fbbcc401cb | ||
|
|
af7d7ec678 | ||
|
|
3b7d85852a | ||
|
|
5362cd66b4 | ||
|
|
2907405d4e | ||
|
|
3c46c39114 | ||
|
|
c6ab889b98 | ||
|
|
a2e2b0ed34 | ||
|
|
bc38b3bc55 | ||
|
|
7611d5c24e | ||
|
|
5d2fd7aafb | ||
|
|
b9ade55ac1 | ||
|
|
d7051c4a3f | ||
|
|
ab50067194 | ||
|
|
cdba1920fc | ||
|
|
98a3508b8f | ||
|
|
446f384405 | ||
|
|
be79b8ecd6 | ||
|
|
371e7b024a | ||
|
|
76807a7e70 | ||
|
|
d3f77bf292 | ||
|
|
2f175eacf5 | ||
|
|
b45aea3f26 | ||
|
|
c401a58bb1 | ||
|
|
05b42c001f | ||
|
|
20f7a5841e | ||
|
|
dbf6bb991f | ||
|
|
75cd5a05b8 | ||
|
|
ca6afed27c | ||
|
|
45250f6f3e | ||
|
|
bd5af9e821 | ||
|
|
4267c9063b | ||
|
|
2b49d96e4a | ||
|
|
127c96b46c | ||
|
|
3595ba9824 | ||
|
|
56acfa38f1 | ||
|
|
e78fb22451 | ||
|
|
99f736b317 | ||
|
|
ee9f80ae3b | ||
|
|
a3c283da5e | ||
|
|
0ef678b2cf | ||
|
|
4f466fd95d | ||
|
|
5672a721d9 | ||
|
|
5ad45900ea | ||
|
|
ada88a2f7e | ||
|
|
9ba90e18de | ||
|
|
a88eb0fdc5 | ||
|
|
4b4c1200cd | ||
|
|
1cea7f9760 | ||
|
|
28dcf69d18 | ||
|
|
83d5176ddb | ||
|
|
3815a4c4bf | ||
|
|
53748eb380 | ||
|
|
28af11f90e | ||
|
|
ea676907b2 | ||
|
|
221c8b75bc | ||
|
|
5b9b7ab22d | ||
|
|
668ea088ef | ||
|
|
8a6c420268 | ||
|
|
d2f8a5c076 | ||
|
|
9c0f7bbfc7 | ||
|
|
8297824051 | ||
|
|
7ff5b66f46 | ||
|
|
55570ed4bf | ||
|
|
51695ee79d | ||
|
|
543466f1b9 | ||
|
|
4abf2d67d9 | ||
|
|
1eeb91a793 | ||
|
|
a4725920ff | ||
|
|
4cc7766f88 | ||
|
|
0c78715e9a | ||
|
|
671d9b4903 | ||
|
|
524cefcbad | ||
|
|
72f72d86b2 | ||
|
|
c5c29322d3 | ||
|
|
863b04e9c1 | ||
|
|
c2833f1b44 | ||
|
|
25df264823 | ||
|
|
e870312ebe | ||
|
|
5371bff6ff | ||
|
|
c791344c8b | ||
|
|
5e149d9f5f |
No files matched your search
@@ -0,0 +1,8 @@
|
|||||||
|
# Changesets
|
||||||
|
|
||||||
|
Hello and welcome! This folder has been automatically generated by `@changesets/cli`, a build tool that works
|
||||||
|
with multi-package repos, or single-package repos to help you version and publish your code. You can
|
||||||
|
find the full documentation for it [in our repository](https://github.com/changesets/changesets)
|
||||||
|
|
||||||
|
We have a quick list of common questions to get you started engaging with this project in
|
||||||
|
[our documentation](https://github.com/changesets/changesets/blob/main/docs/common-questions.md)
|
||||||
@@ -0,0 +1,11 @@
|
|||||||
|
{
|
||||||
|
"$schema": "https://unpkg.com/@changesets/config@2.3.0/schema.json",
|
||||||
|
"changelog": "@changesets/cli/changelog",
|
||||||
|
"commit": false,
|
||||||
|
"fixed": [],
|
||||||
|
"linked": [],
|
||||||
|
"access": "public",
|
||||||
|
"baseBranch": "main",
|
||||||
|
"updateInternalDependencies": "patch",
|
||||||
|
"ignore": []
|
||||||
|
}
|
||||||
@@ -1,42 +0,0 @@
|
|||||||
// For format details, see https://aka.ms/devcontainer.json. For config options, see the
|
|
||||||
// README at: https://github.com/devcontainers/templates/tree/main/src/docker-outside-of-docker
|
|
||||||
{
|
|
||||||
"name": "Docker outside of Docker",
|
|
||||||
// Or use a Dockerfile or Docker Compose file. More info: https://containers.dev/guide/dockerfile
|
|
||||||
"image": "mcr.microsoft.com/devcontainers/base:bullseye",
|
|
||||||
|
|
||||||
"features": {
|
|
||||||
"ghcr.io/devcontainers/features/docker-from-docker:1": {
|
|
||||||
"version": "latest",
|
|
||||||
"enableNonRootDocker": "true",
|
|
||||||
"moby": "true"
|
|
||||||
},
|
|
||||||
"ghcr.io/devcontainers/features/node:1": {},
|
|
||||||
"ghcr.io/devcontainers-contrib/features/npm-package:1": {},
|
|
||||||
"ghcr.io/devcontainers-contrib/features/jest:2": {},
|
|
||||||
"ghcr.io/devcontainers-contrib/features/prisma:2": {},
|
|
||||||
"ghcr.io/guiyomh/features/vim:0": {}
|
|
||||||
},
|
|
||||||
|
|
||||||
// Use this environment variable if you need to bind mount your local source code into a new container.
|
|
||||||
"remoteEnv": {
|
|
||||||
"LOCAL_WORKSPACE_FOLDER": "${localWorkspaceFolder}"
|
|
||||||
},
|
|
||||||
|
|
||||||
"hostRequirements": {
|
|
||||||
"cpus": 4,
|
|
||||||
"memory": "8gb"
|
|
||||||
},
|
|
||||||
|
|
||||||
// Use 'postCreateCommand' to run commands after the container is created.
|
|
||||||
"postCreateCommand": "./deploy/install.sh"
|
|
||||||
|
|
||||||
// Use 'forwardPorts' to make a list of ports inside the container available locally.
|
|
||||||
// "forwardPorts": [],
|
|
||||||
|
|
||||||
// Configure tool-specific properties.
|
|
||||||
// "customizations": {},
|
|
||||||
|
|
||||||
// Uncomment to connect as root instead. More info: https://aka.ms/dev-containers-non-root.
|
|
||||||
// "remoteUser": "root"
|
|
||||||
}
|
|
||||||
@@ -16,11 +16,12 @@
|
|||||||
# - WEB3
|
# - WEB3
|
||||||
# - SALESFORCE
|
# - SALESFORCE
|
||||||
# - ZOHOCRM
|
# - ZOHOCRM
|
||||||
|
# - ZOHO_BIGIN
|
||||||
|
|
||||||
# - APP STORE **********************************************************************************************
|
# - APP STORE **********************************************************************************************
|
||||||
# ⚠️ ⚠️ ⚠️ THESE WILL BE MIGRATED TO THE DATABASE TO PREVENT AWS's 4KB ENV QUOTA ⚠️ ⚠️ ⚠️
|
# ⚠️ ⚠️ ⚠️ THESE WILL BE MIGRATED TO THE DATABASE TO PREVENT AWS's 4KB ENV QUOTA ⚠️ ⚠️ ⚠️
|
||||||
# - DAILY.CO VIDEO
|
# - DAILY.CO VIDEO
|
||||||
# Enables Cal Video. to get your key
|
# Enables Cal Video. to get your key
|
||||||
# 1. Visit our [Daily.co Partnership Form](https://go.cal.com/daily) and enter your information
|
# 1. Visit our [Daily.co Partnership Form](https://go.cal.com/daily) and enter your information
|
||||||
# 2. From within your dashboard, go to the [developers](https://dashboard.daily.co/developers) tab.
|
# 2. From within your dashboard, go to the [developers](https://dashboard.daily.co/developers) tab.
|
||||||
# @see https://github.com/calcom/cal.com#obtaining-daily-api-credentials
|
# @see https://github.com/calcom/cal.com#obtaining-daily-api-credentials
|
||||||
|
|||||||
+8
-4
@@ -138,12 +138,16 @@ API_KEY_PREFIX=cal_
|
|||||||
EMAIL_FROM='notifications@yourselfhostedcal.com'
|
EMAIL_FROM='notifications@yourselfhostedcal.com'
|
||||||
|
|
||||||
# Configure SMTP settings (@see https://nodemailer.com/smtp/).
|
# Configure SMTP settings (@see https://nodemailer.com/smtp/).
|
||||||
|
# Configuration to receive emails locally (mailhog)
|
||||||
|
EMAIL_SERVER_HOST='localhost'
|
||||||
|
EMAIL_SERVER_PORT=1025
|
||||||
|
|
||||||
# Note: The below configuration for Office 365 has been verified to work.
|
# Note: The below configuration for Office 365 has been verified to work.
|
||||||
EMAIL_SERVER_HOST='smtp.office365.com'
|
# EMAIL_SERVER_HOST='smtp.office365.com'
|
||||||
EMAIL_SERVER_PORT=587
|
# EMAIL_SERVER_PORT=587
|
||||||
EMAIL_SERVER_USER='<office365_emailAddress>'
|
# EMAIL_SERVER_USER='<office365_emailAddress>'
|
||||||
# Keep in mind that if you have 2FA enabled, you will need to provision an App Password.
|
# Keep in mind that if you have 2FA enabled, you will need to provision an App Password.
|
||||||
EMAIL_SERVER_PASSWORD='<office365_password>'
|
# EMAIL_SERVER_PASSWORD='<office365_password>'
|
||||||
|
|
||||||
# The following configuration for Gmail has been verified to work.
|
# The following configuration for Gmail has been verified to work.
|
||||||
# EMAIL_SERVER_HOST='smtp.gmail.com'
|
# EMAIL_SERVER_HOST='smtp.gmail.com'
|
||||||
|
|||||||
@@ -7,6 +7,7 @@ on:
|
|||||||
paths-ignore:
|
paths-ignore:
|
||||||
- "**.md"
|
- "**.md"
|
||||||
- ".github/CODEOWNERS"
|
- ".github/CODEOWNERS"
|
||||||
|
merge_group:
|
||||||
workflow_dispatch:
|
workflow_dispatch:
|
||||||
|
|
||||||
concurrency:
|
concurrency:
|
||||||
|
|||||||
@@ -1,28 +0,0 @@
|
|||||||
name: TODO to Issue
|
|
||||||
on:
|
|
||||||
push:
|
|
||||||
branches:
|
|
||||||
- main
|
|
||||||
workflow_dispatch:
|
|
||||||
inputs:
|
|
||||||
MANUAL_COMMIT_REF:
|
|
||||||
description: "The SHA of the commit to get the diff for"
|
|
||||||
required: true
|
|
||||||
MANUAL_BASE_REF:
|
|
||||||
description: "By default, the commit entered above is compared to the one directly before it; to go back further, enter an earlier SHA here"
|
|
||||||
required: false
|
|
||||||
concurrency:
|
|
||||||
group: ${{ github.workflow }}-${{ github.ref }}
|
|
||||||
cancel-in-progress: true
|
|
||||||
jobs:
|
|
||||||
todos:
|
|
||||||
runs-on: ubuntu-latest
|
|
||||||
steps:
|
|
||||||
- uses: actions/checkout@v3
|
|
||||||
- name: "Add TODOs to issues"
|
|
||||||
uses: "alstr/todo-to-issue-action@v4"
|
|
||||||
env:
|
|
||||||
MANUAL_COMMIT_REF: ${{ inputs.MANUAL_COMMIT_REF }}
|
|
||||||
MANUAL_BASE_REF: ${{ inputs.MANUAL_BASE_REF }}
|
|
||||||
with:
|
|
||||||
IDENTIFIERS: '[{"name": "TODO", "labels": ["🙋🏻♂️help wanted"]}, {"name": "FIXME", "labels": ["🐛 bug"]}]'
|
|
||||||
+2
-1
@@ -65,8 +65,9 @@ tsconfig.tsbuildinfo
|
|||||||
# turbo
|
# turbo
|
||||||
.turbo
|
.turbo
|
||||||
|
|
||||||
# Prisma-Zod
|
# Prisma generated files
|
||||||
packages/prisma/zod/*.ts
|
packages/prisma/zod/*.ts
|
||||||
|
packages/prisma/enums
|
||||||
|
|
||||||
# Builds
|
# Builds
|
||||||
dist
|
dist
|
||||||
|
|||||||
+43
@@ -0,0 +1,43 @@
|
|||||||
|
tasks:
|
||||||
|
- init: |
|
||||||
|
yarn &&
|
||||||
|
cp .env.example .env &&
|
||||||
|
next_auth_secret=$(openssl rand -base64 32) &&
|
||||||
|
calendso_encryption_key=$(openssl rand -base64 24) &&
|
||||||
|
sed -i -e "s|^NEXTAUTH_SECRET=.*|NEXTAUTH_SECRET=$next_auth_secret|" \
|
||||||
|
-e "s|^CALENDSO_ENCRYPTION_KEY=.*|CALENDSO_ENCRYPTION_KEY=$calendso_encryption_key|" .env
|
||||||
|
command: yarn dx
|
||||||
|
|
||||||
|
ports:
|
||||||
|
- port: 3000
|
||||||
|
visibility: public
|
||||||
|
onOpen: open-preview
|
||||||
|
- port: 5420
|
||||||
|
visibility: private
|
||||||
|
onOpen: ignore
|
||||||
|
- port: 1025
|
||||||
|
visibility: private
|
||||||
|
onOpen: ignore
|
||||||
|
- port: 8025
|
||||||
|
visibility: private
|
||||||
|
onOpen: ignore
|
||||||
|
|
||||||
|
github:
|
||||||
|
prebuilds:
|
||||||
|
master: true
|
||||||
|
pullRequests: true
|
||||||
|
pullRequestsFromForks: true
|
||||||
|
addCheck: true
|
||||||
|
addComment: true
|
||||||
|
addBadge: true
|
||||||
|
|
||||||
|
vscode:
|
||||||
|
extensions:
|
||||||
|
- DavidAnson.vscode-markdownlint
|
||||||
|
- yzhang.markdown-all-in-one
|
||||||
|
- esbenp.prettier-vscode
|
||||||
|
- dbaeumer.vscode-eslint
|
||||||
|
- bradlc.vscode-tailwindcss
|
||||||
|
- ban.spellright
|
||||||
|
- stripe.vscode-stripe
|
||||||
|
- Prisma.prisma
|
||||||
@@ -14,4 +14,5 @@ public
|
|||||||
.DS_Store
|
.DS_Store
|
||||||
.eslintignore
|
.eslintignore
|
||||||
packages/prisma/zod
|
packages/prisma/zod
|
||||||
|
packages/prisma/enums
|
||||||
apps/web/public/embed
|
apps/web/public/embed
|
||||||
Vendored
+1
-1
@@ -6,6 +6,6 @@
|
|||||||
},
|
},
|
||||||
"typescript.preferences.importModuleSpecifier": "non-relative",
|
"typescript.preferences.importModuleSpecifier": "non-relative",
|
||||||
"spellright.language": ["en"],
|
"spellright.language": ["en"],
|
||||||
"spellright.documentTypes": ["markdown", "typescript"],
|
"spellright.documentTypes": ["markdown", "typescript", "typescriptreact"],
|
||||||
"tailwindCSS.experimental.classRegex": [["cva\\(([^)]*)\\)", "[\"'`]([^\"'`]*).*?[\"'`]"]]
|
"tailwindCSS.experimental.classRegex": [["cva\\(([^)]*)\\)", "[\"'`]([^\"'`]*).*?[\"'`]"]]
|
||||||
}
|
}
|
||||||
@@ -0,0 +1,28 @@
|
|||||||
|
/* eslint-disable */
|
||||||
|
//prettier-ignore
|
||||||
|
module.exports = {
|
||||||
|
name: "@yarnpkg/plugin-workspace-tools",
|
||||||
|
factory: function (require) {
|
||||||
|
var plugin=(()=>{var yr=Object.create;var we=Object.defineProperty;var _r=Object.getOwnPropertyDescriptor;var Er=Object.getOwnPropertyNames;var br=Object.getPrototypeOf,xr=Object.prototype.hasOwnProperty;var W=(e=>typeof require<"u"?require:typeof Proxy<"u"?new Proxy(e,{get:(r,t)=>(typeof require<"u"?require:r)[t]}):e)(function(e){if(typeof require<"u")return require.apply(this,arguments);throw new Error('Dynamic require of "'+e+'" is not supported')});var q=(e,r)=>()=>(r||e((r={exports:{}}).exports,r),r.exports),Cr=(e,r)=>{for(var t in r)we(e,t,{get:r[t],enumerable:!0})},Je=(e,r,t,n)=>{if(r&&typeof r=="object"||typeof r=="function")for(let s of Er(r))!xr.call(e,s)&&s!==t&&we(e,s,{get:()=>r[s],enumerable:!(n=_r(r,s))||n.enumerable});return e};var Be=(e,r,t)=>(t=e!=null?yr(br(e)):{},Je(r||!e||!e.__esModule?we(t,"default",{value:e,enumerable:!0}):t,e)),wr=e=>Je(we({},"__esModule",{value:!0}),e);var ve=q(ee=>{"use strict";ee.isInteger=e=>typeof e=="number"?Number.isInteger(e):typeof e=="string"&&e.trim()!==""?Number.isInteger(Number(e)):!1;ee.find=(e,r)=>e.nodes.find(t=>t.type===r);ee.exceedsLimit=(e,r,t=1,n)=>n===!1||!ee.isInteger(e)||!ee.isInteger(r)?!1:(Number(r)-Number(e))/Number(t)>=n;ee.escapeNode=(e,r=0,t)=>{let n=e.nodes[r];!n||(t&&n.type===t||n.type==="open"||n.type==="close")&&n.escaped!==!0&&(n.value="\\"+n.value,n.escaped=!0)};ee.encloseBrace=e=>e.type!=="brace"?!1:e.commas>>0+e.ranges>>0===0?(e.invalid=!0,!0):!1;ee.isInvalidBrace=e=>e.type!=="brace"?!1:e.invalid===!0||e.dollar?!0:e.commas>>0+e.ranges>>0===0||e.open!==!0||e.close!==!0?(e.invalid=!0,!0):!1;ee.isOpenOrClose=e=>e.type==="open"||e.type==="close"?!0:e.open===!0||e.close===!0;ee.reduce=e=>e.reduce((r,t)=>(t.type==="text"&&r.push(t.value),t.type==="range"&&(t.type="text"),r),[]);ee.flatten=(...e)=>{let r=[],t=n=>{for(let s=0;s<n.length;s++){let i=n[s];Array.isArray(i)?t(i,r):i!==void 0&&r.push(i)}return r};return t(e),r}});var He=q((zn,rt)=>{"use strict";var tt=ve();rt.exports=(e,r={})=>{let t=(n,s={})=>{let i=r.escapeInvalid&&tt.isInvalidBrace(s),a=n.invalid===!0&&r.escapeInvalid===!0,c="";if(n.value)return(i||a)&&tt.isOpenOrClose(n)?"\\"+n.value:n.value;if(n.value)return n.value;if(n.nodes)for(let p of n.nodes)c+=t(p);return c};return t(e)}});var st=q((Vn,nt)=>{"use strict";nt.exports=function(e){return typeof e=="number"?e-e===0:typeof e=="string"&&e.trim()!==""?Number.isFinite?Number.isFinite(+e):isFinite(+e):!1}});var ht=q((Jn,pt)=>{"use strict";var at=st(),le=(e,r,t)=>{if(at(e)===!1)throw new TypeError("toRegexRange: expected the first argument to be a number");if(r===void 0||e===r)return String(e);if(at(r)===!1)throw new TypeError("toRegexRange: expected the second argument to be a number.");let n={relaxZeros:!0,...t};typeof n.strictZeros=="boolean"&&(n.relaxZeros=n.strictZeros===!1);let s=String(n.relaxZeros),i=String(n.shorthand),a=String(n.capture),c=String(n.wrap),p=e+":"+r+"="+s+i+a+c;if(le.cache.hasOwnProperty(p))return le.cache[p].result;let m=Math.min(e,r),h=Math.max(e,r);if(Math.abs(m-h)===1){let y=e+"|"+r;return n.capture?`(${y})`:n.wrap===!1?y:`(?:${y})`}let R=ft(e)||ft(r),f={min:e,max:r,a:m,b:h},$=[],_=[];if(R&&(f.isPadded=R,f.maxLen=String(f.max).length),m<0){let y=h<0?Math.abs(h):1;_=it(y,Math.abs(m),f,n),m=f.a=0}return h>=0&&($=it(m,h,f,n)),f.negatives=_,f.positives=$,f.result=Sr(_,$,n),n.capture===!0?f.result=`(${f.result})`:n.wrap!==!1&&$.length+_.length>1&&(f.result=`(?:${f.result})`),le.cache[p]=f,f.result};function Sr(e,r,t){let n=Pe(e,r,"-",!1,t)||[],s=Pe(r,e,"",!1,t)||[],i=Pe(e,r,"-?",!0,t)||[];return n.concat(i).concat(s).join("|")}function vr(e,r){let t=1,n=1,s=ut(e,t),i=new Set([r]);for(;e<=s&&s<=r;)i.add(s),t+=1,s=ut(e,t);for(s=ct(r+1,n)-1;e<s&&s<=r;)i.add(s),n+=1,s=ct(r+1,n)-1;return i=[...i],i.sort(Tr),i}function Hr(e,r,t){if(e===r)return{pattern:e,count:[],digits:0};let n=$r(e,r),s=n.length,i="",a=0;for(let c=0;c<s;c++){let[p,m]=n[c];p===m?i+=p:p!=="0"||m!=="9"?i+=kr(p,m,t):a++}return a&&(i+=t.shorthand===!0?"\\d":"[0-9]"),{pattern:i,count:[a],digits:s}}function it(e,r,t,n){let s=vr(e,r),i=[],a=e,c;for(let p=0;p<s.length;p++){let m=s[p],h=Hr(String(a),String(m),n),R="";if(!t.isPadded&&c&&c.pattern===h.pattern){c.count.length>1&&c.count.pop(),c.count.push(h.count[0]),c.string=c.pattern+lt(c.count),a=m+1;continue}t.isPadded&&(R=Lr(m,t,n)),h.string=R+h.pattern+lt(h.count),i.push(h),a=m+1,c=h}return i}function Pe(e,r,t,n,s){let i=[];for(let a of e){let{string:c}=a;!n&&!ot(r,"string",c)&&i.push(t+c),n&&ot(r,"string",c)&&i.push(t+c)}return i}function $r(e,r){let t=[];for(let n=0;n<e.length;n++)t.push([e[n],r[n]]);return t}function Tr(e,r){return e>r?1:r>e?-1:0}function ot(e,r,t){return e.some(n=>n[r]===t)}function ut(e,r){return Number(String(e).slice(0,-r)+"9".repeat(r))}function ct(e,r){return e-e%Math.pow(10,r)}function lt(e){let[r=0,t=""]=e;return t||r>1?`{${r+(t?","+t:"")}}`:""}function kr(e,r,t){return`[${e}${r-e===1?"":"-"}${r}]`}function ft(e){return/^-?(0+)\d/.test(e)}function Lr(e,r,t){if(!r.isPaddLine truncated
|
||||||
|
`,CHAR_NO_BREAK_SPACE:"\xA0",CHAR_PERCENT:"%",CHAR_PLUS:"+",CHAR_QUESTION_MARK:"?",CHAR_RIGHT_ANGLE_BRACKET:">",CHAR_RIGHT_CURLY_BRACE:"}",CHAR_RIGHT_SQUARE_BRACKET:"]",CHAR_SEMICOLON:";",CHAR_SINGLE_QUOTE:"'",CHAR_SPACE:" ",CHAR_TAB:" ",CHAR_UNDERSCORE:"_",CHAR_VERTICAL_LINE:"|",CHAR_ZERO_WIDTH_NOBREAK_SPACE:"\uFEFF"}});var Nt=q((ss,Ot)=>{"use strict";var jr=He(),{MAX_LENGTH:Tt,CHAR_BACKSLASH:Ge,CHAR_BACKTICK:Fr,CHAR_COMMA:Qr,CHAR_DOT:Xr,CHAR_LEFT_PARENTHESES:Zr,CHAR_RIGHT_PARENTHESES:Yr,CHAR_LEFT_CURLY_BRACE:zr,CHAR_RIGHT_CURLY_BRACE:Vr,CHAR_LEFT_SQUARE_BRACKET:kt,CHAR_RIGHT_SQUARE_BRACKET:Lt,CHAR_DOUBLE_QUOTE:Jr,CHAR_SINGLE_QUOTE:en,CHAR_NO_BREAK_SPACE:tn,CHAR_ZERO_WIDTH_NOBREAK_SPACE:rn}=$t(),nn=(e,r={})=>{if(typeof e!="string")throw new TypeError("Expected a string");let t=r||{},n=typeof t.maxLength=="number"?Math.min(Tt,t.maxLength):Tt;if(e.length>n)throw new SyntaxError(`Input length (${e.length}), exceeds max characters (${n})`);let s={type:"root",input:e,nodes:[]},i=[s],a=s,c=s,p=0,m=e.length,h=0,R=0,f,$={},_=()=>e[h++],y=E=>{if(E.type==="text"&&c.type==="dot"&&(c.type="text"),c&&c.type==="text"&&E.type==="text"){c.value+=E.value;return}return a.nodes.push(E),E.parent=a,E.prev=c,c=E,E};for(y({type:"bos"});h<m;)if(a=i[i.length-1],f=_(),!(f===rn||f===tn)){if(f===Ge){y({type:"text",value:(r.keepEscaping?f:"")+_()});continue}if(f===Lt){y({type:"text",value:"\\"+f});continue}if(f===kt){p++;let E=!0,S;for(;h<m&&(S=_());){if(f+=S,S===kt){p++;continue}if(S===Ge){f+=_();continue}if(S===Lt&&(p--,p===0))break}y({type:"text",value:f});continue}if(f===Zr){a=y({type:"paren",nodes:[]}),i.push(a),y({type:"text",value:f});continue}if(f===Yr){if(a.type!=="paren"){y({type:"text",value:f});continue}a=i.pop(),y({type:"text",value:f}),a=i[i.length-1];continue}if(f===Jr||f===en||f===Fr){let E=f,S;for(r.keepQuotes!==!0&&(f="");h<m&&(S=_());){if(S===Ge){f+=S+_();continue}if(S===E){r.keepQuotes===!0&&(f+=S);break}f+=S}y({type:"text",value:f});continue}if(f===zr){R++;let E=c.value&&c.value.slice(-1)==="$"||a.dollar===!0;a=y({type:"brace",open:!0,close:!1,dollar:E,depth:R,commas:0,ranges:0,nodes:[]}),i.push(a),y({type:"open",value:f});continue}if(f===Vr){if(a.type!=="brace"){y({type:"text",value:f});continue}let E="close";a=i.pop(),a.close=!0,y({type:E,value:f}),R--,a=i[i.length-1];continue}if(f===Qr&&R>0){if(a.ranges>0){a.ranges=0;let E=a.nodes.shift();a.nodes=[E,{type:"text",value:jr(a)}]}y({type:"comma",value:f}),a.commas++;continue}if(f===Xr&&R>0&&a.commas===0){let E=a.nodes;if(R===0||E.length===0){y({type:"text",value:f});continue}if(c.type==="dot"){if(a.range=[],c.value+=f,c.type="range",a.nodes.length!==3&&a.nodes.length!==5){a.invalid=!0,a.ranges=0,c.type="text";continue}a.ranges++,a.args=[];continue}if(c.type==="range"){E.pop();let S=E[E.length-1];S.value+=c.value+f,c=S,a.ranges--;continue}y({type:"dot",value:f});continue}y({type:"text",value:f})}do if(a=i.pop(),a.type!=="root"){a.nodes.forEach(T=>{T.nodes||(T.type==="open"&&(T.isOpen=!0),T.type==="close"&&(T.isClose=!0),T.nodes||(T.type="text"),T.invalid=!0)});let E=i[i.length-1],S=E.nodes.indexOf(a);E.nodes.splice(S,1,...a.nodes)}while(i.length>0);return y({type:"eos"}),s};Ot.exports=nn});var Pt=q((as,Bt)=>{"use strict";var It=He(),sn=Ct(),an=vt(),on=Nt(),Z=(e,r={})=>{let t=[];if(Array.isArray(e))for(let n of e){let s=Z.create(n,r);Array.isArray(s)?t.push(...s):t.push(s)}else t=[].concat(Z.create(e,r));return r&&r.expand===!0&&r.nodupes===!0&&(t=[...new Set(t)]),t};Z.parse=(e,r={})=>on(e,r);Z.stringify=(e,r={})=>It(typeof e=="string"?Z.parse(e,r):e,r);Z.compile=(e,r={})=>(typeof e=="string"&&(e=Z.parse(e,r)),sn(e,r));Z.expand=(e,r={})=>{typeof e=="string"&&(e=Z.parse(e,r));let t=an(e,r);return r.noempty===!0&&(t=t.filter(Boolean)),r.nodupes===!0&&(t=[...new Set(t)]),t};Z.create=(e,r={})=>e===""||e.length<3?[e]:r.expand!==!0?Z.compile(e,r):Z.expand(e,r);Bt.exports=Z});var me=q((is,qt)=>{"use strict";var un=W("path"),se="\\\\/",Mt=`[^${se}]`,ie="\\.",cn="\\+",ln="\\?",Te="\\/",fn="(?=.)",Dt="[^/]",qe=`(?:${Te}|$)`,Ut=`(?:^|${Te})`,Ke=`${ie}{1,2}${qe}`,pn=`(?!${ie})`,hn=`(?!${Ut}${Ke})`,dn=`(?!${ie}{0,1}${qe})`,gn=`(?!${Ke})`,An=`[^.${Te}]`,mn=`${Dt}*?`,Gt={DOT_LITERAL:ie,PLUS_LITERAL:cn,QMARK_LITERAL:ln,SLASH_LITERAL:Te,ONE_CHAR:fn,QMARK:Dt,END_ANCHOR:qe,DOTS_SLASH:Ke,NO_DOT:pn,NO_DOTS:hn,NO_DOT_SLASH:dn,NO_DOTS_SLASH:gn,QMARK_NO_DOT:An,STAR:mn,START_ANCHOR:Ut},Rn={...Gt,SLASH_LITERAL:`[${se}]`,QMARK:Mt,STAR:`${Mt}*?`,DOTS_SLASH:`${ie}{1,2}(?:[${se}]|$)`,NO_DOT:`(?!${ie})`,NO_DOTS:`(?!(?:^|[${se}])${ie}{1,2}(?:[${se}]|$))`,NO_DOT_SLASH:`(?!${ie}{0,1}(?:[${se}]|$))`,NO_DOTS_SLASH:`(?!${ie}{1,2}(?:[${se}]|$))`,QMARK_NO_DOT:`[^.${se}]`,START_ANCHOR:`(?:^|[${se}])`,END_ANCHOR:`(?:[${se}]|$)`},yn={alnum:"a-zA-Z0-9",alpha:"a-zA-Z",ascii:"\\x00-\\x7F",blank:" \\t",cntrl:"\\x00-\\x1F\\x7F",digit:"0-9",graph:"\\x21-\\x7E",lower:"a-z",print:"\\x20-\\x7E ",punct:"\\-!\"#$%&'()\\*+,./:;<=>?@[\\]^_`{|}~",space:" \\t\\r\\n\\v\\f",upper:"A-Z",word:"A-Za-z0-9_",xdigit:"A-Fa-f0-9"};Line truncated
|
||||||
|
/*!
|
||||||
|
* fill-range <https://github.com/jonschlinkert/fill-range>
|
||||||
|
*
|
||||||
|
* Copyright (c) 2014-present, Jon Schlinkert.
|
||||||
|
* Licensed under the MIT License.
|
||||||
|
*/
|
||||||
|
/*!
|
||||||
|
* is-number <https://github.com/jonschlinkert/is-number>
|
||||||
|
*
|
||||||
|
* Copyright (c) 2014-present, Jon Schlinkert.
|
||||||
|
* Released under the MIT License.
|
||||||
|
*/
|
||||||
|
/*!
|
||||||
|
* to-regex-range <https://github.com/micromatch/to-regex-range>
|
||||||
|
*
|
||||||
|
* Copyright (c) 2015-present, Jon Schlinkert.
|
||||||
|
* Released under the MIT License.
|
||||||
|
*/
|
||||||
|
return plugin;
|
||||||
|
}
|
||||||
|
};
|
||||||
@@ -3,5 +3,7 @@ nodeLinker: node-modules
|
|||||||
plugins:
|
plugins:
|
||||||
- path: .yarn/plugins/@yarnpkg/plugin-interactive-tools.cjs
|
- path: .yarn/plugins/@yarnpkg/plugin-interactive-tools.cjs
|
||||||
spec: "@yarnpkg/plugin-interactive-tools"
|
spec: "@yarnpkg/plugin-interactive-tools"
|
||||||
|
- path: .yarn/plugins/@yarnpkg/plugin-workspace-tools.cjs
|
||||||
|
spec: "@yarnpkg/plugin-workspace-tools"
|
||||||
|
|
||||||
yarnPath: .yarn/releases/yarn-3.4.1.cjs
|
yarnPath: .yarn/releases/yarn-3.4.1.cjs
|
||||||
+6
-1
@@ -86,7 +86,12 @@ To develop locally:
|
|||||||
yarn
|
yarn
|
||||||
```
|
```
|
||||||
|
|
||||||
5. Start developing and watch for code changes:
|
5. Set up your `.env` file:
|
||||||
|
|
||||||
|
- Use `openssl rand -base64 32` to generate a key and add it under `NEXTAUTH_SECRET` in the `.env` file.
|
||||||
|
- Use `openssl rand -base64 24` to generate a key and add it under `CALENDSO_ENCRYPTION_KEY` in the `.env` file.
|
||||||
|
|
||||||
|
6. Start developing and watch for code changes:
|
||||||
|
|
||||||
```sh
|
```sh
|
||||||
yarn dev
|
yarn dev
|
||||||
|
|||||||
@@ -2,7 +2,7 @@ Copyright (c) 2020-present Cal.com, Inc.
|
|||||||
|
|
||||||
Portions of this software are licensed as follows:
|
Portions of this software are licensed as follows:
|
||||||
|
|
||||||
* All content that resides under https://github.com/calcom/cal.com/tree/main/packages/features/ee directory of this repository (Enterprise Edition) is licensed under the license defined in "ee/LICENSE".
|
* All content that resides under https://github.com/calcom/cal.com/tree/main/packages/features/ee directory of this repository (Commercial License) is licensed under the license defined in "ee/LICENSE".
|
||||||
* All third party components incorporated into the Cal.com Software are licensed under the original license provided by the owner of the applicable component.
|
* All third party components incorporated into the Cal.com Software are licensed under the original license provided by the owner of the applicable component.
|
||||||
* Content outside of the above mentioned directories or restrictions above is available under the "AGPLv3" license as defined below.
|
* Content outside of the above mentioned directories or restrictions above is available under the "AGPLv3" license as defined below.
|
||||||
|
|
||||||
|
|||||||
@@ -24,7 +24,7 @@
|
|||||||
|
|
||||||
<p align="center">
|
<p align="center">
|
||||||
<a href="https://cal.com/slack"><img src="https://img.shields.io/badge/Slack-calendso.slack.com-%234A154B" alt="Join Cal.com Slack"></a>
|
<a href="https://cal.com/slack"><img src="https://img.shields.io/badge/Slack-calendso.slack.com-%234A154B" alt="Join Cal.com Slack"></a>
|
||||||
<a href="https://www.producthunt.com/posts/calendso"><img src="https://img.shields.io/badge/Product%20Hunt-%231%20Product%20of%20the%20Month-%23DA552E" alt="Product Hunt"></a>
|
<a href="https://producthunt.com/posts/calendso"><img src="https://img.shields.io/badge/Product%20Hunt-%231%20Product%20of%20the%20Month-%23DA552E" alt="Product Hunt"></a>
|
||||||
<a href="https://status.cal.com"><img height="20px" src="https://betteruptime.com/status-badges/v1/monitor/a9kf.svg" alt="Uptime"></a>
|
<a href="https://status.cal.com"><img height="20px" src="https://betteruptime.com/status-badges/v1/monitor/a9kf.svg" alt="Uptime"></a>
|
||||||
<a href="https://github.com/calcom/cal.com/stargazers"><img src="https://img.shields.io/github/stars/calcom/cal.com" alt="Github Stars"></a>
|
<a href="https://github.com/calcom/cal.com/stargazers"><img src="https://img.shields.io/github/stars/calcom/cal.com" alt="Github Stars"></a>
|
||||||
<a href="https://news.ycombinator.com/item?id=34507672"><img src="https://img.shields.io/badge/Hacker%20News-%231-%23FF6600" alt="Hacker News"></a>
|
<a href="https://news.ycombinator.com/item?id=34507672"><img src="https://img.shields.io/badge/Hacker%20News-%231-%23FF6600" alt="Hacker News"></a>
|
||||||
@@ -38,13 +38,13 @@
|
|||||||
<a href="https://github.com/calcom/cal.com/issues?q=is:issue+is:open+label:%22%F0%9F%99%8B%F0%9F%8F%BB%E2%80%8D%E2%99%82%EF%B8%8Fhelp+wanted%22"><img src="https://img.shields.io/badge/Help%20Wanted-Contribute-blue"></a>
|
<a href="https://github.com/calcom/cal.com/issues?q=is:issue+is:open+label:%22%F0%9F%99%8B%F0%9F%8F%BB%E2%80%8D%E2%99%82%EF%B8%8Fhelp+wanted%22"><img src="https://img.shields.io/badge/Help%20Wanted-Contribute-blue"></a>
|
||||||
<a href="https://cal.com/figma"><img src="https://img.shields.io/badge/Figma-Design%20System-blueviolet"></a>
|
<a href="https://cal.com/figma"><img src="https://img.shields.io/badge/Figma-Design%20System-blueviolet"></a>
|
||||||
<a href="https://calendso.slack.com/archives/C02BY67GMMW"><img src="https://img.shields.io/badge/translations-contribute-brightgreen" /></a>
|
<a href="https://calendso.slack.com/archives/C02BY67GMMW"><img src="https://img.shields.io/badge/translations-contribute-brightgreen" /></a>
|
||||||
<a href="https://www.contributor-covenant.org/version/1/4/code-of-conduct/ "><img src="https://img.shields.io/badge/Contributor%20Covenant-1.4-purple" /></a>
|
<a href="https://contributor-covenant.org/version/1/4/code-of-conduct/ "><img src="https://img.shields.io/badge/Contributor%20Covenant-1.4-purple" /></a>
|
||||||
<a href="https://console.algora.io/org/cal/bounties?status=open"><img src="https://img.shields.io/endpoint?url=https%3A%2F%2Fconsole.algora.io%2Fapi%2Fshields%2Fcal%2Fbounties%3Fstatus%3Dopen" /></a>
|
<a href="https://console.algora.io/org/cal/bounties?status=open"><img src="https://img.shields.io/endpoint?url=https%3A%2F%2Fconsole.algora.io%2Fapi%2Fshields%2Fcal%2Fbounties%3Fstatus%3Dopen" /></a>
|
||||||
</p>
|
</p>
|
||||||
|
|
||||||
<!-- ABOUT THE PROJECT -->
|
<!-- ABOUT THE PROJECT -->
|
||||||
|
|
||||||
## About The Project
|
## About the Project
|
||||||
|
|
||||||
<img width="100%" alt="booking-screen" src="https://user-images.githubusercontent.com/8019099/176390354-f1bc7069-0341-437a-9bb8-eb41092b4016.gif">
|
<img width="100%" alt="booking-screen" src="https://user-images.githubusercontent.com/8019099/176390354-f1bc7069-0341-437a-9bb8-eb41092b4016.gif">
|
||||||
|
|
||||||
@@ -53,13 +53,13 @@
|
|||||||
The open source Calendly alternative. You are in charge
|
The open source Calendly alternative. You are in charge
|
||||||
of your own data, workflow and appearance.
|
of your own data, workflow and appearance.
|
||||||
|
|
||||||
Calendly and other scheduling tools are awesome. It made our lives massively easier. We're using it for business meetings, seminars, yoga classes and even calls with our families. However, most tools are very limited in terms of control and customisations.
|
Calendly and other scheduling tools are awesome. It made our lives massively easier. We're using it for business meetings, seminars, yoga classes and even calls with our families. However, most tools are very limited in terms of control and customizations.
|
||||||
|
|
||||||
That's where Cal.com comes in. Self-hosted or hosted by us. White-label by design. API-driven and ready to be deployed on your own domain. Full control of your events and data.
|
That's where Cal.com comes in. Self-hosted or hosted by us. White-label by design. API-driven and ready to be deployed on your own domain. Full control of your events and data.
|
||||||
|
|
||||||
## Recognition
|
## Recognition
|
||||||
|
|
||||||
#### Hacker News
|
#### [Hacker News](https://news.ycombinator.com/from?site=cal.com)
|
||||||
|
|
||||||
<a href="https://news.ycombinator.com/item?id=34507672">
|
<a href="https://news.ycombinator.com/item?id=34507672">
|
||||||
<img
|
<img
|
||||||
@@ -77,22 +77,22 @@ That's where Cal.com comes in. Self-hosted or hosted by us. White-label by desig
|
|||||||
/>
|
/>
|
||||||
</a>
|
</a>
|
||||||
|
|
||||||
#### [Product Hunt](https://www.producthunt.com/posts/calendso?utm_source=badge-top-post-badge&utm_medium=badge&utm_souce=badge-calendso)
|
#### [Product Hunt](https://producthunt.com/products/cal-com?utm_source=badge-top-post-badge&utm_medium=badge)
|
||||||
|
|
||||||
<a href="https://www.producthunt.com/posts/calendso?utm_source=badge-top-post-badge&utm_medium=badge&utm_souce=badge-calendso" target="_blank"><img src="https://api.producthunt.com/widgets/embed-image/v1/top-post-badge.svg?post_id=291910&theme=light&period=monthly" alt="Cal.com - The open source Calendly alternative | Product Hunt" style="width: 250px; height: 54px;" width="250" height="54" /></a> <a href="https://www.producthunt.com/posts/calendso?utm_source=badge-featured&utm_medium=badge&utm_souce=badge-calendso" target="_blank"><img src="https://api.producthunt.com/widgets/embed-image/v1/featured.svg?post_id=291910&theme=light" alt="Cal.com - The open source Calendly alternative | Product Hunt" style="width: 250px; height: 54px;" width="250" height="54" /></a> <a href="https://www.producthunt.com/stories/how-this-open-source-calendly-alternative-rocketed-to-product-of-the-day" target="_blank"><img src="https://cal.com/maker-grant.svg" alt="Cal.com - The open source Calendly alternative | Product Hunt" style="width: 250px; height: 54px;" width="250" height="54" /></a>
|
<a href="https://producthunt.com/posts/calendso?utm_source=badge-top-post-badge&utm_medium=badge&utm_souce=badge-calendso" target="_blank"><img src="https://api.producthunt.com/widgets/embed-image/v1/top-post-badge.svg?post_id=291910&theme=light&period=monthly" alt="Cal.com - The open source Calendly alternative | Product Hunt" style="width: 250px; height: 54px;" width="250" height="54" /></a> <a href="https://producthunt.com/posts/calendso?utm_source=badge-featured&utm_medium=badge&utm_souce=badge-calendso" target="_blank"><img src="https://api.producthunt.com/widgets/embed-image/v1/featured.svg?post_id=291910&theme=light" alt="Cal.com - The open source Calendly alternative | Product Hunt" style="width: 250px; height: 54px;" width="250" height="54" /></a> <a href="https://producthunt.com/stories/how-this-open-source-calendly-alternative-rocketed-to-product-of-the-day" target="_blank"><img src="https://cal.com/maker-grant.svg" alt="Cal.com - The open source Calendly alternative | Product Hunt" style="width: 250px; height: 54px;" width="250" height="54" /></a>
|
||||||
|
|
||||||
### Built With
|
### Built With
|
||||||
|
|
||||||
- [Next.js](https://nextjs.org/?ref=cal.com)
|
- [Next.js](https://nextjs.org/?ref=cal.com)
|
||||||
- [tRPC](https://trpc.io/?ref=cal.com)
|
- [tRPC](https://trpc.io/?ref=cal.com)
|
||||||
- [React](https://reactjs.org/?ref=cal.com)
|
- [React.js](https://reactjs.org/?ref=cal.com)
|
||||||
- [Tailwind](https://tailwindcss.com/?ref=cal.com)
|
- [Tailwind CSS](https://tailwindcss.com/?ref=cal.com)
|
||||||
- [Prisma](https://prisma.io/?ref=cal.com)
|
- [Prisma.io](https://prisma.io/?ref=cal.com)
|
||||||
- [Daily.co](https://go.cal.com/daily)
|
- [Daily.co](https://go.cal.com/daily)
|
||||||
|
|
||||||
## Stay Up-to-Date
|
## Stay Up-to-Date
|
||||||
|
|
||||||
Cal officially launched as v.1.0 on 15th of September, however a lot of new features are coming. Watch **releases** of this repository to be notified for future updates:
|
Cal.com officially launched as v.1.0 on 15th of September, however a lot of new features are coming. Watch **releases** of this repository to be notified for future updates:
|
||||||
|
|
||||||

|

|
||||||
|
|
||||||
@@ -104,7 +104,7 @@ To get a local copy up and running, please follow these simple steps.
|
|||||||
|
|
||||||
### Prerequisites
|
### Prerequisites
|
||||||
|
|
||||||
Here is what you need to be able to run Cal.
|
Here is what you need to be able to run Cal.com.
|
||||||
|
|
||||||
- Node.js (Version: >=15.x <17)
|
- Node.js (Version: >=15.x <17)
|
||||||
- PostgreSQL
|
- PostgreSQL
|
||||||
@@ -121,6 +121,9 @@ Here is what you need to be able to run Cal.
|
|||||||
```sh
|
```sh
|
||||||
git clone https://github.com/calcom/cal.com.git
|
git clone https://github.com/calcom/cal.com.git
|
||||||
```
|
```
|
||||||
|
> If you are on windows, run the following command on `gitbash` with admin privileges: <br>
|
||||||
|
```git clone -c core.symlinks=true https://github.com/calcom/cal.com.git``` <br>
|
||||||
|
See [docs](https://cal.com/docs/how-to-guides/how-to-troubleshoot-symbolic-link-issues-on-windows#enable-symbolic-links) for more details.
|
||||||
|
|
||||||
1. Go to the project folder
|
1. Go to the project folder
|
||||||
|
|
||||||
@@ -134,10 +137,10 @@ Here is what you need to be able to run Cal.
|
|||||||
yarn
|
yarn
|
||||||
```
|
```
|
||||||
|
|
||||||
1. Set up your .env file
|
1. Set up your `.env` file
|
||||||
- Duplicate `.env.example` to `.env`
|
- Duplicate `.env.example` to `.env`
|
||||||
- Use `openssl rand -base64 32` to generate a key and add it under `NEXTAUTH_SECRET` in the .env file.
|
- Use `openssl rand -base64 32` to generate a key and add it under `NEXTAUTH_SECRET` in the `.env` file.
|
||||||
- Use `openssl rand -base64 24` to generate a key and add it under `CALENDSO_ENCRYPTION_KEY` in the .env file.
|
- Use `openssl rand -base64 24` to generate a key and add it under `CALENDSO_ENCRYPTION_KEY` in the `.env` file.
|
||||||
|
|
||||||
#### Quick start with `yarn dx`
|
#### Quick start with `yarn dx`
|
||||||
|
|
||||||
@@ -150,53 +153,63 @@ yarn dx
|
|||||||
|
|
||||||
#### Development tip
|
#### Development tip
|
||||||
|
|
||||||
> Add `NEXT_PUBLIC_DEBUG=1` anywhere in your `.env` to get logging information for all the queries and mutations driven by **trpc**.
|
> Add `NEXT_PUBLIC_DEBUG=1` anywhere in your `.env` to get logging information for all the queries and mutations driven by **tRPC**.
|
||||||
|
|
||||||
```sh
|
```sh
|
||||||
echo 'NEXT_PUBLIC_DEBUG=1' >> .env
|
echo 'NEXT_PUBLIC_DEBUG=1' >> .env
|
||||||
```
|
```
|
||||||
|
#### Gitpod Setup
|
||||||
|
|
||||||
|
1. Click the button below to open this project in Gitpod.
|
||||||
|
|
||||||
|
2. This will open a fully configured workspace in your browser with all the necessary dependencies already installed.
|
||||||
|
|
||||||
|
[](https://gitpod.io/#https://github.com/calcom/cal.com)
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
#### Manual setup
|
#### Manual setup
|
||||||
|
|
||||||
1. Configure environment variables in the `.env` file. Replace `<user>`, `<pass>`, `<db-host>`, `<db-port>` with their applicable values
|
1. Configure environment variables in the `.env` file. Replace `<user>`, `<pass>`, `<db-host>`, and `<db-port>` with their applicable values
|
||||||
|
|
||||||
```
|
```
|
||||||
DATABASE_URL='postgresql://<user>:<pass>@<db-host>:<db-port>'
|
DATABASE_URL='postgresql://<user>:<pass>@<db-host>:<db-port>'
|
||||||
```
|
```
|
||||||
|
|
||||||
<details>
|
<details>
|
||||||
<summary>If you don't know how to configure the DATABASE_URL, then follow the steps here to create a quick DB using Heroku</summary>
|
<summary>If you don't know how to configure the DATABASE_URL, then follow the steps here to create a quick local DB</summary>
|
||||||
|
|
||||||
1. Create a free account with [Heroku](https://www.heroku.com/).
|
1. [Download](https://www.postgresql.org/download/) and install postgres in your local (if you don't have it already).
|
||||||
|
|
||||||
2. Create a new app.
|
2. Create your own local db by executing `createDB <DB name>`
|
||||||
<img width="306" alt="Create an App" src="https://user-images.githubusercontent.com/16905768/115322780-b3d58c00-a17e-11eb-8a52-b758fb0ea942.png">
|
|
||||||
|
|
||||||
3. In your new app, go to `Overview` and next to `Installed add-ons`, click `Configure Add-ons`. We need this to set up our database.
|
3. Now open your psql shell with the DB you created: `psql -h localhost -U postgres -d <DB name>`
|
||||||

|
|
||||||
|
|
||||||
4. Once you clicked on `Configure Add-ons`, click on `Find more add-ons` and search for `postgres`. One of the options will be `Heroku Postgres` - click on that option.
|
4. Inside the psql shell execute `\conninfo`. And you will get the following info.
|
||||||

|

|
||||||
|
|
||||||
5. Once the pop-up appears, click `Submit Order Form` - plan name should be `Hobby Dev - Free`.
|
5. Now extract all the info and add it to your DATABASE_URL. The url would look something like this
|
||||||
<img width="512" alt="Submit Order Form" src="https://user-images.githubusercontent.com/16905768/115323265-b4baed80-a17f-11eb-99f0-d67f019aa6df.png">
|
`postgresql://postgres:postgres@localhost:5432/Your-DB-Name`.
|
||||||
|
|
||||||
6. Once you completed the above steps, click on your newly created `Heroku Postgres` and go to its `Settings`.
|
|
||||||

|
|
||||||
|
|
||||||
7. In `Settings`, copy your URI to your Cal.com .env file and replace the `postgresql://<user>:<pass>@<db-host>:<db-port>` with it.
|
|
||||||

|
|
||||||

|
|
||||||
|
|
||||||
8. To view your DB, once you add new data in Prisma, you can use [Heroku Data Explorer](https://heroku-data-explorer.herokuapp.com/).
|
|
||||||
</details>
|
</details>
|
||||||
|
|
||||||
1. Set a 32 character random string in your .env file for the `CALENDSO_ENCRYPTION_KEY` (You can use a command like `openssl rand -base64 24` to generate one).
|
If you don't want to create a local DB. Then you can also consider using services like railway.app or render.
|
||||||
|
- [Setup postgres DB with railway.app](https://arctype.com/postgres/setup/railway-postgres)
|
||||||
|
- [Setup postgres DB with render](https://render.com/docs/databases)
|
||||||
|
|
||||||
|
1. Copy and paste your `DATABASE_URL` from `.env` to `.env.appStore`.
|
||||||
|
|
||||||
|
1. Set a 32 character random string in your `.env` file for the `CALENDSO_ENCRYPTION_KEY` (You can use a command like `openssl rand -base64 24` to generate one).
|
||||||
1. Set up the database using the Prisma schema (found in `packages/prisma/schema.prisma`)
|
1. Set up the database using the Prisma schema (found in `packages/prisma/schema.prisma`)
|
||||||
|
|
||||||
```sh
|
```sh
|
||||||
yarn workspace @calcom/prisma db-deploy
|
yarn workspace @calcom/prisma db-deploy
|
||||||
```
|
```
|
||||||
|
1. Run [mailhog](https://github.com/mailhog/MailHog) to view emails sent during development
|
||||||
|
|
||||||
|
```sh
|
||||||
|
docker pull mailhog/mailhog
|
||||||
|
docker run -d -p 8025:8025 -p 1025:1025 mailhog/mailhog
|
||||||
|
```
|
||||||
|
|
||||||
1. Run (in development mode)
|
1. Run (in development mode)
|
||||||
|
|
||||||
@@ -206,7 +219,7 @@ echo 'NEXT_PUBLIC_DEBUG=1' >> .env
|
|||||||
|
|
||||||
#### Setting up your first user
|
#### Setting up your first user
|
||||||
|
|
||||||
1. Open [Prisma Studio](https://www.prisma.io/studio) to look at or modify the database content:
|
1. Open [Prisma Studio](https://prisma.io/studio) to look at or modify the database content:
|
||||||
|
|
||||||
```sh
|
```sh
|
||||||
yarn db-studio
|
yarn db-studio
|
||||||
@@ -251,7 +264,7 @@ yarn playwright show-report test-results/reports/playwright-html-report
|
|||||||
yarn workspace @calcom/prisma db-migrate
|
yarn workspace @calcom/prisma db-migrate
|
||||||
```
|
```
|
||||||
|
|
||||||
(this can clear your development database in some cases)
|
(This can clear your development database in some cases)
|
||||||
|
|
||||||
In a production environment, run:
|
In a production environment, run:
|
||||||
|
|
||||||
@@ -285,7 +298,7 @@ yarn playwright show-report test-results/reports/playwright-html-report
|
|||||||
|
|
||||||
### Docker
|
### Docker
|
||||||
|
|
||||||
The Docker configuration for Cal is an effort powered by people within the community.
|
The Docker configuration for Cal.com is an effort powered by people within the community.
|
||||||
|
|
||||||
If you want to contribute to the Docker repository, [reply here](https://github.com/calcom/docker/discussions/32).
|
If you want to contribute to the Docker repository, [reply here](https://github.com/calcom/docker/discussions/32).
|
||||||
|
|
||||||
@@ -298,14 +311,14 @@ Cal.com, Inc. does not provide official support for Docker, but we will accept f
|
|||||||
### Heroku
|
### Heroku
|
||||||
|
|
||||||
<a href="https://heroku.com/deploy?template=https://github.com/calcom/cal.com">
|
<a href="https://heroku.com/deploy?template=https://github.com/calcom/cal.com">
|
||||||
<img width="185px" height="auto" src="https://www.herokucdn.com/deploy/button.svg" alt="Deploy">
|
<img width="185px" height="auto" src="https://herokucdn.com/deploy/button.svg" alt="Deploy">
|
||||||
</a>
|
</a>
|
||||||
|
|
||||||
### Railway
|
### Railway
|
||||||
|
|
||||||
[](https://railway.app/new/template/cal)
|
[](https://railway.app/new/template/cal)
|
||||||
|
|
||||||
You can deploy Cal on [Railway](https://railway.app/) using the button above. The team at Railway also have a [detailed blog post](https://blog.railway.app/p/calendso) on deploying Cal on their platform.
|
You can deploy Cal.com on [Railway](https://railway.app) using the button above. The team at Railway also have a [detailed blog post](https://blog.railway.app/p/calendso) on deploying Cal.com on their platform.
|
||||||
|
|
||||||
### Vercel
|
### Vercel
|
||||||
|
|
||||||
@@ -386,7 +399,7 @@ Don't code but still want to contribute? Join our [slack](https://cal.com/slack)
|
|||||||
8. Select Web Application as the Application Type.
|
8. Select Web Application as the Application Type.
|
||||||
9. Under Authorized redirect URI's, select Add URI and then add the URI `<Cal.com URL>/api/integrations/googlecalendar/callback` and `<Cal.com URL>/api/auth/callback/google` replacing Cal.com URL with the URI at which your application runs.
|
9. Under Authorized redirect URI's, select Add URI and then add the URI `<Cal.com URL>/api/integrations/googlecalendar/callback` and `<Cal.com URL>/api/auth/callback/google` replacing Cal.com URL with the URI at which your application runs.
|
||||||
10. The key will be created and you will be redirected back to the Credentials page. Select the newly generated client ID under OAuth 2.0 Client IDs.
|
10. The key will be created and you will be redirected back to the Credentials page. Select the newly generated client ID under OAuth 2.0 Client IDs.
|
||||||
11. Select Download JSON. Copy the contents of this file and paste the entire JSON string in the .env file as the value for GOOGLE_API_CREDENTIALS key.
|
11. Select Download JSON. Copy the contents of this file and paste the entire JSON string in the `.env` file as the value for `GOOGLE_API_CREDENTIALS` key.
|
||||||
|
|
||||||
#### _Adding google calendar to Cal.com App Store_
|
#### _Adding google calendar to Cal.com App Store_
|
||||||
|
|
||||||
@@ -423,7 +436,7 @@ following
|
|||||||
5. Choose "User-managed app" as the app type.
|
5. Choose "User-managed app" as the app type.
|
||||||
6. De-select the option to publish the app on the Zoom App Marketplace.
|
6. De-select the option to publish the app on the Zoom App Marketplace.
|
||||||
7. Click "Create".
|
7. Click "Create".
|
||||||
8. Now copy the Client ID and Client Secret to your .env file into the `ZOOM_CLIENT_ID` and `ZOOM_CLIENT_SECRET` fields.
|
8. Now copy the Client ID and Client Secret to your `.env` file into the `ZOOM_CLIENT_ID` and `ZOOM_CLIENT_SECRET` fields.
|
||||||
9. Set the Redirect URL for OAuth `<Cal.com URL>/api/integrations/zoomvideo/callback` replacing Cal.com URL with the URI at which your application runs.
|
9. Set the Redirect URL for OAuth `<Cal.com URL>/api/integrations/zoomvideo/callback` replacing Cal.com URL with the URI at which your application runs.
|
||||||
10. Also add the redirect URL given above as a allow list URL and enable "Subdomain check". Make sure, it says "saved" below the form.
|
10. Also add the redirect URL given above as a allow list URL and enable "Subdomain check". Make sure, it says "saved" below the form.
|
||||||
11. You don't need to provide basic information about your app. Instead click at "Scopes" and then at "+ Add Scopes". On the left, click the category "Meeting" and check the scope `meeting:write`.
|
11. You don't need to provide basic information about your app. Instead click at "Scopes" and then at "+ Add Scopes". On the left, click the category "Meeting" and check the scope `meeting:write`.
|
||||||
@@ -435,8 +448,8 @@ following
|
|||||||
1. Visit our [Daily.co Partnership Form](https://go.cal.com/daily) and enter your information
|
1. Visit our [Daily.co Partnership Form](https://go.cal.com/daily) and enter your information
|
||||||
2. From within your dashboard, go to the [developers](https://dashboard.daily.co/developers) tab.
|
2. From within your dashboard, go to the [developers](https://dashboard.daily.co/developers) tab.
|
||||||
3. Copy your API key.
|
3. Copy your API key.
|
||||||
4. Now paste the API key to your .env file into the `DAILY_API_KEY` field in your .env file.
|
4. Now paste the API key to your `.env` file into the `DAILY_API_KEY` field in your `.env` file.
|
||||||
5. If you have the [Daily Scale Plan](https://www.daily.co/pricing) set the `DAILY_SCALE_PLAN` variable to `true` in order to use features like video recording.
|
5. If you have the [Daily Scale Plan](https://daily.co/pricing) set the `DAILY_SCALE_PLAN` variable to `true` in order to use features like video recording.
|
||||||
|
|
||||||
### Obtaining HubSpot Client ID and Secret
|
### Obtaining HubSpot Client ID and Secret
|
||||||
|
|
||||||
@@ -445,12 +458,16 @@ following
|
|||||||
3. Click "Create app" button top right.
|
3. Click "Create app" button top right.
|
||||||
4. Fill in any information you want in the "App info" tab
|
4. Fill in any information you want in the "App info" tab
|
||||||
5. Go to tab "Auth"
|
5. Go to tab "Auth"
|
||||||
6. Now copy the Client ID and Client Secret to your .env file into the `HUBSPOT_CLIENT_ID` and `HUBSPOT_CLIENT_SECRET` fields.
|
6. Now copy the Client ID and Client Secret to your `.env` file into the `HUBSPOT_CLIENT_ID` and `HUBSPOT_CLIENT_SECRET` fields.
|
||||||
7. Set the Redirect URL for OAuth `<Cal.com URL>/api/integrations/hubspot/callback` replacing Cal.com URL with the URI at which your application runs.
|
7. Set the Redirect URL for OAuth `<Cal.com URL>/api/integrations/hubspot/callback` replacing Cal.com URL with the URI at which your application runs.
|
||||||
8. In the "Scopes" section at the bottom of the page, make sure you select "Read" and "Write" for scope called `crm.objects.contacts`
|
8. In the "Scopes" section at the bottom of the page, make sure you select "Read" and "Write" for scope called `crm.objects.contacts`
|
||||||
9. Click the "Save" button at the bottom footer.
|
9. Click the "Save" button at the bottom footer.
|
||||||
10. You're good to go. Now you can see any booking in Cal.com created as a meeting in HubSpot for your contacts.
|
10. You're good to go. Now you can see any booking in Cal.com created as a meeting in HubSpot for your contacts.
|
||||||
|
|
||||||
|
### Obtaining Webex Client ID and Secret
|
||||||
|
|
||||||
|
[See Webex Readme](./packages/app-store/webex/)
|
||||||
|
|
||||||
### Obtaining ZohoCRM Client ID and Secret
|
### Obtaining ZohoCRM Client ID and Secret
|
||||||
|
|
||||||
1. Open [Zoho API Console](https://api-console.zoho.com/) and sign into your account, or create a new one.
|
1. Open [Zoho API Console](https://api-console.zoho.com/) and sign into your account, or create a new one.
|
||||||
@@ -458,31 +475,33 @@ following
|
|||||||
3. Click "ADD CLIENT" button top right and select "Server-based Applications".
|
3. Click "ADD CLIENT" button top right and select "Server-based Applications".
|
||||||
4. Fill in any information you want in the "Client Details" tab
|
4. Fill in any information you want in the "Client Details" tab
|
||||||
5. Go to tab "Client Secret" tab.
|
5. Go to tab "Client Secret" tab.
|
||||||
6. Now copy the Client ID and Client Secret to your .env file into the `ZOHOCRM_CLIENT_ID` and `ZOHOCRM_CLIENT_SECRET` fields.
|
6. Now copy the Client ID and Client Secret to your `.env` file into the `ZOHOCRM_CLIENT_ID` and `ZOHOCRM_CLIENT_SECRET` fields.
|
||||||
7. Set the Redirect URL for OAuth `<Cal.com URL>/api/integrations/zohocrm/callback` replacing Cal.com URL with the URI at which your application runs.
|
7. Set the Redirect URL for OAuth `<Cal.com URL>/api/integrations/zohocrm/callback` replacing Cal.com URL with the URI at which your application runs.
|
||||||
8. In the "Settings" section check the "Multi-DC" option if you wish to use the same OAuth credentials for all data centers.
|
8. In the "Settings" section check the "Multi-DC" option if you wish to use the same OAuth credentials for all data centers.
|
||||||
9. Click the "Save"/ "UPDATE" button at the bottom footer.
|
9. Click the "Save"/ "UPDATE" button at the bottom footer.
|
||||||
10. You're good to go. Now you can easily add your ZohoCRM integration in the Cal.com settings.
|
10. You're good to go. Now you can easily add your ZohoCRM integration in the Cal.com settings.
|
||||||
|
|
||||||
|
### Obtaining Zoho Bigin Client ID and Secret
|
||||||
|
[Follow these steps](./packages/app-store/zoho-bigin/)
|
||||||
## Workflows
|
## Workflows
|
||||||
|
|
||||||
### Setting up SendGrid for Email reminders
|
### Setting up SendGrid for Email reminders
|
||||||
|
|
||||||
1. Create a SendGrid account (https://signup.sendgrid.com/)
|
1. Create a SendGrid account (https://signup.sendgrid.com/)
|
||||||
2. Go to Settings -> API keys and create an API key
|
2. Go to Settings -> API keys and create an API key
|
||||||
3. Copy API key to your .env file into the SENDGRID_API_KEY field
|
3. Copy API key to your `.env` file into the `SENDGRID_API_KEY` field
|
||||||
4. Go to Settings -> Sender Authentication and verify a single sender
|
4. Go to Settings -> Sender Authentication and verify a single sender
|
||||||
5. Copy the verified E-Mail to your .env file into the SENDGRID_EMAIL field
|
5. Copy the verified E-Mail to your `.env` file into the `SENDGRID_EMAIL` field
|
||||||
6. Add your custom sender name to the .env file into the NEXT_PUBLIC_SENDGRID_SENDER_NAME field (fallback is Cal.com)
|
6. Add your custom sender name to the `.env` file into the `NEXT_PUBLIC_SENDGRID_SENDER_NAME` field (fallback is Cal.com)
|
||||||
|
|
||||||
### Setting up Twilio for SMS reminders
|
### Setting up Twilio for SMS reminders
|
||||||
|
|
||||||
1. Create a Twilio account (https://www.twilio.com/try-twilio)
|
1. Create a Twilio account (https://twilio.com/try-twilio)
|
||||||
2. Click ‘Get a Twilio phone number’
|
2. Click ‘Get a Twilio phone number’
|
||||||
3. Copy Account SID to your .env file into the TWILIO_SID field
|
3. Copy Account SID to your `.env` file into the `TWILIO_SID` field
|
||||||
4. Copy Auth Token to your .env file into the TWILIO_TOKEN field
|
4. Copy Auth Token to your `.env` file into the `TWILIO_TOKEN` field
|
||||||
5. Copy your Twilio phone number to your .env file into the TWILIO_PHONE_NUMBER field
|
5. Copy your Twilio phone number to your `.env` file into the `TWILIO_PHONE_NUMBER` field
|
||||||
6. Add your own sender id to the .env file into the NEXT_PUBLIC_SENDER_ID field (fallback is Cal)
|
6. Add your own sender id to the `.env` file into the `NEXT_PUBLIC_SENDER_ID` field (fallback is Cal.com)
|
||||||
7. Create a messaging service (Develop -> Messaging -> Services)
|
7. Create a messaging service (Develop -> Messaging -> Services)
|
||||||
8. Choose any name for the messaging service
|
8. Choose any name for the messaging service
|
||||||
9. Click 'Add Senders'
|
9. Click 'Add Senders'
|
||||||
@@ -490,15 +509,15 @@ following
|
|||||||
11. Add the listed phone number
|
11. Add the listed phone number
|
||||||
12. Leave all other fields as they are
|
12. Leave all other fields as they are
|
||||||
13. Complete setup and click ‘View my new Messaging Service’
|
13. Complete setup and click ‘View my new Messaging Service’
|
||||||
14. Copy Messaging Service SID to your .env file into the TWILIO_MESSAGING_SID field
|
14. Copy Messaging Service SID to your `.env` file into the `TWILIO_MESSAGING_SID` field
|
||||||
15. Create a verify service
|
15. Create a verify service
|
||||||
16. Copy Verify Service SID to your .env file into the TWILIO_VERIFY_SID field
|
16. Copy Verify Service SID to your `.env` file into the `TWILIO_VERIFY_SID` field
|
||||||
|
|
||||||
<!-- LICENSE -->
|
<!-- LICENSE -->
|
||||||
|
|
||||||
## License
|
## License
|
||||||
|
|
||||||
Distributed under the AGPLv3 License. See `LICENSE` for more information.
|
Distributed under the [AGPLv3 License](https://github.com/calcom/cal.com/blob/main/LICENSE). See `LICENSE` for more information.
|
||||||
|
|
||||||
<!-- ACKNOWLEDGEMENTS -->
|
<!-- ACKNOWLEDGEMENTS -->
|
||||||
|
|
||||||
|
|||||||
@@ -1,6 +1,7 @@
|
|||||||
{
|
{
|
||||||
"name": "@calcom/docs",
|
"name": "@calcom/docs",
|
||||||
"version": "1.0.0",
|
"version": "1.0.0",
|
||||||
|
"private": true,
|
||||||
"description": "",
|
"description": "",
|
||||||
"main": "index.js",
|
"main": "index.js",
|
||||||
"scripts": {
|
"scripts": {
|
||||||
|
|||||||
@@ -71,5 +71,5 @@ module.exports = {
|
|||||||
|
|
||||||
return config;
|
return config;
|
||||||
},
|
},
|
||||||
typescript: { reactDocgen: 'react-docgen' }
|
typescript: { reactDocgen: "react-docgen" },
|
||||||
};
|
};
|
||||||
@@ -0,0 +1,10 @@
|
|||||||
|
# @calcom/web
|
||||||
|
|
||||||
|
## 2.7.16
|
||||||
|
|
||||||
|
### Patch Changes
|
||||||
|
|
||||||
|
- Updated dependencies
|
||||||
|
- @calcom/embed-snippet@1.0.7
|
||||||
|
- @calcom/embed-react@1.0.12
|
||||||
|
- @calcom/embed-core@1.1.5
|
||||||
+264
-273
@@ -1,5 +1,6 @@
|
|||||||
import { Collapsible, CollapsibleContent } from "@radix-ui/react-collapsible";
|
import { Collapsible, CollapsibleContent } from "@radix-ui/react-collapsible";
|
||||||
import classNames from "classnames";
|
import classNames from "classnames";
|
||||||
|
import type { TFunction } from "next-i18next";
|
||||||
import type { NextRouter } from "next/router";
|
import type { NextRouter } from "next/router";
|
||||||
import { useRouter } from "next/router";
|
import { useRouter } from "next/router";
|
||||||
import type { MutableRefObject, RefObject } from "react";
|
import type { MutableRefObject, RefObject } from "react";
|
||||||
@@ -21,11 +22,10 @@ import {
|
|||||||
TextArea,
|
TextArea,
|
||||||
TextField,
|
TextField,
|
||||||
ColorPicker,
|
ColorPicker,
|
||||||
|
Select,
|
||||||
} from "@calcom/ui";
|
} from "@calcom/ui";
|
||||||
import { Code, Trello, Sun, ArrowLeft } from "@calcom/ui/components/icon";
|
import { Code, Trello, Sun, ArrowLeft } from "@calcom/ui/components/icon";
|
||||||
|
|
||||||
import Select from "@components/ui/form/Select";
|
|
||||||
|
|
||||||
type EmbedType = "inline" | "floating-popup" | "element-click";
|
type EmbedType = "inline" | "floating-popup" | "element-click";
|
||||||
type EmbedFramework = "react" | "HTML";
|
type EmbedFramework = "react" | "HTML";
|
||||||
|
|
||||||
@@ -305,190 +305,189 @@ const getEmbedTypeSpecificString = ({
|
|||||||
return "";
|
return "";
|
||||||
};
|
};
|
||||||
|
|
||||||
const embeds: {
|
const embeds = (t: TFunction) =>
|
||||||
illustration: React.ReactElement;
|
(() => {
|
||||||
title: string;
|
return [
|
||||||
subtitle: string;
|
{
|
||||||
type: EmbedType;
|
title: t("inline_embed"),
|
||||||
}[] = [
|
subtitle: t("load_inline_content"),
|
||||||
{
|
type: "inline",
|
||||||
title: "Inline Embed",
|
illustration: (
|
||||||
subtitle: "Loads your event type directly inline with your other website content.",
|
<svg
|
||||||
type: "inline",
|
width="100%"
|
||||||
illustration: (
|
height="100%"
|
||||||
<svg
|
className="rounded-md"
|
||||||
width="100%"
|
viewBox="0 0 308 265"
|
||||||
height="100%"
|
fill="none"
|
||||||
className="rounded-md"
|
xmlns="http://www.w3.org/2000/svg">
|
||||||
viewBox="0 0 308 265"
|
<path
|
||||||
fill="none"
|
d="M0 1.99999C0 0.895423 0.895431 0 2 0H306C307.105 0 308 0.895431 308 2V263C308 264.105 307.105 265 306 265H2C0.895431 265 0 264.105 0 263V1.99999Z"
|
||||||
xmlns="http://www.w3.org/2000/svg">
|
fill="white"
|
||||||
<path
|
/>
|
||||||
d="M0 1.99999C0 0.895423 0.895431 0 2 0H306C307.105 0 308 0.895431 308 2V263C308 264.105 307.105 265 306 265H2C0.895431 265 0 264.105 0 263V1.99999Z"
|
<rect x="24" width="260" height="38.5" rx="6" fill="#F3F4F6" />
|
||||||
fill="white"
|
<rect x="24.5" y="51" width="139" height="163" rx="1.5" fill="#F8F8F8" />
|
||||||
/>
|
<rect opacity="0.8" x="48" y="74.5" width="80" height="8" rx="6" fill="#F3F4F6" />
|
||||||
<rect x="24" width="260" height="38.5" rx="6" fill="#F3F4F6" />
|
<rect x="48" y="86.5" width="48" height="4" rx="6" fill="#F3F4F6" />
|
||||||
<rect x="24.5" y="51" width="139" height="163" rx="1.5" fill="#F8F8F8" />
|
<rect x="49" y="99.5" width="6" height="6" rx="1" fill="#C6C6C6" />
|
||||||
<rect opacity="0.8" x="48" y="74.5" width="80" height="8" rx="6" fill="#F3F4F6" />
|
<rect x="61" y="99.5" width="6" height="6" rx="1" fill="#3E3E3E" />
|
||||||
<rect x="48" y="86.5" width="48" height="4" rx="6" fill="#F3F4F6" />
|
<rect x="73" y="99.5" width="6" height="6" rx="1" fill="#C6C6C6" />
|
||||||
<rect x="49" y="99.5" width="6" height="6" rx="1" fill="#C6C6C6" />
|
<rect x="85" y="99.5" width="6" height="6" rx="1" fill="#C6C6C6" />
|
||||||
<rect x="61" y="99.5" width="6" height="6" rx="1" fill="#3E3E3E" />
|
<rect x="97" y="99.5" width="6" height="6" rx="1" fill="#C6C6C6" />
|
||||||
<rect x="73" y="99.5" width="6" height="6" rx="1" fill="#C6C6C6" />
|
<rect x="109" y="99.5" width="6" height="6" rx="1" fill="#C6C6C6" />
|
||||||
<rect x="85" y="99.5" width="6" height="6" rx="1" fill="#C6C6C6" />
|
<rect x="121" y="99.5" width="6" height="6" rx="1" fill="#C6C6C6" />
|
||||||
<rect x="97" y="99.5" width="6" height="6" rx="1" fill="#C6C6C6" />
|
<rect x="133" y="99.5" width="6" height="6" rx="1" fill="#C6C6C6" />
|
||||||
<rect x="109" y="99.5" width="6" height="6" rx="1" fill="#C6C6C6" />
|
<rect x="85" y="113.5" width="6" height="6" rx="1" fill="#C6C6C6" />
|
||||||
<rect x="121" y="99.5" width="6" height="6" rx="1" fill="#C6C6C6" />
|
<rect x="97" y="113.5" width="6" height="6" rx="1" fill="#C6C6C6" />
|
||||||
<rect x="133" y="99.5" width="6" height="6" rx="1" fill="#C6C6C6" />
|
<rect x="109" y="113.5" width="6" height="6" rx="1" fill="#C6C6C6" />
|
||||||
<rect x="85" y="113.5" width="6" height="6" rx="1" fill="#C6C6C6" />
|
<rect x="121" y="113.5" width="6" height="6" rx="1" fill="#C6C6C6" />
|
||||||
<rect x="97" y="113.5" width="6" height="6" rx="1" fill="#C6C6C6" />
|
<rect x="133" y="113.5" width="6" height="6" rx="1" fill="#C6C6C6" />
|
||||||
<rect x="109" y="113.5" width="6" height="6" rx="1" fill="#C6C6C6" />
|
<rect x="49" y="125.5" width="6" height="6" rx="1" fill="#C6C6C6" />
|
||||||
<rect x="121" y="113.5" width="6" height="6" rx="1" fill="#C6C6C6" />
|
<rect x="61" y="125.5" width="6" height="6" rx="1" fill="#3E3E3E" />
|
||||||
<rect x="133" y="113.5" width="6" height="6" rx="1" fill="#C6C6C6" />
|
<path
|
||||||
<rect x="49" y="125.5" width="6" height="6" rx="1" fill="#C6C6C6" />
|
d="M61 124.5H67V122.5H61V124.5ZM68 125.5V131.5H70V125.5H68ZM67 132.5H61V134.5H67V132.5ZM60 131.5V125.5H58V131.5H60ZM61 132.5C60.4477 132.5 60 132.052 60 131.5H58C58 133.157 59.3431 134.5 61 134.5V132.5ZM68 131.5C68 132.052 67.5523 132.5 67 132.5V134.5C68.6569 134.5 70 133.157 70 131.5H68ZM67 124.5C67.5523 124.5 68 124.948 68 125.5H70C70 123.843 68.6569 122.5 67 122.5V124.5ZM61 122.5C59.3431 122.5 58 123.843 58 125.5H60C60 124.948 60.4477 124.5 61 124.5V122.5Z"
|
||||||
<rect x="61" y="125.5" width="6" height="6" rx="1" fill="#3E3E3E" />
|
fill="#3E3E3E"
|
||||||
<path
|
/>
|
||||||
d="M61 124.5H67V122.5H61V124.5ZM68 125.5V131.5H70V125.5H68ZM67 132.5H61V134.5H67V132.5ZM60 131.5V125.5H58V131.5H60ZM61 132.5C60.4477 132.5 60 132.052 60 131.5H58C58 133.157 59.3431 134.5 61 134.5V132.5ZM68 131.5C68 132.052 67.5523 132.5 67 132.5V134.5C68.6569 134.5 70 133.157 70 131.5H68ZM67 124.5C67.5523 124.5 68 124.948 68 125.5H70C70 123.843 68.6569 122.5 67 122.5V124.5ZM61 122.5C59.3431 122.5 58 123.843 58 125.5H60C60 124.948 60.4477 124.5 61 124.5V122.5Z"
|
<rect x="73" y="125.5" width="6" height="6" rx="1" fill="#C6C6C6" />
|
||||||
fill="#3E3E3E"
|
<rect x="85" y="125.5" width="6" height="6" rx="1" fill="#C6C6C6" />
|
||||||
/>
|
<rect x="97" y="125.5" width="6" height="6" rx="1" fill="#C6C6C6" />
|
||||||
<rect x="73" y="125.5" width="6" height="6" rx="1" fill="#C6C6C6" />
|
<rect x="109" y="125.5" width="6" height="6" rx="1" fill="#C6C6C6" />
|
||||||
<rect x="85" y="125.5" width="6" height="6" rx="1" fill="#C6C6C6" />
|
<rect x="121" y="125.5" width="6" height="6" rx="1" fill="#C6C6C6" />
|
||||||
<rect x="97" y="125.5" width="6" height="6" rx="1" fill="#C6C6C6" />
|
<rect x="133" y="125.5" width="6" height="6" rx="1" fill="#C6C6C6" />
|
||||||
<rect x="109" y="125.5" width="6" height="6" rx="1" fill="#C6C6C6" />
|
<rect x="49" y="137.5" width="6" height="6" rx="1" fill="#C6C6C6" />
|
||||||
<rect x="121" y="125.5" width="6" height="6" rx="1" fill="#C6C6C6" />
|
<rect x="61" y="137.5" width="6" height="6" rx="1" fill="#C6C6C6" />
|
||||||
<rect x="133" y="125.5" width="6" height="6" rx="1" fill="#C6C6C6" />
|
<rect x="73" y="137.5" width="6" height="6" rx="1" fill="#C6C6C6" />
|
||||||
<rect x="49" y="137.5" width="6" height="6" rx="1" fill="#C6C6C6" />
|
<rect x="85" y="137.5" width="6" height="6" rx="1" fill="#C6C6C6" />
|
||||||
<rect x="61" y="137.5" width="6" height="6" rx="1" fill="#C6C6C6" />
|
<rect x="97" y="137.5" width="6" height="6" rx="1" fill="#3E3E3E" />
|
||||||
<rect x="73" y="137.5" width="6" height="6" rx="1" fill="#C6C6C6" />
|
<rect x="109" y="137.5" width="6" height="6" rx="1" fill="#3E3E3E" />
|
||||||
<rect x="85" y="137.5" width="6" height="6" rx="1" fill="#C6C6C6" />
|
<rect x="121" y="137.5" width="6" height="6" rx="1" fill="#C6C6C6" />
|
||||||
<rect x="97" y="137.5" width="6" height="6" rx="1" fill="#3E3E3E" />
|
<rect x="133" y="137.5" width="6" height="6" rx="1" fill="#C6C6C6" />
|
||||||
<rect x="109" y="137.5" width="6" height="6" rx="1" fill="#3E3E3E" />
|
<rect x="49" y="149.5" width="6" height="6" rx="1" fill="#C6C6C6" />
|
||||||
<rect x="121" y="137.5" width="6" height="6" rx="1" fill="#C6C6C6" />
|
<rect x="61" y="149.5" width="6" height="6" rx="1" fill="#C6C6C6" />
|
||||||
<rect x="133" y="137.5" width="6" height="6" rx="1" fill="#C6C6C6" />
|
<rect x="73" y="149.5" width="6" height="6" rx="1" fill="#C6C6C6" />
|
||||||
<rect x="49" y="149.5" width="6" height="6" rx="1" fill="#C6C6C6" />
|
<rect x="85" y="149.5" width="6" height="6" rx="1" fill="#C6C6C6" />
|
||||||
<rect x="61" y="149.5" width="6" height="6" rx="1" fill="#C6C6C6" />
|
<rect x="97" y="149.5" width="6" height="6" rx="1" fill="#3E3E3E" />
|
||||||
<rect x="73" y="149.5" width="6" height="6" rx="1" fill="#C6C6C6" />
|
<rect x="109" y="149.5" width="6" height="6" rx="1" fill="#3E3E3E" />
|
||||||
<rect x="85" y="149.5" width="6" height="6" rx="1" fill="#C6C6C6" />
|
<rect x="121" y="149.5" width="6" height="6" rx="1" fill="#C6C6C6" />
|
||||||
<rect x="97" y="149.5" width="6" height="6" rx="1" fill="#3E3E3E" />
|
<rect x="133" y="149.5" width="6" height="6" rx="1" fill="#C6C6C6" />
|
||||||
<rect x="109" y="149.5" width="6" height="6" rx="1" fill="#3E3E3E" />
|
<rect x="49" y="161.5" width="6" height="6" rx="1" fill="#C6C6C6" />
|
||||||
<rect x="121" y="149.5" width="6" height="6" rx="1" fill="#C6C6C6" />
|
<rect x="61" y="161.5" width="6" height="6" rx="1" fill="#C6C6C6" />
|
||||||
<rect x="133" y="149.5" width="6" height="6" rx="1" fill="#C6C6C6" />
|
<rect x="73" y="161.5" width="6" height="6" rx="1" fill="#C6C6C6" />
|
||||||
<rect x="49" y="161.5" width="6" height="6" rx="1" fill="#C6C6C6" />
|
<rect x="85" y="161.5" width="6" height="6" rx="1" fill="#C6C6C6" />
|
||||||
<rect x="61" y="161.5" width="6" height="6" rx="1" fill="#C6C6C6" />
|
<rect x="97" y="161.5" width="6" height="6" rx="1" fill="#3E3E3E" />
|
||||||
<rect x="73" y="161.5" width="6" height="6" rx="1" fill="#C6C6C6" />
|
<rect x="109" y="161.5" width="6" height="6" rx="1" fill="#C6C6C6" />
|
||||||
<rect x="85" y="161.5" width="6" height="6" rx="1" fill="#C6C6C6" />
|
<rect x="24.5" y="51" width="139" height="163" rx="6" stroke="#292929" />
|
||||||
<rect x="97" y="161.5" width="6" height="6" rx="1" fill="#3E3E3E" />
|
<rect x="176" y="50.5" width="108" height="164" rx="6" fill="#F3F4F6" />
|
||||||
<rect x="109" y="161.5" width="6" height="6" rx="1" fill="#C6C6C6" />
|
<rect x="24" y="226.5" width="260" height="38.5" rx="6" fill="#F3F4F6" />
|
||||||
<rect x="24.5" y="51" width="139" height="163" rx="6" stroke="#292929" />
|
</svg>
|
||||||
<rect x="176" y="50.5" width="108" height="164" rx="6" fill="#F3F4F6" />
|
),
|
||||||
<rect x="24" y="226.5" width="260" height="38.5" rx="6" fill="#F3F4F6" />
|
},
|
||||||
</svg>
|
{
|
||||||
),
|
title: t("floating_pop_up_button"),
|
||||||
},
|
subtitle: t("floating_button_trigger_modal"),
|
||||||
{
|
type: "floating-popup",
|
||||||
title: "Floating pop-up button",
|
illustration: (
|
||||||
subtitle: "Adds a floating button on your site that launches Cal in a dialog.",
|
<svg
|
||||||
type: "floating-popup",
|
width="100%"
|
||||||
illustration: (
|
height="100%"
|
||||||
<svg
|
className="rounded-md"
|
||||||
width="100%"
|
viewBox="0 0 308 265"
|
||||||
height="100%"
|
fill="none"
|
||||||
className="rounded-md"
|
xmlns="http://www.w3.org/2000/svg">
|
||||||
viewBox="0 0 308 265"
|
<path
|
||||||
fill="none"
|
d="M0 1.99999C0 0.895423 0.895431 0 2 0H306C307.105 0 308 0.895431 308 2V263C308 264.105 307.105 265 306 265H2C0.895431 265 0 264.105 0 263V1.99999Z"
|
||||||
xmlns="http://www.w3.org/2000/svg">
|
fill="white"
|
||||||
<path
|
/>
|
||||||
d="M0 1.99999C0 0.895423 0.895431 0 2 0H306C307.105 0 308 0.895431 308 2V263C308 264.105 307.105 265 306 265H2C0.895431 265 0 264.105 0 263V1.99999Z"
|
<rect x="24" width="260" height="38.5" rx="6" fill="#F3F4F6" />
|
||||||
fill="white"
|
<rect x="24" y="50.5" width="120" height="76" rx="6" fill="#F3F4F6" />
|
||||||
/>
|
<rect x="24" y="138.5" width="120" height="76" rx="6" fill="#F3F4F6" />
|
||||||
<rect x="24" width="260" height="38.5" rx="6" fill="#F3F4F6" />
|
<rect x="156" y="50.5" width="128" height="164" rx="6" fill="#F3F4F6" />
|
||||||
<rect x="24" y="50.5" width="120" height="76" rx="6" fill="#F3F4F6" />
|
<rect x="24" y="226.5" width="260" height="38.5" rx="6" fill="#F3F4F6" />
|
||||||
<rect x="24" y="138.5" width="120" height="76" rx="6" fill="#F3F4F6" />
|
<rect x="226" y="223.5" width="66" height="26" rx="6" fill="#292929" />
|
||||||
<rect x="156" y="50.5" width="128" height="164" rx="6" fill="#F3F4F6" />
|
<rect x="242" y="235.5" width="34" height="2" rx="1" fill="white" />
|
||||||
<rect x="24" y="226.5" width="260" height="38.5" rx="6" fill="#F3F4F6" />
|
</svg>
|
||||||
<rect x="226" y="223.5" width="66" height="26" rx="6" fill="#292929" />
|
),
|
||||||
<rect x="242" y="235.5" width="34" height="2" rx="1" fill="white" />
|
},
|
||||||
</svg>
|
{
|
||||||
),
|
title: t("pop_up_element_click"),
|
||||||
},
|
subtitle: t("open_dialog_with_element_click"),
|
||||||
{
|
type: "element-click",
|
||||||
title: "Pop up via element click",
|
illustration: (
|
||||||
subtitle: "Open your Cal dialog when someone clicks an element.",
|
<svg
|
||||||
type: "element-click",
|
width="100%"
|
||||||
illustration: (
|
height="100%"
|
||||||
<svg
|
className="rounded-md"
|
||||||
width="100%"
|
viewBox="0 0 308 265"
|
||||||
height="100%"
|
fill="none"
|
||||||
className="rounded-md"
|
xmlns="http://www.w3.org/2000/svg">
|
||||||
viewBox="0 0 308 265"
|
<path
|
||||||
fill="none"
|
d="M0 1.99999C0 0.895423 0.895431 0 2 0H306C307.105 0 308 0.895431 308 2V263C308 264.105 307.105 265 306 265H2C0.895431 265 0 264.105 0 263V1.99999Z"
|
||||||
xmlns="http://www.w3.org/2000/svg">
|
fill="white"
|
||||||
<path
|
/>
|
||||||
d="M0 1.99999C0 0.895423 0.895431 0 2 0H306C307.105 0 308 0.895431 308 2V263C308 264.105 307.105 265 306 265H2C0.895431 265 0 264.105 0 263V1.99999Z"
|
<rect x="24" y="0.50293" width="260" height="24" rx="6" fill="#F3F4F6" />
|
||||||
fill="white"
|
<rect x="24" y="35" width="259" height="192" rx="5.5" fill="#F9FAFB" />
|
||||||
/>
|
<g filter="url(#filter0_i_3223_14162)">
|
||||||
<rect x="24" y="0.50293" width="260" height="24" rx="6" fill="#F3F4F6" />
|
<rect opacity="0.8" x="40" y="99" width="24" height="24" rx="2" fill="#E5E7EB" />
|
||||||
<rect x="24" y="35" width="259" height="192" rx="5.5" fill="#F9FAFB" />
|
<rect x="40" y="127" width="48" height="8" rx="1" fill="#E5E7EB" />
|
||||||
<g filter="url(#filter0_i_3223_14162)">
|
<rect x="40" y="139" width="82" height="8" rx="1" fill="#E5E7EB" />
|
||||||
<rect opacity="0.8" x="40" y="99" width="24" height="24" rx="2" fill="#E5E7EB" />
|
<rect x="40" y="151" width="34" height="4" rx="1" fill="#E5E7EB" />
|
||||||
<rect x="40" y="127" width="48" height="8" rx="1" fill="#E5E7EB" />
|
<rect x="40" y="159" width="34" height="4" rx="1" fill="#E5E7EB" />
|
||||||
<rect x="40" y="139" width="82" height="8" rx="1" fill="#E5E7EB" />
|
</g>
|
||||||
<rect x="40" y="151" width="34" height="4" rx="1" fill="#E5E7EB" />
|
<rect x="152" y="48" width="2" height="169" rx="2" fill="#E5E7EB" />
|
||||||
<rect x="40" y="159" width="34" height="4" rx="1" fill="#E5E7EB" />
|
|
||||||
</g>
|
<rect opacity="0.8" x="176" y="84" width="80" height="8" rx="2" fill="#E5E7EB" />
|
||||||
<rect x="152" y="48" width="2" height="169" rx="2" fill="#E5E7EB" />
|
<rect x="176" y="96" width="48" height="4" rx="1" fill="#E5E7EB" />
|
||||||
|
<rect x="177" y="109" width="6" height="6" rx="1" fill="#E5E7EB" />
|
||||||
|
<rect x="189" y="109" width="6" height="6" rx="1" fill="#0D121D" />
|
||||||
|
<rect x="201" y="109" width="6" height="6" rx="1" fill="#E5E7EB" />
|
||||||
|
<rect x="213" y="109" width="6" height="6" rx="1" fill="#E5E7EB" />
|
||||||
|
<rect x="225" y="109" width="6" height="6" rx="1" fill="#E5E7EB" />
|
||||||
|
<rect x="237" y="109" width="6" height="6" rx="1" fill="#E5E7EB" />
|
||||||
|
<rect x="249" y="109" width="6" height="6" rx="1" fill="#E5E7EB" />
|
||||||
|
<rect x="261" y="109" width="6" height="6" rx="1" fill="#E5E7EB" />
|
||||||
|
<rect x="213" y="123" width="6" height="6" rx="1" fill="#E5E7EB" />
|
||||||
|
<rect x="225" y="123" width="6" height="6" rx="1" fill="#E5E7EB" />
|
||||||
|
<rect x="237" y="123" width="6" height="6" rx="1" fill="#E5E7EB" />
|
||||||
|
<rect x="249" y="123" width="6" height="6" rx="1" fill="#E5E7EB" />
|
||||||
|
<rect x="261" y="123" width="6" height="6" rx="1" fill="#E5E7EB" />
|
||||||
|
<rect x="177" y="135" width="6" height="6" rx="1" fill="#E5E7EB" />
|
||||||
|
<rect x="189" y="135" width="6" height="6" rx="1" fill="#0D121D" />
|
||||||
|
<rect x="187.3" y="133.4" width="9" height="9" rx="1.5" stroke="#0D121D" />
|
||||||
|
<rect x="201" y="135" width="6" height="6" rx="1" fill="#E5E7EB" />
|
||||||
|
<rect x="213" y="135" width="6" height="6" rx="1" fill="#E5E7EB" />
|
||||||
|
<rect x="225" y="135" width="6" height="6" rx="1" fill="#E5E7EB" />
|
||||||
|
<rect x="237" y="135" width="6" height="6" rx="1" fill="#E5E7EB" />
|
||||||
|
<rect x="249" y="135" width="6" height="6" rx="1" fill="#E5E7EB" />
|
||||||
|
<rect x="261" y="135" width="6" height="6" rx="1" fill="#E5E7EB" />
|
||||||
|
<rect x="177" y="147" width="6" height="6" rx="1" fill="#E5E7EB" />
|
||||||
|
<rect x="189" y="147" width="6" height="6" rx="1" fill="#E5E7EB" />
|
||||||
|
<rect x="201" y="147" width="6" height="6" rx="1" fill="#E5E7EB" />
|
||||||
|
<rect x="213" y="147" width="6" height="6" rx="1" fill="#E5E7EB" />
|
||||||
|
<rect x="225" y="147" width="6" height="6" rx="1" fill="#0D121D" />
|
||||||
|
<rect x="237" y="147" width="6" height="6" rx="1" fill="#0D121D" />
|
||||||
|
<rect x="249" y="147" width="6" height="6" rx="1" fill="#E5E7EB" />
|
||||||
|
<rect x="261" y="147" width="6" height="6" rx="1" fill="#E5E7EB" />
|
||||||
|
<rect x="177" y="159" width="6" height="6" rx="1" fill="#E5E7EB" />
|
||||||
|
<rect x="189" y="159" width="6" height="6" rx="1" fill="#E5E7EB" />
|
||||||
|
<rect x="201" y="159" width="6" height="6" rx="1" fill="#E5E7EB" />
|
||||||
|
<rect x="213" y="159" width="6" height="6" rx="1" fill="#E5E7EB" />
|
||||||
|
<rect x="225" y="159" width="6" height="6" rx="1" fill="#0D121D" />
|
||||||
|
<rect x="237" y="159" width="6" height="6" rx="1" fill="#0D121D" />
|
||||||
|
<rect x="249" y="159" width="6" height="6" rx="1" fill="#E5E7EB" />
|
||||||
|
<rect x="261" y="159" width="6" height="6" rx="1" fill="#E5E7EB" />
|
||||||
|
<rect x="177" y="171" width="6" height="6" rx="1" fill="#E5E7EB" />
|
||||||
|
<rect x="189" y="171" width="6" height="6" rx="1" fill="#E5E7EB" />
|
||||||
|
<rect x="201" y="171" width="6" height="6" rx="1" fill="#E5E7EB" />
|
||||||
|
<rect x="213" y="171" width="6" height="6" rx="1" fill="#E5E7EB" />
|
||||||
|
<rect x="225" y="171" width="6" height="6" rx="1" fill="#0D121D" />
|
||||||
|
<rect x="237" y="171" width="6" height="6" rx="1" fill="#E5E7EB" />
|
||||||
|
<rect x="24" y="35" width="259" height="192" rx="5.5" stroke="#101010" />
|
||||||
|
<rect x="24" y="241.503" width="260" height="24" rx="6" fill="#F3F4F6" />
|
||||||
|
</svg>
|
||||||
|
),
|
||||||
|
},
|
||||||
|
];
|
||||||
|
})();
|
||||||
|
|
||||||
<rect opacity="0.8" x="176" y="84" width="80" height="8" rx="2" fill="#E5E7EB" />
|
|
||||||
<rect x="176" y="96" width="48" height="4" rx="1" fill="#E5E7EB" />
|
|
||||||
<rect x="177" y="109" width="6" height="6" rx="1" fill="#E5E7EB" />
|
|
||||||
<rect x="189" y="109" width="6" height="6" rx="1" fill="#0D121D" />
|
|
||||||
<rect x="201" y="109" width="6" height="6" rx="1" fill="#E5E7EB" />
|
|
||||||
<rect x="213" y="109" width="6" height="6" rx="1" fill="#E5E7EB" />
|
|
||||||
<rect x="225" y="109" width="6" height="6" rx="1" fill="#E5E7EB" />
|
|
||||||
<rect x="237" y="109" width="6" height="6" rx="1" fill="#E5E7EB" />
|
|
||||||
<rect x="249" y="109" width="6" height="6" rx="1" fill="#E5E7EB" />
|
|
||||||
<rect x="261" y="109" width="6" height="6" rx="1" fill="#E5E7EB" />
|
|
||||||
<rect x="213" y="123" width="6" height="6" rx="1" fill="#E5E7EB" />
|
|
||||||
<rect x="225" y="123" width="6" height="6" rx="1" fill="#E5E7EB" />
|
|
||||||
<rect x="237" y="123" width="6" height="6" rx="1" fill="#E5E7EB" />
|
|
||||||
<rect x="249" y="123" width="6" height="6" rx="1" fill="#E5E7EB" />
|
|
||||||
<rect x="261" y="123" width="6" height="6" rx="1" fill="#E5E7EB" />
|
|
||||||
<rect x="177" y="135" width="6" height="6" rx="1" fill="#E5E7EB" />
|
|
||||||
<rect x="189" y="135" width="6" height="6" rx="1" fill="#0D121D" />
|
|
||||||
<rect x="187.3" y="133.4" width="9" height="9" rx="1.5" stroke="#0D121D" />
|
|
||||||
<rect x="201" y="135" width="6" height="6" rx="1" fill="#E5E7EB" />
|
|
||||||
<rect x="213" y="135" width="6" height="6" rx="1" fill="#E5E7EB" />
|
|
||||||
<rect x="225" y="135" width="6" height="6" rx="1" fill="#E5E7EB" />
|
|
||||||
<rect x="237" y="135" width="6" height="6" rx="1" fill="#E5E7EB" />
|
|
||||||
<rect x="249" y="135" width="6" height="6" rx="1" fill="#E5E7EB" />
|
|
||||||
<rect x="261" y="135" width="6" height="6" rx="1" fill="#E5E7EB" />
|
|
||||||
<rect x="177" y="147" width="6" height="6" rx="1" fill="#E5E7EB" />
|
|
||||||
<rect x="189" y="147" width="6" height="6" rx="1" fill="#E5E7EB" />
|
|
||||||
<rect x="201" y="147" width="6" height="6" rx="1" fill="#E5E7EB" />
|
|
||||||
<rect x="213" y="147" width="6" height="6" rx="1" fill="#E5E7EB" />
|
|
||||||
<rect x="225" y="147" width="6" height="6" rx="1" fill="#0D121D" />
|
|
||||||
<rect x="237" y="147" width="6" height="6" rx="1" fill="#0D121D" />
|
|
||||||
<rect x="249" y="147" width="6" height="6" rx="1" fill="#E5E7EB" />
|
|
||||||
<rect x="261" y="147" width="6" height="6" rx="1" fill="#E5E7EB" />
|
|
||||||
<rect x="177" y="159" width="6" height="6" rx="1" fill="#E5E7EB" />
|
|
||||||
<rect x="189" y="159" width="6" height="6" rx="1" fill="#E5E7EB" />
|
|
||||||
<rect x="201" y="159" width="6" height="6" rx="1" fill="#E5E7EB" />
|
|
||||||
<rect x="213" y="159" width="6" height="6" rx="1" fill="#E5E7EB" />
|
|
||||||
<rect x="225" y="159" width="6" height="6" rx="1" fill="#0D121D" />
|
|
||||||
<rect x="237" y="159" width="6" height="6" rx="1" fill="#0D121D" />
|
|
||||||
<rect x="249" y="159" width="6" height="6" rx="1" fill="#E5E7EB" />
|
|
||||||
<rect x="261" y="159" width="6" height="6" rx="1" fill="#E5E7EB" />
|
|
||||||
<rect x="177" y="171" width="6" height="6" rx="1" fill="#E5E7EB" />
|
|
||||||
<rect x="189" y="171" width="6" height="6" rx="1" fill="#E5E7EB" />
|
|
||||||
<rect x="201" y="171" width="6" height="6" rx="1" fill="#E5E7EB" />
|
|
||||||
<rect x="213" y="171" width="6" height="6" rx="1" fill="#E5E7EB" />
|
|
||||||
<rect x="225" y="171" width="6" height="6" rx="1" fill="#0D121D" />
|
|
||||||
<rect x="237" y="171" width="6" height="6" rx="1" fill="#E5E7EB" />
|
|
||||||
<rect x="24" y="35" width="259" height="192" rx="5.5" stroke="#101010" />
|
|
||||||
<rect x="24" y="241.503" width="260" height="24" rx="6" fill="#F3F4F6" />
|
|
||||||
</svg>
|
|
||||||
),
|
|
||||||
},
|
|
||||||
];
|
|
||||||
const tabs = [
|
const tabs = [
|
||||||
{
|
{
|
||||||
name: "HTML",
|
name: "HTML",
|
||||||
@@ -534,11 +533,7 @@ ${getEmbedTypeSpecificString({ embedFramework: "HTML", embedType, calLink, previ
|
|||||||
<!-- Cal ${embedType} embed code ends -->`
|
<!-- Cal ${embedType} embed code ends -->`
|
||||||
}
|
}
|
||||||
/>
|
/>
|
||||||
<p className="text-subtle hidden text-sm">
|
<p className="text-subtle hidden text-sm">{t("need_help_embedding")}</p>
|
||||||
{t(
|
|
||||||
"Need help? See our guides for embedding Cal on Wix, Squarespace, or WordPress, check our common questions, or explore advanced embed options."
|
|
||||||
)}
|
|
||||||
</p>
|
|
||||||
</>
|
</>
|
||||||
);
|
);
|
||||||
}),
|
}),
|
||||||
@@ -622,7 +617,7 @@ Cal("init", {origin:"${WEBAPP_URL}"});
|
|||||||
const ThemeSelectControl = ({ children, ...props }: ControlProps<{ value: Theme; label: string }, false>) => {
|
const ThemeSelectControl = ({ children, ...props }: ControlProps<{ value: Theme; label: string }, false>) => {
|
||||||
return (
|
return (
|
||||||
<components.Control {...props}>
|
<components.Control {...props}>
|
||||||
<Sun className="text-subtle ml-2 h-4 w-4" />
|
<Sun className="text-subtle mr-2 h-4 w-4" />
|
||||||
{children}
|
{children}
|
||||||
</components.Control>
|
</components.Control>
|
||||||
);
|
);
|
||||||
@@ -642,7 +637,7 @@ const ChooseEmbedTypesDialogContent = () => {
|
|||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div className="items-start space-y-2 md:flex md:space-y-0">
|
<div className="items-start space-y-2 md:flex md:space-y-0">
|
||||||
{embeds.map((embed, index) => (
|
{embeds(t).map((embed, index) => (
|
||||||
<button
|
<button
|
||||||
className="hover:bg-subtle bg-muted w-full rounded-md border border-transparent p-6 text-left hover:rounded-md ltr:mr-4 ltr:last:mr-0 rtl:ml-4 rtl:last:ml-0 lg:w-1/3"
|
className="hover:bg-subtle bg-muted w-full rounded-md border border-transparent p-6 text-left hover:rounded-md ltr:mr-4 ltr:last:mr-0 rtl:ml-4 rtl:last:ml-0 lg:w-1/3"
|
||||||
key={index}
|
key={index}
|
||||||
@@ -692,7 +687,7 @@ const EmbedTypeCodeAndPreviewDialogContent = ({
|
|||||||
});
|
});
|
||||||
|
|
||||||
const refOfEmbedCodesRefs = useRef(embedCodeRefs);
|
const refOfEmbedCodesRefs = useRef(embedCodeRefs);
|
||||||
const embed = embeds.find((embed) => embed.type === embedType);
|
const embed = embeds(t).find((embed) => embed.type === embedType);
|
||||||
|
|
||||||
const [isEmbedCustomizationOpen, setIsEmbedCustomizationOpen] = useState(true);
|
const [isEmbedCustomizationOpen, setIsEmbedCustomizationOpen] = useState(true);
|
||||||
const [isBookingCustomizationOpen, setIsBookingCustomizationOpen] = useState(true);
|
const [isBookingCustomizationOpen, setIsBookingCustomizationOpen] = useState(true);
|
||||||
@@ -806,11 +801,11 @@ const EmbedTypeCodeAndPreviewDialogContent = ({
|
|||||||
const FloatingPopupPositionOptions = [
|
const FloatingPopupPositionOptions = [
|
||||||
{
|
{
|
||||||
value: "bottom-right",
|
value: "bottom-right",
|
||||||
label: "Bottom Right",
|
label: "Bottom right",
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
value: "bottom-left",
|
value: "bottom-left",
|
||||||
label: "Bottom Left",
|
label: "Bottom left",
|
||||||
},
|
},
|
||||||
];
|
];
|
||||||
|
|
||||||
@@ -834,7 +829,7 @@ const EmbedTypeCodeAndPreviewDialogContent = ({
|
|||||||
</button>
|
</button>
|
||||||
{embed.title}
|
{embed.title}
|
||||||
</h3>
|
</h3>
|
||||||
<h4 className="text-emphasis mb-6 text-sm font-normal">{embed.subtitle}</h4>
|
<h4 className="text-subtle mb-6 text-sm font-normal">{embed.subtitle}</h4>
|
||||||
<div className="flex flex-col">
|
<div className="flex flex-col">
|
||||||
<div className={classNames("font-medium", embedType === "element-click" ? "hidden" : "")}>
|
<div className={classNames("font-medium", embedType === "element-click" ? "hidden" : "")}>
|
||||||
<Collapsible
|
<Collapsible
|
||||||
@@ -844,7 +839,7 @@ const EmbedTypeCodeAndPreviewDialogContent = ({
|
|||||||
<div className={classNames(embedType === "inline" ? "block" : "hidden")}>
|
<div className={classNames(embedType === "inline" ? "block" : "hidden")}>
|
||||||
{/*TODO: Add Auto/Fixed toggle from Figma */}
|
{/*TODO: Add Auto/Fixed toggle from Figma */}
|
||||||
<div className="text-default mb-[9px] text-sm">Window sizing</div>
|
<div className="text-default mb-[9px] text-sm">Window sizing</div>
|
||||||
<div className="justify-left flex items-center !font-normal">
|
<div className="justify-left mb-6 flex items-center !font-normal ">
|
||||||
<div className="mr-[9px]">
|
<div className="mr-[9px]">
|
||||||
<TextField
|
<TextField
|
||||||
labelProps={{ className: "hidden" }}
|
labelProps={{ className: "hidden" }}
|
||||||
@@ -892,10 +887,10 @@ const EmbedTypeCodeAndPreviewDialogContent = ({
|
|||||||
</div>
|
</div>
|
||||||
<div
|
<div
|
||||||
className={classNames(
|
className={classNames(
|
||||||
"mt-4 items-center justify-between",
|
"items-center justify-between",
|
||||||
embedType === "floating-popup" ? "text-emphasis" : "hidden"
|
embedType === "floating-popup" ? "text-emphasis" : "hidden"
|
||||||
)}>
|
)}>
|
||||||
<div className="mb-2 text-sm">Button Text</div>
|
<div className="mb-2 text-sm">Button text</div>
|
||||||
{/* Default Values should come from preview iframe */}
|
{/* Default Values should come from preview iframe */}
|
||||||
<TextField
|
<TextField
|
||||||
labelProps={{ className: "hidden" }}
|
labelProps={{ className: "hidden" }}
|
||||||
@@ -910,7 +905,7 @@ const EmbedTypeCodeAndPreviewDialogContent = ({
|
|||||||
};
|
};
|
||||||
});
|
});
|
||||||
}}
|
}}
|
||||||
defaultValue="Book my Cal"
|
defaultValue={t("book_my_cal")}
|
||||||
required
|
required
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
@@ -935,14 +930,14 @@ const EmbedTypeCodeAndPreviewDialogContent = ({
|
|||||||
});
|
});
|
||||||
}}
|
}}
|
||||||
/>
|
/>
|
||||||
<div className="text-default text-sm">Display Calendar Icon Button</div>
|
<div className="text-default my-2 text-sm">Display calendar icon</div>
|
||||||
</div>
|
</div>
|
||||||
<div
|
<div
|
||||||
className={classNames(
|
className={classNames(
|
||||||
"mt-4 items-center justify-between",
|
"mt-4 items-center justify-between",
|
||||||
embedType === "floating-popup" ? "text-emphasis" : "hidden"
|
embedType === "floating-popup" ? "text-emphasis" : "hidden"
|
||||||
)}>
|
)}>
|
||||||
<div className="mb-2">Position of Button</div>
|
<div className="mb-2">Position of button</div>
|
||||||
<Select
|
<Select
|
||||||
onChange={(position) => {
|
onChange={(position) => {
|
||||||
setPreviewState((previewState) => {
|
setPreviewState((previewState) => {
|
||||||
@@ -959,54 +954,50 @@ const EmbedTypeCodeAndPreviewDialogContent = ({
|
|||||||
options={FloatingPopupPositionOptions}
|
options={FloatingPopupPositionOptions}
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
<div
|
<div className="mt-3 flex flex-col xl:flex-row xl:justify-between">
|
||||||
className={classNames(
|
<div className={classNames("mt-4", embedType === "floating-popup" ? "" : "hidden")}>
|
||||||
"mt-4",
|
<div className="whitespace-nowrap">Button color</div>
|
||||||
embedType === "floating-popup" ? "text-emphasis" : "hidden"
|
<div className="mt-2 w-40 xl:mt-0 xl:w-full">
|
||||||
)}>
|
<ColorPicker
|
||||||
<div>Button Color</div>
|
className="w-[130px]"
|
||||||
<div className="w-full">
|
popoverAlign="start"
|
||||||
<ColorPicker
|
container={dialogContentRef?.current ?? undefined}
|
||||||
popoverAlign="start"
|
defaultValue="#000000"
|
||||||
container={dialogContentRef?.current ?? undefined}
|
onChange={(color) => {
|
||||||
defaultValue="#000000"
|
setPreviewState((previewState) => {
|
||||||
onChange={(color) => {
|
return {
|
||||||
setPreviewState((previewState) => {
|
...previewState,
|
||||||
return {
|
floatingPopup: {
|
||||||
...previewState,
|
...previewState.floatingPopup,
|
||||||
floatingPopup: {
|
buttonColor: color,
|
||||||
...previewState.floatingPopup,
|
},
|
||||||
buttonColor: color,
|
};
|
||||||
},
|
});
|
||||||
};
|
}}
|
||||||
});
|
/>
|
||||||
}}
|
</div>
|
||||||
/>
|
|
||||||
</div>
|
</div>
|
||||||
</div>
|
<div className={classNames("mt-4", embedType === "floating-popup" ? "" : "hidden")}>
|
||||||
<div
|
<div className="whitespace-nowrap">Text color</div>
|
||||||
className={classNames(
|
<div className="mt-2 mb-6 w-40 xl:mt-0 xl:w-full">
|
||||||
"mt-4",
|
<ColorPicker
|
||||||
embedType === "floating-popup" ? "text-emphasis" : "hidden"
|
className="w-[130px]"
|
||||||
)}>
|
popoverAlign="start"
|
||||||
<div>Text Color</div>
|
container={dialogContentRef?.current ?? undefined}
|
||||||
<div className="w-full">
|
defaultValue="#000000"
|
||||||
<ColorPicker
|
onChange={(color) => {
|
||||||
popoverAlign="start"
|
setPreviewState((previewState) => {
|
||||||
container={dialogContentRef?.current ?? undefined}
|
return {
|
||||||
defaultValue="#000000"
|
...previewState,
|
||||||
onChange={(color) => {
|
floatingPopup: {
|
||||||
setPreviewState((previewState) => {
|
...previewState.floatingPopup,
|
||||||
return {
|
buttonTextColor: color,
|
||||||
...previewState,
|
},
|
||||||
floatingPopup: {
|
};
|
||||||
...previewState.floatingPopup,
|
});
|
||||||
buttonTextColor: color,
|
}}
|
||||||
},
|
/>
|
||||||
};
|
</div>
|
||||||
});
|
|
||||||
}}
|
|
||||||
/>
|
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</CollapsibleContent>
|
</CollapsibleContent>
|
||||||
@@ -1017,7 +1008,30 @@ const EmbedTypeCodeAndPreviewDialogContent = ({
|
|||||||
open={isBookingCustomizationOpen}
|
open={isBookingCustomizationOpen}
|
||||||
onOpenChange={() => setIsBookingCustomizationOpen((val) => !val)}>
|
onOpenChange={() => setIsBookingCustomizationOpen((val) => !val)}>
|
||||||
<CollapsibleContent>
|
<CollapsibleContent>
|
||||||
<div className="mt-6 text-sm">
|
<div className="text-sm">
|
||||||
|
<Label className="mb-6">
|
||||||
|
<div className="mb-2">Theme</div>
|
||||||
|
<Select
|
||||||
|
className="w-full"
|
||||||
|
defaultValue={ThemeOptions[0]}
|
||||||
|
components={{
|
||||||
|
Control: ThemeSelectControl,
|
||||||
|
IndicatorSeparator: () => null,
|
||||||
|
}}
|
||||||
|
onChange={(option) => {
|
||||||
|
if (!option) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
setPreviewState((previewState) => {
|
||||||
|
return {
|
||||||
|
...previewState,
|
||||||
|
theme: option.value,
|
||||||
|
};
|
||||||
|
});
|
||||||
|
}}
|
||||||
|
options={ThemeOptions}
|
||||||
|
/>
|
||||||
|
</Label>
|
||||||
<div className="mb-6 flex items-center justify-start space-x-2 rtl:space-x-reverse">
|
<div className="mb-6 flex items-center justify-start space-x-2 rtl:space-x-reverse">
|
||||||
<Switch
|
<Switch
|
||||||
checked={previewState.hideEventTypeDetails}
|
checked={previewState.hideEventTypeDetails}
|
||||||
@@ -1056,29 +1070,6 @@ const EmbedTypeCodeAndPreviewDialogContent = ({
|
|||||||
</div>
|
</div>
|
||||||
</Label>
|
</Label>
|
||||||
))}
|
))}
|
||||||
<Label>
|
|
||||||
<div className="mb-2">Theme</div>
|
|
||||||
<Select
|
|
||||||
className="w-full"
|
|
||||||
defaultValue={ThemeOptions[0]}
|
|
||||||
components={{
|
|
||||||
Control: ThemeSelectControl,
|
|
||||||
IndicatorSeparator: () => null,
|
|
||||||
}}
|
|
||||||
onChange={(option) => {
|
|
||||||
if (!option) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
setPreviewState((previewState) => {
|
|
||||||
return {
|
|
||||||
...previewState,
|
|
||||||
theme: option.value,
|
|
||||||
};
|
|
||||||
});
|
|
||||||
}}
|
|
||||||
options={ThemeOptions}
|
|
||||||
/>
|
|
||||||
</Label>
|
|
||||||
</div>
|
</div>
|
||||||
</CollapsibleContent>
|
</CollapsibleContent>
|
||||||
</Collapsible>
|
</Collapsible>
|
||||||
|
|||||||
@@ -1,5 +1,4 @@
|
|||||||
import Link from "next/link";
|
import Link from "next/link";
|
||||||
import { useRouter } from "next/router";
|
|
||||||
import type { IframeHTMLAttributes } from "react";
|
import type { IframeHTMLAttributes } from "react";
|
||||||
import React, { useState } from "react";
|
import React, { useState } from "react";
|
||||||
|
|
||||||
@@ -37,13 +36,13 @@ const Component = ({
|
|||||||
tos,
|
tos,
|
||||||
privacy,
|
privacy,
|
||||||
isProOnly,
|
isProOnly,
|
||||||
|
teamsPlanRequired,
|
||||||
descriptionItems,
|
descriptionItems,
|
||||||
isTemplate,
|
isTemplate,
|
||||||
dependencies,
|
dependencies,
|
||||||
}: Parameters<typeof App>[0]) => {
|
}: Parameters<typeof App>[0]) => {
|
||||||
const { t, i18n } = useLocale();
|
const { t, i18n } = useLocale();
|
||||||
const hasDescriptionItems = descriptionItems && descriptionItems.length > 0;
|
const hasDescriptionItems = descriptionItems && descriptionItems.length > 0;
|
||||||
const router = useRouter();
|
|
||||||
|
|
||||||
const mutation = useAddAppMutation(null, {
|
const mutation = useAddAppMutation(null, {
|
||||||
onSuccess: (data) => {
|
onSuccess: (data) => {
|
||||||
@@ -154,6 +153,7 @@ const Component = ({
|
|||||||
type={type}
|
type={type}
|
||||||
isProOnly={isProOnly}
|
isProOnly={isProOnly}
|
||||||
disableInstall={disableInstall}
|
disableInstall={disableInstall}
|
||||||
|
teamsPlanRequired={teamsPlanRequired}
|
||||||
render={({ useDefaultComponent, ...props }) => {
|
render={({ useDefaultComponent, ...props }) => {
|
||||||
if (useDefaultComponent) {
|
if (useDefaultComponent) {
|
||||||
props = {
|
props = {
|
||||||
@@ -186,7 +186,7 @@ const Component = ({
|
|||||||
label={t("disconnect")}
|
label={t("disconnect")}
|
||||||
credentialId={existingCredentials[0]}
|
credentialId={existingCredentials[0]}
|
||||||
onSuccess={() => {
|
onSuccess={() => {
|
||||||
router.replace("/apps/installed");
|
appCredentials.refetch();
|
||||||
}}
|
}}
|
||||||
/>
|
/>
|
||||||
) : (
|
) : (
|
||||||
@@ -194,6 +194,7 @@ const Component = ({
|
|||||||
type={type}
|
type={type}
|
||||||
isProOnly={isProOnly}
|
isProOnly={isProOnly}
|
||||||
disableInstall={disableInstall}
|
disableInstall={disableInstall}
|
||||||
|
teamsPlanRequired={teamsPlanRequired}
|
||||||
render={({ useDefaultComponent, ...props }) => {
|
render={({ useDefaultComponent, ...props }) => {
|
||||||
if (useDefaultComponent) {
|
if (useDefaultComponent) {
|
||||||
props = {
|
props = {
|
||||||
@@ -243,7 +244,9 @@ const Component = ({
|
|||||||
</div>
|
</div>
|
||||||
<h4 className="text-emphasis mt-8 font-semibold ">{t("pricing")}</h4>
|
<h4 className="text-emphasis mt-8 font-semibold ">{t("pricing")}</h4>
|
||||||
<span className="text-default">
|
<span className="text-default">
|
||||||
{price === 0 ? (
|
{teamsPlanRequired ? (
|
||||||
|
t("teams_plan_required")
|
||||||
|
) : price === 0 ? (
|
||||||
t("free_to_use_apps")
|
t("free_to_use_apps")
|
||||||
) : (
|
) : (
|
||||||
<>
|
<>
|
||||||
@@ -360,6 +363,7 @@ export default function App(props: {
|
|||||||
privacy?: string;
|
privacy?: string;
|
||||||
licenseRequired: AppType["licenseRequired"];
|
licenseRequired: AppType["licenseRequired"];
|
||||||
isProOnly: AppType["isProOnly"];
|
isProOnly: AppType["isProOnly"];
|
||||||
|
teamsPlanRequired: AppType["teamsPlanRequired"];
|
||||||
descriptionItems?: Array<string | { iframe: IframeHTMLAttributes<HTMLIFrameElement> }>;
|
descriptionItems?: Array<string | { iframe: IframeHTMLAttributes<HTMLIFrameElement> }>;
|
||||||
isTemplate?: boolean;
|
isTemplate?: boolean;
|
||||||
disableInstall?: boolean;
|
disableInstall?: boolean;
|
||||||
|
|||||||
@@ -1,9 +1,9 @@
|
|||||||
import { useMutation } from "@tanstack/react-query";
|
|
||||||
import Link from "next/link";
|
import Link from "next/link";
|
||||||
import { Fragment } from "react";
|
import { Fragment } from "react";
|
||||||
|
|
||||||
import { InstallAppButton } from "@calcom/app-store/components";
|
import { InstallAppButton } from "@calcom/app-store/components";
|
||||||
import DisconnectIntegration from "@calcom/features/apps/components/DisconnectIntegration";
|
import DisconnectIntegration from "@calcom/features/apps/components/DisconnectIntegration";
|
||||||
|
import { CalendarSwitch } from "@calcom/features/calendars/CalendarSwitch";
|
||||||
import DestinationCalendarSelector from "@calcom/features/calendars/DestinationCalendarSelector";
|
import DestinationCalendarSelector from "@calcom/features/calendars/DestinationCalendarSelector";
|
||||||
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";
|
||||||
@@ -12,12 +12,10 @@ import {
|
|||||||
Button,
|
Button,
|
||||||
EmptyScreen,
|
EmptyScreen,
|
||||||
List,
|
List,
|
||||||
showToast,
|
|
||||||
AppSkeletonLoader as SkeletonLoader,
|
AppSkeletonLoader as SkeletonLoader,
|
||||||
Switch,
|
|
||||||
ShellSubHeading,
|
ShellSubHeading,
|
||||||
} from "@calcom/ui";
|
} from "@calcom/ui";
|
||||||
import { ArrowLeft, Calendar, Plus } from "@calcom/ui/components/icon";
|
import { Calendar, Plus } from "@calcom/ui/components/icon";
|
||||||
|
|
||||||
import { QueryCell } from "@lib/QueryCell";
|
import { QueryCell } from "@lib/QueryCell";
|
||||||
|
|
||||||
@@ -31,83 +29,6 @@ type Props = {
|
|||||||
destinationCalendarId?: string;
|
destinationCalendarId?: string;
|
||||||
};
|
};
|
||||||
|
|
||||||
function CalendarSwitch(props: {
|
|
||||||
type: string;
|
|
||||||
externalId: string;
|
|
||||||
title: string;
|
|
||||||
defaultSelected: boolean;
|
|
||||||
destination?: boolean;
|
|
||||||
}) {
|
|
||||||
const { t } = useLocale();
|
|
||||||
const utils = trpc.useContext();
|
|
||||||
|
|
||||||
const mutation = useMutation<
|
|
||||||
unknown,
|
|
||||||
unknown,
|
|
||||||
{
|
|
||||||
isOn: boolean;
|
|
||||||
}
|
|
||||||
>(
|
|
||||||
async ({ isOn }) => {
|
|
||||||
const body = {
|
|
||||||
integration: props.type,
|
|
||||||
externalId: props.externalId,
|
|
||||||
};
|
|
||||||
if (isOn) {
|
|
||||||
const res = await fetch("/api/availability/calendar", {
|
|
||||||
method: "POST",
|
|
||||||
headers: {
|
|
||||||
"Content-Type": "application/json",
|
|
||||||
},
|
|
||||||
body: JSON.stringify(body),
|
|
||||||
});
|
|
||||||
if (!res.ok) {
|
|
||||||
throw new Error("Something went wrong");
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
const res = await fetch("/api/availability/calendar", {
|
|
||||||
method: "DELETE",
|
|
||||||
headers: {
|
|
||||||
"Content-Type": "application/json",
|
|
||||||
},
|
|
||||||
body: JSON.stringify(body),
|
|
||||||
});
|
|
||||||
|
|
||||||
if (!res.ok) {
|
|
||||||
throw new Error("Something went wrong");
|
|
||||||
}
|
|
||||||
}
|
|
||||||
},
|
|
||||||
{
|
|
||||||
async onSettled() {
|
|
||||||
await utils.viewer.integrations.invalidate();
|
|
||||||
},
|
|
||||||
onError() {
|
|
||||||
showToast(`Something went wrong when toggling "${props.title}""`, "error");
|
|
||||||
},
|
|
||||||
}
|
|
||||||
);
|
|
||||||
return (
|
|
||||||
<div className="flex flex-col py-1 sm:flex-row">
|
|
||||||
<Switch
|
|
||||||
key={props.externalId}
|
|
||||||
name="enabled"
|
|
||||||
label={props.title}
|
|
||||||
defaultChecked={props.defaultSelected}
|
|
||||||
onCheckedChange={(isOn: boolean) => {
|
|
||||||
mutation.mutate({ isOn });
|
|
||||||
}}
|
|
||||||
/>
|
|
||||||
{!!props.destination && (
|
|
||||||
<span className="bg-subtle text-default ml-8 inline-flex items-center gap-1 rounded-md px-2 py-1 text-sm font-normal sm:ml-4">
|
|
||||||
<ArrowLeft className="h-4 w-4" />
|
|
||||||
{t("adding_events_to")}
|
|
||||||
</span>
|
|
||||||
)}
|
|
||||||
</div>
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
function CalendarList(props: Props) {
|
function CalendarList(props: Props) {
|
||||||
const { t } = useLocale();
|
const { t } = useLocale();
|
||||||
const query = trpc.viewer.integrations.useQuery({ variant: "calendar", onlyInstalled: false });
|
const query = trpc.viewer.integrations.useQuery({ variant: "calendar", onlyInstalled: false });
|
||||||
@@ -185,14 +106,15 @@ function ConnectedCalendarsList(props: Props) {
|
|||||||
{!fromOnboarding && (
|
{!fromOnboarding && (
|
||||||
<>
|
<>
|
||||||
<p className="text-subtle px-5 pt-4 text-sm">{t("toggle_calendars_conflict")}</p>
|
<p className="text-subtle px-5 pt-4 text-sm">{t("toggle_calendars_conflict")}</p>
|
||||||
<ul className="space-y-2 px-5 py-4">
|
<ul className="space-y-4 px-5 py-4">
|
||||||
{item.calendars.map((cal) => (
|
{item.calendars.map((cal) => (
|
||||||
<CalendarSwitch
|
<CalendarSwitch
|
||||||
key={cal.externalId}
|
key={cal.externalId}
|
||||||
externalId={cal.externalId}
|
externalId={cal.externalId}
|
||||||
title={cal.name || "Nameless calendar"}
|
title={cal.name || "Nameless calendar"}
|
||||||
|
name={cal.name || "Nameless calendar"}
|
||||||
type={item.integration.type}
|
type={item.integration.type}
|
||||||
defaultSelected={cal.isSelected}
|
isChecked={cal.isSelected}
|
||||||
destination={cal.externalId === props.destinationCalendarId}
|
destination={cal.externalId === props.destinationCalendarId}
|
||||||
/>
|
/>
|
||||||
))}
|
))}
|
||||||
|
|||||||
@@ -18,9 +18,9 @@ export default function AppsLayout({ children, actions, emptyStore, ...rest }: A
|
|||||||
const { t } = useLocale();
|
const { t } = useLocale();
|
||||||
const session = useSession();
|
const session = useSession();
|
||||||
const router = useRouter();
|
const router = useRouter();
|
||||||
|
const isAdmin = session.data?.user.role === "ADMIN";
|
||||||
|
|
||||||
if (session.status === "loading") return <></>;
|
if (session.status === "loading") return <></>;
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Shell {...rest} actions={actions?.("block")} hideHeadingOnMobile>
|
<Shell {...rest} actions={actions?.("block")} hideHeadingOnMobile>
|
||||||
<div className="flex flex-col xl:flex-row">
|
<div className="flex flex-col xl:flex-row">
|
||||||
@@ -29,8 +29,8 @@ export default function AppsLayout({ children, actions, emptyStore, ...rest }: A
|
|||||||
<EmptyScreen
|
<EmptyScreen
|
||||||
Icon={AlertCircle}
|
Icon={AlertCircle}
|
||||||
headline={t("no_apps")}
|
headline={t("no_apps")}
|
||||||
description={session.data?.user.role === "ADMIN" ? "You can enable apps in the settings" : ""}
|
description={isAdmin ? "You can enable apps in the settings" : ""}
|
||||||
buttonText={session.data?.user.role === "ADMIN" ? t("apps_settings") : ""}
|
buttonText={isAdmin ? t("apps_settings") : ""}
|
||||||
buttonOnClick={() => router.push("/settings/admin/apps/calendar")}
|
buttonOnClick={() => router.push("/settings/admin/apps/calendar")}
|
||||||
/>
|
/>
|
||||||
) : (
|
) : (
|
||||||
|
|||||||
@@ -1,4 +1,3 @@
|
|||||||
import { UserPermissionRole } from "@prisma/client";
|
|
||||||
import { useSession } from "next-auth/react";
|
import { useSession } from "next-auth/react";
|
||||||
import { useRouter } from "next/router";
|
import { useRouter } from "next/router";
|
||||||
import type { ComponentProps } from "react";
|
import type { ComponentProps } from "react";
|
||||||
@@ -6,6 +5,7 @@ import React, { useEffect } from "react";
|
|||||||
|
|
||||||
import SettingsLayout from "@calcom/features/settings/layouts/SettingsLayout";
|
import SettingsLayout from "@calcom/features/settings/layouts/SettingsLayout";
|
||||||
import type Shell from "@calcom/features/shell/Shell";
|
import type Shell from "@calcom/features/shell/Shell";
|
||||||
|
import { UserPermissionRole } from "@calcom/prisma/enums";
|
||||||
import { ErrorBoundary } from "@calcom/ui";
|
import { ErrorBoundary } from "@calcom/ui";
|
||||||
|
|
||||||
export default function AdminLayout({
|
export default function AdminLayout({
|
||||||
|
|||||||
@@ -47,7 +47,7 @@ export function AvailableEventLocations({ locations }: { locations: Props["event
|
|||||||
src={eventLocationType.iconUrl}
|
src={eventLocationType.iconUrl}
|
||||||
className={classNames(
|
className={classNames(
|
||||||
"ml-[2px] h-4 w-4 opacity-70 ltr:mr-[10px] rtl:ml-[10px] dark:opacity-100 ",
|
"ml-[2px] h-4 w-4 opacity-70 ltr:mr-[10px] rtl:ml-[10px] dark:opacity-100 ",
|
||||||
!eventLocationType.iconUrl?.includes("api") ? "dark:invert-[.65]" : ""
|
!eventLocationType.iconUrl?.startsWith("/app-store") ? "dark:invert-[.65]" : ""
|
||||||
)}
|
)}
|
||||||
alt={`${eventLocationType.label} icon`}
|
alt={`${eventLocationType.label} icon`}
|
||||||
/>
|
/>
|
||||||
|
|||||||
@@ -11,7 +11,7 @@ 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 { 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/types";
|
||||||
import { SkeletonContainer, SkeletonText, ToggleGroup } from "@calcom/ui";
|
import { SkeletonContainer, SkeletonText, ToggleGroup } from "@calcom/ui";
|
||||||
|
|
||||||
import classNames from "@lib/classNames";
|
import classNames from "@lib/classNames";
|
||||||
@@ -72,6 +72,7 @@ const AvailableTimes: FC<AvailableTimesProps> = ({
|
|||||||
slotUtcStartDate: slot.time,
|
slotUtcStartDate: slot.time,
|
||||||
eventTypeId,
|
eventTypeId,
|
||||||
slotUtcEndDate: dayjs(slot.time).utc().add(duration, "minutes").format(),
|
slotUtcEndDate: dayjs(slot.time).utc().add(duration, "minutes").format(),
|
||||||
|
bookingUid: slot.bookingUid,
|
||||||
});
|
});
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|||||||
@@ -1,10 +1,10 @@
|
|||||||
import { SchedulingType } from "@prisma/client";
|
|
||||||
import type { FC, ReactNode } from "react";
|
import type { FC, ReactNode } from "react";
|
||||||
import { useEffect } from "react";
|
import { useEffect } from "react";
|
||||||
|
|
||||||
import dayjs from "@calcom/dayjs";
|
import dayjs from "@calcom/dayjs";
|
||||||
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 { SchedulingType } from "@calcom/prisma/enums";
|
||||||
import { Badge } from "@calcom/ui";
|
import { Badge } from "@calcom/ui";
|
||||||
import { CheckSquare, Clock } from "@calcom/ui/components/icon";
|
import { CheckSquare, Clock } from "@calcom/ui/components/icon";
|
||||||
|
|
||||||
|
|||||||
@@ -1,4 +1,3 @@
|
|||||||
import { BookingStatus } from "@prisma/client";
|
|
||||||
import { useRouter } from "next/router";
|
import { useRouter } from "next/router";
|
||||||
import { useState } from "react";
|
import { useState } from "react";
|
||||||
|
|
||||||
@@ -13,6 +12,7 @@ import { formatTime } from "@calcom/lib/date-fns";
|
|||||||
import getPaymentAppData from "@calcom/lib/getPaymentAppData";
|
import getPaymentAppData from "@calcom/lib/getPaymentAppData";
|
||||||
import { useLocale } from "@calcom/lib/hooks/useLocale";
|
import { useLocale } from "@calcom/lib/hooks/useLocale";
|
||||||
import { getEveryFreqFor } from "@calcom/lib/recurringStrings";
|
import { getEveryFreqFor } from "@calcom/lib/recurringStrings";
|
||||||
|
import { BookingStatus } from "@calcom/prisma/enums";
|
||||||
import type { RouterInputs, RouterOutputs } from "@calcom/trpc/react";
|
import type { RouterInputs, RouterOutputs } from "@calcom/trpc/react";
|
||||||
import { trpc } from "@calcom/trpc/react";
|
import { trpc } from "@calcom/trpc/react";
|
||||||
import type { ActionType } from "@calcom/ui";
|
import type { ActionType } from "@calcom/ui";
|
||||||
@@ -29,7 +29,7 @@ import {
|
|||||||
TableActions,
|
TableActions,
|
||||||
TextAreaField,
|
TextAreaField,
|
||||||
} from "@calcom/ui";
|
} from "@calcom/ui";
|
||||||
import { Check, Clock, MapPin, RefreshCcw, Send, Slash, X, CreditCard } from "@calcom/ui/components/icon";
|
import { Check, Clock, MapPin, RefreshCcw, Send, Ban, X, CreditCard } from "@calcom/ui/components/icon";
|
||||||
|
|
||||||
import useMeQuery from "@lib/hooks/useMeQuery";
|
import useMeQuery from "@lib/hooks/useMeQuery";
|
||||||
|
|
||||||
@@ -119,7 +119,7 @@ function BookingListItem(booking: BookingItemProps) {
|
|||||||
onClick: () => {
|
onClick: () => {
|
||||||
setRejectionDialogIsOpen(true);
|
setRejectionDialogIsOpen(true);
|
||||||
},
|
},
|
||||||
icon: Slash,
|
icon: Ban,
|
||||||
disabled: mutation.isLoading,
|
disabled: mutation.isLoading,
|
||||||
},
|
},
|
||||||
// For bookings with payment, only confirm if the booking is paid for
|
// For bookings with payment, only confirm if the booking is paid for
|
||||||
@@ -280,13 +280,13 @@ function BookingListItem(booking: BookingItemProps) {
|
|||||||
isOpenDialog={isOpenSetLocationDialog}
|
isOpenDialog={isOpenSetLocationDialog}
|
||||||
setShowLocationModal={setIsOpenLocationDialog}
|
setShowLocationModal={setIsOpenLocationDialog}
|
||||||
/>
|
/>
|
||||||
{booking.paid && (
|
{booking.paid && booking.payment[0] && (
|
||||||
<ChargeCardDialog
|
<ChargeCardDialog
|
||||||
isOpenDialog={chargeCardDialogIsOpen}
|
isOpenDialog={chargeCardDialogIsOpen}
|
||||||
setIsOpenDialog={setChargeCardDialogIsOpen}
|
setIsOpenDialog={setChargeCardDialogIsOpen}
|
||||||
bookingId={booking.id}
|
bookingId={booking.id}
|
||||||
paymentAmount={booking?.payment[0].amount}
|
paymentAmount={booking.payment[0].amount}
|
||||||
paymentCurrency={booking?.payment[0].currency}
|
paymentCurrency={booking.payment[0].currency}
|
||||||
/>
|
/>
|
||||||
)}
|
)}
|
||||||
{showRecordingsButtons && (
|
{showRecordingsButtons && (
|
||||||
@@ -354,11 +354,15 @@ function BookingListItem(booking: BookingItemProps) {
|
|||||||
{booking.eventType.team.name}
|
{booking.eventType.team.name}
|
||||||
</Badge>
|
</Badge>
|
||||||
)}
|
)}
|
||||||
{booking.paid && (
|
{booking.paid && !booking.payment[0] ? (
|
||||||
|
<Badge className="ltr:mr-2 rtl:ml-2" variant="orange">
|
||||||
|
{t("error_collecting_card")}
|
||||||
|
</Badge>
|
||||||
|
) : booking.paid ? (
|
||||||
<Badge className="ltr:mr-2 rtl:ml-2" variant="green">
|
<Badge className="ltr:mr-2 rtl:ml-2" variant="green">
|
||||||
{booking.payment[0].paymentOption === "HOLD" ? t("card_held") : t("paid")}
|
{booking.payment[0].paymentOption === "HOLD" ? t("card_held") : t("paid")}
|
||||||
</Badge>
|
</Badge>
|
||||||
)}
|
) : null}
|
||||||
{recurringDates !== undefined && (
|
{recurringDates !== undefined && (
|
||||||
<div className="text-muted mt-2 text-sm">
|
<div className="text-muted mt-2 text-sm">
|
||||||
<RecurringBookingsTooltip booking={booking} recurringDates={recurringDates} />
|
<RecurringBookingsTooltip booking={booking} recurringDates={recurringDates} />
|
||||||
@@ -458,7 +462,7 @@ function BookingListItem(booking: BookingItemProps) {
|
|||||||
<RequestSentMessage />
|
<RequestSentMessage />
|
||||||
</div>
|
</div>
|
||||||
)}
|
)}
|
||||||
{booking.status === "ACCEPTED" && booking.paid && booking?.payment[0]?.paymentOption === "HOLD" && (
|
{booking.status === "ACCEPTED" && booking.paid && booking.payment[0]?.paymentOption === "HOLD" && (
|
||||||
<div className="ml-2">
|
<div className="ml-2">
|
||||||
<TableActions actions={chargeCardActions} />
|
<TableActions actions={chargeCardActions} />
|
||||||
</div>
|
</div>
|
||||||
@@ -483,12 +487,12 @@ const RecurringBookingsTooltip = ({ booking, recurringDates }: RecurringBookings
|
|||||||
i18n: { language },
|
i18n: { language },
|
||||||
} = useLocale();
|
} = useLocale();
|
||||||
const now = new Date();
|
const now = new Date();
|
||||||
const recurringCount = recurringDates.filter((date) => {
|
const recurringCount = recurringDates.filter((recurringDate) => {
|
||||||
return (
|
return (
|
||||||
date >= now &&
|
recurringDate >= now &&
|
||||||
!booking.recurringInfo?.bookings[BookingStatus.CANCELLED]
|
!booking.recurringInfo?.bookings[BookingStatus.CANCELLED]
|
||||||
.map((date) => date.toDateString())
|
.map((date) => date.toDateString())
|
||||||
.includes(date.toDateString())
|
.includes(recurringDate.toDateString())
|
||||||
);
|
);
|
||||||
}).length;
|
}).length;
|
||||||
|
|
||||||
|
|||||||
@@ -17,7 +17,7 @@ export function TimezoneDropdown({
|
|||||||
|
|
||||||
return (
|
return (
|
||||||
<>
|
<>
|
||||||
<div className="dark:focus-within:bg-darkgray-200 dark:bg-darkgray-100 dark:hover:bg-darkgray-200 !mt-3 flex w-full max-w-[20rem] items-center rounded-[4px] px-1 text-sm font-medium focus-within:bg-gray-200 hover:bg-gray-100 lg:max-w-[12rem] [&_p]:focus-within:text-gray-900 dark:[&_p]:focus-within:text-white [&_svg]:focus-within:text-gray-900 dark:[&_svg]:focus-within:text-white">
|
<div className="dark:focus-within:bg-darkgray-200 dark:hover:bg-darkgray-200 !mt-3 flex w-full max-w-[20rem] items-center rounded-[4px] px-1 text-sm font-medium focus-within:bg-gray-200 hover:bg-gray-100 lg:max-w-[12rem] [&_p]:focus-within:text-gray-900 dark:[&_p]:focus-within:text-white [&_svg]:focus-within:text-gray-900 dark:[&_svg]:focus-within:text-white">
|
||||||
<Globe className="dark:text-darkgray-600 flex h-4 w-4 text-gray-600 ltr:mr-2 rtl:ml-2" />
|
<Globe className="dark:text-darkgray-600 flex h-4 w-4 text-gray-600 ltr:mr-2 rtl:ml-2" />
|
||||||
<TimeOptions onSelectTimeZone={handleSelectTimeZone} />
|
<TimeOptions onSelectTimeZone={handleSelectTimeZone} />
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
@@ -230,12 +230,14 @@ const BookingPage = ({
|
|||||||
}),
|
}),
|
||||||
{}
|
{}
|
||||||
);
|
);
|
||||||
|
|
||||||
const reserveSlot = () => {
|
const reserveSlot = () => {
|
||||||
if (queryDuration) {
|
if (queryDuration) {
|
||||||
reserveSlotMutation.mutate({
|
reserveSlotMutation.mutate({
|
||||||
eventTypeId: eventType.id,
|
eventTypeId: eventType.id,
|
||||||
slotUtcStartDate: dayjs(queryDate).utc().format(),
|
slotUtcStartDate: dayjs(queryDate).utc().format(),
|
||||||
slotUtcEndDate: dayjs(queryDate).utc().add(parseInt(queryDuration), "minutes").format(),
|
slotUtcEndDate: dayjs(queryDate).utc().add(parseInt(queryDuration), "minutes").format(),
|
||||||
|
bookingUid: currentSlotBooking?.uid,
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
@@ -550,11 +552,11 @@ const BookingPage = ({
|
|||||||
)}>
|
)}>
|
||||||
<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} i18n={i18n} />
|
<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="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]" />
|
||||||
<p className="-ml-2 inline-block items-center px-2">
|
<p className="-ml-2 inline-block items-center px-2">
|
||||||
{getEveryFreqFor({
|
{getEveryFreqFor({
|
||||||
|
|||||||
@@ -11,6 +11,10 @@ import { getEventName } from "@calcom/core/event";
|
|||||||
import getLocationsOptionsForSelect from "@calcom/features/bookings/lib/getLocationOptionsForSelect";
|
import getLocationsOptionsForSelect from "@calcom/features/bookings/lib/getLocationOptionsForSelect";
|
||||||
import DestinationCalendarSelector from "@calcom/features/calendars/DestinationCalendarSelector";
|
import DestinationCalendarSelector from "@calcom/features/calendars/DestinationCalendarSelector";
|
||||||
import useLockedFieldsManager from "@calcom/features/ee/managed-event-types/hooks/useLockedFieldsManager";
|
import useLockedFieldsManager from "@calcom/features/ee/managed-event-types/hooks/useLockedFieldsManager";
|
||||||
|
import {
|
||||||
|
allowDisablingAttendeeConfirmationEmails,
|
||||||
|
allowDisablingHostConfirmationEmails,
|
||||||
|
} from "@calcom/features/ee/workflows/lib/allowDisablingStandardEmails";
|
||||||
import { FormBuilder } from "@calcom/features/form-builder/FormBuilder";
|
import { FormBuilder } from "@calcom/features/form-builder/FormBuilder";
|
||||||
import { classNames } from "@calcom/lib";
|
import { classNames } from "@calcom/lib";
|
||||||
import { APP_NAME, CAL_URL } from "@calcom/lib/constants";
|
import { APP_NAME, CAL_URL } from "@calcom/lib/constants";
|
||||||
@@ -43,6 +47,8 @@ export const EventAdvancedTab = ({ eventType, team }: Pick<EventTypeSetupProps,
|
|||||||
|
|
||||||
const bookingFields: Prisma.JsonObject = {};
|
const bookingFields: Prisma.JsonObject = {};
|
||||||
|
|
||||||
|
const workflows = eventType.workflows.map((workflowOnEventType) => workflowOnEventType.workflow);
|
||||||
|
|
||||||
eventType.bookingFields.forEach(({ name }) => {
|
eventType.bookingFields.forEach(({ name }) => {
|
||||||
bookingFields[name] = name + " input";
|
bookingFields[name] = name + " input";
|
||||||
});
|
});
|
||||||
@@ -316,7 +322,6 @@ export const EventAdvancedTab = ({ eventType, team }: Pick<EventTypeSetupProps,
|
|||||||
disabled={seatsLocked.disabled}
|
disabled={seatsLocked.disabled}
|
||||||
defaultValue={value || 2}
|
defaultValue={value || 2}
|
||||||
min={1}
|
min={1}
|
||||||
className="w-24"
|
|
||||||
addOnSuffix={<>{t("seats")}</>}
|
addOnSuffix={<>{t("seats")}</>}
|
||||||
onChange={(e) => {
|
onChange={(e) => {
|
||||||
onChange(Math.abs(Number(e.target.value)));
|
onChange(Math.abs(Number(e.target.value)));
|
||||||
@@ -338,7 +343,51 @@ export const EventAdvancedTab = ({ eventType, team }: Pick<EventTypeSetupProps,
|
|||||||
</>
|
</>
|
||||||
)}
|
)}
|
||||||
/>
|
/>
|
||||||
|
{allowDisablingAttendeeConfirmationEmails(workflows) && (
|
||||||
|
<>
|
||||||
|
<hr className="border-subtle" />
|
||||||
|
<Controller
|
||||||
|
name="metadata.disableStandardEmails.confirmation.attendee"
|
||||||
|
control={formMethods.control}
|
||||||
|
render={({ field: { value, onChange } }) => (
|
||||||
|
<>
|
||||||
|
<SettingsToggle
|
||||||
|
title={t("disable_attendees_confirmation_emails")}
|
||||||
|
description={t("disable_attendees_confirmation_emails_description")}
|
||||||
|
checked={value || false}
|
||||||
|
onCheckedChange={(e) => {
|
||||||
|
formMethods.setValue("metadata.disableStandardEmails.confirmation.attendee", e);
|
||||||
|
onChange(e);
|
||||||
|
}}
|
||||||
|
/>
|
||||||
|
</>
|
||||||
|
)}
|
||||||
|
/>
|
||||||
|
</>
|
||||||
|
)}
|
||||||
|
{allowDisablingHostConfirmationEmails(workflows) && (
|
||||||
|
<>
|
||||||
|
<hr className="border-subtle" />
|
||||||
|
<Controller
|
||||||
|
name="metadata.disableStandardEmails.confirmation.host"
|
||||||
|
control={formMethods.control}
|
||||||
|
defaultValue={!!eventType.seatsPerTimeSlot}
|
||||||
|
render={({ field: { value, onChange } }) => (
|
||||||
|
<>
|
||||||
|
<SettingsToggle
|
||||||
|
title={t("disable_host_confirmation_emails")}
|
||||||
|
description={t("disable_host_confirmation_emails_description")}
|
||||||
|
checked={value || false}
|
||||||
|
onCheckedChange={(e) => {
|
||||||
|
formMethods.setValue("metadata.disableStandardEmails.confirmation.host", e);
|
||||||
|
onChange(e);
|
||||||
|
}}
|
||||||
|
/>
|
||||||
|
</>
|
||||||
|
)}
|
||||||
|
/>
|
||||||
|
</>
|
||||||
|
)}
|
||||||
{showEventNameTip && (
|
{showEventNameTip && (
|
||||||
<CustomEventTypeModal
|
<CustomEventTypeModal
|
||||||
close={closeEventNameTip}
|
close={closeEventNameTip}
|
||||||
|
|||||||
@@ -1,4 +1,3 @@
|
|||||||
import { SchedulingType } from "@prisma/client";
|
|
||||||
import type { EventTypeSetup, FormValues } from "pages/event-types/[type]";
|
import type { EventTypeSetup, FormValues } from "pages/event-types/[type]";
|
||||||
import { useState } from "react";
|
import { useState } from "react";
|
||||||
import { Controller, useFormContext } from "react-hook-form";
|
import { Controller, useFormContext } from "react-hook-form";
|
||||||
@@ -10,6 +9,7 @@ import useLockedFieldsManager from "@calcom/features/ee/managed-event-types/hook
|
|||||||
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 { SchedulingType } from "@calcom/prisma/enums";
|
||||||
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 } from "@calcom/ui";
|
import { Badge, Button, Select, SettingsToggle, SkeletonText } from "@calcom/ui";
|
||||||
@@ -119,7 +119,7 @@ const EventTypeScheduleDetails = ({
|
|||||||
{format(dayRange.startTime, timeFormat === 12)}
|
{format(dayRange.startTime, timeFormat === 12)}
|
||||||
</span>
|
</span>
|
||||||
<span className="ms-4">-</span>
|
<span className="ms-4">-</span>
|
||||||
<div className="ml-6">{format(dayRange.endTime, timeFormat === 12)}</div>
|
<div className="ml-6 sm:w-28">{format(dayRange.endTime, timeFormat === 12)}</div>
|
||||||
</div>
|
</div>
|
||||||
))}
|
))}
|
||||||
</div>
|
</div>
|
||||||
@@ -154,12 +154,11 @@ const EventTypeScheduleDetails = ({
|
|||||||
|
|
||||||
const EventTypeSchedule = ({ eventType }: { eventType: EventTypeSetup }) => {
|
const EventTypeSchedule = ({ eventType }: { eventType: EventTypeSetup }) => {
|
||||||
const { t } = useLocale();
|
const { t } = useLocale();
|
||||||
const { shouldLockIndicator, shouldLockDisableProps, isManagedEventType, isChildrenManagedEventType } =
|
const { shouldLockIndicator, isManagedEventType, isChildrenManagedEventType } = useLockedFieldsManager(
|
||||||
useLockedFieldsManager(
|
eventType,
|
||||||
eventType,
|
t("locked_fields_admin_description"),
|
||||||
t("locked_fields_admin_description"),
|
t("locked_fields_member_description")
|
||||||
t("locked_fields_member_description")
|
);
|
||||||
);
|
|
||||||
const { watch } = useFormContext<FormValues>();
|
const { watch } = useFormContext<FormValues>();
|
||||||
const watchSchedule = watch("schedule");
|
const watchSchedule = watch("schedule");
|
||||||
const formMethods = useFormContext<FormValues>();
|
const formMethods = useFormContext<FormValues>();
|
||||||
@@ -205,6 +204,8 @@ const EventTypeSchedule = ({ eventType }: { eventType: EventTypeSetup }) => {
|
|||||||
const value = options.find((option) =>
|
const value = options.find((option) =>
|
||||||
scheduleId
|
scheduleId
|
||||||
? option.value === scheduleId
|
? option.value === scheduleId
|
||||||
|
: isManagedEventType
|
||||||
|
? option.value === 0
|
||||||
: option.value === schedules.find((schedule) => schedule.isDefault)?.id
|
: option.value === schedules.find((schedule) => schedule.isDefault)?.id
|
||||||
);
|
);
|
||||||
|
|
||||||
|
|||||||
@@ -13,7 +13,7 @@ import type { DurationType } from "@calcom/lib/convertToNewDurationType";
|
|||||||
import convertToNewDurationType from "@calcom/lib/convertToNewDurationType";
|
import convertToNewDurationType from "@calcom/lib/convertToNewDurationType";
|
||||||
import findDurationType from "@calcom/lib/findDurationType";
|
import findDurationType from "@calcom/lib/findDurationType";
|
||||||
import { useLocale } from "@calcom/lib/hooks/useLocale";
|
import { useLocale } from "@calcom/lib/hooks/useLocale";
|
||||||
import type { PeriodType } from "@calcom/prisma/client";
|
import type { PeriodType } from "@calcom/prisma/enums";
|
||||||
import type { IntervalLimit } from "@calcom/types/Calendar";
|
import type { IntervalLimit } from "@calcom/types/Calendar";
|
||||||
import { Button, DateRangePicker, InputField, Label, Select, SettingsToggle, TextField } from "@calcom/ui";
|
import { Button, DateRangePicker, InputField, Label, Select, SettingsToggle, TextField } from "@calcom/ui";
|
||||||
import { Plus, Trash } from "@calcom/ui/components/icon";
|
import { Plus, Trash } from "@calcom/ui/components/icon";
|
||||||
@@ -108,7 +108,7 @@ const MinimumBookingNoticeInput = React.forwardRef<
|
|||||||
});
|
});
|
||||||
|
|
||||||
export const EventLimitsTab = ({ eventType }: Pick<EventTypeSetupProps, "eventType">) => {
|
export const EventLimitsTab = ({ eventType }: Pick<EventTypeSetupProps, "eventType">) => {
|
||||||
const { t } = useLocale();
|
const { t, i18n } = useLocale();
|
||||||
const formMethods = useFormContext<FormValues>();
|
const formMethods = useFormContext<FormValues>();
|
||||||
|
|
||||||
const PERIOD_TYPES = [
|
const PERIOD_TYPES = [
|
||||||
@@ -149,12 +149,25 @@ export const EventLimitsTab = ({ eventType }: Pick<EventTypeSetupProps, "eventTy
|
|||||||
const bookingLimitsLocked = shouldLockDisableProps("bookingLimits");
|
const bookingLimitsLocked = shouldLockDisableProps("bookingLimits");
|
||||||
const durationLimitsLocked = shouldLockDisableProps("durationLimits");
|
const durationLimitsLocked = shouldLockDisableProps("durationLimits");
|
||||||
const periodTypeLocked = shouldLockDisableProps("periodType");
|
const periodTypeLocked = shouldLockDisableProps("periodType");
|
||||||
|
const offsetStartLockedProps = shouldLockDisableProps("offsetStart");
|
||||||
|
|
||||||
const optionsPeriod = [
|
const optionsPeriod = [
|
||||||
{ value: 1, label: t("calendar_days") },
|
{ value: 1, label: t("calendar_days") },
|
||||||
{ value: 0, label: t("business_days") },
|
{ value: 0, label: t("business_days") },
|
||||||
];
|
];
|
||||||
|
|
||||||
|
// offsetStart toggle is client-side only, opened by default if offsetStart is set
|
||||||
|
const offsetStartValue = useWatch({
|
||||||
|
control: formMethods.control,
|
||||||
|
name: "offsetStart",
|
||||||
|
});
|
||||||
|
const [offsetToggle, setOffsetToggle] = useState(() => offsetStartValue > 0);
|
||||||
|
|
||||||
|
// Preview how the offset will affect start times
|
||||||
|
const offsetOriginalTime = new Date();
|
||||||
|
offsetOriginalTime.setHours(9, 0, 0, 0);
|
||||||
|
const offsetAdjustedTime = new Date(offsetOriginalTime.getTime() + offsetStartValue * 60 * 1000);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className="space-y-8">
|
<div className="space-y-8">
|
||||||
<div className="space-y-4 lg:space-y-8">
|
<div className="space-y-4 lg:space-y-8">
|
||||||
@@ -432,6 +445,32 @@ export const EventLimitsTab = ({ eventType }: Pick<EventTypeSetupProps, "eventTy
|
|||||||
</SettingsToggle>
|
</SettingsToggle>
|
||||||
)}
|
)}
|
||||||
/>
|
/>
|
||||||
|
<hr className="border-subtle" />
|
||||||
|
<SettingsToggle
|
||||||
|
title={t("offset_toggle")}
|
||||||
|
description={t("offset_toggle_description")}
|
||||||
|
{...offsetStartLockedProps}
|
||||||
|
checked={offsetToggle}
|
||||||
|
onCheckedChange={(active) => {
|
||||||
|
setOffsetToggle(active);
|
||||||
|
if (!active) {
|
||||||
|
formMethods.setValue("offsetStart", 0);
|
||||||
|
}
|
||||||
|
}}>
|
||||||
|
<TextField
|
||||||
|
required
|
||||||
|
type="number"
|
||||||
|
{...offsetStartLockedProps}
|
||||||
|
label={t("offset_start")}
|
||||||
|
defaultValue={eventType.offsetStart}
|
||||||
|
{...formMethods.register("offsetStart")}
|
||||||
|
addOnSuffix={<>{t("minutes")}</>}
|
||||||
|
hint={t("offset_start_description", {
|
||||||
|
originalTime: offsetOriginalTime.toLocaleTimeString(i18n.language, { timeStyle: "short" }),
|
||||||
|
adjustedTime: offsetAdjustedTime.toLocaleTimeString(i18n.language, { timeStyle: "short" }),
|
||||||
|
})}
|
||||||
|
/>
|
||||||
|
</SettingsToggle>
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -276,7 +276,7 @@ export const EventSetupTab = (
|
|||||||
return (
|
return (
|
||||||
<li
|
<li
|
||||||
key={`${location.type}${index}`}
|
key={`${location.type}${index}`}
|
||||||
className="border-default text-default mb-2 rounded-md border py-1.5 px-2 hover:cursor-pointer">
|
className="border-default text-default mb-2 h-9 rounded-md border py-1.5 px-2 hover:cursor-pointer">
|
||||||
<div className="flex items-center justify-between">
|
<div className="flex items-center justify-between">
|
||||||
<div className="flex items-center">
|
<div className="flex items-center">
|
||||||
<img
|
<img
|
||||||
@@ -285,7 +285,7 @@ export const EventSetupTab = (
|
|||||||
"h-4 w-4",
|
"h-4 w-4",
|
||||||
// invert all the icons except app icons
|
// invert all the icons except app icons
|
||||||
eventLocationType.iconUrl &&
|
eventLocationType.iconUrl &&
|
||||||
!eventLocationType.iconUrl.startsWith("/api") &&
|
!eventLocationType.iconUrl.startsWith("/app-store") &&
|
||||||
"dark:invert"
|
"dark:invert"
|
||||||
)}
|
)}
|
||||||
alt={`${eventLocationType.label} logo`}
|
alt={`${eventLocationType.label} logo`}
|
||||||
@@ -390,7 +390,12 @@ export const EventSetupTab = (
|
|||||||
addOnLeading={
|
addOnLeading={
|
||||||
<>
|
<>
|
||||||
{CAL_URL?.replace(/^(https?:|)\/\//, "")}/
|
{CAL_URL?.replace(/^(https?:|)\/\//, "")}/
|
||||||
{team ? "team/" + team.slug : eventType.users[0].username}/
|
{!isManagedEventType
|
||||||
|
? team
|
||||||
|
? "team/" + team.slug
|
||||||
|
: eventType.users[0].username
|
||||||
|
: t("username_placeholder")}
|
||||||
|
/
|
||||||
</>
|
</>
|
||||||
}
|
}
|
||||||
{...formMethods.register("slug", {
|
{...formMethods.register("slug", {
|
||||||
@@ -466,6 +471,7 @@ export const EventSetupTab = (
|
|||||||
defaultValue={eventType.length ?? 15}
|
defaultValue={eventType.length ?? 15}
|
||||||
{...formMethods.register("length")}
|
{...formMethods.register("length")}
|
||||||
addOnSuffix={<>{t("minutes")}</>}
|
addOnSuffix={<>{t("minutes")}</>}
|
||||||
|
min={1}
|
||||||
/>
|
/>
|
||||||
)}
|
)}
|
||||||
{!lengthLockedProps.disabled && (
|
{!lengthLockedProps.disabled && (
|
||||||
|
|||||||
@@ -1,4 +1,3 @@
|
|||||||
import { SchedulingType } from "@prisma/client";
|
|
||||||
import type { EventTypeSetupProps, FormValues } from "pages/event-types/[type]";
|
import type { EventTypeSetupProps, FormValues } from "pages/event-types/[type]";
|
||||||
import { useEffect, useRef } from "react";
|
import { useEffect, useRef } from "react";
|
||||||
import type { ComponentProps } from "react";
|
import type { ComponentProps } from "react";
|
||||||
@@ -10,6 +9,7 @@ import CheckedTeamSelect from "@calcom/features/eventtypes/components/CheckedTea
|
|||||||
import ChildrenEventTypeSelect from "@calcom/features/eventtypes/components/ChildrenEventTypeSelect";
|
import ChildrenEventTypeSelect from "@calcom/features/eventtypes/components/ChildrenEventTypeSelect";
|
||||||
import { WEBAPP_URL } from "@calcom/lib/constants";
|
import { WEBAPP_URL } from "@calcom/lib/constants";
|
||||||
import { useLocale } from "@calcom/lib/hooks/useLocale";
|
import { useLocale } from "@calcom/lib/hooks/useLocale";
|
||||||
|
import { SchedulingType } from "@calcom/prisma/enums";
|
||||||
import { Label, Select } from "@calcom/ui";
|
import { Label, Select } from "@calcom/ui";
|
||||||
|
|
||||||
interface IUserToValue {
|
interface IUserToValue {
|
||||||
|
|||||||
@@ -170,7 +170,7 @@ export const EventTeamWebhooksTab = ({
|
|||||||
</Dialog>
|
</Dialog>
|
||||||
{/* Edit webhook dialog */}
|
{/* Edit webhook dialog */}
|
||||||
<Dialog open={editModalOpen} onOpenChange={(isOpen) => !isOpen && setEditModalOpen(false)}>
|
<Dialog open={editModalOpen} onOpenChange={(isOpen) => !isOpen && setEditModalOpen(false)}>
|
||||||
<DialogContent title={t("edit_webhook")}>
|
<DialogContent enableOverflow title={t("edit_webhook")}>
|
||||||
<WebhookForm
|
<WebhookForm
|
||||||
webhook={webhookToEdit}
|
webhook={webhookToEdit}
|
||||||
apps={installedApps?.items.map((app) => app.slug)}
|
apps={installedApps?.items.map((app) => app.slug)}
|
||||||
|
|||||||
@@ -1,4 +1,3 @@
|
|||||||
import { SchedulingType } from "@prisma/client";
|
|
||||||
import { Webhook as TbWebhook } from "lucide-react";
|
import { Webhook as TbWebhook } from "lucide-react";
|
||||||
import type { TFunction } from "next-i18next";
|
import type { TFunction } from "next-i18next";
|
||||||
import { Trans } from "next-i18next";
|
import { Trans } from "next-i18next";
|
||||||
@@ -13,6 +12,7 @@ import { classNames } from "@calcom/lib";
|
|||||||
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 { HttpError } from "@calcom/lib/http-error";
|
import { HttpError } from "@calcom/lib/http-error";
|
||||||
|
import { SchedulingType } from "@calcom/prisma/enums";
|
||||||
import { trpc, TRPCClientError } from "@calcom/trpc/react";
|
import { trpc, TRPCClientError } from "@calcom/trpc/react";
|
||||||
import {
|
import {
|
||||||
Button,
|
Button,
|
||||||
@@ -170,7 +170,7 @@ function EventTypeSingleLayout({
|
|||||||
|
|
||||||
// Define tab navigation here
|
// Define tab navigation here
|
||||||
const EventTypeTabs = useMemo(() => {
|
const EventTypeTabs = useMemo(() => {
|
||||||
let navigation = getNavigation({
|
const navigation = getNavigation({
|
||||||
t,
|
t,
|
||||||
eventType,
|
eventType,
|
||||||
enabledAppsNumber,
|
enabledAppsNumber,
|
||||||
@@ -210,7 +210,7 @@ function EventTypeSingleLayout({
|
|||||||
}
|
}
|
||||||
if (isManagedEventType || isChildrenManagedEventType) {
|
if (isManagedEventType || isChildrenManagedEventType) {
|
||||||
// Removing apps and workflows for manageg event types by admins v1
|
// Removing apps and workflows for manageg event types by admins v1
|
||||||
navigation = navigation.slice(0, -2);
|
navigation.splice(-2, 1);
|
||||||
} else {
|
} else {
|
||||||
navigation.push({
|
navigation.push({
|
||||||
name: "webhooks",
|
name: "webhooks",
|
||||||
@@ -238,7 +238,7 @@ function EventTypeSingleLayout({
|
|||||||
<div className="flex items-center justify-end">
|
<div className="flex items-center justify-end">
|
||||||
{!eventType.metadata.managedEventConfig && (
|
{!eventType.metadata.managedEventConfig && (
|
||||||
<>
|
<>
|
||||||
<div className="sm:hover:bg-subtle hidden items-center rounded-md px-2 lg:flex">
|
<div className="sm:hover:bg-muted hidden items-center rounded-md px-2 lg:flex">
|
||||||
<Skeleton
|
<Skeleton
|
||||||
as={Label}
|
as={Label}
|
||||||
htmlFor="hiddenSwitch"
|
htmlFor="hiddenSwitch"
|
||||||
|
|||||||
+7
-5
@@ -3,15 +3,16 @@ import { useLocale } from "@calcom/lib/hooks/useLocale";
|
|||||||
import type { App } from "@calcom/types/App";
|
import type { App } from "@calcom/types/App";
|
||||||
import { Button } from "@calcom/ui";
|
import { Button } from "@calcom/ui";
|
||||||
|
|
||||||
interface ICalendarItem {
|
interface IAppConnectionItem {
|
||||||
title: string;
|
title: string;
|
||||||
description?: string;
|
description?: string;
|
||||||
logo: string;
|
logo: string;
|
||||||
type: App["type"];
|
type: App["type"];
|
||||||
|
installed?: boolean;
|
||||||
}
|
}
|
||||||
|
|
||||||
const CalendarItem = (props: ICalendarItem) => {
|
const AppConnectionItem = (props: IAppConnectionItem) => {
|
||||||
const { title, logo, type } = props;
|
const { title, logo, type, installed } = props;
|
||||||
const { t } = useLocale();
|
const { t } = useLocale();
|
||||||
return (
|
return (
|
||||||
<div className="flex flex-row items-center justify-between p-5">
|
<div className="flex flex-row items-center justify-between p-5">
|
||||||
@@ -25,13 +26,14 @@ const CalendarItem = (props: ICalendarItem) => {
|
|||||||
<Button
|
<Button
|
||||||
{...buttonProps}
|
{...buttonProps}
|
||||||
color="secondary"
|
color="secondary"
|
||||||
|
disabled={installed}
|
||||||
type="button"
|
type="button"
|
||||||
onClick={(event) => {
|
onClick={(event) => {
|
||||||
// Save cookie key to return url step
|
// Save cookie key to return url step
|
||||||
document.cookie = `return-to=${window.location.href};path=/;max-age=3600;SameSite=Lax`;
|
document.cookie = `return-to=${window.location.href};path=/;max-age=3600;SameSite=Lax`;
|
||||||
buttonProps && buttonProps.onClick && buttonProps?.onClick(event);
|
buttonProps && buttonProps.onClick && buttonProps?.onClick(event);
|
||||||
}}>
|
}}>
|
||||||
{t("connect")}
|
{installed ? t("installed") : t("connect")}
|
||||||
</Button>
|
</Button>
|
||||||
)}
|
)}
|
||||||
/>
|
/>
|
||||||
@@ -39,4 +41,4 @@ const CalendarItem = (props: ICalendarItem) => {
|
|||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|
||||||
export { CalendarItem };
|
export { AppConnectionItem };
|
||||||
@@ -1,4 +1,4 @@
|
|||||||
import { CalendarSwitch } from "./CalendarSwitch";
|
import { CalendarSwitch } from "@calcom/features/calendars/CalendarSwitch";
|
||||||
|
|
||||||
interface IConnectedCalendarItem {
|
interface IConnectedCalendarItem {
|
||||||
name: string;
|
name: string;
|
||||||
|
|||||||
@@ -0,0 +1,17 @@
|
|||||||
|
import { SkeletonAvatar, SkeletonText, SkeletonButton } from "@calcom/ui";
|
||||||
|
|
||||||
|
export function StepConnectionLoader() {
|
||||||
|
return (
|
||||||
|
<ul className="bg-default divide-subtle border-subtle divide-y rounded-md border p-0 dark:bg-black">
|
||||||
|
{Array.from({ length: 4 }).map((_item, index) => {
|
||||||
|
return (
|
||||||
|
<li className="flex w-full flex-row justify-center border-b-0 py-6" key={index}>
|
||||||
|
<SkeletonAvatar className="mx-6 h-8 w-8 px-4" />
|
||||||
|
<SkeletonText className="ml-1 mr-4 mt-3 h-5 w-full" />
|
||||||
|
<SkeletonButton className="mr-6 h-8 w-20 rounded-md p-5" />
|
||||||
|
</li>
|
||||||
|
);
|
||||||
|
})}
|
||||||
|
</ul>
|
||||||
|
);
|
||||||
|
}
|
||||||
@@ -1,13 +1,13 @@
|
|||||||
import { ArrowRightIcon } from "@heroicons/react/solid";
|
|
||||||
|
|
||||||
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 { trpc } from "@calcom/trpc/react";
|
import { trpc } from "@calcom/trpc/react";
|
||||||
import { SkeletonAvatar, SkeletonText, SkeletonButton, List } from "@calcom/ui";
|
import { List } from "@calcom/ui";
|
||||||
|
import { ArrowRight } from "@calcom/ui/components/icon";
|
||||||
|
|
||||||
import { CalendarItem } from "../components/CalendarItem";
|
import { AppConnectionItem } from "../components/AppConnectionItem";
|
||||||
import { ConnectedCalendarItem } from "../components/ConnectedCalendarItem";
|
import { ConnectedCalendarItem } from "../components/ConnectedCalendarItem";
|
||||||
import { CreateEventsOnCalendarSelect } from "../components/CreateEventsOnCalendarSelect";
|
import { CreateEventsOnCalendarSelect } from "../components/CreateEventsOnCalendarSelect";
|
||||||
|
import { StepConnectionLoader } from "../components/StepConnectionLoader";
|
||||||
|
|
||||||
interface IConnectCalendarsProps {
|
interface IConnectCalendarsProps {
|
||||||
nextStep: () => void;
|
nextStep: () => void;
|
||||||
@@ -33,7 +33,7 @@ const ConnectedCalendars = (props: IConnectCalendarsProps) => {
|
|||||||
firstCalendar.integration.title &&
|
firstCalendar.integration.title &&
|
||||||
firstCalendar.integration.logo && (
|
firstCalendar.integration.logo && (
|
||||||
<>
|
<>
|
||||||
<List className="bg-default border-subtle rounded-md border p-0 dark:bg-black">
|
<List className="bg-default border-subtle rounded-md border p-0 dark:bg-black ">
|
||||||
<ConnectedCalendarItem
|
<ConnectedCalendarItem
|
||||||
key={firstCalendar.integration.title}
|
key={firstCalendar.integration.title}
|
||||||
name={firstCalendar.integration.title}
|
name={firstCalendar.integration.title}
|
||||||
@@ -60,7 +60,7 @@ const ConnectedCalendars = (props: IConnectCalendarsProps) => {
|
|||||||
queryIntegrations.data.items.map((item) => (
|
queryIntegrations.data.items.map((item) => (
|
||||||
<li key={item.title}>
|
<li key={item.title}>
|
||||||
{item.title && item.logo && (
|
{item.title && item.logo && (
|
||||||
<CalendarItem
|
<AppConnectionItem
|
||||||
type={item.type}
|
type={item.type}
|
||||||
title={item.title}
|
title={item.title}
|
||||||
description={item.description}
|
description={item.description}
|
||||||
@@ -72,19 +72,8 @@ const ConnectedCalendars = (props: IConnectCalendarsProps) => {
|
|||||||
</List>
|
</List>
|
||||||
)}
|
)}
|
||||||
|
|
||||||
{queryIntegrations.isLoading && (
|
{queryIntegrations.isLoading && <StepConnectionLoader />}
|
||||||
<ul className="bg-default divide-subtle border-subtle divide-y rounded-md border p-0 dark:bg-black">
|
|
||||||
{[0, 0, 0, 0].map((_item, index) => {
|
|
||||||
return (
|
|
||||||
<li className="flex w-full flex-row justify-center border-b-0 py-6" key={index}>
|
|
||||||
<SkeletonAvatar className="mx-6 h-8 w-8 px-4" />
|
|
||||||
<SkeletonText className="ml-1 mr-4 mt-3 h-5 w-full" />
|
|
||||||
<SkeletonButton className="mr-6 h-8 w-20 rounded-md p-5" />
|
|
||||||
</li>
|
|
||||||
);
|
|
||||||
})}
|
|
||||||
</ul>
|
|
||||||
)}
|
|
||||||
<button
|
<button
|
||||||
type="button"
|
type="button"
|
||||||
data-testid="save-calendar-button"
|
data-testid="save-calendar-button"
|
||||||
@@ -95,7 +84,7 @@ const ConnectedCalendars = (props: IConnectCalendarsProps) => {
|
|||||||
onClick={() => nextStep()}
|
onClick={() => nextStep()}
|
||||||
disabled={disabledNextButton}>
|
disabled={disabledNextButton}>
|
||||||
{firstCalendar ? `${t("continue")}` : `${t("next_step_text")}`}
|
{firstCalendar ? `${t("continue")}` : `${t("next_step_text")}`}
|
||||||
<ArrowRightIcon className="ml-2 h-4 w-4 self-center" aria-hidden="true" />
|
<ArrowRight className="ml-2 h-4 w-4 self-center" aria-hidden="true" />
|
||||||
</button>
|
</button>
|
||||||
</>
|
</>
|
||||||
);
|
);
|
||||||
|
|||||||
@@ -0,0 +1,67 @@
|
|||||||
|
import classNames from "@calcom/lib/classNames";
|
||||||
|
import { useLocale } from "@calcom/lib/hooks/useLocale";
|
||||||
|
import { trpc } from "@calcom/trpc/react";
|
||||||
|
import { List } from "@calcom/ui";
|
||||||
|
import { ArrowRight } from "@calcom/ui/components/icon";
|
||||||
|
|
||||||
|
import { AppConnectionItem } from "../components/AppConnectionItem";
|
||||||
|
import { StepConnectionLoader } from "../components/StepConnectionLoader";
|
||||||
|
|
||||||
|
interface ConnectedAppStepProps {
|
||||||
|
nextStep: () => void;
|
||||||
|
}
|
||||||
|
|
||||||
|
const ConnectedVideoStep = (props: ConnectedAppStepProps) => {
|
||||||
|
const { nextStep } = props;
|
||||||
|
const { data: queryConnectedVideoApps, isLoading } = trpc.viewer.integrations.useQuery({
|
||||||
|
variant: "conferencing",
|
||||||
|
onlyInstalled: false,
|
||||||
|
});
|
||||||
|
const { t } = useLocale();
|
||||||
|
|
||||||
|
const hasAnyInstalledVideoApps = queryConnectedVideoApps?.items.some(
|
||||||
|
(item) => item.credentialIds.length > 0
|
||||||
|
);
|
||||||
|
|
||||||
|
return (
|
||||||
|
<>
|
||||||
|
{!isLoading && (
|
||||||
|
<List className="bg-default border-subtle divide-subtle scroll-bar mx-1 max-h-[45vh] divide-y !overflow-y-scroll rounded-md border p-0 sm:mx-0">
|
||||||
|
{queryConnectedVideoApps?.items &&
|
||||||
|
queryConnectedVideoApps?.items.map((item) => {
|
||||||
|
if (item.slug === "daily-video") return null; // we dont want to show daily here as it is installed by default
|
||||||
|
return (
|
||||||
|
<li key={item.name}>
|
||||||
|
{item.name && item.logo && (
|
||||||
|
<AppConnectionItem
|
||||||
|
type={item.type}
|
||||||
|
title={item.name}
|
||||||
|
description={item.description}
|
||||||
|
logo={item.logo}
|
||||||
|
installed={item.credentialIds.length > 0}
|
||||||
|
/>
|
||||||
|
)}
|
||||||
|
</li>
|
||||||
|
);
|
||||||
|
})}
|
||||||
|
</List>
|
||||||
|
)}
|
||||||
|
|
||||||
|
{isLoading && <StepConnectionLoader />}
|
||||||
|
<button
|
||||||
|
type="button"
|
||||||
|
data-testid="save-video-button"
|
||||||
|
className={classNames(
|
||||||
|
"text-inverted mt-8 flex w-full flex-row justify-center rounded-md border border-black bg-black p-2 text-center text-sm",
|
||||||
|
!hasAnyInstalledVideoApps ? "cursor-not-allowed opacity-20" : ""
|
||||||
|
)}
|
||||||
|
disabled={!hasAnyInstalledVideoApps}
|
||||||
|
onClick={() => nextStep()}>
|
||||||
|
{t("next_step_text")}
|
||||||
|
<ArrowRight className="ml-2 h-4 w-4 self-center" aria-hidden="true" />
|
||||||
|
</button>
|
||||||
|
</>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
export { ConnectedVideoStep };
|
||||||
@@ -1,4 +1,3 @@
|
|||||||
import { ArrowRightIcon } from "@heroicons/react/solid";
|
|
||||||
import { useRouter } from "next/router";
|
import { useRouter } from "next/router";
|
||||||
import { useForm } from "react-hook-form";
|
import { useForm } from "react-hook-form";
|
||||||
|
|
||||||
@@ -9,6 +8,7 @@ import type { TRPCClientErrorLike } from "@calcom/trpc/react";
|
|||||||
import { trpc } from "@calcom/trpc/react";
|
import { trpc } from "@calcom/trpc/react";
|
||||||
import type { AppRouter } from "@calcom/trpc/server/routers/_app";
|
import type { AppRouter } from "@calcom/trpc/server/routers/_app";
|
||||||
import { Button, Form } from "@calcom/ui";
|
import { Button, Form } from "@calcom/ui";
|
||||||
|
import { ArrowRight } from "@calcom/ui/components/icon";
|
||||||
|
|
||||||
interface ISetupAvailabilityProps {
|
interface ISetupAvailabilityProps {
|
||||||
nextStep: () => void;
|
nextStep: () => void;
|
||||||
@@ -81,7 +81,7 @@ const SetupAvailability = (props: ISetupAvailabilityProps) => {
|
|||||||
type="submit"
|
type="submit"
|
||||||
className="mt-2 w-full justify-center p-2 text-sm sm:mt-8"
|
className="mt-2 w-full justify-center p-2 text-sm sm:mt-8"
|
||||||
disabled={availabilityForm.formState.isSubmitting}>
|
disabled={availabilityForm.formState.isSubmitting}>
|
||||||
{t("next_step_text")} <ArrowRightIcon className="ml-2 h-4 w-4 self-center" />
|
{t("next_step_text")} <ArrowRight className="ml-2 h-4 w-4 self-center" />
|
||||||
</Button>
|
</Button>
|
||||||
</div>
|
</div>
|
||||||
</Form>
|
</Form>
|
||||||
|
|||||||
@@ -1,4 +1,3 @@
|
|||||||
import { ArrowRightIcon } from "@heroicons/react/solid";
|
|
||||||
import { useRouter } from "next/router";
|
import { useRouter } from "next/router";
|
||||||
import type { FormEvent } from "react";
|
import type { FormEvent } from "react";
|
||||||
import { useRef, useState } from "react";
|
import { useRef, useState } from "react";
|
||||||
@@ -11,6 +10,7 @@ import turndown from "@calcom/lib/turndownService";
|
|||||||
import { trpc } from "@calcom/trpc/react";
|
import { trpc } from "@calcom/trpc/react";
|
||||||
import { Button, Editor, ImageUploader, Label, showToast } from "@calcom/ui";
|
import { Button, Editor, ImageUploader, Label, showToast } from "@calcom/ui";
|
||||||
import { Avatar } from "@calcom/ui";
|
import { Avatar } from "@calcom/ui";
|
||||||
|
import { ArrowRight } from "@calcom/ui/components/icon";
|
||||||
|
|
||||||
import type { IOnboardingPageProps } from "../../../pages/getting-started/[[...step]]";
|
import type { IOnboardingPageProps } from "../../../pages/getting-started/[[...step]]";
|
||||||
|
|
||||||
@@ -159,7 +159,7 @@ const UserProfile = (props: IUserProfileProps) => {
|
|||||||
type="submit"
|
type="submit"
|
||||||
className="text-inverted mt-8 flex w-full flex-row justify-center rounded-md border border-black bg-black p-2 text-center text-sm">
|
className="text-inverted mt-8 flex w-full flex-row justify-center rounded-md border border-black bg-black p-2 text-center text-sm">
|
||||||
{t("finish")}
|
{t("finish")}
|
||||||
<ArrowRightIcon className="ml-2 h-4 w-4 self-center" aria-hidden="true" />
|
<ArrowRight className="ml-2 h-4 w-4 self-center" aria-hidden="true" />
|
||||||
</Button>
|
</Button>
|
||||||
</form>
|
</form>
|
||||||
);
|
);
|
||||||
|
|||||||
@@ -1,4 +1,3 @@
|
|||||||
import { ArrowRightIcon } from "@heroicons/react/outline";
|
|
||||||
import { zodResolver } from "@hookform/resolvers/zod";
|
import { zodResolver } from "@hookform/resolvers/zod";
|
||||||
import { useEffect, useState } from "react";
|
import { useEffect, useState } from "react";
|
||||||
import { useForm } from "react-hook-form";
|
import { useForm } from "react-hook-form";
|
||||||
@@ -10,6 +9,7 @@ import { useLocale } from "@calcom/lib/hooks/useLocale";
|
|||||||
import { telemetryEventTypes, useTelemetry } from "@calcom/lib/telemetry";
|
import { telemetryEventTypes, useTelemetry } from "@calcom/lib/telemetry";
|
||||||
import { trpc } from "@calcom/trpc/react";
|
import { trpc } from "@calcom/trpc/react";
|
||||||
import { Button, TimezoneSelect } from "@calcom/ui";
|
import { Button, TimezoneSelect } from "@calcom/ui";
|
||||||
|
import { ArrowRight } from "@calcom/ui/components/icon";
|
||||||
|
|
||||||
import { UsernameAvailabilityField } from "@components/ui/UsernameAvailability";
|
import { UsernameAvailabilityField } from "@components/ui/UsernameAvailability";
|
||||||
|
|
||||||
@@ -116,7 +116,7 @@ const UserSettings = (props: IUserSettingsProps) => {
|
|||||||
className="mt-8 flex w-full flex-row justify-center"
|
className="mt-8 flex w-full flex-row justify-center"
|
||||||
disabled={mutation.isLoading}>
|
disabled={mutation.isLoading}>
|
||||||
{t("next_step_text")}
|
{t("next_step_text")}
|
||||||
<ArrowRightIcon className="ml-2 h-4 w-4 self-center" aria-hidden="true" />
|
<ArrowRight className="ml-2 h-4 w-4 self-center" aria-hidden="true" />
|
||||||
</Button>
|
</Button>
|
||||||
</form>
|
</form>
|
||||||
);
|
);
|
||||||
|
|||||||
@@ -1,85 +0,0 @@
|
|||||||
import { useMutation } from "@tanstack/react-query";
|
|
||||||
|
|
||||||
import { useLocale } from "@calcom/lib/hooks/useLocale";
|
|
||||||
import { trpc } from "@calcom/trpc/react";
|
|
||||||
import { Badge, showToast, Switch } from "@calcom/ui";
|
|
||||||
import { ArrowLeft } from "@calcom/ui/components/icon";
|
|
||||||
|
|
||||||
export function CalendarSwitch(props: {
|
|
||||||
type: string;
|
|
||||||
externalId: string;
|
|
||||||
title: string;
|
|
||||||
defaultSelected: boolean;
|
|
||||||
isSelected: boolean;
|
|
||||||
}) {
|
|
||||||
const { t } = useLocale();
|
|
||||||
|
|
||||||
const utils = trpc.useContext();
|
|
||||||
|
|
||||||
const mutation = useMutation<
|
|
||||||
unknown,
|
|
||||||
unknown,
|
|
||||||
{
|
|
||||||
isOn: boolean;
|
|
||||||
}
|
|
||||||
>(
|
|
||||||
async ({ isOn }) => {
|
|
||||||
const body = {
|
|
||||||
integration: props.type,
|
|
||||||
externalId: props.externalId,
|
|
||||||
};
|
|
||||||
if (isOn) {
|
|
||||||
const res = await fetch("/api/availability/calendar", {
|
|
||||||
method: "POST",
|
|
||||||
headers: {
|
|
||||||
"Content-Type": "application/json",
|
|
||||||
},
|
|
||||||
body: JSON.stringify(body),
|
|
||||||
});
|
|
||||||
if (!res.ok) {
|
|
||||||
throw new Error("Something went wrong");
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
const res = await fetch("/api/availability/calendar", {
|
|
||||||
method: "DELETE",
|
|
||||||
headers: {
|
|
||||||
"Content-Type": "application/json",
|
|
||||||
},
|
|
||||||
body: JSON.stringify(body),
|
|
||||||
});
|
|
||||||
|
|
||||||
if (!res.ok) {
|
|
||||||
throw new Error("Something went wrong");
|
|
||||||
}
|
|
||||||
}
|
|
||||||
},
|
|
||||||
{
|
|
||||||
async onSettled() {
|
|
||||||
await utils.viewer.integrations.invalidate();
|
|
||||||
},
|
|
||||||
onError() {
|
|
||||||
showToast(`Something went wrong when toggling "${props.title}""`, "error");
|
|
||||||
},
|
|
||||||
}
|
|
||||||
);
|
|
||||||
return (
|
|
||||||
<div className="flex space-x-2 py-1 rtl:space-x-reverse">
|
|
||||||
<Switch
|
|
||||||
key={props.externalId}
|
|
||||||
name="enabled"
|
|
||||||
label={props.title}
|
|
||||||
defaultChecked={props.isSelected}
|
|
||||||
onCheckedChange={(isOn: boolean) => {
|
|
||||||
mutation.mutate({ isOn });
|
|
||||||
}}
|
|
||||||
/>
|
|
||||||
{props.defaultSelected && (
|
|
||||||
<Badge variant="gray">
|
|
||||||
<div className="flex items-center">
|
|
||||||
<ArrowLeft className="text-default mr-1 h-4 w-4" /> {t("adding_events_to")}
|
|
||||||
</div>
|
|
||||||
</Badge>
|
|
||||||
)}
|
|
||||||
</div>
|
|
||||||
);
|
|
||||||
}
|
|
||||||
@@ -31,7 +31,7 @@ const Member = ({ member, teamName }: { member: MemberType; teamName: string | n
|
|||||||
{!isBioEmpty ? (
|
{!isBioEmpty ? (
|
||||||
<>
|
<>
|
||||||
<div
|
<div
|
||||||
className=" text-subtle text-sm [&_a]:text-blue-500 [&_a]:underline [&_a]:hover:text-blue-600"
|
className=" text-subtle break-words text-sm [&_a]:text-blue-500 [&_a]:underline [&_a]:hover:text-blue-600"
|
||||||
dangerouslySetInnerHTML={{ __html: md.render(member.bio || "") }}
|
dangerouslySetInnerHTML={{ __html: md.render(member.bio || "") }}
|
||||||
/>
|
/>
|
||||||
</>
|
</>
|
||||||
|
|||||||
@@ -1,4 +1,3 @@
|
|||||||
import { StarIcon as StarIconSolid } from "@heroicons/react/solid";
|
|
||||||
import classNames from "classnames";
|
import classNames from "classnames";
|
||||||
import { debounce, noop } from "lodash";
|
import { debounce, noop } from "lodash";
|
||||||
import { useRouter } from "next/router";
|
import { useRouter } from "next/router";
|
||||||
@@ -15,6 +14,7 @@ import type { RouterOutputs } from "@calcom/trpc/react";
|
|||||||
import { trpc } from "@calcom/trpc/react";
|
import { trpc } from "@calcom/trpc/react";
|
||||||
import type { AppRouter } from "@calcom/trpc/server/routers/_app";
|
import type { AppRouter } from "@calcom/trpc/server/routers/_app";
|
||||||
import { Button, Dialog, DialogClose, DialogContent, Input, Label } from "@calcom/ui";
|
import { Button, Dialog, DialogClose, DialogContent, Input, Label } from "@calcom/ui";
|
||||||
|
import { Star as StarSolid } from "@calcom/ui/components/icon";
|
||||||
import { Check, Edit2, ExternalLink } from "@calcom/ui/components/icon";
|
import { Check, Edit2, ExternalLink } from "@calcom/ui/components/icon";
|
||||||
|
|
||||||
export enum UsernameChangeStatusEnum {
|
export enum UsernameChangeStatusEnum {
|
||||||
@@ -230,12 +230,16 @@ const PremiumTextfield = (props: ICustomUsernameProps) => {
|
|||||||
<div className="absolute top-0 right-2 flex flex-row">
|
<div className="absolute top-0 right-2 flex flex-row">
|
||||||
<span
|
<span
|
||||||
className={classNames(
|
className={classNames(
|
||||||
"mx-2 py-1",
|
"mx-2 py-2",
|
||||||
isInputUsernamePremium ? "text-orange-400" : "",
|
isInputUsernamePremium ? "text-transparent" : "",
|
||||||
usernameIsAvailable ? "" : ""
|
usernameIsAvailable ? "" : ""
|
||||||
)}>
|
)}>
|
||||||
{isInputUsernamePremium ? <StarIconSolid className="mt-[2px] h-4 w-4" /> : <></>}
|
{isInputUsernamePremium ? <StarSolid className="mt-[2px] h-4 w-4 fill-orange-400" /> : <></>}
|
||||||
{!isInputUsernamePremium && usernameIsAvailable ? <Check className="mt-2 h-4 w-4" /> : <></>}
|
{!isInputUsernamePremium && usernameIsAvailable ? (
|
||||||
|
<Check className="mt-[2px] h-4 w-4" />
|
||||||
|
) : (
|
||||||
|
<></>
|
||||||
|
)}
|
||||||
</span>
|
</span>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
@@ -25,7 +25,7 @@ const OptionWithIcon = ({ icon, label }: { icon?: string; label: string }) => {
|
|||||||
src={icon}
|
src={icon}
|
||||||
alt="cover"
|
alt="cover"
|
||||||
// invert all the icons except app icons
|
// invert all the icons except app icons
|
||||||
className={cx("h-3.5 w-3.5", icon && !icon.startsWith("/api") && "dark:invert")}
|
className={cx("h-3.5 w-3.5", icon && !icon.startsWith("/app-store") && "dark:invert")}
|
||||||
/>
|
/>
|
||||||
)}
|
)}
|
||||||
<span className={classNames("text-sm font-medium")}>{label}</span>
|
<span className={classNames("text-sm font-medium")}>{label}</span>
|
||||||
|
|||||||
+108
-47
@@ -19,12 +19,17 @@ import { MetaProvider } from "@calcom/ui";
|
|||||||
import usePublicPage from "@lib/hooks/usePublicPage";
|
import usePublicPage from "@lib/hooks/usePublicPage";
|
||||||
import type { WithNonceProps } from "@lib/withNonce";
|
import type { WithNonceProps } from "@lib/withNonce";
|
||||||
|
|
||||||
|
import { useViewerI18n } from "@components/I18nLanguageHandler";
|
||||||
|
|
||||||
const I18nextAdapter = appWithTranslation<NextJsAppProps<SSRConfig> & { children: React.ReactNode }>(
|
const I18nextAdapter = appWithTranslation<NextJsAppProps<SSRConfig> & { children: React.ReactNode }>(
|
||||||
({ children }) => <>{children}</>
|
({ children }) => <>{children}</>
|
||||||
);
|
);
|
||||||
|
|
||||||
// Workaround for https://github.com/vercel/next.js/issues/8592
|
// Workaround for https://github.com/vercel/next.js/issues/8592
|
||||||
export type AppProps = Omit<NextAppProps<WithNonceProps & Record<string, unknown>>, "Component"> & {
|
export type AppProps = Omit<
|
||||||
|
NextAppProps<WithNonceProps & { themeBasis?: string } & Record<string, unknown>>,
|
||||||
|
"Component"
|
||||||
|
> & {
|
||||||
Component: NextAppProps["Component"] & {
|
Component: NextAppProps["Component"] & {
|
||||||
requiresLicense?: boolean;
|
requiresLicense?: boolean;
|
||||||
isThemeSupported?: boolean;
|
isThemeSupported?: boolean;
|
||||||
@@ -46,9 +51,7 @@ const CustomI18nextProvider = (props: AppPropsWithChildren) => {
|
|||||||
* i18n should never be clubbed with other queries, so that it's caching can be managed independently.
|
* i18n should never be clubbed with other queries, so that it's caching can be managed independently.
|
||||||
* We intend to not cache i18n query
|
* We intend to not cache i18n query
|
||||||
**/
|
**/
|
||||||
const { i18n, locale } = trpc.viewer.public.i18n.useQuery(undefined, {
|
const { i18n, locale } = useViewerI18n().data ?? {
|
||||||
trpc: { context: { skipBatch: true } },
|
|
||||||
}).data ?? {
|
|
||||||
locale: "en",
|
locale: "en",
|
||||||
};
|
};
|
||||||
|
|
||||||
@@ -72,58 +75,21 @@ const enum ThemeSupport {
|
|||||||
Booking = "userConfigured",
|
Booking = "userConfigured",
|
||||||
}
|
}
|
||||||
|
|
||||||
const CalcomThemeProvider = (
|
type CalcomThemeProps = PropsWithChildren<
|
||||||
props: PropsWithChildren<
|
Pick<AppProps["pageProps"], "nonce" | "themeBasis"> &
|
||||||
WithNonceProps & {
|
Pick<AppProps["Component"], "isBookingPage" | "isThemeSupported">
|
||||||
isBookingPage?: boolean | ((arg: { router: NextRouter }) => boolean);
|
>;
|
||||||
isThemeSupported?: boolean;
|
const CalcomThemeProvider = (props: CalcomThemeProps) => {
|
||||||
}
|
|
||||||
>
|
|
||||||
) => {
|
|
||||||
// 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
|
|
||||||
const router = useRouter();
|
const router = useRouter();
|
||||||
const isBookingPage = (() => {
|
|
||||||
if (typeof props.isBookingPage === "function") {
|
|
||||||
return props.isBookingPage({ router: router });
|
|
||||||
}
|
|
||||||
|
|
||||||
return props.isBookingPage;
|
|
||||||
})();
|
|
||||||
|
|
||||||
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";
|
||||||
|
|
||||||
const storageKey = isEmbedMode
|
|
||||||
? `embed-theme-${embedNamespace}`
|
|
||||||
: themeSupport === ThemeSupport.App
|
|
||||||
? "app-theme"
|
|
||||||
: themeSupport === ThemeSupport.Booking
|
|
||||||
? "booking-theme"
|
|
||||||
: undefined;
|
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<ThemeProvider
|
<ThemeProvider {...getThemeProviderProps({ props, isEmbedMode, embedNamespace, router })}>
|
||||||
nonce={props.nonce}
|
|
||||||
enableColorScheme={false}
|
|
||||||
enableSystem={themeSupport !== ThemeSupport.None}
|
|
||||||
forcedTheme={forcedTheme}
|
|
||||||
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">
|
|
||||||
{/* 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. */}
|
||||||
{typeof window !== "undefined" && !isEmbedMode && (
|
{typeof window !== "undefined" && !isEmbedMode && (
|
||||||
@@ -140,6 +106,100 @@ const CalcomThemeProvider = (
|
|||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The most important job for this fn is to generate correct storageKey for theme persistenc.
|
||||||
|
* `storageKey` is important because that key is listened for changes(using [`storage`](https://developer.mozilla.org/en-US/docs/Web/API/Window/storage_event) event) and any pages opened will change it's theme based on that(as part of next-themes implementation).
|
||||||
|
* Choosing the right storageKey avoids theme flickering caused by another page using different theme
|
||||||
|
* So, we handle all the cases here namely,
|
||||||
|
* - Both Booking Pages, /free/30min and /pro/30min but configured with different themes but being operated together.
|
||||||
|
* - Embeds using different namespace. They can be completely themed different on the same page.
|
||||||
|
* - Embeds using the same namespace but showing different cal.com links with different themes
|
||||||
|
* - Embeds using the same namespace and showing same cal.com links with different themes(Different theme is possible for same cal.com link in case of embed because of theme config available in embed)
|
||||||
|
* - App has different theme then Booking Pages.
|
||||||
|
*
|
||||||
|
* All the above cases have one thing in common, which is the origin and thus localStorage is shared and thus `storageKey` is critical to avoid theme flickering.
|
||||||
|
*
|
||||||
|
* Some things to note:
|
||||||
|
* - There is a side effect of so many factors in `storageKey` that many localStorage keys will be created if a user goes through all these scenarios(e.g like booking a lot of different users)
|
||||||
|
* - Some might recommend disabling localStorage persistence but that doesn't give good UX as then we would default to light theme always for a few seconds before switching to dark theme(if that's the user's preference).
|
||||||
|
* - We can't disable [`storage`](https://developer.mozilla.org/en-US/docs/Web/API/Window/storage_event) event handling as well because changing theme in one tab won't change the theme without refresh in other tabs. That's again a bad UX
|
||||||
|
* - Theme flickering becomes infinitely ongoing in case of embeds because of the browser's delay in processing `storage` event within iframes. Consider two embeds simulatenously opened with pages A and B. Note the timeline and keep in mind that it happened
|
||||||
|
* because 'setItem(A)' and 'Receives storageEvent(A)' allowed executing setItem(B) in b/w because of the delay.
|
||||||
|
* - t1 -> setItem(A) & Fires storageEvent(A) - On Page A) - Current State(A)
|
||||||
|
* - t2 -> setItem(B) & Fires storageEvent(B) - On Page B) - Current State(B)
|
||||||
|
* - t3 -> Receives storageEvent(A) & thus setItem(A) & thus fires storageEvent(A) (On Page B) - Current State(A)
|
||||||
|
* - t4 -> Receives storageEvent(B) & thus setItem(B) & thus fires storageEvent(B) (On Page A) - Current State(B)
|
||||||
|
* - ... and so on ...
|
||||||
|
*/
|
||||||
|
function getThemeProviderProps({
|
||||||
|
props,
|
||||||
|
isEmbedMode,
|
||||||
|
embedNamespace,
|
||||||
|
router,
|
||||||
|
}: {
|
||||||
|
props: Omit<CalcomThemeProps, "children">;
|
||||||
|
isEmbedMode: boolean;
|
||||||
|
embedNamespace: string | null;
|
||||||
|
router: NextRouter;
|
||||||
|
}) {
|
||||||
|
const isBookingPage = (() => {
|
||||||
|
if (typeof props.isBookingPage === "function") {
|
||||||
|
return props.isBookingPage({ router: router });
|
||||||
|
}
|
||||||
|
return props.isBookingPage;
|
||||||
|
})();
|
||||||
|
|
||||||
|
const themeSupport = isBookingPage
|
||||||
|
? ThemeSupport.Booking
|
||||||
|
: // if isThemeSupported is explicitly false, we don't use theme there
|
||||||
|
props.isThemeSupported === false
|
||||||
|
? ThemeSupport.None
|
||||||
|
: ThemeSupport.App;
|
||||||
|
|
||||||
|
const isBookingPageThemSupportRequired = themeSupport === ThemeSupport.Booking;
|
||||||
|
const themeBasis = props.themeBasis;
|
||||||
|
|
||||||
|
if ((isBookingPageThemSupportRequired || isEmbedMode) && !themeBasis) {
|
||||||
|
console.warn(
|
||||||
|
"`themeBasis` is required for booking page theme support. Not providing it will cause theme flicker."
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
const appearanceIdSuffix = themeBasis ? ":" + themeBasis : "";
|
||||||
|
const forcedTheme = themeSupport === ThemeSupport.None ? "light" : undefined;
|
||||||
|
let embedExplicitlySetThemeSuffix = "";
|
||||||
|
|
||||||
|
if (typeof window !== "undefined") {
|
||||||
|
const embedTheme = window.getEmbedTheme();
|
||||||
|
if (embedTheme) {
|
||||||
|
embedExplicitlySetThemeSuffix = ":" + embedTheme;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
const storageKey = isEmbedMode
|
||||||
|
? // Same Namespace, Same Organizer but different themes would still work seamless and not cause theme flicker
|
||||||
|
// Even though it's recommended to use different namespaces when you want to theme differently on the same page but if the embeds are on different pages, the problem can still arise
|
||||||
|
`embed-theme-${embedNamespace}${appearanceIdSuffix}${embedExplicitlySetThemeSuffix}`
|
||||||
|
: themeSupport === ThemeSupport.App
|
||||||
|
? "app-theme"
|
||||||
|
: isBookingPageThemSupportRequired
|
||||||
|
? `booking-theme${appearanceIdSuffix}`
|
||||||
|
: undefined;
|
||||||
|
|
||||||
|
return {
|
||||||
|
storageKey,
|
||||||
|
forcedTheme,
|
||||||
|
themeSupport,
|
||||||
|
nonce: props.nonce,
|
||||||
|
enableColorScheme: false,
|
||||||
|
enableSystem: themeSupport !== ThemeSupport.None,
|
||||||
|
// 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",
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
function FeatureFlagsProvider({ children }: { children: React.ReactNode }) {
|
function FeatureFlagsProvider({ children }: { children: React.ReactNode }) {
|
||||||
const flags = useFlags();
|
const flags = useFlags();
|
||||||
return <FeatureProvider value={flags}>{children}</FeatureProvider>;
|
return <FeatureProvider value={flags}>{children}</FeatureProvider>;
|
||||||
@@ -157,6 +217,7 @@ const AppProviders = (props: AppPropsWithChildren) => {
|
|||||||
<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 */}
|
{/* 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
|
||||||
|
themeBasis={props.pageProps.themeBasis}
|
||||||
nonce={props.pageProps.nonce}
|
nonce={props.pageProps.nonce}
|
||||||
isThemeSupported={props.Component.isThemeSupported}
|
isThemeSupported={props.Component.isThemeSupported}
|
||||||
isBookingPage={props.Component.isBookingPage}>
|
isBookingPage={props.Component.isBookingPage}>
|
||||||
|
|||||||
@@ -111,12 +111,6 @@ const nextConfig = {
|
|||||||
transform: "lucide-react/dist/esm/icons/{{ kebabCase member }}",
|
transform: "lucide-react/dist/esm/icons/{{ kebabCase member }}",
|
||||||
preventFullImport: true,
|
preventFullImport: true,
|
||||||
},
|
},
|
||||||
"@heroicons/react/solid": {
|
|
||||||
transform: "@heroicons/react/solid/esm/{{ member }}",
|
|
||||||
},
|
|
||||||
"@heroicons/react/outline": {
|
|
||||||
transform: "@heroicons/react/outline/esm/{{ member }}",
|
|
||||||
},
|
|
||||||
"@calcom/features/insights/components": {
|
"@calcom/features/insights/components": {
|
||||||
transform: "@calcom/features/insights/components/{{member}}",
|
transform: "@calcom/features/insights/components/{{member}}",
|
||||||
skipDefaultConversion: true,
|
skipDefaultConversion: true,
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
{
|
{
|
||||||
"name": "@calcom/web",
|
"name": "@calcom/web",
|
||||||
"version": "2.8.10",
|
"version": "2.9.2",
|
||||||
"private": true,
|
"private": true,
|
||||||
"scripts": {
|
"scripts": {
|
||||||
"analyze": "ANALYZE=true next build",
|
"analyze": "ANALYZE=true next build",
|
||||||
@@ -41,7 +41,6 @@
|
|||||||
"@daily-co/daily-js": "^0.37.0",
|
"@daily-co/daily-js": "^0.37.0",
|
||||||
"@formkit/auto-animate": "^1.0.0-beta.5",
|
"@formkit/auto-animate": "^1.0.0-beta.5",
|
||||||
"@glidejs/glide": "^3.5.2",
|
"@glidejs/glide": "^3.5.2",
|
||||||
"@heroicons/react": "^1.0.6",
|
|
||||||
"@hookform/error-message": "^2.0.0",
|
"@hookform/error-message": "^2.0.0",
|
||||||
"@hookform/resolvers": "^2.9.7",
|
"@hookform/resolvers": "^2.9.7",
|
||||||
"@next-auth/prisma-adapter": "^1.0.4",
|
"@next-auth/prisma-adapter": "^1.0.4",
|
||||||
@@ -115,12 +114,11 @@
|
|||||||
"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",
|
||||||
"remark": "^14.0.2",
|
"remove-markdown": "^0.5.0",
|
||||||
"rrule": "^2.7.1",
|
"rrule": "^2.7.1",
|
||||||
"sanitize-html": "^2.10.0",
|
"sanitize-html": "^2.10.0",
|
||||||
"schema-dts": "^1.1.0",
|
"schema-dts": "^1.1.0",
|
||||||
"short-uuid": "^4.2.0",
|
"short-uuid": "^4.2.0",
|
||||||
"strip-markdown": "^5.0.0",
|
|
||||||
"stripe": "^9.16.0",
|
"stripe": "^9.16.0",
|
||||||
"superjson": "1.9.1",
|
"superjson": "1.9.1",
|
||||||
"tailwindcss-radix": "^2.6.0",
|
"tailwindcss-radix": "^2.6.0",
|
||||||
@@ -153,6 +151,7 @@
|
|||||||
"@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/remove-markdown": "^0.3.1",
|
||||||
"@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",
|
||||||
|
|||||||
@@ -1,4 +1,3 @@
|
|||||||
import { BadgeCheckIcon } from "@heroicons/react/solid";
|
|
||||||
import classNames from "classnames";
|
import classNames from "classnames";
|
||||||
import type { GetServerSidePropsContext } from "next";
|
import type { GetServerSidePropsContext } from "next";
|
||||||
import Link from "next/link";
|
import Link from "next/link";
|
||||||
@@ -23,11 +22,12 @@ 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 { stripMarkdown } from "@calcom/lib/stripMarkdown";
|
||||||
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";
|
||||||
import { Avatar, AvatarGroup, HeadSeo } from "@calcom/ui";
|
import { Avatar, AvatarGroup, HeadSeo } from "@calcom/ui";
|
||||||
import { ArrowRight } from "@calcom/ui/components/icon";
|
import { Verified, 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";
|
||||||
@@ -37,7 +37,16 @@ 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) {
|
||||||
const { users, profile, eventTypes, isDynamicGroup, dynamicNames, dynamicUsernames, isSingleUser } = props;
|
const {
|
||||||
|
users,
|
||||||
|
profile,
|
||||||
|
eventTypes,
|
||||||
|
isDynamicGroup,
|
||||||
|
dynamicNames,
|
||||||
|
dynamicUsernames,
|
||||||
|
isSingleUser,
|
||||||
|
markdownStrippedBio,
|
||||||
|
} = props;
|
||||||
const [user] = users; //To be used when we only have a single user, not dynamic group
|
const [user] = users; //To be used when we only have a single user, not dynamic group
|
||||||
useTheme(user.theme);
|
useTheme(user.theme);
|
||||||
const { t } = useLocale();
|
const { t } = useLocale();
|
||||||
@@ -107,11 +116,9 @@ export default function User(props: inferSSRProps<typeof getServerSideProps> & E
|
|||||||
<>
|
<>
|
||||||
<HeadSeo
|
<HeadSeo
|
||||||
title={isDynamicGroup ? dynamicNames.join(", ") : nameOrUsername}
|
title={isDynamicGroup ? dynamicNames.join(", ") : nameOrUsername}
|
||||||
description={
|
description={isDynamicGroup ? `Book events with ${dynamicUsernames.join(", ")}` : markdownStrippedBio}
|
||||||
isDynamicGroup ? `Book events with ${dynamicUsernames.join(", ")}` : (user.bio as string) || ""
|
|
||||||
}
|
|
||||||
meeting={{
|
meeting={{
|
||||||
title: isDynamicGroup ? "" : `${user.bio}`,
|
title: isDynamicGroup ? "" : markdownStrippedBio,
|
||||||
profile: { name: `${profile.name}`, image: null },
|
profile: { name: `${profile.name}`, image: null },
|
||||||
users: isDynamicGroup
|
users: isDynamicGroup
|
||||||
? dynamicUsernames.map((username, index) => ({ username, name: dynamicNames[index] }))
|
? dynamicUsernames.map((username, index) => ({ username, name: dynamicNames[index] }))
|
||||||
@@ -131,12 +138,14 @@ export default function User(props: inferSSRProps<typeof getServerSideProps> & E
|
|||||||
<Avatar imageSrc={user.avatar} size="xl" alt={nameOrUsername} />
|
<Avatar imageSrc={user.avatar} size="xl" alt={nameOrUsername} />
|
||||||
<h1 className="font-cal text-emphasis mb-1 text-3xl">
|
<h1 className="font-cal text-emphasis mb-1 text-3xl">
|
||||||
{nameOrUsername}
|
{nameOrUsername}
|
||||||
{user.verified && <BadgeCheckIcon className=" mx-1 -mt-1 inline h-6 w-6 text-blue-500" />}
|
{user.verified && (
|
||||||
|
<Verified className=" mx-1 -mt-1 inline h-6 w-6 fill-blue-500 text-white dark:text-black" />
|
||||||
|
)}
|
||||||
</h1>
|
</h1>
|
||||||
{!isBioEmpty && (
|
{!isBioEmpty && (
|
||||||
<>
|
<>
|
||||||
<div
|
<div
|
||||||
className=" text-subtle text-sm [&_a]:text-blue-500 [&_a]:underline [&_a]:hover:text-blue-600"
|
className=" text-subtle break-words text-sm [&_a]:text-blue-500 [&_a]:underline [&_a]:hover:text-blue-600"
|
||||||
dangerouslySetInnerHTML={{ __html: props.safeBio }}
|
dangerouslySetInnerHTML={{ __html: props.safeBio }}
|
||||||
/>
|
/>
|
||||||
</>
|
</>
|
||||||
@@ -342,11 +351,14 @@ export const getServerSideProps = async (context: GetServerSidePropsContext) =>
|
|||||||
|
|
||||||
const safeBio = markdownToSafeHTML(user.bio) || "";
|
const safeBio = markdownToSafeHTML(user.bio) || "";
|
||||||
|
|
||||||
|
const markdownStrippedBio = stripMarkdown(user?.bio || "");
|
||||||
return {
|
return {
|
||||||
props: {
|
props: {
|
||||||
users,
|
users,
|
||||||
safeBio,
|
safeBio,
|
||||||
profile,
|
profile,
|
||||||
|
// Dynamic group has no theme preference right now. It uses system theme.
|
||||||
|
themeBasis: isDynamicGroup ? null : user.username,
|
||||||
user: {
|
user: {
|
||||||
emailMd5: crypto.createHash("md5").update(user.email).digest("hex"),
|
emailMd5: crypto.createHash("md5").update(user.email).digest("hex"),
|
||||||
},
|
},
|
||||||
@@ -361,6 +373,7 @@ export const getServerSideProps = async (context: GetServerSidePropsContext) =>
|
|||||||
dynamicNames,
|
dynamicNames,
|
||||||
dynamicUsernames,
|
dynamicUsernames,
|
||||||
isSingleUser,
|
isSingleUser,
|
||||||
|
markdownStrippedBio,
|
||||||
},
|
},
|
||||||
};
|
};
|
||||||
};
|
};
|
||||||
@@ -177,6 +177,8 @@ async function getUserPageProps(context: GetStaticPropsContext) {
|
|||||||
slug: `${user.username}/${eventType.slug}`,
|
slug: `${user.username}/${eventType.slug}`,
|
||||||
image: `${WEBAPP_URL}/${user.username}/avatar.png`,
|
image: `${WEBAPP_URL}/${user.username}/avatar.png`,
|
||||||
},
|
},
|
||||||
|
// Dynamic group has no theme preference right now. It uses system theme.
|
||||||
|
themeBasis: user.username,
|
||||||
away: user?.away,
|
away: user?.away,
|
||||||
isDynamic: false,
|
isDynamic: false,
|
||||||
trpcState: ssg.dehydrate(),
|
trpcState: ssg.dehydrate(),
|
||||||
@@ -307,6 +309,8 @@ async function getDynamicGroupPageProps(context: GetStaticPropsContext) {
|
|||||||
props: {
|
props: {
|
||||||
eventType: eventTypeObject,
|
eventType: eventTypeObject,
|
||||||
profile,
|
profile,
|
||||||
|
// Dynamic group has no theme preference right now. It uses system theme.
|
||||||
|
themeBasis: null,
|
||||||
isDynamic: true,
|
isDynamic: true,
|
||||||
away: false,
|
away: false,
|
||||||
trpcState: ssg.dehydrate(),
|
trpcState: ssg.dehydrate(),
|
||||||
|
|||||||
@@ -293,6 +293,8 @@ export async function getServerSideProps(context: GetServerSidePropsContext) {
|
|||||||
props: {
|
props: {
|
||||||
away: user.away,
|
away: user.away,
|
||||||
profile,
|
profile,
|
||||||
|
// Dynamic group has no theme preference right now. It uses system theme.
|
||||||
|
themeBasis: isDynamicGroupBooking ? null : user.username,
|
||||||
eventType: eventTypeObject,
|
eventType: eventTypeObject,
|
||||||
booking,
|
booking,
|
||||||
currentSlotBooking: currentSlotBooking,
|
currentSlotBooking: currentSlotBooking,
|
||||||
|
|||||||
@@ -23,6 +23,7 @@ class MyDocument extends Document<Props> {
|
|||||||
// If x-csp not set by gSSP, then it's initialPropsOnly
|
// If x-csp not set by gSSP, then it's initialPropsOnly
|
||||||
setHeader(ctx, "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
|
||||||
const parsedUrl = new URL(asPath, "https://dummyurl");
|
const parsedUrl = new URL(asPath, "https://dummyurl");
|
||||||
@@ -45,7 +46,8 @@ class MyDocument extends Document<Props> {
|
|||||||
<link rel="manifest" href="/site.webmanifest" />
|
<link rel="manifest" href="/site.webmanifest" />
|
||||||
<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" media="(prefers-color-scheme: light)" content="#f9fafb" />
|
||||||
|
<meta name="theme-color" media="(prefers-color-scheme: dark)" content="#1C1C1C" />
|
||||||
</Head>
|
</Head>
|
||||||
|
|
||||||
<body
|
<body
|
||||||
|
|||||||
@@ -1,4 +1,3 @@
|
|||||||
import { IdentityProvider } from "@prisma/client";
|
|
||||||
import type { NextApiRequest, NextApiResponse } from "next";
|
import type { NextApiRequest, NextApiResponse } from "next";
|
||||||
|
|
||||||
import { ErrorCode } from "@calcom/features/auth/lib/ErrorCode";
|
import { ErrorCode } from "@calcom/features/auth/lib/ErrorCode";
|
||||||
@@ -6,6 +5,7 @@ import { getServerSession } from "@calcom/features/auth/lib/getServerSession";
|
|||||||
import { hashPassword } from "@calcom/features/auth/lib/hashPassword";
|
import { hashPassword } from "@calcom/features/auth/lib/hashPassword";
|
||||||
import { verifyPassword } from "@calcom/features/auth/lib/verifyPassword";
|
import { verifyPassword } from "@calcom/features/auth/lib/verifyPassword";
|
||||||
import prisma from "@calcom/prisma";
|
import prisma from "@calcom/prisma";
|
||||||
|
import { IdentityProvider } from "@calcom/prisma/enums";
|
||||||
|
|
||||||
export default async function handler(req: NextApiRequest, res: NextApiResponse) {
|
export default async function handler(req: NextApiRequest, res: NextApiResponse) {
|
||||||
const session = await getServerSession({ req, res });
|
const session = await getServerSession({ req, res });
|
||||||
|
|||||||
@@ -1,4 +1,3 @@
|
|||||||
import { IdentityProvider } from "@prisma/client";
|
|
||||||
import type { NextApiRequest } from "next";
|
import type { NextApiRequest } from "next";
|
||||||
import z from "zod";
|
import z from "zod";
|
||||||
|
|
||||||
@@ -8,6 +7,7 @@ import { HttpError } from "@calcom/lib/http-error";
|
|||||||
import { defaultHandler, defaultResponder } from "@calcom/lib/server";
|
import { defaultHandler, defaultResponder } from "@calcom/lib/server";
|
||||||
import slugify from "@calcom/lib/slugify";
|
import slugify from "@calcom/lib/slugify";
|
||||||
import prisma from "@calcom/prisma";
|
import prisma from "@calcom/prisma";
|
||||||
|
import { IdentityProvider } from "@calcom/prisma/enums";
|
||||||
|
|
||||||
const querySchema = z.object({
|
const querySchema = z.object({
|
||||||
username: z
|
username: z
|
||||||
|
|||||||
@@ -1,10 +1,10 @@
|
|||||||
import { IdentityProvider } from "@prisma/client";
|
|
||||||
import type { NextApiRequest, NextApiResponse } from "next";
|
import type { NextApiRequest, NextApiResponse } from "next";
|
||||||
|
|
||||||
import { hashPassword } from "@calcom/features/auth/lib/hashPassword";
|
import { hashPassword } from "@calcom/features/auth/lib/hashPassword";
|
||||||
import slugify from "@calcom/lib/slugify";
|
import slugify from "@calcom/lib/slugify";
|
||||||
import { closeComUpsertTeamUser } from "@calcom/lib/sync/SyncServiceManager";
|
import { closeComUpsertTeamUser } from "@calcom/lib/sync/SyncServiceManager";
|
||||||
import prisma from "@calcom/prisma";
|
import prisma from "@calcom/prisma";
|
||||||
|
import { IdentityProvider } from "@calcom/prisma/enums";
|
||||||
|
|
||||||
export default async function handler(req: NextApiRequest, res: NextApiResponse) {
|
export default async function handler(req: NextApiRequest, res: NextApiResponse) {
|
||||||
if (req.method !== "POST") {
|
if (req.method !== "POST") {
|
||||||
@@ -36,14 +36,25 @@ export default async function handler(req: NextApiRequest, res: NextApiResponse)
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
// There is actually an existingUser if username matches
|
// There is an existingUser if the username matches
|
||||||
// OR if email matches and both username and password are set
|
// OR if the email matches AND either the email is verified
|
||||||
|
// or both username and password are set
|
||||||
const existingUser = await prisma.user.findFirst({
|
const existingUser = await prisma.user.findFirst({
|
||||||
where: {
|
where: {
|
||||||
OR: [
|
OR: [
|
||||||
{ username },
|
{ username },
|
||||||
{
|
{
|
||||||
AND: [{ email: userEmail }],
|
AND: [
|
||||||
|
{ email: userEmail },
|
||||||
|
{
|
||||||
|
OR: [
|
||||||
|
{ emailVerified: { not: null } },
|
||||||
|
{
|
||||||
|
AND: [{ password: { not: null } }, { username: { not: null } }],
|
||||||
|
},
|
||||||
|
],
|
||||||
|
},
|
||||||
|
],
|
||||||
},
|
},
|
||||||
],
|
],
|
||||||
},
|
},
|
||||||
|
|||||||
@@ -1,4 +1,3 @@
|
|||||||
import { IdentityProvider } from "@prisma/client";
|
|
||||||
import type { NextApiRequest, NextApiResponse } from "next";
|
import type { NextApiRequest, NextApiResponse } from "next";
|
||||||
import { authenticator } from "otplib";
|
import { authenticator } from "otplib";
|
||||||
import qrcode from "qrcode";
|
import qrcode from "qrcode";
|
||||||
@@ -8,6 +7,7 @@ import { getServerSession } from "@calcom/features/auth/lib/getServerSession";
|
|||||||
import { verifyPassword } from "@calcom/features/auth/lib/verifyPassword";
|
import { verifyPassword } from "@calcom/features/auth/lib/verifyPassword";
|
||||||
import { symmetricEncrypt } from "@calcom/lib/crypto";
|
import { symmetricEncrypt } from "@calcom/lib/crypto";
|
||||||
import prisma from "@calcom/prisma";
|
import prisma from "@calcom/prisma";
|
||||||
|
import { IdentityProvider } from "@calcom/prisma/enums";
|
||||||
|
|
||||||
export default async function handler(req: NextApiRequest, res: NextApiResponse) {
|
export default async function handler(req: NextApiRequest, res: NextApiResponse) {
|
||||||
if (req.method !== "POST") {
|
if (req.method !== "POST") {
|
||||||
|
|||||||
@@ -1,4 +1,3 @@
|
|||||||
import { BookingStatus, ReminderType } from "@prisma/client";
|
|
||||||
import type { NextApiRequest, NextApiResponse } from "next";
|
import type { NextApiRequest, NextApiResponse } from "next";
|
||||||
|
|
||||||
import dayjs from "@calcom/dayjs";
|
import dayjs from "@calcom/dayjs";
|
||||||
@@ -7,6 +6,7 @@ import { getCalEventResponses } from "@calcom/features/bookings/lib/getCalEventR
|
|||||||
import { isPrismaObjOrUndefined, parseRecurringEvent } from "@calcom/lib";
|
import { isPrismaObjOrUndefined, parseRecurringEvent } from "@calcom/lib";
|
||||||
import { getTranslation } from "@calcom/lib/server/i18n";
|
import { getTranslation } from "@calcom/lib/server/i18n";
|
||||||
import prisma, { bookingMinimalSelect } from "@calcom/prisma";
|
import prisma, { bookingMinimalSelect } from "@calcom/prisma";
|
||||||
|
import { BookingStatus, ReminderType } from "@calcom/prisma/enums";
|
||||||
import type { CalendarEvent } from "@calcom/types/Calendar";
|
import type { CalendarEvent } from "@calcom/types/Calendar";
|
||||||
|
|
||||||
export default async function handler(req: NextApiRequest, res: NextApiResponse) {
|
export default async function handler(req: NextApiRequest, res: NextApiResponse) {
|
||||||
|
|||||||
@@ -1,4 +1,3 @@
|
|||||||
import { UserPermissionRole } from "@prisma/client";
|
|
||||||
import type { NextApiRequest, NextApiResponse } from "next";
|
import type { NextApiRequest, NextApiResponse } from "next";
|
||||||
import { z } from "zod";
|
import { z } from "zod";
|
||||||
|
|
||||||
@@ -7,7 +6,7 @@ import { defaultResponder } from "@calcom/lib/server";
|
|||||||
import prisma from "@calcom/prisma";
|
import prisma from "@calcom/prisma";
|
||||||
import { TRPCError } from "@calcom/trpc/server";
|
import { TRPCError } from "@calcom/trpc/server";
|
||||||
import { createContext } from "@calcom/trpc/server/createContext";
|
import { createContext } from "@calcom/trpc/server/createContext";
|
||||||
import { viewerRouter } from "@calcom/trpc/server/routers/viewer";
|
import { viewerRouter } from "@calcom/trpc/server/routers/viewer/_router";
|
||||||
|
|
||||||
enum DirectAction {
|
enum DirectAction {
|
||||||
ACCEPT = "accept",
|
ACCEPT = "accept",
|
||||||
@@ -27,31 +26,18 @@ const decryptedSchema = z.object({
|
|||||||
|
|
||||||
async function handler(req: NextApiRequest, res: NextApiResponse<Response>) {
|
async function handler(req: NextApiRequest, res: NextApiResponse<Response>) {
|
||||||
const { action, token, reason } = querySchema.parse(req.query);
|
const { action, token, reason } = querySchema.parse(req.query);
|
||||||
const { bookingUid, userId } = decryptedSchema.parse(
|
const { bookingUid } = decryptedSchema.parse(
|
||||||
JSON.parse(symmetricDecrypt(decodeURIComponent(token), process.env.CALENDSO_ENCRYPTION_KEY || ""))
|
JSON.parse(symmetricDecrypt(decodeURIComponent(token), process.env.CALENDSO_ENCRYPTION_KEY || ""))
|
||||||
);
|
);
|
||||||
|
|
||||||
/** We shape the session as required by tRPC router */
|
|
||||||
async function sessionGetter() {
|
|
||||||
return {
|
|
||||||
user: {
|
|
||||||
id: userId,
|
|
||||||
username: "" /* Not used in this context */,
|
|
||||||
role: UserPermissionRole.USER,
|
|
||||||
},
|
|
||||||
hasValidLicense: true,
|
|
||||||
expires: "" /* Not used in this context */,
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
const booking = await prisma.booking.findUniqueOrThrow({
|
const booking = await prisma.booking.findUniqueOrThrow({
|
||||||
where: { uid: bookingUid },
|
where: { uid: bookingUid },
|
||||||
});
|
});
|
||||||
|
|
||||||
try {
|
try {
|
||||||
/** @see https://trpc.io/docs/server-side-calls */
|
/** @see https://trpc.io/docs/server-side-calls */
|
||||||
const ctx = await createContext({ req, res }, sessionGetter);
|
const ctx = await createContext({ req, res });
|
||||||
const caller = viewerRouter.createCaller(ctx);
|
const caller = viewerRouter.createCaller({ ...ctx, req, res });
|
||||||
await caller.bookings.confirm({
|
await caller.bookings.confirm({
|
||||||
bookingId: booking.id,
|
bookingId: booking.id,
|
||||||
recurringEventId: booking.recurringEventId || undefined,
|
recurringEventId: booking.recurringEventId || undefined,
|
||||||
|
|||||||
@@ -1,3 +1,4 @@
|
|||||||
|
import type { WebhookTriggerEvents } from "@prisma/client";
|
||||||
import type { NextApiRequest, NextApiResponse } from "next";
|
import type { NextApiRequest, NextApiResponse } from "next";
|
||||||
import { z } from "zod";
|
import { z } from "zod";
|
||||||
|
|
||||||
@@ -5,6 +6,8 @@ import { DailyLocationType } from "@calcom/app-store/locations";
|
|||||||
import { getDownloadLinkOfCalVideoByRecordingId } from "@calcom/core/videoClient";
|
import { getDownloadLinkOfCalVideoByRecordingId } from "@calcom/core/videoClient";
|
||||||
import { sendDailyVideoRecordingEmails } from "@calcom/emails";
|
import { sendDailyVideoRecordingEmails } from "@calcom/emails";
|
||||||
import { getServerSession } from "@calcom/features/auth/lib/getServerSession";
|
import { getServerSession } from "@calcom/features/auth/lib/getServerSession";
|
||||||
|
import getWebhooks from "@calcom/features/webhooks/lib/getWebhooks";
|
||||||
|
import sendPayload from "@calcom/features/webhooks/lib/sendPayload";
|
||||||
import { IS_SELF_HOSTED } from "@calcom/lib/constants";
|
import { IS_SELF_HOSTED } from "@calcom/lib/constants";
|
||||||
import { defaultHandler } from "@calcom/lib/server";
|
import { defaultHandler } from "@calcom/lib/server";
|
||||||
import { getTranslation } from "@calcom/lib/server/i18n";
|
import { getTranslation } from "@calcom/lib/server/i18n";
|
||||||
@@ -20,6 +23,38 @@ const downloadLinkSchema = z.object({
|
|||||||
download_link: z.string(),
|
download_link: z.string(),
|
||||||
});
|
});
|
||||||
|
|
||||||
|
const triggerWebhook = async ({
|
||||||
|
evt,
|
||||||
|
downloadLink,
|
||||||
|
booking,
|
||||||
|
}: {
|
||||||
|
evt: CalendarEvent;
|
||||||
|
downloadLink: string;
|
||||||
|
booking: {
|
||||||
|
userId: number | undefined;
|
||||||
|
eventTypeId: number | null;
|
||||||
|
};
|
||||||
|
}) => {
|
||||||
|
const eventTrigger: WebhookTriggerEvents = "RECORDING_READY";
|
||||||
|
// Send Webhook call if hooked to BOOKING.RECORDING_READY
|
||||||
|
const subscriberOptions = {
|
||||||
|
userId: booking.userId ?? 0,
|
||||||
|
eventTypeId: booking.eventTypeId ?? 0,
|
||||||
|
triggerEvent: eventTrigger,
|
||||||
|
};
|
||||||
|
const webhooks = await getWebhooks(subscriberOptions);
|
||||||
|
|
||||||
|
const promises = webhooks.map((webhook) =>
|
||||||
|
sendPayload(webhook.secret, eventTrigger, new Date().toISOString(), webhook, {
|
||||||
|
...evt,
|
||||||
|
downloadLink,
|
||||||
|
}).catch((e) => {
|
||||||
|
console.error(`Error executing webhook for event: ${eventTrigger}, URL: ${webhook.subscriberUrl}`, e);
|
||||||
|
})
|
||||||
|
);
|
||||||
|
await Promise.all(promises);
|
||||||
|
};
|
||||||
|
|
||||||
async function handler(req: NextApiRequest, res: NextApiResponse) {
|
async function handler(req: NextApiRequest, res: NextApiResponse) {
|
||||||
if (!process.env.SENDGRID_API_KEY || !process.env.SENDGRID_EMAIL) {
|
if (!process.env.SENDGRID_API_KEY || !process.env.SENDGRID_EMAIL) {
|
||||||
return res.status(405).json({ message: "No SendGrid API key or email" });
|
return res.status(405).json({ message: "No SendGrid API key or email" });
|
||||||
@@ -51,6 +86,7 @@ async function handler(req: NextApiRequest, res: NextApiResponse) {
|
|||||||
uid: true,
|
uid: true,
|
||||||
location: true,
|
location: true,
|
||||||
isRecorded: true,
|
isRecorded: true,
|
||||||
|
eventTypeId: true,
|
||||||
user: {
|
user: {
|
||||||
select: {
|
select: {
|
||||||
id: true,
|
id: true,
|
||||||
@@ -105,31 +141,36 @@ async function handler(req: NextApiRequest, res: NextApiResponse) {
|
|||||||
},
|
},
|
||||||
});
|
});
|
||||||
|
|
||||||
|
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 triggerWebhook({
|
||||||
|
evt,
|
||||||
|
downloadLink,
|
||||||
|
booking: { userId: booking?.user?.id, eventTypeId: booking.eventTypeId },
|
||||||
|
});
|
||||||
|
|
||||||
const isSendingEmailsAllowed = IS_SELF_HOSTED || session?.user?.belongsToActiveTeam;
|
const isSendingEmailsAllowed = IS_SELF_HOSTED || session?.user?.belongsToActiveTeam;
|
||||||
|
|
||||||
// send emails to all attendees only when user has team plan
|
// send emails to all attendees only when user has team plan
|
||||||
if (isSendingEmailsAllowed) {
|
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);
|
await sendDailyVideoRecordingEmails(evt, downloadLink);
|
||||||
return res.status(200).json({ message: "Success" });
|
return res.status(200).json({ message: "Success" });
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -4,7 +4,6 @@ import type { SatoriOptions } from "satori";
|
|||||||
import { z } from "zod";
|
import { z } from "zod";
|
||||||
|
|
||||||
import { Meeting, App, Generic } from "@calcom/lib/OgImages";
|
import { Meeting, App, Generic } from "@calcom/lib/OgImages";
|
||||||
import { md } from "@calcom/lib/markdownIt";
|
|
||||||
|
|
||||||
const calFont = fetch(new URL("../../../../public/fonts/cal.ttf", import.meta.url)).then((res) =>
|
const calFont = fetch(new URL("../../../../public/fonts/cal.ttf", import.meta.url)).then((res) =>
|
||||||
res.arrayBuffer()
|
res.arrayBuffer()
|
||||||
@@ -75,12 +74,10 @@ export default async function handler(req: NextApiRequest) {
|
|||||||
imageType,
|
imageType,
|
||||||
});
|
});
|
||||||
|
|
||||||
const title_ = md.render(title).replace(/(<([^>]+)>)/gi, "");
|
|
||||||
|
|
||||||
const img = new ImageResponse(
|
const img = new ImageResponse(
|
||||||
(
|
(
|
||||||
<Meeting
|
<Meeting
|
||||||
title={title_}
|
title={title}
|
||||||
profile={{ name: meetingProfileName, image: meetingImage }}
|
profile={{ name: meetingProfileName, image: meetingImage }}
|
||||||
users={names.map((name, index) => ({ name, username: usernames[index] }))}
|
users={names.map((name, index) => ({ name, username: usernames[index] }))}
|
||||||
/>
|
/>
|
||||||
|
|||||||
@@ -1,83 +0,0 @@
|
|||||||
/**
|
|
||||||
* This file contains tRPC's HTTP response handler
|
|
||||||
*/
|
|
||||||
import { z } from "zod";
|
|
||||||
|
|
||||||
import { getServerSession } from "@calcom/features/auth/lib/getServerSession";
|
|
||||||
import * as trpcNext from "@calcom/trpc/server/adapters/next";
|
|
||||||
import { createContext as createTrpcContext } from "@calcom/trpc/server/createContext";
|
|
||||||
import { appRouter } from "@calcom/trpc/server/routers/_app";
|
|
||||||
|
|
||||||
export default trpcNext.createNextApiHandler({
|
|
||||||
router: appRouter,
|
|
||||||
/**
|
|
||||||
* @link https://trpc.io/docs/context
|
|
||||||
*/
|
|
||||||
createContext: ({ req, res }) => {
|
|
||||||
const sessionGetter = () => getServerSession({ req, res });
|
|
||||||
|
|
||||||
return createTrpcContext({ req, res }, sessionGetter);
|
|
||||||
},
|
|
||||||
/**
|
|
||||||
* @link https://trpc.io/docs/error-handling
|
|
||||||
*/
|
|
||||||
onError({ error }) {
|
|
||||||
if (error.code === "INTERNAL_SERVER_ERROR") {
|
|
||||||
// send to bug reporting
|
|
||||||
console.error("Something went wrong", error);
|
|
||||||
}
|
|
||||||
},
|
|
||||||
/**
|
|
||||||
* Enable query batching
|
|
||||||
*/
|
|
||||||
batching: {
|
|
||||||
enabled: true,
|
|
||||||
},
|
|
||||||
/**
|
|
||||||
* @link https://trpc.io/docs/caching#api-response-caching
|
|
||||||
*/
|
|
||||||
responseMeta({ ctx, paths, type, errors }) {
|
|
||||||
// Some helpers relevant to this function only
|
|
||||||
const ONE_DAY_IN_SECONDS = 60 * 60 * 24;
|
|
||||||
// assuming you have all your public routes with the keyword `public` in them
|
|
||||||
const allPublic = paths && paths.every((path) => path.startsWith("viewer.public."));
|
|
||||||
// checking that no procedures errored
|
|
||||||
const allOk = errors.length === 0;
|
|
||||||
// checking we're doing a query request
|
|
||||||
const isQuery = type === "query";
|
|
||||||
const noHeaders = {};
|
|
||||||
|
|
||||||
// We cannot set headers on SSG queries
|
|
||||||
if (!ctx?.res) return noHeaders;
|
|
||||||
|
|
||||||
const defaultHeaders: Record<"headers", Record<string, string>> = {
|
|
||||||
headers: {},
|
|
||||||
};
|
|
||||||
|
|
||||||
const timezone = z.string().safeParse(ctx.req?.headers["x-vercel-ip-timezone"]);
|
|
||||||
if (timezone.success) defaultHeaders.headers["x-cal-timezone"] = timezone.data;
|
|
||||||
|
|
||||||
// We need all these conditions to be true to set cache headers
|
|
||||||
if (!(allPublic && allOk && isQuery)) return defaultHeaders;
|
|
||||||
|
|
||||||
// No cache by default
|
|
||||||
defaultHeaders.headers["cache-control"] = `no-cache`;
|
|
||||||
|
|
||||||
// Our cache can change depending on our current paths value. Since paths is an array,
|
|
||||||
// we want to create a map that can match potential paths with their desired cache value
|
|
||||||
const cacheRules = {
|
|
||||||
"viewer.public.session": `no-cache`,
|
|
||||||
"viewer.public.i18n": `no-cache`,
|
|
||||||
// Revalidation time here should be 1 second, per https://github.com/calcom/cal.com/pull/6823#issuecomment-1423215321
|
|
||||||
"viewer.public.slots.getSchedule": `no-cache`, // FIXME
|
|
||||||
"viewer.public.cityTimezones": `max-age=${ONE_DAY_IN_SECONDS}, stale-while-revalidate`,
|
|
||||||
} as const;
|
|
||||||
|
|
||||||
// Find which element above is an exact match for this group of paths
|
|
||||||
const matchedPath = paths.find((v) => v in cacheRules) as keyof typeof cacheRules;
|
|
||||||
|
|
||||||
if (matchedPath) defaultHeaders.headers["cache-control"] = cacheRules[matchedPath];
|
|
||||||
|
|
||||||
return defaultHeaders;
|
|
||||||
},
|
|
||||||
});
|
|
||||||
@@ -0,0 +1,4 @@
|
|||||||
|
import { createNextApiHandler } from "@calcom/trpc/server/createNextApiHandler";
|
||||||
|
import { apiKeysRouter } from "@calcom/trpc/server/routers/viewer/apiKeys/_router";
|
||||||
|
|
||||||
|
export default createNextApiHandler(apiKeysRouter);
|
||||||
@@ -0,0 +1,4 @@
|
|||||||
|
import appRoutingForms from "@calcom/app-store/routing-forms/trpc-router";
|
||||||
|
import { createNextApiHandler } from "@calcom/trpc/server/createNextApiHandler";
|
||||||
|
|
||||||
|
export default createNextApiHandler(appRoutingForms);
|
||||||
@@ -0,0 +1,4 @@
|
|||||||
|
import { createNextApiHandler } from "@calcom/trpc/server/createNextApiHandler";
|
||||||
|
import { appsRouter } from "@calcom/trpc/server/routers/viewer/apps/_router";
|
||||||
|
|
||||||
|
export default createNextApiHandler(appsRouter);
|
||||||
@@ -0,0 +1,4 @@
|
|||||||
|
import { createNextApiHandler } from "@calcom/trpc/server/createNextApiHandler";
|
||||||
|
import { appsRouter } from "@calcom/trpc/server/routers/viewer/apps/_router";
|
||||||
|
|
||||||
|
export default createNextApiHandler(appsRouter);
|
||||||
@@ -0,0 +1,4 @@
|
|||||||
|
import { createNextApiHandler } from "@calcom/trpc/server/createNextApiHandler";
|
||||||
|
import { authRouter } from "@calcom/trpc/server/routers/viewer/auth/_router";
|
||||||
|
|
||||||
|
export default createNextApiHandler(authRouter);
|
||||||
@@ -0,0 +1,4 @@
|
|||||||
|
import { createNextApiHandler } from "@calcom/trpc/server/createNextApiHandler";
|
||||||
|
import { availabilityRouter } from "@calcom/trpc/server/routers/viewer/availability/_router";
|
||||||
|
|
||||||
|
export default createNextApiHandler(availabilityRouter);
|
||||||
@@ -0,0 +1,4 @@
|
|||||||
|
import { createNextApiHandler } from "@calcom/trpc/server/createNextApiHandler";
|
||||||
|
import { bookingsRouter } from "@calcom/trpc/server/routers/viewer/bookings/_router";
|
||||||
|
|
||||||
|
export default createNextApiHandler(bookingsRouter);
|
||||||
@@ -0,0 +1,4 @@
|
|||||||
|
import { createNextApiHandler } from "@calcom/trpc/server/createNextApiHandler";
|
||||||
|
import { deploymentSetupRouter } from "@calcom/trpc/server/routers/viewer/deploymentSetup/_router";
|
||||||
|
|
||||||
|
export default createNextApiHandler(deploymentSetupRouter);
|
||||||
@@ -0,0 +1,4 @@
|
|||||||
|
import ethRouter from "@calcom/app-store/rainbow/trpc/router";
|
||||||
|
import { createNextApiHandler } from "@calcom/trpc/server/createNextApiHandler";
|
||||||
|
|
||||||
|
export default createNextApiHandler(ethRouter);
|
||||||
@@ -0,0 +1,4 @@
|
|||||||
|
import { createNextApiHandler } from "@calcom/trpc/server/createNextApiHandler";
|
||||||
|
import { eventTypesRouter } from "@calcom/trpc/server/routers/viewer/eventTypes/_router";
|
||||||
|
|
||||||
|
export default createNextApiHandler(eventTypesRouter);
|
||||||
@@ -0,0 +1,4 @@
|
|||||||
|
import { featureFlagRouter } from "@calcom/features/flags/server/router";
|
||||||
|
import { createNextApiHandler } from "@calcom/trpc/server/createNextApiHandler";
|
||||||
|
|
||||||
|
export default createNextApiHandler(featureFlagRouter);
|
||||||
@@ -0,0 +1,4 @@
|
|||||||
|
import { insightsRouter } from "@calcom/features/insights/server/trpc-router";
|
||||||
|
import { createNextApiHandler } from "@calcom/trpc/server/createNextApiHandler";
|
||||||
|
|
||||||
|
export default createNextApiHandler(insightsRouter);
|
||||||
@@ -0,0 +1,4 @@
|
|||||||
|
import { createNextApiHandler } from "@calcom/trpc/server/createNextApiHandler";
|
||||||
|
import { paymentsRouter } from "@calcom/trpc/server/routers/viewer/payments/_router";
|
||||||
|
|
||||||
|
export default createNextApiHandler(paymentsRouter);
|
||||||
@@ -0,0 +1,4 @@
|
|||||||
|
import { createNextApiHandler } from "@calcom/trpc/server/createNextApiHandler";
|
||||||
|
import { publicViewerRouter } from "@calcom/trpc/server/routers/publicViewer/_router";
|
||||||
|
|
||||||
|
export default createNextApiHandler(publicViewerRouter, true);
|
||||||
@@ -0,0 +1,4 @@
|
|||||||
|
import { createNextApiHandler } from "@calcom/trpc/server/createNextApiHandler";
|
||||||
|
import { ssoRouter } from "@calcom/trpc/server/routers/viewer/sso/_router";
|
||||||
|
|
||||||
|
export default createNextApiHandler(ssoRouter);
|
||||||
@@ -0,0 +1,4 @@
|
|||||||
|
import { createNextApiHandler } from "@calcom/trpc/server/createNextApiHandler";
|
||||||
|
import { slotsRouter } from "@calcom/trpc/server/routers/viewer/slots/_router";
|
||||||
|
|
||||||
|
export default createNextApiHandler(slotsRouter);
|
||||||
@@ -0,0 +1,4 @@
|
|||||||
|
import { createNextApiHandler } from "@calcom/trpc/server/createNextApiHandler";
|
||||||
|
import { viewerTeamsRouter } from "@calcom/trpc/server/routers/viewer/teams/_router";
|
||||||
|
|
||||||
|
export default createNextApiHandler(viewerTeamsRouter);
|
||||||
@@ -0,0 +1,4 @@
|
|||||||
|
import { userAdminRouter } from "@calcom/features/ee/users/server/trpc-router";
|
||||||
|
import { createNextApiHandler } from "@calcom/trpc/server/createNextApiHandler";
|
||||||
|
|
||||||
|
export default createNextApiHandler(userAdminRouter);
|
||||||
@@ -0,0 +1,4 @@
|
|||||||
|
import { createNextApiHandler } from "@calcom/trpc/server/createNextApiHandler";
|
||||||
|
import { loggedInViewerRouter } from "@calcom/trpc/server/routers/loggedInViewer/_router";
|
||||||
|
|
||||||
|
export default createNextApiHandler(loggedInViewerRouter);
|
||||||
@@ -0,0 +1,4 @@
|
|||||||
|
import { createNextApiHandler } from "@calcom/trpc/server/createNextApiHandler";
|
||||||
|
import { webhookRouter } from "@calcom/trpc/server/routers/viewer/webhook/_router";
|
||||||
|
|
||||||
|
export default createNextApiHandler(webhookRouter);
|
||||||
@@ -0,0 +1,4 @@
|
|||||||
|
import { createNextApiHandler } from "@calcom/trpc/server/createNextApiHandler";
|
||||||
|
import { workflowsRouter } from "@calcom/trpc/server/routers/viewer/workflows/_router";
|
||||||
|
|
||||||
|
export default createNextApiHandler(workflowsRouter);
|
||||||
@@ -2,10 +2,12 @@ import fs from "fs";
|
|||||||
import matter from "gray-matter";
|
import matter from "gray-matter";
|
||||||
import MarkdownIt from "markdown-it";
|
import MarkdownIt from "markdown-it";
|
||||||
import type { GetStaticPaths, GetStaticPropsContext } from "next";
|
import type { GetStaticPaths, GetStaticPropsContext } from "next";
|
||||||
|
import Link from "next/link";
|
||||||
import path from "path";
|
import path from "path";
|
||||||
import { z } from "zod";
|
import { z } from "zod";
|
||||||
|
|
||||||
import { getAppWithMetadata } from "@calcom/app-store/_appRegistry";
|
import { getAppWithMetadata } from "@calcom/app-store/_appRegistry";
|
||||||
|
import { getAppAssetFullPath } from "@calcom/app-store/getAppAssetFullPath";
|
||||||
import prisma from "@calcom/prisma";
|
import prisma from "@calcom/prisma";
|
||||||
|
|
||||||
import type { inferSSRProps } from "@lib/types/inferSSRProps";
|
import type { inferSSRProps } from "@lib/types/inferSSRProps";
|
||||||
@@ -32,7 +34,26 @@ const sourceSchema = z.object({
|
|||||||
}),
|
}),
|
||||||
});
|
});
|
||||||
|
|
||||||
function SingleAppPage({ data, source }: inferSSRProps<typeof getStaticProps>) {
|
function SingleAppPage(props: inferSSRProps<typeof getStaticProps>) {
|
||||||
|
// If it's not production environment, it would be a better idea to inform that the App is disabled.
|
||||||
|
if (props.isAppDisabled) {
|
||||||
|
if (process.env.NODE_ENV !== "production") {
|
||||||
|
// TODO: Improve disabled App UI. This is just a placeholder.
|
||||||
|
return (
|
||||||
|
<div className="p-2">
|
||||||
|
This App seems to be disabled. If you are an admin, you can enable this app from{" "}
|
||||||
|
<Link href="/settings/admin/apps" className="cursor-pointer text-blue-500 underline">
|
||||||
|
here
|
||||||
|
</Link>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Disabled App should give 404 any ways in production.
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
const { source, data } = props;
|
||||||
return (
|
return (
|
||||||
<App
|
<App
|
||||||
name={data.name}
|
name={data.name}
|
||||||
@@ -51,6 +72,7 @@ function SingleAppPage({ data, source }: inferSSRProps<typeof getStaticProps>) {
|
|||||||
website={data.url}
|
website={data.url}
|
||||||
email={data.email}
|
email={data.email}
|
||||||
licenseRequired={data.licenseRequired}
|
licenseRequired={data.licenseRequired}
|
||||||
|
teamsPlanRequired={data.teamsPlanRequired}
|
||||||
isProOnly={data.isProOnly}
|
isProOnly={data.isProOnly}
|
||||||
descriptionItems={source.data?.items as string[] | undefined}
|
descriptionItems={source.data?.items as string[] | undefined}
|
||||||
isTemplate={data.isTemplate}
|
isTemplate={data.isTemplate}
|
||||||
@@ -79,46 +101,63 @@ export const getStaticPaths: GetStaticPaths<{ slug: string }> = async () => {
|
|||||||
export const getStaticProps = async (ctx: GetStaticPropsContext) => {
|
export const getStaticProps = async (ctx: GetStaticPropsContext) => {
|
||||||
if (typeof ctx.params?.slug !== "string") return { notFound: true };
|
if (typeof ctx.params?.slug !== "string") return { notFound: true };
|
||||||
|
|
||||||
const app = await prisma.app.findUnique({
|
const appMeta = await getAppWithMetadata({
|
||||||
|
slug: ctx.params?.slug,
|
||||||
|
});
|
||||||
|
|
||||||
|
const appFromDb = await prisma.app.findUnique({
|
||||||
where: { slug: ctx.params.slug.toLowerCase() },
|
where: { slug: ctx.params.slug.toLowerCase() },
|
||||||
});
|
});
|
||||||
|
|
||||||
if (!app) return { notFound: true };
|
const isAppAvailableInFileSystem = appMeta;
|
||||||
|
const isAppDisabled = isAppAvailableInFileSystem && (!appFromDb || !appFromDb.enabled);
|
||||||
|
|
||||||
const singleApp = await getAppWithMetadata(app);
|
if (process.env.NODE_ENV !== "production" && isAppDisabled) {
|
||||||
|
return {
|
||||||
|
props: {
|
||||||
|
isAppDisabled: true as const,
|
||||||
|
data: {
|
||||||
|
...appMeta,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
if (!singleApp) return { notFound: true };
|
if (!appFromDb || !appMeta || isAppDisabled) return { notFound: true };
|
||||||
|
|
||||||
const isTemplate = singleApp.isTemplate;
|
const isTemplate = appMeta.isTemplate;
|
||||||
const appDirname = path.join(isTemplate ? "templates" : "", app.dirName);
|
const appDirname = path.join(isTemplate ? "templates" : "", appFromDb.dirName);
|
||||||
const README_PATH = path.join(process.cwd(), "..", "..", `packages/app-store/${appDirname}/DESCRIPTION.md`);
|
const README_PATH = path.join(process.cwd(), "..", "..", `packages/app-store/${appDirname}/DESCRIPTION.md`);
|
||||||
const postFilePath = path.join(README_PATH);
|
const postFilePath = path.join(README_PATH);
|
||||||
let source = "";
|
let source = "";
|
||||||
|
|
||||||
try {
|
try {
|
||||||
source = fs.readFileSync(postFilePath).toString();
|
source = fs.readFileSync(postFilePath).toString();
|
||||||
source = source.replace(/{DESCRIPTION}/g, singleApp.description);
|
source = source.replace(/{DESCRIPTION}/g, appMeta.description);
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
/* If the app doesn't have a README we fallback to the package description */
|
/* If the app doesn't have a README we fallback to the package description */
|
||||||
console.log(`No DESCRIPTION.md provided for: ${appDirname}`);
|
console.log(`No DESCRIPTION.md provided for: ${appDirname}`);
|
||||||
source = singleApp.description;
|
source = appMeta.description;
|
||||||
}
|
}
|
||||||
|
|
||||||
const result = matter(source);
|
const result = matter(source);
|
||||||
const { content, data } = sourceSchema.parse({ content: result.content, data: result.data });
|
const { content, data } = sourceSchema.parse({ content: result.content, data: result.data });
|
||||||
if (data.items) {
|
if (data.items) {
|
||||||
data.items = data.items.map((item) => {
|
data.items = data.items.map((item) => {
|
||||||
if (typeof item === "string" && !item.includes("/api/app-store")) {
|
if (typeof item === "string") {
|
||||||
// Make relative paths absolute
|
return getAppAssetFullPath(item, {
|
||||||
return `/api/app-store/${appDirname}/${item}`;
|
dirName: appMeta.dirName,
|
||||||
|
isTemplate: appMeta.isTemplate,
|
||||||
|
});
|
||||||
}
|
}
|
||||||
return item;
|
return item;
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
return {
|
return {
|
||||||
props: {
|
props: {
|
||||||
|
isAppDisabled: false as const,
|
||||||
source: { content, data },
|
source: { content, data },
|
||||||
data: singleApp,
|
data: appMeta,
|
||||||
},
|
},
|
||||||
};
|
};
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -4,6 +4,7 @@ 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 { HeadSeo } from "@calcom/ui";
|
||||||
|
|
||||||
import PageWrapper from "@components/PageWrapper";
|
import PageWrapper from "@components/PageWrapper";
|
||||||
|
|
||||||
@@ -25,7 +26,13 @@ export default function SetupInformation(props: InferGetStaticPropsType<typeof g
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
return <AppSetupPage slug={slug} {...props} />;
|
return (
|
||||||
|
<>
|
||||||
|
{/* So that the set up page does not get indexed by search engines */}
|
||||||
|
<HeadSeo nextSeoProps={{ noindex: true, nofollow: true }} title={`${slug} | Cal.com`} description="" />
|
||||||
|
<AppSetupPage slug={slug} {...props} />
|
||||||
|
</>
|
||||||
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
SetupInformation.PageWrapper = PageWrapper;
|
SetupInformation.PageWrapper = PageWrapper;
|
||||||
|
|||||||
@@ -1,4 +1,3 @@
|
|||||||
import { AppCategories } from "@prisma/client";
|
|
||||||
import type { GetStaticPropsContext, InferGetStaticPropsType } from "next";
|
import type { GetStaticPropsContext, InferGetStaticPropsType } from "next";
|
||||||
import Link from "next/link";
|
import Link from "next/link";
|
||||||
import { useRouter } from "next/router";
|
import { useRouter } from "next/router";
|
||||||
@@ -7,6 +6,7 @@ import { getAppRegistry } from "@calcom/app-store/_appRegistry";
|
|||||||
import Shell from "@calcom/features/shell/Shell";
|
import Shell from "@calcom/features/shell/Shell";
|
||||||
import { useLocale } from "@calcom/lib/hooks/useLocale";
|
import { useLocale } from "@calcom/lib/hooks/useLocale";
|
||||||
import prisma from "@calcom/prisma";
|
import prisma from "@calcom/prisma";
|
||||||
|
import { AppCategories } from "@calcom/prisma/enums";
|
||||||
import { AppCard, SkeletonText } from "@calcom/ui";
|
import { AppCard, SkeletonText } from "@calcom/ui";
|
||||||
|
|
||||||
import PageWrapper from "@components/PageWrapper";
|
import PageWrapper from "@components/PageWrapper";
|
||||||
|
|||||||
@@ -6,7 +6,7 @@ import { getAppRegistry, getAppRegistryWithCredentials } from "@calcom/app-store
|
|||||||
import { getServerSession } from "@calcom/features/auth/lib/getServerSession";
|
import { getServerSession } from "@calcom/features/auth/lib/getServerSession";
|
||||||
import { classNames } from "@calcom/lib";
|
import { classNames } from "@calcom/lib";
|
||||||
import { useLocale } from "@calcom/lib/hooks/useLocale";
|
import { useLocale } from "@calcom/lib/hooks/useLocale";
|
||||||
import type { AppCategories } from "@calcom/prisma/client";
|
import type { AppCategories } from "@calcom/prisma/enums";
|
||||||
import type { inferSSRProps } from "@calcom/types/inferSSRProps";
|
import type { inferSSRProps } from "@calcom/types/inferSSRProps";
|
||||||
import type { HorizontalTabItemProps } from "@calcom/ui";
|
import type { HorizontalTabItemProps } from "@calcom/ui";
|
||||||
import { AllApps, AppStoreCategories, HorizontalTabs, TextField, PopularAppsSlider } from "@calcom/ui";
|
import { AllApps, AppStoreCategories, HorizontalTabs, TextField, PopularAppsSlider } from "@calcom/ui";
|
||||||
@@ -40,7 +40,7 @@ function AppsSearch({
|
|||||||
className="bg-subtle !border-muted !pl-0 focus:!ring-offset-0"
|
className="bg-subtle !border-muted !pl-0 focus:!ring-offset-0"
|
||||||
addOnLeading={<Search className="text-subtle h-4 w-4" />}
|
addOnLeading={<Search className="text-subtle h-4 w-4" />}
|
||||||
addOnClassname="!border-muted"
|
addOnClassname="!border-muted"
|
||||||
containerClassName={classNames("focus:!ring-offset-0", className)}
|
containerClassName={classNames("focus:!ring-offset-0 m-1", className)}
|
||||||
type="search"
|
type="search"
|
||||||
autoComplete="false"
|
autoComplete="false"
|
||||||
onChange={onChange}
|
onChange={onChange}
|
||||||
|
|||||||
@@ -172,7 +172,7 @@ 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">
|
<div className="absolute -top-[2px] ltr:right-0 rtl:left-0">
|
||||||
<Link
|
<Link
|
||||||
href="/auth/forgot-password"
|
href="/auth/forgot-password"
|
||||||
tabIndex={-1}
|
tabIndex={-1}
|
||||||
|
|||||||
@@ -1,4 +1,3 @@
|
|||||||
import { UserPermissionRole } from "@prisma/client";
|
|
||||||
import type { GetServerSidePropsContext } from "next";
|
import type { GetServerSidePropsContext } from "next";
|
||||||
import { useRouter } from "next/router";
|
import { useRouter } from "next/router";
|
||||||
import { useState } from "react";
|
import { useState } from "react";
|
||||||
@@ -8,6 +7,7 @@ import { getServerSession } from "@calcom/features/auth/lib/getServerSession";
|
|||||||
import { getDeploymentKey } from "@calcom/features/ee/deployment/lib/getDeploymentKey";
|
import { getDeploymentKey } from "@calcom/features/ee/deployment/lib/getDeploymentKey";
|
||||||
import { useLocale } from "@calcom/lib/hooks/useLocale";
|
import { useLocale } from "@calcom/lib/hooks/useLocale";
|
||||||
import prisma from "@calcom/prisma";
|
import prisma from "@calcom/prisma";
|
||||||
|
import { UserPermissionRole } from "@calcom/prisma/enums";
|
||||||
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";
|
||||||
|
|
||||||
@@ -157,10 +157,13 @@ export const getServerSideProps = async (context: GetServerSidePropsContext) =>
|
|||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
let deploymentKey = await getDeploymentKey(prisma);
|
const deploymentKey = await prisma.deployment.findUnique({
|
||||||
|
where: { id: 1 },
|
||||||
|
select: { licenseKey: true },
|
||||||
|
});
|
||||||
|
|
||||||
// Check existant CALCOM_LICENSE_KEY env var and acccount for it
|
// Check existant CALCOM_LICENSE_KEY env var and acccount for it
|
||||||
if (!!process.env.CALCOM_LICENSE_KEY && !deploymentKey) {
|
if (!!process.env.CALCOM_LICENSE_KEY && !deploymentKey?.licenseKey) {
|
||||||
await prisma.deployment.upsert({
|
await prisma.deployment.upsert({
|
||||||
where: { id: 1 },
|
where: { id: 1 },
|
||||||
update: {
|
update: {
|
||||||
@@ -172,10 +175,9 @@ export const getServerSideProps = async (context: GetServerSidePropsContext) =>
|
|||||||
agreedLicenseAt: new Date(),
|
agreedLicenseAt: new Date(),
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
deploymentKey = await getDeploymentKey(prisma);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
const isFreeLicense = deploymentKey === "";
|
const isFreeLicense = (await getDeploymentKey(prisma)) === "";
|
||||||
|
|
||||||
return {
|
return {
|
||||||
props: {
|
props: {
|
||||||
|
|||||||
@@ -1,4 +1,3 @@
|
|||||||
import { CheckIcon, MailOpenIcon, ExclamationIcon } from "@heroicons/react/outline";
|
|
||||||
import { signIn } from "next-auth/react";
|
import { signIn } from "next-auth/react";
|
||||||
import Head from "next/head";
|
import Head from "next/head";
|
||||||
import { useRouter } from "next/router";
|
import { useRouter } from "next/router";
|
||||||
@@ -9,6 +8,7 @@ import z from "zod";
|
|||||||
import { APP_NAME, WEBAPP_URL } from "@calcom/lib/constants";
|
import { APP_NAME, WEBAPP_URL } from "@calcom/lib/constants";
|
||||||
import { trpc } from "@calcom/trpc/react";
|
import { trpc } from "@calcom/trpc/react";
|
||||||
import { Button, showToast } from "@calcom/ui";
|
import { Button, showToast } from "@calcom/ui";
|
||||||
|
import { Check, MailOpen, AlertTriangle } from "@calcom/ui/components/icon";
|
||||||
|
|
||||||
import Loader from "@components/Loader";
|
import Loader from "@components/Loader";
|
||||||
import PageWrapper from "@components/PageWrapper";
|
import PageWrapper from "@components/PageWrapper";
|
||||||
@@ -118,11 +118,11 @@ export default function Verify() {
|
|||||||
<div className="m-10 flex max-w-2xl flex-col items-start border border-white p-12 text-left">
|
<div className="m-10 flex max-w-2xl flex-col items-start border border-white p-12 text-left">
|
||||||
<div className="rounded-full border border-white p-3">
|
<div className="rounded-full border border-white p-3">
|
||||||
{hasPaymentFailed ? (
|
{hasPaymentFailed ? (
|
||||||
<ExclamationIcon className="text-inverted h-12 w-12 flex-shrink-0 p-0.5 font-extralight" />
|
<AlertTriangle className="text-inverted h-12 w-12 flex-shrink-0 p-0.5 font-extralight" />
|
||||||
) : sessionId ? (
|
) : sessionId ? (
|
||||||
<CheckIcon className="text-inverted h-12 w-12 flex-shrink-0 p-0.5 font-extralight" />
|
<Check className="text-inverted h-12 w-12 flex-shrink-0 p-0.5 font-extralight" />
|
||||||
) : (
|
) : (
|
||||||
<MailOpenIcon className="text-inverted h-12 w-12 flex-shrink-0 p-0.5 font-extralight" />
|
<MailOpen className="text-inverted h-12 w-12 flex-shrink-0 p-0.5 font-extralight" />
|
||||||
)}
|
)}
|
||||||
</div>
|
</div>
|
||||||
<h3 className="font-cal my-6 text-3xl font-normal">
|
<h3 className="font-cal my-6 text-3xl font-normal">
|
||||||
|
|||||||
@@ -62,7 +62,7 @@ const DateOverride = ({ workingHours }: { workingHours: WorkingHours[] }) => {
|
|||||||
<h3 className="text-emphasis font-medium leading-6">
|
<h3 className="text-emphasis font-medium leading-6">
|
||||||
{t("date_overrides")}{" "}
|
{t("date_overrides")}{" "}
|
||||||
<Tooltip content={t("date_overrides_info")}>
|
<Tooltip content={t("date_overrides_info")}>
|
||||||
<span className="inline-block">
|
<span className="inline-block align-middle">
|
||||||
<Info className="h-4 w-4" />
|
<Info className="h-4 w-4" />
|
||||||
</span>
|
</span>
|
||||||
</Tooltip>
|
</Tooltip>
|
||||||
@@ -188,7 +188,7 @@ export default function Availability() {
|
|||||||
}
|
}
|
||||||
CTA={
|
CTA={
|
||||||
<div className="flex items-center justify-end">
|
<div className="flex items-center justify-end">
|
||||||
<div className="hidden items-center rounded-md px-2 sm:flex sm:hover:bg-gray-100">
|
<div className="sm:hover:bg-muted hidden items-center rounded-md px-2 sm:flex">
|
||||||
<Skeleton as={Label} htmlFor="hiddenSwitch" className="mt-2 cursor-pointer self-center pr-2 ">
|
<Skeleton as={Label} htmlFor="hiddenSwitch" className="mt-2 cursor-pointer self-center pr-2 ">
|
||||||
{t("set_to_default")}
|
{t("set_to_default")}
|
||||||
</Skeleton>
|
</Skeleton>
|
||||||
@@ -212,7 +212,7 @@ export default function Availability() {
|
|||||||
aria-label={t("delete")}
|
aria-label={t("delete")}
|
||||||
className="hidden sm:inline"
|
className="hidden sm:inline"
|
||||||
disabled={schedule?.isLastSchedule}
|
disabled={schedule?.isLastSchedule}
|
||||||
tooltip={t("requires_at_least_one_schedule")}
|
tooltip={schedule?.isLastSchedule ? t("requires_at_least_one_schedule") : t("delete")}
|
||||||
/>
|
/>
|
||||||
</DialogTrigger>
|
</DialogTrigger>
|
||||||
<ConfirmationDialogContent
|
<ConfirmationDialogContent
|
||||||
|
|||||||
@@ -53,7 +53,7 @@ const AvailabilityView = ({ user }: { user: User }) => {
|
|||||||
{t("overview_of_day")}{" "}
|
{t("overview_of_day")}{" "}
|
||||||
<input
|
<input
|
||||||
type="date"
|
type="date"
|
||||||
className="inline h-8 border-none p-0"
|
className="inline h-8 border-none bg-inherit p-0"
|
||||||
defaultValue={formattedSelectedDate}
|
defaultValue={formattedSelectedDate}
|
||||||
onChange={(e) => {
|
onChange={(e) => {
|
||||||
if (e.target.value) setSelectedDate(e.target.value);
|
if (e.target.value) setSelectedDate(e.target.value);
|
||||||
|
|||||||
@@ -1,4 +1,3 @@
|
|||||||
import { BookingStatus } from "@prisma/client";
|
|
||||||
import { Collapsible, CollapsibleContent, CollapsibleTrigger } from "@radix-ui/react-collapsible";
|
import { Collapsible, CollapsibleContent, CollapsibleTrigger } from "@radix-ui/react-collapsible";
|
||||||
import classNames from "classnames";
|
import classNames from "classnames";
|
||||||
import { createEvent } from "ics";
|
import { createEvent } from "ics";
|
||||||
@@ -28,6 +27,7 @@ import { getBookingWithResponses } from "@calcom/features/bookings/lib/get-booki
|
|||||||
import {
|
import {
|
||||||
SystemField,
|
SystemField,
|
||||||
getBookingFieldsWithSystemFields,
|
getBookingFieldsWithSystemFields,
|
||||||
|
SMS_REMINDER_NUMBER_FIELD,
|
||||||
} from "@calcom/features/bookings/lib/getBookingFields";
|
} from "@calcom/features/bookings/lib/getBookingFields";
|
||||||
import { parseRecurringEvent } from "@calcom/lib";
|
import { parseRecurringEvent } from "@calcom/lib";
|
||||||
import { APP_NAME } from "@calcom/lib/constants";
|
import { APP_NAME } from "@calcom/lib/constants";
|
||||||
@@ -46,6 +46,7 @@ import { getIs24hClockFromLocalStorage, isBrowserLocale24h } from "@calcom/lib/t
|
|||||||
import { localStorage } from "@calcom/lib/webstorage";
|
import { localStorage } from "@calcom/lib/webstorage";
|
||||||
import prisma from "@calcom/prisma";
|
import prisma from "@calcom/prisma";
|
||||||
import type { Prisma } from "@calcom/prisma/client";
|
import type { Prisma } from "@calcom/prisma/client";
|
||||||
|
import { BookingStatus } from "@calcom/prisma/enums";
|
||||||
import { bookingMetadataSchema } from "@calcom/prisma/zod-utils";
|
import { bookingMetadataSchema } from "@calcom/prisma/zod-utils";
|
||||||
import { customInputSchema, EventTypeMetaDataSchema } from "@calcom/prisma/zod-utils";
|
import { customInputSchema, EventTypeMetaDataSchema } from "@calcom/prisma/zod-utils";
|
||||||
import { Button, EmailInput, HeadSeo, Badge, useCalcomTheme } from "@calcom/ui";
|
import { Button, EmailInput, HeadSeo, Badge, useCalcomTheme } from "@calcom/ui";
|
||||||
@@ -308,7 +309,7 @@ export default function Success(props: SuccessProps) {
|
|||||||
<div className="mt-2 ml-4 -mb-4">
|
<div className="mt-2 ml-4 -mb-4">
|
||||||
<Link
|
<Link
|
||||||
href={allRemainingBookings ? "/bookings/recurring" : "/bookings/upcoming"}
|
href={allRemainingBookings ? "/bookings/recurring" : "/bookings/upcoming"}
|
||||||
className="hover:bg-subtle text-subtle dark:hover:text-inverted hover:text-default mt-2 inline-flex px-1 py-2 text-sm dark:hover:bg-transparent">
|
className="hover:bg-subtle text-subtle hover:text-default mt-2 inline-flex px-1 py-2 text-sm dark:hover:bg-transparent">
|
||||||
<ChevronLeft className="h-5 w-5" /> {t("back_to_bookings")}
|
<ChevronLeft className="h-5 w-5" /> {t("back_to_bookings")}
|
||||||
</Link>
|
</Link>
|
||||||
</div>
|
</div>
|
||||||
@@ -442,8 +443,10 @@ export default function Success(props: SuccessProps) {
|
|||||||
)}
|
)}
|
||||||
{bookingInfo?.attendees.map((attendee) => (
|
{bookingInfo?.attendees.map((attendee) => (
|
||||||
<div key={attendee.name + attendee.email} className="mb-3 last:mb-0">
|
<div key={attendee.name + attendee.email} className="mb-3 last:mb-0">
|
||||||
{attendee.name && <p>{attendee.name}</p>}
|
{attendee.name && (
|
||||||
<p data-testid={`attendee-${attendee.email}`}>{attendee.email}</p>
|
<p data-testid={`attendee-name-${attendee.name}`}>{attendee.name}</p>
|
||||||
|
)}
|
||||||
|
<p data-testid={`attendee-email-${attendee.email}`}>{attendee.email}</p>
|
||||||
</div>
|
</div>
|
||||||
))}
|
))}
|
||||||
</div>
|
</div>
|
||||||
@@ -487,7 +490,8 @@ export default function Success(props: SuccessProps) {
|
|||||||
// We show rescheduleReason at the top
|
// We show rescheduleReason at the top
|
||||||
if (!field) return null;
|
if (!field) return null;
|
||||||
const isSystemField = SystemField.safeParse(field.name);
|
const isSystemField = SystemField.safeParse(field.name);
|
||||||
if (isSystemField.success) return null;
|
// SMS_REMINDER_NUMBER_FIELD is a system field but doesn't have a dedicated place in the UI. So, it would be shown through the following responses list
|
||||||
|
if (isSystemField.success && field.name !== SMS_REMINDER_NUMBER_FIELD) return null;
|
||||||
|
|
||||||
const label = field.label || t(field.defaultLabel || "");
|
const label = field.label || t(field.defaultLabel || "");
|
||||||
|
|
||||||
@@ -1090,6 +1094,7 @@ export async function getServerSideProps(context: GetServerSidePropsContext) {
|
|||||||
|
|
||||||
return {
|
return {
|
||||||
props: {
|
props: {
|
||||||
|
themeBasis: eventType.team ? eventType.team.slug : eventType.users[0]?.username,
|
||||||
hideBranding: eventType.team ? eventType.team.hideBranding : eventType.users[0].hideBranding,
|
hideBranding: eventType.team ? eventType.team.hideBranding : eventType.users[0].hideBranding,
|
||||||
profile,
|
profile,
|
||||||
eventType,
|
eventType,
|
||||||
|
|||||||
@@ -160,6 +160,7 @@ export const getServerSideProps = async (context: GetServerSidePropsContext) =>
|
|||||||
return {
|
return {
|
||||||
props: {
|
props: {
|
||||||
away: user.away,
|
away: user.away,
|
||||||
|
themeBasis: user.username,
|
||||||
isDynamicGroup: false,
|
isDynamicGroup: false,
|
||||||
profile,
|
profile,
|
||||||
date,
|
date,
|
||||||
|
|||||||
@@ -122,6 +122,7 @@ export async function getServerSideProps(context: GetServerSidePropsContext) {
|
|||||||
return {
|
return {
|
||||||
props: {
|
props: {
|
||||||
profile,
|
profile,
|
||||||
|
themeBasis: user.username,
|
||||||
eventType: eventTypeObject,
|
eventType: eventTypeObject,
|
||||||
booking: null,
|
booking: null,
|
||||||
currentSlotBooking: null,
|
currentSlotBooking: null,
|
||||||
|
|||||||
@@ -1,7 +1,6 @@
|
|||||||
/* 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, 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";
|
||||||
@@ -19,6 +18,7 @@ import { useTypedQuery } from "@calcom/lib/hooks/useTypedQuery";
|
|||||||
import { HttpError } from "@calcom/lib/http-error";
|
import { HttpError } from "@calcom/lib/http-error";
|
||||||
import { telemetryEventTypes, useTelemetry } 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 type { PeriodType, SchedulingType } from "@calcom/prisma/enums";
|
||||||
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 { eventTypeBookingFields } from "@calcom/prisma/zod-utils";
|
||||||
import type { RouterOutputs } from "@calcom/trpc/react";
|
import type { RouterOutputs } from "@calcom/trpc/react";
|
||||||
@@ -51,6 +51,7 @@ export type FormValues = {
|
|||||||
eventName: string;
|
eventName: string;
|
||||||
slug: string;
|
slug: string;
|
||||||
length: number;
|
length: number;
|
||||||
|
offsetStart: number;
|
||||||
description: string;
|
description: string;
|
||||||
disableGuests: boolean;
|
disableGuests: boolean;
|
||||||
requiresConfirmation: boolean;
|
requiresConfirmation: boolean;
|
||||||
@@ -136,6 +137,13 @@ const EventTypePage = (props: EventTypeSetupProps) => {
|
|||||||
|
|
||||||
const updateMutation = trpc.viewer.eventTypes.update.useMutation({
|
const updateMutation = trpc.viewer.eventTypes.update.useMutation({
|
||||||
onSuccess: async () => {
|
onSuccess: async () => {
|
||||||
|
formMethods.setValue(
|
||||||
|
"children",
|
||||||
|
formMethods.getValues().children.map((child) => ({
|
||||||
|
...child,
|
||||||
|
created: true,
|
||||||
|
}))
|
||||||
|
);
|
||||||
showToast(
|
showToast(
|
||||||
t("event_type_updated_successfully", {
|
t("event_type_updated_successfully", {
|
||||||
eventTypeTitle: eventType.title,
|
eventTypeTitle: eventType.title,
|
||||||
@@ -204,6 +212,7 @@ const EventTypePage = (props: EventTypeSetupProps) => {
|
|||||||
bookingLimits: eventType.bookingLimits || undefined,
|
bookingLimits: eventType.bookingLimits || undefined,
|
||||||
durationLimits: eventType.durationLimits || undefined,
|
durationLimits: eventType.durationLimits || undefined,
|
||||||
length: eventType.length,
|
length: eventType.length,
|
||||||
|
offsetStart: eventType.offsetStart,
|
||||||
hidden: eventType.hidden,
|
hidden: eventType.hidden,
|
||||||
periodDates: {
|
periodDates: {
|
||||||
startDate: periodDates.startDate,
|
startDate: periodDates.startDate,
|
||||||
@@ -248,6 +257,7 @@ const EventTypePage = (props: EventTypeSetupProps) => {
|
|||||||
)
|
)
|
||||||
.optional(),
|
.optional(),
|
||||||
length: z.union([z.string().transform((val) => +val), z.number()]).optional(),
|
length: z.union([z.string().transform((val) => +val), z.number()]).optional(),
|
||||||
|
offsetStart: z.union([z.string().transform((val) => +val), z.number()]).optional(),
|
||||||
bookingFields: eventTypeBookingFields,
|
bookingFields: eventTypeBookingFields,
|
||||||
})
|
})
|
||||||
// TODO: Add schema for other fields later.
|
// TODO: Add schema for other fields later.
|
||||||
|
|||||||
@@ -1,6 +1,5 @@
|
|||||||
import { useAutoAnimate } from "@formkit/auto-animate/react";
|
import { useAutoAnimate } from "@formkit/auto-animate/react";
|
||||||
import type { User } from "@prisma/client";
|
import type { User } from "@prisma/client";
|
||||||
import { SchedulingType } from "@prisma/client";
|
|
||||||
import { Trans } from "next-i18next";
|
import { Trans } from "next-i18next";
|
||||||
import Link from "next/link";
|
import Link from "next/link";
|
||||||
import { useRouter } from "next/router";
|
import { useRouter } from "next/router";
|
||||||
@@ -18,6 +17,7 @@ import { useLocale } from "@calcom/lib/hooks/useLocale";
|
|||||||
import useMediaQuery from "@calcom/lib/hooks/useMediaQuery";
|
import useMediaQuery from "@calcom/lib/hooks/useMediaQuery";
|
||||||
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 { SchedulingType } from "@calcom/prisma/enums";
|
||||||
import type { RouterOutputs } from "@calcom/trpc/react";
|
import type { RouterOutputs } from "@calcom/trpc/react";
|
||||||
import { trpc, TRPCClientError } from "@calcom/trpc/react";
|
import { trpc, TRPCClientError } from "@calcom/trpc/react";
|
||||||
import {
|
import {
|
||||||
@@ -352,6 +352,9 @@ export const EventTypeList = ({ group, groupIndex, readOnly, types }: EventTypeL
|
|||||||
|
|
||||||
const firstItem = types[0];
|
const firstItem = types[0];
|
||||||
const lastItem = types[types.length - 1];
|
const lastItem = types[types.length - 1];
|
||||||
|
const isManagedEventPrefix = () => {
|
||||||
|
return deleteDialogTypeSchedulingType === SchedulingType.MANAGED ? "_managed" : "";
|
||||||
|
};
|
||||||
return (
|
return (
|
||||||
<div className="bg-default border-subtle mb-16 flex overflow-hidden rounded-md border">
|
<div className="bg-default border-subtle mb-16 flex overflow-hidden rounded-md border">
|
||||||
<ul ref={parent} className="divide-subtle !static w-full divide-y" data-testid="event-types">
|
<ul ref={parent} className="divide-subtle !static w-full divide-y" data-testid="event-types">
|
||||||
@@ -487,21 +490,23 @@ export const EventTypeList = ({ group, groupIndex, readOnly, types }: EventTypeL
|
|||||||
{t("duplicate")}
|
{t("duplicate")}
|
||||||
</DropdownItem>
|
</DropdownItem>
|
||||||
</DropdownMenuItem>
|
</DropdownMenuItem>
|
||||||
<DropdownMenuItem className="outline-none">
|
|
||||||
<EmbedButton
|
|
||||||
as={DropdownItem}
|
|
||||||
type="button"
|
|
||||||
StartIcon={Code}
|
|
||||||
className="w-full rounded-none"
|
|
||||||
embedUrl={encodeURIComponent(embedLink)}>
|
|
||||||
{t("embed")}
|
|
||||||
</EmbedButton>
|
|
||||||
</DropdownMenuItem>
|
|
||||||
</>
|
</>
|
||||||
)}
|
)}
|
||||||
|
{!isManagedEventType && (
|
||||||
|
<DropdownMenuItem className="outline-none">
|
||||||
|
<EmbedButton
|
||||||
|
as={DropdownItem}
|
||||||
|
type="button"
|
||||||
|
StartIcon={Code}
|
||||||
|
className="w-full rounded-none"
|
||||||
|
embedUrl={encodeURIComponent(embedLink)}>
|
||||||
|
{t("embed")}
|
||||||
|
</EmbedButton>
|
||||||
|
</DropdownMenuItem>
|
||||||
|
)}
|
||||||
{/* readonly is only set when we are on a team - if we are on a user event type null will be the value. */}
|
{/* readonly is only set when we are on a team - if we are on a user event type null will be the value. */}
|
||||||
{(group.metadata?.readOnly === false || group.metadata.readOnly === null) &&
|
{(group.metadata?.readOnly === false || group.metadata.readOnly === null) &&
|
||||||
isManagedEventType && (
|
!isChildrenManagedEventType && (
|
||||||
<>
|
<>
|
||||||
<DropdownMenuSeparator />
|
<DropdownMenuSeparator />
|
||||||
<DropdownMenuItem>
|
<DropdownMenuItem>
|
||||||
@@ -533,27 +538,31 @@ export const EventTypeList = ({ group, groupIndex, readOnly, types }: EventTypeL
|
|||||||
</DropdownMenuTrigger>
|
</DropdownMenuTrigger>
|
||||||
<DropdownMenuPortal>
|
<DropdownMenuPortal>
|
||||||
<DropdownMenuContent>
|
<DropdownMenuContent>
|
||||||
<DropdownMenuItem className="outline-none">
|
{!isManagedEventType && (
|
||||||
<DropdownItem
|
<>
|
||||||
href={calLink}
|
<DropdownMenuItem className="outline-none">
|
||||||
target="_blank"
|
<DropdownItem
|
||||||
StartIcon={ExternalLink}
|
href={calLink}
|
||||||
className="w-full rounded-none">
|
target="_blank"
|
||||||
{t("preview")}
|
StartIcon={ExternalLink}
|
||||||
</DropdownItem>
|
className="w-full rounded-none">
|
||||||
</DropdownMenuItem>
|
{t("preview")}
|
||||||
<DropdownMenuItem className="outline-none">
|
</DropdownItem>
|
||||||
<DropdownItem
|
</DropdownMenuItem>
|
||||||
data-testid={"event-type-duplicate-" + type.id}
|
<DropdownMenuItem className="outline-none">
|
||||||
onClick={() => {
|
<DropdownItem
|
||||||
navigator.clipboard.writeText(calLink);
|
data-testid={"event-type-duplicate-" + type.id}
|
||||||
showToast(t("link_copied"), "success");
|
onClick={() => {
|
||||||
}}
|
navigator.clipboard.writeText(calLink);
|
||||||
StartIcon={Clipboard}
|
showToast(t("link_copied"), "success");
|
||||||
className="w-full rounded-none text-left">
|
}}
|
||||||
{t("copy_link")}
|
StartIcon={Clipboard}
|
||||||
</DropdownItem>
|
className="w-full rounded-none text-left">
|
||||||
</DropdownMenuItem>
|
{t("copy_link")}
|
||||||
|
</DropdownItem>
|
||||||
|
</DropdownMenuItem>
|
||||||
|
</>
|
||||||
|
)}
|
||||||
{isNativeShare ? (
|
{isNativeShare ? (
|
||||||
<DropdownMenuItem className="outline-none">
|
<DropdownMenuItem className="outline-none">
|
||||||
<DropdownItem
|
<DropdownItem
|
||||||
@@ -574,36 +583,46 @@ export const EventTypeList = ({ group, groupIndex, readOnly, types }: EventTypeL
|
|||||||
</DropdownItem>
|
</DropdownItem>
|
||||||
</DropdownMenuItem>
|
</DropdownMenuItem>
|
||||||
) : null}
|
) : null}
|
||||||
<DropdownMenuItem className="outline-none">
|
{!readOnly && (
|
||||||
<DropdownItem
|
<DropdownMenuItem className="outline-none">
|
||||||
onClick={() => router.push("/event-types/" + type.id)}
|
<DropdownItem
|
||||||
StartIcon={Edit}
|
onClick={() => router.push("/event-types/" + type.id)}
|
||||||
className="w-full rounded-none">
|
StartIcon={Edit}
|
||||||
{t("edit")}
|
className="w-full rounded-none">
|
||||||
</DropdownItem>
|
{t("edit")}
|
||||||
</DropdownMenuItem>
|
</DropdownItem>
|
||||||
<DropdownMenuItem className="outline-none">
|
</DropdownMenuItem>
|
||||||
<DropdownItem
|
)}
|
||||||
onClick={() => openDuplicateModal(type, group)}
|
{!isManagedEventType && !isChildrenManagedEventType && (
|
||||||
StartIcon={Copy}
|
<DropdownMenuItem className="outline-none">
|
||||||
data-testid={"event-type-duplicate-" + type.id}>
|
<DropdownItem
|
||||||
{t("duplicate")}
|
onClick={() => openDuplicateModal(type, group)}
|
||||||
</DropdownItem>
|
StartIcon={Copy}
|
||||||
</DropdownMenuItem>
|
data-testid={"event-type-duplicate-" + type.id}>
|
||||||
<DropdownMenuSeparator />
|
{t("duplicate")}
|
||||||
<DropdownMenuItem className="outline-none">
|
</DropdownItem>
|
||||||
<DropdownItem
|
</DropdownMenuItem>
|
||||||
color="destructive"
|
)}
|
||||||
onClick={() => {
|
{/* readonly is only set when we are on a team - if we are on a user event type null will be the value. */}
|
||||||
setDeleteDialogOpen(true);
|
{(group.metadata?.readOnly === false || group.metadata.readOnly === null) &&
|
||||||
setDeleteDialogTypeId(type.id);
|
!isChildrenManagedEventType && (
|
||||||
setDeleteDialogSchedulingType(type.schedulingType);
|
<>
|
||||||
}}
|
<DropdownMenuSeparator />
|
||||||
StartIcon={Trash}
|
<DropdownMenuItem className="outline-none">
|
||||||
className="w-full rounded-none">
|
<DropdownItem
|
||||||
{t("delete")}
|
color="destructive"
|
||||||
</DropdownItem>
|
onClick={() => {
|
||||||
</DropdownMenuItem>
|
setDeleteDialogOpen(true);
|
||||||
|
setDeleteDialogTypeId(type.id);
|
||||||
|
setDeleteDialogSchedulingType(type.schedulingType);
|
||||||
|
}}
|
||||||
|
StartIcon={Trash}
|
||||||
|
className="w-full rounded-none">
|
||||||
|
{t("delete")}
|
||||||
|
</DropdownItem>
|
||||||
|
</DropdownMenuItem>
|
||||||
|
</>
|
||||||
|
)}
|
||||||
</DropdownMenuContent>
|
</DropdownMenuContent>
|
||||||
</DropdownMenuPortal>
|
</DropdownMenuPortal>
|
||||||
</Dropdown>
|
</Dropdown>
|
||||||
@@ -616,36 +635,24 @@ export const EventTypeList = ({ group, groupIndex, readOnly, types }: EventTypeL
|
|||||||
<Dialog open={deleteDialogOpen} onOpenChange={setDeleteDialogOpen}>
|
<Dialog open={deleteDialogOpen} onOpenChange={setDeleteDialogOpen}>
|
||||||
<ConfirmationDialogContent
|
<ConfirmationDialogContent
|
||||||
variety="danger"
|
variety="danger"
|
||||||
title={t(
|
title={t(`delete${isManagedEventPrefix()}_event_type`)}
|
||||||
`delete_${deleteDialogTypeSchedulingType === SchedulingType.MANAGED && "managed"}_event_type`
|
confirmBtnText={t(`confirm_delete_event_type`)}
|
||||||
)}
|
loadingText={t(`confirm_delete_event_type`)}
|
||||||
confirmBtnText={t(
|
|
||||||
`confirm_delete_${
|
|
||||||
deleteDialogTypeSchedulingType === SchedulingType.MANAGED && "managed"
|
|
||||||
}_event_type`
|
|
||||||
)}
|
|
||||||
loadingText={t(
|
|
||||||
`confirm_delete_${
|
|
||||||
deleteDialogTypeSchedulingType === SchedulingType.MANAGED && "managed"
|
|
||||||
}_event_type`
|
|
||||||
)}
|
|
||||||
onConfirm={(e) => {
|
onConfirm={(e) => {
|
||||||
e.preventDefault();
|
e.preventDefault();
|
||||||
deleteEventTypeHandler(deleteDialogTypeId);
|
deleteEventTypeHandler(deleteDialogTypeId);
|
||||||
}}>
|
}}>
|
||||||
<p className="mt-5">
|
|
||||||
{t(
|
|
||||||
`delete_${
|
|
||||||
deleteDialogTypeSchedulingType === SchedulingType.MANAGED && "managed"
|
|
||||||
}_event_type_description`
|
|
||||||
)}
|
|
||||||
</p>
|
|
||||||
<p className="mt-5">
|
<p className="mt-5">
|
||||||
<Trans
|
<Trans
|
||||||
i18nKey={`delete_${
|
i18nKey={`delete${isManagedEventPrefix()}_event_type_description`}
|
||||||
deleteDialogTypeSchedulingType === SchedulingType.MANAGED && "managed"
|
components={{ li: <li />, ul: <ul className="ml-4 list-disc" /> }}>
|
||||||
}_event_type_warning`}
|
<ul>
|
||||||
/>
|
<li>Members assigned to this event type will also have their event types deleted.</li>
|
||||||
|
<li>
|
||||||
|
Anyone who they've shared their link with will no longer be able to book using it.
|
||||||
|
</li>
|
||||||
|
</ul>
|
||||||
|
</Trans>
|
||||||
</p>
|
</p>
|
||||||
</ConfirmationDialogContent>
|
</ConfirmationDialogContent>
|
||||||
</Dialog>
|
</Dialog>
|
||||||
|
|||||||
Loaded 100 of 838 files, more files were not shown because too many files have changed in this diff.
Show more
Reference in new issue
Block a user