Compare commits

..
44 Commits
Author SHA1 Message Date
GitHub Actions 8ffebdba44 chore: release v4.8.10
Post release / release (push) Canceled after 0s
2024-12-27 21:07:33 +00:00
Udit TakkarandBailey Pumfleet cc1101ac43 refactor: email handler (#18391)
Co-authored-by: Bailey Pumfleet <bailey@pumfleet.co.uk>
2024-12-27 21:02:05 +00:00
Anik Dhabal BabuandUdit Takkar 7a80a5f929 fix: localized date (#18388)
Co-authored-by: Udit Takkar <53316345+Udit-takkar@users.noreply.github.com>
2024-12-27 20:59:53 +00:00
NizzyandUdit Takkar a27e1498b8 fix: removed plain chat from app.cal.com/video (#18385)
* fix: removed plain chat from app.cal.com/video

* take user session and open plain out of use effect

* move undefined check and screen size check above useeffect

* turned restrcited paths into env var

* undefeined window below hooks

* chore: add window check

* fix: infinite re rendering

* remove console.log

---------

Co-authored-by: Udit Takkar <udit222001@gmail.com>
2024-12-27 18:42:36 +00:00
Calcom BotandReplexica a697857fa0 feat: update translations via @replexica (#18389)
* chore: sync with main

* feat: update translations via @replexica

---------

Co-authored-by: Replexica <support@replexica.com>
2024-12-27 17:33:26 +00:00
Udit Takkar e2f45bdf5a perf: improve checkIsAvailable in getAvailable slots (#18352)
* refactor: Update booking query to use userId and optimize availability check logic

- Changed the booking query to use userId instead of nested user object for filtering active bookings.
- Refactored availability check logic to improve readability and performance by using valueOf() for time comparisons and consolidating multiple checks into a single return statement.

* fix: update tests

* chore: use toDate()

* tests: add checkavailable unit tests
2024-12-27 16:01:54 +00:00
Hariom Balhara d2930bc243 fix: Handle more dry-run cases (#18286) 2024-12-27 16:23:14 +01:00
GitHub Actions b0b93f612e chore: release v4.8.9
Post release / release (push) Canceled after 0s
2024-12-27 11:17:18 +00:00
Udit Takkar 558c8be740 perf: getAvailableSlots improvements (#18382) 2024-12-27 11:09:49 +00:00
Alex van Andel 1aee1742d5 chore: rely less on autoinfer, be able to use initial data types in consumers (#18359) 2024-12-27 16:23:49 +05:30
Hariom BalharaandUdit Takkar ab942ed4cd Fix routing form field input loosing focus on typing (#18381)
Co-authored-by: Udit Takkar <53316345+Udit-takkar@users.noreply.github.com>
2024-12-27 14:30:10 +05:30
Hariom Balhara b5bec79354 perf: /router endpoint perf improvement (#18366) 2024-12-27 08:56:52 +00:00
Nizzy b48d0e1f48 remove yarnlock (#18377) 2024-12-27 00:44:21 -05:00
GitHub Actions 4a38b4dc89 chore: release v4.8.8
Post release / release (push) Canceled after 0s
2024-12-26 19:31:22 +00:00
Syed Ali Shahbaz ff12e387b3 fix: update contact email for CalDAV (#18351) 2024-12-26 19:24:11 +00:00
Kartik Saini 6b5e144042 feat: add name and email url params when rescheduling past bookings (#18318)
* feat: add name and email as url params when rescheduling past bookings

* remove logs

* pass booking seat name and email as url params
2024-12-26 19:23:28 +00:00
Anwar Sadath cd7b93a51e fix: Last used label in button shown in 2-factor code and backup screen (#18341) 2024-12-26 19:20:11 +00:00
Anik Dhabal Babu a76366cbcb fix: Recurring meeting creates increasing number of Zoom events (#18360) 2024-12-26 19:19:24 +00:00
Anwar Sadath 860c04bbda fix: discrepancy in description hyperlinks and calendar invite (#18365) 2024-12-26 19:18:54 +00:00
+6 225313e391 feat: Plain chat (#18284)
* plain custom desin

* No display on card

* dynamic plain chat component and hmac hash

* re-route users to plain.com chat instead of intercom chat when going to /support

* provider errors

* yarn lock fix

* plain chat removed unneeded hmac

* fix ts error

* error handling improved

* remove intercome provider from app-dir

* Create getting-started.mdx (#18342)

* Delete help directory (#18343)

* chore: moved docs/help to /help (#18345)

* chore: Delete unused guides directory (#18346)

* feat: booking filters (#18303)

Co-authored-by: Syed Ali Shahbaz <52925846+alishaz-polymath@users.noreply.github.com>

* chore: Remove `HeadSeo` components where no longer needed + improve app router metadata logic (#18348)

* remove HeadSeo for already migrated pages and refactor prepareMetadata

* create _generateMetadataWithoutImage and refactor _generateMetadata

* chore: app router - /bookings status page (#18183)

* chore: app router - /bookings page

* remove env vars

* fix

* Update middleware.ts

* revert unneeded change

* refactor for the better

* fix

* cache i18n instances (#18309)

* feat: virtual queues tab in insights (#18260)

Co-authored-by: CarinaWolli <wollencarina@gmail.com>
Co-authored-by: sean-brydon <sean@cal.com>
Co-authored-by: sean-brydon <55134778+sean-brydon@users.noreply.github.com>
Co-authored-by: Peer Richelsen <peeroke@gmail.com>
Co-authored-by: Omar López <zomars@me.com>

* feat: update translations via @replexica (#18361)

Co-authored-by: Replexica <support@replexica.com>

* update OOO e2e tests to remove flakiness (#18367)

* fix: metadata is overwritten for child managed eventType when updating parent (#18059)

* fix: metadata is overwirten

* Update

* type error

* Update

* fix test

* fix type error

* chore: added routing support link (#18369)

* Added routing form support link

* small change

* Type fix

---------

Co-authored-by: Joe Au-Yeung <j.auyeung419@gmail.com>

* chore: refactor handling logic for embeds in app router (#18362)

* refactor handling logic for embeds in app router

* fix type checks

* add test for withEmbedSsrAppDir

* fix

* review changes

* yarn lock

---------

Co-authored-by: Peer Richelsen <peeroke@gmail.com>
Co-authored-by: Calcom Bot <109866826+calcom-bot@users.noreply.github.com>
Co-authored-by: Udit Takkar <53316345+Udit-takkar@users.noreply.github.com>
Co-authored-by: Syed Ali Shahbaz <52925846+alishaz-polymath@users.noreply.github.com>
Co-authored-by: Benny Joo <sldisek783@gmail.com>
Co-authored-by: Carina Wollendorfer <30310907+CarinaWolli@users.noreply.github.com>
Co-authored-by: CarinaWolli <wollencarina@gmail.com>
Co-authored-by: sean-brydon <sean@cal.com>
Co-authored-by: sean-brydon <55134778+sean-brydon@users.noreply.github.com>
Co-authored-by: Omar López <zomars@me.com>
Co-authored-by: Replexica <support@replexica.com>
Co-authored-by: Vijay <vijayraghav22@gmail.com>
Co-authored-by: Anik Dhabal Babu <81948346+anikdhabal@users.noreply.github.com>
Co-authored-by: Joe Au-Yeung <j.auyeung419@gmail.com>
Co-authored-by: Alex van Andel <me@alexvanandel.com>
2024-12-26 19:07:08 +00:00
Benny Joo 3994929a51 chore: refactor handling logic for embeds in app router (#18362)
* refactor handling logic for embeds in app router

* fix type checks

* add test for withEmbedSsrAppDir

* fix
2024-12-26 10:39:07 +01:00
Peer RichelsenandJoe Au-Yeung ae38027d76 chore: added routing support link (#18369)
* Added routing form support link

* small change

* Type fix

---------

Co-authored-by: Joe Au-Yeung <j.auyeung419@gmail.com>
2024-12-24 17:19:37 +00:00
Anik Dhabal Babu 9036080fcf fix: metadata is overwritten for child managed eventType when updating parent (#18059)
* fix: metadata is overwirten

* Update

* type error

* Update

* fix test

* fix type error
2024-12-24 21:21:43 +05:30
Vijay e6ef4e1988 update OOO e2e tests to remove flakiness (#18367) 2024-12-24 13:49:49 +00:00
Calcom BotandReplexica 3c2619a7a3 feat: update translations via @replexica (#18361)
Co-authored-by: Replexica <support@replexica.com>
2024-12-23 22:42:02 +00:00
cfb09a1b21 feat: virtual queues tab in insights (#18260)
Co-authored-by: CarinaWolli <wollencarina@gmail.com>
Co-authored-by: sean-brydon <sean@cal.com>
Co-authored-by: sean-brydon <55134778+sean-brydon@users.noreply.github.com>
Co-authored-by: Peer Richelsen <peeroke@gmail.com>
Co-authored-by: Omar López <zomars@me.com>
2024-12-23 19:16:38 +00:00
Benny Joo 5fe4e09975 cache i18n instances (#18309) 2024-12-23 17:14:52 +00:00
Benny Joo 542799e2cf chore: app router - /bookings status page (#18183)
* chore: app router - /bookings page

* remove env vars

* fix

* Update middleware.ts

* revert unneeded change

* refactor for the better

* fix
2024-12-23 10:12:22 +01:00
Benny Joo 3eda7858c0 chore: Remove HeadSeo components where no longer needed + improve app router metadata logic (#18348)
* remove HeadSeo for already migrated pages and refactor prepareMetadata

* create _generateMetadataWithoutImage and refactor _generateMetadata
2024-12-23 10:02:59 +01:00
Udit TakkarandSyed Ali Shahbaz b59bf7d1ee feat: booking filters (#18303)
Co-authored-by: Syed Ali Shahbaz <52925846+alishaz-polymath@users.noreply.github.com>
2024-12-23 14:06:29 +05:30
Peer Richelsen 0dadeac352 chore: Delete unused guides directory (#18346) 2024-12-22 21:44:43 +01:00
Peer Richelsen 5eb7a4d56e chore: moved docs/help to /help (#18345) 2024-12-22 21:43:53 +01:00
Calcom Bot 43fa944e2f Delete help directory (#18343) 2024-12-22 21:36:22 +01:00
Peer Richelsen 1866b2845c Create getting-started.mdx (#18342) 2024-12-22 21:35:30 +01:00
Anik Dhabal Babu 2cfc63e6e3 fix: Reschedule email for RR event shows wrong timezone to attendee (#18262)
Post release / release (push) Canceled after 0s
2024-12-20 14:02:03 -06:00
GitHub Actions 4f66f3d2c6 chore: release v4.8.7 2024-12-20 20:01:15 +00:00
Morgan ca3e7bd97d fix: hotfix hide hidden booking fields in event-types GET / (#18306) 2024-12-20 19:37:32 +00:00
Benny Joo cafa3f9cf3 fix: make sure to call refresh for pages rendered by app router (#18304) 2024-12-20 18:44:35 +00:00
Benny Joo e04faffcbb chore: add cache to static pages in app router (#18278)
* add cache

* fix

* refactor
2024-12-20 13:37:20 -05:00
Peer Richelsen 6f1556a2fd feat: added chatbase app (#18302) 2024-12-20 17:50:19 +00:00
mintlify[bot] d72a954896 Mintlify Docs Edits (#18301)
Co-authored-by: mintlify[bot] <109931778+mintlify[bot]@users.noreply.github.com>
2024-12-20 17:10:39 +00:00
amrit abeb20373c fix portugese (pt-BR) password validation from a required eight to seven. (#18273) 2024-12-20 17:33:46 +05:30
MorganandRajiv Sahal 422ef2f5b8 chore: add cal.com logo to platform booker embed (#18269)
* chore: add cal.com logo to platform booker embed

* fixup! chore: add cal.com logo to platform booker embed

---------

Co-authored-by: Rajiv Sahal <sahalrajiv-extc@atharvacoe.ac.in>
2024-12-20 12:55:25 +02:00
Omar López ec920eb2a8 fix: prevents cron to be clogged with calendars with missing credentialId (#18281) 2024-12-19 23:53:51 +00:00
229 changed files with 3631 additions and 1583 deletions
+3 -1
View File
@@ -112,6 +112,9 @@ NEXT_PUBLIC_POSTHOG_HOST=
PLAIN_API_KEY=
PLAIN_API_URL=https://api.plain.com/v1
PLAIN_HMAC_SECRET_KEY=
PLAIN_CHAT_ID=
PLAIN_CHAT_HMAC_SECRET_KEY=
NEXT_PUBLIC_PLAIN_CHAT_EXCLUDED_PATHS=
# Zendesk Config
NEXT_PUBLIC_ZENDESK_KEY=
@@ -361,7 +364,6 @@ APP_ROUTER_APPS_SLUG_SETUP_ENABLED=0
APP_ROUTER_APPS_CATEGORIES_ENABLED=0
# whether we redirect to the future/apps/categories/[category] from /apps/categories/[category] or not
APP_ROUTER_APPS_CATEGORIES_CATEGORY_ENABLED=0
APP_ROUTER_BOOKINGS_STATUS_ENABLED=0
APP_ROUTER_APPS_ENABLED=0
APP_ROUTER_TEAM_ENABLED=0
APP_ROUTER_AUTH_FORGOT_PASSWORD_ENABLED=0
+8
View File
@@ -0,0 +1,8 @@
undecided:
- calcom-monorepo
- "@calcom/app-store-cli"
- "@calcom/platform-constants"
- "@calcom/platform-enums"
- "@calcom/platform-types"
- "@calcom/platform-utils"
- "@calcom/prisma"
View File
+2
View File
@@ -0,0 +1,2 @@
undecided:
- "@calcom/prisma"
+2
View File
@@ -0,0 +1,2 @@
undecided:
- calcom-monorepo
@@ -34,6 +34,7 @@ import {
UpdateEventTypeInput_2024_06_14,
GetEventTypesQuery_2024_06_14,
CreateEventTypeInput_2024_06_14,
EventTypeOutput_2024_06_14,
} from "@calcom/platform-types";
@Controller({
@@ -113,10 +114,26 @@ export class EventTypesController_2024_06_14 {
@Query() queryParams: GetEventTypesQuery_2024_06_14
): Promise<GetEventTypesOutput_2024_06_14> {
const eventTypes = await this.eventTypesService.getEventTypes(queryParams);
const eventTypesFormatted = this.eventTypeResponseTransformPipe.transform(eventTypes);
const eventTypesWithoutHiddenFields = eventTypesFormatted.map((eventType) => {
return {
...eventType,
bookingFields: Array.isArray(eventType?.bookingFields)
? eventType?.bookingFields
.map((field) => {
if ("hidden" in field) {
return field.hidden !== true ? field : null;
}
return field;
})
.filter((f) => f)
: [],
};
}) as EventTypeOutput_2024_06_14[];
return {
status: SUCCESS_STATUS,
data: this.eventTypeResponseTransformPipe.transform(eventTypes),
data: eventTypesWithoutHiddenFields,
};
}
-1
View File
@@ -20,7 +20,6 @@ const ROUTES: [URLPattern, boolean][] = [
["/auth/error", process.env.APP_ROUTER_AUTH_ERROR_ENABLED === "1"] as const,
["/auth/platform/:path*", process.env.APP_ROUTER_AUTH_PLATFORM_ENABLED === "1"] as const,
["/auth/oauth2/:path*", process.env.APP_ROUTER_AUTH_OAUTH2_ENABLED === "1"] as const,
["/bookings/:status", process.env.APP_ROUTER_BOOKINGS_STATUS_ENABLED === "1"] as const,
["/team", process.env.APP_ROUTER_TEAM_ENABLED === "1"] as const,
].map(([pathname, enabled]) => [
new URLPattern({
+21 -14
View File
@@ -1,24 +1,31 @@
import type { GetStaticProps, GetStaticPropsContext } from "next";
import { unstable_cache } from "next/cache";
import { notFound, redirect } from "next/navigation";
export const withAppDirSsg =
<T extends Record<string, any>>(getStaticProps: GetStaticProps<T>) =>
<T extends Record<string, any>>(getStaticProps: GetStaticProps<T>, routePath: string) =>
async (context: GetStaticPropsContext) => {
const ssgResponse = await getStaticProps(context);
const cacheKey = JSON.stringify({
route: routePath,
params: context.params || {},
});
if ("redirect" in ssgResponse) {
redirect(ssgResponse.redirect.destination);
}
const getCachedProps = unstable_cache(async () => {
const ssgResponse = await getStaticProps(context);
if ("notFound" in ssgResponse) {
notFound();
}
if ("redirect" in ssgResponse) {
redirect(ssgResponse.redirect.destination);
}
const props = await Promise.resolve(ssgResponse.props);
if ("notFound" in ssgResponse) {
notFound();
}
return {
...ssgResponse.props,
// includes dehydratedState required for future page trpcPropvider
...("trpcState" in props && { dehydratedState: props.trpcState }),
};
return {
...ssgResponse.props,
...("trpcState" in ssgResponse.props && { dehydratedState: ssgResponse.props.trpcState }),
};
}, [`ssg-${cacheKey}`]);
return getCachedProps();
};
+247
View File
@@ -0,0 +1,247 @@
import type { Request, Response } from "express";
import type { NextApiRequest, NextApiResponse, Redirect } from "next";
import { redirect, notFound } from "next/navigation";
import { createMocks } from "node-mocks-http";
import { describe, expect, it, vi } from "vitest";
import withEmbedSsrAppDir from "./WithEmbedSSR";
export type CustomNextApiRequest = NextApiRequest & Request;
export type CustomNextApiResponse = NextApiResponse & Response;
export function createMockNextJsRequest(...args: Parameters<typeof createMocks>) {
return createMocks<CustomNextApiRequest, CustomNextApiResponse>(...args);
}
vi.mock("next/navigation", () => ({
redirect: vi.fn(),
notFound: vi.fn(),
}));
function getServerSidePropsFnGenerator(
config:
| { redirectUrl: string }
| { props: Record<string, unknown> }
| {
notFound: true;
}
) {
if ("redirectUrl" in config)
return async () => {
return {
redirect: {
permanent: false,
destination: config.redirectUrl,
} satisfies Redirect,
};
};
if ("props" in config)
return async () => {
return {
props: config.props,
};
};
if ("notFound" in config)
return async () => {
return {
notFound: true as const,
};
};
throw new Error("Invalid config");
}
interface ServerSidePropsContext {
embedRelatedParams?: Record<string, string>;
}
function getServerSidePropsContextArg({ embedRelatedParams = {} }: ServerSidePropsContext) {
const { req, res } = createMockNextJsRequest();
return {
req,
res,
query: {
...embedRelatedParams,
},
resolvedUrl: "/MOCKED_RESOLVED_URL",
};
}
describe("withEmbedSsrAppDir", () => {
describe("when gSSP returns redirect", () => {
describe("when redirect destination is relative", () => {
it("should redirect with layout and embed params from the current query", async () => {
const withEmbedGetSsr = withEmbedSsrAppDir(
getServerSidePropsFnGenerator({
redirectUrl: "/reschedule",
})
);
await withEmbedGetSsr(
getServerSidePropsContextArg({
embedRelatedParams: {
layout: "week_view",
embed: "namespace1",
},
})
).catch(() => {});
expect(redirect).toHaveBeenCalledWith("/reschedule/embed?layout=week_view&embed=namespace1");
});
it("should preserve existing query params in redirect URL", async () => {
const withEmbedGetSsr = withEmbedSsrAppDir(
getServerSidePropsFnGenerator({
redirectUrl: "/reschedule?redirectParam=1",
})
);
await withEmbedGetSsr(
getServerSidePropsContextArg({
embedRelatedParams: {
layout: "week_view",
embed: "namespace1",
},
})
).catch(() => {});
expect(redirect).toHaveBeenCalledWith(
"/reschedule/embed?redirectParam=1&layout=week_view&embed=namespace1"
);
});
it("should handle empty embed namespace", async () => {
const withEmbedGetSsr = withEmbedSsrAppDir(
getServerSidePropsFnGenerator({
redirectUrl: "/reschedule?redirectParam=1",
})
);
await withEmbedGetSsr(
getServerSidePropsContextArg({
embedRelatedParams: {
layout: "week_view",
embed: "",
},
})
).catch(() => {});
expect(redirect).toHaveBeenCalledWith("/reschedule/embed?redirectParam=1&layout=week_view&embed=");
});
});
describe("when redirect destination is absolute", () => {
it("should handle HTTPS URLs", async () => {
const withEmbedGetSsr = withEmbedSsrAppDir(
getServerSidePropsFnGenerator({
redirectUrl: "https://calcom.cal.local/owner",
})
);
await withEmbedGetSsr(
getServerSidePropsContextArg({
embedRelatedParams: {
layout: "week_view",
embed: "namespace1",
},
})
).catch(() => {});
expect(redirect).toHaveBeenCalledWith(
"https://calcom.cal.local/owner/embed?layout=week_view&embed=namespace1"
);
});
it("should handle HTTP URLs", async () => {
const withEmbedGetSsr = withEmbedSsrAppDir(
getServerSidePropsFnGenerator({
redirectUrl: "http://calcom.cal.local/owner",
})
);
await withEmbedGetSsr(
getServerSidePropsContextArg({
embedRelatedParams: {
layout: "week_view",
embed: "namespace1",
},
})
).catch(() => {});
expect(redirect).toHaveBeenCalledWith(
"http://calcom.cal.local/owner/embed?layout=week_view&embed=namespace1"
);
});
it("should treat URLs without protocol as relative", async () => {
const withEmbedGetSsr = withEmbedSsrAppDir(
getServerSidePropsFnGenerator({
redirectUrl: "calcom.cal.local/owner",
})
);
await withEmbedGetSsr(
getServerSidePropsContextArg({
embedRelatedParams: {
layout: "week_view",
embed: "namespace1",
},
})
).catch(() => {});
expect(redirect).toHaveBeenCalledWith(
"/calcom.cal.local/owner/embed?layout=week_view&embed=namespace1"
);
});
});
});
describe("when gSSP returns props", () => {
it("should add isEmbed=true prop", async () => {
const withEmbedGetSsr = withEmbedSsrAppDir(
getServerSidePropsFnGenerator({
props: {
prop1: "value1",
},
})
);
const ret = await withEmbedGetSsr(
getServerSidePropsContextArg({
embedRelatedParams: {
layout: "week_view",
embed: "",
},
})
);
expect(ret).toEqual({
prop1: "value1",
isEmbed: true,
});
});
});
describe("when gSSP returns notFound", () => {
it("should throw notFound", async () => {
const withEmbedGetSsr = withEmbedSsrAppDir(
getServerSidePropsFnGenerator({
notFound: true,
})
);
await withEmbedGetSsr(
getServerSidePropsContextArg({
embedRelatedParams: {
layout: "week_view",
embed: "",
},
})
).catch(() => {});
expect(notFound).toHaveBeenCalled();
});
});
});
+36 -40
View File
@@ -1,6 +1,4 @@
import type { GetServerSidePropsContext } from "next";
import { isNotFoundError } from "next/dist/client/components/not-found";
import { getURLFromRedirectError, isRedirectError } from "next/dist/client/components/redirect";
import type { GetServerSideProps, GetServerSidePropsContext } from "next";
import { notFound, redirect } from "next/navigation";
import { WebAppURL } from "@calcom/lib/WebAppURL";
@@ -9,48 +7,46 @@ export type EmbedProps = {
isEmbed?: boolean;
};
export default function withEmbedSsrAppDir<T extends Record<string, any>>(
getData: (context: GetServerSidePropsContext) => Promise<T>
) {
return async (context: GetServerSidePropsContext): Promise<T> => {
const withEmbedSsrAppDir =
<T extends Record<string, any>>(getServerSideProps: GetServerSideProps<T>) =>
async (context: GetServerSidePropsContext): Promise<T> => {
const { embed, layout } = context.query;
try {
const props = await getData(context);
const ssrResponse = await getServerSideProps(context);
return {
...props,
isEmbed: true,
};
} catch (e) {
if (isRedirectError(e)) {
const destinationUrl = getURLFromRedirectError(e);
let urlPrefix = "";
if ("redirect" in ssrResponse) {
const destinationUrl = ssrResponse.redirect.destination;
let urlPrefix = "";
// Get the URL parsed from URL so that we can reliably read pathname and searchParams from it.
const destinationUrlObj = new WebAppURL(destinationUrl);
// Get the URL parsed from URL so that we can reliably read pathname and searchParams from it.
const destinationUrlObj = new WebAppURL(ssrResponse.redirect.destination);
// If it's a complete URL, use the origin as the prefix to ensure we redirect to the same domain.
if (destinationUrl.search(/^(http:|https:).*/) !== -1) {
urlPrefix = destinationUrlObj.origin;
} else {
// Don't use any prefix for relative URLs to ensure we stay on the same domain
urlPrefix = "";
}
const destinationQueryStr = destinationUrlObj.searchParams.toString();
// Make sure that redirect happens to /embed page and pass on embed query param as is for preserving Cal JS API namespace
const newDestinationUrl = `${urlPrefix}${destinationUrlObj.pathname}/embed?${
destinationQueryStr ? `${destinationQueryStr}&` : ""
}layout=${layout}&embed=${embed}`;
redirect(newDestinationUrl);
// If it's a complete URL, use the origin as the prefix to ensure we redirect to the same domain.
if (destinationUrl.search(/^(http:|https:).*/) !== -1) {
urlPrefix = destinationUrlObj.origin;
} else {
// Don't use any prefix for relative URLs to ensure we stay on the same domain
urlPrefix = "";
}
if (isNotFoundError(e)) {
notFound();
}
throw e;
const destinationQueryStr = destinationUrlObj.searchParams.toString();
// Make sure that redirect happens to /embed page and pass on embed query param as is for preserving Cal JS API namespace
const newDestinationUrl = `${urlPrefix}${destinationUrlObj.pathname}/embed?${
destinationQueryStr ? `${destinationQueryStr}&` : ""
}layout=${layout}&embed=${embed}`;
redirect(newDestinationUrl);
}
if ("notFound" in ssrResponse) {
notFound();
}
return {
...ssrResponse.props,
...("trpcState" in ssrResponse.props && {
dehydratedState: ssrResponse.props.trpcState,
}),
isEmbed: true,
};
};
}
export default withEmbedSsrAppDir;
+53 -30
View File
@@ -4,27 +4,39 @@ import { serverSideTranslations } from "next-i18next/serverSideTranslations";
import { headers } from "next/headers";
import { constructGenericImage } from "@calcom/lib/OgImages";
import { IS_CALCOM, WEBAPP_URL, APP_NAME, SEO_IMG_OGIMG } from "@calcom/lib/constants";
import { IS_CALCOM, WEBAPP_URL, APP_NAME, SEO_IMG_OGIMG, CAL_URL } from "@calcom/lib/constants";
import { buildCanonical } from "@calcom/lib/next-seo.config";
import { truncateOnWord } from "@calcom/lib/text";
//@ts-expect-error no type definitions
import config from "@calcom/web/next-i18next.config";
const create = async (locale: string, ns: string) => {
const i18nInstanceCache: Record<string, any> = {};
const createI18nInstance = async (locale: string, ns: string) => {
const cacheKey = `${locale}-${ns}`;
// Check module-level cache first
if (i18nInstanceCache[cacheKey]) {
return i18nInstanceCache[cacheKey];
}
const { _nextI18Next } = await serverSideTranslations(locale, [ns], config);
const _i18n = i18next.createInstance();
_i18n.init({
await _i18n.init({
lng: locale,
resources: _nextI18Next?.initialI18nStore,
fallbackLng: _nextI18Next?.userConfig?.i18n.defaultLocale,
});
// Cache the instance
i18nInstanceCache[cacheKey] = _i18n;
return _i18n;
};
export const getFixedT = async (locale: string, ns = "common") => {
const i18n = await create(locale, ns);
return i18n.getFixedT(locale, ns);
const getTranslationWithCache = async (locale: string, ns: string = "common") => {
const localeWithFallback = locale ?? "en";
const i18n = await createI18nInstance(localeWithFallback, ns);
return i18n.getFixedT(localeWithFallback, ns);
};
export const getTranslate = async () => {
@@ -32,51 +44,62 @@ export const getTranslate = async () => {
// If "x-locale" does not exist in header,
// ensure that config.matcher in middleware includes the page you are testing
const locale = headersList.get("x-locale");
const t = await getFixedT(locale ?? "en");
const t = await getTranslationWithCache(locale ?? "en");
return t;
};
export const _generateMetadata = async (
const _generateMetadataWithoutImage = async (
getTitle: (t: TFunction<string, undefined>) => string,
getDescription: (t: TFunction<string, undefined>) => string,
excludeAppNameFromTitle?: boolean
hideBranding?: boolean,
origin?: string
) => {
const h = headers();
const canonical = h.get("x-pathname") ?? "";
const pathname = h.get("x-pathname") ?? "";
const canonical = buildCanonical({ path: pathname, origin: origin ?? CAL_URL });
const locale = h.get("x-locale") ?? "en";
const t = await getFixedT(locale, "common");
const t = await getTranslationWithCache(locale);
const title = getTitle(t);
const description = getDescription(t);
const metadataBase = new URL(IS_CALCOM ? "https://cal.com" : WEBAPP_URL);
const image =
SEO_IMG_OGIMG +
constructGenericImage({
title,
description,
});
const titleSuffix = `| ${APP_NAME}`;
const displayedTitle =
title.includes(titleSuffix) || excludeAppNameFromTitle ? title : `${title} ${titleSuffix}`;
const displayedTitle = title.includes(titleSuffix) || hideBranding ? title : `${title} ${titleSuffix}`;
const metadataBase = new URL(IS_CALCOM ? "https://cal.com" : WEBAPP_URL);
return {
title: title.length === 0 ? APP_NAME : displayedTitle,
description,
alternates: {
canonical,
},
alternates: { canonical },
openGraph: {
description: truncateOnWord(description, 158),
url: canonical,
type: "website",
siteName: APP_NAME,
title,
images: [image],
title: displayedTitle,
},
metadataBase,
};
};
export const _generateMetadata = async (
getTitle: (t: TFunction<string, undefined>) => string,
getDescription: (t: TFunction<string, undefined>) => string,
hideBranding?: boolean,
origin?: string
) => {
const metadata = await _generateMetadataWithoutImage(getTitle, getDescription, hideBranding, origin);
const image =
SEO_IMG_OGIMG +
constructGenericImage({
title: metadata.title,
description: metadata.description,
});
return {
...metadata,
openGraph: {
...metadata.openGraph,
images: [image],
},
};
};
+5 -24
View File
@@ -26,6 +26,7 @@ export async function handler(request: Request) {
// HMAC verification
const incomingSignature = headersList.get("plain-request-signature");
// eslint-disable-next-line @typescript-eslint/no-non-null-assertion
const expectedSignature = createHmac("sha-256", process.env.PLAIN_HMAC_SECRET_KEY!)
.update(JSON.stringify(requestBody))
.digest("hex");
@@ -88,18 +89,8 @@ export async function handler(request: Request) {
rowAsideContent: [
{
componentBadge: {
badgeLabel:
customer.emailVerified === undefined
? "Unknown"
: customer.emailVerified
? "Yes"
: "No",
badgeColor:
customer.emailVerified === undefined
? "YELLOW"
: customer.emailVerified
? "GREEN"
: "RED",
badgeLabel: customer.emailVerified ? "Yes" : "No",
badgeColor: customer.emailVerified ? "GREEN" : "RED",
},
},
],
@@ -195,18 +186,8 @@ export async function handler(request: Request) {
rowAsideContent: [
{
componentBadge: {
badgeLabel:
customer.twoFactorEnabled === undefined
? "Unknown"
: customer.twoFactorEnabled
? "Yes"
: "No",
badgeColor:
customer.twoFactorEnabled === undefined
? "YELLOW"
: customer.twoFactorEnabled
? "GREEN"
: "RED",
badgeLabel: customer.twoFactorEnabled ? "Yes" : "No",
badgeColor: customer.twoFactorEnabled ? "GREEN" : "RED",
},
},
],
+49
View File
@@ -0,0 +1,49 @@
import { createHmac } from "crypto";
import { NextResponse } from "next/server";
import { z } from "zod";
import { getServerSession } from "@calcom/features/auth/lib/getServerSession";
import { apiRouteMiddleware } from "@calcom/lib/server/apiRouteMiddleware";
const responseSchema = z.object({
hash: z.string(),
email: z.string().email(),
shortName: z.string(),
appId: z.string(),
fullName: z.string(),
chatAvatarUrl: z.string(),
});
async function handler(request: Request) {
// eslint-disable-next-line @typescript-eslint/no-explicit-any
const session = await getServerSession({ req: request as any });
if (!session?.user?.email) {
return new Response("Unauthorized - No session email found", { status: 401 });
}
const secret = process.env.PLAIN_CHAT_HMAC_SECRET_KEY;
if (!secret) {
return new Response("Missing Plain Chat secret", { status: 500 });
}
const hmac = createHmac("sha256", secret);
hmac.update(session.user.email.toLowerCase().trim());
const hash = hmac.digest("hex");
const shortName =
(session.user.name?.split(" ")[0] || session.user.email).charAt(0).toUpperCase() +
(session.user.name?.split(" ")[0] || session.user.email).slice(1) || "User";
const response = responseSchema.parse({
hash,
email: session.user.email || "user@example.com",
shortName,
appId: process.env.PLAIN_CHAT_ID,
fullName: session.user.name || "User",
chatAvatarUrl: session.user.avatarUrl || "",
});
return NextResponse.json(response);
}
export const POST = apiRouteMiddleware(handler);
+1 -4
View File
@@ -1,12 +1,9 @@
import { withAppDirSsr } from "app/WithAppDirSsr";
import withEmbedSsrAppDir from "app/WithEmbedSSR";
import { WithLayout } from "app/layoutHOC";
import OldPage from "~/bookings/views/bookings-single-view";
import { getServerSideProps, type PageProps } from "~/bookings/views/bookings-single-view.getServerSideProps";
const getData = withAppDirSsr<PageProps>(getServerSideProps);
const getEmbedData = withEmbedSsrAppDir(getData);
const getEmbedData = withEmbedSsrAppDir<PageProps>(getServerSideProps);
export default WithLayout({ getLayout: null, getData: getEmbedData, Page: OldPage });
+5 -2
View File
@@ -4,6 +4,7 @@ import { _generateMetadata } from "app/_utils";
import { WithLayout } from "app/layoutHOC";
import { cookies, headers } from "next/headers";
import { getOrgFullOrigin } from "@calcom/features/ee/organizations/lib/orgDomains";
import { BookingStatus } from "@calcom/prisma/enums";
import { buildLegacyCtx } from "@lib/buildLegacyCtx";
@@ -12,7 +13,7 @@ import OldPage from "~/bookings/views/bookings-single-view";
import { getServerSideProps, type PageProps } from "~/bookings/views/bookings-single-view.getServerSideProps";
export const generateMetadata = async ({ params, searchParams }: _PageProps) => {
const { bookingInfo, eventType, recurringBookings } = await getData(
const { bookingInfo, eventType, recurringBookings, orgSlug } = await getData(
buildLegacyCtx(headers(), cookies(), params, searchParams)
);
const needsConfirmation = bookingInfo.status === BookingStatus.PENDING && eventType.requiresConfirmation;
@@ -21,7 +22,9 @@ export const generateMetadata = async ({ params, searchParams }: _PageProps) =>
(t) =>
t(`booking_${needsConfirmation ? "submitted" : "confirmed"}${recurringBookings ? "_recurring" : ""}`),
(t) =>
t(`booking_${needsConfirmation ? "submitted" : "confirmed"}${recurringBookings ? "_recurring" : ""}`)
t(`booking_${needsConfirmation ? "submitted" : "confirmed"}${recurringBookings ? "_recurring" : ""}`),
false,
getOrgFullOrigin(orgSlug)
);
};
@@ -0,0 +1,17 @@
import { _generateMetadata } from "app/_utils";
import { WithLayout } from "app/layoutHOC";
import BookingDryRunSuccessView from "~/bookings/views/booking-dry-run-success-view";
export const generateMetadata = async () => {
return await _generateMetadata(
(t) => t("booking_dry_run_successful"),
(t) => t("booking_dry_run_successful_description")
);
};
const Page = async () => {
return <BookingDryRunSuccessView />;
};
export default WithLayout({ ServerPage: Page });
+33
View File
@@ -0,0 +1,33 @@
import { PageProps } from "app/_types";
import { _generateMetadata } from "app/_utils";
import { WithLayout } from "app/layoutHOC";
import { redirect } from "next/navigation";
import { z } from "zod";
import { validStatuses } from "~/bookings/lib/validStatuses";
import BookingsList from "~/bookings/views/bookings-listing-view";
const querySchema = z.object({
status: z.enum(validStatuses),
});
export const generateMetadata = async () =>
await _generateMetadata(
(t) => t("bookings"),
(t) => t("bookings_description")
);
export const generateStaticParams = async () => {
return validStatuses.map((status) => ({ status }));
};
const Page = async ({ params, searchParams }: PageProps) => {
const parsed = querySchema.safeParse({ ...params, ...searchParams });
if (!parsed.success) {
redirect("/bookings/upcoming");
}
return <BookingsList status={parsed.data.status} />;
};
export default WithLayout({ ServerPage: Page })<"P">;
@@ -1,4 +1,3 @@
import { withAppDirSsr } from "app/WithAppDirSsr";
import withEmbedSsrAppDir from "app/WithEmbedSSR";
import { WithLayout } from "app/layoutHOC";
@@ -6,10 +5,6 @@ import { getServerSideProps } from "@server/lib/[user]/[type]/getServerSideProps
import LegacyPage, { type PageProps } from "~/users/views/users-type-public-view";
export { generateMetadata } from "../page";
const getData = withAppDirSsr<PageProps>(getServerSideProps);
const getEmbedData = withEmbedSsrAppDir(getData);
const getEmbedData = withEmbedSsrAppDir<PageProps>(getServerSideProps);
export default WithLayout({ getLayout: null, getData: getEmbedData, Page: LegacyPage })<"P">;
+1 -6
View File
@@ -1,4 +1,3 @@
import { withAppDirSsr } from "app/WithAppDirSsr";
import withEmbedSsrAppDir from "app/WithEmbedSSR";
import { WithLayout } from "app/layoutHOC";
@@ -7,10 +6,6 @@ import { getServerSideProps } from "@server/lib/[user]/getServerSideProps";
import type { PageProps as UserPageProps } from "~/users/views/users-public-view";
import LegacyPage from "~/users/views/users-public-view";
export { generateMetadata } from "../page";
const getData = withAppDirSsr<UserPageProps>(getServerSideProps);
const getEmbedData = withEmbedSsrAppDir(getData);
const getEmbedData = withEmbedSsrAppDir<UserPageProps>(getServerSideProps);
export default WithLayout({ getLayout: null, getData: getEmbedData, Page: LegacyPage })<"P">;
+1 -1
View File
@@ -13,7 +13,7 @@ import { buildLegacyCtx } from "@lib/buildLegacyCtx";
import type { PageProps } from "~/apps/[slug]/slug-view";
import Page from "~/apps/[slug]/slug-view";
const getData = withAppDirSsg<PageProps>(getStaticProps);
const getData = withAppDirSsg<PageProps>(getStaticProps, "future/apps/[slug]");
export const generateMetadata = async ({ params, searchParams }: _PageProps) => {
const legacyContext = buildLegacyCtx(headers(), cookies(), params, searchParams);
@@ -28,7 +28,7 @@ export const generateStaticParams = async () => {
return paths.map((category) => ({ category }));
};
const getData = withAppDirSsg<PageProps>(getStaticProps);
const getData = withAppDirSsg<PageProps>(getStaticProps, "future/apps/categories/[category]");
export default WithLayout({ getData, Page: CategoryPage, getLayout: null })<"P">;
export const dynamic = "force-static";
+1 -1
View File
@@ -13,7 +13,7 @@ export const generateMetadata = async () => {
);
};
const getData = withAppDirSsg(getStaticProps);
const getData = withAppDirSsg(getStaticProps, "future/auth/error");
export default WithLayout({ getData, Page, getLayout: null })<"P">;
export const dynamic = "force-static";
@@ -1,25 +0,0 @@
import { withAppDirSsg } from "app/WithAppDirSsg";
import { _generateMetadata } from "app/_utils";
import { WithLayout } from "app/layoutHOC";
import type { InferGetStaticPropsType } from "next";
import { validStatuses } from "~/bookings/lib/validStatuses";
import Page from "~/bookings/views/bookings-listing-view";
import { getStaticProps } from "~/bookings/views/bookings-listing-view.getStaticProps";
type Y = InferGetStaticPropsType<typeof getStaticProps>;
const getData = withAppDirSsg<Y>(getStaticProps);
export const generateMetadata = async () =>
await _generateMetadata(
(t) => t("bookings"),
(t) => t("bookings_description")
);
export const generateStaticParams = async () => {
return validStatuses.map((status) => ({ status }));
};
export default WithLayout({ getLayout: null, getData, Page })<"P">;
export const dynamic = "force-static";
@@ -1,12 +1,10 @@
import { type PageProps } from "@pages/org/[orgSlug]/[user]/[type]";
import Page from "@pages/org/[orgSlug]/[user]/[type]/embed";
import { withAppDirSsr } from "app/WithAppDirSsr";
import withEmbedSsrAppDir from "app/WithEmbedSSR";
import { WithLayout } from "app/layoutHOC";
import { getServerSideProps } from "@lib/org/[orgSlug]/[user]/[type]/getServerSideProps";
const getData = withAppDirSsr<PageProps>(getServerSideProps);
const getEmbedData = withEmbedSsrAppDir(getData);
const getEmbedData = withEmbedSsrAppDir<PageProps>(getServerSideProps);
export default WithLayout({ getLayout: null, getData: getEmbedData, isBookingPage: true, Page });
@@ -1,11 +1,8 @@
import { getServerSideProps, type PageProps } from "@pages/org/[orgSlug]/[user]";
import Page from "@pages/org/[orgSlug]/[user]/embed";
import { withAppDirSsr } from "app/WithAppDirSsr";
import withEmbedSsrAppDir from "app/WithEmbedSSR";
import { WithLayout } from "app/layoutHOC";
const getData = withAppDirSsr<PageProps>(getServerSideProps);
const getEmbedData = withEmbedSsrAppDir(getData);
const getEmbedData = withEmbedSsrAppDir<PageProps>(getServerSideProps);
export default WithLayout({ getLayout: null, getData: getEmbedData, isBookingPage: true, Page });
@@ -1,28 +1,14 @@
import { withAppDirSsr } from "app/WithAppDirSsr";
import withEmbedSsrAppDir from "app/WithEmbedSSR";
import type { PageProps as _PageProps } from "app/_types";
import { _generateMetadata } from "app/_utils";
import { WithLayout } from "app/layoutHOC";
import { cookies, headers } from "next/headers";
import { buildLegacyCtx } from "@lib/buildLegacyCtx";
import { getServerSideProps } from "@lib/team/[slug]/getServerSideProps";
import type { PageProps } from "~/team/team-view";
import TeamPage from "~/team/team-view";
const getData = withAppDirSsr<PageProps>(getServerSideProps);
export const generateMetadata = async ({ params, searchParams }: _PageProps) => {
const props = await getData(buildLegacyCtx(headers(), cookies(), params, searchParams));
return await _generateMetadata(
(t) => props.team.name || t("nameless_team"),
(t) => props.team.name || t("nameless_team")
);
};
const getEmbedData = withEmbedSsrAppDir(getData);
const getEmbedData = withEmbedSsrAppDir<PageProps>(getServerSideProps);
export default WithLayout({
Page: TeamPage,
@@ -1,43 +1,13 @@
import { withAppDirSsr } from "app/WithAppDirSsr";
import withEmbedSsrAppDir from "app/WithEmbedSSR";
import type { PageProps as _PageProps } from "app/_types";
import { _generateMetadata } from "app/_utils";
import { WithLayout } from "app/layoutHOC";
import { cookies, headers } from "next/headers";
import { orgDomainConfig } from "@calcom/features/ee/organizations/lib/orgDomains";
import { EventRepository } from "@calcom/lib/server/repository/event";
import { buildLegacyCtx } from "@lib/buildLegacyCtx";
import { getServerSideProps } from "@lib/team/[slug]/[type]/getServerSideProps";
import type { PageProps } from "~/team/type-view";
import LegacyPage from "~/team/type-view";
export const generateMetadata = async ({ params, searchParams }: _PageProps) => {
const legacyCtx = buildLegacyCtx(headers(), cookies(), params, searchParams);
const props = await getData(legacyCtx);
const { user: username, slug: eventSlug, booking } = props;
const { currentOrgDomain, isValidOrgDomain } = orgDomainConfig(legacyCtx.req, legacyCtx.params?.orgSlug);
const event = await EventRepository.getPublicEvent({
username,
eventSlug,
isTeamEvent: true,
org: isValidOrgDomain ? currentOrgDomain : null,
fromRedirectOfNonOrgLink: legacyCtx.query.orgRedirection === "true",
});
const profileName = event?.profile?.name ?? "";
const title = event?.title ?? "";
return await _generateMetadata(
(t) => `${booking?.uid && !!booking ? t("reschedule") : ""} ${title} | ${profileName}`,
(t) => `${booking?.uid ? t("reschedule") : ""} ${title}`
);
};
const getData = withAppDirSsr<PageProps>(getServerSideProps);
const getEmbedData = withEmbedSsrAppDir(getData);
const getEmbedData = withEmbedSsrAppDir<PageProps>(getServerSideProps);
export default WithLayout({ getLayout: null, getData: getEmbedData, Page: LegacyPage })<"P">;
+1 -13
View File
@@ -3,24 +3,12 @@ import withEmbedSsrAppDir from "app/WithEmbedSSR";
import type { PageProps as _PageProps } from "app/_types";
import { _generateMetadata } from "app/_utils";
import { WithLayout } from "app/layoutHOC";
import { cookies, headers } from "next/headers";
import { buildLegacyCtx } from "@lib/buildLegacyCtx";
import { getServerSideProps } from "@lib/team/[slug]/getServerSideProps";
import type { PageProps } from "~/team/team-view";
import LegacyPage from "~/team/team-view";
export const generateMetadata = async ({ params, searchParams }: _PageProps) => {
const props = await getData(buildLegacyCtx(headers(), cookies(), params, searchParams));
return await _generateMetadata(
(t) => props.team.name || t("nameless_team"),
(t) => props.team.name || t("nameless_team")
);
};
const getData = withAppDirSsr<PageProps>(getServerSideProps);
const getEmbedData = withEmbedSsrAppDir(getData);
const getEmbedData = withEmbedSsrAppDir<PageProps>(getServerSideProps);
export default WithLayout({ getLayout: null, getData: getEmbedData, Page: LegacyPage })<"P">;
@@ -0,0 +1,17 @@
import { withAppDirSsr } from "app/WithAppDirSsr";
import { _generateMetadata } from "app/_utils";
import { WithLayout } from "app/layoutHOC";
import { getServerSideProps } from "@lib/insights/getServerSideProps";
import InsightsVirtualQueuesPage from "~/insights/insights-virtual-queues-view";
export const generateMetadata = async () =>
await _generateMetadata(
(t) => t("insights"),
(t) => t("insights_subtitle")
);
const getData = withAppDirSsr(getServerSideProps);
export default WithLayout({ getLayout: null, getData, Page: InsightsVirtualQueuesPage });
+1 -9
View File
@@ -21,15 +21,7 @@ const calFont = localFont({
weight: "600",
});
export const generateMetadata = () =>
prepareRootMetadata({
twitterCreator: "@calcom",
twitterSite: "@calcom",
robots: {
index: false,
follow: false,
},
});
export const generateMetadata = () => prepareRootMetadata();
const getInitialProps = async (url: string) => {
const { pathname, searchParams } = new URL(url);
+1 -3
View File
@@ -1,4 +1,3 @@
import { withAppDirSsr } from "app/WithAppDirSsr";
import withEmbedSsrAppDir from "app/WithEmbedSSR";
import type { PageProps } from "app/_types";
import { cookies, headers } from "next/headers";
@@ -6,8 +5,7 @@ import { cookies, headers } from "next/headers";
import { buildLegacyCtx } from "@lib/buildLegacyCtx";
import { getServerSideProps } from "@lib/reschedule/[uid]/getServerSideProps";
const getData = withAppDirSsr(getServerSideProps);
const getEmbedData = withEmbedSsrAppDir(getData);
const getEmbedData = withEmbedSsrAppDir(getServerSideProps);
const Page = async ({ params, searchParams }: PageProps) => {
const legacyCtx = buildLegacyCtx(headers(), cookies(), params, searchParams);
+7 -5
View File
@@ -17,12 +17,12 @@ import CacheProvider from "react-inlinesvg/provider";
import DynamicPostHogProvider from "@calcom/features/ee/event-tracking/lib/posthog/providerDynamic";
import { OrgBrandingProvider } from "@calcom/features/ee/organizations/context/provider";
import DynamicHelpscoutProvider from "@calcom/features/ee/support/lib/helpscout/providerDynamic";
import DynamicIntercomProvider from "@calcom/features/ee/support/lib/intercom/providerDynamic";
import { FeatureProvider } from "@calcom/features/flags/context/provider";
import { useFlags } from "@calcom/features/flags/hooks";
import { MetaProvider } from "@calcom/ui";
import useIsBookingPage from "@lib/hooks/useIsBookingPage";
import PlainChat from "@lib/plain/plainChat";
import type { WithLocaleProps } from "@lib/withLocale";
import type { WithNonceProps } from "@lib/withNonce";
@@ -265,9 +265,11 @@ function OrgBrandProvider({ children }: { children: React.ReactNode }) {
const AppProviders = (props: PageWrapperProps) => {
// No need to have intercom on public pages - Good for Page Performance
const isBookingPage = useIsBookingPage();
const RemainingProviders = (
<EventCollectionProvider options={{ apiPath: "/api/collect-events" }}>
<SessionProvider>
<PlainChat />
<CustomI18nextProvider i18n={props.i18n}>
<TooltipProvider>
{/* color-scheme makes background:transparent not work which is required by embed. We need to ensure next-theme adds color-scheme to `body` instead of `html`(https://github.com/pacocoursey/next-themes/blob/main/src/index.tsx#L74). Once that's done we can enable color-scheme support */}
@@ -301,11 +303,11 @@ const AppProviders = (props: PageWrapperProps) => {
}
return (
<DynamicHelpscoutProvider>
<DynamicIntercomProvider>
<>
<DynamicHelpscoutProvider>
<DynamicPostHogProvider>{Hydrated}</DynamicPostHogProvider>
</DynamicIntercomProvider>
</DynamicHelpscoutProvider>
</DynamicHelpscoutProvider>
</>
);
};
+6 -7
View File
@@ -16,12 +16,12 @@ import CacheProvider from "react-inlinesvg/provider";
import DynamicPostHogProvider from "@calcom/features/ee/event-tracking/lib/posthog/providerDynamic";
import { OrgBrandingProvider } from "@calcom/features/ee/organizations/context/provider";
import DynamicHelpscoutProvider from "@calcom/features/ee/support/lib/helpscout/providerDynamic";
import DynamicIntercomProvider from "@calcom/features/ee/support/lib/intercom/providerDynamic";
import { FeatureProvider } from "@calcom/features/flags/context/provider";
import { useFlags } from "@calcom/features/flags/hooks";
import { MetaProvider } from "@calcom/ui";
import useIsBookingPage from "@lib/hooks/useIsBookingPage";
import PlainChat from "@lib/plain/plainChat";
import type { WithLocaleProps } from "@lib/withLocale";
import type { WithNonceProps } from "@lib/withNonce";
@@ -282,7 +282,6 @@ function OrgBrandProvider({ children }: { children: React.ReactNode }) {
}
const AppProviders = (props: AppPropsWithChildren) => {
// No need to have intercom on public pages - Good for Page Performance
const isBookingPage = useIsBookingPage();
const { pageProps, ...rest } = props;
@@ -298,9 +297,9 @@ const AppProviders = (props: AppPropsWithChildren) => {
const RemainingProviders = (
<EventCollectionProvider options={{ apiPath: "/api/collect-events" }}>
<SessionProvider session={pageProps.session ?? undefined}>
<PlainChat />
<CustomI18nextProvider {...propsWithoutNonce}>
<TooltipProvider>
{/* color-scheme makes background:transparent not work which is required by embed. We need to ensure next-theme adds color-scheme to `body` instead of `html`(https://github.com/pacocoursey/next-themes/blob/main/src/index.tsx#L74). Once that's done we can enable color-scheme support */}
<CalcomThemeProvider
themeBasis={props.pageProps.themeBasis}
nonce={props.pageProps.nonce}
@@ -327,14 +326,14 @@ const AppProviders = (props: AppPropsWithChildren) => {
}
return (
<DynamicHelpscoutProvider>
<DynamicIntercomProvider>
<>
<DynamicHelpscoutProvider>
<DynamicPostHogProvider>
<PostHogPageView />
{RemainingProviders}
</DynamicPostHogProvider>
</DynamicIntercomProvider>
</DynamicHelpscoutProvider>
</DynamicHelpscoutProvider>
</>
);
};
@@ -12,7 +12,7 @@ import { RedirectType } from "@calcom/prisma/enums";
import { getTemporaryOrgRedirect } from "@lib/getTemporaryOrgRedirect";
import type { inferSSRProps } from "@lib/types/inferSSRProps";
import type { EmbedProps } from "@lib/withEmbedSsr";
import type { EmbedProps } from "app/WithEmbedSSR";
export type PageProps = inferSSRProps<typeof getServerSideProps> & EmbedProps;
+7 -13
View File
@@ -1,14 +1,5 @@
import type { Metadata } from "next";
type RootMetadataRecipe = Readonly<{
twitterCreator: string;
twitterSite: string;
robots: {
index: boolean;
follow: boolean;
};
}>;
export type PageMetadataRecipe = Readonly<{
title: string;
canonical: string;
@@ -18,7 +9,7 @@ export type PageMetadataRecipe = Readonly<{
metadataBase: URL;
}>;
export const prepareRootMetadata = (recipe: RootMetadataRecipe): Metadata => ({
export const prepareRootMetadata = (): Metadata => ({
icons: {
icon: "/favicon.ico",
apple: "/api/logo?type=apple-touch-icon",
@@ -48,7 +39,6 @@ export const prepareRootMetadata = (recipe: RootMetadataRecipe): Metadata => ({
userScalable: false,
viewportFit: "cover",
},
robots: recipe.robots,
other: {
"application-TileColor": "#ff0000",
},
@@ -63,8 +53,12 @@ export const prepareRootMetadata = (recipe: RootMetadataRecipe): Metadata => ({
},
],
twitter: {
site: recipe.twitterSite,
creator: recipe.twitterCreator,
site: "@calcom",
creator: "@calcom",
card: "summary_large_image",
},
robots: {
index: true,
follow: true,
},
});
+281
View File
@@ -0,0 +1,281 @@
"use client";
import { useSession } from "next-auth/react";
import { usePathname, useSearchParams } from "next/navigation";
import Script from "next/script";
import { useEffect, useState, useCallback, useMemo } from "react";
declare global {
interface Window {
Plain?: {
// eslint-disable-next-line @typescript-eslint/no-explicit-any
init: (config: any) => void;
open: () => void;
};
plainScriptLoaded?: () => void;
__PLAIN_CONFIG__?: PlainChatConfig;
}
}
interface PlainChatConfig {
appId: string;
customerDetails: {
email: string;
emailHash: string;
fullName: string;
shortName: string;
chatAvatarUrl: string;
};
links: Array<{
icon: string;
text: string;
url: string;
}>;
chatButtons: Array<{
icon: string;
text: string;
type: string;
form?: {
fields: Array<{
type: string;
placeholder: string;
options: Array<{
icon: string;
text: string;
threadDetails: {
severity: string;
labelTypeIds: Array<string>;
issueType: string;
priority: string;
};
}>;
}>;
};
}>;
entryPoint: {
type: string;
};
hideBranding: boolean;
theme: string;
style: {
brandColor: string;
launcherBackgroundColor: string;
launcherIconColor: string;
};
position: {
bottom: string;
right: string;
};
}
const PlainChat = () => {
const [config, setConfig] = useState<PlainChatConfig | null>(null);
const [isSmallScreen, setIsSmallScreen] = useState(false);
const { data: session } = useSession();
const pathname = usePathname();
const searchParams = useSearchParams();
const shouldOpenPlain = pathname === "/event-types" && searchParams?.has("openPlain");
const userEmail = session?.user?.email;
const isAppDomain = useMemo(() => {
const restrictedPaths = process.env.NEXT_PUBLIC_PLAIN_CHAT_EXCLUDED_PATHS?.split(",") || [];
return (
typeof window !== "undefined" &&
window.location.origin === process.env.NEXT_PUBLIC_WEBAPP_URL &&
!restrictedPaths.some((path) => pathname?.startsWith(path.trim()))
);
}, [pathname]);
const checkScreenSize = useCallback(() => {
if (typeof window === "undefined") return;
const isSmall = window.innerWidth < 768;
setIsSmallScreen(isSmall);
if (isSmall && window.Plain) {
const plainElement = document.querySelector("#plain-container");
plainElement?.remove();
window.Plain = undefined;
} else if (!isSmall && window.Plain === undefined) {
window.plainScriptLoaded?.();
}
}, []);
const initConfig = useCallback(async () => {
if (!userEmail) return;
try {
const response = await fetch("/api/plain-hash", {
method: "POST",
headers: {
"Content-Type": "application/json",
},
});
if (!response.ok) {
const errorText = await response.text();
throw new Error(`Failed to generate hash: ${errorText}`);
}
const data = await response.json();
if (!data.hash || !data.email || !data.appId) {
throw new Error("Missing required fields in API response");
}
const plainChatConfig: PlainChatConfig = {
appId: data.appId,
customerDetails: {
email: data.email,
shortName: data.shortName,
fullName: data.fullName,
emailHash: data.hash,
chatAvatarUrl: data.chatAvatarUrl,
},
links: [
{
icon: "book",
text: "Documentation",
url: "https://cal.com/docs",
},
{
icon: "chat",
text: "Ask the community",
url: "https://github.com/calcom/cal.com/discussions",
},
],
chatButtons: [
{
icon: "chat",
text: "Ask a question",
type: "primary",
},
{
icon: "bulb",
text: "Send feedback",
type: "default",
},
{
icon: "error",
text: "Report an issue",
type: "default",
form: {
fields: [
{
type: "dropdown",
placeholder: "Select severity...",
options: [
{
icon: "support",
text: "I'm unable to use the app",
threadDetails: {
severity: "critical",
issueType: "critical",
labelTypeIds: ["lt_01JFJWNWAC464N8DZ6YE71YJRF"],
priority: "u",
},
},
{
icon: "error",
text: "Major functionality degraded",
threadDetails: {
severity: "major",
issueType: "major",
labelTypeIds: ["lt_01JFJWP3KECF1YQES6XF212RFW"],
priority: "h",
},
},
{
icon: "bug",
text: "Minor annoyance",
threadDetails: {
severity: "minor",
issueType: "minor",
labelTypeIds: ["lt_01JFJWPC8ADW0PK28JHMJR6NSS"],
priority: "l",
},
},
],
},
],
},
},
],
entryPoint: {
type: "chat",
},
hideBranding: true,
theme: "auto",
style: {
brandColor: "#FFFFFF",
launcherBackgroundColor: "#262626",
launcherIconColor: "#FFFFFF",
},
position: {
bottom: "20px",
right: "20px",
},
};
if (process.env.NODE_ENV === "development" || process.env.NODE_ENV === "test") {
window.__PLAIN_CONFIG__ = plainChatConfig;
}
setConfig(plainChatConfig);
if (shouldOpenPlain) {
const timer = setTimeout(() => {
if (window.Plain) {
window.Plain.open();
}
}, 100);
return () => clearTimeout(timer);
}
} catch (error) {
console.error("Failed to initialize Plain Chat:", error);
}
}, [userEmail, shouldOpenPlain]);
useEffect(() => {
if (!isAppDomain) return;
checkScreenSize();
window.addEventListener("resize", checkScreenSize);
initConfig();
return () => window.removeEventListener("resize", checkScreenSize);
}, [isAppDomain, checkScreenSize, initConfig, userEmail]);
const plainChatScript = `
window.plainScriptLoaded = function() {
if (window.Plain && ${Boolean(config)}) {
try {
Plain.init(${config ? JSON.stringify(config) : null});
} catch (error) {
console.error("Failed to initialize Plain:", error);
}
}
}
`;
if (!isAppDomain || isSmallScreen || !config || typeof window === "undefined") return null;
return (
<>
<Script
id="plain-chat"
src="https://chat.cdn-plain.com/index.js"
strategy="afterInteractive"
onLoad={() => window.plainScriptLoaded?.()}
/>
<Script
id="plain-chat-init"
strategy="afterInteractive"
dangerouslySetInnerHTML={{ __html: plainChatScript }}
/>
</>
);
};
export default PlainChat;
@@ -4,6 +4,7 @@ import { URLSearchParams } from "url";
import { z } from "zod";
import { getServerSession } from "@calcom/features/auth/lib/getServerSession";
import { getSafe } from "@calcom/lib/bookingSuccessRedirect";
import { buildEventUrlFromBooking } from "@calcom/lib/bookings/buildEventUrlFromBooking";
import { getDefaultEvent } from "@calcom/lib/defaultEvents";
import { maybeGetBookingUidFromSeat } from "@calcom/lib/server/maybeGetBookingUidFromSeat";
@@ -35,10 +36,11 @@ export async function getServerSideProps(context: GetServerSidePropsContext) {
} = querySchema.parse(context.query);
const coepFlag = context.query["flag.coep"];
const { uid, seatReferenceUid: maybeSeatReferenceUid } = await maybeGetBookingUidFromSeat(
prisma,
bookingUid
);
const {
uid,
seatReferenceUid: maybeSeatReferenceUid,
bookingSeat,
} = await maybeGetBookingUidFromSeat(prisma, bookingUid);
const booking = await prisma.booking.findUnique({
where: {
@@ -46,6 +48,7 @@ export async function getServerSideProps(context: GetServerSidePropsContext) {
},
select: {
...bookingMinimalSelect,
responses: true,
eventType: {
select: {
users: {
@@ -129,9 +132,18 @@ export async function getServerSideProps(context: GetServerSidePropsContext) {
const isBookingInPast = booking.endTime && new Date(booking.endTime) < new Date();
if (isBookingInPast) {
const destinationUrlSearchParams = new URLSearchParams();
const responses = bookingSeat ? getSafe<string>(bookingSeat.data, ["responses"]) : booking.responses;
const name = getSafe<string>(responses, ["name"]);
const email = getSafe<string>(responses, ["email"]);
if (name) destinationUrlSearchParams.set("name", name);
if (email) destinationUrlSearchParams.set("email", email);
const searchParamsString = destinationUrlSearchParams.toString();
return {
redirect: {
destination: eventUrl,
destination: searchParamsString ? `${eventUrl}?${searchParamsString}` : eventUrl,
permanent: false,
},
};
-1
View File
@@ -187,7 +187,6 @@ export const config = {
"/getting-started/:path*",
"/apps",
"/bookings/:status/",
"/future/bookings/:status/",
"/video/:path*",
"/teams",
"/future/teams/",
@@ -18,6 +18,7 @@ import { WEBAPP_URL } from "@calcom/lib/constants";
import { useLocale } from "@calcom/lib/hooks/useLocale";
import type { Team } from "@calcom/prisma/client";
import type { eventTypeBookingFields } from "@calcom/prisma/zod-utils";
import { eventTypeMetaDataSchemaWithTypedApps } from "@calcom/prisma/zod-utils";
import type { EventTypeMetaDataSchema } from "@calcom/prisma/zod-utils";
import { trpc } from "@calcom/trpc/react";
import type { AppMeta } from "@calcom/types/App";
@@ -243,15 +244,20 @@ const OnboardingPage = ({
const promises = group.eventTypes
.filter((eventType) => eventType.selected)
.map((value: TEventType) => {
// Prevent two payment apps to be enabled
// Ok to cast type here because this metadata will be updated as the event type metadata
if (
checkForMultiplePaymentApps(value.metadata as z.infer<typeof EventTypeMetaDataSchema>)
)
throw new Error(t("event_setup_multiple_payment_apps_error"));
if (value.metadata?.apps?.stripe?.paymentOption === "HOLD" && value.seatsPerTimeSlot) {
throw new Error(t("seats_and_no_show_fee_error"));
if (value.metadata) {
const metadata = eventTypeMetaDataSchemaWithTypedApps.parse(value.metadata);
// Prevent two payment apps to be enabled
// Ok to cast type here because this metadata will be updated as the event type metadata
if (checkForMultiplePaymentApps(metadata))
throw new Error(t("event_setup_multiple_payment_apps_error"));
if (
value.metadata?.apps?.stripe?.paymentOption === "HOLD" &&
value.seatsPerTimeSlot
) {
throw new Error(t("seats_and_no_show_fee_error"));
}
}
let updateObject: TUpdateObject = { id: value.id };
if (isConferencing) {
updateObject = {
+1 -1
View File
@@ -282,7 +282,7 @@ PageProps & WithNonceProps<{}>) {
disabled={formState.isSubmitting}
className="w-full justify-center">
<span>{twoFactorRequired ? t("submit") : t("sign_in")}</span>
{lastUsed === "credentials" && <LastUsed className="text-gray-600" />}
{lastUsed === "credentials" && !twoFactorRequired && <LastUsed className="text-gray-600" />}
</Button>
</div>
</form>
@@ -3,8 +3,7 @@
import dynamic from "next/dynamic";
import { Suspense } from "react";
import { useLocale } from "@calcom/lib/hooks/useLocale";
import { HeadSeo, Loader } from "@calcom/ui";
import { Loader } from "@calcom/ui";
const TroubleshooterClientOnly = dynamic(
() => import("@calcom/features/troubleshooter/Troubleshooter").then((mod) => mod.Troubleshooter),
@@ -14,10 +13,8 @@ const TroubleshooterClientOnly = dynamic(
);
function TroubleshooterPage() {
const { t } = useLocale();
return (
<>
<HeadSeo title={t("troubleshoot")} description={t("troubleshoot_availability")} />
<Suspense
fallback={
<div className="flex h-full w-full items-center justify-center">
@@ -0,0 +1,76 @@
"use client";
import { useLocale } from "@calcom/lib/hooks/useLocale";
import { Icon } from "@calcom/ui";
export default function DryRunSuccessful() {
const { t } = useLocale();
const sampleBookingInfo = {
startTime: "2024-01-20T10:00:00Z",
endTime: "2024-01-20T11:00:00Z",
title: "Sample Meeting",
description: "This is a sample dry run booking",
attendees: [
{
email: "attendee@example.com",
name: "Sample Attendee",
timeZone: "America/New_York",
},
],
user: {
name: "Sample Host",
email: "host@example.com",
},
location: "Zoom Meeting",
status: "ACCEPTED",
uid: "sample-uid",
};
const sampleEventType = {
title: "Sample Event Type",
length: 60,
eventName: "Sample Meeting",
description: "This is a sample event type for dry run",
requiresConfirmation: false,
recurringEvent: null,
};
return (
<div className="flex h-screen">
<div className="bg-default m-auto rounded-md p-10 text-center">
<div className="bg-success mx-auto mb-4 flex h-12 w-12 items-center justify-center rounded-full">
<Icon name="check" className="h-6 w-6 text-green-600 dark:text-green-400" />
</div>
<h1 className="text-emphasis mb-4 text-2xl font-medium">{t("booking_dry_run_successful")}</h1>
<p className="text-default mb-8 max-w-2xl text-sm">{t("booking_dry_run_successful_description")}</p>
{/* <div className="border-subtle text-default mt-8 grid grid-cols-3 border-t pt-8 text-left">
<div className="font-medium">{t("what")}</div>
<div className="col-span-2 mb-6">{sampleBookingInfo.title}</div>
<div className="font-medium">{t("when")}</div>
<div className="col-span-2 mb-6">{new Date(sampleBookingInfo.startTime).toLocaleString()}</div>
<div className="font-medium">{t("who")}</div>
<div className="col-span-2 mb-6">
<div className="mb-3">
<div>
<span className="mr-2">{sampleBookingInfo.user.name}</span>
<Badge variant="blue">{t("Host")}</Badge>
</div>
<p className="text-default">{sampleBookingInfo.user.email}</p>
</div>
<div>
<p>{sampleBookingInfo.attendees[0].name}</p>
<p>{sampleBookingInfo.attendees[0].email}</p>
</div>
</div>
<div className="font-medium">{t("where")}</div>
<div className="col-span-2 mb-6">{sampleBookingInfo.location}</div>
</div> */}
</div>
</div>
);
}
@@ -1,24 +0,0 @@
import { type GetStaticProps } from "next";
import { z } from "zod";
import { getTranslations } from "@server/lib/getTranslations";
import { validStatuses } from "~/bookings/lib/validStatuses";
const querySchema = z.object({
status: z.enum(validStatuses),
});
export const getStaticProps: GetStaticProps = async (ctx) => {
const params = querySchema.safeParse(ctx.params);
const i18n = await getTranslations(ctx);
if (!params.success) return { notFound: true };
return {
props: {
status: params.data.status,
i18n,
},
};
};
@@ -13,7 +13,6 @@ import { useFilterQuery } from "@calcom/features/bookings/lib/useFilterQuery";
import Shell from "@calcom/features/shell/Shell";
import { useInViewObserver } from "@calcom/lib/hooks/useInViewObserver";
import { useLocale } from "@calcom/lib/hooks/useLocale";
import { useParamsWithFallback } from "@calcom/lib/hooks/useParamsWithFallback";
import type { RouterOutputs } from "@calcom/trpc/react";
import { trpc } from "@calcom/trpc/react";
import type { HorizontalTabItemProps, VerticalTabItemProps } from "@calcom/ui";
@@ -67,17 +66,13 @@ const descriptionByStatus: Record<NonNullable<BookingListingStatus>, string> = {
unconfirmed: "unconfirmed_bookings",
};
const querySchema = z.object({
status: z.enum(validStatuses),
});
export default function Bookings() {
const params = useParamsWithFallback();
export default function Bookings({ status }: { status: (typeof validStatuses)[number] }) {
const { data: filterQuery } = useFilterQuery();
const { status } = params ? querySchema.parse(params) : { status: "upcoming" as const };
const { t } = useLocale();
const user = useMeQuery().data;
const [isFiltersVisible, setIsFiltersVisible] = useState<boolean>(false);
const query = trpc.viewer.bookings.get.useInfiniteQuery(
{
limit: 10,
@@ -1,153 +0,0 @@
import { render } from "@testing-library/react";
import { useSession } from "next-auth/react";
import React from "react";
import { describe, it, expect, vi } from "vitest";
import type { z } from "zod";
import { getOrgFullOrigin } from "@calcom/features/ee/organizations/lib/orgDomains";
import { useRouterQuery } from "@calcom/lib/hooks/useRouterQuery";
import { BookingStatus } from "@calcom/prisma/enums";
import { HeadSeo } from "@calcom/ui";
import Success from "./bookings-single-view";
function mockedSuccessComponentProps(props: Partial<React.ComponentProps<typeof Success>>) {
return {
eventType: {
id: 1,
title: "Event Title",
description: "",
locations: null,
length: 15,
userId: null,
eventName: "d",
timeZone: null,
recurringEvent: null,
requiresConfirmation: false,
disableGuests: false,
seatsPerTimeSlot: null,
seatsShowAttendees: null,
seatsShowAvailabilityCount: null,
schedulingType: null,
price: 0,
currency: "usd",
successRedirectUrl: null,
customInputs: [],
teamId: null,
team: null,
workflows: [],
hosts: [],
users: [],
owner: null,
isDynamic: false,
periodStartDate: "1",
periodEndDate: "1",
metadata: null,
bookingFields: [] as unknown as [] & z.BRAND<"HAS_SYSTEM_FIELDS">,
},
profile: {
name: "John",
email: null,
theme: null,
brandColor: null,
darkBrandColor: null,
slug: null,
},
bookingInfo: {
uid: "uid",
metadata: null,
customInputs: [],
startTime: new Date(),
endTime: new Date(),
id: 1,
user: null,
eventType: null,
seatsReferences: [],
userPrimaryEmail: null,
eventTypeId: null,
title: "Booking Title",
description: null,
location: null,
recurringEventId: null,
smsReminderNumber: "0",
cancellationReason: null,
rejectionReason: null,
status: BookingStatus.ACCEPTED,
attendees: [],
responses: {
name: "John",
},
rescheduled: false,
fromReschedule: null,
},
orgSlug: null,
userTimeFormat: 12,
requiresLoginToUpdate: false,
themeBasis: "dark",
hideBranding: false,
recurringBookings: null,
trpcState: {
queries: [],
mutations: [],
},
dynamicEventName: "Event Title",
paymentStatus: null,
rescheduledToUid: null,
...props,
} satisfies React.ComponentProps<typeof Success>;
}
describe("Success Component", () => {
it("renders HeadSeo correctly", () => {
vi.mocked(getOrgFullOrigin).mockImplementation((text: string | null) => `${text}.cal.local`);
vi.mocked(useRouterQuery).mockReturnValue({
uid: "uid",
});
vi.mock("@calcom/lib/constants", async (importOriginal) => {
const actual = await importOriginal<any>();
return {
...actual,
CURRENT_TIMEZONE: "Europe/London",
};
});
vi.mocked(useSession).mockReturnValue({
update: vi.fn(),
status: "authenticated",
data: {
hasValidLicense: true,
upId: "1",
expires: "1",
user: {
name: "John",
id: 1,
profile: {
id: null,
upId: "1",
username: null,
organizationId: null,
organization: null,
},
},
},
});
const mockObject = {
props: mockedSuccessComponentProps({
orgSlug: "org1",
}),
};
render(<Success {...mockObject.props} />);
const expectedTitle = `booking_confirmed`;
const expectedDescription = expectedTitle;
expect(HeadSeo).toHaveBeenCalledWith(
{
origin: `${mockObject.props.orgSlug}.cal.local`,
title: expectedTitle,
description: expectedDescription,
},
{}
);
});
});
@@ -48,7 +48,7 @@ import { getEveryFreqFor } from "@calcom/lib/recurringStrings";
import { getIs24hClockFromLocalStorage, isBrowserLocale24h } from "@calcom/lib/timeFormat";
import { localStorage } from "@calcom/lib/webstorage";
import { BookingStatus, SchedulingType } from "@calcom/prisma/enums";
import { bookingMetadataSchema } from "@calcom/prisma/zod-utils";
import { bookingMetadataSchema, eventTypeMetaDataSchemaWithTypedApps } from "@calcom/prisma/zod-utils";
import { trpc } from "@calcom/trpc/react";
import {
Alert,
@@ -56,12 +56,12 @@ import {
Badge,
Button,
EmailInput,
HeadSeo,
useCalcomTheme,
TextArea,
showToast,
EmptyScreen,
Icon,
HeadSeo,
} from "@calcom/ui";
import CancelBooking from "@calcom/web/components/booking/CancelBooking";
import EventReservationSchema from "@calcom/web/components/schemas/EventReservationSchema";
@@ -230,7 +230,13 @@ export default function Success(props: PageProps) {
t,
};
const giphyAppData = getEventTypeAppData(eventType, "giphy");
const giphyAppData = getEventTypeAppData(
{
...eventType,
metadata: eventTypeMetaDataSchemaWithTypedApps.parse(eventType.metadata),
},
"giphy"
);
const giphyImage = giphyAppData?.thankYouPage;
const isRoundRobin = eventType.schedulingType === SchedulingType.ROUND_ROBIN;
@@ -435,7 +441,9 @@ export default function Success(props: PageProps) {
</div>
)}
<HeadSeo origin={getOrgFullOrigin(orgSlug)} title={title} description={title} />
<BookingPageTagManager eventType={eventType} />
<BookingPageTagManager
eventType={{ ...eventType, metadata: eventTypeMetaDataSchemaWithTypedApps.parse(eventType.metadata) }}
/>
<main className={classNames(shouldAlignCentrally ? "mx-auto" : "", isEmbed ? "" : "max-w-3xl")}>
<div className={classNames("overflow-y-auto", isEmbed ? "" : "z-50 ")}>
<div
@@ -0,0 +1,49 @@
"use client";
import { useState } from "react";
import { TestForm } from "@calcom/app-store/routing-forms/components/SingleForm";
import type { RoutingForm } from "@calcom/app-store/routing-forms/types/types";
import { useLocale } from "@calcom/lib/hooks/useLocale";
import { trpc } from "@calcom/trpc";
import { Label, Select } from "@calcom/ui";
import InsightsLayout from "./layout";
export default function InsightsVirtualQueuesPage() {
const { t } = useLocale();
const { data: routingForms, isLoading: isRoutingFormsLoading } =
trpc.viewer.insights.getUserRelevantTeamRoutingForms.useQuery();
const [selectedForm, setSelectedForm] = useState<RoutingForm | undefined>(
routingForms && routingForms.length > 0 ? routingForms[0] : undefined
);
if (routingForms && !selectedForm && routingForms.length > 0) {
setSelectedForm(routingForms[0]);
}
return (
<InsightsLayout>
<Label>{t("routing_form")}</Label>
<Select
placeholder="Select project"
options={routingForms?.map((form) => ({ label: form.name, value: form.id })) ?? []}
isLoading={isRoutingFormsLoading}
className="w-60"
onChange={(e) => {
if (e && routingForms) {
const form = routingForms.find((form) => form.id === e.value);
setSelectedForm(form);
}
}}
value={selectedForm ? { label: selectedForm.name, value: selectedForm.id } : undefined}
/>
<div className="mt-10">
{selectedForm ? <TestForm form={selectedForm} showAllData={false} /> : <></>}
</div>
</InsightsLayout>
);
return <></>;
}
@@ -3,7 +3,6 @@
import MembersView from "@calcom/features/ee/organizations/pages/members";
import Shell from "@calcom/features/shell/Shell";
import { useLocale } from "@calcom/lib/hooks/useLocale";
import { HeadSeo } from "@calcom/ui";
const MembersPage: React.FC = () => {
const { t } = useLocale();
@@ -14,8 +13,6 @@ const MembersPage: React.FC = () => {
description={t("organization_description")}
withoutSeo
subtitle={t("organization_description")}>
<HeadSeo title={t("organization_members")} description={t("organization_description")} />
<MembersView />
</Shell>
);
@@ -6,7 +6,7 @@ import { BookerSeo } from "@calcom/features/bookings/components/BookerSeo";
import type { getServerSideProps } from "@lib/org/[orgSlug]/instant-meeting/team/[slug]/[type]/getServerSideProps";
import type { inferSSRProps } from "@lib/types/inferSSRProps";
import type { EmbedProps } from "@lib/withEmbedSsr";
import type { EmbedProps } from "app/WithEmbedSSR";
export type PageProps = inferSSRProps<typeof getServerSideProps> & EmbedProps;
+1 -1
View File
@@ -8,7 +8,7 @@ import { BookerSeo } from "@calcom/features/bookings/components/BookerSeo";
import type { getServerSideProps } from "@lib/team/[slug]/[type]/getServerSideProps";
import type { inferSSRProps } from "@lib/types/inferSSRProps";
import type { EmbedProps } from "@lib/withEmbedSsr";
import type { EmbedProps } from "app/WithEmbedSSR";
export type PageProps = inferSSRProps<typeof getServerSideProps> & EmbedProps;
+3
View File
@@ -128,6 +128,9 @@ vi.mock("@calcom/prisma/zod-utils", () => ({
EventTypeMetaDataSchema: {
parse: vi.fn(),
},
eventTypeMetaDataSchemaWithTypedApps: {
parse: vi.fn(),
},
bookingMetadataSchema: {
parse: vi.fn(),
},
@@ -7,7 +7,7 @@ import { getBookerWrapperClasses } from "@calcom/features/bookings/Booker/utils/
import { BookerSeo } from "@calcom/features/bookings/components/BookerSeo";
import type { inferSSRProps } from "@lib/types/inferSSRProps";
import type { EmbedProps } from "@lib/withEmbedSsr";
import type { EmbedProps } from "app/WithEmbedSSR";
import type { getServerSideProps } from "@server/lib/[user]/[type]/getServerSideProps";
@@ -4,7 +4,7 @@ import dayjs from "@calcom/dayjs";
import { useLocale } from "@calcom/lib/hooks/useLocale";
import { detectBrowserTimeFormat } from "@calcom/lib/timeFormat";
import type { inferSSRProps } from "@calcom/types/inferSSRProps";
import { Button, HeadSeo, Icon } from "@calcom/ui";
import { Button, Icon } from "@calcom/ui";
import type { getServerSideProps } from "@lib/video/meeting-ended/[uid]/getServerSideProps";
@@ -14,7 +14,6 @@ export default function MeetingUnavailable(props: PageProps) {
return (
<div>
<HeadSeo title={t("meeting_unavailable")} description={t("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 pb-20 pt-4 text-center sm:block sm:p-0">
@@ -4,7 +4,7 @@ import dayjs from "@calcom/dayjs";
import { useLocale } from "@calcom/lib/hooks/useLocale";
import { detectBrowserTimeFormat } from "@calcom/lib/timeFormat";
import type { inferSSRProps } from "@calcom/types/inferSSRProps";
import { Button, HeadSeo, Icon, EmptyScreen } from "@calcom/ui";
import { Button, Icon, EmptyScreen } from "@calcom/ui";
import type { getServerSideProps } from "@lib/video/meeting-not-started/[uid]/getServerSideProps";
@@ -14,7 +14,6 @@ export default function MeetingNotStarted(props: PageProps) {
const { t } = useLocale();
return (
<>
<HeadSeo title={t("this_meeting_has_not_started_yet")} description={props.booking.title} />
<main className="mx-auto my-24 max-w-3xl">
<EmptyScreen
Icon="clock"
@@ -1,14 +1,13 @@
"use client";
import { useLocale } from "@calcom/lib/hooks/useLocale";
import { Button, EmptyScreen, HeadSeo } from "@calcom/ui";
import { Button, EmptyScreen } from "@calcom/ui";
export default function NoMeetingFound() {
const { t } = useLocale();
return (
<>
<HeadSeo title={t("no_meeting_found")} description={t("no_meeting_found")} />
<main className="mx-auto my-24 max-w-3xl">
<EmptyScreen
Icon="x"
+1 -5
View File
@@ -292,10 +292,6 @@ const nextConfig = {
source: "/forms/:formQuery*",
destination: "/apps/routing-forms/routing-link/:formQuery*",
},
{
source: "/router/:path*",
destination: "/apps/routing-forms/router/:path*",
},
{
source: "/success/:path*",
has: [
@@ -594,7 +590,7 @@ const nextConfig = {
value: orgHostPath,
},
],
destination: "/event-types?openIntercom=true",
destination: "/event-types?openPlain=true",
permanent: true,
},
{
+1 -1
View File
@@ -1,6 +1,6 @@
{
"name": "@calcom/web",
"version": "4.8.6",
"version": "4.8.10",
"private": true,
"scripts": {
"analyze": "ANALYZE=true next build",
@@ -55,8 +55,8 @@ export default (
rowAsideContent: [
{
componentBadge: {
badgeLabel: emailVerified === null ? "Unknown" : emailVerified ? "Yes" : "No",
badgeColor: emailVerified === null ? "YELLOW" : emailVerified ? "GREEN" : "RED",
badgeLabel: emailVerified === null ? "No" : emailVerified ? "Yes" : "No",
badgeColor: emailVerified === null ? "RED" : emailVerified ? "GREEN" : "RED",
},
},
],
@@ -152,8 +152,8 @@ export default (
rowAsideContent: [
{
componentBadge: {
badgeLabel: twoFactorEnabled === null ? "Unknown" : twoFactorEnabled ? "Yes" : "No",
badgeColor: twoFactorEnabled === null ? "YELLOW" : twoFactorEnabled ? "GREEN" : "RED",
badgeLabel: twoFactorEnabled === null ? "No" : twoFactorEnabled ? "Yes" : "No",
badgeColor: twoFactorEnabled === null ? "RED" : twoFactorEnabled ? "GREEN" : "RED",
},
},
],
-27
View File
@@ -1,27 +0,0 @@
import type { GetStaticPaths } from "next";
import PageWrapper from "@components/PageWrapper";
import { validStatuses } from "~/bookings/lib/validStatuses";
import BookingsListingView from "~/bookings/views/bookings-listing-view";
export { getStaticProps } from "~/bookings/views/bookings-listing-view.getStaticProps";
const BookingsListingPage = new Proxy<{
(): JSX.Element;
PageWrapper?: typeof PageWrapper;
}>(BookingsListingView, {});
BookingsListingPage.PageWrapper = PageWrapper;
export const getStaticPaths: GetStaticPaths = () => {
return {
paths: validStatuses.map((status) => ({
params: { status },
locale: "en",
})),
fallback: "blocking",
};
};
export default BookingsListingPage;
+6
View File
@@ -0,0 +1,6 @@
import { getServerSideProps as _getServerSideProps } from "../../server/lib/router/getServerSideProps";
export { default } from "./index";
// We are not using withEmbedSsr here because _getServerSideProps already has the logic to append /embed to the redirect URL and it ends up as /embed/embed
// TODO: We should use withEmbedSsr here and remove the logic from _getServerSideProps
export const getServerSideProps = _getServerSideProps;
@@ -4,7 +4,7 @@ import Head from "next/head";
import type { inferSSRProps } from "@calcom/types/inferSSRProps";
import { getServerSideProps } from "./getServerSideProps";
import { getServerSideProps } from "../../server/lib/router/getServerSideProps";
export default function Router({ form, message }: inferSSRProps<typeof getServerSideProps>) {
return (
+1
View File
@@ -24,6 +24,7 @@ let pages = (exports.pages = glob
"global-error",
"WithAppDirSsr",
"WithEmbedSSR",
"WithEmbedSSR.test",
].some((prefix) => v.startsWith(prefix))
));
@@ -3,7 +3,7 @@ import type Prisma from "@prisma/client";
import prisma from "@calcom/prisma";
import { SchedulingType } from "@calcom/prisma/enums";
import { EventTypeMetaDataSchema } from "@calcom/prisma/zod-utils";
import { eventTypeMetaDataSchemaWithTypedApps } from "@calcom/prisma/zod-utils";
import { test, todo } from "./lib/fixtures";
import type { Fixtures } from "./lib/fixtures";
@@ -51,7 +51,7 @@ test.describe("Stripe integration skip true", () => {
},
});
const metadata = EventTypeMetaDataSchema.parse(eventTypeMetadata?.metadata);
const metadata = eventTypeMetaDataSchemaWithTypedApps.parse(eventTypeMetadata?.metadata);
const stripeAppMetadata = metadata?.apps?.stripe;
@@ -93,7 +93,7 @@ test.describe("Stripe integration skip true", () => {
},
});
const metadata = EventTypeMetaDataSchema.parse(eventTypeMetadata?.metadata);
const metadata = eventTypeMetaDataSchemaWithTypedApps.parse(eventTypeMetadata?.metadata);
const stripeAppMetadata = metadata?.apps?.stripe;
@@ -291,7 +291,7 @@ test.describe("Stripe integration with the new app install flow skip false", ()
});
for (const eventTypeMetadata of eventTypeMetadatas) {
const metadata = EventTypeMetaDataSchema.parse(eventTypeMetadata?.metadata);
const metadata = eventTypeMetaDataSchemaWithTypedApps.parse(eventTypeMetadata?.metadata);
const stripeAppMetadata = metadata?.apps?.stripe;
expect(stripeAppMetadata).toHaveProperty("credentialId");
expect(typeof stripeAppMetadata?.credentialId).toBe("number");
@@ -328,7 +328,7 @@ test.describe("Stripe integration with the new app install flow skip false", ()
},
});
const metadata = EventTypeMetaDataSchema.parse(eventTypeMetadata?.metadata);
const metadata = eventTypeMetaDataSchemaWithTypedApps.parse(eventTypeMetadata?.metadata);
const stripeAppMetadata = metadata?.apps?.stripe;
+14 -3
View File
@@ -542,16 +542,27 @@ export async function expectPageToBeNotFound({ page, url }: { page: Page; url: s
export async function clickUntilDialogVisible(
dialogOpenButton: Locator,
visibleLocatorOnDialog: Locator,
page: Page,
matchUrl: string,
retries = 3,
delay = 500
delay = 2000
) {
for (let i = 0; i < retries; i++) {
await dialogOpenButton.click();
try {
const responsePromise = page.waitForResponse(
(response) => response.url().includes(matchUrl) && response.status() === 200
);
await dialogOpenButton.click();
await responsePromise;
await visibleLocatorOnDialog.waitFor({ state: "visible", timeout: delay });
return;
} catch {
if (i === retries - 1) throw new Error("Dialog did not appear after multiple attempts.");
console.warn(`clickUntilDialogVisible: Attempt ${i + 1} failed to open dialog`);
if (i === retries - 1) {
console.log("clickUntilDialogVisible: Dialog did not appear after multiple attempts.");
return;
}
await new Promise((resolve) => setTimeout(resolve, delay));
}
}
}
+35 -20
View File
@@ -21,8 +21,12 @@ test.describe("Out of office", () => {
await user.apiLogin();
await page.goto("/settings/my-account/out-of-office");
await page.waitForLoadState("domcontentloaded");
const addOOOButton = page.getByTestId("add_entry_ooo");
const dateButton = page.locator('[data-testid="date-range"]');
await clickUntilDialogVisible(addOOOButton, dateButton, page, "outOfOfficeReasonList?batch=1");
await page.getByTestId("add_entry_ooo").click();
await page.getByTestId("reason_select").click();
await page.getByTestId("select-option-4").click();
@@ -68,8 +72,12 @@ test.describe("Out of office", () => {
await user.apiLogin();
await page.goto(`/settings/my-account/out-of-office`);
await page.waitForLoadState("domcontentloaded");
const addOOOButton = page.getByTestId("add_entry_ooo");
const dateButton = page.locator('[data-testid="date-range"]');
await clickUntilDialogVisible(addOOOButton, dateButton, page, "outOfOfficeReasonList?batch=1");
await page.getByTestId("add_entry_ooo").click();
await page.getByTestId("reason_select").click();
await page.getByTestId("select-option-4").click();
@@ -204,10 +212,13 @@ test.describe("Out of office", () => {
await user.apiLogin();
await page.goto("/settings/my-account/out-of-office");
await page.waitForLoadState("domcontentloaded");
await page.getByTestId("add_entry_ooo").click();
const addOOOButton = page.getByTestId("add_entry_ooo");
const dateButton = page.locator('[data-testid="date-range"]');
await clickUntilDialogVisible(addOOOButton, dateButton, page, "outOfOfficeReasonList?batch=1");
await page.locator('[data-testid="date-range"]').click();
await dateButton.click();
await selectToAndFromDates(page, "13", "22", true);
@@ -244,10 +255,13 @@ test.describe("Out of office", () => {
await user.apiLogin();
await page.goto("/settings/my-account/out-of-office");
await page.waitForLoadState("domcontentloaded");
await page.getByTestId("add_entry_ooo").click();
const addOOOButton = page.getByTestId("add_entry_ooo");
const dateButton = page.locator('[data-testid="date-range"]');
await clickUntilDialogVisible(addOOOButton, dateButton, page, "outOfOfficeReasonList?batch=1");
await page.locator('[data-testid="date-range"]').click();
await dateButton.click();
await selectToAndFromDates(page, "13", "22");
@@ -256,9 +270,8 @@ test.describe("Out of office", () => {
await expect(page.locator(`data-testid=table-redirect-n-a`)).toBeVisible();
// add another entry
await page.getByTestId("add_entry_ooo").click();
await page.locator('[data-testid="date-range"]').click();
await clickUntilDialogVisible(addOOOButton, dateButton, page, "outOfOfficeReasonList?batch=1");
await dateButton.click();
await selectToAndFromDates(page, "11", "24");
@@ -274,10 +287,13 @@ test.describe("Out of office", () => {
await user.apiLogin();
await page.goto("/settings/my-account/out-of-office");
await page.waitForLoadState("domcontentloaded");
await page.getByTestId("add_entry_ooo").click();
const addOOOButton = page.getByTestId("add_entry_ooo");
const dateButton = page.locator('[data-testid="date-range"]');
await clickUntilDialogVisible(addOOOButton, dateButton, page, "outOfOfficeReasonList?batch=1");
await page.locator('[data-testid="date-range"]').click();
await dateButton.click();
await selectToAndFromDates(page, "13", "22");
@@ -286,9 +302,8 @@ test.describe("Out of office", () => {
await expect(page.locator(`data-testid=table-redirect-n-a`)).toBeVisible();
// add another entry
await page.getByTestId("add_entry_ooo").click();
await page.locator('[data-testid="date-range"]').click();
await clickUntilDialogVisible(addOOOButton, dateButton, page, "outOfOfficeReasonList?batch=1");
await dateButton.click();
await selectToAndFromDates(page, "13", "22");
@@ -308,13 +323,13 @@ test.describe("Out of office", () => {
//Creates 2 OOO entries:
//First OOO is created on Next month 1st - 3rd
await clickUntilDialogVisible(addOOOButton, dateButton);
await clickUntilDialogVisible(addOOOButton, dateButton, page, "outOfOfficeReasonList?batch=1");
await dateButton.click();
await selectDateAndCreateOOO(page, "1", "3");
await expect(page.locator(`data-testid=table-redirect-n-a`).nth(0)).toBeVisible();
//Second OOO is created on Next month 4th - 6th
await clickUntilDialogVisible(addOOOButton, dateButton);
await clickUntilDialogVisible(addOOOButton, dateButton, page, "outOfOfficeReasonList?batch=1");
await dateButton.click();
await selectDateAndCreateOOO(page, "4", "6");
await expect(page.locator(`data-testid=table-redirect-n-a`).nth(1)).toBeVisible();
@@ -341,7 +356,7 @@ test.describe("Out of office", () => {
const dateButton = await page.locator('[data-testid="date-range"]');
//As owner,OOO is created on Next month 1st - 3rd, forwarding to 'member-1'
await clickUntilDialogVisible(addOOOButton, dateButton);
await clickUntilDialogVisible(addOOOButton, dateButton, page, "outOfOfficeReasonList?batch=1");
await dateButton.click();
await selectDateAndCreateOOO(page, "1", "3", "member-1");
await expect(
@@ -352,7 +367,7 @@ test.describe("Out of office", () => {
await member1User?.apiLogin();
await page.goto("/settings/my-account/out-of-office");
await page.waitForLoadState();
await clickUntilDialogVisible(addOOOButton, dateButton);
await clickUntilDialogVisible(addOOOButton, dateButton, page, "outOfOfficeReasonList?batch=1");
await dateButton.click();
await selectDateAndCreateOOO(page, "4", "5", "owner");
await expect(page.locator(`data-testid=table-redirect-${owner.username ?? "n-a"}`).nth(0)).toBeVisible();
@@ -380,7 +395,7 @@ test.describe("Out of office", () => {
const dateButton = await page.locator('[data-testid="date-range"]');
//As owner,OOO is created on Next month 1st - 3rd, forwarding to 'member-1'
await clickUntilDialogVisible(addOOOButton, dateButton);
await clickUntilDialogVisible(addOOOButton, dateButton, page, "outOfOfficeReasonList?batch=1");
await dateButton.click();
await selectDateAndCreateOOO(page, "1", "3", "member-1");
await expect(
@@ -391,7 +406,7 @@ test.describe("Out of office", () => {
await member1User?.apiLogin();
await page.goto("/settings/my-account/out-of-office");
await page.waitForLoadState();
await clickUntilDialogVisible(addOOOButton, dateButton);
await clickUntilDialogVisible(addOOOButton, dateButton, page, "outOfOfficeReasonList?batch=1");
await dateButton.click();
await selectDateAndCreateOOO(page, "2", "5", "owner", 400);
await expect(page.locator(`text=${t("booking_redirect_infinite_not_allowed")}`)).toBeTruthy();
+10 -2
View File
@@ -2216,8 +2216,8 @@
"org_no_teams_yet": "هذه المنظمة ليس لديها فرق بعد",
"org_no_teams_yet_description": "إذا كنت مسؤول، تأكد من إنشاء فرق لعرضها هنا.",
"set_up": "الإعداد",
"my_profile": "الملف الشخصي",
"my_settings": "الإعدادات",
"my_profile": "ملفي الشخصي",
"my_settings": "إعداداتي",
"crm": "CRM",
"messaging": "المراسلة",
"sender_id_info": "الاسم أو الرقم الظاهر على أنه مرسل الرسالة النصية القصيرة (بعض البلدان لا تسمح بمعرفات المرسل الأبجدية الرقمية)",
@@ -2811,6 +2811,7 @@
"salesforce_owner_name_to_change": "اسم المالك المراد تغييره",
"no_filter_set": "لم يتم تعيين أي تصفية",
"booking_dry_run_successful": "تم إجراء المحاكاة التجريبية للحجز بنجاح",
"booking_dry_run_successful_description": "تم إكمال التشغيل التجريبي بنجاح.",
"dont_have_access_this_page": "ليس لديك صلاحية الوصول إلى هذه الصفحة",
"you_need_admin_or_owner_privileges_to_access": "لرؤية هذه الصفحة أو التفاعل معها، يجب أن تكون لديك صلاحيات المدير أو المالك",
"field_type": "نوع الحقل",
@@ -2891,5 +2892,12 @@
"reassigned": "تمت إعادة التعيين",
"rerouted": "تمت إعادة التوجيه",
"salesforce_assigned": "تعيين Salesforce",
"router_position": "موضع الموجه",
"show_matching_hosts": "عرض المضيفين المتطابقين",
"no_active_queues": "لا توجد قوائم انتظار نشطة لهذا المُدخل",
"weight": "الوزن",
"calibration": "المعايرة",
"shortfall": "العجز",
"routing_form": "نموذج التوجيه",
"ADD_NEW_STRINGS_ABOVE_THIS_LINE_TO_PREVENT_MERGE_CONFLICTS": "↑↑↑↑↑↑↑↑↑↑↑↑↑ أضف السلاسل الجديدة أعلاه هنا ↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑"
}
+10 -2
View File
@@ -2216,8 +2216,8 @@
"org_no_teams_yet": "Bu təşkilatda hələ komanda yoxdur",
"org_no_teams_yet_description": "Əgər siz administratorsunuzsa, burada göstərmək üçün komandalar yaratdığınızdan əmin olun.",
"set_up": "Quraşdır",
"my_profile": "Profilim",
"my_settings": "Ayarlarım",
"my_profile": "Mənim profilim",
"my_settings": "Mənim tənzimləmələrim",
"crm": "CRM",
"messaging": "Mesajlaşma",
"sender_id_info": "SMS göndərən kimi göstərilən ad və ya nömrə (bəzi ölkələrdə alfasayısal göndərən ID-lərinə icazə verilmir)",
@@ -2811,6 +2811,7 @@
"salesforce_owner_name_to_change": "Dəyişdiriləcək sahibin adı",
"no_filter_set": "Filtr təyin edilməyib",
"booking_dry_run_successful": "Rezervasiya test yoxlaması uğurlu oldu",
"booking_dry_run_successful_description": "Test yoxlaması uğurla başa çatdı.",
"dont_have_access_this_page": "Bu səhifəyə girişiniz yoxdur",
"you_need_admin_or_owner_privileges_to_access": "Bu səhifəni görmək və ya onunla qarşılıqlı əlaqədə olmaq üçün admin və ya sahib imtiyazlarına malik olmalısınız",
"field_type": "Sahə növü",
@@ -2891,5 +2892,12 @@
"reassigned": "Yenidən təyin edildi",
"rerouted": "Yenidən yönləndirildi",
"salesforce_assigned": "Salesforce təyinatı",
"router_position": "Router mövqeyi",
"show_matching_hosts": "Uyğun hostları göstər",
"no_active_queues": "Bu giriş üçün aktiv növbə yoxdur",
"weight": "Çəki",
"calibration": "Kalibrasiya",
"shortfall": "Çatışmazlıq",
"routing_form": "Marşrutlama forması",
"ADD_NEW_STRINGS_ABOVE_THIS_LINE_TO_PREVENT_MERGE_CONFLICTS": "↑↑↑↑↑↑↑↑↑↑↑↑↑ Yeni sətirləri bura əlavə edin ↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑"
}
@@ -2811,6 +2811,7 @@
"salesforce_owner_name_to_change": "Име на собственик за промяна",
"no_filter_set": "Няма зададен филтър",
"booking_dry_run_successful": "Тестовото резервиране е успешно",
"booking_dry_run_successful_description": "Тестовото изпълнение завърши успешно.",
"dont_have_access_this_page": "Нямате достъп до тази страница",
"you_need_admin_or_owner_privileges_to_access": "За да видите или взаимодействате с тази страница, трябва да имате администраторски права или да сте собственик",
"field_type": "Тип на полето",
@@ -2891,5 +2892,12 @@
"reassigned": "Преразпределен",
"rerouted": "Пренасочен",
"salesforce_assigned": "Възложен в Salesforce",
"router_position": "Позиция на рутера",
"show_matching_hosts": "Покажи съвпадащите хостове",
"no_active_queues": "Няма активни опашки за този вход",
"weight": "Тегло",
"calibration": "Калибриране",
"shortfall": "Недостиг",
"routing_form": "Форма за маршрутизиране",
"ADD_NEW_STRINGS_ABOVE_THIS_LINE_TO_PREVENT_MERGE_CONFLICTS": "↑↑↑↑↑↑↑↑↑↑↑↑↑ Добавете новите си низове над този ред ↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑"
}
@@ -2811,6 +2811,7 @@
"salesforce_owner_name_to_change": "Nom del propietari a canviar",
"no_filter_set": "Cap filtre establert",
"booking_dry_run_successful": "La prova de reserva s'ha realitzat correctament",
"booking_dry_run_successful_description": "La prova de funcionament s'ha completat correctament.",
"dont_have_access_this_page": "No tens accés a aquesta pàgina",
"you_need_admin_or_owner_privileges_to_access": "Per veure o interactuar amb aquesta pàgina, has de tenir privilegis d'administrador o propietari",
"field_type": "Tipus de camp",
@@ -2891,5 +2892,12 @@
"reassigned": "Reassignat",
"rerouted": "Redirigit de nou",
"salesforce_assigned": "Assignació de Salesforce",
"router_position": "Posició del router",
"show_matching_hosts": "Mostra els hosts coincidents",
"no_active_queues": "No hi ha cues actives per a aquesta entrada",
"weight": "Pes",
"calibration": "Calibratge",
"shortfall": "Dèficit",
"routing_form": "Formulari d'encaminament",
"ADD_NEW_STRINGS_ABOVE_THIS_LINE_TO_PREVENT_MERGE_CONFLICTS": "↑↑↑↑↑↑↑↑↑↑↑↑↑ Afegiu les vostres noves cadenes a dalt ↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑"
}
@@ -2811,6 +2811,7 @@
"salesforce_owner_name_to_change": "Jméno vlastníka ke změně",
"no_filter_set": "Není nastaven žádný filtr",
"booking_dry_run_successful": "Testovací rezervace proběhla úspěšně",
"booking_dry_run_successful_description": "Testovací běh byl úspěšně dokončen.",
"dont_have_access_this_page": "K této stránce nemáte přístup",
"you_need_admin_or_owner_privileges_to_access": "Pro zobrazení nebo interakci s touto stránkou musíte mít administrátorská nebo vlastnická práva",
"field_type": "Typ pole",
@@ -2891,5 +2892,12 @@
"reassigned": "Přeřazeno",
"rerouted": "Přesměrováno jinam",
"salesforce_assigned": "Přiřazení Salesforce",
"router_position": "Pozice routeru",
"show_matching_hosts": "Zobrazit odpovídající hosty",
"no_active_queues": "Pro tento vstup nejsou žádné aktivní fronty",
"weight": "Váha",
"calibration": "Kalibrace",
"shortfall": "Nedostatek",
"routing_form": "Směrovací formulář",
"ADD_NEW_STRINGS_ABOVE_THIS_LINE_TO_PREVENT_MERGE_CONFLICTS": "↑↑↑↑↑↑↑↑↑↑↑↑↑ Přidejte své nové řetězce nahoru ↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑"
}
@@ -2811,6 +2811,7 @@
"salesforce_owner_name_to_change": "Ejernavn der skal ændres",
"no_filter_set": "Intet filter indstillet",
"booking_dry_run_successful": "Booking-testkørsel gennemført",
"booking_dry_run_successful_description": "Testkørslen blev gennemført med succes.",
"dont_have_access_this_page": "Du har ikke adgang til denne side",
"you_need_admin_or_owner_privileges_to_access": "For at kunne se eller interagere med denne side, skal du have administrator- eller ejerrettigheder",
"field_type": "Felttype",
@@ -2891,5 +2892,12 @@
"reassigned": "Omfordelt",
"rerouted": "Omdirigeret",
"salesforce_assigned": "Salesforce-tildeling",
"router_position": "Router-position",
"show_matching_hosts": "Vis matchende værter",
"no_active_queues": "Ingen aktive køer for dette input",
"weight": "Vægt",
"calibration": "Kalibrering",
"shortfall": "Underskud",
"routing_form": "Routing-formular",
"ADD_NEW_STRINGS_ABOVE_THIS_LINE_TO_PREVENT_MERGE_CONFLICTS": "↑↑↑↑↑↑↑↑↑↑↑↑↑ Tilføj dine nye strenge ovenfor her ↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑"
}
@@ -2811,6 +2811,7 @@
"salesforce_owner_name_to_change": "Zu ändernder Besitzername",
"no_filter_set": "Kein Filter gesetzt",
"booking_dry_run_successful": "Buchungs-Testlauf erfolgreich",
"booking_dry_run_successful_description": "Der Testlauf wurde erfolgreich abgeschlossen.",
"dont_have_access_this_page": "Sie haben keinen Zugriff auf diese Seite",
"you_need_admin_or_owner_privileges_to_access": "Um diese Seite anzuzeigen oder mit ihr zu interagieren, benötigen Sie Administrator- oder Eigentümerrechte",
"field_type": "Feldtyp",
@@ -2891,5 +2892,12 @@
"reassigned": "Neu zugewiesen",
"rerouted": "Umgeleitet",
"salesforce_assigned": "Salesforce-Zuweisung",
"router_position": "Router-Position",
"show_matching_hosts": "Passende Hosts anzeigen",
"no_active_queues": "Keine aktiven Warteschlangen für diese Eingabe",
"weight": "Gewichtung",
"calibration": "Kalibrierung",
"shortfall": "Fehlmenge",
"routing_form": "Routing-Formular",
"ADD_NEW_STRINGS_ABOVE_THIS_LINE_TO_PREVENT_MERGE_CONFLICTS": "↑↑↑↑↑↑↑↑↑↑↑↑↑ Fügen Sie Ihre neuen Code-Zeilen über dieser hinzu ↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑"
}
@@ -2811,6 +2811,7 @@
"salesforce_owner_name_to_change": "Όνομα κατόχου προς αλλαγή",
"no_filter_set": "Δεν έχει οριστεί φίλτρο",
"booking_dry_run_successful": "Η δοκιμαστική κράτηση ολοκληρώθηκε με επιτυχία",
"booking_dry_run_successful_description": "Η δοκιμαστική εκτέλεση ολοκληρώθηκε με επιτυχία.",
"dont_have_access_this_page": "Δεν έχετε πρόσβαση σε αυτή τη σελίδα",
"you_need_admin_or_owner_privileges_to_access": "Για να δείτε ή να αλληλεπιδράσετε με αυτή τη σελίδα, πρέπει να έχετε δικαιώματα διαχειριστή ή ιδιοκτήτη",
"field_type": "Τύπος πεδίου",
@@ -2891,5 +2892,12 @@
"reassigned": "Επανεκχωρήθηκε",
"rerouted": "Ανακατευθύνθηκε",
"salesforce_assigned": "Ανάθεση Salesforce",
"router_position": "Θέση δρομολογητή",
"show_matching_hosts": "Εμφάνιση αντίστοιχων hosts",
"no_active_queues": "Δεν υπάρχουν ενεργές ουρές για αυτήν την είσοδο",
"weight": "Βάρος",
"calibration": "Βαθμονόμηση",
"shortfall": "Έλλειμμα",
"routing_form": "Φόρμα δρομολόγησης",
"ADD_NEW_STRINGS_ABOVE_THIS_LINE_TO_PREVENT_MERGE_CONFLICTS": "↑↑↑↑↑↑↑↑↑↑↑↑↑ Προσθέστε τις νέες συμβολοσειρές σας πάνω από εδώ ↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑"
}
+10 -2
View File
@@ -2216,8 +2216,8 @@
"org_no_teams_yet": "This organization has no teams yet",
"org_no_teams_yet_description": "If you are an administrator, be sure to create teams to be shown here.",
"set_up": "Set up",
"my_profile": "My Profile",
"my_settings": "My Settings",
"my_profile": "My profile",
"my_settings": "My settings",
"crm": "CRM",
"messaging": "Messaging",
"sender_id_info": "Name or number shown as the sender of an SMS (some countries do not allow alphanumeric sender IDs)",
@@ -2815,6 +2815,7 @@
"salesforce_owner_name_to_change": "Owner name to change",
"no_filter_set": "No filter set",
"booking_dry_run_successful": "Booking dry run successful",
"booking_dry_run_successful_description": "The dry run was completed successfully.",
"dont_have_access_this_page": "You don't have access to this page",
"you_need_admin_or_owner_privileges_to_access": "In order to view or interact with this page, you must have admin or owner privileges",
"field_type": "Field type",
@@ -2895,5 +2896,12 @@
"reassigned": "Reassigned",
"rerouted": "Rerouted",
"salesforce_assigned": "Salesforce assignment",
"router_position": "Router Position",
"show_matching_hosts": "Show matching hosts",
"no_active_queues": "No active queues for this input",
"weight": "Weight",
"calibration": "Calibration",
"shortfall": "Shortfall",
"routing_form": "Routing Form",
"ADD_NEW_STRINGS_ABOVE_THIS_LINE_TO_PREVENT_MERGE_CONFLICTS": "↑↑↑↑↑↑↑↑↑↑↑↑↑ Add your new strings above here ↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑"
}
@@ -2216,8 +2216,8 @@
"org_no_teams_yet": "Esta organización aún no tiene equipos",
"org_no_teams_yet_description": "Si eres administrador, asegúrate de crear equipos para que se muestren aquí.",
"set_up": "Configurar",
"my_profile": "Mi Perfil",
"my_settings": "Mis Configuraciones",
"my_profile": "Mi perfil",
"my_settings": "Mi configuración",
"crm": "CRM",
"messaging": "Mensajería",
"sender_id_info": "Nombre o número mostrado como el remitente de un SMS (algunos países no permiten IDs de remitente alfanuméricos)",
@@ -2811,6 +2811,7 @@
"salesforce_owner_name_to_change": "Nombre del propietario a cambiar",
"no_filter_set": "Sin filtros establecidos",
"booking_dry_run_successful": "Prueba de reserva exitosa",
"booking_dry_run_successful_description": "La prueba de simulación se completó exitosamente.",
"dont_have_access_this_page": "No tienes acceso a esta página",
"you_need_admin_or_owner_privileges_to_access": "Para ver o interactuar con esta página, debes tener privilegios de administrador o propietario",
"field_type": "Tipo de campo",
@@ -2891,5 +2892,12 @@
"reassigned": "Reasignado",
"rerouted": "Reenrutado",
"salesforce_assigned": "Asignación de Salesforce",
"router_position": "Posición del enrutador",
"show_matching_hosts": "Mostrar hosts coincidentes",
"no_active_queues": "No hay colas activas para esta entrada",
"weight": "Peso",
"calibration": "Calibración",
"shortfall": "Déficit",
"routing_form": "Formulario de enrutamiento",
"ADD_NEW_STRINGS_ABOVE_THIS_LINE_TO_PREVENT_MERGE_CONFLICTS": "↑↑↑↑↑↑↑↑↑↑↑↑↑ Agrega tus nuevas cadenas arriba de esta línea ↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑"
}
@@ -2811,6 +2811,7 @@
"salesforce_owner_name_to_change": "Nombre del propietario a cambiar",
"no_filter_set": "Ningún filtro establecido",
"booking_dry_run_successful": "Prueba de reserva exitosa",
"booking_dry_run_successful_description": "La prueba de simulación se completó con éxito.",
"dont_have_access_this_page": "No tienes acceso a esta página",
"you_need_admin_or_owner_privileges_to_access": "Para ver o interactuar con esta página, debes tener privilegios de administrador o propietario",
"field_type": "Tipo de campo",
@@ -2891,5 +2892,12 @@
"reassigned": "Reasignado",
"rerouted": "Reenrutado",
"salesforce_assigned": "Asignación de Salesforce",
"router_position": "Posición del enrutador",
"show_matching_hosts": "Mostrar hosts coincidentes",
"no_active_queues": "No hay colas activas para esta entrada",
"weight": "Peso",
"calibration": "Calibración",
"shortfall": "Déficit",
"routing_form": "Formulario de enrutamiento",
"ADD_NEW_STRINGS_ABOVE_THIS_LINE_TO_PREVENT_MERGE_CONFLICTS": "↑↑↑↑↑↑↑↑↑↑↑↑↑ Agregue sus nuevas cadenas arriba ↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑"
}
@@ -2811,6 +2811,7 @@
"salesforce_owner_name_to_change": "Muudetava omaniku nimi",
"no_filter_set": "Filter määramata",
"booking_dry_run_successful": "Broneeringu proovikäivitus õnnestus",
"booking_dry_run_successful_description": "Proovijooks lõppes edukalt.",
"dont_have_access_this_page": "Teil ei ole sellele lehele juurdepääsu",
"you_need_admin_or_owner_privileges_to_access": "Selle lehe vaatamiseks või sellega suhtlemiseks peate omama administraatori või omaniku õigusi",
"field_type": "Välja tüüp",
@@ -2891,5 +2892,12 @@
"reassigned": "Ümber määratud",
"rerouted": "Ümber suunatud",
"salesforce_assigned": "Salesforce'i määramine",
"router_position": "Ruuteri positsioon",
"show_matching_hosts": "Näita sobivaid hoste",
"no_active_queues": "Sellel sisendit jaoks pole aktiivseid järjekordi",
"weight": "Kaal",
"calibration": "Kalibreerimine",
"shortfall": "Puudujääk",
"routing_form": "Marsruutimise vorm",
"ADD_NEW_STRINGS_ABOVE_THIS_LINE_TO_PREVENT_MERGE_CONFLICTS": "↑↑↑↑↑↑↑↑↑↑↑↑↑ Add your new strings above here ↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑"
}
@@ -2811,6 +2811,7 @@
"salesforce_owner_name_to_change": "Aldatu beharreko jabearen izena",
"no_filter_set": "Ez dago iragazkirik ezarrita",
"booking_dry_run_successful": "Erreserben proba arrakastatsua",
"booking_dry_run_successful_description": "Proba exekuzioa arrakastaz burutu da.",
"dont_have_access_this_page": "Ez duzu orri honetarako sarbiderik",
"you_need_admin_or_owner_privileges_to_access": "Orri hau ikusteko edo erabiltzeko, administratzaile edo jabe baimenak izan behar dituzu",
"field_type": "Eremu mota",
@@ -2891,5 +2892,12 @@
"reassigned": "Berresleituta",
"rerouted": "Berbideratuta",
"salesforce_assigned": "Salesforce esleipena",
"router_position": "Bideratzailearen posizioa",
"show_matching_hosts": "Erakutsi bat datozen ostalariak",
"no_active_queues": "Sarrera honek ez du ilara aktiborik",
"weight": "Pisua",
"calibration": "Kalibrazioa",
"shortfall": "Gabezia",
"routing_form": "Bideratze-formularioa",
"ADD_NEW_STRINGS_ABOVE_THIS_LINE_TO_PREVENT_MERGE_CONFLICTS": "↑↑↑↑↑↑↑↑↑↑↑↑↑ Gehitu zure kate berriak honen gainean ↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑"
}
+10 -2
View File
@@ -2216,8 +2216,8 @@
"org_no_teams_yet": "Tällä organisaatiolla ei ole vielä tiimejä",
"org_no_teams_yet_description": "Jos olet järjestelmänvalvoja, muista luoda tiimejä, jotta ne näkyvät täällä.",
"set_up": "Määritä",
"my_profile": "Oma profiili",
"my_settings": "Omat asetukset",
"my_profile": "Profiilini",
"my_settings": "Asetukseni",
"crm": "CRM",
"messaging": "Viestintä",
"sender_id_info": "Nimi tai numero, joka näkyy tekstiviestin lähettäjänä (kaikki maat eivät salli aakkosnumeerisia lähettäjätunnisteita)",
@@ -2811,6 +2811,7 @@
"salesforce_owner_name_to_change": "Muutettavan omistajan nimi",
"no_filter_set": "Ei suodatinta määritetty",
"booking_dry_run_successful": "Varauksen testaus onnistui",
"booking_dry_run_successful_description": "Testausajo suoritettiin onnistuneesti.",
"dont_have_access_this_page": "Sinulla ei ole pääsyä tälle sivulle",
"you_need_admin_or_owner_privileges_to_access": "Nähdäksesi tai käyttääksesi tätä sivua sinulla täytyy olla ylläpitäjän tai omistajan oikeudet",
"field_type": "Kenttätyyppi",
@@ -2891,5 +2892,12 @@
"reassigned": "Uudelleenmääritetty",
"rerouted": "Uudelleenreititetty",
"salesforce_assigned": "Salesforce-määritys",
"router_position": "Reitittimen sijainti",
"show_matching_hosts": "Näytä täsmäävät isännät",
"no_active_queues": "Ei aktiivisia jonoja tälle syötteelle",
"weight": "Paino",
"calibration": "Kalibrointi",
"shortfall": "Vajaus",
"routing_form": "Reitityskaavake",
"ADD_NEW_STRINGS_ABOVE_THIS_LINE_TO_PREVENT_MERGE_CONFLICTS": "↑↑↑↑↑↑↑↑↑↑↑↑↑ Lisää uudet merkkijonot tämän yläpuolelle ↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑"
}
@@ -2811,6 +2811,7 @@
"salesforce_owner_name_to_change": "Nom du propriétaire à modifier",
"no_filter_set": "Aucun filtre défini",
"booking_dry_run_successful": "Test de réservation réussi",
"booking_dry_run_successful_description": "Le test à blanc a été effectué avec succès.",
"dont_have_access_this_page": "Vous n'avez pas accès à cette page",
"you_need_admin_or_owner_privileges_to_access": "Pour voir ou interagir avec cette page, vous devez avoir des privilèges d'administrateur ou de propriétaire",
"field_type": "Type de champ",
@@ -2891,5 +2892,12 @@
"reassigned": "Réaffecté",
"rerouted": "Réacheminé",
"salesforce_assigned": "Affectation Salesforce",
"router_position": "Position du routeur",
"show_matching_hosts": "Afficher les hôtes correspondants",
"no_active_queues": "Aucune file d'attente active pour cette entrée",
"weight": "Poids",
"calibration": "Calibrage",
"shortfall": "Déficit",
"routing_form": "Formulaire de routage",
"ADD_NEW_STRINGS_ABOVE_THIS_LINE_TO_PREVENT_MERGE_CONFLICTS": "↑↑↑↑↑↑↑↑↑↑↑↑↑ Ajoutez vos nouvelles chaînes ci-dessus ↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑"
}
@@ -2811,6 +2811,7 @@
"salesforce_owner_name_to_change": "שם הבעלים לשינוי",
"no_filter_set": "לא הוגדר סינון",
"booking_dry_run_successful": "הרצת הניסיון של ההזמנה הצליחה",
"booking_dry_run_successful_description": "הריצה הניסיונית הושלמה בהצלחה.",
"dont_have_access_this_page": "אין לך גישה לדף זה",
"you_need_admin_or_owner_privileges_to_access": "כדי לצפות או לפעול בדף זה, עליך להיות בעל הרשאות מנהל או בעלים",
"field_type": "סוג שדה",
@@ -2891,5 +2892,12 @@
"reassigned": "הוקצה מחדש",
"rerouted": "נותב מחדש",
"salesforce_assigned": "הקצאת Salesforce",
"router_position": "מיקום נתב",
"show_matching_hosts": "הצג מארחים תואמים",
"no_active_queues": "אין תורים פעילים עבור קלט זה",
"weight": "משקל",
"calibration": "כיול",
"shortfall": "חוסר",
"routing_form": "טופס ניתוב",
"ADD_NEW_STRINGS_ABOVE_THIS_LINE_TO_PREVENT_MERGE_CONFLICTS": "↑↑↑↑↑↑↑↑↑↑↑↑↑ Add your new strings above here ↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑"
}
@@ -2216,7 +2216,7 @@
"org_no_teams_yet": "Ennek a szervezetnek még nincsenek csapatai",
"org_no_teams_yet_description": "Ha Ön rendszergazda, feltétlenül hozzon létre csapatokat, amelyek itt megjelennek.",
"set_up": "Beállít",
"my_profile": "A profilom",
"my_profile": "Profilom",
"my_settings": "Beállításaim",
"crm": "CRM",
"messaging": "Üzenetküldés",
@@ -2811,6 +2811,7 @@
"salesforce_owner_name_to_change": "Módosítandó tulajdonos neve",
"no_filter_set": "Nincs beállított szűrő",
"booking_dry_run_successful": "A foglalás tesztfuttatása sikeres",
"booking_dry_run_successful_description": "A teszt futtatás sikeresen befejeződött.",
"dont_have_access_this_page": "Nincs hozzáférése ehhez az oldalhoz",
"you_need_admin_or_owner_privileges_to_access": "Az oldal megtekintéséhez vagy használatához adminisztrátori vagy tulajdonosi jogosultság szükséges",
"field_type": "Mező típusa",
@@ -2891,5 +2892,12 @@
"reassigned": "Újra kiosztva",
"rerouted": "Átirányítva",
"salesforce_assigned": "Salesforce hozzárendelés",
"router_position": "Router pozíció",
"show_matching_hosts": "Egyező hosztok megjelenítése",
"no_active_queues": "Nincsenek aktív sorok ehhez a bemenethez",
"weight": "Súly",
"calibration": "Kalibráció",
"shortfall": "Hiány",
"routing_form": "Útválasztási űrlap",
"ADD_NEW_STRINGS_ABOVE_THIS_LINE_TO_PREVENT_MERGE_CONFLICTS": "↑↑↑↑↑↑↑↑↑↑↑↑↑ Adja hozzá az új karakterláncokat fent ↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑"
}
@@ -2811,6 +2811,7 @@
"salesforce_owner_name_to_change": "Nome del proprietario da modificare",
"no_filter_set": "Nessun filtro impostato",
"booking_dry_run_successful": "Test di prenotazione completato con successo",
"booking_dry_run_successful_description": "Il test di prova è stato completato con successo.",
"dont_have_access_this_page": "Non hai accesso a questa pagina",
"you_need_admin_or_owner_privileges_to_access": "Per visualizzare o interagire con questa pagina, devi avere privilegi di amministratore o proprietario",
"field_type": "Tipo di campo",
@@ -2891,5 +2892,12 @@
"reassigned": "Riassegnato",
"rerouted": "Reindirizzato",
"salesforce_assigned": "Assegnazione Salesforce",
"router_position": "Posizione del router",
"show_matching_hosts": "Mostra host corrispondenti",
"no_active_queues": "Nessuna coda attiva per questo input",
"weight": "Peso",
"calibration": "Calibrazione",
"shortfall": "Deficit",
"routing_form": "Modulo di instradamento",
"ADD_NEW_STRINGS_ABOVE_THIS_LINE_TO_PREVENT_MERGE_CONFLICTS": "↑↑↑↑↑↑↑↑↑↑↑↑↑ Aggiungi le tue nuove stringhe qui sopra ↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑"
}
@@ -2216,7 +2216,7 @@
"org_no_teams_yet": "この組織にはまだチームがありません",
"org_no_teams_yet_description": "管理者である場合には、ここに表示されるチームを必ず作成してください。",
"set_up": "設定",
"my_profile": "プロフィール",
"my_profile": "マイプロフィール",
"my_settings": "設定",
"crm": "CRM",
"messaging": "メッセージング",
@@ -2811,6 +2811,7 @@
"salesforce_owner_name_to_change": "変更する所有者名",
"no_filter_set": "フィルター未設定",
"booking_dry_run_successful": "予約のテストが正常に完了しました",
"booking_dry_run_successful_description": "テスト実行は正常に完了しました。",
"dont_have_access_this_page": "このページにアクセスする権限がありません",
"you_need_admin_or_owner_privileges_to_access": "このページを表示または操作するには、管理者または所有者の権限が必要です",
"field_type": "フィールドタイプ",
@@ -2891,5 +2892,12 @@
"reassigned": "再割り当て済み",
"rerouted": "再ルーティング済み",
"salesforce_assigned": "Salesforce割り当て",
"router_position": "ルーターの位置",
"show_matching_hosts": "一致するホストを表示",
"no_active_queues": "この入力に対してアクティブなキューはありません",
"weight": "重み",
"calibration": "キャリブレーション",
"shortfall": "不足",
"routing_form": "ルーティングフォーム",
"ADD_NEW_STRINGS_ABOVE_THIS_LINE_TO_PREVENT_MERGE_CONFLICTS": "↑↑↑↑↑↑↑↑↑↑↑↑↑ この上に新しい文字列を追加してください ↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑"
}
@@ -2811,6 +2811,7 @@
"salesforce_owner_name_to_change": "ឈ្មោះម្ចាស់ដែលត្រូវផ្លាស់ប្តូរ",
"no_filter_set": "មិនមានការកំណត់តម្រង",
"booking_dry_run_successful": "ការសាកល្បងការកក់បានជោគជ័យ",
"booking_dry_run_successful_description": "ការសាកល្បងត្រូវបានបញ្ចប់ដោយជោគជ័យ។",
"dont_have_access_this_page": "អ្នកមិនមានសិទ្ធិចូលប្រើទំព័រនេះទេ",
"you_need_admin_or_owner_privileges_to_access": "ដើម្បីមើល ឬប្រើប្រាស់ទំព័រនេះ អ្នកត្រូវតែមានសិទ្ធិជាអ្នកគ្រប់គ្រង ឬម្ចាស់",
"field_type": "ប្រភេទវាល",
@@ -2891,5 +2892,12 @@
"reassigned": "បានចាត់តាំងឡើងវិញ",
"rerouted": "បញ្ជូនបន្តឡើងវិញ",
"salesforce_assigned": "ការចាត់តាំង Salesforce",
"router_position": "ទីតាំងរ៉ោតទ័រ",
"show_matching_hosts": "បង្ហាញម៉ាស៊ីនដែលត្រូវគ្នា",
"no_active_queues": "គ្មានជួររង់ចាំសកម្មសម្រាប់ការបញ្ចូលនេះទេ",
"weight": "ទម្ងន់",
"calibration": "ការកែតម្រូវ",
"shortfall": "កង្វះខាត",
"routing_form": "ទម្រង់ការបញ្ជូន",
"ADD_NEW_STRINGS_ABOVE_THIS_LINE_TO_PREVENT_MERGE_CONFLICTS": "↑↑↑↑↑↑↑↑↑↑↑↑↑ បន្ថែមខ្សែអក្សរថ្មីរបស់អ្នកនៅខាងលើនេះ ↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑"
}
@@ -2811,6 +2811,7 @@
"salesforce_owner_name_to_change": "변경할 소유자 이름",
"no_filter_set": "설정된 필터 없음",
"booking_dry_run_successful": "예약 테스트 실행 성공",
"booking_dry_run_successful_description": "테스트 실행이 성공적으로 완료되었습니다.",
"dont_have_access_this_page": "이 페이지에 접근할 수 없습니다",
"you_need_admin_or_owner_privileges_to_access": "이 페이지를 보거나 상호작용하려면 관리자 또는 소유자 권한이 필요합니다",
"field_type": "필드 유형",
@@ -2891,5 +2892,12 @@
"reassigned": "재할당됨",
"rerouted": "재라우팅됨",
"salesforce_assigned": "Salesforce 할당",
"router_position": "라우터 위치",
"show_matching_hosts": "일치하는 호스트 표시",
"no_active_queues": "이 입력에 대한 활성 대기열 없음",
"weight": "가중치",
"calibration": "보정",
"shortfall": "부족",
"routing_form": "라우팅 양식",
"ADD_NEW_STRINGS_ABOVE_THIS_LINE_TO_PREVENT_MERGE_CONFLICTS": "↑↑↑↑↑↑↑↑↑↑↑↑↑ 여기에 새 문자열을 추가하세요 ↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑"
}
@@ -2811,6 +2811,7 @@
"salesforce_owner_name_to_change": "Eigenaarsnaam om te wijzigen",
"no_filter_set": "Geen filter ingesteld",
"booking_dry_run_successful": "Boeking test succesvol",
"booking_dry_run_successful_description": "De testrun is succesvol afgerond.",
"dont_have_access_this_page": "Je hebt geen toegang tot deze pagina",
"you_need_admin_or_owner_privileges_to_access": "Om deze pagina te bekijken of ermee te communiceren, moet je over admin- of eigenaarsrechten beschikken",
"field_type": "Veldtype",
@@ -2891,5 +2892,12 @@
"reassigned": "Opnieuw toegewezen",
"rerouted": "Omgeleid",
"salesforce_assigned": "Salesforce toewijzing",
"router_position": "Router positie",
"show_matching_hosts": "Toon overeenkomende hosts",
"no_active_queues": "Geen actieve wachtrijen voor deze invoer",
"weight": "Gewicht",
"calibration": "Kalibratie",
"shortfall": "Tekort",
"routing_form": "Routeringsformulier",
"ADD_NEW_STRINGS_ABOVE_THIS_LINE_TO_PREVENT_MERGE_CONFLICTS": "↑↑↑↑↑↑↑↑↑↑↑↑↑ Voeg uw nieuwe strings hierboven toe ↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑"
}
@@ -2811,6 +2811,7 @@
"salesforce_owner_name_to_change": "Eiernavn som skal endres",
"no_filter_set": "Ingen filter er satt",
"booking_dry_run_successful": "Test av booking var vellykket",
"booking_dry_run_successful_description": "Testkjøringen ble fullført vellykket.",
"dont_have_access_this_page": "Du har ikke tilgang til denne siden",
"you_need_admin_or_owner_privileges_to_access": "For å se eller bruke denne siden, må du ha administrator- eller eierrettigheter",
"field_type": "Felttype",
@@ -2891,5 +2892,12 @@
"reassigned": "Omfordelt",
"rerouted": "Omdirigert",
"salesforce_assigned": "Salesforce-tildeling",
"router_position": "Ruterposisjon",
"show_matching_hosts": "Vis samsvarende verter",
"no_active_queues": "Ingen aktive køer for dette inndata",
"weight": "Vekt",
"calibration": "Kalibrering",
"shortfall": "Underskudd",
"routing_form": "Rutingskjema",
"ADD_NEW_STRINGS_ABOVE_THIS_LINE_TO_PREVENT_MERGE_CONFLICTS": "↑↑↑↑↑↑↑↑↑↑↑↑↑ Legg til dine nye strenger over her ↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑"
}
@@ -2811,6 +2811,7 @@
"salesforce_owner_name_to_change": "Nazwa właściciela do zmiany",
"no_filter_set": "Brak ustawionego filtru",
"booking_dry_run_successful": "Test rezerwacji zakończony pomyślnie",
"booking_dry_run_successful_description": "Test został zakończony pomyślnie.",
"dont_have_access_this_page": "Nie masz dostępu do tej strony",
"you_need_admin_or_owner_privileges_to_access": "Aby wyświetlić lub korzystać z tej strony, musisz mieć uprawnienia administratora lub właściciela",
"field_type": "Typ pola",
@@ -2891,5 +2892,12 @@
"reassigned": "Przypisano ponownie",
"rerouted": "Przekierowano ponownie",
"salesforce_assigned": "Przypisanie Salesforce",
"router_position": "Pozycja routera",
"show_matching_hosts": "Pokaż pasujące hosty",
"no_active_queues": "Brak aktywnych kolejek dla tego wejścia",
"weight": "Waga",
"calibration": "Kalibracja",
"shortfall": "Niedobór",
"routing_form": "Formularz routingu",
"ADD_NEW_STRINGS_ABOVE_THIS_LINE_TO_PREVENT_MERGE_CONFLICTS": "↑↑↑↑↑↑↑↑↑↑↑↑↑ Dodaj nowe ciągi powyżej ↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑"
}
@@ -577,7 +577,7 @@
"forgotten_secret_description": "Caso tenha perdido ou esquecido o segredo, você poderá alterá-lo. Mas fique ciente de que todas as integrações com esse segredo precisarão ser atualizadas",
"current_incorrect_password": "Senha atual está incorreta",
"password_hint_caplow": "Mistura de letras maiúsculas e minúsculas",
"password_hint_min": "Mínimo de oito caracteres",
"password_hint_min": "Mínimo de sete caracteres",
"password_hint_admin_min": "No mínimo 15 caracteres",
"password_hint_num": "Contém pelo menos um número",
"max_limit_allowed_hint": "O tamanho deve ser de {{limit}} ou menos caracteres",
@@ -2811,6 +2811,7 @@
"salesforce_owner_name_to_change": "Nome do proprietário a ser alterado",
"no_filter_set": "Nenhum filtro definido",
"booking_dry_run_successful": "Simulação de agendamento bem-sucedida",
"booking_dry_run_successful_description": "O teste simulado foi concluído com sucesso.",
"dont_have_access_this_page": "Você não tem acesso a esta página",
"you_need_admin_or_owner_privileges_to_access": "Para visualizar ou interagir com esta página, você deve ter privilégios de administrador ou proprietário",
"field_type": "Tipo de campo",
@@ -2891,5 +2892,12 @@
"reassigned": "Reatribuído",
"rerouted": "Reencaminhado",
"salesforce_assigned": "Atribuição do Salesforce",
"router_position": "Posição do roteador",
"show_matching_hosts": "Mostrar hosts correspondentes",
"no_active_queues": "Não há filas ativas para esta entrada",
"weight": "Peso",
"calibration": "Calibração",
"shortfall": "Déficit",
"routing_form": "Formulário de roteamento",
"ADD_NEW_STRINGS_ABOVE_THIS_LINE_TO_PREVENT_MERGE_CONFLICTS": "↑↑↑↑↑↑↑↑↑↑↑↑↑ Adicione suas novas strings aqui em cima ↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑"
}
+10 -2
View File
@@ -2216,8 +2216,8 @@
"org_no_teams_yet": "Esta organização ainda não tem equipas",
"org_no_teams_yet_description": "Se é um administrador, deve criar equipas para serem apresentadas aqui.",
"set_up": "Configurar",
"my_profile": "O meu perfil",
"my_settings": "As minhas definições",
"my_profile": "Meu perfil",
"my_settings": "Minhas configurações",
"crm": "CRM",
"messaging": "Mensagens",
"sender_id_info": "Nome ou número mostrado como remetente de um SMS (alguns países não permitem identificadores alfanuméricos de remetentes)",
@@ -2811,6 +2811,7 @@
"salesforce_owner_name_to_change": "Nome do proprietário a alterar",
"no_filter_set": "Nenhum filtro definido",
"booking_dry_run_successful": "Teste de agendamento realizado com sucesso",
"booking_dry_run_successful_description": "O teste simulado foi concluído com sucesso.",
"dont_have_access_this_page": "Você não tem acesso a esta página",
"you_need_admin_or_owner_privileges_to_access": "Para visualizar ou interagir com esta página, você deve ter privilégios de administrador ou proprietário",
"field_type": "Tipo de campo",
@@ -2891,5 +2892,12 @@
"reassigned": "Reatribuído",
"rerouted": "Reencaminhado",
"salesforce_assigned": "Atribuição do Salesforce",
"router_position": "Posição do roteador",
"show_matching_hosts": "Mostrar hosts correspondentes",
"no_active_queues": "Não há filas ativas para esta entrada",
"weight": "Peso",
"calibration": "Calibração",
"shortfall": "Déficit",
"routing_form": "Formulário de roteamento",
"ADD_NEW_STRINGS_ABOVE_THIS_LINE_TO_PREVENT_MERGE_CONFLICTS": "↑↑↑↑↑↑↑↑↑↑↑↑↑ Add your new strings above here ↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑"
}
@@ -2811,6 +2811,7 @@
"salesforce_owner_name_to_change": "Numele proprietarului de schimbat",
"no_filter_set": "Niciun filtru setat",
"booking_dry_run_successful": "Simulare programare reușită",
"booking_dry_run_successful_description": "Testul de simulare a fost finalizat cu succes.",
"dont_have_access_this_page": "Nu aveți acces la această pagină",
"you_need_admin_or_owner_privileges_to_access": "Pentru a vizualiza sau interacționa cu această pagină, trebuie să aveți privilegii de administrator sau proprietar",
"field_type": "Tip câmp",
@@ -2891,5 +2892,12 @@
"reassigned": "Realocat",
"rerouted": "Redirecționat",
"salesforce_assigned": "Atribuire Salesforce",
"router_position": "Poziția routerului",
"show_matching_hosts": "Afișează gazdele care se potrivesc",
"no_active_queues": "Nu există cozi active pentru această intrare",
"weight": "Greutate",
"calibration": "Calibrare",
"shortfall": "Deficit",
"routing_form": "Formular de rutare",
"ADD_NEW_STRINGS_ABOVE_THIS_LINE_TO_PREVENT_MERGE_CONFLICTS": "↑↑↑↑↑↑↑↑↑↑↑↑↑ Adăugați stringurile noi deasupra acestui rând ↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑"
}
@@ -2811,6 +2811,7 @@
"salesforce_owner_name_to_change": "Имя владельца для изменения",
"no_filter_set": "Фильтр не установлен",
"booking_dry_run_successful": "Тестовое бронирование выполнено успешно",
"booking_dry_run_successful_description": "Пробный запуск успешно завершен.",
"dont_have_access_this_page": "У вас нет доступа к этой странице",
"you_need_admin_or_owner_privileges_to_access": "Для просмотра или взаимодействия с этой страницей необходимы права администратора или владельца",
"field_type": "Тип поля",
@@ -2891,5 +2892,12 @@
"reassigned": "Переназначено",
"rerouted": "Перенаправлено",
"salesforce_assigned": "Назначение в Salesforce",
"router_position": "Позиция роутера",
"show_matching_hosts": "Показать подходящие хосты",
"no_active_queues": "Нет активных очередей для этого входа",
"weight": "Вес",
"calibration": "Калибровка",
"shortfall": "Дефицит",
"routing_form": "Форма маршрутизации",
"ADD_NEW_STRINGS_ABOVE_THIS_LINE_TO_PREVENT_MERGE_CONFLICTS": "↑↑↑↑↑↑↑↑↑↑↑↑↑ Добавьте строки выше ↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑"
}
@@ -2811,6 +2811,7 @@
"salesforce_owner_name_to_change": "Meno vlastníka na zmenu",
"no_filter_set": "Nie je nastavený žiadny filter",
"booking_dry_run_successful": "Testovacia rezervácia prebehla úspešne",
"booking_dry_run_successful_description": "Testovací beh bol úspešne dokončený.",
"dont_have_access_this_page": "Nemáte prístup na túto stránku",
"you_need_admin_or_owner_privileges_to_access": "Na zobrazenie alebo interakciu s touto stránkou musíte mať administrátorské alebo vlastnícke práva",
"field_type": "Typ poľa",
@@ -2891,5 +2892,12 @@
"reassigned": "Preradené",
"rerouted": "Presmerované inam",
"salesforce_assigned": "Priradenie Salesforce",
"router_position": "Pozícia routera",
"show_matching_hosts": "Zobraziť zodpovedajúcich hostiteľov",
"no_active_queues": "Pre tento vstup nie sú aktívne fronty",
"weight": "Váha",
"calibration": "Kalibrácia",
"shortfall": "Deficit",
"routing_form": "Smerovací formulár",
"ADD_NEW_STRINGS_ABOVE_THIS_LINE_TO_PREVENT_MERGE_CONFLICTS": "↑↑↑↑↑↑↑↑↑↑↑↑↑ Pridajte svoje nové reťazce nad túto líniu ↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑"
}
@@ -2811,6 +2811,7 @@
"salesforce_owner_name_to_change": "Ime vlasnika koje treba promeniti",
"no_filter_set": "Nije postavljen filter",
"booking_dry_run_successful": "Test rezervacije uspešno završen",
"booking_dry_run_successful_description": "Test je uspešno završen.",
"dont_have_access_this_page": "Nemate pristup ovoj stranici",
"you_need_admin_or_owner_privileges_to_access": "Da biste pregledali ili koristili ovu stranicu, morate imati administratorske ili vlasničke privilegije",
"field_type": "Tip polja",
@@ -2891,5 +2892,12 @@
"reassigned": "Preraspoređeno",
"rerouted": "Preusmereno",
"salesforce_assigned": "Salesforce dodeljivanje",
"router_position": "Pozicija rutera",
"show_matching_hosts": "Prikaži odgovarajuće hostove",
"no_active_queues": "Nema aktivnih redova za ovaj unos",
"weight": "Težina",
"calibration": "Kalibracija",
"shortfall": "Manjak",
"routing_form": "Formular za rutiranje",
"ADD_NEW_STRINGS_ABOVE_THIS_LINE_TO_PREVENT_MERGE_CONFLICTS": "↑↑↑↑↑↑↑↑↑↑↑↑↑ Dodajte svoje nove stringove iznad ↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑"
}
@@ -2811,6 +2811,7 @@
"salesforce_owner_name_to_change": "Ägarnamn att ändra",
"no_filter_set": "Inget filter inställt",
"booking_dry_run_successful": "Testbokning genomförd",
"booking_dry_run_successful_description": "Testkörningen slutfördes framgångsrikt.",
"dont_have_access_this_page": "Du har inte åtkomst till den här sidan",
"you_need_admin_or_owner_privileges_to_access": "För att visa eller interagera med den här sidan måste du ha administratörs- eller ägarprivilegier",
"field_type": "Fälttyp",
@@ -2891,5 +2892,12 @@
"reassigned": "Omtilldelad",
"rerouted": "Omdirigerad",
"salesforce_assigned": "Salesforce-tilldelning",
"router_position": "Routerposition",
"show_matching_hosts": "Visa matchande värdar",
"no_active_queues": "Inga aktiva köer för denna inmatning",
"weight": "Vikt",
"calibration": "Kalibrering",
"shortfall": "Underskott",
"routing_form": "Routingformulär",
"ADD_NEW_STRINGS_ABOVE_THIS_LINE_TO_PREVENT_MERGE_CONFLICTS": "↑↑↑↑↑↑↑↑↑↑↑↑↑ Add your new strings above here ↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑"
}
@@ -2811,6 +2811,7 @@
"salesforce_owner_name_to_change": "Değiştirilecek sahip adı",
"no_filter_set": "Filtre ayarlanmadı",
"booking_dry_run_successful": "Rezervasyon test çalıştırması başarılı",
"booking_dry_run_successful_description": "Test çalıştırması başarıyla tamamlandı.",
"dont_have_access_this_page": "Bu sayfaya erişiminiz yok",
"you_need_admin_or_owner_privileges_to_access": "Bu sayfayı görüntülemek veya etkileşimde bulunmak için yönetici veya sahip ayrıcalıklarına sahip olmalısınız",
"field_type": "Alan türü",
@@ -2891,5 +2892,12 @@
"reassigned": "Yeniden atandı",
"rerouted": "Yeniden yönlendirildi",
"salesforce_assigned": "Salesforce ataması",
"router_position": "Router konumu",
"show_matching_hosts": "Eşleşen sunucuları göster",
"no_active_queues": "Bu girdi için aktif kuyruk yok",
"weight": "Ağırlık",
"calibration": "Kalibrasyon",
"shortfall": "Açık",
"routing_form": "Yönlendirme formu",
"ADD_NEW_STRINGS_ABOVE_THIS_LINE_TO_PREVENT_MERGE_CONFLICTS": "↑↑↑↑↑↑↑↑↑↑↑↑↑ Yeni dizelerinizi yukarıya ekleyin ↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑"
}
@@ -2811,6 +2811,7 @@
"salesforce_owner_name_to_change": "Ім'я власника для зміни",
"no_filter_set": "Фільтр не встановлено",
"booking_dry_run_successful": "Тестове бронювання успішне",
"booking_dry_run_successful_description": "Тестовий запуск успішно завершено.",
"dont_have_access_this_page": "Ви не маєте доступу до цієї сторінки",
"you_need_admin_or_owner_privileges_to_access": "Щоб переглядати або взаємодіяти з цією сторінкою, ви повинні мати права адміністратора або власника",
"field_type": "Тип поля",
@@ -2891,5 +2892,12 @@
"reassigned": "Перепризначено",
"rerouted": "Перемаршрутизовано",
"salesforce_assigned": "Призначення Salesforce",
"router_position": "Позиція маршрутизатора",
"show_matching_hosts": "Показати відповідні хости",
"no_active_queues": "Немає активних черг для цього вводу",
"weight": "Вага",
"calibration": "Калібрування",
"shortfall": "Дефіцит",
"routing_form": "Форма маршрутизації",
"ADD_NEW_STRINGS_ABOVE_THIS_LINE_TO_PREVENT_MERGE_CONFLICTS": "↑↑↑↑↑↑↑↑↑↑↑↑↑ Add your new strings above here ↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑"
}
@@ -2811,6 +2811,7 @@
"salesforce_owner_name_to_change": "Tên chủ sở hữu cần thay đổi",
"no_filter_set": "Chưa đặt bộ lọc",
"booking_dry_run_successful": "Chạy thử đặt lịch thành công",
"booking_dry_run_successful_description": "Quá trình chạy thử đã hoàn tất thành công.",
"dont_have_access_this_page": "Bạn không có quyền truy cập trang này",
"you_need_admin_or_owner_privileges_to_access": "Để xem hoặc tương tác với trang này, bạn cần có quyền quản trị viên hoặc chủ sở hữu",
"field_type": "Loại trường",
@@ -2891,5 +2892,12 @@
"reassigned": "Đã phân công lại",
"rerouted": "Đã chuyển hướng lại",
"salesforce_assigned": "Phân công Salesforce",
"router_position": "Vị trí bộ định tuyến",
"show_matching_hosts": "Hiển thị các máy chủ phù hợp",
"no_active_queues": "Không có hàng đợi đang hoạt động cho đầu vào này",
"weight": "Trọng số",
"calibration": "Hiệu chuẩn",
"shortfall": "Thiếu hụt",
"routing_form": "Biểu mẫu định tuyến",
"ADD_NEW_STRINGS_ABOVE_THIS_LINE_TO_PREVENT_MERGE_CONFLICTS": "↑↑↑↑↑↑↑↑↑↑↑↑↑ Add your new strings above here ↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑"
}
@@ -2216,7 +2216,7 @@
"org_no_teams_yet": "此组织尚无团队",
"org_no_teams_yet_description": "如果您是管理员,请务必创建要在此处显示的团队。",
"set_up": "设置",
"my_profile": "我的个人资料",
"my_profile": "我的资料",
"my_settings": "我的设置",
"crm": "CRM",
"messaging": "消息",
@@ -2811,6 +2811,7 @@
"salesforce_owner_name_to_change": "要更改的所有者名称",
"no_filter_set": "未设置筛选条件",
"booking_dry_run_successful": "预订测试成功",
"booking_dry_run_successful_description": "测试运行已成功完成。",
"dont_have_access_this_page": "您无权访问此页面",
"you_need_admin_or_owner_privileges_to_access": "要查看或操作此页面,您必须拥有管理员或所有者权限",
"field_type": "字段类型",
@@ -2891,5 +2892,12 @@
"reassigned": "已重新分配",
"rerouted": "已重新路由",
"salesforce_assigned": "Salesforce 分配",
"router_position": "路由位置",
"show_matching_hosts": "显示匹配的主机",
"no_active_queues": "此输入没有活动队列",
"weight": "权重",
"calibration": "校准",
"shortfall": "缺口",
"routing_form": "路由表单",
"ADD_NEW_STRINGS_ABOVE_THIS_LINE_TO_PREVENT_MERGE_CONFLICTS": "↑↑↑↑↑↑↑↑↑↑↑↑↑ 在此上方添加您的新字符串 ↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑"
}

Some files were not shown because too many files have changed in this diff Show More