Compare commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
2a752e5358 | ||
|
|
b2684837c1 | ||
|
|
2fed2133a5 | ||
|
|
7fc0877c26 | ||
|
|
f7ce774ddc | ||
|
|
174c730f1f | ||
|
|
8ad513e4ec | ||
|
|
a0bf5b4067 | ||
|
|
484f603c9e | ||
|
|
94b16fb327 | ||
|
|
55db7bdf76 | ||
|
|
14af241d5f | ||
|
|
4efa90e42f | ||
|
|
581e1759c4 | ||
|
|
61cdac321e | ||
|
|
59a04137a1 | ||
|
|
eba1a1ffff | ||
|
|
df25b179d7 | ||
|
|
ff2d9ad67d | ||
|
|
485b72cd0d | ||
|
|
57eb5a4328 | ||
|
|
2c4954e31c | ||
|
|
748f2b42e8 | ||
|
|
6bc713bc2e | ||
|
|
b1019d6bab | ||
|
|
268f1872e4 | ||
|
|
60d439cc32 | ||
|
|
36d201ac20 | ||
|
|
d6644bc232 | ||
|
|
7a9b893931 | ||
|
|
235c314e68 |
@@ -1,27 +1,75 @@
|
||||
name: "Apply issue labels to PR"
|
||||
|
||||
on:
|
||||
pull_request:
|
||||
types: [opened]
|
||||
pull_request_target:
|
||||
types:
|
||||
- opened
|
||||
|
||||
|
||||
jobs:
|
||||
label_on_pr:
|
||||
runs-on: ubuntu-latest
|
||||
|
||||
|
||||
permissions:
|
||||
contents: none
|
||||
issues: read
|
||||
pull-requests: write
|
||||
|
||||
|
||||
steps:
|
||||
- name: Checkout repository
|
||||
uses: actions/checkout@v2
|
||||
|
||||
- name: Setup Node.js
|
||||
uses: actions/setup-node@v2
|
||||
with:
|
||||
node-version: 16
|
||||
|
||||
- name: Apply labels from linked issue to PR
|
||||
env:
|
||||
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
||||
PR_DATA: ${{ toJson(github.event.pull_request) }}
|
||||
run: node ./.github/workflows/scripts/apply-issue-labels-to-pr.ts
|
||||
uses: actions/github-script@v5
|
||||
with:
|
||||
github-token: ${{ secrets.GITHUB_TOKEN }}
|
||||
script: |
|
||||
async function getLinkedIssues(owner, repo, prNumber) {
|
||||
const query = `query GetLinkedIssues($owner: String!, $repo: String!, $prNumber: Int!) {
|
||||
repository(owner: $owner, name: $repo) {
|
||||
pullRequest(number: $prNumber) {
|
||||
closingIssuesReferences(first: 10) {
|
||||
nodes {
|
||||
number
|
||||
labels(first: 10) {
|
||||
nodes {
|
||||
name
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}`;
|
||||
|
||||
const variables = {
|
||||
owner: owner,
|
||||
repo: repo,
|
||||
prNumber: prNumber,
|
||||
};
|
||||
|
||||
const result = await github.graphql(query, variables);
|
||||
return result.repository.pullRequest.closingIssuesReferences.nodes;
|
||||
}
|
||||
|
||||
const pr = context.payload.pull_request;
|
||||
const linkedIssues = await getLinkedIssues(
|
||||
context.repo.owner,
|
||||
context.repo.repo,
|
||||
pr.number
|
||||
);
|
||||
|
||||
const labelsToAdd = new Set();
|
||||
for (const issue of linkedIssues) {
|
||||
if (issue.labels && issue.labels.nodes) {
|
||||
for (const label of issue.labels.nodes) {
|
||||
labelsToAdd.add(label.name);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (labelsToAdd.size) {
|
||||
await github.rest.issues.addLabels({
|
||||
owner: context.repo.owner,
|
||||
repo: context.repo.repo,
|
||||
issue_number: pr.number,
|
||||
labels: Array.from(labelsToAdd),
|
||||
});
|
||||
}
|
||||
|
||||
@@ -25,7 +25,9 @@ jobs:
|
||||
- uses: ./.github/actions/cache-db
|
||||
- uses: ./.github/actions/cache-build
|
||||
- name: Run Tests
|
||||
run: yarn test-e2e:embed-react
|
||||
run: |
|
||||
yarn test-e2e:embed-react
|
||||
yarn workspace @calcom/embed-react packaged:tests
|
||||
env:
|
||||
DEPLOYSENTINEL_API_KEY: ${{ secrets.DEPLOYSENTINEL_API_KEY }}
|
||||
- name: Upload Test Results
|
||||
|
||||
@@ -1,139 +0,0 @@
|
||||
const https = require("https");
|
||||
|
||||
async function applyLabelFromLinkedIssueToPR(pr, token) {
|
||||
// Get the labels from issues linked to the PR
|
||||
const query = `
|
||||
query GetLinkedIssues($owner: String!, $repo: String!, $prNumber: Int!) {
|
||||
repository(owner: $owner, name: $repo) {
|
||||
pullRequest(number: $prNumber) {
|
||||
closingIssuesReferences(first: 10) {
|
||||
nodes {
|
||||
number
|
||||
labels(first: 10) {
|
||||
nodes {
|
||||
name
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
`;
|
||||
|
||||
const graphqlData = JSON.stringify({
|
||||
query,
|
||||
variables: {
|
||||
owner: pr.base.repo.owner.login,
|
||||
repo: pr.base.repo.name,
|
||||
prNumber: pr.number,
|
||||
},
|
||||
});
|
||||
|
||||
const requestOptions = {
|
||||
hostname: "api.github.com",
|
||||
path: "/graphql",
|
||||
method: "POST",
|
||||
headers: {
|
||||
"Content-Type": "application/json",
|
||||
"Content-Length": graphqlData.length,
|
||||
Authorization: "Bearer " + token,
|
||||
"User-Agent": "Node.js",
|
||||
},
|
||||
};
|
||||
|
||||
// Use the native Node.js request library to make the request
|
||||
let linkedIssues;
|
||||
linkedIssues = await new Promise((resolve, reject) => {
|
||||
const request = https.request(requestOptions, (response) => {
|
||||
let responseBody = "";
|
||||
response.on("data", (chunk) => {
|
||||
responseBody += chunk;
|
||||
});
|
||||
response.on("end", () => {
|
||||
resolve(JSON.parse(responseBody)?.data?.repository?.pullRequest?.closingIssuesReferences?.nodes);
|
||||
});
|
||||
});
|
||||
|
||||
request.on("error", (error) => {
|
||||
reject(error);
|
||||
});
|
||||
|
||||
request.write(graphqlData);
|
||||
request.end();
|
||||
});
|
||||
|
||||
if (!linkedIssues || linkedIssues.length === 0) {
|
||||
console.log("No issue linked.");
|
||||
return;
|
||||
}
|
||||
|
||||
// Iterate over linked issues and apply labels to PR
|
||||
for (const issue of linkedIssues) {
|
||||
const labels = issue?.labels?.nodes?.map((label) => label.name);
|
||||
|
||||
if (!labels || labels.length === 0) {
|
||||
console.log(`No labels found on the linked issue #${issue.number}.`);
|
||||
continue;
|
||||
}
|
||||
|
||||
const labelsData = JSON.stringify({
|
||||
labels: labels,
|
||||
});
|
||||
|
||||
const requestOptions = {
|
||||
hostname: "api.github.com",
|
||||
path: `/repos/${pr.base.repo.owner.login}/${pr.base.repo.name}/issues/${pr.number}/labels`,
|
||||
method: "POST",
|
||||
headers: {
|
||||
"Content-Type": "application/json",
|
||||
"Content-Length": labelsData.length,
|
||||
Authorization: "Bearer " + token,
|
||||
"User-Agent": "Node.js",
|
||||
},
|
||||
};
|
||||
|
||||
let labelResult;
|
||||
labelResult = await new Promise((resolve, reject) => {
|
||||
const request = https.request(requestOptions, (response) => {
|
||||
let responseBody = "";
|
||||
response.on("data", (chunk) => {
|
||||
responseBody += chunk;
|
||||
});
|
||||
response.on("data", () => {});
|
||||
response.on("end", () => {
|
||||
resolve(JSON.parse(responseBody));
|
||||
});
|
||||
});
|
||||
|
||||
request.on("error", (error) => {
|
||||
reject(error);
|
||||
});
|
||||
|
||||
request.write(labelsData);
|
||||
request.end();
|
||||
});
|
||||
|
||||
if (labelResult.message) {
|
||||
console.log(`Error labelling PR: ${labelResult.message}`);
|
||||
continue;
|
||||
}
|
||||
|
||||
console.log(
|
||||
`Applied labels: ${labels.join(", ")} to PR #${pr.number} from linked issue #${issue.number}`
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
// Pass the PR data and GitHub token to the script
|
||||
(async () => {
|
||||
if (!process.env.PR_DATA) {
|
||||
console.log("No PR data found.");
|
||||
return;
|
||||
}
|
||||
|
||||
const prData = JSON.parse(process.env.PR_DATA);
|
||||
const token = process.env.GITHUB_TOKEN;
|
||||
|
||||
await applyLabelFromLinkedIssueToPR(prData, token);
|
||||
})();
|
||||
@@ -33,7 +33,7 @@ import { schemaQuerySingleOrMultipleUserIds } from "~/lib/validations/shared/que
|
||||
* type: integer
|
||||
* example: [2, 3, 4]
|
||||
* - in: query
|
||||
* name: attendeeEmails
|
||||
* name: attendeeEmail
|
||||
* required: false
|
||||
* schema:
|
||||
* oneOf:
|
||||
|
||||
@@ -26,7 +26,6 @@ import { defaultResponder } from "@calcom/lib/server";
|
||||
* required:
|
||||
* - eventTypeId
|
||||
* - start
|
||||
* - end
|
||||
* - name
|
||||
* - email
|
||||
* - timeZone
|
||||
|
||||
@@ -5,6 +5,8 @@ import Script from "next/script";
|
||||
|
||||
import "@calcom/embed-core/src/embed-iframe";
|
||||
import LicenseRequired from "@calcom/features/ee/common/components/LicenseRequired";
|
||||
import { WEBAPP_URL } from "@calcom/lib/constants";
|
||||
import { buildCanonical } from "@calcom/lib/next-seo.config";
|
||||
|
||||
import type { AppProps } from "@lib/app-providers";
|
||||
import AppProviders from "@lib/app-providers";
|
||||
@@ -49,9 +51,24 @@ function PageWrapper(props: AppProps) {
|
||||
};
|
||||
// Use the layout defined at the page level, if available
|
||||
const getLayout = Component.getLayout ?? ((page) => page);
|
||||
|
||||
// Canonical: Check if the URL is from cal.com so that we set the canonical conditionally
|
||||
const isCalcom =
|
||||
WEBAPP_URL &&
|
||||
(new URL(WEBAPP_URL).hostname.endsWith("cal.com") || new URL(WEBAPP_URL).hostname.endsWith("cal.dev"));
|
||||
const path = router.asPath;
|
||||
|
||||
return (
|
||||
<AppProviders {...providerProps}>
|
||||
<DefaultSeo {...seoConfig.defaultNextSeo} />
|
||||
<DefaultSeo
|
||||
// Set canonical to https://cal.com or self-hosted URL
|
||||
canonical={
|
||||
isCalcom
|
||||
? buildCanonical({ path, origin: "https://cal.com" }) // cal.com & .dev
|
||||
: buildCanonical({ path, origin: WEBAPP_URL }) // self-hosted
|
||||
}
|
||||
{...seoConfig.defaultNextSeo}
|
||||
/>
|
||||
<I18nLanguageHandler />
|
||||
<Script
|
||||
nonce={nonce}
|
||||
|
||||
@@ -215,6 +215,11 @@ const nextConfig = {
|
||||
destination: "/new-booker/team/:slug/:type",
|
||||
has: [{ type: "cookie", key: "new-booker-enabled" }],
|
||||
},
|
||||
{
|
||||
source: "/d/:link/:slug",
|
||||
destination: "/new-booker/d/:link/:slug",
|
||||
has: [{ type: "cookie", key: "new-booker-enabled" }],
|
||||
},
|
||||
];
|
||||
},
|
||||
async headers() {
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "@calcom/web",
|
||||
"version": "2.9.5",
|
||||
"version": "2.9.6",
|
||||
"private": true,
|
||||
"scripts": {
|
||||
"analyze": "ANALYZE=true next build",
|
||||
|
||||
@@ -0,0 +1,120 @@
|
||||
import type { GetServerSidePropsContext } from "next";
|
||||
import { z } from "zod";
|
||||
|
||||
import { Booker } from "@calcom/atoms";
|
||||
import { BookerSeo } from "@calcom/features/bookings/components/BookerSeo";
|
||||
import { getBookingByUidOrRescheduleUid } from "@calcom/features/bookings/lib/get-booking";
|
||||
import type { GetBookingType } from "@calcom/features/bookings/lib/get-booking";
|
||||
import prisma from "@calcom/prisma";
|
||||
|
||||
import type { inferSSRProps } from "@lib/types/inferSSRProps";
|
||||
|
||||
import PageWrapper from "@components/PageWrapper";
|
||||
|
||||
type PageProps = inferSSRProps<typeof getServerSideProps>;
|
||||
|
||||
export default function Type({ slug, user, booking, away, isBrandingHidden }: PageProps) {
|
||||
return (
|
||||
<main className="flex h-full min-h-[100dvh] items-center justify-center">
|
||||
<BookerSeo
|
||||
username={user}
|
||||
eventSlug={slug}
|
||||
rescheduleUid={booking?.uid}
|
||||
hideBranding={isBrandingHidden}
|
||||
/>
|
||||
<Booker
|
||||
username={user}
|
||||
eventSlug={slug}
|
||||
rescheduleBooking={booking}
|
||||
isAway={away}
|
||||
hideBranding={isBrandingHidden}
|
||||
/>
|
||||
</main>
|
||||
);
|
||||
}
|
||||
|
||||
Type.PageWrapper = PageWrapper;
|
||||
|
||||
async function getUserPageProps(context: GetServerSidePropsContext) {
|
||||
const { link, slug } = paramsSchema.parse(context.params);
|
||||
const { rescheduleUid } = context.query;
|
||||
|
||||
const { ssrInit } = await import("@server/lib/ssr");
|
||||
const ssr = await ssrInit(context);
|
||||
|
||||
const hashedLink = await prisma.hashedLink.findUnique({
|
||||
where: {
|
||||
link,
|
||||
},
|
||||
select: {
|
||||
eventTypeId: true,
|
||||
eventType: {
|
||||
select: {
|
||||
users: {
|
||||
select: {
|
||||
username: true,
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
});
|
||||
|
||||
const username = hashedLink?.eventType.users[0]?.username;
|
||||
|
||||
if (!hashedLink || !username) {
|
||||
return {
|
||||
notFound: true,
|
||||
};
|
||||
}
|
||||
|
||||
const user = await prisma.user.findUnique({
|
||||
where: {
|
||||
username,
|
||||
},
|
||||
select: {
|
||||
away: true,
|
||||
hideBranding: true,
|
||||
},
|
||||
});
|
||||
|
||||
if (!user) {
|
||||
return {
|
||||
notFound: true,
|
||||
};
|
||||
}
|
||||
|
||||
let booking: GetBookingType | null = null;
|
||||
if (rescheduleUid) {
|
||||
booking = await getBookingByUidOrRescheduleUid(`${rescheduleUid}`);
|
||||
}
|
||||
|
||||
// We use this to both prefetch the query on the server,
|
||||
// as well as to check if the event exist, so we c an show a 404 otherwise.
|
||||
const eventData = await ssr.viewer.public.event.fetch({ username, eventSlug: slug });
|
||||
|
||||
if (!eventData) {
|
||||
return {
|
||||
notFound: true,
|
||||
};
|
||||
}
|
||||
|
||||
return {
|
||||
props: {
|
||||
booking,
|
||||
away: user?.away,
|
||||
user: username,
|
||||
slug,
|
||||
trpcState: ssr.dehydrate(),
|
||||
isBrandingHidden: user?.hideBranding,
|
||||
},
|
||||
};
|
||||
}
|
||||
|
||||
const paramsSchema = z.object({ link: z.string(), slug: z.string() });
|
||||
|
||||
// Booker page fetches a tiny bit of data server side, to determine early
|
||||
// whether the page should show an away state or dynamic booking not allowed.
|
||||
export const getServerSideProps = async (context: GetServerSidePropsContext) => {
|
||||
return await getUserPageProps(context);
|
||||
};
|
||||
@@ -26,6 +26,8 @@
|
||||
"rejection_confirmation": "رفض الحجز",
|
||||
"manage_this_event": "قم بإدارة هذا الحدث",
|
||||
"invite_team_member": "دعوة عضو في الفريق",
|
||||
"invite_team_individual_segment": "دعوة فرد",
|
||||
"invite_team_bulk_segment": "استيراد بحجم جماعي",
|
||||
"invite_team_notifcation_badge": "دعوة",
|
||||
"your_event_has_been_scheduled": "تم جدولة الحدث الخاص بك",
|
||||
"your_event_has_been_scheduled_recurring": "تم جدولة الحدث المتكرر الخاص بك",
|
||||
@@ -222,6 +224,7 @@
|
||||
"go_back_login": "العودة إلى صفحة تسجيل الدخول",
|
||||
"error_during_login": "حدث خطأ ما أثناء تسجيل الدخول. ارجع إلى شاشة تسجيل الدخول وحاول مجددًا.",
|
||||
"request_password_reset": "إرسال بريد إلكتروني لإعادة التعيين",
|
||||
"send_invite": "إرسال دعوة",
|
||||
"forgot_password": "هل نسيت كلمة المرور؟",
|
||||
"forgot": "هل نسيت؟",
|
||||
"done": "تم",
|
||||
@@ -237,6 +240,8 @@
|
||||
"set_availability": "تحديد الوقت الذي تكون فيه متاحًا",
|
||||
"continue_without_calendar": "المتابعة من دون تقويم",
|
||||
"connect_your_calendar": "ربط التقويم لديك",
|
||||
"connect_your_video_app": "اربط تطبيقاتك الفيديو الخاصة بك",
|
||||
"connect_your_video_app_instructions": "قم بتوصيل تطبيقات الفيديو لديك لاستخدامها في أنواع الأحداث الخاصة بك.",
|
||||
"connect_your_calendar_instructions": "اربط التقويم لديك للتحقق تلقائيًا من الأوقات المشغولة والأحداث الجديدة أثناء جدولتها.",
|
||||
"set_up_later": "الإعداد لاحقًا",
|
||||
"current_time": "الوقت الحالي",
|
||||
@@ -366,6 +371,7 @@
|
||||
"create_webhook": "إنشاء الويب هوك",
|
||||
"booking_cancelled": "تم إلغاء الحجز",
|
||||
"booking_rescheduled": "تمت إعادة جدولة الحجز",
|
||||
"recording_ready": "رابط تنزيل التسجيل جاهز",
|
||||
"booking_created": "تم إنشاء الحجز",
|
||||
"meeting_ended": "انتهى الاجتماع",
|
||||
"form_submitted": "تم إرسال النموذج",
|
||||
@@ -464,6 +470,7 @@
|
||||
"friday": "الجمعة",
|
||||
"saturday": "السبت",
|
||||
"sunday": "الأحد",
|
||||
"all_booked_today": "تم حجز الكل.",
|
||||
"slots_load_fail": "تعذر تحميل الفترات الزمنية المتاحة.",
|
||||
"additional_guests": "إضافة ضيوف",
|
||||
"your_name": "اسمك",
|
||||
@@ -751,6 +758,10 @@
|
||||
"new_event_type_to_book_description": "قم بإنشاء نوع حدث جديد ليحجز الأشخاص من خلاله.",
|
||||
"length": "الطول",
|
||||
"minimum_booking_notice": "الحد الأدنى من الوقت للحجز",
|
||||
"offset_toggle": "أوقات بدء الإزاحة",
|
||||
"offset_toggle_description": "إزاحة الفتحات الزمنية المعروضة للحاجزين بعدد محدد من الدقائق",
|
||||
"offset_start": "إزاحة بمقدار",
|
||||
"offset_start_description": "على سبيل المثال، هذا سيُظهر فتحات زمنية لمن يقوم بالحجز لديك {{ adjustedTime }} بدلاً من {{ originalTime }}",
|
||||
"slot_interval": "الفترات الزمنية بين عمليات الحجز",
|
||||
"slot_interval_default": "استخدام طول الحدث (الوضع الافتراضي)",
|
||||
"delete_event_type": "هل تريد حذف نوع الحدث؟",
|
||||
@@ -903,6 +914,7 @@
|
||||
"duplicate": "تكرار",
|
||||
"offer_seats": "عرض مقاعد",
|
||||
"offer_seats_description": "عرض مقاعد للحجز. هذا يعطل تلقائياً حجز الضيوف وحجز الاشتراك.",
|
||||
"seats_available_one": "المقاعد المتاحة",
|
||||
"seats_available_other": "المقاعد المتاحة",
|
||||
"number_of_seats": "عدد المقاعد لكل حجز",
|
||||
"enter_number_of_seats": "أدخل عدد المقاعد",
|
||||
@@ -1037,6 +1049,7 @@
|
||||
"event_cancelled_trigger": "متى تم إلغاء هذا الحدث",
|
||||
"new_event_trigger": "متى تم حجز الحدث الجديد",
|
||||
"email_host_action": "إرسال رسالة إلكترونية إلى المضيف",
|
||||
"email_attendee_action": "إرسال رسالة إلكترونية إلى الحضور",
|
||||
"sms_attendee_action": "إرسال رسالة نصية قصيرة إلى واحد من الحضور",
|
||||
"sms_number_action": "إرسال رسالة نصية قصيرة إلى رقم محدد",
|
||||
"workflows": "سير العمل",
|
||||
@@ -1189,6 +1202,7 @@
|
||||
"create_workflow": "إنشاء سير عمل",
|
||||
"do_this": "افعل هذا",
|
||||
"turn_off": "إيقاف التشغيل",
|
||||
"turn_on": "تشغيل",
|
||||
"settings_updated_successfully": "تم تحديث الإعدادات بنجاح",
|
||||
"error_updating_settings": "خطأ في تحديث الإعدادات",
|
||||
"personal_cal_url": "عنوان {{appName}} URL الخاص بي",
|
||||
@@ -1245,6 +1259,7 @@
|
||||
"calendars_description": "قم بتهيئة كيفية تفاعل أنواع الأحداث مع التقويمات الخاصة بك",
|
||||
"appearance_description": "إدارة إعدادات مظهر الحجز الخاص بك",
|
||||
"conferencing_description": "أضف تطبيقات مؤتمرات الفيديو المفضلة لديك لاجتماعاتك",
|
||||
"add_conferencing_app": "إضافة تطبيق عقد اجتماعات",
|
||||
"password_description": "إدارة إعدادات كلمات مرور حسابك",
|
||||
"2fa_description": "إدارة إعدادات كلمات مرور حسابك",
|
||||
"we_just_need_basic_info": "نحتاج إلى بعض المعلومات الأساسية لإعداد ملفك الشخصي.",
|
||||
@@ -1325,7 +1340,7 @@
|
||||
"exchange_version_2013_SP1": "2013 SP1",
|
||||
"exchange_version_2015": "2015",
|
||||
"exchange_version_2016": "2016",
|
||||
"routing_forms_description": "يمكنك رؤية جميع النماذج والتوجيهات التي أنشأتها هنا.",
|
||||
"routing_forms_description": "أنشئ النماذج لتوجيه الحضور إلى الوجهات الصحيحة",
|
||||
"routing_forms_send_email_owner": "إرسال رسالة إلكترونية إلى المالك",
|
||||
"routing_forms_send_email_owner_description": "يرسل رسالة بريد إلكتروني إلى المالك عند إرسال النموذج",
|
||||
"add_new_form": "إضافة استمارة جديدة",
|
||||
@@ -1619,6 +1634,7 @@
|
||||
"email_user_cta": "عرض الدعوة",
|
||||
"email_no_user_invite_heading": "لقد تمت دعوتك للانضمام إلى فريق في {{appName}}",
|
||||
"email_no_user_invite_subheading": "دعاك {{invitedBy}} للانضمام إلى فريقه على {{appName}}. إن {{appName}} هو برنامج جدولة الأحداث الذي يمكّنك أنت وفريقك من جدولة الاجتماعات دون الحاجة إلى المراسلة عبر البريد الإلكتروني.",
|
||||
"email_user_invite_subheading": "دعاك {{invitedBy}} للانضمام إلى فريقه {{teamName}} على {{appName}}. إن {{appName}} هو برنامج جدولة الأحداث الذي يمكّنك أنت وفريقك من جدولة الاجتماعات دون الحاجة إلى المراسلة عبر البريد الإلكتروني.",
|
||||
"email_no_user_invite_steps_intro": "سنرشدك خلال بضع خطوات قصيرة وستستمتع بجدولة خالية من الإجهاد مع فريقك وسريعة.",
|
||||
"email_no_user_step_one": "اختر اسم المستخدم الخاص بك",
|
||||
"email_no_user_step_two": "ربط حساب التقويم الخاص بك",
|
||||
@@ -1694,6 +1710,15 @@
|
||||
"spot_popular_event_types_description": "تعرف أي أنواع من الأحداث لديك تتلقى أكبر عدد من النقرات والحجوزات",
|
||||
"no_responses_yet": "لا توجد ردود بعد",
|
||||
"this_will_be_the_placeholder": "سيكون هذا هو العنصر النائب",
|
||||
"error_booking_event": "حدث خطأ عند حجز الحدث، الرجاء تحديث الصفحة والمحاولة ثانيةً",
|
||||
"timeslot_missing_title": "لم يتم تحديد مساحة زمنية",
|
||||
"timeslot_missing_description": "الرجاء تحديد فترة زمنية لحجز الحدث.",
|
||||
"timeslot_missing_cta": "حدد الوقت",
|
||||
"switch_monthly": "التبديل إلى العرض الشهري",
|
||||
"switch_weekly": "التبديل إلى العرض الأسبوعي",
|
||||
"switch_multiday": "التبديل إلى العرض اليومي",
|
||||
"num_locations": "خيارات الموقع {{num}}",
|
||||
"select_on_next_step": "حدد في الخطوة التالية",
|
||||
"this_meeting_has_not_started_yet": "لم يبدأ هذا الاجتماع بعد",
|
||||
"this_app_requires_connected_account": "{{appName}} يتطلب حساب {{dependencyName}} متصل",
|
||||
"connect_app": "اتصال {{dependencyName}}",
|
||||
@@ -1723,6 +1748,7 @@
|
||||
"locked_apps_description": "سيتمكن الأعضاء من رؤية التطبيقات النشطة، ولكن لن يتمكنوا من تعديل أي إعدادات للتطبيق",
|
||||
"locked_webhooks_description": "سيكون الأعضاء قادرين على رؤية webhooks النشطة، ولكن من دون القدرة على تعديل أي إعدادات Webhook",
|
||||
"locked_workflows_description": "سيكون الأعضاء قادرين على رؤية مسارات العمل النشطة، ولكن من دون القدرة على تعديل أي إعدادات لمسار العمل",
|
||||
"locked_by_admin": "مقفل من قبل المشرف",
|
||||
"app_not_connected": "أنت غير متصل بحساب {{appName}}.",
|
||||
"connect_now": "اتصل الآن",
|
||||
"managed_event_dialog_confirm_button_one": "الاستبدال وإخطار {{count}} عضو",
|
||||
@@ -1780,5 +1806,34 @@
|
||||
"seats_and_no_show_fee_error": "لا يمكن حاليًا تمكين المقاعد وفرض رسوم عدم الحضور",
|
||||
"complete_your_booking": "أكمل الحجز",
|
||||
"complete_your_booking_subject": "أكمل الحجز: {{title}} في {{date}}",
|
||||
"email_invite_team": "تم دعوة {{email}}"
|
||||
"confirm_your_details": "تأكيد التفاصيل الخاصة بك",
|
||||
"currency_string": "{{amount, currency}}",
|
||||
"charge_card_dialog_body": "أنت على وشك استحصال مبلغ {{amount, currency}} من أحد الحضور. هل أنت متأكد من أنك تريد المتابعة؟",
|
||||
"charge_attendee": "استحصال مبلغ {{amount, currency}} من أحد الحضور",
|
||||
"payment_app_commission": "يتطلب الدفع ({{paymentFeePercentage}}% + {{fee, currency}} عمولة لكل معاملة)",
|
||||
"email_invite_team": "تم دعوة {{email}}",
|
||||
"email_invite_team_bulk": "{{userCount}} مستخدم تم دعوتهم",
|
||||
"error_collecting_card": "خطأ في تحصيل البطاقة",
|
||||
"image_size_limit_exceed": "لا ينبغي أن تتجاوز الصورة التي تم تحميلها الحد الأقصى لحجم 5 ميغابايت",
|
||||
"inline_embed": "Inline Embed",
|
||||
"load_inline_content": "يحمّل نوع الحدث الخاص بك مباشرة مع محتوى موقعك الأخير.",
|
||||
"floating_pop_up_button": "زر منبثق عائم",
|
||||
"floating_button_trigger_modal": "يضع زرًا عائمًا على موقعك يقوم بتشغيل توجيه مع نوع الحدث الخاص بك.",
|
||||
"pop_up_element_click": "ينبثق عبر النقر على العنصر",
|
||||
"open_dialog_with_element_click": "افتح مربع الحوار الخاص بك عندما ينقر شخص على عنصر ما.",
|
||||
"need_help_embedding": "بحاجة إلى مساعدة؟ راجع أدلتنا لإدراج Cal على Wix، Squarespace، أو WordPress، تحقق من أسئلتنا المشتركة، أو استكشف خيارات متقدمة متضمنة.",
|
||||
"book_my_cal": "ثبّت الحجز في رزنامتي",
|
||||
"invite_as": "دعوة باسم",
|
||||
"form_updated_successfully": "تم تحديث الاستمارة بنجاح.",
|
||||
"email_not_cal_member_cta": "انضم إلى فريقك",
|
||||
"disable_attendees_confirmation_emails": "تعطيل رسائل التأكيد الافتراضية للحضور",
|
||||
"disable_attendees_confirmation_emails_description": "يوجد مسار عمل واحد على الأقل نشط في هذا النوع من الأحداث يقوم بإرسال رسائل إلكترونية إلى الحاضرين عند حجز الحدث.",
|
||||
"disable_host_confirmation_emails": "تعطيل رسائل التأكيد الافتراضية لمضيف",
|
||||
"disable_host_confirmation_emails_description": "يوجد مسار عمل واحد على الأقل نشط في هذا النوع من الأحداث يقوم بإرسال رسائل إلكترونية إلى المضيف عند حجز الحدث.",
|
||||
"add_an_override": "إضافة تجاوز",
|
||||
"import_from_google_workspace": "استيراد المستخدمين من مساحة عمل Google",
|
||||
"connect_google_workspace": "ربط مساحة عمل Google",
|
||||
"google_workspace_admin_tooltip": "يجب أن تكون مشرفًا على مساحة العمل لاستخدام هذه الميزة",
|
||||
"first_event_type_webhook_description": "قم بإنشاء أول شبكة ويب هوك لهذا النوع من الفعاليات",
|
||||
"create_for": "إنشاء من أجل"
|
||||
}
|
||||
|
||||
@@ -26,6 +26,8 @@
|
||||
"rejection_confirmation": "Odmítnout rezervaci",
|
||||
"manage_this_event": "Spravovat tuto událost",
|
||||
"invite_team_member": "Pozvat člena týmu",
|
||||
"invite_team_individual_segment": "Pozvat jednotlivce",
|
||||
"invite_team_bulk_segment": "Hromadný import",
|
||||
"invite_team_notifcation_badge": "Pozv.",
|
||||
"your_event_has_been_scheduled": "Vaše událost byla naplánována",
|
||||
"your_event_has_been_scheduled_recurring": "Opakovaná událost byla naplánována",
|
||||
@@ -222,6 +224,7 @@
|
||||
"go_back_login": "Zpět na přihlašovací stránku",
|
||||
"error_during_login": "Při přihlašování došlo k chybě. Přejděte zpět na přihlašovací obrazovku a zkuste to znovu.",
|
||||
"request_password_reset": "Žádost o obnovení hesla",
|
||||
"send_invite": "Odeslat pozvánku",
|
||||
"forgot_password": "Zapomenuté heslo",
|
||||
"forgot": "Zapomněli jste?",
|
||||
"done": "Hotovo",
|
||||
@@ -237,6 +240,8 @@
|
||||
"set_availability": "Nastavte, jak jste dostupní",
|
||||
"continue_without_calendar": "Pokračovat bez kalendáře",
|
||||
"connect_your_calendar": "Připojit svůj kalendář",
|
||||
"connect_your_video_app": "Propojte své video aplikace",
|
||||
"connect_your_video_app_instructions": "Propojte své video aplikace a používejte je v typech událostí.",
|
||||
"connect_your_calendar_instructions": "Připojit kalendář pro automatickou kontrolu časů, kdy nejste dostupní, a nově naplánovaných událostí.",
|
||||
"set_up_later": "Zprovoznit později",
|
||||
"current_time": "Aktuální čas",
|
||||
@@ -366,6 +371,7 @@
|
||||
"create_webhook": "Vytvořit webhook",
|
||||
"booking_cancelled": "Rezervace zrušena",
|
||||
"booking_rescheduled": "Rezervace přesunuta",
|
||||
"recording_ready": "Odkaz ke stažení záznamu je připraven",
|
||||
"booking_created": "Rezervace vytvořena",
|
||||
"meeting_ended": "Schůzka skončila",
|
||||
"form_submitted": "Formulář byl odeslán",
|
||||
@@ -464,6 +470,7 @@
|
||||
"friday": "pátek",
|
||||
"saturday": "sobota",
|
||||
"sunday": "neděle",
|
||||
"all_booked_today": "Vše zarezervováno.",
|
||||
"slots_load_fail": "Nepodařilo se načíst dostupné časové sloty.",
|
||||
"additional_guests": "Přidat hosty",
|
||||
"your_name": "Vaše jméno",
|
||||
@@ -751,6 +758,10 @@
|
||||
"new_event_type_to_book_description": "Vytvořit nový typ události, ve které si lidé mohou rezervovat časy.",
|
||||
"length": "Doba trvání",
|
||||
"minimum_booking_notice": "Minimální potřebná doba",
|
||||
"offset_toggle": "Posunutí časů začátku",
|
||||
"offset_toggle_description": "Posunutí časových slotů zobrazených rezervujícím osobám o zadaný počet minut",
|
||||
"offset_start": "Posunutí o",
|
||||
"offset_start_description": "například v tomto případě se vašim rezervujícím zobrazí časové sloty v {{ adjustedTime }} namísto {{ originalTime }}",
|
||||
"slot_interval": "Časové intervaly",
|
||||
"slot_interval_default": "Použít délku události (výchozí)",
|
||||
"delete_event_type": "Odstranit typ události?",
|
||||
@@ -903,6 +914,7 @@
|
||||
"duplicate": "Duplikovat",
|
||||
"offer_seats": "Nabídka míst",
|
||||
"offer_seats_description": "Nabídnout místa k rezervaci. Tím se vypnou rezervace hostů a volitelné rezervace.",
|
||||
"seats_available_one": "Dostupné místo",
|
||||
"seats_available_other": "Dostupná místa",
|
||||
"number_of_seats": "Počet míst na rezervaci",
|
||||
"enter_number_of_seats": "Zadejte počet míst",
|
||||
@@ -1037,6 +1049,7 @@
|
||||
"event_cancelled_trigger": "při zrušení události",
|
||||
"new_event_trigger": "při rezervaci nové události",
|
||||
"email_host_action": "odeslání e-mailu hostiteli",
|
||||
"email_attendee_action": "odeslat e-mail účastníkům",
|
||||
"sms_attendee_action": "odeslání SMS účastníkovi",
|
||||
"sms_number_action": "odeslání SMS na konkrétní číslo",
|
||||
"workflows": "Pracovní postupy",
|
||||
@@ -1189,6 +1202,7 @@
|
||||
"create_workflow": "Vytvoření pracovního postupu",
|
||||
"do_this": "Provést",
|
||||
"turn_off": "Vypnout",
|
||||
"turn_on": "Zapnout",
|
||||
"settings_updated_successfully": "Nastavení aktualizováno",
|
||||
"error_updating_settings": "Chyba při aktualizaci nastavení",
|
||||
"personal_cal_url": "Moje osobní adresa URL {{appName}}",
|
||||
@@ -1245,6 +1259,7 @@
|
||||
"calendars_description": "Konfigurace způsobu interakce typů událostí s kalendáři",
|
||||
"appearance_description": "Správa nastavení vzhledu rezervace",
|
||||
"conferencing_description": "Přidejte si oblíbené videokonferenční pro vaše schůzky",
|
||||
"add_conferencing_app": "Přidat videokonferenční aplikaci",
|
||||
"password_description": "Správa nastavení hesel k vašemu účtu",
|
||||
"2fa_description": "Správa nastavení hesel k vašemu účtu",
|
||||
"we_just_need_basic_info": "K nastavení vašeho profilu potřebujeme jen několik základních informací.",
|
||||
@@ -1619,6 +1634,7 @@
|
||||
"email_user_cta": "Zobrazit pozvánku",
|
||||
"email_no_user_invite_heading": "Byli jste pozváni do týmu v aplikaci {{appName}}",
|
||||
"email_no_user_invite_subheading": "Uživatel {{invitedBy}} vás pozval, abyste se připojili k jeho týmu v aplikaci {{appName}}. {{appName}} je plánovač událostí, který vám a vašemu týmu umožňuje plánovat schůzky bez e-mailového ping-pongu.",
|
||||
"email_user_invite_subheading": "Uživatel {{invitedBy}} vás pozval, abyste se připojili k jeho týmu „{{teamName}}“ v aplikaci {{appName}}. {{appName}} je plánovač událostí, který vám a vašemu týmu umožňuje plánovat schůzky bez e-mailového pingpongu.",
|
||||
"email_no_user_invite_steps_intro": "Provedeme vás několika krátkými kroky a za malou chvíli si budete se svým týmem užívat plánování bez stresu.",
|
||||
"email_no_user_step_one": "Vyberte si uživatelské jméno",
|
||||
"email_no_user_step_two": "Připojte svůj účet kalendáře",
|
||||
@@ -1694,6 +1710,15 @@
|
||||
"spot_popular_event_types_description": "Zjistěte, které typy vašich událostí získávají nejvíce kliknutí a rezervací",
|
||||
"no_responses_yet": "Zatím žádné odpovědi",
|
||||
"this_will_be_the_placeholder": "Toto bude zástupný znak",
|
||||
"error_booking_event": "Při rezervaci události došlo k chybě, obnovte prosím stránku a zkuste to znovu",
|
||||
"timeslot_missing_title": "Nebyl vybrán žádný časový slot",
|
||||
"timeslot_missing_description": "Vyberte časový slot, aby mohla být událost zaregistrována.",
|
||||
"timeslot_missing_cta": "Vybrat časový slot",
|
||||
"switch_monthly": "Přepnout na měsíční zobrazení",
|
||||
"switch_weekly": "Přepnout na týdenní zobrazení",
|
||||
"switch_multiday": "Přepnout na denní zobrazení",
|
||||
"num_locations": "Možná místa konání: {{num}}",
|
||||
"select_on_next_step": "Vyberte v dalším kroku",
|
||||
"this_meeting_has_not_started_yet": "Tato schůzka ještě nezačala",
|
||||
"this_app_requires_connected_account": "{{appName}} vyžaduje připojený účet {{dependencyName}}",
|
||||
"connect_app": "Připojit aplikaci {{dependencyName}}",
|
||||
@@ -1723,6 +1748,7 @@
|
||||
"locked_apps_description": "Členové uvidí aktivní aplikace, ale nastavení aplikací upravovat nemohou",
|
||||
"locked_webhooks_description": "Členové uvidí aktivní webhooky, ale nastavení webhooků upravovat nemohou",
|
||||
"locked_workflows_description": "Členové uvidí aktivní pracovní postupy, ale nastavení pracovních postupů upravovat nemohou",
|
||||
"locked_by_admin": "Uzamčeno správcem týmu",
|
||||
"app_not_connected": "Nemáte připojený účet {{appName}}.",
|
||||
"connect_now": "Připojte se",
|
||||
"managed_event_dialog_confirm_button_one": "Nahradit a upozornit {{count}} člena",
|
||||
@@ -1780,5 +1806,34 @@
|
||||
"seats_and_no_show_fee_error": "V současné době nelze povolit místa a strhnout poplatek za nedostavení se",
|
||||
"complete_your_booking": "Dokončete svou rezervaci",
|
||||
"complete_your_booking_subject": "Dokončete svou rezervaci: {{title}} dne {{date}}",
|
||||
"email_invite_team": "Pozvánka pro uživatele {{email}} odeslána"
|
||||
"confirm_your_details": "Potvrďte své údaje",
|
||||
"currency_string": "{{amount, currency}}",
|
||||
"charge_card_dialog_body": "Chystáte se účtovat účastníkovi {{amount, currency}}. Opravdu chcete pokračovat?",
|
||||
"charge_attendee": "Naúčtovat účastníkovi {{amount, currency}}",
|
||||
"payment_app_commission": "Vyžadujte platbu ({{paymentFeePercentage}} % + provizi {{fee, currency}} za transakci)",
|
||||
"email_invite_team": "Pozvánka pro uživatele {{email}} odeslána",
|
||||
"email_invite_team_bulk": "Počet pozvaných uživatelů: {{userCount}}",
|
||||
"error_collecting_card": "Chyba při výběru karty",
|
||||
"image_size_limit_exceed": "Velikost nahraného obrázku by neměla překročit limit 5 MB",
|
||||
"inline_embed": "Embed inline",
|
||||
"load_inline_content": "Načte váš typ události přímo „inline“ s ostatním obsahem webu.",
|
||||
"floating_pop_up_button": "Plovoucí překryvné tlačítko",
|
||||
"floating_button_trigger_modal": "Umístí na váš web plovoucí tlačítko, které spustí modální okno s vaším typem události.",
|
||||
"pop_up_element_click": "Překryvné okno při kliknutí na prvek",
|
||||
"open_dialog_with_element_click": "Otevře dialogové okno Cal, pokud někdo klikne na prvek.",
|
||||
"need_help_embedding": "Potřebujete pomoc? Prohlédněte si naše průvodce pro vložení Cal do Wixu, Squarespace nebo WordPressu, podívejte se na naše nejčastější dotazy nebo prozkoumejte pokročilé možnosti vložení.",
|
||||
"book_my_cal": "Book my Cal",
|
||||
"invite_as": "Pozvat jako",
|
||||
"form_updated_successfully": "Formulář byl aktualizován.",
|
||||
"email_not_cal_member_cta": "Připojte se ke svému týmu",
|
||||
"disable_attendees_confirmation_emails": "Vypnutí výchozích potvrzovacích e-mailů pro účastníky",
|
||||
"disable_attendees_confirmation_emails_description": "U tohoto typu události je aktivní alespoň jeden pracovní postup, který po rezervaci události odešle účastníkům e-mail.",
|
||||
"disable_host_confirmation_emails": "Vypnutí výchozích potvrzovacích e-mailů pro hostitele",
|
||||
"disable_host_confirmation_emails_description": "U tohoto typu události je aktivní alespoň jeden pracovní postup, který po rezervaci události odešle hostiteli e-mail.",
|
||||
"add_an_override": "Přidejte změnu",
|
||||
"import_from_google_workspace": "Importovat uživatele z Google Workspace",
|
||||
"connect_google_workspace": "Propojit s Google Workspace",
|
||||
"google_workspace_admin_tooltip": "Používání této funkce vyžaduje, abyste byli administrátory Google Workspace",
|
||||
"first_event_type_webhook_description": "Vytvořte svůj první webhook pro tento typ události",
|
||||
"create_for": "Vytvořit pro"
|
||||
}
|
||||
|
||||
@@ -1281,7 +1281,6 @@
|
||||
"exchange_authentication_standard": "Grundlæggende autentificering",
|
||||
"exchange_authentication_ntlm": "NTLM autentificering",
|
||||
"exchange_compression": "GZip komprimering",
|
||||
"routing_forms_description": "Du kan se alle former og ruter du har oprettet her.",
|
||||
"routing_forms_send_email_owner": "Send e-mail til Ejer",
|
||||
"routing_forms_send_email_owner_description": "Sender en e-mail til ejeren når formularen er indsendt",
|
||||
"add_new_form": "Tilføj ny formular",
|
||||
|
||||
@@ -26,6 +26,8 @@
|
||||
"rejection_confirmation": "Buchung ablehnen",
|
||||
"manage_this_event": "Diesen Termin verwalten",
|
||||
"invite_team_member": "Teammitglied einladen",
|
||||
"invite_team_individual_segment": "Person einladen",
|
||||
"invite_team_bulk_segment": "Massenimport",
|
||||
"invite_team_notifcation_badge": "Eing.",
|
||||
"your_event_has_been_scheduled": "Ihr Termin wurde gebucht",
|
||||
"your_event_has_been_scheduled_recurring": "Ihr wiederkehrender Termin wurde gebucht",
|
||||
@@ -222,6 +224,7 @@
|
||||
"go_back_login": "Zurück zur Login-Seite",
|
||||
"error_during_login": "Beim Anmelden ist ein Fehler aufgetreten. Gehen Sie zurück zum Anmeldebildschirm und versuchen Sie es erneut.",
|
||||
"request_password_reset": "Passwort zurücksetzen",
|
||||
"send_invite": "Einladung senden",
|
||||
"forgot_password": "Passwort vergessen",
|
||||
"forgot": "Vergessen?",
|
||||
"done": "Erledigt",
|
||||
@@ -237,6 +240,8 @@
|
||||
"set_availability": "Verfügbarkeit festlegen",
|
||||
"continue_without_calendar": "Ohne Kalender fortfahren",
|
||||
"connect_your_calendar": "Kalender verbinden",
|
||||
"connect_your_video_app": "Verbinden Sie Ihre Video-Apps",
|
||||
"connect_your_video_app_instructions": "Verbinden Sie Ihre Video-Apps, um sie für Ihre Termintypen zu verwenden.",
|
||||
"connect_your_calendar_instructions": "Verbinden Sie Ihren Kalender, um automatisch zu prüfen, ob Termine verfügbar sind.",
|
||||
"set_up_later": "Später einrichten",
|
||||
"current_time": "Aktuelle Zeit",
|
||||
@@ -366,6 +371,7 @@
|
||||
"create_webhook": "Webhook erstellen",
|
||||
"booking_cancelled": "Termin abgesagt",
|
||||
"booking_rescheduled": "Termin verlegt",
|
||||
"recording_ready": "Download-Link der Aufnahme bereit",
|
||||
"booking_created": "Termin erstellt",
|
||||
"meeting_ended": "Meeting beendet",
|
||||
"form_submitted": "Formular gesendet",
|
||||
@@ -464,6 +470,7 @@
|
||||
"friday": "Freitag",
|
||||
"saturday": "Samstag",
|
||||
"sunday": "Sonntag",
|
||||
"all_booked_today": "Alle gebucht.",
|
||||
"slots_load_fail": "Die verfügbaren Zeitfenster konnten nicht geladen werden.",
|
||||
"additional_guests": "+ Weitere Gäste",
|
||||
"your_name": "Ihr Name",
|
||||
@@ -751,6 +758,10 @@
|
||||
"new_event_type_to_book_description": "Erstellen Sie einen neuen Termintyp, mit dem Personen Zeiten buchen können.",
|
||||
"length": "Länge",
|
||||
"minimum_booking_notice": "Mindesvorlaufzeit für eine Buchung",
|
||||
"offset_toggle": "Startzeiten versetzen",
|
||||
"offset_toggle_description": "Um eine bestimmte Anzahl an Minuten versetzte Zeitfenster, die Buchern angezeigt werden",
|
||||
"offset_start": "Versetzt um",
|
||||
"offset_start_description": "z.B. wird dies Zeitfenster für Ihre Bucher um {{ adjustedTime }} statt {{ originalTime }} anzeigen",
|
||||
"slot_interval": "Zeit-Slot-Intervalle",
|
||||
"slot_interval_default": "Verwende Terminlänge (Standard)",
|
||||
"delete_event_type": "Ereignistyp löschen?",
|
||||
@@ -903,6 +914,7 @@
|
||||
"duplicate": "Duplizieren",
|
||||
"offer_seats": "Plätze anbieten",
|
||||
"offer_seats_description": "Bieten Sie Plätze für Buchungen an (dies deaktiviert Gäste & Opt-in-Buchungen)",
|
||||
"seats_available_one": "Verfügbarer Platz",
|
||||
"seats_available_other": "Verfügbare Plätze",
|
||||
"number_of_seats": "Anzahl der Plätze pro Termin",
|
||||
"enter_number_of_seats": "Anzahl der Plätze eingeben",
|
||||
@@ -1037,6 +1049,7 @@
|
||||
"event_cancelled_trigger": "wenn das Ereignis abgesagt wird",
|
||||
"new_event_trigger": "wenn ein neer Termin gebucht wird",
|
||||
"email_host_action": "E-Mail an den Veranstalter senden",
|
||||
"email_attendee_action": "E-Mail an Teilnehmende senden",
|
||||
"sms_attendee_action": "SMS an Teilnehmende senden",
|
||||
"sms_number_action": "SMS an eine bestimmte Nummer senden",
|
||||
"workflows": "Workflows",
|
||||
@@ -1189,6 +1202,7 @@
|
||||
"create_workflow": "Einen Workflow erstellen",
|
||||
"do_this": "Mache dies",
|
||||
"turn_off": "Abschalten",
|
||||
"turn_on": "Einschalten",
|
||||
"settings_updated_successfully": "Einstellungen erfolgreich aktualisiert",
|
||||
"error_updating_settings": "Fehler beim Aktualisieren der Einstellungen",
|
||||
"personal_cal_url": "Meine persönliche {{appName}}-URL",
|
||||
@@ -1245,6 +1259,7 @@
|
||||
"calendars_description": "Konfigurieren Sie, wie Ihre Event-Typen mit Ihren Kalendern interagieren sollen",
|
||||
"appearance_description": "Einstellungen für Ihre Buchungsdarstellung verwalten",
|
||||
"conferencing_description": "Fügen Sie Ihre favorisierten Videokonferenz-Apps für Ihre Meetings hinzu",
|
||||
"add_conferencing_app": "Konferenz-App hinzufügen",
|
||||
"password_description": "Einstellungen für Ihre Konto-Passwörter verwalten",
|
||||
"2fa_description": "Einstellungen für Ihre Konto-Passwörter verwalten",
|
||||
"we_just_need_basic_info": "Wir benötigen nur ein paar grundlegende Informationen, um Ihr Profil einzurichten.",
|
||||
@@ -1619,6 +1634,7 @@
|
||||
"email_user_cta": "Einladung anzeigen",
|
||||
"email_no_user_invite_heading": "Sie wurden eingeladen, dem Team in {{appName}} beizutreten",
|
||||
"email_no_user_invite_subheading": "{{invitedBy}} hat Sie eingeladen, dem Team in {{appName}} beizutreten. {{appName}} ist der Event-Planer, der es Ihnen und Ihrem Team ermöglicht, Meetings ohne ständiges hin und her zu planen.",
|
||||
"email_user_invite_subheading": "{{invitedBy}} hat Sie eingeladen, dem Team „{{teamName}}“ beizutreten. {{appName}} ist der Event-Planer, der es Ihnen und Ihrem Team ermöglicht, Meetings ohne Hin und Her zu planen.",
|
||||
"email_no_user_invite_steps_intro": "Wir begleiten Sie durch ein paar Schritte, danach können Sie stressfreie Planung mit Ihrem Team in kürzester Zeit genießen.",
|
||||
"email_no_user_step_one": "Wählen Sie Ihren Benutzernamen",
|
||||
"email_no_user_step_two": "Verbinden Sie Ihr Kalenderkonto",
|
||||
@@ -1694,6 +1710,15 @@
|
||||
"spot_popular_event_types_description": "Sehen Sie, welche Ihrer Ereignistypen die meisten Klicks und Buchungen erhalten",
|
||||
"no_responses_yet": "Noch keine Antworten",
|
||||
"this_will_be_the_placeholder": "Dies wird der Platzhalter sein",
|
||||
"error_booking_event": "Bei der Buchung dieses Termins ist ein Fehler aufgetreten, bitte aktualisieren Sie die Seite und versuchen Sie es erneut",
|
||||
"timeslot_missing_title": "Kein Zeitfenster ausgewählt",
|
||||
"timeslot_missing_description": "Bitte wählen Sie einen Zeitraum aus, um den Termin zu buchen.",
|
||||
"timeslot_missing_cta": "Zeitfenster auswählen",
|
||||
"switch_monthly": "Zur monatlichen Ansicht wechseln",
|
||||
"switch_weekly": "Zur Wochenansicht wechseln",
|
||||
"switch_multiday": "Zur Tagesansicht wechseln",
|
||||
"num_locations": "{{num}} Standortoptionen",
|
||||
"select_on_next_step": "Im nächsten Schritt auswählen",
|
||||
"this_meeting_has_not_started_yet": "Dieses Meeting hat noch nicht begonnen",
|
||||
"this_app_requires_connected_account": "{{appName}} benötigt ein verbundenes {{dependencyName}}-Konto",
|
||||
"connect_app": "{{dependencyName}} verbinden",
|
||||
@@ -1723,6 +1748,7 @@
|
||||
"locked_apps_description": "Mitglieder können die aktiven Apps sehen, können aber keine App-Einstellungen anpassen",
|
||||
"locked_webhooks_description": "Mitglieder können die aktiven Webhooks sehen, können aber keine Webhook-Einstellungen anpassen",
|
||||
"locked_workflows_description": "Mitglieder können die aktiven Workflows sehen, können aber keine Workflow-Einstellungen anpassen",
|
||||
"locked_by_admin": "Vom Team-Admin gesperrt",
|
||||
"app_not_connected": "Sie haben kein {{appName}}-Konto verbunden.",
|
||||
"connect_now": "Jetzt verbinden",
|
||||
"managed_event_dialog_confirm_button_one": "{{count}} Mitglied ersetzen & benachrichtigen",
|
||||
@@ -1734,7 +1760,7 @@
|
||||
"managed_event_dialog_clarification": "Wenn Sie die URL ersetzen, werden wir sie benachrichtigen. Gehen Sie zurück und entfernen Sie sie, wenn Sie sie nicht überschreiben möchten.",
|
||||
"review_event_type": "Ereignistyp überprüfen",
|
||||
"looking_for_more_analytics": "Suchen Sie nach weiteren Analysedaten?",
|
||||
"looking_for_more_insights": "Suchen Sie nach weiteren Einsichten?",
|
||||
"looking_for_more_insights": "Möchten Sie mehr Insights?",
|
||||
"add_filter": "Filter hinzufügen",
|
||||
"select_user": "Benutzer auswählen",
|
||||
"select_event_type": "Ereignistyp auswählen",
|
||||
@@ -1780,5 +1806,34 @@
|
||||
"seats_and_no_show_fee_error": "Aktuell können keine Plätze aktiviert oder eine Gebühr für das Nichterscheinen berechnet werden",
|
||||
"complete_your_booking": "Schließen Sie Ihre Buchung ab",
|
||||
"complete_your_booking_subject": "Schließen Sie Ihre Buchung ab: {{title}} am {{date}}",
|
||||
"email_invite_team": "{{email}} wurde eingeladen"
|
||||
"confirm_your_details": "Bestätigen Sie Ihre Daten",
|
||||
"currency_string": "{{amount, currency}}",
|
||||
"charge_card_dialog_body": "Sie sind im Begriff, den Teilnehmer {{amount, currency}} in Rechnung zu stellen. Sind Sie sicher, dass Sie fortfahren möchten?",
|
||||
"charge_attendee": "Teilnehmer {{amount, currency}} berechnen",
|
||||
"payment_app_commission": "Zahlung erfordern ({{paymentFeePercentage}} % + {{fee, currency}} Provision pro Transaktion)",
|
||||
"email_invite_team": "{{email}} wurde eingeladen",
|
||||
"email_invite_team_bulk": "{{userCount}} Benutzer wurden eingeladen",
|
||||
"error_collecting_card": "Fehler beim Erfassen der Karte",
|
||||
"image_size_limit_exceed": "Das hochgeladene Bild sollte die maximale Größe von 5 MB nicht überschreiten",
|
||||
"inline_embed": "Inline-Einbettung",
|
||||
"load_inline_content": "Laden Sie Ihren Event-Typ direkt inline mit Ihren anderen Website-Inhalten.",
|
||||
"floating_pop_up_button": "Schwebende Pop-Up-Schaltfläche",
|
||||
"floating_button_trigger_modal": "Erstellt einen schwebenden Button auf Ihrer Website, der einen modalen Dialog mit Ihrem Termintypen erstellt.",
|
||||
"pop_up_element_click": "Pop-Up über Element-Klick",
|
||||
"open_dialog_with_element_click": "Öffnet Ihren Cal-Dialog, wenn jemand ein Element anklickt.",
|
||||
"need_help_embedding": "Brauchen Sie Hilfe? Sehen Sie sich unsere Anleitungen zum Einbetten von Cal auf Wix, Squarespace, oder WordPress an, schauen Sie sich unsere häufig gestellten Fragen an oder erkunden Sie erweiterte Einbettungsoptionen.",
|
||||
"book_my_cal": "Mein Cal buchen",
|
||||
"invite_as": "Einladen als",
|
||||
"form_updated_successfully": "Formular erfolgreich aktualisiert.",
|
||||
"email_not_cal_member_cta": "Treten Sie Ihrem Team bei",
|
||||
"disable_attendees_confirmation_emails": "Standardmäßige Bestätigungs-E-Mails für Teilnehmer deaktivieren",
|
||||
"disable_attendees_confirmation_emails_description": "Mindestens ein Workflow ist für diesen Termintyp aktiv, welcher bei Buchung eine E-Mail an die Teilnehmer sendet.",
|
||||
"disable_host_confirmation_emails": "Standardmäßige Bestätigungs-E-Mails für Veranstalter deaktivieren",
|
||||
"disable_host_confirmation_emails_description": "Mindestens ein Workflow ist für diesen Termintyp aktiv, welcher bei Buchung eine E-Mail an den Veranstalter sendet.",
|
||||
"add_an_override": "Eine Überschreibung hinzufügen",
|
||||
"import_from_google_workspace": "Benutzer aus Google Workspace importieren",
|
||||
"connect_google_workspace": "Google Workspace verbinden",
|
||||
"google_workspace_admin_tooltip": "Sie müssen ein Workspace-Admin sein, um diese Funktion nutzen zu können",
|
||||
"first_event_type_webhook_description": "Erstellen Sie Ihren ersten Webhook für diesen Termintypen",
|
||||
"create_for": "Erstellen für"
|
||||
}
|
||||
|
||||
@@ -1840,5 +1840,7 @@
|
||||
"connect_google_workspace":"Connect Google Workspace",
|
||||
"google_workspace_admin_tooltip":"You must be a Workspace Admin to use this feature",
|
||||
"first_event_type_webhook_description": "Create your first webhook for this event type",
|
||||
"create_for": "Create for"
|
||||
"create_for": "Create for",
|
||||
"additional_url_parameters": "Additional URL parameters",
|
||||
"ADD_NEW_STRINGS_ABOVE_THIS_LINE_TO_PREVENT_MERGE_CONFLICTS": "↑↑↑↑↑↑↑↑↑↑↑↑↑ Add your new strings above here ↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑"
|
||||
}
|
||||
|
||||
@@ -26,6 +26,8 @@
|
||||
"rejection_confirmation": "Refuser la réservation",
|
||||
"manage_this_event": "Gérer cet événement",
|
||||
"invite_team_member": "Inviter un membre d'équipe",
|
||||
"invite_team_individual_segment": "Inviter une personne",
|
||||
"invite_team_bulk_segment": "Importation multiple",
|
||||
"invite_team_notifcation_badge": "Inv.",
|
||||
"your_event_has_been_scheduled": "Votre événement a été planifié",
|
||||
"your_event_has_been_scheduled_recurring": "Votre événement récurrent a été planifié",
|
||||
@@ -193,6 +195,7 @@
|
||||
"page_doesnt_exist": "Cette page n'existe pas.",
|
||||
"check_spelling_mistakes_or_go_back": "Vérifiez s'il y a des erreurs d'orthographe ou retournez à la page précédente.",
|
||||
"404_page_not_found": "404 : Cette page est introuvable.",
|
||||
"booker_event_not_found": "Nous n'avons pas pu trouver l'événement que vous essayez de réserver.",
|
||||
"getting_started": "Commencer",
|
||||
"15min_meeting": "Rendez-vous de 15 min",
|
||||
"30min_meeting": "Rendez-vous de 30 min",
|
||||
@@ -222,6 +225,7 @@
|
||||
"go_back_login": "Retour à la page de connexion",
|
||||
"error_during_login": "Une erreur s'est produite lors de votre connexion. Retournez à l'écran de connexion et réessayez.",
|
||||
"request_password_reset": "Envoyer l'email de réinitialisation",
|
||||
"send_invite": "Envoyer l'invitation",
|
||||
"forgot_password": "Mot de passe oublié ?",
|
||||
"forgot": "Oublié ?",
|
||||
"done": "Terminé",
|
||||
@@ -360,6 +364,7 @@
|
||||
"user_dynamic_booking_disabled": "Certains utilisateurs du groupe ont actuellement désactivé les réservations de groupe dynamiques",
|
||||
"allow_dynamic_booking_tooltip": "Les liens de réservation de groupe peuvent être créés dynamiquement en ajoutant plusieurs noms d'utilisateur séparés par un « + ». Exemple : « {{appName}}/bailey+peer ».",
|
||||
"allow_dynamic_booking": "Autoriser les participants à prendre rendez-vous avec vous via des réservations de groupe dynamiques",
|
||||
"dynamic_booking": "Liens de groupe dynamiques",
|
||||
"email": "E-mail",
|
||||
"email_placeholder": "jdoe@exemple.com",
|
||||
"full_name": "Nom complet",
|
||||
@@ -370,6 +375,8 @@
|
||||
"booking_rescheduled": "Réservation replanifiée",
|
||||
"recording_ready": "Lien de téléchargement d'enregistrement prêt",
|
||||
"booking_created": "Réservation créée",
|
||||
"booking_rejected": "Réservation rejetée",
|
||||
"booking_requested": "Réservation demandée",
|
||||
"meeting_ended": "Rendez-vous terminé",
|
||||
"form_submitted": "Formulaire envoyé",
|
||||
"event_triggers": "Déclencheurs d'événement",
|
||||
@@ -503,7 +510,7 @@
|
||||
"or": "OU",
|
||||
"go_back": "Retour",
|
||||
"email_or_username": "Adresse e-mail ou identifiant",
|
||||
"send_invite_email": "Envoyer un e-mail d'invitation",
|
||||
"send_invite_email": "Envoyer une invitation par e-mail",
|
||||
"role": "Rôle",
|
||||
"edit_role": "Modifier le rôle",
|
||||
"edit_team": "Modifier l'équipe",
|
||||
@@ -755,6 +762,7 @@
|
||||
"new_event_type_to_book_description": "Créez un nouveau type d’événement pour que les personnes puissent effectuer des réservations.",
|
||||
"length": "Durée",
|
||||
"minimum_booking_notice": "Préavis minimum",
|
||||
"offset_start_description": "p. ex. cela affichera les créneaux à vos utilisateurs à {{ adjustedTime }} au lieu de {{ originalTime }}",
|
||||
"slot_interval": "Fréquence de créneaux horaires",
|
||||
"slot_interval_default": "Utiliser la durée de l'événement (par défaut)",
|
||||
"delete_event_type": "Supprimer le type d'événement ?",
|
||||
@@ -789,7 +797,7 @@
|
||||
"connect_your_favourite_apps": "Connectez vos applications favorites.",
|
||||
"automation": "Automatisation",
|
||||
"configure_how_your_event_types_interact": "Configurez la manière dont vos types d'événements doivent interagir avec vos calendriers.",
|
||||
"toggle_calendars_conflict": "Activez/désactivez les calendriers pour lesquels vous souhaitez vérifier les conflits afin d'éviter les réservations en double.",
|
||||
"toggle_calendars_conflict": "Activez/désactivez les calendriers pour lesquels vous souhaitez vérifier les conflits afin d'éviter les doubles réservations.",
|
||||
"select_destination_calendar": "Créer des événements dans :",
|
||||
"connect_additional_calendar": "Connecter un calendrier supplémentaire",
|
||||
"calendar_updated_successfully": "Calendrier mis à jour avec succès",
|
||||
@@ -828,14 +836,14 @@
|
||||
"installed_apps": "Apps installées",
|
||||
"free_to_use_apps": "Gratuit",
|
||||
"no_category_apps": "Aucune application {{category}}",
|
||||
"no_category_apps_description_calendar": "Ajoutez une application de calendrier pour vérifier les conflits et éviter les réservations en double.",
|
||||
"no_category_apps_description_calendar": "Ajoutez une application de calendrier pour vérifier les conflits et éviter les doubles réservations.",
|
||||
"no_category_apps_description_conferencing": "Essayez d'ajouter une application de visioconférence pour intégrer les appels vidéo avec vos clients.",
|
||||
"no_category_apps_description_payment": "Ajoutez une application de paiement pour faciliter les transactions entre vous et vos clients.",
|
||||
"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_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.",
|
||||
"installed_app_calendar_description": "Définissez les calendriers pour vérifier les conflits afin d'éviter les réservations en double.",
|
||||
"installed_app_calendar_description": "Définissez les calendriers pour vérifier les conflits afin d'éviter les doubles réservations.",
|
||||
"installed_app_conferencing_description": "Ajoutez vos applications de visioconférence préférées pour vos rendez-vous.",
|
||||
"installed_app_payment_description": "Configurez les services de traitement de paiement à utiliser lors de la facturation de vos clients.",
|
||||
"installed_app_analytics_description": "Configurez les applications d'analyse à utiliser pour vos pages de réservation.",
|
||||
@@ -907,6 +915,7 @@
|
||||
"duplicate": "Dupliquer",
|
||||
"offer_seats": "Proposer des places",
|
||||
"offer_seats_description": "Proposez des places de réservation. Cela désactive automatiquement les réservations d'invités et d'opt-in.",
|
||||
"seats_available_one": "Place disponible",
|
||||
"seats_available_other": "places disponibles",
|
||||
"number_of_seats": "Nombre de places par réservation",
|
||||
"enter_number_of_seats": "Saisir le nombre de sièges",
|
||||
@@ -1206,7 +1215,7 @@
|
||||
"recordings_title": "Enregistrements",
|
||||
"recording": "Enregistrement",
|
||||
"happy_scheduling": "Bonne planification",
|
||||
"select_calendars": "Sélectionnez les calendriers dont vous souhaitez vérifier les conflits afin d'éviter les réservations en double.",
|
||||
"select_calendars": "Sélectionnez les calendriers dont vous souhaitez vérifier les conflits afin d'éviter les doubles réservations.",
|
||||
"check_for_conflicts": "Vérifier les conflits",
|
||||
"view_recordings": "Voir les enregistrements",
|
||||
"adding_events_to": "Ajout d'événements",
|
||||
@@ -1804,15 +1813,26 @@
|
||||
"charge_attendee": "Facturer {{amount, currency}} au participant",
|
||||
"payment_app_commission": "Exiger un paiement ({{paymentFeePercentage}} % + {{fee, currency}} de commission par transaction)",
|
||||
"email_invite_team": "{{email}} a été invité",
|
||||
"email_invite_team_bulk": "{{userCount}} utilisateurs ont été invités",
|
||||
"error_collecting_card": "Erreur lors de la collecte de la carte",
|
||||
"image_size_limit_exceed": "L'image téléchargée ne doit pas dépasser 5 Mo",
|
||||
"unauthorized_workflow_error_message": "{{errorCode}} : Vous n'êtes pas autorisé à activer ou désactiver ce workflow",
|
||||
"inline_embed": "Intégration en ligne",
|
||||
"load_inline_content": "Charge votre type d'événement directement en ligne avec le contenu de votre site web.",
|
||||
"floating_pop_up_button": "Bouton pop-up flottant",
|
||||
"need_help_embedding": "Besoin d'aide ? Consultez nos guides pour intégrer Cal sur Wix, Squarespace ou WordPress, consultez nos questions fréquentes ou explorez les options d'intégration avancées.",
|
||||
"invite_as": "Inviter en tant que",
|
||||
"form_updated_successfully": "Formulaire mis à jour avec succès.",
|
||||
"email_not_cal_member_cta": "Rejoignez votre équipe",
|
||||
"disable_attendees_confirmation_emails": "Désactiver les e-mails de confirmation par défaut pour les participants",
|
||||
"disable_attendees_confirmation_emails_description": "Au moins un workflow est actif sur ce type d'événement qui envoie un e-mail aux participants lorsque l'événement est réservé.",
|
||||
"disable_host_confirmation_emails": "Désactiver les e-mails de confirmation par défaut pour l'hôte",
|
||||
"disable_host_confirmation_emails_description": "Au moins un workflow est actif sur ce type d'événement qui envoie un e-mail à l'hôte lorsque l'événement est réservé."
|
||||
"disable_host_confirmation_emails_description": "Au moins un workflow est actif sur ce type d'événement qui envoie un e-mail à l'hôte lorsque l'événement est réservé.",
|
||||
"add_an_override": "Ajouter une superposition",
|
||||
"import_from_google_workspace": "Importer des utilisateurs depuis Google Workspace",
|
||||
"connect_google_workspace": "Connecter Google Workspace",
|
||||
"first_event_type_webhook_description": "Créez votre premier webhook pour ce type d'événement",
|
||||
"create_for": "Créer pour",
|
||||
"additional_url_parameters": "Paramètres d'URL supplémentaires",
|
||||
"ADD_NEW_STRINGS_ABOVE_THIS_LINE_TO_PREVENT_MERGE_CONFLICTS": "↑↑↑↑↑↑↑↑↑↑↑↑↑ Ajoutez vos nouvelles chaînes ci-dessus ↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑"
|
||||
}
|
||||
|
||||
@@ -1634,7 +1634,7 @@
|
||||
"email_user_cta": "Visualizza invito",
|
||||
"email_no_user_invite_heading": "Sei stato invitato a unirti a un team su {{appName}}",
|
||||
"email_no_user_invite_subheading": "{{invitedBy}} ti ha invitato a unirti al suo team su {{appName}}. {{appName}} è uno strumento di pianificazione di eventi che permette al tuo team di pianificare le riunioni senza scambiare decine di e-mail.",
|
||||
"email_user_invite_subheading": "{{invitedBy}} ti ha invitato a unirti al suo team `{{teamName}}` su {{appName}}. {{appName}} è uno strumento di pianificazione di eventi che permette al tuo team di pianificare le riunioni senza scambiare decine di e-mail.",
|
||||
"email_user_invite_subheading": "{{invitedBy}} ti ha invitato a unirti al suo team `{{teamName}}` su {{appName}}. {{appName}} è uno strumento di pianificazione di eventi che permette a te e al tuo team di pianificare le riunioni senza scambiare decine di e-mail.",
|
||||
"email_no_user_invite_steps_intro": "Ti assisteremo nei passaggi iniziali in modo che tu possa pianificare eventi per il tuo team senza stress e in pochissimo tempo.",
|
||||
"email_no_user_step_one": "Scegli il tuo nome utente",
|
||||
"email_no_user_step_two": "Collega il tuo account di calendario",
|
||||
@@ -1760,7 +1760,7 @@
|
||||
"managed_event_dialog_clarification": "Se decidi di sostituirlo, i membri ne saranno avvisati. Torna indietro e rimuovili se non desideri sovrascriverlo.",
|
||||
"review_event_type": "Rivedi tipo di evento",
|
||||
"looking_for_more_analytics": "Hai bisogno di più dati analitici?",
|
||||
"looking_for_more_insights": "Hai bisogno di maggiori approfondimenti?",
|
||||
"looking_for_more_insights": "Ti servono più Insights?",
|
||||
"add_filter": "Aggiungi filtro",
|
||||
"select_user": "Seleziona utente",
|
||||
"select_event_type": "Seleziona tipo di evento",
|
||||
@@ -1821,7 +1821,7 @@
|
||||
"floating_button_trigger_modal": "Inserisce nel sito un pulsante mobile che attiva una finestra modale con il tuo tipo di evento.",
|
||||
"pop_up_element_click": "Finestra popup tramite clic su un elemento",
|
||||
"open_dialog_with_element_click": "Apre la finestra di dialogo di Cal quando un utente clicca su un elemento.",
|
||||
"need_help_embedding": "Hai bisogno di aiuto? Consulta le nostre guide per integrare Cal su Wix, Squarespace o WordPress, legge le risposte alle domande più frequenti o esplora le opzioni di icorporamento avanzate.",
|
||||
"need_help_embedding": "Hai bisogno di aiuto? Consulta le nostre guide per integrare Cal su Wix, Squarespace o WordPress, leggi le risposte alle domande più frequenti o esplora le opzioni di incorporamento avanzate.",
|
||||
"book_my_cal": "Prenota il mio Cal",
|
||||
"invite_as": "Invita come",
|
||||
"form_updated_successfully": "Modulo aggiornato correttamente.",
|
||||
|
||||
@@ -1325,7 +1325,7 @@
|
||||
"exchange_version_2013_SP1": "2013 SP1",
|
||||
"exchange_version_2015": "2015",
|
||||
"exchange_version_2016": "2016",
|
||||
"routing_forms_description": "作成したすべてのフォームとルートをここで確認できます",
|
||||
"routing_forms_description": "出席者を正しい目的地に案内するためのフォームを作成します。",
|
||||
"routing_forms_send_email_owner": "所有者にメールを送信",
|
||||
"routing_forms_send_email_owner_description": "フォームの送信時に所有者にメールを送信します",
|
||||
"add_new_form": "新しいフォームを追加",
|
||||
|
||||
@@ -1258,7 +1258,6 @@
|
||||
"exchange_authentication_standard": "Enkel autentisering",
|
||||
"exchange_authentication_ntlm": "NTLM autentisering",
|
||||
"exchange_compression": "GZip komprimering",
|
||||
"routing_forms_description": "Du kan se alle skjemaer og viderekoblinger du har laget her.",
|
||||
"routing_forms_send_email_owner": "Send E-post til Eier",
|
||||
"routing_forms_send_email_owner_description": "Sender en e-post til eieren når skjemaet er sendt inn",
|
||||
"add_new_form": "Legg til nytt skjema",
|
||||
|
||||
@@ -26,6 +26,7 @@
|
||||
"rejection_confirmation": "Odrzuć prośbę o rezerwację",
|
||||
"manage_this_event": "Zarządzaj wydarzeniem",
|
||||
"invite_team_member": "Zaproś członka zespołu",
|
||||
"invite_team_individual_segment": "Zaproś pojedynczą osobę",
|
||||
"invite_team_bulk_segment": "Import zbiorczy",
|
||||
"invite_team_notifcation_badge": "Zap.",
|
||||
"your_event_has_been_scheduled": "Zaplanowano wydarzenie",
|
||||
@@ -757,9 +758,10 @@
|
||||
"new_event_type_to_book_description": "Utwórz nowy typ wydarzenia, z którym ludzie mogą rezerwować czasy.",
|
||||
"length": "Długość",
|
||||
"minimum_booking_notice": "Minimalne powiadomienie",
|
||||
"offset_toggle": "Opóźnij czasy rozpoczęcia",
|
||||
"offset_toggle": "Przesuń czasy rozpoczęcia",
|
||||
"offset_toggle_description": "Przesuń przedziały czasowe wyświetlane rezerwującym o określoną liczbę minut",
|
||||
"offset_start": "Przesuń o",
|
||||
"offset_start_description": "np. przedziały czasowe do rezerwacji będą dostępne o godz. {{ adjustedTime }} zamiast o {{ originalTime }}.",
|
||||
"slot_interval": "Przedziały czasowe",
|
||||
"slot_interval_default": "Użyj długości zdarzenia (domyślnie)",
|
||||
"delete_event_type": "Usunąć typ wydarzenia?",
|
||||
@@ -1632,6 +1634,7 @@
|
||||
"email_user_cta": "Wyświetl zaproszenie",
|
||||
"email_no_user_invite_heading": "Zaproszono Cię do dołączenia do zespołu w aplikacji {{appName}}",
|
||||
"email_no_user_invite_subheading": "Użytkownik {{invitedBy}} zaprasza Cię do dołączenia do jego zespołu w aplikacji {{appName}}. Aplikacja {{appName}} to terminarz do planowania wydarzeń, który umożliwi Tobie i Twojemu zespołowi planowanie spotkań bez czasochłonnej wymiany wiadomości e-mail.",
|
||||
"email_user_invite_subheading": "Użytkownik {{invitedBy}} zaprasza Cię do dołączenia do jego zespołu „{{teamName}}” w aplikacji {{appName}}. Aplikacja {{appName}} to terminarz do planowania wydarzeń, który umożliwi Tobie i Twojemu zespołowi planowanie spotkań bez czasochłonnej wymiany wiadomości e-mail.",
|
||||
"email_no_user_invite_steps_intro": "Przeprowadzimy Cię przez kilka krótkich etapów i wkrótce zaczniesz z przyjemnością korzystać z bezstresowego narzędzia planowania.",
|
||||
"email_no_user_step_one": "Wybierz nazwę użytkownika",
|
||||
"email_no_user_step_two": "Połącz swoje konto kalendarza",
|
||||
@@ -1707,6 +1710,7 @@
|
||||
"spot_popular_event_types_description": "Zobacz, które typy wydarzeń są najczęściej klikane i rezerwowane",
|
||||
"no_responses_yet": "Nie ma jeszcze odpowiedzi",
|
||||
"this_will_be_the_placeholder": "To będzie symbol zastępczy",
|
||||
"error_booking_event": "Podczas rezerwacji wydarzenia wystąpił błąd, odśwież stronę i spróbuj ponownie.",
|
||||
"timeslot_missing_title": "Nie wybrano przedziału czasowego",
|
||||
"timeslot_missing_description": "Wybierz przedział czasowy, aby zarezerwować wydarzenie.",
|
||||
"timeslot_missing_cta": "Wybierz przedział czasowy",
|
||||
@@ -1811,11 +1815,25 @@
|
||||
"email_invite_team_bulk": "Zaproszono {{userCount}} użytkowników",
|
||||
"error_collecting_card": "Błąd podczas pobierania karty",
|
||||
"image_size_limit_exceed": "Przesłany obraz nie powinien być większy niż 5 MB.",
|
||||
"inline_embed": "Osadź na stronie",
|
||||
"load_inline_content": "Wczytuje typ wydarzenia bezpośrednio w treści Twojej strony internetowej.",
|
||||
"floating_pop_up_button": "Pływający przycisk wyskakujący",
|
||||
"floating_button_trigger_modal": "Wstawia na stronie przycisk pływający, który umożliwia wywołanie okna modalnego z typem wydarzenia.",
|
||||
"pop_up_element_click": "Wyskakujące okienko po kliknięciu elementu",
|
||||
"open_dialog_with_element_click": "Otwiera okno dialogowe Cal, gdy użytkownik kliknie odpowiedni element.",
|
||||
"need_help_embedding": "Potrzebujesz pomocy? Zapoznaj się z naszymi instrukcjami osadzania narzędzi Cal na stronach stworzonych w usługach Wix, Squarespace lub WordPress, sprawdź często zadawane pytania lub poznaj zaawansowane opcje osadzania.",
|
||||
"book_my_cal": "Zarezerwuj w moim kalendarzu",
|
||||
"invite_as": "Zaproś jako",
|
||||
"form_updated_successfully": "Formularz zaktualizowany pomyślnie.",
|
||||
"email_not_cal_member_cta": "Dołącz do zespołu",
|
||||
"disable_attendees_confirmation_emails": "Wyłącz domyślne wiadomości e-mail z potwierdzeniem dla uczestników",
|
||||
"disable_attendees_confirmation_emails_description": "W tym typie wydarzenia aktywny jest co najmniej jeden przepływ pracy, który wysyła wiadomość e-mail do uczestników, gdy wydarzenie zostanie zarezerwowane.",
|
||||
"disable_host_confirmation_emails": "Wyłącz domyślne wiadomości e-mail z potwierdzeniem dla gospodarza",
|
||||
"disable_host_confirmation_emails_description": "W tym typie wydarzenia aktywny jest co najmniej jeden przepływ pracy, który wysyła wiadomość e-mail do gospodarza, gdy wydarzenie zostanie zarezerwowane.",
|
||||
"add_an_override": "Dodaj zastąpienie",
|
||||
"import_from_google_workspace": "Importuj użytkowników z usługi Google Workspace",
|
||||
"connect_google_workspace": "Połącz usługę Google Workspace",
|
||||
"google_workspace_admin_tooltip": "Aby skorzystać z tej funkcji, musisz być administratorem usługi Workspace.",
|
||||
"first_event_type_webhook_description": "Utwórz swój pierwszy element webhook dla tego typu wydarzenia",
|
||||
"create_for": "Utwórz dla"
|
||||
}
|
||||
|
||||
@@ -1340,7 +1340,7 @@
|
||||
"exchange_version_2013_SP1": "2013 SP1",
|
||||
"exchange_version_2015": "2015",
|
||||
"exchange_version_2016": "2016",
|
||||
"routing_forms_description": "Você pode ver todos os formulários e rotas que criou aqui",
|
||||
"routing_forms_description": "Criar formulários para direcionar os participantes para os destinos corretos",
|
||||
"routing_forms_send_email_owner": "Enviar e-mail ao proprietário",
|
||||
"routing_forms_send_email_owner_description": "Envia um e-mail ao proprietário após o envio do formulário",
|
||||
"add_new_form": "Adicionar novo formulário",
|
||||
|
||||
@@ -1340,7 +1340,6 @@
|
||||
"exchange_version_2013_SP1": "2013 SP1",
|
||||
"exchange_version_2015": "2015",
|
||||
"exchange_version_2016": "2016",
|
||||
"routing_forms_description": "Aqui pode ver todos os formulários e rotas que criou.",
|
||||
"routing_forms_send_email_owner": "Enviar e-mail ao proprietário",
|
||||
"routing_forms_send_email_owner_description": "Envia um e-mail ao proprietário quando o formulário for submetido",
|
||||
"add_new_form": "Adicionar novo formulário",
|
||||
|
||||
@@ -26,6 +26,8 @@
|
||||
"rejection_confirmation": "Отказаться от бронирования",
|
||||
"manage_this_event": "Управление этой встречей",
|
||||
"invite_team_member": "Пригласить участника команды",
|
||||
"invite_team_individual_segment": "Пригласить пользователя",
|
||||
"invite_team_bulk_segment": "Массовый импорт",
|
||||
"invite_team_notifcation_badge": "Прг.",
|
||||
"your_event_has_been_scheduled": "Запланирована новая встреча",
|
||||
"your_event_has_been_scheduled_recurring": "Повторяющееся событие запланировано",
|
||||
@@ -222,6 +224,7 @@
|
||||
"go_back_login": "Вернуться на страницу входа",
|
||||
"error_during_login": "Произошла ошибка при входе в систему. Вернитесь на экран входа и повторите попытку.",
|
||||
"request_password_reset": "Запросить сброс пароля",
|
||||
"send_invite": "Отправить приглашение",
|
||||
"forgot_password": "Восстановить пароль",
|
||||
"forgot": "Забыли пароль?",
|
||||
"done": "Готово",
|
||||
@@ -237,6 +240,8 @@
|
||||
"set_availability": "Задайте время в которое вы доступны",
|
||||
"continue_without_calendar": "Продолжить без подключения календаря",
|
||||
"connect_your_calendar": "Подключите календарь",
|
||||
"connect_your_video_app": "Подключите приложения для просмотра видео",
|
||||
"connect_your_video_app_instructions": "Подключите приложения для просмотра видео и используйте их для разных типов событий.",
|
||||
"connect_your_calendar_instructions": "Подключите календарь чтобы автоматически отслеживать вашу занятость при создании встреч.",
|
||||
"set_up_later": "Настроить позже",
|
||||
"current_time": "Текущее время",
|
||||
@@ -366,6 +371,7 @@
|
||||
"create_webhook": "Создать веб-хук",
|
||||
"booking_cancelled": "Бронирование отменено",
|
||||
"booking_rescheduled": "Бронирование изменено",
|
||||
"recording_ready": "Готова ссылка для загрузки записи",
|
||||
"booking_created": "Бронирование создано",
|
||||
"meeting_ended": "Встреча завершилась",
|
||||
"form_submitted": "Форма отправлена",
|
||||
@@ -464,6 +470,7 @@
|
||||
"friday": "Пятница",
|
||||
"saturday": "Суббота",
|
||||
"sunday": "Воскресенье",
|
||||
"all_booked_today": "Всё забронировано.",
|
||||
"slots_load_fail": "Не удалось загрузить доступные временные интервалы.",
|
||||
"additional_guests": "Добавить гостей",
|
||||
"your_name": "Ваше имя",
|
||||
@@ -751,6 +758,10 @@
|
||||
"new_event_type_to_book_description": "Создайте новый тип мероприятия, с помощью которого люди смогут забронировать время.",
|
||||
"length": "Продолжительность",
|
||||
"minimum_booking_notice": "Минимальный срок уведомления о встрече",
|
||||
"offset_toggle": "Скорректировать время начала",
|
||||
"offset_toggle_description": "Откорректировать на указанное количество минут интервалы времени, которые видят пользователи, оформляющие бронирование",
|
||||
"offset_start": "Скорректировать на",
|
||||
"offset_start_description": "Например, в этом случае пользователи, оформляющие бронирование, будут видеть интервалы времени {{ adjustedTime }}, а не {{ originalTime }}",
|
||||
"slot_interval": "Интервалы времени",
|
||||
"slot_interval_default": "Использовать длину события (по умолчанию)",
|
||||
"delete_event_type": "Удалить тип события?",
|
||||
@@ -903,6 +914,7 @@
|
||||
"duplicate": "Создать копию",
|
||||
"offer_seats": "Предложить места",
|
||||
"offer_seats_description": "Предлагать места при бронировании (данная настройка автоматически отключает гостевой режим и бронирование с подтверждением).",
|
||||
"seats_available_one": "Доступное место",
|
||||
"seats_available_other": "Доступные места",
|
||||
"number_of_seats": "Количество мест на одну бронь",
|
||||
"enter_number_of_seats": "Укажите количество мест",
|
||||
@@ -1037,6 +1049,7 @@
|
||||
"event_cancelled_trigger": "когда событие отменено",
|
||||
"new_event_trigger": "когда будет забронировано новое событие",
|
||||
"email_host_action": "отправить письмо организатору",
|
||||
"email_attendee_action": "отправить письмо участникам",
|
||||
"sms_attendee_action": "отправить SMS-сообщение участнику",
|
||||
"sms_number_action": "отправка SMS-сообщение на указанный номер",
|
||||
"workflows": "Рабочие процессы",
|
||||
@@ -1189,6 +1202,7 @@
|
||||
"create_workflow": "Создать рабочий процесс",
|
||||
"do_this": "Сделать",
|
||||
"turn_off": "Выключить",
|
||||
"turn_on": "Включить",
|
||||
"settings_updated_successfully": "Настройки успешно обновлены",
|
||||
"error_updating_settings": "Ошибка при обновлении настроек",
|
||||
"personal_cal_url": "URL-адрес моего персонального {{appName}}",
|
||||
@@ -1245,6 +1259,7 @@
|
||||
"calendars_description": "Настройка взаимодействия типов событий с календарями",
|
||||
"appearance_description": "Настройка внешнего вида страниц бронирования",
|
||||
"conferencing_description": "Настройка приложений для видеоконференций, используемых для проведения онлайн-встреч",
|
||||
"add_conferencing_app": "Добавить приложение для видеоконференций",
|
||||
"password_description": "Настройка паролей для аккаунта",
|
||||
"2fa_description": "Настройка паролей для аккаунта",
|
||||
"we_just_need_basic_info": "Для настройки вашего профиля необходимо указать основную информацию.",
|
||||
@@ -1619,6 +1634,7 @@
|
||||
"email_user_cta": "Посмотреть приглашение",
|
||||
"email_no_user_invite_heading": "Вас пригласили в команду {{appName}}",
|
||||
"email_no_user_invite_subheading": "{{invitedBy}} пригласил(-а) вас в команду в {{appName}}. {{appName}} — это гибкий планировщик событий, с помощью которого пользователи и целые команды могут планировать встречи без утомительной переписки по электронной почте.",
|
||||
"email_user_invite_subheading": "{{invitedBy}} пригласил(а) вас в команду в `{{teamName}}` в приложении {{appName}}. {{appName}} — это гибкий планировщик событий, с помощью которого пользователи и целые команды могут планировать встречи без утомительной переписки по электронной почте.",
|
||||
"email_no_user_invite_steps_intro": "Всего несколько шагов — и вы сможете оперативно и без стресса планировать встречи с командой.",
|
||||
"email_no_user_step_one": "Выберите имя пользователя",
|
||||
"email_no_user_step_two": "Подключите аккаунт календаря",
|
||||
@@ -1694,6 +1710,15 @@
|
||||
"spot_popular_event_types_description": "Узнайте, какие типы событий собирают больше всего кликов и бронирований",
|
||||
"no_responses_yet": "Ответов пока нет",
|
||||
"this_will_be_the_placeholder": "Это текст-заполнитель",
|
||||
"error_booking_event": "При бронировании события произошла ошибка. Обновите страницу и повторите попытку",
|
||||
"timeslot_missing_title": "Не выбран интервал времени",
|
||||
"timeslot_missing_description": "Чтобы забронировать событие, выберите интервал времени.",
|
||||
"timeslot_missing_cta": "Выбрать интервал времени",
|
||||
"switch_monthly": "Перейти в режим «месяц»",
|
||||
"switch_weekly": "Перейти в режим «неделя»",
|
||||
"switch_multiday": "Перейти в режим «день»",
|
||||
"num_locations": "Варианты местоположений: {{num}}",
|
||||
"select_on_next_step": "Выбрать на следующем этапе",
|
||||
"this_meeting_has_not_started_yet": "Эта встреча еще не началась",
|
||||
"this_app_requires_connected_account": "Для {{appName}} требуется подключенный аккаунт {{dependencyName}}",
|
||||
"connect_app": "Подключить {{dependencyName}}",
|
||||
@@ -1723,6 +1748,7 @@
|
||||
"locked_apps_description": "Участники смогут видеть активные приложения, но не смогут редактировать их настройки",
|
||||
"locked_webhooks_description": "Участники смогут видеть активные вебхуки, но не смогут редактировать их настройки",
|
||||
"locked_workflows_description": "Участники смогут видеть активные рабочие процессы, но не смогут редактировать их настройки",
|
||||
"locked_by_admin": "Заблокировано администратором команды",
|
||||
"app_not_connected": "Вы не подключили аккаунт {{appName}}.",
|
||||
"connect_now": "Подключить",
|
||||
"managed_event_dialog_confirm_button_one": "Заменить и уведомить {{count}} участника",
|
||||
@@ -1780,5 +1806,34 @@
|
||||
"seats_and_no_show_fee_error": "Списание платы за неявку при использовании выбора места в настоящее время не поддерживается",
|
||||
"complete_your_booking": "Завершить бронирование",
|
||||
"complete_your_booking_subject": "Завершите бронирование: {{title}}, {{date}}",
|
||||
"email_invite_team": "Участник {{email}} приглашен(а)"
|
||||
"confirm_your_details": "Подтвердите свои данные",
|
||||
"currency_string": "{{amount, currency}}",
|
||||
"charge_card_dialog_body": "Вы собираетесь получить с участника {{amount, currency}}. Продолжить?",
|
||||
"charge_attendee": "Получить с участника {{amount, currency}}",
|
||||
"payment_app_commission": "Необходима оплата: ({{paymentFeePercentage}}% + комиссия за операцию {{fee, currency}})",
|
||||
"email_invite_team": "Участник {{email}} приглашен(а)",
|
||||
"email_invite_team_bulk": "Приглашено пользователей: {{userCount}}",
|
||||
"error_collecting_card": "Карточка ошибок",
|
||||
"image_size_limit_exceed": "Размер загружаемого изображения не должен превышать 5 Мб",
|
||||
"inline_embed": "Встроить",
|
||||
"load_inline_content": "Загрузите тип события на сайт вместе с другим контентом.",
|
||||
"floating_pop_up_button": "Плавающая кнопка",
|
||||
"floating_button_trigger_modal": "Размещает на сайте плавающую кнопку, которая открывает модальное окно с типом события.",
|
||||
"pop_up_element_click": "Окно, всплывающее при нажатии на элемент",
|
||||
"open_dialog_with_element_click": "Открыть диалог Cal при нажатии на элемент.",
|
||||
"need_help_embedding": "Нужна помощь? Воспользуйтесь нашими руководствами, чтобы узнать, как встроить Cal в Wix, Squarespace или WordPress, изучите часто встречающиеся вопросы или расширенные возможности интеграции.",
|
||||
"book_my_cal": "Забронировать в Cal",
|
||||
"invite_as": "Пригласить как",
|
||||
"form_updated_successfully": "Форма успешно обновлена.",
|
||||
"email_not_cal_member_cta": "Присоединиться к команде",
|
||||
"disable_attendees_confirmation_emails": "Отключить для участников подтверждение по электронной почте по умолчанию",
|
||||
"disable_attendees_confirmation_emails_description": "В этом типе события активен по крайней мере один рабочий процесс, который при бронировании события отправляет участникам сообщение по электронной почте.",
|
||||
"disable_host_confirmation_emails": "Отключить для организатора подтверждение по электронной почте по умолчанию",
|
||||
"disable_host_confirmation_emails_description": "В этом типе события активен по крайней мере один рабочий процесс, который при бронировании события отправляет организатору сообщение по электронной почте.",
|
||||
"add_an_override": "Добавить переопределение",
|
||||
"import_from_google_workspace": "Импортировать пользователей из Google Workspace",
|
||||
"connect_google_workspace": "Подключить Google Workspace",
|
||||
"google_workspace_admin_tooltip": "Эту функцию может использовать только администратор Workspace",
|
||||
"first_event_type_webhook_description": "Создайте первый вебхук для этого типа событий",
|
||||
"create_for": "Создать для"
|
||||
}
|
||||
|
||||
@@ -195,6 +195,7 @@
|
||||
"page_doesnt_exist": "Ova stranica ne postoji.",
|
||||
"check_spelling_mistakes_or_go_back": "Proverite greške u pisanju ili idite nazad na prethodnu stranicu.",
|
||||
"404_page_not_found": "404: Ovu stranicu ne možemo da nadjemo.",
|
||||
"booker_event_not_found": "Nismo mogli da pronađemo događaj koji pokušavate da zakažete.",
|
||||
"getting_started": "Započnite",
|
||||
"15min_meeting": "Sastanak od 15 Min",
|
||||
"30min_meeting": "Sastanak od 30 Min",
|
||||
@@ -363,6 +364,7 @@
|
||||
"user_dynamic_booking_disabled": "Neki od korisnika u ovoj grupi trenutno su onemogućili dinamičke grupne rezervacije",
|
||||
"allow_dynamic_booking_tooltip": "Moguće je dinamičko kreiranje linkova za grupne rezervacije navođenjem više korisničkih imena sa znakom „+”. Na primer: „{{appName}}/bailey+peer”",
|
||||
"allow_dynamic_booking": "Dozvolite učesnicima da rezervišu termin sa vama putem dinamičkih grupnih rezervacija",
|
||||
"dynamic_booking": "Dinamični linkovi grupa",
|
||||
"email": "E-pošta",
|
||||
"email_placeholder": "ppetrovic@example.com",
|
||||
"full_name": "Ime i prezime",
|
||||
@@ -373,6 +375,8 @@
|
||||
"booking_rescheduled": "Rezervacija Odložena",
|
||||
"recording_ready": "Link za preuzimanje snimka je spreman",
|
||||
"booking_created": "Rezervacija Napravljena",
|
||||
"booking_rejected": "Rezevacija je odbijena",
|
||||
"booking_requested": "Rezervacija je zahtevana",
|
||||
"meeting_ended": "Sastanak se završio",
|
||||
"form_submitted": "Formular poslat",
|
||||
"event_triggers": "Okidači Dogadjaja",
|
||||
@@ -1815,6 +1819,7 @@
|
||||
"email_invite_team_bulk": "Pozvano je {{userCount}} korisnika",
|
||||
"error_collecting_card": "Greška u sakupljanju kartice",
|
||||
"image_size_limit_exceed": "Otpremljena slika ne sme da prevazilazi ograničenje od 5mb",
|
||||
"unauthorized_workflow_error_message": "{{errorCode}}: Niste ovlašćeni da omogućite ili onemogućite ovaj radni tok",
|
||||
"inline_embed": "Ugradi neposredno",
|
||||
"load_inline_content": "Direktno učitava vaš tip događaja neposredno sa vašim ostalim sadržajem na veb sajtu.",
|
||||
"floating_pop_up_button": "Plutajuće iskačuće dugme",
|
||||
|
||||
@@ -760,6 +760,7 @@
|
||||
"minimum_booking_notice": "Minimum Bildirim",
|
||||
"offset_toggle": "Ofset başlangıç saatleri",
|
||||
"offset_toggle_description": "Belirli bir dakika sayısına göre rezervasyon yapanlara gösterilen ofset zaman aralıkları",
|
||||
"offset_start": "Ofset",
|
||||
"offset_start_description": "örneğin; bu işlem rezervasyon yapan kişinin zaman aralıklarını {{ originalTime }} yerine {{ adjustedTime }} olarak görmesine neden olur",
|
||||
"slot_interval": "Zaman dilimi aralıkları",
|
||||
"slot_interval_default": "Etkinlik uzunluğunu kullan (varsayılan)",
|
||||
@@ -1748,7 +1749,7 @@
|
||||
"locked_webhooks_description": "Üyeler aktif web kancalarını görebilir, ancak hiçbir web kancası ayarını düzenleyemez",
|
||||
"locked_workflows_description": "Üyeler aktif iş akışlarını görebilir, ancak hiçbir iş akışı ayarını düzenleyemez",
|
||||
"locked_by_admin": "Ekip yöneticisi tarafından kilitlendi",
|
||||
"app_not_connected": "Bir {{appName}} hesabı bağlamadınız.",
|
||||
"app_not_connected": "{{appName}} hesabı bağlamadınız.",
|
||||
"connect_now": "Hemen bağlan",
|
||||
"managed_event_dialog_confirm_button_one": "{{count}} üyeyi değiştirin ve bilgilendirin",
|
||||
"managed_event_dialog_confirm_button_other": "{{count}} üyeyi değiştirin ve bilgilendirin",
|
||||
@@ -1756,10 +1757,10 @@
|
||||
"managed_event_dialog_title_other": "URL /{{slug}}, {{count}} üye için zaten mevcut. Bunu değiştirmek istiyor musunuz?",
|
||||
"managed_event_dialog_information_one": "<strong>{{names}}</strong> zaten <strong>/{{slug}}</strong> URL'sini kullanıyor.",
|
||||
"managed_event_dialog_information_other": "<strong>{{names}}</strong> zaten <strong>/{{slug}}</strong> URL'sini kullanıyor.",
|
||||
"managed_event_dialog_clarification": "Değiştirmeyi seçmeniz durumunda kullanıcıları bilgilendireceğiz. Üzerine yazmak istemiyorsanız geri dönerek onları kaldırın.",
|
||||
"managed_event_dialog_clarification": "Değiştirmeyi seçmeniz durumunda onları bilgilendiririz. Üzerine yazmak istemiyorsanız geri dönerek onları kaldırın.",
|
||||
"review_event_type": "Etkinlik Türünü inceleyin",
|
||||
"looking_for_more_analytics": "Daha fazla analiz mi arıyorsunuz?",
|
||||
"looking_for_more_insights": "Daha fazla Öngörü mü arıyorsunuz?",
|
||||
"looking_for_more_insights": "Daha fazla Insights mı arıyorsunuz?",
|
||||
"add_filter": "Filtre ekle",
|
||||
"select_user": "Kullanıcı seç",
|
||||
"select_event_type": "Etkinlik Türü seç",
|
||||
|
||||
@@ -1760,7 +1760,7 @@
|
||||
"managed_event_dialog_clarification": "Якщо ви захочете замінити її, ми повідомимо учасників. Поверніться і вилучіть їх, замість того щоб перезаписувати.",
|
||||
"review_event_type": "Переглянути тип заходу",
|
||||
"looking_for_more_analytics": "Потрібно більше аналітичних даних?",
|
||||
"looking_for_more_insights": "Потрібно більше аналітики?",
|
||||
"looking_for_more_insights": "Потрібно більше висновків Insights?",
|
||||
"add_filter": "Додати фільтр",
|
||||
"select_user": "Вибрати користувача",
|
||||
"select_event_type": "Вибрати тип заходу",
|
||||
|
||||
@@ -1340,7 +1340,7 @@
|
||||
"exchange_version_2013_SP1": "2013 SP1",
|
||||
"exchange_version_2015": "2015",
|
||||
"exchange_version_2016": "2016",
|
||||
"routing_forms_description": "创建表格以将参与者引导到正确的目的地",
|
||||
"routing_forms_description": "创建表格以将参与者引导到正确的目标",
|
||||
"routing_forms_send_email_owner": "向所有者发送电子邮件",
|
||||
"routing_forms_send_email_owner_description": "提交表单后向所有者发送电子邮件",
|
||||
"add_new_form": "添加新表格",
|
||||
|
||||
@@ -26,6 +26,8 @@
|
||||
"rejection_confirmation": "拒絕預約",
|
||||
"manage_this_event": "管理活動",
|
||||
"invite_team_member": "邀請團隊成員",
|
||||
"invite_team_individual_segment": "邀請個人",
|
||||
"invite_team_bulk_segment": "大量匯入",
|
||||
"invite_team_notifcation_badge": "邀請",
|
||||
"your_event_has_been_scheduled": "活動已預定時間",
|
||||
"your_event_has_been_scheduled_recurring": "您的定期活動已預定",
|
||||
@@ -222,6 +224,7 @@
|
||||
"go_back_login": "回到登入頁面",
|
||||
"error_during_login": "登入的時候發生錯誤。請回到登入畫面,再試一次。",
|
||||
"request_password_reset": "要求重設密碼",
|
||||
"send_invite": "傳送邀請",
|
||||
"forgot_password": "忘記密碼",
|
||||
"forgot": "忘記了?",
|
||||
"done": "完成",
|
||||
@@ -237,6 +240,8 @@
|
||||
"set_availability": "設定開放時間",
|
||||
"continue_without_calendar": "在沒有行事曆的情況下繼續",
|
||||
"connect_your_calendar": "連結行事曆",
|
||||
"connect_your_video_app": "連結視訊應用程式",
|
||||
"connect_your_video_app_instructions": "連結視訊應用程式,以便用於活動類型。",
|
||||
"connect_your_calendar_instructions": "連結行事曆之後,就可以在預約時,自動檢查忙碌時段和新活動。",
|
||||
"set_up_later": "稍後設定",
|
||||
"current_time": "現在時間",
|
||||
@@ -366,6 +371,7 @@
|
||||
"create_webhook": "建立 Webhook",
|
||||
"booking_cancelled": "預約已取消",
|
||||
"booking_rescheduled": "已重新安排預約",
|
||||
"recording_ready": "錄製內容下載連結已準備就緒",
|
||||
"booking_created": "已建立預約",
|
||||
"meeting_ended": "會議已結束",
|
||||
"form_submitted": "表單已提交",
|
||||
@@ -464,6 +470,7 @@
|
||||
"friday": "星期五",
|
||||
"saturday": "星期六",
|
||||
"sunday": "星期日",
|
||||
"all_booked_today": "全部已預約。",
|
||||
"slots_load_fail": "無法讀取剩下的時間區間。",
|
||||
"additional_guests": "新增賓客",
|
||||
"your_name": "名字",
|
||||
@@ -751,6 +758,10 @@
|
||||
"new_event_type_to_book_description": "新增大家可以用來預約時段的活動類型。",
|
||||
"length": "長度",
|
||||
"minimum_booking_notice": "最晚預約通知",
|
||||
"offset_toggle": "偏移開始時間",
|
||||
"offset_toggle_description": "針對向預約者顯示的時段偏移指定的分鐘數",
|
||||
"offset_start": "偏移時間",
|
||||
"offset_start_description": "例如,這麼做會向您的預約者顯示 {{ adjustedTime }},而非 {{ originalTime }}",
|
||||
"slot_interval": "時間單位",
|
||||
"slot_interval_default": "使用活動長度(預設)",
|
||||
"delete_event_type": "要刪除活動類型嗎?",
|
||||
@@ -903,6 +914,7 @@
|
||||
"duplicate": "複製",
|
||||
"offer_seats": "提供座位",
|
||||
"offer_seats_description": "為預約提供座位 (這將停用賓客和選擇加入的預約)",
|
||||
"seats_available_one": "可用座位",
|
||||
"seats_available_other": "尚有座位",
|
||||
"number_of_seats": "每次預約的座位數",
|
||||
"enter_number_of_seats": "輸入座位數",
|
||||
@@ -1037,6 +1049,7 @@
|
||||
"event_cancelled_trigger": "活動取消時",
|
||||
"new_event_trigger": "預約新活動時",
|
||||
"email_host_action": "傳送電子郵件給主辦者",
|
||||
"email_attendee_action": "傳送電子郵件給與會者",
|
||||
"sms_attendee_action": "傳送簡訊給與會者",
|
||||
"sms_number_action": "傳送簡訊至特定號碼",
|
||||
"workflows": "工作流程",
|
||||
@@ -1189,6 +1202,7 @@
|
||||
"create_workflow": "建立工作流程",
|
||||
"do_this": "執行此操作",
|
||||
"turn_off": "關閉",
|
||||
"turn_on": "開啟",
|
||||
"settings_updated_successfully": "成功更新設定",
|
||||
"error_updating_settings": "更新設定時發生錯誤",
|
||||
"personal_cal_url": "我的個人 {{appName}} 網址",
|
||||
@@ -1245,6 +1259,7 @@
|
||||
"calendars_description": "設定活動類型跟行事曆互動的方式",
|
||||
"appearance_description": "管理預約外觀設定",
|
||||
"conferencing_description": "為您的會議加入最愛的視訊會議應用程式",
|
||||
"add_conferencing_app": "新增會議應用程式",
|
||||
"password_description": "管理帳號密碼設定",
|
||||
"2fa_description": "管理帳號密碼設定",
|
||||
"we_just_need_basic_info": "我們只需要一些基本資料,就能完成您的個人資料設定。",
|
||||
@@ -1619,6 +1634,7 @@
|
||||
"email_user_cta": "檢視邀請",
|
||||
"email_no_user_invite_heading": "您已獲邀加入 {{appName}} 上的團隊",
|
||||
"email_no_user_invite_subheading": "{{invitedBy}} 已邀請您加入對方在 {{appName}} 上的團隊。{{appName}} 是活動多工排程工具,可讓您和您的團隊無須透過繁複的電子郵件往來就能輕鬆預定會議。",
|
||||
"email_user_invite_subheading": "{{invitedBy}} 已邀請您加入對方在 {{appName}} 上的「{{teamName}}」團隊。{{appName}} 為一款活動多功能排程工具,可讓您和團隊無須透過繁複的電子郵件往來,就能輕鬆預定會議。",
|
||||
"email_no_user_invite_steps_intro": "我們會逐步引導您完成幾個簡短的步驟,然後您就能馬上與團隊享受沒有壓力的排程預定功能。",
|
||||
"email_no_user_step_one": "選擇使用者名稱",
|
||||
"email_no_user_step_two": "連結行事曆帳號",
|
||||
@@ -1694,6 +1710,15 @@
|
||||
"spot_popular_event_types_description": "查看哪種活動類型的點閱次數及預約次數最多",
|
||||
"no_responses_yet": "還沒有回應",
|
||||
"this_will_be_the_placeholder": "此處會是佔位符",
|
||||
"error_booking_event": "預約活動時發生錯誤,請重新整理頁面然後再試一次",
|
||||
"timeslot_missing_title": "未選擇任何時段",
|
||||
"timeslot_missing_description": "請選擇要預約活動的時段。",
|
||||
"timeslot_missing_cta": "選擇時段",
|
||||
"switch_monthly": "切換至每月檢視",
|
||||
"switch_weekly": "切換至每週檢視",
|
||||
"switch_multiday": "切換至每日檢視",
|
||||
"num_locations": "{{num}} 個地點選項",
|
||||
"select_on_next_step": "於下一步選擇",
|
||||
"this_meeting_has_not_started_yet": "此會議尚未開始",
|
||||
"this_app_requires_connected_account": "{{appName}} 需要已連結的 {{dependencyName}} 帳號",
|
||||
"connect_app": "連結 {{dependencyName}}",
|
||||
@@ -1723,6 +1748,7 @@
|
||||
"locked_apps_description": "成員將能查看啟用的應用程式,但無法編輯任何應用程式設定",
|
||||
"locked_webhooks_description": "成員將能查看啟用的 Webhook,但無法編輯任何 Webhook 設定",
|
||||
"locked_workflows_description": "成員將能查看啟用的工作流程,但無法編輯任何工作流程設定",
|
||||
"locked_by_admin": "已由團隊管理員鎖定",
|
||||
"app_not_connected": "您尚未連結 {{appName}} 帳號。",
|
||||
"connect_now": "立即連結",
|
||||
"managed_event_dialog_confirm_button_one": "取代並通知 {{count}} 位成員",
|
||||
@@ -1780,5 +1806,34 @@
|
||||
"seats_and_no_show_fee_error": "目前無法啟用座位並收取缺席費",
|
||||
"complete_your_booking": "完成您的預約",
|
||||
"complete_your_booking_subject": "完成您的預約:{{date}} 的 {{title}}",
|
||||
"email_invite_team": "已邀請 {{email}}"
|
||||
"confirm_your_details": "確認詳細資料",
|
||||
"currency_string": "{{amount, currency}}",
|
||||
"charge_card_dialog_body": "您即將向與會者收取 {{amount, currency}}。確定要繼續嗎?",
|
||||
"charge_attendee": "向與會者收取 {{amount, currency}}",
|
||||
"payment_app_commission": "需付款 (每筆交易佣金為 {{paymentFeePercentage}}% + {{fee, currency}})",
|
||||
"email_invite_team": "已邀請 {{email}}",
|
||||
"email_invite_team_bulk": "已邀請 {{userCount}} 位使用者",
|
||||
"error_collecting_card": "收集卡片時發生錯誤",
|
||||
"image_size_limit_exceed": "上傳的圖片大小不得超過 5MB 限制",
|
||||
"inline_embed": "內嵌式嵌入",
|
||||
"load_inline_content": "直接與其他網站內容內嵌式載入您的活動類型。",
|
||||
"floating_pop_up_button": "浮動式彈出按鈕",
|
||||
"floating_button_trigger_modal": "在網站上放置浮動式按鈕,以根據活動類型觸發強制回應。",
|
||||
"pop_up_element_click": "透過點閱元素彈出",
|
||||
"open_dialog_with_element_click": "有人按一下元素即開啟 Cal 對話方塊。",
|
||||
"need_help_embedding": "需要協助嗎?歡迎查看我們的指南了解如何在 Wix、Squarespace 或 WordPress 上嵌入 Cal,也歡迎參閱我們的常見問題,或探索進階嵌入方案。",
|
||||
"book_my_cal": "預約我的 Cal",
|
||||
"invite_as": "以下列身分邀請:",
|
||||
"form_updated_successfully": "表單更新成功。",
|
||||
"email_not_cal_member_cta": "加入您的團隊",
|
||||
"disable_attendees_confirmation_emails": "為與會者停用預設確認電子郵件",
|
||||
"disable_attendees_confirmation_emails_description": "預約活動時,此活動類型至少要啟用一個工作流程來傳送電子郵件給與會者。",
|
||||
"disable_host_confirmation_emails": "為主辦人停用預設確認電子郵件",
|
||||
"disable_host_confirmation_emails_description": "預約活動時,此活動類型至少要啟用一個工作流程來傳送電子郵件給主辦人。",
|
||||
"add_an_override": "新增覆寫",
|
||||
"import_from_google_workspace": "從 Google Workspace 匯入使用者",
|
||||
"connect_google_workspace": "連結 Google Workspace",
|
||||
"google_workspace_admin_tooltip": "您必須是 Workspace 管理員才能使用此功能",
|
||||
"first_event_type_webhook_description": "為此活動類型建立第一個 Webhook",
|
||||
"create_for": "適用目標"
|
||||
}
|
||||
|
||||
+3
-2
@@ -17,7 +17,8 @@
|
||||
"app-store": "yarn app-store-cli cli",
|
||||
"create-app": "yarn app-store create",
|
||||
"edit-app": "yarn app-store edit",
|
||||
"publish-embed": "yarn workspaces foreach --from=\"@calcom/embed*\" npm publish --access=public",
|
||||
"withEmbedPublishEnv": "NEXT_PUBLIC_EMBED_LIB_URL='https://app.cal.com/embed/embed.js' NEXT_PUBLIC_WEBAPP_URL='https://app.cal.com' yarn",
|
||||
"publish-embed": "yarn withEmbedPublishEnv workspace @calcom/embed-core build && yarn withEmbedPublishEnv workspace @calcom/embed-snippet build && yarn workspaces foreach --from=\"@calcom/embed*\" npm publish --access=public",
|
||||
"delete-app": "yarn app-store delete",
|
||||
"create-app-template": "yarn app-store create-template",
|
||||
"edit-app-template": "yarn app-store edit-template",
|
||||
@@ -93,7 +94,7 @@
|
||||
"city-timezones": "^1.2.1",
|
||||
"eslint": "^8.34.0",
|
||||
"lucide-react": "^0.171.0",
|
||||
"turbo": "^1.8.3"
|
||||
"turbo": "^1.10.1"
|
||||
},
|
||||
"resolutions": {
|
||||
"@apidevtools/json-schema-ref-parser": "9.0.9",
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
import { v4 as uuidv4 } from "uuid";
|
||||
|
||||
import type { CalendarEvent } from "@calcom/types/Calendar";
|
||||
import type { PartialReference } from "@calcom/types/EventManager";
|
||||
import type { VideoApiAdapter, VideoCallData } from "@calcom/types/VideoApiAdapter";
|
||||
|
||||
@@ -11,19 +12,28 @@ const JitsiVideoApiAdapter = (): VideoApiAdapter => {
|
||||
getAvailability: () => {
|
||||
return Promise.resolve([]);
|
||||
},
|
||||
createMeeting: async (): Promise<VideoCallData> => {
|
||||
createMeeting: async (eventData: CalendarEvent): Promise<VideoCallData> => {
|
||||
const appKeys = await getAppKeysFromSlug(metadata.slug);
|
||||
|
||||
const meetingID = uuidv4();
|
||||
const meetingPattern = (appKeys.jitsiPathPattern as string) || "{uuid}";
|
||||
const hostUrl = (appKeys.jitsiHost as string) || "https://meet.jit.si/cal";
|
||||
|
||||
// Default Value
|
||||
const hostUrl = appKeys.jitsiHost || "https://meet.jit.si/cal";
|
||||
//Allows "/{Type}-with-{Attendees}" slug
|
||||
const meetingID = meetingPattern
|
||||
.replaceAll("{uuid}", uuidv4())
|
||||
.replaceAll("{Title}", eventData.title)
|
||||
.replaceAll("{Event Type Title}", eventData.type)
|
||||
.replaceAll("{Scheduler}", eventData.attendees.map((a) => a.name).join("-"))
|
||||
.replaceAll("{Organizer}", eventData.organizer.name)
|
||||
.replaceAll("{Location}", eventData.location || "")
|
||||
.replaceAll("{Team}", eventData.team?.name || "")
|
||||
.replaceAll(" ", "-"); //Last Rule! - Replace all blanks (%20) with dashes;
|
||||
|
||||
return Promise.resolve({
|
||||
type: metadata.type,
|
||||
id: meetingID,
|
||||
password: "",
|
||||
url: hostUrl + "/" + meetingID,
|
||||
url: hostUrl + "/" + encodeURIComponent(meetingID),
|
||||
});
|
||||
},
|
||||
deleteMeeting: async (): Promise<void> => {
|
||||
|
||||
@@ -2,6 +2,7 @@ import { z } from "zod";
|
||||
|
||||
export const appKeysSchema = z.object({
|
||||
jitsiHost: z.string().optional(),
|
||||
jitsiPathPattern: z.string().optional(),
|
||||
});
|
||||
|
||||
export const appDataSchema = z.object({});
|
||||
|
||||
@@ -3,15 +3,19 @@ import { useState } from "react";
|
||||
import { useAppContextWithSchema } from "@calcom/app-store/EventTypeAppContext";
|
||||
import AppCard from "@calcom/app-store/_components/AppCard";
|
||||
import type { EventTypeAppCardComponent } from "@calcom/app-store/types";
|
||||
import { Tooltip } from "@calcom/ui";
|
||||
import { useLocale } from "@calcom/lib/hooks/useLocale";
|
||||
import { Tooltip, TextField } from "@calcom/ui";
|
||||
|
||||
import type { appDataSchema } from "../zod";
|
||||
|
||||
const EventTypeAppCard: EventTypeAppCardComponent = function EventTypeAppCard({ eventType, app }) {
|
||||
const { t } = useLocale();
|
||||
const [getAppData, setAppData] = useAppContextWithSchema<typeof appDataSchema>();
|
||||
const [enabled, setEnabled] = useState(getAppData("enabled"));
|
||||
const [additionalParameters, setAdditionalParameters] = useState("");
|
||||
|
||||
const eventTypeURL = eventType.URL;
|
||||
const query = additionalParameters !== "" ? `?${additionalParameters}` : "";
|
||||
const eventTypeURL = eventType.URL + query;
|
||||
|
||||
function QRCode({ size, data }: { size: number; data: string }) {
|
||||
const QR_URL = "https://api.qrserver.com/v1/create-qr-code/?size=" + size + "&data=" + data;
|
||||
@@ -19,7 +23,7 @@ const EventTypeAppCard: EventTypeAppCardComponent = function EventTypeAppCard({
|
||||
<Tooltip content={eventTypeURL}>
|
||||
<a download href={QR_URL} target="_blank" rel="noreferrer">
|
||||
<img
|
||||
className="hover:bg-muted border hover:shadow-sm"
|
||||
className="hover:bg-muted border-default border hover:shadow-sm"
|
||||
style={{ padding: size / 16, borderRadius: size / 20 }}
|
||||
width={size}
|
||||
src={QR_URL}
|
||||
@@ -42,10 +46,22 @@ const EventTypeAppCard: EventTypeAppCardComponent = function EventTypeAppCard({
|
||||
}
|
||||
}}
|
||||
switchChecked={enabled}>
|
||||
<div className="max-w-60 flex items-baseline justify-between gap-2 text-sm ">
|
||||
<QRCode size={256} data={eventTypeURL} />
|
||||
<QRCode size={128} data={eventTypeURL} />
|
||||
<QRCode size={64} data={eventTypeURL} />
|
||||
<div className="flex w-full flex-col gap-2 text-sm">
|
||||
<div className="flex w-full">
|
||||
<TextField
|
||||
name="hello"
|
||||
value={additionalParameters}
|
||||
onChange={(e) => setAdditionalParameters(e.target.value)}
|
||||
label={t("additional_url_parameters")}
|
||||
containerClassName="w-full"
|
||||
/>
|
||||
</div>
|
||||
|
||||
<div className="max-w-60 flex items-baseline gap-2">
|
||||
<QRCode size={256} data={eventTypeURL} />
|
||||
<QRCode size={128} data={eventTypeURL} />
|
||||
<QRCode size={64} data={eventTypeURL} />
|
||||
</div>
|
||||
</div>
|
||||
</AppCard>
|
||||
);
|
||||
|
||||
@@ -39,7 +39,8 @@ async function* getResponses(
|
||||
if (value instanceof Array) {
|
||||
serializedValue = value.map((val) => escapeCsvText(val)).join(" | ");
|
||||
} else {
|
||||
serializedValue = escapeCsvText(value);
|
||||
// value can be a number as well for type Number field
|
||||
serializedValue = escapeCsvText(String(value));
|
||||
}
|
||||
csvCells.push(serializedValue);
|
||||
});
|
||||
|
||||
@@ -4,6 +4,7 @@ import type { Dispatch, SetStateAction } from "react";
|
||||
import getFieldIdentifier from "../lib/getFieldIdentifier";
|
||||
import { getQueryBuilderConfig } from "../lib/getQueryBuilderConfig";
|
||||
import isRouterLinkedField from "../lib/isRouterLinkedField";
|
||||
import transformResponse from "../lib/transformResponse";
|
||||
import type { SerializableForm, Response } from "../types/types";
|
||||
|
||||
type Props = {
|
||||
@@ -54,14 +55,14 @@ export default function FormInputFields(props: Props) {
|
||||
required={!!field.required}
|
||||
listValues={options}
|
||||
data-testid={`form-field-${getFieldIdentifier(field)}`}
|
||||
setValue={(value) => {
|
||||
setValue={(value: number | string | string[]) => {
|
||||
setResponse((response) => {
|
||||
response = response || {};
|
||||
return {
|
||||
...response,
|
||||
[field.id]: {
|
||||
label: field.label,
|
||||
value,
|
||||
value: transformResponse({ field, value }),
|
||||
},
|
||||
};
|
||||
});
|
||||
|
||||
@@ -43,6 +43,22 @@ const OPERATOR_MAP = {
|
||||
operator: "NOT.equals",
|
||||
secondaryOperand: "",
|
||||
},
|
||||
">": {
|
||||
operator: "gt",
|
||||
secondaryOperand: null,
|
||||
},
|
||||
">=": {
|
||||
operator: "gte",
|
||||
secondaryOperand: null,
|
||||
},
|
||||
"<": {
|
||||
operator: "lt",
|
||||
secondaryOperand: null,
|
||||
},
|
||||
"<=": {
|
||||
operator: "lte",
|
||||
secondaryOperand: null,
|
||||
},
|
||||
all: {
|
||||
operator: "array_contains",
|
||||
secondaryOperand: null,
|
||||
@@ -58,6 +74,7 @@ const GROUP_OPERATOR_MAP = {
|
||||
"!": "NOT",
|
||||
} as const;
|
||||
|
||||
const NumberOperators = [">", ">=", "<", "<="];
|
||||
const convertSingleQueryToPrismaWhereClause = (
|
||||
operatorName: keyof typeof OPERATOR_MAP,
|
||||
logicData: LogicData,
|
||||
@@ -71,13 +88,56 @@ const convertSingleQueryToPrismaWhereClause = (
|
||||
logicData[operatorName] instanceof Array ? logicData[operatorName] : [logicData[operatorName]];
|
||||
|
||||
const mainOperand = operatorName !== "in" ? operands[0].var : operands[1].var;
|
||||
|
||||
let secondaryOperand = staticSecondaryOperand || (operatorName !== "in" ? operands[1] : operands[0]) || "";
|
||||
if (operatorName === "all") {
|
||||
secondaryOperand = secondaryOperand.in[1];
|
||||
}
|
||||
const prismaWhere = {
|
||||
response: { path: [mainOperand, "value"], [`${prismaOperator}`]: secondaryOperand },
|
||||
};
|
||||
|
||||
const isNumberOperator = NumberOperators.includes(operatorName);
|
||||
const secondaryOperandAsNumber = typeof secondaryOperand === "string" ? Number(secondaryOperand) : null;
|
||||
|
||||
let prismaWhere;
|
||||
if (secondaryOperandAsNumber) {
|
||||
// We know that it's number operator so Prisma should query number
|
||||
// Note that if we get string values in DB(e.g. '100'), those values can't be filtered with number operators.
|
||||
if (isNumberOperator) {
|
||||
prismaWhere = {
|
||||
response: {
|
||||
path: [mainOperand, "value"],
|
||||
[`${prismaOperator}`]: secondaryOperandAsNumber,
|
||||
},
|
||||
};
|
||||
} else {
|
||||
// We know that it's not number operator but the input field might have been a number and thus stored value in DB as number.
|
||||
// Also, even for input type=number we might accidentally get string value(e.g. '100'). So, let reporting do it's best job with both number and string.
|
||||
prismaWhere = {
|
||||
OR: [
|
||||
{
|
||||
response: {
|
||||
path: [mainOperand, "value"],
|
||||
// Query as string e.g. equals '100'
|
||||
[`${prismaOperator}`]: secondaryOperand,
|
||||
},
|
||||
},
|
||||
{
|
||||
response: {
|
||||
path: [mainOperand, "value"],
|
||||
// Query as number e.g. equals 100
|
||||
[`${prismaOperator}`]: secondaryOperandAsNumber,
|
||||
},
|
||||
},
|
||||
],
|
||||
};
|
||||
}
|
||||
} else {
|
||||
prismaWhere = {
|
||||
response: {
|
||||
path: [mainOperand, "value"],
|
||||
[`${prismaOperator}`]: secondaryOperand,
|
||||
},
|
||||
};
|
||||
}
|
||||
|
||||
if (isNegation) {
|
||||
return {
|
||||
|
||||
@@ -50,8 +50,14 @@ export function getQueryBuilderConfig(form: RoutingForm, forReporting = false) {
|
||||
|
||||
const initialConfigCopy = { ...InitialConfig, operators: { ...InitialConfig.operators } };
|
||||
if (forReporting) {
|
||||
// Empty and Not empty doesn't work well with JSON querying in prisma. Try to implement these when we desperately need these operators.
|
||||
delete initialConfigCopy.operators.is_empty;
|
||||
delete initialConfigCopy.operators.is_not_empty;
|
||||
|
||||
// Between and Not between aren't directly supported by prisma. So, we need to update jsonLogicToPrisma to generate gte and lte query for between. It can be implemented later.
|
||||
delete initialConfigCopy.operators.between;
|
||||
delete initialConfigCopy.operators.not_between;
|
||||
|
||||
initialConfigCopy.operators.__calReporting = true;
|
||||
}
|
||||
// You need to provide your own config. See below 'Config format'
|
||||
|
||||
@@ -50,13 +50,13 @@ export function processRoute({
|
||||
const jsonLogicQuery = QbUtils.jsonLogicFormat(state.tree, state.config);
|
||||
const logic = jsonLogicQuery.logic;
|
||||
let result = false;
|
||||
const responseValues: Record<string, string | string[]> = {};
|
||||
const responseValues: Record<string, Response[string]["value"]> = {};
|
||||
for (const [uuid, { value }] of Object.entries(response)) {
|
||||
responseValues[uuid] = value;
|
||||
}
|
||||
|
||||
if (logic) {
|
||||
// Leave the logs for easy debugging of routing form logic test.
|
||||
// Leave the logs for debugging of routing form logic test in production
|
||||
console.log("Checking logic with response", logic, responseValues);
|
||||
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
||||
result = jsonLogic.apply(logic as any, responseValues);
|
||||
|
||||
@@ -0,0 +1,12 @@
|
||||
import type { Field, Response } from "../types/types";
|
||||
|
||||
export default function transformResponse({
|
||||
field,
|
||||
value,
|
||||
}: {
|
||||
field: Field;
|
||||
value: Response[string]["value"];
|
||||
}) {
|
||||
// type="number" still gives value as a string but we need to store that as number so that number operators can work.
|
||||
return field.type === "number" && typeof value === "string" ? Number(value) : value;
|
||||
}
|
||||
@@ -7,6 +7,7 @@ import type { inferSSRProps } from "@calcom/types/inferSSRProps";
|
||||
import getFieldIdentifier from "../../lib/getFieldIdentifier";
|
||||
import { getSerializableForm } from "../../lib/getSerializableForm";
|
||||
import { processRoute } from "../../lib/processRoute";
|
||||
import transformResponse from "../../lib/transformResponse";
|
||||
import type { Response } from "../../types/types";
|
||||
|
||||
export default function Router({ form, message }: inferSSRProps<typeof getServerSideProps>) {
|
||||
@@ -60,12 +61,11 @@ export const getServerSideProps = async function getServerSideProps(
|
||||
|
||||
const response: Record<string, Pick<Response[string], "value">> = {};
|
||||
serializableForm.fields?.forEach((field) => {
|
||||
const rawFieldResponse = fieldsResponses[getFieldIdentifier(field)] || "";
|
||||
console.log(field);
|
||||
const fieldResponse =
|
||||
field.type === "multiselect" ? rawFieldResponse.split(",").map((r) => r.trim()) : rawFieldResponse;
|
||||
const fieldResponse = fieldsResponses[getFieldIdentifier(field)] || "";
|
||||
const value =
|
||||
field.type === "multiselect" ? fieldResponse.split(",").map((r) => r.trim()) : fieldResponse;
|
||||
response[field.id] = {
|
||||
value: fieldResponse,
|
||||
value: transformResponse({ field, value }),
|
||||
};
|
||||
});
|
||||
|
||||
|
||||
@@ -187,8 +187,10 @@ function getUrlSearchParamsToForward(response: Response, fields: NonNullable<Pro
|
||||
// If for some reason, the field isn't there, let's just
|
||||
return;
|
||||
}
|
||||
const valueAsStringOrStringArray =
|
||||
typeof fieldResponse.value === "number" ? String(fieldResponse.value) : fieldResponse.value;
|
||||
paramsFromResponse[getFieldIdentifier(foundField) as keyof typeof paramsFromResponse] =
|
||||
fieldResponse.value;
|
||||
valueAsStringOrStringArray;
|
||||
});
|
||||
|
||||
// Build query params from current URL. It excludes route params
|
||||
|
||||
@@ -6,89 +6,229 @@ afterEach(() => {
|
||||
vi.resetAllMocks();
|
||||
});
|
||||
|
||||
describe("jsonLogicToPrisma - Single Query", () => {
|
||||
it("should support Short 'Equals' operator", () => {
|
||||
const prismaWhere = jsonLogicToPrisma({
|
||||
logic: { and: [{ "==": [{ var: "505d3c3c-aa71-4220-93a9-6fd1e1087939" }, "A"] }] },
|
||||
});
|
||||
expect(prismaWhere).toEqual({
|
||||
AND: [
|
||||
{
|
||||
response: {
|
||||
path: ["505d3c3c-aa71-4220-93a9-6fd1e1087939", "value"],
|
||||
equals: "A",
|
||||
},
|
||||
},
|
||||
],
|
||||
});
|
||||
});
|
||||
|
||||
it("should support Short 'Not Equals' operator", () => {
|
||||
const prismaWhere = jsonLogicToPrisma({
|
||||
logic: { and: [{ "!=": [{ var: "505d3c3c-aa71-4220-93a9-6fd1e1087939" }, "abc"] }] },
|
||||
});
|
||||
expect(prismaWhere).toEqual({
|
||||
AND: [
|
||||
{
|
||||
NOT: {
|
||||
describe("jsonLogicToPrisma(Reporting)", () => {
|
||||
describe("Text Operand", () => {
|
||||
it("should support 'Equals' operator", () => {
|
||||
const prismaWhere = jsonLogicToPrisma({
|
||||
logic: { and: [{ "==": [{ var: "505d3c3c-aa71-4220-93a9-6fd1e1087939" }, "A"] }] },
|
||||
});
|
||||
expect(prismaWhere).toEqual({
|
||||
AND: [
|
||||
{
|
||||
response: {
|
||||
path: ["505d3c3c-aa71-4220-93a9-6fd1e1087939", "value"],
|
||||
equals: "abc",
|
||||
equals: "A",
|
||||
},
|
||||
},
|
||||
},
|
||||
],
|
||||
],
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
it("should support Short 'Contains' operator", () => {
|
||||
const prismaWhere = jsonLogicToPrisma({
|
||||
logic: { and: [{ in: ["A", { var: "505d3c3c-aa71-4220-93a9-6fd1e1087939" }] }] },
|
||||
});
|
||||
expect(prismaWhere).toEqual({
|
||||
AND: [
|
||||
{
|
||||
response: {
|
||||
path: ["505d3c3c-aa71-4220-93a9-6fd1e1087939", "value"],
|
||||
string_contains: "A",
|
||||
it("should support 'Not Equals' operator", () => {
|
||||
const prismaWhere = jsonLogicToPrisma({
|
||||
logic: { and: [{ "!=": [{ var: "505d3c3c-aa71-4220-93a9-6fd1e1087939" }, "abc"] }] },
|
||||
});
|
||||
expect(prismaWhere).toEqual({
|
||||
AND: [
|
||||
{
|
||||
NOT: {
|
||||
response: {
|
||||
path: ["505d3c3c-aa71-4220-93a9-6fd1e1087939", "value"],
|
||||
equals: "abc",
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
],
|
||||
],
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
it("should support Short 'Not Contains' operator", () => {
|
||||
const prismaWhere = jsonLogicToPrisma({
|
||||
logic: { and: [{ "!": { in: ["a", { var: "505d3c3c-aa71-4220-93a9-6fd1e1087939" }] } }] },
|
||||
});
|
||||
expect(prismaWhere).toEqual({
|
||||
AND: [
|
||||
{
|
||||
NOT: {
|
||||
it("should support 'Contains' operator", () => {
|
||||
const prismaWhere = jsonLogicToPrisma({
|
||||
logic: { and: [{ in: ["A", { var: "505d3c3c-aa71-4220-93a9-6fd1e1087939" }] }] },
|
||||
});
|
||||
expect(prismaWhere).toEqual({
|
||||
AND: [
|
||||
{
|
||||
response: {
|
||||
path: ["505d3c3c-aa71-4220-93a9-6fd1e1087939", "value"],
|
||||
string_contains: "a",
|
||||
string_contains: "A",
|
||||
},
|
||||
},
|
||||
},
|
||||
],
|
||||
],
|
||||
});
|
||||
});
|
||||
|
||||
it("should support 'Not Contains' operator", () => {
|
||||
const prismaWhere = jsonLogicToPrisma({
|
||||
logic: { and: [{ "!": { in: ["a", { var: "505d3c3c-aa71-4220-93a9-6fd1e1087939" }] } }] },
|
||||
});
|
||||
expect(prismaWhere).toEqual({
|
||||
AND: [
|
||||
{
|
||||
NOT: {
|
||||
response: {
|
||||
path: ["505d3c3c-aa71-4220-93a9-6fd1e1087939", "value"],
|
||||
string_contains: "a",
|
||||
},
|
||||
},
|
||||
},
|
||||
],
|
||||
});
|
||||
});
|
||||
|
||||
describe("Number Type", () => {
|
||||
it("should support 'greater than' operator", () => {
|
||||
let prismaWhere = jsonLogicToPrisma({
|
||||
logic: {
|
||||
and: [
|
||||
{
|
||||
">": [
|
||||
{
|
||||
var: "a0d113a8-8e40-49b7-87b1-7f4ab57d226f",
|
||||
},
|
||||
// Giving a string here to test that it is converted to a number
|
||||
"100",
|
||||
],
|
||||
},
|
||||
],
|
||||
},
|
||||
});
|
||||
|
||||
expect(prismaWhere).toEqual({
|
||||
AND: [{ response: { path: ["a0d113a8-8e40-49b7-87b1-7f4ab57d226f", "value"], gt: 100 } }],
|
||||
});
|
||||
|
||||
prismaWhere = jsonLogicToPrisma({
|
||||
logic: {
|
||||
and: [
|
||||
{
|
||||
">": [
|
||||
{
|
||||
var: "a0d113a8-8e40-49b7-87b1-7f4ab57d226f",
|
||||
},
|
||||
// A number would also work
|
||||
100,
|
||||
],
|
||||
},
|
||||
],
|
||||
},
|
||||
});
|
||||
|
||||
expect(prismaWhere).toEqual({
|
||||
AND: [{ response: { path: ["a0d113a8-8e40-49b7-87b1-7f4ab57d226f", "value"], gt: 100 } }],
|
||||
});
|
||||
});
|
||||
it("should support 'greater than or equal to' operator", () => {
|
||||
const prismaWhere = jsonLogicToPrisma({
|
||||
logic: {
|
||||
and: [
|
||||
{
|
||||
">=": [
|
||||
{
|
||||
var: "a0d113a8-8e40-49b7-87b1-7f4ab57d226f",
|
||||
},
|
||||
// Giving a string here to test that it is converted to a number
|
||||
"100",
|
||||
],
|
||||
},
|
||||
],
|
||||
},
|
||||
});
|
||||
|
||||
expect(prismaWhere).toEqual({
|
||||
AND: [{ response: { path: ["a0d113a8-8e40-49b7-87b1-7f4ab57d226f", "value"], gte: 100 } }],
|
||||
});
|
||||
});
|
||||
it("should support 'less than' operator", () => {
|
||||
const prismaWhere = jsonLogicToPrisma({
|
||||
logic: {
|
||||
and: [
|
||||
{
|
||||
"<": [
|
||||
{
|
||||
var: "a0d113a8-8e40-49b7-87b1-7f4ab57d226f",
|
||||
},
|
||||
// Giving a string here to test that it is converted to a number
|
||||
"100",
|
||||
],
|
||||
},
|
||||
],
|
||||
},
|
||||
});
|
||||
|
||||
expect(prismaWhere).toEqual({
|
||||
AND: [{ response: { path: ["a0d113a8-8e40-49b7-87b1-7f4ab57d226f", "value"], lt: 100 } }],
|
||||
});
|
||||
});
|
||||
it("should support 'less than or equal to' operator", () => {
|
||||
const prismaWhere = jsonLogicToPrisma({
|
||||
logic: {
|
||||
and: [
|
||||
{
|
||||
"<=": [
|
||||
{
|
||||
var: "a0d113a8-8e40-49b7-87b1-7f4ab57d226f",
|
||||
},
|
||||
// Giving a string here to test that it is converted to a number
|
||||
"100",
|
||||
],
|
||||
},
|
||||
],
|
||||
},
|
||||
});
|
||||
|
||||
expect(prismaWhere).toEqual({
|
||||
AND: [{ response: { path: ["a0d113a8-8e40-49b7-87b1-7f4ab57d226f", "value"], lte: 100 } }],
|
||||
});
|
||||
});
|
||||
it("'Equals' operator should query with string as well as number", () => {
|
||||
const prismaWhere = jsonLogicToPrisma({
|
||||
logic: { and: [{ "==": [{ var: "505d3c3c-aa71-4220-93a9-6fd1e1087939" }, "1"] }] },
|
||||
});
|
||||
|
||||
expect(prismaWhere).toEqual({
|
||||
AND: [
|
||||
{
|
||||
OR: [
|
||||
{
|
||||
response: {
|
||||
path: ["505d3c3c-aa71-4220-93a9-6fd1e1087939", "value"],
|
||||
equals: "1",
|
||||
},
|
||||
},
|
||||
{
|
||||
response: {
|
||||
path: ["505d3c3c-aa71-4220-93a9-6fd1e1087939", "value"],
|
||||
equals: 1,
|
||||
},
|
||||
},
|
||||
],
|
||||
},
|
||||
],
|
||||
});
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
it("should support 'MultiSelect' 'Equals' operator", () => {
|
||||
const prismaWhere = jsonLogicToPrisma({
|
||||
logic: {
|
||||
and: [{ all: [{ var: "267c7817-81a5-4bef-9d5b-d0faa4cd0d71" }, { in: [{ var: "" }, ["C", "D"]] }] }],
|
||||
},
|
||||
});
|
||||
expect(prismaWhere).toEqual({
|
||||
AND: [
|
||||
{ response: { path: ["267c7817-81a5-4bef-9d5b-d0faa4cd0d71", "value"], array_contains: ["C", "D"] } },
|
||||
],
|
||||
describe("MultiSelect", () => {
|
||||
it("should support 'Equals' operator", () => {
|
||||
const prismaWhere = jsonLogicToPrisma({
|
||||
logic: {
|
||||
and: [
|
||||
{ all: [{ var: "267c7817-81a5-4bef-9d5b-d0faa4cd0d71" }, { in: [{ var: "" }, ["C", "D"]] }] },
|
||||
],
|
||||
},
|
||||
});
|
||||
expect(prismaWhere).toEqual({
|
||||
AND: [
|
||||
{
|
||||
response: { path: ["267c7817-81a5-4bef-9d5b-d0faa4cd0d71", "value"], array_contains: ["C", "D"] },
|
||||
},
|
||||
],
|
||||
});
|
||||
});
|
||||
});
|
||||
});
|
||||
describe("jsonLogicToPrisma - Single Query", () => {
|
||||
|
||||
it("should support where All Match ['Equals', 'Equals'] operator", () => {
|
||||
const prismaWhere = jsonLogicToPrisma({
|
||||
logic: {
|
||||
@@ -116,6 +256,7 @@ describe("jsonLogicToPrisma - Single Query", () => {
|
||||
],
|
||||
});
|
||||
});
|
||||
|
||||
it("should support where Any Match ['Equals', 'Equals'] operator", () => {
|
||||
const prismaWhere = jsonLogicToPrisma({
|
||||
logic: {
|
||||
@@ -143,6 +284,7 @@ describe("jsonLogicToPrisma - Single Query", () => {
|
||||
],
|
||||
});
|
||||
});
|
||||
|
||||
it("should support where None Match ['Equals', 'Equals'] operator", () => {
|
||||
const prismaWhere = jsonLogicToPrisma({
|
||||
logic: {
|
||||
|
||||
@@ -52,9 +52,9 @@ export const reportHandler = async ({ ctx: { prisma }, input }: ReportHandlerOpt
|
||||
});
|
||||
const fields = serializedForm?.fields || [];
|
||||
const headers = fields.map((f) => f.label + (f.deleted ? "(Deleted)" : ""));
|
||||
const responses: string[][] = [];
|
||||
const responses: (string | number)[][] = [];
|
||||
rows.forEach((r) => {
|
||||
const rowResponses: string[] = [];
|
||||
const rowResponses: (string | number)[] = [];
|
||||
responses.push(rowResponses);
|
||||
fields.forEach((field) => {
|
||||
if (!r.response) {
|
||||
@@ -62,13 +62,13 @@ export const reportHandler = async ({ ctx: { prisma }, input }: ReportHandlerOpt
|
||||
}
|
||||
const response = r.response as Response;
|
||||
const value = response[field.id]?.value || "";
|
||||
let stringValue = "";
|
||||
let transformedValue;
|
||||
if (value instanceof Array) {
|
||||
stringValue = value.join(", ");
|
||||
transformedValue = value.join(", ");
|
||||
} else {
|
||||
stringValue = value;
|
||||
transformedValue = value;
|
||||
}
|
||||
rowResponses.push(stringValue);
|
||||
rowResponses.push(transformedValue);
|
||||
});
|
||||
});
|
||||
const areThereNoResultsOrLessThanAskedFor = !rows.length || rows.length < take;
|
||||
|
||||
@@ -6,7 +6,7 @@ export const ZResponseInputSchema = z.object({
|
||||
response: z.record(
|
||||
z.object({
|
||||
label: z.string(),
|
||||
value: z.union([z.string(), z.array(z.string())]),
|
||||
value: z.union([z.string(), z.number(), z.array(z.string())]),
|
||||
})
|
||||
),
|
||||
});
|
||||
|
||||
+1
-1
@@ -14,7 +14,7 @@ export type Response = Record<
|
||||
// Field ID
|
||||
string,
|
||||
{
|
||||
value: string | string[];
|
||||
value: number | string | string[];
|
||||
label: string;
|
||||
}
|
||||
>;
|
||||
|
||||
@@ -15,7 +15,7 @@
|
||||
"embed-web-start": "yarn workspace @calcom/web start",
|
||||
"__build": "yarn tailwind && vite build && tsc --emitDeclarationOnly --declarationDir dist && cp -r ../../../apps/web/public/embed ./dist/",
|
||||
"__dev": "yarn tailwind && vite build --mode development",
|
||||
"build": "NEXT_PUBLIC_EMBED_FINGER_PRINT=$(git rev-parse --short HEAD) yarn __build",
|
||||
"build": "rm -rf dist && NEXT_PUBLIC_EMBED_FINGER_PRINT=$(git rev-parse --short HEAD) yarn __build",
|
||||
"build-preview": "PREVIEW_BUILD=1 yarn __build ",
|
||||
"vite": "vite",
|
||||
"tailwind": "yarn tailwindcss -i ./src/styles.css -o ./src/tailwind.generated.css",
|
||||
@@ -29,7 +29,8 @@
|
||||
"embed-tests": "yarn playwright test --config=playwright/config/playwright.config.ts",
|
||||
"embed-tests-quick": "QUICK=true yarn embed-tests",
|
||||
"embed-tests-update-snapshots:ci": "yarn embed-tests-quick --update-snapshots",
|
||||
"prepack": "NEXT_PUBLIC_WEBAPP_URL='https://app.cal.com' yarn build",
|
||||
"withEmbedPublishEnv": "NEXT_PUBLIC_EMBED_LIB_URL='https://app.cal.com/embed/embed.js' NEXT_PUBLIC_WEBAPP_URL='https://app.cal.com' yarn",
|
||||
"prepack": "yarn withEmbedPublishEnv build",
|
||||
"clean": "rm -rf .turbo && rm -rf node_modules && rm -rf ../../../apps/web/public/embed"
|
||||
},
|
||||
"files": [
|
||||
|
||||
@@ -131,9 +131,9 @@ export async function bookFirstEvent(username: string, frame: Frame, page: Page)
|
||||
return booking;
|
||||
}
|
||||
|
||||
export async function rescheduleEvent(username, frame, page) {
|
||||
export async function rescheduleEvent(username: string, frame: Frame, page: Page) {
|
||||
await selectFirstAvailableTimeSlotNextMonth(frame, page);
|
||||
await frame.waitForURL((url) => {
|
||||
await frame.waitForURL((url: { pathname: string | string[] }) => {
|
||||
return url.pathname.includes(`/${username}/book`);
|
||||
});
|
||||
// --- fill form
|
||||
|
||||
@@ -125,7 +125,7 @@ type SingleInstruction = SingleInstructionMap[keyof SingleInstructionMap];
|
||||
export type Instruction = SingleInstruction | SingleInstruction[];
|
||||
export type InstructionQueue = Instruction[];
|
||||
|
||||
type PrefillAndIframeAttrsConfig = Record<string, string | string[] | Record<string, string>> & {
|
||||
export type PrefillAndIframeAttrsConfig = Record<string, string | string[] | Record<string, string>> & {
|
||||
iframeAttrs?: Record<string, string> & {
|
||||
id?: string;
|
||||
};
|
||||
@@ -551,7 +551,13 @@ class CalApi {
|
||||
this.cal.actionManager.on(action, callback);
|
||||
}
|
||||
|
||||
off({ action, callback }: { action: never; callback: never }) {
|
||||
off<T extends keyof EventDataMap>({
|
||||
action,
|
||||
callback,
|
||||
}: {
|
||||
action: T;
|
||||
callback: (arg0: CustomEvent<EventData<T>>) => void;
|
||||
}) {
|
||||
this.cal.actionManager.off(action, callback);
|
||||
}
|
||||
|
||||
|
||||
@@ -3,16 +3,19 @@ import { useEffect } from "react";
|
||||
import { useState } from "react";
|
||||
import ReactDom from "react-dom";
|
||||
|
||||
// Because we don't import from @calcom/embed-react, this file isn't able to test if the build is successful or not and thus npm package would work or not correctly.
|
||||
// There are tests in test/built which verifiy that the types from built package are correctly generated and exported correctly.
|
||||
import Cal, { getCalApi } from "./src/index";
|
||||
|
||||
const api = getCalApi();
|
||||
|
||||
function App() {
|
||||
const [loaded, setLoaded] = useState(false);
|
||||
const [, setLoaded] = useState(false);
|
||||
useEffect(() => {
|
||||
// Simulate state change causing config object to change, causing rerender of Cal
|
||||
setTimeout(setLoaded.bind(true), 1000);
|
||||
const callback = (event) => {
|
||||
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
||||
const callback = (event: any) => {
|
||||
console.log(event.detail);
|
||||
};
|
||||
api.then((api) => {
|
||||
|
||||
@@ -11,16 +11,17 @@
|
||||
},
|
||||
"scripts": {
|
||||
"dev": "vite --port=3101 --open",
|
||||
"build": "vite build && tsc --emitDeclarationOnly --declarationDir dist",
|
||||
"build": "rm -rf dist && vite build && tsc --emitDeclarationOnly --declarationDir dist",
|
||||
"preview": "vite preview",
|
||||
"prepare": "NEXT_PUBLIC_EMBED_LIB_URL='https://app.cal.com/embed/embed.js' NEXT_PUBLIC_WEBAPP_URL='https://app.cal.com' yarn build",
|
||||
"type-check": "tsc --pretty --noEmit",
|
||||
"type-check:ci": "tsc-absolute --pretty --noEmit",
|
||||
"lint": "eslint --ext .ts,.js,.tsx,.jsx ./src",
|
||||
"embed-tests": "yarn playwright test --config=./playwright/config/playwright.config.ts",
|
||||
"embed-tests-quick": "QUICK=true yarn embed-tests",
|
||||
"embed-tests-update-snapshots:ci": "yarn embed-tests-quick --update-snapshots",
|
||||
"prepack": "NEXT_PUBLIC_EMBED_LIB_URL='https://app.cal.com/embed/embed.js' NEXT_PUBLIC_WEBAPP_URL='https://app.cal.com' yarn build",
|
||||
"packaged:tests": "cd test/packaged && yarn tsc --noEmit && yarn run -T test -- --packaged-embed-tests-only",
|
||||
"withEmbedPublishEnv": "NEXT_PUBLIC_EMBED_LIB_URL='https://app.cal.com/embed/embed.js' NEXT_PUBLIC_WEBAPP_URL='https://app.cal.com' yarn",
|
||||
"prepack": "yarn withEmbedPublishEnv build && yarn packaged:tests",
|
||||
"embed-web-start": "yarn workspace @calcom/web start",
|
||||
"embed-dev": "yarn workspace @calcom/embed-react dev",
|
||||
"clean": "rm -rf .turbo && rm -rf node_modules && rm -rf dist"
|
||||
|
||||
@@ -3,16 +3,22 @@ import { expect } from "@playwright/test";
|
||||
import { getEmbedIframe } from "@calcom/embed-core/playwright/lib/testUtils";
|
||||
import { test } from "@calcom/web/playwright/lib/fixtures";
|
||||
|
||||
test("Inline Usage Snapshot", async ({ page, getActionFiredDetails, addEmbedListeners }) => {
|
||||
//TODO: Do it with page.goto automatically
|
||||
await addEmbedListeners("");
|
||||
await page.goto("/");
|
||||
const embedIframe = await getEmbedIframe({ page, pathname: "/pro" });
|
||||
expect(embedIframe).toBeEmbedCalLink("", getActionFiredDetails, {
|
||||
pathname: "/pro",
|
||||
searchParams: {
|
||||
theme: "dark",
|
||||
},
|
||||
test.describe("Inline Embed", () => {
|
||||
test("should verify that the iframe got created with correct URL", async ({
|
||||
page,
|
||||
getActionFiredDetails,
|
||||
addEmbedListeners,
|
||||
}) => {
|
||||
//TODO: Do it with page.goto automatically
|
||||
await addEmbedListeners("");
|
||||
await page.goto("/");
|
||||
const embedIframe = await getEmbedIframe({ page, pathname: "/pro" });
|
||||
expect(embedIframe).toBeEmbedCalLink("", getActionFiredDetails, {
|
||||
pathname: "/pro",
|
||||
searchParams: {
|
||||
theme: "dark",
|
||||
},
|
||||
});
|
||||
// expect(await page.screenshot()).toMatchSnapshot("react-component-inline.png");
|
||||
});
|
||||
// expect(await page.screenshot()).toMatchSnapshot("react-component-inline.png");
|
||||
});
|
||||
|
||||
@@ -2,6 +2,8 @@
|
||||
|
||||
import { useEffect, useRef } from "react";
|
||||
|
||||
import type { PrefillAndIframeAttrsConfig } from "@calcom/embed-core";
|
||||
|
||||
import useEmbed from "./useEmbed";
|
||||
|
||||
type CalProps = {
|
||||
@@ -11,7 +13,7 @@ type CalProps = {
|
||||
debug?: boolean;
|
||||
uiDebug?: boolean;
|
||||
};
|
||||
config?: Record<string, string>;
|
||||
config?: PrefillAndIframeAttrsConfig;
|
||||
embedJsUrl?: string;
|
||||
} & React.HTMLAttributes<HTMLDivElement>;
|
||||
|
||||
|
||||
@@ -0,0 +1,5 @@
|
||||
# Packaged
|
||||
|
||||
The tests in this file are run on the packaged code that is published to npm. The packaged code is different from the source code in atleast the following ways
|
||||
- Not all files go to packaged code.If package.json -> files field is specified then only the files that are specified there would be published. So, one might accidentally miss an important file that's available otherwise.
|
||||
- The packaged code doesn't have .ts files. Those files are actually converted to .js files and .d.ts files are generated separately for TypeScript support. It allows the package to work in both TypeScript and non TypeScript environments.
|
||||
@@ -0,0 +1,16 @@
|
||||
/**
|
||||
* @fileoverview
|
||||
* This file tests two things in 2 ways
|
||||
* - It is a vitest test file and thus it tests if the code executes without any error. Thus, it tests that package.json->main/module fields are correctly defined. It obviously verifies the assertions as well.
|
||||
* - It is also validates for it's types and thus verifies that @calcom/embed-react has correctly specified it's types in package.json->types field.
|
||||
*/
|
||||
import { expect, test } from "vitest";
|
||||
|
||||
// This import may show up as an error in your IDE, but it's fine because typings are available only after embed-react is built.
|
||||
import { getCalApi } from "@calcom/embed-react";
|
||||
|
||||
const api = getCalApi();
|
||||
|
||||
test("Check that the API is available", () => {
|
||||
expect(api).toBeDefined()
|
||||
});
|
||||
@@ -0,0 +1,14 @@
|
||||
{
|
||||
"extends": "@calcom/tsconfig/base.json",
|
||||
"compilerOptions": {
|
||||
"module": "ESNext",
|
||||
"target": "ES2015",
|
||||
"moduleResolution": "Node",
|
||||
"baseUrl": ".",
|
||||
"declaration": true,
|
||||
"jsx": "preserve",
|
||||
"outDir": "dist",
|
||||
},
|
||||
"include": ["**/*.ts"],
|
||||
}
|
||||
|
||||
@@ -13,6 +13,7 @@
|
||||
"@calcom/embed-snippet": ["../embed-snippet/src"]
|
||||
}
|
||||
},
|
||||
"include": ["src", "env.d.ts"],
|
||||
"exclude": ["dist", "build", "node_modules", "test-cal.tsx"]
|
||||
"include": ["**/*.ts","**/*.tsx", "env.d.ts"],
|
||||
// Exclude "test" because that has `api.test.ts` which imports @calcom/embed-react which needs it to be built using this tsconfig.json first. Excluding it here prevents type-check from validating test folder
|
||||
"exclude": ["node_modules", "test"]
|
||||
}
|
||||
|
||||
@@ -12,11 +12,12 @@
|
||||
"directory": "packages/embeds/embed-snippet"
|
||||
},
|
||||
"scripts": {
|
||||
"build": "vite build && tsc --emitDeclarationOnly --declarationDir dist",
|
||||
"build": "rm -rf dist && vite build && tsc --emitDeclarationOnly --declarationDir dist",
|
||||
"type-check": "tsc --pretty --noEmit",
|
||||
"type-check:ci": "tsc-absolute --pretty --noEmit",
|
||||
"lint": "eslint --ext .ts,.js src",
|
||||
"prepack": "NEXT_PUBLIC_EMBED_LIB_URL='https://app.cal.com/embed/embed.js' NEXT_PUBLIC_WEBAPP_URL='https://app.cal.com' yarn build",
|
||||
"withEmbedPublishEnv": "NEXT_PUBLIC_EMBED_LIB_URL='https://app.cal.com/embed/embed.js' NEXT_PUBLIC_WEBAPP_URL='https://app.cal.com' yarn",
|
||||
"prepack": "yarn withEmbedPublishEnv build",
|
||||
"clean": "rm -rf .turbo && rm -rf node_modules && rm -rf dist"
|
||||
},
|
||||
"files": [
|
||||
|
||||
@@ -103,7 +103,7 @@ const BookerComponent = ({
|
||||
since that's not a valid option, so it would set the layout to null.
|
||||
*/}
|
||||
{!isMobile && (
|
||||
<div className="[&>div]:bg-default fixed top-2 right-3 z-10">
|
||||
<div className="[&>div]:bg-default dark:[&>div]:bg-muted fixed top-2 right-3 z-10">
|
||||
<ToggleGroup
|
||||
onValueChange={onLayoutToggle}
|
||||
defaultValue={layout}
|
||||
@@ -133,7 +133,7 @@ const BookerComponent = ({
|
||||
className={classNames(
|
||||
// Sets booker size css variables for the size of all the columns.
|
||||
...getBookerSizeClassNames(layout, bookerState),
|
||||
"bg-default grid max-w-full auto-rows-fr items-start overflow-clip dark:[color-scheme:dark] sm:transition-[width] sm:duration-300 sm:motion-reduce:transition-none md:flex-row",
|
||||
"bg-default dark:bg-muted grid max-w-full auto-rows-fr items-start overflow-clip dark:[color-scheme:dark] sm:transition-[width] sm:duration-300 sm:motion-reduce:transition-none md:flex-row",
|
||||
layout === "small_calendar" && "border-subtle rounded-md border"
|
||||
)}>
|
||||
<AnimatePresence>
|
||||
|
||||
@@ -20,7 +20,7 @@ export function BookFormAsModal({ visible, onCancel }: { visible: boolean; onCan
|
||||
<DialogContent
|
||||
type={undefined}
|
||||
enableOverflow
|
||||
className="[&_.modalsticky]:border-t-subtle [&_.modalsticky]:bg-default max-h-[80vh] pt-6 pb-0 [&_.modalsticky]:sticky [&_.modalsticky]:bottom-0 [&_.modalsticky]:left-0 [&_.modalsticky]:right-0 [&_.modalsticky]:-mx-8 [&_.modalsticky]:border-t [&_.modalsticky]:px-6 [&_.modalsticky]:py-4">
|
||||
className="[&_.modalsticky]:border-t-subtle [&_.modalsticky]:bg-default dark:[&_.modalsticky]:bg-muted max-h-[80vh] pt-6 pb-0 [&_.modalsticky]:sticky [&_.modalsticky]:bottom-0 [&_.modalsticky]:left-0 [&_.modalsticky]:right-0 [&_.modalsticky]:-mx-8 [&_.modalsticky]:border-t [&_.modalsticky]:px-6 [&_.modalsticky]:py-4">
|
||||
<h1 className="font-cal text-emphasis text-xl leading-5">{t("confirm_your_details")} </h1>
|
||||
<div className="mt-4 flex space-x-2 rounded-md leading-none">
|
||||
<Badge variant="grayWithoutHover" startIcon={Calendar} size="lg">
|
||||
|
||||
@@ -11,7 +11,7 @@ export const LargeCalendar = () => {
|
||||
);
|
||||
|
||||
return (
|
||||
<div className="bg-default flex h-full w-full flex-col items-center justify-center">
|
||||
<div className="bg-default dark:bg-muted flex h-full w-full flex-col items-center justify-center">
|
||||
Something big is coming...
|
||||
<br />
|
||||
<button
|
||||
|
||||
@@ -2,7 +2,7 @@ import { useLocale } from "@calcom/lib/hooks/useLocale";
|
||||
|
||||
const UnAvailableMessage = ({ children, title }: { children: React.ReactNode; title: string }) => (
|
||||
<div className="mx-auto w-full max-w-2xl">
|
||||
<div className="border-subtle bg-default overflow-hidden rounded-lg border p-10">
|
||||
<div className="border-subtle bg-default dark:bg-muted overflow-hidden rounded-lg border p-10">
|
||||
<h2 className="font-cal mb-4 text-3xl">{title}</h2>
|
||||
{children}
|
||||
</div>
|
||||
|
||||
@@ -39,7 +39,7 @@ export const AvailableTimes = ({
|
||||
|
||||
return (
|
||||
<div className={classNames("text-default", className)}>
|
||||
<header className="bg-muted before:bg-muted mb-5 flex w-full flex-row items-center font-medium">
|
||||
<header className="bg-default before:bg-default dark:bg-muted dark:before:bg-muted mb-5 flex w-full flex-row items-center font-medium">
|
||||
<span className={classNames(isLargeTimeslots && "w-full text-center")}>
|
||||
<span className="text-emphasis font-semibold">
|
||||
{nameOfDay(i18n.language, Number(date.format("d")), "short")}
|
||||
|
||||
@@ -468,6 +468,13 @@ function getBookingData({
|
||||
if (val.responses) {
|
||||
const unwantedProps: string[] = [];
|
||||
legacyProps.forEach((legacyProp) => {
|
||||
if (typeof val[legacyProp as keyof typeof val] !== "undefined") {
|
||||
console.error(
|
||||
`Deprecated: Unexpected falsy value for: ${unwantedProps.join(
|
||||
","
|
||||
)}. They can't be used with \`responses\`. This will become a 400 error in the future.`
|
||||
);
|
||||
}
|
||||
if (val[legacyProp as keyof typeof val]) {
|
||||
unwantedProps.push(legacyProp);
|
||||
}
|
||||
@@ -489,8 +496,24 @@ function getBookingData({
|
||||
}
|
||||
}
|
||||
});
|
||||
debugger;
|
||||
|
||||
const reqBody = bookingDataSchema.parse(req.body);
|
||||
|
||||
// Work with Typescript to require reqBody.end
|
||||
type ReqBodyWithoutEnd = z.infer<typeof bookingDataSchema>;
|
||||
type ReqBodyWithEnd = ReqBodyWithoutEnd & { end: string };
|
||||
|
||||
const reqBodyWithEnd = (reqBody: ReqBodyWithoutEnd): reqBody is ReqBodyWithEnd => {
|
||||
// Use the event length to auto-set the event end time.
|
||||
if (!Object.prototype.hasOwnProperty.call(reqBody, "end")) {
|
||||
reqBody.end = dayjs.utc(reqBody.start).add(eventType.length, "minutes").format();
|
||||
}
|
||||
return true;
|
||||
};
|
||||
if (!reqBodyWithEnd(reqBody)) {
|
||||
throw new Error("Internal Error.");
|
||||
}
|
||||
// reqBody.end is no longer an optional property.
|
||||
if ("customInputs" in reqBody) {
|
||||
if (reqBody.customInputs) {
|
||||
// Check if required custom inputs exist
|
||||
@@ -585,7 +608,6 @@ async function handler(
|
||||
...eventType,
|
||||
bookingFields: getBookingFieldsWithSystemFields(eventType),
|
||||
};
|
||||
debugger;
|
||||
const {
|
||||
recurringCount,
|
||||
allRecurringDates,
|
||||
@@ -669,7 +691,7 @@ async function handler(
|
||||
metadata: true,
|
||||
},
|
||||
})
|
||||
: !!eventType.hosts?.length
|
||||
: eventType.hosts?.length
|
||||
? eventType.hosts.map(({ user, isFixed }) => ({
|
||||
...user,
|
||||
isFixed,
|
||||
@@ -733,14 +755,17 @@ async function handler(
|
||||
defaultLocationUrl = firstUsersMetadata?.defaultConferencingApp?.appLink;
|
||||
}
|
||||
|
||||
if (eventType && eventType.hasOwnProperty("bookingLimits") && eventType?.bookingLimits) {
|
||||
if (
|
||||
Object.prototype.hasOwnProperty.call(eventType, "bookingLimits") ||
|
||||
Object.prototype.hasOwnProperty.call(eventType, "durationLimits")
|
||||
) {
|
||||
const startAsDate = dayjs(reqBody.start).toDate();
|
||||
await checkBookingLimits(eventType.bookingLimits, startAsDate, eventType.id);
|
||||
}
|
||||
|
||||
if (eventType && eventType.hasOwnProperty("durationLimits") && eventType?.durationLimits) {
|
||||
const startAsDate = dayjs(reqBody.start).toDate();
|
||||
await checkDurationLimits(eventType.durationLimits, startAsDate, eventType.id);
|
||||
if (eventType.bookingLimits) {
|
||||
await checkBookingLimits(eventType.bookingLimits, startAsDate, eventType.id);
|
||||
}
|
||||
if (eventType.durationLimits) {
|
||||
await checkDurationLimits(eventType.durationLimits, startAsDate, eventType.id);
|
||||
}
|
||||
}
|
||||
|
||||
if (!eventType.seatsPerTimeSlot) {
|
||||
@@ -913,7 +938,7 @@ async function handler(
|
||||
requiresConfirmation: requiresConfirmation ?? false,
|
||||
eventTypeId: eventType.id,
|
||||
// if seats are not enabled we should default true
|
||||
seatsShowAttendees: !!eventType.seatsPerTimeSlot ? eventType.seatsShowAttendees : true,
|
||||
seatsShowAttendees: eventType.seatsPerTimeSlot ? eventType.seatsShowAttendees : true,
|
||||
seatsPerTimeSlot: eventType.seatsPerTimeSlot,
|
||||
};
|
||||
|
||||
|
||||
@@ -1,17 +1,27 @@
|
||||
import { Suspense } from "react";
|
||||
|
||||
import NoSSR from "@calcom/core/components/NoSSR";
|
||||
import { Meta } from "@calcom/ui";
|
||||
import { Loader } from "@calcom/ui/components/icon";
|
||||
import { Meta, SkeletonText, SkeletonContainer } from "@calcom/ui";
|
||||
|
||||
import { FlagAdminList } from "../components/FlagAdminList";
|
||||
|
||||
const SkeletonLoader = () => {
|
||||
return (
|
||||
<SkeletonContainer>
|
||||
<div className="divide-subtle mt-6 mb-8 space-y-6">
|
||||
<SkeletonText className="h-8 w-full" />
|
||||
<SkeletonText className="h-8 w-full" />
|
||||
</div>
|
||||
</SkeletonContainer>
|
||||
);
|
||||
};
|
||||
|
||||
export const FlagListingView = () => {
|
||||
return (
|
||||
<>
|
||||
<Meta title="Feature Flags" description="Here you can toggle your Cal.com instance features." />
|
||||
<NoSSR>
|
||||
<Suspense fallback={<Loader />}>
|
||||
<Suspense fallback={<SkeletonLoader />}>
|
||||
<FlagAdminList />
|
||||
</Suspense>
|
||||
</NoSSR>
|
||||
|
||||
@@ -29,7 +29,7 @@ export const DateSelect = () => {
|
||||
range &&
|
||||
(range === "tdy" || range === "w" || range === "t" || range === "m" || range === "y")
|
||||
) {
|
||||
setDateRange([dayjs(start), dayjs(end), range]);
|
||||
setDateRange([dayjs(start).startOf("d"), dayjs(end).endOf("d"), range]);
|
||||
return;
|
||||
} else if (start && !end) {
|
||||
// If only start time has value that means selected date should push to dateRange with last value null
|
||||
|
||||
@@ -5,7 +5,7 @@ import Link from "next/link";
|
||||
import type { NextRouter } from "next/router";
|
||||
import { useRouter } from "next/router";
|
||||
import type { Dispatch, ReactNode, SetStateAction } from "react";
|
||||
import React, { Fragment, useEffect, useState, useRef, useLayoutEffect } from "react";
|
||||
import React, { Fragment, useEffect, useState, useRef } from "react";
|
||||
import { Toaster } from "react-hot-toast";
|
||||
|
||||
import dayjs from "@calcom/dayjs";
|
||||
@@ -21,6 +21,7 @@ import AdminPasswordBanner from "@calcom/features/users/components/AdminPassword
|
||||
import classNames from "@calcom/lib/classNames";
|
||||
import { APP_NAME, DESKTOP_APP_LINK, JOIN_SLACK, ROADMAP, WEBAPP_URL } from "@calcom/lib/constants";
|
||||
import getBrandColours from "@calcom/lib/getBrandColours";
|
||||
import { useIsomorphicLayoutEffect } from "@calcom/lib/hooks/useIsomorphicLayoutEffect";
|
||||
import { useLocale } from "@calcom/lib/hooks/useLocale";
|
||||
import { isKeyInObject } from "@calcom/lib/isKeyInObject";
|
||||
import { trpc } from "@calcom/trpc/react";
|
||||
@@ -143,7 +144,7 @@ const Layout = (props: LayoutProps) => {
|
||||
const bannerRef = useRef<HTMLDivElement | null>(null);
|
||||
const [bannersHeight, setBannersHeight] = useState<number>(0);
|
||||
|
||||
useLayoutEffect(() => {
|
||||
useIsomorphicLayoutEffect(() => {
|
||||
const resizeObserver = new ResizeObserver((entries) => {
|
||||
const { offsetHeight } = entries[0].target as HTMLElement;
|
||||
setBannersHeight(offsetHeight);
|
||||
@@ -641,34 +642,34 @@ const NavigationItem: React.FC<{
|
||||
return (
|
||||
<Fragment>
|
||||
<Tooltip side="right" content={t(item.name)} className="lg:hidden">
|
||||
<Link
|
||||
href={item.href}
|
||||
aria-label={t(item.name)}
|
||||
className={classNames(
|
||||
"hover:bg-emphasis [&[aria-current='page']]:bg-emphasis hover:text-emphasis text-default group flex items-center rounded-md py-2 px-3 text-sm font-medium",
|
||||
isChild
|
||||
? `[&[aria-current='page']]:text-emphasis hidden h-8 pl-16 lg:flex lg:pl-11 [&[aria-current='page']]:bg-transparent ${
|
||||
props.index === 0 ? "mt-0" : "mt-px"
|
||||
}`
|
||||
: "[&[aria-current='page']]:text-emphasis mt-0.5 text-sm"
|
||||
)}
|
||||
aria-current={current ? "page" : undefined}>
|
||||
{item.icon && (
|
||||
<item.icon
|
||||
className="h-4 w-4 flex-shrink-0 ltr:mr-2 rtl:ml-2 [&[aria-current='page']]:text-inherit"
|
||||
aria-hidden="true"
|
||||
aria-current={current ? "page" : undefined}
|
||||
/>
|
||||
)}
|
||||
{isLocaleReady ? (
|
||||
<span className="hidden w-full justify-between lg:flex">
|
||||
<div className="flex">{t(item.name)}</div>
|
||||
{item.badge && item.badge}
|
||||
</span>
|
||||
) : (
|
||||
<SkeletonText className="h-3 w-32" />
|
||||
)}
|
||||
</Link>
|
||||
<Link
|
||||
href={item.href}
|
||||
aria-label={t(item.name)}
|
||||
className={classNames(
|
||||
"hover:bg-emphasis [&[aria-current='page']]:bg-emphasis hover:text-emphasis text-default group flex items-center rounded-md py-2 px-3 text-sm font-medium",
|
||||
isChild
|
||||
? `[&[aria-current='page']]:text-emphasis hidden h-8 pl-16 lg:flex lg:pl-11 [&[aria-current='page']]:bg-transparent ${
|
||||
props.index === 0 ? "mt-0" : "mt-px"
|
||||
}`
|
||||
: "[&[aria-current='page']]:text-emphasis mt-0.5 text-sm"
|
||||
)}
|
||||
aria-current={current ? "page" : undefined}>
|
||||
{item.icon && (
|
||||
<item.icon
|
||||
className="h-4 w-4 flex-shrink-0 ltr:mr-2 rtl:ml-2 [&[aria-current='page']]:text-inherit"
|
||||
aria-hidden="true"
|
||||
aria-current={current ? "page" : undefined}
|
||||
/>
|
||||
)}
|
||||
{isLocaleReady ? (
|
||||
<span className="hidden w-full justify-between lg:flex">
|
||||
<div className="flex">{t(item.name)}</div>
|
||||
{item.badge && item.badge}
|
||||
</span>
|
||||
) : (
|
||||
<SkeletonText className="h-3 w-32" />
|
||||
)}
|
||||
</Link>
|
||||
</Tooltip>
|
||||
{item.child &&
|
||||
isCurrent({ router, isChild, item }) &&
|
||||
|
||||
@@ -4,6 +4,7 @@ type BrowserInfo = {
|
||||
referrer: string;
|
||||
title: string;
|
||||
query: string;
|
||||
origin: string;
|
||||
};
|
||||
|
||||
export const getBrowserInfo = (): Partial<BrowserInfo> => {
|
||||
@@ -16,5 +17,6 @@ export const getBrowserInfo = (): Partial<BrowserInfo> => {
|
||||
referrer: window.document?.referrer ?? undefined,
|
||||
title: window.document.title ?? undefined,
|
||||
query: window.document.location?.search,
|
||||
origin: window.document.location?.origin,
|
||||
};
|
||||
};
|
||||
|
||||
@@ -0,0 +1,3 @@
|
||||
import { useEffect, useLayoutEffect } from "react";
|
||||
|
||||
export const useIsomorphicLayoutEffect = typeof document !== "undefined" ? useLayoutEffect : useEffect;
|
||||
@@ -1,4 +1,5 @@
|
||||
import type { DefaultSeoProps, NextSeoProps } from "next-seo";
|
||||
import type { Router } from "next/router";
|
||||
|
||||
import { APP_NAME, SEO_IMG_DEFAULT, SEO_IMG_OGIMG } from "@calcom/lib/constants";
|
||||
|
||||
@@ -39,3 +40,13 @@ export const seoConfig: {
|
||||
},
|
||||
},
|
||||
} as const;
|
||||
|
||||
/**
|
||||
* This function builds a canonical URL from a given host and path omitting the query params. Note: on homepage it omits the trailing slash
|
||||
* @param origin The protocol + host, e.g. `https://cal.com` or `https://cal.dev`
|
||||
* @param path NextJS' useRouter().asPath
|
||||
* @returns
|
||||
*/
|
||||
export const buildCanonical = ({ origin, path }: { origin: Location["origin"]; path: Router["asPath"] }) => {
|
||||
return `${origin}${path === "/" ? "" : path}`.split("?")[0];
|
||||
};
|
||||
|
||||
@@ -161,7 +161,7 @@ export const stringOrNumber = z.union([
|
||||
export const stringToDayjs = z.string().transform((val) => dayjs(val));
|
||||
|
||||
export const bookingCreateBodySchema = z.object({
|
||||
end: z.string(),
|
||||
end: z.string().optional(),
|
||||
eventTypeId: z.number(),
|
||||
eventTypeSlug: z.string().optional(),
|
||||
rescheduleUid: z.string().optional(),
|
||||
|
||||
@@ -5,6 +5,7 @@ import type { NextApiResponse, GetServerSidePropsContext } from "next";
|
||||
import { stripeDataSchema } from "@calcom/app-store/stripepayment/lib/server";
|
||||
import updateChildrenEventTypes from "@calcom/features/ee/managed-event-types/lib/handleChildrenEventTypes";
|
||||
import { validateIntervalLimitOrder } from "@calcom/lib";
|
||||
import logger from "@calcom/lib/logger";
|
||||
import { WorkflowActions, WorkflowTriggerEvents } from "@calcom/prisma/client";
|
||||
import { SchedulingType } from "@calcom/prisma/enums";
|
||||
|
||||
@@ -287,7 +288,12 @@ export const updateHandler = async ({ ctx, input }: UpdateOptions) => {
|
||||
});
|
||||
const res = ctx.res as NextApiResponse;
|
||||
if (typeof res?.revalidate !== "undefined") {
|
||||
await res?.revalidate(`/${ctx.user.username}/${eventType.slug}`);
|
||||
try {
|
||||
await res?.revalidate(`/${ctx.user.username}/${eventType.slug}`);
|
||||
} catch (e) {
|
||||
// if reach this it is because the event type page has not been created, so it is not possible to revalidate it
|
||||
logger.debug((e as Error)?.message);
|
||||
}
|
||||
}
|
||||
return { eventType };
|
||||
};
|
||||
|
||||
+3
-3
@@ -1,7 +1,7 @@
|
||||
import { GetServerSidePropsContext, GetServerSidePropsResult } from "next";
|
||||
import { CalendsoSessionUser } from "next-auth";
|
||||
import type { GetServerSidePropsContext, GetServerSidePropsResult } from "next";
|
||||
import type { CalendsoSessionUser } from "next-auth";
|
||||
|
||||
import prisma from "@calcom/prisma";
|
||||
import type prisma from "@calcom/prisma";
|
||||
|
||||
import type { ssrInit } from "@server/lib/ssr";
|
||||
|
||||
|
||||
@@ -30,7 +30,7 @@ const ColorPicker = (props: ColorPickerProps) => {
|
||||
return (
|
||||
<div className="mt-1 flex h-[38px] items-center justify-center">
|
||||
<Popover.Root>
|
||||
<div className="border-default flex h-full w-10 items-center items-center justify-center border ltr:rounded-l-md ltr:border-r-0 rtl:rounded-r-md rtl:border-l-0">
|
||||
<div className="border-default min-w-9 flex h-full items-center justify-center border ltr:rounded-l-md ltr:border-r-0 rtl:rounded-r-md rtl:border-l-0">
|
||||
<Popover.Trigger asChild>
|
||||
<button
|
||||
className="h-5 w-5 rounded-sm"
|
||||
|
||||
@@ -6,8 +6,8 @@ import { useRouter } from "next/router";
|
||||
import type { AppImageProps, MeetingImageProps } from "@calcom/lib/OgImages";
|
||||
import { constructAppImage, constructGenericImage, constructMeetingImage } from "@calcom/lib/OgImages";
|
||||
import { getBrowserInfo } from "@calcom/lib/browser/browser.utils";
|
||||
import { APP_NAME } from "@calcom/lib/constants";
|
||||
import { seoConfig, getSeoImage } from "@calcom/lib/next-seo.config";
|
||||
import { APP_NAME, WEBSITE_URL } from "@calcom/lib/constants";
|
||||
import { seoConfig, getSeoImage, buildCanonical } from "@calcom/lib/next-seo.config";
|
||||
import { truncateOnWord } from "@calcom/lib/text";
|
||||
|
||||
export type HeadSeoProps = {
|
||||
@@ -73,16 +73,17 @@ export const HeadSeo = (props: HeadSeoProps): JSX.Element => {
|
||||
// The below code sets the defaultUrl for our canonical tags
|
||||
|
||||
// Get the current URL from the window object
|
||||
const url = getBrowserInfo()?.url;
|
||||
const { url } = getBrowserInfo();
|
||||
// Check if the URL is from cal.com
|
||||
const isCalcom =
|
||||
url && (new URL(url).hostname.endsWith("cal.com") || new URL(url).hostname.endsWith("cal.dev"));
|
||||
// Get the router's path
|
||||
const path = useRouter().asPath;
|
||||
// Build the canonical URL using the router's path, without query parameters. Note: on homepage it omits the trailing slash
|
||||
const calcomCanonical = `https://cal.com${path === "/" ? "" : path}`.split("?")[0];
|
||||
const selfHostedOrigin = WEBSITE_URL || "https://cal.com";
|
||||
// Set the default URL to either the current URL (if self-hosted) or https://cal.com canonical URL
|
||||
const defaultUrl = isCalcom ? calcomCanonical : url;
|
||||
const defaultUrl = isCalcom
|
||||
? buildCanonical({ path, origin: "https://cal.com" })
|
||||
: buildCanonical({ path, origin: selfHostedOrigin });
|
||||
|
||||
const {
|
||||
title,
|
||||
|
||||
@@ -30,7 +30,9 @@ function BasePhoneInput({ name, className = "", onChange, ...rest }: PhoneInputP
|
||||
required: rest.required,
|
||||
placeholder: rest.placeholder,
|
||||
}}
|
||||
onChange={(value) => onChange(value)}
|
||||
onChange={(value) => {
|
||||
onChange("+" + value);
|
||||
}}
|
||||
containerClass={classNames(
|
||||
"hover:border-emphasis dark:focus:border-emphasis border-default !bg-default rounded-md border focus-within:outline-none focus-within:ring-2 focus-within:ring-brand-default disabled:cursor-not-allowed",
|
||||
className
|
||||
@@ -63,7 +65,7 @@ const useDefaultCountry = () => {
|
||||
retry: false,
|
||||
onSuccess: (data) => {
|
||||
if (isSupportedCountry(data?.countryCode)) {
|
||||
setDefaultCountry(data.countryCode);
|
||||
setDefaultCountry(data.countryCode.toLowerCase());
|
||||
}
|
||||
},
|
||||
});
|
||||
|
||||
@@ -104,7 +104,7 @@ const config: PlaywrightTestConfig = {
|
||||
{
|
||||
name: "@calcom/embed-core",
|
||||
testDir: "./packages/embeds/embed-core/",
|
||||
testMatch: /.*\.(e2e|test)\.tsx?/,
|
||||
testMatch: /.*\.e2e\.tsx?/,
|
||||
expect: {
|
||||
timeout: DEFAULT_EXPECT_TIMEOUT,
|
||||
},
|
||||
@@ -116,7 +116,7 @@ const config: PlaywrightTestConfig = {
|
||||
expect: {
|
||||
timeout: DEFAULT_EXPECT_TIMEOUT,
|
||||
},
|
||||
testMatch: /.*\.(e2e|test)\.tsx?/,
|
||||
testMatch: /.*\.e2e\.tsx?/,
|
||||
use: { ...devices["Desktop Chrome"], baseURL: "http://localhost:3101/" },
|
||||
},
|
||||
{
|
||||
|
||||
+29
-17
@@ -1,20 +1,32 @@
|
||||
import { defineWorkspace } from "vitest/config";
|
||||
|
||||
const packagedEmbedTestsOnly = process.argv.includes("--packaged-embed-tests-only");
|
||||
// defineWorkspace provides a nice type hinting DX
|
||||
export default defineWorkspace([
|
||||
{
|
||||
test: {
|
||||
include: ["packages/**/*.{test,spec}.{ts,js}", "apps/**/*.{test,spec}.{ts,js}"],
|
||||
// TODO: Ignore the api until tests are fixed
|
||||
exclude: ["apps/api/**/*", "**/node_modules/**/*"],
|
||||
},
|
||||
},
|
||||
{
|
||||
test: {
|
||||
name: "@calcom/closecom",
|
||||
include: ["packages/app-store/closecom/**/*.{test,spec}.{ts,js}"],
|
||||
environment: "jsdom",
|
||||
setupFiles: ["packages/app-store/closecom/test/globals.ts"],
|
||||
},
|
||||
},
|
||||
]);
|
||||
const workspaces = packagedEmbedTestsOnly
|
||||
? [
|
||||
{
|
||||
test: {
|
||||
include: ["packages/embeds/**/*.{test,spec}.{ts,js}"],
|
||||
environment: "jsdom",
|
||||
},
|
||||
},
|
||||
]
|
||||
: [
|
||||
{
|
||||
test: {
|
||||
include: ["packages/**/*.{test,spec}.{ts,js}", "apps/**/*.{test,spec}.{ts,js}"],
|
||||
// TODO: Ignore the api until tests are fixed
|
||||
exclude: ["apps/api/**/*", "**/node_modules/**/*", "packages/embeds/**/*"],
|
||||
},
|
||||
},
|
||||
{
|
||||
test: {
|
||||
name: "@calcom/closecom",
|
||||
include: ["packages/app-store/closecom/**/*.{test,spec}.{ts,js}"],
|
||||
environment: "jsdom",
|
||||
setupFiles: ["packages/app-store/closecom/test/globals.ts"],
|
||||
},
|
||||
},
|
||||
];
|
||||
|
||||
export default defineWorkspace(workspaces);
|
||||
|
||||
@@ -5088,7 +5088,7 @@ __metadata:
|
||||
next: ~13.2.1
|
||||
next-auth: ^4.20.1
|
||||
next-i18next: ^11.3.0
|
||||
playwright: ^1.31.2
|
||||
playwright-core: ^1.34.3
|
||||
postcss: ^8.4.18
|
||||
prism-react-renderer: ^1.3.5
|
||||
react: ^18.2.0
|
||||
@@ -16833,7 +16833,7 @@ __metadata:
|
||||
lucide-react: ^0.171.0
|
||||
prettier: ^2.8.6
|
||||
tsc-absolute: ^1.0.0
|
||||
turbo: ^1.8.3
|
||||
turbo: ^1.10.1
|
||||
typescript: ^4.9.4
|
||||
vitest: ^0.31.1
|
||||
vitest-mock-extended: ^1.1.3
|
||||
@@ -30849,23 +30849,12 @@ __metadata:
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
"playwright-core@npm:1.33.0":
|
||||
version: 1.33.0
|
||||
resolution: "playwright-core@npm:1.33.0"
|
||||
"playwright-core@npm:^1.34.3":
|
||||
version: 1.34.3
|
||||
resolution: "playwright-core@npm:1.34.3"
|
||||
bin:
|
||||
playwright: cli.js
|
||||
checksum: 5fb7bda06a8b73b56b85b5a0b8f711211dde57a375d9379289e22239b2de879c6d93c8fdc9ba44b932bf100914ab1ca1a55697ad88440fdd0a39101fc020b77f
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
"playwright@npm:^1.31.2":
|
||||
version: 1.33.0
|
||||
resolution: "playwright@npm:1.33.0"
|
||||
dependencies:
|
||||
playwright-core: 1.33.0
|
||||
bin:
|
||||
playwright: cli.js
|
||||
checksum: fb3934c56ed749cf412ea35b82052f013872d7f7b7747ab7042580af473dfc1b038956a31c132ee8c0098f74150c75e2073e9c737fd4c1ed54700a23fffc079f
|
||||
playwright-core: cli.js
|
||||
checksum: eaf9e9b2d77b9726867dcbc641a1c72b0e8f680cdd71ff904366deea1c96141ff7563f6c6fb29f9975309d1b87dead97ea93f6f44953b59946882fb785b34867
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
@@ -37147,58 +37136,58 @@ __metadata:
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
"turbo-darwin-64@npm:1.8.3":
|
||||
version: 1.8.3
|
||||
resolution: "turbo-darwin-64@npm:1.8.3"
|
||||
"turbo-darwin-64@npm:1.10.1":
|
||||
version: 1.10.1
|
||||
resolution: "turbo-darwin-64@npm:1.10.1"
|
||||
conditions: os=darwin & cpu=x64
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
"turbo-darwin-arm64@npm:1.8.3":
|
||||
version: 1.8.3
|
||||
resolution: "turbo-darwin-arm64@npm:1.8.3"
|
||||
"turbo-darwin-arm64@npm:1.10.1":
|
||||
version: 1.10.1
|
||||
resolution: "turbo-darwin-arm64@npm:1.10.1"
|
||||
conditions: os=darwin & cpu=arm64
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
"turbo-linux-64@npm:1.8.3":
|
||||
version: 1.8.3
|
||||
resolution: "turbo-linux-64@npm:1.8.3"
|
||||
"turbo-linux-64@npm:1.10.1":
|
||||
version: 1.10.1
|
||||
resolution: "turbo-linux-64@npm:1.10.1"
|
||||
conditions: os=linux & cpu=x64
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
"turbo-linux-arm64@npm:1.8.3":
|
||||
version: 1.8.3
|
||||
resolution: "turbo-linux-arm64@npm:1.8.3"
|
||||
"turbo-linux-arm64@npm:1.10.1":
|
||||
version: 1.10.1
|
||||
resolution: "turbo-linux-arm64@npm:1.10.1"
|
||||
conditions: os=linux & cpu=arm64
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
"turbo-windows-64@npm:1.8.3":
|
||||
version: 1.8.3
|
||||
resolution: "turbo-windows-64@npm:1.8.3"
|
||||
"turbo-windows-64@npm:1.10.1":
|
||||
version: 1.10.1
|
||||
resolution: "turbo-windows-64@npm:1.10.1"
|
||||
conditions: os=win32 & cpu=x64
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
"turbo-windows-arm64@npm:1.8.3":
|
||||
version: 1.8.3
|
||||
resolution: "turbo-windows-arm64@npm:1.8.3"
|
||||
"turbo-windows-arm64@npm:1.10.1":
|
||||
version: 1.10.1
|
||||
resolution: "turbo-windows-arm64@npm:1.10.1"
|
||||
conditions: os=win32 & cpu=arm64
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
"turbo@npm:^1.8.3":
|
||||
version: 1.8.3
|
||||
resolution: "turbo@npm:1.8.3"
|
||||
"turbo@npm:^1.10.1":
|
||||
version: 1.10.1
|
||||
resolution: "turbo@npm:1.10.1"
|
||||
dependencies:
|
||||
turbo-darwin-64: 1.8.3
|
||||
turbo-darwin-arm64: 1.8.3
|
||||
turbo-linux-64: 1.8.3
|
||||
turbo-linux-arm64: 1.8.3
|
||||
turbo-windows-64: 1.8.3
|
||||
turbo-windows-arm64: 1.8.3
|
||||
turbo-darwin-64: 1.10.1
|
||||
turbo-darwin-arm64: 1.10.1
|
||||
turbo-linux-64: 1.10.1
|
||||
turbo-linux-arm64: 1.10.1
|
||||
turbo-windows-64: 1.10.1
|
||||
turbo-windows-arm64: 1.10.1
|
||||
dependenciesMeta:
|
||||
turbo-darwin-64:
|
||||
optional: true
|
||||
@@ -37214,7 +37203,7 @@ __metadata:
|
||||
optional: true
|
||||
bin:
|
||||
turbo: bin/turbo
|
||||
checksum: 4a07d120ef8adf6c8e58a48abd02e075ffa215287cc6c3ef843d4fb08aeb0a566fe810ec9bfc376254468a2aa4f29bae154a60804a83af78dfa86d0e8e995476
|
||||
checksum: 3551c57be67e833583de549d74d5ec991e0f1e38047e25600b8acc085a1a8a4007992259a015eb9f2c0abd68156545800bd34e7b5ad337d1998b32461952d3fb
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
|
||||
Reference in New Issue
Block a user