Merge branch 'main' into bugfix/console-error-for-user-errors

This commit is contained in:
Peer Richelsen 2022-10-19 22:08:20 +01:00 committed by GitHub
commit 57cb427d2b
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
218 changed files with 4787 additions and 3809 deletions

View File

@ -37,7 +37,7 @@
<a href="https://hub.docker.com/r/calendso/calendso"><img src="https://img.shields.io/docker/pulls/calendso/calendso"></a>
<a href="https://twitter.com/calcom"><img src="https://img.shields.io/twitter/follow/calcom?style=flat"></a>
<a href="https://twitch.tv/calcomtv"><img src="https://img.shields.io/twitch/status/calcomtv?style=flat"></a>
<a href="https://github.com/orgs/calcom/projects/1/views/25"><img src="https://img.shields.io/badge/Help%20Wanted-Contribute-blue"></a>
<a href="https://github.com/orgs/calcom/projects/9"><img src="https://img.shields.io/badge/Help%20Wanted-Contribute-blue"></a>
<a href="https://cal.com/figma"><img src="https://img.shields.io/badge/Figma-Design%20System-blueviolet"></a>
<a href="https://calendso.slack.com/archives/C02BY67GMMW"><img src="https://img.shields.io/badge/translations-contribute-brightgreen" /></a>
<a href="https://www.contributor-covenant.org/version/1/4/code-of-conduct/ "><img src="https://img.shields.io/badge/Contributor%20Covenant-1.4-purple" /></a>

View File

@ -1,62 +0,0 @@
import { signIn } from "next-auth/react";
import { Dispatch, SetStateAction } from "react";
import { useFormContext } from "react-hook-form";
import { collectPageParameters, telemetryEventTypes, useTelemetry } from "@calcom/lib/telemetry";
import { trpc } from "@calcom/trpc/react";
import Button from "@calcom/ui/Button";
import { useLocale } from "@lib/hooks/useLocale";
interface Props {
email: string;
samlTenantID: string;
samlProductID: string;
hostedCal: boolean;
setErrorMessage: Dispatch<SetStateAction<string | null>>;
}
export default function SAMLLogin(props: Props) {
const { t } = useLocale();
const methods = useFormContext();
const telemetry = useTelemetry();
const mutation = trpc.useMutation("viewer.public.samlTenantProduct", {
onSuccess: async (data) => {
await signIn("saml", {}, { tenant: data.tenant, product: data.product });
},
onError: (err) => {
props.setErrorMessage(err.message);
},
});
return (
<div className="mt-5">
<Button
color="secondary"
data-testid="saml"
className="flex w-full justify-center"
onClick={async (event) => {
event.preventDefault();
// track Google logins. Without personal data/payload
telemetry.event(telemetryEventTypes.googleLogin, collectPageParameters());
if (!props.hostedCal) {
await signIn("saml", {}, { tenant: props.samlTenantID, product: props.samlProductID });
} else {
if (props.email.length === 0) {
props.setErrorMessage(t("saml_email_required"));
return;
}
// hosted solution, fetch tenant and product from the backend
mutation.mutate({
email: methods.getValues("email"),
});
}
}}>
{t("signin_with_saml")}
</Button>
</div>
);
}

View File

