Compare commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
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 |
@@ -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
|
||||
# - SALESFORCE
|
||||
# - ZOHOCRM
|
||||
# - ZOHO_BIGIN
|
||||
|
||||
# - APP STORE **********************************************************************************************
|
||||
# ⚠️ ⚠️ ⚠️ THESE WILL BE MIGRATED TO THE DATABASE TO PREVENT AWS's 4KB ENV QUOTA ⚠️ ⚠️ ⚠️
|
||||
# - 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
|
||||
# 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
|
||||
|
||||
+8
-4
@@ -138,12 +138,16 @@ API_KEY_PREFIX=cal_
|
||||
EMAIL_FROM='notifications@yourselfhostedcal.com'
|
||||
|
||||
# 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.
|
||||
EMAIL_SERVER_HOST='smtp.office365.com'
|
||||
EMAIL_SERVER_PORT=587
|
||||
EMAIL_SERVER_USER='<office365_emailAddress>'
|
||||
# EMAIL_SERVER_HOST='smtp.office365.com'
|
||||
# EMAIL_SERVER_PORT=587
|
||||
# EMAIL_SERVER_USER='<office365_emailAddress>'
|
||||
# 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.
|
||||
# EMAIL_SERVER_HOST='smtp.gmail.com'
|
||||
|
||||
@@ -7,6 +7,7 @@ on:
|
||||
paths-ignore:
|
||||
- "**.md"
|
||||
- ".github/CODEOWNERS"
|
||||
merge_group:
|
||||
workflow_dispatch:
|
||||
|
||||
concurrency:
|
||||
|
||||
+2
-1
@@ -65,8 +65,9 @@ tsconfig.tsbuildinfo
|
||||
# turbo
|
||||
.turbo
|
||||
|
||||
# Prisma-Zod
|
||||
# Prisma generated files
|
||||
packages/prisma/zod/*.ts
|
||||
packages/prisma/enums
|
||||
|
||||
# Builds
|
||||
dist
|
||||
|
||||
@@ -14,4 +14,5 @@ public
|
||||
.DS_Store
|
||||
.eslintignore
|
||||
packages/prisma/zod
|
||||
packages/prisma/enums
|
||||
apps/web/public/embed
|
||||
|
||||
Vendored
+1
-1
@@ -6,6 +6,6 @@
|
||||
},
|
||||
"typescript.preferences.importModuleSpecifier": "non-relative",
|
||||
"spellright.language": ["en"],
|
||||
"spellright.documentTypes": ["markdown", "typescript"],
|
||||
"spellright.documentTypes": ["markdown", "typescript", "typescriptreact"],
|
||||
"tailwindCSS.experimental.classRegex": [["cva\\(([^)]*)\\)", "[\"'`]([^\"'`]*).*?[\"'`]"]]
|
||||
}
|
||||
|
||||
@@ -24,7 +24,7 @@
|
||||
|
||||
<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://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://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>
|
||||
@@ -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://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://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>
|
||||
</p>
|
||||
|
||||
<!-- 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">
|
||||
|
||||
@@ -53,13 +53,13 @@
|
||||
The open source Calendly alternative. You are in charge
|
||||
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.
|
||||
|
||||
## Recognition
|
||||
|
||||
#### Hacker News
|
||||
#### [Hacker News](https://news.ycombinator.com/from?site=cal.com)
|
||||
|
||||
<a href="https://news.ycombinator.com/item?id=34507672">
|
||||
<img
|
||||
@@ -77,22 +77,22 @@ That's where Cal.com comes in. Self-hosted or hosted by us. White-label by desig
|
||||
/>
|
||||
</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
|
||||
|
||||
- [Next.js](https://nextjs.org/?ref=cal.com)
|
||||
- [tRPC](https://trpc.io/?ref=cal.com)
|
||||
- [React](https://reactjs.org/?ref=cal.com)
|
||||
- [Tailwind](https://tailwindcss.com/?ref=cal.com)
|
||||
- [Prisma](https://prisma.io/?ref=cal.com)
|
||||
- [React.js](https://reactjs.org/?ref=cal.com)
|
||||
- [Tailwind CSS](https://tailwindcss.com/?ref=cal.com)
|
||||
- [Prisma.io](https://prisma.io/?ref=cal.com)
|
||||
- [Daily.co](https://go.cal.com/daily)
|
||||
|
||||
## 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
|
||||
|
||||
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)
|
||||
- PostgreSQL
|
||||
@@ -121,6 +121,9 @@ Here is what you need to be able to run Cal.
|
||||
```sh
|
||||
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
|
||||
|
||||
@@ -134,10 +137,10 @@ Here is what you need to be able to run Cal.
|
||||
yarn
|
||||
```
|
||||
|
||||
1. Set up your .env file
|
||||
1. Set up your `.env` file
|
||||
- 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 24` to generate a key and add it under `CALENDSO_ENCRYPTION_KEY` 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.
|
||||
|
||||
#### Quick start with `yarn dx`
|
||||
|
||||
@@ -150,7 +153,7 @@ yarn dx
|
||||
|
||||
#### 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
|
||||
echo 'NEXT_PUBLIC_DEBUG=1' >> .env
|
||||
@@ -158,16 +161,16 @@ echo 'NEXT_PUBLIC_DEBUG=1' >> .env
|
||||
|
||||
#### 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>'
|
||||
```
|
||||
|
||||
<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 DB using Heroku.</summary>
|
||||
|
||||
1. Create a free account with [Heroku](https://www.heroku.com/).
|
||||
1. Create a free account with [Heroku](https://heroku.com).
|
||||
|
||||
2. Create a new app.
|
||||
<img width="306" alt="Create an App" src="https://user-images.githubusercontent.com/16905768/115322780-b3d58c00-a17e-11eb-8a52-b758fb0ea942.png">
|
||||
@@ -184,19 +187,25 @@ echo 'NEXT_PUBLIC_DEBUG=1' >> .env
|
||||
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.
|
||||
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/).
|
||||
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>
|
||||
|
||||
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 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`)
|
||||
|
||||
```sh
|
||||
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)
|
||||
|
||||
@@ -206,7 +215,7 @@ echo 'NEXT_PUBLIC_DEBUG=1' >> .env
|
||||
|
||||
#### 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
|
||||
yarn db-studio
|
||||
@@ -251,7 +260,7 @@ yarn playwright show-report test-results/reports/playwright-html-report
|
||||
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:
|
||||
|
||||
@@ -285,7 +294,7 @@ yarn playwright show-report test-results/reports/playwright-html-report
|
||||
|
||||
### 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).
|
||||
|
||||
@@ -298,14 +307,14 @@ Cal.com, Inc. does not provide official support for Docker, but we will accept f
|
||||
### Heroku
|
||||
|
||||
<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>
|
||||
|
||||
### Railway
|
||||
|
||||
[](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
|
||||
|
||||
@@ -386,7 +395,7 @@ Don't code but still want to contribute? Join our [slack](https://cal.com/slack)
|
||||
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.
|
||||
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_
|
||||
|
||||
@@ -423,7 +432,7 @@ following
|
||||
5. Choose "User-managed app" as the app type.
|
||||
6. De-select the option to publish the app on the Zoom App Marketplace.
|
||||
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.
|
||||
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`.
|
||||
@@ -435,8 +444,8 @@ following
|
||||
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.
|
||||
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.
|
||||
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.
|
||||
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://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
|
||||
|
||||
@@ -445,7 +454,7 @@ following
|
||||
3. Click "Create app" button top right.
|
||||
4. Fill in any information you want in the "App info" tab
|
||||
5. Go to tab "Auth"
|
||||
6. Now copy the Client ID and Client Secret to your .env file into the `HUBSPOT_CLIENT_ID` and `HUBSPOT_CLIENT_SECRET` fields.
|
||||
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.
|
||||
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.
|
||||
@@ -458,31 +467,33 @@ following
|
||||
3. Click "ADD CLIENT" button top right and select "Server-based Applications".
|
||||
4. Fill in any information you want in the "Client Details" 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.
|
||||
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.
|
||||
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
|
||||
|
||||
### Setting up SendGrid for Email reminders
|
||||
|
||||
1. Create a SendGrid account (https://signup.sendgrid.com/)
|
||||
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
|
||||
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)
|
||||
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)
|
||||
|
||||
### 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’
|
||||
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
|
||||
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)
|
||||
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
|
||||
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.com)
|
||||
7. Create a messaging service (Develop -> Messaging -> Services)
|
||||
8. Choose any name for the messaging service
|
||||
9. Click 'Add Senders'
|
||||
@@ -490,15 +501,15 @@ following
|
||||
11. Add the listed phone number
|
||||
12. Leave all other fields as they are
|
||||
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
|
||||
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
|
||||
|
||||
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 -->
|
||||
|
||||
|
||||
@@ -71,5 +71,5 @@ module.exports = {
|
||||
|
||||
return config;
|
||||
},
|
||||
typescript: { reactDocgen: 'react-docgen' }
|
||||
typescript: { reactDocgen: "react-docgen" },
|
||||
};
|
||||
|
||||
+264
-273
@@ -1,5 +1,6 @@
|
||||
import { Collapsible, CollapsibleContent } from "@radix-ui/react-collapsible";
|
||||
import classNames from "classnames";
|
||||
import type { TFunction } from "next-i18next";
|
||||
import type { NextRouter } from "next/router";
|
||||
import { useRouter } from "next/router";
|
||||
import type { MutableRefObject, RefObject } from "react";
|
||||
@@ -21,11 +22,10 @@ import {
|
||||
TextArea,
|
||||
TextField,
|
||||
ColorPicker,
|
||||
Select,
|
||||
} from "@calcom/ui";
|
||||
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 EmbedFramework = "react" | "HTML";
|
||||
|
||||
@@ -305,190 +305,189 @@ const getEmbedTypeSpecificString = ({
|
||||
return "";
|
||||
};
|
||||
|
||||
const embeds: {
|
||||
illustration: React.ReactElement;
|
||||
title: string;
|
||||
subtitle: string;
|
||||
type: EmbedType;
|
||||
}[] = [
|
||||
{
|
||||
title: "Inline Embed",
|
||||
subtitle: "Loads your event type directly inline with your other website content.",
|
||||
type: "inline",
|
||||
illustration: (
|
||||
<svg
|
||||
width="100%"
|
||||
height="100%"
|
||||
className="rounded-md"
|
||||
viewBox="0 0 308 265"
|
||||
fill="none"
|
||||
xmlns="http://www.w3.org/2000/svg">
|
||||
<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"
|
||||
fill="white"
|
||||
/>
|
||||
<rect x="24" width="260" height="38.5" rx="6" fill="#F3F4F6" />
|
||||
<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="48" y="86.5" width="48" height="4" rx="6" fill="#F3F4F6" />
|
||||
<rect x="49" 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="73" 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="97" 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="121" 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="85" 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="109" 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="133" 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="61" y="125.5" width="6" height="6" rx="1" 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"
|
||||
fill="#3E3E3E"
|
||||
/>
|
||||
<rect x="73" 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="97" 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="121" 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="49" 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="73" 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="97" y="137.5" width="6" height="6" rx="1" fill="#3E3E3E" />
|
||||
<rect x="109" y="137.5" width="6" height="6" rx="1" fill="#3E3E3E" />
|
||||
<rect x="121" y="137.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="49" 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="73" 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="97" y="149.5" width="6" height="6" rx="1" fill="#3E3E3E" />
|
||||
<rect x="109" y="149.5" width="6" height="6" rx="1" fill="#3E3E3E" />
|
||||
<rect x="121" y="149.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="49" 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="73" 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="97" y="161.5" width="6" height="6" rx="1" fill="#3E3E3E" />
|
||||
<rect x="109" 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="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: "Floating pop-up button",
|
||||
subtitle: "Adds a floating button on your site that launches Cal in a dialog.",
|
||||
type: "floating-popup",
|
||||
illustration: (
|
||||
<svg
|
||||
width="100%"
|
||||
height="100%"
|
||||
className="rounded-md"
|
||||
viewBox="0 0 308 265"
|
||||
fill="none"
|
||||
xmlns="http://www.w3.org/2000/svg">
|
||||
<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"
|
||||
fill="white"
|
||||
/>
|
||||
<rect x="24" width="260" height="38.5" rx="6" fill="#F3F4F6" />
|
||||
<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="156" y="50.5" width="128" height="164" rx="6" fill="#F3F4F6" />
|
||||
<rect x="24" y="226.5" width="260" height="38.5" rx="6" fill="#F3F4F6" />
|
||||
<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: "Pop up via element click",
|
||||
subtitle: "Open your Cal dialog when someone clicks an element.",
|
||||
type: "element-click",
|
||||
illustration: (
|
||||
<svg
|
||||
width="100%"
|
||||
height="100%"
|
||||
className="rounded-md"
|
||||
viewBox="0 0 308 265"
|
||||
fill="none"
|
||||
xmlns="http://www.w3.org/2000/svg">
|
||||
<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"
|
||||
fill="white"
|
||||
/>
|
||||
<rect x="24" y="0.50293" width="260" height="24" rx="6" fill="#F3F4F6" />
|
||||
<rect x="24" y="35" width="259" height="192" rx="5.5" fill="#F9FAFB" />
|
||||
<g filter="url(#filter0_i_3223_14162)">
|
||||
<rect opacity="0.8" x="40" y="99" width="24" height="24" rx="2" fill="#E5E7EB" />
|
||||
<rect x="40" y="127" width="48" height="8" rx="1" fill="#E5E7EB" />
|
||||
<rect x="40" y="139" width="82" height="8" rx="1" fill="#E5E7EB" />
|
||||
<rect x="40" y="151" width="34" height="4" rx="1" fill="#E5E7EB" />
|
||||
<rect x="40" y="159" width="34" height="4" rx="1" fill="#E5E7EB" />
|
||||
</g>
|
||||
<rect x="152" y="48" width="2" height="169" rx="2" fill="#E5E7EB" />
|
||||
const embeds = (t: TFunction) =>
|
||||
(() => {
|
||||
return [
|
||||
{
|
||||
title: t("inline_embed"),
|
||||
subtitle: t("load_inline_content"),
|
||||
type: "inline",
|
||||
illustration: (
|
||||
<svg
|
||||
width="100%"
|
||||
height="100%"
|
||||
className="rounded-md"
|
||||
viewBox="0 0 308 265"
|
||||
fill="none"
|
||||
xmlns="http://www.w3.org/2000/svg">
|
||||
<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"
|
||||
fill="white"
|
||||
/>
|
||||
<rect x="24" width="260" height="38.5" rx="6" fill="#F3F4F6" />
|
||||
<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="48" y="86.5" width="48" height="4" rx="6" fill="#F3F4F6" />
|
||||
<rect x="49" 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="73" 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="97" 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="121" 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="85" 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="109" 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="133" 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="61" y="125.5" width="6" height="6" rx="1" 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"
|
||||
fill="#3E3E3E"
|
||||
/>
|
||||
<rect x="73" 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="97" 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="121" 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="49" 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="73" 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="97" y="137.5" width="6" height="6" rx="1" fill="#3E3E3E" />
|
||||
<rect x="109" y="137.5" width="6" height="6" rx="1" fill="#3E3E3E" />
|
||||
<rect x="121" y="137.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="49" 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="73" 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="97" y="149.5" width="6" height="6" rx="1" fill="#3E3E3E" />
|
||||
<rect x="109" y="149.5" width="6" height="6" rx="1" fill="#3E3E3E" />
|
||||
<rect x="121" y="149.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="49" 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="73" 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="97" y="161.5" width="6" height="6" rx="1" fill="#3E3E3E" />
|
||||
<rect x="109" 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="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",
|
||||
illustration: (
|
||||
<svg
|
||||
width="100%"
|
||||
height="100%"
|
||||
className="rounded-md"
|
||||
viewBox="0 0 308 265"
|
||||
fill="none"
|
||||
xmlns="http://www.w3.org/2000/svg">
|
||||
<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"
|
||||
fill="white"
|
||||
/>
|
||||
<rect x="24" width="260" height="38.5" rx="6" fill="#F3F4F6" />
|
||||
<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="156" y="50.5" width="128" height="164" rx="6" fill="#F3F4F6" />
|
||||
<rect x="24" y="226.5" width="260" height="38.5" rx="6" fill="#F3F4F6" />
|
||||
<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",
|
||||
illustration: (
|
||||
<svg
|
||||
width="100%"
|
||||
height="100%"
|
||||
className="rounded-md"
|
||||
viewBox="0 0 308 265"
|
||||
fill="none"
|
||||
xmlns="http://www.w3.org/2000/svg">
|
||||
<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"
|
||||
fill="white"
|
||||
/>
|
||||
<rect x="24" y="0.50293" width="260" height="24" rx="6" fill="#F3F4F6" />
|
||||
<rect x="24" y="35" width="259" height="192" rx="5.5" fill="#F9FAFB" />
|
||||
<g filter="url(#filter0_i_3223_14162)">
|
||||
<rect opacity="0.8" x="40" y="99" width="24" height="24" rx="2" fill="#E5E7EB" />
|
||||
<rect x="40" y="127" width="48" height="8" rx="1" fill="#E5E7EB" />
|
||||
<rect x="40" y="139" width="82" height="8" rx="1" fill="#E5E7EB" />
|
||||
<rect x="40" y="151" width="34" height="4" rx="1" fill="#E5E7EB" />
|
||||
<rect x="40" y="159" width="34" height="4" rx="1" fill="#E5E7EB" />
|
||||
</g>
|
||||
<rect x="152" y="48" width="2" height="169" rx="2" fill="#E5E7EB" />
|
||||
|
||||
<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>
|
||||
),
|
||||
},
|
||||
];
|
||||
})();
|
||||
|
||||
<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 = [
|
||||
{
|
||||
name: "HTML",
|
||||
@@ -534,11 +533,7 @@ ${getEmbedTypeSpecificString({ embedFramework: "HTML", embedType, calLink, previ
|
||||
<!-- Cal ${embedType} embed code ends -->`
|
||||
}
|
||||
/>
|
||||
<p className="text-subtle hidden text-sm">
|
||||
{t(
|
||||
"Need help? See our guides for embedding Cal on Wix, Squarespace, or WordPress, check our common questions, or explore advanced embed options."
|
||||
)}
|
||||
</p>
|
||||
<p className="text-subtle hidden text-sm">{t("need_help_embedding")}</p>
|
||||
</>
|
||||
);
|
||||
}),
|
||||
@@ -622,7 +617,7 @@ Cal("init", {origin:"${WEBAPP_URL}"});
|
||||
const ThemeSelectControl = ({ children, ...props }: ControlProps<{ value: Theme; label: string }, false>) => {
|
||||
return (
|
||||
<components.Control {...props}>
|
||||
<Sun className="text-subtle ml-2 h-4 w-4" />
|
||||
<Sun className="text-subtle mr-2 h-4 w-4" />
|
||||
{children}
|
||||
</components.Control>
|
||||
);
|
||||
@@ -642,7 +637,7 @@ const ChooseEmbedTypesDialogContent = () => {
|
||||
</div>
|
||||
</div>
|
||||
<div className="items-start space-y-2 md:flex md:space-y-0">
|
||||
{embeds.map((embed, index) => (
|
||||
{embeds(t).map((embed, index) => (
|
||||
<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"
|
||||
key={index}
|
||||
@@ -692,7 +687,7 @@ const EmbedTypeCodeAndPreviewDialogContent = ({
|
||||
});
|
||||
|
||||
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 [isBookingCustomizationOpen, setIsBookingCustomizationOpen] = useState(true);
|
||||
@@ -806,11 +801,11 @@ const EmbedTypeCodeAndPreviewDialogContent = ({
|
||||
const FloatingPopupPositionOptions = [
|
||||
{
|
||||
value: "bottom-right",
|
||||
label: "Bottom Right",
|
||||
label: "Bottom right",
|
||||
},
|
||||
{
|
||||
value: "bottom-left",
|
||||
label: "Bottom Left",
|
||||
label: "Bottom left",
|
||||
},
|
||||
];
|
||||
|
||||
@@ -834,7 +829,7 @@ const EmbedTypeCodeAndPreviewDialogContent = ({
|
||||
</button>
|
||||
{embed.title}
|
||||
</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={classNames("font-medium", embedType === "element-click" ? "hidden" : "")}>
|
||||
<Collapsible
|
||||
@@ -844,7 +839,7 @@ const EmbedTypeCodeAndPreviewDialogContent = ({
|
||||
<div className={classNames(embedType === "inline" ? "block" : "hidden")}>
|
||||
{/*TODO: Add Auto/Fixed toggle from Figma */}
|
||||
<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]">
|
||||
<TextField
|
||||
labelProps={{ className: "hidden" }}
|
||||
@@ -892,10 +887,10 @@ const EmbedTypeCodeAndPreviewDialogContent = ({
|
||||
</div>
|
||||
<div
|
||||
className={classNames(
|
||||
"mt-4 items-center justify-between",
|
||||
"items-center justify-between",
|
||||
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 */}
|
||||
<TextField
|
||||
labelProps={{ className: "hidden" }}
|
||||
@@ -910,7 +905,7 @@ const EmbedTypeCodeAndPreviewDialogContent = ({
|
||||
};
|
||||
});
|
||||
}}
|
||||
defaultValue="Book my Cal"
|
||||
defaultValue={t("book_my_cal")}
|
||||
required
|
||||
/>
|
||||
</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
|
||||
className={classNames(
|
||||
"mt-4 items-center justify-between",
|
||||
embedType === "floating-popup" ? "text-emphasis" : "hidden"
|
||||
)}>
|
||||
<div className="mb-2">Position of Button</div>
|
||||
<div className="mb-2">Position of button</div>
|
||||
<Select
|
||||
onChange={(position) => {
|
||||
setPreviewState((previewState) => {
|
||||
@@ -959,54 +954,50 @@ const EmbedTypeCodeAndPreviewDialogContent = ({
|
||||
options={FloatingPopupPositionOptions}
|
||||
/>
|
||||
</div>
|
||||
<div
|
||||
className={classNames(
|
||||
"mt-4",
|
||||
embedType === "floating-popup" ? "text-emphasis" : "hidden"
|
||||
)}>
|
||||
<div>Button Color</div>
|
||||
<div className="w-full">
|
||||
<ColorPicker
|
||||
popoverAlign="start"
|
||||
container={dialogContentRef?.current ?? undefined}
|
||||
defaultValue="#000000"
|
||||
onChange={(color) => {
|
||||
setPreviewState((previewState) => {
|
||||
return {
|
||||
...previewState,
|
||||
floatingPopup: {
|
||||
...previewState.floatingPopup,
|
||||
buttonColor: color,
|
||||
},
|
||||
};
|
||||
});
|
||||
}}
|
||||
/>
|
||||
<div className="mt-3 flex flex-col xl:flex-row xl:justify-between">
|
||||
<div className={classNames("mt-4", embedType === "floating-popup" ? "" : "hidden")}>
|
||||
<div className="whitespace-nowrap">Button color</div>
|
||||
<div className="mt-2 w-40 xl:mt-0 xl:w-full">
|
||||
<ColorPicker
|
||||
className="w-[130px]"
|
||||
popoverAlign="start"
|
||||
container={dialogContentRef?.current ?? undefined}
|
||||
defaultValue="#000000"
|
||||
onChange={(color) => {
|
||||
setPreviewState((previewState) => {
|
||||
return {
|
||||
...previewState,
|
||||
floatingPopup: {
|
||||
...previewState.floatingPopup,
|
||||
buttonColor: color,
|
||||
},
|
||||
};
|
||||
});
|
||||
}}
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div
|
||||
className={classNames(
|
||||
"mt-4",
|
||||
embedType === "floating-popup" ? "text-emphasis" : "hidden"
|
||||
)}>
|
||||
<div>Text Color</div>
|
||||
<div className="w-full">
|
||||
<ColorPicker
|
||||
popoverAlign="start"
|
||||
container={dialogContentRef?.current ?? undefined}
|
||||
defaultValue="#000000"
|
||||
onChange={(color) => {
|
||||
setPreviewState((previewState) => {
|
||||
return {
|
||||
...previewState,
|
||||
floatingPopup: {
|
||||
...previewState.floatingPopup,
|
||||
buttonTextColor: color,
|
||||
},
|
||||
};
|
||||
});
|
||||
}}
|
||||
/>
|
||||
<div className={classNames("mt-4", embedType === "floating-popup" ? "" : "hidden")}>
|
||||
<div className="whitespace-nowrap">Text color</div>
|
||||
<div className="mt-2 mb-6 w-40 xl:mt-0 xl:w-full">
|
||||
<ColorPicker
|
||||
className="w-[130px]"
|
||||
popoverAlign="start"
|
||||
container={dialogContentRef?.current ?? undefined}
|
||||
defaultValue="#000000"
|
||||
onChange={(color) => {
|
||||
setPreviewState((previewState) => {
|
||||
return {
|
||||
...previewState,
|
||||
floatingPopup: {
|
||||
...previewState.floatingPopup,
|
||||
buttonTextColor: color,
|
||||
},
|
||||
};
|
||||
});
|
||||
}}
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</CollapsibleContent>
|
||||
@@ -1017,7 +1008,30 @@ const EmbedTypeCodeAndPreviewDialogContent = ({
|
||||
open={isBookingCustomizationOpen}
|
||||
onOpenChange={() => setIsBookingCustomizationOpen((val) => !val)}>
|
||||
<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">
|
||||
<Switch
|
||||
checked={previewState.hideEventTypeDetails}
|
||||
@@ -1056,29 +1070,6 @@ const EmbedTypeCodeAndPreviewDialogContent = ({
|
||||
</div>
|
||||
</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>
|
||||
</CollapsibleContent>
|
||||
</Collapsible>
|
||||
|
||||
@@ -1,5 +1,4 @@
|
||||
import Link from "next/link";
|
||||
import { useRouter } from "next/router";
|
||||
import type { IframeHTMLAttributes } from "react";
|
||||
import React, { useState } from "react";
|
||||
|
||||
@@ -43,7 +42,6 @@ const Component = ({
|
||||
}: Parameters<typeof App>[0]) => {
|
||||
const { t, i18n } = useLocale();
|
||||
const hasDescriptionItems = descriptionItems && descriptionItems.length > 0;
|
||||
const router = useRouter();
|
||||
|
||||
const mutation = useAddAppMutation(null, {
|
||||
onSuccess: (data) => {
|
||||
@@ -186,7 +184,7 @@ const Component = ({
|
||||
label={t("disconnect")}
|
||||
credentialId={existingCredentials[0]}
|
||||
onSuccess={() => {
|
||||
router.replace("/apps/installed");
|
||||
appCredentials.refetch();
|
||||
}}
|
||||
/>
|
||||
) : (
|
||||
|
||||
@@ -1,9 +1,9 @@
|
||||
import { useMutation } from "@tanstack/react-query";
|
||||
import Link from "next/link";
|
||||
import { Fragment } from "react";
|
||||
|
||||
import { InstallAppButton } from "@calcom/app-store/components";
|
||||
import DisconnectIntegration from "@calcom/features/apps/components/DisconnectIntegration";
|
||||
import { CalendarSwitch } from "@calcom/features/calendars/CalendarSwitch";
|
||||
import DestinationCalendarSelector from "@calcom/features/calendars/DestinationCalendarSelector";
|
||||
import { useLocale } from "@calcom/lib/hooks/useLocale";
|
||||
import { trpc } from "@calcom/trpc/react";
|
||||
@@ -12,12 +12,10 @@ import {
|
||||
Button,
|
||||
EmptyScreen,
|
||||
List,
|
||||
showToast,
|
||||
AppSkeletonLoader as SkeletonLoader,
|
||||
Switch,
|
||||
ShellSubHeading,
|
||||
} 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";
|
||||
|
||||
@@ -31,83 +29,6 @@ type Props = {
|
||||
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) {
|
||||
const { t } = useLocale();
|
||||
const query = trpc.viewer.integrations.useQuery({ variant: "calendar", onlyInstalled: false });
|
||||
@@ -185,14 +106,15 @@ function ConnectedCalendarsList(props: Props) {
|
||||
{!fromOnboarding && (
|
||||
<>
|
||||
<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) => (
|
||||
<CalendarSwitch
|
||||
key={cal.externalId}
|
||||
externalId={cal.externalId}
|
||||
title={cal.name || "Nameless calendar"}
|
||||
name={cal.name || "Nameless calendar"}
|
||||
type={item.integration.type}
|
||||
defaultSelected={cal.isSelected}
|
||||
isChecked={cal.isSelected}
|
||||
destination={cal.externalId === props.destinationCalendarId}
|
||||
/>
|
||||
))}
|
||||
|
||||
@@ -1,4 +1,3 @@
|
||||
import { UserPermissionRole } from "@prisma/client";
|
||||
import { useSession } from "next-auth/react";
|
||||
import { useRouter } from "next/router";
|
||||
import type { ComponentProps } from "react";
|
||||
@@ -6,6 +5,7 @@ import React, { useEffect } from "react";
|
||||
|
||||
import SettingsLayout from "@calcom/features/settings/layouts/SettingsLayout";
|
||||
import type Shell from "@calcom/features/shell/Shell";
|
||||
import { UserPermissionRole } from "@calcom/prisma/enums";
|
||||
import { ErrorBoundary } from "@calcom/ui";
|
||||
|
||||
export default function AdminLayout({
|
||||
|
||||
@@ -47,7 +47,7 @@ export function AvailableEventLocations({ locations }: { locations: Props["event
|
||||
src={eventLocationType.iconUrl}
|
||||
className={classNames(
|
||||
"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`}
|
||||
/>
|
||||
|
||||
@@ -11,7 +11,7 @@ import useMediaQuery from "@calcom/lib/hooks/useMediaQuery";
|
||||
import { TimeFormat } from "@calcom/lib/timeFormat";
|
||||
import { nameOfDay } from "@calcom/lib/weekday";
|
||||
import { trpc } from "@calcom/trpc/react";
|
||||
import type { Slot } from "@calcom/trpc/server/routers/viewer/slots";
|
||||
import type { Slot } from "@calcom/trpc/server/routers/viewer/slots/types";
|
||||
import { SkeletonContainer, SkeletonText, ToggleGroup } from "@calcom/ui";
|
||||
|
||||
import classNames from "@lib/classNames";
|
||||
@@ -72,6 +72,7 @@ const AvailableTimes: FC<AvailableTimesProps> = ({
|
||||
slotUtcStartDate: slot.time,
|
||||
eventTypeId,
|
||||
slotUtcEndDate: dayjs(slot.time).utc().add(duration, "minutes").format(),
|
||||
bookingAttendees: bookingAttendees || undefined,
|
||||
});
|
||||
};
|
||||
|
||||
|
||||
@@ -1,10 +1,10 @@
|
||||
import { SchedulingType } from "@prisma/client";
|
||||
import type { FC, ReactNode } from "react";
|
||||
import { useEffect } from "react";
|
||||
|
||||
import dayjs from "@calcom/dayjs";
|
||||
import classNames from "@calcom/lib/classNames";
|
||||
import { useLocale } from "@calcom/lib/hooks/useLocale";
|
||||
import { SchedulingType } from "@calcom/prisma/enums";
|
||||
import { Badge } from "@calcom/ui";
|
||||
import { CheckSquare, Clock } from "@calcom/ui/components/icon";
|
||||
|
||||
|
||||
@@ -1,4 +1,3 @@
|
||||
import { BookingStatus } from "@prisma/client";
|
||||
import { useRouter } from "next/router";
|
||||
import { useState } from "react";
|
||||
|
||||
@@ -13,6 +12,7 @@ import { formatTime } from "@calcom/lib/date-fns";
|
||||
import getPaymentAppData from "@calcom/lib/getPaymentAppData";
|
||||
import { useLocale } from "@calcom/lib/hooks/useLocale";
|
||||
import { getEveryFreqFor } from "@calcom/lib/recurringStrings";
|
||||
import { BookingStatus } from "@calcom/prisma/enums";
|
||||
import type { RouterInputs, RouterOutputs } from "@calcom/trpc/react";
|
||||
import { trpc } from "@calcom/trpc/react";
|
||||
import type { ActionType } from "@calcom/ui";
|
||||
@@ -280,13 +280,13 @@ function BookingListItem(booking: BookingItemProps) {
|
||||
isOpenDialog={isOpenSetLocationDialog}
|
||||
setShowLocationModal={setIsOpenLocationDialog}
|
||||
/>
|
||||
{booking.paid && (
|
||||
{booking.paid && booking.payment[0] && (
|
||||
<ChargeCardDialog
|
||||
isOpenDialog={chargeCardDialogIsOpen}
|
||||
setIsOpenDialog={setChargeCardDialogIsOpen}
|
||||
bookingId={booking.id}
|
||||
paymentAmount={booking?.payment[0].amount}
|
||||
paymentCurrency={booking?.payment[0].currency}
|
||||
paymentAmount={booking.payment[0].amount}
|
||||
paymentCurrency={booking.payment[0].currency}
|
||||
/>
|
||||
)}
|
||||
{showRecordingsButtons && (
|
||||
@@ -354,11 +354,15 @@ function BookingListItem(booking: BookingItemProps) {
|
||||
{booking.eventType.team.name}
|
||||
</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">
|
||||
{booking.payment[0].paymentOption === "HOLD" ? t("card_held") : t("paid")}
|
||||
</Badge>
|
||||
)}
|
||||
) : null}
|
||||
{recurringDates !== undefined && (
|
||||
<div className="text-muted mt-2 text-sm">
|
||||
<RecurringBookingsTooltip booking={booking} recurringDates={recurringDates} />
|
||||
@@ -458,7 +462,7 @@ function BookingListItem(booking: BookingItemProps) {
|
||||
<RequestSentMessage />
|
||||
</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">
|
||||
<TableActions actions={chargeCardActions} />
|
||||
</div>
|
||||
@@ -483,12 +487,12 @@ const RecurringBookingsTooltip = ({ booking, recurringDates }: RecurringBookings
|
||||
i18n: { language },
|
||||
} = useLocale();
|
||||
const now = new Date();
|
||||
const recurringCount = recurringDates.filter((date) => {
|
||||
const recurringCount = recurringDates.filter((recurringDate) => {
|
||||
return (
|
||||
date >= now &&
|
||||
recurringDate >= now &&
|
||||
!booking.recurringInfo?.bookings[BookingStatus.CANCELLED]
|
||||
.map((date) => date.toDateString())
|
||||
.includes(date.toDateString())
|
||||
.includes(recurringDate.toDateString())
|
||||
);
|
||||
}).length;
|
||||
|
||||
|
||||
@@ -17,7 +17,7 @@ export function TimezoneDropdown({
|
||||
|
||||
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" />
|
||||
<TimeOptions onSelectTimeZone={handleSelectTimeZone} />
|
||||
</div>
|
||||
|
||||
@@ -230,12 +230,14 @@ const BookingPage = ({
|
||||
}),
|
||||
{}
|
||||
);
|
||||
|
||||
const reserveSlot = () => {
|
||||
if (queryDuration) {
|
||||
reserveSlotMutation.mutate({
|
||||
eventTypeId: eventType.id,
|
||||
slotUtcStartDate: dayjs(queryDate).utc().format(),
|
||||
slotUtcEndDate: dayjs(queryDate).utc().add(parseInt(queryDuration), "minutes").format(),
|
||||
bookingAttendees: currentSlotBooking ? currentSlotBooking.attendees.length : undefined,
|
||||
});
|
||||
}
|
||||
};
|
||||
@@ -550,11 +552,11 @@ const BookingPage = ({
|
||||
)}>
|
||||
<div className="sm:flex">
|
||||
{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}>
|
||||
<BookingDescriptionPayment eventType={eventType} t={t} i18n={i18n} />
|
||||
{!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]" />
|
||||
<p className="-ml-2 inline-block items-center px-2">
|
||||
{getEveryFreqFor({
|
||||
|
||||
@@ -11,6 +11,10 @@ import { getEventName } from "@calcom/core/event";
|
||||
import getLocationsOptionsForSelect from "@calcom/features/bookings/lib/getLocationOptionsForSelect";
|
||||
import DestinationCalendarSelector from "@calcom/features/calendars/DestinationCalendarSelector";
|
||||
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 { classNames } from "@calcom/lib";
|
||||
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 workflows = eventType.workflows.map((workflowOnEventType) => workflowOnEventType.workflow);
|
||||
|
||||
eventType.bookingFields.forEach(({ name }) => {
|
||||
bookingFields[name] = name + " input";
|
||||
});
|
||||
@@ -338,7 +344,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 && (
|
||||
<CustomEventTypeModal
|
||||
close={closeEventNameTip}
|
||||
|
||||
@@ -1,4 +1,3 @@
|
||||
import { SchedulingType } from "@prisma/client";
|
||||
import type { EventTypeSetup, FormValues } from "pages/event-types/[type]";
|
||||
import { useState } from "react";
|
||||
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 { useLocale } from "@calcom/lib/hooks/useLocale";
|
||||
import { weekdayNames } from "@calcom/lib/weekday";
|
||||
import { SchedulingType } from "@calcom/prisma/enums";
|
||||
import { trpc } from "@calcom/trpc/react";
|
||||
import useMeQuery from "@calcom/trpc/react/hooks/useMeQuery";
|
||||
import { Badge, Button, Select, SettingsToggle, SkeletonText } from "@calcom/ui";
|
||||
@@ -154,12 +154,11 @@ const EventTypeScheduleDetails = ({
|
||||
|
||||
const EventTypeSchedule = ({ eventType }: { eventType: EventTypeSetup }) => {
|
||||
const { t } = useLocale();
|
||||
const { shouldLockIndicator, shouldLockDisableProps, isManagedEventType, isChildrenManagedEventType } =
|
||||
useLockedFieldsManager(
|
||||
eventType,
|
||||
t("locked_fields_admin_description"),
|
||||
t("locked_fields_member_description")
|
||||
);
|
||||
const { shouldLockIndicator, isManagedEventType, isChildrenManagedEventType } = useLockedFieldsManager(
|
||||
eventType,
|
||||
t("locked_fields_admin_description"),
|
||||
t("locked_fields_member_description")
|
||||
);
|
||||
const { watch } = useFormContext<FormValues>();
|
||||
const watchSchedule = watch("schedule");
|
||||
const formMethods = useFormContext<FormValues>();
|
||||
@@ -205,6 +204,8 @@ const EventTypeSchedule = ({ eventType }: { eventType: EventTypeSetup }) => {
|
||||
const value = options.find((option) =>
|
||||
scheduleId
|
||||
? option.value === scheduleId
|
||||
: isManagedEventType
|
||||
? option.value === 0
|
||||
: 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 findDurationType from "@calcom/lib/findDurationType";
|
||||
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 { Button, DateRangePicker, InputField, Label, Select, SettingsToggle, TextField } from "@calcom/ui";
|
||||
import { Plus, Trash } from "@calcom/ui/components/icon";
|
||||
|
||||
@@ -276,7 +276,7 @@ export const EventSetupTab = (
|
||||
return (
|
||||
<li
|
||||
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">
|
||||
<img
|
||||
@@ -285,7 +285,7 @@ export const EventSetupTab = (
|
||||
"h-4 w-4",
|
||||
// invert all the icons except app icons
|
||||
eventLocationType.iconUrl &&
|
||||
!eventLocationType.iconUrl.startsWith("/api") &&
|
||||
!eventLocationType.iconUrl.startsWith("/app-store") &&
|
||||
"dark:invert"
|
||||
)}
|
||||
alt={`${eventLocationType.label} logo`}
|
||||
@@ -466,6 +466,7 @@ export const EventSetupTab = (
|
||||
defaultValue={eventType.length ?? 15}
|
||||
{...formMethods.register("length")}
|
||||
addOnSuffix={<>{t("minutes")}</>}
|
||||
min={1}
|
||||
/>
|
||||
)}
|
||||
{!lengthLockedProps.disabled && (
|
||||
|
||||
@@ -1,4 +1,3 @@
|
||||
import { SchedulingType } from "@prisma/client";
|
||||
import type { EventTypeSetupProps, FormValues } from "pages/event-types/[type]";
|
||||
import { useEffect, useRef } 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 { WEBAPP_URL } from "@calcom/lib/constants";
|
||||
import { useLocale } from "@calcom/lib/hooks/useLocale";
|
||||
import { SchedulingType } from "@calcom/prisma/enums";
|
||||
import { Label, Select } from "@calcom/ui";
|
||||
|
||||
interface IUserToValue {
|
||||
|
||||
@@ -1,4 +1,3 @@
|
||||
import { SchedulingType } from "@prisma/client";
|
||||
import { Webhook as TbWebhook } from "lucide-react";
|
||||
import type { TFunction } 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 { useLocale } from "@calcom/lib/hooks/useLocale";
|
||||
import { HttpError } from "@calcom/lib/http-error";
|
||||
import { SchedulingType } from "@calcom/prisma/enums";
|
||||
import { trpc, TRPCClientError } from "@calcom/trpc/react";
|
||||
import {
|
||||
Button,
|
||||
@@ -238,7 +238,7 @@ function EventTypeSingleLayout({
|
||||
<div className="flex items-center justify-end">
|
||||
{!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
|
||||
as={Label}
|
||||
htmlFor="hiddenSwitch"
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
import { CalendarSwitch } from "./CalendarSwitch";
|
||||
import { CalendarSwitch } from "@calcom/features/calendars/CalendarSwitch";
|
||||
|
||||
interface IConnectedCalendarItem {
|
||||
name: string;
|
||||
|
||||
@@ -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>
|
||||
);
|
||||
}
|
||||
@@ -25,7 +25,7 @@ const OptionWithIcon = ({ icon, label }: { icon?: string; label: string }) => {
|
||||
src={icon}
|
||||
alt="cover"
|
||||
// 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>
|
||||
|
||||
@@ -19,6 +19,8 @@ import { MetaProvider } from "@calcom/ui";
|
||||
import usePublicPage from "@lib/hooks/usePublicPage";
|
||||
import type { WithNonceProps } from "@lib/withNonce";
|
||||
|
||||
import { useViewerI18n } from "@components/I18nLanguageHandler";
|
||||
|
||||
const I18nextAdapter = appWithTranslation<NextJsAppProps<SSRConfig> & { children: React.ReactNode }>(
|
||||
({ children }) => <>{children}</>
|
||||
);
|
||||
@@ -46,9 +48,7 @@ const CustomI18nextProvider = (props: AppPropsWithChildren) => {
|
||||
* i18n should never be clubbed with other queries, so that it's caching can be managed independently.
|
||||
* We intend to not cache i18n query
|
||||
**/
|
||||
const { i18n, locale } = trpc.viewer.public.i18n.useQuery(undefined, {
|
||||
trpc: { context: { skipBatch: true } },
|
||||
}).data ?? {
|
||||
const { i18n, locale } = useViewerI18n().data ?? {
|
||||
locale: "en",
|
||||
};
|
||||
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "@calcom/web",
|
||||
"version": "2.8.10",
|
||||
"version": "2.8.13",
|
||||
"private": true,
|
||||
"scripts": {
|
||||
"analyze": "ANALYZE=true next build",
|
||||
|
||||
@@ -23,6 +23,7 @@ class MyDocument extends Document<Props> {
|
||||
// If x-csp not set by gSSP, then it's initialPropsOnly
|
||||
setHeader(ctx, "x-csp", "initialPropsOnly");
|
||||
}
|
||||
|
||||
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
|
||||
const parsedUrl = new URL(asPath, "https://dummyurl");
|
||||
|
||||
@@ -1,4 +1,3 @@
|
||||
import { IdentityProvider } from "@prisma/client";
|
||||
import type { NextApiRequest, NextApiResponse } from "next";
|
||||
|
||||
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 { verifyPassword } from "@calcom/features/auth/lib/verifyPassword";
|
||||
import prisma from "@calcom/prisma";
|
||||
import { IdentityProvider } from "@calcom/prisma/enums";
|
||||
|
||||
export default async function handler(req: NextApiRequest, res: NextApiResponse) {
|
||||
const session = await getServerSession({ req, res });
|
||||
|
||||
@@ -1,4 +1,3 @@
|
||||
import { IdentityProvider } from "@prisma/client";
|
||||
import type { NextApiRequest } from "next";
|
||||
import z from "zod";
|
||||
|
||||
@@ -8,6 +7,7 @@ import { HttpError } from "@calcom/lib/http-error";
|
||||
import { defaultHandler, defaultResponder } from "@calcom/lib/server";
|
||||
import slugify from "@calcom/lib/slugify";
|
||||
import prisma from "@calcom/prisma";
|
||||
import { IdentityProvider } from "@calcom/prisma/enums";
|
||||
|
||||
const querySchema = z.object({
|
||||
username: z
|
||||
|
||||
@@ -1,10 +1,10 @@
|
||||
import { IdentityProvider } from "@prisma/client";
|
||||
import type { NextApiRequest, NextApiResponse } from "next";
|
||||
|
||||
import { hashPassword } from "@calcom/features/auth/lib/hashPassword";
|
||||
import slugify from "@calcom/lib/slugify";
|
||||
import { closeComUpsertTeamUser } from "@calcom/lib/sync/SyncServiceManager";
|
||||
import prisma from "@calcom/prisma";
|
||||
import { IdentityProvider } from "@calcom/prisma/enums";
|
||||
|
||||
export default async function handler(req: NextApiRequest, res: NextApiResponse) {
|
||||
if (req.method !== "POST") {
|
||||
@@ -36,14 +36,25 @@ export default async function handler(req: NextApiRequest, res: NextApiResponse)
|
||||
return;
|
||||
}
|
||||
|
||||
// There is actually an existingUser if username matches
|
||||
// OR if email matches and both username and password are set
|
||||
// There is an existingUser if the username matches
|
||||
// OR if the email matches AND either the email is verified
|
||||
// or both username and password are set
|
||||
const existingUser = await prisma.user.findFirst({
|
||||
where: {
|
||||
OR: [
|
||||
{ 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 { authenticator } from "otplib";
|
||||
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 { symmetricEncrypt } from "@calcom/lib/crypto";
|
||||
import prisma from "@calcom/prisma";
|
||||
import { IdentityProvider } from "@calcom/prisma/enums";
|
||||
|
||||
export default async function handler(req: NextApiRequest, res: NextApiResponse) {
|
||||
if (req.method !== "POST") {
|
||||
|
||||
@@ -1,4 +1,3 @@
|
||||
import { BookingStatus, ReminderType } from "@prisma/client";
|
||||
import type { NextApiRequest, NextApiResponse } from "next";
|
||||
|
||||
import dayjs from "@calcom/dayjs";
|
||||
@@ -7,6 +6,7 @@ import { getCalEventResponses } from "@calcom/features/bookings/lib/getCalEventR
|
||||
import { isPrismaObjOrUndefined, parseRecurringEvent } from "@calcom/lib";
|
||||
import { getTranslation } from "@calcom/lib/server/i18n";
|
||||
import prisma, { bookingMinimalSelect } from "@calcom/prisma";
|
||||
import { BookingStatus, ReminderType } from "@calcom/prisma/enums";
|
||||
import type { CalendarEvent } from "@calcom/types/Calendar";
|
||||
|
||||
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 { z } from "zod";
|
||||
|
||||
@@ -7,7 +6,7 @@ import { defaultResponder } from "@calcom/lib/server";
|
||||
import prisma from "@calcom/prisma";
|
||||
import { TRPCError } from "@calcom/trpc/server";
|
||||
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 {
|
||||
ACCEPT = "accept",
|
||||
@@ -27,31 +26,18 @@ const decryptedSchema = z.object({
|
||||
|
||||
async function handler(req: NextApiRequest, res: NextApiResponse<Response>) {
|
||||
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 || ""))
|
||||
);
|
||||
|
||||
/** 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({
|
||||
where: { uid: bookingUid },
|
||||
});
|
||||
|
||||
try {
|
||||
/** @see https://trpc.io/docs/server-side-calls */
|
||||
const ctx = await createContext({ req, res }, sessionGetter);
|
||||
const caller = viewerRouter.createCaller(ctx);
|
||||
const ctx = await createContext({ req, res });
|
||||
const caller = viewerRouter.createCaller({ ...ctx, req, res });
|
||||
await caller.bookings.confirm({
|
||||
bookingId: booking.id,
|
||||
recurringEventId: booking.recurringEventId || undefined,
|
||||
|
||||
@@ -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);
|
||||
@@ -6,6 +6,7 @@ import path from "path";
|
||||
import { z } from "zod";
|
||||
|
||||
import { getAppWithMetadata } from "@calcom/app-store/_appRegistry";
|
||||
import { getAppAssetFullPath } from "@calcom/app-store/getAppAssetFullPath";
|
||||
import prisma from "@calcom/prisma";
|
||||
|
||||
import type { inferSSRProps } from "@lib/types/inferSSRProps";
|
||||
@@ -108,9 +109,11 @@ export const getStaticProps = async (ctx: GetStaticPropsContext) => {
|
||||
const { content, data } = sourceSchema.parse({ content: result.content, data: result.data });
|
||||
if (data.items) {
|
||||
data.items = data.items.map((item) => {
|
||||
if (typeof item === "string" && !item.includes("/api/app-store")) {
|
||||
// Make relative paths absolute
|
||||
return `/api/app-store/${appDirname}/${item}`;
|
||||
if (typeof item === "string") {
|
||||
return getAppAssetFullPath(item, {
|
||||
dirName: singleApp.dirName,
|
||||
isTemplate: singleApp.isTemplate,
|
||||
});
|
||||
}
|
||||
return item;
|
||||
});
|
||||
|
||||
@@ -4,6 +4,7 @@ import { useRouter } from "next/router";
|
||||
|
||||
import { AppSetupPage } from "@calcom/app-store/_pages/setup";
|
||||
import { getStaticProps } from "@calcom/app-store/_pages/setup/_getStaticProps";
|
||||
import { HeadSeo } from "@calcom/ui";
|
||||
|
||||
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;
|
||||
|
||||
@@ -1,4 +1,3 @@
|
||||
import { AppCategories } from "@prisma/client";
|
||||
import type { GetStaticPropsContext, InferGetStaticPropsType } from "next";
|
||||
import Link from "next/link";
|
||||
import { useRouter } from "next/router";
|
||||
@@ -7,6 +6,7 @@ import { getAppRegistry } from "@calcom/app-store/_appRegistry";
|
||||
import Shell from "@calcom/features/shell/Shell";
|
||||
import { useLocale } from "@calcom/lib/hooks/useLocale";
|
||||
import prisma from "@calcom/prisma";
|
||||
import { AppCategories } from "@calcom/prisma/enums";
|
||||
import { AppCard, SkeletonText } from "@calcom/ui";
|
||||
|
||||
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 { classNames } from "@calcom/lib";
|
||||
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 { HorizontalTabItemProps } 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"
|
||||
addOnLeading={<Search className="text-subtle h-4 w-4" />}
|
||||
addOnClassname="!border-muted"
|
||||
containerClassName={classNames("focus:!ring-offset-0", className)}
|
||||
containerClassName={classNames("focus:!ring-offset-0 m-1", className)}
|
||||
type="search"
|
||||
autoComplete="false"
|
||||
onChange={onChange}
|
||||
|
||||
@@ -1,4 +1,3 @@
|
||||
import { UserPermissionRole } from "@prisma/client";
|
||||
import type { GetServerSidePropsContext } from "next";
|
||||
import { useRouter } from "next/router";
|
||||
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 { useLocale } from "@calcom/lib/hooks/useLocale";
|
||||
import prisma from "@calcom/prisma";
|
||||
import { UserPermissionRole } from "@calcom/prisma/enums";
|
||||
import type { inferSSRProps } from "@calcom/types/inferSSRProps";
|
||||
import { Meta, WizardForm } from "@calcom/ui";
|
||||
|
||||
|
||||
@@ -188,7 +188,7 @@ export default function Availability() {
|
||||
}
|
||||
CTA={
|
||||
<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 ">
|
||||
{t("set_to_default")}
|
||||
</Skeleton>
|
||||
|
||||
@@ -1,4 +1,3 @@
|
||||
import { BookingStatus } from "@prisma/client";
|
||||
import { Collapsible, CollapsibleContent, CollapsibleTrigger } from "@radix-ui/react-collapsible";
|
||||
import classNames from "classnames";
|
||||
import { createEvent } from "ics";
|
||||
@@ -28,6 +27,7 @@ import { getBookingWithResponses } from "@calcom/features/bookings/lib/get-booki
|
||||
import {
|
||||
SystemField,
|
||||
getBookingFieldsWithSystemFields,
|
||||
SMS_REMINDER_NUMBER_FIELD,
|
||||
} from "@calcom/features/bookings/lib/getBookingFields";
|
||||
import { parseRecurringEvent } from "@calcom/lib";
|
||||
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 prisma from "@calcom/prisma";
|
||||
import type { Prisma } from "@calcom/prisma/client";
|
||||
import { BookingStatus } from "@calcom/prisma/enums";
|
||||
import { bookingMetadataSchema } from "@calcom/prisma/zod-utils";
|
||||
import { customInputSchema, EventTypeMetaDataSchema } from "@calcom/prisma/zod-utils";
|
||||
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">
|
||||
<Link
|
||||
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")}
|
||||
</Link>
|
||||
</div>
|
||||
@@ -487,7 +488,8 @@ export default function Success(props: SuccessProps) {
|
||||
// We show rescheduleReason at the top
|
||||
if (!field) return null;
|
||||
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 || "");
|
||||
|
||||
|
||||
@@ -1,7 +1,6 @@
|
||||
/* eslint-disable @typescript-eslint/no-empty-function */
|
||||
import { useAutoAnimate } from "@formkit/auto-animate/react";
|
||||
import { zodResolver } from "@hookform/resolvers/zod";
|
||||
import type { PeriodType, SchedulingType } from "@prisma/client";
|
||||
import type { GetServerSidePropsContext } from "next";
|
||||
import { Trans } from "next-i18next";
|
||||
import { useEffect, useState } from "react";
|
||||
@@ -19,6 +18,7 @@ import { useTypedQuery } from "@calcom/lib/hooks/useTypedQuery";
|
||||
import { HttpError } from "@calcom/lib/http-error";
|
||||
import { telemetryEventTypes, useTelemetry } from "@calcom/lib/telemetry";
|
||||
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 { eventTypeBookingFields } from "@calcom/prisma/zod-utils";
|
||||
import type { RouterOutputs } from "@calcom/trpc/react";
|
||||
@@ -136,6 +136,13 @@ const EventTypePage = (props: EventTypeSetupProps) => {
|
||||
|
||||
const updateMutation = trpc.viewer.eventTypes.update.useMutation({
|
||||
onSuccess: async () => {
|
||||
formMethods.setValue(
|
||||
"children",
|
||||
formMethods.getValues().children.map((child) => ({
|
||||
...child,
|
||||
created: true,
|
||||
}))
|
||||
);
|
||||
showToast(
|
||||
t("event_type_updated_successfully", {
|
||||
eventTypeTitle: eventType.title,
|
||||
|
||||
@@ -1,6 +1,5 @@
|
||||
import { useAutoAnimate } from "@formkit/auto-animate/react";
|
||||
import type { User } from "@prisma/client";
|
||||
import { SchedulingType } from "@prisma/client";
|
||||
import { Trans } from "next-i18next";
|
||||
import Link from "next/link";
|
||||
import { useRouter } from "next/router";
|
||||
@@ -18,6 +17,7 @@ import { useLocale } from "@calcom/lib/hooks/useLocale";
|
||||
import useMediaQuery from "@calcom/lib/hooks/useMediaQuery";
|
||||
import { useTypedQuery } from "@calcom/lib/hooks/useTypedQuery";
|
||||
import { HttpError } from "@calcom/lib/http-error";
|
||||
import { SchedulingType } from "@calcom/prisma/enums";
|
||||
import type { RouterOutputs } from "@calcom/trpc/react";
|
||||
import { trpc, TRPCClientError } from "@calcom/trpc/react";
|
||||
import {
|
||||
@@ -352,6 +352,9 @@ export const EventTypeList = ({ group, groupIndex, readOnly, types }: EventTypeL
|
||||
|
||||
const firstItem = types[0];
|
||||
const lastItem = types[types.length - 1];
|
||||
const isManagedEventPrefix = () => {
|
||||
return deleteDialogTypeSchedulingType === SchedulingType.MANAGED ? "_managed" : "";
|
||||
};
|
||||
return (
|
||||
<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">
|
||||
@@ -501,7 +504,7 @@ export const EventTypeList = ({ group, groupIndex, readOnly, types }: EventTypeL
|
||||
)}
|
||||
{/* 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) &&
|
||||
isManagedEventType && (
|
||||
!isChildrenManagedEventType && (
|
||||
<>
|
||||
<DropdownMenuSeparator />
|
||||
<DropdownMenuItem>
|
||||
@@ -533,27 +536,31 @@ export const EventTypeList = ({ group, groupIndex, readOnly, types }: EventTypeL
|
||||
</DropdownMenuTrigger>
|
||||
<DropdownMenuPortal>
|
||||
<DropdownMenuContent>
|
||||
<DropdownMenuItem className="outline-none">
|
||||
<DropdownItem
|
||||
href={calLink}
|
||||
target="_blank"
|
||||
StartIcon={ExternalLink}
|
||||
className="w-full rounded-none">
|
||||
{t("preview")}
|
||||
</DropdownItem>
|
||||
</DropdownMenuItem>
|
||||
<DropdownMenuItem className="outline-none">
|
||||
<DropdownItem
|
||||
data-testid={"event-type-duplicate-" + type.id}
|
||||
onClick={() => {
|
||||
navigator.clipboard.writeText(calLink);
|
||||
showToast(t("link_copied"), "success");
|
||||
}}
|
||||
StartIcon={Clipboard}
|
||||
className="w-full rounded-none text-left">
|
||||
{t("copy_link")}
|
||||
</DropdownItem>
|
||||
</DropdownMenuItem>
|
||||
{!isManagedEventType && (
|
||||
<>
|
||||
<DropdownMenuItem className="outline-none">
|
||||
<DropdownItem
|
||||
href={calLink}
|
||||
target="_blank"
|
||||
StartIcon={ExternalLink}
|
||||
className="w-full rounded-none">
|
||||
{t("preview")}
|
||||
</DropdownItem>
|
||||
</DropdownMenuItem>
|
||||
<DropdownMenuItem className="outline-none">
|
||||
<DropdownItem
|
||||
data-testid={"event-type-duplicate-" + type.id}
|
||||
onClick={() => {
|
||||
navigator.clipboard.writeText(calLink);
|
||||
showToast(t("link_copied"), "success");
|
||||
}}
|
||||
StartIcon={Clipboard}
|
||||
className="w-full rounded-none text-left">
|
||||
{t("copy_link")}
|
||||
</DropdownItem>
|
||||
</DropdownMenuItem>
|
||||
</>
|
||||
)}
|
||||
{isNativeShare ? (
|
||||
<DropdownMenuItem className="outline-none">
|
||||
<DropdownItem
|
||||
@@ -574,36 +581,46 @@ export const EventTypeList = ({ group, groupIndex, readOnly, types }: EventTypeL
|
||||
</DropdownItem>
|
||||
</DropdownMenuItem>
|
||||
) : null}
|
||||
<DropdownMenuItem className="outline-none">
|
||||
<DropdownItem
|
||||
onClick={() => router.push("/event-types/" + type.id)}
|
||||
StartIcon={Edit}
|
||||
className="w-full rounded-none">
|
||||
{t("edit")}
|
||||
</DropdownItem>
|
||||
</DropdownMenuItem>
|
||||
<DropdownMenuItem className="outline-none">
|
||||
<DropdownItem
|
||||
onClick={() => openDuplicateModal(type, group)}
|
||||
StartIcon={Copy}
|
||||
data-testid={"event-type-duplicate-" + type.id}>
|
||||
{t("duplicate")}
|
||||
</DropdownItem>
|
||||
</DropdownMenuItem>
|
||||
<DropdownMenuSeparator />
|
||||
<DropdownMenuItem className="outline-none">
|
||||
<DropdownItem
|
||||
color="destructive"
|
||||
onClick={() => {
|
||||
setDeleteDialogOpen(true);
|
||||
setDeleteDialogTypeId(type.id);
|
||||
setDeleteDialogSchedulingType(type.schedulingType);
|
||||
}}
|
||||
StartIcon={Trash}
|
||||
className="w-full rounded-none">
|
||||
{t("delete")}
|
||||
</DropdownItem>
|
||||
</DropdownMenuItem>
|
||||
{!readOnly && (
|
||||
<DropdownMenuItem className="outline-none">
|
||||
<DropdownItem
|
||||
onClick={() => router.push("/event-types/" + type.id)}
|
||||
StartIcon={Edit}
|
||||
className="w-full rounded-none">
|
||||
{t("edit")}
|
||||
</DropdownItem>
|
||||
</DropdownMenuItem>
|
||||
)}
|
||||
{!isManagedEventType && !isChildrenManagedEventType && (
|
||||
<DropdownMenuItem className="outline-none">
|
||||
<DropdownItem
|
||||
onClick={() => openDuplicateModal(type, group)}
|
||||
StartIcon={Copy}
|
||||
data-testid={"event-type-duplicate-" + type.id}>
|
||||
{t("duplicate")}
|
||||
</DropdownItem>
|
||||
</DropdownMenuItem>
|
||||
)}
|
||||
{/* 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) &&
|
||||
!isChildrenManagedEventType && (
|
||||
<>
|
||||
<DropdownMenuSeparator />
|
||||
<DropdownMenuItem className="outline-none">
|
||||
<DropdownItem
|
||||
color="destructive"
|
||||
onClick={() => {
|
||||
setDeleteDialogOpen(true);
|
||||
setDeleteDialogTypeId(type.id);
|
||||
setDeleteDialogSchedulingType(type.schedulingType);
|
||||
}}
|
||||
StartIcon={Trash}
|
||||
className="w-full rounded-none">
|
||||
{t("delete")}
|
||||
</DropdownItem>
|
||||
</DropdownMenuItem>
|
||||
</>
|
||||
)}
|
||||
</DropdownMenuContent>
|
||||
</DropdownMenuPortal>
|
||||
</Dropdown>
|
||||
@@ -616,36 +633,24 @@ export const EventTypeList = ({ group, groupIndex, readOnly, types }: EventTypeL
|
||||
<Dialog open={deleteDialogOpen} onOpenChange={setDeleteDialogOpen}>
|
||||
<ConfirmationDialogContent
|
||||
variety="danger"
|
||||
title={t(
|
||||
`delete_${deleteDialogTypeSchedulingType === SchedulingType.MANAGED && "managed"}_event_type`
|
||||
)}
|
||||
confirmBtnText={t(
|
||||
`confirm_delete_${
|
||||
deleteDialogTypeSchedulingType === SchedulingType.MANAGED && "managed"
|
||||
}_event_type`
|
||||
)}
|
||||
loadingText={t(
|
||||
`confirm_delete_${
|
||||
deleteDialogTypeSchedulingType === SchedulingType.MANAGED && "managed"
|
||||
}_event_type`
|
||||
)}
|
||||
title={t(`delete${isManagedEventPrefix()}_event_type`)}
|
||||
confirmBtnText={t(`confirm_delete_event_type`)}
|
||||
loadingText={t(`confirm_delete_event_type`)}
|
||||
onConfirm={(e) => {
|
||||
e.preventDefault();
|
||||
deleteEventTypeHandler(deleteDialogTypeId);
|
||||
}}>
|
||||
<p className="mt-5">
|
||||
{t(
|
||||
`delete_${
|
||||
deleteDialogTypeSchedulingType === SchedulingType.MANAGED && "managed"
|
||||
}_event_type_description`
|
||||
)}
|
||||
</p>
|
||||
<p className="mt-5">
|
||||
<Trans
|
||||
i18nKey={`delete_${
|
||||
deleteDialogTypeSchedulingType === SchedulingType.MANAGED && "managed"
|
||||
}_event_type_warning`}
|
||||
/>
|
||||
i18nKey={`delete${isManagedEventPrefix()}_event_type_description`}
|
||||
components={{ li: <li />, ul: <ul className="ml-4 list-disc" /> }}>
|
||||
<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>
|
||||
</ConfirmationDialogContent>
|
||||
</Dialog>
|
||||
|
||||
@@ -15,7 +15,7 @@ type PageProps = inferSSRProps<typeof getServerSideProps>;
|
||||
|
||||
export default function Type({ slug, user, booking, away }: PageProps) {
|
||||
return (
|
||||
<main className="flex justify-center">
|
||||
<main className="flex h-full min-h-[100dvh] items-center justify-center">
|
||||
<Booker username={user} eventSlug={slug} rescheduleBooking={booking} isAway={away} />
|
||||
</main>
|
||||
);
|
||||
|
||||
@@ -14,7 +14,7 @@ type PageProps = inferSSRProps<typeof getServerSideProps>;
|
||||
|
||||
export default function Type({ slug, user, booking, away }: PageProps) {
|
||||
return (
|
||||
<main className="flex justify-center">
|
||||
<main className="flex h-full min-h-[100dvh] items-center justify-center">
|
||||
<Booker username={user} eventSlug={slug} rescheduleBooking={booking} isAway={away} />
|
||||
</main>
|
||||
);
|
||||
|
||||
@@ -146,6 +146,7 @@ const AppearanceView = () => {
|
||||
<p className="text-default mb-2 block text-sm font-medium">{t("light_brand_color")}</p>
|
||||
<ColorPicker
|
||||
defaultValue={user.brandColor}
|
||||
resetDefaultValue="#292929"
|
||||
onChange={(value) => {
|
||||
if (!checkWCAGContrastColor("#ffffff", value)) {
|
||||
setLightModeError(true);
|
||||
@@ -167,6 +168,7 @@ const AppearanceView = () => {
|
||||
<p className="text-default mb-2 block text-sm font-medium">{t("dark_brand_color")}</p>
|
||||
<ColorPicker
|
||||
defaultValue={user.darkBrandColor}
|
||||
resetDefaultValue="#fafafa"
|
||||
onChange={(value) => {
|
||||
if (!checkWCAGContrastColor("#101010", value)) {
|
||||
setDarkModeError(true);
|
||||
|
||||
@@ -4,6 +4,7 @@ import { useRouter } from "next/router";
|
||||
import { Fragment } from "react";
|
||||
|
||||
import DisconnectIntegration from "@calcom/features/apps/components/DisconnectIntegration";
|
||||
import { CalendarSwitch } from "@calcom/features/calendars/CalendarSwitch";
|
||||
import DestinationCalendarSelector from "@calcom/features/calendars/DestinationCalendarSelector";
|
||||
import { getLayout } from "@calcom/features/settings/layouts/SettingsLayout";
|
||||
import { classNames } from "@calcom/lib";
|
||||
@@ -29,7 +30,6 @@ import { Plus, Calendar } from "@calcom/ui/components/icon";
|
||||
import { QueryCell } from "@lib/QueryCell";
|
||||
|
||||
import PageWrapper from "@components/PageWrapper";
|
||||
import { CalendarSwitch } from "@components/settings/CalendarSwitch";
|
||||
|
||||
const SkeletonLoader = () => {
|
||||
return (
|
||||
@@ -178,15 +178,17 @@ const CalendarsView = () => {
|
||||
</div>
|
||||
<div className="border-subtle w-full border-t">
|
||||
<p className="text-subtle px-2 pt-4 text-sm">{t("toggle_calendars_conflict")}</p>
|
||||
<ul className="space-y-2 p-4">
|
||||
<ul className="space-y-4 p-4">
|
||||
{item.calendars.map((cal) => (
|
||||
<CalendarSwitch
|
||||
key={cal.externalId}
|
||||
externalId={cal.externalId}
|
||||
title={cal.name || "Nameless calendar"}
|
||||
name={cal.name || "Nameless calendar"}
|
||||
type={item.integration.type}
|
||||
isSelected={cal.isSelected}
|
||||
defaultSelected={cal.externalId === data?.destinationCalendar?.externalId}
|
||||
isChecked={
|
||||
cal.isSelected || cal.externalId === data?.destinationCalendar?.externalId
|
||||
}
|
||||
/>
|
||||
))}
|
||||
</ul>
|
||||
|
||||
@@ -24,7 +24,7 @@ import {
|
||||
SkeletonContainer,
|
||||
SkeletonText,
|
||||
} from "@calcom/ui";
|
||||
import { AlertCircle, MoreHorizontal, Trash, Video } from "@calcom/ui/components/icon";
|
||||
import { AlertCircle, MoreHorizontal, Trash, Video, Plus } from "@calcom/ui/components/icon";
|
||||
|
||||
import AppListCard from "@components/AppListCard";
|
||||
import PageWrapper from "@components/PageWrapper";
|
||||
@@ -41,6 +41,18 @@ const SkeletonLoader = ({ title, description }: { title: string; description: st
|
||||
);
|
||||
};
|
||||
|
||||
const AddConferencingButton = () => {
|
||||
const { t } = useLocale();
|
||||
|
||||
return (
|
||||
<>
|
||||
<Button color="secondary" StartIcon={Plus} href="/apps/categories/video">
|
||||
{t("add_conferencing_app")}
|
||||
</Button>
|
||||
</>
|
||||
);
|
||||
};
|
||||
|
||||
const ConferencingLayout = () => {
|
||||
const { t } = useLocale();
|
||||
const utils = trpc.useContext();
|
||||
@@ -91,7 +103,11 @@ const ConferencingLayout = () => {
|
||||
|
||||
return (
|
||||
<div className="bg-default w-full sm:mx-0 xl:mt-0">
|
||||
<Meta title={t("conferencing")} description={t("conferencing_description")} />
|
||||
<Meta
|
||||
title={t("conferencing")}
|
||||
description={t("conferencing_description")}
|
||||
CTA={<AddConferencingButton />}
|
||||
/>
|
||||
<List>
|
||||
{apps?.items &&
|
||||
apps.items
|
||||
|
||||
@@ -1,5 +1,4 @@
|
||||
import { zodResolver } from "@hookform/resolvers/zod";
|
||||
import { IdentityProvider } from "@prisma/client";
|
||||
import { signOut } from "next-auth/react";
|
||||
import type { BaseSyntheticEvent } from "react";
|
||||
import { useRef, useState } from "react";
|
||||
@@ -13,6 +12,7 @@ import { APP_NAME } from "@calcom/lib/constants";
|
||||
import { useLocale } from "@calcom/lib/hooks/useLocale";
|
||||
import { md } from "@calcom/lib/markdownIt";
|
||||
import turndown from "@calcom/lib/turndownService";
|
||||
import { IdentityProvider } from "@calcom/prisma/enums";
|
||||
import type { TRPCClientErrorLike } from "@calcom/trpc/client";
|
||||
import { trpc } from "@calcom/trpc/react";
|
||||
import type { AppRouter } from "@calcom/trpc/server/routers/_app";
|
||||
|
||||
@@ -1,10 +1,10 @@
|
||||
import { IdentityProvider } from "@prisma/client";
|
||||
import { signOut, useSession } from "next-auth/react";
|
||||
import { useForm } from "react-hook-form";
|
||||
|
||||
import { identityProviderNameMap } from "@calcom/features/auth/lib/identityProviderNameMap";
|
||||
import { getLayout } from "@calcom/features/settings/layouts/SettingsLayout";
|
||||
import { useLocale } from "@calcom/lib/hooks/useLocale";
|
||||
import { IdentityProvider } from "@calcom/prisma/enums";
|
||||
import { userMetadata } from "@calcom/prisma/zod-utils";
|
||||
import { trpc } from "@calcom/trpc/react";
|
||||
import { Alert, Button, Form, Meta, PasswordField, Select, SettingsToggle, showToast } from "@calcom/ui";
|
||||
@@ -199,6 +199,7 @@ const PasswordView = () => {
|
||||
color="primary"
|
||||
className="mt-8"
|
||||
type="submit"
|
||||
onClick={() => formMethods.clearErrors("apiError")}
|
||||
disabled={isDisabled || passwordMutation.isLoading || sessionMutation.isLoading}>
|
||||
{t("update")}
|
||||
</Button>
|
||||
|
||||
@@ -46,10 +46,10 @@ const TwoFactorAuthView = () => {
|
||||
user?.twoFactorEnabled ? setDisableModalOpen(true) : setEnableModalOpen(true)
|
||||
}
|
||||
/>
|
||||
<div>
|
||||
<div className="!mx-4">
|
||||
<div className="flex">
|
||||
<p className="text-default font-semibold">{t("two_factor_auth")}</p>
|
||||
<Badge className="ml-2 text-xs" variant={user?.twoFactorEnabled ? "success" : "gray"}>
|
||||
<Badge className="mx-2 text-xs" variant={user?.twoFactorEnabled ? "success" : "gray"}>
|
||||
{user?.twoFactorEnabled ? t("enabled") : t("disabled")}
|
||||
</Badge>
|
||||
</div>
|
||||
|
||||
@@ -263,7 +263,7 @@ export function VideoMeetingInfo(props: VideoMeetingInfo) {
|
||||
);
|
||||
}
|
||||
|
||||
VideoMeetingInfo.PageWrapper = PageWrapper;
|
||||
JoinCall.PageWrapper = PageWrapper;
|
||||
|
||||
export async function getServerSideProps(context: GetServerSidePropsContext) {
|
||||
const { req, res } = context;
|
||||
|
||||
@@ -12,6 +12,7 @@ test.describe("App Store - Authed", () => {
|
||||
await pro.login();
|
||||
await page.goto("/apps/categories/calendar");
|
||||
await page.click('[data-testid="app-store-app-card-apple-calendar"]');
|
||||
await page.waitForURL("/apps/apple-calendar");
|
||||
await page.click('[data-testid="install-app-button"]');
|
||||
await expect(page.locator(`text=Connect to Apple Server`)).toBeVisible();
|
||||
});
|
||||
|
||||
@@ -19,7 +19,10 @@ test("Can delete user account", async ({ page, users }) => {
|
||||
const $passwordField = page.locator("[data-testid=password]");
|
||||
await $passwordField.fill(user.username);
|
||||
|
||||
await Promise.all([page.waitForNavigation({ url: "/auth/logout" }), page.click("text=Delete my account")]);
|
||||
await Promise.all([
|
||||
page.waitForURL((url) => url.pathname === "/auth/logout"),
|
||||
page.click("text=Delete my account"),
|
||||
]);
|
||||
|
||||
await expect(page.locator(`[id="modal-title"]`)).toHaveText("You've been logged out");
|
||||
});
|
||||
|
||||
@@ -17,9 +17,7 @@ test("Can reset forgotten password", async ({ page, users }) => {
|
||||
|
||||
// Press Enter
|
||||
await Promise.all([
|
||||
page.waitForNavigation({
|
||||
url: (u) => u.pathname.startsWith("/auth/forgot-password/"),
|
||||
}),
|
||||
page.waitForURL((u) => u.pathname.startsWith("/auth/forgot-password/")),
|
||||
page.press('input[name="email"]', "Enter"),
|
||||
]);
|
||||
|
||||
@@ -38,7 +36,7 @@ test("Can reset forgotten password", async ({ page, users }) => {
|
||||
await expect(page.locator(`text=Password updated`)).toBeVisible();
|
||||
// Click button:has-text("Login")
|
||||
await Promise.all([
|
||||
page.waitForNavigation({ url: (u) => u.pathname.startsWith("/auth/login") }),
|
||||
page.waitForURL((u) => u.pathname.startsWith("/auth/login")),
|
||||
page.click('a:has-text("Login")'),
|
||||
]);
|
||||
|
||||
|
||||
@@ -33,7 +33,7 @@ test.describe("Availablity tests", () => {
|
||||
});
|
||||
|
||||
await test.step("Date override is displayed in troubleshooter", async () => {
|
||||
const response = await page.waitForResponse("**/api/trpc/viewer.availability.schedule.update?batch=1");
|
||||
const response = await page.waitForResponse("**/api/trpc/availability/schedule.update?batch=1");
|
||||
const json = await response.json();
|
||||
// @ts-expect-error trust me bro
|
||||
const date = json[0].result.data.json.schedule.availability.find((a) => !!a.date);
|
||||
|
||||
@@ -31,10 +31,8 @@ testBothBookers.describe("free user", (bookerVariant) => {
|
||||
// eslint-disable-next-line playwright/no-conditional-in-test
|
||||
if (bookerVariant !== "new-booker") {
|
||||
// Navigate to book page
|
||||
await page.waitForNavigation({
|
||||
url(url) {
|
||||
return url.pathname.endsWith("/book");
|
||||
},
|
||||
await page.waitForURL((url) => {
|
||||
return url.pathname.endsWith("/book");
|
||||
});
|
||||
}
|
||||
|
||||
@@ -84,23 +82,24 @@ testBothBookers.describe("pro user", () => {
|
||||
await page.waitForSelector('[data-testid="bookings"]');
|
||||
await page.locator('[data-testid="edit_booking"]').nth(0).click();
|
||||
await page.locator('[data-testid="reschedule"]').click();
|
||||
await page.waitForNavigation({
|
||||
url: (url) => {
|
||||
const bookingId = url.searchParams.get("rescheduleUid");
|
||||
return !!bookingId;
|
||||
},
|
||||
await page.waitForURL((url) => {
|
||||
const bookingId = url.searchParams.get("rescheduleUid");
|
||||
return !!bookingId;
|
||||
});
|
||||
await selectSecondAvailableTimeSlotNextMonth(page);
|
||||
// --- fill form
|
||||
await page.locator('[data-testid="confirm-reschedule-button"]').click();
|
||||
await page.waitForNavigation({
|
||||
url(url) {
|
||||
return url.pathname.startsWith("/booking");
|
||||
},
|
||||
await page.waitForURL((url) => {
|
||||
return url.pathname.startsWith("/booking");
|
||||
});
|
||||
});
|
||||
|
||||
test("Can cancel the recently created booking and rebook the same timeslot", async ({ page, users }) => {
|
||||
test("Can cancel the recently created booking and rebook the same timeslot", async ({
|
||||
page,
|
||||
users,
|
||||
}, testInfo) => {
|
||||
// Because it tests the entire booking flow + the cancellation + rebooking
|
||||
test.setTimeout(testInfo.timeout * 3);
|
||||
await bookFirstEvent(page);
|
||||
|
||||
const [pro] = users.get();
|
||||
@@ -108,10 +107,8 @@ testBothBookers.describe("pro user", () => {
|
||||
|
||||
await page.goto("/bookings/upcoming");
|
||||
await page.locator('[data-testid="cancel"]').first().click();
|
||||
await page.waitForNavigation({
|
||||
url: (url) => {
|
||||
return url.pathname.startsWith("/booking");
|
||||
},
|
||||
await page.waitForURL((url) => {
|
||||
return url.pathname.startsWith("/booking/");
|
||||
});
|
||||
await page.locator('[data-testid="cancel"]').click();
|
||||
|
||||
@@ -134,7 +131,7 @@ testBothBookers.describe("pro user", () => {
|
||||
await page.goto("/bookings/unconfirmed");
|
||||
await Promise.all([
|
||||
page.click('[data-testid="confirm"]'),
|
||||
page.waitForResponse((response) => response.url().includes("/api/trpc/viewer.bookings.confirm")),
|
||||
page.waitForResponse((response) => response.url().includes("/api/trpc/bookings/confirm")),
|
||||
]);
|
||||
// This is the only booking in there that needed confirmation and now it should be empty screen
|
||||
await expect(page.locator('[data-testid="empty-screen"]')).toBeVisible();
|
||||
|
||||
@@ -1,9 +1,9 @@
|
||||
import { expect } from "@playwright/test";
|
||||
import type { Prisma } from "@prisma/client";
|
||||
import { BookingStatus } from "@prisma/client";
|
||||
import { v4 as uuidv4 } from "uuid";
|
||||
|
||||
import prisma from "@calcom/prisma";
|
||||
import { BookingStatus } from "@calcom/prisma/enums";
|
||||
|
||||
import type { Fixtures } from "./lib/fixtures";
|
||||
import { test } from "./lib/fixtures";
|
||||
@@ -71,10 +71,8 @@ testBothBookers.describe("Booking with Seats", (bookerVariant) => {
|
||||
// to change the test as little as possible.
|
||||
// eslint-disable-next-line playwright/no-conditional-in-test
|
||||
if (bookerVariant === "old-booker") {
|
||||
await page.waitForNavigation({
|
||||
url(url) {
|
||||
return url.pathname.endsWith("/book");
|
||||
},
|
||||
await page.waitForURL((url) => {
|
||||
return url.pathname.endsWith("/book");
|
||||
});
|
||||
}
|
||||
|
||||
@@ -192,7 +190,7 @@ testBothBookers.describe("Booking with Seats", (bookerVariant) => {
|
||||
|
||||
await page.locator('[data-testid="confirm-reschedule-button"]').click();
|
||||
|
||||
await page.waitForNavigation({ url: /.*booking/ });
|
||||
await page.waitForURL(/.*booking/);
|
||||
|
||||
await page.goto(`/reschedule/${references[1].referenceUid}`);
|
||||
|
||||
@@ -200,6 +198,7 @@ testBothBookers.describe("Booking with Seats", (bookerVariant) => {
|
||||
|
||||
await page.locator('[data-testid="confirm-reschedule-button"]').click();
|
||||
|
||||
// Using waitForUrl here fails the assertion `expect(oldBooking?.status).toBe(BookingStatus.CANCELLED);` probably because waitForUrl is considered complete before waitForNavigation and till that time the booking is not cancelled
|
||||
await page.waitForNavigation({ url: /.*booking/ });
|
||||
|
||||
// Should expect old booking to be cancelled
|
||||
|
||||
@@ -31,19 +31,15 @@ test.skip("dynamic booking", async ({ page, users }) => {
|
||||
await page.goto("/bookings/upcoming");
|
||||
await page.locator('[data-testid="edit_booking"]').nth(0).click();
|
||||
await page.locator('[data-testid="reschedule"]').click();
|
||||
await page.waitForNavigation({
|
||||
url: (url) => {
|
||||
const bookingId = url.searchParams.get("rescheduleUid");
|
||||
return !!bookingId;
|
||||
},
|
||||
await page.waitForURL((url) => {
|
||||
const bookingId = url.searchParams.get("rescheduleUid");
|
||||
return !!bookingId;
|
||||
});
|
||||
await selectSecondAvailableTimeSlotNextMonth(page);
|
||||
// --- fill form
|
||||
await page.locator('[data-testid="confirm-reschedule-button"]').click();
|
||||
await page.waitForNavigation({
|
||||
url(url) {
|
||||
return url.pathname.startsWith("/booking");
|
||||
},
|
||||
await page.waitForURL((url) => {
|
||||
return url.pathname.startsWith("/booking");
|
||||
});
|
||||
await expect(page.locator("[data-testid=success-page]")).toBeVisible();
|
||||
});
|
||||
@@ -51,10 +47,8 @@ test.skip("dynamic booking", async ({ page, users }) => {
|
||||
await test.step("Can cancel the recently created booking", async () => {
|
||||
await page.goto("/bookings/upcoming");
|
||||
await page.locator('[data-testid="cancel"]').first().click();
|
||||
await page.waitForNavigation({
|
||||
url: (url) => {
|
||||
return url.pathname.startsWith("/booking");
|
||||
},
|
||||
await page.waitForURL((url) => {
|
||||
return url.pathname.startsWith("/booking");
|
||||
});
|
||||
await page.locator('[data-testid="cancel"]').click();
|
||||
|
||||
|
||||
@@ -36,14 +36,12 @@ async function expectToBeNavigatingToEmbedTypesDialog(
|
||||
if (!embedUrl) {
|
||||
throw new Error("Couldn't find embedUrl");
|
||||
}
|
||||
await page.waitForNavigation({
|
||||
url: (url) => {
|
||||
return (
|
||||
url.pathname === basePage &&
|
||||
url.searchParams.get("dialog") === "embed" &&
|
||||
url.searchParams.get("embedUrl") === embedUrl
|
||||
);
|
||||
},
|
||||
await page.waitForURL((url) => {
|
||||
return (
|
||||
url.pathname === basePage &&
|
||||
url.searchParams.get("dialog") === "embed" &&
|
||||
url.searchParams.get("embedUrl") === embedUrl
|
||||
);
|
||||
});
|
||||
}
|
||||
|
||||
@@ -54,16 +52,14 @@ async function expectToBeNavigatingToEmbedCodeAndPreviewDialog(
|
||||
if (!embedUrl) {
|
||||
throw new Error("Couldn't find embedUrl");
|
||||
}
|
||||
await page.waitForNavigation({
|
||||
url: (url) => {
|
||||
return (
|
||||
url.pathname === basePage &&
|
||||
url.searchParams.get("dialog") === "embed" &&
|
||||
url.searchParams.get("embedUrl") === embedUrl &&
|
||||
url.searchParams.get("embedType") === embedType &&
|
||||
url.searchParams.get("embedTabName") === "embed-code"
|
||||
);
|
||||
},
|
||||
await page.waitForURL((url) => {
|
||||
return (
|
||||
url.pathname === basePage &&
|
||||
url.searchParams.get("dialog") === "embed" &&
|
||||
url.searchParams.get("embedUrl") === embedUrl &&
|
||||
url.searchParams.get("embedType") === embedType &&
|
||||
url.searchParams.get("embedTabName") === "embed-code"
|
||||
);
|
||||
});
|
||||
}
|
||||
|
||||
@@ -183,9 +179,7 @@ test.describe("Embed Code Generator Tests", () => {
|
||||
await page.goto(`/event-types`);
|
||||
await Promise.all([
|
||||
page.locator('[href*="/event-types/"]').first().click(),
|
||||
page.waitForNavigation({
|
||||
url: (url) => url.pathname.startsWith("/event-types/"),
|
||||
}),
|
||||
page.waitForURL((url) => url.pathname.startsWith("/event-types/")),
|
||||
]);
|
||||
});
|
||||
|
||||
|
||||
@@ -94,10 +94,8 @@ test.describe("Event Types tests", () => {
|
||||
const $eventTypes = page.locator("[data-testid=event-types] > li a");
|
||||
const firstEventTypeElement = $eventTypes.first();
|
||||
await firstEventTypeElement.click();
|
||||
await page.waitForNavigation({
|
||||
url: (url) => {
|
||||
return !!url.pathname.match(/\/event-types\/.+/);
|
||||
},
|
||||
await page.waitForURL((url) => {
|
||||
return !!url.pathname.match(/\/event-types\/.+/);
|
||||
});
|
||||
await page.locator("[data-testid=update-eventtype]").click();
|
||||
const toast = await page.waitForSelector("div[class*='data-testid-toast-success']");
|
||||
@@ -108,10 +106,8 @@ test.describe("Event Types tests", () => {
|
||||
const $eventTypes = page.locator("[data-testid=event-types] > li a");
|
||||
const firstEventTypeElement = $eventTypes.first();
|
||||
await firstEventTypeElement.click();
|
||||
await page.waitForNavigation({
|
||||
url: (url) => {
|
||||
return !!url.pathname.match(/\/event-types\/.+/);
|
||||
},
|
||||
await page.waitForURL((url) => {
|
||||
return !!url.pathname.match(/\/event-types\/.+/);
|
||||
});
|
||||
|
||||
const locationData = ["location 1", "location 2", "location 3"];
|
||||
@@ -153,10 +149,8 @@ test.describe("Event Types tests", () => {
|
||||
// to change the test as little as possible.
|
||||
// eslint-disable-next-line playwright/no-conditional-in-test
|
||||
if (bookerVariant === "old-booker") {
|
||||
await page.waitForNavigation({
|
||||
url(url) {
|
||||
return url.pathname.endsWith("/book");
|
||||
},
|
||||
await page.waitForURL((url) => {
|
||||
return url.pathname.endsWith("/book");
|
||||
});
|
||||
}
|
||||
|
||||
|
||||
@@ -1,11 +1,12 @@
|
||||
import type { Page, WorkerInfo } from "@playwright/test";
|
||||
import type Prisma from "@prisma/client";
|
||||
import { MembershipRole, Prisma as PrismaType } from "@prisma/client";
|
||||
import { Prisma as PrismaType } from "@prisma/client";
|
||||
import { hashSync as hash } from "bcryptjs";
|
||||
|
||||
import dayjs from "@calcom/dayjs";
|
||||
import { DEFAULT_SCHEDULE, getAvailabilityFromSchedule } from "@calcom/lib/availability";
|
||||
import { prisma } from "@calcom/prisma";
|
||||
import { MembershipRole } from "@calcom/prisma/enums";
|
||||
|
||||
import type { TimeZoneEnum } from "./types";
|
||||
|
||||
@@ -345,12 +346,12 @@ export async function getPaymentCredential(page: Page) {
|
||||
|
||||
/** We start the Stripe flow */
|
||||
await Promise.all([
|
||||
page.waitForNavigation({ url: "https://connect.stripe.com/oauth/v2/authorize?*" }),
|
||||
page.waitForURL("https://connect.stripe.com/oauth/v2/authorize?*"),
|
||||
page.click('[data-testid="install-app-button"]'),
|
||||
]);
|
||||
|
||||
await Promise.all([
|
||||
page.waitForNavigation({ url: "/apps/installed/payment?hl=stripe" }),
|
||||
page.waitForURL("/apps/installed/payment?hl=stripe"),
|
||||
/** We skip filling Stripe forms (testing mode only) */
|
||||
page.click('[id="skip-account-app"]'),
|
||||
]);
|
||||
|
||||
@@ -49,7 +49,7 @@ test.describe("Stripe integration", () => {
|
||||
await page.fill('[name="name"]', "Stripe Stripeson");
|
||||
await page.fill('[name="email"]', "test@example.com");
|
||||
|
||||
await Promise.all([page.waitForNavigation({ url: "/payment/*" }), page.press('[name="email"]', "Enter")]);
|
||||
await Promise.all([page.waitForURL("/payment/*"), page.press('[name="email"]', "Enter")]);
|
||||
|
||||
const stripeFrame = page
|
||||
.frameLocator('iframe[src^="https://js.stripe.com/v3/elements-inner-card-"]')
|
||||
|
||||
@@ -114,10 +114,8 @@ async function bookEventOnThisPage(page: Page) {
|
||||
await bookTimeSlot(page);
|
||||
|
||||
// Make sure we're navigated to the success page
|
||||
await page.waitForNavigation({
|
||||
url(url) {
|
||||
return url.pathname.startsWith("/booking");
|
||||
},
|
||||
await page.waitForURL((url) => {
|
||||
return url.pathname.startsWith("/booking");
|
||||
});
|
||||
await expect(page.locator("[data-testid=success-page]")).toBeVisible();
|
||||
}
|
||||
@@ -156,10 +154,8 @@ export const createNewEventType = async (page: Page, args: { eventTitle: string
|
||||
await page.fill("[name=length]", "10");
|
||||
await page.click("[type=submit]");
|
||||
|
||||
await page.waitForNavigation({
|
||||
url(url) {
|
||||
return url.pathname !== "/event-types";
|
||||
},
|
||||
await page.waitForURL((url) => {
|
||||
return url.pathname !== "/event-types";
|
||||
});
|
||||
};
|
||||
|
||||
|
||||
@@ -1,10 +1,10 @@
|
||||
import type { Locator, Page, PlaywrightTestArgs } from "@playwright/test";
|
||||
import { expect } from "@playwright/test";
|
||||
import { WebhookTriggerEvents } from "@prisma/client";
|
||||
import type { createUsersFixture } from "playwright/fixtures/users";
|
||||
import { uuid } from "short-uuid";
|
||||
|
||||
import prisma from "@calcom/prisma";
|
||||
import { WebhookTriggerEvents } from "@calcom/prisma/enums";
|
||||
|
||||
import { test } from "./lib/fixtures";
|
||||
import { testBothBookers } from "./lib/new-booker";
|
||||
@@ -362,8 +362,8 @@ async function rescheduleFromTheLinkOnPage({
|
||||
await page.waitForLoadState();
|
||||
await selectFirstAvailableTimeSlotNextMonth(page);
|
||||
if (bookerVariant === "old-booker") {
|
||||
await page.waitForNavigation({
|
||||
url: (url) => url.pathname.endsWith("/book"),
|
||||
await page.waitForURL((url) => {
|
||||
return url.pathname.endsWith("/book");
|
||||
});
|
||||
}
|
||||
await page.click('[data-testid="confirm-reschedule-button"]');
|
||||
@@ -380,8 +380,8 @@ async function openBookingFormInPreviewTab(
|
||||
await previewTabPage.waitForLoadState();
|
||||
await selectFirstAvailableTimeSlotNextMonth(previewTabPage);
|
||||
if (bookerVariant === "old-booker") {
|
||||
await previewTabPage.waitForNavigation({
|
||||
url: (url) => url.pathname.endsWith("/book"),
|
||||
await previewTabPage.waitForURL((url) => {
|
||||
return url.pathname.endsWith("/book");
|
||||
});
|
||||
}
|
||||
return previewTabPage;
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
import { expect } from "@playwright/test";
|
||||
import { BookingStatus } from "@prisma/client";
|
||||
|
||||
import prisma from "@calcom/prisma";
|
||||
import { BookingStatus } from "@calcom/prisma/enums";
|
||||
|
||||
import { test } from "./lib/fixtures";
|
||||
import { testBothBookers } from "./lib/new-booker";
|
||||
@@ -149,10 +149,8 @@ testBothBookers.describe("Reschedule Tests", async () => {
|
||||
|
||||
await page.locator('[data-testid="confirm-reschedule-button"]').click();
|
||||
|
||||
await page.waitForNavigation({
|
||||
url(url) {
|
||||
return url.pathname.indexOf("/payment") > -1;
|
||||
},
|
||||
await page.waitForURL((url) => {
|
||||
return url.pathname.indexOf("/payment") > -1;
|
||||
});
|
||||
|
||||
await expect(page).toHaveURL(/.*payment/);
|
||||
|
||||
@@ -24,9 +24,7 @@ test("add webhook & test that creating an event triggers a webhook call", async
|
||||
|
||||
await Promise.all([
|
||||
page.click("[type=submit]"),
|
||||
page.waitForNavigation({
|
||||
url: (url) => url.pathname.endsWith("/settings/developer/webhooks"),
|
||||
}),
|
||||
page.waitForURL((url) => url.pathname.endsWith("/settings/developer/webhooks")),
|
||||
]);
|
||||
|
||||
// page contains the url
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
{
|
||||
"identity_provider": "موفر الهوية",
|
||||
"trial_days_left": "يتبقى لديك $t(day, {\"count\": {{days}} }) في الفترة التجريبية من PRO",
|
||||
"day_one": "{{count}} يوم",
|
||||
"day_other": "{{count}} يوم",
|
||||
@@ -70,6 +71,7 @@
|
||||
"event_still_awaiting_approval": "الحدث لا يزال في انتظار موافقتك",
|
||||
"booking_submitted_subject": "تم إرسال الحجز: {{title}} في {{date}}",
|
||||
"download_recording_subject": "تنزيل التسجيل: {{title}} في {{date}}",
|
||||
"download_your_recording": "تنزيل تسجيلك",
|
||||
"your_meeting_has_been_booked": "لقد تم حجز الاجتماع الخاص بك",
|
||||
"event_type_has_been_rescheduled_on_time_date": "تم إعادة جدولة {{title}} إلى {{date}}.",
|
||||
"event_has_been_rescheduled": "تم التحديث - تم إعادة جدولة الحدث الخاص بك",
|
||||
@@ -112,7 +114,7 @@
|
||||
"hidden_team_owner_message": "تحتاج إلى حساب Pro لاستخدام خدمة الفرق، وأنت حاليًا في الوضع الخفي حتى تقوم بالترقية.",
|
||||
"link_expires": "ملاحظة: تنتهي الصلاحية في غضون {{expiresIn}} من الساعات.",
|
||||
"upgrade_to_per_seat": "الترقية إلى \"كل مقعد بشكل منفرد\"",
|
||||
"seat_options_doesnt_support_confirmation": "لا يدعم خيار المقاعد متطلبات التأكيد",
|
||||
"seat_options_doesnt_support_confirmation": "الحجز الذي يتطلب التأكيد لا يدعم خيار حجز المقاعد",
|
||||
"team_upgrade_seats_details": "من بين {{memberCount}} من الأعضاء في فريقك، لم يتم الدفع مقابل {{unpaidCount}} من المقاعد. بما أن سعر المقعد ${{seatPrice}} شهريًا، تبلغ التكلفة الإجمالية المقدرة لعضويتك ${{totalCost}} شهريًا.",
|
||||
"team_upgrade_banner_description": "شكراً لك على تجربة خطة الفريق الجديدة التي نقدمها. لقد لاحظنا أن فريقك \"{{teamName}}\" بحاجة إلى الترقية.",
|
||||
"team_upgrade_banner_action": "قم بالترقية هنا",
|
||||
@@ -225,6 +227,7 @@
|
||||
"done": "تم",
|
||||
"all_done": "اكتمل كل شيء!",
|
||||
"all_apps": "الكل",
|
||||
"available_apps": "التطبيقات المتوفرة",
|
||||
"check_email_reset_password": "تحقق من البريد الإلكتروني. لقد أرسلنا رابطًا لإعادة تعيين كلمة المرور.",
|
||||
"finish": "إنهاء",
|
||||
"few_sentences_about_yourself": "اكتب بضع جمل عن نفسك. سيظهر هذا على صفحة عنوان URL الشخصية لديك.",
|
||||
@@ -405,6 +408,7 @@
|
||||
"add_time_availability": "إضافة فتحة زمنية جديدة",
|
||||
"add_an_extra_layer_of_security": "أضف إلى حسابك طبقة أمان إضافية للاحتياط من سرقة كلمة المرور الخاصة بك.",
|
||||
"2fa": "المصادقة من عاملين",
|
||||
"2fa_disabled": "يمكن تمكين المصادقة الثنائية فقط لمصادقة البريد الإلكتروني وكلمة المرور",
|
||||
"enable_2fa": "تمكين المصادقة من عاملين",
|
||||
"disable_2fa": "تعطيل المصادقة من عاملين",
|
||||
"disable_2fa_recommendation": "إذا كنت بحاجة إلى تعطيل المصادقة من عاملين، فنوصيك بإعادة تمكينها في أقرب وقت ممكن.",
|
||||
@@ -434,7 +438,7 @@
|
||||
"password_hint_min": "7 أحرف كحد أدنى",
|
||||
"password_hint_admin_min": "15 محرفًا كحد أدنى",
|
||||
"password_hint_num": "يحتوي على رقم واحد على الأقل",
|
||||
"max_limit_allowed_hint": "يجب أن يكون بعدد محارف هو {{limit}} أو أقل",
|
||||
"max_limit_allowed_hint": "يجب أن يكون عدد الأحرف {{limit}} أو أقل",
|
||||
"invalid_password_hint": "يجب أن تتألف كلمة المرور على الأقل من {{passwordLength}} أحرف، ورقم واحد على الأقل، ومزيج من الحروف الكبيرة والصغيرة",
|
||||
"incorrect_password": "كلمة المرور غير صحيحة.",
|
||||
"incorrect_username_password": "اسم المستخدم أو كلمة المرور غير صحيحة.",
|
||||
@@ -461,7 +465,6 @@
|
||||
"friday": "الجمعة",
|
||||
"saturday": "السبت",
|
||||
"sunday": "الأحد",
|
||||
"all_booked_today": "تم حجز الكل اليوم.",
|
||||
"slots_load_fail": "تعذر تحميل الفترات الزمنية المتاحة.",
|
||||
"additional_guests": "إضافة ضيوف",
|
||||
"your_name": "اسمك",
|
||||
@@ -588,9 +591,18 @@
|
||||
"round_robin_description": "نقل الاجتماعات بشكل دوري بين أعضاء الفريق المتعددين.",
|
||||
"managed_event": "الحدث المدار",
|
||||
"username_placeholder": "اسم المستخدم",
|
||||
"managed_event_description": "إنشاء وتوزيع أنواع الأحداث بكميات كبيرة على أعضاء الفريق",
|
||||
"managed": "مدارة",
|
||||
"managed_event_url_clarification": "سيتم ملء \"اسم المستخدم\" باسم المستخدم للأعضاء المعينين",
|
||||
"assign_to": "تعيين إلى",
|
||||
"add_members": "إضافة أعضاء...",
|
||||
"count_members_one": "{{count}} عضو",
|
||||
"count_members_other": "{{count}} أعضاء",
|
||||
"no_assigned_members": "لا يوجد أعضاء معينين",
|
||||
"assigned_to": "تم تعيينه إلى",
|
||||
"start_assigning_members_above": "بدء تعيين الأعضاء أعلاه",
|
||||
"locked_fields_admin_description": "لن يتمكن الأعضاء من تعديل هذا",
|
||||
"locked_fields_member_description": "قام مشرف الفريق بقفل هذا الخيار",
|
||||
"url": "URL",
|
||||
"hidden": "مخفي",
|
||||
"readonly": "للقراءة فقط",
|
||||
@@ -742,6 +754,11 @@
|
||||
"minimum_booking_notice": "الحد الأدنى من الوقت للحجز",
|
||||
"slot_interval": "الفترات الزمنية بين عمليات الحجز",
|
||||
"slot_interval_default": "استخدام طول الحدث (الوضع الافتراضي)",
|
||||
"delete_event_type": "حذف نوع الحدث؟",
|
||||
"delete_managed_event_type": "حذف نوع الحدث المُدار؟",
|
||||
"delete_event_type_description": "لن يكون بإمكان أي شخص قمت بمشاركة الرابط معه الوصول إلى الرابط.",
|
||||
"delete_managed_event_type_description": "<ul><li>سيتم أيضًا حذف أنواع الأحداث الخاصة بالأعضاء المعينين في هذا النوع من الأحداث.</li><li>لن يتمكن أي شخص شاركوا رابطهم معه من الحجز باستخدامه.</li></ul>",
|
||||
"confirm_delete_event_type": "نعم، قم بالحذف",
|
||||
"delete_account": "حذف الحساب",
|
||||
"confirm_delete_account": "نعم، احذف الحساب",
|
||||
"delete_account_confirmation_message": "هل تريد بالتأكيد حذف حسابك على {{appName}}؟ أي شخص شاركت رابط حسابك معه لن يستطيع بعد الآن الحجز باستخدامه، وستفقد أي تفضيلات حفظتها.",
|
||||
@@ -866,7 +883,7 @@
|
||||
"add_new_calendar": "إضافة تقويم جديد",
|
||||
"set_calendar": "حدد أين سيتم إضافة الأحداث الجديدة عند الحجز.",
|
||||
"delete_schedule": "حذف الجدول",
|
||||
"delete_schedule_description": "سيؤدي حذف الجدول الزمني إلى إزالته من جميع أنواع الفعاليات، ولا يمكن التراجع عن هذا الإجراء.",
|
||||
"delete_schedule_description": "سيؤدي حذف الجدول الزمني إلى إزالته من جميع أنواع الأحداث، ولا يمكن التراجع عن هذا الإجراء.",
|
||||
"schedule_created_successfully": "تم إنشاء جدول {{scheduleName}} بنجاح",
|
||||
"availability_updated_successfully": "تم تحديث جدول {{scheduleName}} بنجاح",
|
||||
"schedule_deleted_successfully": "تم حذف الجدول بنجاح",
|
||||
@@ -1021,7 +1038,6 @@
|
||||
"event_cancelled_trigger": "متى تم إلغاء هذا الحدث",
|
||||
"new_event_trigger": "متى تم حجز الحدث الجديد",
|
||||
"email_host_action": "إرسال رسالة إلكترونية إلى المضيف",
|
||||
"email_attendee_action": "إرسال رسالة إلكترونية إلى واحد من الحضور",
|
||||
"sms_attendee_action": "إرسال رسالة نصية قصيرة إلى واحد من الحضور",
|
||||
"sms_number_action": "إرسال رسالة نصية قصيرة إلى رقم محدد",
|
||||
"workflows": "سير العمل",
|
||||
@@ -1138,6 +1154,7 @@
|
||||
"variable_format": "تنسيق المتغير",
|
||||
"webhook_subscriber_url_reserved": "رابط مشترك Webhook معرّف بالفعل",
|
||||
"custom_input_as_variable_info": "تجاهل جميع الأحرف الخاصة من تسمية الإدخال الإضافي (استخدم الأحرف والأرقام فقط)، واستخدم الحروف الكبيرة لجميع الأحرف واستبدل المساحات البيضاء بالشرط.",
|
||||
"using_booking_questions_as_variables": "كيف يمكنني استخدام أسئلة الحجز كمتغيرات؟",
|
||||
"download_desktop_app": "تحميل تطبيق سطح المكتب",
|
||||
"set_ping_link": "تعيين رابط Ping",
|
||||
"rate_limit_exceeded": "تم تجاوز حد السعر",
|
||||
@@ -1161,7 +1178,7 @@
|
||||
"event_setup_multiple_duration_error": "إعداد الحدث: تستلزم فترات متعددة خيارًا واحدًا على الأقل.",
|
||||
"event_setup_multiple_duration_default_error": "إعداد الحدث: الرجاء تحديد مدة افتراضية صالحة.",
|
||||
"event_setup_booking_limits_error": "يجب أن تكون حدود الحجز بترتيب تصاعدي. [يوم،أسبوع،شهر،سنة]",
|
||||
"event_setup_duration_limits_error": "يجب أن تكون حدود الحجز بترتيب تصاعدي. [يوم، أسبوع، شهر، سنة]",
|
||||
"event_setup_duration_limits_error": "يجب أن تكون حدود المدة بترتيب تصاعدي. [يوم، أسبوع، شهر، سنة]",
|
||||
"select_which_cal": "اختر أي رزنامة تريد إضافة الحجز إليها",
|
||||
"custom_event_name": "اسم حدث مخصص",
|
||||
"custom_event_name_description": "إنشاء أسماء أحداث مخصصة لعرضها في حدث الرزنامة",
|
||||
@@ -1183,6 +1200,7 @@
|
||||
"start_of_week": "بداية الأسبوع",
|
||||
"recordings_title": "التسجيلات",
|
||||
"recording": "التسجيل",
|
||||
"happy_scheduling": "جدولة سعيدة",
|
||||
"select_calendars": "حدد الرزنامات التي تريد التحقق منها بحثًا عن تضاربات لمنع الحجوزات المزدوجة.",
|
||||
"check_for_conflicts": "التحقق من وجود تضاربات",
|
||||
"view_recordings": "عرض التسجيلات",
|
||||
@@ -1220,6 +1238,7 @@
|
||||
"impersonation": "انتحال الشخصية",
|
||||
"impersonation_description": "إعدادات إدارة انتحال شخصية مستخدم",
|
||||
"users": "المستخدمون",
|
||||
"user": "المستخدم",
|
||||
"profile_description": "إدارة الإعدادات لملفك الشخصي على {{appName}}",
|
||||
"users_description": "هنا يمكنك العثور على قائمة بجميع المستخدمين",
|
||||
"users_listing": "قائمة المستخدمين",
|
||||
@@ -1260,6 +1279,8 @@
|
||||
"download_responses": "تنزيل الردود",
|
||||
"download_responses_description": "تنزيل جميع الردود إلى النموذج بتنسيق CSV.",
|
||||
"download": "التنزيل",
|
||||
"download_recording": "تنزيل التسجيل",
|
||||
"recording_from_your_recent_call": "تسجيل من مكالمتك الأخيرة على Cal.com جاهز للتنزيل",
|
||||
"create_your_first_form": "إنشاء أول استمارة",
|
||||
"create_your_first_form_description": "باستخدام نماذج المسارات، يمكنك طرح أسئلة التأهيل والتوجيه إلى الشخص/نوع الحدث المناسب.",
|
||||
"create_your_first_webhook": "إنشاء أول Webhook",
|
||||
@@ -1297,6 +1318,14 @@
|
||||
"exchange_authentication_ntlm": "مصادقة NTLM",
|
||||
"exchange_compression": "ضغط Gzip",
|
||||
"exchange_version": "نسخة التبادل",
|
||||
"exchange_version_2007_SP1": "2007 SP1",
|
||||
"exchange_version_2010": "2010",
|
||||
"exchange_version_2010_SP1": "2010 SP1",
|
||||
"exchange_version_2010_SP2": "2010 SP2",
|
||||
"exchange_version_2013": "2013",
|
||||
"exchange_version_2013_SP1": "2013 SP1",
|
||||
"exchange_version_2015": "2015",
|
||||
"exchange_version_2016": "2016",
|
||||
"routing_forms_description": "يمكنك رؤية جميع النماذج والتوجيهات التي أنشأتها هنا.",
|
||||
"routing_forms_send_email_owner": "إرسال رسالة إلكترونية إلى المالك",
|
||||
"routing_forms_send_email_owner_description": "يرسل رسالة بريد إلكتروني إلى المالك عند إرسال النموذج",
|
||||
@@ -1347,10 +1376,11 @@
|
||||
"limit_booking_frequency": "الحد من تواتر الحجز",
|
||||
"limit_booking_frequency_description": "الحد من عدد مرات حجز هذا الحدث",
|
||||
"limit_total_booking_duration": "الحد من مدة الحجز الإجمالية",
|
||||
"limit_total_booking_duration_description": "الحد من الوقت المخصص الذي يمكن حجزه لهذه الفعالية",
|
||||
"limit_total_booking_duration_description": "الحد من إجمالي الوقت المخصص الذي يمكن حجزه لهذا الحدث",
|
||||
"add_limit": "إضافة حد",
|
||||
"team_name_required": "اسم الفريق مطلوب",
|
||||
"show_attendees": "مشاركة معلومات الحضور مع الضيوف",
|
||||
"how_booking_questions_as_variables": "كيف يمكن استخدام أسئلة الحجز كمتغيرات؟",
|
||||
"format": "التنسيق",
|
||||
"uppercase_for_letters": "استخدم الحروف الكبيرة لجميع الأحرف",
|
||||
"replace_whitespaces_underscores": "استبدل المساحات البيضاء بالشرطات السفلية",
|
||||
@@ -1365,6 +1395,7 @@
|
||||
"billing_help_title": "هل تحتاج إلى أي شيء آخر؟",
|
||||
"billing_help_description": "إذا كنت تحتاج إلى أي مساعدة إضافية بخصوص الفوترة، فإن فريق الدعم لدينا في انتظارك لتقديم المساعدة.",
|
||||
"billing_help_cta": "الاتصال بالدعم",
|
||||
"ignore_special_characters_booking_questions": "تجاهل المحارف الخاصة في معرّف سؤال الحجز لديك. استخدم فقط الأحرف والأرقام",
|
||||
"retry": "إعادة المحاولة",
|
||||
"fetching_calendars_error": "حدثت مشكلة أثناء جلب التقويمات الخاصة بك. يرجى <1>إعادة المحاولة</1> أو الاتصال بدعم العملاء.",
|
||||
"calendar_connection_fail": "فشل الاتصال بالتقويم",
|
||||
@@ -1447,12 +1478,17 @@
|
||||
"fixed_round_robin": "جدولة دوارة",
|
||||
"add_one_fixed_attendee": "أضف حاضرًا ثابتًا وجدولة دوارة عبر عدد من الحاضرين.",
|
||||
"calcom_is_better_with_team": "يعمل Cal.com أفضل مع الفرق",
|
||||
"the_calcom_team": "فريق Cal.com",
|
||||
"add_your_team_members": "أضف أعضاء فريقك إلى أنواع الأحداث الخاصة بك. استخدم الجدولة الجماعية لإدراج الجميع أو العثور على أنسب شخص باستخدام الجدولة الدوارة.",
|
||||
"booking_limit_reached": "تم الوصول إلى الحد الأقصى للحجز لهذا النوع من الأحداث",
|
||||
"duration_limit_reached": "تم الوصول إلى الحد الأقصى للحجز لهذا النوع من الفعاليات",
|
||||
"duration_limit_reached": "تم الوصول إلى الحد الأقصى لمدة الحجز لهذا النوع من الأحداث",
|
||||
"admin_has_disabled": "قام مشرفًا بتعطيل {{appName}}",
|
||||
"disabled_app_affects_event_type": "قام مشرفًا بتعطيل {{appName}} مما يؤثر على نوع الحدث {{eventType}} لديك",
|
||||
"event_replaced_notice": "استبدل المشرف أحد أنواع الأحداث لديك",
|
||||
"email_subject_slug_replacement": "استبدل مسؤول فريق الحدث لديك /{{slug}}",
|
||||
"email_body_slug_replacement_notice": "استبدل مسؤول في فريق <strong>{{teamName}}</strong> نوع الحدث لديك<strong>/{{slug}}</strong> بنوع الحدث المُدار الذي يتحكم فيه.",
|
||||
"email_body_slug_replacement_info": "سيستمر رابطك في العمل، ولكن بعض إعداداته قد تغيرت. يمكنك مراجعتها في أنواع الأحداث.",
|
||||
"email_body_slug_replacement_suggestion": "يرجى الاتصال بالمسؤول إذا كان لديك أي أسئلة حول نوع الحدث.<br /><br />جدولة سعيدة، <br />فريق Cal.com",
|
||||
"disable_payment_app": "قام المشرف بتعطيل {{appName}} الذي يؤثر على نوع الحدث {{title}} لديك. لا يزال بإمكان الحاضرين حجز هذا النوع من الأحداث، ولكن لن يُطالبوا بالدفع. يمكنك إخفاء نوع الحدث لمنع حدوث هذا إلى أن يقوم المشرف بتغيير طريقة الدفع لك.",
|
||||
"payment_disabled_still_able_to_book": "لا يزال بإمكان الحاضرين حجز هذا النوع من الأحداث، ولكن لن يُطالبوا بالدفع. يمكنك إخفاء نوع الحدث لمنع حدوث هذا إلى أن يقوم المشرف بتغيير طريقة الدفع لك.",
|
||||
"app_disabled_with_event_type": "عطّل المشرف {{appName}} مما يؤثر على نوع الحدث {{title}} لديك.",
|
||||
@@ -1501,6 +1537,7 @@
|
||||
"date_overrides_update_btn": "تحديث التجاوز",
|
||||
"event_type_duplicate_copy_text": "{{slug}}-نسخ",
|
||||
"set_as_default": "تعيين كافتراضي",
|
||||
"hide_eventtype_details": "إخفاء تفاصيل نوع الحدث",
|
||||
"show_navigation": "إظهار التنقل",
|
||||
"hide_navigation": "إخفاء التنقل",
|
||||
"verification_code_sent": "تم إرسال رمز التحقق",
|
||||
@@ -1514,6 +1551,7 @@
|
||||
"create_your_first_team_webhook_description": "قم بإنشاء أول شبكة ويب هوك لهذا النوع من فعاليات الفريق",
|
||||
"create_webhook_team_event_type": "قم بإنشاء ويب هوك لهذا النوع من فعاليات الفريق",
|
||||
"disable_success_page": "تعطيل صفحة النجاح (يعمل فقط إذا كان لديك رابط إعادة توجيه)",
|
||||
"invalid_admin_password": "تم تعيينك كمشرف ولكن ليس لديك كلمة مرور بطول 15 مِحرَفاً على الأقل",
|
||||
"change_password_admin": "قم بتغيير كلمة المرور للحصول على وصول المشرف",
|
||||
"username_already_taken": "اسم المستخدم هذا مستخدم مسبقًا",
|
||||
"assignment": "تعيين",
|
||||
@@ -1592,27 +1630,28 @@
|
||||
"available_variables": "المتغيرات المتوفرة",
|
||||
"scheduler": "{Scheduler}",
|
||||
"no_workflows": "لا يوجد أي سير عمل",
|
||||
"change_filter": "قم بتغيير عامل الفرز لمشاهدة سير عملك وفريقك.",
|
||||
"change_filter": "قم بتغيير عامل التصفية لمشاهدة سير العمل الشخصي وفريقك.",
|
||||
"recommended_next_steps": "الخطوات التالية الموصى بها",
|
||||
"create_a_managed_event": "إنشاء نوع من الأحداث المُدارة",
|
||||
"meetings_are_better_with_the_right": "الاجتماعات أفضل مع وجود أعضاء الفريق المناسبين. قم بدعوتهم الآن.",
|
||||
"create_a_one_one_template": "إنشاء قالب فردي واحد لنوع الفعالية وتوزيعه على عدة أعضاء.",
|
||||
"create_a_one_one_template": "إنشاء قالب فردي واحد لنوع الحدث وتوزيعه على عدة أعضاء.",
|
||||
"collective_or_roundrobin": "جماعية أو دوارة",
|
||||
"book_your_team_members": "احجز أعضاء فريقك والأحداث الجماعية أو تمكن من الوصول إلى الشخص المناسب باستخدام الجدولة الدوارة.",
|
||||
"event_no_longer_attending_subject": "لن يتم حضور {{title}} في {{date}}",
|
||||
"no_longer_attending": "لن تحضر هذا الحدث بعد الآن",
|
||||
"no_longer_attending": "لن تحضر هذا الحدث",
|
||||
"attendee_no_longer_attending_subject": "أحد الحضور لن يحضر {{title}} في {{date}}",
|
||||
"attendee_no_longer_attending": "أحد الحضور لن يحضر فعاليتك بعد الآن",
|
||||
"attendee_no_longer_attending_subtitle": "تم إلغاء الفعالية {{name}}، وهو ما يعني توافر معقد شاغر في هذا التوقيت",
|
||||
"create_event_on": "إنشاء أحداث في",
|
||||
"attendee_no_longer_attending": "أحد الحضور لن يحضر الحدث الخاص بك",
|
||||
"attendee_no_longer_attending_subtitle": "تم إلغاء الحدث {{name}}، وهو ما يعني توافر معقد شاغر في هذا التوقيت",
|
||||
"create_event_on": "إنشاء حدث في",
|
||||
"default_app_link_title": "تعيين رابط تطبيق افتراضي",
|
||||
"default_app_link_description": "يسمح تعيين رابط التطبيق الافتراضي لجميع أنواع الأحداث التي تم إنشاؤها حديثًا باستخدام رابط التطبيق الذي عينته.",
|
||||
"change_default_conferencing_app": "تعيين كافتراضي",
|
||||
"organizer_default_conferencing_app": "تطبيق المنظمة الافتراضي",
|
||||
"under_maintenance": "معطل للصيانة",
|
||||
"under_maintenance_description": "يجري فريق {{appName}} صيانة مجدولة. إذا كان لديك أي أسئلة، يرجى الاتصال بالدعم.",
|
||||
"event_type_seats": "{{numberOfSeats}} من المقاعد",
|
||||
"booking_questions_title": "أسئلة حول الحجز",
|
||||
"booking_questions_description": "اعمل على تخصيص الأسئلة المطروحة في صفحة الحجز",
|
||||
"booking_questions_description": "يمكنك تخصيص الأسئلة المطروحة في صفحة الحجز",
|
||||
"add_a_booking_question": "اطرح سؤالاً",
|
||||
"duplicate_email": "البريد الإلكتروني مكرر",
|
||||
"booking_with_payment_cancelled": "لم يعد الدفع لهذا الحدث ممكنًا",
|
||||
@@ -1620,20 +1659,21 @@
|
||||
"booking_with_payment_cancelled_refunded": "تم استرداد مبلغ هذا الحجز.",
|
||||
"booking_confirmation_failed": "فشل تأكيد الحجز",
|
||||
"not_enough_seats": "لا توجد مقاعد كافية",
|
||||
"form_builder_field_already_exists": "ثمة حقل بهذا الاسم موجود بالفعل",
|
||||
"form_builder_field_add_subtitle": "اعمل على تخصيص الأسئلة المطروحة في صفحة الحجز",
|
||||
"form_builder_field_already_exists": "يوجد حقل بهذا الاسم بالفعل",
|
||||
"form_builder_field_add_subtitle": "يمكنك تخصيص الأسئلة المطروحة في صفحة الحجز",
|
||||
"show_on_booking_page": "إظهار في صفحة الحجز",
|
||||
"get_started_zapier_templates": "البدء في استخدام قوالب Zapier",
|
||||
"team_is_unpublished": "لم يُنشر {{team}}",
|
||||
"team_is_unpublished_description": "رابط هذا الفريق غير متوفر حالياً. يرجى الاتصال بمالك الفريق أو الطلب منهم نشره.",
|
||||
"team_is_unpublished_description": "رابط هذا الفريق غير متاح حاليًا. يرجى الاتصال بمالك الفريق أو طلب نشره منه.",
|
||||
"team_member": "عضو الفريق",
|
||||
"a_routing_form": "نموذج توجيه",
|
||||
"form_description_placeholder": "وصف النموذج",
|
||||
"keep_me_connected_with_form": "ابقني متصلاً بالنموذج",
|
||||
"fields_in_form_duplicated": "أي تغييرات في جهاز التوجيه والحقول الخاصة بالنموذج المكرر، ستنعكس في النسخة المكررة.",
|
||||
"form_deleted": "تم حذف النموذج",
|
||||
"delete_form": "حذف النموذج",
|
||||
"delete_form": "هل أنت متأكد من أنك تريد حذف هذا القالب؟",
|
||||
"delete_form_action": "نعم، حذف النموذج",
|
||||
"delete_form_confirmation": "هل أنت متأكد من أنك تريد حذف هذا النموذج؟ لن يتمكن أي شخص تشارك الرابط معه من الحجز بعد الآن. كما ستحذف جميع الردود المرتبطة بذلك.",
|
||||
"delete_form_confirmation": "أي شخص قمت بمشاركة الرابط معه لن يكون قادرًا على الوصول إليه.",
|
||||
"delete_form_confirmation_2": "سيتم حذف جميع الردود ذات الصلة.",
|
||||
"typeform_redirect_url_copied": "تم نسخ رابط Typeform Redirect! يمكنك الانتقال وتعيين الرابط في نموذج Typeform.",
|
||||
"modifications_in_fields_warning": "ستنعكس التعديلات في الحقول والمسارات للنماذج التالية في هذا النموذج.",
|
||||
@@ -1647,32 +1687,100 @@
|
||||
"add_a_new_route": "إضافة مسار جديد",
|
||||
"make_informed_decisions": "اتخاذ قرارات على اطّلاع باستخدام الرؤى",
|
||||
"make_informed_decisions_description": "تقدّم لوحة الرؤى لدينا جميع الأنشطة لفريقك وتظهر لك الاتجاهات السائدة التي تمكّنك من جدولة الفريق واتخاذ القرار بشكل أفضل.",
|
||||
"view_bookings_across": "عرض الحجز أمام جميع الأعضاء",
|
||||
"view_bookings_across_description": "شاهد من يتلقى معظم الحجوزات وتأكد من إجراء أفضل عملية توزيع عبر فريقك",
|
||||
"identify_booking_trends": "تحديد الاتجاهات السائدة للحجز",
|
||||
"identify_booking_trends_description": "شاهد أي الأوقات في الأسبوع وخلال اليوم تحظى بشعبية لدى من يقومون بالحجز لديك",
|
||||
"spot_popular_event_types": "أنواع الأحداث الشائعة",
|
||||
"spot_popular_event_types_description": "شاهد أي من نوع من الأحداث لديك يتلقى معظم النقرات والحجز",
|
||||
"no_responses_yet": "لا توجد ردود بعد",
|
||||
"this_will_be_the_placeholder": "سيكون هذا هو العنصر النائب",
|
||||
"this_meeting_has_not_started_yet": "لم يبدأ هذا الاجتماع بعد",
|
||||
"this_app_requires_connected_account": "{{appName}} يتطلب حساب {{dependencyName}} متصل",
|
||||
"connect_app": "الاتصال {{dependencyName}}",
|
||||
"connect_app": "اتصال {{dependencyName}}",
|
||||
"app_is_connected": "{{dependencyName}} متصل",
|
||||
"requires_app": "يتطلب {{dependencyName}}",
|
||||
"verification_code": "رمز التحقق",
|
||||
"can_you_try_again": "هلّا جربت المحاولة ثانيةً في وقت مختلف؟",
|
||||
"can_you_try_again": "هل يمكنك المحاولة مرة أخرى في وقت مختلف؟",
|
||||
"verify": "التحقق",
|
||||
"timezone_variable": "المنطقة الزمنية",
|
||||
"timezone_info": "المنطقة الزمنية للشخص المتلقي",
|
||||
"invalid_event_name_variables": "ثمة متغيّر غير غير صالح في اسم فعاليتك",
|
||||
"select_all": "اختيار الكل",
|
||||
"default_conferencing_bulk_title": "التحديث الجماعي لمجموعة أنواع الفعاليات الحالية",
|
||||
"default_conferencing_bulk_description": "تحديث المواقع لأنواع الفعاليات المحددة",
|
||||
"event_end_time_variable": "وقت انتهاء الحدث",
|
||||
"event_end_time_info": "زمن نهاية الحدث",
|
||||
"cancel_url_variable": "إلغاء عنوان URL",
|
||||
"cancel_url_info": "عنوان URL لإلغاء الحجز",
|
||||
"reschedule_url_variable": "إعادة جدولة عنوان URL",
|
||||
"reschedule_url_info": "عنوان URL لإعادة جدولة الحجز",
|
||||
"invalid_event_name_variables": "هناك متغيّر غير صالح في اسم الحدث",
|
||||
"select_all": "تحديد الكل",
|
||||
"default_conferencing_bulk_title": "التحديث الجماعي لمجموعة أنواع الأحداث الحالية",
|
||||
"members_default_schedule": "الجدول الزمني الافتراضي للعضو",
|
||||
"set_by_admin": "تعيين بواسطة مشرف الفريق",
|
||||
"members_default_location": "الموقع الافتراضي للعضو",
|
||||
"members_default_schedule_description": "سنستخدم جدول الإتاحة الافتراضي لكل عضو، وسيكون بإمكانه تعديله أو تغييره.",
|
||||
"requires_at_least_one_schedule": "يجب أن يكون لديك جدول واحد على الأقل",
|
||||
"default_conferencing_bulk_description": "تحديث المواقع لأنواع الأحداث المحددة",
|
||||
"locked_for_members": "مقفل أمام الأعضاء",
|
||||
"locked_apps_description": "سيكون الأعضاء قادرين على رؤية التطبيقات النشطة، ولكن من دون القدرة على تعديل أي إعدادات للتطبيق",
|
||||
"locked_webhooks_description": "سيكون الأعضاء قادرين على رؤية قوالب الويب النشطة، ولكن من دون القدرة على تعديل أي إعدادات للقوالب",
|
||||
"locked_workflows_description": "سيكون الأعضاء قادرين على رؤية مسارات العمل النشطة، ولكن من دون القدرة على تعديل أي إعدادات لمسار العمل",
|
||||
"locked_by_admin": "مقفل من قبل المشرف",
|
||||
"app_not_connected": "أنت غير متصل بحساب {{appName}}.",
|
||||
"connect_now": "اتصل الآن",
|
||||
"managed_event_dialog_confirm_button_one": "استبدال وإشعار العضو {{count}}",
|
||||
"managed_event_dialog_confirm_button_other": "استبدال وإشعار الأعضاء {{count}}",
|
||||
"managed_event_dialog_title_one": "عنوان url /{{slug}} موجود بالفعل من أجل {{count}} عضو. هل تريد استبداله؟",
|
||||
"managed_event_dialog_title_other": "عنوان url /{{slug}} موجود بالفعل لـ {{count}} أعضاء. هل تريد استبداله؟",
|
||||
"managed_event_dialog_information_one": "<strong>{{names}}</strong> يستخدم بالفعل عنوان url <strong>/{{slug}}</strong>.",
|
||||
"managed_event_dialog_information_other": "<strong>{{names}}</strong> يستخدم بالفعل عنوان url <string>/{{slug}}</strong>.",
|
||||
"managed_event_dialog_clarification": "سنقوم بإعلامهم إذا اخترت استبداله. قم بالعودة وإزالتهم إذا كنت لا تريد الكتابة فوقها.",
|
||||
"review_event_type": "مراجعة نوع الحدث",
|
||||
"looking_for_more_analytics": "هل تبحث عن تحليلات إضافية؟",
|
||||
"looking_for_more_insights": "هل تبحث عن رؤى أكثر؟",
|
||||
"add_filter": "إضافة عامل فرز",
|
||||
"select_user": "اختر مستخدم",
|
||||
"select_event_type": "اختر نوع الحدث",
|
||||
"select_date_range": "اختر نطاق التاريخ",
|
||||
"popular_events": "الأحداث الشائعة",
|
||||
"no_event_types_found": "لم يتم العثور على أنواع الأحداث",
|
||||
"average_event_duration": "متوسط مدة الحدث",
|
||||
"most_booked_members": "أكثر الأعضاء حجزاً",
|
||||
"least_booked_members": "أقل الأعضاء حجزاً",
|
||||
"events_created": "تم إنشاء الأحداث",
|
||||
"events_completed": "تم اكتمال الأحداث",
|
||||
"events_cancelled": "تم إلغاء الأحداث",
|
||||
"events_rescheduled": "تم إعادة جدولة الأحداث",
|
||||
"from_last_period": "من آخر فترة",
|
||||
"from_to_date_period": "من: {{startDate}} إلى: {{endDate}}",
|
||||
"analytics_for_organisation": "الرؤى",
|
||||
"subtitle_analytics": "اعرف المزيد عن نشاط فريقك",
|
||||
"redirect_url_warning": "سيؤدي إضافة إعادة توجيه إلى تعطيل صفحة النجاح. تأكد من ذكر \"تأكيد الحجز\" في صفحة النجاح المخصصة.",
|
||||
"event_trends": "الاتجاهات السائدة للحدث",
|
||||
"clear_filters": "مسح عوامل الفرز",
|
||||
"hold": "تعليق",
|
||||
"on_booking_option": "تحصيل الدفعة عن الحجز",
|
||||
"hold_option": "فرض رسوم لقاء عدم الحضور",
|
||||
"card_held": "تم تعليق البطاقة",
|
||||
"charge_card": "الدفع باستخدام البطاقة",
|
||||
"card_charged": "تم استقطاع الدفعة من البطاقة",
|
||||
"no_show_fee_amount": "رسم عدم الحضور {{amount, currency}}",
|
||||
"no_show_fee": "رسم عدم الحضور",
|
||||
"submit_card": "تقديم بطاقة",
|
||||
"submit_payment_information": "تقديم معلومات الدفع",
|
||||
"meeting_awaiting_payment_method": "اجتماعك في انتظار طريقة الدفع",
|
||||
"no_show_fee_charged_email_subject": "تم تحصيل رسم عدم حضور مقداره {{amount, currency}} من أجل {{title}} في {{date}}",
|
||||
"no_show_fee_charged_text_body": "تم تحصيل رسم عدم حضور",
|
||||
"no_show_fee_charged_subtitle": "تم تحصيل رسم عدم حضور مقداره {{amount, currency}} من أجل الحدث التالي",
|
||||
"error_charging_card": "حدثت مشكلة في استقطاع رسم عدم الحضور. يرجى المحاولة مرة أخرى لاحقاً.",
|
||||
"collect_no_show_fee": "تحصيل رسم عدم الحضور",
|
||||
"no_show_fee_charged": "لم يتم تحصيل رسوم عدم حضور",
|
||||
"insights": "الرؤى",
|
||||
"acknowledge_booking_no_show_fee": "أقر بأنه إذا لم أحضر هذا الحدث فإنه لن يتم استخدام بطاقتي للدقع لرسوم الحدث {{amount, currency}}."
|
||||
"testing_workflow_info_message": "عند اختبار مسار العمل هذا، كن على علم بأنه يمكن جدولة رسائل البريد الإلكتروني والرسائل القصيرة فقط قبل ساعة واحدة على الأقل",
|
||||
"insights_no_data_found_for_filter": "لم يتم العثور على بيانات لعامل الفرز المحدد أو التواريخ المحددة.",
|
||||
"acknowledge_booking_no_show_fee": "أقر بأنه إذا لم أحضر هذا الحدث فإنه لن يتم استخدام بطاقتي للدقع لرسوم الحدث {{amount, currency}}.",
|
||||
"card_details": "تفاصيل البطاقة",
|
||||
"seats_and_no_show_fee_error": "لا يمكن حالياً تمكين المقاعد وفرض رسوم عدم الحضور",
|
||||
"complete_your_booking": "أكمل الحجز",
|
||||
"complete_your_booking_subject": "أكمل الحجز: {{title}} في {{date}}",
|
||||
"email_invite_team": "تم دعوة {{email}}"
|
||||
}
|
||||
|
||||
@@ -465,7 +465,6 @@
|
||||
"friday": "pátek",
|
||||
"saturday": "sobota",
|
||||
"sunday": "neděle",
|
||||
"all_booked_today": "Všechen čas dnes rezervován.",
|
||||
"slots_load_fail": "Nepodařilo se načíst dostupné časové sloty.",
|
||||
"additional_guests": "Přidat hosty",
|
||||
"your_name": "Vaše jméno",
|
||||
@@ -1039,7 +1038,6 @@
|
||||
"event_cancelled_trigger": "při zrušení události",
|
||||
"new_event_trigger": "při rezervaci nové události",
|
||||
"email_host_action": "odeslání e-mailu hostiteli",
|
||||
"email_attendee_action": "odeslání e-mailu účastníkovi",
|
||||
"sms_attendee_action": "odeslání SMS účastníkovi",
|
||||
"sms_number_action": "odeslání SMS na konkrétní číslo",
|
||||
"workflows": "Pracovní postupy",
|
||||
|
||||
@@ -457,7 +457,6 @@
|
||||
"friday": "Fredag",
|
||||
"saturday": "Lørdag",
|
||||
"sunday": "Søndag",
|
||||
"all_booked_today": "Alle booket i dag.",
|
||||
"slots_load_fail": "Kunne ikke indlæse de tilgængelige tidsrum.",
|
||||
"additional_guests": "Tilføj gæster",
|
||||
"your_name": "Dit navn",
|
||||
@@ -1011,7 +1010,6 @@
|
||||
"event_cancelled_trigger": "når begivenheden er aflyst",
|
||||
"new_event_trigger": "når ny begivenhed er booket",
|
||||
"email_host_action": "send e-mail til vært",
|
||||
"email_attendee_action": "send e-mail til deltager",
|
||||
"sms_attendee_action": "Send SMS til deltager",
|
||||
"sms_number_action": "send SMS til et specifikt nummer",
|
||||
"workflows": "Workflows",
|
||||
|
||||
@@ -465,7 +465,6 @@
|
||||
"friday": "Freitag",
|
||||
"saturday": "Samstag",
|
||||
"sunday": "Sonntag",
|
||||
"all_booked_today": "Ausgebucht für heute.",
|
||||
"slots_load_fail": "Die verfügbaren Zeitfenster konnten nicht geladen werden.",
|
||||
"additional_guests": "+ Weitere Gäste",
|
||||
"your_name": "Ihr Name",
|
||||
@@ -1039,7 +1038,6 @@
|
||||
"event_cancelled_trigger": "wenn das Ereignis abgesagt wird",
|
||||
"new_event_trigger": "wenn ein neer Termin gebucht wird",
|
||||
"email_host_action": "E-Mail an den Veranstalter senden",
|
||||
"email_attendee_action": "E-Mail an Teilnehmende senden",
|
||||
"sms_attendee_action": "SMS an Teilnehmende senden",
|
||||
"sms_number_action": "SMS an eine bestimmte Nummer senden",
|
||||
"workflows": "Workflows",
|
||||
|
||||
@@ -465,7 +465,7 @@
|
||||
"friday": "Friday",
|
||||
"saturday": "Saturday",
|
||||
"sunday": "Sunday",
|
||||
"all_booked_today": "All booked today.",
|
||||
"all_booked_today": "All booked.",
|
||||
"slots_load_fail": "Could not load the available time slots.",
|
||||
"additional_guests": "Add guests",
|
||||
"your_name": "Your name",
|
||||
@@ -1039,7 +1039,7 @@
|
||||
"event_cancelled_trigger": "when event is cancelled",
|
||||
"new_event_trigger": "when new event is booked",
|
||||
"email_host_action": "send email to host",
|
||||
"email_attendee_action": "send email to attendee",
|
||||
"email_attendee_action": "send email to attendees",
|
||||
"sms_attendee_action": "send SMS to attendee",
|
||||
"sms_number_action": "send SMS to a specific number",
|
||||
"workflows": "Workflows",
|
||||
@@ -1248,6 +1248,7 @@
|
||||
"calendars_description": "Configure how your event types interact with your calendars",
|
||||
"appearance_description": "Manage settings for your booking appearance",
|
||||
"conferencing_description": "Add your favourite video conferencing apps for your meetings",
|
||||
"add_conferencing_app": "Add Conferencing App",
|
||||
"password_description": "Manage settings for your account passwords",
|
||||
"2fa_description": "Manage settings for your account passwords",
|
||||
"we_just_need_basic_info": "We just need some basic info to get your profile setup.",
|
||||
@@ -1799,5 +1800,18 @@
|
||||
"charge_attendee": "Charge attendee {{amount, currency}}",
|
||||
"payment_app_commission": "Require payment ({{paymentFeePercentage}}% + {{fee, currency}} commission per transaction)",
|
||||
"email_invite_team": "{{email}} has been invited",
|
||||
"image_size_limit_exceed": "Uploaded image shouldn't exceed 5mb size limit"
|
||||
"error_collecting_card": "Error collecting card",
|
||||
"image_size_limit_exceed": "Uploaded image shouldn't exceed 5mb size limit",
|
||||
"inline_embed": "Inline Embed",
|
||||
"load_inline_content": "Loads your event type directly inline with your other website content.",
|
||||
"floating_pop_up_button": "Floating pop-up button",
|
||||
"floating_button_trigger_modal": "Puts a floating button on your site that triggers a modal with your event type.",
|
||||
"pop_up_element_click": "Pop up via element click",
|
||||
"open_dialog_with_element_click": "Open your Cal dialog when someone clicks an element.",
|
||||
"need_help_embedding": "Need help? See our guides for embedding Cal on Wix, Squarespace, or WordPress, check our common questions, or explore advanced embed options.",
|
||||
"book_my_cal": "Book my Cal",
|
||||
"disable_attendees_confirmation_emails": "Disable default confirmation emails for attendees",
|
||||
"disable_attendees_confirmation_emails_description": "At least one workflow is active on this event type that sends an email to the attendees when the event is booked.",
|
||||
"disable_host_confirmation_emails": "Disable default confirmation emails for host",
|
||||
"disable_host_confirmation_emails_description": "At least one workflow is active on this event type that sends an email to the host when the event is booked."
|
||||
}
|
||||
|
||||
@@ -465,7 +465,6 @@
|
||||
"friday": "Viernes",
|
||||
"saturday": "Sábado",
|
||||
"sunday": "Domingo",
|
||||
"all_booked_today": "Todo Reservado Hoy.",
|
||||
"slots_load_fail": "No se pudo cargar el intervalo de tiempo disponible.",
|
||||
"additional_guests": "Añadir invitados",
|
||||
"your_name": "Tu Nombre",
|
||||
@@ -1039,7 +1038,6 @@
|
||||
"event_cancelled_trigger": "cuando se cancela el evento",
|
||||
"new_event_trigger": "cuando se reserva un nuevo evento",
|
||||
"email_host_action": "enviar correo electrónico al anfitrión",
|
||||
"email_attendee_action": "enviar correo electrónico al asistente",
|
||||
"sms_attendee_action": "enviar SMS al asistente",
|
||||
"sms_number_action": "enviar SMS a un número específico",
|
||||
"workflows": "Flujos de trabajo",
|
||||
|
||||
@@ -465,7 +465,7 @@
|
||||
"friday": "Vendredi",
|
||||
"saturday": "Samedi",
|
||||
"sunday": "Dimanche",
|
||||
"all_booked_today": "Tout est réservé pour aujourd'hui.",
|
||||
"all_booked_today": "Tout est réservé.",
|
||||
"slots_load_fail": "Impossible de charger les créneaux disponibles.",
|
||||
"additional_guests": "Ajouter des invités",
|
||||
"your_name": "Votre nom",
|
||||
@@ -1039,7 +1039,7 @@
|
||||
"event_cancelled_trigger": "lorsque l'événement est annulé",
|
||||
"new_event_trigger": "lorsqu'un nouvel événement est réservé",
|
||||
"email_host_action": "envoyer un e-mail à l'hôte",
|
||||
"email_attendee_action": "envoyer un e-mail au participant",
|
||||
"email_attendee_action": "envoyer un e-mail aux participants",
|
||||
"sms_attendee_action": "envoyer un SMS au participant",
|
||||
"sms_number_action": "envoyer un SMS à un numéro spécifique",
|
||||
"workflows": "Workflows",
|
||||
@@ -1248,6 +1248,7 @@
|
||||
"calendars_description": "Configurez la manière dont vos types d'événements interagissent avec vos calendriers.",
|
||||
"appearance_description": "Gérez les paramètres d'apparence de vos réservations.",
|
||||
"conferencing_description": "Ajoutez vos applications de visioconférence préférées pour vos rendez-vous.",
|
||||
"add_conferencing_app": "Ajouter une application de conférence",
|
||||
"password_description": "Gérez les paramètres pour les mots de passe de votre compte.",
|
||||
"2fa_description": "Gérez les paramètres pour les mots de passe de votre compte.",
|
||||
"we_just_need_basic_info": "Nous avons juste besoin de quelques informations de base pour configurer votre profil.",
|
||||
@@ -1793,10 +1794,19 @@
|
||||
"seats_and_no_show_fee_error": "Il n'est pas possible d'activer les places et de facturer des frais d'absence pour le moment",
|
||||
"complete_your_booking": "Terminer votre réservation",
|
||||
"complete_your_booking_subject": "Terminer votre réservation : {{title}} le {{date}}",
|
||||
"confirm_your_details": "Confirmez vos coordonnées",
|
||||
"currency_string": "{{amount, currency}}",
|
||||
"charge_card_dialog_body": "Vous êtes sur le point de facturer {{amount, currency}} au participant. Voulez-vous vraiment continuer ?",
|
||||
"charge_attendee": "Facturer {{amount, currency}} au participant",
|
||||
"payment_app_commission": "Exiger un paiement ({{paymentFeePercentage}} % + {{fee, currency}} de commission par transaction)",
|
||||
"email_invite_team": "{{email}} a été invité",
|
||||
"image_size_limit_exceed": "L'image téléchargée ne doit pas dépasser 5 Mo"
|
||||
"error_collecting_card": "Erreur lors de la collecte de la carte",
|
||||
"image_size_limit_exceed": "L'image téléchargée ne doit pas dépasser 5 Mo",
|
||||
"inline_embed": "Intégration en ligne",
|
||||
"load_inline_content": "Charge votre type d'événement directement en ligne avec le contenu de votre site web.",
|
||||
"floating_pop_up_button": "Bouton pop-up flottant",
|
||||
"disable_attendees_confirmation_emails": "Désactiver les e-mails de confirmation par défaut pour les participants",
|
||||
"disable_attendees_confirmation_emails_description": "Au moins un workflow est actif sur ce type d'événement qui envoie un e-mail aux participants lorsque l'événement est réservé.",
|
||||
"disable_host_confirmation_emails": "Désactiver les e-mails de confirmation par défaut pour l'hôte",
|
||||
"disable_host_confirmation_emails_description": "Au moins un workflow est actif sur ce type d'événement qui envoie un e-mail à l'hôte lorsque l'événement est réservé."
|
||||
}
|
||||
|
||||
@@ -460,7 +460,6 @@
|
||||
"friday": "יום שישי",
|
||||
"saturday": "יום שבת",
|
||||
"sunday": "יום ראשון",
|
||||
"all_booked_today": "כל חלונות הזמן להיום הוזמנו.",
|
||||
"slots_load_fail": "לא ניתן לטעון את חלונות הזמן הזמינים.",
|
||||
"additional_guests": "הוספת אורחים",
|
||||
"your_name": "שמך",
|
||||
@@ -1015,7 +1014,6 @@
|
||||
"event_cancelled_trigger": "כאשר אירוע מבוטל",
|
||||
"new_event_trigger": "כאשר אירוע חדש מוזמן",
|
||||
"email_host_action": "שלח מייל למארח",
|
||||
"email_attendee_action": "שלח מייל למשתתף",
|
||||
"sms_attendee_action": "שלח SMS למשתתף",
|
||||
"sms_number_action": "שלח SMS למספר מסוים",
|
||||
"workflows": "תהליכים",
|
||||
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user