Compare commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
9441da9d4e | ||
|
|
1510740cb2 | ||
|
|
8da68cc1fe | ||
|
|
6dbc8dd820 | ||
|
|
4ada428bcb | ||
|
|
6adaf0d3e8 | ||
|
|
c9e6acfa69 | ||
|
|
2272da5ee7 | ||
|
|
79b3875af3 | ||
|
|
d16b6fce8f | ||
|
|
40b35bdbe4 | ||
|
|
0af76ec5ab | ||
|
|
f7b20da640 | ||
|
|
eedf6913b8 | ||
|
|
78393a2b8b | ||
|
|
b3d64104b0 | ||
|
|
9266ba1e48 | ||
|
|
ec212e4317 | ||
|
|
02d4f0542a | ||
|
|
0aae07ae2f | ||
|
|
80ab6c9c98 | ||
|
|
86ba0766fc | ||
|
|
0adde17f38 | ||
|
|
9e4700f9df | ||
|
|
f4a87c66b3 | ||
|
|
0c06f1f6cb | ||
|
|
b731dfd421 | ||
|
|
0b704b9ed4 | ||
|
|
a0ece0d77e | ||
|
|
7839381911 | ||
|
|
6a976f5b42 | ||
|
|
8718905474 | ||
|
|
ff968d3867 | ||
|
|
8f5e6370b0 | ||
|
|
0a0c400333 | ||
|
|
0eb05aa540 | ||
|
|
efe18ebdcd | ||
|
|
ce097b89ba | ||
|
|
bb0e6389fa | ||
|
|
19a7cd1a50 | ||
|
|
49bcd5fec7 | ||
|
|
000a6e0d35 | ||
|
|
52936f60b4 | ||
|
|
2d6eebcd8a | ||
|
|
cfa6a5b996 | ||
|
|
7413ba6aa0 | ||
|
|
e9cbe5d353 | ||
|
|
d8bc9acbbc | ||
|
|
4f0e843ac6 | ||
|
|
02f1c50bb2 | ||
|
|
5952a38ab9 | ||
|
|
e5fb19163e | ||
|
|
79f34a4fc7 | ||
|
|
3e90c08d55 | ||
|
|
697d8eca43 | ||
|
|
5cecf3104f | ||
|
|
a3a6caa6dd | ||
|
|
d3586c9039 | ||
|
|
71eafa287a | ||
|
|
828ef8580e | ||
|
|
a348c3a20a | ||
|
|
3c68c7f7cb | ||
|
|
820b2da16d | ||
|
|
6bdb2b94eb | ||
|
|
3c3db0b3a6 | ||
|
|
4748641c22 | ||
|
|
804787f2a3 | ||
|
|
b7c3b31e0c | ||
|
|
ab6781cd72 | ||
|
|
b070cfb227 | ||
|
|
5acb3a09e5 | ||
|
|
69c9f690cb | ||
|
|
d2a12ea834 | ||
|
|
16f24b4831 | ||
|
|
a3eeeab259 |
@@ -26,11 +26,13 @@ Fixes # (issue)
|
||||
- [ ] Test B
|
||||
|
||||
## Mandatory Tasks
|
||||
|
||||
- [ ] Make sure you have self-reviewed the code. A decent size PR without self-review might be rejected.
|
||||
|
||||
## Checklist
|
||||
|
||||
<!-- Please remove all the irrelevant bullets to your PR -->
|
||||
|
||||
- I haven't read the [contributing guide](https://github.com/calcom/cal.com/blob/main/CONTRIBUTING.md)
|
||||
- My code doesn't follow the style guidelines of this project
|
||||
- I haven't commented my code, particularly in hard-to-understand areas
|
||||
|
||||
@@ -16,6 +16,7 @@ jobs:
|
||||
steps:
|
||||
- uses: actions/stale@v7
|
||||
with:
|
||||
ascending: true
|
||||
days-before-issue-stale: 60
|
||||
days-before-issue-close: -1
|
||||
days-before-pr-stale: 14
|
||||
|
||||
@@ -31,6 +31,8 @@ env:
|
||||
# INPUT_ENV_MS_GRAPH_CLIENT_SECRET: xxx
|
||||
# INPUT_ENV_ZOOM_CLIENT_ID: xxx
|
||||
# INPUT_ENV_ZOOM_CLIENT_SECRET: xxx
|
||||
INPUT_ENV_SENDGRID_API_KEY: ${{ secrets.CI_SENDGRID_API_KEY }}
|
||||
INPUT_ENV_SENDGRID_EMAIL: ${{ secrets.CI_SENDGRID_EMAIL }}
|
||||
INPUT_ENV_TURBO_TOKEN: ${{ secrets.TURBO_TOKEN }}
|
||||
INPUT_ENV_TURBO_TEAM: ${{ secrets.TURBO_TEAM }}
|
||||
|
||||
|
||||
@@ -34,12 +34,15 @@ jobs:
|
||||
app-store:
|
||||
- 'apps/web/**'
|
||||
- 'packages/app-store/**'
|
||||
- 'playwright.config.ts'
|
||||
embed:
|
||||
- 'apps/web/**'
|
||||
- 'packages/embeds/**'
|
||||
- 'playwright.config.ts'
|
||||
embed-react:
|
||||
- 'apps/web/**'
|
||||
- 'packages/embeds/**'
|
||||
- 'playwright.config.ts'
|
||||
|
||||
env:
|
||||
name: Create env file
|
||||
|
||||
@@ -121,9 +121,9 @@ Here is what you need to be able to run Cal.com.
|
||||
```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.
|
||||
|
||||
> 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
|
||||
|
||||
@@ -158,6 +158,7 @@ yarn dx
|
||||
```sh
|
||||
echo 'NEXT_PUBLIC_DEBUG=1' >> .env
|
||||
```
|
||||
|
||||
#### Gitpod Setup
|
||||
|
||||
1. Click the button below to open this project in Gitpod.
|
||||
@@ -166,8 +167,6 @@ echo 'NEXT_PUBLIC_DEBUG=1' >> .env
|
||||
|
||||
[](https://gitpod.io/#https://github.com/calcom/cal.com)
|
||||
|
||||
|
||||
|
||||
#### Manual setup
|
||||
|
||||
1. Configure environment variables in the `.env` file. Replace `<user>`, `<pass>`, `<db-host>`, and `<db-port>` with their applicable values
|
||||
@@ -175,24 +174,26 @@ echo 'NEXT_PUBLIC_DEBUG=1' >> .env
|
||||
```
|
||||
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 local DB</summary>
|
||||
|
||||
1. [Download](https://www.postgresql.org/download/) and install postgres in your local (if you don't have it already).
|
||||
|
||||
2. Create your own local db by executing `createDB <DB name>`
|
||||
2. Create your own local db by executing `createDB <DB name>`
|
||||
|
||||
3. Now open your psql shell with the DB you created: `psql -h localhost -U postgres -d <DB name>`
|
||||
|
||||
4. Inside the psql shell execute `\conninfo`. And you will get the following info.
|
||||
4. Inside the psql shell execute `\conninfo`. And you will get the following info.
|
||||

|
||||
|
||||
5. Now extract all the info and add it to your DATABASE_URL. The url would look something like this
|
||||
`postgresql://postgres:postgres@localhost:5432/Your-DB-Name`.
|
||||
5. Now extract all the info and add it to your DATABASE_URL. The url would look something like this
|
||||
`postgresql://postgres:postgres@localhost:5432/Your-DB-Name`.
|
||||
|
||||
</details>
|
||||
|
||||
If you don't want to create a local DB. Then you can also consider using services like railway.app or render.
|
||||
|
||||
- [Setup postgres DB with railway.app](https://arctype.com/postgres/setup/railway-postgres)
|
||||
- [Setup postgres DB with render](https://render.com/docs/databases)
|
||||
|
||||
@@ -208,10 +209,11 @@ echo 'NEXT_PUBLIC_DEBUG=1' >> .env
|
||||
```
|
||||
|
||||
In a production environment, run:
|
||||
|
||||
|
||||
```sh
|
||||
yarn workspace @calcom/prisma db-deploy
|
||||
```
|
||||
|
||||
1. Run [mailhog](https://github.com/mailhog/MailHog) to view emails sent during development
|
||||
|
||||
```sh
|
||||
@@ -319,7 +321,7 @@ 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://herokucdn.com/deploy/button.svg" alt="Deploy">
|
||||
<img width="185px" height="auto" src="https://www.herokucdn.com/deploy/button.svg" alt="Deploy">
|
||||
</a>
|
||||
|
||||
### Railway
|
||||
@@ -490,7 +492,9 @@ following
|
||||
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
-1
@@ -7,7 +7,7 @@
|
||||
<a href="https://cal.com/docs/enterprise-features/api#api-server-specifications">Read the API docs</a>
|
||||
</div>
|
||||
|
||||
# Cal.com Public API
|
||||
# Commercial Cal.com Public API
|
||||
|
||||
Welcome to the Public API ("/apps/api") of the Cal.com.
|
||||
|
||||
|
||||
@@ -32,9 +32,9 @@
|
||||
"@sentry/nextjs": "^7.20.0",
|
||||
"bcryptjs": "^2.4.3",
|
||||
"memory-cache": "^0.2.0",
|
||||
"next": "~13.4.6",
|
||||
"next": "^13.4.6",
|
||||
"next-api-middleware": "^1.0.1",
|
||||
"next-axiom": "^0.16.0",
|
||||
"next-axiom": "^0.17.0",
|
||||
"next-swagger-doc": "^0.3.6",
|
||||
"next-validations": "^0.2.0",
|
||||
"typescript": "^4.9.4",
|
||||
|
||||
@@ -2,7 +2,7 @@ import { CAL_URL } from "@calcom/lib/constants";
|
||||
|
||||
export default function getCalLink(eventType: {
|
||||
team?: { slug: string | null } | null;
|
||||
owner: { username: string | null } | null;
|
||||
owner?: { username: string | null } | null;
|
||||
users?: { username: string | null }[];
|
||||
slug: string;
|
||||
}) {
|
||||
|
||||
@@ -26,15 +26,15 @@ export function VariantsTable({
|
||||
<table>
|
||||
<RowTitles titles={titles} />
|
||||
{columns.map((column) => (
|
||||
<tr className="p-2 pr-6 pb-6" key={column.props.variant}>
|
||||
<tr className="p-2 pb-6 pr-6" key={column.props.variant}>
|
||||
<th
|
||||
className="p-2 pr-6 pb-6 text-left text-sm font-normal text-[#8F8F8F]"
|
||||
className="p-2 pb-6 pr-6 text-left text-sm font-normal text-[#8F8F8F]"
|
||||
key={column.props.variant}>
|
||||
{column.props.variant}
|
||||
</th>
|
||||
{React.Children.count(column.props.children) &&
|
||||
React.Children.map(column.props.children, (cell) => (
|
||||
<td className="p-2 pr-6 pb-6" style={{ minWidth: `${columnMinWidth}px` }}>
|
||||
<td className="p-2 pb-6 pr-6" style={{ minWidth: `${columnMinWidth}px` }}>
|
||||
{cell}
|
||||
</td>
|
||||
))}
|
||||
@@ -70,9 +70,9 @@ export function VariantRow({ children }: RowProps) {
|
||||
export function RowTitles({ titles }: { titles: string[] }) {
|
||||
return (
|
||||
<tr>
|
||||
<th className="p-2 pr-6 pb-6 text-left text-sm font-normal text-[#8F8F8F]" />
|
||||
<th className="p-2 pb-6 pr-6 text-left text-sm font-normal text-[#8F8F8F]" />
|
||||
{titles.map((title) => (
|
||||
<th className="p-2 pr-6 pb-6 text-left text-sm font-normal text-[#8F8F8F]" key={title}>
|
||||
<th className="p-2 pb-6 pr-6 text-left text-sm font-normal text-[#8F8F8F]" key={title}>
|
||||
{title}
|
||||
</th>
|
||||
))}
|
||||
|
||||
@@ -13,7 +13,7 @@
|
||||
"@radix-ui/react-avatar": "^1.0.0",
|
||||
"@radix-ui/react-collapsible": "^1.0.0",
|
||||
"@radix-ui/react-dialog": "^1.0.0",
|
||||
"@radix-ui/react-dropdown-menu": "^1.0.0",
|
||||
"@radix-ui/react-dropdown-menu": "^2.0.5",
|
||||
"@radix-ui/react-id": "^1.0.0",
|
||||
"@radix-ui/react-popover": "^1.0.2",
|
||||
"@radix-ui/react-radio-group": "^1.0.0",
|
||||
@@ -37,8 +37,8 @@
|
||||
"@storybook/react": "^6.5.13",
|
||||
"@storybook/testing-library": "^0.0.13",
|
||||
"@types/react": "18.0.26",
|
||||
"@types/react-dom": "18.0.9",
|
||||
"@vitejs/plugin-react": "^2.1.0",
|
||||
"@types/react-dom": "^18.0.9",
|
||||
"@vitejs/plugin-react": "^2.2.0",
|
||||
"autoprefixer": "^10.4.12",
|
||||
"babel-loader": "^8.2.5",
|
||||
"fs": "^0.0.1-security",
|
||||
@@ -48,7 +48,7 @@
|
||||
"storybook-addon-designs": "^6.3.1",
|
||||
"storybook-addon-next": "^1.6.9",
|
||||
"storybook-react-i18next": "^1.1.2",
|
||||
"tailwindcss": "^3.2.1",
|
||||
"tailwindcss": "^3.3.1",
|
||||
"typescript": "^4.9.4",
|
||||
"vite": "^4.1.2"
|
||||
}
|
||||
|
||||
@@ -209,6 +209,7 @@
|
||||
--cal-bg-success: #E2FBE8;
|
||||
--cal-bg-attention: #FCEED8;
|
||||
--cal-bg-error: #F9E3E2;
|
||||
--cal-bg-dark-error: #752522;
|
||||
|
||||
|
||||
/* Borders */
|
||||
@@ -216,6 +217,7 @@
|
||||
--cal-border: #D1D5DB;
|
||||
--cal-border-subtle:#E5E7EB;
|
||||
--cal-border-muted:#F3F4F6;
|
||||
--cal-border-error: #aa2e26;
|
||||
|
||||
/* Content/Text */
|
||||
--cal-text-emphasis: #111827;
|
||||
@@ -251,6 +253,7 @@
|
||||
--cal-bg-success: #E2FBE8;
|
||||
--cal-bg-attention: #FCEED8;
|
||||
--cal-bg-error: #F9E3E2;
|
||||
--cal-bg-dark-error: #752522;
|
||||
|
||||
|
||||
/* Borders */
|
||||
@@ -258,6 +261,7 @@
|
||||
--cal-border: #444444;
|
||||
--cal-border-subtle: #2B2B2B;
|
||||
--cal-border-muted: #1C1C1C;
|
||||
--cal-border-error: #aa2e26;
|
||||
|
||||
/* Content/Text */
|
||||
--cal-text-emphasis: #F3F4F6;
|
||||
|
||||
@@ -23,7 +23,7 @@
|
||||
"devDependencies": {
|
||||
"@types/node": "16.9.1",
|
||||
"@types/react": "18.0.26",
|
||||
"@types/react-dom": "18.0.9",
|
||||
"@types/react-dom": "^18.0.9",
|
||||
"typescript": "^4.9.4"
|
||||
}
|
||||
}
|
||||
|
||||
@@ -10,7 +10,7 @@ import { components } from "react-select";
|
||||
|
||||
import type { BookerLayout } from "@calcom/features/bookings/Booker/types";
|
||||
import { useFlagMap } from "@calcom/features/flags/context/provider";
|
||||
import { APP_NAME, EMBED_LIB_URL, WEBAPP_URL } from "@calcom/lib/constants";
|
||||
import { APP_NAME, EMBED_LIB_URL, IS_SELF_HOSTED, WEBAPP_URL } from "@calcom/lib/constants";
|
||||
import { useLocale } from "@calcom/lib/hooks/useLocale";
|
||||
import { BookerLayouts } from "@calcom/prisma/zod-utils";
|
||||
import {
|
||||
@@ -38,13 +38,21 @@ const enum Theme {
|
||||
dark = "dark",
|
||||
}
|
||||
|
||||
const EMBED_CAL_ORIGIN = WEBAPP_URL;
|
||||
const EMBED_CAL_JS_URL = `${WEBAPP_URL}/embed/embed.js`;
|
||||
|
||||
type PreviewState = {
|
||||
inline: {
|
||||
width: string;
|
||||
height: string;
|
||||
};
|
||||
theme: Theme;
|
||||
floatingPopup: Record<string, string | boolean | undefined>;
|
||||
floatingPopup: {
|
||||
config?: {
|
||||
layout: BookerLayouts;
|
||||
};
|
||||
[key: string]: string | boolean | undefined | Record<string, string>;
|
||||
};
|
||||
elementClick: Record<string, string>;
|
||||
palette: {
|
||||
brandColor: string;
|
||||
@@ -175,14 +183,24 @@ const Codes: Record<string, Record<string, (...args: any[]) => string>> = {
|
||||
return code`
|
||||
import Cal, { getCalApi } from "@calcom/embed-react";
|
||||
import { useEffect } from "react";
|
||||
function MyComponent() {
|
||||
export default function MyApp() {
|
||||
useEffect(()=>{
|
||||
(async function () {
|
||||
const cal = await getCalApi();
|
||||
${uiInstructionCode}
|
||||
})();
|
||||
}, [])
|
||||
return <Cal calLink="${calLink}" style={{width:"${width}",height:"${height}",overflow:"scroll"}} />;
|
||||
return <Cal
|
||||
calLink="${calLink}"
|
||||
style={{width:"${width}",height:"${height}",overflow:"scroll"}}
|
||||
${previewState.layout ? "config={{layout: '" + previewState.layout + "'}}" : ""}${
|
||||
IS_SELF_HOSTED
|
||||
? `
|
||||
calOrigin="${EMBED_CAL_ORIGIN}"
|
||||
calJsUrl="${EMBED_CAL_JS_URL}"`
|
||||
: ""
|
||||
}
|
||||
/>;
|
||||
};`;
|
||||
},
|
||||
"floating-popup": ({
|
||||
@@ -193,38 +211,60 @@ function MyComponent() {
|
||||
uiInstructionCode: string;
|
||||
}) => {
|
||||
return code`
|
||||
import Cal, { getCalApi } from "@calcom/embed-react";
|
||||
import { getCalApi } from "@calcom/embed-react";
|
||||
import { useEffect } from "react";
|
||||
function MyComponent() {
|
||||
export default function App() {
|
||||
useEffect(()=>{
|
||||
(async function () {
|
||||
const cal = await getCalApi();
|
||||
const cal = await getCalApi(${IS_SELF_HOSTED ? `"${EMBED_CAL_JS_URL}"` : ""});
|
||||
cal("floatingButton", ${floatingButtonArg});
|
||||
${uiInstructionCode}
|
||||
})();
|
||||
}, [])
|
||||
};`;
|
||||
},
|
||||
"element-click": ({ calLink, uiInstructionCode }: { calLink: string; uiInstructionCode: string }) => {
|
||||
"element-click": ({
|
||||
calLink,
|
||||
uiInstructionCode,
|
||||
previewState,
|
||||
}: {
|
||||
calLink: string;
|
||||
uiInstructionCode: string;
|
||||
previewState: PreviewState;
|
||||
}) => {
|
||||
return code`
|
||||
import Cal, { getCalApi } from "@calcom/embed-react";
|
||||
import { getCalApi } from "@calcom/embed-react";
|
||||
import { useEffect } from "react";
|
||||
function MyComponent() {
|
||||
export default function App() {
|
||||
useEffect(()=>{
|
||||
(async function () {
|
||||
const cal = await getCalApi();
|
||||
const cal = await getCalApi(${IS_SELF_HOSTED ? `"${EMBED_CAL_JS_URL}"` : ""});
|
||||
${uiInstructionCode}
|
||||
})();
|
||||
}, [])
|
||||
return <button data-cal-link="${calLink}" />;
|
||||
return <button
|
||||
data-cal-link="${calLink}"${IS_SELF_HOSTED ? `\ndata-cal-origin="${EMBED_CAL_ORIGIN}"` : ""}
|
||||
${`data-cal-config='${JSON.stringify({
|
||||
layout: previewState.layout,
|
||||
})}'`}
|
||||
>Click me</button>;
|
||||
};`;
|
||||
},
|
||||
},
|
||||
HTML: {
|
||||
inline: ({ calLink, uiInstructionCode }: { calLink: string; uiInstructionCode: string }) => {
|
||||
inline: ({
|
||||
calLink,
|
||||
uiInstructionCode,
|
||||
previewState,
|
||||
}: {
|
||||
calLink: string;
|
||||
uiInstructionCode: string;
|
||||
previewState: PreviewState;
|
||||
}) => {
|
||||
return code`Cal("inline", {
|
||||
elementOrSelector:"#my-cal-inline",
|
||||
calLink: "${calLink}"
|
||||
calLink: "${calLink}",
|
||||
layout: "${previewState.layout}"
|
||||
});
|
||||
|
||||
${uiInstructionCode}`;
|
||||
@@ -240,8 +280,22 @@ ${uiInstructionCode}`;
|
||||
return code`Cal("floatingButton", ${floatingButtonArg});
|
||||
${uiInstructionCode}`;
|
||||
},
|
||||
"element-click": ({ calLink, uiInstructionCode }: { calLink: string; uiInstructionCode: string }) => {
|
||||
return code`// Important: Make sure to add \`data-cal-link="${calLink}"\` attribute to the element you want to open Cal on click
|
||||
"element-click": ({
|
||||
calLink,
|
||||
uiInstructionCode,
|
||||
previewState,
|
||||
}: {
|
||||
calLink: string;
|
||||
uiInstructionCode: string;
|
||||
previewState: PreviewState;
|
||||
}) => {
|
||||
return code`
|
||||
// Important: Please add following attributes to the element you want to open Cal on click
|
||||
// \`data-cal-link="${calLink}"\`
|
||||
// \`data-cal-config='${JSON.stringify({
|
||||
layout: previewState.layout,
|
||||
})}'\`
|
||||
|
||||
${uiInstructionCode}`;
|
||||
},
|
||||
},
|
||||
@@ -298,6 +352,7 @@ const getEmbedTypeSpecificString = ({
|
||||
} else if (embedType === "floating-popup") {
|
||||
const floatingButtonArg = {
|
||||
calLink,
|
||||
...(IS_SELF_HOSTED ? { calOrigin: EMBED_CAL_ORIGIN } : null),
|
||||
...previewState.floatingPopup,
|
||||
};
|
||||
return frameworkCodes[embedType]({
|
||||
@@ -710,13 +765,13 @@ const EmbedTypeCodeAndPreviewDialogContent = ({
|
||||
height: "100%",
|
||||
},
|
||||
theme: Theme.auto,
|
||||
layout: BookerLayouts.MONTH_VIEW,
|
||||
floatingPopup: {},
|
||||
elementClick: {},
|
||||
hideEventTypeDetails: false,
|
||||
palette: {
|
||||
brandColor: "#000000",
|
||||
},
|
||||
layout: BookerLayouts.MONTH_VIEW,
|
||||
});
|
||||
|
||||
const close = () => {
|
||||
@@ -778,8 +833,8 @@ const EmbedTypeCodeAndPreviewDialogContent = ({
|
||||
name: "ui",
|
||||
arg: {
|
||||
theme: previewState.theme,
|
||||
hideEventTypeDetails: previewState.hideEventTypeDetails,
|
||||
layout: previewState.layout,
|
||||
hideEventTypeDetails: previewState.hideEventTypeDetails,
|
||||
styles: {
|
||||
branding: {
|
||||
...previewState.palette,
|
||||
@@ -1000,7 +1055,7 @@ const EmbedTypeCodeAndPreviewDialogContent = ({
|
||||
</div>
|
||||
<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">
|
||||
<div className="mb-6 mt-2 w-40 xl:mt-0 xl:w-full">
|
||||
<ColorPicker
|
||||
className="w-[130px]"
|
||||
popoverAlign="start"
|
||||
@@ -1102,8 +1157,15 @@ const EmbedTypeCodeAndPreviewDialogContent = ({
|
||||
return;
|
||||
}
|
||||
setPreviewState((previewState) => {
|
||||
const config = {
|
||||
...(previewState.floatingPopup.config ?? {}),
|
||||
layout: option.value,
|
||||
};
|
||||
return {
|
||||
...previewState,
|
||||
floatingPopup: {
|
||||
config,
|
||||
},
|
||||
layout: option.value,
|
||||
};
|
||||
});
|
||||
|
||||
@@ -71,7 +71,7 @@ const NavTabs: FC<NavTabProps> = ({ tabs, linkProps, ...props }) => {
|
||||
isCurrent
|
||||
? "text-emphasis border-gray-900"
|
||||
: "hover:border-default hover:text-default text-subtle border-transparent",
|
||||
"group inline-flex items-center border-b-2 py-4 px-1 text-sm font-medium",
|
||||
"group inline-flex items-center border-b-2 px-1 py-4 text-sm font-medium",
|
||||
className
|
||||
)}
|
||||
aria-current={isCurrent ? "page" : undefined}>
|
||||
|
||||
@@ -85,7 +85,7 @@ const Component = ({
|
||||
return (
|
||||
<div className="relative flex-1 flex-col items-start justify-start px-4 md:flex md:px-8 lg:flex-row lg:px-0">
|
||||
{hasDescriptionItems && (
|
||||
<div className="align-center bg-subtle mb-4 -ml-4 -mr-4 flex min-h-[450px] w-auto basis-3/5 snap-x snap-mandatory flex-row overflow-auto whitespace-nowrap p-4 md:mb-8 md:-ml-8 md:-mr-8 md:p-8 lg:mx-0 lg:mb-0 lg:max-w-2xl lg:flex-col lg:justify-center lg:rounded-md">
|
||||
<div className="align-center bg-subtle -ml-4 -mr-4 mb-4 flex min-h-[450px] w-auto basis-3/5 snap-x snap-mandatory flex-row overflow-auto whitespace-nowrap p-4 md:-ml-8 md:-mr-8 md:mb-8 md:p-8 lg:mx-0 lg:mb-0 lg:max-w-2xl lg:flex-col lg:justify-center lg:rounded-md">
|
||||
{descriptionItems ? (
|
||||
descriptionItems.map((descriptionItem, index) =>
|
||||
typeof descriptionItem === "object" ? (
|
||||
@@ -260,7 +260,7 @@ const Component = ({
|
||||
)}
|
||||
</span>
|
||||
|
||||
<h4 className="text-emphasis mt-8 mb-2 font-semibold ">{t("contact")}</h4>
|
||||
<h4 className="text-emphasis mb-2 mt-8 font-semibold ">{t("contact")}</h4>
|
||||
<ul className="prose-sm -ml-1 -mr-1 leading-5">
|
||||
{docs && (
|
||||
<li>
|
||||
@@ -269,7 +269,7 @@ const Component = ({
|
||||
rel="noreferrer"
|
||||
className="text-emphasis text-sm font-normal no-underline hover:underline"
|
||||
href={docs}>
|
||||
<BookOpen className="text-subtle mr-1 -mt-1 inline h-4 w-4" />
|
||||
<BookOpen className="text-subtle -mt-1 mr-1 inline h-4 w-4" />
|
||||
{t("documentation")}
|
||||
</a>
|
||||
</li>
|
||||
@@ -281,7 +281,7 @@ const Component = ({
|
||||
rel="noreferrer"
|
||||
className="text-emphasis font-normal no-underline hover:underline"
|
||||
href={website}>
|
||||
<ExternalLink className="text-subtle mr-1 -mt-px inline h-4 w-4" />
|
||||
<ExternalLink className="text-subtle -mt-px mr-1 inline h-4 w-4" />
|
||||
{website.replace("https://", "")}
|
||||
</a>
|
||||
</li>
|
||||
@@ -293,7 +293,7 @@ const Component = ({
|
||||
rel="noreferrer"
|
||||
className="text-emphasis font-normal no-underline hover:underline"
|
||||
href={"mailto:" + email}>
|
||||
<Mail className="text-subtle mr-1 -mt-px inline h-4 w-4" />
|
||||
<Mail className="text-subtle -mt-px mr-1 inline h-4 w-4" />
|
||||
|
||||
{email}
|
||||
</a>
|
||||
@@ -306,7 +306,7 @@ const Component = ({
|
||||
rel="noreferrer"
|
||||
className="text-emphasis font-normal no-underline hover:underline"
|
||||
href={tos}>
|
||||
<File className="text-subtle mr-1 -mt-px inline h-4 w-4" />
|
||||
<File className="text-subtle -mt-px mr-1 inline h-4 w-4" />
|
||||
{t("terms_of_service")}
|
||||
</a>
|
||||
</li>
|
||||
@@ -318,7 +318,7 @@ const Component = ({
|
||||
rel="noreferrer"
|
||||
className="text-emphasis font-normal no-underline hover:underline"
|
||||
href={privacy}>
|
||||
<Shield className="text-subtle mr-1 -mt-px inline h-4 w-4" />
|
||||
<Shield className="text-subtle -mt-px mr-1 inline h-4 w-4" />
|
||||
{t("privacy_policy")}
|
||||
</a>
|
||||
</li>
|
||||
|
||||
@@ -28,8 +28,8 @@ export default function AppsLayout({ children, actions, emptyStore, ...rest }: A
|
||||
{emptyStore ? (
|
||||
<EmptyScreen
|
||||
Icon={AlertCircle}
|
||||
headline={t("no_apps")}
|
||||
description={isAdmin ? "You can enable apps in the settings" : ""}
|
||||
headline={isAdmin ? t("no_apps") : t("no_apps_configured")}
|
||||
description={isAdmin ? t("enable_in_settings") : t("please_contact_admin")}
|
||||
buttonText={isAdmin ? t("apps_settings") : ""}
|
||||
buttonOnClick={() => router.push("/settings/admin/apps/calendar")}
|
||||
/>
|
||||
|
||||
@@ -1,6 +1,4 @@
|
||||
import { z } from "zod";
|
||||
|
||||
import { getEventLocationType, locationKeyToString } from "@calcom/app-store/locations";
|
||||
import { getEventLocationType, getTranslatedLocation } from "@calcom/app-store/locations";
|
||||
import { classNames } from "@calcom/lib";
|
||||
import { useLocale } from "@calcom/lib/hooks/useLocale";
|
||||
import { Tooltip } from "@calcom/ui";
|
||||
@@ -23,20 +21,7 @@ export function AvailableEventLocations({ locations }: { locations: Props["event
|
||||
return null;
|
||||
}
|
||||
|
||||
const translateAbleKeys = [
|
||||
"in_person_attendee_address",
|
||||
"in_person",
|
||||
"attendee_phone_number",
|
||||
"link_meeting",
|
||||
"organizer_phone_number",
|
||||
];
|
||||
|
||||
const locationKey = z.string().default("").parse(locationKeyToString(location));
|
||||
const translatedLocation = location.type.startsWith("integrations:")
|
||||
? eventLocationType.label
|
||||
: translateAbleKeys.includes(locationKey)
|
||||
? t(locationKey)
|
||||
: locationKey;
|
||||
const translatedLocation = getTranslatedLocation(location, eventLocationType, t);
|
||||
|
||||
return (
|
||||
<div key={`${location.type}-${index}`} className="flex flex-row items-center text-sm font-medium">
|
||||
|
||||
@@ -83,7 +83,7 @@ const BookingDescription: FC<Props> = (props) => {
|
||||
size="sm"
|
||||
truncateAfter={3}
|
||||
/>
|
||||
<h2 className="text-default mt-1 mb-2 break-words text-sm font-medium ">
|
||||
<h2 className="text-default mb-2 mt-1 break-words text-sm font-medium ">
|
||||
{eventType.team?.parent?.name} {profile.name}
|
||||
</h2>
|
||||
<h1 className="font-cal text-emphasis mb-6 break-words text-2xl font-semibold leading-none">
|
||||
|
||||
@@ -19,7 +19,7 @@ const BookingDescriptionPayment = (props: {
|
||||
|
||||
return (
|
||||
<p className="text-bookinglight -ml-2 px-2 text-sm ">
|
||||
<CreditCard className="ml-[2px] -mt-1 inline-block h-4 w-4 ltr:mr-[10px] rtl:ml-[10px]" />
|
||||
<CreditCard className="-mt-1 ml-[2px] inline-block h-4 w-4 ltr:mr-[10px] rtl:ml-[10px]" />
|
||||
{paymentAppData.paymentOption === "HOLD" ? (
|
||||
<>{props.t("no_show_fee_amount", params)}</>
|
||||
) : (
|
||||
|
||||
@@ -374,7 +374,7 @@ function BookingListItem(booking: BookingItemProps) {
|
||||
</td>
|
||||
<td className={"w-full px-4" + (isRejected ? " line-through" : "")} onClick={onClickTableData}>
|
||||
{/* Time and Badges for mobile */}
|
||||
<div className="w-full pt-4 pb-2 sm:hidden">
|
||||
<div className="w-full pb-2 pt-4 sm:hidden">
|
||||
<div className="flex w-full items-center justify-between sm:hidden">
|
||||
<div className="text-emphasis text-sm leading-6">{startTime}</div>
|
||||
<div className="text-subtle pr-2 text-sm">
|
||||
@@ -423,7 +423,7 @@ function BookingListItem(booking: BookingItemProps) {
|
||||
<span> </span>
|
||||
|
||||
{paymentAppData.enabled && !booking.paid && booking.payment.length && (
|
||||
<Badge className="ms-2 me-2 hidden sm:inline-flex" variant="orange">
|
||||
<Badge className="me-2 ms-2 hidden sm:inline-flex" variant="orange">
|
||||
{t("pending_payment")}
|
||||
</Badge>
|
||||
)}
|
||||
|
||||
@@ -64,7 +64,7 @@ export default function CancelBooking(props: Props) {
|
||||
placeholder={t("cancellation_reason_placeholder")}
|
||||
value={cancellationReason}
|
||||
onChange={(e) => setCancellationReason(e.target.value)}
|
||||
className="mt-2 mb-4 w-full "
|
||||
className="mb-4 mt-2 w-full "
|
||||
rows={3}
|
||||
/>
|
||||
<div className="flex flex-col-reverse rtl:space-x-reverse ">
|
||||
|
||||
@@ -25,7 +25,7 @@ function SkeletonItem() {
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div className="mt-4 hidden flex-shrink-0 sm:mt-0 sm:ml-5 lg:flex">
|
||||
<div className="mt-4 hidden flex-shrink-0 sm:ml-5 sm:mt-0 lg:flex">
|
||||
<div className="flex justify-between space-x-2 rtl:space-x-reverse">
|
||||
<SkeletonText className="h-6 w-16" />
|
||||
<SkeletonText className="h-6 w-32" />
|
||||
|
||||
@@ -191,9 +191,9 @@ const AvailabilityPage = ({ profile, eventType, ...restProps }: Props) => {
|
||||
)}
|
||||
{!rescheduleUid && eventType.recurringEvent && (
|
||||
<div className="flex items-start text-sm font-medium">
|
||||
<RefreshCcw className="float-left mt-[7px] ml-[2px] inline-block h-4 w-4 ltr:mr-[10px] rtl:ml-[10px] " />
|
||||
<RefreshCcw className="float-left ml-[2px] mt-[7px] inline-block h-4 w-4 ltr:mr-[10px] rtl:ml-[10px] " />
|
||||
<div>
|
||||
<p className="mb-1 -ml-2 inline px-2 py-1">
|
||||
<p className="-ml-2 mb-1 inline px-2 py-1">
|
||||
{getRecurringFreq({ t, recurringEvent: eventType.recurringEvent })}
|
||||
</p>
|
||||
|
||||
@@ -218,7 +218,7 @@ const AvailabilityPage = ({ profile, eventType, ...restProps }: Props) => {
|
||||
)}
|
||||
{paymentAppData.price > 0 && (
|
||||
<p className="-ml-2 px-2 text-sm font-medium">
|
||||
<CreditCard className="ml-[2px] -mt-1 inline-block h-4 w-4 ltr:mr-[10px] rtl:ml-[10px]" />
|
||||
<CreditCard className="-mt-1 ml-[2px] inline-block h-4 w-4 ltr:mr-[10px] rtl:ml-[10px]" />
|
||||
{paymentAppData.paymentOption === "HOLD" ? (
|
||||
<>
|
||||
{t("no_show_fee_amount", {
|
||||
|
||||
@@ -534,7 +534,7 @@ 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 pb-0 pt-6 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 && (
|
||||
@@ -561,7 +561,7 @@ const BookingPage = ({
|
||||
recurringStrings.slice(0, 5).map((timeFormatted, key) => {
|
||||
return <p key={key}>{timeFormatted}</p>;
|
||||
})}
|
||||
{!rescheduleUid && eventType.recurringEvent?.freq && recurringStrings.length > 5 && (
|
||||
{!rescheduleUid && eventType?.recurringEvent?.freq && recurringStrings.length > 5 && (
|
||||
<div className="flex">
|
||||
<Tooltip
|
||||
content={recurringStrings.slice(5).map((timeFormatted, key) => (
|
||||
@@ -577,11 +577,11 @@ const BookingPage = ({
|
||||
</div>
|
||||
{booking?.startTime && rescheduleUid && (
|
||||
<div>
|
||||
<p className="mt-8 mb-2 text-sm " data-testid="former_time_p">
|
||||
<p className="mb-2 mt-8 text-sm " data-testid="former_time_p">
|
||||
{t("former_time")}
|
||||
</p>
|
||||
<p className="line-through ">
|
||||
<Calendar className="ml-[2px] -mt-1 inline-block h-4 w-4 ltr:mr-[10px] rtl:ml-[10px]" />
|
||||
<Calendar className="-mt-1 ml-[2px] inline-block h-4 w-4 ltr:mr-[10px] rtl:ml-[10px]" />
|
||||
{isClientTimezoneAvailable &&
|
||||
typeof booking.startTime === "string" &&
|
||||
parseDateTimeWithTimeZone(booking.startTime, i18n.language, timezone, {
|
||||
|
||||
@@ -259,7 +259,7 @@ export const EditLocationDialog = (props: ISetLocationDialog) => {
|
||||
|
||||
{booking && (
|
||||
<>
|
||||
<p className="text-emphasis mt-6 mb-2 ml-1 text-sm font-bold">{t("current_location")}:</p>
|
||||
<p className="text-emphasis mb-2 ml-1 mt-6 text-sm font-bold">{t("current_location")}:</p>
|
||||
<p className="text-emphasis mb-2 ml-1 text-sm">
|
||||
{getHumanReadableLocationValue(booking.location, t)}
|
||||
</p>
|
||||
|
||||
@@ -49,7 +49,7 @@ export const RescheduleDialog = (props: IRescheduleDialog) => {
|
||||
<div className="pt-1">
|
||||
<DialogHeader title={t("send_reschedule_request")} />
|
||||
<p className="text-subtle text-sm">{t("reschedule_modal_description")}</p>
|
||||
<p className="text-emphasis mt-6 mb-2 text-sm font-bold">
|
||||
<p className="text-emphasis mb-2 mt-6 text-sm font-bold">
|
||||
{t("reason_for_reschedule_request")}
|
||||
<span className="text-subtle font-normal"> (Optional)</span>
|
||||
</p>
|
||||
|
||||
@@ -35,7 +35,7 @@ const ErrorDebugPanel: React.FC<{ error: Props["error"]; children?: never }> = (
|
||||
{debugMap.map(([key, value]) => {
|
||||
if (value !== undefined) {
|
||||
return (
|
||||
<div key={key} className="py-4 sm:grid sm:grid-cols-3 sm:gap-4 sm:py-5 sm:px-6">
|
||||
<div key={key} className="py-4 sm:grid sm:grid-cols-3 sm:gap-4 sm:px-6 sm:py-5">
|
||||
<dt className="text-emphasis text-sm font-bold">{key}</dt>
|
||||
<dd className="text-emphasis mt-1 text-sm sm:col-span-2 sm:mt-0">{value}</dd>
|
||||
</div>
|
||||
|
||||
@@ -84,7 +84,7 @@ const CustomEventTypeModalForm: FC<CustomEventTypeModalFormProps> = (props) => {
|
||||
backgroundImage: "url(/calendar-preview.svg)",
|
||||
}}>
|
||||
<div className="m-auto flex items-center justify-center self-stretch">
|
||||
<div className="bg-subtle mt-3 ml-11 box-border h-[110px] w-[120px] flex-col items-start gap-1 rounded-md border border-solid border-black text-[12px] leading-3">
|
||||
<div className="bg-subtle ml-11 mt-3 box-border h-[110px] w-[120px] flex-col items-start gap-1 rounded-md border border-solid border-black text-[12px] leading-3">
|
||||
<p className="text-emphasis overflow-hidden text-ellipsis p-1.5 font-medium">{previewText}</p>
|
||||
<p className="text-default ml-1.5 text-[10px] font-normal">8 - 10 AM</p>
|
||||
</div>
|
||||
|
||||
@@ -38,6 +38,7 @@ const generateHashedLink = (id: number) => {
|
||||
|
||||
export const EventAdvancedTab = ({ eventType, team }: Pick<EventTypeSetupProps, "eventType" | "team">) => {
|
||||
const connectedCalendarsQuery = trpc.viewer.connectedCalendars.useQuery();
|
||||
const { data: user } = trpc.viewer.me.useQuery();
|
||||
const formMethods = useFormContext<FormValues>();
|
||||
const { t } = useLocale();
|
||||
|
||||
@@ -49,6 +50,9 @@ export const EventAdvancedTab = ({ eventType, team }: Pick<EventTypeSetupProps,
|
||||
const bookingFields: Prisma.JsonObject = {};
|
||||
|
||||
const workflows = eventType.workflows.map((workflowOnEventType) => workflowOnEventType.workflow);
|
||||
const selectedThemeIsDark =
|
||||
user?.theme === "dark" ||
|
||||
(!user?.theme && typeof document !== "undefined" && document.documentElement.classList.contains("dark"));
|
||||
|
||||
eventType.bookingFields.forEach(({ name }) => {
|
||||
bookingFields[name] = name + " input";
|
||||
@@ -153,7 +157,7 @@ export const EventAdvancedTab = ({ eventType, team }: Pick<EventTypeSetupProps,
|
||||
color="minimal"
|
||||
size="sm"
|
||||
aria-label="edit custom name"
|
||||
className="hover:stroke-3 hover:text-emphasis min-w-fit px-0 !py-0 hover:bg-transparent"
|
||||
className="hover:stroke-3 hover:text-emphasis min-w-fit !py-0 px-0 hover:bg-transparent"
|
||||
onClick={() => setShowEventNameTip((old) => !old)}>
|
||||
<Edit className="h-4 w-4" />
|
||||
</Button>
|
||||
@@ -162,7 +166,7 @@ export const EventAdvancedTab = ({ eventType, team }: Pick<EventTypeSetupProps,
|
||||
</div>
|
||||
<hr className="border-subtle [&:has(+div:empty)]:hidden" />
|
||||
<div>
|
||||
<BookerLayoutSelector fallbackToUserSettings />
|
||||
<BookerLayoutSelector fallbackToUserSettings isDark={selectedThemeIsDark} />
|
||||
</div>
|
||||
<hr className="border-subtle" />
|
||||
<FormBuilder
|
||||
@@ -216,7 +220,7 @@ export const EventAdvancedTab = ({ eventType, team }: Pick<EventTypeSetupProps,
|
||||
onChange(e ? value : "");
|
||||
}}>
|
||||
{/* Textfield has some margin by default we remove that so we can keep consistent alignment */}
|
||||
<div className="lg:-ml-2 lg:-mb-2">
|
||||
<div className="lg:-mb-2 lg:-ml-2">
|
||||
<TextField
|
||||
className="w-full"
|
||||
label={t("redirect_success_booking")}
|
||||
@@ -268,7 +272,7 @@ export const EventAdvancedTab = ({ eventType, team }: Pick<EventTypeSetupProps,
|
||||
color="minimal"
|
||||
size="sm"
|
||||
type="button"
|
||||
className="hover:stroke-3 hover:text-emphasis min-w-fit px-0 !py-0 hover:bg-transparent"
|
||||
className="hover:stroke-3 hover:text-emphasis min-w-fit !py-0 px-0 hover:bg-transparent"
|
||||
aria-label="copy link"
|
||||
onClick={() => {
|
||||
navigator.clipboard.writeText(placeholderHashedLink);
|
||||
|
||||
@@ -171,7 +171,7 @@ export const EventLimitsTab = ({ eventType }: Pick<EventTypeSetupProps, "eventTy
|
||||
return (
|
||||
<div className="space-y-8">
|
||||
<div className="space-y-4 lg:space-y-8">
|
||||
<div className="flex flex-col space-y-4 lg:flex-row lg:space-y-0 lg:space-x-4">
|
||||
<div className="flex flex-col space-y-4 lg:flex-row lg:space-x-4 lg:space-y-0">
|
||||
<div className="w-full">
|
||||
<Label htmlFor="beforeBufferTime">
|
||||
{t("before_event")}
|
||||
@@ -245,7 +245,7 @@ export const EventLimitsTab = ({ eventType }: Pick<EventTypeSetupProps, "eventTy
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
<div className="flex flex-col space-y-4 lg:flex-row lg:space-y-0 lg:space-x-4">
|
||||
<div className="flex flex-col space-y-4 lg:flex-row lg:space-x-4 lg:space-y-0">
|
||||
<div className="w-full">
|
||||
<Label htmlFor="minimumBookingNotice">
|
||||
{t("minimum_booking_notice")}
|
||||
@@ -416,7 +416,7 @@ export const EventLimitsTab = ({ eventType }: Pick<EventTypeSetupProps, "eventTy
|
||||
</div>
|
||||
)}
|
||||
{period.type === "RANGE" && (
|
||||
<div className="ms-2 me-2 inline-flex space-x-2 rtl:space-x-reverse">
|
||||
<div className="me-2 ms-2 inline-flex space-x-2 rtl:space-x-reverse">
|
||||
<Controller
|
||||
name="periodDates"
|
||||
control={formMethods.control}
|
||||
@@ -437,7 +437,7 @@ export const EventLimitsTab = ({ eventType }: Pick<EventTypeSetupProps, "eventTy
|
||||
/>
|
||||
</div>
|
||||
)}
|
||||
{period.suffix ? <span className="ms-2 me-2"> {period.suffix}</span> : null}
|
||||
{period.suffix ? <span className="me-2 ms-2"> {period.suffix}</span> : null}
|
||||
</div>
|
||||
);
|
||||
})}
|
||||
|
||||
@@ -284,7 +284,7 @@ export const EventSetupTab = (
|
||||
return (
|
||||
<li
|
||||
key={`${location.type}${index}`}
|
||||
className="border-default text-default mb-2 h-9 rounded-md border py-1.5 px-2 hover:cursor-pointer">
|
||||
className="border-default text-default mb-2 h-9 rounded-md border px-2 py-1.5 hover:cursor-pointer">
|
||||
<div className="flex items-center justify-between">
|
||||
<div className="flex items-center">
|
||||
<img
|
||||
@@ -298,7 +298,7 @@ export const EventSetupTab = (
|
||||
)}
|
||||
alt={`${eventLocationType.label} logo`}
|
||||
/>
|
||||
<span className="line-clamp-1 ms-1 text-sm">{eventLabel}</span>
|
||||
<span className="ms-1 line-clamp-1 text-sm">{eventLabel}</span>
|
||||
</div>
|
||||
<div className="flex">
|
||||
<button
|
||||
@@ -332,7 +332,7 @@ export const EventSetupTab = (
|
||||
location.type === MeetLocationType && destinationCalendar?.integration !== "google_calendar"
|
||||
) && (
|
||||
<div className="text-default flex text-sm">
|
||||
<Check className="mt-0.5 mr-1.5 h-2 w-2.5" />
|
||||
<Check className="mr-1.5 mt-0.5 h-2 w-2.5" />
|
||||
<Trans i18nKey="event_type_requres_google_cal">
|
||||
<p>
|
||||
The “Add to calendar” for this event type needs to be a Google Calendar for Meet to work.
|
||||
|
||||
@@ -369,7 +369,7 @@ function EventTypeSingleLayout({
|
||||
</DropdownItem>
|
||||
</DropdownMenuItem>
|
||||
<DropdownMenuSeparator />
|
||||
<div className="hover:bg-subtle flex h-9 cursor-pointer flex-row items-center justify-between py-2 px-4">
|
||||
<div className="hover:bg-subtle flex h-9 cursor-pointer flex-row items-center justify-between px-4 py-2">
|
||||
<Skeleton
|
||||
as={Label}
|
||||
htmlFor="hiddenSwitch"
|
||||
|
||||
@@ -32,11 +32,11 @@ function SkeletonItem() {
|
||||
<div className="">
|
||||
<ul className="mt-2 flex space-x-4 rtl:space-x-reverse ">
|
||||
<li className="flex items-center whitespace-nowrap">
|
||||
<Clock className="text-subtle mt-0.5 mr-1.5 inline h-4 w-4" />
|
||||
<Clock className="text-subtle mr-1.5 mt-0.5 inline h-4 w-4" />
|
||||
<SkeletonText className="h-4 w-12" />
|
||||
</li>
|
||||
<li className="flex items-center whitespace-nowrap">
|
||||
<User className="text-subtle mt-0.5 mr-1.5 inline h-4 w-4" />
|
||||
<User className="text-subtle mr-1.5 mt-0.5 inline h-4 w-4" />
|
||||
<SkeletonText className="h-4 w-12" />
|
||||
</li>
|
||||
</ul>
|
||||
|
||||
@@ -3,7 +3,7 @@ import { useForm } from "react-hook-form";
|
||||
|
||||
import { ErrorCode } from "@calcom/features/auth/lib/ErrorCode";
|
||||
import { useLocale } from "@calcom/lib/hooks/useLocale";
|
||||
import { Button, Dialog, DialogContent, Form, Label, PasswordField } from "@calcom/ui";
|
||||
import { Button, Dialog, DialogContent, Form, PasswordField } from "@calcom/ui";
|
||||
|
||||
import TwoFactor from "@components/auth/TwoFactor";
|
||||
|
||||
@@ -75,14 +75,12 @@ const DisableTwoFactorAuthModal = ({ onDisable, onCancel }: DisableTwoFactorAuth
|
||||
{...form.register("password")}
|
||||
className="border-default mt-1 block w-full rounded-md border px-3 py-2 text-sm focus:border-black focus:outline-none focus:ring-black"
|
||||
/>
|
||||
<Label className="mt-4"> {t("2fa_code")}</Label>
|
||||
|
||||
<TwoFactor center={false} />
|
||||
{errorMessage && <p className="mt-1 text-sm text-red-700">{errorMessage}</p>}
|
||||
</div>
|
||||
|
||||
<div className="mt-5 sm:mt-4 sm:flex sm:flex-row-reverse">
|
||||
<Button type="submit" className="ms-2 me-2" disabled={isDisabling}>
|
||||
<Button type="submit" className="me-2 ms-2" disabled={isDisabling}>
|
||||
{t("disable")}
|
||||
</Button>
|
||||
<Button color="secondary" onClick={onCancel}>
|
||||
|
||||
@@ -19,7 +19,7 @@ const DisableUserImpersonation = ({ disableImpersonation }: { disableImpersonati
|
||||
|
||||
return (
|
||||
<>
|
||||
<div className="flex flex-col justify-between pt-9 pl-2 sm:flex-row">
|
||||
<div className="flex flex-col justify-between pl-2 pt-9 sm:flex-row">
|
||||
<div>
|
||||
<div className="flex flex-row items-center">
|
||||
<h2 className="font-cal text-emphasis text-lg font-medium leading-6">
|
||||
|
||||
@@ -178,19 +178,19 @@ const EnableTwoFactorModal = ({ onEnable, onCancel }: EnableTwoFactorModalProps)
|
||||
<WithStep step={SetupStep.ConfirmPassword} current={step}>
|
||||
<Button
|
||||
type="submit"
|
||||
className="ms-2 me-2"
|
||||
className="me-2 ms-2"
|
||||
onClick={handleSetup}
|
||||
disabled={password.length === 0 || isSubmitting}>
|
||||
{t("continue")}
|
||||
</Button>
|
||||
</WithStep>
|
||||
<WithStep step={SetupStep.DisplayQrCode} current={step}>
|
||||
<Button type="submit" className="ms-2 me-2" onClick={() => setStep(SetupStep.EnterTotpCode)}>
|
||||
<Button type="submit" className="me-2 ms-2" onClick={() => setStep(SetupStep.EnterTotpCode)}>
|
||||
{t("continue")}
|
||||
</Button>
|
||||
</WithStep>
|
||||
<WithStep step={SetupStep.EnterTotpCode} current={step}>
|
||||
<Button type="submit" className="ms-2 me-2" disabled={isSubmitting}>
|
||||
<Button type="submit" className="me-2 ms-2" disabled={isSubmitting}>
|
||||
{t("enable")}
|
||||
</Button>
|
||||
</WithStep>
|
||||
|
||||
@@ -14,7 +14,7 @@ const TwoFactorAuthSection = ({ twoFactorEnabled }: { twoFactorEnabled: boolean
|
||||
|
||||
return (
|
||||
<>
|
||||
<div className="flex flex-col justify-between pt-9 pl-2 sm:flex-row">
|
||||
<div className="flex flex-col justify-between pl-2 pt-9 sm:flex-row">
|
||||
<div>
|
||||
<div className="flex flex-row items-center">
|
||||
<h2 className="font-cal text-emphasis text-lg font-medium leading-6">{t("2fa")}</h2>
|
||||
|
||||
@@ -6,7 +6,7 @@ const TwoFactorModalHeader = ({ title, description }: { title: string; descripti
|
||||
<div className="bg-brand text-brandcontrast dark:bg-darkmodebrand dark:text-darkmodebrandcontrast mx-auto flex h-12 w-12 flex-shrink-0 items-center justify-center rounded-full bg-opacity-5 sm:mx-0 sm:h-10 sm:w-10">
|
||||
<Shield className="text-inverted h-6 w-6" />
|
||||
</div>
|
||||
<div className="mt-3 text-center sm:mt-0 sm:ml-4 sm:text-left">
|
||||
<div className="mt-3 text-center sm:ml-4 sm:mt-0 sm:text-left">
|
||||
<h3 className="font-cal text-emphasis text-lg font-medium leading-6" id="modal-title">
|
||||
{title}
|
||||
</h3>
|
||||
|
||||
@@ -3,7 +3,7 @@ import { useForm } from "react-hook-form";
|
||||
|
||||
import { ErrorCode } from "@calcom/features/auth/lib/ErrorCode";
|
||||
import { useLocale } from "@calcom/lib/hooks/useLocale";
|
||||
import { Button, Dialog, DialogContent, Form, Label, PasswordField } from "@calcom/ui";
|
||||
import { Button, Dialog, DialogContent, Form, PasswordField } from "@calcom/ui";
|
||||
|
||||
import TwoFactor from "@components/auth/TwoFactor";
|
||||
|
||||
@@ -82,15 +82,13 @@ const DisableTwoFactorAuthModal = ({
|
||||
{...form.register("password")}
|
||||
className="border-default mt-1 block w-full rounded-md border px-3 py-2 text-sm focus:border-black focus:outline-none focus:ring-black"
|
||||
/>
|
||||
<Label className="mt-4"> {t("2fa_code")}</Label>
|
||||
|
||||
<TwoFactor center={false} />
|
||||
|
||||
{errorMessage && <p className="mt-1 text-sm text-red-700">{errorMessage}</p>}
|
||||
</div>
|
||||
|
||||
<div className="mt-5 sm:mt-4 sm:flex sm:flex-row-reverse">
|
||||
<Button type="submit" className="ms-2 me-2" data-testid="disable-2fa" disabled={isDisabling}>
|
||||
<Button type="submit" className="me-2 ms-2" data-testid="disable-2fa" disabled={isDisabling}>
|
||||
{t("disable")}
|
||||
</Button>
|
||||
<Button color="secondary" onClick={onCancel}>
|
||||
|
||||
@@ -177,7 +177,7 @@ const EnableTwoFactorModal = ({ onEnable, onCancel, open, onOpenChange }: Enable
|
||||
<WithStep step={SetupStep.ConfirmPassword} current={step}>
|
||||
<Button
|
||||
type="submit"
|
||||
className="ms-2 me-2"
|
||||
className="me-2 ms-2"
|
||||
onClick={handleSetup}
|
||||
disabled={password.length === 0 || isSubmitting}>
|
||||
{t("continue")}
|
||||
@@ -187,13 +187,13 @@ const EnableTwoFactorModal = ({ onEnable, onCancel, open, onOpenChange }: Enable
|
||||
<Button
|
||||
type="submit"
|
||||
data-testid="goto-otp-screen"
|
||||
className="ms-2 me-2"
|
||||
className="me-2 ms-2"
|
||||
onClick={() => setStep(SetupStep.EnterTotpCode)}>
|
||||
{t("continue")}
|
||||
</Button>
|
||||
</WithStep>
|
||||
<WithStep step={SetupStep.EnterTotpCode} current={step}>
|
||||
<Button type="submit" className="ms-2 me-2" data-testid="enable-2fa" disabled={isSubmitting}>
|
||||
<Button type="submit" className="me-2 ms-2" data-testid="enable-2fa" disabled={isSubmitting}>
|
||||
{t("enable")}
|
||||
</Button>
|
||||
</WithStep>
|
||||
|
||||
@@ -111,7 +111,7 @@ export const AdminUser = (props: { onSubmit: () => void; onError: () => void; on
|
||||
<Label htmlFor="username" className={classNames(longWebsiteUrl && "mb-0")}>
|
||||
<span className="block">{t("username")}</span>
|
||||
{longWebsiteUrl && (
|
||||
<small className="items-centerpx-3 bg-subtle border-default text-subtle mt-2 inline-flex rounded-t-md border border-b-0 py-1 px-3">
|
||||
<small className="items-centerpx-3 bg-subtle border-default text-subtle mt-2 inline-flex rounded-t-md border border-b-0 px-3 py-1">
|
||||
{process.env.NEXT_PUBLIC_WEBSITE_URL}
|
||||
</small>
|
||||
)}
|
||||
|
||||
@@ -27,7 +27,7 @@ const StepDone = (props: {
|
||||
<Check className="text-inverted dark:bg-default dark:text-default inline-block h-10 w-10" />
|
||||
</div>
|
||||
<div className="max-w-[420px] text-center">
|
||||
<h2 className="mt-6 mb-1 text-lg font-medium dark:text-gray-300">{t("all_done")}</h2>
|
||||
<h2 className="mb-1 mt-6 text-lg font-medium dark:text-gray-300">{t("all_done")}</h2>
|
||||
</div>
|
||||
</div>
|
||||
</form>
|
||||
|
||||
@@ -30,9 +30,9 @@ const Member = ({ member, teamName }: { member: MemberType; teamName: string | n
|
||||
alt={member.name || ""}
|
||||
imageSrc={WEBAPP_URL + "/" + member.username + "/avatar.png"}
|
||||
/>
|
||||
<section className="line-clamp-4 mt-2 w-full space-y-1">
|
||||
<section className="mt-2 line-clamp-4 w-full space-y-1">
|
||||
<p className="text-default font-medium">{member.name}</p>
|
||||
<div className="line-clamp-3 text-subtle overflow-ellipsis text-sm font-normal">
|
||||
<div className="text-subtle line-clamp-3 overflow-ellipsis text-sm font-normal">
|
||||
{!isBioEmpty ? (
|
||||
<>
|
||||
<div
|
||||
|
||||
@@ -23,7 +23,7 @@ const EditableHeading = function EditableHeading({
|
||||
<label className="min-w-8 relative inline-block">
|
||||
<span className="whitespace-pre text-xl tracking-normal text-transparent">{value} </span>
|
||||
{!isEditing && isReady && (
|
||||
<Edit2 className=" text-subtle group-hover:text-subtle ml-1 -mt-px inline h-3 w-3" />
|
||||
<Edit2 className=" text-subtle group-hover:text-subtle -mt-px ml-1 inline h-3 w-3" />
|
||||
)}
|
||||
<input
|
||||
{...passThroughProps}
|
||||
@@ -31,7 +31,7 @@ const EditableHeading = function EditableHeading({
|
||||
value={value}
|
||||
required
|
||||
className={classNames(
|
||||
"text-emphasis hover:text-default focus:text-emphasis absolute top-0 left-0 w-full cursor-pointer truncate border-none bg-transparent p-0 align-top text-xl focus:outline-none focus:ring-0"
|
||||
"text-emphasis hover:text-default focus:text-emphasis absolute left-0 top-0 w-full cursor-pointer truncate border-none bg-transparent p-0 align-top text-xl focus:outline-none focus:ring-0"
|
||||
)}
|
||||
onFocus={(e) => {
|
||||
setIsEditing(true);
|
||||
|
||||
@@ -6,7 +6,7 @@ export default function InfoBadge({ content }: { content: string }) {
|
||||
<>
|
||||
<Tooltip side="top" content={content}>
|
||||
<span title={content}>
|
||||
<Info className="text-subtle relative top-px left-1 right-1 mt-px h-4 w-4" />
|
||||
<Info className="text-subtle relative left-1 right-1 top-px mt-px h-4 w-4" />
|
||||
</span>
|
||||
</Tooltip>
|
||||
</>
|
||||
|
||||
@@ -14,7 +14,7 @@ export default function ModalContainer(
|
||||
}>
|
||||
) {
|
||||
return (
|
||||
<div className="flex min-h-screen items-end justify-center px-4 pt-4 pb-20 text-center sm:block sm:p-0">
|
||||
<div className="flex min-h-screen items-end justify-center px-4 pb-20 pt-4 text-center sm:block sm:p-0">
|
||||
<Dialog open={props.isOpen} onOpenChange={props.onExit}>
|
||||
<DialogContent>
|
||||
<div
|
||||
|
||||
@@ -224,7 +224,7 @@ const PremiumTextfield = (props: ICustomUsernameProps) => {
|
||||
}}
|
||||
data-testid="username-input"
|
||||
/>
|
||||
<div className="absolute top-0 right-2 flex flex-row">
|
||||
<div className="absolute right-2 top-0 flex flex-row">
|
||||
<span
|
||||
className={classNames(
|
||||
"mx-2 py-2",
|
||||
|
||||
@@ -80,7 +80,7 @@ const UsernameTextfield = (props: ICustomUsernameProps & Partial<React.Component
|
||||
|
||||
const ActionButtons = () => {
|
||||
return usernameIsAvailable && currentUsername !== inputUsernameValue ? (
|
||||
<div className="ms-2 me-2 flex flex-row space-x-2">
|
||||
<div className="me-2 ms-2 flex flex-row space-x-2">
|
||||
<Button
|
||||
type="button"
|
||||
onClick={() => setOpenDialogSaveUsername(true)}
|
||||
|
||||
@@ -5,7 +5,8 @@ import type { GetServerSidePropsContext } from "next";
|
||||
import { getServerSession } from "@calcom/features/auth/lib/getServerSession";
|
||||
import type { Maybe } from "@calcom/trpc/server";
|
||||
|
||||
import { i18n } from "../../../next-i18next.config";
|
||||
// eslint-disable-next-line @typescript-eslint/no-var-requires
|
||||
const { i18n } = require("@calcom/config/next-i18next.config");
|
||||
|
||||
export function getLocaleFromHeaders(req: IncomingMessage): string {
|
||||
let preferredLocale: string | null | undefined;
|
||||
|
||||
@@ -3,7 +3,6 @@ import { collectEvents } from "next-collect/server";
|
||||
import type { NextMiddleware } from "next/server";
|
||||
import { NextResponse, userAgent } from "next/server";
|
||||
|
||||
import { orgDomainConfig } from "@calcom/features/ee/organizations/lib/orgDomains";
|
||||
import { CONSOLE_URL, WEBAPP_URL, WEBSITE_URL } from "@calcom/lib/constants";
|
||||
import { isIpInBanlist } from "@calcom/lib/getIP";
|
||||
import { extendEventData, nextCollectBasicSettings } from "@calcom/lib/telemetry";
|
||||
@@ -11,20 +10,11 @@ import { extendEventData, nextCollectBasicSettings } from "@calcom/lib/telemetry
|
||||
const middleware: NextMiddleware = async (req) => {
|
||||
const url = req.nextUrl;
|
||||
const requestHeaders = new Headers(req.headers);
|
||||
const { currentOrgDomain, isValidOrgDomain } = orgDomainConfig(req.headers.get("host") ?? "");
|
||||
/**
|
||||
* We are using env variable to toggle new-booker because using flags would be an unnecessary delay for booking pages
|
||||
* Also, we can't easily identify the booker page requests here(to just fetch the flags for those requests)
|
||||
*/
|
||||
|
||||
// Make sure we are in the presence of an organization
|
||||
if (isValidOrgDomain && url.pathname === "/") {
|
||||
// In the presence of an organization, cover its profile page at "/"
|
||||
// rewrites for org profile page using team profile page
|
||||
url.pathname = `/org/${currentOrgDomain}`;
|
||||
return NextResponse.rewrite(url);
|
||||
}
|
||||
|
||||
if (isIpInBanlist(req) && url.pathname !== "/api/nope") {
|
||||
// DDOS Prevention: Immediately end request with no response - Avoids a redirect as well initiated by NextAuth on invalid callback
|
||||
req.nextUrl.pathname = "/api/nope";
|
||||
@@ -83,26 +73,6 @@ const middleware: NextMiddleware = async (req) => {
|
||||
requestHeaders.set("x-csp-enforce", "true");
|
||||
}
|
||||
|
||||
if (isValidOrgDomain) {
|
||||
// Match /:slug to determine if it corresponds to org subteam slug or org user slug
|
||||
const slugs = /^\/([^/]+)(\/[^/]+)?$/.exec(url.pathname);
|
||||
// In the presence of an organization, if not team profile, a user or team is being accessed
|
||||
if (slugs) {
|
||||
const [_, teamName, eventType] = slugs;
|
||||
// Fetch the corresponding subteams for the entered organization
|
||||
const getSubteams = await fetch(`${WEBAPP_URL}/api/organizations/${currentOrgDomain}/subteams`);
|
||||
if (getSubteams.ok) {
|
||||
const data = await getSubteams.json();
|
||||
// Treat entered slug as a team if found in the subteams fetched
|
||||
if (data.slugs.includes(teamName)) {
|
||||
// Rewriting towards /team/:slug to bring up the team profile within the org
|
||||
url.pathname = `/team/${teamName}${eventType ?? ""}`;
|
||||
return NextResponse.rewrite(url);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return NextResponse.next({
|
||||
request: {
|
||||
headers: requestHeaders,
|
||||
|
||||
+68
-27
@@ -1,10 +1,10 @@
|
||||
require("dotenv").config({ path: "../../.env" });
|
||||
const CopyWebpackPlugin = require("copy-webpack-plugin");
|
||||
const os = require("os");
|
||||
const glob = require("glob");
|
||||
const englishTranslation = require("./public/static/locales/en/common.json");
|
||||
const { withAxiom } = require("next-axiom");
|
||||
const { i18n } = require("./next-i18next.config");
|
||||
const { pages } = require("./pages");
|
||||
|
||||
if (!process.env.NEXTAUTH_SECRET) throw new Error("Please set NEXTAUTH_SECRET");
|
||||
if (!process.env.CALENDSO_ENCRYPTION_KEY) throw new Error("Please set CALENDSO_ENCRYPTION_KEY");
|
||||
@@ -71,6 +71,13 @@ const informAboutDuplicateTranslations = () => {
|
||||
|
||||
informAboutDuplicateTranslations();
|
||||
|
||||
const getSubdomain = () => {
|
||||
const _url = new URL(process.env.NEXT_PUBLIC_WEBAPP_URL);
|
||||
const regex = new RegExp(/^([a-z]+\:\/{2})?((?<subdomain>[\w-]+)\.[\w-]+\.\w+)$/);
|
||||
//console.log(_url.hostname, _url.hostname.match(regex));
|
||||
return _url.hostname.match(regex)?.groups?.subdomain || null;
|
||||
};
|
||||
|
||||
const plugins = [];
|
||||
if (process.env.ANALYZE === "true") {
|
||||
// only load dependency if env `ANALYZE` was set
|
||||
@@ -82,16 +89,19 @@ if (process.env.ANALYZE === "true") {
|
||||
|
||||
plugins.push(withAxiom);
|
||||
|
||||
/** Needed to rewrite public booking page, gets all static pages but [user] */
|
||||
const pages = glob
|
||||
.sync("pages/**/[^_]*.{tsx,js,ts}", { cwd: __dirname })
|
||||
.map((filename) =>
|
||||
filename
|
||||
.substr(6)
|
||||
.replace(/(\.tsx|\.js|\.ts)/, "")
|
||||
.replace(/\/.*/, "")
|
||||
)
|
||||
.filter((v, i, self) => self.indexOf(v) === i && !v.startsWith("[user]"));
|
||||
// .* matches / as well(Note: *(i.e wildcard) doesn't match / but .*(i.e. RegExp) does)
|
||||
// It would match /free/30min but not /bookings/upcoming because 'bookings' is an item in pages
|
||||
// It would also not match /free/30min/embed because we are ensuring just two slashes
|
||||
// ?!book ensures it doesn't match /free/book page which doesn't have a corresponding new-booker page.
|
||||
// [^/]+ makes the RegExp match the full path, it seems like a partial match doesn't work.
|
||||
// book$ ensures that only /book is excluded from rewrite(which is at the end always) and not /booked
|
||||
|
||||
// Important Note: When modifying these RegExps update apps/web/test/lib/next-config.test.ts as well
|
||||
const userTypeRouteRegExp = `/:user((?!${pages.join("/|")})[^/]*)/:type((?!book$)[^/]+)`;
|
||||
const teamTypeRouteRegExp = "/team/:slug/:type((?!book$)[^/]+)";
|
||||
const privateLinkRouteRegExp = "/d/:link/:slug((?!book$)[^/]+)";
|
||||
const embedUserTypeRouteRegExp = `/:user((?!${pages.join("/|")})[^/]*)/:type/embed`;
|
||||
const embedTeamTypeRouteRegExp = "/team/:slug/:type/embed";
|
||||
|
||||
/** @type {import("next").NextConfig} */
|
||||
const nextConfig = {
|
||||
@@ -183,15 +193,43 @@ const nextConfig = {
|
||||
return config;
|
||||
},
|
||||
async rewrites() {
|
||||
// .* matches / as well(Note: *(i.e wildcard) doesn't match / but .*(i.e. RegExp) does)
|
||||
// It would match /free/30min but not /bookings/upcoming because 'bookings' is an item in pages
|
||||
// It would also not match /free/30min/embed because we are ensuring just two slashes
|
||||
// ?!book ensures it doesn't match /free/book page which doesn't have a corresponding new-booker page.
|
||||
// [^/]+ makes the RegExp match the full path, it seems like a partial match doesn't work.
|
||||
const userTypeRouteRegExp = `/:user((?!${pages.join("/|")}$)[^/]*)/:type((?!book)[^/]+)`;
|
||||
const teamTypeRouteRegExp = "/team/:slug/:type((?!book)[^/]+)";
|
||||
const privateLinkRouteRegExp = "/d/:link/:slug((?!book)[^/]+)";
|
||||
let rewrites = [
|
||||
const defaultSubdomain = getSubdomain();
|
||||
const subdomain = defaultSubdomain ? `(?!${defaultSubdomain})[^.]+` : "[^.]+";
|
||||
|
||||
const beforeFiles = [
|
||||
{
|
||||
has: [
|
||||
{
|
||||
type: "host",
|
||||
value: `^(?<orgSlug>${subdomain})\\..*`,
|
||||
},
|
||||
],
|
||||
source: "/",
|
||||
destination: "/team/:orgSlug",
|
||||
},
|
||||
{
|
||||
has: [
|
||||
{
|
||||
type: "host",
|
||||
value: `^(?<orgSlug>${subdomain})\\..*`,
|
||||
},
|
||||
],
|
||||
source: `/:user((?!${pages.join("|")}|_next|public)[a-zA-Z0-9\-_]+)`,
|
||||
destination: "/org/:orgSlug/:user",
|
||||
},
|
||||
{
|
||||
has: [
|
||||
{
|
||||
type: "host",
|
||||
value: `^(?<orgSlug>${subdomain}[^.]+)\\..*`,
|
||||
},
|
||||
],
|
||||
source: `/:user((?!${pages.join("|")}|_next|public))/:path*`,
|
||||
destination: "/:user/:path*",
|
||||
},
|
||||
];
|
||||
|
||||
let afterFiles = [
|
||||
{
|
||||
source: "/org/:slug",
|
||||
destination: "/team/:slug",
|
||||
@@ -248,12 +286,12 @@ const nextConfig = {
|
||||
// Keep cookie based booker enabled to test new-booker embed in production
|
||||
...[
|
||||
{
|
||||
source: `/:user((?!${pages.join("|")}).*)/:type/embed`,
|
||||
source: embedUserTypeRouteRegExp,
|
||||
destination: "/new-booker/:user/:type/embed",
|
||||
has: [{ type: "cookie", key: "new-booker-enabled" }],
|
||||
},
|
||||
{
|
||||
source: "/team/:slug/:type/embed",
|
||||
source: embedTeamTypeRouteRegExp,
|
||||
destination: "/new-booker/team/:slug/:type/embed",
|
||||
has: [{ type: "cookie", key: "new-booker-enabled" }],
|
||||
},
|
||||
@@ -271,14 +309,14 @@ const nextConfig = {
|
||||
// Enable New Booker for all Embed Requests
|
||||
if (process.env.NEW_BOOKER_ENABLED_FOR_EMBED === "1") {
|
||||
console.log("Enabling New Booker for Embed");
|
||||
rewrites.push(
|
||||
afterFiles.push(
|
||||
...[
|
||||
{
|
||||
source: `/:user((?!${pages.join("|")}).*)/:type/embed`,
|
||||
source: embedUserTypeRouteRegExp,
|
||||
destination: "/new-booker/:user/:type/embed",
|
||||
},
|
||||
{
|
||||
source: "/team/:slug/:type/embed",
|
||||
source: embedTeamTypeRouteRegExp,
|
||||
destination: "/new-booker/team/:slug/:type/embed",
|
||||
},
|
||||
]
|
||||
@@ -288,7 +326,7 @@ const nextConfig = {
|
||||
// Enable New Booker for All but embed Requests
|
||||
if (process.env.NEW_BOOKER_ENABLED_FOR_NON_EMBED === "1") {
|
||||
console.log("Enabling New Booker for Non-Embed");
|
||||
rewrites.push(
|
||||
afterFiles.push(
|
||||
...[
|
||||
{
|
||||
source: userTypeRouteRegExp,
|
||||
@@ -305,7 +343,10 @@ const nextConfig = {
|
||||
]
|
||||
);
|
||||
}
|
||||
return rewrites;
|
||||
return {
|
||||
beforeFiles,
|
||||
afterFiles,
|
||||
};
|
||||
},
|
||||
async headers() {
|
||||
return [
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "@calcom/web",
|
||||
"version": "3.0.4",
|
||||
"version": "3.0.7",
|
||||
"private": true,
|
||||
"scripts": {
|
||||
"analyze": "ANALYZE=true next build",
|
||||
@@ -48,7 +48,7 @@
|
||||
"@radix-ui/react-avatar": "^1.0.0",
|
||||
"@radix-ui/react-collapsible": "^1.0.0",
|
||||
"@radix-ui/react-dialog": "^1.0.0",
|
||||
"@radix-ui/react-dropdown-menu": "^1.0.0",
|
||||
"@radix-ui/react-dropdown-menu": "^2.0.5",
|
||||
"@radix-ui/react-id": "^1.0.0",
|
||||
"@radix-ui/react-popover": "^1.0.2",
|
||||
"@radix-ui/react-radio-group": "^1.0.0",
|
||||
@@ -90,10 +90,10 @@
|
||||
"mime-types": "^2.1.35",
|
||||
"next": "^13.4.6",
|
||||
"next-auth": "^4.20.1",
|
||||
"next-axiom": "^0.16.0",
|
||||
"next-axiom": "^0.17.0",
|
||||
"next-collect": "^0.2.1",
|
||||
"next-i18next": "^11.3.0",
|
||||
"next-seo": "^4.26.0",
|
||||
"next-i18next": "^13.2.2",
|
||||
"next-seo": "^6.0.0",
|
||||
"next-themes": "^0.2.0",
|
||||
"nodemailer": "^6.7.8",
|
||||
"otplib": "^12.0.1",
|
||||
@@ -109,7 +109,7 @@
|
||||
"react-hook-form": "^7.43.3",
|
||||
"react-hot-toast": "^2.3.0",
|
||||
"react-intl": "^5.25.1",
|
||||
"react-live-chat-loader": "^2.7.3",
|
||||
"react-live-chat-loader": "^2.8.1",
|
||||
"react-multi-email": "^0.5.3",
|
||||
"react-phone-input-2": "^2.15.1",
|
||||
"react-phone-number-input": "^3.2.7",
|
||||
@@ -162,7 +162,6 @@
|
||||
"copy-webpack-plugin": "^11.0.0",
|
||||
"detect-port": "^1.3.0",
|
||||
"env-cmd": "^10.1.0",
|
||||
"mockdate": "^3.0.5",
|
||||
"module-alias": "^2.2.2",
|
||||
"msw": "^0.42.3",
|
||||
"postcss": "^8.4.18",
|
||||
|
||||
@@ -0,0 +1,14 @@
|
||||
const glob = require("glob");
|
||||
|
||||
/** Needed to rewrite public booking page, gets all static pages but [user] */
|
||||
const pages = glob
|
||||
.sync("pages/**/[^_]*.{tsx,js,ts}", { cwd: __dirname })
|
||||
.map((filename) =>
|
||||
filename
|
||||
.substr(6)
|
||||
.replace(/(\.tsx|\.js|\.ts)/, "")
|
||||
.replace(/\/.*/, "")
|
||||
)
|
||||
.filter((v, i, self) => self.indexOf(v) === i && !v.startsWith("[user]"));
|
||||
|
||||
exports.pages = pages;
|
||||
+92
-54
@@ -3,22 +3,37 @@ import Link from "next/link";
|
||||
import { useRouter } from "next/router";
|
||||
import { useEffect, useState } from "react";
|
||||
|
||||
import { orgDomainConfig, subdomainSuffix } from "@calcom/features/ee/organizations/lib/orgDomains";
|
||||
import { DOCS_URL, JOIN_SLACK, WEBSITE_URL } from "@calcom/lib/constants";
|
||||
import { useLocale } from "@calcom/lib/hooks/useLocale";
|
||||
import { HeadSeo } from "@calcom/ui";
|
||||
import { BookOpen, Check, ChevronRight, FileText, Slack } from "@calcom/ui/components/icon";
|
||||
import { BookOpen, Check, ChevronRight, FileText, Slack, Shield } from "@calcom/ui/components/icon";
|
||||
|
||||
import PageWrapper from "@components/PageWrapper";
|
||||
|
||||
import { ssgInit } from "@server/lib/ssg";
|
||||
|
||||
enum pageType {
|
||||
ORG = "org",
|
||||
TEAM = "team",
|
||||
USER = "user",
|
||||
OTHER = "other",
|
||||
}
|
||||
|
||||
export default function Custom404() {
|
||||
const { t } = useLocale();
|
||||
|
||||
const router = useRouter();
|
||||
const [username] = router.asPath.replace("%20", "-").split(/[?#]/);
|
||||
const [username, setUsername] = useState<string>("");
|
||||
const [currentPageType, setCurrentPageType] = useState<pageType>(pageType.USER);
|
||||
|
||||
const links = [
|
||||
{
|
||||
title: "Enterprise",
|
||||
description: "Learn more about organizations and subdomains in our enterprise plan.",
|
||||
icon: Shield,
|
||||
href: `${WEBSITE_URL}/enterprise`,
|
||||
},
|
||||
{
|
||||
title: t("documentation"),
|
||||
description: t("documentation_description"),
|
||||
@@ -33,19 +48,34 @@ export default function Custom404() {
|
||||
},
|
||||
];
|
||||
|
||||
const [url, setUrl] = useState(`${WEBSITE_URL}/signup?username=`);
|
||||
const [url, setUrl] = useState(`${WEBSITE_URL}/signup`);
|
||||
useEffect(() => {
|
||||
setUrl(`${WEBSITE_URL}/signup?username=${username.replace("/", "")}`);
|
||||
}, [username]);
|
||||
const { isValidOrgDomain, currentOrgDomain } = orgDomainConfig(window.location.host);
|
||||
const [routerUsername] = router.asPath.replace("%20", "-").split(/[?#]/);
|
||||
if (!isValidOrgDomain || !currentOrgDomain) {
|
||||
const splitPath = routerUsername.split("/");
|
||||
if (splitPath[1] === "team" && splitPath.length === 3) {
|
||||
// Accessing a non-existent team
|
||||
setUsername(splitPath[2]);
|
||||
setCurrentPageType(pageType.TEAM);
|
||||
setUrl(`${WEBSITE_URL}/signup?callbackUrl=settings/teams/new%3Fslug%3D${username.replace("/", "")}`);
|
||||
} else {
|
||||
setUsername(routerUsername);
|
||||
setUrl(`${WEBSITE_URL}/signup?username=${username.replace("/", "")}`);
|
||||
}
|
||||
} else {
|
||||
setUsername(currentOrgDomain);
|
||||
setCurrentPageType(pageType.ORG);
|
||||
setUrl(
|
||||
`${WEBSITE_URL}/signup?callbackUrl=settings/organizations/new%3Fslug%3D${username.replace("/", "")}`
|
||||
);
|
||||
}
|
||||
}, []);
|
||||
|
||||
const isSuccessPage = router.asPath.startsWith("/booking");
|
||||
const isSubpage = router.asPath.includes("/", 2) || isSuccessPage;
|
||||
const isSignup = router.asPath.startsWith("/signup");
|
||||
const isCalcom = process.env.NEXT_PUBLIC_WEBAPP_URL === "https://app.cal.com";
|
||||
|
||||
// In case of invalid email verification token, we intentionally redirect to 404 from the API
|
||||
const isInvalidToken = router.asPath === "/404";
|
||||
|
||||
/**
|
||||
* If we're on 404 and the route is insights it means it is disabled
|
||||
* TODO: Abstract this for all disabled features
|
||||
@@ -63,7 +93,7 @@ export default function Custom404() {
|
||||
}}
|
||||
/>
|
||||
<div className="min-h-screen bg-white px-4" data-testid="404-page">
|
||||
<main className="mx-auto max-w-xl pt-16 pb-6 sm:pt-24">
|
||||
<main className="mx-auto max-w-xl pb-6 pt-16 sm:pt-24">
|
||||
<div className="text-center">
|
||||
<p className="text-sm font-semibold uppercase tracking-wide text-black">{t("error_404")}</p>
|
||||
<h1 className="font-cal mt-2 text-4xl font-extrabold text-gray-900 sm:text-5xl">
|
||||
@@ -84,6 +114,8 @@ export default function Custom404() {
|
||||
);
|
||||
}
|
||||
|
||||
if (!username) return null;
|
||||
|
||||
return (
|
||||
<>
|
||||
<HeadSeo
|
||||
@@ -95,7 +127,7 @@ export default function Custom404() {
|
||||
}}
|
||||
/>
|
||||
<div className="bg-default min-h-screen px-4" data-testid="404-page">
|
||||
<main className="mx-auto max-w-xl pt-16 pb-6 sm:pt-24">
|
||||
<main className="mx-auto max-w-xl pb-6 pt-16 sm:pt-24">
|
||||
{isSignup && process.env.NEXT_PUBLIC_WEBAPP_URL !== "https://app.cal.com" ? (
|
||||
<div>
|
||||
<div>
|
||||
@@ -204,21 +236,15 @@ export default function Custom404() {
|
||||
{t("error_404")}
|
||||
</p>
|
||||
<h1 className="font-cal text-emphasis mt-2 text-4xl font-extrabold sm:text-5xl">
|
||||
{isSuccessPage
|
||||
? t("booking_not_found")
|
||||
: isInvalidToken
|
||||
? t("invalid_verification_link")
|
||||
: t("page_doesnt_exist")}
|
||||
{isSuccessPage ? "Booking not found" : t("page_doesnt_exist")}
|
||||
</h1>
|
||||
{isSubpage ? (
|
||||
{isSubpage && currentPageType !== pageType.TEAM ? (
|
||||
<span className="mt-2 inline-block text-lg ">
|
||||
{t("check_spelling_mistakes_or_go_back")}
|
||||
</span>
|
||||
) : isInvalidToken ? (
|
||||
<span className="mt-2 inline-block text-lg">{t("token_invalid_expired")}</span>
|
||||
) : isCalcom ? (
|
||||
<a target="_blank" href={url} className="mt-2 inline-block text-lg" rel="noreferrer">
|
||||
{t("the_username")}{" "}
|
||||
{t(`404_the_${currentPageType.toLowerCase()}`)}{" "}
|
||||
<strong className="text-blue-500">
|
||||
{new URL(WEBSITE_URL).hostname}
|
||||
{username}
|
||||
@@ -227,18 +253,17 @@ export default function Custom404() {
|
||||
</a>
|
||||
) : (
|
||||
<span className="mt-2 inline-block text-lg">
|
||||
{t("the_username")}{" "}
|
||||
{t(`404_the_${currentPageType.toLowerCase()}`)}{" "}
|
||||
<strong className="text-lgtext-green-500 mt-2 inline-block">{username}</strong>{" "}
|
||||
{t("is_still_available")}
|
||||
</span>
|
||||
)}
|
||||
</div>
|
||||
<div className="mt-12">
|
||||
<h2 className="text-subtle text-sm font-semibold uppercase tracking-wide">
|
||||
{t("popular_pages")}
|
||||
</h2>
|
||||
{!isSubpage && isCalcom && (
|
||||
<ul role="list" className="mt-4">
|
||||
{((!isSubpage && isCalcom) ||
|
||||
currentPageType === pageType.ORG ||
|
||||
currentPageType === pageType.TEAM) && (
|
||||
<ul role="list" className="my-4">
|
||||
<li className="border-2 border-green-500 px-4 py-2">
|
||||
<a
|
||||
href={url}
|
||||
@@ -255,11 +280,20 @@ export default function Custom404() {
|
||||
<span className="focus-within:ring-empthasis rounded-sm focus-within:ring-2 focus-within:ring-offset-2">
|
||||
<span className="focus:outline-none">
|
||||
<span className="absolute inset-0" aria-hidden="true" />
|
||||
{t("register")} <strong className="text-green-500">{username}</strong>
|
||||
{t("register")}{" "}
|
||||
<strong className="text-green-500">{`${
|
||||
currentPageType === pageType.TEAM
|
||||
? `${new URL(WEBSITE_URL).host}/team/`
|
||||
: ""
|
||||
}${username}${
|
||||
currentPageType === pageType.ORG ? `.${subdomainSuffix()}` : ""
|
||||
}`}</strong>
|
||||
</span>
|
||||
</span>
|
||||
</h3>
|
||||
<p className="text-subtle text-base">{t("claim_username_and_schedule_events")}</p>
|
||||
<p className="text-subtle text-base">
|
||||
{t(`404_claim_entity_${currentPageType.toLowerCase()}`)}
|
||||
</p>
|
||||
</div>
|
||||
<div className="flex-shrink-0 self-center">
|
||||
<ChevronRight className="text-muted h-5 w-5" aria-hidden="true" />
|
||||
@@ -268,33 +302,37 @@ export default function Custom404() {
|
||||
</li>
|
||||
</ul>
|
||||
)}
|
||||
|
||||
<ul role="list" className="border-subtle divide-subtle mt-4 divide-y">
|
||||
{links.map((link, linkIdx) => (
|
||||
<li key={linkIdx} className="px-4 py-2">
|
||||
<Link
|
||||
href={link.href}
|
||||
className="relative flex items-start space-x-4 py-6 rtl:space-x-reverse">
|
||||
<div className="flex-shrink-0">
|
||||
<span className="bg-muted flex h-12 w-12 items-center justify-center rounded-lg">
|
||||
<link.icon className="text-default h-6 w-6" aria-hidden="true" />
|
||||
</span>
|
||||
</div>
|
||||
<div className="min-w-0 flex-1">
|
||||
<h3 className="text-emphasis text-base font-medium">
|
||||
<span className="focus-within:ring-empthasis rounded-sm focus-within:ring-2 focus-within:ring-offset-2">
|
||||
<span className="absolute inset-0" aria-hidden="true" />
|
||||
{link.title}
|
||||
<h2 className="text-subtle text-sm font-semibold uppercase tracking-wide">
|
||||
{t("popular_pages")}
|
||||
</h2>
|
||||
<ul role="list" className="border-subtle divide-subtle divide-y">
|
||||
{links
|
||||
.filter((_, idx) => currentPageType === pageType.ORG || idx !== 0)
|
||||
.map((link, linkIdx) => (
|
||||
<li key={linkIdx} className="px-4 py-2">
|
||||
<Link
|
||||
href={link.href}
|
||||
className="relative flex items-start space-x-4 py-6 rtl:space-x-reverse">
|
||||
<div className="flex-shrink-0">
|
||||
<span className="bg-muted flex h-12 w-12 items-center justify-center rounded-lg">
|
||||
<link.icon className="text-default h-6 w-6" aria-hidden="true" />
|
||||
</span>
|
||||
</h3>
|
||||
<p className="text-subtle text-base">{link.description}</p>
|
||||
</div>
|
||||
<div className="flex-shrink-0 self-center">
|
||||
<ChevronRight className="text-muted h-5 w-5" aria-hidden="true" />
|
||||
</div>
|
||||
</Link>
|
||||
</li>
|
||||
))}
|
||||
</div>
|
||||
<div className="min-w-0 flex-1">
|
||||
<h3 className="text-emphasis text-base font-medium">
|
||||
<span className="focus-within:ring-empthasis rounded-sm focus-within:ring-2 focus-within:ring-offset-2">
|
||||
<span className="absolute inset-0" aria-hidden="true" />
|
||||
{link.title}
|
||||
</span>
|
||||
</h3>
|
||||
<p className="text-subtle text-base">{link.description}</p>
|
||||
</div>
|
||||
<div className="flex-shrink-0 self-center">
|
||||
<ChevronRight className="text-muted h-5 w-5" aria-hidden="true" />
|
||||
</div>
|
||||
</Link>
|
||||
</li>
|
||||
))}
|
||||
<li className="px-4 py-2">
|
||||
<a
|
||||
href={JOIN_SLACK}
|
||||
|
||||
@@ -21,7 +21,7 @@ export default function Error500() {
|
||||
<div className="rtl: bg-default m-auto rounded-md p-10 text-right ltr:text-left">
|
||||
<h1 className="font-cal text-emphasis text-6xl">500</h1>
|
||||
<h2 className="text-emphasis mt-6 text-2xl font-medium">It's not you, it's us.</h2>
|
||||
<p className="text-default mt-4 mb-6 max-w-2xl text-sm">
|
||||
<p className="text-default mb-6 mt-4 max-w-2xl text-sm">
|
||||
Something went wrong on our end. Get in touch with our support team, and we’ll get it fixed right
|
||||
away for you.
|
||||
</p>
|
||||
|
||||
@@ -37,7 +37,8 @@ import PageWrapper from "@components/PageWrapper";
|
||||
|
||||
import { ssrInit } from "@server/lib/ssr";
|
||||
|
||||
export default function User(props: inferSSRProps<typeof getServerSideProps> & EmbedProps) {
|
||||
export type UserPageProps = inferSSRProps<typeof getServerSideProps> & EmbedProps;
|
||||
export function UserPage(props: UserPageProps) {
|
||||
const {
|
||||
users,
|
||||
profile,
|
||||
@@ -102,6 +103,7 @@ export default function User(props: inferSSRProps<typeof getServerSideProps> & E
|
||||
const shouldAlignCentrally = !isEmbed || shouldAlignCentrallyInEmbed;
|
||||
const query = { ...router.query };
|
||||
delete query.user; // So it doesn't display in the Link (and make tests fail)
|
||||
delete query.orgSlug;
|
||||
const nameOrUsername = user.name || user.username || "";
|
||||
|
||||
/*
|
||||
@@ -132,7 +134,7 @@ export default function User(props: inferSSRProps<typeof getServerSideProps> & E
|
||||
className={classNames(
|
||||
shouldAlignCentrally ? "mx-auto" : "",
|
||||
isEmbed ? "border-booker border-booker-width bg-default rounded-md border" : "",
|
||||
"max-w-3xl py-24 px-4"
|
||||
"max-w-3xl px-4 py-24"
|
||||
)}>
|
||||
{isSingleUser && ( // When we deal with a single user, not dynamic group
|
||||
<div className="mb-8 text-center">
|
||||
@@ -207,8 +209,8 @@ export default function User(props: inferSSRProps<typeof getServerSideProps> & E
|
||||
);
|
||||
}
|
||||
|
||||
User.isBookingPage = true;
|
||||
User.PageWrapper = PageWrapper;
|
||||
UserPage.isBookingPage = true;
|
||||
UserPage.PageWrapper = PageWrapper;
|
||||
|
||||
const getEventTypesWithHiddenFromDB = async (userId: number) => {
|
||||
return (
|
||||
@@ -360,6 +362,7 @@ export const getServerSideProps = async (context: GetServerSidePropsContext) =>
|
||||
const safeBio = markdownToSafeHTML(user.bio) || "";
|
||||
|
||||
const markdownStrippedBio = stripMarkdown(user?.bio || "");
|
||||
|
||||
return {
|
||||
props: {
|
||||
users,
|
||||
@@ -385,3 +388,5 @@ export const getServerSideProps = async (context: GetServerSidePropsContext) =>
|
||||
},
|
||||
};
|
||||
};
|
||||
|
||||
export default UserPage;
|
||||
|
||||
@@ -19,11 +19,11 @@ export default async function handler(req: NextApiRequest, res: NextApiResponse)
|
||||
});
|
||||
|
||||
if (!foundToken) {
|
||||
return res.redirect(`${WEBAPP_URL}/404`);
|
||||
return res.status(401).json({ message: "No token found" });
|
||||
}
|
||||
|
||||
if (dayjs(foundToken?.expires).isBefore(dayjs())) {
|
||||
return res.redirect(`${WEBAPP_URL}/404`);
|
||||
return res.status(401).json({ message: "Token expired" });
|
||||
}
|
||||
|
||||
const user = await prisma.user.update({
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
import type { GetStaticPropsContext } from "next";
|
||||
import Link from "next/link";
|
||||
import { useRouter } from "next/router";
|
||||
import type { ReactElement } from "react";
|
||||
import z from "zod";
|
||||
|
||||
import { useLocale } from "@calcom/lib/hooks/useLocale";
|
||||
@@ -21,7 +22,7 @@ export default function Error() {
|
||||
const router = useRouter();
|
||||
const { error } = querySchema.parse(router.query);
|
||||
const isTokenVerificationError = error?.toLowerCase() === "verification";
|
||||
let errorMsg = <SkeletonText />;
|
||||
let errorMsg: string | ReactElement = <SkeletonText />;
|
||||
if (router.isReady) {
|
||||
errorMsg = isTokenVerificationError ? t("token_invalid_expired") : t("error_during_login");
|
||||
}
|
||||
|
||||
@@ -254,7 +254,7 @@ export default function Availability() {
|
||||
</ConfirmationDialogContent>
|
||||
</Dialog>
|
||||
<DropdownMenuSeparator />
|
||||
<div className="flex h-9 flex-row items-center justify-between py-2 px-4 hover:bg-gray-100">
|
||||
<div className="flex h-9 flex-row items-center justify-between px-4 py-2 hover:bg-gray-100">
|
||||
<Skeleton
|
||||
as={Label}
|
||||
htmlFor="hiddenSwitch"
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
import { useAutoAnimate } from "@formkit/auto-animate/react";
|
||||
import { useRouter } from "next/router";
|
||||
|
||||
import { NewScheduleButton, ScheduleListItem } from "@calcom/features/schedules";
|
||||
import Shell from "@calcom/features/shell/Shell";
|
||||
@@ -20,6 +21,8 @@ export function AvailabilityList({ schedules }: RouterOutputs["viewer"]["availab
|
||||
|
||||
const meQuery = trpc.viewer.me.useQuery();
|
||||
|
||||
const router = useRouter();
|
||||
|
||||
const deleteMutation = trpc.viewer.availability.schedule.delete.useMutation({
|
||||
onMutate: async ({ scheduleId }) => {
|
||||
await utils.viewer.availability.list.cancel();
|
||||
@@ -67,6 +70,19 @@ export function AvailabilityList({ schedules }: RouterOutputs["viewer"]["availab
|
||||
},
|
||||
});
|
||||
|
||||
const duplicateMutation = trpc.viewer.availability.schedule.duplicate.useMutation({
|
||||
onSuccess: async ({ schedule }) => {
|
||||
await router.push(`/availability/${schedule.id}`);
|
||||
showToast(t("schedule_created_successfully", { scheduleName: schedule.name }), "success");
|
||||
},
|
||||
onError: (err) => {
|
||||
if (err instanceof HttpError) {
|
||||
const message = `${err.statusCode}: ${err.message}`;
|
||||
showToast(message, "error");
|
||||
}
|
||||
},
|
||||
});
|
||||
|
||||
// Adds smooth delete button - item fades and old item slides into place
|
||||
|
||||
const [animationParentRef] = useAutoAnimate<HTMLUListElement>();
|
||||
@@ -96,6 +112,7 @@ export function AvailabilityList({ schedules }: RouterOutputs["viewer"]["availab
|
||||
isDeletable={schedules.length !== 1}
|
||||
updateDefault={updateMutation.mutate}
|
||||
deleteFunction={deleteMutation.mutate}
|
||||
duplicateFunction={duplicateMutation.mutate}
|
||||
/>
|
||||
))}
|
||||
</ul>
|
||||
|
||||
@@ -49,8 +49,9 @@ 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";
|
||||
import { Button, EmailInput, HeadSeo, Badge, useCalcomTheme, Alert } from "@calcom/ui";
|
||||
import { X, ExternalLink, ChevronLeft, Check, Calendar } from "@calcom/ui/components/icon";
|
||||
import { AlertCircle } from "@calcom/ui/components/icon";
|
||||
|
||||
import { timeZone } from "@lib/clock";
|
||||
import type { inferSSRProps } from "@lib/types/inferSSRProps";
|
||||
@@ -127,6 +128,10 @@ export default function Success(props: SuccessProps) {
|
||||
? props?.bookingInfo?.attendees?.[0]?.name
|
||||
: "Nameless";
|
||||
|
||||
const attendees = props?.bookingInfo?.attendees;
|
||||
|
||||
const isGmail = !!attendees.find((attendee) => attendee.email.includes("gmail.com"));
|
||||
|
||||
const [is24h, setIs24h] = useState(isBrowserLocale24h());
|
||||
const { data: session } = useSession();
|
||||
|
||||
@@ -306,7 +311,7 @@ export default function Success(props: SuccessProps) {
|
||||
/>
|
||||
)}
|
||||
{userIsOwner && !isEmbed && (
|
||||
<div className="mt-2 ml-4 -mb-4">
|
||||
<div className="-mb-4 ml-4 mt-2">
|
||||
<Link
|
||||
href={allRemainingBookings ? "/bookings/recurring" : "/bookings/upcoming"}
|
||||
className="hover:bg-subtle text-subtle hover:text-default mt-2 inline-flex px-1 py-2 text-sm dark:hover:bg-transparent">
|
||||
@@ -321,7 +326,7 @@ export default function Success(props: SuccessProps) {
|
||||
<div
|
||||
className={classNames(
|
||||
shouldAlignCentrally ? "text-center" : "",
|
||||
"flex items-end justify-center px-4 pt-4 pb-20 sm:block sm:p-0"
|
||||
"flex items-end justify-center px-4 pb-20 pt-4 sm:block sm:p-0"
|
||||
)}>
|
||||
<div
|
||||
className={classNames("my-4 transition-opacity sm:my-0", isEmbed ? "" : " inset-0")}
|
||||
@@ -330,7 +335,7 @@ export default function Success(props: SuccessProps) {
|
||||
className={classNames(
|
||||
"main inline-block transform overflow-hidden rounded-lg border sm:my-8 sm:max-w-xl",
|
||||
!isBackgroundTransparent && " bg-default dark:bg-muted border-booker border-booker-width",
|
||||
"px-8 pt-5 pb-4 text-left align-bottom transition-all sm:w-full sm:py-8 sm:align-middle"
|
||||
"px-8 pb-4 pt-5 text-left align-bottom transition-all sm:w-full sm:py-8 sm:align-middle"
|
||||
)}
|
||||
role="dialog"
|
||||
aria-modal="true"
|
||||
@@ -356,7 +361,7 @@ export default function Success(props: SuccessProps) {
|
||||
{needsConfirmation && !isCancelled && <Calendar className="text-emphasis h-5 w-5" />}
|
||||
{isCancelled && <X className="h-5 w-5 text-red-600" />}
|
||||
</div>
|
||||
<div className="mt-6 mb-8 text-center last:mb-0">
|
||||
<div className="mb-8 mt-6 text-center last:mb-0">
|
||||
<h3
|
||||
className="text-emphasis text-2xl font-semibold leading-6"
|
||||
data-testid={isCancelled ? "cancelled-headline" : ""}
|
||||
@@ -615,7 +620,7 @@ export default function Success(props: SuccessProps) {
|
||||
className="border-subtle text-default mx-2 h-10 w-10 rounded-sm border px-3 py-2"
|
||||
target="_blank">
|
||||
<svg
|
||||
className="mr-1 -mt-1.5 inline-block h-4 w-4"
|
||||
className="-mt-1.5 mr-1 inline-block h-4 w-4"
|
||||
fill="currentColor"
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
viewBox="0 0 24 24">
|
||||
@@ -642,7 +647,7 @@ export default function Success(props: SuccessProps) {
|
||||
className="text-default border-subtle mx-2 h-10 w-10 rounded-sm border px-3 py-2"
|
||||
target="_blank">
|
||||
<svg
|
||||
className="mr-1 -mt-1.5 inline-block h-4 w-4"
|
||||
className="-mt-1.5 mr-1 inline-block h-4 w-4"
|
||||
fill="currentColor"
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
viewBox="0 0 24 24">
|
||||
@@ -659,7 +664,7 @@ export default function Success(props: SuccessProps) {
|
||||
fill="currentColor"
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
viewBox="0 0 1000 1000"
|
||||
className="mr-1 -mt-1.5 inline-block h-4 w-4">
|
||||
className="-mt-1.5 mr-1 inline-block h-4 w-4">
|
||||
<title>{t("other")}</title>
|
||||
<path d="M971.3,154.9c0-34.7-28.2-62.9-62.9-62.9H611.7c-1.3,0-2.6,0.1-3.9,0.2V10L28.7,87.3v823.4L607.8,990v-84.6c1.3,0.1,2.6,0.2,3.9,0.2h296.7c34.7,0,62.9-28.2,62.9-62.9V154.9z M607.8,636.1h44.6v-50.6h-44.6v-21.9h44.6v-50.6h-44.6v-92h277.9v230.2c0,3.8-3.1,7-7,7H607.8V636.1z M117.9,644.7l-50.6-2.4V397.5l50.6-2.2V644.7z M288.6,607.3c17.6,0.6,37.3-2.8,49.1-7.2l9.1,48c-11,5.1-35.6,9.9-66.9,8.3c-85.4-4.3-127.5-60.7-127.5-132.6c0-86.2,57.8-136.7,133.2-140.1c30.3-1.3,53.7,4,64.3,9.2l-12.2,48.9c-12.1-4.9-28.8-9.2-49.5-8.6c-45.3,1.2-79.5,30.1-79.5,87.4C208.8,572.2,237.8,605.7,288.6,607.3z M455.5,665.2c-32.4-1.6-63.7-11.3-79.1-20.5l12.6-50.7c16.8,9.1,42.9,18.5,70.4,19.4c30.1,1,46.3-10.7,46.3-29.3c0-17.8-14-28.1-48.8-40.6c-46.9-16.4-76.8-41.7-76.8-81.5c0-46.6,39.3-84.1,106.8-87.1c33.3-1.5,58.3,4.2,76.5,11.2l-15.4,53.3c-12.1-5.3-33.5-12.8-62.3-12c-28.3,0.8-41.9,13.6-41.9,28.1c0,17.8,16.1,25.5,53.6,39c52.9,18.5,78.4,45.3,78.4,86.4C575.6,629.7,536.2,669.2,455.5,665.2z M935.3,842.7c0,14.9-12.1,27-27,27H611.7c-1.3,0-2.6-0.2-3.9-0.4V686.2h270.9c19.2,0,34.9-15.6,34.9-34.9V398.4c0-19.2-15.6-34.9-34.9-34.9h-47.1v-32.3H808v32.3h-44.8v-32.3h-22.7v32.3h-43.3v-32.3h-22.7v32.3H628v-32.3h-20.2v-203c1.31.2,2.6-0.4,3.9-0.4h296.7c14.9,0,27,12.1,27,27L935.3,842.7L935.3,842.7z" />
|
||||
</svg>
|
||||
@@ -705,6 +710,26 @@ export default function Success(props: SuccessProps) {
|
||||
</>
|
||||
)}
|
||||
</div>
|
||||
{isGmail && (
|
||||
<Alert
|
||||
className="main -mb-20 mt-4 inline-block text-left sm:-mt-4 sm:mb-4 sm:w-full sm:max-w-xl sm:align-middle"
|
||||
severity="warning"
|
||||
message={
|
||||
<div>
|
||||
<p className="font-semibold">{t("google_new_spam_policy")}</p>
|
||||
<span className="underline">
|
||||
<a
|
||||
target="_blank"
|
||||
href="https://cal.com/blog/google-s-new-spam-policy-may-be-affecting-your-invitations">
|
||||
{t("resolve")}
|
||||
</a>
|
||||
</span>
|
||||
</div>
|
||||
}
|
||||
CustomIcon={AlertCircle}
|
||||
customIconColor="text-attention dark:text-orange-200"
|
||||
/>
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@@ -168,18 +168,14 @@ const EventTypePage = (props: EventTypeSetupProps) => {
|
||||
}
|
||||
|
||||
if (err.data?.code === "UNAUTHORIZED") {
|
||||
message = `${err.data.code}: You are not able to update this event`;
|
||||
message = `${err.data.code}: ${t("error_event_type_unauthorized_update")}`;
|
||||
}
|
||||
|
||||
if (err.data?.code === "PARSE_ERROR" || err.data?.code === "BAD_REQUEST") {
|
||||
message = `${err.data.code}: ${err.message}`;
|
||||
message = `${err.data.code}: ${t(err.message)}`;
|
||||
}
|
||||
|
||||
if (message) {
|
||||
showToast(message, "error");
|
||||
} else {
|
||||
showToast(err.message, "error");
|
||||
}
|
||||
showToast(message ? t(message) : t(err.message), "error");
|
||||
},
|
||||
});
|
||||
|
||||
|
||||
@@ -383,7 +383,7 @@ export const EventTypeList = ({ group, groupIndex, readOnly, types }: EventTypeL
|
||||
<div className="group flex w-full max-w-full items-center justify-between overflow-hidden px-4 py-4 sm:px-6">
|
||||
{!(firstItem && firstItem.id === type.id) && (
|
||||
<button
|
||||
className="bg-default text-muted hover:text-emphasis border-default hover:border-emphasis invisible absolute left-[5px] -mt-4 mb-4 -ml-4 hidden h-6 w-6 scale-0 items-center justify-center rounded-md border p-1 transition-all group-hover:visible group-hover:scale-100 sm:ml-0 sm:flex lg:left-[36px]"
|
||||
className="bg-default text-muted hover:text-emphasis border-default hover:border-emphasis invisible absolute left-[5px] -ml-4 -mt-4 mb-4 hidden h-6 w-6 scale-0 items-center justify-center rounded-md border p-1 transition-all group-hover:visible group-hover:scale-100 sm:ml-0 sm:flex lg:left-[36px]"
|
||||
onClick={() => moveEventType(index, -1)}>
|
||||
<ArrowUp className="h-5 w-5" />
|
||||
</button>
|
||||
@@ -391,7 +391,7 @@ export const EventTypeList = ({ group, groupIndex, readOnly, types }: EventTypeL
|
||||
|
||||
{!(lastItem && lastItem.id === type.id) && (
|
||||
<button
|
||||
className="bg-default text-muted border-default hover:text-emphasis hover:border-emphasis invisible absolute left-[5px] mt-8 -ml-4 hidden h-6 w-6 scale-0 items-center justify-center rounded-md border p-1 transition-all group-hover:visible group-hover:scale-100 sm:ml-0 sm:flex lg:left-[36px]"
|
||||
className="bg-default text-muted border-default hover:text-emphasis hover:border-emphasis invisible absolute left-[5px] -ml-4 mt-8 hidden h-6 w-6 scale-0 items-center justify-center rounded-md border p-1 transition-all group-hover:visible group-hover:scale-100 sm:ml-0 sm:flex lg:left-[36px]"
|
||||
onClick={() => moveEventType(index, 1)}>
|
||||
<ArrowDown className="h-5 w-5" />
|
||||
</button>
|
||||
@@ -401,7 +401,7 @@ export const EventTypeList = ({ group, groupIndex, readOnly, types }: EventTypeL
|
||||
<div className="flex justify-between space-x-2 rtl:space-x-reverse">
|
||||
{type.team && !isManagedEventType && (
|
||||
<AvatarGroup
|
||||
className="relative top-1 right-3"
|
||||
className="relative right-3 top-1"
|
||||
size="sm"
|
||||
truncateAfter={4}
|
||||
items={type.users.map(
|
||||
@@ -415,12 +415,12 @@ export const EventTypeList = ({ group, groupIndex, readOnly, types }: EventTypeL
|
||||
)}
|
||||
{isManagedEventType && (
|
||||
<AvatarGroup
|
||||
className="relative top-1 right-3"
|
||||
className="relative right-3 top-1"
|
||||
size="sm"
|
||||
truncateAfter={4}
|
||||
items={type.children
|
||||
.flatMap((ch) => ch.users)
|
||||
.map((user: User) => ({
|
||||
.map((user: Pick<User, "name" | "username">) => ({
|
||||
alt: user.name || "",
|
||||
image: `${WEBAPP_URL}/${user.username}/avatar.png`,
|
||||
title: user.name || "",
|
||||
@@ -640,7 +640,7 @@ export const EventTypeList = ({ group, groupIndex, readOnly, types }: EventTypeL
|
||||
)}
|
||||
<DropdownMenuSeparator />
|
||||
{!isManagedEventType && (
|
||||
<div className="hover:bg-subtle flex h-9 cursor-pointer flex-row items-center justify-between py-2 px-4">
|
||||
<div className="hover:bg-subtle flex h-9 cursor-pointer flex-row items-center justify-between px-4 py-2">
|
||||
<Skeleton
|
||||
as={Label}
|
||||
htmlFor="hiddenSwitch"
|
||||
@@ -728,10 +728,10 @@ const EventTypeListHeading = ({
|
||||
{profile?.name || ""}
|
||||
</Link>
|
||||
{membershipCount && teamId && (
|
||||
<span className="text-subtle ms-2 me-2 relative -top-px text-xs">
|
||||
<span className="text-subtle relative -top-px me-2 ms-2 text-xs">
|
||||
<Link href={`/settings/teams/${teamId}/members`}>
|
||||
<Badge variant="gray">
|
||||
<Users className="mr-1 -mt-px inline h-3 w-3" />
|
||||
<Users className="-mt-px mr-1 inline h-3 w-3" />
|
||||
{membershipCount}
|
||||
</Badge>
|
||||
</Link>
|
||||
@@ -747,7 +747,7 @@ const EventTypeListHeading = ({
|
||||
</div>
|
||||
{!profile?.slug && !!teamId && (
|
||||
<button onClick={() => publishTeamMutation.mutate({ teamId })}>
|
||||
<Badge variant="gray" className="mb-1 -ml-2">
|
||||
<Badge variant="gray" className="-ml-2 mb-1">
|
||||
{t("upgrade")}
|
||||
</Badge>
|
||||
</button>
|
||||
@@ -861,7 +861,7 @@ const EventTypesPage = () => {
|
||||
const filters = getTeamsFiltersFromQuery(router.query);
|
||||
|
||||
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
||||
const WithQuery = withQuery(trpc.viewer.eventTypes.getByViewer as any, { filters });
|
||||
const WithQuery = withQuery(trpc.viewer.eventTypes.getByViewer as any, filters && { filters });
|
||||
|
||||
return (
|
||||
<div>
|
||||
|
||||
@@ -77,7 +77,7 @@ export default function InsightsPage() {
|
||||
<></>
|
||||
) : (
|
||||
<FiltersProvider>
|
||||
<div className="mb-8 mt-0 ml-auto flex w-full flex-wrap justify-between md:-mt-8">
|
||||
<div className="mb-8 ml-auto mt-0 flex w-full flex-wrap justify-between md:-mt-8">
|
||||
<Heading />
|
||||
<Filters />
|
||||
</div>
|
||||
|
||||
@@ -19,7 +19,7 @@ export default function MaintenancePage() {
|
||||
</Head>
|
||||
<div className="bg-default m-auto rounded-md p-10 text-right ltr:text-left">
|
||||
<h1 className="text-emphasis text-2xl font-medium">{t("under_maintenance")}</h1>
|
||||
<p className="text-default mt-4 mb-6 max-w-2xl text-sm">
|
||||
<p className="text-default mb-6 mt-4 max-w-2xl text-sm">
|
||||
{t("under_maintenance_description", { appName: APP_NAME })}
|
||||
</p>
|
||||
<Button href={`${WEBSITE_URL}/support`}>{t("contact_support")}</Button>
|
||||
|
||||
@@ -14,7 +14,7 @@ import PageWrapper from "@components/PageWrapper";
|
||||
|
||||
type PageProps = inferSSRProps<typeof getServerSideProps>;
|
||||
|
||||
export default function Type({ slug, user, booking, away, isBrandingHidden }: PageProps) {
|
||||
export default function Type({ slug, user, booking, away, isBrandingHidden, isTeamEvent }: PageProps) {
|
||||
return (
|
||||
<main className="flex h-full min-h-[100dvh] items-center justify-center">
|
||||
<BookerSeo
|
||||
@@ -29,6 +29,7 @@ export default function Type({ slug, user, booking, away, isBrandingHidden }: Pa
|
||||
rescheduleBooking={booking}
|
||||
isAway={away}
|
||||
hideBranding={isBrandingHidden}
|
||||
isTeamEvent={isTeamEvent}
|
||||
/>
|
||||
</main>
|
||||
);
|
||||
@@ -57,6 +58,11 @@ async function getUserPageProps(context: GetServerSidePropsContext) {
|
||||
username: true,
|
||||
},
|
||||
},
|
||||
team: {
|
||||
select: {
|
||||
id: true,
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
@@ -96,9 +102,11 @@ async function getUserPageProps(context: GetServerSidePropsContext) {
|
||||
booking = await getBookingByUidOrRescheduleUid(`${rescheduleUid}`);
|
||||
}
|
||||
|
||||
const isTeamEvent = !!hashedLink.eventType?.team?.id;
|
||||
|
||||
// We use this to both prefetch the query on the server,
|
||||
// as well as to check if the event exist, so we c an show a 404 otherwise.
|
||||
const eventData = await ssr.viewer.public.event.fetch({ username, eventSlug: slug });
|
||||
const eventData = await ssr.viewer.public.event.fetch({ username, eventSlug: slug, isTeamEvent });
|
||||
|
||||
if (!eventData) {
|
||||
return {
|
||||
@@ -114,6 +122,9 @@ async function getUserPageProps(context: GetServerSidePropsContext) {
|
||||
slug,
|
||||
trpcState: ssr.dehydrate(),
|
||||
isBrandingHidden: user?.hideBranding,
|
||||
// Sending the team event from the server, because this template file
|
||||
// is reused for both team and user events.
|
||||
isTeamEvent,
|
||||
},
|
||||
};
|
||||
}
|
||||
|
||||
@@ -30,6 +30,7 @@ export default function Type({ slug, user, booking, away, isBrandingHidden }: Pa
|
||||
rescheduleBooking={booking}
|
||||
isAway={away}
|
||||
hideBranding={isBrandingHidden}
|
||||
isTeamEvent
|
||||
/>
|
||||
</main>
|
||||
);
|
||||
@@ -71,7 +72,11 @@ export const getServerSideProps = async (context: GetServerSidePropsContext) =>
|
||||
|
||||
// We use this to both prefetch the query on the server,
|
||||
// as well as to check if the event exist, so we c an show a 404 otherwise.
|
||||
const eventData = await ssr.viewer.public.event.fetch({ username: teamSlug, eventSlug: meetingSlug });
|
||||
const eventData = await ssr.viewer.public.event.fetch({
|
||||
username: teamSlug,
|
||||
eventSlug: meetingSlug,
|
||||
isTeamEvent: true,
|
||||
});
|
||||
|
||||
if (!eventData) {
|
||||
return {
|
||||
|
||||
@@ -0,0 +1 @@
|
||||
export { getServerSideProps, default } from "../../../[user]/[type]";
|
||||
@@ -0,0 +1,40 @@
|
||||
import type { GetServerSidePropsContext } from "next";
|
||||
|
||||
import prisma from "@calcom/prisma";
|
||||
|
||||
import PageWrapper from "@components/PageWrapper";
|
||||
|
||||
import type { UserPageProps } from "../../../[user]";
|
||||
import UserPage, { getServerSideProps as GSSUserPage } from "../../../[user]";
|
||||
import type { TeamPageProps } from "../../../team/[slug]";
|
||||
import TeamPage, { getServerSideProps as GSSTeamPage } from "../../../team/[slug]";
|
||||
|
||||
export const getServerSideProps = async (ctx: GetServerSidePropsContext) => {
|
||||
const team = await prisma.team.findFirst({
|
||||
where: {
|
||||
slug: ctx.query.user as string,
|
||||
parentId: {
|
||||
not: null,
|
||||
},
|
||||
parent: {
|
||||
slug: ctx.query.orgSlug as string,
|
||||
},
|
||||
},
|
||||
select: {
|
||||
id: true,
|
||||
},
|
||||
});
|
||||
if (team) {
|
||||
return GSSTeamPage({ ...ctx, query: { slug: ctx.query.user } });
|
||||
}
|
||||
return GSSUserPage({ ...ctx, query: { user: ctx.query.user } });
|
||||
};
|
||||
|
||||
type Props = UserPageProps | TeamPageProps;
|
||||
|
||||
export default function Page(props: Props) {
|
||||
if ((props as TeamPageProps)?.team) return TeamPage(props as TeamPageProps);
|
||||
return UserPage(props as UserPageProps);
|
||||
}
|
||||
|
||||
Page.PageWrapper = PageWrapper;
|
||||
@@ -0,0 +1 @@
|
||||
export { getServerSideProps, default } from "../../team/[slug]";
|
||||
@@ -0,0 +1 @@
|
||||
export { getServerSideProps, default } from "../../../../team/[slug]/[type]";
|
||||
@@ -0,0 +1 @@
|
||||
export { getServerSideProps, default } from "../../../../team/[slug]";
|
||||
@@ -25,7 +25,7 @@ const CtaRow = ({ title, description, className, children }: CtaRowProps) => {
|
||||
<h2 className="font-medium">{title}</h2>
|
||||
<p>{description}</p>
|
||||
</div>
|
||||
<div className="flex-shrink-0 pt-3 sm:ml-auto sm:pt-0 sm:pl-3">{children}</div>
|
||||
<div className="flex-shrink-0 pt-3 sm:ml-auto sm:pl-3 sm:pt-0">{children}</div>
|
||||
</section>
|
||||
<hr className="border-subtle" />
|
||||
</>
|
||||
|
||||
@@ -57,7 +57,7 @@ const ApiKeysView = () => {
|
||||
<div>
|
||||
{isLoading ? null : data?.length ? (
|
||||
<>
|
||||
<div className="mt-6 mb-8 rounded-md border">
|
||||
<div className="mb-8 mt-6 rounded-md border">
|
||||
{data.map((apiKey, index) => (
|
||||
<ApiKeyListItem
|
||||
key={apiKey.id}
|
||||
|
||||
@@ -32,7 +32,7 @@ const SkeletonLoader = ({ title, description }: { title: string; description: st
|
||||
return (
|
||||
<SkeletonContainer>
|
||||
<Meta title={title} description={description} />
|
||||
<div className="mt-6 mb-8 space-y-6">
|
||||
<div className="mb-8 mt-6 space-y-6">
|
||||
<div className="flex items-center">
|
||||
<SkeletonButton className="mr-6 h-32 w-48 rounded-md p-5" />
|
||||
<SkeletonButton className="mr-6 h-32 w-48 rounded-md p-5" />
|
||||
@@ -70,6 +70,13 @@ const AppearanceView = () => {
|
||||
},
|
||||
});
|
||||
|
||||
const selectedTheme = formMethods.watch("theme");
|
||||
const selectedThemeIsDark =
|
||||
selectedTheme === "dark" ||
|
||||
(selectedTheme === "" &&
|
||||
typeof document !== "undefined" &&
|
||||
document.documentElement.classList.contains("dark"));
|
||||
|
||||
const {
|
||||
formState: { isSubmitting, isDirty },
|
||||
reset,
|
||||
@@ -144,6 +151,7 @@ const AppearanceView = () => {
|
||||
|
||||
<hr className="border-subtle my-8 border [&:has(+hr)]:hidden" />
|
||||
<BookerLayoutSelector
|
||||
isDark={selectedThemeIsDark}
|
||||
name="metadata.defaultBookerLayouts"
|
||||
title={t("bookerlayout_user_settings_title")}
|
||||
description={t("bookerlayout_user_settings_description")}
|
||||
|
||||
@@ -34,7 +34,7 @@ import PageWrapper from "@components/PageWrapper";
|
||||
const SkeletonLoader = () => {
|
||||
return (
|
||||
<SkeletonContainer>
|
||||
<div className="mt-6 mb-8 space-y-6">
|
||||
<div className="mb-8 mt-6 space-y-6">
|
||||
<SkeletonText className="h-8 w-full" />
|
||||
<SkeletonText className="h-8 w-full" />
|
||||
<SkeletonText className="h-8 w-full" />
|
||||
|
||||
@@ -33,7 +33,7 @@ const SkeletonLoader = ({ title, description }: { title: string; description: st
|
||||
return (
|
||||
<SkeletonContainer>
|
||||
<Meta title={title} description={description} />
|
||||
<div className="divide-subtle mt-6 mb-8 space-y-6">
|
||||
<div className="divide-subtle mb-8 mt-6 space-y-6">
|
||||
<SkeletonText className="h-8 w-full" />
|
||||
<SkeletonText className="h-8 w-full" />
|
||||
</div>
|
||||
|
||||
@@ -29,7 +29,7 @@ const SkeletonLoader = ({ title, description }: { title: string; description: st
|
||||
return (
|
||||
<SkeletonContainer>
|
||||
<Meta title={title} description={description} />
|
||||
<div className="mt-6 mb-8 space-y-6">
|
||||
<div className="mb-8 mt-6 space-y-6">
|
||||
<SkeletonText className="h-8 w-full" />
|
||||
<SkeletonText className="h-8 w-full" />
|
||||
<SkeletonText className="h-8 w-full" />
|
||||
|
||||
@@ -48,7 +48,7 @@ const SkeletonLoader = ({ title, description }: { title: string; description: st
|
||||
return (
|
||||
<SkeletonContainer>
|
||||
<Meta title={title} description={description} />
|
||||
<div className="mt-6 mb-8 space-y-6">
|
||||
<div className="mb-8 mt-6 space-y-6">
|
||||
<div className="flex items-center">
|
||||
<SkeletonAvatar className="me-4 h-16 w-16 px-4" />
|
||||
<SkeletonButton className="h-6 w-32 rounded-md p-5" />
|
||||
|
||||
@@ -12,7 +12,7 @@ import EnableTwoFactorModal from "@components/settings/EnableTwoFactorModal";
|
||||
const SkeletonLoader = () => {
|
||||
return (
|
||||
<SkeletonContainer>
|
||||
<div className="mt-6 mb-8 space-y-6">
|
||||
<div className="mb-8 mt-6 space-y-6">
|
||||
<div className="flex items-center">
|
||||
<SkeletonButton className="mr-6 h-8 w-20 rounded-md p-5" />
|
||||
<SkeletonText className="h-8 w-full" />
|
||||
|
||||
@@ -29,6 +29,7 @@ import Team from "@components/team/screens/Team";
|
||||
import { ssrInit } from "@server/lib/ssr";
|
||||
|
||||
export type TeamPageProps = inferSSRProps<typeof getServerSideProps>;
|
||||
|
||||
function TeamPage({ team, isUnpublished, markdownStrippedBio, isValidOrgDomain }: TeamPageProps) {
|
||||
useTheme(team.theme);
|
||||
const showMembers = useToggleQuery("members");
|
||||
@@ -170,7 +171,7 @@ function TeamPage({ team, isUnpublished, markdownStrippedBio, isValidOrgDomain }
|
||||
profile: { name: `${team.name}`, image: getPlaceholderAvatar(team.logo, team.name) },
|
||||
}}
|
||||
/>
|
||||
<main className="dark:bg-darkgray-50 bg-subtle mx-auto max-w-3xl rounded-md px-4 pt-12 pb-12">
|
||||
<main className="dark:bg-darkgray-50 bg-subtle mx-auto max-w-3xl rounded-md px-4 pb-12 pt-12">
|
||||
<div className="mx-auto mb-8 max-w-3xl text-center">
|
||||
<div className="relative">
|
||||
<Avatar
|
||||
|
||||
@@ -205,7 +205,7 @@ export function VideoMeetingInfo(props: VideoMeetingInfo) {
|
||||
<>
|
||||
<aside
|
||||
className={classNames(
|
||||
"no-scrollbar fixed top-0 left-0 z-30 flex h-full w-64 transform justify-between overflow-x-hidden overflow-y-scroll transition-all duration-300 ease-in-out",
|
||||
"no-scrollbar fixed left-0 top-0 z-30 flex h-full w-64 transform justify-between overflow-x-hidden overflow-y-scroll transition-all duration-300 ease-in-out",
|
||||
open ? "translate-x-0" : "-translate-x-[232px]"
|
||||
)}>
|
||||
<main className="prose-sm prose max-w-64 prose-a:text-white prose-h3:text-white prose-h3:font-cal scroll-bar scrollbar-track-w-20 w-full overflow-scroll overflow-x-hidden border-r border-gray-300/20 bg-black/80 p-4 text-white shadow-sm backdrop-blur-lg">
|
||||
|
||||
@@ -19,13 +19,13 @@ export default function MeetingUnavailable(props: inferSSRProps<typeof getServer
|
||||
<HeadSeo title="Meeting Unavailable" description="Meeting Unavailable" />
|
||||
<main className="mx-auto my-24 max-w-3xl">
|
||||
<div className="fixed inset-0 z-50 overflow-y-auto">
|
||||
<div className="flex min-h-screen items-end justify-center px-4 pt-4 pb-20 text-center sm:block sm:p-0">
|
||||
<div className="flex min-h-screen items-end justify-center px-4 pb-20 pt-4 text-center sm:block sm:p-0">
|
||||
<div className="fixed inset-0 my-4 transition-opacity sm:my-0" aria-hidden="true">
|
||||
<span className="hidden sm:inline-block sm:h-screen sm:align-middle" aria-hidden="true">
|
||||
​
|
||||
</span>
|
||||
<div
|
||||
className="bg-default inline-block transform overflow-hidden rounded-lg px-4 pt-5 pb-4 text-left align-bottom shadow-xl transition-all sm:my-8 sm:w-full sm:max-w-sm sm:p-6 sm:align-middle"
|
||||
className="bg-default inline-block transform overflow-hidden rounded-lg px-4 pb-4 pt-5 text-left align-bottom shadow-xl transition-all sm:my-8 sm:w-full sm:max-w-sm sm:p-6 sm:align-middle"
|
||||
role="dialog"
|
||||
aria-modal="true"
|
||||
aria-labelledby="modal-headline">
|
||||
@@ -38,12 +38,12 @@ export default function MeetingUnavailable(props: inferSSRProps<typeof getServer
|
||||
This meeting is in the past.
|
||||
</h3>
|
||||
</div>
|
||||
<div className="mt-4 border-t border-b py-4">
|
||||
<div className="mt-4 border-b border-t py-4">
|
||||
<h2 className="font-cal text-default mb-2 text-center text-lg font-medium">
|
||||
{props.booking.title}
|
||||
</h2>
|
||||
<p className="text-subtle text-center">
|
||||
<Calendar className="mr-1 -mt-1 inline-block h-4 w-4" />
|
||||
<Calendar className="-mt-1 mr-1 inline-block h-4 w-4" />
|
||||
{dayjs(props.booking.startTime).format(detectBrowserTimeFormat + ", dddd DD MMMM YYYY")}
|
||||
</p>
|
||||
</div>
|
||||
|
||||
@@ -23,7 +23,7 @@ export default function MeetingNotStarted(props: inferSSRProps<typeof getServerS
|
||||
<>
|
||||
<h2 className="mb-2 text-center font-medium">{props.booking.title}</h2>
|
||||
<p className="text-subtle text-center">
|
||||
<Calendar className="mr-1 -mt-1 inline-block h-4 w-4" />
|
||||
<Calendar className="-mt-1 mr-1 inline-block h-4 w-4" />
|
||||
{dayjs(props.booking.startTime).format(detectBrowserTimeFormat + ", dddd DD MMMM YYYY")}
|
||||
</p>
|
||||
</>
|
||||
|
||||
File diff suppressed because one or more lines are too long
|
After Width: | Height: | Size: 18 KiB |
File diff suppressed because one or more lines are too long
|
After Width: | Height: | Size: 16 KiB |
File diff suppressed because one or more lines are too long
|
After Width: | Height: | Size: 24 KiB |
@@ -172,7 +172,7 @@
|
||||
"guests": "الضيوف",
|
||||
"guest": "الضيف",
|
||||
"web_conferencing_details_to_follow": "تفاصيل مؤتمرات الويب القادمة.",
|
||||
"the_username": "اسم المستخدم",
|
||||
"404_the_user": "اسم المستخدم",
|
||||
"username": "اسم المستخدم",
|
||||
"is_still_available": "لا يزال متاحًا.",
|
||||
"documentation": "المستندات",
|
||||
@@ -183,7 +183,7 @@
|
||||
"blog_description": "اقرأ أحدث أخبارنا ومقالاتنا",
|
||||
"join_our_community": "انضم إلى مجتمعنا",
|
||||
"join_our_slack": "انضم إلى Slack الخاص بنا",
|
||||
"claim_username_and_schedule_events": "احصل على اسم المستخدم وقم بجدولة الأحداث",
|
||||
"404_claim_entity_user": "احصل على اسم المستخدم وقم بجدولة الأحداث",
|
||||
"popular_pages": "الصفحات الشائعة",
|
||||
"register_now": "التسجيل الآن",
|
||||
"register": "تسجيل",
|
||||
@@ -231,7 +231,6 @@
|
||||
"finish": "إنهاء",
|
||||
"few_sentences_about_yourself": "اكتب بضع جمل عن نفسك. سيظهر هذا على صفحة عنوان URL الشخصية لديك.",
|
||||
"nearly_there": "أوشكت على الانتهاء!",
|
||||
"nearly_there_instructions": "آخر شيء: تساعدك كتابة وصف موجز عنك ووضع صورة في الحصول على عمليات الحجز وإعلام الأشخاص بمَن سيحجزون معه حقًا.",
|
||||
"set_availability_instructions": "حدد الفترات الزمنية التي تكون متاحًا فيها بشكل متكرر. يمكنك لاحقًا تحديد المزيد منها وربطها مع تقاويم مختلفة.",
|
||||
"set_availability": "تحديد الوقت الذي تكون فيه متاحًا",
|
||||
"continue_without_calendar": "المتابعة من دون تقويم",
|
||||
@@ -1408,7 +1407,6 @@
|
||||
"team_url_required": "يجب إدخال عنوان URL للفريق",
|
||||
"team_url_taken": "عنوان URL هذا مأخوذ بالفعل",
|
||||
"team_publish": "نشر فريق",
|
||||
"number_sms_notifications": "رقم الهاتف (إشعارات الرسائل النصية)",
|
||||
"attendee_email_variable": "اسم الحاضر",
|
||||
"attendee_email_info": "البريد الإلكتروني للشخص الحجز",
|
||||
"kbar_search_placeholder": "اكتب أمرًا أو بحثًا...",
|
||||
|
||||
@@ -172,7 +172,7 @@
|
||||
"guests": "Hosté",
|
||||
"guest": "Host",
|
||||
"web_conferencing_details_to_follow": "Podrobnosti k videohovoru, které je třeba následovat.",
|
||||
"the_username": "Uživatelské jméno",
|
||||
"404_the_user": "Uživatelské jméno",
|
||||
"username": "Uživatelské jméno",
|
||||
"is_still_available": "je stále dostupný.",
|
||||
"documentation": "Dokumentace",
|
||||
@@ -183,7 +183,7 @@
|
||||
"blog_description": "Přečtěte si naše nejnovější novinky a články",
|
||||
"join_our_community": "Připojte se k naší komunitě",
|
||||
"join_our_slack": "Připojte se k našemu Slacku",
|
||||
"claim_username_and_schedule_events": "Nárokujte si své uživatelské jméno a plánujte události",
|
||||
"404_claim_entity_user": "Nárokujte si své uživatelské jméno a plánujte události",
|
||||
"popular_pages": "Oblíbené stránky",
|
||||
"register_now": "Zaregistrovat se nyní",
|
||||
"register": "Registrovat se",
|
||||
@@ -231,7 +231,6 @@
|
||||
"finish": "Dokončit",
|
||||
"few_sentences_about_yourself": "Pár vět o vás. Budou se zobrazovat na URL vaší osobní stránky.",
|
||||
"nearly_there": "Téměř hotovo!",
|
||||
"nearly_there_instructions": "A na závěr: Stručný text o vás a fotografie vám značně pomůžou v získávání rezervací. A ostatní díky tomu budou vědět, koho si rezervují.",
|
||||
"set_availability_instructions": "Určete si časové intervaly, kdy jste pravidelně dostupní. Později můžete vytvořit další a přiřadit je různým kalendářům.",
|
||||
"set_availability": "Nastavte, jak jste dostupní",
|
||||
"continue_without_calendar": "Pokračovat bez kalendáře",
|
||||
@@ -1408,7 +1407,6 @@
|
||||
"team_url_required": "Musíte zadat adresu URL týmu",
|
||||
"team_url_taken": "Tato adresa URL je už obsazená",
|
||||
"team_publish": "Zveřejnit tým",
|
||||
"number_sms_notifications": "Číslo telefonu (SMS oznámení)",
|
||||
"attendee_email_variable": "E-mail účastníka",
|
||||
"attendee_email_info": "E-mail osoby provádějící rezervaci",
|
||||
"kbar_search_placeholder": "Zadejte příkaz nebo vyhledejte...",
|
||||
|
||||
@@ -165,7 +165,7 @@
|
||||
"guests": "Gæster",
|
||||
"guest": "Gæst",
|
||||
"web_conferencing_details_to_follow": "Webkonference detaljer følger i bekræftelses-e-mailen.",
|
||||
"the_username": "Brugernavnet",
|
||||
"404_the_user": "Brugernavnet",
|
||||
"username": "Brugernavn",
|
||||
"is_still_available": "er stadig tilgængeligt.",
|
||||
"documentation": "Dokumentation",
|
||||
@@ -176,7 +176,7 @@
|
||||
"blog_description": "Læs vores seneste nyheder og artikler",
|
||||
"join_our_community": "Tilmeld dig vores fællesskab",
|
||||
"join_our_slack": "Tilmeld dig vores Slack",
|
||||
"claim_username_and_schedule_events": "Få dit brugernavn og planlæg begivenheder",
|
||||
"404_claim_entity_user": "Få dit brugernavn og planlæg begivenheder",
|
||||
"popular_pages": "Populære sider",
|
||||
"register_now": "Tilmeld dig nu",
|
||||
"register": "Registrér",
|
||||
@@ -222,7 +222,6 @@
|
||||
"finish": "Afslut",
|
||||
"few_sentences_about_yourself": "Et par sætninger om dig selv. Dette vil blive vist på din personlige url side.",
|
||||
"nearly_there": "Du er der næsten!",
|
||||
"nearly_there_instructions": "Til sidst, en kort beskrivelse om dig og et foto vil virkelig hjælpe dig med at få bookinger og lade folk vide, hvem de booker med.",
|
||||
"set_availability_instructions": "Definér tidsintervaller hvor du er tilgængelig på en tilbagevendende basis. Du kan oprette flere af disse senere og tildele dem til forskellige kalendere.",
|
||||
"set_availability": "Angiv din tilgængelighed",
|
||||
"continue_without_calendar": "Fortsæt uden kalender",
|
||||
@@ -1344,7 +1343,6 @@
|
||||
"team_url_required": "Der skal indtastes et team URL",
|
||||
"team_url_taken": "Denne URL er allerede i brug",
|
||||
"team_publish": "Publicer team",
|
||||
"number_sms_notifications": "Telefonnummer (SMS notifikationer)",
|
||||
"attendee_email_variable": "Deltagers e-mail",
|
||||
"attendee_email_info": "Personen der booker's e-mail",
|
||||
"kbar_search_placeholder": "Indtast en kommando eller søgning...",
|
||||
|
||||
@@ -172,7 +172,7 @@
|
||||
"guests": "Gäste",
|
||||
"guest": "Gast",
|
||||
"web_conferencing_details_to_follow": "Web-Konferenzdetails folgen.",
|
||||
"the_username": "Der Benutzername",
|
||||
"404_the_user": "Der Benutzername",
|
||||
"username": "Benutzername",
|
||||
"is_still_available": "ist noch verfügbar.",
|
||||
"documentation": "Dokumentation",
|
||||
@@ -183,7 +183,7 @@
|
||||
"blog_description": "Lesen Sie unsere Neuigkeiten und Artikel",
|
||||
"join_our_community": "Treten Sie unserer Community bei",
|
||||
"join_our_slack": "Treten Sie unserem Slack bei",
|
||||
"claim_username_and_schedule_events": "Benutzername registrieren und Termine vergeben",
|
||||
"404_claim_entity_user": "Benutzername registrieren und Termine vergeben",
|
||||
"popular_pages": "Beliebte Seiten",
|
||||
"register_now": "Jetzt registrieren",
|
||||
"register": "Registrieren",
|
||||
@@ -231,7 +231,6 @@
|
||||
"finish": "Fertig",
|
||||
"few_sentences_about_yourself": "Schreiben Sie eine kurze Beschreibung, welche auf Ihrer persönlichen URL-Seite erscheinen wird.",
|
||||
"nearly_there": "Fast fertig",
|
||||
"nearly_there_instructions": "Zuletzt, eine kurze Beschreibung über Sie und ein Foto hilft, mehr Buchungen zu machen, da Kunden dann besser wissen, wen sie buchen.",
|
||||
"set_availability_instructions": "Definieren Sie Zeiträume, wenn Sie wiederkehrend verfügbar sind. Sie können später weitere Zeiträume erstellen und verschiedenen Kalendern zuordnen.",
|
||||
"set_availability": "Verfügbarkeit festlegen",
|
||||
"continue_without_calendar": "Ohne Kalender fortfahren",
|
||||
@@ -747,7 +746,7 @@
|
||||
"delete_managed_event_type": "Verwalteten Ereignistyp löschen?",
|
||||
"delete_event_type_description": "Jede Person, mit der Sie diesen Link geteilt haben, kann nicht mehr über den Link buchen.",
|
||||
"delete_managed_event_type_description": "<ul><li>Die Ereignistypen der diesem Ereignistypen zugewiesenen Mitglieder werden ebenfalls gelöscht.</li><li>Jeder, mit dem sie ihren Link geteilt haben, wird nicht mehr über diesen Link buchen können.</li></ul>",
|
||||
"confirm_delete_event_type": "<ul><li>Die Ereignistypen der diesem Ereignistypen zugewiesenen Mitglieder werden ebenfalls gelöscht.</li><li>Jeder, mit dem sie ihren Link geteilt haben, wird nicht mehr über diesen Link buchen können.</li></ul>",
|
||||
"confirm_delete_event_type": "Ja, löschen",
|
||||
"delete_account": "Account löschen",
|
||||
"confirm_delete_account": "Ja, Account löschen",
|
||||
"delete_account_confirmation_message": "Sind Sie sicher, dass Sie Ihr {{appName}}-Konto löschen möchten? Sie können dann nicht mehr über Ihre geteilten Links gebucht werden und alle Einstellungen werden gelöscht.",
|
||||
@@ -1408,7 +1407,6 @@
|
||||
"team_url_required": "Team-URL muss eingegeben werden",
|
||||
"team_url_taken": "Diese URL ist bereits vergeben",
|
||||
"team_publish": "Team veröffentlichen",
|
||||
"number_sms_notifications": "Telefonnummer (SMS-Benachrichtigungen)",
|
||||
"attendee_email_variable": "Teilnehmer E-Mail",
|
||||
"attendee_email_info": "Die E-Mail-Adresse der buchenden Person",
|
||||
"kbar_search_placeholder": "Geben Sie einen Befehl ein oder suchen Sie ...",
|
||||
|
||||
@@ -187,7 +187,7 @@
|
||||
"guests": "Guests",
|
||||
"guest": "Guest",
|
||||
"web_conferencing_details_to_follow": "Web conferencing details to follow in the confirmation email.",
|
||||
"the_username": "The username",
|
||||
"404_the_user": "The username",
|
||||
"username": "Username",
|
||||
"is_still_available": "is still available.",
|
||||
"documentation": "Documentation",
|
||||
@@ -198,7 +198,7 @@
|
||||
"blog_description": "Read our latest news and articles",
|
||||
"join_our_community": "Join our community",
|
||||
"join_our_slack": "Join our Slack",
|
||||
"claim_username_and_schedule_events": "Claim your username and schedule events",
|
||||
"404_claim_entity_user": "Claim your username and schedule events",
|
||||
"popular_pages": "Popular pages",
|
||||
"register_now": "Register now",
|
||||
"register": "Register",
|
||||
@@ -246,10 +246,10 @@
|
||||
"available_apps": "Available Apps",
|
||||
"check_email_reset_password": "Check your email. We sent you a link to reset your password.",
|
||||
"finish": "Finish",
|
||||
"organization_general_description":"Manage settings for your team language and timezone",
|
||||
"organization_general_description": "Manage settings for your team language and timezone",
|
||||
"few_sentences_about_yourself": "A few sentences about yourself. This will appear on your personal url page.",
|
||||
"nearly_there": "Nearly there!",
|
||||
"nearly_there_instructions": "Last thing, a brief description about you and a photo really help you get bookings and let people know who they’re booking with.",
|
||||
"nearly_there_instructions": "Last thing, a brief description about you and a photo really helps you get bookings and let people know who they’re booking with.",
|
||||
"set_availability_instructions": "Define ranges of time when you are available on a recurring basis. You can create more of these later and assign them to different calendars.",
|
||||
"set_availability": "Set your availability",
|
||||
"continue_without_calendar": "Continue without calendar",
|
||||
@@ -339,6 +339,11 @@
|
||||
"cancelling_event_recurring": "The event is one instance of a recurring event.",
|
||||
"cancelling_all_recurring": "These are all remaining instances in the recurring event.",
|
||||
"error_with_status_code_occured": "An error with status code {{status}} occurred.",
|
||||
"error_event_type_url_duplicate": "An event type with this URL already exists.",
|
||||
"error_event_type_unauthorized_create": "You are not able to create this event",
|
||||
"error_event_type_unauthorized_update": "You are not able to edit this event",
|
||||
"error_workflow_unauthorized_create": "You are not able to create this workflow",
|
||||
"error_schedule_unauthorized_create": "You are not able to create this schedule",
|
||||
"booking_already_cancelled": "This booking was already canceled",
|
||||
"booking_already_accepted_rejected": "This booking was already accepted or rejected",
|
||||
"go_back_home": "Go back home",
|
||||
@@ -349,7 +354,6 @@
|
||||
"no_status_bookings_yet_description": "You have no {{status}} bookings. {{description}}",
|
||||
"event_between_users": "{{eventName}} between {{host}} and {{attendeeName}}",
|
||||
"bookings": "Bookings",
|
||||
"booking_not_found":"Booking not found",
|
||||
"bookings_description": "See upcoming and past events booked through your event type links.",
|
||||
"upcoming_bookings": "As soon as someone books a time with you it will show up here.",
|
||||
"recurring_bookings": "As soon as someone books a recurring meeting with you it will show up here.",
|
||||
@@ -547,7 +551,7 @@
|
||||
"team_description": "A few sentences about your team. This will appear on your team's url page.",
|
||||
"org_description": "A few sentances about your organization. This will appear on your organization's url page.",
|
||||
"members": "Members",
|
||||
"organization_members":"Organization members",
|
||||
"organization_members": "Organization members",
|
||||
"member": "Member",
|
||||
"number_member_one": "{{count}} member",
|
||||
"number_member_other": "{{count}} members",
|
||||
@@ -897,6 +901,7 @@
|
||||
"create_events_on": "Create events on",
|
||||
"enterprise_license": "This is an enterprise feature",
|
||||
"enterprise_license_description": "To enable this feature, have an administrator go to {{setupUrl}} to enter a license key. If a license key is already in place, please contact {{supportMail}} for help.",
|
||||
"enterprise_license_development": "You can test this feature on development mode. For production usage please have an administrator go to <2>/auth/setup</2> to enter a license key.",
|
||||
"missing_license": "Missing License",
|
||||
"signup_requires": "Commercial license required",
|
||||
"signup_requires_description": "{{companyName}} currently does not offer a free open source version of the sign up page. To receive full access to the signup components you need to acquire a commercial license. For personal use we recommend the Prisma Data Platform or any other Postgres interface to create accounts.",
|
||||
@@ -1466,7 +1471,7 @@
|
||||
"team_url_required": "Must enter a team URL",
|
||||
"team_url_taken": "This URL is already taken",
|
||||
"team_publish": "Publish team",
|
||||
"number_sms_notifications": "Phone number (SMS notifications)",
|
||||
"number_sms_notifications": "Phone number (SMS notifications)",
|
||||
"attendee_email_variable": "Attendee email",
|
||||
"attendee_email_info": "The person booking's email",
|
||||
"kbar_search_placeholder": "Type a command or search...",
|
||||
@@ -1523,6 +1528,9 @@
|
||||
"no_available_apps": "There are no available apps",
|
||||
"no_available_apps_description": "Please ensure there are apps in your deployment under 'packages/app-store'",
|
||||
"no_apps": "There are no apps enabled in this instance of Cal",
|
||||
"no_apps_configured": "No app has been configured yet",
|
||||
"enable_in_settings": "You can enable apps in the settings",
|
||||
"please_contact_admin": "Please contact your admin",
|
||||
"apps_settings": "Apps settings",
|
||||
"fill_this_field": "Please fill in this field",
|
||||
"options": "Options",
|
||||
@@ -1726,7 +1734,6 @@
|
||||
"app_is_connected": "{{dependencyName}} is connected",
|
||||
"requires_app": "Requires {{dependencyName}}",
|
||||
"verification_code": "Verification code",
|
||||
"invalid_verification_link": "Invalid verification link",
|
||||
"can_you_try_again": "Can you try again with a different time?",
|
||||
"verify": "Verify",
|
||||
"timezone_info": "The timezone of the person receiving",
|
||||
@@ -1889,9 +1896,15 @@
|
||||
"my_profile": "My Profile",
|
||||
"my_settings": "My Settings",
|
||||
"sender_id_info": "Name or number shown as the sender of an SMS (some countries do not allow alphanumeric sender IDs)",
|
||||
"google_new_spam_policy": "Google’s new spam policy could prevent you from receiving any email and calendar notifications about this booking.",
|
||||
"resolve": "Resolve",
|
||||
"no_organization_slug": "There was an error creating teams for this organization. Missing URL slug.",
|
||||
"org_name": "Organization name",
|
||||
"org_url": "Organization URL",
|
||||
"copy_link_org": "Copy link to organization",
|
||||
"404_the_org": "The organization",
|
||||
"404_the_team": "The team",
|
||||
"404_claim_entity_org": "Claim your subdomain for your organization",
|
||||
"404_claim_entity_team": "Claim this team and start managing schedules collectively",
|
||||
"ADD_NEW_STRINGS_ABOVE_THIS_LINE_TO_PREVENT_MERGE_CONFLICTS": "↑↑↑↑↑↑↑↑↑↑↑↑↑ Add your new strings above here ↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑"
|
||||
}
|
||||
|
||||
@@ -172,7 +172,7 @@
|
||||
"guests": "Invitados",
|
||||
"guest": "Invitado",
|
||||
"web_conferencing_details_to_follow": "Los detalles a seguir de su conferencia, en el correo de confirmación.",
|
||||
"the_username": "El Nombre de Usuario",
|
||||
"404_the_user": "El Nombre de Usuario",
|
||||
"username": "Usuario",
|
||||
"is_still_available": "aún está disponible.",
|
||||
"documentation": "Documentación",
|
||||
@@ -183,7 +183,7 @@
|
||||
"blog_description": "Lee nuestras últimas noticias y artículos",
|
||||
"join_our_community": "Únete a nuestra comunidad",
|
||||
"join_our_slack": "Únete a nuestro Slack",
|
||||
"claim_username_and_schedule_events": "Reclama tu nombre de usuario y programa eventos",
|
||||
"404_claim_entity_user": "Reclama tu nombre de usuario y programa eventos",
|
||||
"popular_pages": "Páginas Populares",
|
||||
"register_now": "Regístrate Ahora",
|
||||
"register": "Registrar",
|
||||
@@ -231,7 +231,6 @@
|
||||
"finish": "Terminar",
|
||||
"few_sentences_about_yourself": "Unas pocas frases sobre ti mismo. Esto aparecerá en tu página de url personal.",
|
||||
"nearly_there": "¡Ya casi está!",
|
||||
"nearly_there_instructions": "Por último, una breve descripción de ti y una foto realmente te ayudan a hacer tus reservas y a informar a la gente con quién está reservando.",
|
||||
"set_availability_instructions": "Defina los intervalos de tiempo cuando esté disponible de forma recurrente. Puede crear más tarde y asignarlos a diferentes calendarios.",
|
||||
"set_availability": "Establecer Disponibilidad",
|
||||
"continue_without_calendar": "Continuar sin calendario",
|
||||
@@ -1408,7 +1407,6 @@
|
||||
"team_url_required": "Debe introducir una URL del equipo",
|
||||
"team_url_taken": "Esta URL ya está en uso",
|
||||
"team_publish": "Publicar equipo",
|
||||
"number_sms_notifications": "Número de teléfono (notificaciones por SMS)",
|
||||
"attendee_email_variable": "Correo electrónico del asistente",
|
||||
"attendee_email_info": "El correo electrónico de la persona que reserva",
|
||||
"kbar_search_placeholder": "Escriba un comando o búsqueda...",
|
||||
|
||||
@@ -187,7 +187,7 @@
|
||||
"guests": "Invités",
|
||||
"guest": "Invité(e)",
|
||||
"web_conferencing_details_to_follow": "Les détails du rendez-vous sont à retrouver dans l'e-mail de confirmation.",
|
||||
"the_username": "Le nom d'utilisateur",
|
||||
"404_the_user": "Le nom d'utilisateur",
|
||||
"username": "Nom d'utilisateur",
|
||||
"is_still_available": "est toujours disponible.",
|
||||
"documentation": "Documentation",
|
||||
@@ -198,7 +198,7 @@
|
||||
"blog_description": "Lire nos dernières actualités et articles",
|
||||
"join_our_community": "Rejoignez notre communauté",
|
||||
"join_our_slack": "Rejoindre notre Slack",
|
||||
"claim_username_and_schedule_events": "Réservez votre nom d'utilisateur et planifiez des événements",
|
||||
"404_claim_entity_user": "Réservez votre nom d'utilisateur et planifiez des événements",
|
||||
"popular_pages": "Pages populaires",
|
||||
"register_now": "S'inscrire maintenant",
|
||||
"register": "S'inscrire",
|
||||
@@ -339,6 +339,11 @@
|
||||
"cancelling_event_recurring": "L'événement est une instance d'un événement récurrent.",
|
||||
"cancelling_all_recurring": "Voici toutes les instances restantes dans l'événement récurrent.",
|
||||
"error_with_status_code_occured": "Une erreur avec le code de statut {{status}} est survenue.",
|
||||
"error_event_type_url_duplicate": "Un type d'événement avec cette URL existe déjà.",
|
||||
"error_event_type_unauthorized_create": "Vous n'êtes pas autorisé à créer cet événement",
|
||||
"error_event_type_unauthorized_update": "Vous n'êtes pas autorisé à modifier cet événement",
|
||||
"error_workflow_unauthorized_create": "Vous n'êtes pas autorisé à créer ce workflow",
|
||||
"error_schedule_unauthorized_create": "Vous n'êtes pas autorisé à créer ce planning",
|
||||
"booking_already_cancelled": "Cette réservation a déjà été annulée",
|
||||
"booking_already_accepted_rejected": "Cette réservation a déjà été acceptée ou refusée.",
|
||||
"go_back_home": "Retour à la page d'accueil",
|
||||
@@ -349,7 +354,6 @@
|
||||
"no_status_bookings_yet_description": "Vous n'avez pas de réservations {{status}}. {{description}}",
|
||||
"event_between_users": "{{eventName}} entre {{host}} et {{attendeeName}}",
|
||||
"bookings": "Réservations",
|
||||
"booking_not_found": "Réservation introuvable",
|
||||
"bookings_description": "Consultez les événements à venir et passés réservés via vos liens de type d'événement.",
|
||||
"upcoming_bookings": "Dès que quelqu'un réservera un rendez-vous avec vous, il apparaîtra ici.",
|
||||
"recurring_bookings": "Dès que quelqu'un réserve un rendez-vous récurrent avec vous, il apparaît ici.",
|
||||
@@ -622,7 +626,7 @@
|
||||
"default_duration_no_options": "Veuillez d'abord choisir les durées disponibles",
|
||||
"multiple_duration_mins": "{{count}} $t(minute_timeUnit)",
|
||||
"minutes": "minutes",
|
||||
"round_robin": "Round-Robin",
|
||||
"round_robin": "Round-robin",
|
||||
"round_robin_description": "Alternez vos rendez-vous entre plusieurs membres d'équipe.",
|
||||
"managed_event": "Événement géré",
|
||||
"username_placeholder": "nom d'utilisateur",
|
||||
@@ -897,6 +901,7 @@
|
||||
"create_events_on": "Créer des événements dans :",
|
||||
"enterprise_license": "Il s'agit d'une fonctionnalité d'entreprise",
|
||||
"enterprise_license_description": "Pour activer cette fonctionnalité, demandez à un administrateur d'accéder à {{setupUrl}} pour saisir une clé de licence. Si une clé de licence est déjà en place, veuillez contacter {{supportMail}} pour obtenir de l'aide.",
|
||||
"enterprise_license_development": "Vous pouvez tester cette fonctionnalité en mode développement. Pour une utilisation en production, veuillez demander à un administrateur d'aller à <2>/auth/setup</2> pour entrer une clé de licence.",
|
||||
"missing_license": "Licence manquante",
|
||||
"signup_requires": "Licence commerciale requise",
|
||||
"signup_requires_description": "{{companyName}} ne propose actuellement pas de version open source gratuite de la page d'inscription. Pour recevoir un accès complet aux composants d'inscription, vous devez acquérir une licence commerciale. Pour un usage personnel, nous recommandons la plateforme de données Prisma ou toute autre interface Postgres pour créer des comptes.",
|
||||
@@ -1405,7 +1410,7 @@
|
||||
"manage_billing": "Gérer la facturation",
|
||||
"manage_billing_description": "Gérez tout ce qui est en lien avec la facturation.",
|
||||
"billing_freeplan_title": "Vous êtes actuellement sur le plan Gratuit",
|
||||
"billing_freeplan_description": "Nous travaillons mieux en équipe. Élargissez vos workflows avec le Round-Robin et les événements collectifs et produisez des formulaires de routage avancés.",
|
||||
"billing_freeplan_description": "Nous travaillons mieux en équipe. Élargissez vos workflows avec les événements round-robin et collectifs et produisez des formulaires de routage avancés.",
|
||||
"billing_freeplan_cta": "Essayer maintenant",
|
||||
"billing_portal": "Portail de facturation",
|
||||
"billing_help_cta": "Contacter l'assistance",
|
||||
@@ -1487,11 +1492,11 @@
|
||||
"collective_scheduling": "Planification collective",
|
||||
"make_it_easy_to_book": "Facilitez la réservation de votre équipe lorsque tout le monde est disponible.",
|
||||
"find_the_best_person": "Trouvez la meilleure personne disponible et alternez à travers votre équipe.",
|
||||
"fixed_round_robin": "Round-Robin fixe",
|
||||
"add_one_fixed_attendee": "Ajoutez un participant fixe et le Round-Robin via un certain nombre de participants.",
|
||||
"fixed_round_robin": "Round-robin fixe",
|
||||
"add_one_fixed_attendee": "Ajoutez un participant fixe et le round-robin via un certain nombre de participants.",
|
||||
"calcom_is_better_with_team": "{{appName}} est meilleur en équipe",
|
||||
"the_calcom_team": "L'équipe {{companyName}}",
|
||||
"add_your_team_members": "Ajoutez les membres de votre équipe à vos types d'événements. Utilisez la planification collective pour inclure tout le monde ou trouvez la personne la plus appropriée avec la planification Round-Robin.",
|
||||
"add_your_team_members": "Ajoutez les membres de votre équipe à vos types d'événements. Utilisez la planification collective pour inclure tout le monde ou trouvez la personne la plus appropriée avec la planification round-robin.",
|
||||
"booking_limit_reached": "La limite de réservation pour ce type d'événement a été atteinte",
|
||||
"duration_limit_reached": "La limite de durée pour ce type d'événement a été atteinte",
|
||||
"admin_has_disabled": "Un administrateur a désactivé {{appName}}",
|
||||
@@ -1523,6 +1528,9 @@
|
||||
"no_available_apps": "Il n'y a aucune application disponible",
|
||||
"no_available_apps_description": "Veuillez vous assurer qu'il y a des applications dans votre déploiement sous \"packages/app-store\"",
|
||||
"no_apps": "Aucune application n'est activée dans cette instance de Cal",
|
||||
"no_apps_configured": "Aucune application n'a encore été configurée",
|
||||
"enable_in_settings": "Vous pouvez activer les applications dans les paramètres",
|
||||
"please_contact_admin": "Veuillez contacter votre administrateur",
|
||||
"apps_settings": "Paramètres des applications",
|
||||
"fill_this_field": "Veuillez remplir ce champ",
|
||||
"options": "Options",
|
||||
@@ -1569,7 +1577,7 @@
|
||||
"assignment": "Affectation",
|
||||
"fixed_hosts": "Hôtes fixes",
|
||||
"add_fixed_hosts": "Ajouter des hôtes fixes",
|
||||
"round_robin_hosts": "Hôtes Round-Robin",
|
||||
"round_robin_hosts": "Hôtes round-robin",
|
||||
"minimum_round_robin_hosts_count": "Nombre d'hôtes requis pour participer",
|
||||
"hosts": "Hôtes",
|
||||
"upgrade_to_enable_feature": "Vous devez créer une équipe pour activer cette fonctionnalité. Cliquez pour créer une équipe.",
|
||||
@@ -1649,8 +1657,8 @@
|
||||
"create_a_managed_event": "Créer un type d'événement géré",
|
||||
"meetings_are_better_with_the_right": "Les rendez-vous sont meilleurs avec les bons membres d'équipe. Invitez-les maintenant.",
|
||||
"create_a_one_one_template": "Créez un modèle unique pour un type d'événement et distribuez-le à plusieurs membres.",
|
||||
"collective_or_roundrobin": "Collective ou Round-Robin",
|
||||
"book_your_team_members": "Réservez les membres de votre équipe entre eux avec des événements collectifs ou mettez en place un cycle pour obtenir la bonne personne avec le Round-Robin.",
|
||||
"collective_or_roundrobin": "Collective ou round-robin",
|
||||
"book_your_team_members": "Réservez les membres de votre équipe entre eux avec des événements collectifs ou mettez en place un cycle pour obtenir la bonne personne avec le round-robin.",
|
||||
"event_no_longer_attending_subject": "Ne participe plus à {{title}} le {{date}}",
|
||||
"no_longer_attending": "Vous ne participez plus à cet événement",
|
||||
"attendee_no_longer_attending_subject": "Un participant ne participe plus à {{title}} le {{date}}",
|
||||
@@ -1726,7 +1734,6 @@
|
||||
"app_is_connected": "{{dependencyName}} est connectée",
|
||||
"requires_app": "Requiert {{dependencyName}}",
|
||||
"verification_code": "Code de vérification",
|
||||
"invalid_verification_link": "Lien de vérification introuvable",
|
||||
"can_you_try_again": "Pouvez-vous réessayer avec un autre créneau ?",
|
||||
"verify": "Vérifier",
|
||||
"timezone_info": "Le fuseau horaire du destinataire",
|
||||
@@ -1833,6 +1840,7 @@
|
||||
"pop_up_element_click": "Clic sur un élément",
|
||||
"open_dialog_with_element_click": "Ouvre votre boîte de dialogue Cal lorsque quelqu'un clique sur un élément.",
|
||||
"need_help_embedding": "Besoin d'aide ? Consultez nos guides pour intégrer Cal sur Wix, Squarespace ou WordPress, consultez nos questions fréquentes ou explorez les options d'intégration avancées.",
|
||||
"book_my_cal": "Prendre rendez-vous",
|
||||
"invite_as": "Inviter en tant que",
|
||||
"form_updated_successfully": "Formulaire mis à jour avec succès.",
|
||||
"disable_attendees_confirmation_emails": "Désactiver les e-mails de confirmation par défaut pour les participants",
|
||||
@@ -1846,7 +1854,7 @@
|
||||
"first_event_type_webhook_description": "Créez votre premier webhook pour ce type d'événement",
|
||||
"create_for": "Créer pour",
|
||||
"setup_organization": "Configurer une organisation",
|
||||
"organization_banner_description": "Créez un environnement où vos équipes peuvent créer des applications partagées, des workflows et des types d'événements avec une planification Round-Robin et collective.",
|
||||
"organization_banner_description": "Créez un environnement où vos équipes peuvent créer des applications partagées, des workflows et des types d'événements avec une planification round-robin et collective.",
|
||||
"organization_banner_title": "Gérer les organisations avec plusieurs équipes",
|
||||
"set_up_your_organization": "Configurer votre organisation",
|
||||
"organizations_description": "Les organisations sont des environnements partagés dans lesquels les équipes peuvent créer des types d'événements partagés, des applications, des workflows, etc.",
|
||||
@@ -1888,9 +1896,15 @@
|
||||
"my_profile": "Mon profil",
|
||||
"my_settings": "Mes paramètres",
|
||||
"sender_id_info": "Nom ou numéro affiché comme expéditeur d'un SMS (certains pays n'autorisent pas les ID d'expéditeur alphanumériques)",
|
||||
"google_new_spam_policy": "La nouvelle politique anti-spam de Google peut vous empêcher de recevoir des notifications par e-mail ou sur votre calendrier concernant cette réservation.",
|
||||
"resolve": "Résoudre",
|
||||
"no_organization_slug": "Une erreur s'est produite lors de la création des équipes pour cette organisation. Le slug du lien est manquant.",
|
||||
"org_name": "Nom de l'organisation",
|
||||
"org_url": "Lien de l'organisation",
|
||||
"copy_link_org": "Copier le lien vers l’organisation",
|
||||
"404_the_org": "L'organisation",
|
||||
"404_the_team": "L'équipe",
|
||||
"404_claim_entity_org": "Réservez votre sous-domaine pour votre organisation",
|
||||
"404_claim_entity_team": "Revendiquez cette équipe et commencez à gérer vos plannings collectivement",
|
||||
"ADD_NEW_STRINGS_ABOVE_THIS_LINE_TO_PREVENT_MERGE_CONFLICTS": "↑↑↑↑↑↑↑↑↑↑↑↑↑ Ajoutez vos nouvelles chaînes ci-dessus ↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑"
|
||||
}
|
||||
|
||||
@@ -179,7 +179,7 @@
|
||||
"guests": "אורחים",
|
||||
"guest": "אורח",
|
||||
"web_conferencing_details_to_follow": "פרטי שיחת הוועידה המקוונת, הכלולים בדוא\"ל האישור, שיש לפעול על פיהם.",
|
||||
"the_username": "שם המשתמש",
|
||||
"404_the_user": "שם המשתמש",
|
||||
"username": "שם משתמש",
|
||||
"is_still_available": "עדיין זמין.",
|
||||
"documentation": "תיעוד",
|
||||
@@ -190,7 +190,7 @@
|
||||
"blog_description": "לעיון בחדשות ובמאמרים האחרונים שלנו",
|
||||
"join_our_community": "הצטרף לקהילה שלנו",
|
||||
"join_our_slack": "הצטרף לצ׳אט שלנו ב-Slack",
|
||||
"claim_username_and_schedule_events": "קבל את שם המשתמש שלך והתחל לקבוע מועדים לאירועים",
|
||||
"404_claim_entity_user": "קבל את שם המשתמש שלך והתחל לקבוע מועדים לאירועים",
|
||||
"popular_pages": "דפים פופולריים",
|
||||
"register_now": "להירשם עכשיו",
|
||||
"register": "הרשמה",
|
||||
@@ -239,7 +239,6 @@
|
||||
"finish": "סיום",
|
||||
"few_sentences_about_yourself": "מספר משפטים אודותיך. המידע הזה יופיע בדף ה-URL האישי שלך.",
|
||||
"nearly_there": "כמעט סיימנו!",
|
||||
"nearly_there_instructions": "דבר אחרון: תיאור קצר אודותיך בתוספת תמונה ממש עוזרים להשיג הזמנות ומאפשרים לאנשים לדעת עם מי הם עומדים להיפגש.",
|
||||
"set_availability_instructions": "הגדר טווחי זמן שבהם אתה זמין באופן קבוע. ניתן יהיה ליצור טווחי זמן נוספים מאוחר יותר ולהקצות אותם ללוחות שנה אחרים.",
|
||||
"set_availability": "ציין את הזמינות שלך",
|
||||
"continue_without_calendar": "להמשיך בלי לוח שנה",
|
||||
@@ -1421,7 +1420,6 @@
|
||||
"team_url_required": "יש להזין כתובת URL של הצוות",
|
||||
"team_url_taken": "כתובת ה-URL הזו כבר תפוסה",
|
||||
"team_publish": "פרסום צוות",
|
||||
"number_sms_notifications": "מספר טלפון (התראות SMS)",
|
||||
"attendee_email_variable": "כתובת הדוא\"ל של המשתתף",
|
||||
"attendee_email_info": "כתובת הדוא\"ל של האדם שביצע את ההזמנה",
|
||||
"kbar_search_placeholder": "הקלד/י פקודה או חפש/י...",
|
||||
|
||||
@@ -161,7 +161,7 @@
|
||||
"guests": "Gosti",
|
||||
"guest": "Gost",
|
||||
"web_conferencing_details_to_follow": "Pojedinosti o web konferenciji slijede u potvrdnoj e-poruci.",
|
||||
"the_username": "Korisničko ime",
|
||||
"404_the_user": "Korisničko ime",
|
||||
"username": "Korisničko ime",
|
||||
"is_still_available": "je i dalje dostupno.",
|
||||
"documentation": "Dokumentacija",
|
||||
@@ -172,7 +172,7 @@
|
||||
"blog_description": "Pročitajte naše najnovije vijesti i članke",
|
||||
"join_our_community": "Pridružite se našoj zajednici",
|
||||
"join_our_slack": "Pridružite se na naš Slack",
|
||||
"claim_username_and_schedule_events": "Zatražite svoje korisničko ime i zakažite događaje",
|
||||
"404_claim_entity_user": "Zatražite svoje korisničko ime i zakažite događaje",
|
||||
"popular_pages": "Popularne stranice",
|
||||
"register_now": "Registriraj se sad",
|
||||
"register": "Registriraj se",
|
||||
@@ -218,7 +218,6 @@
|
||||
"finish": "Završi",
|
||||
"few_sentences_about_yourself": "Nekoliko rečenica o sebi. Ovo će se pojaviti na vašoj osobnoj stranici.",
|
||||
"nearly_there": "Skoro pa gotovo!",
|
||||
"nearly_there_instructions": "Posljednja stvar, vaš kratki opis i fotografija stvarno pomažu da dobijete rezervacije i daju ljudima do znanja kod koga rezerviraju.",
|
||||
"set_availability_instructions": "Definirajte periode kad ste stalno dostupni. Kasnije možete kreirati više njih i dodijeliti ih različitim kalendarima.",
|
||||
"set_availability": "Postavite svoju dostupnost",
|
||||
"continue_without_calendar": "Nastavite bez kalendara",
|
||||
|
||||
@@ -180,7 +180,7 @@
|
||||
"guests": "Ospiti",
|
||||
"guest": "Ospite",
|
||||
"web_conferencing_details_to_follow": "Dettagli della conferenza web da seguire.",
|
||||
"the_username": "Il nome utente",
|
||||
"404_the_user": "Il nome utente",
|
||||
"username": "Username",
|
||||
"is_still_available": "è ancora disponibile.",
|
||||
"documentation": "Documentazione",
|
||||
@@ -191,7 +191,7 @@
|
||||
"blog_description": "Leggi le nostre ultime notizie e articoli",
|
||||
"join_our_community": "Unisciti alla nostra community",
|
||||
"join_our_slack": "Unisciti al nostro Slack",
|
||||
"claim_username_and_schedule_events": "Richiedi il tuo nome utente e pianifica eventi",
|
||||
"404_claim_entity_user": "Richiedi il tuo nome utente e pianifica eventi",
|
||||
"popular_pages": "Pagine popolari",
|
||||
"register_now": "Registrati ora",
|
||||
"register": "Registrati",
|
||||
@@ -240,7 +240,6 @@
|
||||
"finish": "Fine",
|
||||
"few_sentences_about_yourself": "Alcune frasi su di te. Questo apparirà sulla tua pagina url personale.",
|
||||
"nearly_there": "Ci siamo quasi",
|
||||
"nearly_there_instructions": "Ultima cosa, una breve descrizione di te e una foto ti aiutano davvero a ottenere prenotazioni e far sapere alle persone con chi stanno prenotando.",
|
||||
"set_availability_instructions": "Definisci intervalli di tempo di quando sei disponibile su una base ricorrente. Puoi crearne di più in seguito e assegnarli a calendari diversi.",
|
||||
"set_availability": "Imposta la tua disponibilità",
|
||||
"continue_without_calendar": "Continua senza calendario",
|
||||
@@ -1438,7 +1437,6 @@
|
||||
"team_url_required": "Immettere un URL per il team",
|
||||
"team_url_taken": "URL già occupato",
|
||||
"team_publish": "Pubblica team",
|
||||
"number_sms_notifications": "Numero di telefono (notifiche SMS)",
|
||||
"attendee_email_variable": "E-mail partecipante",
|
||||
"attendee_email_info": "E-mail della persona che prenota",
|
||||
"kbar_search_placeholder": "Digita un comando o esegui una ricerca...",
|
||||
|
||||
@@ -172,7 +172,7 @@
|
||||
"guests": "ゲスト",
|
||||
"guest": "ゲスト",
|
||||
"web_conferencing_details_to_follow": "ウェブ会議の詳細には確認メールでお送りします。",
|
||||
"the_username": "ユーザー名",
|
||||
"404_the_user": "ユーザー名",
|
||||
"username": "ユーザー名",
|
||||
"is_still_available": "まだ利用可能。",
|
||||
"documentation": "文書",
|
||||
@@ -183,7 +183,7 @@
|
||||
"blog_description": "最新のニュースや記事を読む",
|
||||
"join_our_community": "コミュニティーに参加する",
|
||||
"join_our_slack": "Slack に参加する",
|
||||
"claim_username_and_schedule_events": "ユーザー名を獲得してイベントをスケジュールする",
|
||||
"404_claim_entity_user": "ユーザー名を獲得してイベントをスケジュールする",
|
||||
"popular_pages": "人気のページ",
|
||||
"register_now": "今すぐ登録",
|
||||
"register": "登録",
|
||||
@@ -231,7 +231,6 @@
|
||||
"finish": "完了",
|
||||
"few_sentences_about_yourself": "あなたについてのいくつかの文章。これはあなたの個人URLページに表示されます。",
|
||||
"nearly_there": "あと少しで完了です!",
|
||||
"nearly_there_instructions": "最後に、あなたについての簡単な説明と写真は、あなたが予約を取得し、誰と一緒に予約しているかを人々に知らせるのに役立ちます。",
|
||||
"set_availability_instructions": "定期的に利用できる時間の範囲を定義します。後でこれらをさらに作成し、別のカレンダーに割り当てることができます。",
|
||||
"set_availability": "利用可否の設定",
|
||||
"continue_without_calendar": "カレンダーなしで続行",
|
||||
@@ -1408,7 +1407,6 @@
|
||||
"team_url_required": "チームの URL の入力は必須です",
|
||||
"team_url_taken": "この URL は既に使用されています",
|
||||
"team_publish": "チームを公開",
|
||||
"number_sms_notifications": "電話番号(SMS 通知)",
|
||||
"attendee_email_variable": "出席者のメールアドレス",
|
||||
"attendee_email_info": "予約者のメールアドレス",
|
||||
"kbar_search_placeholder": "コマンドを入力するか、検索してください...",
|
||||
|
||||
@@ -173,7 +173,7 @@
|
||||
"guests": "게스트",
|
||||
"guest": "게스트",
|
||||
"web_conferencing_details_to_follow": "따라야 할 웹 회의 세부 정보.",
|
||||
"the_username": "사용자 이름",
|
||||
"404_the_user": "사용자 이름",
|
||||
"username": "사용자 이름",
|
||||
"is_still_available": "아직 사용할 수 있습니다.",
|
||||
"documentation": "정보 관리",
|
||||
@@ -184,7 +184,7 @@
|
||||
"blog_description": "최신 뉴스 및 기사 읽기",
|
||||
"join_our_community": "커뮤니티 가입하기",
|
||||
"join_our_slack": "슬랙 가입하기",
|
||||
"claim_username_and_schedule_events": "사용자 이름을 입력하고 이벤트를 만드세요.",
|
||||
"404_claim_entity_user": "사용자 이름을 입력하고 이벤트를 만드세요.",
|
||||
"popular_pages": "인기 페이지",
|
||||
"register_now": "지금 등록하세요",
|
||||
"register": "등록하기",
|
||||
@@ -232,7 +232,6 @@
|
||||
"finish": "끝",
|
||||
"few_sentences_about_yourself": "자신에 대해 몇 마디를 써보세요. 개인 URL 페이지에 보이는 내용입니다.",
|
||||
"nearly_there": "거의 다 되었습니다!",
|
||||
"nearly_there_instructions": "마지막으로, 당신에 대해 간단한 소개와 사진을 올려주세요. 소개와 사진은 예약에 도움을 줍니다.",
|
||||
"set_availability_instructions": "주기적으로 시간이 되는 때가 언제인지 알려주세요. 나중에 더 추가할 수 있습니다.",
|
||||
"set_availability": "언제 여유가 있는지 설정하세요.",
|
||||
"continue_without_calendar": "캘린더 없이 계속하기",
|
||||
@@ -1409,7 +1408,6 @@
|
||||
"team_url_required": "팀 URL을 입력하세요",
|
||||
"team_url_taken": "이미 사용 중인 URL입니다",
|
||||
"team_publish": "팀 게시",
|
||||
"number_sms_notifications": "전화번호 (SMS notifications)",
|
||||
"attendee_email_variable": "참석자 이메일",
|
||||
"attendee_email_info": "예약자 이메일",
|
||||
"kbar_search_placeholder": "명령어를 입력하거나 검색하세요...",
|
||||
|
||||
@@ -172,7 +172,7 @@
|
||||
"guests": "Bezoekers",
|
||||
"guest": "Gast",
|
||||
"web_conferencing_details_to_follow": "Details van de web conferentie zullen volgen.",
|
||||
"the_username": "De gebruikersnaam",
|
||||
"404_the_user": "De gebruikersnaam",
|
||||
"username": "Gebruikersnaam",
|
||||
"is_still_available": "is nog steeds beschikbaar.",
|
||||
"documentation": "Documentatie",
|
||||
@@ -183,7 +183,7 @@
|
||||
"blog_description": "Lees ons laatste nieuws en artikelen",
|
||||
"join_our_community": "Word lid van onze community",
|
||||
"join_our_slack": "Bezoek onze Slack",
|
||||
"claim_username_and_schedule_events": "Claim uw gebruikersnaam en plan evenementen",
|
||||
"404_claim_entity_user": "Claim uw gebruikersnaam en plan evenementen",
|
||||
"popular_pages": "Populaire pagina's",
|
||||
"register_now": "Registreer nu",
|
||||
"register": "Registreer",
|
||||
@@ -231,7 +231,6 @@
|
||||
"finish": "Voltooien",
|
||||
"few_sentences_about_yourself": "Een korte introductie over uzelf. Dit wordt weergegeven op uw persoonlijke pagina.",
|
||||
"nearly_there": "Bijna klaar!",
|
||||
"nearly_there_instructions": "Tot slot, een korte beschrijving van u en een foto helpt u echt boekingen te krijgen en laat iedereen weten bij wie ze zijn geboekt.",
|
||||
"set_availability_instructions": "Bepaal tijdbereiken waarin u periodiek beschikbaar bent. U kunt op een later tijdstip terugkomen en hier meer van aanmaken en toewijzen aan verschillende kalenders.",
|
||||
"set_availability": "Beschikbaarheid bepalen",
|
||||
"continue_without_calendar": "Doorgaan zonder kalender",
|
||||
@@ -1408,7 +1407,6 @@
|
||||
"team_url_required": "Moet een team-URL invoeren",
|
||||
"team_url_taken": "Deze URL is al in gebruik",
|
||||
"team_publish": "Team publiceren",
|
||||
"number_sms_notifications": "Telefoonnummer (sms-meldingen)",
|
||||
"attendee_email_variable": "E-mailadres deelnemer",
|
||||
"attendee_email_info": "Het e-mailadres van de persoon die boekt",
|
||||
"kbar_search_placeholder": "Typ een opdracht of zoek...",
|
||||
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user