@ -1,33 +1,41 @@
import { getEventLocationType, locationKeyToString } from "@calcom/app-store/locations";
import { classNames } from "@calcom/lib";
import Tooltip from "@calcom/ui/v2/core/Tooltip";
import { Props } from "./pages/AvailabilityPage";
export function AvailableEventLocations({ locations }: { locations: Props["eventType"]["locations"] }) {
return locations.length ? (
<div>
<div className="dark:text-darkgray-600 mr-6 flex w-full flex-col space-y-2 break-words text-sm text-gray-600">
{locations.map((location) => {
const eventLocationType = getEventLocationType(location.type);
if (!eventLocationType) {
// It's possible that the location app got uninstalled
return null;
}
return (
<div key={location.type} className="flex flex-row items-center text-sm font-medium">
<img
src={eventLocationType.iconUrl}
className={classNames(
"mr-[10px] ml-[2px] h-4 w-4 opacity-70 dark:opacity-100 ",
!eventLocationType.iconUrl?.includes("api") ? "dark:invert" : ""
)}
alt={`${eventLocationType.label} icon`}
/>
<span key={location.type}>{locationKeyToString(location)} </span>
</div>
);
})}
</div>
<div className="dark:text-darkgray-600 mr-6 flex w-full flex-col space-y-2 break-words text-sm text-gray-600">
{locations.map((location) => {
const eventLocationType = getEventLocationType(location.type);
if (!eventLocationType) {
// It's possible that the location app got uninstalled
return null;
}
return (
<div key={location.type} className="flex flex-row items-center text-sm font-medium">
<img
src={eventLocationType.iconUrl}
className={classNames(
"mr-[10px] ml-[2px] h-4 w-4 opacity-70 dark:opacity-100 ",
!eventLocationType.iconUrl?.includes("api") ? "dark:invert" : ""
)}
alt={`${eventLocationType.label} icon`}
/>
<Tooltip content={locationKeyToString(location)}>
<a
target="_blank"
href={locationKeyToString(location) ?? "/"}
className="truncate"
key={location.type}
rel="noreferrer">
{locationKeyToString(location)}
</a>
</Tooltip>
</div>
);
})}
</div>
) : (
<></>

View File

@ -34,6 +34,7 @@ interface Props {
isBookingPage?: boolean;
children: ReactNode;
isMobile?: boolean;
rescheduleUid?: string;
}
const BookingDescription: FC<Props> = (props) => {
@ -84,11 +85,9 @@ const BookingDescription: FC<Props> = (props) => {
{t("requires_confirmation")}
</div>
)}
{!isBookingPage ? (
<AvailableEventLocations
locations={eventType.locations as AvailabilityPageProps["eventType"]["locations"]}
/>
) : null}
<AvailableEventLocations
locations={eventType.locations as AvailabilityPageProps["eventType"]["locations"]}
/>
<p
className={classNames(
"text-sm font-medium",

View File

@ -1,10 +1,11 @@
import { BookingStatus } from "@prisma/client";
import { useRouter } from "next/router";
import { useState } from "react";
import { useState, useRef } from "react";
import { EventLocationType, getEventLocationType } from "@calcom/app-store/locations";
import dayjs from "@calcom/dayjs";
import classNames from "@calcom/lib/classNames";
import { formatTime } from "@calcom/lib/date-fns";
import { useLocale } from "@calcom/lib/hooks/useLocale";
import showToast from "@calcom/lib/notification";
import { getEveryFreqFor } from "@calcom/lib/recurringStrings";
@ -15,9 +16,9 @@ import { Tooltip } from "@calcom/ui/Tooltip";
import { TextArea } from "@calcom/ui/form/fields";
import Badge from "@calcom/ui/v2/core/Badge";
import Button from "@calcom/ui/v2/core/Button";
import MeetingTimeInTimezones from "@calcom/ui/v2/core/MeetingTimeInTimezones";
import useMeQuery from "@lib/hooks/useMeQuery";
import { extractRecurringDates } from "@lib/parseDate";
import { EditLocationDialog } from "@components/dialog/EditLocationDialog";
import { RescheduleDialog } from "@components/dialog/RescheduleDialog";
@ -175,16 +176,17 @@ function BookingListItem(booking: BookingItemProps) {
};
// Calculate the booking date(s) and setup recurring event data to show
let recurringStrings: string[] = [];
let recurringDates: Date[] = [];
const recurringStrings: string[] = [];
const recurringDates: Date[] = [];
if (booking.recurringBookings !== undefined && booking.eventType.recurringEvent?.freq !== undefined) {
[recurringStrings, recurringDates] = extractRecurringDates(booking, user?.timeZone, i18n);
}
// @FIXME: This is importing the RRULE library which is already heavy. Find out a more optimal way do this.
// if (booking.recurringBookings !== undefined && booking.eventType.recurringEvent?.freq !== undefined) {
// [recurringStrings, recurringDates] = extractRecurringDates(booking, user?.timeZone, i18n);
// }
const location = booking.location || "";
const onClick = () => {
const onClickTableData = () => {
router.push({
pathname: "/success",
query: {
@ -205,6 +207,7 @@ function BookingListItem(booking: BookingItemProps) {
},
});
};
return (
<>
<RescheduleDialog
@ -253,17 +256,21 @@ function BookingListItem(booking: BookingItemProps) {
</Dialog>
<tr className="flex flex-col hover:bg-neutral-50 sm:flex-row">
<td className="hidden align-top ltr:pl-6 rtl:pr-6 sm:table-cell sm:min-w-[10rem]" onClick={onClick}>
<td
className="hidden align-top ltr:pl-6 rtl:pr-6 sm:table-cell sm:min-w-[12rem]"
onClick={onClickTableData}>
<div className="cursor-pointer py-4">
<div className="text-sm leading-6 text-gray-900">{startTime}</div>
<div className="text-sm text-gray-500">
{dayjs(booking.startTime)
.tz(user?.timeZone)
.format(user && user.timeFormat === 12 ? "h:mma" : "HH:mm")}{" "}
-{" "}
{dayjs(booking.endTime)
.tz(user?.timeZone)
.format(user && user.timeFormat === 12 ? "h:mma" : "HH:mm")}
{formatTime(booking.startTime, user?.timeFormat, user?.timeZone)} -{" "}
{formatTime(booking.endTime, user?.timeFormat, user?.timeZone)}
<MeetingTimeInTimezones
timeFormat={user?.timeFormat}
userTimezone={user?.timeZone}
startTime={booking.startTime}
endTime={booking.endTime}
attendees={booking.attendees}
/>
</div>
{isPending && (
@ -292,19 +299,21 @@ function BookingListItem(booking: BookingItemProps) {
</div>
</div>
</td>
<td className={"w-full px-4" + (isRejected ? " line-through" : "")} onClick={onClick}>
<td className={"w-full px-4" + (isRejected ? " line-through" : "")} onClick={onClickTableData}>
{/* Time and Badges for mobile */}
<div className="w-full pt-4 pb-2 sm:hidden">
<div className="flex w-full items-center justify-between sm:hidden">
<div className="text-sm leading-6 text-gray-900">{startTime}</div>
<div className="pr-2 text-sm text-gray-500">
{dayjs(booking.startTime)
.tz(user?.timeZone)
.format(user && user.timeFormat === 12 ? "h:mma" : "HH:mm")}{" "}
-{" "}
{dayjs(booking.endTime)
.tz(user?.timeZone)
.format(user && user.timeFormat === 12 ? "h:mma" : "HH:mm")}
{formatTime(booking.startTime, user?.timeFormat, user?.timeZone)} -{" "}
{formatTime(booking.endTime, user?.timeFormat, user?.timeZone)}
<MeetingTimeInTimezones
timeFormat={user?.timeFormat}
userTimezone={user?.timeZone}
startTime={booking.startTime}
endTime={booking.endTime}
attendees={booking.attendees}
/>
</div>
</div>

View File

@ -57,9 +57,9 @@ export default function CancelBooking(props: Props) {
className="mt-2 mb-3 w-full dark:border-gray-900 dark:bg-gray-700 dark:text-white sm:mb-3 "
rows={3}
/>
<div className=" flex flex-col-reverse rtl:space-x-reverse sm:flex-row">
<div className="flex flex-col-reverse rtl:space-x-reverse sm:flex-row">
{!props.recurringEvent && (
<div className=" border-bookinglightest mt-5 flex w-full justify-center border-t pt-3 sm:mt-0 sm:justify-start sm:border-0 sm:pt-0">
<div className="border-bookinglightest mt-5 flex w-full justify-center border-t pt-3 sm:mt-0 sm:justify-start sm:border-0 sm:pt-0">
<Button
color="secondary"
className="border-0 sm:border"
@ -80,7 +80,7 @@ export default function CancelBooking(props: Props) {
const payload = {
uid: booking?.uid,
reason: cancellationReason,
cancellationReason: cancellationReason,
};
telemetry.event(telemetryEventTypes.bookingCancelled, collectPageParameters());

View File

@ -371,7 +371,6 @@ const AvailabilityPage = ({ profile, eventType }: Props) => {
const rainbowAppData = getEventTypeAppData(eventType, "rainbow") || {};
const rawSlug = profile.slug ? profile.slug.split("/") : [];
if (rawSlug.length > 1) rawSlug.pop(); //team events have team name as slug, but user events have [user]/[type] as slug.
const slug = rawSlug.join("/");
// Define conditional gates here
const gates = [
@ -386,8 +385,16 @@ const AvailabilityPage = ({ profile, eventType }: Props) => {
<HeadSeo
title={`${rescheduleUid ? t("reschedule") : ""} ${eventType.title} | ${profile.name}`}
description={`${rescheduleUid ? t("reschedule") : ""} ${eventType.title}`}
name={profile.name || undefined}
username={slug || undefined}
meeting={{
title: eventType.title,
profile: { name: `${profile.name}`, image: profile.image },
users: [
...(eventType.users || []).map((user) => ({
name: `${user.name}`,
username: `${user.username}`,
})),
],
}}
nextSeoProps={{
nofollow: eventType.hidden,
noindex: eventType.hidden,
@ -419,7 +426,7 @@ const AvailabilityPage = ({ profile, eventType }: Props) => {
"min-w-full md:w-[280px] md:min-w-[280px]",
recurringEventCount && "xl:w-[380px] xl:min-w-[380px]"
)}>
<BookingDescription profile={profile} eventType={eventType}>
<BookingDescription profile={profile} eventType={eventType} rescheduleUid={rescheduleUid}>
{!rescheduleUid && eventType.recurringEvent && (
<div className="flex items-start text-sm font-medium">
<Icon.FiRefreshCcw className="float-left mr-[10px] mt-[7px] ml-[2px] inline-block h-4 w-4 " />

View File

@ -19,6 +19,7 @@ import {
getEventLocationValue,
getEventLocationType,
EventLocationType,
getHumanReadableLocationValue,
} from "@calcom/app-store/locations";
import { createPaymentLink } from "@calcom/app-store/stripepayment/lib/client";
import { getEventTypeAppData } from "@calcom/app-store/utils";
@ -53,8 +54,6 @@ import slugify from "@lib/slugify";
import Gates, { Gate, GateState } from "@components/Gates";
import BookingDescription from "@components/booking/BookingDescription";
import { UserAvatars } from "@components/booking/UserAvatars";
import EventTypeDescriptionSafeHTML from "@components/eventtype/EventTypeDescriptionSafeHTML";
import { BookPageProps } from "../../../pages/[user]/book";
import { HashLinkPageProps } from "../../../pages/d/[link]/book";
@ -116,14 +115,14 @@ const BookingPage = ({
const mutation = useMutation(createBooking, {
onSuccess: async (responseData) => {
const { id, attendees, paymentUid } = responseData;
const { id, paymentUid } = responseData;
if (paymentUid) {
return await router.push(
createPaymentLink({
paymentUid,
date,
name: attendees[0].name,
email: attendees[0].email,
name: bookingForm.getValues("name"),
email: bookingForm.getValues("email"),
absolute: false,
})
);
@ -137,8 +136,8 @@ const BookingPage = ({
eventSlug: eventType.slug,
username: profile.slug,
reschedule: !!rescheduleUid,
name: attendees[0].name,
email: attendees[0].email,
name: bookingForm.getValues("name"),
email: bookingForm.getValues("email"),
location: responseData.location,
eventName: profile.eventName || "",
bookingId: id,
@ -267,7 +266,8 @@ const BookingPage = ({
smsReminderNumber: z
.string()
.refine((val) => isValidPhoneNumber(val))
.optional(),
.optional()
.nullable(),
})
.passthrough();
@ -359,7 +359,9 @@ const BookingPage = ({
hasHashedBookingLink,
hashedLink,
smsReminderNumber:
selectedLocationType === LocationType.Phone ? booking.phone : booking.smsReminderNumber,
selectedLocationType === LocationType.Phone
? booking.phone
: booking.smsReminderNumber || undefined,
ethSignature: gateState.rainbowToken,
}));
recurringMutation.mutate(recurringBookings);
@ -387,7 +389,9 @@ const BookingPage = ({
hasHashedBookingLink,
hashedLink,
smsReminderNumber:
selectedLocationType === LocationType.Phone ? booking.phone : booking.smsReminderNumber,
selectedLocationType === LocationType.Phone
? booking.phone
: booking.smsReminderNumber || undefined,
ethSignature: gateState.rainbowToken,
});
}
@ -401,6 +405,7 @@ const BookingPage = ({
"dark:placeholder:text-darkgray-600 focus:border-brand dark:border-darkgray-300 dark:text-darkgray-900 block w-full rounded-md border-gray-300 text-sm focus:ring-black disabled:bg-gray-200 disabled:hover:cursor-not-allowed dark:bg-transparent dark:selection:bg-green-500 disabled:dark:text-gray-500";
let isSmsReminderNumberNeeded = false;
let isSmsReminderNumberRequired = false;
if (eventType.workflows.length > 0) {
eventType.workflows.forEach((workflowReference) => {
@ -408,6 +413,7 @@ const BookingPage = ({
workflowReference.workflow.steps.forEach((step) => {
if (step.action === WorkflowActions.SMS_ATTENDEE) {
isSmsReminderNumberNeeded = true;
isSmsReminderNumberRequired = step.numberRequired || false;
return;
}
});
@ -517,7 +523,15 @@ const BookingPage = ({
)}
{!!eventType.seatsPerTimeSlot && (
<div className="text-bookinghighlight flex items-start text-sm">
<Icon.FiUser className="mr-[10px] ml-[2px] mt-[2px] inline-block h-4 w-4" />
<Icon.FiUser
className={`mr-[10px] ml-[2px] mt-[2px] inline-block h-4 w-4 ${
booking && booking.attendees.length / eventType.seatsPerTimeSlot >= 0.5
? "text-rose-600"
: booking && booking.attendees.length / eventType.seatsPerTimeSlot >= 0.33
? "text-yellow-500"
: "text-bookinghighlight"
}`}
/>
<p
className={`${
booking && booking.attendees.length / eventType.seatsPerTimeSlot >= 0.5
@ -578,37 +592,51 @@ const BookingPage = ({
)}
</div>
</div>
{locations.length > 1 && (
<div className="mb-4">
<span className="block text-sm font-medium text-gray-700 dark:text-white">
{t("location")}
</span>
{locations.map((location, i) => {
const locationString = locationKeyToString(location);
// TODO: Right now selectedLocationType isn't send by getSSP. Once that's available defaultChecked should work and show the location in the original booking
const defaultChecked = rescheduleUid ? selectedLocationType === location.type : i === 0;
if (typeof locationString !== "string") {
// It's possible that location app got uninstalled
return null;
}
return (
<label key={i} className="block">
<input
type="radio"
disabled={!!disableLocations}
className="location dark:bg-darkgray-300 dark:border-darkgray-300 h-4 w-4 border-gray-300 text-black focus:ring-black ltr:mr-2 rtl:ml-2"
{...bookingForm.register("locationType", { required: true })}
value={location.type}
defaultChecked={defaultChecked}
/>
<span className="text-sm ltr:ml-2 rtl:mr-2 dark:text-white">
{locationKeyToString(location)}
</span>
</label>
);
})}
</div>
)}
<>
{rescheduleUid ? (
<div className="mb-4">
<span className="block text-sm font-medium text-gray-700 dark:text-white">
{t("location")}
</span>
<p className="mt-1 text-sm text-gray-500">
{getHumanReadableLocationValue(booking?.location, t)}
</p>
</div>
) : (
locations.length > 1 && (
<div className="mb-4">
<span className="block text-sm font-medium text-gray-700 dark:text-white">
{t("location")}
</span>
{locations.map((location, i) => {
const locationString = locationKeyToString(location);
if (!selectedLocationType) {
bookingForm.setValue("locationType", locations[0].type);
}
if (typeof locationString !== "string") {
// It's possible that location app got uninstalled
return null;
}
return (
<label key={i} className="block">
<input
type="radio"
disabled={!!disableLocations}
className="location dark:bg-darkgray-300 dark:border-darkgray-300 h-4 w-4 border-gray-300 text-black focus:ring-black ltr:mr-2 rtl:ml-2"
{...bookingForm.register("locationType", { required: true })}
value={location.type}
defaultChecked={i === 0}
/>
<span className="text-sm ltr:ml-2 rtl:mr-2 dark:text-white">
{locationKeyToString(location)}
</span>
</label>
);
})}
</div>
)
)}
</>
{/* TODO: Change name and id ="phone" to something generic */}
{AttendeeInput && (
<div className="mb-4">
@ -776,7 +804,7 @@ const BookingPage = ({
name="smsReminderNumber"
placeholder={t("enter_phone_number")}
id="smsReminderNumber"
required
required={isSmsReminderNumberRequired}
/>
</div>
{bookingForm.formState.errors.smsReminderNumber && (
@ -805,6 +833,7 @@ const BookingPage = ({
) : (
<textarea
{...bookingForm.register("notes")}
required={!!eventType.metadata.additionalNotesRequired}
id="notes"
name="notes"
rows={3}

View File

@ -1,5 +1,3 @@
import { useMutation } from "@tanstack/react-query";
import { RescheduleResponse } from "pages/api/book/request-reschedule";
import React, { useState, Dispatch, SetStateAction } from "react";
import { useLocale } from "@calcom/lib/hooks/useLocale";
@ -10,8 +8,6 @@ import { Icon } from "@calcom/ui/Icon";
import { TextArea } from "@calcom/ui/form/fields";
import Button from "@calcom/ui/v2/core/Button";
import * as fetchWrapper from "@lib/core/http/fetch-wrapper";
interface IRescheduleDialog {
isOpenDialog: boolean;
setIsOpenDialog: Dispatch<SetStateAction<boolean>>;
@ -23,35 +19,18 @@ export const RescheduleDialog = (props: IRescheduleDialog) => {
const utils = trpc.useContext();
const { isOpenDialog, setIsOpenDialog, bookingUId: bookingId } = props;
const [rescheduleReason, setRescheduleReason] = useState("");
const [isLoading, setIsLoading] = useState(false);
const rescheduleApi = useMutation(
async () => {
setIsLoading(true);
try {
const result = await fetchWrapper.post<
{ bookingId: string; rescheduleReason: string },
RescheduleResponse
>("/api/book/request-reschedule", {
bookingId,
rescheduleReason,
});
if (result) {
showToast(t("reschedule_request_sent"), "success");
setIsOpenDialog(false);
}
} catch (error) {
showToast(t("unexpected_error_try_again"), "error");
// @TODO: notify sentry
}
setIsLoading(false);
const { mutate: rescheduleApi, isLoading } = trpc.useMutation("viewer.bookings.requestReschedule", {
async onSuccess() {
showToast(t("reschedule_request_sent"), "success");
setIsOpenDialog(false);
await utils.invalidateQueries(["viewer.bookings"]);
},
{
async onSettled() {
await utils.invalidateQueries(["viewer.bookings"]);
},
}
);
onError() {
showToast(t("unexpected_error_try_again"), "error");
// @TODO: notify sentry
},
});
return (
<Dialog open={isOpenDialog} onOpenChange={setIsOpenDialog}>
@ -84,7 +63,10 @@ export const RescheduleDialog = (props: IRescheduleDialog) => {
data-testid="send_request"
disabled={isLoading}
onClick={() => {
rescheduleApi.mutate();
rescheduleApi({
bookingId,
rescheduleReason,
});
}}>
{t("send_reschedule_request")}
</Button>

View File

@ -22,12 +22,12 @@ const Steps = (props: ISteps) => {
key={`step-${index}`}
onClick={() => navigateToStep(index)}
className={classNames(
"h-1 w-1/4 rounded-[1px] bg-black dark:bg-white",
"h-1 w-full rounded-[1px] bg-black dark:bg-white",
index < currentStep ? "cursor-pointer" : ""
)}
/>
) : (
<div key={`step-${index}`} className="h-1 w-1/4 rounded-[1px] bg-black bg-opacity-25" />
<div key={`step-${index}`} className="h-1 w-full rounded-[1px] bg-black bg-opacity-25" />
);
})}
</div>

View File

@ -1,6 +1,13 @@
import merge from "lodash/merge";
import { NextSeo, NextSeoProps } from "next-seo";
import React from "react";
import {
AppImageProps,
constructAppImage,
constructMeetingImage,
MeetingImageProps,
} from "@calcom/lib/OgImages";
import { truncate, truncateOnWord } from "@calcom/lib/text";
import { getSeoImage, seoConfig } from "@lib/config/next-seo.config";
import { getBrowserInfo } from "@lib/core/browser/browser.utils";
@ -9,11 +16,11 @@ export type HeadSeoProps = {
title: string;
description: string;
siteName?: string;
name?: string;
url?: string;
username?: string;
canonical?: string;
nextSeoProps?: NextSeoProps;
app?: AppImageProps;
meeting?: MeetingImageProps;
};
/**
@ -63,32 +70,15 @@ const buildSeoMeta = (pageProps: {
};
};
const constructImage = (name: string, description: string, username: string): string => {
return (
encodeURIComponent("Meet **" + name + "** <br>" + description).replace(/'/g, "%27") +
".png?md=1&images=https%3A%2F%2Fcal.com%2Flogo-white.svg&images=" +
(process.env.NEXT_PUBLIC_WEBSITE_URL || process.env.NEXT_PUBLIC_WEBAPP_URL) +
"/" +
username +
"/avatar.png"
);
};
export const HeadSeo = (props: HeadSeoProps): JSX.Element => {
const defaultUrl = getBrowserInfo()?.url;
const image = getSeoImage("default");
const {
title,
description,
name = null,
username = null,
siteName,
canonical = defaultUrl,
nextSeoProps = {},
} = props;
const { title, description, siteName, canonical = defaultUrl, nextSeoProps = {}, app, meeting } = props;
const truncatedDescription = truncate(description, 24);
const longerTruncatedDescriptionOnWords = truncateOnWord(description, 148);
const truncatedDescription = description.length > 24 ? description.substring(0, 23) + "..." : description;
const pageTitle = title + " | Cal.com";
let seoObject = buildSeoMeta({
title: pageTitle,
@ -98,8 +88,20 @@ export const HeadSeo = (props: HeadSeoProps): JSX.Element => {
siteName,
});
if (name && username) {
const pageImage = getSeoImage("ogImage") + constructImage(name, truncatedDescription, username);
if (meeting) {
const pageImage = getSeoImage("ogImage") + constructMeetingImage(meeting);
seoObject = buildSeoMeta({
title: pageTitle,
description: truncatedDescription,
image: pageImage,
canonical,
siteName,
});
}
if (app) {
const pageImage =
getSeoImage("ogImage") + constructAppImage({ ...app, description: longerTruncatedDescriptionOnWords });
seoObject = buildSeoMeta({
title: pageTitle,
description: truncatedDescription,

View File

@ -27,7 +27,7 @@ const ImageOption = (optionProps: OptionProps<{ [key: string]: string; type: App
{data.image && (
<img className="float-left mr-3 inline h-5 w-5" src={data.image} alt={data.label} />
)}
<p>{data.label}</p>
<p>{`${t("add")} ${data.label}`}</p>
</Button>
);
}}
@ -35,7 +35,7 @@ const ImageOption = (optionProps: OptionProps<{ [key: string]: string; type: App
) : (
<Button className="w-full" color="minimal" href="/apps/categories/calendar">
<Icon.FiPlus className="text-color mr-3 ml-1 h-4 w-4" />
<p>{t("add_new_calendar")}...</p>
<p>{t("install_new_calendar_app")}</p>
</Button>
);
};
@ -63,11 +63,43 @@ const AdditionalCalendarSelector = ({ isLoading }: AdditionalCalendarSelectorPro
return (
<Select
name="additionalCalendar"
placeholder={t("install_another")}
placeholder={
<Button className="rounded-md" StartIcon={Icon.FiPlus} color="secondary">
{t("add")}
</Button>
}
options={options}
isSearchable={false}
isLoading={isLoading}
components={{ Option: ImageOption }}
styles={{
menu: (defaultStyles) => ({
...defaultStyles,
"@media only screen and (max-width: 640px)": {
left: "0",
},
width: "max-content",
right: "0",
}),
control: (defaultStyles) => ({
...defaultStyles,
padding: "0",
border: "0",
borderRadius: "inherit",
}),
dropdownIndicator: (defaultStyles) => ({
...defaultStyles,
display: "none",
}),
valueContainer: (defaultStyles) => ({
...defaultStyles,
padding: "0",
}),
placeholder: (defaultStyles) => ({
...defaultStyles,
margin: "0",
}),
}}
/>
);
}}

View File

@ -14,6 +14,8 @@ import { showToast, SkeletonText } from "@calcom/ui/v2";
import { Button, SkeletonButton, Shell } from "@calcom/ui/v2";
import DisconnectIntegration from "@calcom/ui/v2/modules/integrations/DisconnectIntegration";
import HeadSeo from "@components/seo/head-seo";
const Component = ({
name,
type,
@ -276,6 +278,7 @@ const Component = ({
export default function App(props: {
name: string;
description: AppType["description"];
type: AppType["type"];
isGlobal?: AppType["isGlobal"];
logo: string;
@ -300,7 +303,16 @@ export default function App(props: {
const { t } = useLocale();
return (
<Shell large isPublic heading={t("app_store")} backPath="/apps">
<Shell large isPublic heading={t("app_store")} backPath="/apps" withoutSeo>
<HeadSeo
title={props.name}
description={props.description}
app={{ slug: props.logo, name: props.name, description: props.description }}
nextSeoProps={{
nofollow: true,
noindex: true,
}}
/>
{props.licenseRequired ? (
<LicenseRequired>
<Component {...props} />

View File

@ -3,13 +3,13 @@ import Link from "next/link";
import { Fragment } from "react";
import { InstallAppButton } from "@calcom/app-store/components";
import DestinationCalendarSelector from "@calcom/features/calendars/DestinationCalendarSelector";
import { useLocale } from "@calcom/lib/hooks/useLocale";
import showToast from "@calcom/lib/notification";
import { trpc } from "@calcom/trpc/react";
import Button from "@calcom/ui/Button";
import { Icon } from "@calcom/ui/Icon";
import SkeletonLoader from "@calcom/ui/apps/SkeletonLoader";
import { Alert, EmptyScreen } from "@calcom/ui/v2";
import { Alert, Button, EmptyScreen } from "@calcom/ui/v2";
import { List } from "@calcom/ui/v2/core/List";
import { ShellSubHeading } from "@calcom/ui/v2/core/Shell";
import Switch from "@calcom/ui/v2/core/Switch";
@ -19,7 +19,6 @@ import { QueryCell } from "@lib/QueryCell";
import SubHeadingTitleWithConnections from "@components/integrations/SubHeadingTitleWithConnections";
import AdditionalCalendarSelector from "@components/v2/apps/AdditionalCalendarSelector";
import DestinationCalendarSelector from "@components/v2/apps/DestinationCalendarSelector";
import IntegrationListItem from "@components/v2/apps/IntegrationListItem";
type Props = {
@ -264,7 +263,7 @@ export function CalendarListContainer(props: { heading?: boolean; fromOnboarding
<ShellSubHeading
title={t("calendar")}
subtitle={t("installed_app_calendar_description")}
className="mb-0 flex flex-wrap items-center gap-4 md:mb-3 md:gap-0"
className="mb-0 flex flex-wrap items-center gap-4 sm:flex-nowrap md:mb-3 md:gap-0"
actions={
<div className="flex flex-col xl:flex-row xl:space-x-5">
{!!data.connectedCalendars.length && (
@ -288,7 +287,7 @@ export function CalendarListContainer(props: { heading?: boolean; fromOnboarding
<h1 className="text-sm font-semibold">{t("create_events_on")}</h1>
<p className="text-sm font-normal">{t("set_calendar")}</p>
</div>
<div className="flex justify-end md:w-6/12">
<div className="justify-end md:w-6/12">
<DestinationCalendarSelector
onChange={mutation.mutate}
hidePlaceholder

View File

@ -1,4 +1,4 @@
import { FormValues } from "pages/v2/event-types/[type]";
import { FormValues } from "pages/event-types/[type]";
import { Controller, useFormContext } from "react-hook-form";
import dayjs from "@calcom/dayjs";

View File

@ -1,12 +1,13 @@
import autoAnimate from "@formkit/auto-animate";
import { EventTypeCustomInput } from "@prisma/client/";
import Link from "next/link";
import { EventTypeSetupInfered, FormValues } from "pages/v2/event-types/[type]";
import { EventTypeSetupInfered, FormValues } from "pages/event-types/[type]";
import { useEffect, useRef, useState } from "react";
import { Controller, useFormContext } from "react-hook-form";
import short from "short-uuid";
import { v5 as uuidv5 } from "uuid";
import DestinationCalendarSelector from "@calcom/features/calendars/DestinationCalendarSelector";
import { CAL_URL } from "@calcom/lib/constants";
import { useLocale } from "@calcom/lib/hooks/useLocale";
import { trpc } from "@calcom/trpc/react";
@ -14,7 +15,6 @@ import { Icon } from "@calcom/ui";
import {
Button,
CustomInputItem,
DestinationCalendarSelector,
Dialog,
DialogContent,
Label,
@ -24,6 +24,7 @@ import {
TextField,
Tooltip,
} from "@calcom/ui/v2";
import CheckboxField from "@calcom/ui/v2/core/form/Checkbox";
import CustomInputTypeForm from "@components/v2/eventtype/CustomInputTypeForm";
@ -249,7 +250,30 @@ export const EventAdvancedTab = ({ eventType, team }: Pick<EventTypeSetupInfered
)}
/>
<hr />
<Controller
name="metadata.additionalNotesRequired"
control={formMethods.control}
defaultValue={!!eventType.metadata.additionalNotesRequired}
render={({ field: { value, onChange } }) => (
<div className="flex space-x-3 ">
<Switch
name="additionalNotesRequired"
fitToHeight={true}
checked={value}
onCheckedChange={(e) => onChange(e)}
/>
<div className="flex flex-col">
<Skeleton as={Label} className="text-sm font-semibold leading-none text-black">
{t("require_additional_notes")}
</Skeleton>
<Skeleton as="p" className="-mt-2 text-sm leading-normal text-gray-600">
{t("require_additional_notes_description")}
</Skeleton>
</div>
</div>
)}
/>
<hr />
<Controller
name="successRedirectUrl"
control={formMethods.control}
@ -406,6 +430,13 @@ export const EventAdvancedTab = ({ eventType, team }: Pick<EventTypeSetupInfered
onChange(Number(e.target.value));
}}
/>
<div className="mt-6">
<CheckboxField
description={t("show_attendees")}
onChange={(e) => formMethods.setValue("seatsShowAttendees", e.target.checked)}
defaultChecked={!!eventType.seatsShowAttendees}
/>
</div>
</div>
)}
/>

View File

@ -1,9 +1,10 @@
import { EventTypeSetupInfered, FormValues } from "pages/v2/event-types/[type]";
import { EventTypeSetupInfered, FormValues } from "pages/event-types/[type]";
import React from "react";
import { useFormContext } from "react-hook-form";
import EventTypeAppContext, { GetAppData, SetAppData } from "@calcom/app-store/EventTypeAppContext";
import { EventTypeAddonMap } from "@calcom/app-store/apps.browser.generated";
import { EventTypeAppCardComponentProps } from "@calcom/app-store/types";
import { EventTypeAppsList } from "@calcom/app-store/utils";
import { useLocale } from "@calcom/lib/hooks/useLocale";
import { inferQueryOutput, trpc } from "@calcom/trpc/react";
@ -11,7 +12,9 @@ import { Icon } from "@calcom/ui";
import ErrorBoundary from "@calcom/ui/ErrorBoundary";
import { Button, EmptyScreen } from "@calcom/ui/v2";
type EventType = Pick<EventTypeSetupInfered, "eventType">["eventType"];
type EventType = Pick<EventTypeSetupInfered, "eventType">["eventType"] &
EventTypeAppCardComponentProps["eventType"];
function AppCardWrapper({
app,
eventType,

View File

@ -1,6 +1,6 @@
import autoAnimate from "@formkit/auto-animate";
import * as RadioGroup from "@radix-ui/react-radio-group";
import { EventTypeSetupInfered, FormValues } from "pages/v2/event-types/[type]";
import { EventTypeSetupInfered, FormValues } from "pages/event-types/[type]";
import { useEffect, useRef, useState } from "react";
import { useFormContext, Controller, useWatch } from "react-hook-form";

View File

@ -1,4 +1,4 @@
import { EventTypeSetupInfered } from "pages/v2/event-types/[type]";
import { EventTypeSetupInfered } from "pages/event-types/[type]";
import { useState } from "react";
import getStripeAppData from "@calcom/lib/getStripeAppData";

View File

@ -1,7 +1,7 @@
import autoAnimate from "@formkit/auto-animate";
import { zodResolver } from "@hookform/resolvers/zod";
import { isValidPhoneNumber } from "libphonenumber-js";
import { EventTypeSetupInfered, FormValues } from "pages/v2/event-types/[type]";
import { EventTypeSetupInfered, FormValues } from "pages/event-types/[type]";
import { useEffect, useRef, useState } from "react";
import { Controller, useForm, useFormContext } from "react-hook-form";
import { z } from "zod";

View File

@ -1,5 +1,5 @@
import { SchedulingType } from "@prisma/client/";
import { EventTypeSetupInfered, FormValues } from "pages/v2/event-types/[type]";
import { EventTypeSetupInfered, FormValues } from "pages/event-types/[type]";
import { useMemo } from "react";
import { Controller, useFormContext } from "react-hook-form";

View File

@ -1,6 +1,6 @@
import { TFunction } from "next-i18next";
import { useRouter } from "next/router";
import { EventTypeSetupInfered, FormValues } from "pages/v2/event-types/[type]";
import { EventTypeSetupInfered, FormValues } from "pages/event-types/[type]";
import { useMemo, useState } from "react";
import { Loader } from "react-feather";
import { UseFormReturn } from "react-hook-form";

View File

@ -1,4 +1,4 @@
import type { FormValues } from "pages/v2/event-types/[type]";
import type { FormValues } from "pages/event-types/[type]";
import { useState } from "react";
import { useFormContext } from "react-hook-form";

View File

@ -10,6 +10,7 @@ async function getBooking(prisma: PrismaClient, uid: string) {
description: true,
customInputs: true,
smsReminderNumber: true,
location: true,
attendees: {
select: {
email: true,

View File

@ -1,40 +0,0 @@
import jackson, { IAPIController, IOAuthController, JacksonOption } from "@boxyhq/saml-jackson";
import { BASE_URL } from "@lib/config/constants";
import { samlDatabaseUrl } from "@lib/saml";
// Set the required options. Refer to https://github.com/boxyhq/jackson#configuration for the full list
const opts: JacksonOption = {
externalUrl: BASE_URL,
samlPath: "/api/auth/saml/callback",
db: {
engine: "sql",
type: "postgres",
url: samlDatabaseUrl,
encryptionKey: process.env.CALENDSO_ENCRYPTION_KEY,
},
samlAudience: "https://saml.cal.com",
};
let apiController: IAPIController;
let oauthController: IOAuthController;
const g = global as any;
export default async function init() {
if (!g.apiController || !g.oauthController) {
const ret = await jackson(opts);
apiController = ret.apiController;
oauthController = ret.oauthController;
g.apiController = apiController;
g.oauthController = oauthController;
} else {
apiController = g.apiController;
oauthController = g.oauthController;
}
return {
apiController,
oauthController,
};
}

View File

@ -1,22 +1,46 @@
import { AppsStatus } from "@calcom/types/Calendar";
import * as fetch from "@lib/core/http/fetch-wrapper";
import { BookingCreateBody, BookingResponse } from "@lib/types/booking";
type ExtendedBookingCreateBody = BookingCreateBody & { noEmail?: boolean; recurringCount?: number };
const createRecurringBooking = async (data: ExtendedBookingCreateBody[]) => {
return Promise.all(
data.map((booking, key) => {
// We only want to send the first occurrence of the meeting at the moment, not all at once
if (key === 0) {
return fetch.post<ExtendedBookingCreateBody, BookingResponse>("/api/book/event", booking);
} else {
return fetch.post<ExtendedBookingCreateBody, BookingResponse>("/api/book/event", {
...booking,
noEmail: true,
});
}
})
);
const createdBookings: BookingResponse[] = [];
// Reversing to accumulate results for noEmail instances first, to then lastly, create the
// emailed booking taking into account accumulated results to send app status accurately
for (let key = 0; key < data.length; key++) {
const booking = data[key];
if (key === data.length - 1) {
const calcAppsStatus: { [key: string]: AppsStatus } = createdBookings
.flatMap((book) => (book.appsStatus !== undefined ? book.appsStatus : []))
.reduce((prev, curr) => {
if (prev[curr.type]) {
prev[curr.type].failures += curr.failures;
prev[curr.type].success += curr.success;
} else {
prev[curr.type] = curr;
}
return prev;
}, {} as { [key: string]: AppsStatus });
const appsStatus = Object.values(calcAppsStatus);
const response = await fetch.post<
ExtendedBookingCreateBody & { appsStatus: AppsStatus[] },
BookingResponse
>("/api/book/event", {
...booking,
appsStatus,
});
createdBookings.push(response);
} else {
const response = await fetch.post<ExtendedBookingCreateBody, BookingResponse>("/api/book/event", {
...booking,
noEmail: true,
});
createdBookings.push(response);
}
}
return createdBookings;
};
export default createRecurringBooking;

View File

@ -39,10 +39,20 @@ export const parseRecurringDates = (
const rule = new RRule({
...restRecurringEvent,
count: recurringCount,
dtstart: dayjs(startDate).toDate(),
dtstart: new Date(
Date.UTC(
dayjs.utc(startDate).get("year"),
dayjs.utc(startDate).get("month"),
dayjs.utc(startDate).get("date"),
dayjs.utc(startDate).get("hour"),
dayjs.utc(startDate).get("minute")
)
),
});
const utcOffset = dayjs(startDate).tz(timeZone).utcOffset();
const dateStrings = rule.all().map((r) => {
return processDate(dayjs(r).tz(timeZone), i18n);
return processDate(dayjs.utc(r).utcOffset(utcOffset), i18n);
});
return [dateStrings, rule.all()];
};
@ -66,8 +76,9 @@ export const extractRecurringDates = (
count: recurringInfo?._count.recurringEventId,
dtstart: recurringInfo?._min.startTime,
}).all();
const utcOffset = dayjs(recurringInfo?._min.startTime).tz(timeZone).utcOffset();
const dateStrings = allDates.map((r) => {
return processDate(dayjs(r).tz(timeZone), i18n);
return processDate(dayjs.utc(r).utcOffset(utcOffset), i18n);
});
return [dateStrings, allDates];
};

View File

@ -1,59 +0,0 @@
import { PrismaClient } from "@prisma/client";
import { TRPCError } from "@calcom/trpc/server";
import { BASE_URL } from "@lib/config/constants";
export const samlDatabaseUrl = process.env.SAML_DATABASE_URL || "";
export const samlLoginUrl = BASE_URL;
export const isSAMLLoginEnabled = samlDatabaseUrl.length > 0;
export const samlTenantID = "Cal.com";
export const samlProductID = "Cal.com";
const samlAdmins = (process.env.SAML_ADMINS || "").split(",");
export const hostedCal = BASE_URL === "https://app.cal.com";
export const tenantPrefix = "team-";
export const isSAMLAdmin = (email: string) => {
for (const admin of samlAdmins) {
if (admin.toLowerCase() === email.toLowerCase() && admin.toUpperCase() === email.toUpperCase()) {
return true;
}
}
return false;
};
export const samlTenantProduct = async (prisma: PrismaClient, email: string) => {
const user = await prisma.user.findUnique({
where: {
email,
},
select: {
id: true,
invitedTo: true,
},
});
if (!user) {
throw new TRPCError({
code: "UNAUTHORIZED",
message: "Unauthorized Request",
});
}
if (!user.invitedTo) {
throw new TRPCError({
code: "BAD_REQUEST",
message:
"Could not find a SAML Identity Provider for your email. Please contact your admin to ensure you have been given access to Cal",
});
}
return {
tenant: tenantPrefix + user.invitedTo,
product: samlProductID,
};
};

View File

@ -1,5 +1,7 @@
import { Attendee, Booking } from "@prisma/client";
import { AppsStatus } from "@calcom/types/Calendar";
export type BookingCreateBody = {
email: string;
end: string;
@ -33,4 +35,5 @@ export type BookingCreateBody = {
export type BookingResponse = Booking & {
paymentUid?: string;
attendees: Attendee[];
appsStatus?: AppsStatus[];
};

View File

@ -5,29 +5,6 @@ import { CONSOLE_URL, WEBAPP_URL, WEBSITE_URL } from "@calcom/lib/constants";
import { isIpInBanlist } from "@calcom/lib/getIP";
import { extendEventData, nextCollectBasicSettings } from "@calcom/lib/telemetry";
const V2_WHITELIST = [
"/settings/admin",
"/settings/billing",
"/settings/developer/webhooks",
"/settings/developer/api-keys",
"/settings/my-account",
"/settings/security",
"/settings/teams",
"/availability",
"/bookings",
"/event-types",
"/workflows",
"/apps",
"/teams",
"/success",
"/auth/login",
];
// For pages
// - which has V1 versions being modified as V2
// - Add routing_forms to keep old links working
const V2_BLACKLIST = ["/apps/routing_forms/", "/apps/routing-forms/", "/apps/typeform/"];
const middleware: NextMiddleware = async (req) => {
const url = req.nextUrl;
@ -52,16 +29,6 @@ const middleware: NextMiddleware = async (req) => {
return NextResponse.rewrite(url);
}
/** Display available V2 pages */
if (
!V2_BLACKLIST.some((p) => url.pathname.startsWith(p)) &&
V2_WHITELIST.some((p) => url.pathname.startsWith(p))
) {
// rewrite to the current subdomain under the pages/sites folder
url.pathname = `/v2${url.pathname}`;
return NextResponse.rewrite(url);
}
return NextResponse.next();
};

View File

@ -1,6 +1,6 @@
{
"name": "@calcom/web",
"version": "2.0.5",
"version": "2.1.0",
"private": true,
"scripts": {
"analyze": "ANALYZE=true next build",
@ -23,7 +23,7 @@
"yarn": ">=1.19.0 < 2.0.0"
},
"dependencies": {
"@boxyhq/saml-jackson": "0.3.6",
"@boxyhq/saml-jackson": "1.3.1",
"@calcom/app-store": "*",
"@calcom/app-store-cli": "*",
"@calcom/core": "*",
@ -60,6 +60,7 @@
"@stripe/stripe-js": "^1.35.0",
"@tanstack/react-query": "^4.3.9",
"@vercel/edge-functions-ui": "^0.2.1",
"@vercel/og": "^0.0.19",
"@wojtekmaj/react-daterange-picker": "^3.3.1",
"accept-language-parser": "^1.5.0",
"async": "^3.2.4",
@ -119,7 +120,7 @@
"tailwindcss-radix": "^2.6.0",
"uuid": "^8.3.2",
"web3": "^1.7.5",
"zod": "^3.18.0"
"zod": "^3.19.1"
},
"devDependencies": {
"@babel/core": "^7.18.10",

View File

@ -110,9 +110,13 @@ export default function User(props: inferSSRProps<typeof getServerSideProps>) {
description={
isDynamicGroup ? `Book events with ${dynamicUsernames.join(", ")}` : (user.bio as string) || ""
}
name={isDynamicGroup ? dynamicNames.join(", ") : nameOrUsername}
username={isDynamicGroup ? dynamicUsernames.join(", ") : (user.username as string) || ""}
// avatar={user.avatar || undefined}
meeting={{
title: isDynamicGroup ? "" : `${user.bio}`,
profile: { name: `${profile.name}`, image: null },
users: isDynamicGroup
? dynamicUsernames.map((username, index) => ({ username, name: dynamicNames[index] }))
: [{ username: `${user.username}`, name: `${user.name}` }],
}}
/>
<CustomBranding lightVal={profile.brandColor} darkVal={profile.darkBrandColor} />

View File

@ -12,6 +12,7 @@ import path from "path";
import checkLicense from "@calcom/features/ee/common/server/checkLicense";
import ImpersonationProvider from "@calcom/features/ee/impersonation/lib/ImpersonationProvider";
import { hostedCal, isSAMLLoginEnabled } from "@calcom/features/ee/sso/lib/saml";
import { WEBAPP_URL } from "@calcom/lib/constants";
import { symmetricDecrypt } from "@calcom/lib/crypto";
import { defaultCookies } from "@calcom/lib/default-cookies";
@ -22,7 +23,6 @@ import prisma from "@calcom/prisma";
import { ErrorCode, verifyPassword } from "@lib/auth";
import CalComAdapter from "@lib/auth/next-auth-custom-adapter";
import { randomString } from "@lib/random";
import { hostedCal, isSAMLLoginEnabled, samlLoginUrl } from "@lib/saml";
import slugify from "@lib/slugify";
import { GOOGLE_CLIENT_ID, GOOGLE_CLIENT_SECRET, IS_GOOGLE_LOGIN_ENABLED } from "@server/lib/constants";
@ -135,7 +135,7 @@ if (isSAMLLoginEnabled) {
version: "2.0",
checks: ["pkce", "state"],
authorization: {
url: `${samlLoginUrl}/api/auth/saml/authorize`,
url: `${WEBAPP_URL}/api/auth/saml/authorize`,
params: {
scope: "",
response_type: "code",
@ -143,10 +143,10 @@ if (isSAMLLoginEnabled) {
},
},
token: {
url: `${samlLoginUrl}/api/auth/saml/token`,
url: `${WEBAPP_URL}/api/auth/saml/token`,
params: { grant_type: "authorization_code" },
},
userinfo: `${samlLoginUrl}/api/auth/saml/userinfo`,
userinfo: `${WEBAPP_URL}/api/auth/saml/userinfo`,
profile: (profile) => {
return {
id: profile.id || "",

View File

@ -1,25 +1,24 @@
import { OAuthReqBody } from "@boxyhq/saml-jackson";
import { OAuthReq } from "@boxyhq/saml-jackson";
import { NextApiRequest, NextApiResponse } from "next";
import { HttpError } from "@calcom/lib/http-error";
import jackson from "@calcom/features/ee/sso/lib/jackson";
import jackson from "@lib/jackson";
import { HttpError } from "@lib/core/http/error";
export default async function handler(req: NextApiRequest, res: NextApiResponse) {
try {
if (req.method !== "GET") {
throw new Error("Method not allowed");
}
const { oauthController } = await jackson();
const { oauthController } = await jackson();
const { redirect_url } = await oauthController.authorize(req.query as unknown as OAuthReqBody);
res.redirect(302, redirect_url);
} catch (err: unknown) {
if (err instanceof HttpError) {
console.error("authorize error:", err);
const { message, statusCode = 500 } = err;
return res.status(statusCode).send(message);
}
return res.status(500).send("Unknown error");
if (req.method !== "GET") {
return res.status(400).send("Method not allowed");
}
try {
const { redirect_url } = await oauthController.authorize(req.query as unknown as OAuthReq);
return res.redirect(302, redirect_url as string);
} catch (err) {
const { message, statusCode = 500 } = err as HttpError;
return res.status(statusCode).send(message);
}
}

View File

@ -1,25 +1,14 @@
import { NextApiRequest, NextApiResponse } from "next";
import { HttpError } from "@calcom/lib/http-error";
import jackson from "@calcom/features/ee/sso/lib/jackson";
import { defaultHandler, defaultResponder } from "@calcom/lib/server";
import jackson from "@lib/jackson";
export default async function handler(req: NextApiRequest, res: NextApiResponse) {
try {
if (req.method !== "POST") {
throw new Error("Method not allowed");
}
const { oauthController } = await jackson();
const { redirect_url } = await oauthController.samlResponse(req.body);
res.redirect(302, redirect_url);
} catch (err: unknown) {
if (err instanceof HttpError) {
console.error("callback error:", err);
const { message, statusCode = 500 } = err;
return res.status(statusCode).send(message);
}
return res.status(500).send("Unknown error");
}
async function postHandler(req: NextApiRequest, res: NextApiResponse) {
const { oauthController } = await jackson();
const { redirect_url } = await oauthController.samlResponse(req.body);
if (redirect_url) return res.redirect(302, redirect_url);
}
export default defaultHandler({
POST: Promise.resolve({ default: defaultResponder(postHandler) }),
});

View File

@ -1,21 +1,13 @@
import { NextApiRequest, NextApiResponse } from "next";
import { NextApiRequest } from "next";
import jackson from "@lib/jackson";
import jackson from "@calcom/features/ee/sso/lib/jackson";
import { defaultHandler, defaultResponder } from "@calcom/lib/server";
export default async function handler(req: NextApiRequest, res: NextApiResponse) {
try {
if (req.method !== "POST") {
throw new Error("Method not allowed");
}
const { oauthController } = await jackson();
const result = await oauthController.token(req.body);
res.json(result);
} catch (err: any) {
console.error("token error:", err);
const { message, statusCode = 500 } = err;
res.status(statusCode).send(message);
}
async function postHandler(req: NextApiRequest) {
const { oauthController } = await jackson();
return await oauthController.token(req.body);
}
export default defaultHandler({
POST: Promise.resolve({ default: defaultResponder(postHandler) }),
});

View File

@ -1,53 +1,34 @@
import { NextApiRequest, NextApiResponse } from "next";
import { NextApiRequest } from "next";
import z from "zod";
import jackson from "@lib/jackson";
import jackson from "@calcom/features/ee/sso/lib/jackson";
import { HttpError } from "@calcom/lib/http-error";
import { defaultHandler, defaultResponder } from "@calcom/lib/server";
const extractAuthToken = (req: NextApiRequest) => {
const authHeader = req.headers["authorization"];
const parts = (authHeader || "").split(" ");
if (parts.length > 1) {
return parts[1];
}
if (parts.length > 1) return parts[1];
return null;
// check for query param
let arr: string[] = [];
const { access_token } = requestQuery.parse(req.query);
arr = arr.concat(access_token);
if (arr[0].length > 0) return arr[0];
throw new HttpError({ statusCode: 401, message: "Unauthorized" });
};
const requestQuery = z.object({
access_token: z.string(),
});
export default async function handler(req: NextApiRequest, res: NextApiResponse) {
try {
if (req.method !== "GET") {
throw new Error("Method not allowed");
}
const { oauthController } = await jackson();
let token: string | null = extractAuthToken(req);
// check for query param
if (!token) {
let arr: string[] = [];
const { access_token } = requestQuery.parse(req.query);
arr = arr.concat(access_token);
if (arr[0].length > 0) {
token = arr[0];
}
}
if (!token) {
res.status(401).json({ message: "Unauthorized" });
return;
}
const profile = await oauthController.userInfo(token);
res.json(profile);
} catch (err: any) {
console.error("userinfo error:", err);
const { message, statusCode = 500 } = err;
res.status(statusCode).json({ message });
}
async function getHandler(req: NextApiRequest) {
const { oauthController } = await jackson();
const token = extractAuthToken(req);
return await oauthController.userInfo(token);
}
export default defaultHandler({
GET: Promise.resolve({ default: defaultResponder(getHandler) }),
});

View File

@ -1,294 +0,0 @@
import {
Attendee,
Booking,
BookingReference,
BookingStatus,
EventType,
User,
WebhookTriggerEvents,
} from "@prisma/client";
import type { NextApiRequest, NextApiResponse } from "next";
import { getSession } from "next-auth/react";
import type { TFunction } from "next-i18next";
import { z, ZodError } from "zod";
import { getCalendar } from "@calcom/app-store/_utils/getCalendar";
import { CalendarEventBuilder } from "@calcom/core/builders/CalendarEvent/builder";
import { CalendarEventDirector } from "@calcom/core/builders/CalendarEvent/director";
import { deleteMeeting } from "@calcom/core/videoClient";
import dayjs from "@calcom/dayjs";
import { sendRequestRescheduleEmail } from "@calcom/emails";
import getWebhooks from "@calcom/features/webhooks/lib/getWebhooks";
import sendPayload from "@calcom/features/webhooks/lib/sendPayload";
import { isPrismaObjOrUndefined } from "@calcom/lib";
import { getTranslation } from "@calcom/lib/server/i18n";
import prisma from "@calcom/prisma";
import type { CalendarEvent, Person } from "@calcom/types/Calendar";
export type RescheduleResponse = Booking & {
attendees: Attendee[];
};
export type PersonAttendeeCommonFields = Pick<
User,
"id" | "email" | "name" | "locale" | "timeZone" | "username"
>;
const rescheduleSchema = z.object({
bookingId: z.string(),
rescheduleReason: z.string().optional(),
});
const findUserDataByUserId = async (userId: number) => {
return await prisma.user.findUniqueOrThrow({
where: {
id: userId,
},
select: {
id: true,
name: true,
username: true,
email: true,
timeZone: true,
locale: true,
credentials: true,
destinationCalendar: true,
teams: true,
},
});
};
const handler = async (
req: NextApiRequest,
res: NextApiResponse
): Promise<RescheduleResponse | NextApiResponse | void> => {
const session = await getSession({ req });
const {
bookingId,
rescheduleReason: cancellationReason,
}: { bookingId: string; rescheduleReason: string; cancellationReason: string } = req.body;
let user: Awaited<ReturnType<typeof findUserDataByUserId>>;
try {
if (session?.user?.id) {
user = await findUserDataByUserId(session?.user.id);
} else {
return res.status(501).end();
}
const bookingToReschedule = await prisma.booking.findFirstOrThrow({
select: {
id: true,
uid: true,
userId: true,
title: true,
description: true,
startTime: true,
endTime: true,
eventTypeId: true,
eventType: true,
location: true,
attendees: true,
references: true,
customInputs: true,
dynamicEventSlugRef: true,
dynamicGroupSlugRef: true,
destinationCalendar: true,
},
where: {
uid: bookingId,
NOT: {
status: {
in: [BookingStatus.CANCELLED, BookingStatus.REJECTED],
},
},
},
});
if (!bookingToReschedule.userId) {
throw new Error("Booking to reschedule doesn't have an owner.");
}
if (!bookingToReschedule.eventType) {
throw new Error("EventType not found for current booking.");
}
const bookingBelongsToTeam = !!bookingToReschedule.eventType?.teamId;
if (bookingBelongsToTeam && bookingToReschedule.eventType?.teamId) {
const userTeamIds = user.teams.map((item) => item.teamId);
if (userTeamIds.indexOf(bookingToReschedule?.eventType?.teamId) === -1) {
throw new Error("User isn't a member on the team");
}
}
if (!bookingBelongsToTeam && bookingToReschedule.userId !== user.id) {
throw new Error("User isn't owner of the current booking");
}
if (bookingToReschedule && user) {
let event: Partial<EventType> = {};
if (bookingToReschedule.eventTypeId) {
event = await prisma.eventType.findFirstOrThrow({
select: {
title: true,
users: true,
schedulingType: true,
recurringEvent: true,
},
where: {
id: bookingToReschedule.eventTypeId,
},
});
}
await prisma.booking.update({
where: {
id: bookingToReschedule.id,
},
data: {
rescheduled: true,
cancellationReason,
status: BookingStatus.CANCELLED,
updatedAt: dayjs().toISOString(),
},
});
const [mainAttendee] = bookingToReschedule.attendees;
// @NOTE: Should we assume attendees language?
const tAttendees = await getTranslation(mainAttendee.locale ?? "en", "common");
const usersToPeopleType = (
users: PersonAttendeeCommonFields[],
selectedLanguage: TFunction
): Person[] => {
return users?.map((user) => {
return {
email: user.email || "",
name: user.name || "",
username: user?.username || "",
language: { translate: selectedLanguage, locale: user.locale || "en" },
timeZone: user?.timeZone,
};
});
};
const userTranslation = await getTranslation(user.locale ?? "en", "common");
const [userAsPeopleType] = usersToPeopleType([user], userTranslation);
const builder = new CalendarEventBuilder();
builder.init({
title: bookingToReschedule.title,
type: event && event.title ? event.title : bookingToReschedule.title,
startTime: bookingToReschedule.startTime.toISOString(),
endTime: bookingToReschedule.endTime.toISOString(),
attendees: usersToPeopleType(
// username field doesn't exists on attendee but could be in the future
bookingToReschedule.attendees as unknown as PersonAttendeeCommonFields[],
tAttendees
),
organizer: userAsPeopleType,
});
const director = new CalendarEventDirector();
director.setBuilder(builder);
director.setExistingBooking(bookingToReschedule);
director.setCancellationReason(cancellationReason);
if (event) {
await director.buildForRescheduleEmail();
} else {
await director.buildWithoutEventTypeForRescheduleEmail();
}
// Handling calendar and videos cancellation
// This can set previous time as available, until virtual calendar is done
const credentialsMap = new Map();
user.credentials.forEach((credential) => {
credentialsMap.set(credential.type, credential);
});
const bookingRefsFiltered: BookingReference[] = bookingToReschedule.references.filter(
(ref) => !!credentialsMap.get(ref.type)
);
bookingRefsFiltered.forEach((bookingRef) => {
if (bookingRef.uid) {
if (bookingRef.type.endsWith("_calendar")) {
const calendar = getCalendar(credentialsMap.get(bookingRef.type));
return calendar?.deleteEvent(
bookingRef.uid,
builder.calendarEvent,
bookingRef.externalCalendarId
);
} else if (bookingRef.type.endsWith("_video")) {
return deleteMeeting(credentialsMap.get(bookingRef.type), bookingRef.uid);
}
}
});
// Send emails
await sendRequestRescheduleEmail(builder.calendarEvent, {
rescheduleLink: builder.rescheduleLink,
});
const evt: CalendarEvent = {
title: bookingToReschedule?.title,
type: event && event.title ? event.title : bookingToReschedule.title,
description: bookingToReschedule?.description || "",
customInputs: isPrismaObjOrUndefined(bookingToReschedule.customInputs),
startTime: bookingToReschedule?.startTime ? dayjs(bookingToReschedule.startTime).format() : "",
endTime: bookingToReschedule?.endTime ? dayjs(bookingToReschedule.endTime).format() : "",
organizer: userAsPeopleType,
attendees: usersToPeopleType(
// username field doesn't exists on attendee but could be in the future
bookingToReschedule.attendees as unknown as PersonAttendeeCommonFields[],
tAttendees
),
uid: bookingToReschedule?.uid,
location: bookingToReschedule?.location,
destinationCalendar:
bookingToReschedule?.destinationCalendar || bookingToReschedule?.destinationCalendar,
cancellationReason: `Please reschedule. ${cancellationReason}`, // TODO::Add i18-next for this
};
// Send webhook
const eventTrigger: WebhookTriggerEvents = "BOOKING_CANCELLED";
// Send Webhook call if hooked to BOOKING.CANCELLED
const subscriberOptions = {
userId: bookingToReschedule.userId,
eventTypeId: (bookingToReschedule.eventTypeId as number) || 0,
triggerEvent: eventTrigger,
};
const webhooks = await getWebhooks(subscriberOptions);
const promises = webhooks.map((webhook) =>
sendPayload(webhook.secret, eventTrigger, new Date().toISOString(), webhook, evt).catch((e) => {
console.error(
`Error executing webhook for event: ${eventTrigger}, URL: ${webhook.subscriberUrl}`,
e
);
})
);
await Promise.all(promises);
}
return res.status(200).json(bookingToReschedule);
} catch (error) {
if (error instanceof Error) {
throw new Error("Error.request.reschedule " + error?.message);
}
}
};
function validate(
handler: (req: NextApiRequest, res: NextApiResponse) => Promise<RescheduleResponse | NextApiResponse | void>
) {
return async (req: NextApiRequest, res: NextApiResponse) => {
if (req.method === "POST") {
try {
rescheduleSchema.parse(req.body);
} catch (error) {
if (error instanceof ZodError && error?.name === "ZodError") {
return res.status(400).json(error?.issues);
}
return res.status(402).end();
}
} else {
return res.status(405).end();
}
await handler(req, res);
};
}
export default validate(handler);

View File

@ -32,6 +32,18 @@ const handler = async (req: NextApiRequest, res: NextApiResponse) => {
timeZone: "America/Chihuahua",
language,
},
{
email: "pro@example.com",
name: "pro@example.com",
timeZone: "America/Chihuahua",
language,
},
{
email: "pro@example.com",
name: "pro@example.com",
timeZone: "America/Chihuahua",
language,
},
],
location: "Zoom video",
destinationCalendar: null,
@ -48,7 +60,7 @@ const handler = async (req: NextApiRequest, res: NextApiResponse) => {
res.setHeader("Content-Type", "text/html");
res.setHeader("Cache-Control", "no-cache, no-store, private, must-revalidate");
res.write(
renderEmail("OrganizerRequestReminderEmail", {
renderEmail("AttendeeScheduledEmail", {
attendee: evt.attendees[0],
calEvent: evt,
})

View File

@ -0,0 +1,90 @@
import { ImageResponse } from "@vercel/og";
import { NextApiRequest } from "next";
import type { SatoriOptions } from "satori";
import { z } from "zod";
import { Meeting, App } from "@calcom/lib/OgImages";
const calFont = fetch(new URL("../../../../public/fonts/cal.ttf", import.meta.url)).then((res) =>
res.arrayBuffer()
);
const interFont = fetch(new URL("../../../../public/fonts/Inter-Regular.ttf", import.meta.url)).then((res) =>
res.arrayBuffer()
);
const interFontMedium = fetch(new URL("../../../../public/fonts/Inter-Medium.ttf", import.meta.url)).then(
(res) => res.arrayBuffer()
);
export const config = {
runtime: "experimental-edge",
};
const meetingSchema = z.object({
imageType: z.literal("meeting"),
title: z.string(),
names: z.string().array(),
usernames: z.string().array(),
meetingProfileName: z.string(),
meetingImage: z.string().nullable().optional(),
});
const appSchema = z.object({
imageType: z.literal("app"),
name: z.string(),
description: z.string(),
slug: z.string(),
});
export default async function handler(req: NextApiRequest) {
const { searchParams } = new URL(`${req.url}`);
const imageType = searchParams.get("type");
const [calFontData, interFontData, interFontMediumData] = await Promise.all([
calFont,
interFont,
interFontMedium,
]);
const ogConfig = {
fonts: [
{ name: "inter", data: interFontData, weight: 400 },
{ name: "inter", data: interFontMediumData, weight: 500 },
{ name: "cal", data: calFontData, weight: 400 },
] as SatoriOptions["fonts"],
};
switch (imageType) {
case "meeting": {
const { names, usernames, title, meetingProfileName, meetingImage } = meetingSchema.parse({
names: searchParams.getAll("names"),
usernames: searchParams.getAll("usernames"),
title: searchParams.get("title"),
meetingProfileName: searchParams.get("meetingProfileName"),
meetingImage: searchParams.get("meetingImage"),
imageType,
});
return new ImageResponse(
(
<Meeting
title={title}
profile={{ name: meetingProfileName, image: meetingImage }}
users={names.map((name, index) => ({ name, username: usernames[index] }))}
/>
),
ogConfig
);
}
case "app": {
const { name, description, slug } = appSchema.parse({
name: searchParams.get("name"),
description: searchParams.get("description"),
slug: searchParams.get("slug"),
imageType,
});
return new ImageResponse(<App name={name} description={description} slug={slug} />, ogConfig);
}
default:
return new Response("What you're looking for is not here..", { status: 404 });
}
}

View File

@ -10,11 +10,9 @@ import path from "path";
import { getAppWithMetadata } from "@calcom/app-store/_appRegistry";
import prisma from "@calcom/prisma";
import useMediaQuery from "@lib/hooks/useMediaQuery";
import { inferSSRProps } from "@lib/types/inferSSRProps";
import App from "@components/App";
import Slider from "@components/apps/Slider";
import App from "@components/v2/apps/App";
const components = {
a: ({ href = "", ...otherProps }: JSX.IntrinsicElements["a"]) => (
@ -25,49 +23,21 @@ const components = {
img: ({ src = "", alt = "", placeholder, ...rest }: JSX.IntrinsicElements["img"]) => (
<Image src={src} alt={alt} {...rest} />
),
Slider: ({ items }: { items: string[] }) => {
const isTabletAndUp = useMediaQuery("(min-width: 960px)");
return (
<Slider<string>
items={items}
title="Screenshots"
options={{
perView: 1,
}}
renderItem={(item) =>
isTabletAndUp ? (
<Image
src={item}
alt=""
loading="eager"
layout="fixed"
objectFit="contain"
objectPosition="center center"
width={573}
height={382}
/>
) : (
<Image
src={item}
alt=""
layout="responsive"
objectFit="contain"
objectPosition="center center"
width={573}
height={382}
/>
)
}
/>
);
},
// @TODO: In v2 the slider isn't shown anymore. However, to ensure the v1 pages keep
// working, this component is still rendered in the MDX content. To skip them in the v2
// content we have to render null here. In v2 the gallery is shown by directly
// using the `items` property from the MDX's meta data.
Slider: () => <></>,
};
function SingleAppPage({ data, source }: inferSSRProps<typeof getStaticProps>) {
return (
<App
name={data.name}
description={data.description}
isGlobal={data.isGlobal}
slug={data.slug}
variant={data.variant}
type={data.type}
logo={data.logo}
categories={data.categories ?? [data.category]}
@ -80,6 +50,7 @@ function SingleAppPage({ data, source }: inferSSRProps<typeof getStaticProps>) {
email={data.email}
licenseRequired={data.licenseRequired}
isProOnly={data.isProOnly}
images={source?.scope?.items as string[] | undefined}
// tos="https://zoom.us/terms"
// privacy="https://zoom.us/privacy"
body={<MDXRemote {...source} components={components} />}

View File

@ -2,9 +2,8 @@ import { GetStaticPaths, InferGetStaticPropsType } from "next";
import { useSession } from "next-auth/react";
import { useRouter } from "next/router";
import { AppSetupPage } from "@calcom/app-store/_pages/setup";
import { getStaticProps } from "@calcom/app-store/_pages/setup/_getStaticProps";
import Loader from "@calcom/ui/Loader";
import { AppSetupPage } from "@calcom/app-store/_pages/v2/setup";
export default function SetupInformation(props: InferGetStaticPropsType<typeof getStaticProps>) {
const router = useRouter();
@ -12,11 +11,7 @@ export default function SetupInformation(props: InferGetStaticPropsType<typeof g
const { status } = useSession();
if (status === "loading") {
return (
<div className="absolute z-50 flex h-screen w-full items-center bg-gray-200">
<Loader />
</div>
);
return <div className="absolute z-50 flex h-screen w-full items-center bg-gray-200" />;
}
if (status === "unauthenticated") {
@ -28,7 +23,7 @@ export default function SetupInformation(props: InferGetStaticPropsType<typeof g
});
}
return <AppSetupPage slug={slug} {...props} />;
return <AppSetupPage slug={`${slug}-V2`} {...props} />;
}
export const getStaticPaths: GetStaticPaths = async () => {

View File

@ -9,6 +9,8 @@ import AppStoreCategories from "@calcom/ui/v2/core/apps/Categories";
import TrendingAppsSlider from "@calcom/ui/v2/core/apps/TrendingAppsSlider";
import AppsLayout from "@calcom/ui/v2/core/layouts/AppsLayout";
import { ssgInit } from "@server/lib/ssg";
export default function Apps({ appStore, categories }: InferGetStaticPropsType<typeof getServerSideProps>) {
const { t } = useLocale();
@ -22,6 +24,8 @@ export default function Apps({ appStore, categories }: InferGetStaticPropsType<t
}
export const getServerSideProps = async (context: NextPageContext) => {
const ssg = await ssgInit(context);
const session = await getSession(context);
let appStore;
@ -42,6 +46,7 @@ export const getServerSideProps = async (context: NextPageContext) => {
}, {} as Record<string, number>);
return {
props: {
trpcState: ssg.dehydrate(),
categories: Object.entries(categories)
.map(([name, count]): { name: AppCategories; count: number } => ({
name: name as AppCategories,

View File

@ -1,4 +1,5 @@
import { InferGetServerSidePropsType } from "next";
import { useRouter } from "next/router";
import z from "zod";
import { AppSettings } from "@calcom/app-store/_components/AppSettings";
@ -188,9 +189,10 @@ const querySchema = z.object({
category: z.nativeEnum(InstalledAppVariants),
});
export default function InstalledApps({ category }: InferGetServerSidePropsType<typeof getServerSideProps>) {
export default function InstalledApps() {
const { t } = useLocale();
const router = useRouter();
const category = router.query.category;
return (
<InstalledAppsLayout heading={t("installed_apps")} subtitle={t("manage_your_connected_apps")}>
{(category === InstalledAppVariants.payment || category === InstalledAppVariants.conferencing) && (

View File

@ -4,26 +4,26 @@ import { getCsrfToken, signIn } from "next-auth/react";
import Link from "next/link";
import { useRouter } from "next/router";
import { useState } from "react";
import { useForm } from "react-hook-form";
import { useForm, FormProvider } from "react-hook-form";
import { FaGoogle } from "react-icons/fa";
import { isSAMLLoginEnabled, samlProductID, samlTenantID } from "@calcom/features/ee/sso/lib/saml";
import { getSafeRedirectUrl } from "@calcom/lib/getSafeRedirectUrl";
import { useLocale } from "@calcom/lib/hooks/useLocale";
import { collectPageParameters, telemetryEventTypes, useTelemetry } from "@calcom/lib/telemetry";
import prisma from "@calcom/prisma";
import { Icon } from "@calcom/ui";
import { Alert } from "@calcom/ui/Alert";
import Button from "@calcom/ui/Button";
import { Icon } from "@calcom/ui/Icon";
import { EmailField, Form, PasswordField } from "@calcom/ui/form/fields";
import { Button, EmailField, PasswordField } from "@calcom/ui/v2";
import SAMLLogin from "@calcom/ui/v2/modules/auth/SAMLLogin";
import { ErrorCode, getSession } from "@lib/auth";
import { WEBAPP_URL, WEBSITE_URL } from "@lib/config/constants";
import { hostedCal, isSAMLLoginEnabled, samlProductID, samlTenantID } from "@lib/saml";
import { inferSSRProps } from "@lib/types/inferSSRProps";
import AddToHomescreen from "@components/AddToHomescreen";
import SAMLLogin from "@components/auth/SAMLLogin";
import TwoFactor from "@components/auth/TwoFactor";
import AuthContainer from "@components/ui/AuthContainer";
import AuthContainer from "@components/v2/ui/AuthContainer";
import { IS_GOOGLE_LOGIN_ENABLED } from "@server/lib/constants";
import { ssrInit } from "@server/lib/ssr";
@ -39,22 +39,20 @@ export default function Login({
csrfToken,
isGoogleLoginEnabled,
isSAMLLoginEnabled,
hostedCal,
samlTenantID,
samlProductID,
}: inferSSRProps<typeof getServerSideProps>) {
const { t } = useLocale();
const router = useRouter();
const form = useForm<LoginValues>();
const { formState } = form;
const { isSubmitting } = formState;
const methods = useForm<LoginValues>();
const { register, formState } = methods;
const [twoFactorRequired, setTwoFactorRequired] = useState(false);
const [errorMessage, setErrorMessage] = useState<string | null>(null);
const errorMessages: { [key: string]: string } = {
[ErrorCode.RateLimitExceeded]: t("rate_limit_exceeded"),
[ErrorCode.SecondFactorRequired]: t("2fa_enabled_instructions"),
// [ErrorCode.SecondFactorRequired]: t("2fa_enabled_instructions"),
[ErrorCode.IncorrectPassword]: `${t("incorrect_password")} ${t("please_try_again")}`,
[ErrorCode.UserNotFound]: t("no_account_exists"),
[ErrorCode.IncorrectTwoFactorCode]: `${t("incorrect_2fa_code")} ${t("please_try_again")}`,
@ -78,19 +76,16 @@ export default function Login({
callbackUrl = safeCallbackUrl || "";
const LoginFooter = (
<span>
{t("dont_have_an_account")}{" "}
<a href={`${WEBSITE_URL}/signup`} className="font-medium text-neutral-900">
{t("create_an_account")}
</a>
</span>
<a href={`${WEBSITE_URL}/signup`} className="text-brand-500 font-medium">
{t("dont_have_an_account")}
</a>
);
const TwoFactorFooter = (
<Button
onClick={() => {
setTwoFactorRequired(false);
form.setValue("totpCode", "");
methods.setValue("totpCode", "");
}}
StartIcon={Icon.FiArrowLeft}
color="minimal">
@ -98,108 +93,106 @@ export default function Login({
</Button>
);
const onSubmit = async (values: LoginValues) => {
setErrorMessage(null);
telemetry.event(telemetryEventTypes.login, collectPageParameters());
const res = await signIn<"credentials">("credentials", {
...values,
callbackUrl,
redirect: false,
});
if (!res) setErrorMessage(errorMessages[ErrorCode.InternalServerError]);
// we're logged in! let's do a hard refresh to the desired url
else if (!res.error) router.push(callbackUrl);
// reveal two factor input if required
else if (res.error === ErrorCode.SecondFactorRequired) setTwoFactorRequired(true);
// fallback if error not found
else setErrorMessage(errorMessages[res.error] || t("something_went_wrong"));
};
return (
<>
<AuthContainer
title={t("login")}
description={t("login")}
showLogo
heading={twoFactorRequired ? t("2fa_code") : t("sign_in_account")}
heading={twoFactorRequired ? t("2fa_code") : t("welcome_back")}
footerText={twoFactorRequired ? TwoFactorFooter : LoginFooter}>
<Form
form={form}
className="space-y-6"
handleSubmit={async (values) => {
setErrorMessage(null);
telemetry.event(telemetryEventTypes.login, collectPageParameters());
const res = await signIn<"credentials">("credentials", {
...values,
callbackUrl,
redirect: false,
});
if (!res) setErrorMessage(errorMessages[ErrorCode.InternalServerError]);
// we're logged in! let's do a hard refresh to the desired url
else if (!res.error) router.push(callbackUrl);
// reveal two factor input if required
else if (res.error === ErrorCode.SecondFactorRequired) setTwoFactorRequired(true);
// fallback if error not found
else setErrorMessage(errorMessages[res.error] || t("something_went_wrong"));
}}
data-testid="login-form">
<div>
<input
defaultValue={csrfToken || undefined}
type="hidden"
hidden
{...form.register("csrfToken")}
/>
</div>
<div className={classNames("space-y-6", { hidden: twoFactorRequired })}>
<EmailField
id="email"
label={t("email_address")}
defaultValue={router.query.email as string}
placeholder="john.doe@example.com"
required
{...form.register("email")}
/>
<div className="relative">
<div className="absolute right-0 -top-[2px]">
<Link href="/auth/forgot-password">
<a tabIndex={-1} className="text-primary-600 text-sm font-medium">
{t("forgot")}
</a>
</Link>
</div>
<PasswordField
id="password"
type="password"
autoComplete="current-password"
required
{...form.register("password")}
/>
<FormProvider {...methods}>
<form onSubmit={methods.handleSubmit(onSubmit)} data-testid="login-form">
<div>
<input defaultValue={csrfToken || undefined} type="hidden" hidden {...register("csrfToken")} />
</div>
</div>
{twoFactorRequired && <TwoFactor center />}
{errorMessage && <Alert severity="error" title={errorMessage} />}
<div className="flex space-y-2">
<Button className="flex w-full justify-center" type="submit" disabled={isSubmitting}>
{twoFactorRequired ? t("submit") : t("sign_in")}
</Button>
</div>
</Form>
{!twoFactorRequired && (
<>
{isGoogleLoginEnabled && (
<div className="mt-5">
<Button
color="secondary"
className="flex w-full justify-center"
data-testid="google"
onClick={async (e) => {
e.preventDefault();
// track Google logins. Without personal data/payload
telemetry.event(telemetryEventTypes.googleLogin, collectPageParameters());
await signIn("google");
}}>
{t("signin_with_google")}
</Button>
<div className="space-y-6">
<div className={classNames("space-y-6", { hidden: twoFactorRequired })}>
<EmailField
id="email"
label={t("email_address")}
defaultValue={router.query.email as string}
placeholder="john.doe@example.com"
required
{...register("email")}
/>
<div className="relative">
<div className="absolute right-0 -top-[6px] z-10">
<Link href="/auth/forgot-password">
<a tabIndex={-1} className="text-sm font-medium text-gray-600">
{t("forgot")}
</a>
</Link>
</div>
<PasswordField
id="password"
autoComplete="current-password"
required
className="mb-0"
{...register("password")}
/>
</div>
</div>
)}
{isSAMLLoginEnabled && (
<SAMLLogin
email={form.getValues("email")}
samlTenantID={samlTenantID}
samlProductID={samlProductID}
hostedCal={hostedCal}
setErrorMessage={setErrorMessage}
/>
)}
</>
)}
{twoFactorRequired && <TwoFactor center />}
{errorMessage && <Alert severity="error" title={errorMessage} />}
<Button
type="submit"
color="primary"
disabled={formState.isSubmitting}
className="w-full justify-center">
{twoFactorRequired ? t("submit") : t("sign_in")}
</Button>
</div>
</form>
{!twoFactorRequired && (
<>
{(isGoogleLoginEnabled || isSAMLLoginEnabled) && <hr className="my-8" />}
<div className="space-y-3">
{isGoogleLoginEnabled && (
<Button
color="secondary"
className="w-full justify-center"
data-testid="google"
StartIcon={FaGoogle}
onClick={async (e) => {
e.preventDefault();
// track Google logins. Without personal data/payload
telemetry.event(telemetryEventTypes.googleLogin, collectPageParameters());
await signIn("google");
}}>
{t("signin_with_google")}
</Button>
)}
{isSAMLLoginEnabled && (
<SAMLLogin
samlTenantID={samlTenantID}
samlProductID={samlProductID}
setErrorMessage={setErrorMessage}
/>
)}
</div>
</>
)}
</FormProvider>
</AuthContainer>
<AddToHomescreen />
</>
@ -237,7 +230,6 @@ export async function getServerSideProps(context: GetServerSidePropsContext) {
trpcState: ssr.dehydrate(),
isGoogleLoginEnabled: IS_GOOGLE_LOGIN_ENABLED,
isSAMLLoginEnabled,
hostedCal,
samlTenantID,
samlProductID,
},

View File

@ -5,12 +5,18 @@ import { useEffect } from "react";
import { getPremiumPlanPrice } from "@calcom/app-store/stripepayment/lib/utils";
import stripe from "@calcom/features/ee/payments/server/stripe";
import {
hostedCal,
isSAMLLoginEnabled,
samlProductID,
samlTenantID,
samlTenantProduct,
} from "@calcom/features/ee/sso/lib/saml";
import { checkUsername } from "@calcom/lib/server/checkUsername";
import prisma from "@calcom/prisma";
import { asStringOrNull } from "@lib/asStringOrNull";
import { getSession } from "@lib/auth";
import { hostedCal, isSAMLLoginEnabled, samlProductID, samlTenantID, samlTenantProduct } from "@lib/saml";
import { inferSSRProps } from "@lib/types/inferSSRProps";
import { ssrInit } from "@server/lib/ssr";

View File

@ -51,7 +51,7 @@ export default function Type(props: inferSSRProps<typeof getServerSideProps>) {
<CustomBranding lightVal={props.profile?.brandColor} darkVal={props.profile?.darkBrandColor} />
<main className="h-full sm:flex sm:items-center">
<div className="mx-auto flex justify-center px-4 pt-4 pb-20 sm:block sm:p-0">
<div className="inline-block transform overflow-hidden rounded-md border bg-white px-8 pt-5 pb-4 text-left align-bottom transition-all dark:border-neutral-700 dark:bg-gray-800 sm:my-8 sm:w-full sm:max-w-lg sm:py-6 sm:align-middle">
<div className="inline-block transform overflow-hidden rounded-md border bg-white px-8 pt-5 pb-4 text-left align-bottom transition-all dark:border-neutral-700 dark:bg-gray-800 sm:my-8 sm:w-full sm:max-w-lg sm:py-6 sm:align-middle">
<div>
<div>
{error && (
@ -178,7 +178,7 @@ export default function Type(props: inferSSRProps<typeof getServerSideProps>) {
const payload = {
uid: uid,
reason: cancellationReason,
cancellationReason: cancellationReason,
allRemainingBookings: !!props.recurringInstances,
};

View File

@ -69,6 +69,7 @@ export type FormValues = {
periodCountCalendarDays: "1" | "0";
periodDates: { startDate: Date; endDate: Date };
seatsPerTimeSlot: number | null;
seatsShowAttendees: boolean | null;
seatsPerTimeSlotEnabled: boolean;
minimumBookingNotice: number;
beforeBufferTime: number;
@ -178,6 +179,10 @@ const EventTypePage = (props: inferSSRProps<typeof getServerSideProps>) => {
).length;
}
const permalink = `${CAL_URL}/${team ? `team/${team.slug}` : eventType.users[0].username}/${
eventType.slug
}`;
const tabMap = {
setup: (
<EventSetupTab
@ -199,7 +204,7 @@ const EventTypePage = (props: inferSSRProps<typeof getServerSideProps>) => {
limits: <EventLimitsTab eventType={eventType} />,
advanced: <EventAdvancedTab eventType={eventType} team={team} />,
recurring: <EventRecurringTab eventType={eventType} />,
apps: <EventAppsTab eventType={eventType} />,
apps: <EventAppsTab eventType={{ ...eventType, URL: permalink }} />,
workflows: (
<EventWorkflowsTab
eventType={eventType}
@ -228,6 +233,7 @@ const EventTypePage = (props: inferSSRProps<typeof getServerSideProps>) => {
beforeBufferTime,
afterBufferTime,
seatsPerTimeSlot,
seatsShowAttendees,
bookingLimits,
recurringEvent,
locations,
@ -255,6 +261,7 @@ const EventTypePage = (props: inferSSRProps<typeof getServerSideProps>) => {
afterEventBuffer: afterBufferTime,
bookingLimits,
seatsPerTimeSlot,
seatsShowAttendees,
metadata,
});
}}>
@ -387,6 +394,7 @@ export const getServerSideProps = async (context: GetServerSidePropsContext) =>
price: true,
destinationCalendar: true,
seatsPerTimeSlot: true,
seatsShowAttendees: true,
workflows: {
include: {
workflow: {

View File

@ -98,13 +98,34 @@ export const EventTypeList = ({ group, groupIndex, readOnly, types }: EventTypeL
});
const setHiddenMutation = trpc.useMutation("viewer.eventTypes.update", {
onError: async (err) => {
console.error(err.message);
onMutate: async ({ id }) => {
await utils.cancelQuery(["viewer.eventTypes"]);
await utils.invalidateQueries(["viewer.eventTypes"]);
const previousValue = utils.getQueryData(["viewer.eventTypes"]);
if (previousValue) {
const newList = [...types];
const itemIndex = newList.findIndex((item) => item.id === id);
if (itemIndex !== -1 && newList[itemIndex]) {
newList[itemIndex].hidden = !newList[itemIndex].hidden;
}
utils.setQueryData(["viewer.eventTypes"], {
...previousValue,
eventTypeGroups: [
...previousValue.eventTypeGroups.slice(0, groupIndex),
{ ...group, eventTypes: newList },
...previousValue.eventTypeGroups.slice(groupIndex + 1),
],
});
}
return { previousValue };
},
onSettled: async () => {
await utils.invalidateQueries(["viewer.eventTypes"]);
onError: async (err, _, context) => {
if (context?.previousValue) {
utils.setQueryData(["viewer.eventTypes"], context.previousValue);
}
console.error(err.message);
},
onSettled: () => {
utils.invalidateQueries(["viewer.eventTypes"]);
},
});
@ -169,12 +190,31 @@ export const EventTypeList = ({ group, groupIndex, readOnly, types }: EventTypeL
};
const deleteMutation = trpc.useMutation("viewer.eventTypes.delete", {
onSuccess: async () => {
await utils.invalidateQueries(["viewer.eventTypes"]);
onSuccess: () => {
showToast(t("event_type_deleted_successfully"), "success");
setDeleteDialogOpen(false);
},
onError: (err) => {
onMutate: async ({ id }) => {
await utils.cancelQuery(["viewer.eventTypes"]);
const previousValue = utils.getQueryData(["viewer.eventTypes"]);
if (previousValue) {
const newList = types.filter((item) => item.id !== id);
utils.setQueryData(["viewer.eventTypes"], {
...previousValue,
eventTypeGroups: [
...previousValue.eventTypeGroups.slice(0, groupIndex),
{ ...group, eventTypes: newList },
...previousValue.eventTypeGroups.slice(groupIndex + 1),
],
});
}
return { previousValue };
},
onError: (err, _, context) => {
if (context?.previousValue) {
utils.setQueryData(["viewer.eventTypes"], context.previousValue);
}
if (err instanceof HttpError) {
const message = `${err.statusCode}: ${err.message}`;
showToast(message, "error");
@ -183,6 +223,9 @@ export const EventTypeList = ({ group, groupIndex, readOnly, types }: EventTypeL
showToast(err.message, "error");
}
},
onSettled: () => {
utils.invalidateQueries(["viewer.eventTypes"]);
},
});
const [isNativeShare, setNativeShare] = useState(true);
@ -439,7 +482,6 @@ export const EventTypeList = ({ group, groupIndex, readOnly, types }: EventTypeL
</ul>
<Dialog open={deleteDialogOpen} onOpenChange={setDeleteDialogOpen}>
<ConfirmationDialogContent
isLoading={deleteMutation.isLoading}
variety="danger"
title={t("delete_event_type")}
confirmBtnText={t("confirm_delete_event_type")}

View File

@ -3,6 +3,7 @@ import Link from "next/link";
import { useRouter } from "next/router";
import { Fragment } from "react";
import DestinationCalendarSelector from "@calcom/features/calendars/DestinationCalendarSelector";
import { WEBAPP_URL } from "@calcom/lib/constants";
import { useLocale } from "@calcom/lib/hooks/useLocale";
import { trpc } from "@calcom/trpc/react";
@ -14,7 +15,6 @@ import Meta from "@calcom/ui/v2/core/Meta";
import { getLayout } from "@calcom/ui/v2/core/layouts/SettingsLayout";
import { SkeletonContainer, SkeletonText, SkeletonButton } from "@calcom/ui/v2/core/skeleton";
import { List, ListItem, ListItemText, ListItemTitle } from "@calcom/ui/v2/modules/List";
import DestinationCalendarSelector from "@calcom/ui/v2/modules/event-types/DestinationCalendarSelector";
import DisconnectIntegration from "@calcom/ui/v2/modules/integrations/DisconnectIntegration";
import { QueryCell } from "@lib/QueryCell";
@ -63,7 +63,7 @@ const CalendarsView = () => {
<Icon.FiCalendar className="h-6 w-6" />
</div>
<div className="flex flex-col space-y-3">
<div className="flex w-full flex-col space-y-3">
<div>
<h4 className=" pb-2 text-base font-semibold leading-5 text-black">
{t("add_to_calendar")}

View File

@ -0,0 +1,71 @@
import { useForm } from "react-hook-form";
import { useLocale } from "@calcom/lib/hooks/useLocale";
import { trpc } from "@calcom/trpc/react";
import { Label, showToast } from "@calcom/ui/v2";
import { Switch, Skeleton, Form, Button } from "@calcom/ui/v2/core";
import Meta from "@calcom/ui/v2/core/Meta";
import { getLayout } from "@calcom/ui/v2/core/layouts/SettingsLayout";
const ProfileImpersonationView = () => {
const { t } = useLocale();
const utils = trpc.useContext();
const { data: user } = trpc.useQuery(["viewer.me"]);
const mutation = trpc.useMutation("viewer.updateProfile", {
onSuccess: () => {
showToast(t("profile_updated_successfully"), "success");
},
onError: (error) => {
showToast(`${t("error")}, ${error.message}`, "error");
},
});
const formMethods = useForm<{ disableImpersonation: boolean }>({
defaultValues: {
disableImpersonation: user?.disableImpersonation,
},
});
const {
formState: { isSubmitting },
setValue,
} = formMethods;
return (
<>
<Meta title="Impersonation Settings" description="" />
<Form
form={formMethods}
handleSubmit={({ disableImpersonation }) => {
mutation.mutate({ disableImpersonation });
utils.invalidateQueries(["viewer.me"]);
}}>
<div className="flex space-x-3">
<Switch
{...formMethods.register("disableImpersonation")}
defaultChecked={!user?.disableImpersonation}
onCheckedChange={(e) => {
setValue("disableImpersonation", !e);
}}
fitToHeight={true}
/>
<div className="flex flex-col">
<Skeleton as={Label} className="text-sm font-semibold leading-none text-black">
{t("user_impersonation_heading")}
</Skeleton>
<Skeleton as="p" className="-mt-2 text-sm leading-normal text-gray-600">
{t("user_impersonation_description")}
</Skeleton>
</div>
</div>
<Button color="primary" className="mt-8" type="submit" disabled={isSubmitting || mutation.isLoading}>
{t("update")}
</Button>
</Form>
</>
);
};
ProfileImpersonationView.getLayout = getLayout;
export default ProfileImpersonationView;

View File

@ -0,0 +1 @@
export { default } from "@calcom/features/ee/sso/page/user-sso-view";

View File

@ -0,0 +1 @@
export { default } from "@calcom/features/ee/sso/page/teams-sso-view";

View File

@ -4,6 +4,7 @@ import { useRouter } from "next/router";
import { FormProvider, SubmitHandler, useForm } from "react-hook-form";
import LicenseRequired from "@calcom/features/ee/common/components/v2/LicenseRequired";
import { isSAMLLoginEnabled } from "@calcom/features/ee/sso/lib/saml";
import { useLocale } from "@calcom/lib/hooks/useLocale";
import { inferSSRProps } from "@calcom/types/inferSSRProps";
import { Alert } from "@calcom/ui/Alert";
@ -13,7 +14,6 @@ import { HeadSeo } from "@calcom/web/components/seo/head-seo";
import { asStringOrNull } from "@calcom/web/lib/asStringOrNull";
import { WEBAPP_URL } from "@calcom/web/lib/config/constants";
import prisma from "@calcom/web/lib/prisma";
import { isSAMLLoginEnabled } from "@calcom/web/lib/saml";
import { IS_GOOGLE_LOGIN_ENABLED } from "@calcom/web/server/lib/constants";
import { ssrInit } from "@calcom/web/server/lib/ssr";

View File

@ -141,7 +141,15 @@ type SuccessProps = inferSSRProps<typeof getServerSideProps>;
export default function Success(props: SuccessProps) {
const { t } = useLocale();
const router = useRouter();
const { location: _location, name, reschedule, listingStatus, status, isSuccessBookingPage } = router.query;
const {
location: _location,
name,
email,
reschedule,
listingStatus,
status,
isSuccessBookingPage,
} = router.query;
const location: ReturnType<typeof getEventLocationValue> = Array.isArray(_location)
? _location[0] || ""
: _location || "";
@ -357,14 +365,23 @@ export default function Success(props: SuccessProps) {
<p className="text-bookinglight">{bookingInfo.user.email}</p>
</div>
)}
{bookingInfo?.attendees.map((attendee, index) => (
<div
key={attendee.name}
className={index === bookingInfo.attendees.length - 1 ? "" : "mb-3"}>
<p>{attendee.name}</p>
<p className="text-bookinglight">{attendee.email}</p>
</div>
))}
{!!eventType.seatsShowAttendees
? bookingInfo?.attendees
.filter((attendee) => attendee.email === email)
.map((attendee) => (
<div key={attendee.name} className="mb-3">
<p>{attendee.name}</p>
<p className="text-bookinglight">{attendee.email}</p>
</div>
))
: bookingInfo?.attendees.map((attendee, index) => (
<div
key={attendee.name}
className={index === bookingInfo.attendees.length - 1 ? "" : "mb-3"}>
<p>{attendee.name}</p>
<p className="text-bookinglight">{attendee.email}</p>
</div>
))}
</div>
</>
)}
@ -620,52 +637,60 @@ export function RecurringBookings({
? recurringBookings.sort((a, b) => (dayjs(a).isAfter(dayjs(b)) ? 1 : -1))
: null;
return recurringBookingsSorted && listingStatus === "recurring" ? (
<>
{eventType.recurringEvent?.count && (
<span className="font-medium">
{getEveryFreqFor({
t,
recurringEvent: eventType.recurringEvent,
recurringCount: recurringBookings?.length ?? undefined,
})}
</span>
)}
{eventType.recurringEvent?.count &&
recurringBookingsSorted.slice(0, 4).map((dateStr, idx) => (
<div key={idx} className="mb-2">
{dayjs(dateStr).format("MMMM DD, YYYY")}
<br />
{dayjs(dateStr).format("LT")} - {dayjs(dateStr).add(eventType.length, "m").format("LT")}{" "}
<span className="text-bookinglight">
({localStorage.getItem("timeOption.preferredTimeZone") || dayjs.tz.guess()})
</span>
</div>
))}
{recurringBookingsSorted.length > 4 && (
<Collapsible open={moreEventsVisible} onOpenChange={() => setMoreEventsVisible(!moreEventsVisible)}>
<CollapsibleTrigger
type="button"
className={classNames("flex w-full", moreEventsVisible ? "hidden" : "")}>
{t("plus_more", { count: recurringBookingsSorted.length - 4 })}
</CollapsibleTrigger>
<CollapsibleContent>
{eventType.recurringEvent?.count &&
recurringBookingsSorted.slice(4).map((dateStr, idx) => (
<div key={idx} className="mb-2">
{dayjs(dateStr).format("MMMM DD, YYYY")}
<br />
{dayjs(dateStr).format("LT")} - {dayjs(dateStr).add(eventType.length, "m").format("LT")}{" "}
<span className="text-bookinglight">
({localStorage.getItem("timeOption.preferredTimeZone") || dayjs.tz.guess()})
</span>
</div>
))}
</CollapsibleContent>
</Collapsible>
)}
</>
) : (
if (recurringBookingsSorted && listingStatus === "recurring") {
// recurring bookings should only be adjusted to the start date.
const utcOffset = dayjs(recurringBookingsSorted[0]).utcOffset();
return (
<>
{eventType.recurringEvent?.count && (
<span className="font-medium">
{getEveryFreqFor({
t,
recurringEvent: eventType.recurringEvent,
recurringCount: recurringBookings?.length ?? undefined,
})}
</span>
)}
{eventType.recurringEvent?.count &&
recurringBookingsSorted.slice(0, 4).map((dateStr, idx) => (
<div key={idx} className="mb-2">
{dayjs(dateStr).utcOffset(utcOffset).format("MMMM DD, YYYY")}
<br />
{dayjs(dateStr).utcOffset(utcOffset).format("LT")} -{" "}
{dayjs(dateStr).utcOffset(utcOffset).add(eventType.length, "m").format("LT")}{" "}
<span className="text-bookinglight">
({localStorage.getItem("timeOption.preferredTimeZone") || dayjs.tz.guess()})
</span>
</div>
))}
{recurringBookingsSorted.length > 4 && (
<Collapsible open={moreEventsVisible} onOpenChange={() => setMoreEventsVisible(!moreEventsVisible)}>
<CollapsibleTrigger
type="button"
className={classNames("flex w-full", moreEventsVisible ? "hidden" : "")}>
{t("plus_more", { count: recurringBookingsSorted.length - 4 })}
</CollapsibleTrigger>
<CollapsibleContent>
{eventType.recurringEvent?.count &&
recurringBookingsSorted.slice(4).map((dateStr, idx) => (
<div key={idx} className="mb-2">
{dayjs(dateStr).utcOffset(utcOffset).format("MMMM DD, YYYY")}
<br />
{dayjs(dateStr).utcOffset(utcOffset).format("LT")} -{" "}
{dayjs(dateStr).utcOffset(utcOffset).add(eventType.length, "m").format("LT")}{" "}
<span className="text-bookinglight">
({localStorage.getItem("timeOption.preferredTimeZone") || dayjs.tz.guess()})
</span>
</div>
))}
</CollapsibleContent>
</Collapsible>
)}
</>
);
}
return (
<>
{date.format("MMMM DD, YYYY")}
<br />
@ -717,6 +742,7 @@ const getEventTypesFromDB = async (id: number) => {
},
},
metadata: true,
seatsShowAttendees: true,
},
});

View File

@ -84,6 +84,14 @@ function TeamPage({ team }: TeamPageProps) {
return (
<div>
<HeadSeo title={teamName} description={teamName} />
<HeadSeo
title={teamName}
description={teamName}
meeting={{
title: team?.bio || "",
profile: { name: `${team.name}`, image: getPlaceholderAvatar(team.logo, team.name) },
}}
/>
<div className="dark:bg-darkgray-50 h-screen rounded-md bg-gray-100 px-4 pt-12 pb-12">
<div className="max-w-96 mx-auto mb-8 text-center">
<Avatar alt={teamName} imageSrc={getPlaceholderAvatar(team.logo, team.name)} size="lg" />

View File

@ -1,107 +0,0 @@
import fs from "fs";
import matter from "gray-matter";
import { GetStaticPaths, GetStaticPropsContext } from "next";
import { MDXRemote } from "next-mdx-remote";
import { serialize } from "next-mdx-remote/serialize";
import Image from "next/image";
import Link from "next/link";
import path from "path";
import { getAppWithMetadata } from "@calcom/app-store/_appRegistry";
import prisma from "@calcom/prisma";
import { inferSSRProps } from "@lib/types/inferSSRProps";
import App from "@components/v2/apps/App";
const components = {
a: ({ href = "", ...otherProps }: JSX.IntrinsicElements["a"]) => (
<Link href={href}>
<a {...otherProps} />
</Link>
),
img: ({ src = "", alt = "", placeholder, ...rest }: JSX.IntrinsicElements["img"]) => (
<Image src={src} alt={alt} {...rest} />
),
// @TODO: In v2 the slider isn't shown anymore. However, to ensure the v1 pages keep
// working, this component is still rendered in the MDX content. To skip them in the v2
// content we have to render null here. In v2 the gallery is shown by directly
// using the `items` property from the MDX's meta data.
Slider: () => <></>,
};
function SingleAppPage({ data, source }: inferSSRProps<typeof getStaticProps>) {
return (
<App
name={data.name}
isGlobal={data.isGlobal}
slug={data.slug}
variant={data.variant}
type={data.type}
logo={data.logo}
categories={data.categories ?? [data.category]}
author={data.publisher}
feeType={data.feeType || "usage-based"}
price={data.price || 0}
commission={data.commission || 0}
docs={data.docsUrl}
website={data.url}
email={data.email}
licenseRequired={data.licenseRequired}
isProOnly={data.isProOnly}
images={source?.scope?.items as string[] | undefined}
// tos="https://zoom.us/terms"
// privacy="https://zoom.us/privacy"
body={<MDXRemote {...source} components={components} />}
/>
);
}
export const getStaticPaths: GetStaticPaths<{ slug: string }> = async () => {
const appStore = await prisma.app.findMany({ select: { slug: true } });
const paths = appStore.map(({ slug }) => ({ params: { slug } }));
return {
paths,
fallback: false,
};
};
export const getStaticProps = async (ctx: GetStaticPropsContext) => {
if (typeof ctx.params?.slug !== "string") return { notFound: true };
const app = await prisma.app.findUnique({
where: { slug: ctx.params.slug },
});
if (!app) return { notFound: true };
const singleApp = await getAppWithMetadata(app);
if (!singleApp) return { notFound: true };
const appDirname = app.dirName;
const README_PATH = path.join(process.cwd(), "..", "..", `packages/app-store/${appDirname}/README.mdx`);
const postFilePath = path.join(README_PATH);
let source = "";
try {
/* If the app doesn't have a README we fallback to the package description */
source = fs.readFileSync(postFilePath).toString();
} catch (error) {
console.log(`No README.mdx provided for: ${appDirname}`);
source = singleApp.description;
}
const { content, data } = matter(source);
const mdxSource = await serialize(content, { scope: data });
return {
props: {
source: mdxSource,
data: singleApp,
},
};
};
export default SingleAppPage;

View File

@ -1,36 +0,0 @@
import { GetStaticPaths, InferGetStaticPropsType } from "next";
import { useSession } from "next-auth/react";
import { useRouter } from "next/router";
import { getStaticProps } from "@calcom/app-store/_pages/setup/_getStaticProps";
import { AppSetupPage } from "@calcom/app-store/_pages/v2/setup";
export default function SetupInformation(props: InferGetStaticPropsType<typeof getStaticProps>) {
const router = useRouter();
const slug = router.query.slug as string;
const { status } = useSession();
if (status === "loading") {
return <div className="absolute z-50 flex h-screen w-full items-center bg-gray-200" />;
}
if (status === "unauthenticated") {
router.replace({
pathname: "/auth/login",
query: {
callbackUrl: `/apps/${slug}/setup`,
},
});
}
return <AppSetupPage slug={`${slug}-V2`} {...props} />;
}
export const getStaticPaths: GetStaticPaths = async () => {
return {
paths: [],
fallback: "blocking",
};
};
export { getStaticProps };

View File

@ -1,241 +0,0 @@
import classNames from "classnames";
import { GetServerSidePropsContext } from "next";
import { getCsrfToken, signIn } from "next-auth/react";
import Link from "next/link";
import { useRouter } from "next/router";
import { useState } from "react";
import { useForm } from "react-hook-form";
import { FaGoogle } from "react-icons/fa";
import { getSafeRedirectUrl } from "@calcom/lib/getSafeRedirectUrl";
import { useLocale } from "@calcom/lib/hooks/useLocale";
import { collectPageParameters, telemetryEventTypes, useTelemetry } from "@calcom/lib/telemetry";
import prisma from "@calcom/prisma";
import { Icon } from "@calcom/ui";
import { Alert } from "@calcom/ui/Alert";
import { Button, EmailField, Form, PasswordField } from "@calcom/ui/v2";
import SAMLLogin from "@calcom/ui/v2/modules/auth/SAMLLogin";
import { ErrorCode, getSession } from "@lib/auth";
import { WEBAPP_URL, WEBSITE_URL } from "@lib/config/constants";
import { hostedCal, isSAMLLoginEnabled, samlProductID, samlTenantID } from "@lib/saml";
import { inferSSRProps } from "@lib/types/inferSSRProps";
import AddToHomescreen from "@components/AddToHomescreen";
import TwoFactor from "@components/auth/TwoFactor";
import AuthContainer from "@components/v2/ui/AuthContainer";
import { IS_GOOGLE_LOGIN_ENABLED } from "@server/lib/constants";
import { ssrInit } from "@server/lib/ssr";
interface LoginValues {
email: string;
password: string;
totpCode: string;
csrfToken: string;
}
export default function Login({
csrfToken,
isGoogleLoginEnabled,
isSAMLLoginEnabled,
hostedCal,
samlTenantID,
samlProductID,
}: inferSSRProps<typeof getServerSideProps>) {
const { t } = useLocale();
const router = useRouter();
const form = useForm<LoginValues>();
const { formState } = form;
const { isSubmitting } = formState;
const [twoFactorRequired, setTwoFactorRequired] = useState(false);
const [errorMessage, setErrorMessage] = useState<string | null>(null);
const errorMessages: { [key: string]: string } = {
// [ErrorCode.SecondFactorRequired]: t("2fa_enabled_instructions"),
[ErrorCode.IncorrectPassword]: `${t("incorrect_password")} ${t("please_try_again")}`,
[ErrorCode.UserNotFound]: t("no_account_exists"),
[ErrorCode.IncorrectTwoFactorCode]: `${t("incorrect_2fa_code")} ${t("please_try_again")}`,
[ErrorCode.InternalServerError]: `${t("something_went_wrong")} ${t("please_try_again_and_contact_us")}`,
[ErrorCode.ThirdPartyIdentityProviderEnabled]: t("account_created_with_identity_provider"),
};
const telemetry = useTelemetry();
let callbackUrl = typeof router.query?.callbackUrl === "string" ? router.query.callbackUrl : "";
if (/"\//.test(callbackUrl)) callbackUrl = callbackUrl.substring(1);
// If not absolute URL, make it absolute
if (!/^https?:\/\//.test(callbackUrl)) {
callbackUrl = `${WEBAPP_URL}/${callbackUrl}`;
}
const safeCallbackUrl = getSafeRedirectUrl(callbackUrl);
callbackUrl = safeCallbackUrl || "";
const LoginFooter = (
<a href={`${WEBSITE_URL}/signup`} className="text-brand-500 font-medium">
{t("dont_have_an_account")}
</a>
);
const TwoFactorFooter = (
<Button
onClick={() => {
setTwoFactorRequired(false);
form.setValue("totpCode", "");
}}
StartIcon={Icon.FiArrowLeft}
color="minimal">
{t("go_back")}
</Button>
);
return (
<>
<AuthContainer
title={t("login")}
description={t("login")}
showLogo
heading={twoFactorRequired ? t("2fa_code") : t("welcome_back")}
footerText={twoFactorRequired ? TwoFactorFooter : LoginFooter}>
<Form
form={form}
handleSubmit={async (values) => {
setErrorMessage(null);
telemetry.event(telemetryEventTypes.login, collectPageParameters());
const res = await signIn<"credentials">("credentials", {
...values,
callbackUrl,
redirect: false,
});
if (!res) setErrorMessage(errorMessages[ErrorCode.InternalServerError]);
// we're logged in! let's do a hard refresh to the desired url
else if (!res.error) router.push(callbackUrl);
// reveal two factor input if required
else if (res.error === ErrorCode.SecondFactorRequired) setTwoFactorRequired(true);
// fallback if error not found
else setErrorMessage(errorMessages[res.error] || t("something_went_wrong"));
}}
data-testid="login-form">
<div>
<input
defaultValue={csrfToken || undefined}
type="hidden"
hidden
{...form.register("csrfToken")}
/>
</div>
<div className="space-y-6">
<div className={classNames("space-y-6", { hidden: twoFactorRequired })}>
<EmailField
id="email"
label={t("email_address")}
defaultValue={router.query.email as string}
placeholder="john.doe@example.com"
required
{...form.register("email")}
/>
<div className="relative">
<div className="absolute right-0 -top-[6px] z-10">
<Link href="/auth/forgot-password">
<a tabIndex={-1} className="text-sm font-medium text-gray-600">
{t("forgot")}
</a>
</Link>
</div>
<PasswordField
id="password"
autoComplete="current-password"
required
className="mb-0"
{...form.register("password")}
/>
</div>
</div>
{twoFactorRequired && <TwoFactor center />}
{errorMessage && <Alert severity="error" title={errorMessage} />}
<Button type="submit" color="primary" disabled={isSubmitting} className="w-full justify-center">
{twoFactorRequired ? t("submit") : t("sign_in")}
</Button>
</div>
</Form>
{!twoFactorRequired && (
<>
{(isGoogleLoginEnabled || isSAMLLoginEnabled) && <hr className="my-8" />}
<div className="space-y-3">
{isGoogleLoginEnabled && (
<Button
color="secondary"
className="w-full justify-center"
data-testid="google"
StartIcon={FaGoogle}
onClick={async (e) => {
e.preventDefault();
// track Google logins. Without personal data/payload
telemetry.event(telemetryEventTypes.googleLogin, collectPageParameters());
await signIn("google");
}}>
{t("signin_with_google")}
</Button>
)}
{isSAMLLoginEnabled && (
<SAMLLogin
email={form.getValues("email")}
samlTenantID={samlTenantID}
samlProductID={samlProductID}
hostedCal={hostedCal}
setErrorMessage={setErrorMessage}
/>
)}
</div>
</>
)}
</AuthContainer>
<AddToHomescreen />
</>
);
}
export async function getServerSideProps(context: GetServerSidePropsContext) {
const { req } = context;
const session = await getSession({ req });
const ssr = await ssrInit(context);
if (session) {
return {
redirect: {
destination: "/",
permanent: false,
},
};
}
const userCount = await prisma.user.count();
if (userCount === 0) {
// Proceed to new onboarding to create first admin user
return {
redirect: {
destination: "/auth/setup",
permanent: false,
},
};
}
return {
props: {
csrfToken: await getCsrfToken(context),
trpcState: ssr.dehydrate(),
isGoogleLoginEnabled: IS_GOOGLE_LOGIN_ENABLED,
isSAMLLoginEnabled,
hostedCal,
samlTenantID,
samlProductID,
},
};
}

View File

@ -1 +1 @@
{"triggerEvent":"BOOKING_CREATED","createdAt":"[redacted/dynamic]","payload":{"type":"30 min","title":"30 min between PRO and Test Testson","description":"","additionalNotes":"","customInputs":{},"startTime":"[redacted/dynamic]","endTime":"[redacted/dynamic]","organizer":{"name":"PRO","email":"[redacted/dynamic]","timeZone":"[redacted/dynamic]","language":"[redacted/dynamic]"},"attendees":[{"email":"test@example.com","name":"Test Testson","timeZone":"[redacted/dynamic]","language":"[redacted/dynamic]"}],"location":"[redacted/dynamic]","destinationCalendar":null,"hideCalendarNotes":false,"requiresConfirmation":"[redacted/dynamic]","eventTypeId":"[redacted/dynamic]","uid":"[redacted/dynamic]","bookingId":"[redacted/dynamic]","metadata":{},"additionalInformation":"[redacted/dynamic]"}}
{"triggerEvent":"BOOKING_CREATED","createdAt":"[redacted/dynamic]","payload":{"type":"30 min","title":"30 min between PRO and Test Testson","description":"","additionalNotes":"","customInputs":{},"startTime":"[redacted/dynamic]","endTime":"[redacted/dynamic]","organizer":{"name":"PRO","email":"[redacted/dynamic]","timeZone":"[redacted/dynamic]","language":"[redacted/dynamic]"},"attendees":[{"email":"test@example.com","name":"Test Testson","timeZone":"[redacted/dynamic]","language":"[redacted/dynamic]"}],"location":"[redacted/dynamic]","destinationCalendar":null,"hideCalendarNotes":false,"requiresConfirmation":"[redacted/dynamic]","eventTypeId":"[redacted/dynamic]","seatsShowAttendees":false,"uid":"[redacted/dynamic]","bookingId":"[redacted/dynamic]","metadata":{},"additionalInformation":"[redacted/dynamic]"}}

View File

@ -10,7 +10,7 @@ test.describe("SAML tests", () => {
// eslint-disable-next-line playwright/no-skipped-test
test.skip(!IS_SAML_LOGIN_ENABLED, "It should only run if SAML is enabled");
// Try to go Security page
await page.goto("/settings/security");
await page.goto("/settings/security/sso");
// It should redirect you to the event-types page
// await page.waitForSelector("[data-testid=saml_config]");
});

View File

@ -1 +1 @@
{"triggerEvent":"BOOKING_CREATED","createdAt":"[redacted/dynamic]","payload":{"type":"30 min","title":"30 min between PRO and Test Testson","description":"","additionalNotes":"","customInputs":{},"startTime":"[redacted/dynamic]","endTime":"[redacted/dynamic]","organizer":{"name":"PRO","email":"[redacted/dynamic]","timeZone":"[redacted/dynamic]","language":"[redacted/dynamic]"},"attendees":[{"email":"test@example.com","name":"Test Testson","timeZone":"[redacted/dynamic]","language":"[redacted/dynamic]"}],"location":"[redacted/dynamic]","destinationCalendar":null,"hideCalendarNotes":false,"requiresConfirmation":"[redacted/dynamic]","eventTypeId":"[redacted/dynamic]","uid":"[redacted/dynamic]","eventTitle":"30 min","eventDescription":null,"price":0,"currency":"usd","length":30,"bookingId":"[redacted/dynamic]","metadata":{},"status":"ACCEPTED","additionalInformation":"[redacted/dynamic]"}}
{"triggerEvent":"BOOKING_CREATED","createdAt":"[redacted/dynamic]","payload":{"type":"30 min","title":"30 min between PRO and Test Testson","description":"","additionalNotes":"","customInputs":{},"startTime":"[redacted/dynamic]","endTime":"[redacted/dynamic]","organizer":{"name":"PRO","email":"[redacted/dynamic]","timeZone":"[redacted/dynamic]","language":"[redacted/dynamic]"},"attendees":[{"email":"test@example.com","name":"Test Testson","timeZone":"[redacted/dynamic]","language":"[redacted/dynamic]"}],"location":"[redacted/dynamic]","destinationCalendar":null,"hideCalendarNotes":false,"requiresConfirmation":"[redacted/dynamic]","eventTypeId":"[redacted/dynamic]","seatsShowAttendees":false,"uid":"[redacted/dynamic]","eventTitle":"30 min","eventDescription":null,"price":0,"currency":"usd","length":30,"bookingId":"[redacted/dynamic]","metadata":{},"status":"ACCEPTED","additionalInformation":"[redacted/dynamic]"}}

View File

@ -0,0 +1,9 @@
<svg width="101" height="22" viewBox="0 0 101 22" fill="none" xmlns="http://www.w3.org/2000/svg">
<path d="M10.0582 20.817C4.32115 20.817 0 16.2763 0 10.6704C0 5.04589 4.1005 0.467773 10.0582 0.467773C13.2209 0.467773 15.409 1.43945 17.1191 3.66311L14.3609 5.96151C13.2025 4.72822 11.805 4.11158 10.0582 4.11158C6.17833 4.11158 4.04533 7.08268 4.04533 10.6704C4.04533 14.2582 6.38059 17.1732 10.0582 17.1732C11.7866 17.1732 13.2577 16.5566 14.4161 15.3233L17.1375 17.7151C15.501 19.8453 13.2577 20.817 10.0582 20.817Z" fill="#000000"/>
<path d="M29.0161 5.88601H32.7304V20.4612H29.0161V18.331C28.2438 19.8446 26.9566 20.8536 24.4927 20.8536C20.5577 20.8536 17.4133 17.4341 17.4133 13.2297C17.4133 9.02528 20.5577 5.60571 24.4927 5.60571C26.9383 5.60571 28.2438 6.61477 29.0161 8.12835V5.88601ZM29.1264 13.2297C29.1264 10.95 27.5634 9.06266 25.0995 9.06266C22.7274 9.06266 21.1828 10.9686 21.1828 13.2297C21.1828 15.4346 22.7274 17.3967 25.0995 17.3967C27.5451 17.3967 29.1264 15.4907 29.1264 13.2297Z" fill="#000000"/>
<path d="M35.3599 0H39.0742V20.4427H35.3599V0Z" fill="#000000"/>
<path d="M40.7291 18.5182C40.7291 17.3223 41.6853 16.3132 42.9908 16.3132C44.2964 16.3132 45.2158 17.3223 45.2158 18.5182C45.2158 19.7515 44.278 20.7605 42.9908 20.7605C41.7037 20.7605 40.7291 19.7515 40.7291 18.5182Z" fill="#000000"/>
<path d="M59.4296 18.1068C58.0505 19.7885 55.9543 20.8536 53.4719 20.8536C49.0404 20.8536 45.7858 17.4341 45.7858 13.2297C45.7858 9.02528 49.0404 5.60571 53.4719 5.60571C55.8623 5.60571 57.9402 6.61477 59.3193 8.20309L56.4508 10.6136C55.7336 9.71667 54.7958 9.04397 53.4719 9.04397C51.0999 9.04397 49.5553 10.95 49.5553 13.211C49.5553 15.472 51.0999 17.378 53.4719 17.378C54.9062 17.378 55.8991 16.6306 56.6346 15.6215L59.4296 18.1068Z" fill="#000000"/>
<path d="M59.7422 13.2297C59.7422 9.02528 62.9968 5.60571 67.4283 5.60571C71.8598 5.60571 75.1144 9.02528 75.1144 13.2297C75.1144 17.4341 71.8598 20.8536 67.4283 20.8536C62.9968 20.8349 59.7422 17.4341 59.7422 13.2297ZM71.3449 13.2297C71.3449 10.95 69.8003 9.06266 67.4283 9.06266C65.0563 9.04397 63.5117 10.95 63.5117 13.2297C63.5117 15.4907 65.0563 17.3967 67.4283 17.3967C69.8003 17.3967 71.3449 15.4907 71.3449 13.2297Z" fill="#000000"/>
<path d="M100.232 11.5482V20.4428H96.518V12.4638C96.518 9.94119 95.3412 8.85739 93.576 8.85739C91.921 8.85739 90.7442 9.67958 90.7442 12.4638V20.4428H87.0299V12.4638C87.0299 9.94119 85.8346 8.85739 84.0878 8.85739C82.4329 8.85739 80.9802 9.67958 80.9802 12.4638V20.4428H77.2659V5.8676H80.9802V7.88571C81.7525 6.31607 83.15 5.53125 85.3014 5.53125C87.3425 5.53125 89.0525 6.5403 89.9903 8.24074C90.9281 6.50293 92.3072 5.53125 94.8079 5.53125C97.8603 5.54994 100.232 7.86702 100.232 11.5482Z" fill="#000000"/>
</svg>

After

Width:  |  Height:  |  Size: 2.7 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 197 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 139 KiB

View File

@ -67,7 +67,7 @@
"event_still_awaiting_approval": "الحدث لا يزال في انتظار موافقتك",
"booking_submitted_subject": "تم إرسال الحجز: {{eventType}} مع {{name}} في {{date}}",
"your_meeting_has_been_booked": "لقد تم حجز الاجتماع الخاص بك",
"event_type_has_been_rescheduled_on_time_date": "تمت إعادة جدولة {{eventType}} لديك مع {{name}} إلى {{time}} ({{timeZone}}) في {{date}}.",
"event_type_has_been_rescheduled_on_time_date": "تم إعادة جدولة {{eventType}} مع {{name}} إلى {{date}}.",
"event_has_been_rescheduled": "تم التحديث - تم إعادة جدولة الحدث الخاص بك",
"request_reschedule_title_attendee": "طلب إعادة جدولة الحجز الخاص بك",
"request_reschedule_subtitle": "ألغى {{organizer}} الحجز وطلب منك اختيار حجز في وقت آخر.",
@ -210,16 +210,16 @@
"sign_in": "تسجيل الدخول",
"go_back_login": "العودة إلى صفحة تسجيل الدخول",
"error_during_login": "حدث خطأ ما أثناء تسجيل الدخول. ارجع إلى شاشة تسجيل الدخول وحاول مجددًا.",
"request_password_reset": "اطلب إعادة تعيين كلمة المرور",
"request_password_reset": "إرسال بريد إلكتروني لإعادة التعيين",
"forgot_password": "هل نسيت كلمة المرور؟",
"forgot": "هل نسيت؟",
"done": "تم",
"all_done": "اكتمل كل شيء!",
"all_apps": "كل التطبيقات",
"all_apps": "الكل",
"check_email_reset_password": "تحقق من البريد الإلكتروني. لقد أرسلنا رابطًا لإعادة تعيين كلمة المرور.",
"finish": "إنهاء",
"few_sentences_about_yourself": "اكتب بضع جمل عن نفسك. سيظهر هذا على صفحة عنوان URL الشخصية لديك.",
"nearly_there": "على وشك الانتهاء",
"nearly_there": "أوشكت على الانتهاء!",
"nearly_there_instructions": "آخر شيء: تساعدك كتابة وصف موجز عنك ووضع صورة في الحصول على عمليات الحجز وإعلام الأشخاص بمَن سيحجزون معه حقًا.",
"set_availability_instructions": "حدد الفترات الزمنية التي تكون متاحًا فيها بشكل متكرر. يمكنك لاحقًا تحديد المزيد منها وربطها مع تقاويم مختلفة.",
"set_availability": "تحديد الوقت الذي تكون فيه متاحًا",
@ -243,7 +243,7 @@
"when": "متى",
"where": "أين",
"add_to_calendar": "إضافة إلى التقويم",
"add_another_calendar": "إضافة رزنامة أخرى",
"add_another_calendar": "إضافة تقويم آخر",
"other": "آخر",
"emailed_you_and_attendees": "لقد أرسلنا إليك وإلى الحضور الآخرين دعوة للتقويم عبر البريد الإلكتروني تتضمن كل التفاصيل.",
"emailed_you_and_attendees_recurring": "لقد أرسلنا إليك وإلى الحضور الآخرين دعوة تقويم عبر البريد الإلكتروني لأول هذه الأحداث المتكررة.",
@ -303,7 +303,7 @@
"no_availability": "غير متاح",
"no_meeting_found": "لم يتم العثور على اجتماعات",
"no_meeting_found_description": "هذا الاجتماع غير موجود. اتصل بصاحب الاجتماع للحصول على الرابط المُحدَّث.",
"no_status_bookings_yet": "لا توجد عمليات حجز في حالة {{status}} حتى الآن",
"no_status_bookings_yet": "لا توجد عمليات حجز {{status}}",
"no_status_bookings_yet_description": "ليست لديك عمليات حجز في حالة {{status}}. {{description}}",
"event_between_users": "{{eventName}} بين {{host}} و{{attendeeName}}",
"bookings": "عمليات الحجز",
@ -356,7 +356,7 @@
"meeting_ended": "انتهى الاجتماع",
"form_submitted": "تم إرسال النموذج",
"event_triggers": "مشغلات الحدث",
"subscriber_url": "عنوان URL للمشترك",
"subscriber_url": "رابط المشترك",
"create_new_webhook": "إنشاء ويب هوك جديد",
"webhooks": "ويبهوكس",
"team_webhooks": "فريق ويبهوكس",
@ -445,7 +445,7 @@
"sunday": "الأحد",
"all_booked_today": "تم حجز الكل اليوم.",
"slots_load_fail": "تعذر تحميل الفترات الزمنية المتاحة.",
"additional_guests": "إضافة مزيد من الضيوف",
"additional_guests": "إضافة ضيوف",
"your_name": "اسمك",
"email_address": "عنوان البريد الإلكتروني",
"enter_valid_email": "يرجى إدخال بريد إلكتروني صالح",
@ -487,7 +487,7 @@
"team_updated_successfully": "تم تحديث الفريق بنجاح",
"your_team_updated_successfully": "تم تحديث فريقك بنجاح.",
"about": "حول",
"team_description": "اكتب بضع جمل عن فريقك. سيظهر هذا على صفحة عنوان URL الخاصة بفريقك.",
"team_description": "بضع جمل عن فريقك. ستظهر على صفحة رابط فريقك.",
"members": "الأعضاء",
"member": "العضو",
"owner": "المالك",
@ -497,9 +497,9 @@
"new_member": "العضو الجديد",
"invite": "دعوة",
"add_team_members": "إضافة أعضاء الفريق",
"add_team_members_description": "قم بدعوة الآخرين للانضمام إلى فريقك",
"add_team_members_description": "دعوة الآخرين للانضمام إلى فريقك",
"add_team_member": "إضافة عضو فريق",
"invite_new_member": "دعوة عضو جديد",
"invite_new_member": "دعوة عضو جديد في الفريق",
"invite_new_team_member": "دعوة شخص ما إلى فريقك.",
"change_member_role": "تغيير دور العضو في الفريق",
"disable_cal_branding": "تعطيل علامات Cal.com التجارية",
@ -632,7 +632,7 @@
"billing": "الفوترة",
"manage_your_billing_info": "قم بإدارة معلومات الفوترة لديك وإلغاء اشتراكك.",
"availability": "الأوقات المتاحة",
"edit_availability": "تعديل التوفر",
"edit_availability": "تعديل الإتاحة",
"configure_availability": "اضبط الأوقات التي تكون متاحًا فيها للحجز.",
"copy_times_to": "نسخ الأوقات إلى",
"change_weekly_schedule": "تغيير جدولك الأسبوعي",
@ -732,7 +732,7 @@
"connect_additional_calendar": "ربط رزنامة إضافية",
"conferencing": "المؤتمرات عبر الفيديو",
"calendar": "التقويم",
"payments": "الدفعات",
"payments": "المدفوعات",
"not_installed": "لم يتم التثبيت",
"error_password_mismatch": "كلمات المرور غير متطابقة.",
"error_required_field": "هذا الحقل مطلوب.",
@ -745,42 +745,31 @@
"user_away_description": "حالة الشخص الذي تحاول حجز خدماته \"ليس موجودًا\"، ولذا فهو لا يقبل عمليات حجز جديدة.",
"meet_people_with_the_same_tokens": "الاجتماع مع أشخاص بنفس ال tokens",
"only_book_people_and_allow": "ما عليك سوى إجراء حجز والسماح بعمليات الحجز من الأشخاص الذين يتشاركون نفس ال tokens أو DAOs أو NFTs.",
"saml_config_deleted_successfully": "تم حذف تكوين SAML بنجاح",
"account_created_with_identity_provider": "تم إنشاء حسابك باستخدام مزود هوية.",
"account_managed_by_identity_provider": "تجري إدارة حسابك بواسطة {{provider}}",
"account_managed_by_identity_provider_description": "لتغيير بريدك الإلكتروني وكلمة المرور وتمكين المصادقة من عاملين والمزيد، يُرجى زيارة إعدادات حساب {{provider}} لديك.",
"signin_with_google": "تسجيل الدخول عبر Google",
"signin_with_saml": "تسجيل الدخول عبر SAML",
"saml_configuration": "تكوين SAML",
"delete_saml_configuration": "حذف تكوين SAML",
"delete_saml_configuration_confirmation_message": "هل تريد بالتأكيد حذف تكوين SAML؟ لن يتمكن بعد الآن أعضاء فريقك الذين يستخدمون تسجيل الدخول عبر SAML من الوصول إلى Cal.com.",
"confirm_delete_saml_configuration": "نعم، احذف تكوين SAML",
"saml_not_configured_yet": "لم يتم تكوين SAML حتى الآن",
"saml_configuration_description": "يُرجى لصق بيانات تعريف SAML المقدمة من مزود الهوية في مربع النص أدناه لتحديث تكوين SAML.",
"saml_configuration_placeholder": "يُرجى لصق بيانات تعريف SAML المقدمة من مزود الهوية هنا",
"saml_configuration_update_failed": "فشل تحديث تكوين SAML",
"saml_configuration_delete_failed": "فشل حذف تكوين SAML",
"saml_email_required": "يُرجى إدخال بريد إلكتروني حتى نتمكن من العثور على مزود هوية SAML الخاص بك",
"you_will_need_to_generate": "ستحتاج إلى إنشاء access token من أداة الجدولة القديمة لديك.",
"import": "استيراد",
"import_from": "الاستيراد من",
"access_token": "Access token",
"visit_roadmap": "المخطط",
"featured_categories": "الفئات البارزة",
"featured_categories": "الفئات المميزة",
"popular_categories": "الفئات الشائعة",
"number_apps_one": "تطبيق {{count}}",
"number_apps_other": "تطبيقات {{count}}",
"number_apps_one": "{{count}} تطبيق",
"number_apps_other": "{{count}} من التطبيقات",
"trending_apps": "التطبيقات الرائجة",
"explore_apps": "تطبيقات {{category}}",
"installed_apps": "التطبيقات المثبتة",
"no_category_apps": "لا تطبيقات {{category}}",
"no_category_apps_description_calendar": "أضف تطبيق رزنامة للتحقق من أي تضارب لمنع أي حجوزات مزدوجة",
"no_category_apps_description_conferencing": "حاول إضافة تطبيق مؤتمرات لدمج مكالمة الفيديو مع عملائك",
"no_category_apps": "لا توجد تطبيقات {{category}}",
"no_category_apps_description_calendar": "إضافة تطبيق تقويم للتحقق من أي تضارب لمنع أي حجوزات مزدوجة",
"no_category_apps_description_conferencing": "جرب إضافة تطبيق مؤتمرات لإنشاء مكالمات فيديو مع عملائك",
"no_category_apps_description_payment": "أضف تطبيق دفع لتسهيل التعاملات المالية بينك وبين عملائك",
"no_category_apps_description_other": "أضف أي نوع آخر من التطبيقات للقيام بأي شيء",
"installed_app_calendar_description": "اضبط الرزنامة (الرزنامات) للتحقق من أي تضارب لمنع الحجوزات المزدوجة.",
"installed_app_calendar_description": "قم بتعيين التقويم (التقويمات) للتحقق من أي تضارب لمنع الحجوزات المزدوجة.",
"installed_app_conferencing_description": "أضف تطبيقات مؤتمرات الفيديو المفضلة لديك لاجتماعاتك",
"installed_app_payment_description": "قم بإعداد أي خدمات معالجة دفع تود استخدامها عند طلب النقود من العملاء.",
"installed_app_payment_description": "قم بإعداد أي خدمات معالجة دفع تود استخدامها عند التحصيل من العملاء.",
"installed_app_other_description": "جميع تطبيقاتك المثبتة من الفئات الأخرى.",
"empty_installed_apps_headline": "لم يتم تثبيت أي تطبيق",
"empty_installed_apps_description": "تعطيك التطبيقات القدرة على تعزيز سير عملك وتحسين الجدولة في حياتك بشدة.",
@ -805,7 +794,7 @@
"installed_other": "{{count}} مثبت",
"verify_wallet": "تأكيد المحفظة",
"connect_metamask": "توصيل Metamask",
"create_events_on": "إنشاء أحداث في:",
"create_events_on": "إنشاء أحداث في",
"missing_license": "الترخيص مفقود",
"signup_requires": "يلزم ترخيص تجاري",
"signup_requires_description": "لا تقدم شركة Cal.com, Inc. حاليًا إصدارًا مجانيًا مفتوح المصدر لصفحة التسجيل. للحصول على حق الوصول الكامل إلى مكونات التسجيل، يجب أن تحصل على ترخيص تجاري. للاستخدام الشخصي، نوصي باستخدام منصة Prisma Data أو أي واجهة أخرى من واجهات Postgres لإنشاء الحسابات.",
@ -820,7 +809,7 @@
"set_to_default": "تعيين إلى الافتراضي",
"new_schedule_btn": "جدول جديد",
"add_new_schedule": "إضافة جدول جديد",
"add_new_calendar": "إضافة رزنامة جديدة",
"add_new_calendar": "إضافة تقويم جديد",
"set_calendar": "حدد أين سيتم إضافة الأحداث الجديدة عند الحجز.",
"delete_schedule": "حذف الجدول",
"schedule_created_successfully": "تم إنشاء جدول {{scheduleName}} بنجاح",
@ -837,10 +826,10 @@
"redirect_success_booking": "إعادة التوجيه عند الحجز ",
"you_are_being_redirected": "ستتم إعادة توجيهك إلى {{ url }} خلال $t(second, {\"count\": {{seconds}} }).",
"external_redirect_url": "https://example.com/redirect-to-my-success-page",
"redirect_url_description": "إعادة التوجيه إلى عنوان URL مخصص بعد الحجز بنجاح",
"redirect_url_description": "إعادة التوجيه إلى رابط مخصص بعد الحجز بنجاح",
"duplicate": "تكرار",
"offer_seats": "عرض مقاعد",
"offer_seats_description": "عرض مقاعد للحجز (هذا يعطل الضيوف والتسجيل في الحجز)",
"offer_seats_description": "عرض مقاعد للحجز. هذا يعطل تلقائياً حجز الضيوف وحجز الاشتراك.",
"seats_available": "المقاعد المتاحة",
"number_of_seats": "عدد المقاعد لكل حجز",
"enter_number_of_seats": "أدخل عدد المقاعد",
@ -894,7 +883,7 @@
"impersonate": "Impersonate",
"user_impersonation_heading": "انتحال شخصية مستخدم",
"user_impersonation_description": "يسمح لفريق الدعم الخاص بنا بتسجيل الدخول مؤقتًا نيابة عنك لمساعدتنا على حل أي مشاكل تبلغ عنها بسرعة.",
"team_impersonation_description": "يسمح لمشرفي الفريق بتسجيل الدخول بصفتك مؤقتًا.",
"team_impersonation_description": "يسمح لأعضاء فريقك بتسجيل الدخول نيابة عنك مؤقتًا.",
"impersonate_user_tip": "يتم مراجعة جميع استخدامات هذه الميزة.",
"impersonating_user_warning": "انتحال اسم مستخدم \"{{user}}\".",
"impersonating_stop_instructions": "<0>انقر هنا لإيقاف </0>.",
@ -928,7 +917,7 @@
"credentials_stored_encrypted": "سيتم تشفير بياناتك وتخزينها.",
"it_stored_encrypted": "سيتم تخزينه وتشفيره.",
"go_to_app_store": "الذهاب إلى App Store",
"calendar_error": "حدث خطأ ما، حاول إعادة توصيل التقويم مع جميع الأذونات اللازمة",
"calendar_error": "جرب إعادة توصيل التقويم بجميع الأذونات اللازمة",
"set_your_phone_number": "تعيين رقم هاتف للاجتماع",
"calendar_no_busy_slots": "لا توجد فترات زمنية مشغولة",
"display_location_label": "عرض في صفحة الحجز",
@ -1027,7 +1016,7 @@
"add_exchange2016": "الاتصال بخادم Exchange 2016",
"custom_template": "قالب مخصص",
"email_body": "نص البريد الإلكتروني",
"subject": "الموضوع",
"subject": "موضوع البريد الإلكتروني",
"text_message": "رسالة نصية",
"specific_issue": "هل لديك مشكلة معينة؟",
"browse_our_docs": "تصفح مستنداتنا",
@ -1065,8 +1054,8 @@
"navigate": "تنقّل",
"open": "فتح",
"close": "إغلاق",
"team_feature_teams": "هذه ميزة في Team. قم بالترقية إلى Team لرؤية توفر فريقك.",
"team_feature_workflows": "هذه ميزة في Team. قم بالترقية إلى Team لأتمتة إشعارات الحدث والتذكير مع سير العمل.",
"team_feature_teams": "هذه ميزة في Team. قم بالترقية إلى Team لرؤية إتاحة فريقك.",
"team_feature_workflows": "هذه ميزة في Team. قم بالترقية إلى Team لأتمتة إشعارات الحدث والتذكيرات عند استخدام سير العمل.",
"show_eventtype_on_profile": "إظهار على الملف الشخصي",
"embed": "تضمين",
"new_username": "اسم مستخدم جديد",
@ -1128,14 +1117,14 @@
"removes_cal_branding": "إزالة أي علامات تجارية ذات صلة بـCal ، أي 'تم تشغيلها من قبل Cal.'",
"profile_picture": "صورة الملف الشخصي",
"upload": "تحميل",
"web3": "الويب 3",
"rainbow_token_gated": "هذا النوع من الأحداث موجود وراء بوابة رمز.",
"rainbow_connect_wallet_gate": "قم بتوصيل محفظتك إذا كنت تملك <1>{{name}}</1> (<3>{{symbol}}</3>).",
"rainbow_insufficient_balance": "لا تحتوي محفظتك المتصلة على ما يكفي من 1>{{symbol}}</1>.",
"web3": "Web3",
"rainbow_token_gated": "يتم فتح هذا النوع من الأحداث برمز مميز.",
"rainbow_connect_wallet_gate": "قم بربط محفظتك إذا كنت تملك <1>{{name}}</1> (<3>{{symbol}}</3>).",
"rainbow_insufficient_balance": "لا تحتوي محفظتك المربوطة على ما يكفي من <1>{{symbol}}</1>.",
"rainbow_sign_message_request": "قم بتوقيع طلب الرسالة على محفظتك.",
"rainbow_signature_error": "خطأ في طلب التوقيع من محفظتك.",
"token_address": "عنوان الرمز المميز",
"blockchain": "البلوكشين",
"blockchain": "سلسلة الكتل",
"old_password": "كلمة مرور قديمة",
"secure_password": "كلمة المرور الجديدة الآمنة كلياً",
"error_updating_password": "خطأ في تحديث كلمة المرور",
@ -1146,35 +1135,35 @@
"appearance_subtitle": "إدارة إعدادات مظهر الحجز الخاص بك",
"my_account": "حسابي",
"general": "عام",
"calendars": "الرزنامات",
"calendars": "التقويمات",
"2fa_auth": "المصادقة الثنائية",
"invoices": "الفواتير",
"embeds": "التضمينات",
"impersonation": "التقمّص",
"impersonation": "انتحال الشخصية",
"users": "المستخدمون",
"profile_description": "إدارة الإعدادات لملفك الرزنامة الشخصي لديك",
"profile_description": "إدارة الإعدادات لملفك الشخصي على cal",
"general_description": "إدارة الإعدادات للغة والمنطقة الزمنية الخاصة بك",
"calendars_description": "اضبط كيفية تفاعل أنواع الأحداث لديك مع رزناماتك",
"calendars_description": "قم بتهيئة كيفية تفاعل أنواع الأحداث مع التقويمات الخاصة بك",
"appearance_description": "إدارة إعدادات مظهر الحجز الخاص بك",
"conferencing_description": "إدارة تطبيقات مؤتمرات الفيديو لديك لاجتماعاتك",
"conferencing_description": "إدارة تطبيقات مؤتمرات الفيديو لاجتماعاتك",
"password_description": "إدارة إعدادات كلمات مرور حسابك",
"2fa_description": "إدارة إعدادات كلمات مرور حسابك",
"we_just_need_basic_info": "نحتاج فقط إلى بعض المعلومات الأساسية لإعداد ملفك الشخصي.",
"we_just_need_basic_info": "نحتاج إلى بعض المعلومات الأساسية لإعداد ملفك الشخصي.",
"skip": "تخطي",
"do_this_later": "القيام بهذا لاحقاً",
"set_availability_getting_started_subtitle_1": "تحديد الفترات الزمنية التي أنت فيها متاح",
"set_availability_getting_started_subtitle_2": "يمكنك تخصيص كل هذا في وقت لاحق في صفحة التوفر.",
"connect_calendars_from_app_store": "يمكنك إضافة المزيد من الرزنامات من متجر التطبيقات",
"do_this_later": "القيام بذلك لاحقًا",
"set_availability_getting_started_subtitle_1": "حدد الفترات الزمنية التي تكون متاحًا فيها",
"set_availability_getting_started_subtitle_2": "يمكنك تخصيص كل هذا لاحقًا في صفحة الإتاحة.",
"connect_calendars_from_app_store": "يمكنك إضافة مزيد من التقويمات من متجر التطبيقات",
"connect_conference_apps": "ربط تطبيقات المؤتمرات",
"connect_calendar_apps": "ربط تطبيقات الرزنامة",
"connect_calendar_apps": "ربط تطبيقات التقويم",
"connect_payment_apps": "ربط تطبيقات الدفع",
"connect_other_apps": "ربط تطبيقات أخرى",
"current_step_of_total": "الخطوة {{currentStep}} من {{maxSteps}}",
"add_variable": "إضافة متغير",
"custom_phone_number": "رقم هاتف مخصص",
"message_template": "نموذج رسالة",
"email_subject": "موضوع الرسالة الإلكترونية",
"add_dynamic_variables": "أضف متغيرات نص ديناميكية",
"message_template": "قالب الرسالة",
"email_subject": "موضوع البريد الإلكتروني",
"add_dynamic_variables": "إضافة متغيرات نص ديناميكية",
"event_name_info": "اسم نوع الحدث",
"event_date_info": "تاريخ الحدث",
"event_time_info": "وقت بدء الحدث",
@ -1183,61 +1172,59 @@
"additional_notes_info": "ملاحظات إضافية للحجز",
"attendee_name_info": "اسم الشخص صاحب الحجز",
"to": "إلى",
"attendee_required_enter_number": "سيتطلب هذا من الحضور إدخال رقم هاتف عند الحجز",
"workflow_turned_on_successfully": "تم تحديث سير العمل {{workflowName}} بنجاح {{offOn}}",
"download_responses": "تحميل الردود",
"workflow_turned_on_successfully": "تم تشغيل {{offOn}} سير العمل {{workflowName}} بنجاح",
"download_responses": "تنزيل الردود",
"create_your_first_form": "إنشاء أول استمارة",
"create_your_first_form_description": "يمكنك من خلال استمارات التوجيه طرح أسئلة التأهيل والتوجيه إلى الشخص/نوع الحدث المناسب.",
"create_your_first_form_description": "باستخدام نماذج المسارات، يمكنك طرح أسئلة التأهيل والتوجيه إلى الشخص/نوع الحدث المناسب.",
"create_your_first_webhook": "إنشاء أول Webhook",
"create_your_first_webhook_description": "يمكنك باستخدام Webhooks تلقي بيانات الاجتماع في الوقت الفعلي عندما يحدث شيء ما في Cal.com.",
"for_a_maximum_of": "كحد أقصى لـ",
"create_your_first_webhook_description": "باستخدام Webhooks، يمكنك تلقي بيانات الاجتماع في الوقت الفعلي عندما يحدث شيء ما في Cal.com.",
"for_a_maximum_of": "بحد أقصى",
"event_one": "حدث",
"event_other": "أحداث",
"profile_team_description": "إدارة إعدادات الملف الشخصي لفريقك",
"members_team_description": "المستخدمين الذين هم في المجموعة",
"team_url": "عنوان URL للفريق",
"members_team_description": "المستخدمين الموجودين في المجموعة",
"team_url": "رابط الفريق",
"delete_team": "حذف الفريق",
"team_members": "أعضاء الفريق",
"more": "المزيد",
"more_page_footer": "نعتبر تطبيق الهاتف المحمول امتداداً لتطبيق الويب، لكن يرجعى العودة لتطبيق الويب إذا كنت تقوم بأي إجراءات معقدة.",
"workflow_example_1": "إرسال رسائل نصية للحضور للتذكير قبل 24 ساعة من بدء الحدث",
"workflow_example_2": "إرسال رسالة نصية مخصصة عند إعادة جدولة الحدث للحضور",
"workflow_example_3": "إرسال بريد إلكتروني مخصص عندما يتم حجز حدث جديد للاستضافة",
"workflow_example_4": "إرسال رسائل نصية للحضور للتذكير قبل 1 ساعة من بدء الحدث",
"workflow_example_5": "إرسال رسالة نصية مخصصة عند إعادة جدولة الحدث للحضور",
"workflow_example_6": "إرسال بريد إلكتروني مخصص عندما يتم حجز حدث جديد للاستضافة",
"more_page_footer": "نعتبر تطبيق الهاتف المحمول امتداداً لتطبيق الويب، لكن يرجى العودة لتطبيق الويب إذا كنت تقوم بأي إجراءات معقدة.",
"workflow_example_1": "إرسال رسائل قصيرة للحضور للتذكير قبل 24 ساعة من بدء الحدث",
"workflow_example_2": "إرسال رسالة قصيرة مخصصة للحضور عند إعادة جدولة الحدث",
"workflow_example_3": "إرسال بريد إلكتروني مخصص للمضيف عندما يتم حجز حدث جديد",
"workflow_example_4": "إرسال رسائل قصيرة للحضور للتذكير قبل ساعة من بدء الحدث",
"workflow_example_5": "إرسال رسالة قصيرة مخصصة للحضور عند إعادة جدولة الحدث",
"workflow_example_6": "إرسال بريد إلكتروني مخصص للمضيف عندما يتم حجز حدث جديد",
"welcome_to_cal_header": "مرحبًا بك في Cal.com!",
"edit_form_later_subtitle": وف تكون قادراً على تعديل هذا لاحقاً.",
"connect_calendar_later": "سأقوم بتوصيل التقويم في وقت لاحق",
"set_my_availability_later": "سأحدد متى أكون متوفراً لاحقاً",
"problem_saving_user_profile": "حدثت مشكلة أثناء حفظ بياناتك. الرجاء المحاولة من جديد أو التواصل مع دعم العملاء.",
"purchase_missing_seats": "شراء المقاعد الناقصة",
"slot_length": "طول الفتحة",
"edit_form_later_subtitle": تتمكن من تعديل هذا لاحقًا.",
"connect_calendar_later": "سأقوم بربط التقويم لاحقًا",
"set_my_availability_later": "سأحدد متى أكون متاحًا لاحقًا",
"problem_saving_user_profile": "حدثت مشكلة أثناء حفظ بياناتك. يرجى إعادة المحاولة أو التواصل مع دعم العملاء.",
"purchase_missing_seats": "شراء المقاعد الخالية",
"slot_length": "طول الفترة الزمنية",
"booking_appearance": "مظهر الحجز",
"appearance_team_description": "إدارة إعدادات مظهر الحجز الخاص بك",
"appearance_team_description": "إدارة إعدادات مظهر حجز فريقك",
"only_owner_change": "يمكن فقط لمالك هذا الفريق إجراء تغييرات على حجز الفريق ",
"team_disable_cal_branding_description": "إزالة أي علامات تجارية ذات صلة بـCal ، أي 'تم تشغيلها من قبل Cal'",
"team_disable_cal_branding_description": "إزالة أي علامات تجارية ذات صلة بـCal، أي 'مقدمة بواسطة Cal'",
"invited_by_team": "دعاك {{teamName}} للانضمام إلى فريقهم كـ{{role}}",
"token_invalid_expired": "الرمز المميز إما غير صالح أو انتهت صلاحيته.",
"exchange_add": "الربط مع Microsoft Exchange",
"exchange_add": "الربط بـ Microsoft Exchange",
"exchange_authentication": "طريقة المصادقة",
"exchange_authentication_standard": "المصادقة الأساسية",
"exchange_authentication_ntlm": "مصادقة NTLM",
"exchange_compression": "ضغط وفق Gzip",
"routing_forms_description": "يمكنك هنا رؤية جميع النماذج والمسارات التي أنشأتها.",
"exchange_compression": "ضغط Gzip",
"routing_forms_description": "يمكنك رؤية جميع النماذج والتوجيهات التي أنشأتها هنا.",
"add_new_form": "إضافة استمارة جديدة",
"form_description": "إنشاء استمارتك لتوجيه طالب حجز",
"copy_link_to_form": "نسخ رابط إلى الاستمارة",
"form_description": "قم بإنشاء النموذج الخاص بك لتوجيه صاحب حجز",
"copy_link_to_form": "نسخ الرابط إلى النموذج",
"theme": "السمة",
"theme_applies_note": "ينطبق هذا فقط على صفحات الحجز العامة الخاصة بك",
"theme_applies_note": "ينطبق هذا فقط على صفحات الحجز العامة",
"theme_light": "فاتح",
"theme_dark": "داكن",
"theme_system": "النظام الافتراضي",
"theme_system": "الافتراضي للنظام",
"add_a_team": "إضافة فريق",
"saml_config": "إعدادات SAML",
"add_webhook_description": "تلقي بيانات الاجتماع في الوقت الحقيقي حالما يحدث شيء ما في Cal.com",
"add_webhook_description": "تلقي بيانات الاجتماع في الوقت الحقيقي عندما يحدث شيء ما في Cal.com",
"triggers_when": "المشغلات عندما",
"test_webhook": "الرجاء إجراء اختبار ping قبل الإنشاء.",
"test_webhook": "يرجى الاختبار قبل الإنشاء.",
"enable_webhook": "تمكين Webhook",
"add_webhook": "إضافة Webhook",
"webhook_edited_successfully": "تم حفظ Webhook",
@ -1248,44 +1235,44 @@
"api_key_updated": "تم تحديث اسم مفتاح API",
"api_key_update_failed": "خطأ في تحديث اسم مفتاح API",
"embeds_title": "تضمين HTML iframe",
"embeds_description": "تضمين جميع أنواع الأحداث لديك على موقعك على الإنترنت",
"embeds_description": "تضمين جميع أنواع الأحداث على موقعك على ويب",
"create_first_api_key": "إنشاء أول مفتاح API لك",
"create_first_api_key_description": "تسمح مفاتيح API بتواصل التطبيقات الأخرى مع Cal.com",
"back_to_signin": "العودة لتسجيل الدخول",
"reset_link_sent": "تم إرسال رابط إعادة الضبط",
"password_reset_email": "ثمة بريد إلكتروني في طريقه إلى {{email}} مع تعليمات لإعادة تعيين كلمة المرور الخاصة بك.",
"password_reset_leading": "إذا لم تتلقَ رسالة إلكترونية قريباُ، تحقق من أن عنوان البريد الإلكتروني الذي أدخلته صحيح، وتحقق من مجلد البريد الطفيلي لديك، ثم تواصل مع قسم الدعم إذا استمرت المشكلة.",
"password_reset_email": "تم إرسال بريد إلكتروني إلى {{email}} يحتوي على تعليمات لإعادة تعيين كلمة المرور.",
"password_reset_leading": "إذا لم تستلم رسالة بريد إلكتروني قريبًا، تحقق من أن عنوان البريد الإلكتروني الذي أدخلته صحيح، وتحقق من مجلد البريد المزعج أو اتصل بالدعم إذا استمرت المشكلة.",
"password_updated": "تم تحديث كلمة المرور!",
"pending_payment": "في انتظار الدفع",
"confirmation_page_rainbow": "رمز بوابة الحدث الخاص بك مع الرموز أو NFTs على Ethereum وPolygon وغيرها.",
"not_on_cal": "ليس على Cal.com",
"no_calendar_installed": "لا يوجد رزنامة مثبتة",
"no_calendar_installed_description": "لم تقم بعد بالاتصال بأي من التقويمات الخاصة بك",
"add_a_calendar": "إضافة رزنامة",
"change_email_hint": "قد تحتاج إلى تسجيل الخروج والعودة مجددًا لرؤية التغييرات التي تم تنفيذها",
"confirm_password_change_email": "الرجاء تأكيد كلمة المرور الخاصة بك قبل تغيير عنوان بريدك الإلكتروني",
"confirmation_page_rainbow": "قم بتعيين رمز مميز لفتح الحدث الخاص بك باستخدام الرموز أو NFTs على Ethereum وPolygon وغيرها.",
"not_on_cal": "غير موجود على Cal.com",
"no_calendar_installed": "لا يوجد تقويم مثبت",
"no_calendar_installed_description": "لم تقم بربط أي من التقويمات الخاصة بك",
"add_a_calendar": "إضافة تقويم",
"change_email_hint": "قد تحتاج إلى تسجيل الخروج والعودة لرؤية التغييرات التي تم تنفيذها",
"confirm_password_change_email": "يرجى تأكيد كلمة المرور قبل تغيير عنوان بريدك الإلكتروني",
"seats": "مقاعد",
"every_app_published": "كل تطبيق منشور على متجر تطبيقات Cal.com هو مصدر مفتوح ويتم اختباره بشكل شامل عن طريق مراجعات الأقران. لا يدعم Cal.com أو يصادق على هذه التطبيقات ما لم يتم نشرها بواسطة Cal.com. إذا واجهت محتوى أو سلوك غير مناسب يرجى الإبلاغ عن ذلك.",
"report_app": "الإبلاغ عن التطبيق",
"every_app_published": "كل تطبيق منشور على Cal.com App Store مفتوح المصدر ويتم اختباره بدقة من خلال مراجعات الأقران. ومع ذلك، فإن شركة Cal.com لا تدعم هذه التطبيقات أو تصرح بها ما لم يتم نشرها بواسطة Cal.com. إذا واجهت محتوى أو سلوكًا غير لائق، فيرجى الإبلاغ عنه.",
"report_app": "الإبلاغ عن تطبيق",
"team_name_required": "اسم الفريق مطلوب",
"how_additional_inputs_as_variables": "كيفية استخدام مدخلات إضافية كمتغيرات",
"format": "التنسيق",
"uppercase_for_letters": "استخدام الحروف الكبيرة لجميع الأحرف",
"replace_whitespaces_underscores": "استبدال المساحات البيضاء بالشرطات السفلية",
"uppercase_for_letters": "استخدم الحروف الكبيرة لجميع الأحرف",
"replace_whitespaces_underscores": "استبدل المساحات البيضاء بالشرطات السفلية",
"manage_billing": "إدارة الفواتير",
"manage_billing_description": "إدارة كل الأشياء المفوترة",
"manage_billing_description": "إدارة كل ما يخص الفواتير",
"billing_freeplan_title": "أنت حاليًا على الخطة المجانية",
"billing_freeplan_description": "نحن نعمل بشكل أفضل ضمن فرق. وسّع سير عملك مع أحداث الذهاب والإياب والأحداث الجماعية و صنع نماذج مسارات متقدمة",
"billing_freeplan_description": "نحن نعمل بشكل أفضل ضمن فرق. وسّع سير عملك من خلال الأحداث الدورية والجماعية وإنشاء نماذج توجيه متقدمة",
"billing_freeplan_cta": "جرب الآن",
"billing_manage_details_title": "عرض تفاصيل الفوترة وإدارتها",
"billing_manage_details_description": "اعرض تفاصيل الفوترة وعدّلها، بالإضافة إلى إمكانية إلغاء الاشتراك.",
"billing_manage_details_title": "عرض تفاصيل الفواتير وإدارتها",
"billing_manage_details_description": "اعرض تفاصيل الفواتير وعدّلها، كما يمكنك إلغاء الاشتراك.",
"billing_portal": "بوابة الفواتير",
"billing_help_title": "هل تحتاج إلى أي شيء آخر؟",
"billing_help_description": "إذا كنت تحتاج إلى أي مساعدة إضافية بخصوص الفوترة، فإن فريق الدعم لدينا في انتظارك لتقديم المساعدة.",
"billing_help_cta": "الاتصال بالدعم",
"ignore_special_characters": "تجاهل الأحرف الخاصة في تسمية الإدخال الإضافية لديك. استخدم فقط الأحرف والأرقام",
"ignore_special_characters": "تجاهل الأحرف الخاصة في تسمية الإدخال الإضافية. استخدم فقط الأحرف والأرقام",
"retry": "إعادة المحاولة",
"fetching_calendars_error": "حدثت مشكلة أثناء حفظ بياناتك. الرجاء <1>المحاولة من جديد</1> أو التواصل مع دعم العملاء.",
"fetching_calendars_error": "حدثت مشكلة أثناء جلب التقويمات الخاصة بك. يرجى <1>إعادة المحاولة</1> أو الاتصال بدعم العملاء.",
"calendar_connection_fail": "فشل الاتصال بالتقويم",
"booking_confirmation_success": "تم تأكيد الحجز بنجاح",
"booking_confirmation_fail": "فشل تأكيد الحجز",
@ -1293,7 +1280,17 @@
"couldnt_update_timezone": "لم نتمكن من تحديث المنطقة الزمنية",
"updated_timezone_to": "تم تحديث المنطقة الزمنية إلى {{formattedCurrentTz}}",
"update_timezone": "تحديث المنطقة الزمنية",
"update_timezone_question": "تحديث المنطقة الزمنية؟",
"update_timezone_description": "يبدو أن منطقتك الزمنية المحلية قد تغيرت إلى {{formattedCurrentTz}}. من المهم جداً أن يكون لديك المنطقة الزمنية المناسبة لمنع الحجز في أوقات غير مرغوب فيها. هل تريد تحديثها؟",
"dont_update": "لا تقم بالتحديث"
"update_timezone_question": "هل تريد تحديث المنطقة الزمنية؟",
"update_timezone_description": "يبدو أن منطقتك الزمنية المحلية قد تغيرت إلى {{formattedCurrentTz}}. من المهم جداً تحديد المنطقة الزمنية المناسبة لمنع الحجز في أوقات غير مرغوب فيها. هل تريد تحديثها؟",
"dont_update": "لا تقم بالتحديث",
"saml_config": "تهيئة SAML",
"saml_config_deleted_successfully": "تم حذف تكوين SAML بنجاح",
"saml_configuration": "تكوين SAML",
"delete_saml_configuration": "حذف تكوين SAML",
"delete_saml_configuration_confirmation_message": "هل تريد بالتأكيد حذف تكوين SAML؟ لن يتمكن بعد الآن أعضاء فريقك الذين يستخدمون تسجيل الدخول عبر SAML من الوصول إلى Cal.com.",
"confirm_delete_saml_configuration": "نعم، احذف تكوين SAML",
"saml_not_configured_yet": "لم يتم تكوين SAML حتى الآن",
"saml_configuration_description": "يُرجى لصق بيانات تعريف SAML المقدمة من مزود الهوية في مربع النص أدناه لتحديث تكوين SAML.",
"saml_configuration_placeholder": "يُرجى لصق بيانات تعريف SAML المقدمة من مزود الهوية هنا",
"saml_email_required": "يُرجى إدخال بريد إلكتروني حتى نتمكن من العثور على مزود هوية SAML الخاص بك"
}

View File

@ -67,7 +67,7 @@
"event_still_awaiting_approval": "Událost stále čeká na schválení",
"booking_submitted_subject": "Rezervace odeslána: {{eventType}} s {{name}} během {{date}}",
"your_meeting_has_been_booked": "Vaše schůzka byla rezervována",
"event_type_has_been_rescheduled_on_time_date": "Váš {{eventType}} s {{name}} byl přesunut na {{time}} ({{timeZone}}) dne {{date}}.",
"event_type_has_been_rescheduled_on_time_date": "Váš {{eventType}} s {{name}} byl přesunut na {{date}}.",
"event_has_been_rescheduled": "Změna - Vaše událost byla přesunuta na jindy",
"request_reschedule_title_attendee": "Žádost o přeplánování rezervace",
"request_reschedule_subtitle": "Organizátor {{organizer}} zrušil rezervaci a požádal vás, abyste si vybrali jiný čas.",
@ -219,7 +219,7 @@
"check_email_reset_password": "Podívejte se do e-mailu. Poslali jsme vám odkaz pro obnovení hesla.",
"finish": "Dokončit",
"few_sentences_about_yourself": "Pár vět o vás. Budou se zobrazovat na URL vaší osobní stránky.",
"nearly_there": "Už to bude",
"nearly_there": "Téměř hotovo!",
"nearly_there_instructions": "A na závěr: Stručný text o vás a fotografie vám značně pomůžou v získávání rezervací. A ostatní díky tomu budou vědět, koho si rezervují.",
"set_availability_instructions": "Určete si časové intervaly, kdy jste pravidelně dostupní. Později můžete vytvořit další a přiřadit je různým kalendářům.",
"set_availability": "Nastavte, jak jste dostupní",
@ -233,7 +233,7 @@
"welcome_back": "Vítejte zpět",
"welcome_to_calcom": "Vítejte na webu Cal.com",
"welcome_instructions": "Řekněte nám vaše jméno a v jaké časové zóně se pohybujete. Později můžete tyto informace změnit.",
"connect_caldav": "Připojit se k CalDav (Beta)u",
"connect_caldav": "Připojit se k CalDav (Beta)",
"credentials_stored_and_encrypted": "Vaše přihlašovací údaje budou uloženy a zašifrovány.",
"connect": "Připojit",
"try_for_free": "Vyzkoušet zdarma",
@ -445,7 +445,7 @@
"sunday": "neděle",
"all_booked_today": "Všechen čas dnes rezervován.",
"slots_load_fail": "Nepodařilo se načíst dostupné časové sloty.",
"additional_guests": "+ Další hosté",
"additional_guests": "Přidat hosty",
"your_name": "Vaše jméno",
"email_address": "E-mailová adresa",
"enter_valid_email": "Zadejte platný e-mail",
@ -707,7 +707,7 @@
"delete_account_confirmation_message": "Opravdu chcete odstranit svůj účet na Cal.com? Všichni, se kterými jste sdíleli odkaz na účet, přijdou o možnost vytvářet rezervace a vy přijdete o uložené předvolby.",
"integrations": "Integrace",
"apps": "Aplikace",
"category_apps": "Aplikace ({{category}})",
"category_apps": "Aplikace v kategorii {{category}}",
"app_store": "App Store",
"app_store_description": "Propojování lidí, technologií a pracoviště.",
"settings": "Nastavení",
@ -745,22 +745,11 @@
"user_away_description": "Osoba, kterou se snažíte rezervovat, má nastavený status \"pryč\" a nepřijímá tak nové rezervace.",
"meet_people_with_the_same_tokens": "Seznamte se s lidmi se stejnými tokeny",
"only_book_people_and_allow": "Rezervujte si a dovolte rezervace od lidí se stejnými tokeny, DAO nebo NFT.",
"saml_config_deleted_successfully": "SAML konfigurace byla úspěšně smazána",
"account_created_with_identity_provider": "Váš účet byl vytvořen pomocí Identity Providera.",
"account_managed_by_identity_provider": "Váš účet spravuje {{provider}}",
"account_managed_by_identity_provider_description": "Pro změnu e-mailu, hesla, dvoufázového ověřování apod. prosím přejděte do nastavení účtu {{provider}}.",
"signin_with_google": "Přihlásit se pomocí Googlu",
"signin_with_saml": "Přihlásit se pomocí SAML",
"saml_configuration": "Konfigurace SAML",
"delete_saml_configuration": "Smazat konfiguraci SAML",
"delete_saml_configuration_confirmation_message": "Opravdu chcete smazat konfiguraci SAML? Vaši členové týmu, kteří využívají přihlašování přes SAML, přijdou o přístup ke Cal.com.",
"confirm_delete_saml_configuration": "Ano, smazat konfiguraci SAML",
"saml_not_configured_yet": "SAML zatím není nakonfigurován",
"saml_configuration_description": "Pro aktualizaci SAML vložte prosím metadata SAML od poskytovatele vaší identity do textového pole níže.",
"saml_configuration_placeholder": "Vložte sem SAML metadata vašeho Identity Providera",
"saml_configuration_update_failed": "Aktualizace konfigurace SAML se nezdařila",
"saml_configuration_delete_failed": "Smazání konfigurace SAML se nezdařilo",
"saml_email_required": "Zadejte prosím e-mail, abychom mohli určit vašeho SAML Identity Providera",
"you_will_need_to_generate": "Budete muset vygenerovat přístupový token ve vašem dřívějším plánovacím nástroji.",
"import": "Importovat",
"import_from": "Importovat z",
@ -768,19 +757,19 @@
"visit_roadmap": "Roadmapa",
"featured_categories": "Doporučené kategorie",
"popular_categories": "Oblíbené kategorie",
"number_apps_one": "Aplikace: {{count}}",
"number_apps_other": "Aplikace: {{count}}",
"number_apps_one": "Počet aplikací: {{count}}",
"number_apps_other": "Počet aplikací: {{count}}",
"trending_apps": "Populární aplikace",
"explore_apps": "Aplikace ({{category}})",
"explore_apps": "Aplikace v kategorii {{category}}",
"installed_apps": "Nainstalované aplikace",
"no_category_apps": "Žádné aplikace ({{kategorie}})",
"no_category_apps": "Žádné aplikace v kategorii {{category}}",
"no_category_apps_description_calendar": "Přidejte aplikaci kalendáře, ať máte přehled v zájmu prevence dvojích rezervací",
"no_category_apps_description_conferencing": "Zkuste přidat aplikaci pro konference, která umožní propojit videohovory s vašimi klienty",
"no_category_apps_description_payment": "Přidejte platební aplikaci, která usnadní provádění transakcí mezi vámi a vašimi klienty",
"no_category_apps_description_other": "Přidejte jakýkoli jiný typ aplikace pro nejrůznější činnosti",
"installed_app_calendar_description": "Nastavte si kalendáře, ať můžete kontrolovat konflikty a zabránit tak dvojím rezervacím.",
"installed_app_conferencing_description": "Přidejte své oblíbené aplikace pro videokonference pro vaše schůzky",
"installed_app_payment_description": "Nakonfigurujte služby zpracování plateb, které se mají používat při strhávání plateb z klientů.",
"installed_app_payment_description": "Nakonfigurujte služby zpracování plateb, které se mají používat při strhávání plateb od klientů.",
"installed_app_other_description": "Všechny vaše nainstalované aplikace z ostatních kategorií.",
"empty_installed_apps_headline": "Nenainstalovány žádné aplikace",
"empty_installed_apps_description": "Aplikace vám umožní zlepšit pracovní postupy a výrazně zkvalitnit plánování.",
@ -820,7 +809,7 @@
"set_to_default": "Nastavit jako výchozí",
"new_schedule_btn": "Nový plán",
"add_new_schedule": "Přidat nový plán",
"add_new_calendar": "Přidání nového kalendáře",
"add_new_calendar": "Přidat nový kalendář",
"set_calendar": "Nastavte, kam se mají při rezervaci přidávat nové události.",
"delete_schedule": "Odstranit plán",
"schedule_created_successfully": "Plán {{scheduleName}} byl úspěšně vytvořen",
@ -839,7 +828,7 @@
"external_redirect_url": "https://example.com/redirect-to-my-success-page",
"duplicate": "Duplikovat",
"offer_seats": "Nabídka míst",
"offer_seats_description": "Nabídnout místa rezervacím (tím se vypnou rezervace hostů a s potvrzením)",
"offer_seats_description": "Nabídnout místa k rezervaci. Tím se vypnou rezervace hostů a volitelné rezervace.",
"seats_available": "Dostupná místa",
"number_of_seats": "Počet míst na rezervaci",
"enter_number_of_seats": "Zadejte počet míst",
@ -893,7 +882,7 @@
"impersonate": "Zosobnit",
"user_impersonation_heading": "Přizpůsobení uživatele",
"user_impersonation_description": "Náš tým podpory se bude moci dočasně přihlásit místo vás, aby nám pomohl rychle vyřešit jakékoli problémy, které nám nahlásíte.",
"team_impersonation_description": "Administrátoři vašeho týmu se mohou dočasně přihlásit jako vy.",
"team_impersonation_description": "Členové vašeho týmu se mohou dočasně přihlásit jako vy.",
"impersonate_user_tip": "Všechna použití této funkce jsou kontrolována.",
"impersonating_user_warning": "Zosobnění uživatelského jména „{{user}}“.",
"impersonating_stop_instructions": "<0>Kliknutím zde zastavte</0>.",
@ -921,13 +910,13 @@
"zapier_setup_instructions": "<0>Přihlaste se ke svému účtu Zapier a vytvořte nový Zap.</0><1>Vyberte Cal.com jako svoji aplikaci Trigger. Také vyberte událost Trigger.</1><2>Vyberte si svůj účet a pak zadejte svůj unikátní klíč API.</2><3>Otestujte aplikaci Trigger.</3><4>Vše máte nastaveno!</4>",
"install_zapier_app": "Nejdřív si z App Store nainstalujte aplikaci Zapier.",
"connect_apple_server": "Připojit se k serveru Apple",
"connect_caldav_server": "Připojit se k CalDav (Beta)u",
"connect_caldav_server": "Připojit se k CalDav (Beta)",
"calendar_url": "URL kalendáře",
"apple_server_generate_password": "Vygenerujte si specifické heslo pro aplikaci Cal.com na",
"credentials_stored_encrypted": "Vaše přihlašovací údaje budou uloženy a zašifrovány.",
"it_stored_encrypted": "Bude uložen a zašifrován.",
"go_to_app_store": "Přejít do App Store",
"calendar_error": "Něco se pokazilo, zkuste znovu připojit kalendář se všemi nezbytnými oprávněními",
"calendar_error": "Zkuste znovu připojit kalendář se všemi nezbytnými oprávněními",
"set_your_phone_number": "Nastavte telefonní číslo pro schůzku",
"calendar_no_busy_slots": "Neexistují žádné obsazené sloty",
"display_location_label": "Zobrazit na stránce rezervace",
@ -1007,8 +996,8 @@
"remove_app": "Odstranit aplikaci",
"yes_remove_app": "Ano, odstranit aplikaci",
"are_you_sure_you_want_to_remove_this_app": "Opravdu chcete odstranit tuto aplikaci?",
"app_removed_successfully": "Aplikace odstraněna",
"error_removing_app": "Chyba při odstraňování aplikace",
"app_removed_successfully": "Aplikace byla odstraněna",
"error_removing_app": "Při odstraňování aplikace došlo k chybě",
"web_conference": "Webová konference",
"number_for_sms_reminders": "Telefonní číslo (pro upomínky přes SMS)",
"requires_confirmation": "Vyžaduje potvrzení",
@ -1064,8 +1053,8 @@
"navigate": "Navigace",
"open": "Otevřít",
"close": "Zavřít",
"team_feature_teams": "Toto je funkce Team. Pokud chcete zobrazit dostupnost svého týmu, upgradujte na verzi Team.",
"team_feature_workflows": "Jedná se o funkci Team. Upgradujte na verzi Team, abyste mohli automatizovat upozornění na události a upomínky pomocí pracovních postupů.",
"team_feature_teams": "Toto je funkce verze Team. Pokud chcete zobrazit dostupnost svého týmu, upgradujte na verzi Team.",
"team_feature_workflows": "Jedná se o funkci verze Team. Upgradujte na verzi Team, abyste mohli automatizovat upozornění na události a upomínky pomocí pracovních postupů.",
"show_eventtype_on_profile": "Zobrazit na profilu",
"embed": "Vložit",
"new_username": "Nové uživatelské jméno",
@ -1125,13 +1114,13 @@
"customize_your_brand_colors": "Vytvořte si rezervační stránku na míru v barvách své značky.",
"pro": "Pro",
"removes_cal_branding": "Odstraní všechny značky související s Cal, tj. „Běží na platformě Cal.“",
"profile_picture": "Profilový obrázek",
"profile_picture": "Profilová fotka",
"upload": "Nahrát",
"web3": "Web3",
"rainbow_token_gated": "Tento typ události je uzamčený tokenem.",
"rainbow_connect_wallet_gate": "Propojte svoji peněženku, pokud vlastníte <1>{{name}}</1> (<3>{{symbol}}</3>).",
"rainbow_insufficient_balance": "Vaše propojená peněženka neobsahuje dostatek <1>{{symbol}}</1>.",
"rainbow_sign_message_request": "Podepište zprávu s žádostí ve své peněžence.",
"rainbow_sign_message_request": "Podepište zprávu s požadavkem ve své peněžence.",
"rainbow_signature_error": "Chyba při vyžádání podpisu z vaší peněženky.",
"token_address": "Adresa tokenu",
"blockchain": "Blockchain",
@ -1153,11 +1142,11 @@
"users": "Uživatelé",
"profile_description": "Správa nastavení profilu Cal",
"general_description": "Správa nastavení jazyka a časového pásma",
"calendars_description": "Konfigurace způsobu interkace typů událostí s kalendáři",
"calendars_description": "Konfigurace způsobu interakce typů událostí s kalendáři",
"appearance_description": "Správa nastavení vzhledu rezervace",
"conferencing_description": "Správa videokonferenčních aplikací pro vaše schůzky",
"password_description": "Správa nastavení pro hesla vašich účtů",
"2fa_description": "Správa nastavení pro hesla vašich účtů",
"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í.",
"skip": "Přeskočit",
"do_this_later": "Provést později",
@ -1166,8 +1155,8 @@
"connect_calendars_from_app_store": "Další kalendáře můžete přidat přes obchod s aplikacemi",
"connect_conference_apps": "Propojit konferenční aplikace",
"connect_calendar_apps": "Propojit kalendářové aplikace",
"connect_payment_apps": "Připojit platební aplikace",
"connect_other_apps": "Připojit další aplikace",
"connect_payment_apps": "Propojit platební aplikace",
"connect_other_apps": "Propojit další aplikace",
"current_step_of_total": "Krok {{currentStep}} / {{maxSteps}}",
"add_variable": "Přidat proměnnou",
"custom_phone_number": "Vlastní telefonní číslo",
@ -1182,7 +1171,6 @@
"additional_notes_info": "Další poznámky k rezervaci",
"attendee_name_info": "Jméno osoby provádějící rezervaci",
"to": "Komu",
"attendee_required_enter_number": "Při rezervaci je nutné, aby účastník zadal telefonní číslo",
"workflow_turned_on_successfully": "Pracovní postup {{workflowName}} byl {{offOn}}",
"download_responses": "Stáhnout odpovědi",
"create_your_first_form": "Vytvořte svůj první formulář",
@ -1198,7 +1186,7 @@
"delete_team": "Odstranit tým",
"team_members": "Členové týmu",
"more": "Více",
"more_page_footer": "Mobilní aplikaci považujeme za rozšíření webové aplikace. Pokud provádíte složitější akce, vraťte se prosím k webové aplikaci.",
"more_page_footer": "Mobilní aplikaci považujeme za rozšíření webové aplikace. Pokud provádíte složitější akce, proveďte je prosím ve webové aplikaci.",
"workflow_example_1": "Odeslat účastníkovi SMS upomínku 24 hodin před začátkem akce",
"workflow_example_2": "Odeslat účastníkovi vlastní SMS, pokud je událost přeložena na jiný termín",
"workflow_example_3": "Odeslat hostiteli vlastní e-mail, pokud je rezervována nová událost",
@ -1217,15 +1205,15 @@
"only_owner_change": "Změny v rezervaci týmu může provádět pouze jeho vlastník ",
"team_disable_cal_branding_description": "Odstraní všechny značky související s Cal, tj. „Běží na platformě Cal.“",
"invited_by_team": "Tým {{teamName}} vás pozval do svého týmu na pozici {{role}}",
"token_invalid_expired": "Token je buď neplatný, nebo jeho platnost vypršela.",
"token_invalid_expired": "Token je buď neplatný, nebo vypršela jeho platnost.",
"exchange_add": "Propojit s Microsoft Exchange",
"exchange_authentication": "Metoda ověřování",
"exchange_authentication_standard": "Základní ověření",
"exchange_authentication": "Způsob ověřování",
"exchange_authentication_standard": "Základní ověřování",
"exchange_authentication_ntlm": "Ověřování NTLM",
"exchange_compression": "Komprese GZip",
"routing_forms_description": "Zde si můžete prohlédnout všechny vytvořené formuláře a trasy.",
"add_new_form": "Přidat nový formulář",
"form_description": "Vytvořte formulář, který adresujete rezervující osobě",
"form_description": "Vytvořte vlastní formulář, který adresujete rezervující osobě",
"copy_link_to_form": "Kopírovat odkaz na formulář",
"theme": "Motiv",
"theme_applies_note": "Toto se týká pouze vašich veřejných stránek s rezervacemi",
@ -1233,40 +1221,49 @@
"theme_dark": "Tmavý",
"theme_system": "Výchozí nastavení systému",
"add_a_team": "Přidat tým",
"saml_config": "Konfig. SAML",
"add_webhook_description": "Přijímejte data o schůzkách v reálném čase, pokud v Cal.com dojde k nějaké akci",
"triggers_when": "Spustí se, pokud:",
"test_webhook": "Před vytvořením proveďte test ping.",
"enable_webhook": "Povolit webhook",
"add_webhook": "Přidat webhook",
"webhook_edited_successfully": "Webhook uložen",
"webhook_edited_successfully": "Webhook byl uložen",
"webhooks_description": "Přijímejte data o schůzkách v reálném čase, pokud v Cal.com dojde k nějaké akci",
"api_keys_description": "Vygenerujte klíče API pro přístup k vlastnímu účtu",
"api_keys_description": "Vygenerujte si klíče API pro přístup k vlastnímu účtu",
"new_api_key": "Nový klíč API",
"active": "Aktivní",
"api_key_updated": "Aktualizace názvu klíče API",
"api_key_update_failed": "Chyba při aktualizaci názvu klíče API",
"api_key_updated": "Název klíče API byl aktualizován",
"api_key_update_failed": "Při aktualizaci názvu klíče API došlo k chybě",
"embeds_title": "Vložení rámce HTML iframe",
"embeds_description": "Vložte všechny typy událostí na vaši webovou stránku",
"create_first_api_key": "Vytvořte svůj první klíč API",
"create_first_api_key_description": "Klíče API umožňují ostatním aplikacím komunikovat se službou Cal.com",
"back_to_signin": "Zpět na přihlášení",
"reset_link_sent": "Odkaz pro obnovení odeslán",
"password_reset_email": "Na adresu {{email}} je na cestě e-mail s pokyny k obnovení hesla.",
"password_updated": "Heslo aktualizováno!",
"reset_link_sent": "Odkaz pro obnovení byl odeslán",
"password_reset_email": "E-mail s pokyny k obnovení hesla byl zaslán na adresu {{email}}.",
"password_updated": "Heslo bylo úspěšně změněno!",
"pending_payment": "Čeká se na platbu",
"confirmation_page_rainbow": "Zamkněte svoji událost pomocí tokenů nebo NFT na platformách Ethereum, Polygon a dalších.",
"not_on_cal": "Nenachází se na Cal.com",
"no_calendar_installed": "Není nainstalován kalendář",
"no_calendar_installed_description": "Ještě jste nepropojili žádný ze svých kalendářů",
"add_a_calendar": "Přidejte kalendář",
"add_a_calendar": "Přidat kalendář",
"change_email_hint": "Možná se budete muset odhlásit a znovu přihlásit, aby se změny projevily",
"confirm_password_change_email": "Před změnou e-mailové adresy potvrďte své heslo",
"seats": "míst",
"every_app_published": "Každá aplikace zveřejněná v obchodě s aplikacemi na Cal.com má otevřený zdrojový kód a je důkladně testována prostřednictvím vzájemného hodnocení. Společnost Cal.com, Inc. nicméně tyto aplikace neschvaluje ani necertifikuje, pokud nejsou zveřejněny přímo společností Cal.com. Pokud se setkáte s nevhodným obsahem nebo chováním, nahlaste to.",
"report_app": "Nahlásit aplikaci",
"billing_manage_details_title": "Zobrazit a spravovat fakturační údaje",
"billing_manage_details_description": "Zobrazit a upravovat fakturační údaje, možnost zrušit předplatné.",
"billing_manage_details_description": "Zobrazit a spravovat fakturační údaje, možnost zrušit předplatné.",
"billing_help_title": "Potřebujete ještě něco?",
"billing_help_description": "Pokud potřebujete další pomoc s fakturací, náš tým podpory je zde, aby vám pomohl."
"billing_help_description": "Pokud potřebujete jakoukoliv pomoc s fakturací, náš tým zákaznické podpory je tu pro Vás.",
"saml_config": "Konfigurace SAML",
"saml_config_deleted_successfully": "SAML konfigurace byla úspěšně smazána",
"saml_configuration": "Konfigurace SAML",
"delete_saml_configuration": "Smazat konfiguraci SAML",
"delete_saml_configuration_confirmation_message": "Opravdu chcete smazat konfiguraci SAML? Vaši členové týmu, kteří využívají přihlašování přes SAML, přijdou o přístup ke Cal.com.",
"confirm_delete_saml_configuration": "Ano, smazat konfiguraci SAML",
"saml_not_configured_yet": "SAML zatím není nakonfigurován",
"saml_configuration_description": "Pro aktualizaci SAML vložte prosím metadata SAML od poskytovatele vaší identity do textového pole níže.",
"saml_configuration_placeholder": "Vložte sem SAML metadata vašeho Identity Providera",
"saml_email_required": "Zadejte prosím e-mail, abychom mohli určit vašeho SAML Identity Providera"
}

View File

@ -745,22 +745,11 @@
"user_away_description": "Die Person, mi der Sie einen Termin buchen wollen, ist zur Zeit abwesend und akzeptiert daher keine neuen Buchungen.",
"meet_people_with_the_same_tokens": "Triff Leute mit den selben Token",
"only_book_people_and_allow": "Nur Buchungen von Personen erlauben, die die gleichen Token, DAOs oder NFTs teilen.",
"saml_config_deleted_successfully": "SAML-Konfiguration wurde erfolgreich gelöscht",
"account_created_with_identity_provider": "Ihr Konto wurde mit einem Identitäts-Anbieter erstellt.",
"account_managed_by_identity_provider": "Ihr Konto wird verwaltet von {{provider}}",
"account_managed_by_identity_provider_description": "Um Ihre E-Mail-Adresse, Ihr Passwort, die Zwei-Faktor-Authentifizierung und sonstiges zu ändern, besuchen Sie bitte Ihre {{provider}} -Kontoeinstellungen.",
"signin_with_google": "Mit Google anmelden",
"signin_with_saml": "Anmelden mit SAML",
"saml_configuration": "SAML-Einstellungen",
"delete_saml_configuration": "SAML-Konfiguration löschen",
"delete_saml_configuration_confirmation_message": "Sind Sie sicher, dass Sie die SAML-Konfiguration löschen möchten? Ihre Teammitglieder, die SAML-Login verwenden, können nicht mehr auf Cal.com zugreifen.",
"confirm_delete_saml_configuration": "Ja, SAML-Einstellungen löschen",
"saml_not_configured_yet": "SAML wurde noch nicht konfiguriert",
"saml_configuration_description": "Bitte fügen Sie die SAML-Metadaten Ihres SAML-Anbieters in die Textbox ein, um Ihre SAML-Konfiguration zu aktualisieren.",
"saml_configuration_placeholder": "Bitte fügen Sie die SAML-Metadaten von Ihrem SAML-Anbieter hier ein",
"saml_configuration_update_failed": "Aktualisieren der SAML-Konfiguration fehlgeschlagen",
"saml_configuration_delete_failed": "Löschen der SAML-Konfiguration fehlgeschlagen",
"saml_email_required": "Bitte geben Sie eine E-Mail ein, damit wir Ihren SAML Anbieter finden können",
"you_will_need_to_generate": "Sie müssen ein Zugriffstoken in ihrem alten Kalender-Tool erzeugen.",
"import": "Importieren",
"import_from": "Importieren von",
@ -1182,7 +1171,6 @@
"additional_notes_info": "Die zusätzlichen Anmerkungen der Buchung",
"attendee_name_info": "Name der buchenden Person",
"to": "An",
"attendee_required_enter_number": "Hier muss der Teilnehmer bei der Buchung eine Telefonnummer eingeben",
"workflow_turned_on_successfully": "{{workflowName}} Workflow erfolgreich {{offOn}}geschaltet",
"download_responses": "Antworten herunterladen",
"create_your_first_form": "Erstellen Sie Ihr erstes Formular",
@ -1233,7 +1221,6 @@
"theme_dark": "Dunkel",
"theme_system": "Systemstandard",
"add_a_team": "Team hinzufügen",
"saml_config": "SAML-Konfiguration",
"add_webhook_description": "Erhalten Sie Meetingdaten in Echtzeit, wenn etwas auf Cal.com passiert",
"triggers_when": "Löst aus, wenn",
"test_webhook": "Bitte führen Sie einen Pingtest vor der Erstellung durch.",
@ -1268,5 +1255,16 @@
"billing_manage_details_title": "Ihre Rechnungsdetails ansehen und verwalten",
"billing_manage_details_description": "Rechnungsdaten ansehen, bearbeiten oder Abonnement kündigen.",
"billing_help_title": "Brauchen Sie etwas anderes?",
"billing_help_description": "Wenn Sie weitere Hilfe bei der Rechnungsstellung benötigen, hilft Ihnen unser Support-Team gerne weiter."
"billing_help_description": "Wenn Sie weitere Hilfe bei der Rechnungsstellung benötigen, hilft Ihnen unser Support-Team gerne weiter.",
"saml_config": "SAML-Konfiguration",
"saml_config_deleted_successfully": "SAML-Konfiguration wurde erfolgreich gelöscht",
"saml_config_updated_successfully": "SAML-Konfiguration erfolgreich aktualisiert",
"saml_configuration": "SAML-Einstellungen",
"delete_saml_configuration": "SAML-Konfiguration löschen",
"delete_saml_configuration_confirmation_message": "Sind Sie sicher, dass Sie die SAML-Konfiguration löschen möchten? Ihre Teammitglieder, die SAML-Login verwenden, können nicht mehr auf Cal.com zugreifen.",
"confirm_delete_saml_configuration": "Ja, SAML-Einstellungen löschen",
"saml_not_configured_yet": "SAML wurde noch nicht konfiguriert",
"saml_configuration_description": "Bitte fügen Sie die SAML-Metadaten Ihres SAML-Anbieters in die Textbox ein, um Ihre SAML-Konfiguration zu aktualisieren.",
"saml_configuration_placeholder": "Bitte fügen Sie die SAML-Metadaten von Ihrem SAML-Anbieter hier ein",
"saml_email_required": "Bitte geben Sie eine E-Mail ein, damit wir Ihren SAML Anbieter finden können"
}

View File

@ -543,6 +543,7 @@
"link_shared": "Link shared!",
"title": "Title",
"description": "Description",
"apps_status": "Apps Status",
"quick_video_meeting": "A quick video meeting.",
"scheduling_type": "Scheduling Type",
"preview_team": "Preview team",
@ -746,22 +747,11 @@
"user_away_description": "The person you are trying to book has set themselves to away, and therefore is not accepting new bookings.",
"meet_people_with_the_same_tokens": "Meet people with the same tokens",
"only_book_people_and_allow": "Only book and allow bookings from people who share the same tokens, DAOs, or NFTs.",
"saml_config_deleted_successfully": "SAML configuration deleted successfully",
"account_created_with_identity_provider": "Your account was created using an Identity Provider.",
"account_managed_by_identity_provider": "Your account is managed by {{provider}}",
"account_managed_by_identity_provider_description": "To change your email, password, enable two-factor authentication and more, please visit your {{provider}} account settings.",
"signin_with_google": "Sign in with Google",
"signin_with_saml": "Sign in with SAML",
"saml_configuration": "SAML configuration",
"delete_saml_configuration": "Delete SAML configuration",
"delete_saml_configuration_confirmation_message": "Are you sure you want to delete the SAML configuration? Your team members who use SAML login will no longer be able to access Cal.com.",
"confirm_delete_saml_configuration": "Yes, delete SAML configuration",
"saml_not_configured_yet": "SAML not configured yet",
"saml_configuration_description": "Please paste the SAML metadata from your Identity Provider in the textbox below to update your SAML configuration.",
"saml_configuration_placeholder": "Please paste the SAML metadata from your Identity Provider here",
"saml_configuration_update_failed": "SAML configuration update failed",
"saml_configuration_delete_failed": "SAML configuration delete failed",
"saml_email_required": "Please enter an email so we can find your SAML Identity Provider",
"you_will_need_to_generate": "You will need to generate an access token from your old scheduling tool.",
"import": "Import",
"import_from": "Import from",
@ -1191,7 +1181,6 @@
"additional_notes_info": "The Additional notes of booking",
"attendee_name_info": "The person booking's name",
"to": "To",
"attendee_required_enter_number": "This will require the attendee to enter a phone number when booking",
"workflow_turned_on_successfully": "{{workflowName}} workflow turned {{offOn}} successfully",
"download_responses": "Download Responses",
"create_your_first_form": "Create your first form",
@ -1242,7 +1231,6 @@
"theme_dark": "Dark",
"theme_system": "System default",
"add_a_team": "Add a team",
"saml_config": "SAML Config",
"add_webhook_description": "Receive meeting data in real-time when something happens in Cal.com",
"triggers_when": "Triggers when",
"test_webhook": "Please ping test before creating.",
@ -1279,6 +1267,7 @@
"limit_booking_frequency_description":"Limit how many times this event can be booked",
"add_limit":"Add Limit",
"team_name_required": "Team name required",
"show_attendees": "Share attendee information between guests",
"how_additional_inputs_as_variables": "How to use Additional Inputs as Variables",
"format": "Format",
"uppercase_for_letters": "Use uppercase for all letters",
@ -1307,9 +1296,33 @@
"update_timezone_question": "Update Timezone?",
"update_timezone_description": "It seems like your local timezone has changed to {{formattedCurrentTz}}. It's very important to have the correct timezone to prevent bookings at undesired times. Do you want to update it?",
"dont_update": "Don't update",
"require_additional_notes": "Require additional notes",
"require_additional_notes_description": "Require additional notes to be filled out when booking",
"email_address_action": "send email to a specific email address",
"after_event_trigger": "after event ends",
"how_long_after": "How long after event ends?",
"no_available_slots": "No Available slots",
"time_available": "Time available"
"time_available": "Time available",
"install_new_calendar_app": "Install new calendar app",
"make_phone_number_required": "Make phone number required for booking event",
"dont_have_permission": "You don't have permission to access this resource.",
"saml_config": "Single Sign-On",
"saml_description": "Allow team members to login using an Identity Provider",
"saml_config_deleted_successfully": "SAML configuration deleted successfully",
"saml_config_updated_successfully": "SAML configuration updated successfully",
"saml_configuration": "SAML configuration",
"delete_saml_configuration": "Delete SAML configuration",
"delete_saml_configuration_confirmation_message": "Are you sure you want to delete the SAML configuration? Your team members who use SAML login will no longer be able to access Cal.com.",
"confirm_delete_saml_configuration": "Yes, delete SAML configuration",
"saml_not_configured_yet": "SAML not configured yet",
"saml_configuration_description": "Please paste the SAML metadata from your Identity Provider in the textbox below to update your SAML configuration.",
"saml_configuration_placeholder": "Please paste the SAML metadata from your Identity Provider here",
"saml_email_required": "Please enter an email so we can find your SAML Identity Provider",
"saml_sp_title": "Service Provider Details",
"saml_sp_description": "Your Identity Provider (IdP) will ask you for the following details to complete the SAML application configuration.",
"saml_sp_acs_url": "ACS URL",
"saml_sp_entity_id": "SP Entity ID",
"saml_sp_acs_url_copied": "ACS URL copied!",
"saml_sp_entity_id_copied": "SP Entity ID copied!",
"saml_btn_configure": "Configure"
}

View File

@ -67,7 +67,7 @@
"event_still_awaiting_approval": "Un evento aún está esperando su aprobación",
"booking_submitted_subject": "Reserva enviada: {{eventType}} con {{name}} el {{date}}",
"your_meeting_has_been_booked": "Su reunión ha sido reservada",
"event_type_has_been_rescheduled_on_time_date": "Tu {{eventType}} con {{name}} ha sido reprogramado a {{time}} ({{timeZone}}) el {{date}}.",
"event_type_has_been_rescheduled_on_time_date": "Tu {{eventType}} con {{name}} se reprogramó para el {{date}}.",
"event_has_been_rescheduled": "Tu evento ha sido reprogramado.",
"request_reschedule_title_attendee": "Solicitud para reprogramar su reserva",
"request_reschedule_subtitle": "{{organizer}} ha cancelado la reserva y le ha solicitado que elija otra hora.",
@ -210,7 +210,7 @@
"sign_in": "Iniciar Sesión",
"go_back_login": "Volver a la página de inicio",
"error_during_login": "Se ha producido un error al iniciar sesión. Vuelve a la pantalla de inicio de sesión e inténtalo de nuevo.",
"request_password_reset": "Reiniciar Contraseña",
"request_password_reset": "Restablecer contraseña",
"forgot_password": "Olvidé la contraseña",
"forgot": "¿Olvidado?",
"done": "Hecho",
@ -219,7 +219,7 @@
"check_email_reset_password": "Revisa tu email. Te hemos enviado un enlace para restablecer tu contraseña.",
"finish": "Terminar",
"few_sentences_about_yourself": "Unas pocas frases sobre ti mismo. Esto aparecerá en tu página de url personal.",
"nearly_there": "Cerca de Allí",
"nearly_there": "¡Casi listo!",
"nearly_there_instructions": "Por último, una breve descripción de ti y una foto realmente te ayudan a hacer tus reservas y a informar a la gente con quién está reservando.",
"set_availability_instructions": "Defina los intervalos de tiempo cuando esté disponible de forma recurrente. Puede crear más tarde y asignarlos a diferentes calendarios.",
"set_availability": "Establecer Disponibilidad",
@ -233,7 +233,7 @@
"welcome_back": "Bienvenido de nuevo",
"welcome_to_calcom": "Bienvenido a Cal.com",
"welcome_instructions": "Cuéntanos cómo llamarte y dinos la zona horaria en la que estás. Podrás editarla más tarde.",
"connect_caldav": "Conectar con el servidor de CalDav",
"connect_caldav": "Conectar con CalDav (Beta)",
"credentials_stored_and_encrypted": "Sus credenciales serán almacenadas y cifradas.",
"connect": "Conectar",
"try_for_free": "Pruébalo Gratis",
@ -303,7 +303,7 @@
"no_availability": "Sin disponibilidad",
"no_meeting_found": "Reunión no Encontrada",
"no_meeting_found_description": "Esta reunión no existe. Póngase en contacto con el responsable de la reunión para obtener un enlace actualizado.",
"no_status_bookings_yet": "Aún no hay {{status}} reservas,",
"no_status_bookings_yet": "Aún no hay reservas {{status}}",
"no_status_bookings_yet_description": "No tienes {{status}} reservas. {{description}}",
"event_between_users": "{{eventName}} entre {{host}} y {{attendeeName}}",
"bookings": "Reservas",
@ -356,7 +356,7 @@
"meeting_ended": "Reunión finalizada",
"form_submitted": "Formulario enviado",
"event_triggers": "Activadores de Eventos",
"subscriber_url": "Url del suscriptor",
"subscriber_url": "URL del suscriptor",
"create_new_webhook": "Crear un nuevo Webhook",
"webhooks": "Webhooks",
"team_webhooks": "Webhooks del equipo",
@ -445,7 +445,7 @@
"sunday": "Domingo",
"all_booked_today": "Todo Reservado Hoy.",
"slots_load_fail": "No se pudo cargar el intervalo de tiempo disponible.",
"additional_guests": "+ Invitados Adicionales",
"additional_guests": "Añadir invitados",
"your_name": "Tu Nombre",
"email_address": "Email",
"enter_valid_email": "Ingresa un correo electrónico válido",
@ -482,12 +482,12 @@
"leave": "Abandonar",
"profile": "Perfil",
"my_team_url": "URL de Mi Equipo",
"team_name": "Nombre del Equipo",
"team_name": "Nombre del equipo",
"your_team_name": "Nombre de tu Equipo",
"team_updated_successfully": "Equipo Actualizado Correctamente",
"your_team_updated_successfully": "Tu equipo ha sido actualizado con éxito.",
"about": "Acerca de",
"team_description": "Algunas frases sobre tu equipo. Esto aparecerá en la página de la URL de tu equipo.",
"team_description": "Comentarios sobre tu equipo. Esta información aparecerá en la página de la URL de tu equipo.",
"members": "Miembros",
"member": "Miembro",
"owner": "Propietario",
@ -497,9 +497,9 @@
"new_member": "Nuevo Miembro",
"invite": "Invitación",
"add_team_members": "Añadir miembros del equipo",
"add_team_members_description": "Invite a otros para unirse a su equipo",
"add_team_members_description": "Invitar a otros a unirse a tu equipo",
"add_team_member": "Añadir miembro del equipo",
"invite_new_member": "Invita a un Nuevo Miembro",
"invite_new_member": "Invitar a un nuevo miembro",
"invite_new_team_member": "Invita a alguien a tu equipo.",
"change_member_role": "Cambiar rol del miembro del equipo",
"disable_cal_branding": "Desactivar marca de Cal.com",
@ -745,22 +745,11 @@
"user_away_description": "La persona a la que estás intentando reservar se ha marcado a sí misma como Ausente, por lo tanto, no acepta nuevas reservas por ahora.",
"meet_people_with_the_same_tokens": "Conoce personas con los mismos tokens",
"only_book_people_and_allow": "Solo reservar y permitir reservas de personas que compartan los mismos tokens, DAO o NFT.",
"saml_config_deleted_successfully": "Configuración SAML eliminada correctamente",
"account_created_with_identity_provider": "Tu cuenta se creó mediante un Proveedor de Identidad.",
"account_managed_by_identity_provider": "Tu cuenta es administrada por {{provider}}",
"account_managed_by_identity_provider_description": "Para cambiar tu correo electrónico, contraseña, habilitar la autenticación de doble factor y más, por favor visita la configuración de tu cuenta de {{provider}}.",
"signin_with_google": "Iniciar sesión con Google",
"signin_with_saml": "Iniciar sesión con SAML",
"saml_configuration": "Configuración SAML",
"delete_saml_configuration": "Eliminar configuración SAML",
"delete_saml_configuration_confirmation_message": "¿Estás seguro de que deseas eliminar la configuración SAML? Los miembros de tu equipo que utilicen el inicio de sesión SAML ya no podrán acceder a Cal.com.",
"confirm_delete_saml_configuration": "Sí, eliminar la configuración SAML",
"saml_not_configured_yet": "SAML no está configurado aún",
"saml_configuration_description": "Pega los metadatos SAML de tu Proveedor de Identidad en el cuadro de texto de abajo para actualizar tu configuración SAML.",
"saml_configuration_placeholder": "Pega los metadatos SAML de tu Proveedor de Identidad aquí",
"saml_configuration_update_failed": "Error al actualizar la configuración SAML",
"saml_configuration_delete_failed": "Error al eliminar la configuración SAML",
"saml_email_required": "Por favor, introduce un correo electrónico para que podamos encontrar tu Proveedor de Identidad SAML",
"you_will_need_to_generate": "Necesitarás generar un token de acceso desde tu antigua herramienta de programación.",
"import": "Importar",
"import_from": "Importar desde",
@ -774,14 +763,14 @@
"explore_apps": "Aplicaciones {{category}}",
"installed_apps": "Aplicaciones instaladas",
"no_category_apps": "No hay aplicaciones de {{category}}",
"no_category_apps_description_calendar": "Añada una aplicación de calendario para comprobar si hay conflictos y evitar reservas dobles",
"no_category_apps_description_conferencing": "Intente añadir una aplicación de conferencias para integrar las videollamadas con sus clientes",
"no_category_apps_description_payment": "Añada una aplicación de pagos para facilitar las transacciones con sus clientes",
"no_category_apps_description_other": "Añada cualquier otro tipo de aplicación para hacer todo tipo de tareas",
"installed_app_calendar_description": "Configure los calendarios para comprobar conflictos y evitar reservas dobles.",
"installed_app_conferencing_description": "Añada sus aplicaciones de videoconferencia favoritas para sus reuniones",
"installed_app_payment_description": "Configure qué servicios de procesamiento de pagos se van a usar al cobrarles a sus clientes.",
"installed_app_other_description": "Todas sus aplicaciones instaladas de otras categorías.",
"no_category_apps_description_calendar": "Añade una aplicación de calendario para comprobar si hay conflictos y evitar reservas duplicadas",
"no_category_apps_description_conferencing": "Intenta añadir una aplicación de conferencias para integrar las videollamadas con tus clientes",
"no_category_apps_description_payment": "Añade una aplicación de pagos para facilitar las transacciones con tus clientes",
"no_category_apps_description_other": "Añade cualquier otro tipo de aplicación para hacer todo tipo de tareas",
"installed_app_calendar_description": "Configura el(los) calendario(s) para comprobar conflictos y evitar reservas duplicadas.",
"installed_app_conferencing_description": "Añade tus aplicaciones de videoconferencia favoritas para las reuniones",
"installed_app_payment_description": "Configura los servicios de procesamiento de pagos que vas a utilizar para cobrar a tus clientes.",
"installed_app_other_description": "Todas tus aplicaciones instaladas de otras categorías.",
"empty_installed_apps_headline": "No hay aplicaciones instaladas",
"empty_installed_apps_description": "Las aplicaciones le permiten mejorar su flujo de trabajo y mejorar su tiempo calendario de forma significativa.",
"empty_installed_apps_button": "Explore la tienda de aplicaciones",
@ -801,11 +790,11 @@
"terms_of_service": "Términos de servicio",
"remove": "Eliminar",
"add": "Añadir",
"installed_one": "Instalado",
"installed_one": "Instalada",
"installed_other": "{{count}} instaladas",
"verify_wallet": "Verificar billetera",
"connect_metamask": "Conectar Metamask",
"create_events_on": "Crear eventos en:",
"create_events_on": "Crear eventos en",
"missing_license": "Falta la licencia",
"signup_requires": "Licencia comercial requerida",
"signup_requires_description": "Actualmente, Cal.com, Inc. no ofrece una versión gratuita de código abierto de la página de registro. Para recibir acceso completo a los componentes de registro es necesario adquirir una licencia comercial. Para uso personal, recomendamos la Plataforma de Datos Prisma o cualquier otra interfaz Postgres para crear cuentas.",
@ -821,7 +810,7 @@
"new_schedule_btn": "Nuevo horario",
"add_new_schedule": "Añadir un nuevo horario",
"add_new_calendar": "Añadir un calendario nuevo",
"set_calendar": "Especifique dónde se añadirán los eventos nuevos cuando haya reservado.",
"set_calendar": "Especifica dónde se añadirán los eventos nuevos cuando haya reservas.",
"delete_schedule": "Eliminar horario",
"schedule_created_successfully": "Horario {{scheduleName}} creado con éxito",
"availability_updated_successfully": "Disponibilidad actualizada correctamente",
@ -839,7 +828,7 @@
"external_redirect_url": "https://example.com/redirect-to-my-success-page",
"duplicate": "Duplicar",
"offer_seats": "Ofrecer plazas",
"offer_seats_description": "Ofrecer asientos para las reservas (desactiva las reservas de invitados y lsa optativas)",
"offer_seats_description": "Ofrecer plazas para las reservas (desactiva las reservas de invitados y las optativas)",
"seats_available": "Plazas disponibles",
"number_of_seats": "Número de plazas por reserva",
"enter_number_of_seats": "Introduzca el número de plazas",
@ -927,7 +916,7 @@
"credentials_stored_encrypted": "Sus credenciales serán almacenadas y cifradas.",
"it_stored_encrypted": "Se almacenará y cifrará.",
"go_to_app_store": "Ir a App Store",
"calendar_error": "Algo salió mal, intente volver a conectar su calendario con todos los permisos necesarios",
"calendar_error": "Intenta volver a conectar tu calendario con todos los permisos necesarios",
"set_your_phone_number": "Configurar un número de teléfono para la reunión",
"calendar_no_busy_slots": "No hay franjas horarias ocupadas",
"display_location_label": "Mostrar en la página de reservas",
@ -1026,7 +1015,7 @@
"add_exchange2016": "Conectar con Exchange 2016 Server",
"custom_template": "Plantilla personalizada",
"email_body": "Cuerpo del mensaje",
"subject": "Asunto",
"subject": "Asunto del correo electrónico",
"text_message": "Mensaje de texto",
"specific_issue": "¿Tienes un problema específico?",
"browse_our_docs": "navega por nuestros documentos",
@ -1064,8 +1053,8 @@
"navigate": "Navegar",
"open": "Abrir",
"close": "Cerrar",
"team_feature_teams": "Esta es una función Team. Actualice a Team para ver la disponibilidad de su equipo.",
"team_feature_workflows": "Esta es una función Team. Actualice a Team para automatizar los recordadorios y las notificaciones de eventos con flujos de trabajo.",
"team_feature_teams": "Esta es una función Team. Actualiza a Team para ver la disponibilidad de tu equipo.",
"team_feature_workflows": "Esta es una función Team. Actualiza a Team para automatizar los recordadorios y las notificaciones de eventos con flujos de trabajo.",
"show_eventtype_on_profile": "Mostrar en perfil",
"embed": "Insertar",
"new_username": "Nombre de usuario nuevo",
@ -1129,10 +1118,10 @@
"upload": "Cargar",
"web3": "Web3",
"rainbow_token_gated": "Este tipo de evento tiene acceso cerrado por token.",
"rainbow_connect_wallet_gate": "Conecte su billetera si posee <1>{{name}}</1> (<3>{{symbol}}</3>).",
"rainbow_insufficient_balance": "Su billetera conectada no contiene suficiente <1>{{symbol}}</1>.",
"rainbow_sign_message_request": "Firme la solicitud de mensaje en su billetera.",
"rainbow_signature_error": "Error al solicitar la firma desde su billetera.",
"rainbow_connect_wallet_gate": "Conecta tu billetera si posees <1>{{name}}</1> (<3>{{symbol}}</3>).",
"rainbow_insufficient_balance": "Tu billetera conectada no contiene suficiente <1>{{symbol}}</1>.",
"rainbow_sign_message_request": "Firma la solicitud de mensaje en tu billetera.",
"rainbow_signature_error": "Error al solicitar la firma desde tu billetera.",
"token_address": "Dirección del token",
"blockchain": "Blockchain",
"old_password": "Contraseña anterior",
@ -1142,7 +1131,7 @@
"recurring_event_tab_description": "Configurar un calendario repetitivo",
"today": "hoy",
"appearance": "Aspecto",
"appearance_subtitle": "Administre los ajustes para el aspecto de su reserva",
"appearance_subtitle": "Administra los ajustes para el aspecto de tu reserva",
"my_account": "Mi cuenta",
"general": "General",
"calendars": "Calendarios",
@ -1151,19 +1140,19 @@
"embeds": "Inserciones",
"impersonation": "Suplantación",
"users": "Usuarios",
"profile_description": "Administre los ajustes para su perfil de Cal",
"general_description": "Administre los ajustes para su idioma y zona horaria",
"calendars_description": "Configure cómo deben interactuar sus tipos de eventos con sus calendarios",
"appearance_description": "Administre los ajustes para el aspecto de su reserva",
"conferencing_description": "Administre sus aplicaciones de videoconferencia para sus reuniones",
"password_description": "Administre los ajustes para las contraseñas de su cuenta",
"2fa_description": "Administre los ajustes para las contraseñas de su cuenta",
"we_just_need_basic_info": "Solo necesitamos información básica para configurar su perfil.",
"profile_description": "Administra los ajustes para tu perfil de Cal",
"general_description": "Administre los ajustes para tu idioma y zona horaria",
"calendars_description": "Configura cómo interactúan tus tipos de eventos con tus calendarios",
"appearance_description": "Administra los ajustes para el aspecto de tu reserva",
"conferencing_description": "Administra tus aplicaciones de videoconferencia para tus reuniones",
"password_description": "Administra los ajustes para las contraseñas de tu cuenta",
"2fa_description": "Administra los ajustes para las contraseñas de tu cuenta",
"we_just_need_basic_info": "Solo necesitamos información básica para configurar tu perfil.",
"skip": "Omitir",
"do_this_later": "Hacerlo más tarde",
"set_availability_getting_started_subtitle_1": "Defina los intervalos de tiempo en los que esté disponible",
"set_availability_getting_started_subtitle_2": "Puede personalizar todo esto más tarde en la página de disponibilidad.",
"connect_calendars_from_app_store": "Puede añadir más calendarios desde la Tienda de aplicaciones",
"set_availability_getting_started_subtitle_1": "Define los rangos de tiempo en los que estás disponible",
"set_availability_getting_started_subtitle_2": "Puedes personalizar todo esto más tarde en la página de disponibilidad.",
"connect_calendars_from_app_store": "Puedes añadir más calendarios desde la tienda de aplicaciones",
"connect_conference_apps": "Conectar aplicaciones de conferencia",
"connect_calendar_apps": "Conectar aplicaciones de calendario",
"connect_payment_apps": "Conectar aplicaciones de pago",
@ -1174,99 +1163,107 @@
"message_template": "Plantilla de mensaje",
"email_subject": "Asunto del correo electrónico",
"add_dynamic_variables": "Añadir variables de texto dinámico",
"event_name_info": "El nombre del tipo de evento",
"event_date_info": "La fecha del evento",
"event_time_info": "La hora de inicio del evento",
"location_info": "La ubicación del evento",
"event_name_info": "Nombre del tipo de evento",
"event_date_info": "Fecha del evento",
"event_time_info": "Hora de inicio del evento",
"location_info": "Ubicación del evento",
"organizer_name_info": "Tu Nombre",
"additional_notes_info": "Las notas adicionales de la reserva",
"attendee_name_info": "El nombre de la persona que reserva",
"additional_notes_info": "Notas adicionales de la reserva",
"attendee_name_info": "Nombre de la persona que reserva",
"to": "Para",
"attendee_required_enter_number": "Será necesario que el asistente introduzca un número de teléfono al reservar",
"workflow_turned_on_successfully": "El flujo de trabajo {{workflowName}} se ajustó como {{offOn}} correctamente",
"workflow_turned_on_successfully": "El flujo de trabajo {{workflowName}} se ha activado {{offOn}} con éxito",
"download_responses": "Descargar respuestas",
"create_your_first_form": "Cree su primer formulario",
"create_your_first_form_description": "Con los formularios de orientación puede hacer preguntas de cualificación y llegar a la persona o tipo de evento correctos.",
"create_your_first_webhook": "Cree su primer Webhook",
"create_your_first_webhook_description": "Con Webhooks puede recibir datos de reuniones en tiempo real cuando suceda algo en Cal.com.",
"create_your_first_form": "Crea tu primer formulario",
"create_your_first_form_description": "Con los formularios de orientación puedes hacer preguntas calificadoras y encaminarte hacia la persona o tipo de evento correctos.",
"create_your_first_webhook": "Crea tu primer Webhook",
"create_your_first_webhook_description": "Con Webhooks puedes recibir datos de reuniones en tiempo real cuando suceda algo en Cal.com.",
"for_a_maximum_of": "Para un máximo de",
"event_one": "evento",
"event_other": "eventos",
"profile_team_description": "Administre ajustes para el perfil de su equipo",
"profile_team_description": "Administra los ajustes para el perfil de tu equipo",
"members_team_description": "Usuarios que están en el grupo",
"team_url": "URL del equipo",
"delete_team": "Eliminar equipo",
"team_members": "Miembros del equipo",
"more": "Más",
"more_page_footer": "Vemos la aplicación móvil como una extensión de la aplicación web. Si está realizando acciones complicadas, consulte la aplicación web.",
"workflow_example_1": "Enviar recordatorio por SMS al asistente 24 horas antes de que el evento comience",
"workflow_example_2": "Enviar SMS personalizado al asistente cuando se cambie la planificación del evento",
"more_page_footer": "Consideramos la aplicación móvil como una extensión de la aplicación web. Si está realizando acciones complicadas, consulta la aplicación web.",
"workflow_example_1": "Enviar recordatorio por SMS al asistente, 24 horas antes de que el evento comience",
"workflow_example_2": "Enviar SMS personalizado al asistente cuando se reprograma el evento",
"workflow_example_3": "Enviar correo electrónico personalizado al anfitrión cuando se reserve un evento nuevo",
"workflow_example_4": "Enviar recordatorio por correo electrónico al asistente 1 hora antes de que el evento comience",
"workflow_example_5": "Enviar correo electrónico personalizado al anfitrión cuando se cambie la planificación del evento",
"workflow_example_4": "Enviar recordatorio por correo electrónico al asistente, 1 hora antes de que el evento comience",
"workflow_example_5": "Enviar correo electrónico personalizado al anfitrión cuando se reprograme el evento",
"workflow_example_6": "Enviar SMS personalizado al anfitrión cuando se reserve un evento nuevo",
"welcome_to_cal_header": "¡Bienvenido a Cal.com!",
"edit_form_later_subtitle": "Podrá editarlo más tarde.",
"edit_form_later_subtitle": "Podrá editar esto más tarde.",
"connect_calendar_later": "Conectaré mi calendario más tarde",
"set_my_availability_later": "Indicaré mi disponibilidad más tarde",
"problem_saving_user_profile": "Se produjo un problema al guardar sus datos. Inténtelo de nuevo o póngase en contacto con el servicio de atención al cliente.",
"purchase_missing_seats": "Comprar licencias que faltan",
"slot_length": "Duración de franja",
"problem_saving_user_profile": "Se produjo un problema al guardar tus datos. Inténtalo de nuevo o ponte en contacto con el servicio de atención al cliente.",
"purchase_missing_seats": "Adquirir las plazas que faltan",
"slot_length": "Duración de la franja",
"booking_appearance": "Aspecto de la reserva",
"appearance_team_description": "Administre los ajustes para el aspecto de la reserva de su equipo",
"only_owner_change": "Solo el propietario de este equipo puede hacer cambios en la reserva del equipo ",
"appearance_team_description": "Administra los ajustes para el aspecto de la reserva de tu equipo",
"only_owner_change": "Solo el propietario de este equipo puede hacer cambios en la reserva ",
"team_disable_cal_branding_description": "Elimina cualquier marca relacionada con Cal, p. ej., \"Powered by Cal\"",
"invited_by_team": "{{teamName}} le invitó a unirse a su equipo como {{role}}",
"invited_by_team": "{{teamName}} te invitó a unirte a su equipo como {{role}}",
"token_invalid_expired": "El token no es válido o ha caducado.",
"exchange_add": "Conectar a Microsoft Exchange",
"exchange_authentication": "Método de autenticación",
"exchange_authentication_standard": "Autenticación básica",
"exchange_authentication_ntlm": "Autenticación NTLM",
"exchange_compression": "Compresión GZip",
"routing_forms_description": "Puede ver todos los formularios y rutas que ha creado aquí.",
"routing_forms_description": "Aquí puedes ver todos los formularios y rutas que has creado.",
"add_new_form": "Añadir formulario nuevo",
"form_description": "Cree su formulario para dirigirlo a un agente de reservas",
"form_description": "Crea tu formulario para dirigirlo a un agente de reservas",
"copy_link_to_form": "Copiar enlace al formulario",
"theme": "Tema",
"theme_applies_note": "Solo se aplica a sus páginas de reservas públicas",
"theme_applies_note": "Esto sólo se aplica a tus páginas públicas de reservas",
"theme_light": "Claro",
"theme_dark": "Oscuro",
"theme_system": "Predeterminado del sistema",
"add_a_team": "Añadir un equipo",
"saml_config": "Configuración de SAML",
"add_webhook_description": "Reciba datos de reuniones en tiempo real cuando suceda algo en Cal.com",
"add_webhook_description": "Recibe los datos de la reunión en tiempo real cuando ocurra algo en Cal.com",
"triggers_when": "Se activa cuando",
"test_webhook": "Antes de crear, ejecute una prueba de ping.",
"test_webhook": "Antes de crear, ejecuta una prueba de ping.",
"enable_webhook": "Activar Webhook",
"add_webhook": "Añadir Webhook",
"webhook_edited_successfully": "Webhook guardado",
"webhooks_description": "Reciba datos de reuniones en tiempo real cuando suceda algo en Cal.com",
"api_keys_description": "Genere claves API para acceder a su propia cuenta",
"webhooks_description": "Recibe los datos de la reunión en tiempo real cuando ocurra algo en Cal.com",
"api_keys_description": "Genera claves API para acceder a tu propia cuenta",
"new_api_key": "Clave API nueva",
"active": "Activo",
"api_key_updated": "Nombre de clave API actualizado",
"api_key_updated": "Nombre de la clave API actualizado",
"api_key_update_failed": "Error al actualizar el nombre de la clave API",
"embeds_title": "Inserción de iframe HTML",
"embeds_description": "Inserte todos sus tipos de eventos en su sitio web",
"create_first_api_key": "Cree su primera clave API",
"embeds_description": "Incorpora todos tus tipos de eventos en tu sitio web",
"create_first_api_key": "Genera tu primera clave API",
"create_first_api_key_description": "Las claves API permiten a otras aplicaciones comunicarse con Cal.com",
"back_to_signin": "Volver a iniciar sesión",
"reset_link_sent": "Enlace de restablecimiento enviado",
"password_reset_email": "Se ha enviado un correo electrónico a {{email}} con instrucciones para restablecer su contraseña.",
"password_reset_email": "Se envió un correo electrónico a {{email}} con instrucciones para restablecer tu contraseña.",
"password_updated": "¡Contraseña actualizada!",
"pending_payment": "Pago pendiente",
"confirmation_page_rainbow": "Cierre el acceso a su evento con tokens o NFT en Ethereum, Polygon, etc.",
"not_on_cal": "No está en Cal.com",
"confirmation_page_rainbow": "Cierra el acceso a tu evento con tokens o NFTs en Ethereum, Polygon, etc.",
"not_on_cal": "No estás en Cal.com",
"no_calendar_installed": "No hay ningún calendario instalado",
"no_calendar_installed_description": "Aún no ha conectado ninguno de sus calendarios",
"no_calendar_installed_description": "Aún no has conectado ninguno de tus calendarios",
"add_a_calendar": "Añadir un calendario",
"change_email_hint": "Quizá necesite cerrar sesión y volver a entrar para ver que el cambio surta efecto",
"confirm_password_change_email": "Confirme su contraseña antes de cambiar su dirección de correo electrónico",
"seats": "asientos",
"every_app_published": "Las aplicaciones publicadas en la tienda de aplicaciones de Cal.com son de código abierto y se han sometido a pruebas exhaustivas con las reseñas de pares. Sin embargo, Cal.com, Inc. no avala ni certifica estas aplicaciones a menos que Cal.com las haya publicado expresamente. Si encuentra contenido o comportamiento inapropiados, informe de ello.",
"change_email_hint": "Es posible que tengas que cerrar la sesión y volver a entrar para que los cambios surtan efecto",
"confirm_password_change_email": "Confirma tu contraseña antes de cambiar la dirección de correo electrónico",
"seats": "plazas",
"every_app_published": "Todas las aplicaciones publicadas en la tienda de aplicaciones de Cal.com son de código abierto y han sido probadas exhaustivamente mediante revisiones por parte de los usuarios. Sin embargo, Cal.com, Inc. no respalda ni certifica estas aplicaciones a menos que sean publicadas por Cal.com. Si encuentras contenidos o comportamientos inapropiados, notifícalos.",
"report_app": "Denunciar aplicación",
"billing_manage_details_title": "Ver y administrar sus datos de facturación",
"billing_manage_details_description": "Vea y edite sus datos de facturación, así como cancele su suscripción.",
"billing_help_title": "¿Necesita algo más?",
"billing_help_description": "Si necesita más ayuda con la facturación, nuestro equipo de soporte está aquí para ayudarlo."
"billing_manage_details_title": "Ver y administrar tus datos de facturación",
"billing_manage_details_description": "Ver y editar tus datos de facturación, así como cancelar tu suscripción.",
"billing_help_title": "¿Necesitas algo más?",
"billing_help_description": "Si necesitas más ayuda con la facturación, nuestro equipo de soporte está a tu disposición.",
"saml_config": "Configuración de SAML",
"saml_config_deleted_successfully": "Configuración SAML eliminada correctamente",
"saml_configuration": "Configuración SAML",
"delete_saml_configuration": "Eliminar configuración SAML",
"delete_saml_configuration_confirmation_message": "¿Estás seguro de que deseas eliminar la configuración SAML? Los miembros de tu equipo que utilicen el inicio de sesión SAML ya no podrán acceder a Cal.com.",
"confirm_delete_saml_configuration": "Sí, eliminar la configuración SAML",
"saml_not_configured_yet": "SAML no está configurado aún",
"saml_configuration_description": "Pega los metadatos SAML de tu Proveedor de Identidad en el cuadro de texto de abajo para actualizar tu configuración SAML.",
"saml_configuration_placeholder": "Pega los metadatos SAML de tu Proveedor de Identidad aquí",
"saml_email_required": "Por favor, introduce un correo electrónico para que podamos encontrar tu Proveedor de Identidad SAML"
}

View File

@ -487,7 +487,7 @@
"team_updated_successfully": "Équipe mise à jour avec succès",
"your_team_updated_successfully": "Votre équipe a été mise à jour avec succès.",
"about": "À propos",
"team_description": "Quelques phrases à propos de votre équipe. Ces informations apparaîtront sur la page URL de votre équipe.",
"team_description": "Quelques mots à propos de votre équipe. Ces informations apparaîtront sur la page URL de votre équipe.",
"members": "Membres",
"member": "Membre",
"owner": "Propriétaire",
@ -720,7 +720,7 @@
"active_install_one": "{{count}} installation active",
"active_install_other": "{{count}} installations actives",
"globally_install": "Installation globale",
"app_successfully_installed": "Application installée avec succès",
"app_successfully_installed": "Application installée",
"app_could_not_be_installed": "Impossible d'installer l'application",
"disconnect": "Déconnecter",
"embed_your_calendar": "Intégrez votre calendrier dans votre page web",
@ -745,22 +745,11 @@
"user_away_description": "La personne pour laquelle vous essayez de réserver est absente, et n'accepte donc pas de nouvelles réservations.",
"meet_people_with_the_same_tokens": "Rencontrez des personnes avec les mêmes jetons",
"only_book_people_and_allow": "Réservez et autorisez uniquement les réservations de personnes qui partagent les mêmes jetons, DAO ou NFT.",
"saml_config_deleted_successfully": "Configuration SAML supprimée avec succès",
"account_created_with_identity_provider": "Votre compte a été créé à l'aide d'un fournisseur d'identité.",
"account_managed_by_identity_provider": "Votre compte est géré par {{provider}}",
"account_managed_by_identity_provider_description": "Pour modifier votre adresse e-mail et/ou votre mot de passe, activez l'authentification à deux facteurs et plus, veuillez consulter les paramètres de votre compte {{provider}}.",
"signin_with_google": "Se connecter avec Google",
"signin_with_saml": "Se connecter avec SAML",
"saml_configuration": "Configuration SAML",
"delete_saml_configuration": "Supprimer la configuration SAML",
"delete_saml_configuration_confirmation_message": "Êtes-vous sûr de vouloir supprimer la configuration SAML ? Les membres de votre équipe qui utilisent la connexion SAML ne pourront plus accéder à Cal.com.",
"confirm_delete_saml_configuration": "Oui, supprimer la configuration SAML",
"saml_not_configured_yet": "SAML non configuré pour l'instant",
"saml_configuration_description": "Veuillez coller les métadonnées SAML de votre fournisseur d'identité dans la zone de texte ci-dessous pour mettre à jour votre configuration SAML.",
"saml_configuration_placeholder": "Veuillez coller les métadonnées SAML de votre fournisseur d'identité ici",
"saml_configuration_update_failed": "La mise à jour de la configuration SAML a échoué",
"saml_configuration_delete_failed": "La suppression de la configuration SAML a échoué",
"saml_email_required": "Veuillez saisir une adresse e-mail pour que nous puissions trouver votre fournisseur d'identité SAML",
"you_will_need_to_generate": "Vous devrez générer un jeton d'accès à partir de votre ancien outil de planification.",
"import": "Importer",
"import_from": "Importer depuis",
@ -778,7 +767,7 @@
"no_category_apps_description_conferencing": "Essayez d'ajouter une application de conférence pour interconnecter les appels vidéo avec vos clients",
"no_category_apps_description_payment": "Ajouter une application de paiement pour faciliter les transactions entre vous et vos clients",
"no_category_apps_description_other": "Ajouter n'importe quel autre type d'application pour faire toutes sortes de choses",
"installed_app_calendar_description": "Définir le(s) calendrier(s) pour vérifier les conflits pour éviter les doubles réservations.",
"installed_app_calendar_description": "Définir le ou les calendriers pour vérifier les conflits pour éviter les doubles réservations.",
"installed_app_conferencing_description": "Ajoutez vos applications de vidéoconférence préférées pour vos réunions",
"installed_app_payment_description": "Configurez les services de traitement de paiement à utiliser lors de la facturation de vos clients.",
"installed_app_other_description": "Toutes vos applications installées à partir d'autres catégories.",
@ -1007,7 +996,7 @@
"remove_app": "Supprimer l'application",
"yes_remove_app": "Oui, supprimer l'application",
"are_you_sure_you_want_to_remove_this_app": "Voulez-vous vraiment supprimer cette application ?",
"app_removed_successfully": "Application supprimée avec succès",
"app_removed_successfully": "Application supprimée",
"error_removing_app": "Erreur lors de la suppression de l'application",
"web_conference": "Conférence en ligne",
"number_for_sms_reminders": "Numéro de téléphone (pour les rappels par SMS)",
@ -1064,8 +1053,8 @@
"navigate": "Naviguer",
"open": "Ouvrir",
"close": "Fermer",
"team_feature_teams": "Ceci est une fonctionnalité d'équipe. Mettez à niveau vers l'équipe pour voir la disponibilité de votre équipe.",
"team_feature_workflows": "Ceci est une fonctionnalité d'équipe. Mettez à niveau vers l'équipe pour automatiser vos notifications et rappels d'événements avec les flux de travail.",
"team_feature_teams": "Ceci est une fonctionnalité Team. Passez sur l'offre Team pour voir la disponibilité de votre équipe.",
"team_feature_workflows": "Ceci est une fonctionnalité Team. Passez sur l'offre Team pour automatiser vos notifications et rappels d'événements avec les flux de travail.",
"show_eventtype_on_profile": "Afficher sur le profil",
"embed": "Intégré",
"new_username": "Nouveau nom d'utilisateur",
@ -1126,7 +1115,7 @@
"pro": "Pro",
"removes_cal_branding": "Supprime les marques associées à Cal, c'est-à-dire \"Powered by Cal.\"",
"profile_picture": "Photo de profil",
"upload": "Télécharger",
"upload": "Téléverser",
"web3": "Web3",
"rainbow_token_gated": "Ce type d'événement nécessite un jeton d'authentification.",
"rainbow_connect_wallet_gate": "Connectez votre portefeuille si vous possédez <1>{{name}}</1> (<3>{{symbol}}</3>).",
@ -1161,7 +1150,7 @@
"we_just_need_basic_info": "Nous avons juste besoin de quelques informations de base pour configurer votre profil.",
"skip": "Ignorer",
"do_this_later": "Effectuer ceci plus tard",
"set_availability_getting_started_subtitle_1": "Définir les plages de temps lorsque vous êtes disponible",
"set_availability_getting_started_subtitle_1": "Définir les plages temporelles de vos disponibilités",
"set_availability_getting_started_subtitle_2": "Vous pouvez personnaliser tout cela plus tard dans la page de disponibilité.",
"connect_calendars_from_app_store": "Vous pouvez ajouter plus de calendriers depuis l'App Store",
"connect_conference_apps": "Connecter les applications de conférence",
@ -1182,10 +1171,9 @@
"additional_notes_info": "Notes supplémentaires de réservation",
"attendee_name_info": "Nom de la personne réservant",
"to": "À/vers",
"attendee_required_enter_number": "Le participant devra entrer un numéro de téléphone lors de la réservation",
"workflow_turned_on_successfully": "Le workflow {{workflowName}} a été {{offOn}} avec succès",
"workflow_turned_on_successfully": "Le flux de travail {{workflowName}} a bien été {{offOn}}",
"download_responses": "Télécharger les réponses",
"create_your_first_form": "Créer votre premier formulaire",
"create_your_first_form": "Créez votre premier formulaire",
"create_your_first_form_description": "Grâce aux formulaires d'acheminement, vous pouvez poser des questions de qualification et vous diriger vers la personne ou le type d'événement appropriés.",
"create_your_first_webhook": "Créez votre premier Webhook",
"create_your_first_webhook_description": "Avec les Webhooks, vous pouvez recevoir des données de réunion en temps réel lorsque quelque chose se passe sur Cal.com.",
@ -1208,7 +1196,7 @@
"welcome_to_cal_header": "Bienvenue sur Cal.com !",
"edit_form_later_subtitle": "Vous pourrez modifier cela plus tard.",
"connect_calendar_later": "Je connecterai mon calendrier plus tard",
"set_my_availability_later": "Je vais définir ma disponibilité plus tard",
"set_my_availability_later": "Je définirai ma disponibilité plus tard",
"problem_saving_user_profile": "Un problème est survenu lors de l'enregistrement de vos données. Veuillez réessayer ou contacter le service client.",
"purchase_missing_seats": "Acheter des places manquantes",
"slot_length": "Durée du créneau",
@ -1216,7 +1204,7 @@
"appearance_team_description": "Gérer les paramètres pour l'apparence de réservation de votre équipe",
"only_owner_change": "Seul le propriétaire de cette équipe peut apporter des modifications à la réservation de l'équipe ",
"team_disable_cal_branding_description": "Supprime les marques associées à Cal, c'est-à-dire \"Powered by Cal\"",
"invited_by_team": "{{teamName}} vous a invité à rejoindre son équipe en tant que {{role}}",
"invited_by_team": "{{teamName}} vous a envoyé une invitation à rejoindre son équipe en tant que {{role}}",
"token_invalid_expired": "Le jeton est invalide ou expiré.",
"exchange_add": "Se connecter à Microsoft Exchange",
"exchange_authentication": "Méthode d'authentification",
@ -1231,9 +1219,8 @@
"theme_applies_note": "Ceci s'applique uniquement à vos pages publiques de réservation",
"theme_light": "Clair",
"theme_dark": "Sombre",
"theme_system": "Défaut du système",
"theme_system": "Valeur système par défaut",
"add_a_team": "Ajouter une équipe",
"saml_config": "Configuration SAML",
"add_webhook_description": "Recevoir les données de la réunion en temps réel lorsque quelque chose se passe sur Cal.com",
"triggers_when": "Se déclenche quand",
"test_webhook": "Veuillez faire un test de ping avant de créer.",
@ -1266,7 +1253,17 @@
"every_app_published": "Chaque application publiée sur l'App Store de Cal.com est open source et soigneusement testée via les évaluations par les pairs. Néanmoins, Cal.com, Inc. n'approuve pas ou ne certifie pas ces applications à moins qu'elles ne soient publiées par Cal.com. Si vous rencontrez un contenu ou un comportement inapproprié, veuillez le signaler.",
"report_app": "Signaler une application",
"billing_manage_details_title": "Affichez et gérez vos informations de facturation",
"billing_manage_details_description": "Affichez et modifiez vos informations de facturation, et annuler votre abonnement.",
"billing_manage_details_description": "Affichez et modifiez vos informations de facturation, et annulez votre abonnement.",
"billing_help_title": "Besoin d'autre chose ?",
"billing_help_description": "Si vous avez besoin d'aide pour la facturation, notre équipe d'assistance est là pour vous aider."
"billing_help_description": "Si vous avez besoin d'aide pour la facturation, notre équipe d'assistance est là pour vous aider.",
"saml_config": "Configuration SAML",
"saml_config_deleted_successfully": "Configuration SAML supprimée avec succès",
"saml_configuration": "Configuration SAML",
"delete_saml_configuration": "Supprimer la configuration SAML",
"delete_saml_configuration_confirmation_message": "Êtes-vous sûr de vouloir supprimer la configuration SAML ? Les membres de votre équipe qui utilisent la connexion SAML ne pourront plus accéder à Cal.com.",
"confirm_delete_saml_configuration": "Oui, supprimer la configuration SAML",
"saml_not_configured_yet": "SAML non configuré pour l'instant",
"saml_configuration_description": "Veuillez coller les métadonnées SAML de votre fournisseur d'identité dans la zone de texte ci-dessous pour mettre à jour votre configuration SAML.",
"saml_configuration_placeholder": "Veuillez coller les métadonnées SAML de votre fournisseur d'identité ici",
"saml_email_required": "Veuillez saisir une adresse e-mail pour que nous puissions trouver votre fournisseur d'identité SAML"
}

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