Merge remote-tracking branch 'origin/main' into e2e-limits

This commit is contained in:
Hariom 2023-12-08 11:15:39 +05:30
commit 98b79e9c93
304 changed files with 9275 additions and 10641 deletions

View File

@ -37,6 +37,7 @@ BASECAMP3_USER_AGENT=
DAILY_API_KEY= DAILY_API_KEY=
DAILY_SCALE_PLAN='' DAILY_SCALE_PLAN=''
DAILY_WEBHOOK_SECRET=''
# - GOOGLE CALENDAR/MEET/LOGIN # - GOOGLE CALENDAR/MEET/LOGIN
# Needed to enable Google Calendar integration and Login with Google # Needed to enable Google Calendar integration and Login with Google

View File

@ -196,6 +196,10 @@ EMAIL_SERVER_PORT=1025
# Make sure to run mailhog container manually or with `yarn dx` # Make sure to run mailhog container manually or with `yarn dx`
E2E_TEST_MAILHOG_ENABLED= E2E_TEST_MAILHOG_ENABLED=
# Resend
# Send transactional email using resend
# RESEND_API_KEY=
# ********************************************************************************************************** # **********************************************************************************************************
# Set the following value to true if you wish to enable Team Impersonation # Set the following value to true if you wish to enable Team Impersonation
@ -266,7 +270,7 @@ CALCOM_WEBHOOK_SECRET=""
CALCOM_WEBHOOK_HEADER_NAME="calcom-webhook-secret" CALCOM_WEBHOOK_HEADER_NAME="calcom-webhook-secret"
CALCOM_CREDENTIAL_SYNC_ENDPOINT="" CALCOM_CREDENTIAL_SYNC_ENDPOINT=""
# Key should match on Cal.com and your application # Key should match on Cal.com and your application
# must be 32 bytes for AES256 encryption algorithm # must be 24 bytes for AES256 encryption algorithm
# You can use: `openssl rand -base64 24` to generate one # You can use: `openssl rand -base64 24` to generate one
CALCOM_APP_CREDENTIAL_ENCRYPTION_KEY="" CALCOM_APP_CREDENTIAL_ENCRYPTION_KEY=""
@ -290,3 +294,4 @@ E2E_TEST_OIDC_USER_PASSWORD=
AB_TEST_BUCKET_PROBABILITY=50 AB_TEST_BUCKET_PROBABILITY=50
# whether we redirect to the future/event-types from event-types or not # whether we redirect to the future/event-types from event-types or not
APP_ROUTER_EVENT_TYPES_ENABLED=1 APP_ROUTER_EVENT_TYPES_ENABLED=1
APP_ROUTER_SETTINGS_ADMIN_ENABLED=1

View File

@ -24,6 +24,8 @@ runs:
**/.turbo/** **/.turbo/**
**/dist/** **/dist/**
key: ${{ runner.os }}-${{ env.cache-name }}-${{ env.key-1 }}-${{ env.key-2 }}-${{ env.key-3 }}-${{ env.key-4 }} key: ${{ runner.os }}-${{ env.cache-name }}-${{ env.key-1 }}-${{ env.key-2 }}-${{ env.key-3 }}-${{ env.key-4 }}
- run: yarn build - run: |
export NODE_OPTIONS="--max_old_space_size=8192"
yarn build
if: steps.cache-build.outputs.cache-hit != 'true' if: steps.cache-build.outputs.cache-hit != 'true'
shell: bash shell: bash

View File

@ -24,7 +24,7 @@ jobs:
strategy: strategy:
fail-fast: false fail-fast: false
matrix: matrix:
shard: [1, 2, 3, 4, 5] shard: [1, 2, 3, 4, 5, 6, 7, 8, 9, 10]
steps: steps:
- uses: actions/checkout@v3 - uses: actions/checkout@v3
- uses: ./.github/actions/dangerous-git-checkout - uses: ./.github/actions/dangerous-git-checkout

View File

@ -7,7 +7,7 @@
<h3 align="center">Cal.com (formerly Calendso)</h3> <h3 align="center">Cal.com (formerly Calendso)</h3>
<p align="center"> <p align="center">
The open-source Calendly alternative. The open-source Calendly successor.
<br /> <br />
<a href="https://cal.com"><strong>Learn more »</strong></a> <a href="https://cal.com"><strong>Learn more »</strong></a>
<br /> <br />
@ -50,7 +50,7 @@
# Scheduling infrastructure for absolutely everyone # Scheduling infrastructure for absolutely everyone
The open source Calendly alternative. You are in charge The open source Calendly successor. You are in charge
of your own data, workflow, and appearance. of your own data, workflow, and appearance.
Calendly and other scheduling tools are awesome. It made our lives massively easier. We're using it for business meetings, seminars, yoga classes, and even calls with our families. However, most tools are very limited in terms of control and customization. Calendly and other scheduling tools are awesome. It made our lives massively easier. We're using it for business meetings, seminars, yoga classes, and even calls with our families. However, most tools are very limited in terms of control and customization.

View File

@ -60,6 +60,7 @@ export const schemaEventTypeBaseBodyParams = EventType.pick({
successRedirectUrl: true, successRedirectUrl: true,
locations: true, locations: true,
bookingLimits: true, bookingLimits: true,
onlyShowFirstAvailableSlot: true,
durationLimits: true, durationLimits: true,
}) })
.merge( .merge(
@ -147,6 +148,7 @@ export const schemaEventTypeReadPublic = EventType.pick({
seatsShowAvailabilityCount: true, seatsShowAvailabilityCount: true,
bookingFields: true, bookingFields: true,
bookingLimits: true, bookingLimits: true,
onlyShowFirstAvailableSlot: true,
durationLimits: true, durationLimits: true,
}).merge( }).merge(
z.object({ z.object({

View File

@ -6,18 +6,44 @@ import { schemaQueryIdParseInt } from "~/lib/validations/shared/queryIdTransform
async function authMiddleware(req: NextApiRequest) { async function authMiddleware(req: NextApiRequest) {
const { userId, prisma, isAdmin, query } = req; const { userId, prisma, isAdmin, query } = req;
if (isAdmin) {
return;
}
const { id } = schemaQueryIdParseInt.parse(query); const { id } = schemaQueryIdParseInt.parse(query);
const userWithBookings = await prisma.user.findUnique({ const userWithBookingsAndTeamIds = await prisma.user.findUnique({
where: { id: userId }, where: { id: userId },
include: { bookings: true }, include: {
bookings: true,
teams: {
select: {
teamId: true,
},
},
},
}); });
if (!userWithBookings) throw new HttpError({ statusCode: 404, message: "User not found" }); if (!userWithBookingsAndTeamIds) throw new HttpError({ statusCode: 404, message: "User not found" });
const userBookingIds = userWithBookings.bookings.map((booking) => booking.id); const userBookingIds = userWithBookingsAndTeamIds.bookings.map((booking) => booking.id);
if (!isAdmin && !userBookingIds.includes(id)) { if (!userBookingIds.includes(id)) {
throw new HttpError({ statusCode: 401, message: "You are not authorized" }); const teamBookings = await prisma.booking.findUnique({
where: {
id: id,
eventType: {
team: {
id: {
in: userWithBookingsAndTeamIds.teams.map((team) => team.teamId),
},
},
},
},
});
if (!teamBookings) {
throw new HttpError({ statusCode: 401, message: "You are not authorized" });
}
} }
} }

View File

@ -1,5 +1,9 @@
import timezone from "dayjs/plugin/timezone";
import utc from "dayjs/plugin/utc";
import type { NextApiRequest, NextApiResponse } from "next"; import type { NextApiRequest, NextApiResponse } from "next";
import dayjs from "@calcom/dayjs";
import { isSupportedTimeZone } from "@calcom/lib/date-fns";
import { HttpError } from "@calcom/lib/http-error"; import { HttpError } from "@calcom/lib/http-error";
import { defaultResponder } from "@calcom/lib/server"; import { defaultResponder } from "@calcom/lib/server";
import { createContext } from "@calcom/trpc/server/createContext"; import { createContext } from "@calcom/trpc/server/createContext";
@ -9,10 +13,34 @@ import { getAvailableSlots } from "@calcom/trpc/server/routers/viewer/slots/util
import { TRPCError } from "@trpc/server"; import { TRPCError } from "@trpc/server";
import { getHTTPStatusCodeFromError } from "@trpc/server/http"; import { getHTTPStatusCodeFromError } from "@trpc/server/http";
// Apply plugins
dayjs.extend(utc);
dayjs.extend(timezone);
async function handler(req: NextApiRequest, res: NextApiResponse) { async function handler(req: NextApiRequest, res: NextApiResponse) {
try { try {
const input = getScheduleSchema.parse(req.query); const { usernameList, ...rest } = req.query;
return await getAvailableSlots({ ctx: await createContext({ req, res }), input }); let slugs = usernameList;
if (!Array.isArray(usernameList)) {
slugs = usernameList ? [usernameList] : [];
}
const input = getScheduleSchema.parse({ usernameList: slugs, ...rest });
const timeZoneSupported = input.timeZone ? isSupportedTimeZone(input.timeZone) : false;
const availableSlots = await getAvailableSlots({ ctx: await createContext({ req, res }), input });
const slotsInProvidedTimeZone = timeZoneSupported
? Object.keys(availableSlots.slots).reduce(
(acc: Record<string, { time: string; attendees?: number; bookingUid?: string }[]>, date) => {
acc[date] = availableSlots.slots[date].map((slot) => ({
...slot,
time: dayjs(slot.time).tz(input.timeZone).format(),
}));
return acc;
},
{}
)
: availableSlots;
return slotsInProvidedTimeZone;
// eslint-disable-next-line @typescript-eslint/no-explicit-any // eslint-disable-next-line @typescript-eslint/no-explicit-any
} catch (cause) { } catch (cause) {
if (cause instanceof TRPCError) { if (cause instanceof TRPCError) {

View File

@ -8,7 +8,9 @@ pnpm-debug.log*
lerna-debug.log* lerna-debug.log*
node_modules node_modules
storybook-static storybook-static/*
!storybook-static/favicon.ico
!storybook-static/sb-cover.jpg
dist dist
dist-ssr dist-ssr
*.local *.local

View File

@ -1,76 +0,0 @@
const path = require("path");
module.exports = {
stories: [
"../intro.stories.mdx",
"../../../packages/ui/components/**/*.stories.mdx",
"../../../packages/atoms/**/*.stories.mdx",
"../../../packages/features/**/*.stories.mdx",
"../../../packages/ui/components/**/*.stories.@(js|jsx|ts|tsx)",
],
addons: [
"@storybook/addon-links",
"@storybook/addon-essentials",
"@storybook/addon-interactions",
"storybook-addon-rtl-direction",
"storybook-react-i18next",
"storybook-addon-next",
"storybook-addon-next-router",
/*{
name: "storybook-addon-next",
options: {
nextConfigPath: path.resolve(__dirname, "../../web/next.config.js"),
},
},*/
],
framework: "@storybook/react",
core: {
builder: "webpack5",
},
staticDirs: ["../public"],
webpackFinal: async (config, { configType }) => {
config.resolve.fallback = {
fs: false,
assert: false,
buffer: false,
console: false,
constants: false,
crypto: false,
domain: false,
events: false,
http: false,
https: false,
os: false,
path: false,
punycode: false,
process: false,
querystring: false,
stream: false,
string_decoder: false,
sys: false,
timers: false,
tty: false,
url: false,
util: false,
vm: false,
zlib: false,
};
config.module.rules.push({
test: /\.css$/,
use: [
"style-loader",
{
loader: "css-loader",
options: {
modules: true, // Enable modules to help you using className
},
},
],
include: path.resolve(__dirname, "../src"),
});
return config;
},
typescript: { reactDocgen: "react-docgen" },
};

View File

@ -0,0 +1,96 @@
import type { StorybookConfig } from "@storybook/nextjs";
import path, { dirname, join } from "path";
const config: StorybookConfig = {
stories: [
"../intro.stories.mdx",
"../../../packages/ui/components/**/*.stories.mdx", // legacy SB6 stories
"../../../packages/ui/components/**/*.stories.@(js|jsx|ts|tsx)",
"../../../packages/ui/components/**/*.docs.mdx",
"../../../packages/features/**/*.stories.@(js|jsx|ts|tsx)",
"../../../packages/features/**/*.docs.mdx",
"../../../packages/atoms/**/*.stories.@(js|jsx|ts|tsx)",
"../../../packages/atoms/**/*.docs.mdx",
],
addons: [
getAbsolutePath("@storybook/addon-links"),
getAbsolutePath("@storybook/addon-essentials"),
getAbsolutePath("@storybook/addon-interactions"),
getAbsolutePath("storybook-addon-rtl-direction"),
getAbsolutePath("storybook-react-i18next"),
],
framework: {
name: getAbsolutePath("@storybook/nextjs") as "@storybook/nextjs",
options: {
// builder: {
// fsCache: true,
// lazyCompilation: true,
// },
},
},
staticDirs: ["../public"],
webpackFinal: async (config, { configType }) => {
config.resolve = config.resolve || {};
config.resolve.fallback = {
fs: false,
assert: false,
buffer: false,
console: false,
constants: false,
crypto: false,
domain: false,
events: false,
http: false,
https: false,
os: false,
path: false,
punycode: false,
process: false,
querystring: false,
stream: false,
string_decoder: false,
sys: false,
timers: false,
tty: false,
url: false,
util: false,
vm: false,
zlib: false,
};
config.module = config.module || {};
config.module.rules = config.module.rules || [];
config.module.rules.push({
test: /\.css$/,
use: [
"style-loader",
{
loader: "css-loader",
options: {
modules: true, // Enable modules to help you using className
},
},
],
include: path.resolve(__dirname, "../src"),
});
return config;
},
typescript: { reactDocgen: "react-docgen" },
docs: {
autodocs: true,
},
};
export default config;
function getAbsolutePath(value) {
return dirname(require.resolve(join(value, "package.json")));
}

View File

@ -1,48 +0,0 @@
import { addDecorator } from "@storybook/react";
import { AppRouterContext } from "next/dist/shared/lib/app-router-context";
import { I18nextProvider } from "react-i18next";
import "../styles/globals.css";
import "../styles/storybook-styles.css";
import i18n from "./i18next";
export const parameters = {
actions: { argTypesRegex: "^on[A-Z].*" },
controls: {
matchers: {
color: /(background|color)$/i,
date: /Date$/,
},
},
nextRouter: {
pathname: "/",
asPath: "/",
query: {},
push() {},
Provider: AppRouterContext.Provider,
},
globals: {
locale: "en",
locales: {
en: "English",
fr: "Français",
},
},
i18n,
};
addDecorator((storyFn) => (
<I18nextProvider i18n={i18n}>
<div style={{ margin: "2rem" }}>{storyFn()}</div>
</I18nextProvider>
));
window.getEmbedNamespace = () => {
const url = new URL(document.URL);
const namespace = url.searchParams.get("embed");
return namespace;
};
window.getEmbedTheme = () => {
return "auto";
};

View File

@ -0,0 +1,73 @@
// adds tooltip context to all stories
import { TooltipProvider } from "@radix-ui/react-tooltip";
import type { Preview } from "@storybook/react";
import React from "react";
import { I18nextProvider } from "react-i18next";
import type { EmbedThemeConfig } from "@calcom/embed-core/src/types";
// adds trpc context to all stories (esp. booker)
import { StorybookTrpcProvider } from "@calcom/ui";
import "../styles/globals.css";
import "../styles/storybook-styles.css";
import i18n from "./i18next";
const preview: Preview = {
parameters: {
actions: { argTypesRegex: "^on[A-Z].*" },
controls: {
matchers: {
color: /(background|color)$/i,
date: /Date$/,
},
},
globals: {
locale: "en",
locales: {
en: "English",
fr: "Français",
},
},
i18n,
nextjs: {
appDirectory: true,
},
},
decorators: [
(Story) => (
<StorybookTrpcProvider>
<TooltipProvider>
<I18nextProvider i18n={i18n}>
<div style={{ margin: "2rem" }}>
<Story />
</div>
</I18nextProvider>
</TooltipProvider>
</StorybookTrpcProvider>
),
],
};
export default preview;
declare global {
interface Window {
getEmbedNamespace: () => string | null;
getEmbedTheme: () => EmbedThemeConfig | null;
}
}
window.getEmbedNamespace = () => {
const url = new URL(document.URL);
const namespace = url.searchParams.get("embed");
return namespace;
};
window.getEmbedTheme = () => {
return "auto";
};

View File

@ -1,6 +1,6 @@
import { ArgsTable } from "@storybook/addon-docs"; import { ArgsTable } from "@storybook/addon-docs";
import { SortType } from "@storybook/components"; import type { SortType } from "@storybook/blocks";
import { PropDescriptor } from "@storybook/store"; import type { PropDescriptor } from "@storybook/preview-api";
// eslint-disable-next-line @typescript-eslint/no-explicit-any -- ignore storybook addon types component as any so we have to do // eslint-disable-next-line @typescript-eslint/no-explicit-any -- ignore storybook addon types component as any so we have to do
type Component = any; type Component = any;

View File

@ -9,10 +9,7 @@ import { Meta } from "@storybook/addon-docs";
library, we will be adding more components as we go along. library, we will be adding more components as we go along.
</p> </p>
<p> <p>
Our{" "} Our <a href="https://www.figma.com/file/9MOufQNLtdkpnDucmNX10R/%E2%9D%96-Cal-DS" target="_blank">Figma</a>
<a href="https://www.figma.com/file/9MOufQNLtdkpnDucmNX10R/%E2%9D%96-Cal-DS" target="_blank">
Figma
</a>{" "}
library is available for anyone to view and use. If you have any questions or concerns, please reach out to library is available for anyone to view and use. If you have any questions or concerns, please reach out to
the design team. the design team.
</p> </p>

View File

