Compare commits

..
13 Commits
Author SHA1 Message Date
zomars a7f16462db v2.3.5
release.yml / release (push) Canceled after 0s
2022-12-02 13:33:41 -07:00
zomars 2c7fcbcf51 hotfix: 404 for mixed case usernames 2022-12-02 13:32:56 -07:00
Syed Ali ShahbazandAlex van Andel d3e4d26fa5 fixes UTC consideration in CalDAV (#5827)
* fixes UTC consideration

* typecheck fix

* code quality

* comment location

Co-authored-by: Alex van Andel <me@alexvanandel.com>
2022-12-02 19:46:38 +00:00
Carina WollendorferandCarinaWolli 3be6c05722 Fixes some translation issues (#5841)
* fix french translation for rescheduled email

* fix translations for team invite

* fix rescheduling email in german

Co-authored-by: CarinaWolli <wollencarina@gmail.com>
2022-12-02 17:56:48 +00:00
Carina WollendorferandCarinaWolli e293e590bd remove if and at to query (#5838)
Co-authored-by: CarinaWolli <wollencarina@gmail.com>
2022-12-02 13:43:07 +00:00
+1 d8c1c103a1 [CAL-103] All Bookings show up under Today and Today should be uppercase (#5155)
Co-authored-by: Nitesh Singh <nitesh.singh@gitstart.dev>
Co-authored-by: gitstart <gitstart@users.noreply.github.com>
Co-authored-by: Thiago Nascimbeni <tnascimbeni@gmail.com>
Co-authored-by: Rafael Toledo <87545086+Toledodev@users.noreply.github.com>
Co-authored-by: Matheus Benini Ferreira <88898100+MatheusBeniniF@users.noreply.github.com>
Co-authored-by: Matheus Muniz <87545749+matheusmuniz03@users.noreply.github.com>
Co-authored-by: Rafael <rafael.toledo@engenharia.ufjf.br>
Co-authored-by: gitstart <gitstart@gitstart.com>
Co-authored-by: niteshsingh1357 <niteshsingh1357@gmail.com>

Co-authored-by: gitstart <gitstart@users.noreply.github.com>
Co-authored-by: Nitesh Singh <nitesh.singh@gitstart.dev>
Co-authored-by: Thiago Nascimbeni <tnascimbeni@gmail.com>
Co-authored-by: Rafael Toledo <87545086+Toledodev@users.noreply.github.com>
Co-authored-by: Matheus Benini Ferreira <88898100+MatheusBeniniF@users.noreply.github.com>
Co-authored-by: Matheus Muniz <87545749+matheusmuniz03@users.noreply.github.com>
Co-authored-by: Rafael <rafael.toledo@engenharia.ufjf.br>
Co-authored-by: gitstart <gitstart@gitstart.com>
Co-authored-by: niteshsingh1357 <niteshsingh1357@gmail.com>
Co-authored-by: Peer Richelsen <peeroke@gmail.com>
2022-12-02 10:08:12 +00:00
Joe Au-YeungandAlan 19d00e691c Fix attendees on a booking with seats and payments, navigate to the payment page. (#5788)
* Payment booking logic to seats

* Pass email to success page from payment page

* Fix type error

Co-authored-by: Alan <alannnc@gmail.com>
2022-12-02 00:12:06 +00:00
78aa4aecef Settings: Tab item line height fix && Stop x-axis overflow of settings tab (#5699)
Co-authored-by: Guest <guest@pop-os.localdomain>
Co-authored-by: Peer Richelsen <peeroke@gmail.com>
2022-12-01 23:09:14 +00:00
3ab002e547 5444 cal 339 radio option in additional questions on public booking page (#5804)
* Use field array intro

* WIP - form submitting wrong form

* WIP with fake useFormHook

* WORKING! OMG

Co-authored-by: Alex <alex@cal.com>
Co-authored-by: Jeroen Reumkens <hello@jeroenreumkens.nl>

* Booking Page styling

* Fix duplicate fields

* Radio string

* Type error

* Linting errors

* Remove unused duplicate file

* Fixed user related type error

* remove log

* Remove console logs

* remove console log

* fix dark mode text and comment style

Co-authored-by: Alex <alex@cal.com>
Co-authored-by: Jeroen Reumkens <hello@jeroenreumkens.nl>
Co-authored-by: Alex van Andel <me@alexvanandel.com>
Co-authored-by: alannnc <alannnc@gmail.com>
2022-12-01 21:53:52 +00:00
Nafees Nazikandalannnc 7f461bc275 fix: padding (#5808)
Co-authored-by: alannnc <alannnc@gmail.com>
2022-12-01 13:30:57 -07:00
Carina WollendorferandCarinaWolli 571176102e add smsReminderNumber to webhook payload (#5823)
Co-authored-by: CarinaWolli <wollencarina@gmail.com>
2022-12-01 12:51:59 -07:00
github-actions[bot]andCrowdin Bot 8f1d3fbc50 New Crowdin translations by Github Action (#5816)
Co-authored-by: Crowdin Bot <support+bot@crowdin.com>
2022-12-01 15:42:24 +00:00
Joe Au-YeungandAlex van Andel 5e88ad50ea If no location is set, default to Cal Video (#5812)
* Default to Cal Video

* Show web conferencing details to follow on success page

* Show web conferencing details to follow on success page

* Show web conferencing details to follow on success page

* Type fixes

* Pass tests

Co-authored-by: Alex van Andel <me@alexvanandel.com>
2022-12-01 10:20:01 -05:00
59 changed files with 638 additions and 536 deletions
@@ -39,11 +39,11 @@ import { HttpError } from "@calcom/lib/http-error";
import { getEveryFreqFor } from "@calcom/lib/recurringStrings";
import { collectPageParameters, telemetryEventTypes, useTelemetry } from "@calcom/lib/telemetry";
import { AddressInput, Button, EmailInput, Form, Icon, PhoneInput, Tooltip } from "@calcom/ui";
import { Group, RadioField } from "@calcom/ui";
import { asStringOrNull } from "@lib/asStringOrNull";
import { timeZone } from "@lib/clock";
import { ensureArray } from "@lib/ensureArray";
import useMeQuery from "@lib/hooks/useMeQuery";
import useRouterQuery from "@lib/hooks/useRouterQuery";
import createBooking from "@lib/mutations/bookings/create-booking";
import createRecurringBooking from "@lib/mutations/bookings/create-recurring-booking";
@@ -103,7 +103,6 @@ const BookingPage = ({
{}
);
const stripeAppData = getStripeAppData(eventType);
// Define duration now that we support multiple duration eventTypes
let duration = eventType.length;
if (queryDuration && !isNaN(Number(queryDuration))) {
@@ -745,6 +744,27 @@ const BookingPage = ({
</div>
</div>
)}
{input.options && input.type === EventTypeCustomInputType.RADIO && (
<div className="">
<div className="flex">
<Group
onValueChange={(e) => {
bookingForm.setValue(`customInputs.${input.id}`, e);
}}>
<>
{input.options.map((option, i) => (
<RadioField
label={option.label}
key={`option.${i}.radio`}
value={option.label}
id={`option.${i}.radio`}
/>
))}
</>
</Group>
</div>
</div>
)}
</div>
))}
{!eventType.disableGuests && guestToggle && (
@@ -1,23 +1,33 @@
import { EventTypeCustomInput, EventTypeCustomInputType } from "@prisma/client";
import { useAutoAnimate } from "@formkit/auto-animate/react";
import { EventTypeCustomInputType } from "@prisma/client";
import type { CustomInputParsed } from "pages/event-types/[type]";
import { FC } from "react";
import { Controller, SubmitHandler, useForm, useWatch } from "react-hook-form";
import { Control, Controller, useFieldArray, useForm, UseFormRegister, useWatch } from "react-hook-form";
import { Button, Select, TextField } from "@calcom/ui";
import { useLocale } from "@lib/hooks/useLocale";
import { useLocale } from "@calcom/lib/hooks/useLocale";
import { Button, Icon, Label, Select, TextField } from "@calcom/ui";
interface OptionTypeBase {
label: string;
value: EventTypeCustomInputType;
options?: { label: string; type: string }[];
}
interface Props {
onSubmit: SubmitHandler<IFormInput>;
onSubmit: (output: CustomInputParsed) => void;
onCancel: () => void;
selectedCustomInput?: EventTypeCustomInput;
selectedCustomInput?: CustomInputParsed;
}
type IFormInput = EventTypeCustomInput;
type IFormInput = CustomInputParsed;
/**
* Getting a random ID gives us the option to know WHICH field is changed
* when the user edits a custom field.
* This UUID is only used to check for changes in the UI and not the ID we use in the DB
* There is very very very slim chance that this will cause a collision
* */
const randomId = () => Math.floor(Math.random() * 1000000 + new Date().getTime());
const CustomInputTypeForm: FC<Props> = (props) => {
const { t } = useLocale();
@@ -26,10 +36,22 @@ const CustomInputTypeForm: FC<Props> = (props) => {
{ value: EventTypeCustomInputType.TEXTLONG, label: t("multiline_text") },
{ value: EventTypeCustomInputType.NUMBER, label: t("number") },
{ value: EventTypeCustomInputType.BOOL, label: t("checkbox") },
{
value: EventTypeCustomInputType.RADIO,
label: t("radio"),
},
];
const { selectedCustomInput } = props;
const defaultValues = selectedCustomInput || { type: inputOptions[0].value };
const { register, control, handleSubmit } = useForm<IFormInput>({
const defaultValues = selectedCustomInput
? { ...selectedCustomInput, id: selectedCustomInput?.id || randomId() }
: {
id: randomId(),
type: EventTypeCustomInputType.TEXT,
};
const { register, control, getValues } = useForm<IFormInput>({
defaultValues,
});
const selectedInputType = useWatch({ name: "type", control });
@@ -40,7 +62,7 @@ const CustomInputTypeForm: FC<Props> = (props) => {
};
return (
<form className="flex flex-col space-y-4">
<div className="flex flex-col space-y-4">
<div>
<label htmlFor="type" className="block text-sm font-medium text-gray-700">
{t("input_type")}
@@ -83,6 +105,10 @@ const CustomInputTypeForm: FC<Props> = (props) => {
{...register("placeholder")}
/>
)}
{selectedInputType === EventTypeCustomInputType.RADIO && (
<RadioInputHandler control={control} register={register} />
)}
<div className="flex h-5 items-center">
<input
id="required"
@@ -111,12 +137,75 @@ const CustomInputTypeForm: FC<Props> = (props) => {
<Button onClick={onCancel} type="button" color="secondary" className="ltr:mr-2">
{t("cancel")}
</Button>
<Button onClick={handleSubmit(props.onSubmit)} form="custom-input">
<Button
type="button"
onClick={() => {
props.onSubmit(getValues());
}}>
{t("save")}
</Button>
</div>
</form>
</div>
);
};
function RadioInputHandler({
register,
control,
}: {
register: UseFormRegister<IFormInput>;
control: Control<IFormInput>;
}) {
const { t } = useLocale();
const { fields, append, remove } = useFieldArray<IFormInput>({
control,
name: "options",
shouldUnregister: true,
});
const [animateRef] = useAutoAnimate<HTMLUListElement>();
return (
<div className="flex flex-col ">
<Label htmlFor="radio_options">{t("options")}</Label>
<ul
className="flex max-h-80 w-full flex-col space-y-1 overflow-y-scroll rounded-md bg-gray-50 p-4"
ref={animateRef}>
<>
{fields.map((option, index) => (
<li key={`${option.id}`}>
<TextField
id={option.id}
placeholder={t("enter_option", { index: index + 1 })}
addOnFilled={false}
label={t("option", { index: index + 1 })}
labelSrOnly
{...register(`options.${index}.label` as const, { required: true })}
addOnSuffix={
<Button
size="icon"
color="minimal"
StartIcon={Icon.FiX}
onClick={() => {
remove(index);
}}
/>
}
/>
</li>
))}
<Button
color="minimal"
StartIcon={Icon.FiPlus}
className="!text-sm !font-medium"
onClick={() => {
append({ label: "", type: "text" });
}}>
{t("add_an_option")}
</Button>
</>
</ul>
</div>
);
}
export default CustomInputTypeForm;
@@ -1,6 +1,5 @@
import { EventTypeCustomInput } from "@prisma/client/";
import Link from "next/link";
import { EventTypeSetupInfered, FormValues } from "pages/event-types/[type]";
import type { CustomInputParsed, EventTypeSetupInfered, FormValues } from "pages/event-types/[type]";
import { useEffect, useState } from "react";
import { Controller, useFormContext } from "react-hook-form";
import short from "short-uuid";
@@ -43,10 +42,10 @@ export const EventAdvancedTab = ({ eventType, team }: Pick<EventTypeSetupInfered
const [hashedLinkVisible, setHashedLinkVisible] = useState(!!eventType.hashedLink);
const [redirectUrlVisible, setRedirectUrlVisible] = useState(!!eventType.successRedirectUrl);
const [hashedUrl, setHashedUrl] = useState(eventType.hashedLink?.link);
const [customInputs, setCustomInputs] = useState<EventTypeCustomInput[]>(
const [customInputs, setCustomInputs] = useState<CustomInputParsed[]>(
eventType.customInputs.sort((a, b) => a.id - b.id) || []
);
const [selectedCustomInput, setSelectedCustomInput] = useState<EventTypeCustomInput | undefined>(undefined);
const [selectedCustomInput, setSelectedCustomInput] = useState<CustomInputParsed | undefined>(undefined);
const [selectedCustomInputModalOpen, setSelectedCustomInputModalOpen] = useState(false);
const placeholderHashedLink = `${CAL_URL}/d/${hashedUrl}/${eventType.slug}`;
@@ -125,15 +124,13 @@ export const EventAdvancedTab = ({ eventType, team }: Pick<EventTypeSetupInfered
onCheckedChange={(e) => {
if (e && customInputs.length === 0) {
// Push a placeholders
setSelectedCustomInput(undefined);
setSelectedCustomInputModalOpen(true);
} else if (!e) {
setCustomInputs([]);
formMethods.setValue("customInputs", []);
}
}}>
<ul className="my-4 rounded-md border">
{customInputs.map((customInput: EventTypeCustomInput, idx: number) => (
{customInputs.map((customInput, idx) => (
<CustomInputItem
key={idx}
question={customInput.label}
@@ -391,24 +388,36 @@ export const EventAdvancedTab = ({ eventType, team }: Pick<EventTypeSetupInfered
<CustomInputTypeForm
selectedCustomInput={selectedCustomInput}
onSubmit={(values) => {
const customInput: EventTypeCustomInput = {
const customInput: CustomInputParsed = {
id: -1,
eventTypeId: -1,
label: values.label,
placeholder: values.placeholder,
required: values.required,
type: values.type,
options: values.options,
};
if (selectedCustomInput) {
selectedCustomInput.label = customInput.label;
selectedCustomInput.placeholder = customInput.placeholder;
selectedCustomInput.required = customInput.required;
selectedCustomInput.type = customInput.type;
selectedCustomInput.options = customInput.options || undefined;
// Update by id
const inputIndex = customInputs.findIndex((input) => input.id === values.id);
customInputs[inputIndex] = selectedCustomInput;
setCustomInputs(customInputs);
formMethods.setValue("customInputs", customInputs);
} else {
setCustomInputs(customInputs.concat(customInput));
formMethods.setValue("customInputs", customInputs.concat(customInput));
const concatted = customInputs.concat({
...customInput,
options: customInput.options,
});
console.log(concatted);
setCustomInputs(concatted);
formMethods.setValue("customInputs", concatted);
}
setSelectedCustomInputModalOpen(false);
}}
onCancel={() => {
@@ -1,134 +0,0 @@
import { EventTypeCustomInput, EventTypeCustomInputType } from "@prisma/client";
import React, { FC } from "react";
import { Controller, SubmitHandler, useForm, useWatch } from "react-hook-form";
import { Button } from "@calcom/ui";
import { useLocale } from "@lib/hooks/useLocale";
import Select from "@components/ui/form/Select";
interface OptionTypeBase {
label: string;
value: EventTypeCustomInputType;
}
interface Props {
onSubmit: SubmitHandler<IFormInput>;
onCancel: () => void;
selectedCustomInput?: EventTypeCustomInput;
}
type IFormInput = EventTypeCustomInput;
const CustomInputTypeForm: FC<Props> = (props) => {
const { t } = useLocale();
const inputOptions: OptionTypeBase[] = [
{ value: EventTypeCustomInputType.TEXT, label: t("text") },
{ value: EventTypeCustomInputType.TEXTLONG, label: t("multiline_text") },
{ value: EventTypeCustomInputType.NUMBER, label: t("number") },
{ value: EventTypeCustomInputType.BOOL, label: t("checkbox") },
];
const { selectedCustomInput } = props;
const defaultValues = selectedCustomInput || { type: inputOptions[0].value };
const { register, control, handleSubmit } = useForm<IFormInput>({
defaultValues,
});
const selectedInputType = useWatch({ name: "type", control });
const selectedInputOption = inputOptions.find((e) => selectedInputType === e.value);
const onCancel = () => {
props.onCancel();
};
return (
<form onSubmit={handleSubmit(props.onSubmit)}>
<div className="mb-2">
<label htmlFor="type" className="block text-sm font-medium text-gray-700">
{t("input_type")}
</label>
<Controller
name="type"
control={control}
render={({ field }) => (
<Select
id="type"
defaultValue={selectedInputOption}
options={inputOptions}
isSearchable={false}
className="mt-1 mb-2 block w-full min-w-0 flex-1 text-sm"
onChange={(option) => option && field.onChange(option.value)}
value={selectedInputOption}
onBlur={field.onBlur}
name={field.name}
/>
)}
/>
</div>
<div className="mb-2">
<label htmlFor="label" className="block text-sm font-medium text-gray-700">
{t("label")}
</label>
<div className="mt-1">
<input
type="text"
id="label"
required
className="block w-full rounded-sm border-gray-300 text-sm"
defaultValue={selectedCustomInput?.label}
{...register("label", { required: true })}
/>
</div>
</div>
{(selectedInputType === EventTypeCustomInputType.TEXT ||
selectedInputType === EventTypeCustomInputType.TEXTLONG) && (
<div className="mb-2">
<label htmlFor="placeholder" className="block text-sm font-medium text-gray-700">
{t("placeholder")}
</label>
<div className="mt-1">
<input
type="text"
id="placeholder"
className="block w-full rounded-sm border-gray-300 text-sm"
defaultValue={selectedCustomInput?.placeholder}
{...register("placeholder")}
/>
</div>
</div>
)}
<div className="flex h-5 items-center">
<input
id="required"
type="checkbox"
className="text-primary-600 focus:ring-primary-500 h-4 w-4 rounded border-gray-300 ltr:mr-2 rtl:ml-2"
defaultChecked={selectedCustomInput?.required ?? true}
{...register("required")}
/>
<label htmlFor="required" className="block text-sm font-medium text-gray-700">
{t("is_required")}
</label>
</div>
<input
type="hidden"
id="eventTypeId"
value={selectedCustomInput?.eventTypeId || -1}
{...register("eventTypeId", { valueAsNumber: true })}
/>
<input
type="hidden"
id="id"
value={selectedCustomInput?.id || -1}
{...register("id", { valueAsNumber: true })}
/>
<div className="mt-5 flex space-x-2 sm:mt-4">
<Button onClick={onCancel} type="button" color="secondary" className="ltr:mr-2">
{t("cancel")}
</Button>
<Button type="submit">{t("save")}</Button>
</div>
</form>
);
};
export default CustomInputTypeForm;
+1 -1
View File
@@ -1,6 +1,6 @@
{
"name": "@calcom/web",
"version": "2.3.4",
"version": "2.3.5",
"private": true,
"scripts": {
"analyze": "ANALYZE=true next build",
+2 -1
View File
@@ -12,7 +12,7 @@ import {
import { useLocale } from "@calcom/lib/hooks/useLocale";
import { bookEventTypeSelect } from "@calcom/prisma";
import prisma from "@calcom/prisma";
import { EventTypeMetaDataSchema } from "@calcom/prisma/zod-utils";
import { customInputSchema, EventTypeMetaDataSchema } from "@calcom/prisma/zod-utils";
import { asStringOrNull, asStringOrThrow } from "@lib/asStringOrNull";
import getBooking, { GetBookingType } from "@lib/getBooking";
@@ -127,6 +127,7 @@ export async function getServerSideProps(context: GetServerSidePropsContext) {
periodStartDate: e.periodStartDate?.toString() ?? null,
periodEndDate: e.periodEndDate?.toString() ?? null,
schedulingType: null,
customInputs: customInputSchema.array().parse(e.customInputs || []),
users: users.map((u) => ({
id: u.id,
name: u.name,
+49 -11
View File
@@ -34,7 +34,7 @@ import { getIs24hClockFromLocalStorage, isBrowserLocale24h } from "@calcom/lib/t
import { localStorage } from "@calcom/lib/webstorage";
import prisma, { baseUserSelect } from "@calcom/prisma";
import { Prisma } from "@calcom/prisma/client";
import { EventTypeMetaDataSchema } from "@calcom/prisma/zod-utils";
import { customInputSchema, EventTypeMetaDataSchema } from "@calcom/prisma/zod-utils";
import { Button, EmailInput, Icon } from "@calcom/ui";
import { timeZone } from "@lib/clock";
@@ -167,9 +167,11 @@ export default function Success(props: SuccessProps) {
if ((isCancellationMode || changes) && typeof window !== "undefined") {
window.scrollTo(0, document.body.scrollHeight);
}
const location: ReturnType<typeof getEventLocationValue> = Array.isArray(props.bookingInfo.location)
? props.bookingInfo.location[0] || ""
: props.bookingInfo.location || "";
? props.bookingInfo.location[0]
: // If there is no location set then we default to Cal Video
"integrations:daily";
if (!location) {
// Can't use logger.error because it throws error on client. stdout isn't available to it.
@@ -454,10 +456,37 @@ export default function Success(props: SuccessProps) {
)}
{customInputs &&
Object.keys(customInputs).map((key) => {
// This breaks if you have two label that are the same.
// TODO: Fix this in another PR
const customInput = customInputs[key as keyof typeof customInputs];
const eventTypeCustomFound = eventType.customInputs?.find((ci) => ci.label === key);
return (
<>
{customInput !== "" && (
{eventTypeCustomFound?.type === "RADIO" && (
<>
<div className="col-span-3 mt-8 border-t pt-8 pr-3 font-medium">
{eventTypeCustomFound.label}
</div>
<div className="col-span-3 mt-1 mb-2">
{eventTypeCustomFound.options &&
eventTypeCustomFound.options.map((option) => {
const selected = option.label == customInput;
return (
<div
key={option.label}
className={classNames(
"flex space-x-1",
!selected && "text-gray-500"
)}>
<p>{option.label}</p>
<span>{option.label === customInput && "✅"}</span>
</div>
);
})}
</div>
</>
)}
{eventTypeCustomFound?.type !== "RADIO" && customInput !== "" && (
<>
<div className="col-span-3 mt-8 border-t pt-8 pr-3 font-medium">{key}</div>
<div className="col-span-3 mt-2 mb-2">
@@ -785,6 +814,7 @@ const getEventTypesFromDB = async (id: number) => {
requiresConfirmation: true,
userId: true,
successRedirectUrl: true,
customInputs: true,
locations: true,
price: true,
currency: true,
@@ -851,22 +881,25 @@ const handleSeatsEventTypeOnBooking = (
seatsShowAttendees: boolean | null;
[x: string | number | symbol]: unknown;
},
booking: Partial<
bookingInfo: Partial<
Prisma.BookingGetPayload<{ include: { attendees: { select: { name: true; email: true } } } }>
>,
email: string
) => {
if (eventType?.seatsPerTimeSlot !== null) {
// @TODO: right now bookings with seats doesn't save every description that its entered by every user
delete booking.description;
delete bookingInfo.description;
} else {
return;
}
if (!eventType.seatsShowAttendees) {
const attendee = booking?.attendees?.find((a) => a.email === email);
booking["attendees"] = attendee ? [attendee] : [];
const attendee = bookingInfo?.attendees?.find((a) => {
return a.email === email;
});
bookingInfo["attendees"] = attendee ? [attendee] : [];
}
return;
return bookingInfo;
};
export async function getServerSideProps(context: GetServerSidePropsContext) {
@@ -945,7 +978,11 @@ export async function getServerSideProps(context: GetServerSidePropsContext) {
select: baseUserSelect,
});
if (user) {
eventTypeRaw.users.push(user);
eventTypeRaw.users.push({
...user,
avatar: "",
allowDynamicBooking: true,
});
}
}
@@ -961,6 +998,7 @@ export async function getServerSideProps(context: GetServerSidePropsContext) {
periodEndDate: eventTypeRaw.periodEndDate?.toString() ?? null,
metadata: EventTypeMetaDataSchema.parse(eventTypeRaw.metadata),
recurringEvent: parseRecurringEvent(eventTypeRaw.recurringEvent),
customInputs: customInputSchema.array().parse(eventTypeRaw.customInputs),
};
const profile = {
@@ -972,7 +1010,7 @@ export async function getServerSideProps(context: GetServerSidePropsContext) {
slug: eventType.team?.slug || eventType.users[0]?.username || null,
};
if (bookingInfo !== null && email) {
if (bookingInfo !== null && email && eventType.seatsPerTimeSlot) {
handleSeatsEventTypeOnBooking(eventType, bookingInfo, email);
}
+72 -33
View File
@@ -17,6 +17,13 @@ import SkeletonLoader from "@components/booking/SkeletonLoader";
type BookingListingStatus = RouterInputs["viewer"]["bookings"]["get"]["status"];
type BookingOutput = RouterOutputs["viewer"]["bookings"]["get"]["bookings"][0];
type RecurringInfo = {
recurringEventId: string | null;
count: number;
firstDate: Date | null;
bookings: { [key: string]: Date[] };
};
const validStatuses = ["upcoming", "recurring", "past", "cancelled", "unconfirmed"] as const;
const descriptionByStatus: Record<BookingListingStatus, string> = {
@@ -73,6 +80,18 @@ export default function Bookings() {
return true;
};
let recurringInfoToday: RecurringInfo | undefined;
const bookingsToday =
query.data?.pages.map((page) =>
page.bookings.filter((booking: BookingOutput) => {
recurringInfoToday = page.recurringInfo.find(
(info) => info.recurringEventId === booking.recurringEventId
);
return new Date(booking.startTime).toDateString() === new Date().toDateString();
})
)[0] || [];
const [animationParentRef] = useAutoAnimate<HTMLDivElement>();
return (
@@ -83,45 +102,65 @@ export default function Bookings() {
)}
{(query.status === "loading" || query.isPaused) && <SkeletonLoader />}
{query.status === "success" && !isEmpty && (
<div className="pt-2 xl:pt-0">
<WipeMyCalActionButton bookingStatus={status} bookingsEmpty={isEmpty} />
{/* TODO: add today only for the current day
<p className="pb-3 text-xs font-medium leading-4 text-gray-500 uppercase">{t("today")}</p>
*/}
<div className="overflow-hidden rounded-md border border-gray-200">
<table className="w-full max-w-full table-fixed">
<tbody className="divide-y divide-gray-200 bg-white" data-testid="bookings">
{query.data.pages.map((page, index) => (
<Fragment key={index}>
{page.bookings.filter(filterBookings).map((booking: BookingOutput) => {
const recurringInfo = page.recurringInfo.find(
(info) => info.recurringEventId === booking.recurringEventId
);
return (
<>
{!!bookingsToday.length && status === "upcoming" && (
<div className="mb-6 pt-2 xl:pt-0">
<WipeMyCalActionButton bookingStatus={status} bookingsEmpty={isEmpty} />
<p className="mb-2 text-xs font-medium uppercase leading-4 text-gray-500">{t("today")}</p>
<div className="overflow-hidden rounded-md border border-gray-200">
<table className="w-full max-w-full table-fixed">
<tbody className="divide-y divide-gray-200 bg-white">
<Fragment>
{bookingsToday.map((booking: BookingOutput) => (
<BookingListItem
key={booking.id}
listingStatus={status}
recurringInfo={recurringInfo}
recurringInfo={recurringInfoToday}
{...booking}
/>
);
})}
</Fragment>
))}
</tbody>
</table>
))}
</Fragment>
</tbody>
</table>
</div>
</div>
)}
<div className="pt-2 xl:pt-0">
<p className="mb-2 text-xs font-medium uppercase leading-4 text-gray-500">{t("all")}</p>
<div className="overflow-hidden rounded-md border border-gray-200">
<table className="w-full max-w-full table-fixed">
<tbody className="divide-y divide-gray-200 bg-white" data-testid="bookings">
{query.data.pages.map((page, index) => (
<Fragment key={index}>
{page.bookings.filter(filterBookings).map((booking: BookingOutput) => {
const recurringInfo = page.recurringInfo.find(
(info) => info.recurringEventId === booking.recurringEventId
);
return (
<BookingListItem
key={booking.id}
listingStatus={status}
recurringInfo={recurringInfo}
{...booking}
/>
);
})}
</Fragment>
))}
</tbody>
</table>
</div>
<div className="p-4 text-center" ref={buttonInView.ref}>
<Button
color="minimal"
loading={query.isFetchingNextPage}
disabled={!query.hasNextPage}
onClick={() => query.fetchNextPage()}>
{query.hasNextPage ? t("load_more_results") : t("no_more_results")}
</Button>
</div>
</div>
<div className="p-4 text-center" ref={buttonInView.ref}>
<Button
color="minimal"
loading={query.isFetchingNextPage}
disabled={!query.hasNextPage}
onClick={() => query.fetchNextPage()}>
{query.hasNextPage ? t("load_more_results") : t("no_more_results")}
</Button>
</div>
</div>
</>
)}
{query.status === "success" && isEmpty && (
<div className="flex items-center justify-center pt-2 xl:mx-6 xl:pt-0">
+2 -1
View File
@@ -4,7 +4,7 @@ import { JSONObject } from "superjson/dist/types";
import { parseRecurringEvent } from "@calcom/lib";
import prisma from "@calcom/prisma";
import { bookEventTypeSelect } from "@calcom/prisma/selects";
import { EventTypeMetaDataSchema } from "@calcom/prisma/zod-utils";
import { customInputSchema, EventTypeMetaDataSchema } from "@calcom/prisma/zod-utils";
import { asStringOrNull, asStringOrThrow } from "@lib/asStringOrNull";
import { inferSSRProps } from "@lib/types/inferSSRProps";
@@ -79,6 +79,7 @@ export async function getServerSideProps(context: GetServerSidePropsContext) {
...e,
periodStartDate: e.periodStartDate?.toString() ?? null,
periodEndDate: e.periodEndDate?.toString() ?? null,
customInputs: customInputSchema.array().parse(e.customInputs || []),
schedulingType: null,
users: users.map((u) => ({
id: u.id,
+11 -4
View File
@@ -1,6 +1,6 @@
/* eslint-disable @typescript-eslint/no-empty-function */
import { useAutoAnimate } from "@formkit/auto-animate/react";
import { EventTypeCustomInput, PeriodType, Prisma, SchedulingType } from "@prisma/client";
import { PeriodType, Prisma, SchedulingType } from "@prisma/client";
import { GetServerSidePropsContext } from "next";
import { useRouter } from "next/router";
import { useState } from "react";
@@ -16,15 +16,15 @@ import convertToNewDurationType from "@calcom/lib/convertToNewDurationType";
import findDurationType from "@calcom/lib/findDurationType";
import getStripeAppData from "@calcom/lib/getStripeAppData";
import { useLocale } from "@calcom/lib/hooks/useLocale";
import { HttpError } from "@calcom/lib/http-error";
import prisma from "@calcom/prisma";
import { EventTypeMetaDataSchema } from "@calcom/prisma/zod-utils";
import { customInputSchema, EventTypeMetaDataSchema } from "@calcom/prisma/zod-utils";
import { trpc } from "@calcom/trpc/react";
import type { BookingLimit, RecurringEvent } from "@calcom/types/Calendar";
import { Form, showToast } from "@calcom/ui";
import { asStringOrThrow } from "@lib/asStringOrNull";
import { getSession } from "@lib/auth";
import { HttpError } from "@lib/core/http/error";
import { inferSSRProps } from "@lib/types/inferSSRProps";
import { AvailabilityTab } from "@components/eventtype/AvailabilityTab";
@@ -63,7 +63,7 @@ export type FormValues = {
displayLocationPublicly?: boolean;
phone?: string;
}[];
customInputs: EventTypeCustomInput[];
customInputs: CustomInputParsed[];
users: string[];
schedule: number;
periodType: PeriodType;
@@ -87,6 +87,8 @@ export type FormValues = {
bookingLimits?: BookingLimit;
};
export type CustomInputParsed = typeof customInputSchema._output;
const querySchema = z.object({
tabName: z
.enum(["setup", "availability", "apps", "limits", "recurring", "team", "advanced", "workflows"])
@@ -261,6 +263,7 @@ const EventTypePage = (props: inferSSRProps<typeof getServerSideProps>) => {
recurringEvent,
locations,
metadata,
customInputs,
// We don't need to send send these values to the backend
// eslint-disable-next-line @typescript-eslint/no-unused-vars
seatsPerTimeSlotEnabled,
@@ -298,6 +301,7 @@ const EventTypePage = (props: inferSSRProps<typeof getServerSideProps>) => {
seatsPerTimeSlot,
seatsShowAttendees,
metadata,
customInputs,
});
}}>
<div ref={animationParentRef} className="space-y-6">
@@ -495,6 +499,8 @@ export const getServerSideProps = async (context: GetServerSidePropsContext) =>
// const parsedMetaData = _EventTypeModel.parse(newMetadata);
const parsedMetaData = newMetadata;
const parsedCustomInputs = (rawEventType.customInputs || []).map((input) => customInputSchema.parse(input));
const eventType = {
...restEventType,
schedule: rawEventType.schedule?.id || rawEventType.users[0]?.defaultScheduleId || null,
@@ -502,6 +508,7 @@ export const getServerSideProps = async (context: GetServerSidePropsContext) =>
bookingLimits: parseBookingLimit(restEventType.bookingLimits),
locations: locations as unknown as LocationObject[],
metadata: parsedMetaData,
customInputs: parsedCustomInputs,
};
// backwards compat
@@ -71,7 +71,7 @@ const ConferencingLayout = () => {
.map((app) => ({ ...app, title: app.title || app.name }))
.map((app) => (
<ListItem className="flex-col border-0" key={app.title}>
<div className="flex w-full flex-1 items-center space-x-3 pl-1 pt-1 rtl:space-x-reverse">
<div className="flex w-full flex-1 items-center space-x-2 p-4 rtl:space-x-reverse">
{
// eslint-disable-next-line @next/next/no-img-element
app.logo && <img className="h-10 w-10" src={app.logo} alt={app.title} />
+2 -1
View File
@@ -4,7 +4,7 @@ import { JSONObject } from "superjson/dist/types";
import { LocationObject, privacyFilteredLocations } from "@calcom/app-store/locations";
import { parseRecurringEvent } from "@calcom/lib";
import prisma from "@calcom/prisma";
import { EventTypeMetaDataSchema } from "@calcom/prisma/zod-utils";
import { customInputSchema, EventTypeMetaDataSchema } from "@calcom/prisma/zod-utils";
import { asStringOrNull, asStringOrThrow } from "@lib/asStringOrNull";
import getBooking, { GetBookingType } from "@lib/getBooking";
@@ -96,6 +96,7 @@ export async function getServerSideProps(context: GetServerSidePropsContext) {
metadata: EventTypeMetaDataSchema.parse(eventType.metadata || {}),
periodStartDate: e.periodStartDate?.toString() ?? null,
periodEndDate: e.periodEndDate?.toString() ?? null,
customInputs: customInputSchema.array().parse(e.customInputs || []),
users: eventType.users.map((u) => ({
id: u.id,
name: u.name,
+2
View File
@@ -65,6 +65,8 @@ test("add webhook & test that creating an event triggers a webhook call", async
body.payload.additionalInformation = dynamic;
body.payload.requiresConfirmation = dynamic;
body.payload.eventTypeId = dynamic;
body.payload.videoCallData = dynamic;
body.payload.appsStatus = dynamic;
// if we change the shape of our webhooks, we can simply update this by clicking `u`
// console.log("BODY", body);
@@ -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]","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]"}}
{"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]","videoCallData":"[redacted/dynamic]","appsStatus":"[redacted/dynamic]","eventTitle":"30 min","eventDescription":null,"price":0,"currency":"usd","length":30,"bookingId":"[redacted/dynamic]","metadata":{},"status":"ACCEPTED","additionalInformation":"[redacted/dynamic]"}}
@@ -1115,7 +1115,6 @@
"error_updating_settings": "خطأ في تحديث الإعدادات",
"personal_cal_url": "عنوان {{appName}} URL الخاص بي",
"bio_hint": "اكتب بضع جمل عن نفسك، والتي ستظهر على صفحة عنوان Url الشخصية لديك.",
"timeformat_profile_hint": "هذا إعداد داخلي، ولن يؤثر على كيفية عرض الأوقات على صفحات الحجز العامة لديك أو لدى أي شخص يقوم بحجزك.",
"delete_account_modal_title": "حذف الحساب",
"confirm_delete_account_modal": "هل أنت متأكد من أنك تريد حذف حساب {{appName}} الخاص بك؟",
"delete_my_account": "حذف حسابي",
@@ -1324,7 +1323,7 @@
"saml_config_updated_successfully": "تم تحديث تهيئة SAML بنجاح",
"saml_configuration": "تكوين SAML",
"delete_saml_configuration": "حذف تكوين SAML",
"delete_saml_configuration_confirmation_message": "هل تريد بالتأكيد حذف تكوين SAML؟ لن يتمكن بعد الآن أعضاء فريقك الذين يستخدمون تسجيل الدخول عبر SAML من الوصول إلى {{appName}}.",
"delete_saml_configuration_confirmation_message": "هل تريد بالتأكيد حذف تكوين SAML؟ لن يتمكن بعد الآن أعضاء فريقك الذين يستخدمون تسجيل الدخول عبر SAML من الوصول إلى Cal.com.",
"confirm_delete_saml_configuration": "نعم، احذف تكوين SAML",
"saml_not_configured_yet": "لم يتم تكوين SAML حتى الآن",
"saml_configuration_description": "يُرجى لصق بيانات تعريف SAML المقدمة من مزود الهوية في مربع النص أدناه لتحديث تكوين SAML.",
@@ -1115,7 +1115,6 @@
"error_updating_settings": "Chyba při aktualizaci nastavení",
"personal_cal_url": "Moje osobní adresa URL {{appName}}",
"bio_hint": "Několik vět o vás. Obsah se zobrazí se na vaší osobní stránce URL.",
"timeformat_profile_hint": "Jedná se o interní nastavení, které nemá vliv na to, jak se vám nebo komukoli, kdo vás rezervuje, na veřejných rezervačních stránkách zobrazují časy.",
"delete_account_modal_title": "Odstranit účet",
"confirm_delete_account_modal": "Opravdu chcete odstranit svůj účet {{appName}}?",
"delete_my_account": "Odstranit můj účet",
+7 -53
View File
@@ -6,9 +6,7 @@
"second_other": "{{count}} Sekunden",
"upgrade_now": "Jetzt upgraden",
"accept_invitation": "Einladung annehmen",
"calcom_explained": "{{appName}} ist die Open Source Calendly Alternative, die Ihnen die Kontrolle über Ihre eigenen Daten, Workflow und Erscheinungsbild gibt.",
"have_any_questions": "Haben Sie Fragen? Wir sind hier um Ihnen zu helfen.",
"reset_password_subject": "{{appName}}: Anleitung zum Zurücksetzen des Passworts",
"event_declined_subject": "Abgelehnt: {{eventType}} mit {{name}} am {{date}}",
"event_cancelled_subject": "Abgesagt: {{eventType}} mit {{name}} am {{date}}",
"event_request_declined": "Ihre Event-Anfrage wurde abgelehnt",
@@ -67,7 +65,7 @@
"event_still_awaiting_approval": "Ein Event wartet noch auf Ihre Bestätigung",
"booking_submitted_subject": "Buchung eingereicht: {{eventType}} mit {{name}} am {{date}}",
"your_meeting_has_been_booked": "Dein Termin wurde gebucht",
"event_type_has_been_rescheduled_on_time_date": "Ihr {{eventType}} mit {{name}} wurde auf {{time}} ({{timeZone}}) am {{date}} verschoben.",
"event_type_has_been_rescheduled_on_time_date": "Ihr {{eventType}} mit {{name}} wurde verschoben auf {{date}}.",
"event_has_been_rescheduled": "Ihr Event wurde verschoben.",
"request_reschedule_title_attendee": "Anfrage zur Neuplanung Ihrer Buchung",
"request_reschedule_subtitle": "{{organizer}} hat die Buchung storniert und Sie gebeten, eine andere Zeit auszuwählen.",
@@ -99,9 +97,7 @@
"join_team": "Team beitreten",
"manage_this_team": "Dieses Team verwalten",
"team_info": "Informationen zum Team",
"request_another_invitation_email": "Falls Sie {{toEmail}} lieber nicht als Ihre {{appName}}-E-Mail verwenden möchten oder bereits über ein {{appName}}-Konto verfügen, fordern Sie bitte eine weitere Einladung zu dieser E-Mail an.",
"you_have_been_invited": "Sie wurden eingeladen, dem Team {{teamName}} beizutreten",
"user_invited_you": "{{user}} hat Sie eingeladen, dem Team {{teamName}} beizutreten",
"hidden_team_member_title": "Sie sind in diesem Team ausgeblendet",
"hidden_team_member_message": "Ihre Teammitgliedschaft ist nicht bezahlt, entweder upgraden Sie Ihren Account auf Pro oder lassen den Team-Organisator wissen, dass er für Ihren Platz im Team bezahlen kann.",
"hidden_team_owner_message": "Sie benötigen einen Pro-Account um die Teamfunktionen zu verweden. Sie werden dem Team nicht angezeigt, bis der Account upgegradet wurde.",
@@ -113,8 +109,6 @@
"team_upgraded_successfully": "Das Update für Ihr Team war erfolgreich!",
"use_link_to_reset_password": "Benutzen Sie den Link, um Ihr Passwort zurückzusetzen",
"hey_there": "Hallo,",
"forgot_your_password_calcom": "Passwort vergessen? - {{appName}}",
"delete_webhook_confirmation_message": "Sind Sie sicher, dass Sie diesen Webhook löschen möchten? Sie werden keine {{appName}} Termindaten mehr erhalten.",
"confirm_delete_webhook": "Ja, Webhook löschen",
"edit_webhook": "Webhook bearbeiten",
"delete_webhook": "Webhook löschen",
@@ -190,7 +184,6 @@
"30min_meeting": "30 Minuten Termin",
"secret": "Passwort",
"leave_blank_to_remove_secret": "Leer lassen, um Passwort zu entfernen",
"webhook_secret_key_description": "Stellen Sie sicher, dass Ihr Server aus Sicherheitsgründen nur die erwarteten {{appName}}-Anfragen empfängt",
"secret_meeting": "Geheimer Termin",
"login_instead": "Stattdessen anmelden",
"already_have_an_account": "Haben Sie bereits ein Konto?",
@@ -234,13 +227,11 @@
"details": "Details",
"welcome": "Willkommen",
"welcome_back": "Willkommen zurück",
"welcome_to_calcom": "Willkommen bei {{appName}}",
"welcome_instructions": "Wie ist Ihr Name und in welcher Zeitzone sind Sie? Dies kann später bearbeitet werden.",
"connect_caldav": "Mit CalDav-Server verbinden",
"credentials_stored_and_encrypted": "Ihre Zugangsdaten werden verschlüsselt gespeichert.",
"connect": "Verbinden",
"try_for_free": "Kostenlos testen",
"create_booking_link_with_calcom": "Erstellen Sie Ihren eigenen Buchungslink mit {{appName}}",
"who": "Wer",
"what": "Was",
"when": "Wann",
@@ -346,7 +337,6 @@
"dark": "Dunkel",
"automatically_adjust_theme": "Theme automatisch basierend auf den Einstellungen des Teilnehmers anpassen",
"user_dynamic_booking_disabled": "Einige der Benutzer in der Gruppe haben derzeit dynamische Gruppenbuchungen deaktiviert",
"allow_dynamic_booking_tooltip": "Gruppenbuchungslinks, die dynamisch durch Hinzufügen mehrerer Benutzernamen mit einem '+' erstellt werden können. Beispiel: '{{appName}}/bailey+peer'",
"allow_dynamic_booking": "Ermöglichen Sie es den Teilnehmenden, Sie über dynamische Gruppenbuchungen zu buchen",
"email": "Email",
"email_placeholder": "jdoe@example.com",
@@ -366,8 +356,6 @@
"team_webhooks": "Team Webhooks",
"create_new_webhook_to_account": "Erstellen Sie einen neuen Webhook in Ihrem Account",
"new_webhook": "Neuer Webhook",
"receive_cal_meeting_data": "Erhalte {{appName}} Termindaten unter einer bestimmten URL in Echtzeit, wenn ein Termin geplant oder abgesagt wird.",
"receive_cal_event_meeting_data": "Erhalte {{appName}} Termindaten unter einer bestimmten URL in Echtzeit, wenn ein Ereignis geplant oder abgesagt wird.",
"responsive_fullscreen_iframe": "Responsives Vollbild-iframe",
"loading": "Wird geladen...",
"deleting": "Lösche...",
@@ -375,8 +363,7 @@
"developer": "Entwickler",
"manage_developer_settings": "Verwalten Sie Ihre Entwicklereinstellungen.",
"iframe_embed": "iframe Embed",
"embed_calcom": "Der einfachste Weg, {{appName}} auf Ihrer Website einzubinden.",
"integrate_using_embed_or_webhooks": "Integrieren Sie {{appName}} in Ihre Website mit unseren Einbettungs-Optionen oder erhalten Sie Buchungsdaten mit Webhooks.",
"integrate_using_embed_or_webhooks": "Integrieren Sie Cal.com in Ihre Website mit unseren Einbettungs-Optionen oder erhalten Sie Buchungsdaten mit Webhooks.",
"schedule_a_meeting": "Termin planen",
"view_and_manage_billing_details": "Ihre Rechnungsdetails ansehen und verwalten",
"view_and_edit_billing_details": "Rechnungsdaten ansehen, bearbeiten oder Abonnement kündigen.",
@@ -507,8 +494,6 @@
"invite_new_member_description": "Hinweis: Dies wird <1>einen zusätzlichen Platz(15 $/m)</1> in Ihrem Abonnement kosten.",
"invite_new_team_member": "Laden Sie jemanden in Ihr Team ein.",
"change_member_role": "Rolle des Teammitglieds ändern",
"disable_cal_branding": "{{appName}} Werbung deaktivieren",
"disable_cal_branding_description": "Verstecken Sie {{appName}} Werbung auf Ihren öffentlichen Seiten.",
"danger_zone": "Achtung",
"back": "Zurück",
"cancel": "Abbrechen",
@@ -624,7 +609,6 @@
"changed_team_billing_info": "Ab Januar 2022 berechnen wir pro Sitzplatz für Teammitglieder. Mitglieder deines Teams, die Pro kostenlos hatten, sind jetzt in einer 14 Tage Testphase. Sobald die Testversion abläuft, werden diese Mitglieder vor Ihrem Team verborgen, es sei denn, Sie upgraden jetzt.",
"create_manage_teams_collaborative": "Erstellen und verwalten Sie Teams, um kollaborative Funktionen zu nutzen.",
"only_available_on_pro_plan": "Diese Funktion ist nur im Pro Plan verfügbar",
"remove_cal_branding_description": "Um die {{appName}} Werbung von Ihren Buchungsseiten zu entfernen, müssen Sie auf ein Pro-Konto upgraden.",
"edit_profile_info_description": "Bearbeiten Sie Ihre Profilinformationen, welche bei Ihrem Cal-Link angezeigt werden.",
"change_email_tip": "Möglicherweise müssen Sie sich aus- und einloggen, um die Änderung wirksam zu machen.",
"little_something_about": "Ein wenig über sich selbst.",
@@ -673,21 +657,13 @@
"private_link_label": "Privater Link",
"private_link_hint": "Ihr privater Link wird nach jeder Nutzung neu generiert",
"copy_private_link": "Privaten Link kopieren",
"private_link_description": "Erzeuge einen privaten Link um einen Link zu teilen, der deinen {{appName}} Benutzername nicht preisgibt",
"invitees_can_schedule": "Teilnehmer können folgendes buchen",
"date_range": "Datumszeitraum",
"calendar_days": "Kalendertage",
"business_days": "Werktage",
"set_address_place": "Adresse oder Ort festlegen",
"set_link_meeting": "Einen Link zum Termin setzen",
"cal_invitee_phone_number_scheduling": "{{appName}} wird Ihren Teilnehmer bitten, vor der Planung eine Telefonnummer einzugeben.",
"cal_provide_google_meet_location": "{{appName}} wird einen Google Meet Termin zur Verfügung stellen.",
"cal_provide_zoom_meeting_url": "{{appName}} stellt eine Zoom Termin-URL zur Verfügung.",
"cal_provide_tandem_meeting_url": "{{appName}} stellt eine Tandem Termin-URL zur Verfügung.",
"cal_provide_video_meeting_url": "{{appName}} stellt eine tägliche Video-Termin-URL zur Verfügung.",
"cal_provide_jitsi_meeting_url": "{{appName}} stellt eine Jitsi Meet URL zur Verfügung.",
"cal_provide_huddle01_meeting_url": "{{appName}} stellt eine tägliche Huddle01-Web3-Termin-URL zur Verfügung.",
"cal_provide_teams_meeting_url": "{{appName}} stellt eine MS Teams Termin-URL zur Verfügung. HINWEIS: Sie brauchen ein berufliches- oder Schulkonto.",
"cal_provide_jitsi_meeting_url": "Cal stellt eine Jitsi Meet URL zur Verfügung.",
"require_payment": "Zahlung erforderlich",
"commission_per_transaction": "Provision pro Transaktion",
"event_type_updated_successfully_description": "Ihr Termintyp wurde erfolgreich aktualisiert.",
@@ -710,7 +686,6 @@
"confirm_delete_event_type": "Ja, Termintyp löschen",
"delete_account": "Account löschen",
"confirm_delete_account": "Ja, Account löschen",
"delete_account_confirmation_message": "Sind Sie sicher, dass Sie ihren {{appName}} Account löschen wollen? Sie können dann nicht mehr über Ihre geteilten Links gebucht werden und und alle Einstellungen gehen verloren.",
"integrations": "Integrationen",
"apps": "Apps",
"category_apps": "{{category}} Apps",
@@ -768,11 +743,12 @@
"trending_apps": "Angesagte Apps",
"explore_apps": "{{category}} Apps",
"installed_apps": "Installierte Apps",
"free_to_use_apps": "Kostenlos",
"no_category_apps": "Keine {{category}} Apps",
"no_category_apps_description_calendar": "Fügen Sie eine Kalender-App hinzu, in der nach Konflikten gesucht werden soll, um Doppelbuchungen zu vermeiden",
"no_category_apps_description_conferencing": "Versuchen Sie, eine Konferenz-App hinzuzufügen, um Videoanrufe mit Ihren Kunden zu integrieren",
"no_category_apps_description_payment": "Fügen Sie eine Zahlungs-App hinzu, um die Transaktion zwischen Ihnen und Ihren Kunden zu erleichtern",
"no_category_apps_description_analytics": "Fügen Sie eine Analyse-App für Ihre Buchungsseiten hinzu",
"no_category_apps_description_automation": "Fügen Sie eine Automatisierungs-App hinzu, die verwendet werden soll",
"no_category_apps_description_other": "Füge jede beliebige andere Art von App hinzu, um alle möglichen Dinge zu tun",
"installed_app_calendar_description": "Legen Sie den/die Kalender in denen nach Konflikten gesucht werden sollen fest, um Doppelbuchungen zu vermeiden.",
"installed_app_conferencing_description": "Fügen Sie Ihre liebsten Videokonferenz-Apps für Ihre Meetings hinzu",
@@ -805,11 +781,8 @@
"verify_wallet": "Wallet verifizieren",
"connect_metamask": "Metamask verbinden",
"create_events_on": "Erstelle Termine in:",
"enterprise_license": "Das ist eine Enterprise-Funktion",
"enterprise_license_description": "Um diese Funktion zu aktivieren, holen Sie sich einen Deployment-Schlüssel von der {{consoleUrl}}-Konsole und fügen Sie ihn als CALCOM_LICENSE_KEY zu Ihrer .env hinzu. Wenn Ihr Team bereits eine Lizenz hat, wenden Sie sich bitte an {{supportMail}} für Hilfe.",
"missing_license": "Lizenz fehlt",
"signup_requires": "Kommerzielle Lizenz erforderlich",
"signup_requires_description": "{{companyName}} bietet zur Zeit keine kostenlose Open-Source-Version der Anmeldeseite an. Um vollen Zugriff auf die Registrierungskomponenten zu erhalten, müssen Sie eine kommerzielle Lizenz erwerben. Für den persönlichen Gebrauch empfehlen wir die Prisma Data Platform oder jede andere Postgres-Schnittstelle zur Erstellung von Accounts.",
"next_steps": "Nächste Schritte",
"acquire_commercial_license": "Eine kommerzielle Lizenz erwerben",
"the_infrastructure_plan": "Der Infrastrukturplan ist nutzungsbasiert und bietet Startup-freundliche Discounts an.",
@@ -911,23 +884,17 @@
"update_location": "Ort aktualisieren",
"location_updated": "Ort aktualisiert",
"email_validation_error": "Das sieht nicht wie eine E-Mail-Adresse aus",
"place_where_cal_widget_appear": "Kopieren Sie diesen Code in ihr HTML wo ihr {{appName}}-Widget erscheinen soll.",
"create_update_react_component": "Erstellen oder Aktualisieren einer existierenden Reaktionskomponente wie unten gezeigt.",
"copy_code": "Code kopieren",
"code_copied": "Code kopiert!",
"how_you_want_add_cal_site": "Wie möchten Sie {{appName}} zu Ihrer Website hinzufügen?",
"choose_ways_put_cal_site": "Wählen Sie eine der folgenden Möglichkeiten, {{appName}} auf Ihrer Website zu integrieren.",
"setting_up_zapier": "Einrichtung Ihrer Zapier-Integration",
"generate_api_key": "Api Key generieren",
"generate_api_key_description": "Generieren Sie einen API-Schlüssel für {{appName}} auf",
"your_unique_api_key": "Ihr eindeutiger API-Key",
"copy_safe_api_key": "Kopieren Sie diesen API-Key und speichern Sie ihn an einem sicheren Ort. Wenn Sie diesen Key verlieren, müssen Sie einen neuen generieren.",
"zapier_setup_instructions": "<0>Gehen Sie zu: <1>Zapier-Einladungs-Link</1></0><1>Logge Sie sich in Ihr Zapier-Konto ein und erstelle einen neuen Zap.</1><2>Wählen Sie {{appName}} als ihre Trigger-App aus. Wählen Sie auch ein Auslöserereignis.</2><3>Wähle Sie ihr Konto aus und geben dann ihren eindeutigen API-Key ein.</3><4>Teste Sie den Trigger.</4><5>Alles fertig!</5>",
"install_zapier_app": "Bitte installieren Sie zuerst die Zapier-App im App Store.",
"connect_apple_server": "Mit Apple-Server verbinden",
"connect_caldav_server": "Mit CalDav-Server verbinden",
"calendar_url": "Kalender-URL",
"apple_server_generate_password": "Generieren Sie ein App-spezifisches Passwort für {{appName}} unter",
"credentials_stored_encrypted": "Ihre Zugangsdaten werden verschlüsselt gespeichert.",
"it_stored_encrypted": "Er wird gespeichert und verschlüsselt.",
"go_to_app_store": "Zum App Store",
@@ -1114,11 +1081,8 @@
"turn_off": "Abschalten",
"settings_updated_successfully": "Einstellungen erfolgreich aktualisiert",
"error_updating_settings": "Fehler beim Aktualisieren der Einstellungen",
"personal_cal_url": "Meine persönliche {{appName}} URL",
"bio_hint": "Schreiben Sie eine kurze Beschreibung, welche auf Ihrer persönlichen Url-Seite erscheinen wird.",
"timeformat_profile_hint": "Dies ist eine interne Einstellung und hat keinen Einfluss darauf, wie die Zeiten auf den öffentlichen Buchungsseiten für Sie oder jemanden, der Sie buchen möchte, angezeigt werden.",
"delete_account_modal_title": "Account löschen",
"confirm_delete_account_modal": "Sind Sie sicher, dass Sie Ihr {{appName}}-Konto löschen möchten?",
"delete_my_account": "Meinen Account löschen",
"start_of_week": "Wochenstart",
"select_calendars": "Wählen Sie aus, in welchen Kalendern Sie nach Konflikten suchen wollen, um Doppelbuchungen zu vermeiden.",
@@ -1128,7 +1092,6 @@
"custom_brand_colors": "Eigene Marken-Farben",
"customize_your_brand_colors": "Passen Sie Ihre eigene Markenfarbe auf Ihrer Buchungsseite an.",
"pro": "Pro",
"removes_cal_branding": "Entfernt alle {{appName}}-bezogenen Markenzeichen, z.B. \"Powered by {{appName}}.\"",
"profile_picture": "Profilbild",
"upload": "Upload",
"add_profile_photo": "Profilbild hinzufügen",
@@ -1156,7 +1119,6 @@
"embeds": "Embeds",
"impersonation": "Benutzeridentitätswechsel",
"users": "Benutzer",
"profile_description": "Einstellungen für Ihr {{appName}} Profil verwalten",
"general_description": "Einstellungen für Ihre Sprache und Zeitzone verwalten",
"calendars_description": "Konfigurieren Sie, wie Ihre Event-Typen mit Ihren Kalendern interagieren sollen",
"appearance_description": "Einstellungen für Ihre Buchungsdarstellung verwalten",
@@ -1194,7 +1156,6 @@
"create_your_first_form": "Erstellen Sie Ihr erstes Formular",
"create_your_first_form_description": "Mithilfe von Leitungsformularen können Sie qualifizierende Fragen stellen und zur richtigen Person oder zum richtigen Veranstaltungstyp weiterleiten.",
"create_your_first_webhook": "Erstellen Sie Ihren ersten Webhook",
"create_your_first_webhook_description": "Mit Webhooks können Sie Meetingdaten in Echtzeit erhalten, sobald etwas auf {{appName}} passiert.",
"for_a_maximum_of": "Für maximal",
"event_one": "Ereignis",
"event_other": "Ereignisse",
@@ -1210,7 +1171,6 @@
"workflow_example_4": "Eine Erinnerung per SMS an die Teilnehmer verschicken, 24 Stunden bevor das Event startet",
"workflow_example_5": "Benutzerdefinierte E-Mail senden, wenn der Termin zum Moderieren neu geplant wird",
"workflow_example_6": "Benutzerdefinierte SMS senden, wenn der Termin zum Moderieren gebucht wird",
"welcome_to_cal_header": "Willkommen bei {{appName}}!",
"edit_form_later_subtitle": "Sie können dies später bearbeiten.",
"connect_calendar_later": "Ich verbinde meinen Kalender später",
"set_my_availability_later": "Ich werde meine Verfügbarkeit später festlegen",
@@ -1220,7 +1180,6 @@
"booking_appearance": "Darstellung der Buchungsseite",
"appearance_team_description": "Einstellungen für die Buchungsdarstellung Ihres Teams verwalten",
"only_owner_change": "Nur der Besitzer dieses Teams kann Änderungen an den Buchungen des Teams vornehmen ",
"team_disable_cal_branding_description": "Entfernt alle {{appName}}-bezogenen Markenzeichen, z.B. 'Powered by {{appName}}.'",
"invited_by_team": "{{teamName}} hat Sie eingeladen, dem Team als {{role}} beizutreten",
"token_invalid_expired": "Token ist entweder ungültig oder abgelaufen.",
"exchange_add": "Mit Microsoft Exchange verbinden",
@@ -1240,13 +1199,11 @@
"theme_dark": "Dunkel",
"theme_system": "Systemstandard",
"add_a_team": "Team hinzufügen",
"add_webhook_description": "Erhalten Sie Meetingdaten in Echtzeit, wenn etwas auf {{appName}} passiert",
"triggers_when": "Löst aus, wenn",
"test_webhook": "Bitte führen Sie einen Pingtest vor der Erstellung durch.",
"enable_webhook": "Webhook aktivieren",
"add_webhook": "Webhook hinzufügen",
"webhook_edited_successfully": "Webhook gespeichert",
"webhooks_description": "Erhalten Sie Meetingdaten in Echtzeit, wenn etwas auf {{appName}} passiert",
"api_keys_description": "Generieren Sie API-Schlüssel für den Zugriff auf Ihren eigenen Account",
"new_api_key": "Neuer API-Schlüssel",
"active": "Aktiv",
@@ -1255,21 +1212,19 @@
"embeds_title": "HTML iframe-Einbettung",
"embeds_description": "Alle Event-Typen auf Ihrer Website einbetten",
"create_first_api_key": "Erstellen Sie Ihren ersten API-Schlüssel",
"create_first_api_key_description": "API-Schlüssel erlauben es anderen Apps, mit {{appName}} zu kommunizieren",
"back_to_signin": "Zurück zur Anmeldung",
"reset_link_sent": "Zurücksetzungslink gesendet",
"password_reset_email": "Eine E-Mail mit Anweisungen zum Zurücksetzen Ihres Passworts ist auf dem Weg zu {{email}}.",
"password_reset_leading": "Falls Sie nicht bald eine E-Mail erhalten, überprüfen Sie, ob die eingegebene E-Mail-Adresse korrekt ist. Überprüfen Sie außerdem Ihren Spam-Ordner oder wenden Sie sich an den Support, wenn das Problem weiterhin besteht.",
"password_updated": "Passwort aktualisiert!",
"pending_payment": "Zahlung ausstehend",
"confirmation_page_rainbow": "Sperren Sie Ihr Ereignis hinter Tokens oder NFTs auf Ethereum, Polygon und mehr.",
"not_on_cal": "Nicht auf {{appName}}",
"no_calendar_installed": "Kein Kalender installiert",
"no_calendar_installed_description": "Sie haben noch keinen Ihrer Kalender verbunden",
"add_a_calendar": "Kalender hinzufügen",
"change_email_hint": "Möglicherweise müssen Sie sich aus- und einloggen, um jegliche Änderung wirksam zu machen",
"confirm_password_change_email": "Bitte bestätigen Sie Ihr Passwort, bevor Sie Ihre E-Mail-Adresse ändern",
"seats": "Plätze",
"every_app_published": "Jede App, die im {{appName}} App Store veröffentlicht wird, ist Open Source und gründlich durch Peer Reviews getestet. {{companyName}} unterstützt oder zertifiziert diese Apps jedoch nicht, es sei denn, sie werden von {{appName}} veröffentlicht. Wenn Sie auf unangemessene Inhalte oder Verhaltensweisen stoßen, melden Sie diese bitte.",
"report_app": "App melden",
"limit_booking_frequency": "Buchungsfrequenz begrenzen",
"limit_booking_frequency_description": "Begrenzen, wie oft dieses Ereignis gebucht werden kann",
@@ -1324,7 +1279,7 @@
"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 {{appName}} zugreifen.",
"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.",
@@ -1337,7 +1292,6 @@
"saml_sp_acs_url_copied": "ACS-URL kopiert!",
"saml_sp_entity_id_copied": "SP-Entitäts-ID kopiert!",
"saml_btn_configure": "Konfigurieren",
"kbar_search_placeholder": "Geben Sie einen Befehl ein oder suchen Sie..."
"add_calendar": "Kalender hinzufügen",
"limit_future_bookings": "Zukünftige Buchungen begrenzen",
"limit_future_bookings_description": "Begrenzt, wie weit in die Zukunft dieses Ereignis gebucht werden kann",
@@ -1388,5 +1388,9 @@
"test_preview_description": "Test your routing form without submitting any data",
"test_routing": "Test Routing",
"booking_limit_reached":"Booking Limit for this event type has been reached",
"fill_this_field": "Please fill in this field"
"fill_this_field": "Please fill in this field",
"options": "Options",
"enter_option": "Enter Option {{index}}",
"add_an_option": "Add an option",
"radio": "Radio"
}
@@ -101,7 +101,7 @@
"team_info": "Información del equipo",
"request_another_invitation_email": "Si prefiere no utilizar {{toEmail}} como su correo electrónico de {{appName}} o ya tiene una cuenta de {{appName}}, por favor solicite otra invitación a ese email.",
"you_have_been_invited": "Has sido invitado a unirte al equipo {{teamName}}",
"user_invited_you": "{{user}} te invitó a unirte al equipo {{teamName}}",
"user_invited_you": "{{user}} te invitó a unirte al equipo {{team}}",
"hidden_team_member_title": "Estás oculto en este equipo",
"hidden_team_member_message": "No está paga tu plaza, actualiza a PRO o infórmale al propietario del equipo para que pague tu plaza.",
"hidden_team_owner_message": "Necesitas una cuenta Pro para gestionar equipos, permanecerás oculto hasta que se realice la mejora.",
@@ -316,6 +316,7 @@
"past_bookings": "Sus reservas pasadas aparecerán aquí.",
"cancelled_bookings": "Las reservas canceladas aparecerán aquí.",
"unconfirmed_bookings": "Las reservas sin confirmar aparecerán aquí.",
"unconfirmed_bookings_tooltip": "Reservas sin confirmar",
"on": "en",
"and": "y",
"calendar_shows_busy_between": "Tu calendario te muestra como ocupado entre",
@@ -534,7 +535,7 @@
"open_options": "Abrir Opciones",
"copy_link": "Copiar Enlace",
"share": "Compartir",
"share_event": "¿Quieres reservar mi {{appName}} o enviarme tu enlace?",
"share_event": "¿Quieres reservar mi Cal o enviarme tu enlace?",
"copy_link_team": "Copiar enlace al equipo",
"leave_team": "Abandonar equipo",
"confirm_leave_team": "Sí, abandonar equipo",
@@ -684,7 +685,7 @@
"cal_provide_zoom_meeting_url": "{{appName}} proporcionará una URL de reunión de Zoom.",
"cal_provide_tandem_meeting_url": "{{appName}} proporcionará una URL de reunión de Tandem.",
"cal_provide_video_meeting_url": "{{appName}} proporcionará una URL de reunión de Daily Video.",
"cal_provide_jitsi_meeting_url": "{{appName}} proporcionará una URL de reunión de Jitsi Meet.",
"cal_provide_jitsi_meeting_url": "Cal proporcionará una URL de reunión de Jitsi Meet.",
"cal_provide_huddle01_meeting_url": "{{appName}} proporcionará una URL de reunión de Huddle01 Web3 Video.",
"cal_provide_teams_meeting_url": "{{appName}} proporcionará una URL de reunión de MS Teams. NOTA: DEBE TENER UNA CUENTA DE TRABAJO O DE EDUCACIÓN",
"require_payment": "Requiere Pago",
@@ -838,6 +839,7 @@
"redirect_success_booking": "Redirigir al reservar ",
"you_are_being_redirected": "Serás redirigido a {{ url }} en $t(second, {\"count\": {{seconds}} }).",
"external_redirect_url": "https://example.com/redirect-to-my-success-page",
"redirect_url_description": "Redirija a una URL personalizada después de una reserva exitosa",
"duplicate": "Duplicar",
"offer_seats": "Ofrecer plazas",
"offer_seats_description": "Ofrecer plazas para las reservas (desactiva las reservas de invitados y las optativas)",
@@ -1113,7 +1115,6 @@
"error_updating_settings": "Error al actualizar la configuración",
"personal_cal_url": "Mi URL personal de {{appName}}",
"bio_hint": "Algunas frases sobre usted, esta información aparecerá en la página de su URL personal.",
"timeformat_profile_hint": "Esta es una configuración interna y no afectará la manera en que se muestran los horarios en las páginas de reservas públicas para usted o para cualquier persona que lo solicite.",
"delete_account_modal_title": "Eliminar cuenta",
"confirm_delete_account_modal": "¿Está seguro que desea eliminar su cuenta de {{appName}}?",
"delete_my_account": "Eliminar mi cuenta",
@@ -1169,6 +1170,8 @@
"connect_conference_apps": "Conectar aplicaciones de conferencia",
"connect_calendar_apps": "Conectar aplicaciones de calendario",
"connect_payment_apps": "Conectar aplicaciones de pago",
"connect_automation_apps": "Conecte aplicaciones de automatización",
"connect_analytics_apps": "Conecte aplicaciones de análisis",
"connect_other_apps": "Conectar otras aplicaciones",
"current_step_of_total": "Paso {{currentStep}} de {{maxSteps}}",
"add_variable": "Añadir variable",
@@ -1320,7 +1323,7 @@
"saml_config_updated_successfully": "Configuración de SAML actualizada 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 {{appName}}.",
"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.",
@@ -67,7 +67,7 @@
"event_still_awaiting_approval": "Un événement attend toujours votre approbation",
"booking_submitted_subject": "Réservation confirmée : {{eventType}} avec {{name}} le {{date}}",
"your_meeting_has_been_booked": "Votre réunion a été réservée",
"event_type_has_been_rescheduled_on_time_date": "Votre {{eventType}} avec {{name}} a été reporté à {{time}} ({{timeZone}}) le {{date}}.",
"event_type_has_been_rescheduled_on_time_date": "Votre {{eventType}} avec {{name}} a été reporté au {{date}}.",
"event_has_been_rescheduled": "Votre évènement a été reprogrammé.",
"request_reschedule_title_attendee": "Demande de report de votre réservation",
"request_reschedule_subtitle": "{{organizer}} a annulé la réservation et vous a demandé de choisir un autre moment.",
@@ -101,7 +101,7 @@
"team_info": "Informations sur l'équipe",
"request_another_invitation_email": "Si vous préférez ne pas utiliser {{toEmail}} comme votre adresse e-mail {{appName}} ou si vous avez déjà un compte {{appName}}, veuillez demander une autre invitation à cette adresse.",
"you_have_been_invited": "Vous avez été invité à rejoindre l'équipe {{teamName}}",
"user_invited_you": "{{user}} vous a invité à rejoindre l'équipe {{teamName}}",
"user_invited_you": "{{user}} vous a invité à rejoindre l'équipe {{team}}",
"hidden_team_member_title": "Vous êtes caché dans cette équipe",
"hidden_team_member_message": "Votre place n'est pas payée, passez en version Pro ou faites savoir au propriétaire de l'équipe qu'il peut payer votre place.",
"hidden_team_owner_message": "Vous avez besoin d'un compte pro pour utiliser les équipes, vous êtes caché jusqu'à ce que vous mettiez à niveau.",
@@ -535,7 +535,7 @@
"open_options": "Ouvrir les options",
"copy_link": "Copier le lien vers l'événement",
"share": "Partager",
"share_event": "Voulez-vous réserver mon {{appName}} ou m'envoyer votre lien ?",
"share_event": "Voulez-vous réserver mon cal ou m'envoyer votre lien ?",
"copy_link_team": "Copier le lien vers l'équipe",
"leave_team": "Quitter l'équipe",
"confirm_leave_team": "Oui, quitter l'équipe",
@@ -685,7 +685,7 @@
"cal_provide_zoom_meeting_url": "{{appName}} fournira une URL de réunion Zoom.",
"cal_provide_tandem_meeting_url": "{{appName}} fournira une URL de réunion Tandem.",
"cal_provide_video_meeting_url": "{{appName}} fournira une URL de réunion Daily video.",
"cal_provide_jitsi_meeting_url": "{{appName}} fournira une URL de réunion Jitsi Meet.",
"cal_provide_jitsi_meeting_url": "Cal fournira une URL de réunion Jitsi Meet.",
"cal_provide_huddle01_meeting_url": "{{appName}} fournira une URL de réunion Huddle01 web3 video.",
"cal_provide_teams_meeting_url": "{{appName}} fournira une URL de réunion MS Teams. NOTE : IL FAUT AVOIR UN COMPTE PROFESSIONNEL OU SCOLAIRE",
"require_payment": "Exiger un paiement",
@@ -1115,7 +1115,6 @@
"error_updating_settings": "Erreur lors de la mise à jour des paramètres",
"personal_cal_url": "Mon URL {{appName}} personnelle",
"bio_hint": "Quelques phrases à propos de vous. Ces informations apparaîtront sur votre page d'url personnelle.",
"timeformat_profile_hint": "Ceci est un paramètre interne et n'affectera pas la façon dont les heures sont affichées sur les pages publiques de réservation pour vous ou toute personne qui réserve votre temps.",
"delete_account_modal_title": "Supprimer le compte",
"confirm_delete_account_modal": "Êtes-vous sûr de vouloir supprimer votre compte {{appName}} ?",
"delete_my_account": "Supprimer mon compte",
@@ -1258,6 +1257,7 @@
"back_to_signin": "Retour à la connexion",
"reset_link_sent": "Lien de réinitialisation envoyé",
"password_reset_email": "Un e-mail est transmis vers {{email}} avec les instructions pour réinitialiser votre mot de passe.",
"password_reset_leading": "Si vous ne recevez pas de courriel bientôt, vérifiez que l'adresse e-mail que vous avez saisie est correcte, vérifiez votre dossier spam ou contactez le support si le problème persiste.",
"password_updated": "Mot de passe mis à jour !",
"pending_payment": "Paiement en attente",
"confirmation_page_rainbow": "Le jeton limite l'accès à votre événement en fonction des jetons ou des NFT sur Ethereum, Polygone et plus encore.",
@@ -1323,7 +1323,7 @@
"saml_config_updated_successfully": "Configuration SAML mise à jour",
"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 à {{appName}}.",
"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.",
@@ -924,7 +924,7 @@
"zapier_setup_instructions": "<0>התחבר/י לחשבון Zapier שלך וצור/י Zap חדש.</0><1>בחר/י את {{appName}} כאפליקציית ה-Trigger. בנוסף, בחר/י אירוע Trigger.</1><2>בחר/י את החשבון שלך ולאחר מכן הזן/י את מפתח ה-API הייחודי שלך.</2><3>בדוק/י את ה-Trigger.</3><4>וזהו, הכל מוכן!</4>",
"install_zapier_app": "תחילה עליך להוריד את אפליקציית Zapier מה-App Store ולהתקין אותה.",
"connect_apple_server": "חיבור לשרת Apple",
"connect_caldav_server": "חיבור לשרת CalDav",
"connect_caldav_server": "חיבור לשרת CalDav (בטא)",
"calendar_url": "כתובת ה-URL של לוח השנה",
"apple_server_generate_password": "צור סיסמה ספציפית לאפליקציה כדי להשתמש ב-{{appName}} ב:",
"credentials_stored_encrypted": "פרטי הכניסה שלך יישמרו ויוצפנו.",
@@ -1115,7 +1115,6 @@
"error_updating_settings": "שגיאה במהלך עדכון ההגדרות",
"personal_cal_url": "כתובת ה-URL של ה-{{appName}} האישי שלי",
"bio_hint": "מספר משפטים אודותיך. המידע הזה יופיע בדף ה-URL האישי שלך.",
"timeformat_profile_hint": "זוהי הגדרה פנימית שלא תשפיע על אופן התצוגה של תאריכים ושעות בדפים של הזמנות ציבוריות עבורך או עבור מי שמבצע הזמנות בשמך.",
"delete_account_modal_title": "מחיקת חשבון",
"confirm_delete_account_modal": "בטוח שברצונך למחוק את חשבון {{appName}} שלך?",
"delete_my_account": "מחיקת החשבון שלי",
@@ -1324,7 +1323,7 @@
"saml_config_updated_successfully": "תצורת SAML עודכנה בהצלחה",
"saml_configuration": "תצורת SAML",
"delete_saml_configuration": "מחיקת תצורת SAML",
"delete_saml_configuration_confirmation_message": "בטוח שברצונך למחוק את תצורת SAML? חברי הצוות שלך שנכנסים אמצעות SAML לא יוכלו יותר לגשת ל-{{appName}}.",
"delete_saml_configuration_confirmation_message": "בטוח שברצונך למחוק את תצורת SAML? חברי הצוות שלך שנכנסים אמצעות SAML לא יוכלו יותר לגשת ל-Cal.com.",
"confirm_delete_saml_configuration": "כן, אני רוצה למחוק את תצורת SAML",
"saml_not_configured_yet": "SAML עדיין לא הוגדר",
"saml_configuration_description": "יש להדביק את המטא-נתונים של SAML מספק הזהויות בתיבת הטקסט הבאה כדי לעדכן את תצורת SAML.",
@@ -1115,7 +1115,6 @@
"error_updating_settings": "Errore durante l'aggiornamento delle impostazioni",
"personal_cal_url": "Il mio URL personale di {{appName}}",
"bio_hint": "Scrivi qualcosa di te. Queste informazioni appariranno nella tua pagina personale.",
"timeformat_profile_hint": "Questa è un'impostazione interna e non influenzerà il modo in cui gli orari sono visualizzati nelle pagine di prenotazione pubbliche a te e a chiunque faccia una prenotazione.",
"delete_account_modal_title": "Elimina account",
"confirm_delete_account_modal": "Eliminare il tuo account {{appName}}?",
"delete_my_account": "Elimina il mio account",
@@ -1258,6 +1257,7 @@
"back_to_signin": "Torna alla pagina di accesso",
"reset_link_sent": "Link per la reimpostazione inviato",
"password_reset_email": "Un'e-mail con le istruzioni per reimpostare la password è stata inviata all'indirizzo {{email}}.",
"password_reset_leading": "Se un'e-mail non ti dovesse arrivare a breve, assicurati che l'indirizzo specificato sia corretto e controlla la cartella della posta indesiderata. Se il problema persiste, contatta l'assistenza clienti.",
"password_updated": "Password aggiornata!",
"pending_payment": "Pagamento in sospeso",
"confirmation_page_rainbow": "Proteggi i tuoi eventi con token o NFT su Ethereum, Polygon e altri.",
@@ -1323,7 +1323,7 @@
"saml_config_updated_successfully": "Configurazione SAML aggiornata correttamente",
"saml_configuration": "Configurazione SAML",
"delete_saml_configuration": "Elimina configurazione SAML",
"delete_saml_configuration_confirmation_message": "Sei sicuro di voler eliminare la configurazione SAML? I membri del tuo team che utilizzano l'accesso SAML non saranno più in grado di accedere a {{appName}}.",
"delete_saml_configuration_confirmation_message": "Sei sicuro di voler eliminare la configurazione SAML? I membri del tuo team che utilizzano l'accesso SAML non saranno più in grado di accedere a Cal.com.",
"confirm_delete_saml_configuration": "Sì, elimina configurazione SAML",
"saml_not_configured_yet": "SAML non ancora configurato",
"saml_configuration_description": "Per aggiornare la configurazione SAML, incolla nella casella sottostante i metadati SAML forniti dal tuo provider di identità.",
@@ -235,7 +235,7 @@
"welcome": "ようこそ",
"welcome_back": "おかえりなさい",
"welcome_to_calcom": "{{appName}} へようこそ",
"welcome_instructions": "{{appName}} でご使用になるお名前とタイムゾーンを教えてください。これらは後から編集できます。",
"welcome_instructions": "Cal.com でご使用になるお名前とタイムゾーンを教えてください。これらは後から編集できます。",
"connect_caldav": "CalDav (ベータ版) に接続",
"credentials_stored_and_encrypted": "認証情報は保存され暗号化されます。",
"connect": "接続",
@@ -685,7 +685,7 @@
"cal_provide_zoom_meeting_url": "{{appName}} は Zoom ミーティングの URLを提供する。",
"cal_provide_tandem_meeting_url": "{{appName}} は Tandem ミーティングの URL を提供する。",
"cal_provide_video_meeting_url": "{{appName}} はビデオミーティングの URL を提供する。",
"cal_provide_jitsi_meeting_url": "{{appName}} は Jitsi Meet の URL を提供する。",
"cal_provide_jitsi_meeting_url": "Cal は Jitsi Meet の URL を提供する。",
"cal_provide_huddle01_meeting_url": "{{appName}} は Huddle01 web3 ビデオミーティングの URL を提供する。",
"cal_provide_teams_meeting_url": "{{appName}} は、MS Teams のミーティングの URL を提供する。注: 職場や学校のアカウントが必要です。",
"require_payment": "有料",
@@ -1115,7 +1115,6 @@
"error_updating_settings": "設定の更新中にエラーが発生しました",
"personal_cal_url": "私の個人 {{appName}} URL",
"bio_hint": "あなたに関する簡潔な説明。これはあなたの個人 URL ページに表示されます。",
"timeformat_profile_hint": "これは内部設定であり、あなたやあなたを予約した人の公開予約ページの時間表示には影響しません。",
"delete_account_modal_title": "アカウントを削除する",
"confirm_delete_account_modal": "{{appName}} アカウントを削除してもよろしいですか?",
"delete_my_account": "アカウントを削除する",
@@ -1324,7 +1323,7 @@
"saml_config_updated_successfully": "SAML の構成が正常に更新されました",
"saml_configuration": "SAML の構成",
"delete_saml_configuration": "SAML の構成を削除する",
"delete_saml_configuration_confirmation_message": "SAML の構成を本当に削除してよろしいですか?SAML を使用してログインしているチームメンバーは、{{appName}} にアクセスすることができなくなります。",
"delete_saml_configuration_confirmation_message": "SAML の構成を本当に削除してよろしいですか?SAML を使用してログインしているチームメンバーは、Cal.com にアクセスすることができなくなります。",
"confirm_delete_saml_configuration": "はい。SAML の構成を削除します",
"saml_not_configured_yet": "SAML がまだ構成されていません",
"saml_configuration_description": "SAML の構成を更新するには、以下のテキストボックスに ID プロバイダーから提供されている SAML のメタデータを貼り付けてください。",
@@ -535,7 +535,7 @@
"open_options": "옵션 열기",
"copy_link": "이벤트 링크 복사",
"share": "공유",
"share_event": "내 {{appName}}을 예약하거나 링크를 보내 주시겠습니까?",
"share_event": "내 Cal을 예약하거나 링크를 보내 주시겠습니까?",
"copy_link_team": "팀에 링크 복사",
"leave_team": "팀 떠나기",
"confirm_leave_team": "예, 팀을 떠납니다",
@@ -1115,7 +1115,6 @@
"error_updating_settings": "설정 업데이트 중 오류 발생",
"personal_cal_url": "내 개인 {{appName}} URL",
"bio_hint": "자신에 대해 몇 마디를 써보세요. 개인 URL 페이지에 보이는 내용입니다.",
"timeformat_profile_hint": "이것은 내부 설정이며 귀하 또는 귀하를 예약하는 모든 사람의 공개 예약 페이지에 시간이 표시되는 방식에는 영향을 미치지 않습니다.",
"delete_account_modal_title": "계정 삭제",
"confirm_delete_account_modal": "{{appName}} 계정을 삭제하시겠습니까?",
"delete_my_account": "내 계정 삭제",
@@ -1188,7 +1187,6 @@
"additional_notes_info": "예약 추가 메모",
"attendee_name_info": "예약자 이름",
"to": "대상",
"attendee_required_enter_number": "예약 시 참석자가 전화번호를 입력해야 합니다",
"workflow_turned_on_successfully": "{{workflowName}} 워크플로 {{offOn}} 설정 완료",
"download_responses": "다운로드 응답",
"create_your_first_form": "첫 양식 작성",
@@ -1198,7 +1196,7 @@
"for_a_maximum_of": "최대",
"event_one": "이벤트",
"event_other": "이벤트",
"profile_team_description": "사용자 팀 프로필 설정 관리",
"profile_team_description": "팀 프로필 설정 관리",
"members_team_description": "그룹 내 사용자",
"team_url": "팀 URL",
"team_members": "팀원",
@@ -535,7 +535,7 @@
"open_options": "Opties openen",
"copy_link": "Link naar afspraak kopiëren",
"share": "Delen",
"share_event": "Zou u mijn {{appName}} willen boeken of mij uw link willen sturen?",
"share_event": "Zou u mijn Cal willen boeken of mij uw link willen sturen?",
"copy_link_team": "Link naar team kopiëren",
"leave_team": "Team verlaten",
"confirm_leave_team": "Ja, team verlaten",
@@ -1115,7 +1115,6 @@
"error_updating_settings": "Fout bij het bijwerken van de instellingen",
"personal_cal_url": "Mijn persoonlijke {{appName}}-URL",
"bio_hint": "Een korte introductie over uzelf. Dit wordt weergegeven op uw persoonlijke pagina.",
"timeformat_profile_hint": "Dit is een interne instelling en heeft geen invloed op hoe de tijden worden weergegeven op de openbare boekingspagina's voor u of voor iemand die u boekt.",
"delete_account_modal_title": "Account verwijderen",
"confirm_delete_account_modal": "Weet u zeker dat u uw {{appName}}-account wilt verwijderen?",
"delete_my_account": "Verwijder mijn account",
@@ -1258,6 +1257,7 @@
"back_to_signin": "Terug naar aanmelden",
"reset_link_sent": "Link om opnieuw in te stellen is verzonden",
"password_reset_email": "Een e-mail is onderweg naar {{email}} met instructies om uw wachtwoord opnieuw in te stellen.",
"password_reset_leading": "Als u niet spoedig een e-mail ontvangt, controleer dan of het ingevoerde e-mailadres correct is, controleer uw spamfolder of neem contact op met de ondersteuning als het probleem zich blijft voordoen.",
"password_updated": "Wachtwoord bijgewerkt!",
"pending_payment": "In afwachting van betaling",
"confirmation_page_rainbow": "Scherm uw gebeurtenis af met tokens of NFT's op Ethereum, Polygon en meer.",
@@ -1323,7 +1323,7 @@
"saml_config_updated_successfully": "SAML-configuratie bijgewerkt",
"saml_configuration": "SAML-configuratie",
"delete_saml_configuration": "SAML-configuratie verwijderen",
"delete_saml_configuration_confirmation_message": "Weet u zeker dat u de SAML-configuratie wilt verwijderen? Uw teamleden die SAML-aanmelding gebruiken hebben niet langer toegang tot {{appName}}.",
"delete_saml_configuration_confirmation_message": "Weet u zeker dat u de SAML-configuratie wilt verwijderen? Uw teamleden die SAML-aanmelding gebruiken hebben niet langer toegang tot Cal.com.",
"confirm_delete_saml_configuration": "Ja, SAML-configuratie verwijderen",
"saml_not_configured_yet": "SAML nog niet geconfigureerd",
"saml_configuration_description": "Plak de SAML-metagegevens van uw identiteitsprovider in het tekstvak hieronder om uw SAML-configuratie bij te werken.",
@@ -1127,7 +1127,6 @@
"error_updating_settings": "Feil under oppdatering av innstillinger",
"personal_cal_url": "Min personlige Cal URL",
"bio_hint": "Noen setninger om deg selv. Dette vil vises på din personlige url-side.",
"timeformat_profile_hint": "Dette er en intern innstilling og vil ikke påvirke hvordan tidspunkt vises på offentlige bookingsider for deg eller noen som booker deg.",
"delete_account_modal_title": "Slett Konto",
"confirm_delete_account_modal": "Er du sikker på at du vil slette Cal.com-kontoen din?",
"delete_my_account": "Slett kontoen min",
@@ -1115,7 +1115,6 @@
"error_updating_settings": "Błąd podczas aktualizacji ustawień",
"personal_cal_url": "Mój osobisty adres URL aplikacji {{appName}}",
"bio_hint": "Kilka zdań o Tobie. Pojawią się one na Twojej osobistej stronie URL.",
"timeformat_profile_hint": "To jest ustawienie wewnętrzne, które nie będzie miało wpływu na to, jak godziny będą wyświetlane Tobie lub komuś, kto dokonuje u Ciebie rezerwacji, na publicznych stronach rezerwacji.",
"delete_account_modal_title": "Usuń konto",
"confirm_delete_account_modal": "Czy na pewno chcesz usunąć swoje konto {{appName}}?",
"delete_my_account": "Usuń konto",
@@ -536,7 +536,7 @@
"open_options": "Abrir opções",
"copy_link": "Copiar link do evento",
"share": "Compartilhar",
"share_event": "Você se importa de fazer uma reserva para mim no {{appName}} ou enviar seu link?",
"share_event": "Você se importa de fazer uma reserva para mim no Cal ou enviar seu link?",
"copy_link_team": "Copiar link do time",
"leave_team": "Sair do time",
"confirm_leave_team": "Sim, sair do time",
@@ -686,7 +686,7 @@
"cal_provide_zoom_meeting_url": "{{appName}} fornecerá uma URL de reunião do Zoom.",
"cal_provide_tandem_meeting_url": "{{appName}} fornecerá um URL de reunião do Tandem.",
"cal_provide_video_meeting_url": "O {{appName}} irá fornecer um URL de reunião do Daily video.",
"cal_provide_jitsi_meeting_url": "O {{appName}} fornecerá um URL de reunião do Jitsi Meet.",
"cal_provide_jitsi_meeting_url": "O Cal fornecerá um URL de reunião do Jitsi Meet.",
"cal_provide_huddle01_meeting_url": "O {{appName}} fornecerá um URL de reunião do Huddle01 Web3 video.",
"cal_provide_teams_meeting_url": "O {{appName}} enviará um URL da reunião para o MS Teams. OBSERVAÇÃO: É PRECISO TER UMA CONTA ESCOLAR OU CORPORATIVA",
"require_payment": "Requerer Pagamento",
@@ -1116,7 +1116,6 @@
"error_updating_settings": "Erro ao atualizar configurações",
"personal_cal_url": "Meu URL pessoal da {{appName}}",
"bio_hint": "Escreva algumas frases sobre você. Isso aparecerá no URL da sua página pessoal.",
"timeformat_profile_hint": "Esta é uma configuração interna e não afetará a quantidade de exibições em páginas públicas de reservas para você ou outra pessoa que estiver fazendo reserva.",
"delete_account_modal_title": "Excluir conta",
"confirm_delete_account_modal": "Tem certeza de que deseja excluir sua conta {{appName}}?",
"delete_my_account": "Excluir minha conta",
@@ -1325,7 +1324,7 @@
"saml_config_updated_successfully": "Configuração do SAML atualizada com sucesso",
"saml_configuration": "Configuração do SAML",
"delete_saml_configuration": "Remover configuração do SAML",
"delete_saml_configuration_confirmation_message": "Você tem certeza que deseja remover esta configuração SAML? Os membros do seu time que utilizam SAML para fazer login não conseguirão acessar o {{appName}}.",
"delete_saml_configuration_confirmation_message": "Você tem certeza que deseja remover esta configuração SAML? Os membros do seu time que utilizam SAML para fazer login não conseguirão acessar o Cal.com.",
"confirm_delete_saml_configuration": "Sim, remover configuração do SAML",
"saml_not_configured_yet": "O SAML ainda não está configurado",
"saml_configuration_description": "Por favor cole o SAML metadata obtido de seu Provedor de Identidade na caixa de texto a seguir para atualizar sua configuração do SAML.",
@@ -101,7 +101,7 @@
"team_info": "Informações da equipa",
"request_another_invitation_email": "Se não quiser utilizar {{toEmail}} como email do {{appName}}, ou já tem uma conta em {{appName}}, por favor peça outro convite para esse email.",
"you_have_been_invited": "Recebeu um convite para juntar-se à equipa {{teamName}}",
"user_invited_you": "{{user}} enviou um convite para juntar-se à equipa {{teamName}}",
"user_invited_you": "{{user}} enviou um convite para juntar-se à equipa {{team}}",
"hidden_team_member_title": "Está oculto nesta equipa",
"hidden_team_member_message": "O seu lugar não está pago, faça upgrade para PRO ou informe ao proprietário da equipa que pode pagar pelo seu lugar.",
"hidden_team_owner_message": "Precisa de uma conta Pro para usar equipas, ficará oculto até que actualize.",
@@ -1115,7 +1115,6 @@
"error_updating_settings": "Erro ao atualizar as definições",
"personal_cal_url": "O meu endereço pessoal do {{appName}}",
"bio_hint": "Algumas frases sobre si. Isto irá ser mostrado no endereço da sua página pessoal.",
"timeformat_profile_hint": "Esta é uma configuração interna e que não irá afetar como os horários serão apresentados nas páginas públicas de agendamento, seja para si ou para qualquer pessoa que esteja a fazer uma reserva.",
"delete_account_modal_title": "Eliminar conta",
"confirm_delete_account_modal": "Tem a certeza que pretende eliminar a sua conta {{appName}}?",
"delete_my_account": "Eliminar a minha conta",
@@ -1258,6 +1257,7 @@
"back_to_signin": "Voltar para o início de sessão",
"reset_link_sent": "Ligação de reposição enviada",
"password_reset_email": "Um e-mail está a caminho de {{email}} com instruções para redefinir a sua palavra-passe.",
"password_reset_leading": "Se não receber um e-mail em breve, confirme se o endereço de e-mail especificado está correto, verifique a sua pasta de SPAM ou contacte o apoio se o problema persistir.",
"password_updated": "Palavra-passe atualizada!",
"pending_payment": "Pagamento pendente",
"confirmation_page_rainbow": "Controle o acesso ao seu evento com tokens ou NFT na Ethereum, Polygon e muito mais.",
@@ -101,7 +101,7 @@
"team_info": "Informații echipă",
"request_another_invitation_email": "Dacă preferați să nu utilizați {{toEmail}} ca e-mail {{appName}} sau aveți deja un cont {{appName}}, vă rugăm să solicitați o altă invitație la acel e-mail.",
"you_have_been_invited": "Ați fost invitat să vă alăturați echipei {{teamName}}",
"user_invited_you": "{{user}} v-a invitat să vă alăturați echipei {{teamName}}",
"user_invited_you": "{{user}} v-a invitat să vă alăturați echipei {{team}}",
"hidden_team_member_title": "Sunteți ascuns în această echipă",
"hidden_team_member_message": "Licența dvs. nu este plătită. Fie faceți upgrade la Pro, fie anunțați proprietarul echipei că vă poate plăti licența.",
"hidden_team_owner_message": "Aveți nevoie de un cont Pro pentru a utiliza echipe. Sunteți ascuns până când faceți upgrade.",
@@ -536,7 +536,7 @@
"open_options": "Opțiuni deschise",
"copy_link": "Copiază link-ul la eveniment",
"share": "Distribuire",
"share_event": "Puteți să îmi faceți o rezervare în {{appName}} sau să îmi trimiteți linkul dvs.?",
"share_event": "Puteți să îmi faceți o rezervare în Cal sau să îmi trimiteți linkul dvs.?",
"copy_link_team": "Copiați linkul în echipă",
"leave_team": "Părăsiți echipa",
"confirm_leave_team": "Da, părăsiți echipa",
@@ -686,7 +686,7 @@
"cal_provide_zoom_meeting_url": "{{appName}} va oferi un URL pentru ședința de Zoom.",
"cal_provide_tandem_meeting_url": "{{appName}} va oferi un URL pentru ședința de Tandem.",
"cal_provide_video_meeting_url": "{{appName}} va oferi un URL pentru ședința de Daily.",
"cal_provide_jitsi_meeting_url": "{{appName}} va oferi un URL pentru ședința de Jitsi Meet.",
"cal_provide_jitsi_meeting_url": "Cal va oferi un URL pentru ședința de Jitsi Meet.",
"cal_provide_huddle01_meeting_url": "{{appName}} va oferi un URL pentru ședința de Huddle01 Web3.",
"cal_provide_teams_meeting_url": "{{appName}} va pune la dispoziție un URL pentru ședința prin MS Teams. OBSERVAȚIE: TREBUIE SĂ AVEȚI UN CONT PROFESIONAL SAU ȘCOLAR",
"require_payment": "Solicită plata",
@@ -840,6 +840,7 @@
"redirect_success_booking": "Redirecționare la rezervare ",
"you_are_being_redirected": "Sunteți redirecționat către {{ url }} în $t(second, {\"count\": {{seconds}} }).",
"external_redirect_url": "https://example.com/redirect-to-my-success-page",
"redirect_url_description": "Redirecționați către un URL personalizat după o rezervare reușită",
"duplicate": "Duplicare",
"offer_seats": "Oferiți locuri",
"offer_seats_description": "Opțiunea oferă locuri pentru rezervare și dezactivează automat rezervările pentru oaspeți și cele la alegere.",
@@ -1115,7 +1116,6 @@
"error_updating_settings": "Eroare la actualizarea setărilor",
"personal_cal_url": "URL-ul meu personal {{appName}}",
"bio_hint": "Câteva propoziții despre dvs. Acestea vor apărea pe pagina URL-ului dvs. personal.",
"timeformat_profile_hint": "Aceasta este o setare internă și nu va afecta modul în care sunt afișate orele pe paginile publice de rezervare pentru dvs. sau pentru oricine vă face rezervarea.",
"delete_account_modal_title": "Ștergeți contul",
"confirm_delete_account_modal": "Sigur doriți să vă ștergeți contul {{appName}}?",
"delete_my_account": "Ștergeți contul meu",
@@ -1324,7 +1324,7 @@
"saml_config_updated_successfully": "Configurația SAML a fost actualizată cu succes",
"saml_configuration": "Configurație SAML",
"delete_saml_configuration": "Ștergeți configurația SAML",
"delete_saml_configuration_confirmation_message": "Sigur doriți să ștergeți configurația SAML? Membrii echipei dvs. care utilizează conectarea prin SAML nu vor mai putea accesa {{appName}}.",
"delete_saml_configuration_confirmation_message": "Sigur doriți să ștergeți configurația SAML? Membrii echipei dvs. care utilizează conectarea prin SAML nu vor mai putea accesa Cal.com.",
"confirm_delete_saml_configuration": "Da, ștergeți configurația SAML",
"saml_not_configured_yet": "SAML nu este configurat încă",
"saml_configuration_description": "Pentru a actualiza configurația SAML, inserați metadatele SAML generate de furnizorul dvs. de identitate în caseta de text de mai jos.",
@@ -101,7 +101,7 @@
"team_info": "Информация о команде",
"request_another_invitation_email": "Если вы не хотите использовать {{toEmail}} как ваш {{appName}} адрес электронной почты или уже есть аккаунт {{appName}}, пожалуйста, запросите другое приглашение на это письмо.",
"you_have_been_invited": "Вас пригласили присоединиться к команде {{teamName}}",
"user_invited_you": "{{user}} приглашает вас присоединиться к команде {{teamName}}",
"user_invited_you": "{{user}} приглашает вас присоединиться к команде {{team}}",
"hidden_team_member_title": "В этой команде вы скрытый пользователь",
"hidden_team_member_message": "Ваше место не оплачено. Перейдите на аккаунт PRO или свяжитесь с руководителем команды, чтобы сообщить ему, что он может оплатить ваше место.",
"hidden_team_owner_message": "Чтобы работать с командами, необходим аккаунт Pro. До перехода на этот тариф Вы остаетесь скрытым пользователем.",
@@ -536,7 +536,7 @@
"open_options": "Открыть настройки",
"copy_link": "Скопировать ссылку на событие",
"share": "Поделиться",
"share_event": "Не могли бы вы забронировать нам встречу в {{appName}} или отправить мне свою ссылку?",
"share_event": "Не могли бы вы забронировать нам встречу в Cal или отправить мне свою ссылку?",
"copy_link_team": "Скопировать ссылку на команду",
"leave_team": "Выйти из команды",
"confirm_leave_team": "Да, выйти из команды",
@@ -1116,7 +1116,6 @@
"error_updating_settings": "Ошибка при обновлении настроек",
"personal_cal_url": "URL-адрес моего персонального {{appName}}",
"bio_hint": "Несколько предложений о себе. Они будут отображаться на вашей персональной странице.",
"timeformat_profile_hint": "Это внутренняя настройка. Для вас и других пользователей, оформляющих бронирование, отображение времени на публичных страницах бронирования не изменится.",
"delete_account_modal_title": "Удалить аккаунт",
"confirm_delete_account_modal": "Удалить аккаунт {{appName}}?",
"delete_my_account": "Удалить мой аккаунт",
@@ -535,7 +535,7 @@
"open_options": "Otvori opcije",
"copy_link": "Kopiraj link do događaja",
"share": "Podeli",
"share_event": "Da li biste želeli da rezervišete moj {{appName}} ili da mi pošaljete vaš link?",
"share_event": "Da li biste želeli da rezervišete moj Cal ili da mi pošaljete vaš link?",
"copy_link_team": "Kopiraj link do tima",
"leave_team": "Napusti tim",
"confirm_leave_team": "Da, napusti tim",
@@ -1115,7 +1115,6 @@
"error_updating_settings": "Greška pri ažuriranju podešavanja",
"personal_cal_url": "Moj lični {{appName}} URL",
"bio_hint": "Par rečenica o vama. Ovo će se pojaviti na vašoj ličnoj url stranici.",
"timeformat_profile_hint": "Ovo je interno podešavanje i neće uticati koliko puta će biti prikazano na javnim stranicama za rezervisanje, za vas ili bilo koga ko vas rezerviše.",
"delete_account_modal_title": "Izbriši nalog",
"confirm_delete_account_modal": "Jeste li sigurni da želite da izbrišete vaš {{appName}} nalog?",
"delete_my_account": "Izbriši moj nalog",
@@ -1258,6 +1257,7 @@
"back_to_signin": "Nazad na prijavljivanje",
"reset_link_sent": "Veza za resetovanje je poslata",
"password_reset_email": "Imejl je na putu do {{email}} sa instrukcijama za resetovanje vaše lozinke.",
"password_reset_leading": "Ako uskoro ne primite imejl, proverite da li ste uneli ispravnu imejl adresu, proverite vašu fasciklu za neželjenu poštu ili se obratite podršci ako se problem nastavi.",
"password_updated": "Lozinka je ažurirana!",
"pending_payment": "Čeka se na plaćanje",
"confirmation_page_rainbow": "Zaštitite vaš događaj tokenom ili NFT-om na Ethereum-u, Polygon-u, itd.",
@@ -1115,7 +1115,6 @@
"error_updating_settings": "Fel vid uppdatering av inställningar",
"personal_cal_url": "Min personliga {{appName}}-URL",
"bio_hint": "Några meningar om dig själv. Detta kommer att visas på din personliga webbsida.",
"timeformat_profile_hint": "Detta är en intern inställning och kommer inte att påverka hur tider visas på offentliga bokningssidor för dig eller någon som bokar dig.",
"delete_account_modal_title": "Radera konto",
"confirm_delete_account_modal": "Är du säker på att du vill radera ditt {{appName}}-konto?",
"delete_my_account": "Radera mitt konto",
@@ -1171,6 +1170,8 @@
"connect_conference_apps": "Anslut konferensappar",
"connect_calendar_apps": "Anslut kalenderappar",
"connect_payment_apps": "Anslut betalningsappar",
"connect_automation_apps": "Anslut automationsappar",
"connect_analytics_apps": "Anslut analysappar",
"connect_other_apps": "Anslut andra appar",
"current_step_of_total": "Steg {{currentStep}} av {{maxSteps}}",
"add_variable": "Lägg till variabel",
@@ -1256,6 +1257,7 @@
"back_to_signin": "Tillbaka för att logga in",
"reset_link_sent": "Återställningslänk skickad",
"password_reset_email": "Ett e-postmeddelande är på väg till {{email}} med anvisningar för att återställa ditt lösenord.",
"password_reset_leading": "Om du inte fick e-postmeddelandet snart, kontrollera att e-postadressen du angav är korrekt, kontrollera din skräppostmapp eller kontakta kundsupport om problemet kvarstår.",
"password_updated": "Lösenord uppdaterat!",
"pending_payment": "Väntande betalning",
"confirmation_page_rainbow": "Tokenskydda din händelse med tokens eller NFT:er på Ethereum, Polygon med mera.",
@@ -535,7 +535,7 @@
"open_options": "Seçenekleri aç",
"copy_link": "Etkinlik bağlantısını kopyala",
"share": "Paylaş",
"share_event": "{{appName}} hesabım üzerinden rezervasyon yapmak veya bana bağlantınızı göndermek ister misiniz?",
"share_event": "Cal hesabım üzerinden rezervasyon yapmak veya bana bağlantınızı göndermek ister misiniz?",
"copy_link_team": "Ekip bağlantısını kopyala",
"leave_team": "Ekipten ayrıl",
"confirm_leave_team": "Evet, ekipten ayrıl",
@@ -839,6 +839,7 @@
"redirect_success_booking": "Rezervasyonu yönlendir ",
"you_are_being_redirected": "$t(second, {\"count\": {{seconds}} }) içinde {{ url }} adresine yönlendirileceksiniz.",
"external_redirect_url": "https://example.com/redirect-to-my-success-page",
"redirect_url_description": "Başarılı bir rezervasyondan sonra özel bir URL'ye yönlendirin",
"duplicate": "Çoğalt",
"offer_seats": "Yer teklif et",
"offer_seats_description": "Rezervasyonlar için yer teklif edin. Bu işlem, misafirleri ve rezervasyon onaylarını devre dışı bırakır.",
@@ -1114,7 +1115,6 @@
"error_updating_settings": "Ayarlar güncellenirken bir hata oluştu",
"personal_cal_url": "Kişisel {{appName}} URL'm",
"bio_hint": "Birkaç cümleyle kendinizden bahsedin. Bu, kişisel URL sayfanızda görünecektir.",
"timeformat_profile_hint": "Bu dahili bir ayardır ve sizin yaptığınız veya sizin için rezervasyon yapan herhangi birinin genel rezervasyon sayfalarında saatlerin nasıl görüntülendiğini etkilemez.",
"delete_account_modal_title": "Hesabı Sil",
"confirm_delete_account_modal": "{{appName}} hesabınızı silmek istediğinizden emin misiniz?",
"delete_my_account": "Hesabımı sil",
@@ -1187,7 +1187,6 @@
"additional_notes_info": "Ek rezervasyon notları",
"attendee_name_info": "Rezervasyon yaptıran kişinin adı",
"to": "Kime",
"attendee_required_enter_number": "Bu, katılımcının rezervasyon yapmak için bir telefon numarası girmesini gerektirecektir",
"workflow_turned_on_successfully": "{{workflowName}} iş akışı başarıyla {{offOn}} hale getirildi",
"download_responses": "Yanıtları İndir",
"create_your_first_form": "İlk formunuzu oluşturun",
@@ -1258,6 +1257,7 @@
"back_to_signin": "Oturum açmak için geri dön",
"reset_link_sent": "Sıfırlama bağlantısı gönderildi",
"password_reset_email": "{{email}} adresine şifrenizi sıfırlama talimatlarını içeren bir e-posta gönderildi.",
"password_reset_leading": "Kısa süre içinde bir e-posta almazsanız, lütfen girdiğiniz e-posta adresinin doğru olup olmadığını ve spam klasörünüzü kontrol edin veya sorunun devam etmesi halinde destek ekibi ile iletişime geçin.",
"password_updated": "Şifre güncellendi!",
"pending_payment": "Bekleyen ödeme",
"confirmation_page_rainbow": "Ethereum, Polygon ve diğer token'lar veya NFT ile etkinliğinize token kapısı ekleyin.",
@@ -1323,7 +1323,7 @@
"saml_config_updated_successfully": "SAML yapılandırması başarıyla güncellendi",
"saml_configuration": "SAML yapılandırması",
"delete_saml_configuration": "SAML yapılandırmasını sil",
"delete_saml_configuration_confirmation_message": "SAML yapılandırmasını silmek istediğinizden emin misiniz? SAML girişini kullanan ekip üyeleriniz artık {{appName}}'a erişemeyecek.",
"delete_saml_configuration_confirmation_message": "SAML yapılandırmasını silmek istediğinizden emin misiniz? SAML girişini kullanan ekip üyeleriniz artık Cal.com'a erişemeyecek.",
"confirm_delete_saml_configuration": "Evet, SAML yapılandırmasını sil",
"saml_not_configured_yet": "SAML henüz yapılandırılmadı",
"saml_configuration_description": "SAML yapılandırmanızı güncellemek için lütfen Kimlik Sağlayıcınızın gönderdiği SAML meta verilerini aşağıdaki metin kutusuna yapıştırın.",
@@ -1115,7 +1115,6 @@
"error_updating_settings": "Помилка оновлення параметрів",
"personal_cal_url": "Моя персональна URL-адреса {{appName}}",
"bio_hint": "Кілька речень про вас. Ця інформація з’явиться на сторінці за вашою URL-адресою.",
"timeformat_profile_hint": "Це внутрішній параметр, який не вплине на відображення часу на загальнодоступних сторінках бронювання.",
"delete_account_modal_title": "Видалення облікового запису",
"confirm_delete_account_modal": "Справді видалити свій обліковий запис {{appName}}?",
"delete_my_account": "Видалити мій обліковий запис",
@@ -1258,6 +1257,7 @@
"back_to_signin": "Повернутися до входу",
"reset_link_sent": "Посилання для скидання надіслано",
"password_reset_email": "Електронний лист з інструкціями зі скидання пароля надіслано на {{email}}.",
"password_reset_leading": "Якщо ви не отримаєте електронний лист найближчим часом, перевірте, чи правильну адресу електронної пошти введено та перевірте папку спаму. Якщо проблема не зникне, зверніться в нашу службу підтримки.",
"password_updated": "Пароль оновлено!",
"pending_payment": "Очікується оплата",
"confirmation_page_rainbow": "Увімкніть доступ до свого заходу тільки для власників звичайних або невзаємозамінних токенів Ethereum, Polygon тощо.",
@@ -1323,7 +1323,7 @@
"saml_config_updated_successfully": "Конфігурацію SAML оновлено",
"saml_configuration": "Конфігурація SAML",
"delete_saml_configuration": "Видаліть конфігурацію SAML",
"delete_saml_configuration_confirmation_message": "Справді конфігурацію SAML? Учасники вашої команди, які використовують вхід через SAML, утратять доступ до {{appName}}.",
"delete_saml_configuration_confirmation_message": "Справді конфігурацію SAML? Учасники вашої команди, які використовують вхід через SAML, утратять доступ до Cal.com.",
"confirm_delete_saml_configuration": "Так, видалити конфігурацію SAML",
"saml_not_configured_yet": "SAML ще не налаштовано",
"saml_configuration_description": "Вставте метадані SAML від свого постачальника посвідчень у текстове поле нижче, щоб оновити конфігурацію SAML.",
@@ -1115,7 +1115,6 @@
"error_updating_settings": "Lỗi khi cập nhật cài đặt",
"personal_cal_url": "URL {{appName}} cá nhân của tôi",
"bio_hint": "Nói mấy lời về bản thân. Thông tin này sẽ xuất hiện trên trang url cá nhân của bạn.",
"timeformat_profile_hint": "Đây là cài đặt nội bộ và sẽ không ảnh hưởng đến cách hiển thị thời gian trên trang lịch hẹn công cộng cho bạn hoặc cho bất kỳ ai đặt lịch hẹn với bạn.",
"delete_account_modal_title": "Xoá tài khoản",
"confirm_delete_account_modal": "Bạn có chắc chắn muốn xoá tài khoản {{appName}}?",
"delete_my_account": "Xoá tài khoản của tôi",
@@ -1324,7 +1323,7 @@
"saml_config_updated_successfully": "Đã cập nhật cấu hình SAML thành công",
"saml_configuration": "Cấu hình SAML",
"delete_saml_configuration": "Xóa cấu hình SAML",
"delete_saml_configuration_confirmation_message": "Bạn có chắc chắn muốn xóa cấu hình SAML không? Các thành viên trong nhóm của bạn sử dụng thông tin đăng nhập SAML sẽ không thể truy cập vào {{appName}} được nữa.",
"delete_saml_configuration_confirmation_message": "Bạn có chắc chắn muốn xóa cấu hình SAML không? Các thành viên trong nhóm của bạn sử dụng thông tin đăng nhập SAML sẽ không thể truy cập vào Cal.com được nữa.",
"confirm_delete_saml_configuration": "Có, xóa cấu hình SAML",
"saml_not_configured_yet": "SAML chưa được định cấu hình",
"saml_configuration_description": "Vui lòng dán metadata SAML từ Nhà cung cấp danh tính của bạn vào hộp văn bản bên dưới để cập nhật cấu hình SAML của bạn.",
@@ -772,11 +772,16 @@
"no_category_apps_description_calendar": "添加日历应用以检查冲突,防止重复预约",
"no_category_apps_description_conferencing": "尝试添加会议应用以整合与客户的视频通话",
"no_category_apps_description_payment": "添加支付应用以简化您和客户之间的交易",
"no_category_apps_description_analytics": "为您的预约页面添加分析应用",
"no_category_apps_description_automation": "添加要使用的自动化应用",
"no_category_apps_description_other": "添加任何其他类型的应用以执行各种操作",
"installed_app_calendar_description": "设置日历以检查冲突,防止重复预约。",
"installed_app_conferencing_description": "添加您喜欢的视频会议应用以用于开会",
"installed_app_payment_description": "配置向客户收费时要使用的支付处理服务。",
"installed_app_analytics_description": "配置将用于您的预约页面的分析应用",
"installed_app_other_description": "所有属于其他类别的已安装应用。",
"installed_app_automation_description": "配置要使用的自动化应用",
"analytics": "分析",
"empty_installed_apps_headline": "未安装任何应用",
"empty_installed_apps_description": "通过应用可以显著增强工作流程和改善日程安排生活。",
"empty_installed_apps_button": "浏览 App Store 或从以下应用中选择安装",
@@ -1110,7 +1115,6 @@
"error_updating_settings": "更新设置时出错",
"personal_cal_url": "我的个人 {{appName}} URL",
"bio_hint": "请写一段简单的自我介绍,该介绍将会展示在您的个人 url 页面上。",
"timeformat_profile_hint": "这是一项内部设置,不会影响公开预约页面上向您或向预约您的任何人显示时间的方式。",
"delete_account_modal_title": "删除账户",
"confirm_delete_account_modal": "您确定要删除您的 {{appName}} 账户吗?",
"delete_my_account": "删除我的账户",
@@ -1253,6 +1257,7 @@
"back_to_signin": "返回以登录",
"reset_link_sent": "重置链接已发送",
"password_reset_email": "正在将包含重设密码说明的电子邮件发送至 {{email}}。",
"password_reset_leading": "如果您没有很快收到该电子邮件,请检查您输入的电子邮件地址是否正确,如果问题仍然存在,请检查垃圾邮件文件夹或联系支持。",
"password_updated": "密码已更新!",
"pending_payment": "待付款",
"confirmation_page_rainbow": "使用 Ethereum、Polygon 等平台的代币或 NFT 对您的活动进行代币门控。",
@@ -1115,7 +1115,6 @@
"error_updating_settings": "更新設定時發生錯誤",
"personal_cal_url": "我的個人 {{appName}} 網址",
"bio_hint": "一些關於自己的句子,會顯示在個人網址頁面。",
"timeformat_profile_hint": "此為內部設定,您或要預約您時間的使用者在公開預約頁面上看見的時間並不會受到影響。",
"delete_account_modal_title": "刪除帳號",
"confirm_delete_account_modal": "確定要刪除您的 {{appName}} 的帳號嗎?",
"delete_my_account": "刪除我的帳號",
@@ -1258,6 +1257,7 @@
"back_to_signin": "返回登入",
"reset_link_sent": "重設連結已傳送",
"password_reset_email": "含有密碼重設說明的電子郵件已在前往 {{email}} 的路上。",
"password_reset_leading": "如果您稍後還是沒有收到電子郵件,請檢查您輸入的電子郵件地址是否正確,並查看您的垃圾郵件資料夾;若問題持續發生,請聯絡支援團隊。",
"password_updated": "密碼已更新!",
"pending_payment": "待付款",
"confirmation_page_rainbow": "使用 Ethereum、Polygon 等的 Token 或 NFT 來以代幣管制您的活動。",
+3 -1
View File
@@ -100,6 +100,8 @@ export default class EventManager {
*/
public async create(event: CalendarEvent): Promise<CreateUpdateResult> {
const evt = processLocation(event);
// Fallback to cal video if no location is set
if (!evt.location) evt["location"] = "integrations:daily";
const isDedicated = evt.location ? isDedicatedIntegration(evt.location) : null;
const results: Array<EventResult<Exclude<Event, AdditionalInformation>>> = [];
@@ -107,7 +109,7 @@ export default class EventManager {
if (isDedicated) {
const result = await this.createVideoEvent(evt);
if (result.createdEvent) {
if (result?.createdEvent) {
evt.videoCallData = result.createdEvent;
evt.location = result.originalEvent.location;
result.type = result.createdEvent.type;
+8
View File
@@ -47,6 +47,14 @@ const createMeeting = async (credential: CredentialWithAppName, calEvent: Calend
const [firstVideoAdapter] = videoAdapters;
let createdMeeting;
try {
if (!calEvent.location) {
const defaultMeeting = await createMeetingWithCalVideo(calEvent);
if (defaultMeeting) {
createdMeeting = defaultMeeting;
calEvent.location = "integrations:dailyvideo";
}
}
createdMeeting = await firstVideoAdapter?.createMeeting(calEvent);
if (!createdMeeting) {
@@ -178,6 +178,7 @@ async function handler(req: NextApiRequest & { userId?: number }) {
...evt,
...eventTypeInfo,
status: "CANCELLED",
smsReminderNumber: bookingToDelete.smsReminderNumber || undefined,
}).catch((e) => {
console.error(`Error executing webhook for event: ${eventTrigger}, URL: ${webhook.subscriberUrl}`, e);
})
@@ -42,7 +42,11 @@ import { checkBookingLimits, getLuckyUser } from "@calcom/lib/server";
import { getTranslation } from "@calcom/lib/server/i18n";
import { updateWebUser as syncServicesUpdateWebUser } from "@calcom/lib/sync/SyncServiceManager";
import prisma, { userSelect } from "@calcom/prisma";
import { EventTypeMetaDataSchema, extendedBookingCreateBody } from "@calcom/prisma/zod-utils";
import {
customInputSchema,
EventTypeMetaDataSchema,
extendedBookingCreateBody,
} from "@calcom/prisma/zod-utils";
import type { BufferedBusyTime } from "@calcom/types/BufferedBusyTime";
import type { AdditionalInformation, AppsStatus, CalendarEvent } from "@calcom/types/Calendar";
import type { EventResult, PartialReference } from "@calcom/types/EventManager";
@@ -203,6 +207,7 @@ const getEventTypesFromDB = async (eventTypeId: number) => {
...eventType,
metadata: EventTypeMetaDataSchema.parse(eventType.metadata),
recurringEvent: parseRecurringEvent(eventType.recurringEvent),
customInputs: customInputSchema.array().parse(eventType.customInputs),
locations: (eventType.locations ?? []) as LocationObject[],
};
};
@@ -310,7 +315,7 @@ async function handler(req: NextApiRequest & { userId?: number | undefined }) {
const stripeAppData = getStripeAppData(eventType);
// Check if required custom inputs exist
handleCustomInputs(eventType.customInputs, reqBody.customInputs);
handleCustomInputs(eventType.customInputs as EventTypeCustomInput[], reqBody.customInputs);
let timeOutOfBounds = false;
try {
@@ -514,16 +519,9 @@ async function handler(req: NextApiRequest & { userId?: number | undefined }) {
id: true,
attendees: true,
userId: true,
references: {
select: {
type: true,
uid: true,
meetingId: true,
meetingPassword: true,
meetingUrl: true,
externalCalendarId: true,
},
},
references: true,
startTime: true,
user: true,
},
});
if (!booking) {
@@ -574,6 +572,20 @@ async function handler(req: NextApiRequest & { userId?: number | undefined }) {
const eventManager = new EventManager({ ...organizerUser, credentials });
await eventManager.updateCalendarAttendees(evt, booking);
if (!Number.isNaN(stripeAppData.price) && stripeAppData.price > 0 && !!booking) {
const [firstStripeCredential] = organizerUser.credentials.filter(
(cred) => cred.type == "stripe_payment"
);
if (!firstStripeCredential)
throw new HttpError({ statusCode: 400, message: "Missing payment credentials" });
const payment = await handlePayment(evt, eventType, firstStripeCredential, booking);
req.statusCode = 201;
return { ...booking, message: "Payment required", paymentUid: payment.uid };
}
req.statusCode = 201;
return booking;
}
@@ -984,6 +996,7 @@ async function handler(req: NextApiRequest & { userId?: number | undefined }) {
metadata: reqBody.metadata,
eventTypeId,
status: "ACCEPTED",
smsReminderNumber: booking?.smsReminderNumber || undefined,
}).catch((e) => {
console.error(`Error executing webhook for event: ${eventTrigger}, URL: ${sub.subscriberUrl}`, e);
})
@@ -66,7 +66,7 @@ export default function PaymentComponent(props: Props) {
const handleSubmit = async (ev: SyntheticEvent) => {
ev.preventDefault();
if (!stripe || !elements) return;
if (!stripe || !elements || !router.isReady) return;
const card = elements.getElement(CardElement);
if (!card) return;
setState({ status: "processing" });
@@ -81,7 +81,10 @@ export default function PaymentComponent(props: Props) {
error: new Error(`Payment failed: ${payload.error.message}`),
});
} else {
const params: { [k: string]: any } = {};
const params: { [k: string]: any } = {
uid: props.bookingUid,
email: router.query.email,
};
if (props.location) {
if (props.location.includes("integration")) {
@@ -43,6 +43,9 @@ async function handler(req: NextApiRequest, res: NextApiResponse) {
where: {
method: WorkflowMethods.EMAIL,
scheduled: false,
scheduledDate: {
lte: dayjs().add(72, "hour").toISOString(),
},
},
include: {
workflowStep: true,
@@ -61,116 +64,112 @@ async function handler(req: NextApiRequest, res: NextApiResponse) {
return;
}
const dateInSeventyTwoHours = dayjs().add(72, "hour");
for (const reminder of unscheduledReminders) {
if (dayjs(reminder.scheduledDate).isBefore(dateInSeventyTwoHours)) {
try {
let sendTo;
try {
let sendTo;
switch (reminder.workflowStep.action) {
case WorkflowActions.EMAIL_HOST:
sendTo = reminder.booking?.user?.email;
break;
case WorkflowActions.EMAIL_ATTENDEE:
sendTo = reminder.booking?.attendees[0].email;
break;
case WorkflowActions.EMAIL_ADDRESS:
sendTo = reminder.workflowStep.sendTo;
}
const name =
reminder.workflowStep.action === WorkflowActions.EMAIL_ATTENDEE
? reminder.booking?.attendees[0].name
: reminder.booking?.user?.name;
const attendeeName =
reminder.workflowStep.action === WorkflowActions.EMAIL_ATTENDEE
? reminder.booking?.user?.name
: reminder.booking?.attendees[0].name;
const timeZone =
reminder.workflowStep.action === WorkflowActions.EMAIL_ATTENDEE
? reminder.booking?.attendees[0].timeZone
: reminder.booking?.user?.timeZone;
let emailContent = {
emailSubject: reminder.workflowStep.emailSubject || "",
emailBody: {
text: reminder.workflowStep.reminderBody || "",
html: `<body style="white-space: pre-wrap;">${reminder.workflowStep.reminderBody || ""}</body>`,
},
};
switch (reminder.workflowStep.template) {
case WorkflowTemplates.REMINDER:
emailContent = emailReminderTemplate(
reminder.booking?.startTime.toISOString() || "",
reminder.booking?.endTime.toISOString() || "",
reminder.booking?.eventType?.title || "",
timeZone || "",
attendeeName || "",
name || ""
);
break;
case WorkflowTemplates.CUSTOM:
const variables: VariablesType = {
eventName: reminder.booking?.eventType?.title || "",
organizerName: reminder.booking?.user?.name || "",
attendeeName: reminder.booking?.attendees[0].name,
attendeeEmail: reminder.booking?.attendees[0].email,
eventDate: dayjs(reminder.booking?.startTime).tz(timeZone),
eventTime: dayjs(reminder.booking?.startTime).tz(timeZone),
timeZone: timeZone,
location: reminder.booking?.location || "",
additionalNotes: reminder.booking?.description,
customInputs: reminder.booking?.customInputs,
};
const emailSubject = await customTemplate(
reminder.workflowStep.emailSubject || "",
variables,
reminder.booking?.user?.locale || ""
);
emailContent.emailSubject = emailSubject.text;
emailContent.emailBody = await customTemplate(
reminder.workflowStep.reminderBody || "",
variables,
reminder.booking?.user?.locale || ""
);
break;
}
if (emailContent.emailSubject.length > 0 && emailContent.emailBody.text.length > 0 && sendTo) {
const batchIdResponse = await client.request({
url: "/v3/mail/batch",
method: "POST",
});
const batchId = batchIdResponse[1].batch_id;
await sgMail.send({
to: sendTo,
from: senderEmail,
subject: emailContent.emailSubject,
text: emailContent.emailBody.text,
html: emailContent.emailBody.html,
batchId: batchId,
sendAt: dayjs(reminder.scheduledDate).unix(),
replyTo: reminder.booking?.user?.email || senderEmail,
});
await prisma.workflowReminder.update({
where: {
id: reminder.id,
},
data: {
scheduled: true,
referenceId: batchId,
},
});
}
} catch (error) {
console.log(`Error scheduling Email with error ${error}`);
switch (reminder.workflowStep.action) {
case WorkflowActions.EMAIL_HOST:
sendTo = reminder.booking?.user?.email;
break;
case WorkflowActions.EMAIL_ATTENDEE:
sendTo = reminder.booking?.attendees[0].email;
break;
case WorkflowActions.EMAIL_ADDRESS:
sendTo = reminder.workflowStep.sendTo;
}
const name =
reminder.workflowStep.action === WorkflowActions.EMAIL_ATTENDEE
? reminder.booking?.attendees[0].name
: reminder.booking?.user?.name;
const attendeeName =
reminder.workflowStep.action === WorkflowActions.EMAIL_ATTENDEE
? reminder.booking?.user?.name
: reminder.booking?.attendees[0].name;
const timeZone =
reminder.workflowStep.action === WorkflowActions.EMAIL_ATTENDEE
? reminder.booking?.attendees[0].timeZone
: reminder.booking?.user?.timeZone;
let emailContent = {
emailSubject: reminder.workflowStep.emailSubject || "",
emailBody: {
text: reminder.workflowStep.reminderBody || "",
html: `<body style="white-space: pre-wrap;">${reminder.workflowStep.reminderBody || ""}</body>`,
},
};
switch (reminder.workflowStep.template) {
case WorkflowTemplates.REMINDER:
emailContent = emailReminderTemplate(
reminder.booking?.startTime.toISOString() || "",
reminder.booking?.endTime.toISOString() || "",
reminder.booking?.eventType?.title || "",
timeZone || "",
attendeeName || "",
name || ""
);
break;
case WorkflowTemplates.CUSTOM:
const variables: VariablesType = {
eventName: reminder.booking?.eventType?.title || "",
organizerName: reminder.booking?.user?.name || "",
attendeeName: reminder.booking?.attendees[0].name,
attendeeEmail: reminder.booking?.attendees[0].email,
eventDate: dayjs(reminder.booking?.startTime).tz(timeZone),
eventTime: dayjs(reminder.booking?.startTime).tz(timeZone),
timeZone: timeZone,
location: reminder.booking?.location || "",
additionalNotes: reminder.booking?.description,
customInputs: reminder.booking?.customInputs,
};
const emailSubject = await customTemplate(
reminder.workflowStep.emailSubject || "",
variables,
reminder.booking?.user?.locale || ""
);
emailContent.emailSubject = emailSubject.text;
emailContent.emailBody = await customTemplate(
reminder.workflowStep.reminderBody || "",
variables,
reminder.booking?.user?.locale || ""
);
break;
}
if (emailContent.emailSubject.length > 0 && emailContent.emailBody.text.length > 0 && sendTo) {
const batchIdResponse = await client.request({
url: "/v3/mail/batch",
method: "POST",
});
const batchId = batchIdResponse[1].batch_id;
await sgMail.send({
to: sendTo,
from: senderEmail,
subject: emailContent.emailSubject,
text: emailContent.emailBody.text,
html: emailContent.emailBody.html,
batchId: batchId,
sendAt: dayjs(reminder.scheduledDate).unix(),
replyTo: reminder.booking?.user?.email || senderEmail,
});
await prisma.workflowReminder.update({
where: {
id: reminder.id,
},
data: {
scheduled: true,
referenceId: batchId,
},
});
}
} catch (error) {
console.log(`Error scheduling Email with error ${error}`);
}
}
res.status(200).json({ message: "Emails scheduled" });
@@ -32,6 +32,9 @@ async function handler(req: NextApiRequest, res: NextApiResponse) {
where: {
method: WorkflowMethods.SMS,
scheduled: false,
scheduledDate: {
lte: dayjs().add(7, "day").toISOString(),
},
},
include: {
workflowStep: true,
@@ -47,84 +50,80 @@ async function handler(req: NextApiRequest, res: NextApiResponse) {
if (!unscheduledReminders.length) res.json({ ok: true });
const dateInSevenDays = dayjs().add(7, "day");
for (const reminder of unscheduledReminders) {
if (dayjs(reminder.scheduledDate).isBefore(dateInSevenDays)) {
try {
const sendTo =
reminder.workflowStep.action === WorkflowActions.SMS_NUMBER
? reminder.workflowStep.sendTo
: reminder.booking?.smsReminderNumber;
try {
const sendTo =
reminder.workflowStep.action === WorkflowActions.SMS_NUMBER
? reminder.workflowStep.sendTo
: reminder.booking?.smsReminderNumber;
const userName =
reminder.workflowStep.action === WorkflowActions.SMS_ATTENDEE
? reminder.booking?.attendees[0].name
: "";
const userName =
reminder.workflowStep.action === WorkflowActions.SMS_ATTENDEE
? reminder.booking?.attendees[0].name
: "";
const attendeeName =
reminder.workflowStep.action === WorkflowActions.SMS_ATTENDEE
? reminder.booking?.user?.name
: reminder.booking?.attendees[0].name;
const attendeeName =
reminder.workflowStep.action === WorkflowActions.SMS_ATTENDEE
? reminder.booking?.user?.name
: reminder.booking?.attendees[0].name;
const timeZone =
reminder.workflowStep.action === WorkflowActions.SMS_ATTENDEE
? reminder.booking?.attendees[0].timeZone
: reminder.booking?.user?.timeZone;
const timeZone =
reminder.workflowStep.action === WorkflowActions.SMS_ATTENDEE
? reminder.booking?.attendees[0].timeZone
: reminder.booking?.user?.timeZone;
let message: string | null = reminder.workflowStep.reminderBody;
switch (reminder.workflowStep.template) {
case WorkflowTemplates.REMINDER:
message = smsReminderTemplate(
reminder.booking?.startTime.toISOString() || "",
reminder.booking?.eventType?.title || "",
timeZone || "",
attendeeName || "",
userName
);
break;
case WorkflowTemplates.CUSTOM:
const variables: VariablesType = {
eventName: reminder.booking?.eventType?.title,
organizerName: reminder.booking?.user?.name || "",
attendeeName: reminder.booking?.attendees[0].name,
attendeeEmail: reminder.booking?.attendees[0].email,
eventDate: dayjs(reminder.booking?.startTime).tz(timeZone),
eventTime: dayjs(reminder.booking?.startTime).tz(timeZone),
timeZone: timeZone,
location: reminder.booking?.location || "",
additionalNotes: reminder.booking?.description,
customInputs: reminder.booking?.customInputs,
};
const customMessage = await customTemplate(
reminder.workflowStep.reminderBody || "",
variables,
reminder.booking?.user?.locale || ""
);
message = customMessage.text;
break;
}
if (message?.length && message?.length > 0 && sendTo) {
const scheduledSMS = await twilio.scheduleSMS(
sendTo,
message,
reminder.scheduledDate,
reminder.workflowStep.sender || "Cal"
let message: string | null = reminder.workflowStep.reminderBody;
switch (reminder.workflowStep.template) {
case WorkflowTemplates.REMINDER:
message = smsReminderTemplate(
reminder.booking?.startTime.toISOString() || "",
reminder.booking?.eventType?.title || "",
timeZone || "",
attendeeName || "",
userName
);
await prisma.workflowReminder.update({
where: {
id: reminder.id,
},
data: {
scheduled: true,
referenceId: scheduledSMS.sid,
},
});
}
} catch (error) {
console.log(`Error scheduling SMS with error ${error}`);
break;
case WorkflowTemplates.CUSTOM:
const variables: VariablesType = {
eventName: reminder.booking?.eventType?.title,
organizerName: reminder.booking?.user?.name || "",
attendeeName: reminder.booking?.attendees[0].name,
attendeeEmail: reminder.booking?.attendees[0].email,
eventDate: dayjs(reminder.booking?.startTime).tz(timeZone),
eventTime: dayjs(reminder.booking?.startTime).tz(timeZone),
timeZone: timeZone,
location: reminder.booking?.location || "",
additionalNotes: reminder.booking?.description,
customInputs: reminder.booking?.customInputs,
};
const customMessage = await customTemplate(
reminder.workflowStep.reminderBody || "",
variables,
reminder.booking?.user?.locale || ""
);
message = customMessage.text;
break;
}
if (message?.length && message?.length > 0 && sendTo) {
const scheduledSMS = await twilio.scheduleSMS(
sendTo,
message,
reminder.scheduledDate,
reminder.workflowStep.sender || "Cal"
);
await prisma.workflowReminder.update({
where: {
id: reminder.id,
},
data: {
scheduled: true,
referenceId: scheduledSMS.sid,
},
});
}
} catch (error) {
console.log(`Error scheduling SMS with error ${error}`);
}
}
res.status(200).json({ message: "SMS scheduled" });
@@ -85,6 +85,7 @@ const sendPayload = async (
rescheduleUid?: string;
bookingId?: number;
status?: string;
smsReminderNumber?: string;
}
) => {
const { appId, payloadTemplate: template } = webhook;
+3 -1
View File
@@ -313,7 +313,9 @@ export default abstract class BaseCalendarService implements Calendar {
const event = new ICAL.Event(vevent);
const dtstart: { [key: string]: string } | undefined = vevent?.getFirstPropertyValue("dtstart");
const timezone = dtstart ? dtstart["timezone"] : undefined;
const tzid: string | undefined = vevent?.getFirstPropertyValue("tzid") || timezone;
// We check if the dtstart timezone is in UTC which is actually represented by Z instead, but not recognized as that in ICAL.js as UTC
const isUTC = timezone === "Z";
const tzid: string | undefined = vevent?.getFirstPropertyValue("tzid") || isUTC ? "UTC" : timezone;
// In case of icalendar, when only tzid is available without vtimezone, we need to add vtimezone explicitly to take care of timezone diff
if (!vcalendar.getFirstSubcomponent("vtimezone") && tzid) {
const timezoneComp = new ICAL.Component("vtimezone");
+3 -5
View File
@@ -1,10 +1,8 @@
import type { EventTypeCustomInput } from "@prisma/client";
import { PeriodType, Prisma, SchedulingType, UserPlan } from "@prisma/client";
import { DailyLocationType } from "@calcom/app-store/locations";
import { userSelect } from "@calcom/prisma/selects";
import { _EventTypeModel } from "@calcom/prisma/zod";
import { EventTypeMetaDataSchema } from "@calcom/prisma/zod-utils";
import { CustomInputSchema, EventTypeMetaDataSchema } from "@calcom/prisma/zod-utils";
type User = Prisma.UserGetPayload<typeof userSelect>;
@@ -50,7 +48,7 @@ const user: User = {
allowDynamicBooking: true,
};
const customInputs: EventTypeCustomInput[] = [];
const customInputs: CustomInputSchema[] = [];
const commons = {
isDynamic: true,
@@ -166,7 +164,7 @@ export const getUsernameList = (users: string | string[] | undefined): string[]
const allUsers = users.map((user) =>
user
.toLowerCase()
//.toLowerCase() // This was causing 404s for mixed-case usernames
.replace(/( |%20)/g, "+")
.split("+")
);
@@ -0,0 +1,5 @@
-- AlterEnum
ALTER TYPE "EventTypeCustomInputType" ADD VALUE 'radio';
-- AlterTable
ALTER TABLE "EventTypeCustomInput" ADD COLUMN "options" JSONB;
+3
View File
@@ -347,6 +347,7 @@ enum EventTypeCustomInputType {
TEXTLONG @map("textLong")
NUMBER @map("number")
BOOL @map("bool")
RADIO @map("radio")
}
model EventTypeCustomInput {
@@ -355,6 +356,8 @@ model EventTypeCustomInput {
eventType EventType @relation(fields: [eventTypeId], references: [id], onDelete: Cascade)
label String
type EventTypeCustomInputType
// @zod.custom(imports.customInputOptionSchema)
options Json?
required Boolean
placeholder String @default("")
}
+20
View File
@@ -1,3 +1,4 @@
import { EventTypeCustomInputType } from "@prisma/client";
import z, { ZodNullable, ZodObject, ZodOptional } from "zod";
/* eslint-disable no-underscore-dangle */
@@ -212,6 +213,25 @@ export const teamMetadataSchema = z
.partial()
.nullable();
export const customInputOptionSchema = z.array(
z.object({
label: z.string(),
type: z.string(),
})
);
export const customInputSchema = z.object({
id: z.number(),
eventTypeId: z.number(),
label: z.string(),
type: z.nativeEnum(EventTypeCustomInputType),
options: customInputOptionSchema.optional().nullable(),
required: z.boolean(),
placeholder: z.string(),
});
export type CustomInputSchema = z.infer<typeof customInputSchema>;
/**
* Ensures that it is a valid HTTP URL
* It automatically avoids
@@ -356,6 +356,7 @@ export const bookingsRouter = router({
dynamicEventSlugRef: true,
dynamicGroupSlugRef: true,
destinationCalendar: true,
smsReminderNumber: true,
},
where: {
uid: bookingId,
@@ -528,7 +529,10 @@ export const bookingsRouter = router({
};
const webhooks = await getWebhooks(subscriberOptions);
const promises = webhooks.map((webhook) =>
sendPayload(webhook.secret, eventTrigger, new Date().toISOString(), webhook, evt).catch((e) => {
sendPayload(webhook.secret, eventTrigger, new Date().toISOString(), webhook, {
...evt,
smsReminderNumber: bookingToReschedule.smsReminderNumber || undefined,
}).catch((e) => {
console.error(
`Error executing webhook for event: ${eventTrigger}, URL: ${webhook.subscriberUrl}`,
e
@@ -982,6 +986,7 @@ export const bookingsRouter = router({
bookingId,
eventTypeId: booking.eventType?.id,
status: "ACCEPTED",
smsReminderNumber: booking.smsReminderNumber || undefined,
}).catch((e) => {
console.error(
`Error executing webhook for event: ${WebhookTriggerEvents.BOOKING_CREATED}, URL: ${sub.subscriberUrl}`,
@@ -11,7 +11,12 @@ import { validateBookingLimitOrder } from "@calcom/lib";
import { CAL_URL } from "@calcom/lib/constants";
import { baseEventTypeSelect, baseUserSelect } from "@calcom/prisma";
import { _DestinationCalendarModel, _EventTypeCustomInputModel, _EventTypeModel } from "@calcom/prisma/zod";
import { EventTypeMetaDataSchema, stringOrNumber } from "@calcom/prisma/zod-utils";
import {
customInputSchema,
EventTypeMetaDataSchema,
stringOrNumber,
CustomInputSchema,
} from "@calcom/prisma/zod-utils";
import { createEventTypeInput } from "@calcom/prisma/zod/custom/eventtype";
import { TRPCError } from "@trpc/server";
@@ -30,7 +35,7 @@ function handlePeriodType(periodType: string | undefined): PeriodType | undefine
return PeriodType[passedPeriodType];
}
function handleCustomInputs(customInputs: EventTypeCustomInput[], eventTypeId: number) {
function handleCustomInputs(customInputs: CustomInputSchema[], eventTypeId: number) {
const cInputsIdsToDelete = customInputs.filter((input) => input.id > 0).map((e) => e.id);
const cInputsToCreate = customInputs
.filter((input) => input.id < 0)
@@ -39,6 +44,7 @@ function handleCustomInputs(customInputs: EventTypeCustomInput[], eventTypeId: n
label: input.label,
required: input.required,
placeholder: input.placeholder,
options: input.options || undefined,
}));
const cInputsToUpdate = customInputs
.filter((input) => input.id > 0)
@@ -48,6 +54,7 @@ function handleCustomInputs(customInputs: EventTypeCustomInput[], eventTypeId: n
label: input.label,
required: input.required,
placeholder: input.placeholder,
options: input.options || undefined,
},
where: {
id: input.id,
@@ -71,7 +78,7 @@ function handleCustomInputs(customInputs: EventTypeCustomInput[], eventTypeId: n
const EventTypeUpdateInput = _EventTypeModel
/** Optional fields */
.extend({
customInputs: z.array(_EventTypeCustomInputModel),
customInputs: z.array(customInputSchema).optional(),
destinationCalendar: _DestinationCalendarModel.pick({
integration: true,
externalId: true,
+1 -1
View File
@@ -86,7 +86,7 @@ export {
DropdownMenuSeparator,
DropdownMenuTrigger,
} from "./v2/core/Dropdown";
export { RadioGroup } from "./v2/core/form";
export { RadioGroup, Radio, Group, RadioField } from "./v2/core/form";
export { BooleanToggleGroupField } from "./v2/core/form/BooleanToggleGroup";
export { DateRangePickerLazy as DateRangePicker } from "./v2/core/form/date-range-picker";
export { default as DatePickerField } from "./v2/core/form/DatePicker";
+7 -1
View File
@@ -1,4 +1,10 @@
export { RadioGroup, /* TODO: solve this conflict -> Select, */ Radio } from "./radio-area";
export {
RadioGroup,
/* TODO: solve this conflict -> Select, */
Radio,
Group,
RadioField,
} from "./radio-area";
export { default as Checkbox } from "../../../components/form/checkbox/Checkbox";
export { default as DatePicker } from "./DatePicker";
export { default as Select, SelectWithValidation, SelectField, getReactSelectProps } from "./select";
@@ -29,7 +29,7 @@ export const Label = (props: JSX.IntrinsicElements["label"] & { disabled?: boole
<label
{...props}
className={classNames(
"ml-2 text-sm font-medium leading-5 text-gray-900",
"ml-2 text-sm font-medium leading-5 text-gray-900 dark:text-white",
props.disabled && "text-gray-500"
)}
/>
+1 -1
View File
@@ -1,3 +1,3 @@
export * as RadioGroup from "./RadioAreaGroup";
export { default as Select } from "./Select";
export * as Radio from "./Radio";
export { Group, Indicator, Label, Radio, RadioField } from "./Radio";
@@ -117,7 +117,7 @@ const SettingsSidebarContainer = ({ className = "" }) => {
return (
<nav
className={`no-scrollbar flex w-56 flex-col space-y-1 overflow-scroll py-3 px-2 ${className}`}
className={`no-scrollbar flex w-56 flex-col space-y-1 overflow-x-hidden overflow-y-scroll py-3 px-2 ${className}`}
aria-label="Tabs">
<>
<div className="desktop-only pt-4" />
@@ -47,7 +47,7 @@ const VerticalTabItem = function ({
target={props.isExternalLink ? "_blank" : "_self"}
className={classNames(
props.textClassNames || "text-sm font-medium leading-none text-gray-600",
"min-h-9 group flex w-64 flex-row rounded-md px-3 py-[10px] hover:bg-gray-100 group-hover:text-gray-700 [&[aria-current='page']]:bg-gray-200 [&[aria-current='page']]:text-gray-900",
"min-h-9 group flex w-64 flex-row rounded-md px-3 py-[10px] leading-4 hover:bg-gray-100 group-hover:text-gray-700 [&[aria-current='page']]:bg-gray-200 [&[aria-current='page']]:text-gray-900",
props.disabled && "pointer-events-none !opacity-30",
(isChild || !props.icon) && "ml-7 mr-5 w-auto",
!info ? "h-6" : "h-14",