@ -3,8 +3,8 @@
"private": true, "private": true,
"version": "0.0.0", "version": "0.0.0",
"scripts": { "scripts": {
"dev": "start-storybook -p 6006", "dev": "storybook dev -p 6006",
"build": "build-storybook" "build": "storybook build"
}, },
"dependencies": { "dependencies": {
"@calcom/config": "*", "@calcom/config": "*",
@ -20,23 +20,25 @@
"@radix-ui/react-slider": "^1.0.0", "@radix-ui/react-slider": "^1.0.0",
"@radix-ui/react-switch": "^1.0.0", "@radix-ui/react-switch": "^1.0.0",
"@radix-ui/react-tooltip": "^1.0.0", "@radix-ui/react-tooltip": "^1.0.0",
"@storybook/addon-docs": "^7.6.3",
"@storybook/blocks": "^7.6.3",
"@storybook/nextjs": "^7.6.3",
"@storybook/preview-api": "^7.6.3",
"next": "^13.4.6", "next": "^13.4.6",
"react": "^18.2.0", "react": "^18.2.0",
"react-dom": "^18.2.0", "react-dom": "^18.2.0",
"storybook-addon-next-router": "^4.0.2",
"storybook-addon-rtl-direction": "^0.0.19" "storybook-addon-rtl-direction": "^0.0.19"
}, },
"devDependencies": { "devDependencies": {
"@babel/core": "^7.19.6", "@babel/core": "^7.19.6",
"@storybook/addon-actions": "^6.5.13", "@storybook/addon-actions": "^7.6.3",
"@storybook/addon-essentials": "^6.5.13", "@storybook/addon-designs": "^7.0.7",
"@storybook/addon-interactions": "^6.5.13", "@storybook/addon-essentials": "^7.6.3",
"@storybook/addon-links": "^6.5.13", "@storybook/addon-interactions": "^7.6.3",
"@storybook/builder-vite": "^0.2.4", "@storybook/addon-links": "^7.6.3",
"@storybook/builder-webpack5": "^6.5.13", "@storybook/nextjs": "^7.6.3",
"@storybook/manager-webpack5": "^6.5.13", "@storybook/react": "^7.6.3",
"@storybook/react": "^6.5.13", "@storybook/testing-library": "^0.2.2",
"@storybook/testing-library": "^0.0.13",
"@types/react": "18.0.26", "@types/react": "18.0.26",
"@types/react-dom": "^18.0.9", "@types/react-dom": "^18.0.9",
"@vitejs/plugin-react": "^2.2.0", "@vitejs/plugin-react": "^2.2.0",
@ -46,9 +48,8 @@
"postcss": "^8.4.18", "postcss": "^8.4.18",
"postcss-pseudo-companion-classes": "^0.1.1", "postcss-pseudo-companion-classes": "^0.1.1",
"rollup-plugin-polyfill-node": "^0.10.2", "rollup-plugin-polyfill-node": "^0.10.2",
"storybook-addon-designs": "^6.3.1", "storybook": "^7.6.3",
"storybook-addon-next": "^1.6.9", "storybook-react-i18next": "^2.0.9",
"storybook-react-i18next": "^1.1.2",
"tailwindcss": "^3.3.3", "tailwindcss": "^3.3.3",
"typescript": "^4.9.4", "typescript": "^4.9.4",
"vite": "^4.1.2" "vite": "^4.1.2"

View File

@ -5,6 +5,7 @@ import z from "zod";
const ROUTES: [URLPattern, boolean][] = [ const ROUTES: [URLPattern, boolean][] = [
["/event-types", process.env.APP_ROUTER_EVENT_TYPES_ENABLED === "1"] as const, ["/event-types", process.env.APP_ROUTER_EVENT_TYPES_ENABLED === "1"] as const,
["/settings/admin/:path*", process.env.APP_ROUTER_SETTINGS_ADMIN_ENABLED === "1"] as const,
].map(([pathname, enabled]) => [ ].map(([pathname, enabled]) => [
new URLPattern({ new URLPattern({
pathname, pathname,
@ -27,7 +28,6 @@ export const abTestMiddlewareFactory =
const override = req.cookies.has(FUTURE_ROUTES_OVERRIDE_COOKIE_NAME); const override = req.cookies.has(FUTURE_ROUTES_OVERRIDE_COOKIE_NAME);
const route = ROUTES.find(([regExp]) => regExp.test(req.url)) ?? null; const route = ROUTES.find(([regExp]) => regExp.test(req.url)) ?? null;
const enabled = route !== null ? route[1] || override : false; const enabled = route !== null ? route[1] || override : false;
if (pathname.includes("future") || !enabled) { if (pathname.includes("future") || !enabled) {

View File

@ -0,0 +1,212 @@
// originally from in the "experimental playground for tRPC + next.js 13" repo owned by trpc team
// file link: https://github.com/trpc/next-13/blob/main/%40trpc/next-layout/createTRPCNextLayout.ts
// repo link: https://github.com/trpc/next-13
// code is / will continue to be adapted for our usage
import { dehydrate, QueryClient } from "@tanstack/query-core";
import type { DehydratedState, QueryKey } from "@tanstack/react-query";
import type { Maybe, TRPCClientError, TRPCClientErrorLike } from "@calcom/trpc";
import {
callProcedure,
type AnyProcedure,
type AnyQueryProcedure,
type AnyRouter,
type DataTransformer,
type inferProcedureInput,
type inferProcedureOutput,
type inferRouterContext,
type MaybePromise,
type ProcedureRouterRecord,
} from "@calcom/trpc/server";
import { createRecursiveProxy, createFlatProxy } from "@trpc/server/shared";
export function getArrayQueryKey(
queryKey: string | [string] | [string, ...unknown[]] | unknown[],
type: string
): QueryKey {
const queryKeyArrayed = Array.isArray(queryKey) ? queryKey : [queryKey];
const [arrayPath, input] = queryKeyArrayed;
if (!input && (!type || type === "any")) {
return Array.isArray(arrayPath) && arrayPath.length !== 0 ? [arrayPath] : ([] as unknown as QueryKey);
}
return [
arrayPath,
{
...(typeof input !== "undefined" && { input: input }),
...(type && type !== "any" && { type: type }),
},
];
}
// copy starts
// copied from trpc/trpc repo
// ref: https://github.com/trpc/trpc/blob/main/packages/next/src/withTRPC.tsx#L37-#L58
function transformQueryOrMutationCacheErrors<
TState extends DehydratedState["queries"][0] | DehydratedState["mutations"][0]
>(result: TState): TState {
const error = result.state.error as Maybe<TRPCClientError<any>>;
if (error instanceof Error && error.name === "TRPCClientError") {
const newError: TRPCClientErrorLike<any> = {
message: error.message,
data: error.data,
shape: error.shape,
};
return {
...result,
state: {
...result.state,
error: newError,
},
};
}
return result;
}
// copy ends
interface CreateTRPCNextLayoutOptions<TRouter extends AnyRouter> {
router: TRouter;
createContext: () => MaybePromise<inferRouterContext<TRouter>>;
transformer?: DataTransformer;
}
/**
* @internal
*/
export type DecorateProcedure<TProcedure extends AnyProcedure> = TProcedure extends AnyQueryProcedure
? {
fetch(input: inferProcedureInput<TProcedure>): Promise<inferProcedureOutput<TProcedure>>;
fetchInfinite(input: inferProcedureInput<TProcedure>): Promise<inferProcedureOutput<TProcedure>>;
prefetch(input: inferProcedureInput<TProcedure>): Promise<inferProcedureOutput<TProcedure>>;
prefetchInfinite(input: inferProcedureInput<TProcedure>): Promise<inferProcedureOutput<TProcedure>>;
}
: never;
type OmitNever<TType> = Pick<
TType,
{
[K in keyof TType]: TType[K] extends never ? never : K;
}[keyof TType]
>;
/**
* @internal
*/
export type DecoratedProcedureRecord<
TProcedures extends ProcedureRouterRecord,
TPath extends string = ""
> = OmitNever<{
[TKey in keyof TProcedures]: TProcedures[TKey] extends AnyRouter
? DecoratedProcedureRecord<TProcedures[TKey]["_def"]["record"], `${TPath}${TKey & string}.`>
: TProcedures[TKey] extends AnyQueryProcedure
? DecorateProcedure<TProcedures[TKey]>
: never;
}>;
type CreateTRPCNextLayout<TRouter extends AnyRouter> = DecoratedProcedureRecord<TRouter["_def"]["record"]> & {
dehydrate(): Promise<DehydratedState>;
queryClient: QueryClient;
};
const getStateContainer = <TRouter extends AnyRouter>(opts: CreateTRPCNextLayoutOptions<TRouter>) => {
let _trpc: {
queryClient: QueryClient;
context: inferRouterContext<TRouter>;
} | null = null;
return () => {
if (_trpc === null) {
_trpc = {
context: opts.createContext(),
queryClient: new QueryClient(),
};
}
return _trpc;
};
};
export function createTRPCNextLayout<TRouter extends AnyRouter>(
opts: CreateTRPCNextLayoutOptions<TRouter>
): CreateTRPCNextLayout<TRouter> {
const getState = getStateContainer(opts);
const transformer = opts.transformer ?? {
serialize: (v) => v,
deserialize: (v) => v,
};
return createFlatProxy((key) => {
const state = getState();
const { queryClient } = state;
if (key === "queryClient") {
return queryClient;
}
if (key === "dehydrate") {
// copy starts
// copied from trpc/trpc repo
// ref: https://github.com/trpc/trpc/blob/main/packages/next/src/withTRPC.tsx#L214-#L229
const dehydratedCache = dehydrate(queryClient, {
shouldDehydrateQuery() {
// makes sure errors are also dehydrated
return true;
},
});
// since error instances can't be serialized, let's make them into `TRPCClientErrorLike`-objects
const dehydratedCacheWithErrors = {
...dehydratedCache,
queries: dehydratedCache.queries.map(transformQueryOrMutationCacheErrors),
mutations: dehydratedCache.mutations.map(transformQueryOrMutationCacheErrors),
};
return () => transformer.serialize(dehydratedCacheWithErrors);
}
// copy ends
return createRecursiveProxy(async (callOpts) => {
const path = [key, ...callOpts.path];
const utilName = path.pop();
const ctx = await state.context;
const caller = opts.router.createCaller(ctx);
const pathStr = path.join(".");
const input = callOpts.args[0];
if (utilName === "fetchInfinite") {
return queryClient.fetchInfiniteQuery(getArrayQueryKey([path, input], "infinite"), () =>
caller.query(pathStr, input)
);
}
if (utilName === "prefetch") {
return queryClient.prefetchQuery({
queryKey: getArrayQueryKey([path, input], "query"),
queryFn: async () => {
const res = await callProcedure({
procedures: opts.router._def.procedures,
path: pathStr,
rawInput: input,
ctx,
type: "query",
});
return res;
},
});
}
if (utilName === "prefetchInfinite") {
return queryClient.prefetchInfiniteQuery(getArrayQueryKey([path, input], "infinite"), () =>
caller.query(pathStr, input)
);
}
return queryClient.fetchQuery(getArrayQueryKey([path, input], "query"), () =>
caller.query(pathStr, input)
);
}) as CreateTRPCNextLayout<TRouter>;
});
}

View File

@ -0,0 +1,4 @@
import type { TRPCContext } from "@calcom/trpc/server/createContext";
import { appRouter } from "@calcom/trpc/server/routers/_app";
export const getServerCaller = (ctx: TRPCContext) => appRouter.createCaller(ctx);

View File

@ -0,0 +1,34 @@
import { serverSideTranslations } from "next-i18next/serverSideTranslations";
import { headers } from "next/headers";
import superjson from "superjson";
import { CALCOM_VERSION } from "@calcom/lib/constants";
import prisma, { readonlyPrisma } from "@calcom/prisma";
import { appRouter } from "@calcom/trpc/server/routers/_app";
import { createTRPCNextLayout } from "./createTRPCNextLayout";
export async function ssgInit() {
const locale = headers().get("x-locale") ?? "en";
const i18n = (await serverSideTranslations(locale, ["common"])) || "en";
const ssg = createTRPCNextLayout({
router: appRouter,
transformer: superjson,
createContext() {
return { prisma, insightsDb: readonlyPrisma, session: null, locale, i18n };
},
});
// i18n translations are already retrieved from serverSideTranslations call, there is no need to run a i18n.fetch
// we can set query data directly to the queryClient
const queryKey = [
["viewer", "public", "i18n"],
{ input: { locale, CalComVersion: CALCOM_VERSION }, type: "query" },
];
ssg.queryClient.setQueryData(queryKey, { i18n });
return ssg;
}

View File

@ -0,0 +1,57 @@
import { type GetServerSidePropsContext } from "next";
import { serverSideTranslations } from "next-i18next/serverSideTranslations";
import { headers, cookies } from "next/headers";
import superjson from "superjson";
import { getLocale } from "@calcom/features/auth/lib/getLocale";
import { CALCOM_VERSION } from "@calcom/lib/constants";
import prisma, { readonlyPrisma } from "@calcom/prisma";
import { appRouter } from "@calcom/trpc/server/routers/_app";
import { createTRPCNextLayout } from "./createTRPCNextLayout";
export async function ssrInit(options?: { noI18nPreload: boolean }) {
const req = {
headers: headers(),
cookies: cookies(),
};
const locale = await getLocale(req);
const i18n = (await serverSideTranslations(locale, ["common", "vital"])) || "en";
const ssr = createTRPCNextLayout({
router: appRouter,
transformer: superjson,
createContext() {
return {
prisma,
insightsDb: readonlyPrisma,
session: null,
locale,
i18n,
req: req as unknown as GetServerSidePropsContext["req"],
};
},
});
// i18n translations are already retrieved from serverSideTranslations call, there is no need to run a i18n.fetch
// we can set query data directly to the queryClient
const queryKey = [
["viewer", "public", "i18n"],
{ input: { locale, CalComVersion: CALCOM_VERSION }, type: "query" },
];
if (!options?.noI18nPreload) {
ssr.queryClient.setQueryData(queryKey, { i18n });
}
await Promise.allSettled([
// So feature flags are available on first render
ssr.viewer.features.map.prefetch(),
// Provides a better UX to the users who have already upgraded.
ssr.viewer.teams.hasTeamPlan.prefetch(),
ssr.viewer.public.session.prefetch(),
]);
return ssr;
}

View File

@ -8,33 +8,8 @@ import { httpBatchLink } from "@calcom/trpc/client/links/httpBatchLink";
import { httpLink } from "@calcom/trpc/client/links/httpLink"; import { httpLink } from "@calcom/trpc/client/links/httpLink";
import { loggerLink } from "@calcom/trpc/client/links/loggerLink"; import { loggerLink } from "@calcom/trpc/client/links/loggerLink";
import { splitLink } from "@calcom/trpc/client/links/splitLink"; import { splitLink } from "@calcom/trpc/client/links/splitLink";
import { ENDPOINTS } from "@calcom/trpc/react/shared";
const ENDPOINTS = [
"admin",
"apiKeys",
"appRoutingForms",
"apps",
"auth",
"availability",
"appBasecamp3",
"bookings",
"deploymentSetup",
"eventTypes",
"features",
"insights",
"payments",
"public",
"saml",
"slots",
"teams",
"organizations",
"users",
"viewer",
"webhook",
"workflows",
"appsRouter",
"googleWorkspace",
] as const;
export type Endpoint = (typeof ENDPOINTS)[number]; export type Endpoint = (typeof ENDPOINTS)[number];
// eslint-disable-next-line @typescript-eslint/no-explicit-any // eslint-disable-next-line @typescript-eslint/no-explicit-any

3
apps/web/app/_types.ts Normal file
View File

@ -0,0 +1,3 @@
export type Params = {
[param: string]: string | string[] | undefined;
};

View File

@ -0,0 +1,20 @@
import { headers } from "next/headers";
import { type ReactElement } from "react";
import PageWrapper from "@components/PageWrapperAppDir";
import { getLayout } from "@components/auth/layouts/AdminLayoutAppDir";
type WrapperWithLayoutProps = {
children: ReactElement;
};
export default async function WrapperWithLayout({ children }: WrapperWithLayoutProps) {
const h = headers();
const nonce = h.get("x-nonce") ?? undefined;
return (
<PageWrapper getLayout={getLayout} requiresLicense={false} nonce={nonce} themeBasis={null}>
{children}
</PageWrapper>
);
}

View File

@ -0,0 +1,10 @@
import Page from "@pages/settings/admin/apps/[category]";
import { _generateMetadata } from "app/_utils";
export const generateMetadata = async () =>
await _generateMetadata(
(t) => t("apps"),
(t) => t("admin_apps_description")
);
export default Page;

View File

@ -0,0 +1,10 @@
import Page from "@pages/settings/admin/apps/index";
import { _generateMetadata } from "app/_utils";
export const generateMetadata = async () =>
await _generateMetadata(
(t) => t("apps"),
(t) => t("admin_apps_description")
);
export default Page;

View File

@ -0,0 +1,10 @@
import Page from "@pages/settings/admin/flags";
import { _generateMetadata } from "app/_utils";
export const generateMetadata = async () =>
await _generateMetadata(
() => "Feature Flags",
() => "Here you can toggle your Cal.com instance features."
);
export default Page;

View File

@ -0,0 +1,10 @@
import Page from "@pages/settings/admin/impersonation";
import { _generateMetadata } from "app/_utils";
export const generateMetadata = async () =>
await _generateMetadata(
(t) => t("admin"),
(t) => t("impersonation")
);
export default Page;

View File

@ -0,0 +1,10 @@
import Page from "@pages/settings/admin/oAuth/index";
import { _generateMetadata } from "app/_utils";
export const generateMetadata = async () =>
await _generateMetadata(
() => "OAuth",
() => "Add new OAuth Clients"
);
export default Page;

View File

@ -0,0 +1,10 @@
import Page from "@pages/settings/admin/index";
import { _generateMetadata } from "app/_utils";
export const generateMetadata = async () =>
await _generateMetadata(
() => "Admin",
() => "admin_description"
);
export default Page;

View File

@ -0,0 +1,20 @@
// pages containing layout (e.g., /availability/[schedule].tsx) are supposed to go under (no-layout) folder
import { headers } from "next/headers";
import { type ReactElement } from "react";
import PageWrapper from "@components/PageWrapperAppDir";
type WrapperWithoutLayoutProps = {
children: ReactElement;
};
export default async function WrapperWithoutLayout({ children }: WrapperWithoutLayoutProps) {
const h = headers();
const nonce = h.get("x-nonce") ?? undefined;
return (
<PageWrapper getLayout={null} requiresLicense={false} nonce={nonce} themeBasis={null}>
{children}
</PageWrapper>
);
}

View File

@ -0,0 +1,10 @@
import Page from "@pages/settings/admin/oAuth/oAuthView";
import { _generateMetadata } from "app/_utils";
export const generateMetadata = async () =>
await _generateMetadata(
() => "OAuth",
() => "Add new OAuth Clients"
);
export default Page;

View File

@ -0,0 +1,21 @@
import { headers } from "next/headers";
import { type ReactElement } from "react";
import { getLayout } from "@calcom/features/settings/layouts/SettingsLayoutAppDir";
import PageWrapper from "@components/PageWrapperAppDir";
type WrapperWithLayoutProps = {
children: ReactElement;
};
export default async function WrapperWithLayout({ children }: WrapperWithLayoutProps) {
const h = headers();
const nonce = h.get("x-nonce") ?? undefined;
return (
<PageWrapper getLayout={getLayout} requiresLicense={false} nonce={nonce} themeBasis={null}>
{children}
</PageWrapper>
);
}

View File

@ -0,0 +1,11 @@
import { _generateMetadata } from "app/_utils";
import Page from "@calcom/features/ee/organizations/pages/settings/admin/AdminOrgPage";
export const generateMetadata = async () =>
await _generateMetadata(
(t) => t("organizations"),
(t) => t("orgs_page_description")
);
export default Page;

View File

@ -0,0 +1,36 @@
import { getServerCaller } from "app/_trpc/serverClient";
import { type Params } from "app/_types";
import { _generateMetadata } from "app/_utils";
import { cookies, headers } from "next/headers";
import { z } from "zod";
import Page from "@calcom/features/ee/users/pages/users-edit-view";
import prisma from "@calcom/prisma";
const userIdSchema = z.object({ id: z.coerce.number() });
export const generateMetadata = async ({ params }: { params: Params }) => {
const input = userIdSchema.safeParse(params);
let title = "";
if (!input.success) {
title = "Editing user";
} else {
const req = {
headers: headers(),
cookies: cookies(),
};
// @ts-expect-error Type '{ headers: ReadonlyHeaders; cookies: ReadonlyRequestCookies; }' is not assignable to type 'NextApiRequest'
const data = await getServerCaller({ req, prisma }).viewer.users.get({ userId: input.data.id });
const { user } = data;
title = `Editing user: ${user.username}`;
}
return await _generateMetadata(
() => title,
() => "Here you can edit a current user."
);
};
export default Page;

View File

@ -0,0 +1,11 @@
import { _generateMetadata } from "app/_utils";
import Page from "@calcom/features/ee/users/pages/users-add-view";
export const generateMetadata = async () =>
await _generateMetadata(
() => "Add new user",
() => "Here you can add a new user."
);
export default Page;

View File

@ -0,0 +1,11 @@
import { _generateMetadata } from "app/_utils";
import Page from "@calcom/features/ee/users/pages/users-listing-view";
export const generateMetadata = async () =>
await _generateMetadata(
() => "Users",
() => "A list of all the users in your account including their name, title, email and role."
);
export default Page;

View File

@ -0,0 +1,40 @@
"use client";
import { useSession } from "next-auth/react";
import { usePathname, useRouter } from "next/navigation";
import type { ComponentProps } from "react";
import React, { useEffect } from "react";
import SettingsLayout from "@calcom/features/settings/layouts/SettingsLayout";
import type Shell from "@calcom/features/shell/Shell";
import { UserPermissionRole } from "@calcom/prisma/enums";
import { ErrorBoundary } from "@calcom/ui";
export default function AdminLayout({
children,
...rest
}: { children: React.ReactNode } & ComponentProps<typeof Shell>) {
const pathname = usePathname();
const session = useSession();
const router = useRouter();
// Force redirect on component level
useEffect(() => {
if (session.data && session.data.user.role !== UserPermissionRole.ADMIN) {
router.replace("/settings/my-account/profile");
}
}, [session, router]);
const isAppsPage = pathname?.startsWith("/settings/admin/apps");
return (
<SettingsLayout {...rest}>
<div className="divide-subtle mx-auto flex max-w-4xl flex-row divide-y">
<div className={isAppsPage ? "min-w-0" : "flex flex-1 [&>*]:flex-1"}>
<ErrorBoundary>{children}</ErrorBoundary>
</div>
</div>
</SettingsLayout>
);
}
export const getLayout = (page: React.ReactElement) => <AdminLayout>{page}</AdminLayout>;

View File

@ -525,8 +525,8 @@ const RecurringBookingsTooltip = ({
return ( return (
recurringDate >= now && recurringDate >= now &&
!booking.recurringInfo?.bookings[BookingStatus.CANCELLED] !booking.recurringInfo?.bookings[BookingStatus.CANCELLED]
.map((date) => date.toDateString()) .map((date) => date.toString())
.includes(recurringDate.toDateString()) .includes(recurringDate.toString())
); );
}).length; }).length;
@ -543,8 +543,8 @@ const RecurringBookingsTooltip = ({
const pastOrCancelled = const pastOrCancelled =
aDate < now || aDate < now ||
booking.recurringInfo?.bookings[BookingStatus.CANCELLED] booking.recurringInfo?.bookings[BookingStatus.CANCELLED]
.map((date) => date.toDateString()) .map((date) => date.toString())
.includes(aDate.toDateString()); .includes(aDate.toString());
return ( return (
<p key={key} className={classNames(pastOrCancelled && "line-through")}> <p key={key} className={classNames(pastOrCancelled && "line-through")}>
{formatTime(aDate, userTimeFormat, userTimeZone)} {formatTime(aDate, userTimeFormat, userTimeZone)}

View File

@ -382,7 +382,7 @@ export const EditLocationDialog = (props: ISetLocationDialog) => {
}} }}
/> />
{selectedLocation && LocationOptions} {selectedLocation && LocationOptions}
<DialogFooter className="mt-4"> <DialogFooter className="relative">
<Button <Button
onClick={() => { onClick={() => {
setShowLocationModal(false); setShowLocationModal(false);

View File

@ -298,6 +298,29 @@ export const EventLimitsTab = ({ eventType }: Pick<EventTypeSetupProps, "eventTy
); );
}} }}
/> />
<Controller
name="onlyShowFirstAvailableSlot"
control={formMethods.control}
render={({ field: { value } }) => {
const isChecked = value;
return (
<SettingsToggle
toggleSwitchAtTheEnd={true}
labelClassName="text-sm"
title={t("limit_booking_only_first_slot")}
description={t("limit_booking_only_first_slot_description")}
checked={isChecked}
onCheckedChange={(active) => {
formMethods.setValue("onlyShowFirstAvailableSlot", active ?? false);
}}
switchContainerClassName={classNames(
"border-subtle mt-6 rounded-lg border py-6 px-4 sm:px-6",
isChecked && "rounded-b-none"
)}
/>
);
}}
/>
<Controller <Controller
name="durationLimits" name="durationLimits"
control={formMethods.control} control={formMethods.control}

View File

@ -156,11 +156,8 @@ const UserProfile = () => {
{t("few_sentences_about_yourself")} {t("few_sentences_about_yourself")}
</p> </p>
</fieldset> </fieldset>
<Button <Button EndIcon={ArrowRight} type="submit" className="mt-8 w-full items-center justify-center">
type="submit"
className="text-inverted mt-8 flex w-full flex-row justify-center rounded-md border border-black bg-black p-2 text-center text-sm">
{t("finish")} {t("finish")}
<ArrowRight className="ml-2 h-4 w-4 self-center" aria-hidden="true" />
</Button> </Button>
</form> </form>
); );

View File

@ -9,7 +9,7 @@ import { FULL_NAME_LENGTH_MAX_LIMIT } from "@calcom/lib/constants";
import { useLocale } from "@calcom/lib/hooks/useLocale"; import { useLocale } from "@calcom/lib/hooks/useLocale";
import { telemetryEventTypes, useTelemetry } from "@calcom/lib/telemetry"; import { telemetryEventTypes, useTelemetry } from "@calcom/lib/telemetry";
import { trpc } from "@calcom/trpc/react"; import { trpc } from "@calcom/trpc/react";
import { Button, TimezoneSelect } from "@calcom/ui"; import { Button, TimezoneSelect, Input } from "@calcom/ui";
import { ArrowRight } from "@calcom/ui/components/icon"; import { ArrowRight } from "@calcom/ui/components/icon";
import { UsernameAvailabilityField } from "@components/ui/UsernameAvailability"; import { UsernameAvailabilityField } from "@components/ui/UsernameAvailability";
@ -76,7 +76,7 @@ const UserSettings = (props: IUserSettingsProps) => {
<label htmlFor="name" className="text-default mb-2 block text-sm font-medium"> <label htmlFor="name" className="text-default mb-2 block text-sm font-medium">
{t("full_name")} {t("full_name")}
</label> </label>
<input <Input
{...register("name", { {...register("name", {
required: true, required: true,
})} })}
@ -85,7 +85,6 @@ const UserSettings = (props: IUserSettingsProps) => {
type="text" type="text"
autoComplete="off" autoComplete="off"
autoCorrect="off" autoCorrect="off"
className="border-default w-full rounded-md border text-sm"
/> />
{errors.name && ( {errors.name && (
<p data-testid="required" className="py-2 text-xs text-red-500"> <p data-testid="required" className="py-2 text-xs text-red-500">
@ -106,7 +105,7 @@ const UserSettings = (props: IUserSettingsProps) => {
className="mt-2 w-full rounded-md text-sm" className="mt-2 w-full rounded-md text-sm"
/> />
<p className="text-subtle dark:text-inverted mt-3 flex flex-row font-sans text-xs leading-tight"> <p className="text-subtle mt-3 flex flex-row font-sans text-xs leading-tight">
{t("current_time")} {dayjs().tz(selectedTimeZone).format("LT").toString().toLowerCase()} {t("current_time")} {dayjs().tz(selectedTimeZone).format("LT").toString().toLowerCase()}
</p> </p>
</div> </div>

View File

@ -101,6 +101,8 @@ export const config = {
"/apps/routing_forms/:path*", "/apps/routing_forms/:path*",
"/event-types", "/event-types",
"/future/event-types/", "/future/event-types/",
"/settings/admin/:path*",
"/future/settings/admin/:path*",
], ],
}; };

View File

@ -154,6 +154,9 @@ const matcherConfigUserTypeEmbedRoute = {
/** @type {import("next").NextConfig} */ /** @type {import("next").NextConfig} */
const nextConfig = { const nextConfig = {
experimental: {
serverComponentsExternalPackages: ["next-i18next"],
},
i18n: { i18n: {
...i18n, ...i18n,
localeDetection: false, localeDetection: false,
@ -231,6 +234,9 @@ const nextConfig = {
...config.resolve.fallback, // if you miss it, all the other options in fallback, specified ...config.resolve.fallback, // if you miss it, all the other options in fallback, specified
// by next.js will be dropped. Doesn't make much sense, but how it is // by next.js will be dropped. Doesn't make much sense, but how it is
fs: false, fs: false,
// ignore module resolve errors caused by the server component bundler
"pg-native": false,
"superagent-proxy": false,
}; };
/** /**

View File

@ -1,6 +1,6 @@
{ {
"name": "@calcom/web", "name": "@calcom/web",
"version": "3.5.3", "version": "3.5.4",
"private": true, "private": true,
"scripts": { "scripts": {
"analyze": "ANALYZE=true next build", "analyze": "ANALYZE=true next build",
@ -130,7 +130,6 @@
"tailwindcss-radix": "^2.6.0", "tailwindcss-radix": "^2.6.0",
"turndown": "^7.1.1", "turndown": "^7.1.1",
"uuid": "^8.3.2", "uuid": "^8.3.2",
"web3": "^1.7.5",
"zod": "^3.22.2" "zod": "^3.22.2"
}, },
"devDependencies": { "devDependencies": {

View File

@ -6,8 +6,18 @@ import { type RequestWithUsernameStatus } from "@calcom/features/auth/signup/use
import { IS_PREMIUM_USERNAME_ENABLED } from "@calcom/lib/constants"; import { IS_PREMIUM_USERNAME_ENABLED } from "@calcom/lib/constants";
import { HttpError } from "@calcom/lib/http-error"; import { HttpError } from "@calcom/lib/http-error";
import logger from "@calcom/lib/logger"; import logger from "@calcom/lib/logger";
import { signupSchema } from "@calcom/prisma/zod-utils";
function ensureSignupIsEnabled(req: RequestWithUsernameStatus) {
const { token } = signupSchema
.pick({
token: true,
})
.parse(req.body);
// Stil allow signups if there is a team invite
if (token) return;
function ensureSignupIsEnabled() {
if (process.env.NEXT_PUBLIC_DISABLE_SIGNUP === "true") { if (process.env.NEXT_PUBLIC_DISABLE_SIGNUP === "true") {
throw new HttpError({ throw new HttpError({
statusCode: 403, statusCode: 403,
@ -29,7 +39,7 @@ export default async function handler(req: RequestWithUsernameStatus, res: NextA
// Use a try catch instead of returning res every time // Use a try catch instead of returning res every time
try { try {
ensureReqIsPost(req); ensureReqIsPost(req);
ensureSignupIsEnabled(); ensureSignupIsEnabled(req);
/** /**
* Im not sure its worth merging these two handlers. They are different enough to be separate. * Im not sure its worth merging these two handlers. They are different enough to be separate.

View File

@ -13,7 +13,7 @@ const handler = async (req: NextApiRequest, res: NextApiResponse) => {
res.setHeader("Content-Type", "text/html"); res.setHeader("Content-Type", "text/html");
res.setHeader("Cache-Control", "no-cache, no-store, private, must-revalidate"); res.setHeader("Cache-Control", "no-cache, no-store, private, must-revalidate");
res.write( res.write(
renderEmail("MonthlyDigestEmail", { await renderEmail("MonthlyDigestEmail", {
language: t, language: t,
Created: 12, Created: 12,
Completed: 13, Completed: 13,

View File

@ -1,24 +1,38 @@
import type { WebhookTriggerEvents } from "@prisma/client"; import type { WebhookTriggerEvents } from "@prisma/client";
import { createHmac } from "crypto";
import type { NextApiRequest, NextApiResponse } from "next"; import type { NextApiRequest, NextApiResponse } from "next";
import { z } from "zod"; import { z } from "zod";
import { DailyLocationType } from "@calcom/app-store/locations"; import { DailyLocationType } from "@calcom/app-store/locations";
import { getDownloadLinkOfCalVideoByRecordingId } from "@calcom/core/videoClient"; import { getDownloadLinkOfCalVideoByRecordingId } from "@calcom/core/videoClient";
import { sendDailyVideoRecordingEmails } from "@calcom/emails"; import { sendDailyVideoRecordingEmails } from "@calcom/emails";
import { getServerSession } from "@calcom/features/auth/lib/getServerSession";
import getWebhooks from "@calcom/features/webhooks/lib/getWebhooks"; import getWebhooks from "@calcom/features/webhooks/lib/getWebhooks";
import sendPayload from "@calcom/features/webhooks/lib/sendPayload"; import sendPayload from "@calcom/features/webhooks/lib/sendPayload";
import { IS_SELF_HOSTED } from "@calcom/lib/constants";
import { getTeamIdFromEventType } from "@calcom/lib/getTeamIdFromEventType"; import { getTeamIdFromEventType } from "@calcom/lib/getTeamIdFromEventType";
import { defaultHandler } from "@calcom/lib/server"; import { defaultHandler } from "@calcom/lib/server";
import { getTranslation } from "@calcom/lib/server/i18n"; import { getTranslation } from "@calcom/lib/server/i18n";
import prisma, { bookingMinimalSelect } from "@calcom/prisma"; import prisma, { bookingMinimalSelect } from "@calcom/prisma";
import type { CalendarEvent } from "@calcom/types/Calendar"; import type { CalendarEvent } from "@calcom/types/Calendar";
const schema = z.object({ const schema = z
recordingId: z.string(), .object({
bookingUID: z.string(), version: z.string(),
}); type: z.string(),
id: z.string(),
payload: z.object({
recording_id: z.string(),
end_ts: z.number(),
room_name: z.string(),
start_ts: z.number(),
status: z.string(),
max_participants: z.number(),
duration: z.number(),
s3_key: z.string(),
}),
event_ts: z.number(),
})
.passthrough();
const downloadLinkSchema = z.object({ const downloadLinkSchema = z.object({
download_link: z.string(), download_link: z.string(),
@ -39,8 +53,8 @@ const triggerWebhook = async ({
}; };
}) => { }) => {
const eventTrigger: WebhookTriggerEvents = "RECORDING_READY"; const eventTrigger: WebhookTriggerEvents = "RECORDING_READY";
// Send Webhook call if hooked to BOOKING.RECORDING_READY
// Send Webhook call if hooked to BOOKING.RECORDING_READY
const triggerForUser = !booking.teamId || (booking.teamId && booking.eventTypeParentId); const triggerForUser = !booking.teamId || (booking.teamId && booking.eventTypeParentId);
const subscriberOptions = { const subscriberOptions = {
@ -62,71 +76,62 @@ const triggerWebhook = async ({
await Promise.all(promises); await Promise.all(promises);
}; };
const checkIfUserIsPartOfTheSameTeam = async ( const testRequestSchema = z.object({
teamId: number | undefined | null, test: z.enum(["test"]),
userId: number, });
userEmail: string | undefined | null
) => {
if (!teamId) return false;
const getUserQuery = () => {
if (!!userEmail) {
return {
OR: [
{
id: userId,
},
{
email: userEmail,
},
],
};
} else {
return {
id: userId,
};
}
};
const team = await prisma.team.findFirst({
where: {
id: teamId,
members: {
some: {
user: getUserQuery(),
},
},
},
});
return !!team;
};
async function handler(req: NextApiRequest, res: NextApiResponse) { async function handler(req: NextApiRequest, res: NextApiResponse) {
if (!process.env.SENDGRID_API_KEY || !process.env.SENDGRID_EMAIL) { if (!process.env.SENDGRID_API_KEY || !process.env.SENDGRID_EMAIL) {
return res.status(405).json({ message: "No SendGrid API key or email" }); return res.status(405).json({ message: "No SendGrid API key or email" });
} }
const response = schema.safeParse(JSON.parse(req.body));
if (!response.success) { if (testRequestSchema.safeParse(req.body).success) {
return res.status(200).json({ message: "Test request successful" });
}
const hmacSecret = process.env.DAILY_WEBHOOK_SECRET;
if (!hmacSecret) {
return res.status(405).json({ message: "No Daily Webhook Secret" });
}
const signature = `${req.headers["x-webhook-timestamp"]}.${JSON.stringify(req.body)}`;
const base64DecodedSecret = Buffer.from(hmacSecret, "base64");
const hmac = createHmac("sha256", base64DecodedSecret);
const computed_signature = hmac.update(signature).digest("base64");
if (req.headers["x-webhook-signature"] !== computed_signature) {
return res.status(403).json({ message: "Signature does not match" });
}
const response = schema.safeParse(req.body);
if (!response.success || response.data.type !== "recording.ready-to-download") {
return res.status(400).send({ return res.status(400).send({
message: "Invalid Payload", message: "Invalid Payload",
}); });
} }
const { recordingId, bookingUID } = response.data; const { room_name, recording_id, status } = response.data.payload;
const session = await getServerSession({ req, res });
if (!session?.user) { if (status !== "finished") {
return res.status(401).send({ return res.status(400).send({
message: "User not logged in", message: "Recording not finished",
}); });
} }
try { try {
const booking = await prisma.booking.findFirst({ const bookingReference = await prisma.bookingReference.findFirst({
where: { type: "daily_video", uid: room_name, meetingId: room_name },
select: { bookingId: true },
});
if (!bookingReference || !bookingReference.bookingId) {
return res.status(404).send({ message: "Booking reference not found" });
}
const booking = await prisma.booking.findUniqueOrThrow({
where: { where: {
uid: bookingUID, id: bookingReference.bookingId,
}, },
select: { select: {
...bookingMinimalSelect, ...bookingMinimalSelect,
@ -153,9 +158,9 @@ async function handler(req: NextApiRequest, res: NextApiResponse) {
}, },
}); });
if (!booking || booking.location !== DailyLocationType) { if (!booking || !(booking.location === DailyLocationType || booking?.location?.trim() === "")) {
return res.status(404).send({ return res.status(404).send({
message: `Booking of uid ${bookingUID} does not exist or does not contain daily video as location`, message: `Booking of room_name ${room_name} does not exist or does not contain daily video as location`,
}); });
} }
@ -175,26 +180,6 @@ async function handler(req: NextApiRequest, res: NextApiResponse) {
const attendeesList = await Promise.all(attendeesListPromises); const attendeesList = await Promise.all(attendeesListPromises);
const isUserAttendeeOrOrganiser =
booking?.user?.id === session.user.id ||
attendeesList.find(
(attendee) => attendee.id === session.user.id || attendee.email === session.user.email
);
if (!isUserAttendeeOrOrganiser) {
const isUserMemberOfTheTeam = checkIfUserIsPartOfTheSameTeam(
booking?.eventType?.teamId,
session.user.id,
session.user.email
);
if (!isUserMemberOfTheTeam) {
return res.status(403).send({
message: "Unauthorised",
});
}
}
await prisma.booking.update({ await prisma.booking.update({
where: { where: {
uid: booking.uid, uid: booking.uid,
@ -204,7 +189,7 @@ async function handler(req: NextApiRequest, res: NextApiResponse) {
}, },
}); });
const response = await getDownloadLinkOfCalVideoByRecordingId(recordingId); const response = await getDownloadLinkOfCalVideoByRecordingId(recording_id);
const downloadLinkResponse = downloadLinkSchema.parse(response); const downloadLinkResponse = downloadLinkSchema.parse(response);
const downloadLink = downloadLinkResponse.download_link; const downloadLink = downloadLinkResponse.download_link;
@ -242,17 +227,11 @@ async function handler(req: NextApiRequest, res: NextApiResponse) {
}, },
}); });
const isSendingEmailsAllowed = IS_SELF_HOSTED || session?.user?.belongsToActiveTeam;
// send emails to all attendees only when user has team plan // send emails to all attendees only when user has team plan
if (isSendingEmailsAllowed) { await sendDailyVideoRecordingEmails(evt, downloadLink);
await sendDailyVideoRecordingEmails(evt, downloadLink); return res.status(200).json({ message: "Success" });
return res.status(200).json({ message: "Success" });
}
return res.status(403).json({ message: "User does not have team plan to send out emails" });
} catch (err) { } catch (err) {
console.warn("Error in /recorded-daily-video", err); console.error("Error in /recorded-daily-video", err);
return res.status(500).json({ message: "something went wrong" }); return res.status(500).json({ message: "something went wrong" });
} }
} }

View File

@ -0,0 +1,92 @@
import type { NextApiRequest, NextApiResponse } from "next";
import type Stripe from "stripe";
import { z } from "zod";
import stripe from "@calcom/features/ee/payments/server/stripe";
import { HttpError } from "@calcom/lib/http-error";
import { defaultHandler, defaultResponder } from "@calcom/lib/server";
import prisma from "@calcom/prisma";
import { MembershipRole } from "@calcom/prisma/enums";
const querySchema = z.object({
session_id: z.string().min(1),
});
const checkoutSessionMetadataSchema = z.object({
teamName: z.string(),
teamSlug: z.string(),
userId: z.string().transform(Number),
});
const generateRandomString = () => {
return Math.random().toString(36).substring(2, 10);
};
async function handler(req: NextApiRequest, res: NextApiResponse) {
const { session_id } = querySchema.parse(req.query);
const checkoutSession = await stripe.checkout.sessions.retrieve(session_id, {
expand: ["subscription"],
});
if (!checkoutSession) throw new HttpError({ statusCode: 404, message: "Checkout session not found" });
const subscription = checkoutSession.subscription as Stripe.Subscription;
if (checkoutSession.payment_status !== "paid")
throw new HttpError({ statusCode: 402, message: "Payment required" });
// Let's query to ensure that the team metadata carried over from the checkout session.
const parseCheckoutSessionMetadata = checkoutSessionMetadataSchema.safeParse(checkoutSession.metadata);
if (!parseCheckoutSessionMetadata.success) {
console.error(
"Team metadata not found in checkout session",
parseCheckoutSessionMetadata.error,
checkoutSession.id
);
}
if (!checkoutSession.metadata?.userId) {
throw new HttpError({
statusCode: 400,
message: "Can't publish team/org without userId",
});
}
const checkoutSessionMetadata = parseCheckoutSessionMetadata.success
? parseCheckoutSessionMetadata.data
: {
teamName: checkoutSession?.metadata?.teamName ?? generateRandomString(),
teamSlug: checkoutSession?.metadata?.teamSlug ?? generateRandomString(),
userId: checkoutSession.metadata.userId,
};
const team = await prisma.team.create({
data: {
name: checkoutSessionMetadata.teamName,
slug: checkoutSessionMetadata.teamSlug,
members: {
create: {
userId: checkoutSessionMetadata.userId as number,
role: MembershipRole.OWNER,
accepted: true,
},
},
metadata: {
paymentId: checkoutSession.id,
subscriptionId: subscription.id || null,
subscriptionItemId: subscription.items.data[0].id || null,
},
},
});
// Sync Services: Close.com
// closeComUpdateTeam(prevTeam, team);
// redirect to team screen
res.redirect(302, `/settings/teams/${team.id}/onboard-members?event=team_created`);
}
export default defaultHandler({
GET: Promise.resolve({ default: defaultResponder(handler) }),
});

View File

@ -54,8 +54,8 @@ const IntegrationsContainer = ({
automation: Share2, automation: Share2,
analytics: BarChart, analytics: BarChart,
payment: CreditCard, payment: CreditCard,
web3: BarChart, // deprecated
other: Grid, other: Grid,
web3: CreditCard, // deprecated
video: Video, // deprecated video: Video, // deprecated
messaging: Mail, messaging: Mail,
crm: Contact, crm: Contact,

View File

@ -5,7 +5,6 @@ import type { GetServerSidePropsContext } from "next";
import { getCsrfToken, signIn } from "next-auth/react"; import { getCsrfToken, signIn } from "next-auth/react";
import Link from "next/link"; import Link from "next/link";
import { useRouter } from "next/navigation"; import { useRouter } from "next/navigation";
import type { CSSProperties } from "react";
import { useState } from "react"; import { useState } from "react";
import { FormProvider, useForm } from "react-hook-form"; import { FormProvider, useForm } from "react-hook-form";
import { FaGoogle } from "react-icons/fa"; import { FaGoogle } from "react-icons/fa";
@ -174,15 +173,7 @@ inferSSRProps<typeof _getServerSideProps> & WithNonceProps<{}>) {
: isSAMLLoginEnabled && !isLoading && data?.connectionExists; : isSAMLLoginEnabled && !isLoading && data?.connectionExists;
return ( return (
<div <div className="dark:bg-brand dark:text-brand-contrast text-emphasis min-h-screen [--cal-brand-emphasis:#101010] [--cal-brand-subtle:9CA3AF] [--cal-brand-text:white] [--cal-brand:#111827] dark:[--cal-brand-emphasis:#e1e1e1] dark:[--cal-brand-text:black] dark:[--cal-brand:white]">
style={
{
"--cal-brand": "#111827",
"--cal-brand-emphasis": "#101010",
"--cal-brand-text": "white",
"--cal-brand-subtle": "#9CA3AF",
} as CSSProperties
}>
<AuthContainer <AuthContainer
title={t("login")} title={t("login")}
description={t("login")} description={t("login")}
@ -238,7 +229,7 @@ inferSSRProps<typeof _getServerSideProps> & WithNonceProps<{}>) {
type="submit" type="submit"
color="primary" color="primary"
disabled={formState.isSubmitting} disabled={formState.isSubmitting}
className="w-full justify-center dark:bg-white dark:text-black"> className="w-full justify-center">
{twoFactorRequired ? t("submit") : t("sign_in")} {twoFactorRequired ? t("submit") : t("sign_in")}
</Button> </Button>
</div> </div>
@ -251,6 +242,7 @@ inferSSRProps<typeof _getServerSideProps> & WithNonceProps<{}>) {
<Button <Button
color="secondary" color="secondary"
className="w-full justify-center" className="w-full justify-center"
disabled={formState.isSubmitting}
data-testid="google" data-testid="google"
StartIcon={FaGoogle} StartIcon={FaGoogle}
onClick={async (e) => { onClick={async (e) => {

View File

@ -1244,6 +1244,7 @@ async function getRecurringBookings(recurringEventId: string | null) {
const recurringBookings = await prisma.booking.findMany({ const recurringBookings = await prisma.booking.findMany({
where: { where: {
recurringEventId, recurringEventId,
status: BookingStatus.ACCEPTED,
}, },
select: { select: {
startTime: true, startTime: true,

View File

@ -131,6 +131,7 @@ export type FormValues = {
successRedirectUrl: string; successRedirectUrl: string;
durationLimits?: IntervalLimit; durationLimits?: IntervalLimit;
bookingLimits?: IntervalLimit; bookingLimits?: IntervalLimit;
onlyShowFirstAvailableSlot: boolean;
children: ChildrenEventType[]; children: ChildrenEventType[];
hosts: { userId: number; isFixed: boolean }[]; hosts: { userId: number; isFixed: boolean }[];
bookingFields: z.infer<typeof eventTypeBookingFields>; bookingFields: z.infer<typeof eventTypeBookingFields>;
@ -250,6 +251,7 @@ const EventTypePage = (props: EventTypeSetupProps) => {
description: eventType.description ?? undefined, description: eventType.description ?? undefined,
schedule: eventType.schedule || undefined, schedule: eventType.schedule || undefined,
bookingLimits: eventType.bookingLimits || undefined, bookingLimits: eventType.bookingLimits || undefined,
onlyShowFirstAvailableSlot: eventType.onlyShowFirstAvailableSlot || undefined,
durationLimits: eventType.durationLimits || undefined, durationLimits: eventType.durationLimits || undefined,
length: eventType.length, length: eventType.length,
hidden: eventType.hidden, hidden: eventType.hidden,
@ -429,6 +431,7 @@ const EventTypePage = (props: EventTypeSetupProps) => {
seatsShowAttendees, seatsShowAttendees,
seatsShowAvailabilityCount, seatsShowAvailabilityCount,
bookingLimits, bookingLimits,
onlyShowFirstAvailableSlot,
durationLimits, durationLimits,
recurringEvent, recurringEvent,
locations, locations,
@ -491,6 +494,7 @@ const EventTypePage = (props: EventTypeSetupProps) => {
beforeEventBuffer: beforeBufferTime, beforeEventBuffer: beforeBufferTime,
afterEventBuffer: afterBufferTime, afterEventBuffer: afterBufferTime,
bookingLimits, bookingLimits,
onlyShowFirstAvailableSlot,
durationLimits, durationLimits,
seatsPerTimeSlot, seatsPerTimeSlot,
seatsShowAttendees, seatsShowAttendees,
@ -532,6 +536,7 @@ const EventTypePage = (props: EventTypeSetupProps) => {
seatsShowAttendees, seatsShowAttendees,
seatsShowAvailabilityCount, seatsShowAvailabilityCount,
bookingLimits, bookingLimits,
onlyShowFirstAvailableSlot,
durationLimits, durationLimits,
recurringEvent, recurringEvent,
locations, locations,
@ -584,6 +589,7 @@ const EventTypePage = (props: EventTypeSetupProps) => {
beforeEventBuffer: beforeBufferTime, beforeEventBuffer: beforeBufferTime,
afterEventBuffer: afterBufferTime, afterEventBuffer: afterBufferTime,
bookingLimits, bookingLimits,
onlyShowFirstAvailableSlot,
durationLimits, durationLimits,
seatsPerTimeSlot, seatsPerTimeSlot,
seatsShowAttendees, seatsShowAttendees,

View File

@ -2,7 +2,6 @@ import type { GetServerSidePropsContext } from "next";
import { serverSideTranslations } from "next-i18next/serverSideTranslations"; import { serverSideTranslations } from "next-i18next/serverSideTranslations";
import Head from "next/head"; import Head from "next/head";
import { usePathname, useRouter } from "next/navigation"; import { usePathname, useRouter } from "next/navigation";
import type { CSSProperties } from "react";
import { Suspense } from "react"; import { Suspense } from "react";
import { z } from "zod"; import { z } from "zod";
@ -106,16 +105,8 @@ const OnboardingPage = () => {
return ( return (
<div <div
className="dark:bg-brand dark:text-brand-contrast text-emphasis min-h-screen" className="dark:bg-brand dark:text-brand-contrast text-emphasis min-h-screen [--cal-brand-emphasis:#101010] [--cal-brand-subtle:9CA3AF] [--cal-brand:#111827] [--cal-brand-text:#FFFFFF] dark:[--cal-brand-emphasis:#e1e1e1] dark:[--cal-brand:white] dark:[--cal-brand-text:#000000]"
data-testid="onboarding" data-testid="onboarding"
style={
{
"--cal-brand": "#111827",
"--cal-brand-emphasis": "#101010",
"--cal-brand-text": "white",
"--cal-brand-subtle": "#9CA3AF",
} as CSSProperties
}
key={pathname}> key={pathname}>
<Head> <Head>
<title>{`${APP_NAME} - ${t("getting_started")}`}</title> <title>{`${APP_NAME} - ${t("getting_started")}`}</title>
@ -231,7 +222,6 @@ export const getServerSideProps = async (context: GetServerSidePropsContext) =>
}; };
}; };
OnboardingPage.isThemeSupported = false;
OnboardingPage.PageWrapper = PageWrapper; OnboardingPage.PageWrapper = PageWrapper;
export default OnboardingPage; export default OnboardingPage;

View File

@ -1,3 +1,5 @@
"use client";
import AdminAppsList from "@calcom/features/apps/AdminAppsList"; import AdminAppsList from "@calcom/features/apps/AdminAppsList";
import { useLocale } from "@calcom/lib/hooks/useLocale"; import { useLocale } from "@calcom/lib/hooks/useLocale";
import { Meta } from "@calcom/ui"; import { Meta } from "@calcom/ui";

View File

@ -1 +1,2 @@
"use client";
export { default } from "./[category]"; export { default } from "./[category]";

View File

@ -1,3 +1,5 @@
"use client";
import { FlagListingView } from "@calcom/features/flags/pages/flag-listing-view"; import { FlagListingView } from "@calcom/features/flags/pages/flag-listing-view";
import PageWrapper from "@components/PageWrapper"; import PageWrapper from "@components/PageWrapper";

View File

@ -1,3 +1,5 @@
"use client";
import { signIn } from "next-auth/react"; import { signIn } from "next-auth/react";
import { useRef } from "react"; import { useRef } from "react";

View File

@ -1,3 +1,5 @@
"use client";
import { Meta } from "@calcom/ui"; import { Meta } from "@calcom/ui";
import PageWrapper from "@components/PageWrapper"; import PageWrapper from "@components/PageWrapper";

View File

@ -1,3 +1,5 @@
"use client";
import PageWrapper from "@components/PageWrapper"; import PageWrapper from "@components/PageWrapper";
import { getLayout } from "@components/auth/layouts/AdminLayout"; import { getLayout } from "@components/auth/layouts/AdminLayout";

View File

@ -1,3 +1,5 @@
"use client";
import { useState } from "react"; import { useState } from "react";
import { useForm } from "react-hook-form"; import { useForm } from "react-hook-form";

View File

@ -4,7 +4,6 @@ import type { GetServerSidePropsContext } from "next";
import { signIn } from "next-auth/react"; import { signIn } from "next-auth/react";
import Link from "next/link"; import Link from "next/link";
import { useRouter } from "next/navigation"; import { useRouter } from "next/navigation";
import type { CSSProperties } from "react";
import { useState, useEffect } from "react"; import { useState, useEffect } from "react";
import type { SubmitHandler } from "react-hook-form"; import type { SubmitHandler } from "react-hook-form";
import { useForm, useFormContext } from "react-hook-form"; import { useForm, useFormContext } from "react-hook-form";
@ -122,21 +121,23 @@ function UsernameField({
/> />
{(!formState.isSubmitting || !formState.isSubmitted) && ( {(!formState.isSubmitting || !formState.isSubmitted) && (
<div className="text-gray text-default flex items-center text-sm"> <div className="text-gray text-default flex items-center text-sm">
<p className="flex items-center text-sm "> <div className="text-sm ">
{usernameTaken ? ( {usernameTaken ? (
<div className="text-error"> <div className="text-error flex items-center">
<Info className="mr-1 inline-block h-4 w-4" /> <Info className="mr-1 inline-block h-4 w-4" />
{t("already_in_use_error")} <p>{t("already_in_use_error")}</p>
</div> </div>
) : premium ? ( ) : premium ? (
<div data-testid="premium-username-warning"> <div data-testid="premium-username-warning" className="flex items-center">
<StarIcon className="mr-1 inline-block h-4 w-4" /> <StarIcon className="mr-1 inline-block h-4 w-4" />
{t("premium_username", { <p>
price: getPremiumPlanPriceValue(), {t("premium_username", {
})} price: getPremiumPlanPriceValue(),
})}
</p>
</div> </div>
) : null} ) : null}
</p> </div>
</div> </div>
)} )}
</div> </div>
@ -161,6 +162,7 @@ export default function Signup({
}: SignupProps) { }: SignupProps) {
const [premiumUsername, setPremiumUsername] = useState(false); const [premiumUsername, setPremiumUsername] = useState(false);
const [usernameTaken, setUsernameTaken] = useState(false); const [usernameTaken, setUsernameTaken] = useState(false);
const [isGoogleLoading, setIsGoogleLoading] = useState(false);
const searchParams = useCompatSearchParams(); const searchParams = useCompatSearchParams();
const telemetry = useTelemetry(); const telemetry = useTelemetry();
@ -235,31 +237,23 @@ export default function Signup({
}; };
return ( return (
<div <div className="light bg-muted 2xl:bg-default flex min-h-screen w-full flex-col items-center justify-center [--cal-brand-emphasis:#101010] [--cal-brand:#111827] [--cal-brand-text:#FFFFFF] [--cal-brand-subtle:#9CA3AF] dark:[--cal-brand-emphasis:#e1e1e1] dark:[--cal-brand:white] dark:[--cal-brand-text:#000000]">
className="light bg-muted 2xl:bg-default flex min-h-screen w-full flex-col items-center justify-center" <div className="bg-muted 2xl:border-subtle grid w-full max-w-[1440px] grid-cols-1 grid-rows-1 overflow-hidden lg:grid-cols-2 2xl:rounded-[20px] 2xl:border 2xl:py-6">
style={
{
"--cal-brand": "#111827",
"--cal-brand-emphasis": "#101010",
"--cal-brand-text": "white",
"--cal-brand-subtle": "#9CA3AF",
} as CSSProperties
}>
<div className="bg-muted 2xl:border-subtle grid max-h-[800px] w-full max-w-[1440px] grid-cols-1 grid-rows-1 lg:grid-cols-2 2xl:rounded-lg 2xl:border ">
<HeadSeo title={t("sign_up")} description={t("sign_up")} /> <HeadSeo title={t("sign_up")} description={t("sign_up")} />
<div className="flex w-full flex-col px-4 py-6 sm:px-16 md:px-24 2xl:px-28"> {/* Left side */}
<div className="flex w-full flex-col px-4 pt-6 sm:px-16 md:px-20 2xl:px-28">
{/* Header */} {/* Header */}
{errors.apiError && ( {errors.apiError && (
<Alert severity="error" message={errors.apiError?.message} data-testid="signup-error-message" /> <Alert severity="error" message={errors.apiError?.message} data-testid="signup-error-message" />
)} )}
<div className="flex flex-col gap-1"> <div className="flex flex-col gap-2">
<h1 className="font-cal text-[28px] "> <h1 className="font-cal text-[28px] leading-none ">
{IS_CALCOM ? t("create_your_calcom_account") : t("create_your_account")} {IS_CALCOM ? t("create_your_calcom_account") : t("create_your_account")}
</h1> </h1>
{IS_CALCOM ? ( {IS_CALCOM ? (
<p className="text-subtle text-base font-medium leading-6">{t("cal_signup_description")}</p> <p className="text-subtle text-base font-medium leading-5">{t("cal_signup_description")}</p>
) : ( ) : (
<p className="text-subtle text-base font-medium leading-6"> <p className="text-subtle text-base font-medium leading-5">
{t("calcom_explained", { {t("calcom_explained", {
appName: APP_NAME, appName: APP_NAME,
})} })}
@ -318,6 +312,9 @@ export default function Signup({
disabled={ disabled={
!!formMethods.formState.errors.username || !!formMethods.formState.errors.username ||
!!formMethods.formState.errors.email || !!formMethods.formState.errors.email ||
!formMethods.getValues("email") ||
!formMethods.getValues("password") ||
isSubmitting ||
usernameTaken usernameTaken
}> }>
{premiumUsername && !usernameTaken {premiumUsername && !usernameTaken
@ -344,11 +341,25 @@ export default function Signup({
<Button <Button
color="secondary" color="secondary"
disabled={!!formMethods.formState.errors.username || premiumUsername} disabled={!!formMethods.formState.errors.username || premiumUsername}
loading={isGoogleLoading}
StartIcon={() => (
<>
<img
className={classNames(
"text-subtle mr-2 h-4 w-4 dark:invert",
premiumUsername && "opacity-50"
)}
src="/google-icon.svg"
alt=""
/>
</>
)}
className={classNames( className={classNames(
"w-full justify-center rounded-md text-center", "w-full justify-center rounded-md text-center",
formMethods.formState.errors.username ? "opacity-50" : "" formMethods.formState.errors.username ? "opacity-50" : ""
)} )}
onClick={async () => { onClick={async () => {
setIsGoogleLoading(true);
const username = formMethods.getValues("username"); const username = formMethods.getValues("username");
const baseUrl = process.env.NEXT_PUBLIC_WEBAPP_URL; const baseUrl = process.env.NEXT_PUBLIC_WEBAPP_URL;
const GOOGLE_AUTH_URL = `${baseUrl}/auth/sso/google`; const GOOGLE_AUTH_URL = `${baseUrl}/auth/sso/google`;
@ -362,11 +373,6 @@ export default function Signup({
} }
router.push(GOOGLE_AUTH_URL); router.push(GOOGLE_AUTH_URL);
}}> }}>
<img
className={classNames("text-emphasis mr-2 h-5 w-5", premiumUsername && "opacity-50")}
src="/google-icon.svg"
alt=""
/>
Google Google
</Button> </Button>
) : null} ) : null}
@ -376,7 +382,9 @@ export default function Signup({
disabled={ disabled={
!!formMethods.formState.errors.username || !!formMethods.formState.errors.username ||
!!formMethods.formState.errors.email || !!formMethods.formState.errors.email ||
premiumUsername premiumUsername ||
isSubmitting ||
isGoogleLoading
} }
className={classNames( className={classNames(
"w-full justify-center rounded-md text-center", "w-full justify-center rounded-md text-center",
@ -390,6 +398,7 @@ export default function Signup({
} }
if (!formMethods.getValues("email")) { if (!formMethods.getValues("email")) {
formMethods.trigger("email"); formMethods.trigger("email");
return; return;
} }
const username = formMethods.getValues("username"); const username = formMethods.getValues("username");
@ -410,17 +419,20 @@ export default function Signup({
)} )}
</div> </div>
{/* Already have an account & T&C */} {/* Already have an account & T&C */}
<div className="mt-6"> <div className="mt-10 flex h-full flex-col justify-end text-xs">
<div className="flex flex-col text-sm"> <div className="flex flex-col text-sm">
<Link href="/auth/login" className="text-emphasis hover:underline"> <div className="flex gap-1">
{t("already_have_account")} <p className="text-subtle">{t("already_have_account")}</p>
</Link> <Link href="/auth/login" className="text-emphasis hover:underline">
{t("sign_in")}
</Link>
</div>
<div className="text-subtle"> <div className="text-subtle">
By signing up, you agree to our{" "} By signing up, you agree to our{" "}
<Link className="text-emphasis hover:underline" href={`${WEBSITE_URL}/terms`}> <Link className="text-emphasis hover:underline" href={`${WEBSITE_URL}/terms`}>
Terms of Service{" "} Terms{" "}
</Link> </Link>
<span>and</span>{" "} <span>&</span>{" "}
<Link className="text-emphasis hover:underline" href={`${WEBSITE_URL}/privacy`}> <Link className="text-emphasis hover:underline" href={`${WEBSITE_URL}/privacy`}>
Privacy Policy. Privacy Policy.
</Link> </Link>
@ -428,7 +440,12 @@ export default function Signup({
</div> </div>
</div> </div>
</div> </div>
<div className="bg-subtle border-subtle hidden w-full flex-col justify-between rounded-l-2xl py-12 pl-12 lg:flex"> <div
className="border-subtle hidden w-full flex-col justify-between rounded-l-2xl border py-12 pl-12 lg:flex"
style={{
background:
"radial-gradient(162.05% 170% at 109.58% 35%, rgba(102, 117, 147, 0.7) 0%, rgba(212, 212, 213, 0.4) 100%) ",
}}>
{IS_CALCOM && ( {IS_CALCOM && (
<div className="mb-12 mr-12 grid h-full w-full grid-cols-4 gap-4 "> <div className="mb-12 mr-12 grid h-full w-full grid-cols-4 gap-4 ">
<div className=""> <div className="">
@ -443,7 +460,7 @@ export default function Signup({
</div> </div>
)} )}
<div <div
className="rounded-2xl border-y border-l border-dashed border-[#D1D5DB5A] py-[6px] pl-[6px]" className="border-default rounded-bl-2xl rounded-br-none rounded-tl-2xl border-dashed py-[6px] pl-[6px]"
style={{ style={{
backgroundColor: "rgba(236,237,239,0.9)", backgroundColor: "rgba(236,237,239,0.9)",
}}> }}>
@ -502,7 +519,7 @@ export const getServerSideProps = async (ctx: GetServerSidePropsContext) => {
// username + email prepopulated from query params // username + email prepopulated from query params
const { username: preFillusername, email: prefilEmail } = querySchema.parse(ctx.query); const { username: preFillusername, email: prefilEmail } = querySchema.parse(ctx.query);
if (process.env.NEXT_PUBLIC_DISABLE_SIGNUP === "true" || flags["disable-signup"]) { if ((process.env.NEXT_PUBLIC_DISABLE_SIGNUP === "true" && !token) || flags["disable-signup"]) {
return { return {
notFound: true, notFound: true,
}; };
@ -625,5 +642,4 @@ export const getServerSideProps = async (ctx: GetServerSidePropsContext) => {
}; };
}; };
Signup.isThemeSupported = false;
Signup.PageWrapper = PageWrapper; Signup.PageWrapper = PageWrapper;

View File

@ -1,10 +1,8 @@
import type { DailyEventObjectRecordingStarted } from "@daily-co/daily-js";
import DailyIframe from "@daily-co/daily-js"; import DailyIframe from "@daily-co/daily-js";
import MarkdownIt from "markdown-it"; import MarkdownIt from "markdown-it";
import type { GetServerSidePropsContext } from "next"; import type { GetServerSidePropsContext } from "next";
import Head from "next/head"; import Head from "next/head";
import { useState, useEffect, useRef } from "react"; import { useState, useEffect, useRef } from "react";
import z from "zod";
import dayjs from "@calcom/dayjs"; import dayjs from "@calcom/dayjs";
import { getServerSession } from "@calcom/features/auth/lib/getServerSession"; import { getServerSession } from "@calcom/features/auth/lib/getServerSession";
@ -21,19 +19,12 @@ import PageWrapper from "@components/PageWrapper";
import { ssrInit } from "@server/lib/ssr"; import { ssrInit } from "@server/lib/ssr";
const recordingStartedEventResponse = z
.object({
recordingId: z.string(),
})
.passthrough();
export type JoinCallPageProps = inferSSRProps<typeof getServerSideProps>; export type JoinCallPageProps = inferSSRProps<typeof getServerSideProps>;
const md = new MarkdownIt("default", { html: true, breaks: true, linkify: true }); const md = new MarkdownIt("default", { html: true, breaks: true, linkify: true });
export default function JoinCall(props: JoinCallPageProps) { export default function JoinCall(props: JoinCallPageProps) {
const { t } = useLocale(); const { t } = useLocale();
const { meetingUrl, meetingPassword, booking } = props; const { meetingUrl, meetingPassword, booking } = props;
const recordingId = useRef<string | null>(null);
useEffect(() => { useEffect(() => {
const callFrame = DailyIframe.createFrame({ const callFrame = DailyIframe.createFrame({
@ -61,31 +52,12 @@ export default function JoinCall(props: JoinCallPageProps) {
...(typeof meetingPassword === "string" && { token: meetingPassword }), ...(typeof meetingPassword === "string" && { token: meetingPassword }),
}); });
callFrame.join(); callFrame.join();
callFrame.on("recording-started", onRecordingStarted).on("recording-stopped", onRecordingStopped);
return () => { return () => {
callFrame.destroy(); callFrame.destroy();
}; };
// eslint-disable-next-line react-hooks/exhaustive-deps // eslint-disable-next-line react-hooks/exhaustive-deps
}, []); }, []);
const onRecordingStopped = () => {
const data = { recordingId: recordingId.current, bookingUID: booking.uid };
fetch("/api/recorded-daily-video", {
method: "POST",
body: JSON.stringify(data),
}).catch((err) => {
console.log(err);
});
recordingId.current = null;
};
const onRecordingStarted = (event?: DailyEventObjectRecordingStarted | undefined) => {
const response = recordingStartedEventResponse.parse(event);
recordingId.current = response.recordingId;
};
const title = `${APP_NAME} Video`; const title = `${APP_NAME} Video`;
return ( return (
<> <>

View File

@ -24,7 +24,7 @@ test("Can reset forgotten password", async ({ page, users }) => {
// there should be one, otherwise we throw // there should be one, otherwise we throw
const { id } = await prisma.resetPasswordRequest.findFirstOrThrow({ const { id } = await prisma.resetPasswordRequest.findFirstOrThrow({
where: { where: {
email: `${user.username}@example.com`, email: user.email,
}, },
select: { select: {
id: true, id: true,
@ -37,7 +37,7 @@ test("Can reset forgotten password", async ({ page, users }) => {
// Test when a user changes his email after starting the password reset flow // Test when a user changes his email after starting the password reset flow
await prisma.user.update({ await prisma.user.update({
where: { where: {
email: `${user.username}@example.com`, email: user.email,
}, },
data: { data: {
email: `${user.username}-2@example.com`, email: `${user.username}-2@example.com`,
@ -54,7 +54,7 @@ test("Can reset forgotten password", async ({ page, users }) => {
email: `${user.username}-2@example.com`, email: `${user.username}-2@example.com`,
}, },
data: { data: {
email: `${user.username}@example.com`, email: user.email,
}, },
}); });
@ -75,7 +75,7 @@ test("Can reset forgotten password", async ({ page, users }) => {
// we're not logging in to the UI to speed up test performance. // we're not logging in to the UI to speed up test performance.
const updatedUser = await prisma.user.findUniqueOrThrow({ const updatedUser = await prisma.user.findUniqueOrThrow({
where: { where: {
email: `${user.username}@example.com`, email: user.email,
}, },
select: { select: {
id: true, id: true,
@ -84,10 +84,10 @@ test("Can reset forgotten password", async ({ page, users }) => {
}); });
// eslint-disable-next-line @typescript-eslint/no-non-null-assertion // eslint-disable-next-line @typescript-eslint/no-non-null-assertion
await expect(await verifyPassword(newPassword, updatedUser.password!)).toBeTruthy(); expect(await verifyPassword(newPassword, updatedUser.password!)).toBeTruthy();
// finally, make sure the same URL cannot be used to reset the password again, as it should be expired. // finally, make sure the same URL cannot be used to reset the password again, as it should be expired.
await page.goto(`/auth/forgot-password/${id}`); await page.goto(`/auth/forgot-password/${id}`);
await page.waitForSelector("text=That request is expired."); await expect(page.locator(`text=Whoops`)).toBeVisible();
}); });

View File

@ -673,8 +673,8 @@ export async function login(
await passwordLocator.fill(user.password ?? user.username!); await passwordLocator.fill(user.password ?? user.username!);
await signInLocator.click(); await signInLocator.click();
// Moving away from waiting 2 seconds, as it is not a reliable way to expect session to be started // waiting for specific login request to resolve
await page.waitForLoadState("networkidle"); await page.waitForResponse(/\/api\/auth\/callback\/credentials/);
} }
export async function apiLogin( export async function apiLogin(

View File

@ -328,3 +328,11 @@ export function generateTotpCode(email: string) {
totp.options = { step: 90 }; totp.options = { step: 90 };
return totp.generate(secret); return totp.generate(secret);
} }
export async function fillStripeTestCheckout(page: Page) {
await page.fill("[name=cardNumber]", "4242424242424242");
await page.fill("[name=cardExpiry]", "12/30");
await page.fill("[name=cardCvc]", "111");
await page.fill("[name=billingName]", "Stripe Stripeson");
await page.click(".SubmitButton--complete-Shimmer");
}

View File

@ -11,7 +11,8 @@ test.describe("unauthorized user sees correct translations (de)", async () => {
test("should use correct translations and html attributes", async ({ page }) => { test("should use correct translations and html attributes", async ({ page }) => {
await page.goto("/"); await page.goto("/");
await page.waitForLoadState("load"); // we dont need to wait for styles and images, only for dom
await page.waitForLoadState("domcontentloaded");
await page.locator("html[lang=de]").waitFor({ state: "attached" }); await page.locator("html[lang=de]").waitFor({ state: "attached" });
await page.locator("html[dir=ltr]").waitFor({ state: "attached" }); await page.locator("html[dir=ltr]").waitFor({ state: "attached" });
@ -35,7 +36,7 @@ test.describe("unauthorized user sees correct translations (ar)", async () => {
test("should use correct translations and html attributes", async ({ page }) => { test("should use correct translations and html attributes", async ({ page }) => {
await page.goto("/"); await page.goto("/");
await page.waitForLoadState("load"); await page.waitForLoadState("domcontentloaded");
await page.locator("html[lang=ar]").waitFor({ state: "attached" }); await page.locator("html[lang=ar]").waitFor({ state: "attached" });
await page.locator("html[dir=rtl]").waitFor({ state: "attached" }); await page.locator("html[dir=rtl]").waitFor({ state: "attached" });
@ -59,7 +60,7 @@ test.describe("unauthorized user sees correct translations (zh)", async () => {
test("should use correct translations and html attributes", async ({ page }) => { test("should use correct translations and html attributes", async ({ page }) => {
await page.goto("/"); await page.goto("/");
await page.waitForLoadState("load"); await page.waitForLoadState("domcontentloaded");
await page.locator("html[lang=zh]").waitFor({ state: "attached" }); await page.locator("html[lang=zh]").waitFor({ state: "attached" });
await page.locator("html[dir=ltr]").waitFor({ state: "attached" }); await page.locator("html[dir=ltr]").waitFor({ state: "attached" });
@ -83,7 +84,7 @@ test.describe("unauthorized user sees correct translations (zh-CN)", async () =>
test("should use correct translations and html attributes", async ({ page }) => { test("should use correct translations and html attributes", async ({ page }) => {
await page.goto("/"); await page.goto("/");
await page.waitForLoadState("load"); await page.waitForLoadState("domcontentloaded");
await page.locator("html[lang=zh-CN]").waitFor({ state: "attached" }); await page.locator("html[lang=zh-CN]").waitFor({ state: "attached" });
await page.locator("html[dir=ltr]").waitFor({ state: "attached" }); await page.locator("html[dir=ltr]").waitFor({ state: "attached" });
@ -107,7 +108,7 @@ test.describe("unauthorized user sees correct translations (zh-TW)", async () =>
test("should use correct translations and html attributes", async ({ page }) => { test("should use correct translations and html attributes", async ({ page }) => {
await page.goto("/"); await page.goto("/");
await page.waitForLoadState("load"); await page.waitForLoadState("domcontentloaded");
await page.locator("html[lang=zh-TW]").waitFor({ state: "attached" }); await page.locator("html[lang=zh-TW]").waitFor({ state: "attached" });
await page.locator("html[dir=ltr]").waitFor({ state: "attached" }); await page.locator("html[dir=ltr]").waitFor({ state: "attached" });
@ -131,7 +132,7 @@ test.describe("unauthorized user sees correct translations (pt)", async () => {
test("should use correct translations and html attributes", async ({ page }) => { test("should use correct translations and html attributes", async ({ page }) => {
await page.goto("/"); await page.goto("/");
await page.waitForLoadState("load"); await page.waitForLoadState("domcontentloaded");
await page.locator("html[lang=pt]").waitFor({ state: "attached" }); await page.locator("html[lang=pt]").waitFor({ state: "attached" });
await page.locator("html[dir=ltr]").waitFor({ state: "attached" }); await page.locator("html[dir=ltr]").waitFor({ state: "attached" });
@ -155,7 +156,7 @@ test.describe("unauthorized user sees correct translations (pt-br)", async () =>
test("should use correct translations and html attributes", async ({ page }) => { test("should use correct translations and html attributes", async ({ page }) => {
await page.goto("/"); await page.goto("/");
await page.waitForLoadState("load"); await page.waitForLoadState("domcontentloaded");
await page.locator("html[lang=pt-BR]").waitFor({ state: "attached" }); await page.locator("html[lang=pt-BR]").waitFor({ state: "attached" });
await page.locator("html[dir=ltr]").waitFor({ state: "attached" }); await page.locator("html[dir=ltr]").waitFor({ state: "attached" });
@ -179,7 +180,7 @@ test.describe("unauthorized user sees correct translations (es-419)", async () =
test("should use correct translations and html attributes", async ({ page }) => { test("should use correct translations and html attributes", async ({ page }) => {
await page.goto("/"); await page.goto("/");
await page.waitForLoadState("load"); await page.waitForLoadState("domcontentloaded");
// es-419 is disabled in i18n config, so es should be used as fallback // es-419 is disabled in i18n config, so es should be used as fallback
await page.locator("html[lang=es]").waitFor({ state: "attached" }); await page.locator("html[lang=es]").waitFor({ state: "attached" });
@ -213,57 +214,61 @@ test.describe("authorized user sees correct translations (de)", async () => {
await test.step("should navigate to /event-types and show German translations", async () => { await test.step("should navigate to /event-types and show German translations", async () => {
await page.goto("/event-types"); await page.goto("/event-types");
await page.waitForLoadState("networkidle"); await page.waitForLoadState("domcontentloaded");
await page.locator("html[lang=de]").waitFor({ state: "attached" }); await page.locator("html[lang=de]").waitFor({ state: "attached" });
await page.locator("html[dir=ltr]").waitFor({ state: "attached" }); await page.locator("html[dir=ltr]").waitFor({ state: "attached" });
{ {
const locator = page.getByText("Ereignistypen", { exact: true }); const locator = page.getByRole("heading", { name: "Ereignistypen", exact: true });
expect(await locator.count()).toBeGreaterThanOrEqual(1); // locator.count() does not wait for elements
// but event-types page is client side, so it takes some time to render html
// thats why we need to use method that awaits for the element
// https://github.com/microsoft/playwright/issues/14278#issuecomment-1131754679
await expect(locator).toHaveCount(1);
} }
{ {
const locator = page.getByText("Event Types", { exact: true }); const locator = page.getByText("Event Types", { exact: true });
expect(await locator.count()).toEqual(0); await expect(locator).toHaveCount(0);
} }
}); });
await test.step("should navigate to /bookings and show German translations", async () => { await test.step("should navigate to /bookings and show German translations", async () => {
await page.goto("/bookings"); await page.goto("/bookings");
await page.waitForLoadState("networkidle"); await page.waitForLoadState("domcontentloaded");
await page.locator("html[lang=de]").waitFor({ state: "attached" }); await page.locator("html[lang=de]").waitFor({ state: "attached" });
await page.locator("html[dir=ltr]").waitFor({ state: "attached" }); await page.locator("html[dir=ltr]").waitFor({ state: "attached" });
{ {
const locator = page.getByText("Buchungen", { exact: true }); const locator = page.getByRole("heading", { name: "Buchungen", exact: true });
expect(await locator.count()).toBeGreaterThanOrEqual(1); await expect(locator).toHaveCount(1);
} }
{ {
const locator = page.getByText("Bookings", { exact: true }); const locator = page.getByText("Bookings", { exact: true });
expect(await locator.count()).toEqual(0); await expect(locator).toHaveCount(0);
} }
}); });
await test.step("should reload the /bookings and show German translations", async () => { await test.step("should reload the /bookings and show German translations", async () => {
await page.reload(); await page.reload();
await page.waitForLoadState("networkidle"); await page.waitForLoadState("domcontentloaded");
await page.locator("html[lang=de]").waitFor({ state: "attached" }); await page.locator("html[lang=de]").waitFor({ state: "attached" });
await page.locator("html[dir=ltr]").waitFor({ state: "attached" }); await page.locator("html[dir=ltr]").waitFor({ state: "attached" });
{ {
const locator = page.getByText("Buchungen", { exact: true }); const locator = page.getByRole("heading", { name: "Buchungen", exact: true });
expect(await locator.count()).toBeGreaterThanOrEqual(1); await expect(locator).toHaveCount(1);
} }
{ {
const locator = page.getByText("Bookings", { exact: true }); const locator = page.getByText("Bookings", { exact: true });
expect(await locator.count()).toEqual(0); await expect(locator).toHaveCount(0);
} }
}); });
}); });
@ -285,57 +290,57 @@ test.describe("authorized user sees correct translations (pt-br)", async () => {
await test.step("should navigate to /event-types and show Brazil-Portuguese translations", async () => { await test.step("should navigate to /event-types and show Brazil-Portuguese translations", async () => {
await page.goto("/event-types"); await page.goto("/event-types");
await page.waitForLoadState("networkidle"); await page.waitForLoadState("domcontentloaded");
await page.locator("html[lang=pt-br]").waitFor({ state: "attached" }); await page.locator("html[lang=pt-br]").waitFor({ state: "attached" });
await page.locator("html[dir=ltr]").waitFor({ state: "attached" }); await page.locator("html[dir=ltr]").waitFor({ state: "attached" });
{ {
const locator = page.getByText("Tipos de Eventos", { exact: true }); const locator = page.getByRole("heading", { name: "Tipos de Eventos", exact: true });
expect(await locator.count()).toBeGreaterThanOrEqual(1); await expect(locator).toHaveCount(1);
} }
{ {
const locator = page.getByText("Event Types", { exact: true }); const locator = page.getByText("Event Types", { exact: true });
expect(await locator.count()).toEqual(0); await expect(locator).toHaveCount(0);
} }
}); });
await test.step("should navigate to /bookings and show Brazil-Portuguese translations", async () => { await test.step("should navigate to /bookings and show Brazil-Portuguese translations", async () => {
await page.goto("/bookings"); await page.goto("/bookings");
await page.waitForLoadState("networkidle"); await page.waitForLoadState("domcontentloaded");
await page.locator("html[lang=pt-br]").waitFor({ state: "attached" }); await page.locator("html[lang=pt-br]").waitFor({ state: "attached" });
await page.locator("html[dir=ltr]").waitFor({ state: "attached" }); await page.locator("html[dir=ltr]").waitFor({ state: "attached" });
{ {
const locator = page.getByText("Reservas", { exact: true }); const locator = page.getByRole("heading", { name: "Reservas", exact: true });
expect(await locator.count()).toBeGreaterThanOrEqual(1); await expect(locator).toHaveCount(1);
} }
{ {
const locator = page.getByText("Bookings", { exact: true }); const locator = page.getByText("Bookings", { exact: true });
expect(await locator.count()).toEqual(0); await expect(locator).toHaveCount(0);
} }
}); });
await test.step("should reload the /bookings and show Brazil-Portuguese translations", async () => { await test.step("should reload the /bookings and show Brazil-Portuguese translations", async () => {
await page.reload(); await page.reload();
await page.waitForLoadState("networkidle"); await page.waitForLoadState("domcontentloaded");
await page.locator("html[lang=pt-br]").waitFor({ state: "attached" }); await page.locator("html[lang=pt-br]").waitFor({ state: "attached" });
await page.locator("html[dir=ltr]").waitFor({ state: "attached" }); await page.locator("html[dir=ltr]").waitFor({ state: "attached" });
{ {
const locator = page.getByText("Reservas", { exact: true }); const locator = page.getByRole("heading", { name: "Reservas", exact: true });
expect(await locator.count()).toBeGreaterThanOrEqual(1); await expect(locator).toHaveCount(1);
} }
{ {
const locator = page.getByText("Bookings", { exact: true }); const locator = page.getByText("Bookings", { exact: true });
expect(await locator.count()).toEqual(0); await expect(locator).toHaveCount(0);
} }
}); });
}); });
@ -357,57 +362,57 @@ test.describe("authorized user sees correct translations (ar)", async () => {
await test.step("should navigate to /event-types and show Arabic translations", async () => { await test.step("should navigate to /event-types and show Arabic translations", async () => {
await page.goto("/event-types"); await page.goto("/event-types");
await page.waitForLoadState("networkidle"); await page.waitForLoadState("domcontentloaded");
await page.locator("html[lang=ar]").waitFor({ state: "attached" }); await page.locator("html[lang=ar]").waitFor({ state: "attached" });
await page.locator("html[dir=rtl]").waitFor({ state: "attached" }); await page.locator("html[dir=rtl]").waitFor({ state: "attached" });
{ {
const locator = page.getByText("أنواع الحدث", { exact: true }); const locator = page.getByRole("heading", { name: "أنواع الحدث", exact: true });
expect(await locator.count()).toBeGreaterThanOrEqual(1); await expect(locator).toHaveCount(1);
} }
{ {
const locator = page.getByText("Event Types", { exact: true }); const locator = page.getByText("Event Types", { exact: true });
expect(await locator.count()).toEqual(0); await expect(locator).toHaveCount(0);
} }
}); });
await test.step("should navigate to /bookings and show Arabic translations", async () => { await test.step("should navigate to /bookings and show Arabic translations", async () => {
await page.goto("/bookings"); await page.goto("/bookings");
await page.waitForLoadState("networkidle"); await page.waitForLoadState("domcontentloaded");
await page.locator("html[lang=ar]").waitFor({ state: "attached" }); await page.locator("html[lang=ar]").waitFor({ state: "attached" });
await page.locator("html[dir=rtl]").waitFor({ state: "attached" }); await page.locator("html[dir=rtl]").waitFor({ state: "attached" });
{ {
const locator = page.getByText("عمليات الحجز", { exact: true }); const locator = page.getByRole("heading", { name: "عمليات الحجز", exact: true });
expect(await locator.count()).toBeGreaterThanOrEqual(1); await expect(locator).toHaveCount(1);
} }
{ {
const locator = page.getByText("Bookings", { exact: true }); const locator = page.getByText("Bookings", { exact: true });
expect(await locator.count()).toEqual(0); await expect(locator).toHaveCount(0);
} }
}); });
await test.step("should reload the /bookings and show Arabic translations", async () => { await test.step("should reload the /bookings and show Arabic translations", async () => {
await page.reload(); await page.reload();
await page.waitForLoadState("networkidle"); await page.waitForLoadState("domcontentloaded");
await page.locator("html[lang=ar]").waitFor({ state: "attached" }); await page.locator("html[lang=ar]").waitFor({ state: "attached" });
await page.locator("html[dir=rtl]").waitFor({ state: "attached" }); await page.locator("html[dir=rtl]").waitFor({ state: "attached" });
{ {
const locator = page.getByText("عمليات الحجز", { exact: true }); const locator = page.getByRole("heading", { name: "عمليات الحجز", exact: true });
expect(await locator.count()).toBeGreaterThanOrEqual(1); await expect(locator).toHaveCount(1);
} }
{ {
const locator = page.getByText("Bookings", { exact: true }); const locator = page.getByText("Bookings", { exact: true });
expect(await locator.count()).toEqual(0); await expect(locator).toHaveCount(0);
} }
}); });
}); });
@ -429,7 +434,7 @@ test.describe("authorized user sees changed translations (de->ar)", async () =>
await test.step("should change the language and show Arabic translations", async () => { await test.step("should change the language and show Arabic translations", async () => {
await page.goto("/settings/my-account/general"); await page.goto("/settings/my-account/general");
await page.waitForLoadState("networkidle"); await page.waitForLoadState("domcontentloaded");
await page.locator(".bg-default > div > div:nth-child(2)").first().click(); await page.locator(".bg-default > div > div:nth-child(2)").first().click();
await page.locator("#react-select-2-option-0").click(); await page.locator("#react-select-2-option-0").click();
@ -444,32 +449,33 @@ test.describe("authorized user sees changed translations (de->ar)", async () =>
await page.locator("html[dir=rtl]").waitFor({ state: "attached" }); await page.locator("html[dir=rtl]").waitFor({ state: "attached" });
{ {
const locator = page.getByText("عام", { exact: true }); // "general" // at least one is visible
expect(await locator.count()).toBeGreaterThanOrEqual(1); const locator = page.getByText("عام", { exact: true }).last(); // "general"
await expect(locator).toBeVisible();
} }
{ {
const locator = page.getByText("Allgemein", { exact: true }); // "general" const locator = page.getByText("Allgemein", { exact: true }); // "general"
expect(await locator.count()).toEqual(0); await expect(locator).toHaveCount(0);
} }
}); });
await test.step("should reload and show Arabic translations", async () => { await test.step("should reload and show Arabic translations", async () => {
await page.reload(); await page.reload();
await page.waitForLoadState("networkidle"); await page.waitForLoadState("domcontentloaded");
await page.locator("html[lang=ar]").waitFor({ state: "attached" }); await page.locator("html[lang=ar]").waitFor({ state: "attached" });
await page.locator("html[dir=rtl]").waitFor({ state: "attached" }); await page.locator("html[dir=rtl]").waitFor({ state: "attached" });
{ {
const locator = page.getByText("عام", { exact: true }); // "general" const locator = page.getByText("عام", { exact: true }).last(); // "general"
expect(await locator.count()).toBeGreaterThanOrEqual(1); await expect(locator).toBeVisible();
} }
{ {
const locator = page.getByText("Allgemein", { exact: true }); // "general" const locator = page.getByText("Allgemein", { exact: true }); // "general"
expect(await locator.count()).toEqual(0); await expect(locator).toHaveCount(0);
} }
}); });
}); });
@ -491,7 +497,7 @@ test.describe("authorized user sees changed translations (de->pt-BR) [locale1]",
await test.step("should change the language and show Brazil-Portuguese translations", async () => { await test.step("should change the language and show Brazil-Portuguese translations", async () => {
await page.goto("/settings/my-account/general"); await page.goto("/settings/my-account/general");
await page.waitForLoadState("networkidle"); await page.waitForLoadState("domcontentloaded");
await page.locator(".bg-default > div > div:nth-child(2)").first().click(); await page.locator(".bg-default > div > div:nth-child(2)").first().click();
await page.locator("#react-select-2-option-14").click(); await page.locator("#react-select-2-option-14").click();
@ -506,32 +512,32 @@ test.describe("authorized user sees changed translations (de->pt-BR) [locale1]",
await page.locator("html[dir=ltr]").waitFor({ state: "attached" }); await page.locator("html[dir=ltr]").waitFor({ state: "attached" });
{ {
const locator = page.getByText("Geral", { exact: true }); // "general" const locator = page.getByText("Geral", { exact: true }).last(); // "general"
expect(await locator.count()).toBeGreaterThanOrEqual(1); await expect(locator).toBeVisible();
} }
{ {
const locator = page.getByText("Allgemein", { exact: true }); // "general" const locator = page.getByText("Allgemein", { exact: true }); // "general"
expect(await locator.count()).toEqual(0); await expect(locator).toHaveCount(0);
} }
}); });
await test.step("should reload and show Brazil-Portuguese translations", async () => { await test.step("should reload and show Brazil-Portuguese translations", async () => {
await page.reload(); await page.reload();
await page.waitForLoadState("networkidle"); await page.waitForLoadState("domcontentloaded");
await page.locator("html[lang=pt-BR]").waitFor({ state: "attached" }); await page.locator("html[lang=pt-BR]").waitFor({ state: "attached" });
await page.locator("html[dir=ltr]").waitFor({ state: "attached" }); await page.locator("html[dir=ltr]").waitFor({ state: "attached" });
{ {
const locator = page.getByText("Geral", { exact: true }); // "general" const locator = page.getByText("Geral", { exact: true }).last(); // "general"
expect(await locator.count()).toBeGreaterThanOrEqual(1); await expect(locator).toBeVisible();
} }
{ {
const locator = page.getByText("Allgemein", { exact: true }); // "general" const locator = page.getByText("Allgemein", { exact: true }); // "general"
expect(await locator.count()).toEqual(0); await expect(locator).toHaveCount(0);
} }
}); });
}); });

View File

@ -1,9 +1,15 @@
import { expect } from "@playwright/test";
import type { Page } from "@playwright/test"; import type { Page } from "@playwright/test";
import { expect } from "@playwright/test";
import { IS_TEAM_BILLING_ENABLED } from "@calcom/lib/constants";
import { test } from "./lib/fixtures"; import { test } from "./lib/fixtures";
import { selectFirstAvailableTimeSlotNextMonth, bookTimeSlot } from "./lib/testUtils"; import {
import { localize } from "./lib/testUtils"; bookTimeSlot,
fillStripeTestCheckout,
localize,
selectFirstAvailableTimeSlotNextMonth,
} from "./lib/testUtils";
test.afterEach(({ users }) => users.deleteAll()); test.afterEach(({ users }) => users.deleteAll());
@ -21,18 +27,20 @@ test.describe("Managed Event Types tests", () => {
await test.step("Managed event option exists for team admin", async () => { await test.step("Managed event option exists for team admin", async () => {
// Filling team creation form wizard // Filling team creation form wizard
await page.locator('input[name="name"]').waitFor();
await page.locator('input[name="name"]').fill(`${adminUser.username}'s Team`); await page.locator('input[name="name"]').fill(`${adminUser.username}'s Team`);
await page.locator("text=Continue").click(); await page.click("[type=submit]");
await page.waitForURL(/\/settings\/teams\/(\d+)\/onboard-members$/i); // TODO: Figure out a way to make this more reliable
// eslint-disable-next-line playwright/no-conditional-in-test
if (IS_TEAM_BILLING_ENABLED) await fillStripeTestCheckout(page);
await page.waitForURL(/\/settings\/teams\/(\d+)\/onboard-members.*$/i);
await page.getByTestId("new-member-button").click(); await page.getByTestId("new-member-button").click();
await page.locator('[placeholder="email\\@example\\.com"]').fill(`${memberUser.username}@example.com`); await page.locator('[placeholder="email\\@example\\.com"]').fill(`${memberUser.username}@example.com`);
await page.getByTestId("invite-new-member-button").click(); await page.getByTestId("invite-new-member-button").click();
// wait for the second member to be added to the pending-member-list. // wait for the second member to be added to the pending-member-list.
await page.getByTestId("pending-member-list").locator("li:nth-child(2)").waitFor(); await page.getByTestId("pending-member-list").locator("li:nth-child(2)").waitFor();
// and publish // and publish
await page.locator("text=Publish team").click(); await page.locator("[data-testid=publish-button]").click();
await page.waitForURL("/settings/teams/**"); await expect(page).toHaveURL(/\/settings\/teams\/(\d+)\/profile$/i);
// Going to create an event type // Going to create an event type
await page.goto("/event-types"); await page.goto("/event-types");
await page.getByTestId("new-event-type").click(); await page.getByTestId("new-event-type").click();

View File

@ -54,7 +54,6 @@ test.describe("Organization", () => {
// Code verification // Code verification
await expect(page.locator("#modal-title")).toBeVisible(); await expect(page.locator("#modal-title")).toBeVisible();
await page.locator("input[name='2fa1']").fill(generateTotpCode(`john@${orgDomain}.com`)); await page.locator("input[name='2fa1']").fill(generateTotpCode(`john@${orgDomain}.com`));
await page.locator("button:text('Verify')").click();
// Check admin email about DNS pending action // Check admin email about DNS pending action
await expectInvitationEmailToBeReceived( await expectInvitationEmailToBeReceived(

View File

@ -48,8 +48,6 @@ test.describe("Organization", () => {
await newPage.waitForLoadState("networkidle"); await newPage.waitForLoadState("networkidle");
// Check required fields // Check required fields
await newPage.locator("button[type=submit]").click();
await expect(newPage.locator(".text-red-700")).toHaveCount(3); // 3 password hints
await newPage.locator("input[name=password]").fill(`P4ssw0rd!`); await newPage.locator("input[name=password]").fill(`P4ssw0rd!`);
await newPage.locator("button[type=submit]").click(); await newPage.locator("button[type=submit]").click();
await newPage.waitForURL("/getting-started?from=signup"); await newPage.waitForURL("/getting-started?from=signup");
@ -78,8 +76,8 @@ test.describe("Organization", () => {
await inviteLinkPage.waitForLoadState("networkidle"); await inviteLinkPage.waitForLoadState("networkidle");
// Check required fields // Check required fields
await inviteLinkPage.locator("button[type=submit]").click(); const button = inviteLinkPage.locator("button[type=submit][disabled]");
await expect(inviteLinkPage.locator(".text-red-700")).toHaveCount(4); // email + 3 password hints await expect(button).toBeVisible(); // email + 3 password hints
// Happy path // Happy path
await inviteLinkPage.locator("input[name=email]").fill(`rick@domain-${Date.now()}.com`); await inviteLinkPage.locator("input[name=email]").fill(`rick@domain-${Date.now()}.com`);

View File

@ -0,0 +1,33 @@
import { expect } from "@playwright/test";
import { test } from "./lib/fixtures";
test.describe.configure({ mode: "parallel" });
test.describe("Settings/admin A/B tests", () => {
test("should point to the /future/settings/admin page", async ({ page, users, context }) => {
await context.addCookies([
{
name: "x-calcom-future-routes-override",
value: "1",
url: "http://localhost:3000",
},
]);
const user = await users.create();
await user.apiLogin();
await page.goto("/settings/admin");
await page.waitForLoadState();
const dataNextJsRouter = await page.evaluate(() =>
window.document.documentElement.getAttribute("data-nextjs-router")
);
expect(dataNextJsRouter).toEqual("app");
const locator = page.getByRole("heading", { name: "Feature Flags" });
await expect(locator).toBeVisible();
});
});

View File

@ -4,7 +4,8 @@ import { randomBytes } from "crypto";
import { APP_NAME, IS_PREMIUM_USERNAME_ENABLED, IS_MAILHOG_ENABLED } from "@calcom/lib/constants"; import { APP_NAME, IS_PREMIUM_USERNAME_ENABLED, IS_MAILHOG_ENABLED } from "@calcom/lib/constants";
import { test } from "./lib/fixtures"; import { test } from "./lib/fixtures";
import { getEmailsReceivedByUser } from "./lib/testUtils"; import { getEmailsReceivedByUser, localize } from "./lib/testUtils";
import { expectInvitationEmailToBeReceived } from "./team/expects";
test.describe.configure({ mode: "parallel" }); test.describe.configure({ mode: "parallel" });
@ -12,8 +13,9 @@ test.describe("Signup Flow Test", async () => {
test.beforeEach(async ({ features }) => { test.beforeEach(async ({ features }) => {
features.reset(); // This resets to the inital state not an empt yarray features.reset(); // This resets to the inital state not an empt yarray
}); });
test.afterAll(async ({ users }) => { test.afterAll(async ({ users, emails }) => {
await users.deleteAll(); await users.deleteAll();
emails?.deleteAll();
}); });
test("Username is taken", async ({ page, users }) => { test("Username is taken", async ({ page, users }) => {
// log in trail user // log in trail user
@ -228,4 +230,55 @@ test.describe("Signup Flow Test", async () => {
const verifyEmail = receivedEmails?.items[0]; const verifyEmail = receivedEmails?.items[0];
expect(verifyEmail?.subject).toBe(`${APP_NAME}: Verify your account`); expect(verifyEmail?.subject).toBe(`${APP_NAME}: Verify your account`);
}); });
test("If signup is disabled allow team invites", async ({ browser, page, users, emails }) => {
// eslint-disable-next-line playwright/no-skipped-test
test.skip(process.env.NEXT_PUBLIC_DISABLE_SIGNUP !== "true", "Skipping due to signup being enabled");
const t = await localize("en");
const teamOwner = await users.create(undefined, { hasTeam: true });
const { team } = await teamOwner.getFirstTeam();
await teamOwner.apiLogin();
await page.goto(`/settings/teams/${team.id}/members`);
await page.waitForLoadState("networkidle");
await test.step("Invite User to team", async () => {
// TODO: This invite logic should live in a fixture - its used in team and orgs invites (Duplicated from team/org invites)
const invitedUserEmail = `rick_${Date.now()}@domain-${Date.now()}.com`;
await page.locator(`button:text("${t("add")}")`).click();
await page.locator('input[name="inviteUser"]').fill(invitedUserEmail);
await page.locator(`button:text("${t("send_invite")}")`).click();
await page.waitForLoadState("networkidle");
const inviteLink = await expectInvitationEmailToBeReceived(
page,
emails,
invitedUserEmail,
`${team.name}'s admin invited you to join the team ${team.name} on Cal.com`,
"signup?token"
);
//Check newly invited member exists and is pending
await expect(
page.locator(`[data-testid="email-${invitedUserEmail.replace("@", "")}-pending"]`)
).toHaveCount(1);
// eslint-disable-next-line playwright/no-conditional-in-test
if (!inviteLink) return;
// Follow invite link to new window
const context = await browser.newContext();
const newPage = await context.newPage();
await newPage.goto(inviteLink);
await newPage.waitForLoadState("networkidle");
const url = new URL(newPage.url());
expect(url.pathname).toBe("/signup");
// Check required fields
await newPage.locator("input[name=password]").fill(`P4ssw0rd!`);
await newPage.locator("button[type=submit]").click();
await newPage.waitForURL("/getting-started?from=signup");
await newPage.close();
await context.close();
});
});
}); });

View File

@ -51,8 +51,10 @@ test.describe("Team", () => {
await newPage.waitForLoadState("networkidle"); await newPage.waitForLoadState("networkidle");
// Check required fields // Check required fields
await newPage.locator("button[type=submit]").click(); const button = newPage.locator("button[type=submit][disabled]");
await expect(newPage.locator('[data-testid="hint-error"]')).toHaveCount(3); await expect(button).toBeVisible(); // email + 3 password hints
// Check required fields
await newPage.locator("input[name=password]").fill(`P4ssw0rd!`); await newPage.locator("input[name=password]").fill(`P4ssw0rd!`);
await newPage.locator("button[type=submit]").click(); await newPage.locator("button[type=submit]").click();
await newPage.waitForURL("/getting-started?from=signup"); await newPage.waitForURL("/getting-started?from=signup");

View File

@ -1,34 +1,32 @@
import type { Page } from "@playwright/test"; import type { Page } from "@playwright/test";
import { expect } from "@playwright/test"; import { expect } from "@playwright/test";
import { IS_TEAM_BILLING_ENABLED } from "@calcom/lib/constants";
import { prisma } from "@calcom/prisma"; import { prisma } from "@calcom/prisma";
import { MembershipRole, SchedulingType } from "@calcom/prisma/enums"; import { MembershipRole, SchedulingType } from "@calcom/prisma/enums";
import { test } from "./lib/fixtures"; import { test } from "./lib/fixtures";
import { bookTimeSlot, selectFirstAvailableTimeSlotNextMonth, testName, todo } from "./lib/testUtils"; import {
bookTimeSlot,
fillStripeTestCheckout,
selectFirstAvailableTimeSlotNextMonth,
testName,
todo,
} from "./lib/testUtils";
test.describe.configure({ mode: "parallel" }); test.describe.configure({ mode: "parallel" });
test.describe("Teams - NonOrg", () => { test.describe("Teams - NonOrg", () => {
test.afterEach(({ users }) => users.deleteAll()); test.afterEach(({ users }) => users.deleteAll());
test("Can create teams via Wizard", async ({ page, users }) => {
const user = await users.create();
const inviteeEmail = `${user.username}+invitee@example.com`;
await user.apiLogin();
await page.goto("/teams");
await test.step("Can create team", async () => { test("Team Onboarding Invite Members", async ({ page, users }) => {
// Click text=Create Team const user = await users.create(undefined, { hasTeam: true });
await page.locator("text=Create Team").click(); const { team } = await user.getFirstTeam();
await page.waitForURL("/settings/teams/new"); const inviteeEmail = `${user.username}+invitee@example.com`;
// Fill input[name="name"]
await page.locator('input[name="name"]').fill(`${user.username}'s Team`); await user.apiLogin();
// Click text=Continue
await page.locator("text=Continue").click(); page.goto(`/settings/teams/${team.id}/onboard-members`);
await page.waitForURL(/\/settings\/teams\/(\d+)\/onboard-members$/i);
await page.waitForSelector('[data-testid="pending-member-list"]');
expect(await page.locator('[data-testid="pending-member-item"]').count()).toBe(1);
});
await test.step("Can add members", async () => { await test.step("Can add members", async () => {
// Click [data-testid="new-member-button"] // Click [data-testid="new-member-button"]
@ -50,9 +48,9 @@ test.describe("Teams - NonOrg", () => {
await prisma.user.delete({ where: { email: inviteeEmail } }); await prisma.user.delete({ where: { email: inviteeEmail } });
}); });
await test.step("Can publish team", async () => { await test.step("Finishing brings you to team profile page", async () => {
await page.locator("text=Publish team").click(); await page.locator("[data-testid=publish-button]").click();
await page.waitForURL(/\/settings\/teams\/(\d+)\/profile$/i); await expect(page).toHaveURL(/\/settings\/teams\/(\d+)\/profile$/i);
}); });
await test.step("Can disband team", async () => { await test.step("Can disband team", async () => {
@ -66,7 +64,6 @@ test.describe("Teams - NonOrg", () => {
}); });
test("Can create a booking for Collective EventType", async ({ page, users }) => { test("Can create a booking for Collective EventType", async ({ page, users }) => {
const ownerObj = { username: "pro-user", name: "pro-user" };
const teamMatesObj = [ const teamMatesObj = [
{ name: "teammate-1" }, { name: "teammate-1" },
{ name: "teammate-2" }, { name: "teammate-2" },
@ -74,11 +71,14 @@ test.describe("Teams - NonOrg", () => {
{ name: "teammate-4" }, { name: "teammate-4" },
]; ];
const owner = await users.create(ownerObj, { const owner = await users.create(
hasTeam: true, { username: "pro-user", name: "pro-user" },
teammates: teamMatesObj, {
schedulingType: SchedulingType.COLLECTIVE, hasTeam: true,
}); teammates: teamMatesObj,
schedulingType: SchedulingType.COLLECTIVE,
}
);
const { team } = await owner.getFirstTeam(); const { team } = await owner.getFirstTeam();
const { title: teamEventTitle, slug: teamEventSlug } = await owner.getFirstTeamEvent(team.id); const { title: teamEventTitle, slug: teamEventSlug } = await owner.getFirstTeamEvent(team.id);
@ -102,18 +102,20 @@ test.describe("Teams - NonOrg", () => {
}); });
test("Can create a booking for Round Robin EventType", async ({ page, users }) => { test("Can create a booking for Round Robin EventType", async ({ page, users }) => {
const ownerObj = { username: "pro-user", name: "pro-user" };
const teamMatesObj = [ const teamMatesObj = [
{ name: "teammate-1" }, { name: "teammate-1" },
{ name: "teammate-2" }, { name: "teammate-2" },
{ name: "teammate-3" }, { name: "teammate-3" },
{ name: "teammate-4" }, { name: "teammate-4" },
]; ];
const owner = await users.create(ownerObj, { const owner = await users.create(
hasTeam: true, { username: "pro-user", name: "pro-user" },
teammates: teamMatesObj, {
schedulingType: SchedulingType.ROUND_ROBIN, hasTeam: true,
}); teammates: teamMatesObj,
schedulingType: SchedulingType.ROUND_ROBIN,
}
);
const { team } = await owner.getFirstTeam(); const { team } = await owner.getFirstTeam();
const { title: teamEventTitle, slug: teamEventSlug } = await owner.getFirstTeamEvent(team.id); const { title: teamEventTitle, slug: teamEventSlug } = await owner.getFirstTeamEvent(team.id);
@ -134,7 +136,7 @@ test.describe("Teams - NonOrg", () => {
// Anyone of the teammates could be the Host of the booking. // Anyone of the teammates could be the Host of the booking.
const chosenUser = await page.getByTestId("booking-host-name").textContent(); const chosenUser = await page.getByTestId("booking-host-name").textContent();
expect(chosenUser).not.toBeNull(); expect(chosenUser).not.toBeNull();
expect(teamMatesObj.concat([{ name: ownerObj.name }]).some(({ name }) => name === chosenUser)).toBe(true); expect(teamMatesObj.concat([{ name: owner.name! }]).some(({ name }) => name === chosenUser)).toBe(true);
// TODO: Assert whether the user received an email // TODO: Assert whether the user received an email
}); });
@ -164,8 +166,7 @@ test.describe("Teams - NonOrg", () => {
await page.goto("/settings/teams/new"); await page.goto("/settings/teams/new");
// Fill input[name="name"] // Fill input[name="name"]
await page.locator('input[name="name"]').fill(uniqueName); await page.locator('input[name="name"]').fill(uniqueName);
await page.locator("text=Continue").click(); await page.click("[type=submit]");
await expect(page.locator("[data-testid=alert]")).toBeVisible();
// cleanup // cleanup
const org = await owner.getOrgMembership(); const org = await owner.getOrgMembership();
@ -174,11 +175,9 @@ test.describe("Teams - NonOrg", () => {
}); });
test("Can create team with same name as user", async ({ page, users }) => { test("Can create team with same name as user", async ({ page, users }) => {
const user = await users.create();
// Name to be used for both user and team // Name to be used for both user and team
const uniqueName = "test-unique-name"; const uniqueName = user.username!;
const ownerObj = { username: uniqueName, name: uniqueName, useExactUsername: true };
const user = await users.create(ownerObj);
await user.apiLogin(); await user.apiLogin();
await page.goto("/teams"); await page.goto("/teams");
@ -189,11 +188,14 @@ test.describe("Teams - NonOrg", () => {
// Fill input[name="name"] // Fill input[name="name"]
await page.locator('input[name="name"]').fill(uniqueName); await page.locator('input[name="name"]').fill(uniqueName);
// Click text=Continue // Click text=Continue
await page.locator("text=Continue").click(); await page.click("[type=submit]");
await page.waitForURL(/\/settings\/teams\/(\d+)\/onboard-members$/i); // TODO: Figure out a way to make this more reliable
// eslint-disable-next-line playwright/no-conditional-in-test
if (IS_TEAM_BILLING_ENABLED) await fillStripeTestCheckout(page);
await page.waitForURL(/\/settings\/teams\/(\d+)\/onboard-members.*$/i);
// Click text=Continue // Click text=Continue
await page.locator("text=Publish team").click(); await page.locator("[data-testid=publish-button]").click();
await page.waitForURL(/\/settings\/teams\/(\d+)\/profile$/i); await expect(page).toHaveURL(/\/settings\/teams\/(\d+)\/profile$/i);
}); });
await test.step("Can access user and team with same slug", async () => { await test.step("Can access user and team with same slug", async () => {
@ -210,13 +212,11 @@ test.describe("Teams - NonOrg", () => {
await expect(page.locator("[data-testid=name-title]")).toHaveText(uniqueName); await expect(page.locator("[data-testid=name-title]")).toHaveText(uniqueName);
// cleanup team // cleanup team
const team = await prisma.team.findFirst({ where: { slug: uniqueName } }); await prisma.team.deleteMany({ where: { slug: uniqueName } });
await prisma.team.delete({ where: { id: team?.id } });
}); });
}); });
test("Can create a private team", async ({ page, users }) => { test("Can create a private team", async ({ page, users }) => {
const ownerObj = { username: "pro-user", name: "pro-user" };
const teamMatesObj = [ const teamMatesObj = [
{ name: "teammate-1" }, { name: "teammate-1" },
{ name: "teammate-2" }, { name: "teammate-2" },
@ -224,11 +224,14 @@ test.describe("Teams - NonOrg", () => {
{ name: "teammate-4" }, { name: "teammate-4" },
]; ];
const owner = await users.create(ownerObj, { const owner = await users.create(
hasTeam: true, { username: "pro-user", name: "pro-user" },
teammates: teamMatesObj, {
schedulingType: SchedulingType.COLLECTIVE, hasTeam: true,
}); teammates: teamMatesObj,
schedulingType: SchedulingType.COLLECTIVE,
}
);
await owner.apiLogin(); await owner.apiLogin();
const { team } = await owner.getFirstTeam(); const { team } = await owner.getFirstTeam();
@ -278,45 +281,43 @@ test.describe("Teams - Org", () => {
// Fill input[name="name"] // Fill input[name="name"]
await page.locator('input[name="name"]').fill(`${user.username}'s Team`); await page.locator('input[name="name"]').fill(`${user.username}'s Team`);
// Click text=Continue // Click text=Continue
await page.locator("text=Continue").click(); await page.click("[type=submit]");
await page.waitForURL(/\/settings\/teams\/(\d+)\/onboard-members$/i); // TODO: Figure out a way to make this more reliable
// eslint-disable-next-line playwright/no-conditional-in-test
if (IS_TEAM_BILLING_ENABLED) await fillStripeTestCheckout(page);
await expect(page).toHaveURL(/\/settings\/teams\/(\d+)\/onboard-members.*$/i);
await page.waitForSelector('[data-testid="pending-member-list"]'); await page.waitForSelector('[data-testid="pending-member-list"]');
expect(await page.locator('[data-testid="pending-member-item"]').count()).toBe(1); expect(await page.getByTestId("pending-member-item").count()).toBe(1);
}); });
await test.step("Can add members", async () => { await test.step("Can add members", async () => {
// Click [data-testid="new-member-button"] await page.getByTestId("new-member-button").click();
await page.locator('[data-testid="new-member-button"]').click();
// Fill [placeholder="email\@example\.com"]
await page.locator('[placeholder="email\\@example\\.com"]').fill(inviteeEmail); await page.locator('[placeholder="email\\@example\\.com"]').fill(inviteeEmail);
// Click [data-testid="invite-new-member-button"] await page.getByTestId("invite-new-member-button").click();
await page.locator('[data-testid="invite-new-member-button"]').click();
await expect(page.locator(`li:has-text("${inviteeEmail}")`)).toBeVisible(); await expect(page.locator(`li:has-text("${inviteeEmail}")`)).toBeVisible();
expect(await page.locator('[data-testid="pending-member-item"]').count()).toBe(2); expect(await page.getByTestId("pending-member-item").count()).toBe(2);
}); });
await test.step("Can remove members", async () => { await test.step("Can remove members", async () => {
expect(await page.locator('[data-testid="pending-member-item"]').count()).toBe(2); expect(await page.getByTestId("pending-member-item").count()).toBe(2);
const lastRemoveMemberButton = page.getByTestId("remove-member-button").last();
const lastRemoveMemberButton = page.locator('[data-testid="remove-member-button"]').last();
await lastRemoveMemberButton.click(); await lastRemoveMemberButton.click();
await page.waitForLoadState("networkidle"); await page.waitForLoadState("networkidle");
expect(await page.locator('[data-testid="pending-member-item"]').count()).toBe(1); expect(await page.getByTestId("pending-member-item").count()).toBe(1);
// Cleanup here since this user is created without our fixtures. // Cleanup here since this user is created without our fixtures.
await prisma.user.delete({ where: { email: inviteeEmail } }); await prisma.user.delete({ where: { email: inviteeEmail } });
}); });
await test.step("Can finish team creation", async () => { await test.step("Can finish team creation", async () => {
await page.locator("text=Finish").click(); await page.getByTestId("publish-button").click();
await page.waitForURL("/settings/teams"); await expect(page).toHaveURL(/\/settings\/teams\/(\d+)\/profile$/i);
}); });
await test.step("Can disband team", async () => { await test.step("Can disband team", async () => {
await page.locator('[data-testid="team-list-item-link"]').click();
await page.waitForURL(/\/settings\/teams\/(\d+)\/profile$/i); await page.waitForURL(/\/settings\/teams\/(\d+)\/profile$/i);
await page.locator("text=Disband Team").click(); await page.getByTestId("disband-team-button").click();
await page.locator("text=Yes, disband team").click(); await page.getByTestId("dialog-confirmation").click();
await page.waitForURL("/teams"); await page.waitForURL("/teams");
expect(await page.locator(`text=${user.username}'s Team`).count()).toEqual(0); expect(await page.locator(`text=${user.username}'s Team`).count()).toEqual(0);
}); });
@ -361,13 +362,13 @@ test.describe("Teams - Org", () => {
await page.goto(`/team/${team.slug}/${teamEventSlug}`); await page.goto(`/team/${team.slug}/${teamEventSlug}`);
await selectFirstAvailableTimeSlotNextMonth(page); await selectFirstAvailableTimeSlotNextMonth(page);
await bookTimeSlot(page); await bookTimeSlot(page);
await expect(page.locator("[data-testid=success-page]")).toBeVisible(); await expect(page.getByTestId("success-page")).toBeVisible();
// The title of the booking // The title of the booking
const BookingTitle = `${teamEventTitle} between ${team.name} and ${testName}`; const BookingTitle = `${teamEventTitle} between ${team.name} and ${testName}`;
await expect(page.locator("[data-testid=booking-title]")).toHaveText(BookingTitle); await expect(page.getByTestId("booking-title")).toHaveText(BookingTitle);
// The booker should be in the attendee list // The booker should be in the attendee list
await expect(page.locator(`[data-testid="attendee-name-${testName}"]`)).toHaveText(testName); await expect(page.getByTestId(`attendee-name-${testName}`)).toHaveText(testName);
// All the teammates should be in the booking // All the teammates should be in the booking
for (const teammate of teamMatesObj.concat([{ name: owner.name || "" }])) { for (const teammate of teamMatesObj.concat([{ name: owner.name || "" }])) {
@ -380,18 +381,20 @@ test.describe("Teams - Org", () => {
}); });
test("Can create a booking for Round Robin EventType", async ({ page, users }) => { test("Can create a booking for Round Robin EventType", async ({ page, users }) => {
const ownerObj = { username: "pro-user", name: "pro-user" };
const teamMatesObj = [ const teamMatesObj = [
{ name: "teammate-1" }, { name: "teammate-1" },
{ name: "teammate-2" }, { name: "teammate-2" },
{ name: "teammate-3" }, { name: "teammate-3" },
{ name: "teammate-4" }, { name: "teammate-4" },
]; ];
const owner = await users.create(ownerObj, { const owner = await users.create(
hasTeam: true, { username: "pro-user", name: "pro-user" },
teammates: teamMatesObj, {
schedulingType: SchedulingType.ROUND_ROBIN, hasTeam: true,
}); teammates: teamMatesObj,
schedulingType: SchedulingType.ROUND_ROBIN,
}
);
const { team } = await owner.getFirstTeam(); const { team } = await owner.getFirstTeam();
const { title: teamEventTitle, slug: teamEventSlug } = await owner.getFirstTeamEvent(team.id); const { title: teamEventTitle, slug: teamEventSlug } = await owner.getFirstTeamEvent(team.id);
@ -402,17 +405,17 @@ test.describe("Teams - Org", () => {
await expect(page.locator("[data-testid=success-page]")).toBeVisible(); await expect(page.locator("[data-testid=success-page]")).toBeVisible();
// The person who booked the meeting should be in the attendee list // The person who booked the meeting should be in the attendee list
await expect(page.locator(`[data-testid="attendee-name-${testName}"]`)).toHaveText(testName); await expect(page.getByTestId(`attendee-name-${testName}`)).toHaveText(testName);
// The title of the booking // The title of the booking
const BookingTitle = `${teamEventTitle} between ${team.name} and ${testName}`; const BookingTitle = `${teamEventTitle} between ${team.name} and ${testName}`;
await expect(page.locator("[data-testid=booking-title]")).toHaveText(BookingTitle); await expect(page.getByTestId("booking-title")).toHaveText(BookingTitle);
// Since all the users have the same leastRecentlyBooked value // Since all the users have the same leastRecentlyBooked value
// Anyone of the teammates could be the Host of the booking. // Anyone of the teammates could be the Host of the booking.
const chosenUser = await page.getByTestId("booking-host-name").textContent(); const chosenUser = await page.getByTestId("booking-host-name").textContent();
expect(chosenUser).not.toBeNull(); expect(chosenUser).not.toBeNull();
expect(teamMatesObj.concat([{ name: ownerObj.name }]).some(({ name }) => name === chosenUser)).toBe(true); expect(teamMatesObj.concat([{ name: owner.name! }]).some(({ name }) => name === chosenUser)).toBe(true);
// TODO: Assert whether the user received an email // TODO: Assert whether the user received an email
}); });
}); });

View File

@ -801,7 +801,7 @@
"cal_provide_tandem_meeting_url": "سيوفر {{appName}} رابطًا للاجتماع عبر Tandem.", "cal_provide_tandem_meeting_url": "سيوفر {{appName}} رابطًا للاجتماع عبر Tandem.",
"cal_provide_video_meeting_url": "سيوفر {{appName}} رابطًا للاجتماع عبر الفيديو على Daily.", "cal_provide_video_meeting_url": "سيوفر {{appName}} رابطًا للاجتماع عبر الفيديو على Daily.",
"cal_provide_jitsi_meeting_url": "سنوفر لك رابطًا للاجتماع عبر Jitsi Meet.", "cal_provide_jitsi_meeting_url": "سنوفر لك رابطًا للاجتماع عبر Jitsi Meet.",
"cal_provide_huddle01_meeting_url": "سيوفر {{appName}} رابطًا للاجتماع عبر الفيديو على Huddle01 web3.", "cal_provide_huddle01_meeting_url": "سيوفر {{appName}} رابطًا للاجتماع عبر الفيديو على Huddle01.",
"cal_provide_teams_meeting_url": "سيوفر {{appName}} عنوان URL لاجتماعات MS Teams. ملاحظة: يجب أن يكون لديك حساب عمل أو حساب مدرسة", "cal_provide_teams_meeting_url": "سيوفر {{appName}} عنوان URL لاجتماعات MS Teams. ملاحظة: يجب أن يكون لديك حساب عمل أو حساب مدرسة",
"require_payment": "يلزم الدفع", "require_payment": "يلزم الدفع",
"you_need_to_add_a_name": "تحتاج إلى إضافة اسم", "you_need_to_add_a_name": "تحتاج إلى إضافة اسم",
@ -904,7 +904,6 @@
"no_category_apps_description_analytics": "إضافة تطبيق تحليلات لصفحات الحجز الخاصة بك", "no_category_apps_description_analytics": "إضافة تطبيق تحليلات لصفحات الحجز الخاصة بك",
"no_category_apps_description_automation": "إضافة تطبيق أتمتة لاستخدامه", "no_category_apps_description_automation": "إضافة تطبيق أتمتة لاستخدامه",
"no_category_apps_description_other": "أضف أي نوع آخر من التطبيقات للقيام بأي شيء", "no_category_apps_description_other": "أضف أي نوع آخر من التطبيقات للقيام بأي شيء",
"no_category_apps_description_web3": "إضافة تطبيق web3 لصفحات الحجز لديك",
"no_category_apps_description_messaging": "إضافة تطبيق مراسلة لإعداد الإشعارات والتذكيرات المخصصة", "no_category_apps_description_messaging": "إضافة تطبيق مراسلة لإعداد الإشعارات والتذكيرات المخصصة",
"no_category_apps_description_crm": "إضافة تطبيق CRM لتتبع من قابلته", "no_category_apps_description_crm": "إضافة تطبيق CRM لتتبع من قابلته",
"installed_app_calendar_description": "قم بتعيين التقويمات للتحقق من وجود تعارضات لمنع الحجوزات المزدوجة.", "installed_app_calendar_description": "قم بتعيين التقويمات للتحقق من وجود تعارضات لمنع الحجوزات المزدوجة.",
@ -913,7 +912,6 @@
"installed_app_other_description": "جميع تطبيقاتك المثبتة من الفئات الأخرى.", "installed_app_other_description": "جميع تطبيقاتك المثبتة من الفئات الأخرى.",
"installed_app_conferencing_description": "تكوين تطبيقات المؤتمرات المراد استخدامها", "installed_app_conferencing_description": "تكوين تطبيقات المؤتمرات المراد استخدامها",
"installed_app_automation_description": "تهيئة تطبيقات الأتمتة التي سيتم استخدامها", "installed_app_automation_description": "تهيئة تطبيقات الأتمتة التي سيتم استخدامها",
"installed_app_web3_description": "تهيئة تطبيقات web3 التي يمكن استخدامها لصفحات الحجز لديك",
"installed_app_messaging_description": "تكوين تطبيقات المراسلة المراد استخدامها لإعداد الإشعارات والتذكيرات المخصصة", "installed_app_messaging_description": "تكوين تطبيقات المراسلة المراد استخدامها لإعداد الإشعارات والتذكيرات المخصصة",
"installed_app_crm_description": "تهيئة تطبيقات CRM التي يمكن استخدامها لتتبع من قابلته", "installed_app_crm_description": "تهيئة تطبيقات CRM التي يمكن استخدامها لتتبع من قابلته",
"analytics": "التحليلات", "analytics": "التحليلات",
@ -1304,7 +1302,6 @@
"profile_picture": "صورة الملف الشخصي", "profile_picture": "صورة الملف الشخصي",
"upload": "تحميل", "upload": "تحميل",
"add_profile_photo": "إضافة صورة الملف الشخصي", "add_profile_photo": "إضافة صورة الملف الشخصي",
"web3": "Web3",
"token_address": "عنوان الرمز المميز", "token_address": "عنوان الرمز المميز",
"blockchain": "سلسلة الكتل", "blockchain": "سلسلة الكتل",
"old_password": "كلمة مرور قديمة", "old_password": "كلمة مرور قديمة",
@ -1346,7 +1343,6 @@
"connect_automation_apps": "توصيل تطبيقات الأتمتة", "connect_automation_apps": "توصيل تطبيقات الأتمتة",
"connect_analytics_apps": "توصيل تطبيقات التحليلات", "connect_analytics_apps": "توصيل تطبيقات التحليلات",
"connect_other_apps": "ربط تطبيقات أخرى", "connect_other_apps": "ربط تطبيقات أخرى",
"connect_web3_apps": "ربط تطبيقات web3",
"connect_messaging_apps": "ربط تطبيقات المراسلة", "connect_messaging_apps": "ربط تطبيقات المراسلة",
"connect_crm_apps": "ربط تطبيقات CRM", "connect_crm_apps": "ربط تطبيقات CRM",
"current_step_of_total": "الخطوة {{currentStep}} من {{maxSteps}}", "current_step_of_total": "الخطوة {{currentStep}} من {{maxSteps}}",
@ -1585,6 +1581,7 @@
"enable_apps": "تمكين التطبيقات", "enable_apps": "تمكين التطبيقات",
"enable_apps_description": "تمكين التطبيقات التي يمكن للمستخدمين دمجها مع {{appName}}", "enable_apps_description": "تمكين التطبيقات التي يمكن للمستخدمين دمجها مع {{appName}}",
"purchase_license": "شراء ترخيص", "purchase_license": "شراء ترخيص",
"already_have_account": "هل لديك حساب بالفعل؟",
"already_have_key": "لدي مفتاح بالفعل:", "already_have_key": "لدي مفتاح بالفعل:",
"already_have_key_suggestion": "يرجى نسخ متغير البيئة الحالي CALCOM_LICENSE_KEY هنا.", "already_have_key_suggestion": "يرجى نسخ متغير البيئة الحالي CALCOM_LICENSE_KEY هنا.",
"app_is_enabled": "تم تمكين {{appName}}", "app_is_enabled": "تم تمكين {{appName}}",

View File

@ -801,7 +801,7 @@
"cal_provide_tandem_meeting_url": "{{appName}} poskytne URL Tandem meetingu.", "cal_provide_tandem_meeting_url": "{{appName}} poskytne URL Tandem meetingu.",
"cal_provide_video_meeting_url": "{{appName}} poskytne URL Daily video meetingu.", "cal_provide_video_meeting_url": "{{appName}} poskytne URL Daily video meetingu.",
"cal_provide_jitsi_meeting_url": "{{appName}} poskytne URL Jitsi Meet video meetingu.", "cal_provide_jitsi_meeting_url": "{{appName}} poskytne URL Jitsi Meet video meetingu.",
"cal_provide_huddle01_meeting_url": "{{appName}} poskytne URL Huddle01 web3 video meetingu.", "cal_provide_huddle01_meeting_url": "{{appName}} poskytne URL Huddle01 video meetingu.",
"cal_provide_teams_meeting_url": "{{appName}} poskytne URL schůzky aplikace MS Teams. POZNÁMKA: MUSÍ SE JEDNAT O PRACOVNÍ NEBO ŠKOLNÍ ÚČET", "cal_provide_teams_meeting_url": "{{appName}} poskytne URL schůzky aplikace MS Teams. POZNÁMKA: MUSÍ SE JEDNAT O PRACOVNÍ NEBO ŠKOLNÍ ÚČET",
"require_payment": "Vyžadovat platbu", "require_payment": "Vyžadovat platbu",
"you_need_to_add_a_name": "Musíte přidat název", "you_need_to_add_a_name": "Musíte přidat název",
@ -904,7 +904,6 @@
"no_category_apps_description_analytics": "Přidejte analytickou aplikaci pro vaše rezervační stránky", "no_category_apps_description_analytics": "Přidejte analytickou aplikaci pro vaše rezervační stránky",
"no_category_apps_description_automation": "Přidejte aplikaci pro automatizaci", "no_category_apps_description_automation": "Přidejte aplikaci pro automatizaci",
"no_category_apps_description_other": "Přidejte jakýkoli jiný typ aplikace pro nejrůznější činnosti", "no_category_apps_description_other": "Přidejte jakýkoli jiný typ aplikace pro nejrůznější činnosti",
"no_category_apps_description_web3": "Přidejte aplikaci web3 pro vaše rezervační stránky",
"no_category_apps_description_messaging": "Přidejte aplikaci pro zasílání zpráv a nastavte vlastní oznámení a připomenutí", "no_category_apps_description_messaging": "Přidejte aplikaci pro zasílání zpráv a nastavte vlastní oznámení a připomenutí",
"no_category_apps_description_crm": "Přidejte aplikaci CRM, ať jste v obraze, s kým jste se setkali", "no_category_apps_description_crm": "Přidejte aplikaci CRM, ať jste v obraze, s kým jste se setkali",
"installed_app_calendar_description": "Nastavte si kalendáře, ať můžete kontrolovat konflikty a zabránit tak dvojím rezervacím.", "installed_app_calendar_description": "Nastavte si kalendáře, ať můžete kontrolovat konflikty a zabránit tak dvojím rezervacím.",
@ -913,7 +912,6 @@
"installed_app_other_description": "Všechny vaše nainstalované aplikace z ostatních kategorií.", "installed_app_other_description": "Všechny vaše nainstalované aplikace z ostatních kategorií.",
"installed_app_conferencing_description": "Nakonfigurujte konferenční aplikace, které chcete používat", "installed_app_conferencing_description": "Nakonfigurujte konferenční aplikace, které chcete používat",
"installed_app_automation_description": "Konfigurovat aplikace pro automatizaci", "installed_app_automation_description": "Konfigurovat aplikace pro automatizaci",
"installed_app_web3_description": "Nakonfigurujte aplikace web3, které se mají použít pro vaše rezervační stránky",
"installed_app_messaging_description": "Nakonfigurujte aplikace pro zasílání zpráv a nastavte vlastní oznámení a připomenutí", "installed_app_messaging_description": "Nakonfigurujte aplikace pro zasílání zpráv a nastavte vlastní oznámení a připomenutí",
"installed_app_crm_description": "Nakonfigurujte aplikace CRM, které se mají používat ke sledování toho, s kým jste se setkali", "installed_app_crm_description": "Nakonfigurujte aplikace CRM, které se mají používat ke sledování toho, s kým jste se setkali",
"analytics": "Analytické nástroje", "analytics": "Analytické nástroje",
@ -1304,7 +1302,6 @@
"profile_picture": "Profilová fotka", "profile_picture": "Profilová fotka",
"upload": "Nahrát", "upload": "Nahrát",
"add_profile_photo": "Přidat profilovou fotku", "add_profile_photo": "Přidat profilovou fotku",
"web3": "Web3",
"token_address": "Adresa tokenu", "token_address": "Adresa tokenu",
"blockchain": "Blockchain", "blockchain": "Blockchain",
"old_password": "Staré heslo", "old_password": "Staré heslo",
@ -1346,7 +1343,6 @@
"connect_automation_apps": "Připojit aplikace pro automatizaci", "connect_automation_apps": "Připojit aplikace pro automatizaci",
"connect_analytics_apps": "Připojit analytické aplikace", "connect_analytics_apps": "Připojit analytické aplikace",
"connect_other_apps": "Propojit další aplikace", "connect_other_apps": "Propojit další aplikace",
"connect_web3_apps": "Připojení aplikací web3",
"connect_messaging_apps": "Připojení aplikací pro zasílání zpráv", "connect_messaging_apps": "Připojení aplikací pro zasílání zpráv",
"connect_crm_apps": "Připojení aplikací CRM", "connect_crm_apps": "Připojení aplikací CRM",
"current_step_of_total": "Krok {{currentStep}} / {{maxSteps}}", "current_step_of_total": "Krok {{currentStep}} / {{maxSteps}}",
@ -1585,6 +1581,7 @@
"enable_apps": "Povolit aplikace", "enable_apps": "Povolit aplikace",
"enable_apps_description": "Zapněte aplikace, které mohou uživatelé integrovat s aplikací {{appName}}", "enable_apps_description": "Zapněte aplikace, které mohou uživatelé integrovat s aplikací {{appName}}",
"purchase_license": "Kupte si licenci", "purchase_license": "Kupte si licenci",
"already_have_account": "Už máte účet?",
"already_have_key": "Klíč již mám:", "already_have_key": "Klíč již mám:",
"already_have_key_suggestion": "Zkopírujte sem svou stávající proměnnou prostředí CALCOM_LICENSE_KEY.", "already_have_key_suggestion": "Zkopírujte sem svou stávající proměnnou prostředí CALCOM_LICENSE_KEY.",
"app_is_enabled": "Aplikace {{appName}} je povolena", "app_is_enabled": "Aplikace {{appName}} je povolena",

View File

@ -681,7 +681,7 @@
"cal_provide_tandem_meeting_url": "{{appName}} angiver en URL-adresse for Tandem-mødet.", "cal_provide_tandem_meeting_url": "{{appName}} angiver en URL-adresse for Tandem-mødet.",
"cal_provide_video_meeting_url": "{{appName}} angiver en URL-adresse for videomødet.", "cal_provide_video_meeting_url": "{{appName}} angiver en URL-adresse for videomødet.",
"cal_provide_jitsi_meeting_url": "Vi genererer en Jitsi Meet URL til dig.", "cal_provide_jitsi_meeting_url": "Vi genererer en Jitsi Meet URL til dig.",
"cal_provide_huddle01_meeting_url": "{{appName}} vil levere en Huddle01 web3 videomøde-URL.", "cal_provide_huddle01_meeting_url": "{{appName}} vil levere en Huddle01 videomøde-URL.",
"cal_provide_teams_meeting_url": "{{appName}} vil levere en MS Teams møde URL. BEMÆRK: SKAL HA EN ARBEJDS- ELLER SKOLE KONTO", "cal_provide_teams_meeting_url": "{{appName}} vil levere en MS Teams møde URL. BEMÆRK: SKAL HA EN ARBEJDS- ELLER SKOLE KONTO",
"require_payment": "Kræv Betaling", "require_payment": "Kræv Betaling",
"commission_per_transaction": "provision pr. transaktion", "commission_per_transaction": "provision pr. transaktion",
@ -770,13 +770,11 @@
"no_category_apps_description_analytics": "Tilføj en analyseapp til dine bookingsider", "no_category_apps_description_analytics": "Tilføj en analyseapp til dine bookingsider",
"no_category_apps_description_automation": "Tilføj en automatiseringsapp til brug", "no_category_apps_description_automation": "Tilføj en automatiseringsapp til brug",
"no_category_apps_description_other": "Tilføj enhver anden type app for at gøre alle mulige ting", "no_category_apps_description_other": "Tilføj enhver anden type app for at gøre alle mulige ting",
"no_category_apps_description_web3": "Tilføj en web3 app til dine bookingsider",
"installed_app_calendar_description": "Indstil kalenderne til at tjekke for konflikter, for at forhindre dobbeltbookinger.", "installed_app_calendar_description": "Indstil kalenderne til at tjekke for konflikter, for at forhindre dobbeltbookinger.",
"installed_app_payment_description": "Indstil hvilke betalingsbehandlingstjenester der skal bruges, når du opkræver betaling fra dine kunder.", "installed_app_payment_description": "Indstil hvilke betalingsbehandlingstjenester der skal bruges, når du opkræver betaling fra dine kunder.",
"installed_app_analytics_description": "Indstil hvilke analyseapps der skal bruges til dine bookingsider", "installed_app_analytics_description": "Indstil hvilke analyseapps der skal bruges til dine bookingsider",
"installed_app_other_description": "Alle dine installerede apps fra andre kategorier.", "installed_app_other_description": "Alle dine installerede apps fra andre kategorier.",
"installed_app_automation_description": "Indstil hvilke automatiseringsapps der skal bruges", "installed_app_automation_description": "Indstil hvilke automatiseringsapps der skal bruges",
"installed_app_web3_description": "Indstil hvilke web3 apps der skal bruges til dine bookingsider",
"analytics": "Analyser", "analytics": "Analyser",
"empty_installed_apps_headline": "Ingen apps installeret", "empty_installed_apps_headline": "Ingen apps installeret",
"empty_installed_apps_description": "Apps giver dig mulighed for at forbedre din arbejdsgang og forbedre dit planlægningsliv betydeligt.", "empty_installed_apps_description": "Apps giver dig mulighed for at forbedre din arbejdsgang og forbedre dit planlægningsliv betydeligt.",
@ -1132,7 +1130,6 @@
"profile_picture": "Profilbillede", "profile_picture": "Profilbillede",
"upload": "Upload", "upload": "Upload",
"add_profile_photo": "Tilføj profilbillede", "add_profile_photo": "Tilføj profilbillede",
"web3": "Web3",
"token_address": "Token Adresse", "token_address": "Token Adresse",
"blockchain": "Blockchain", "blockchain": "Blockchain",
"old_password": "Gammel adgangskode", "old_password": "Gammel adgangskode",
@ -1171,7 +1168,6 @@
"connect_automation_apps": "Tilslut automatiseringsapps", "connect_automation_apps": "Tilslut automatiseringsapps",
"connect_analytics_apps": "Tilslut analyseapps", "connect_analytics_apps": "Tilslut analyseapps",
"connect_other_apps": "Tilslut andre apps", "connect_other_apps": "Tilslut andre apps",
"connect_web3_apps": "Tilslut web3 apps",
"current_step_of_total": "Trin {{currentStep}} af {{maxSteps}}", "current_step_of_total": "Trin {{currentStep}} af {{maxSteps}}",
"add_variable": "Tilføj variabel", "add_variable": "Tilføj variabel",
"custom_phone_number": "Brugerdefineret telefonnummer", "custom_phone_number": "Brugerdefineret telefonnummer",
@ -1371,6 +1367,7 @@
"disabled_calendar": "Hvis du har en anden kalender installeret vil nye bookinger blive tilføjet til den. Hvis ikke, så forbind en ny kalender, så du ikke går glip af nye bookinger.", "disabled_calendar": "Hvis du har en anden kalender installeret vil nye bookinger blive tilføjet til den. Hvis ikke, så forbind en ny kalender, så du ikke går glip af nye bookinger.",
"enable_apps": "Aktivér Apps", "enable_apps": "Aktivér Apps",
"purchase_license": "Køb en licens", "purchase_license": "Køb en licens",
"already_have_account": "Har du allerede en konto?",
"already_have_key": "Jeg har allerede en nøgle:", "already_have_key": "Jeg har allerede en nøgle:",
"already_have_key_suggestion": "Kopiér venligst din eksisterende CALCOM_LICENSE_KEY environment variabel her.", "already_have_key_suggestion": "Kopiér venligst din eksisterende CALCOM_LICENSE_KEY environment variabel her.",
"app_is_enabled": "{{appName}} er aktiveret", "app_is_enabled": "{{appName}} er aktiveret",

View File

@ -801,7 +801,7 @@
"cal_provide_tandem_meeting_url": "{{appName}} wird eine Tandem Meeting URL zur Verfügung stellen.", "cal_provide_tandem_meeting_url": "{{appName}} wird eine Tandem Meeting URL zur Verfügung stellen.",
"cal_provide_video_meeting_url": "{{appName}} wird eine Video-Meeting URL zur Verfügung stellen.", "cal_provide_video_meeting_url": "{{appName}} wird eine Video-Meeting URL zur Verfügung stellen.",
"cal_provide_jitsi_meeting_url": "Cal stellt eine Jitsi Meet URL zur Verfügung.", "cal_provide_jitsi_meeting_url": "Cal stellt eine Jitsi Meet URL zur Verfügung.",
"cal_provide_huddle01_meeting_url": "{{appName}} wird eine Huddle01 web3 Video-Meeting URL zur Verfügung stellen.", "cal_provide_huddle01_meeting_url": "{{appName}} wird eine Huddle01 Video-Meeting URL zur Verfügung stellen.",
"cal_provide_teams_meeting_url": "{{appName}} wird eine MS Teams Meeting-URL zur Verfügung stellen. HINWEIS: SIE BRAUCHEN EIN BERUFS- ODER SCHULKONTO", "cal_provide_teams_meeting_url": "{{appName}} wird eine MS Teams Meeting-URL zur Verfügung stellen. HINWEIS: SIE BRAUCHEN EIN BERUFS- ODER SCHULKONTO",
"require_payment": "Zahlung erforderlich", "require_payment": "Zahlung erforderlich",
"you_need_to_add_a_name": "Sie müssen einen Namen hinzufügen", "you_need_to_add_a_name": "Sie müssen einen Namen hinzufügen",
@ -904,7 +904,6 @@
"no_category_apps_description_analytics": "Fügen Sie eine Analyse-App für Ihre Buchungsseiten hinzu", "no_category_apps_description_analytics": "Fügen Sie eine Analyse-App für Ihre Buchungsseiten hinzu",
"no_category_apps_description_automation": "Fügen Sie eine Automatisierungs-App hinzu, die verwendet werden soll", "no_category_apps_description_automation": "Fügen Sie eine Automatisierungs-App hinzu, die verwendet werden soll",
"no_category_apps_description_other": "Füge jede beliebige andere Art von App hinzu, um alle möglichen Dinge zu tun", "no_category_apps_description_other": "Füge jede beliebige andere Art von App hinzu, um alle möglichen Dinge zu tun",
"no_category_apps_description_web3": "Fügen Sie eine web3-App für Ihre Buchungsseiten hinzu",
"no_category_apps_description_messaging": "Fügen Sie eine Nachrichten-App hinzu, um benutzerdefinierte Benachrichtigungen und Erinnerungen einzurichten", "no_category_apps_description_messaging": "Fügen Sie eine Nachrichten-App hinzu, um benutzerdefinierte Benachrichtigungen und Erinnerungen einzurichten",
"no_category_apps_description_crm": "Fügen Sie eine CRM-App hinzu, um einen Überblick über die Personen zu behalten, mit denen Sie sich getroffen haben", "no_category_apps_description_crm": "Fügen Sie eine CRM-App hinzu, um einen Überblick über die Personen zu behalten, mit denen Sie sich getroffen haben",
"installed_app_calendar_description": "Legen Sie den/die Kalender in denen nach Konflikten gesucht werden sollen fest, um Doppelbuchungen zu vermeiden.", "installed_app_calendar_description": "Legen Sie den/die Kalender in denen nach Konflikten gesucht werden sollen fest, um Doppelbuchungen zu vermeiden.",
@ -913,7 +912,6 @@
"installed_app_other_description": "Alle von Ihnen installierten Apps aus anderen Kategorien.", "installed_app_other_description": "Alle von Ihnen installierten Apps aus anderen Kategorien.",
"installed_app_conferencing_description": "Konfigurieren Sie, welche Konferenz-Apps verwendet werden sollen", "installed_app_conferencing_description": "Konfigurieren Sie, welche Konferenz-Apps verwendet werden sollen",
"installed_app_automation_description": "Konfigurieren Sie, welche Automatisierungs-Apps verwendet werden sollen", "installed_app_automation_description": "Konfigurieren Sie, welche Automatisierungs-Apps verwendet werden sollen",
"installed_app_web3_description": "Konfigurieren Sie, welche web3-Apps für Ihre Buchungsseiten verwendet werden sollen",
"installed_app_messaging_description": "Konfigurieren Sie zur Einrichtung benutzerdefinierter Benachrichtigungen und Erinnerungen, welche Nachrichten-Apps verwendet werden sollen", "installed_app_messaging_description": "Konfigurieren Sie zur Einrichtung benutzerdefinierter Benachrichtigungen und Erinnerungen, welche Nachrichten-Apps verwendet werden sollen",
"installed_app_crm_description": "Konfigurieren Sie, welche CRM-Apps verwendet werden sollen, um einen Überblick über die Personen zu behalten, mit denen Sie sich getroffen haben", "installed_app_crm_description": "Konfigurieren Sie, welche CRM-Apps verwendet werden sollen, um einen Überblick über die Personen zu behalten, mit denen Sie sich getroffen haben",
"analytics": "Analyse", "analytics": "Analyse",
@ -1304,7 +1302,6 @@
"profile_picture": "Profilbild", "profile_picture": "Profilbild",
"upload": "Upload", "upload": "Upload",
"add_profile_photo": "Profilbild hinzufügen", "add_profile_photo": "Profilbild hinzufügen",
"web3": "Web3",
"token_address": "Adresse des Token", "token_address": "Adresse des Token",
"blockchain": "Blockchain", "blockchain": "Blockchain",
"old_password": "Altes Passwort", "old_password": "Altes Passwort",
@ -1346,7 +1343,6 @@
"connect_automation_apps": "Automatisierungs-Apps verbinden", "connect_automation_apps": "Automatisierungs-Apps verbinden",
"connect_analytics_apps": "Analyse-Apps verbinden", "connect_analytics_apps": "Analyse-Apps verbinden",
"connect_other_apps": "Andere Apps verbinden", "connect_other_apps": "Andere Apps verbinden",
"connect_web3_apps": "web3-Apps verbinden",
"connect_messaging_apps": "Nachrichten-Apps verbinden", "connect_messaging_apps": "Nachrichten-Apps verbinden",
"connect_crm_apps": "CRM-Apps verbinden", "connect_crm_apps": "CRM-Apps verbinden",
"current_step_of_total": "Schritt {{currentStep}} von {{maxSteps}}", "current_step_of_total": "Schritt {{currentStep}} von {{maxSteps}}",
@ -1585,6 +1581,7 @@
"enable_apps": "Apps aktivieren", "enable_apps": "Apps aktivieren",
"enable_apps_description": "Apps aktivieren, die Benutzer mit {{appName}} integrieren können", "enable_apps_description": "Apps aktivieren, die Benutzer mit {{appName}} integrieren können",
"purchase_license": "Lizenz kaufen", "purchase_license": "Lizenz kaufen",
"already_have_account": "Haben Sie bereits ein Konto?",
"already_have_key": "Ich habe bereits einen Schlüssel:", "already_have_key": "Ich habe bereits einen Schlüssel:",
"already_have_key_suggestion": "Bitte fügen Sie Ihre vorhandene CALCOM_LICENSE_KEY Umgebungsvariable hier ein.", "already_have_key_suggestion": "Bitte fügen Sie Ihre vorhandene CALCOM_LICENSE_KEY Umgebungsvariable hier ein.",
"app_is_enabled": "{{appName}} ist aktiviert", "app_is_enabled": "{{appName}} ist aktiviert",

View File

@ -274,5 +274,6 @@
"event_name_tooltip": "Το όνομα που θα εμφανίζεται στα ημερολόγια", "event_name_tooltip": "Το όνομα που θα εμφανίζεται στα ημερολόγια",
"label": "Ετικέτα", "label": "Ετικέτα",
"edit": "Επεξεργασία", "edit": "Επεξεργασία",
"disable_guests": "Απενεργοποίηση επισκεπτών" "disable_guests": "Απενεργοποίηση επισκεπτών",
"already_have_account": "Έχετε ήδη λογαριασμό;"
} }

View File

@ -78,7 +78,7 @@
"cannot_repackage_codebase": "You can not repackage or sell the codebase", "cannot_repackage_codebase": "You can not repackage or sell the codebase",
"acquire_license": "Acquire a commercial license to remove these terms by emailing", "acquire_license": "Acquire a commercial license to remove these terms by emailing",
"terms_summary": "Summary of terms", "terms_summary": "Summary of terms",
"signing_up_terms":"By signing up, you agree to our <2>Terms of Service</2> and <3>Privacy Policy</3>.", "signing_up_terms":"By signing up you agree to our <2>Terms</2> & <3>Privacy Policy</3>.",
"open_env": "Open .env and agree to our License", "open_env": "Open .env and agree to our License",
"env_changed": "I've changed my .env", "env_changed": "I've changed my .env",
"accept_license": "Accept License", "accept_license": "Accept License",
@ -673,6 +673,7 @@
"default_duration": "Default duration", "default_duration": "Default duration",
"default_duration_no_options": "Please choose available durations first", "default_duration_no_options": "Please choose available durations first",
"multiple_duration_mins": "{{count}} $t(minute_timeUnit)", "multiple_duration_mins": "{{count}} $t(minute_timeUnit)",
"multiple_duration_timeUnit": "{{count}} $t({{unit}}_timeUnit)",
"minutes": "Minutes", "minutes": "Minutes",
"round_robin": "Round Robin", "round_robin": "Round Robin",
"round_robin_description": "Cycle meetings between multiple team members.", "round_robin_description": "Cycle meetings between multiple team members.",
@ -818,7 +819,7 @@
"cal_provide_tandem_meeting_url": "{{appName}} will provide a Tandem meeting URL.", "cal_provide_tandem_meeting_url": "{{appName}} will provide a Tandem meeting URL.",
"cal_provide_video_meeting_url": "{{appName}} will provide a video meeting URL.", "cal_provide_video_meeting_url": "{{appName}} will provide a video meeting URL.",
"cal_provide_jitsi_meeting_url": "We will generate a Jitsi Meet URL for you.", "cal_provide_jitsi_meeting_url": "We will generate a Jitsi Meet URL for you.",
"cal_provide_huddle01_meeting_url": "{{appName}} will provide a Huddle01 web3 video meeting URL.", "cal_provide_huddle01_meeting_url": "{{appName}} will provide a Huddle01 video meeting URL.",
"cal_provide_teams_meeting_url": "{{appName}} will provide a MS Teams meeting URL. NOTE: MUST HAVE A WORK OR SCHOOL ACCOUNT", "cal_provide_teams_meeting_url": "{{appName}} will provide a MS Teams meeting URL. NOTE: MUST HAVE A WORK OR SCHOOL ACCOUNT",
"require_payment": "Require Payment", "require_payment": "Require Payment",
"you_need_to_add_a_name": "You need to add a name", "you_need_to_add_a_name": "You need to add a name",
@ -879,6 +880,7 @@
"toggle_calendars_conflict": "Toggle the calendars you want to check for conflicts to prevent double bookings.", "toggle_calendars_conflict": "Toggle the calendars you want to check for conflicts to prevent double bookings.",
"connect_additional_calendar": "Connect additional calendar", "connect_additional_calendar": "Connect additional calendar",
"calendar_updated_successfully": "Calendar updated successfully", "calendar_updated_successfully": "Calendar updated successfully",
"check_here":"Check here",
"conferencing": "Conferencing", "conferencing": "Conferencing",
"calendar": "Calendar", "calendar": "Calendar",
"payments": "Payments", "payments": "Payments",
@ -922,7 +924,6 @@
"no_category_apps_description_analytics": "Add an analytics app for your booking pages", "no_category_apps_description_analytics": "Add an analytics app for your booking pages",
"no_category_apps_description_automation": "Add an automation app to use", "no_category_apps_description_automation": "Add an automation app to use",
"no_category_apps_description_other": "Add any other type of app to do all sorts of things", "no_category_apps_description_other": "Add any other type of app to do all sorts of things",
"no_category_apps_description_web3": "Add a web3 app for your booking pages",
"no_category_apps_description_messaging": "Add a messaging app to set up custom notifications & reminders", "no_category_apps_description_messaging": "Add a messaging app to set up custom notifications & reminders",
"no_category_apps_description_crm": "Add a CRM app to keep track of who you've met with", "no_category_apps_description_crm": "Add a CRM app to keep track of who you've met with",
"installed_app_calendar_description": "Set the calendars to check for conflicts to prevent double bookings.", "installed_app_calendar_description": "Set the calendars to check for conflicts to prevent double bookings.",
@ -931,7 +932,6 @@
"installed_app_other_description": "All your installed apps from other categories.", "installed_app_other_description": "All your installed apps from other categories.",
"installed_app_conferencing_description": "Configure which conferencing apps to use", "installed_app_conferencing_description": "Configure which conferencing apps to use",
"installed_app_automation_description": "Configure which automation apps to use", "installed_app_automation_description": "Configure which automation apps to use",
"installed_app_web3_description": "Configure which web3 apps to use for your booking pages",
"installed_app_messaging_description": "Configure which messaging apps to use for setting up custom notifications & reminders", "installed_app_messaging_description": "Configure which messaging apps to use for setting up custom notifications & reminders",
"installed_app_crm_description": "Configure which CRM apps to use for keeping track of who you've met with", "installed_app_crm_description": "Configure which CRM apps to use for keeping track of who you've met with",
"analytics": "Analytics", "analytics": "Analytics",
@ -1324,7 +1324,6 @@
"profile_picture": "Profile Picture", "profile_picture": "Profile Picture",
"upload": "Upload", "upload": "Upload",
"add_profile_photo": "Add profile photo", "add_profile_photo": "Add profile photo",
"web3": "Web3",
"token_address": "Token Address", "token_address": "Token Address",
"blockchain": "Blockchain", "blockchain": "Blockchain",
"old_password": "Old password", "old_password": "Old password",
@ -1366,7 +1365,6 @@
"connect_automation_apps": "Connect automation apps", "connect_automation_apps": "Connect automation apps",
"connect_analytics_apps": "Connect analytics apps", "connect_analytics_apps": "Connect analytics apps",
"connect_other_apps": "Connect other apps", "connect_other_apps": "Connect other apps",
"connect_web3_apps": "Connect web3 apps",
"connect_messaging_apps": "Connect messaging apps", "connect_messaging_apps": "Connect messaging apps",
"connect_crm_apps": "Connect CRM apps", "connect_crm_apps": "Connect CRM apps",
"current_step_of_total": "Step {{currentStep}} of {{maxSteps}}", "current_step_of_total": "Step {{currentStep}} of {{maxSteps}}",
@ -1484,6 +1482,8 @@
"report_app": "Report app", "report_app": "Report app",
"limit_booking_frequency": "Limit booking frequency", "limit_booking_frequency": "Limit booking frequency",
"limit_booking_frequency_description": "Limit how many times this event can be booked", "limit_booking_frequency_description": "Limit how many times this event can be booked",
"limit_booking_only_first_slot": "Limit booking only first slot",
"limit_booking_only_first_slot_description": "Allow only the first slot of every day to be booked",
"limit_total_booking_duration": "Limit total booking duration", "limit_total_booking_duration": "Limit total booking duration",
"limit_total_booking_duration_description": "Limit total amount of time that this event can be booked", "limit_total_booking_duration_description": "Limit total amount of time that this event can be booked",
"add_limit": "Add Limit", "add_limit": "Add Limit",
@ -1556,6 +1556,9 @@
"member_already_invited": "Member has already been invited", "member_already_invited": "Member has already been invited",
"already_in_use_error": "Username already in use", "already_in_use_error": "Username already in use",
"enter_email_or_username": "Enter an email or username", "enter_email_or_username": "Enter an email or username",
"enter_email": "Enter an email",
"enter_emails": "Enter emails",
"too_many_invites": "You are limited to inviting a maximum of {{nbUsers}} users at once.",
"team_name_taken": "This name is already taken", "team_name_taken": "This name is already taken",
"must_enter_team_name": "Must enter a team name", "must_enter_team_name": "Must enter a team name",
"team_url_required": "Must enter a team URL", "team_url_required": "Must enter a team URL",
@ -1608,7 +1611,7 @@
"enable_apps": "Enable Apps", "enable_apps": "Enable Apps",
"enable_apps_description": "Enable apps that users can integrate with {{appName}}", "enable_apps_description": "Enable apps that users can integrate with {{appName}}",
"purchase_license": "Purchase a License", "purchase_license": "Purchase a License",
"already_have_account":"I already have an account", "already_have_account":"Already have an account?",
"already_have_key": "I already have a key:", "already_have_key": "I already have a key:",
"already_have_key_suggestion": "Please copy your existing CALCOM_LICENSE_KEY environment variable here.", "already_have_key_suggestion": "Please copy your existing CALCOM_LICENSE_KEY environment variable here.",
"app_is_enabled": "{{appName}} is enabled", "app_is_enabled": "{{appName}} is enabled",
@ -1866,6 +1869,7 @@
"looking_for_more_analytics": "Looking for more analytics?", "looking_for_more_analytics": "Looking for more analytics?",
"looking_for_more_insights": "Looking for more Insights?", "looking_for_more_insights": "Looking for more Insights?",
"add_filter": "Add filter", "add_filter": "Add filter",
"remove_filters": "Clear all filters",
"select_user": "Select User", "select_user": "Select User",
"select_event_type": "Select Event Type", "select_event_type": "Select Event Type",
"select_date_range": "Select Date Range", "select_date_range": "Select Date Range",

View File

@ -904,7 +904,6 @@
"no_category_apps_description_analytics": "Añada una aplicación de análisis para sus páginas de reserva", "no_category_apps_description_analytics": "Añada una aplicación de análisis para sus páginas de reserva",
"no_category_apps_description_automation": "Añada una aplicación de automatización para utilizar", "no_category_apps_description_automation": "Añada una aplicación de automatización para utilizar",
"no_category_apps_description_other": "Añade cualquier otro tipo de aplicación para hacer todo tipo de tareas", "no_category_apps_description_other": "Añade cualquier otro tipo de aplicación para hacer todo tipo de tareas",
"no_category_apps_description_web3": "Agregue una aplicación web3 para sus páginas de reserva",
"no_category_apps_description_messaging": "Agregue una aplicación de mensajería para configurar notificaciones y recordatorios personalizados", "no_category_apps_description_messaging": "Agregue una aplicación de mensajería para configurar notificaciones y recordatorios personalizados",
"no_category_apps_description_crm": "Agregue una aplicación CRM para realizar un seguimiento de con quiénes se ha reunido", "no_category_apps_description_crm": "Agregue una aplicación CRM para realizar un seguimiento de con quiénes se ha reunido",
"installed_app_calendar_description": "Configure el(los) calendario(s) para comprobar conflictos y evitar reservas duplicadas.", "installed_app_calendar_description": "Configure el(los) calendario(s) para comprobar conflictos y evitar reservas duplicadas.",
@ -913,7 +912,6 @@
"installed_app_other_description": "Todas tus aplicaciones instaladas de otras categorías.", "installed_app_other_description": "Todas tus aplicaciones instaladas de otras categorías.",
"installed_app_conferencing_description": "Configure qué aplicaciones de conferencia usará", "installed_app_conferencing_description": "Configure qué aplicaciones de conferencia usará",
"installed_app_automation_description": "Configure qué aplicaciones de automatización va a utilizar", "installed_app_automation_description": "Configure qué aplicaciones de automatización va a utilizar",
"installed_app_web3_description": "Configure qué aplicaciones web3 usar para sus páginas de reserva",
"installed_app_messaging_description": "Configure qué aplicaciones de mensajería usar para configurar notificaciones y recordatorios personalizados", "installed_app_messaging_description": "Configure qué aplicaciones de mensajería usar para configurar notificaciones y recordatorios personalizados",
"installed_app_crm_description": "Configure qué aplicaciones de CRM usar para realizar un seguimiento de las personas con las que se ha reunido", "installed_app_crm_description": "Configure qué aplicaciones de CRM usar para realizar un seguimiento de las personas con las que se ha reunido",
"analytics": "Análisis", "analytics": "Análisis",
@ -1304,7 +1302,6 @@
"profile_picture": "Imagen de perfil", "profile_picture": "Imagen de perfil",
"upload": "Cargar", "upload": "Cargar",
"add_profile_photo": "Agregar foto de perfil", "add_profile_photo": "Agregar foto de perfil",
"web3": "Web3",
"token_address": "Dirección del token", "token_address": "Dirección del token",
"blockchain": "Blockchain", "blockchain": "Blockchain",
"old_password": "Contraseña anterior", "old_password": "Contraseña anterior",
@ -1346,7 +1343,6 @@
"connect_automation_apps": "Conecte aplicaciones de automatización", "connect_automation_apps": "Conecte aplicaciones de automatización",
"connect_analytics_apps": "Conecte aplicaciones de análisis", "connect_analytics_apps": "Conecte aplicaciones de análisis",
"connect_other_apps": "Conectar otras aplicaciones", "connect_other_apps": "Conectar otras aplicaciones",
"connect_web3_apps": "Conectar aplicaciones web3",
"connect_messaging_apps": "Conectar aplicaciones de mensajería", "connect_messaging_apps": "Conectar aplicaciones de mensajería",
"connect_crm_apps": "Conectar aplicaciones CRM", "connect_crm_apps": "Conectar aplicaciones CRM",
"current_step_of_total": "Paso {{currentStep}} de {{maxSteps}}", "current_step_of_total": "Paso {{currentStep}} de {{maxSteps}}",
@ -1585,6 +1581,7 @@
"enable_apps": "Activar aplicaciones", "enable_apps": "Activar aplicaciones",
"enable_apps_description": "Habilite las aplicaciones que los usuarios pueden integrar con {{appName}}", "enable_apps_description": "Habilite las aplicaciones que los usuarios pueden integrar con {{appName}}",
"purchase_license": "Compre una licencia", "purchase_license": "Compre una licencia",
"already_have_account": "¿Ya tienes una cuenta?",
"already_have_key": "Ya tengo una clave:", "already_have_key": "Ya tengo una clave:",
"already_have_key_suggestion": "Copie aquí su variable de entorno CALCOM_LICENSE_KEY existente.", "already_have_key_suggestion": "Copie aquí su variable de entorno CALCOM_LICENSE_KEY existente.",
"app_is_enabled": "{{appName}} está activada", "app_is_enabled": "{{appName}} está activada",

View File

@ -679,7 +679,6 @@
"profile_picture": "Profileko irudia", "profile_picture": "Profileko irudia",
"upload": "Kargatu", "upload": "Kargatu",
"add_profile_photo": "Gehitu profileko argazkia", "add_profile_photo": "Gehitu profileko argazkia",
"web3": "Web3",
"old_password": "Pasahitz zaharra", "old_password": "Pasahitz zaharra",
"secure_password": "Zure pasahitz berri super segurua", "secure_password": "Zure pasahitz berri super segurua",
"error_updating_password": "Errorea pasahitza eguneratzean", "error_updating_password": "Errorea pasahitza eguneratzean",
@ -753,6 +752,7 @@
"enter_email_or_username": "Sartu email edo erabiltzaile izen bat", "enter_email_or_username": "Sartu email edo erabiltzaile izen bat",
"team_name_taken": "Izen hau dagoeneko hartua dago", "team_name_taken": "Izen hau dagoeneko hartua dago",
"must_enter_team_name": "Taldearentzat izen bat behar da", "must_enter_team_name": "Taldearentzat izen bat behar da",
"already_have_account": "Baduzu kontua dagoeneko?",
"fill_this_field": "Mesedez, bete ezazu eremu hau", "fill_this_field": "Mesedez, bete ezazu eremu hau",
"options": "Aukerak", "options": "Aukerak",
"add_an_option": "Gehitu aukera bat", "add_an_option": "Gehitu aukera bat",

View File

@ -660,6 +660,7 @@
"default_duration": "Durée par défaut", "default_duration": "Durée par défaut",
"default_duration_no_options": "Veuillez d'abord choisir les durées disponibles", "default_duration_no_options": "Veuillez d'abord choisir les durées disponibles",
"multiple_duration_mins": "{{count}} $t(minute_timeUnit)", "multiple_duration_mins": "{{count}} $t(minute_timeUnit)",
"multiple_duration_timeUnit": "{{count}} $t({{unit}}_timeUnit)",
"minutes": "minutes", "minutes": "minutes",
"round_robin": "Round-robin", "round_robin": "Round-robin",
"round_robin_description": "Alternez vos rendez-vous entre plusieurs membres d'équipe.", "round_robin_description": "Alternez vos rendez-vous entre plusieurs membres d'équipe.",
@ -805,7 +806,7 @@
"cal_provide_tandem_meeting_url": "{{appName}} fournira un lien de rendez-vous Tandem.", "cal_provide_tandem_meeting_url": "{{appName}} fournira un lien de rendez-vous Tandem.",
"cal_provide_video_meeting_url": "{{appName}} fournira un lien de rendez-vous vidéo.", "cal_provide_video_meeting_url": "{{appName}} fournira un lien de rendez-vous vidéo.",
"cal_provide_jitsi_meeting_url": "Nous générerons un lien de réunion Jitsi Meet pour vous.", "cal_provide_jitsi_meeting_url": "Nous générerons un lien de réunion Jitsi Meet pour vous.",
"cal_provide_huddle01_meeting_url": "{{appName}} fournira un lien de rendez-vous vidéo Huddle01 web3.", "cal_provide_huddle01_meeting_url": "{{appName}} fournira un lien de rendez-vous vidéo Huddle01.",
"cal_provide_teams_meeting_url": "{{appName}} fournira un lien de rendez-vous MS Teams. NOTE : IL FAUT AVOIR UN COMPTE PROFESSIONNEL OU SCOLAIRE", "cal_provide_teams_meeting_url": "{{appName}} fournira un lien de rendez-vous MS Teams. NOTE : IL FAUT AVOIR UN COMPTE PROFESSIONNEL OU SCOLAIRE",
"require_payment": "Exiger un paiement", "require_payment": "Exiger un paiement",
"you_need_to_add_a_name": "Vous devez ajouter un nom", "you_need_to_add_a_name": "Vous devez ajouter un nom",
@ -909,7 +910,6 @@
"no_category_apps_description_analytics": "Ajoutez une application d'analyse pour vos pages de réservation.", "no_category_apps_description_analytics": "Ajoutez une application d'analyse pour vos pages de réservation.",
"no_category_apps_description_automation": "Ajoutez une application d'automatisation.", "no_category_apps_description_automation": "Ajoutez une application d'automatisation.",
"no_category_apps_description_other": "Ajoutez n'importe quel autre type d'application pour faire toutes sortes de choses.", "no_category_apps_description_other": "Ajoutez n'importe quel autre type d'application pour faire toutes sortes de choses.",
"no_category_apps_description_web3": "Ajoutez une application Web3 pour vos pages de réservation.",
"no_category_apps_description_messaging": "Ajoutez une application de messagerie pour configurer des notifications et des rappels personnalisés.", "no_category_apps_description_messaging": "Ajoutez une application de messagerie pour configurer des notifications et des rappels personnalisés.",
"no_category_apps_description_crm": "Ajoutez une application de CRM pour garder une trace des personnes que vous avez rencontrées.", "no_category_apps_description_crm": "Ajoutez une application de CRM pour garder une trace des personnes que vous avez rencontrées.",
"installed_app_calendar_description": "Définissez les calendriers pour vérifier les conflits afin d'éviter les doubles réservations.", "installed_app_calendar_description": "Définissez les calendriers pour vérifier les conflits afin d'éviter les doubles réservations.",
@ -918,7 +918,6 @@
"installed_app_other_description": "Toutes vos applications installées à partir d'autres catégories.", "installed_app_other_description": "Toutes vos applications installées à partir d'autres catégories.",
"installed_app_conferencing_description": "Configurez les applications de conférence à utiliser.", "installed_app_conferencing_description": "Configurez les applications de conférence à utiliser.",
"installed_app_automation_description": "Configurez les applications d'automatisation à utiliser.", "installed_app_automation_description": "Configurez les applications d'automatisation à utiliser.",
"installed_app_web3_description": "Configurez les applications Web3 à utiliser pour vos pages de réservation.",
"installed_app_messaging_description": "Configurez les applications de messagerie à utiliser pour configurer des notifications et des rappels personnalisés.", "installed_app_messaging_description": "Configurez les applications de messagerie à utiliser pour configurer des notifications et des rappels personnalisés.",
"installed_app_crm_description": "Configurez les applications de CRM à utiliser pour garder une trace des personnes que vous avez rencontrées.", "installed_app_crm_description": "Configurez les applications de CRM à utiliser pour garder une trace des personnes que vous avez rencontrées.",
"analytics": "Analytiques", "analytics": "Analytiques",
@ -1309,7 +1308,6 @@
"profile_picture": "Photo de profil", "profile_picture": "Photo de profil",
"upload": "Télécharger", "upload": "Télécharger",
"add_profile_photo": "Ajouter une photo de profil", "add_profile_photo": "Ajouter une photo de profil",
"web3": "Web3",
"token_address": "Adresse du token", "token_address": "Adresse du token",
"blockchain": "Blockchain", "blockchain": "Blockchain",
"old_password": "Ancien mot de passe", "old_password": "Ancien mot de passe",
@ -1351,7 +1349,6 @@
"connect_automation_apps": "Connecter des apps d'automatisation", "connect_automation_apps": "Connecter des apps d'automatisation",
"connect_analytics_apps": "Connecter des apps d'analytique", "connect_analytics_apps": "Connecter des apps d'analytique",
"connect_other_apps": "Connecter d'autres applications", "connect_other_apps": "Connecter d'autres applications",
"connect_web3_apps": "Connecter des apps Web3",
"connect_messaging_apps": "Connecter des apps de messagerie", "connect_messaging_apps": "Connecter des apps de messagerie",
"connect_crm_apps": "Connecter des apps de CRM", "connect_crm_apps": "Connecter des apps de CRM",
"current_step_of_total": "Étape {{currentStep}} sur {{maxSteps}}", "current_step_of_total": "Étape {{currentStep}} sur {{maxSteps}}",
@ -1847,6 +1844,7 @@
"looking_for_more_analytics": "Vous cherchez plus d'analyses ?", "looking_for_more_analytics": "Vous cherchez plus d'analyses ?",
"looking_for_more_insights": "Vous cherchez plus de statistiques ?", "looking_for_more_insights": "Vous cherchez plus de statistiques ?",
"add_filter": "Ajouter un filtre", "add_filter": "Ajouter un filtre",
"remove_filters": "Effacer les filtres",
"select_user": "Sélectionner un utilisateur", "select_user": "Sélectionner un utilisateur",
"select_event_type": "Sélectionner un type d'événement", "select_event_type": "Sélectionner un type d'événement",
"select_date_range": "Sélectionner une plage de dates", "select_date_range": "Sélectionner une plage de dates",
@ -2089,11 +2087,17 @@
"overlay_my_calendar": "Superposer mon calendrier", "overlay_my_calendar": "Superposer mon calendrier",
"overlay_my_calendar_toc": "En vous connectant à votre calendrier, vous acceptez notre politique de confidentialité et nos conditions d'utilisation. Vous pouvez révoquer cet accès à tout moment.", "overlay_my_calendar_toc": "En vous connectant à votre calendrier, vous acceptez notre politique de confidentialité et nos conditions d'utilisation. Vous pouvez révoquer cet accès à tout moment.",
"view_overlay_calendar_events": "Consultez les événements de votre calendrier afin d'éviter les réservations incompatibles.", "view_overlay_calendar_events": "Consultez les événements de votre calendrier afin d'éviter les réservations incompatibles.",
"calendars_were_checking_for_conflicts": "Calendriers dont nous vérifions les conflits",
"availabilty_schedules": "Horaires de disponibilité",
"manage_calendars": "Gérer les calendriers",
"manage_availability_schedules": "Gérer les horaires de disponibilité",
"lock_timezone_toggle_on_booking_page": "Verrouiller le fuseau horaire sur la page de réservation", "lock_timezone_toggle_on_booking_page": "Verrouiller le fuseau horaire sur la page de réservation",
"description_lock_timezone_toggle_on_booking_page": "Pour verrouiller le fuseau horaire sur la page de réservation, utile pour les événements en personne.", "description_lock_timezone_toggle_on_booking_page": "Pour verrouiller le fuseau horaire sur la page de réservation, utile pour les événements en personne.",
"number_in_international_format": "Veuillez entrer le numéro au format international.", "number_in_international_format": "Veuillez entrer le numéro au format international.",
"extensive_whitelabeling": "Marque blanche étendue", "extensive_whitelabeling": "Marque blanche étendue",
"unlimited_teams": "Équipes illimitées", "unlimited_teams": "Équipes illimitées",
"troubleshooter_tooltip": "Ouvrez l'outil de dépannage et déterminez ce qui ne va pas avec votre planning",
"need_help": "Besoin d'aide ?", "need_help": "Besoin d'aide ?",
"troubleshooter": "Dépannage",
"ADD_NEW_STRINGS_ABOVE_THIS_LINE_TO_PREVENT_MERGE_CONFLICTS": "↑↑↑↑↑↑↑↑↑↑↑↑↑ Ajoutez vos nouvelles chaînes ci-dessus ↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑" "ADD_NEW_STRINGS_ABOVE_THIS_LINE_TO_PREVENT_MERGE_CONFLICTS": "↑↑↑↑↑↑↑↑↑↑↑↑↑ Ajoutez vos nouvelles chaînes ci-dessus ↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑"
} }

View File

@ -801,7 +801,7 @@
"cal_provide_tandem_meeting_url": "{{appName}} יספק כתובת קישור לפגישת Tandem.", "cal_provide_tandem_meeting_url": "{{appName}} יספק כתובת קישור לפגישת Tandem.",
"cal_provide_video_meeting_url": "{{appName}} יספק כתובת קישור לפגישת וידאו.", "cal_provide_video_meeting_url": "{{appName}} יספק כתובת קישור לפגישת וידאו.",
"cal_provide_jitsi_meeting_url": "אנחנו ניצור עבורך כתובת URL לפגישת Jitsi Meet.", "cal_provide_jitsi_meeting_url": "אנחנו ניצור עבורך כתובת URL לפגישת Jitsi Meet.",
"cal_provide_huddle01_meeting_url": "{{appName}} יספק כתובת קישור לפגישת וידאו web3 ב- Huddle01.", "cal_provide_huddle01_meeting_url": "{{appName}} יספק כתובת קישור לפגישת וידאו ב- Huddle01.",
"cal_provide_teams_meeting_url": "{{appName}} יספק כתובת קישור לפגישת MS Teams. הערה: חייב להיות חשבון של מקום עבודה או בית ספר", "cal_provide_teams_meeting_url": "{{appName}} יספק כתובת קישור לפגישת MS Teams. הערה: חייב להיות חשבון של מקום עבודה או בית ספר",
"require_payment": "דרישת תשלום", "require_payment": "דרישת תשלום",
"you_need_to_add_a_name": "יש להוסיף שם", "you_need_to_add_a_name": "יש להוסיף שם",
@ -904,7 +904,6 @@
"no_category_apps_description_analytics": "להוסיף אפליקציה לניתוח נתונים עבור דפי ההזמנות שלך", "no_category_apps_description_analytics": "להוסיף אפליקציה לניתוח נתונים עבור דפי ההזמנות שלך",
"no_category_apps_description_automation": "להוסיף אפליקציית אוטומציה שברצונך להשתמש בה", "no_category_apps_description_automation": "להוסיף אפליקציית אוטומציה שברצונך להשתמש בה",
"no_category_apps_description_other": "הוסף/הוסיפי אפליקציה מכל סוג אחר לביצוע פעולות שונות", "no_category_apps_description_other": "הוסף/הוסיפי אפליקציה מכל סוג אחר לביצוע פעולות שונות",
"no_category_apps_description_web3": "הוסף אפליקצית web3 לעמודי תזמון הפגישות שלך",
"no_category_apps_description_messaging": "הוסף/הוסיפי אפליקציית מסרים מידיים כדי להגדיר התראות ותזכורות בהתאמה אישית", "no_category_apps_description_messaging": "הוסף/הוסיפי אפליקציית מסרים מידיים כדי להגדיר התראות ותזכורות בהתאמה אישית",
"no_category_apps_description_crm": "הוסף/י אפליקציית CRM כדי לנהל מעקב אחר האנשים שנפגשת איתם", "no_category_apps_description_crm": "הוסף/י אפליקציית CRM כדי לנהל מעקב אחר האנשים שנפגשת איתם",
"installed_app_calendar_description": "הגדר/הגדירח את לוחות השנה כדי לבדוק אם יש התנגשויות על מנת למנוע כפל הזמנות.", "installed_app_calendar_description": "הגדר/הגדירח את לוחות השנה כדי לבדוק אם יש התנגשויות על מנת למנוע כפל הזמנות.",
@ -913,7 +912,6 @@
"installed_app_other_description": "כל האפליקציות המותקנות שלך מקטגוריות אחרות.", "installed_app_other_description": "כל האפליקציות המותקנות שלך מקטגוריות אחרות.",
"installed_app_conferencing_description": "הגדר/הגדירי את האפליקציות לשיחות ועידה שבהן ברצונך להשתמש", "installed_app_conferencing_description": "הגדר/הגדירי את האפליקציות לשיחות ועידה שבהן ברצונך להשתמש",
"installed_app_automation_description": "הגדרת אפליקציות האוטומציה שבהן ברצונך להשתמש", "installed_app_automation_description": "הגדרת אפליקציות האוטומציה שבהן ברצונך להשתמש",
"installed_app_web3_description": "הגדר באיזה אפליקציות web3 להשתמש בעמודי תזמון הפגישות שלך",
"installed_app_messaging_description": "הגדר/הגדירי את אפליקציות המסרים המידיים שבהן ברצונך להשתמש להגדרת עדכונים ותזכורות", "installed_app_messaging_description": "הגדר/הגדירי את אפליקציות המסרים המידיים שבהן ברצונך להשתמש להגדרת עדכונים ותזכורות",
"installed_app_crm_description": "הגדר/הגדירי את אפליקציות ה-CRM שבהן ברצונך להשתמש לניהול מעקב אחר האנשים שנפגשת איתם", "installed_app_crm_description": "הגדר/הגדירי את אפליקציות ה-CRM שבהן ברצונך להשתמש לניהול מעקב אחר האנשים שנפגשת איתם",
"analytics": "ניתוח נתונים", "analytics": "ניתוח נתונים",
@ -1304,7 +1302,6 @@
"profile_picture": "תמונת פרופיל", "profile_picture": "תמונת פרופיל",
"upload": "העלאה", "upload": "העלאה",
"add_profile_photo": "הוספת תמונת פרופיל", "add_profile_photo": "הוספת תמונת פרופיל",
"web3": "Web3",
"token_address": "כתובת הטוקן", "token_address": "כתובת הטוקן",
"blockchain": "בלוקצ'יין", "blockchain": "בלוקצ'יין",
"old_password": "סיסמה ישנה", "old_password": "סיסמה ישנה",
@ -1346,7 +1343,6 @@
"connect_automation_apps": "קישור אפליקציות אוטומציה", "connect_automation_apps": "קישור אפליקציות אוטומציה",
"connect_analytics_apps": "קישור אפליקציות ניתוח נתונים", "connect_analytics_apps": "קישור אפליקציות ניתוח נתונים",
"connect_other_apps": "חיבור אפליקציות אחרות", "connect_other_apps": "חיבור אפליקציות אחרות",
"connect_web3_apps": "חבר אפליקציות web3",
"connect_messaging_apps": "קישור אפליקציות מסרים מידיים", "connect_messaging_apps": "קישור אפליקציות מסרים מידיים",
"connect_crm_apps": "קישור אפליקציות CRM", "connect_crm_apps": "קישור אפליקציות CRM",
"current_step_of_total": "שלב {{currentStep}} מתוך {{maxSteps}}", "current_step_of_total": "שלב {{currentStep}} מתוך {{maxSteps}}",
@ -1585,6 +1581,7 @@
"enable_apps": "הפעלת אפליקציות", "enable_apps": "הפעלת אפליקציות",
"enable_apps_description": "הפעל/הפעילי אפליקציות שמשתמשים יוכלו לשלב עם {{appName}}", "enable_apps_description": "הפעל/הפעילי אפליקציות שמשתמשים יוכלו לשלב עם {{appName}}",
"purchase_license": "רכוש רישיון", "purchase_license": "רכוש רישיון",
"already_have_account": "כבר יש לך חשבון?",
"already_have_key": "כבר יש לי מפתח:", "already_have_key": "כבר יש לי מפתח:",
"already_have_key_suggestion": "אנא העתק את משתנה הסביבה CALCOM_LICENSE_KEY הקיים שלך לכאן.", "already_have_key_suggestion": "אנא העתק את משתנה הסביבה CALCOM_LICENSE_KEY הקיים שלך לכאן.",
"app_is_enabled": "האפליקציה {{appName}} מופעלת", "app_is_enabled": "האפליקציה {{appName}} מופעלת",

View File

@ -333,5 +333,6 @@
"dark": "Tamna", "dark": "Tamna",
"automatically_adjust_theme": "Automatski prilagodite temu na temelju preferencija pozvanih osoba", "automatically_adjust_theme": "Automatski prilagodite temu na temelju preferencija pozvanih osoba",
"user_dynamic_booking_disabled": "Neki od korisnika u grupi trenutno su onemogućili dinamičke grupne rezervacije", "user_dynamic_booking_disabled": "Neki od korisnika u grupi trenutno su onemogućili dinamičke grupne rezervacije",
"full_name": "Puno ime" "full_name": "Puno ime",
"already_have_account": "Već imate račun?"
} }

View File

@ -801,7 +801,7 @@
"cal_provide_tandem_meeting_url": "{{appName}} fornirà un URL di riunione Tandem.", "cal_provide_tandem_meeting_url": "{{appName}} fornirà un URL di riunione Tandem.",
"cal_provide_video_meeting_url": "{{appName}} fornirà un URL di riunione Daily video.", "cal_provide_video_meeting_url": "{{appName}} fornirà un URL di riunione Daily video.",
"cal_provide_jitsi_meeting_url": "{{appName}} fornirà un URL di riunione Jitsi Meet.", "cal_provide_jitsi_meeting_url": "{{appName}} fornirà un URL di riunione Jitsi Meet.",
"cal_provide_huddle01_meeting_url": "{{appName}} fornirà un URL di riunione Huddle01 web3 video.", "cal_provide_huddle01_meeting_url": "{{appName}} fornirà un URL di riunione Huddle01 video.",
"cal_provide_teams_meeting_url": "{{appName}} fornirà un URL per la riunione MS Teams. NOTA: È NECESSARIO POSSEDERE UN ACCOUNT LAVORATIVO O SCOLASTICO", "cal_provide_teams_meeting_url": "{{appName}} fornirà un URL per la riunione MS Teams. NOTA: È NECESSARIO POSSEDERE UN ACCOUNT LAVORATIVO O SCOLASTICO",
"require_payment": "Richiedi Pagamento", "require_payment": "Richiedi Pagamento",
"you_need_to_add_a_name": "Indica un nome", "you_need_to_add_a_name": "Indica un nome",
@ -904,7 +904,6 @@
"no_category_apps_description_analytics": "Aggiungi un'app di analisi per le tue pagine di prenotazione", "no_category_apps_description_analytics": "Aggiungi un'app di analisi per le tue pagine di prenotazione",
"no_category_apps_description_automation": "Aggiungi un'app di automazione da usare", "no_category_apps_description_automation": "Aggiungi un'app di automazione da usare",
"no_category_apps_description_other": "Aggiungi qualsiasi altra app per fare altre attività", "no_category_apps_description_other": "Aggiungi qualsiasi altra app per fare altre attività",
"no_category_apps_description_web3": "Aggiungi un'app Web3 per le tue pagine di prenotazione",
"no_category_apps_description_messaging": "Aggiungi un'app di messaggistica per impostare notifiche e promemoria personalizzati", "no_category_apps_description_messaging": "Aggiungi un'app di messaggistica per impostare notifiche e promemoria personalizzati",
"no_category_apps_description_crm": "Aggiungi un'app CRM per tenere traccia delle persone che hai incontrato", "no_category_apps_description_crm": "Aggiungi un'app CRM per tenere traccia delle persone che hai incontrato",
"installed_app_calendar_description": "Imposta uno o più calendari per controllare i conflitti ed evitare doppie prenotazioni.", "installed_app_calendar_description": "Imposta uno o più calendari per controllare i conflitti ed evitare doppie prenotazioni.",
@ -913,7 +912,6 @@
"installed_app_other_description": "Tutte le app installate appartenenti ad altre categorie.", "installed_app_other_description": "Tutte le app installate appartenenti ad altre categorie.",
"installed_app_conferencing_description": "Scegli quali app di conferenza utilizzare", "installed_app_conferencing_description": "Scegli quali app di conferenza utilizzare",
"installed_app_automation_description": "Imposta quali app di automazione usare", "installed_app_automation_description": "Imposta quali app di automazione usare",
"installed_app_web3_description": "Imposta quali app Web3 usare per le tue pagine di prenotazione",
"installed_app_messaging_description": "Scegli le app di messaggistica da usare per impostare notifiche e promemoria personalizzati", "installed_app_messaging_description": "Scegli le app di messaggistica da usare per impostare notifiche e promemoria personalizzati",
"installed_app_crm_description": "Scegli quali app CRM utilizzare per tenere traccia delle persone che hai incontrato", "installed_app_crm_description": "Scegli quali app CRM utilizzare per tenere traccia delle persone che hai incontrato",
"analytics": "Analisi", "analytics": "Analisi",
@ -1304,7 +1302,6 @@
"profile_picture": "Immagine del profilo", "profile_picture": "Immagine del profilo",
"upload": "Carica", "upload": "Carica",
"add_profile_photo": "Aggiungi foto del profilo", "add_profile_photo": "Aggiungi foto del profilo",
"web3": "Web3",
"token_address": "Indirizzo token", "token_address": "Indirizzo token",
"blockchain": "Blockchain", "blockchain": "Blockchain",
"old_password": "Vecchia password", "old_password": "Vecchia password",
@ -1346,7 +1343,6 @@
"connect_automation_apps": "Connetti app di automazione", "connect_automation_apps": "Connetti app di automazione",
"connect_analytics_apps": "Connetti app di analisi", "connect_analytics_apps": "Connetti app di analisi",
"connect_other_apps": "Connetti altre app", "connect_other_apps": "Connetti altre app",
"connect_web3_apps": "Connetti app Web3",
"connect_messaging_apps": "Connetti app di messaggistica", "connect_messaging_apps": "Connetti app di messaggistica",
"connect_crm_apps": "Connetti app CRM", "connect_crm_apps": "Connetti app CRM",
"current_step_of_total": "Passo {{currentStep}} di {{maxSteps}}", "current_step_of_total": "Passo {{currentStep}} di {{maxSteps}}",
@ -1585,6 +1581,7 @@
"enable_apps": "Abilita app", "enable_apps": "Abilita app",
"enable_apps_description": "Abilita le app che gli utenti possono integrare con {{appName}}", "enable_apps_description": "Abilita le app che gli utenti possono integrare con {{appName}}",
"purchase_license": "Acquista una licenza", "purchase_license": "Acquista una licenza",
"already_have_account": "Hai già un account?",
"already_have_key": "Ho già una chiave:", "already_have_key": "Ho già una chiave:",
"already_have_key_suggestion": "Copia qui la variabile di ambiente CALCOM_LICENSE_KEY esistente.", "already_have_key_suggestion": "Copia qui la variabile di ambiente CALCOM_LICENSE_KEY esistente.",
"app_is_enabled": "{{appName}} è abilitato", "app_is_enabled": "{{appName}} è abilitato",

View File

@ -801,7 +801,7 @@
"cal_provide_tandem_meeting_url": "{{appName}} は Tandem ミーティングの URL を提供する。", "cal_provide_tandem_meeting_url": "{{appName}} は Tandem ミーティングの URL を提供する。",
"cal_provide_video_meeting_url": "{{appName}} はビデオミーティングの URL を提供する。", "cal_provide_video_meeting_url": "{{appName}} はビデオミーティングの URL を提供する。",
"cal_provide_jitsi_meeting_url": "Cal は Jitsi Meet の URL を提供する。", "cal_provide_jitsi_meeting_url": "Cal は Jitsi Meet の URL を提供する。",
"cal_provide_huddle01_meeting_url": "{{appName}} は Huddle01 web3 ビデオミーティングの URL を提供する。", "cal_provide_huddle01_meeting_url": "{{appName}} は Huddle01 ビデオミーティングの URL を提供する。",
"cal_provide_teams_meeting_url": "{{appName}} は、MS Teams のミーティングの URL を提供する。注: 職場や学校のアカウントが必要です。", "cal_provide_teams_meeting_url": "{{appName}} は、MS Teams のミーティングの URL を提供する。注: 職場や学校のアカウントが必要です。",
"require_payment": "有料", "require_payment": "有料",
"you_need_to_add_a_name": "名前を追加する必要があります", "you_need_to_add_a_name": "名前を追加する必要があります",
@ -904,7 +904,6 @@
"no_category_apps_description_analytics": "予約ページに分析アプリを追加する", "no_category_apps_description_analytics": "予約ページに分析アプリを追加する",
"no_category_apps_description_automation": "使用する自動化アプリを追加する", "no_category_apps_description_automation": "使用する自動化アプリを追加する",
"no_category_apps_description_other": "その他のアプリを追加して、様々なことを実現しましょう", "no_category_apps_description_other": "その他のアプリを追加して、様々なことを実現しましょう",
"no_category_apps_description_web3": "予約ページに Web3 アプリを追加",
"no_category_apps_description_messaging": "カスタム通知とリマインダーを設定するには、メッセージングアプリを追加します", "no_category_apps_description_messaging": "カスタム通知とリマインダーを設定するには、メッセージングアプリを追加します",
"no_category_apps_description_crm": "会った人を記録するには、CRM アプリを追加します", "no_category_apps_description_crm": "会った人を記録するには、CRM アプリを追加します",
"installed_app_calendar_description": "ダブルブッキングを防ぐために、カレンダーの重複をチェックするように設定します。", "installed_app_calendar_description": "ダブルブッキングを防ぐために、カレンダーの重複をチェックするように設定します。",
@ -913,7 +912,6 @@
"installed_app_other_description": "その他のカテゴリーからインストールしたすべてのアプリ。", "installed_app_other_description": "その他のカテゴリーからインストールしたすべてのアプリ。",
"installed_app_conferencing_description": "どの会議アプリを使用するかを設定します", "installed_app_conferencing_description": "どの会議アプリを使用するかを設定します",
"installed_app_automation_description": "どの自動化アプリを使用するかを構成する", "installed_app_automation_description": "どの自動化アプリを使用するかを構成する",
"installed_app_web3_description": "予約ページでどの Web3 アプリを使用するかを構成します",
"installed_app_messaging_description": "カスタム通知とリマインダーの設定にどのメッセージングアプリを使用するかを設定します", "installed_app_messaging_description": "カスタム通知とリマインダーの設定にどのメッセージングアプリを使用するかを設定します",
"installed_app_crm_description": "会った人を記録するのにどの CRM アプリを使用するかを設定します", "installed_app_crm_description": "会った人を記録するのにどの CRM アプリを使用するかを設定します",
"analytics": "分析", "analytics": "分析",
@ -1304,7 +1302,6 @@
"profile_picture": "プロフィール写真", "profile_picture": "プロフィール写真",
"upload": "アップロード", "upload": "アップロード",
"add_profile_photo": "プロフィール写真を追加", "add_profile_photo": "プロフィール写真を追加",
"web3": "Web3",
"token_address": "トークンアドレス", "token_address": "トークンアドレス",
"blockchain": "ブロックチェーン", "blockchain": "ブロックチェーン",
"old_password": "古いパスワード", "old_password": "古いパスワード",
@ -1346,7 +1343,6 @@
"connect_automation_apps": "自動化アプリを接続する", "connect_automation_apps": "自動化アプリを接続する",
"connect_analytics_apps": "分析アプリを接続する", "connect_analytics_apps": "分析アプリを接続する",
"connect_other_apps": "その他のアプリを接続", "connect_other_apps": "その他のアプリを接続",
"connect_web3_apps": "Web3 アプリを接続",
"connect_messaging_apps": "メッセージングアプリを接続する", "connect_messaging_apps": "メッセージングアプリを接続する",
"connect_crm_apps": "CRM アプリを接続する", "connect_crm_apps": "CRM アプリを接続する",
"current_step_of_total": "ステップ {{currentStep}}/{{maxSteps}}", "current_step_of_total": "ステップ {{currentStep}}/{{maxSteps}}",
@ -1585,6 +1581,7 @@
"enable_apps": "アプリを有効にする", "enable_apps": "アプリを有効にする",
"enable_apps_description": "ユーザーが {{appName}} と連携できるアプリを有効にします", "enable_apps_description": "ユーザーが {{appName}} と連携できるアプリを有効にします",
"purchase_license": "ライセンスを購入", "purchase_license": "ライセンスを購入",
"already_have_account": "既にアカウントをお持ちですか?",
"already_have_key": "すでにキーを持っています:", "already_have_key": "すでにキーを持っています:",
"already_have_key_suggestion": "ここに既存の CALCOM_LICENSE_KEY 環境変数をコピーしてください。", "already_have_key_suggestion": "ここに既存の CALCOM_LICENSE_KEY 環境変数をコピーしてください。",
"app_is_enabled": "{{appName}} は有効です", "app_is_enabled": "{{appName}} は有効です",

View File

@ -904,7 +904,6 @@
"no_category_apps_description_analytics": "예약 페이지에 대한 분석 앱 추가", "no_category_apps_description_analytics": "예약 페이지에 대한 분석 앱 추가",
"no_category_apps_description_automation": "사용할 자동화 앱 추가", "no_category_apps_description_automation": "사용할 자동화 앱 추가",
"no_category_apps_description_other": "모든 종류의 작업을 수행하는 다른 유형의 앱을 추가합니다", "no_category_apps_description_other": "모든 종류의 작업을 수행하는 다른 유형의 앱을 추가합니다",
"no_category_apps_description_web3": "예약 페이지에 web3 앱 추가",
"no_category_apps_description_messaging": "사용자 정의 알림 및 미리 알림을 설정하려면 메시징 앱을 추가하세요", "no_category_apps_description_messaging": "사용자 정의 알림 및 미리 알림을 설정하려면 메시징 앱을 추가하세요",
"no_category_apps_description_crm": "CRM 앱을 추가하여 만났던 사람을 추적하세요", "no_category_apps_description_crm": "CRM 앱을 추가하여 만났던 사람을 추적하세요",
"installed_app_calendar_description": "중복 예약을 방지하기 위해 충돌을 확인하도록 캘린더를 설정합니다.", "installed_app_calendar_description": "중복 예약을 방지하기 위해 충돌을 확인하도록 캘린더를 설정합니다.",
@ -913,7 +912,6 @@
"installed_app_other_description": "기타 카테고리에서 설치된 모든 앱.", "installed_app_other_description": "기타 카테고리에서 설치된 모든 앱.",
"installed_app_conferencing_description": "사용할 회의 앱 구성", "installed_app_conferencing_description": "사용할 회의 앱 구성",
"installed_app_automation_description": "사용할 자동화 앱 구성", "installed_app_automation_description": "사용할 자동화 앱 구성",
"installed_app_web3_description": "예약 페이지에 사용할 web3 앱 구성",
"installed_app_messaging_description": "사용자 정의 알림 및 미리 알림 설정에 사용할 메시징 앱 구성", "installed_app_messaging_description": "사용자 정의 알림 및 미리 알림 설정에 사용할 메시징 앱 구성",
"installed_app_crm_description": "만났던 사람을 추적하는 데 사용할 CRM 앱 구성", "installed_app_crm_description": "만났던 사람을 추적하는 데 사용할 CRM 앱 구성",
"analytics": "분석", "analytics": "분석",
@ -1304,7 +1302,6 @@
"profile_picture": "프로필 사진", "profile_picture": "프로필 사진",
"upload": "업로드", "upload": "업로드",
"add_profile_photo": "프로필 사진 추가", "add_profile_photo": "프로필 사진 추가",
"web3": "Web3",
"token_address": "토큰 주소", "token_address": "토큰 주소",
"blockchain": "블록체인", "blockchain": "블록체인",
"old_password": "이전 비밀번호", "old_password": "이전 비밀번호",
@ -1346,7 +1343,6 @@
"connect_automation_apps": "자동화 앱 연결", "connect_automation_apps": "자동화 앱 연결",
"connect_analytics_apps": "분석 앱 연결", "connect_analytics_apps": "분석 앱 연결",
"connect_other_apps": "기타 앱 연결", "connect_other_apps": "기타 앱 연결",
"connect_web3_apps": "web3 앱 연결",
"connect_messaging_apps": "메시징 앱 연결", "connect_messaging_apps": "메시징 앱 연결",
"connect_crm_apps": "CRM 앱 연결", "connect_crm_apps": "CRM 앱 연결",
"current_step_of_total": "{{maxSteps}} 중 {{currentStep}} 단계", "current_step_of_total": "{{maxSteps}} 중 {{currentStep}} 단계",
@ -1585,6 +1581,7 @@
"enable_apps": "앱 활성화", "enable_apps": "앱 활성화",
"enable_apps_description": "사용자가 {{appName}} 앱과 통합할 수 있는 앱 활성화", "enable_apps_description": "사용자가 {{appName}} 앱과 통합할 수 있는 앱 활성화",
"purchase_license": "라이선스 구매", "purchase_license": "라이선스 구매",
"already_have_account": "이미 계정이 있으신가요?",
"already_have_key": "이미 키가 있습니다:", "already_have_key": "이미 키가 있습니다:",
"already_have_key_suggestion": "기존 CALCOM_LICENSE_KEY 환경 변수를 여기에 복사하십시오.", "already_have_key_suggestion": "기존 CALCOM_LICENSE_KEY 환경 변수를 여기에 복사하십시오.",
"app_is_enabled": "{{appName}} 앱이 활성화되었습니다", "app_is_enabled": "{{appName}} 앱이 활성화되었습니다",

View File

@ -124,5 +124,6 @@
"already_have_an_account": "Vai jums jau ir konts?", "already_have_an_account": "Vai jums jau ir konts?",
"create_account": "Izveidot Kontu", "create_account": "Izveidot Kontu",
"confirm_password": "Apstiprināt paroli", "confirm_password": "Apstiprināt paroli",
"confirm_auth_change": "Šis mainīs veidu, kā jūs autorizējaties" "confirm_auth_change": "Šis mainīs veidu, kā jūs autorizējaties",
"already_have_account": "Vai jums jau ir konts?"
} }

View File

@ -801,7 +801,7 @@
"cal_provide_tandem_meeting_url": "{{appName}} zal een Tandem meeting-URL meegeven in de afspraak bevestiging.", "cal_provide_tandem_meeting_url": "{{appName}} zal een Tandem meeting-URL meegeven in de afspraak bevestiging.",
"cal_provide_video_meeting_url": "{{appName}} zal een Daily meeting-URL meegeven in de afspraak bevestiging.", "cal_provide_video_meeting_url": "{{appName}} zal een Daily meeting-URL meegeven in de afspraak bevestiging.",
"cal_provide_jitsi_meeting_url": "{{appName}} zal een Jitsi Meet meeting-URL meegeven in de afspraak bevestiging.", "cal_provide_jitsi_meeting_url": "{{appName}} zal een Jitsi Meet meeting-URL meegeven in de afspraak bevestiging.",
"cal_provide_huddle01_meeting_url": "{{appName}} zal een Huddle01 web3 meeting-URL meegeven in de afspraak bevestiging.", "cal_provide_huddle01_meeting_url": "{{appName}} zal een Huddle01 meeting-URL meegeven in de afspraak bevestiging.",
"cal_provide_teams_meeting_url": "{{appName}} geeft een vergaderings-URL voor MS Teams. OPMERKING: MOET EEN WERK- OF SCHOOLACCOUNT HEBBEN", "cal_provide_teams_meeting_url": "{{appName}} geeft een vergaderings-URL voor MS Teams. OPMERKING: MOET EEN WERK- OF SCHOOLACCOUNT HEBBEN",
"require_payment": "Betaling vereisen", "require_payment": "Betaling vereisen",
"you_need_to_add_a_name": "U moet een naam toevoegen", "you_need_to_add_a_name": "U moet een naam toevoegen",
@ -904,7 +904,6 @@
"no_category_apps_description_analytics": "Voeg een analyse-app toe aan uw boekingspagina's", "no_category_apps_description_analytics": "Voeg een analyse-app toe aan uw boekingspagina's",
"no_category_apps_description_automation": "Voeg een automatiseringsapp toe om te gebruiken", "no_category_apps_description_automation": "Voeg een automatiseringsapp toe om te gebruiken",
"no_category_apps_description_other": "Voeg een ander type app toe om allerlei soorten dingen te doen", "no_category_apps_description_other": "Voeg een ander type app toe om allerlei soorten dingen te doen",
"no_category_apps_description_web3": "Voeg een web3-app toe aan uw boekingspagina's",
"no_category_apps_description_messaging": "Voeg een berichtenapp toe om aangepaste meldingen en herinneringen in te stellen", "no_category_apps_description_messaging": "Voeg een berichtenapp toe om aangepaste meldingen en herinneringen in te stellen",
"no_category_apps_description_crm": "Voeg een CRM-app toe om bij te houden met wie u heeft ontmoet", "no_category_apps_description_crm": "Voeg een CRM-app toe om bij te houden met wie u heeft ontmoet",
"installed_app_calendar_description": "Stel de agenda('s) in om te controleren op conflicten om dubbele boekingen te voorkomen.", "installed_app_calendar_description": "Stel de agenda('s) in om te controleren op conflicten om dubbele boekingen te voorkomen.",
@ -913,7 +912,6 @@
"installed_app_other_description": "Alle geïnstalleerde apps uit andere categorieën.", "installed_app_other_description": "Alle geïnstalleerde apps uit andere categorieën.",
"installed_app_conferencing_description": "Configureer welke conferentieapps moeten worden gebruikt", "installed_app_conferencing_description": "Configureer welke conferentieapps moeten worden gebruikt",
"installed_app_automation_description": "Configureer welke automatiseringsapps moeten worden gebruikt", "installed_app_automation_description": "Configureer welke automatiseringsapps moeten worden gebruikt",
"installed_app_web3_description": "Configureer welke web3-apps moeten worden gebruikt voor uw boekingspagina's",
"installed_app_messaging_description": "Configureer welke berichtenapps moeten worden gebruikt voor het instellen van aangepaste meldingen en herinneringen", "installed_app_messaging_description": "Configureer welke berichtenapps moeten worden gebruikt voor het instellen van aangepaste meldingen en herinneringen",
"installed_app_crm_description": "Configureer welke CRM-apps moeten worden gebruikt voor het bijhouden van wie u hebt ontmoet", "installed_app_crm_description": "Configureer welke CRM-apps moeten worden gebruikt voor het bijhouden van wie u hebt ontmoet",
"analytics": "Analyse", "analytics": "Analyse",
@ -1304,7 +1302,6 @@
"profile_picture": "Profielafbeelding", "profile_picture": "Profielafbeelding",
"upload": "Uploaden", "upload": "Uploaden",
"add_profile_photo": "Profielfoto toevoegen", "add_profile_photo": "Profielfoto toevoegen",
"web3": "Web3",
"token_address": "Tokenadres", "token_address": "Tokenadres",
"blockchain": "Blockchain", "blockchain": "Blockchain",
"old_password": "Oude wachtwoord", "old_password": "Oude wachtwoord",
@ -1346,7 +1343,6 @@
"connect_automation_apps": "Koppel automatiseringsapps", "connect_automation_apps": "Koppel automatiseringsapps",
"connect_analytics_apps": "Koppel analyse-apps", "connect_analytics_apps": "Koppel analyse-apps",
"connect_other_apps": "Andere apps koppelen", "connect_other_apps": "Andere apps koppelen",
"connect_web3_apps": "Web3-apps koppelen",
"connect_messaging_apps": "Berichtenapps koppelen", "connect_messaging_apps": "Berichtenapps koppelen",
"connect_crm_apps": "CRM-apps koppelen", "connect_crm_apps": "CRM-apps koppelen",
"current_step_of_total": "Stap {{currentStep}} van {{maxSteps}}", "current_step_of_total": "Stap {{currentStep}} van {{maxSteps}}",
@ -1585,6 +1581,7 @@
"enable_apps": "Apps inschakelen", "enable_apps": "Apps inschakelen",
"enable_apps_description": "Schakel apps in die gebruikers kunnen integreren met {{appName}}", "enable_apps_description": "Schakel apps in die gebruikers kunnen integreren met {{appName}}",
"purchase_license": "Koop een licentie", "purchase_license": "Koop een licentie",
"already_have_account": "Heeft u al een account?",
"already_have_key": "Ik heb al een code:", "already_have_key": "Ik heb al een code:",
"already_have_key_suggestion": "Kopieer hier uw bestaande CALCOM_LICENSE_KEY-omgevingsvariabele.", "already_have_key_suggestion": "Kopieer hier uw bestaande CALCOM_LICENSE_KEY-omgevingsvariabele.",
"app_is_enabled": "{{appName}} is ingeschakeld", "app_is_enabled": "{{appName}} is ingeschakeld",

View File

@ -670,7 +670,7 @@
"cal_provide_tandem_meeting_url": "{{appName}} vil angi en URL for Tandem-møtet.", "cal_provide_tandem_meeting_url": "{{appName}} vil angi en URL for Tandem-møtet.",
"cal_provide_video_meeting_url": "{{appName}} vil angi en videomøte-URL.", "cal_provide_video_meeting_url": "{{appName}} vil angi en videomøte-URL.",
"cal_provide_jitsi_meeting_url": "Vi vil generere en Jitsi Meet URL for deg.", "cal_provide_jitsi_meeting_url": "Vi vil generere en Jitsi Meet URL for deg.",
"cal_provide_huddle01_meeting_url": "{{appName}} vil angi en Huddle01 web3 videomøte-URL.", "cal_provide_huddle01_meeting_url": "{{appName}} vil angi en Huddle01 videomøte-URL.",
"cal_provide_teams_meeting_url": "{{appName}} vil angi en URL for MS Teams møte. MERK: MÅ HA EN ARBEIDS- ELLER SKOLEKONTO", "cal_provide_teams_meeting_url": "{{appName}} vil angi en URL for MS Teams møte. MERK: MÅ HA EN ARBEIDS- ELLER SKOLEKONTO",
"require_payment": "Krev Betaling", "require_payment": "Krev Betaling",
"commission_per_transaction": "provisjon per transaksjon", "commission_per_transaction": "provisjon per transaksjon",
@ -1112,7 +1112,6 @@
"profile_picture": "Profilbilde", "profile_picture": "Profilbilde",
"upload": "Last opp", "upload": "Last opp",
"add_profile_photo": "Legg til profilbilde", "add_profile_photo": "Legg til profilbilde",
"web3": "Web3",
"token_address": "Token Adresse", "token_address": "Token Adresse",
"blockchain": "Blockchain", "blockchain": "Blockchain",
"old_password": "Gammelt passord", "old_password": "Gammelt passord",
@ -1339,6 +1338,7 @@
"app_disabled_subject": "{{appName}} har blitt deaktivert", "app_disabled_subject": "{{appName}} har blitt deaktivert",
"navigate_installed_apps": "Gå til installerte apper", "navigate_installed_apps": "Gå til installerte apper",
"enable_apps": "Aktiver Apper", "enable_apps": "Aktiver Apper",
"already_have_account": "Har du allerede en bruker?",
"app_is_enabled": "{{appName}} er aktivert", "app_is_enabled": "{{appName}} er aktivert",
"app_is_disabled": "{{appName}} er deaktivert", "app_is_disabled": "{{appName}} er deaktivert",
"disable_app": "Deaktiver App", "disable_app": "Deaktiver App",

View File

@ -904,7 +904,6 @@
"no_category_apps_description_analytics": "Dodaj aplikację analityczną do stron rezerwacji", "no_category_apps_description_analytics": "Dodaj aplikację analityczną do stron rezerwacji",
"no_category_apps_description_automation": "Dodaj aplikację automatyzującą, która ma być używana", "no_category_apps_description_automation": "Dodaj aplikację automatyzującą, która ma być używana",
"no_category_apps_description_other": "Dodaj dowolny typ aplikacji, aby uzyskać dostęp do różnych innych funkcji", "no_category_apps_description_other": "Dodaj dowolny typ aplikacji, aby uzyskać dostęp do różnych innych funkcji",
"no_category_apps_description_web3": "Dodaj aplikację web3 do stron rezerwacji",
"no_category_apps_description_messaging": "Dodaj aplikację do wysyłania wiadomości, aby skonfigurować własne powiadomienia i przypomnienia", "no_category_apps_description_messaging": "Dodaj aplikację do wysyłania wiadomości, aby skonfigurować własne powiadomienia i przypomnienia",
"no_category_apps_description_crm": "Dodaj aplikację CRM, aby monitorować to, z kim się spotykasz", "no_category_apps_description_crm": "Dodaj aplikację CRM, aby monitorować to, z kim się spotykasz",
"installed_app_calendar_description": "Ustaw kalendarze, aby wykrywać konflikty i unikać podwójnych rezerwacji.", "installed_app_calendar_description": "Ustaw kalendarze, aby wykrywać konflikty i unikać podwójnych rezerwacji.",
@ -913,7 +912,6 @@
"installed_app_other_description": "Wszystkie zainstalowane aplikacje z innych kategorii.", "installed_app_other_description": "Wszystkie zainstalowane aplikacje z innych kategorii.",
"installed_app_conferencing_description": "Skonfiguruj, z których aplikacji konferencyjnych chcesz korzystać", "installed_app_conferencing_description": "Skonfiguruj, z których aplikacji konferencyjnych chcesz korzystać",
"installed_app_automation_description": "Skonfiguruj, które aplikacje automatyzujące mają być używane", "installed_app_automation_description": "Skonfiguruj, które aplikacje automatyzujące mają być używane",
"installed_app_web3_description": "Skonfiguruj aplikacje web3, które mają być używane na stronach rezerwacji",
"installed_app_messaging_description": "Skonfiguruj aplikacje, które będą używane do ustawiania niestandardowych powiadomień i przypomnień", "installed_app_messaging_description": "Skonfiguruj aplikacje, które będą używane do ustawiania niestandardowych powiadomień i przypomnień",
"installed_app_crm_description": "Skonfiguruj aplikacje CRM, za pomocą których chcesz monitorować to, z kim się spotykasz", "installed_app_crm_description": "Skonfiguruj aplikacje CRM, za pomocą których chcesz monitorować to, z kim się spotykasz",
"analytics": "Analityka", "analytics": "Analityka",
@ -1304,7 +1302,6 @@
"profile_picture": "Zdjęcie profilowe", "profile_picture": "Zdjęcie profilowe",
"upload": "Prześlij", "upload": "Prześlij",
"add_profile_photo": "Dodaj zdjęcie profilowe", "add_profile_photo": "Dodaj zdjęcie profilowe",
"web3": "Web3",
"token_address": "Adres tokena", "token_address": "Adres tokena",
"blockchain": "Blockchain", "blockchain": "Blockchain",
"old_password": "Stare hasło", "old_password": "Stare hasło",
@ -1346,7 +1343,6 @@
"connect_automation_apps": "Połącz aplikacje automatyzujące", "connect_automation_apps": "Połącz aplikacje automatyzujące",
"connect_analytics_apps": "Połącz aplikacje analityczne", "connect_analytics_apps": "Połącz aplikacje analityczne",
"connect_other_apps": "Połącz inne aplikacje", "connect_other_apps": "Połącz inne aplikacje",
"connect_web3_apps": "Połącz aplikacje web3",
"connect_messaging_apps": "Podłącz aplikacje do wysyłania wiadomości", "connect_messaging_apps": "Podłącz aplikacje do wysyłania wiadomości",
"connect_crm_apps": "Podłącz aplikacje CRM", "connect_crm_apps": "Podłącz aplikacje CRM",
"current_step_of_total": "Krok {{currentStep}} z {{maxSteps}}", "current_step_of_total": "Krok {{currentStep}} z {{maxSteps}}",
@ -1585,6 +1581,7 @@
"enable_apps": "Włącz aplikacje", "enable_apps": "Włącz aplikacje",
"enable_apps_description": "Włącz aplikacje, które użytkownicy mogą zintegrować z aplikacją {{appName}}", "enable_apps_description": "Włącz aplikacje, które użytkownicy mogą zintegrować z aplikacją {{appName}}",
"purchase_license": "Kup licencję", "purchase_license": "Kup licencję",
"already_have_account": "Masz już konto?",
"already_have_key": "Mam już klucz:", "already_have_key": "Mam już klucz:",
"already_have_key_suggestion": "Skopiuj istniejącą zmienną środowiskową CALCOM_LICENSE_KEY tutaj.", "already_have_key_suggestion": "Skopiuj istniejącą zmienną środowiskową CALCOM_LICENSE_KEY tutaj.",
"app_is_enabled": "Aplikacja {{appName}} jest włączona", "app_is_enabled": "Aplikacja {{appName}} jest włączona",

View File

@ -904,7 +904,6 @@
"no_category_apps_description_analytics": "Adicione um aplicativo de análise às suas páginas de reservas", "no_category_apps_description_analytics": "Adicione um aplicativo de análise às suas páginas de reservas",
"no_category_apps_description_automation": "Adicione um aplicativo de automação para usar", "no_category_apps_description_automation": "Adicione um aplicativo de automação para usar",
"no_category_apps_description_other": "Adicione qualquer outro tipo de aplicativo para fazer todos os tipos de coisas", "no_category_apps_description_other": "Adicione qualquer outro tipo de aplicativo para fazer todos os tipos de coisas",
"no_category_apps_description_web3": "Adicione um aplicativo web3 para suas páginas de reservas",
"no_category_apps_description_messaging": "Adicione um aplicativo de mensagem para definir lembretes e notificações personalizados", "no_category_apps_description_messaging": "Adicione um aplicativo de mensagem para definir lembretes e notificações personalizados",
"no_category_apps_description_crm": "Adicione um aplicativo CRM para acompanhar todos os integrantes das suas reuniões", "no_category_apps_description_crm": "Adicione um aplicativo CRM para acompanhar todos os integrantes das suas reuniões",
"installed_app_calendar_description": "Defina o(s) calendário(s) para verificar se há conflitos e evitar reservas duplas.", "installed_app_calendar_description": "Defina o(s) calendário(s) para verificar se há conflitos e evitar reservas duplas.",
@ -913,7 +912,6 @@
"installed_app_other_description": "Todos os seus aplicativos instalados de outras categorias.", "installed_app_other_description": "Todos os seus aplicativos instalados de outras categorias.",
"installed_app_conferencing_description": "Configure quais aplicativos de conferência serão usados", "installed_app_conferencing_description": "Configure quais aplicativos de conferência serão usados",
"installed_app_automation_description": "Configure quais aplicativos de automação serão usados", "installed_app_automation_description": "Configure quais aplicativos de automação serão usados",
"installed_app_web3_description": "Configure quais aplicativos web3 serão usados nas suas páginas de reservas",
"installed_app_messaging_description": "Configure quais aplicativos de mensagem usar para definir lembretes e notificações", "installed_app_messaging_description": "Configure quais aplicativos de mensagem usar para definir lembretes e notificações",
"installed_app_crm_description": "Configure quais aplicativos CRM usar para acompanhar os integrantes das suas reuniões", "installed_app_crm_description": "Configure quais aplicativos CRM usar para acompanhar os integrantes das suas reuniões",
"analytics": "Análise", "analytics": "Análise",
@ -1304,7 +1302,6 @@
"profile_picture": "Imagem do perfil", "profile_picture": "Imagem do perfil",
"upload": "Enviar", "upload": "Enviar",
"add_profile_photo": "Adicionar foto do perfil", "add_profile_photo": "Adicionar foto do perfil",
"web3": "Web3",
"token_address": "Endereço do Token", "token_address": "Endereço do Token",
"blockchain": "Blockchain", "blockchain": "Blockchain",
"old_password": "Senha antiga", "old_password": "Senha antiga",
@ -1346,7 +1343,6 @@
"connect_automation_apps": "Conecte aplicativos de automação", "connect_automation_apps": "Conecte aplicativos de automação",
"connect_analytics_apps": "Conecte aplicativos de análise", "connect_analytics_apps": "Conecte aplicativos de análise",
"connect_other_apps": "Conectar outros aplicativos", "connect_other_apps": "Conectar outros aplicativos",
"connect_web3_apps": "Conecte aplicativos web3",
"connect_messaging_apps": "Conectar com aplicativos de mensagem", "connect_messaging_apps": "Conectar com aplicativos de mensagem",
"connect_crm_apps": "Conecte aplicativos CRM", "connect_crm_apps": "Conecte aplicativos CRM",
"current_step_of_total": "Passo {{currentStep}} de {{maxSteps}}", "current_step_of_total": "Passo {{currentStep}} de {{maxSteps}}",
@ -1585,6 +1581,7 @@
"enable_apps": "Ativar aplicativos", "enable_apps": "Ativar aplicativos",
"enable_apps_description": "Ative aplicativos que os usuários podem integrar com o {{appName}}", "enable_apps_description": "Ative aplicativos que os usuários podem integrar com o {{appName}}",
"purchase_license": "Comprar uma licença", "purchase_license": "Comprar uma licença",
"already_have_account": "Já tem uma conta?",
"already_have_key": "Já tenho uma chave:", "already_have_key": "Já tenho uma chave:",
"already_have_key_suggestion": "Copie a variável de ambiente CALCOM_LICENSE_KEY existente aqui.", "already_have_key_suggestion": "Copie a variável de ambiente CALCOM_LICENSE_KEY existente aqui.",
"app_is_enabled": "{{appName}} foi ativado", "app_is_enabled": "{{appName}} foi ativado",

View File

@ -904,7 +904,6 @@
"no_category_apps_description_analytics": "Adicionar uma aplicação de análise às suas páginas de reservas", "no_category_apps_description_analytics": "Adicionar uma aplicação de análise às suas páginas de reservas",
"no_category_apps_description_automation": "Adicionar uma aplicação de automatização a utilizar", "no_category_apps_description_automation": "Adicionar uma aplicação de automatização a utilizar",
"no_category_apps_description_other": "Adicione qualquer outro tipo de aplicações para fazer todos os tipos de coisas", "no_category_apps_description_other": "Adicione qualquer outro tipo de aplicações para fazer todos os tipos de coisas",
"no_category_apps_description_web3": "Adicione uma aplicação web3 às suas páginas de reservas",
"no_category_apps_description_messaging": "Adicione uma aplicação de mensagens para configurar notificações e lembretes personalizados", "no_category_apps_description_messaging": "Adicione uma aplicação de mensagens para configurar notificações e lembretes personalizados",
"no_category_apps_description_crm": "Adicione uma aplicação de CRM para manter um registo das pessoas com quem se reuniu", "no_category_apps_description_crm": "Adicione uma aplicação de CRM para manter um registo das pessoas com quem se reuniu",
"installed_app_calendar_description": "Defina o(s) calendário(s) para verificar se existem conflitos e assim evitar marcações sobrepostas.", "installed_app_calendar_description": "Defina o(s) calendário(s) para verificar se existem conflitos e assim evitar marcações sobrepostas.",
@ -913,7 +912,6 @@
"installed_app_other_description": "Todas as aplicações instaladas de outras categorias.", "installed_app_other_description": "Todas as aplicações instaladas de outras categorias.",
"installed_app_conferencing_description": "Configurar as aplicações de conferência a utilizar", "installed_app_conferencing_description": "Configurar as aplicações de conferência a utilizar",
"installed_app_automation_description": "Configurar as aplicações de automatização a utilizar", "installed_app_automation_description": "Configurar as aplicações de automatização a utilizar",
"installed_app_web3_description": "Configure as aplicações web3 a utilizar nas suas páginas de reservas",
"installed_app_messaging_description": "Configurar as aplicações de mensagens a utilizar para definir notificações e lembretes personalizados", "installed_app_messaging_description": "Configurar as aplicações de mensagens a utilizar para definir notificações e lembretes personalizados",
"installed_app_crm_description": "Configurar as aplicações de CRM a utilizar para manter um registo das pessoas com quem se reuniu", "installed_app_crm_description": "Configurar as aplicações de CRM a utilizar para manter um registo das pessoas com quem se reuniu",
"analytics": "Estatísticas", "analytics": "Estatísticas",
@ -1304,7 +1302,6 @@
"profile_picture": "Imagem do perfil", "profile_picture": "Imagem do perfil",
"upload": "Carregar", "upload": "Carregar",
"add_profile_photo": "Adicionar imagem de perfil", "add_profile_photo": "Adicionar imagem de perfil",
"web3": "Web3",
"token_address": "Endereço do Token", "token_address": "Endereço do Token",
"blockchain": "Blockchain", "blockchain": "Blockchain",
"old_password": "Palavra-passe antiga", "old_password": "Palavra-passe antiga",
@ -1346,7 +1343,6 @@
"connect_automation_apps": "Associar aplicações de automatização", "connect_automation_apps": "Associar aplicações de automatização",
"connect_analytics_apps": "Associar aplicações de estatística", "connect_analytics_apps": "Associar aplicações de estatística",
"connect_other_apps": "Associe outras aplicações", "connect_other_apps": "Associe outras aplicações",
"connect_web3_apps": "Associe aplicações web3",
"connect_messaging_apps": "Ligar aplicações de mensagens", "connect_messaging_apps": "Ligar aplicações de mensagens",
"connect_crm_apps": "Ligar aplicações CRM", "connect_crm_apps": "Ligar aplicações CRM",
"current_step_of_total": "Passo {{currentStep}} de {{maxSteps}}", "current_step_of_total": "Passo {{currentStep}} de {{maxSteps}}",
@ -1585,6 +1581,7 @@
"enable_apps": "Ativar aplicações", "enable_apps": "Ativar aplicações",
"enable_apps_description": "Ativar aplicações que os utilizadores podem integrar com {{appName}}", "enable_apps_description": "Ativar aplicações que os utilizadores podem integrar com {{appName}}",
"purchase_license": "Adquira uma Licença", "purchase_license": "Adquira uma Licença",
"already_have_account": "Já tem uma conta?",
"already_have_key": "Eu já tenho uma chave:", "already_have_key": "Eu já tenho uma chave:",
"already_have_key_suggestion": "Por favor, copie a sua variável de ambiente CALCOM_LICENSE_KEY existente aqui.", "already_have_key_suggestion": "Por favor, copie a sua variável de ambiente CALCOM_LICENSE_KEY existente aqui.",
"app_is_enabled": "{{appName}} foi ativada", "app_is_enabled": "{{appName}} foi ativada",

View File

@ -904,7 +904,6 @@
"no_category_apps_description_analytics": "Adăugați o aplicație de analiză pentru paginile dvs. de rezervări", "no_category_apps_description_analytics": "Adăugați o aplicație de analiză pentru paginile dvs. de rezervări",
"no_category_apps_description_automation": "Adăugați o aplicație de automatizare de utilizat", "no_category_apps_description_automation": "Adăugați o aplicație de automatizare de utilizat",
"no_category_apps_description_other": "Adăugați orice alt tip de aplicație pentru a întreprinde diverse acțiuni", "no_category_apps_description_other": "Adăugați orice alt tip de aplicație pentru a întreprinde diverse acțiuni",
"no_category_apps_description_web3": "Adăugați o aplicație Web3 pentru paginile dvs. de rezervări",
"no_category_apps_description_messaging": "Adăugați o aplicație de mesagerie pentru a configura notificări și mementouri personalizate", "no_category_apps_description_messaging": "Adăugați o aplicație de mesagerie pentru a configura notificări și mementouri personalizate",
"no_category_apps_description_crm": "Adăugați o aplicație CRM pentru a ține evidența persoanelor cu care v-ați întâlnit", "no_category_apps_description_crm": "Adăugați o aplicație CRM pentru a ține evidența persoanelor cu care v-ați întâlnit",
"installed_app_calendar_description": "Pentru a evita rezervările suprapuse, configurează calendarele astfel încât să poată verifica existența conflictelor.", "installed_app_calendar_description": "Pentru a evita rezervările suprapuse, configurează calendarele astfel încât să poată verifica existența conflictelor.",
@ -913,7 +912,6 @@
"installed_app_other_description": "Toate aplicațiile instalate din alte categorii.", "installed_app_other_description": "Toate aplicațiile instalate din alte categorii.",
"installed_app_conferencing_description": "Configurați ce aplicații de conferințe vor fi utilizate", "installed_app_conferencing_description": "Configurați ce aplicații de conferințe vor fi utilizate",
"installed_app_automation_description": "Configurați care aplicații de automatizare vor fi utilizate", "installed_app_automation_description": "Configurați care aplicații de automatizare vor fi utilizate",
"installed_app_web3_description": "Configurați care aplicații Web3 vor fi utilizate pentru paginile dvs. de rezervări",
"installed_app_messaging_description": "Definiți ce aplicații de mesagerie vor fi utilizate pentru configurarea de notificări și mementouri personalizate", "installed_app_messaging_description": "Definiți ce aplicații de mesagerie vor fi utilizate pentru configurarea de notificări și mementouri personalizate",
"installed_app_crm_description": "Configurați ce aplicații CRM vor fi utilizate pentru a ține evidența persoanelor cu care v-ați întâlnit", "installed_app_crm_description": "Configurați ce aplicații CRM vor fi utilizate pentru a ține evidența persoanelor cu care v-ați întâlnit",
"analytics": "Analiză", "analytics": "Analiză",
@ -1304,7 +1302,6 @@
"profile_picture": "Fotografie de profil", "profile_picture": "Fotografie de profil",
"upload": "Încărcare", "upload": "Încărcare",
"add_profile_photo": "Adăugați poză de profil", "add_profile_photo": "Adăugați poză de profil",
"web3": "Web3",
"token_address": "Adresă token", "token_address": "Adresă token",
"blockchain": "Blockchain", "blockchain": "Blockchain",
"old_password": "Parola veche", "old_password": "Parola veche",
@ -1346,7 +1343,6 @@
"connect_automation_apps": "Conectați aplicații de automatizare", "connect_automation_apps": "Conectați aplicații de automatizare",
"connect_analytics_apps": "Conectați aplicații de analiză", "connect_analytics_apps": "Conectați aplicații de analiză",
"connect_other_apps": "Conectați alte aplicații", "connect_other_apps": "Conectați alte aplicații",
"connect_web3_apps": "Conectați aplicații Web3",
"connect_messaging_apps": "Conectați aplicații de mesagerie", "connect_messaging_apps": "Conectați aplicații de mesagerie",
"connect_crm_apps": "Conectați aplicații CRM", "connect_crm_apps": "Conectați aplicații CRM",
"current_step_of_total": "Pasul {{currentStep}} din {{maxSteps}}", "current_step_of_total": "Pasul {{currentStep}} din {{maxSteps}}",
@ -1585,6 +1581,7 @@
"enable_apps": "Activează aplicații", "enable_apps": "Activează aplicații",
"enable_apps_description": "Activați aplicațiile pe care utilizatorii le pot integra cu {{appName}}", "enable_apps_description": "Activați aplicațiile pe care utilizatorii le pot integra cu {{appName}}",
"purchase_license": "Achiziționați o licență", "purchase_license": "Achiziționați o licență",
"already_have_account": "Aveți deja un cont?",
"already_have_key": "Am deja o cheie:", "already_have_key": "Am deja o cheie:",
"already_have_key_suggestion": "Copiați aici variabila de mediu CALCOM_LICENSE_KEY existentă.", "already_have_key_suggestion": "Copiați aici variabila de mediu CALCOM_LICENSE_KEY existentă.",
"app_is_enabled": "{{appName}} este activat", "app_is_enabled": "{{appName}} este activat",

View File

@ -904,7 +904,6 @@
"no_category_apps_description_analytics": "Добавьте приложение аналитики для использования на страницах бронирования", "no_category_apps_description_analytics": "Добавьте приложение аналитики для использования на страницах бронирования",
"no_category_apps_description_automation": "Добавьте приложение для автоматизации", "no_category_apps_description_automation": "Добавьте приложение для автоматизации",
"no_category_apps_description_other": "Добавляйте всевозможные приложения для решения своих задач", "no_category_apps_description_other": "Добавляйте всевозможные приложения для решения своих задач",
"no_category_apps_description_web3": "Добавьте приложение web3 для использования на страницах бронирования",
"no_category_apps_description_messaging": "Добавьте мессенджер, чтобы настроить пользовательские уведомления и напоминания", "no_category_apps_description_messaging": "Добавьте мессенджер, чтобы настроить пользовательские уведомления и напоминания",
"no_category_apps_description_crm": "Добавьте приложение CRM, чтобы отслеживать, с кем у вас были встречи", "no_category_apps_description_crm": "Добавьте приложение CRM, чтобы отслеживать, с кем у вас были встречи",
"installed_app_calendar_description": "Настройте проверку календарей на предмет конфликтов для избежания двойного бронирования.", "installed_app_calendar_description": "Настройте проверку календарей на предмет конфликтов для избежания двойного бронирования.",
@ -913,7 +912,6 @@
"installed_app_other_description": "Все установленные приложения из других категорий.", "installed_app_other_description": "Все установленные приложения из других категорий.",
"installed_app_conferencing_description": "Выберите приложения для конференц-связи", "installed_app_conferencing_description": "Выберите приложения для конференц-связи",
"installed_app_automation_description": "Выберите приложения для автоматизации", "installed_app_automation_description": "Выберите приложения для автоматизации",
"installed_app_web3_description": "Выберите приложения web3 для страниц бронирования",
"installed_app_messaging_description": "Выберите мессенджеры, с помощью которых вы хотите отправлять пользовательские уведомления и напоминания", "installed_app_messaging_description": "Выберите мессенджеры, с помощью которых вы хотите отправлять пользовательские уведомления и напоминания",
"installed_app_crm_description": "Выберите CRM-приложения, в которых вы хотите отслеживать, с кем у вас были встречи", "installed_app_crm_description": "Выберите CRM-приложения, в которых вы хотите отслеживать, с кем у вас были встречи",
"analytics": "Аналитика", "analytics": "Аналитика",
@ -1304,7 +1302,6 @@
"profile_picture": "Фото профиля", "profile_picture": "Фото профиля",
"upload": "Загрузить", "upload": "Загрузить",
"add_profile_photo": "Добавить фото профиля", "add_profile_photo": "Добавить фото профиля",
"web3": "Web3",
"token_address": "Адрес токена", "token_address": "Адрес токена",
"blockchain": "Блокчейн", "blockchain": "Блокчейн",
"old_password": "Старый пароль", "old_password": "Старый пароль",
@ -1346,7 +1343,6 @@
"connect_automation_apps": "Подключить приложения для автоматизации", "connect_automation_apps": "Подключить приложения для автоматизации",
"connect_analytics_apps": "Подключить приложения аналитики", "connect_analytics_apps": "Подключить приложения аналитики",
"connect_other_apps": "Подключить другие приложения", "connect_other_apps": "Подключить другие приложения",
"connect_web3_apps": "Подключить приложения web3",
"connect_messaging_apps": "Подключить мессенджеры", "connect_messaging_apps": "Подключить мессенджеры",
"connect_crm_apps": "Подключить CRM-приложения", "connect_crm_apps": "Подключить CRM-приложения",
"current_step_of_total": "Шаг {{currentStep}} из {{maxSteps}}", "current_step_of_total": "Шаг {{currentStep}} из {{maxSteps}}",
@ -1585,6 +1581,7 @@
"enable_apps": "Включить приложения", "enable_apps": "Включить приложения",
"enable_apps_description": "Выберите приложения, которые пользователи смогут интегрировать с {{appName}}", "enable_apps_description": "Выберите приложения, которые пользователи смогут интегрировать с {{appName}}",
"purchase_license": "Купить лицензию", "purchase_license": "Купить лицензию",
"already_have_account": "Уже есть аккаунт?",
"already_have_key": "У меня уже есть ключ:", "already_have_key": "У меня уже есть ключ:",
"already_have_key_suggestion": "Скопируйте сюда переменную окружения CALCOM_LICENSE_KEY.", "already_have_key_suggestion": "Скопируйте сюда переменную окружения CALCOM_LICENSE_KEY.",
"app_is_enabled": "{{appName}} включено", "app_is_enabled": "{{appName}} включено",

View File

@ -801,7 +801,7 @@
"cal_provide_tandem_meeting_url": "{{appName}} će obezbediti URL Tandem sastanka.", "cal_provide_tandem_meeting_url": "{{appName}} će obezbediti URL Tandem sastanka.",
"cal_provide_video_meeting_url": "{{appName}} će obezbediti Daily video meeting URL.", "cal_provide_video_meeting_url": "{{appName}} će obezbediti Daily video meeting URL.",
"cal_provide_jitsi_meeting_url": "Mi ćemo generisati Jitsi Meet URL za vas.", "cal_provide_jitsi_meeting_url": "Mi ćemo generisati Jitsi Meet URL za vas.",
"cal_provide_huddle01_meeting_url": "{{appName}} će obezbediti Huddle01 web3 video meeting URL.", "cal_provide_huddle01_meeting_url": "{{appName}} će obezbediti Huddle01 video meeting URL.",
"cal_provide_teams_meeting_url": "{{appName}} će dostaviti URL adresu MS Teams sastanka. NAPOMENA: NEOPHODAN JE POSLOVNI ILI ŠKOLSKI NALOG", "cal_provide_teams_meeting_url": "{{appName}} će dostaviti URL adresu MS Teams sastanka. NAPOMENA: NEOPHODAN JE POSLOVNI ILI ŠKOLSKI NALOG",
"require_payment": "Obavezno plaćanje", "require_payment": "Obavezno plaćanje",
"you_need_to_add_a_name": "Potrebno je da dodate ime", "you_need_to_add_a_name": "Potrebno je da dodate ime",
@ -904,7 +904,6 @@
"no_category_apps_description_analytics": "Dodajte analitičku aplikaciju za vaše stranice za rezervacije", "no_category_apps_description_analytics": "Dodajte analitičku aplikaciju za vaše stranice za rezervacije",
"no_category_apps_description_automation": "Dodajte aplikaciju za automatizaciju za korišćenje", "no_category_apps_description_automation": "Dodajte aplikaciju za automatizaciju za korišćenje",
"no_category_apps_description_other": "Dodajte bilo koju vrstu aplikacije da biste uradili svakakve stvari", "no_category_apps_description_other": "Dodajte bilo koju vrstu aplikacije da biste uradili svakakve stvari",
"no_category_apps_description_web3": "Dodajte web3 aplikaciju na stranice za rezervacije",
"no_category_apps_description_messaging": "Dodajte aplikaciju za poruke da biste podesili uobičajena obaveštenja i podsetnike", "no_category_apps_description_messaging": "Dodajte aplikaciju za poruke da biste podesili uobičajena obaveštenja i podsetnike",
"no_category_apps_description_crm": "Dodajte aplikaciju za CRM da biste pratili sa kim ste se sreli", "no_category_apps_description_crm": "Dodajte aplikaciju za CRM da biste pratili sa kim ste se sreli",
"installed_app_calendar_description": "Podesite kalendar(e) da biste proverili da li postoje konflikti i time sprečili dvostruka zakazivanja.", "installed_app_calendar_description": "Podesite kalendar(e) da biste proverili da li postoje konflikti i time sprečili dvostruka zakazivanja.",
@ -913,7 +912,6 @@
"installed_app_other_description": "Sve vaše instalirane aplikacije iz drugih kategorija.", "installed_app_other_description": "Sve vaše instalirane aplikacije iz drugih kategorija.",
"installed_app_conferencing_description": "Konfigurišite koje aplikacije ćete koristiti za konferencije", "installed_app_conferencing_description": "Konfigurišite koje aplikacije ćete koristiti za konferencije",
"installed_app_automation_description": "Konfigurišite koje ćete aplikacije za automatizaciju da koristite", "installed_app_automation_description": "Konfigurišite koje ćete aplikacije za automatizaciju da koristite",
"installed_app_web3_description": "Konfigurišite koje web3 aplikacije će se koristiti za stranice za rezervacije",
"installed_app_messaging_description": "Konfigurišite koje aplikacije za poruke ćete koristiti da biste podesili uobičajena obaveštenja i podsetnike", "installed_app_messaging_description": "Konfigurišite koje aplikacije za poruke ćete koristiti da biste podesili uobičajena obaveštenja i podsetnike",
"installed_app_crm_description": "Konfigurišite koje CRM aplikacije ćete koristiti za praćenje sa kim ste se sreli", "installed_app_crm_description": "Konfigurišite koje CRM aplikacije ćete koristiti za praćenje sa kim ste se sreli",
"analytics": "Analitika", "analytics": "Analitika",
@ -1304,7 +1302,6 @@
"profile_picture": "Profilna slika", "profile_picture": "Profilna slika",
"upload": "Otpremi", "upload": "Otpremi",
"add_profile_photo": "Dodajte profilnu fotografiju", "add_profile_photo": "Dodajte profilnu fotografiju",
"web3": "Web3",
"token_address": "Adresa tokena", "token_address": "Adresa tokena",
"blockchain": "Lanac blokova", "blockchain": "Lanac blokova",
"old_password": "Stara lozinka", "old_password": "Stara lozinka",
@ -1346,7 +1343,6 @@
"connect_automation_apps": "Povežite aplikacije za automatizaciju", "connect_automation_apps": "Povežite aplikacije za automatizaciju",
"connect_analytics_apps": "Povežite analitičke aplikacije", "connect_analytics_apps": "Povežite analitičke aplikacije",
"connect_other_apps": "Povežite druge aplikacije", "connect_other_apps": "Povežite druge aplikacije",
"connect_web3_apps": "Povežite web3 aplikacije",
"connect_messaging_apps": "Povežite aplikacije za razmenu poruka", "connect_messaging_apps": "Povežite aplikacije za razmenu poruka",
"connect_crm_apps": "Povežite CRM aplikacije", "connect_crm_apps": "Povežite CRM aplikacije",
"current_step_of_total": "Korak {{currentStep}} od {{maxSteps}}", "current_step_of_total": "Korak {{currentStep}} od {{maxSteps}}",
@ -1585,6 +1581,7 @@
"enable_apps": "Omogući aplikacije", "enable_apps": "Omogući aplikacije",
"enable_apps_description": "Omogući aplikacije koje korisnici mogu da integrišu sa aplikacijom {{appName}}", "enable_apps_description": "Omogući aplikacije koje korisnici mogu da integrišu sa aplikacijom {{appName}}",
"purchase_license": "Kupovina licence", "purchase_license": "Kupovina licence",
"already_have_account": "Već imate nalog?",
"already_have_key": "Već imam ključ:", "already_have_key": "Već imam ključ:",
"already_have_key_suggestion": "Kopirajte ovde postojeću CALCOM_LICENSE_KEY promenljivu okruženja.", "already_have_key_suggestion": "Kopirajte ovde postojeću CALCOM_LICENSE_KEY promenljivu okruženja.",
"app_is_enabled": "Aplikacija {{appName}} je omogućena", "app_is_enabled": "Aplikacija {{appName}} je omogućena",

View File

@ -801,7 +801,7 @@
"cal_provide_tandem_meeting_url": "{{appName}} kommer att generera en URL för Tandem-möte.", "cal_provide_tandem_meeting_url": "{{appName}} kommer att generera en URL för Tandem-möte.",
"cal_provide_video_meeting_url": "{{appName}} kommer att generera en URL för Daily video-möte.", "cal_provide_video_meeting_url": "{{appName}} kommer att generera en URL för Daily video-möte.",
"cal_provide_jitsi_meeting_url": "Vi kommer att generera en Jitsi Meet URL för dig.", "cal_provide_jitsi_meeting_url": "Vi kommer att generera en Jitsi Meet URL för dig.",
"cal_provide_huddle01_meeting_url": "{{appName}} kommer att generera en URL för Huddle01 web3-möte.", "cal_provide_huddle01_meeting_url": "{{appName}} kommer att generera en URL för Huddle01-möte.",
"cal_provide_teams_meeting_url": "{{appName}} kommer att tillhandahålla en mötes-URL för MS Teams. OBS! MÅSTE HA ETT ARBETS- ELLER SKOLKONTO", "cal_provide_teams_meeting_url": "{{appName}} kommer att tillhandahålla en mötes-URL för MS Teams. OBS! MÅSTE HA ETT ARBETS- ELLER SKOLKONTO",
"require_payment": "Begär betalning", "require_payment": "Begär betalning",
"you_need_to_add_a_name": "Du måste lägga till ett namn", "you_need_to_add_a_name": "Du måste lägga till ett namn",
@ -904,7 +904,6 @@
"no_category_apps_description_analytics": "Lägg till en analysapp för dina bokningssidor", "no_category_apps_description_analytics": "Lägg till en analysapp för dina bokningssidor",
"no_category_apps_description_automation": "Lägg till en automationsapp att använda", "no_category_apps_description_automation": "Lägg till en automationsapp att använda",
"no_category_apps_description_other": "Lägg till en annan typ av app för att göra alla möjliga saker", "no_category_apps_description_other": "Lägg till en annan typ av app för att göra alla möjliga saker",
"no_category_apps_description_web3": "Lägg till en web3-app för dina bokningssidor",
"no_category_apps_description_messaging": "Lägg till en meddelandeapp för att skapa anpassade aviseringar och påminnelser", "no_category_apps_description_messaging": "Lägg till en meddelandeapp för att skapa anpassade aviseringar och påminnelser",
"no_category_apps_description_crm": "Lägg till en CRM-app för att hålla reda på vilka du har träffat", "no_category_apps_description_crm": "Lägg till en CRM-app för att hålla reda på vilka du har träffat",
"installed_app_calendar_description": "Ställ in kalendrar att söka efter konflikter för att undvika dubbelbokningar.", "installed_app_calendar_description": "Ställ in kalendrar att söka efter konflikter för att undvika dubbelbokningar.",
@ -913,7 +912,6 @@
"installed_app_other_description": "Alla dina installerade appar från andra kategorier.", "installed_app_other_description": "Alla dina installerade appar från andra kategorier.",
"installed_app_conferencing_description": "Konfigurera vilka konferensappar som ska användas", "installed_app_conferencing_description": "Konfigurera vilka konferensappar som ska användas",
"installed_app_automation_description": "Konfigurera vilka automationsappar som ska användas", "installed_app_automation_description": "Konfigurera vilka automationsappar som ska användas",
"installed_app_web3_description": "Konfigurera vilka web3-appar som ska användas för dina bokningssidor",
"installed_app_messaging_description": "Konfigurera vilka meddelandeappar som ska användas för att skapa anpassade aviseringar och påminnelser", "installed_app_messaging_description": "Konfigurera vilka meddelandeappar som ska användas för att skapa anpassade aviseringar och påminnelser",
"installed_app_crm_description": "Konfigurera vilka CRM-appar som ska användas för att hålla reda på vilka du har träffat", "installed_app_crm_description": "Konfigurera vilka CRM-appar som ska användas för att hålla reda på vilka du har träffat",
"analytics": "Analys", "analytics": "Analys",
@ -1304,7 +1302,6 @@
"profile_picture": "Profilbild", "profile_picture": "Profilbild",
"upload": "Ladda upp", "upload": "Ladda upp",
"add_profile_photo": "Lägg till profilfoto", "add_profile_photo": "Lägg till profilfoto",
"web3": "Web3",
"token_address": "Tokenadress", "token_address": "Tokenadress",
"blockchain": "Blockkedja", "blockchain": "Blockkedja",
"old_password": "Gammalt lösenord", "old_password": "Gammalt lösenord",
@ -1346,7 +1343,6 @@
"connect_automation_apps": "Anslut automationsappar", "connect_automation_apps": "Anslut automationsappar",
"connect_analytics_apps": "Anslut analysappar", "connect_analytics_apps": "Anslut analysappar",
"connect_other_apps": "Anslut andra appar", "connect_other_apps": "Anslut andra appar",
"connect_web3_apps": "Anslut web3-appar",
"connect_messaging_apps": "Anslut meddelandeappar", "connect_messaging_apps": "Anslut meddelandeappar",
"connect_crm_apps": "Anslut CRM-appar", "connect_crm_apps": "Anslut CRM-appar",
"current_step_of_total": "Steg {{currentStep}} av {{maxSteps}}", "current_step_of_total": "Steg {{currentStep}} av {{maxSteps}}",
@ -1585,6 +1581,7 @@
"enable_apps": "Aktivera appar", "enable_apps": "Aktivera appar",
"enable_apps_description": "Aktivera appar som användare kan integrera med {{appName}}", "enable_apps_description": "Aktivera appar som användare kan integrera med {{appName}}",
"purchase_license": "Köp en licens", "purchase_license": "Köp en licens",
"already_have_account": "Har du redan ett konto?",
"already_have_key": "Jag har redan en nyckel:", "already_have_key": "Jag har redan en nyckel:",
"already_have_key_suggestion": "Kopiera din befintliga CALCOM_LICENSE_KEY-miljövariabel här.", "already_have_key_suggestion": "Kopiera din befintliga CALCOM_LICENSE_KEY-miljövariabel här.",
"app_is_enabled": "{{appName}} har aktiverats", "app_is_enabled": "{{appName}} har aktiverats",

View File

@ -801,7 +801,7 @@
"cal_provide_tandem_meeting_url": "{{appName}}, bir Tandem toplantı URL'si sağlayacaktır.", "cal_provide_tandem_meeting_url": "{{appName}}, bir Tandem toplantı URL'si sağlayacaktır.",
"cal_provide_video_meeting_url": "{{appName}}, bir görüntülü toplantı URL'si sağlayacaktır.", "cal_provide_video_meeting_url": "{{appName}}, bir görüntülü toplantı URL'si sağlayacaktır.",
"cal_provide_jitsi_meeting_url": "Sizin için bir Jitsi Meet URL'si oluşturacağız.", "cal_provide_jitsi_meeting_url": "Sizin için bir Jitsi Meet URL'si oluşturacağız.",
"cal_provide_huddle01_meeting_url": "{{appName}}, bir Huddle01 web3 görüntülü toplantı URL'si sağlayacaktır.", "cal_provide_huddle01_meeting_url": "{{appName}}, bir Huddle01 görüntülü toplantı URL'si sağlayacaktır.",
"cal_provide_teams_meeting_url": "{{appName}}, bir MS Teams toplantı URL'si sağlayacaktır. NOT: İŞ VEYA OKUL HESABINIZIN OLMASI GEREKİR", "cal_provide_teams_meeting_url": "{{appName}}, bir MS Teams toplantı URL'si sağlayacaktır. NOT: İŞ VEYA OKUL HESABINIZIN OLMASI GEREKİR",
"require_payment": "Ödemeyi Gerekli Kılın", "require_payment": "Ödemeyi Gerekli Kılın",
"you_need_to_add_a_name": "Ad eklemeniz gerekiyor", "you_need_to_add_a_name": "Ad eklemeniz gerekiyor",
@ -904,7 +904,6 @@
"no_category_apps_description_analytics": "Rezervasyon sayfalarınız için bir analiz uygulaması ekleyin", "no_category_apps_description_analytics": "Rezervasyon sayfalarınız için bir analiz uygulaması ekleyin",
"no_category_apps_description_automation": "Kullanılacak otomasyon uygulamasını ekleyin", "no_category_apps_description_automation": "Kullanılacak otomasyon uygulamasını ekleyin",
"no_category_apps_description_other": "Her türlü işlemi yapmak için farklı bir uygulama ekleyin", "no_category_apps_description_other": "Her türlü işlemi yapmak için farklı bir uygulama ekleyin",
"no_category_apps_description_web3": "Rezervasyon sayfalarınız için bir web3 uygulaması ekleyin",
"no_category_apps_description_messaging": "Özel bildirimler ve hatırlatıcılar ayarlamak için bir mesajlaşma uygulaması ekleyin", "no_category_apps_description_messaging": "Özel bildirimler ve hatırlatıcılar ayarlamak için bir mesajlaşma uygulaması ekleyin",
"no_category_apps_description_crm": "Tanıştığınız kullanıcıları takip etmek için bir CRM uygulaması ekleyin", "no_category_apps_description_crm": "Tanıştığınız kullanıcıları takip etmek için bir CRM uygulaması ekleyin",
"installed_app_calendar_description": "Çifte rezervasyonları önlemek amacıyla çakışmaları kontrol etmek için takvimleri ayarlayın.", "installed_app_calendar_description": "Çifte rezervasyonları önlemek amacıyla çakışmaları kontrol etmek için takvimleri ayarlayın.",
@ -913,7 +912,6 @@
"installed_app_other_description": "Diğer kategorilerdeki tüm yüklü uygulamalarınız.", "installed_app_other_description": "Diğer kategorilerdeki tüm yüklü uygulamalarınız.",
"installed_app_conferencing_description": "Hangi konferans uygulamalarının kullanılacağını belirleyin", "installed_app_conferencing_description": "Hangi konferans uygulamalarının kullanılacağını belirleyin",
"installed_app_automation_description": "Kullanılacak otomasyon uygulamalarını yapılandırın", "installed_app_automation_description": "Kullanılacak otomasyon uygulamalarını yapılandırın",
"installed_app_web3_description": "Rezervasyon sayfalarınız için hangi web3 uygulamalarının kullanılacağını yapılandırın",
"installed_app_messaging_description": "Özel bildirimleri ve hatırlatıcıları ayarlamak için hangi mesajlaşma uygulamalarının kullanılacağını belirleyin", "installed_app_messaging_description": "Özel bildirimleri ve hatırlatıcıları ayarlamak için hangi mesajlaşma uygulamalarının kullanılacağını belirleyin",
"installed_app_crm_description": "Tanıştığınız kullanıcıları takip etmek için hangi CRM uygulamalarının kullanılacağını belirleyin", "installed_app_crm_description": "Tanıştığınız kullanıcıları takip etmek için hangi CRM uygulamalarının kullanılacağını belirleyin",
"analytics": "Analizler", "analytics": "Analizler",
@ -1304,7 +1302,6 @@
"profile_picture": "Profil resmi", "profile_picture": "Profil resmi",
"upload": "Yükle", "upload": "Yükle",
"add_profile_photo": "Profil fotoğrafı ekleyin", "add_profile_photo": "Profil fotoğrafı ekleyin",
"web3": "Web3",
"token_address": "Token Adresi", "token_address": "Token Adresi",
"blockchain": "Blok zinciri", "blockchain": "Blok zinciri",
"old_password": "Eski şifre", "old_password": "Eski şifre",
@ -1346,7 +1343,6 @@
"connect_automation_apps": "Otomasyon uygulamaları bağlayın", "connect_automation_apps": "Otomasyon uygulamaları bağlayın",
"connect_analytics_apps": "Analiz uygulamaları bağlayın", "connect_analytics_apps": "Analiz uygulamaları bağlayın",
"connect_other_apps": "Diğer uygulamaları bağlayın", "connect_other_apps": "Diğer uygulamaları bağlayın",
"connect_web3_apps": "Web3 uygulamalarını bağlayın",
"connect_messaging_apps": "Mesajlaşma uygulamalarını bağlayın", "connect_messaging_apps": "Mesajlaşma uygulamalarını bağlayın",
"connect_crm_apps": "CRM uygulamalarını bağlayın", "connect_crm_apps": "CRM uygulamalarını bağlayın",
"current_step_of_total": "{{currentStep}} / {{maxSteps}}. Adım", "current_step_of_total": "{{currentStep}} / {{maxSteps}}. Adım",
@ -1585,6 +1581,7 @@
"enable_apps": "Uygulamaları Etkinleştir", "enable_apps": "Uygulamaları Etkinleştir",
"enable_apps_description": "Kullanıcıların {{appName}} ile entegre edebileceği uygulamaları etkinleştirin", "enable_apps_description": "Kullanıcıların {{appName}} ile entegre edebileceği uygulamaları etkinleştirin",
"purchase_license": "Lisans satın alın", "purchase_license": "Lisans satın alın",
"already_have_account": "Zaten bir hesabınız var mı?",
"already_have_key": "Zaten bir anahtarım var:", "already_have_key": "Zaten bir anahtarım var:",
"already_have_key_suggestion": "Lütfen mevcut CALCOM_LICENSE_KEY ortam değişkeninizi buraya kopyalayın.", "already_have_key_suggestion": "Lütfen mevcut CALCOM_LICENSE_KEY ortam değişkeninizi buraya kopyalayın.",
"app_is_enabled": "{{appName}} etkinleştirildi", "app_is_enabled": "{{appName}} etkinleştirildi",

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