Merge remote-tracking branch 'origin/main' into feature/booking-filters

# Conflicts:
#	apps/web/public/static/locales/en/common.json
This commit is contained in:
sean-brydon 2022-12-13 12:30:20 +00:00
commit 2abed498c4
103 changed files with 1542 additions and 1123 deletions

View File

@ -7,14 +7,13 @@
# - E-MAIL SETTINGS
# - LICENSE *************************************************************************************************
# Set this value to 'agree' to accept our license:
# LICENSE: https://github.com/calendso/calendso/blob/main/LICENSE
# https://github.com/calendso/calendso/blob/main/LICENSE
#
# Summary of terms:
# - The codebase has to stay open source, whether it was modified or not
# - You can not repackage or sell the codebase
# - Acquire a commercial license to remove these terms by visiting: cal.com/sales
NEXT_PUBLIC_LICENSE_CONSENT=''
#
# To enable enterprise-only features, fill your license key in here.
# @see https://console.cal.com
CALCOM_LICENSE_KEY=
@ -86,6 +85,8 @@ SENDGRID_EMAIL=
TWILIO_SID=
TWILIO_TOKEN=
TWILIO_MESSAGING_SID=
TWILIO_PHONE_NUMBER=
NEXT_PUBLIC_SENDER_ID=
# This is used so we can bypass emails in auth flows for E2E testing
# Set it to "1" if you need to run E2E tests locally

View File

@ -292,7 +292,7 @@ You can deploy Cal on [Railway](https://railway.app/) using the button above. Th
Currently Vercel Pro Plan is required to be able to Deploy this application with Vercel, due to limitations on the number of serverless functions on the free plan.
[![Deploy with Vercel](https://vercel.com/button)](https://vercel.com/new/clone?repository-url=https%3A%2F%2Fgithub.com%2Fcalcom%2Fcal.com&env=DATABASE_URL,NEXT_PUBLIC_WEBAPP_URL,NEXTAUTH_URL,NEXTAUTH_SECRET,CRON_API_KEY,CALENDSO_ENCRYPTION_KEY,NEXT_PUBLIC_LICENSE_CONSENT&envDescription=See%20all%20available%20env%20vars&envLink=https%3A%2F%2Fgithub.com%2Fcalcom%2Fcal.com%2Fblob%2Fmain%2F.env.example&project-name=cal&repo-name=cal.com&build-command=cd%20../..%20%26%26%20yarn%20build&root-directory=apps%2Fweb%2F)
[![Deploy with Vercel](https://vercel.com/button)](https://vercel.com/new/clone?repository-url=https%3A%2F%2Fgithub.com%2Fcalcom%2Fcal.com&env=DATABASE_URL,NEXT_PUBLIC_WEBAPP_URL,NEXTAUTH_URL,NEXTAUTH_SECRET,CRON_API_KEY,CALENDSO_ENCRYPTION_KEY&envDescription=See%20all%20available%20env%20vars&envLink=https%3A%2F%2Fgithub.com%2Fcalcom%2Fcal.com%2Fblob%2Fmain%2F.env.example&project-name=cal&repo-name=cal.com&build-command=cd%20../..%20%26%26%20yarn%20build&root-directory=apps%2Fweb%2F)
<!-- ROADMAP -->
@ -419,14 +419,16 @@ following
2. Click Get a Twilio phone number
3. Copy Account SID to your .env file into the TWILIO_SID field
4. Copy Auth Token to your .env file into the TWILIO_TOKEN field
5. Create a messaging service (Develop -> Messaging -> Services)
6. Choose any name for the messaging service
7. Click 'Add Senders'
8. Choose phone number as sender type
9. Add the listed phone number
10. Leave all other fields as they are
11. Complete setup and click View my new Messaging Service
12. Copy Messaging Service SID to your .env file into the TWILIO_MESSAGING_SID field
5. Copy your Twilio phone number to your .env file into the TWILIO_PHONE_NUMBER field
6. Add your own sender id to the .env file into the NEXT_PUBLIC_SENDER_ID field (fallback is Cal)
7. Create a messaging service (Develop -> Messaging -> Services)
8. Choose any name for the messaging service
9. Click 'Add Senders'
10. Choose phone number as sender type
11. Add the listed phone number
12. Leave all other fields as they are
13. Complete setup and click View my new Messaging Service
14. Copy Messaging Service SID to your .env file into the TWILIO_MESSAGING_SID field
<!-- LICENSE -->

View File

@ -33,11 +33,6 @@
"generator": "secret",
"required": "true"
},
"NEXT_PUBLIC_LICENSE_CONSENT": {
"description": "License consent",
"value": "true",
"required": "true"
},
"CRON_API_KEY": {
"description": "ApiKey for cronjobs",
"value": ""

@ -1 +1 @@
Subproject commit 3d84ce68c9baa5d4ce7c85a37a9b8678f399b7a7
Subproject commit 8b74f463f454cf84e0f39bf78ff7d0f245014caa

@ -1 +1 @@
Subproject commit d2445da9f5e623e2c93248f8d43daf78a2eb3946
Subproject commit 564f9b2faa03bd4e7da6d978f990d53aebe3626f

View File

@ -44,6 +44,7 @@ type PreviewState = {
palette: {
brandColor: string;
};
hideEventTypeDetails: boolean;
};
const queryParamsForDialog = ["embedType", "embedTabName", "embedUrl"];
@ -125,10 +126,12 @@ const getEmbedUIInstructionString = ({
apiName,
theme,
brandColor,
hideEventTypeDetails,
}: {
apiName: string;
theme?: string;
brandColor: string;
hideEventTypeDetails: boolean;
}) => {
theme = theme !== "auto" ? theme : undefined;
return getInstructionString({
@ -141,6 +144,7 @@ const getEmbedUIInstructionString = ({
brandColor,
},
},
hideEventTypeDetails: hideEventTypeDetails,
},
});
};
@ -253,18 +257,21 @@ const getEmbedTypeSpecificString = ({
apiName: string;
theme: PreviewState["theme"];
brandColor: string;
hideEventTypeDetails: boolean;
};
if (embedFramework === "react") {
uiInstructionStringArg = {
apiName: "cal",
theme: previewState.theme,
brandColor: previewState.palette.brandColor,
hideEventTypeDetails: previewState.hideEventTypeDetails,
};
} else {
uiInstructionStringArg = {
apiName: "Cal",
theme: previewState.theme,
brandColor: previewState.palette.brandColor,
hideEventTypeDetails: previewState.hideEventTypeDetails,
};
}
if (!frameworkCodes[embedType]) {
@ -689,6 +696,7 @@ const EmbedTypeCodeAndPreviewDialogContent = ({
theme: Theme.auto,
floatingPopup: {},
elementClick: {},
hideEventTypeDetails: false,
palette: {
brandColor: "#000000",
},
@ -753,6 +761,7 @@ const EmbedTypeCodeAndPreviewDialogContent = ({
name: "ui",
arg: {
theme: previewState.theme,
hideEventTypeDetails: previewState.hideEventTypeDetails,
styles: {
branding: {
...previewState.palette,
@ -811,7 +820,7 @@ const EmbedTypeCodeAndPreviewDialogContent = ({
{embed.title}
</h3>
<hr className={classNames("mt-4", embedType === "element-click" ? "hidden" : "")} />
<div className="max-h-97 flex flex-col overflow-y-auto">
<div className="flex flex-col overflow-y-auto">
<div className={classNames("mt-4 font-medium", embedType === "element-click" ? "hidden" : "")}>
<Collapsible
open={isEmbedCustomizationOpen}
@ -838,7 +847,6 @@ const EmbedTypeCodeAndPreviewDialogContent = ({
<div className="text-sm">Embed Window Sizing</div>
<div className="justify-left flex items-center">
<TextField
name="width"
labelProps={{ className: "hidden" }}
required
value={previewState.inline.width}
@ -860,7 +868,6 @@ const EmbedTypeCodeAndPreviewDialogContent = ({
<span className="p-2">×</span>
<TextField
labelProps={{ className: "hidden" }}
name="height"
value={previewState.inline.height}
required
onChange={(e) => {
@ -888,7 +895,6 @@ const EmbedTypeCodeAndPreviewDialogContent = ({
<div className="mb-2 text-sm">Button Text</div>
{/* Default Values should come from preview iframe */}
<TextField
name="buttonText"
labelProps={{ className: "hidden" }}
onChange={(e) => {
setPreviewState((previewState) => {
@ -1004,6 +1010,20 @@ const EmbedTypeCodeAndPreviewDialogContent = ({
</CollapsibleTrigger>
<CollapsibleContent>
<div className="mt-6 text-sm">
<div className="mb-4 flex items-center justify-start space-x-2">
<Switch
checked={previewState.hideEventTypeDetails}
onCheckedChange={(checked) => {
setPreviewState((previewState) => {
return {
...previewState,
hideEventTypeDetails: checked,
};
});
}}
/>
<div className="text-sm">{t("hide_eventtype_details")}</div>
</div>
<Label className="">
<div className="mb-2">Theme</div>
<Select

View File

@ -13,6 +13,7 @@ import dayjs, { Dayjs } from "@calcom/dayjs";
import {
useEmbedNonStylesConfig,
useEmbedStyles,
useEmbedUiConfig,
useIsBackgroundTransparent,
useIsEmbed,
} from "@calcom/embed-core/embed-iframe";
@ -29,10 +30,7 @@ import { trpc } from "@calcom/trpc/react";
import { Icon, DatePicker } from "@calcom/ui";
import { timeZone as localStorageTimeZone } from "@lib/clock";
// import { timeZone } from "@lib/clock";
import { useExposePlanGlobally } from "@lib/hooks/useExposePlanGlobally";
import useRouterQuery from "@lib/hooks/useRouterQuery";
import { isBrandingHidden } from "@lib/isBrandingHidden";
import Gates, { Gate, GateState } from "@components/Gates";
import AvailableTimes from "@components/booking/AvailableTimes";
@ -256,6 +254,7 @@ const AvailabilityPage = ({ profile, eventType, ...restProps }: Props) => {
useTheme(profile.theme);
const { t } = useLocale();
const availabilityDatePickerEmbedStyles = useEmbedStyles("availabilityDatePicker");
//TODO: Plan to remove shouldAlignCentrallyInEmbed config
const shouldAlignCentrallyInEmbed = useEmbedNonStylesConfig("align") !== "left";
const shouldAlignCentrally = !isEmbed || shouldAlignCentrallyInEmbed;
const isBackgroundTransparent = useIsBackgroundTransparent();
@ -280,9 +279,6 @@ const AvailabilityPage = ({ profile, eventType, ...restProps }: Props) => {
setTimeZone(localStorageTimeZone() || dayjs.tz.guess());
}, []);
// TODO: Improve this;
useExposePlanGlobally(eventType.users.length === 1 ? eventType.users[0].plan : "PRO");
const [recurringEventCount, setRecurringEventCount] = useState(eventType.recurringEvent?.count);
const telemetry = useTelemetry();
@ -295,7 +291,7 @@ const AvailabilityPage = ({ profile, eventType, ...restProps }: Props) => {
);
}
}, [telemetry]);
const embedUiConfig = useEmbedUiConfig();
// get dynamic user list here
const userList = eventType.users ? eventType.users.map((user) => user.username).filter(notEmpty) : [];
@ -308,6 +304,8 @@ const AvailabilityPage = ({ profile, eventType, ...restProps }: Props) => {
const rawSlug = profile.slug ? profile.slug.split("/") : [];
if (rawSlug.length > 1) rawSlug.pop(); //team events have team name as slug, but user events have [user]/[type] as slug.
const showEventTypeDetails = (isEmbed && !embedUiConfig.hideEventTypeDetails) || !isEmbed;
// Define conditional gates here
const gates = [
// Rainbow gate is only added if the event has both a `blockchainId` and a `smartContractAddress`
@ -341,7 +339,7 @@ const AvailabilityPage = ({ profile, eventType, ...restProps }: Props) => {
<div>
<main
className={classNames(
"flex-col md:mx-4 lg:flex",
"flex flex-col md:mx-4",
shouldAlignCentrally ? "items-center" : "items-start",
!isEmbed && classNames("mx-auto my-0 ease-in-out md:my-24")
)}>
@ -356,54 +354,55 @@ const AvailabilityPage = ({ profile, eventType, ...restProps }: Props) => {
isEmbed && "mx-auto"
)}>
<div className="overflow-hidden md:flex">
<div
className={classNames(
"sm:dark:border-darkgray-200 flex flex-col border-gray-200 p-5 sm:border-r",
"min-w-full md:w-[230px] md:min-w-[230px]",
recurringEventCount && "xl:w-[380px] xl:min-w-[380px]"
)}>
<BookingDescription profile={profile} eventType={eventType} rescheduleUid={rescheduleUid}>
{!rescheduleUid && eventType.recurringEvent && (
<div className="flex items-start text-sm font-medium">
<Icon.FiRefreshCcw className="float-left mr-[10px] mt-[7px] ml-[2px] inline-block h-4 w-4 " />
<div>
<p className="mb-1 -ml-2 inline px-2 py-1">
{getRecurringFreq({ t, recurringEvent: eventType.recurringEvent })}
</p>
<input
type="number"
min="1"
max={eventType.recurringEvent.count}
className="w-15 dark:bg-darkgray-200 h-7 rounded-sm border-gray-300 bg-white text-sm font-medium [appearance:textfield] ltr:mr-2 rtl:ml-2 dark:border-gray-500"
defaultValue={eventType.recurringEvent.count}
onChange={(event) => {
setRecurringEventCount(parseInt(event?.target.value));
}}
/>
<p className="inline">
{t("occurrence", {
count: recurringEventCount,
})}
</p>
{showEventTypeDetails && (
<div
className={classNames(
"sm:dark:border-darkgray-200 flex flex-col border-gray-200 p-5 sm:border-r",
"min-w-full md:w-[230px] md:min-w-[230px]",
recurringEventCount && "xl:w-[380px] xl:min-w-[380px]"
)}>
<BookingDescription profile={profile} eventType={eventType} rescheduleUid={rescheduleUid}>
{!rescheduleUid && eventType.recurringEvent && (
<div className="flex items-start text-sm font-medium">
<Icon.FiRefreshCcw className="float-left mr-[10px] mt-[7px] ml-[2px] inline-block h-4 w-4 " />
<div>
<p className="mb-1 -ml-2 inline px-2 py-1">
{getRecurringFreq({ t, recurringEvent: eventType.recurringEvent })}
</p>
<input
type="number"
min="1"
max={eventType.recurringEvent.count}
className="w-15 dark:bg-darkgray-200 h-7 rounded-sm border-gray-300 bg-white text-sm font-medium [appearance:textfield] ltr:mr-2 rtl:ml-2 dark:border-gray-500"
defaultValue={eventType.recurringEvent.count}
onChange={(event) => {
setRecurringEventCount(parseInt(event?.target.value));
}}
/>
<p className="inline">
{t("occurrence", {
count: recurringEventCount,
})}
</p>
</div>
</div>
</div>
)}
{stripeAppData.price > 0 && (
<p className="-ml-2 px-2 text-sm font-medium">
<Icon.FiCreditCard className="mr-[10px] ml-[2px] -mt-1 inline-block h-4 w-4" />
<IntlProvider locale="en">
<FormattedNumber
value={stripeAppData.price / 100.0}
style="currency"
currency={stripeAppData.currency.toUpperCase()}
/>
</IntlProvider>
</p>
)}
{timezoneDropdown}
</BookingDescription>
)}
{stripeAppData.price > 0 && (
<p className="-ml-2 px-2 text-sm font-medium">
<Icon.FiCreditCard className="mr-[10px] ml-[2px] -mt-1 inline-block h-4 w-4" />
<IntlProvider locale="en">
<FormattedNumber
value={stripeAppData.price / 100.0}
style="currency"
currency={stripeAppData.currency.toUpperCase()}
/>
</IntlProvider>
</p>
)}
{timezoneDropdown}
</BookingDescription>
{/* Temporarily disabled - booking?.startTime && rescheduleUid && (
{/* Temporarily disabled - booking?.startTime && rescheduleUid && (
<div>
<p
className="mt-4 mb-3 text-gray-600 dark:text-darkgray-600"
@ -416,7 +415,8 @@ const AvailabilityPage = ({ profile, eventType, ...restProps }: Props) => {
</p>
</div>
)*/}
</div>
</div>
)}
<SlotPicker
weekStart={
typeof profile.weekStart === "string"
@ -442,7 +442,8 @@ const AvailabilityPage = ({ profile, eventType, ...restProps }: Props) => {
/>
</div>
</div>
{(!restProps.isBrandingHidden || isEmbed) && <PoweredByCal />}
{/* FIXME: We don't show branding in Embed yet because we need to place branding on top of the main content. Keeping it outside the main content would have visibility issues because outside main content background is transparent */}
{!restProps.isBrandingHidden && !isEmbed && <PoweredByCal />}
</div>
</main>
</div>

View File

@ -26,6 +26,7 @@ import { LocationObject, LocationType } from "@calcom/core/location";
import dayjs from "@calcom/dayjs";
import {
useEmbedNonStylesConfig,
useEmbedUiConfig,
useIsBackgroundTransparent,
useIsEmbed,
} from "@calcom/embed-core/embed-iframe";
@ -89,6 +90,7 @@ const BookingPage = ({
const { t, i18n } = useLocale();
const { duration: queryDuration } = useRouterQuery("duration");
const isEmbed = useIsEmbed(restProps.isEmbed);
const embedUiConfig = useEmbedUiConfig();
const shouldAlignCentrallyInEmbed = useEmbedNonStylesConfig("align") !== "left";
const shouldAlignCentrally = !isEmbed || shouldAlignCentrallyInEmbed;
const router = useRouter();
@ -315,6 +317,22 @@ const BookingPage = ({
{}
);
if (eventType.customInputs.length > 0) {
// find all required custom inputs and ensure they are filled out in the booking form
const requiredCustomInputs = eventType.customInputs.filter((input) => input.required);
const missingRequiredCustomInputs = requiredCustomInputs.filter(
(input) => !booking?.customInputs?.[input.id]
);
if (missingRequiredCustomInputs.length > 0) {
missingRequiredCustomInputs.forEach((input) => {
bookingForm.setError(`customInputs.${input.id}`, {
type: "required",
});
});
return;
}
}
if (recurringDates.length) {
// Identify set of bookings to one intance of recurring event to support batch changes
const recurringEventId = uuidv4();
@ -406,6 +424,7 @@ const BookingPage = ({
}
});
}
const showEventTypeDetails = (isEmbed && !embedUiConfig.hideEventTypeDetails) || !isEmbed;
const rainbowAppData = getEventTypeAppData(eventType, "rainbow") || {};
// Define conditional gates here
@ -439,7 +458,7 @@ const BookingPage = ({
className={classNames(
shouldAlignCentrally ? "mx-auto" : "",
isEmbed ? "" : "sm:my-24",
"my-0 max-w-3xl "
"my-0 max-w-3xl"
)}>
<div
className={classNames(
@ -448,95 +467,97 @@ const BookingPage = ({
"dark:border-darkgray-300 rounded-md sm:border"
)}>
<div className="sm:flex">
<div className="sm:dark:border-darkgray-300 dark:text-darkgray-600 flex flex-col px-6 pt-6 pb-0 text-gray-600 sm:w-1/2 sm:border-r sm:pb-6">
<BookingDescription isBookingPage profile={profile} eventType={eventType}>
{stripeAppData.price > 0 && (
<p className="text-bookinglight -ml-2 px-2 text-sm ">
<Icon.FiCreditCard className="mr-[10px] ml-[2px] -mt-1 inline-block h-4 w-4" />
<IntlProvider locale="en">
<FormattedNumber
value={stripeAppData.price / 100.0}
style="currency"
currency={stripeAppData.currency.toUpperCase()}
/>
</IntlProvider>
</p>
)}
{!rescheduleUid && eventType.recurringEvent?.freq && recurringEventCount && (
<div className="items-start text-sm font-medium text-gray-600 dark:text-white">
<Icon.FiRefreshCw className="mr-[10px] ml-[2px] inline-block h-4 w-4" />
<p className="-ml-2 inline-block items-center px-2">
{getEveryFreqFor({
t,
recurringEvent: eventType.recurringEvent,
recurringCount: recurringEventCount,
})}
{showEventTypeDetails && (
<div className="sm:dark:border-darkgray-300 dark:text-darkgray-600 flex flex-col px-6 pt-6 pb-0 text-gray-600 sm:w-1/2 sm:border-r sm:pb-6">
<BookingDescription isBookingPage profile={profile} eventType={eventType}>
{stripeAppData.price > 0 && (
<p className="text-bookinglight -ml-2 px-2 text-sm ">
<Icon.FiCreditCard className="mr-[10px] ml-[2px] -mt-1 inline-block h-4 w-4" />
<IntlProvider locale="en">
<FormattedNumber
value={stripeAppData.price / 100.0}
style="currency"
currency={stripeAppData.currency.toUpperCase()}
/>
</IntlProvider>
</p>
</div>
)}
<div className="text-bookinghighlight flex items-start text-sm">
<Icon.FiCalendar className="mr-[10px] ml-[2px] mt-[2px] inline-block h-4 w-4" />
<div className="text-sm font-medium">
{(rescheduleUid || !eventType.recurringEvent?.freq) && `${parseDate(date, i18n)}`}
{!rescheduleUid &&
eventType.recurringEvent?.freq &&
recurringStrings.slice(0, 5).map((timeFormatted, key) => {
return <p key={key}>{timeFormatted}</p>;
})}
{!rescheduleUid && eventType.recurringEvent?.freq && recurringStrings.length > 5 && (
<div className="flex">
<Tooltip
content={recurringStrings.slice(5).map((timeFormatted, key) => (
<p key={key}>{timeFormatted}</p>
))}>
<p className="dark:text-darkgray-600 text-sm">
+ {t("plus_more", { count: recurringStrings.length - 5 })}
</p>
</Tooltip>
</div>
)}
</div>
</div>
{booking?.startTime && rescheduleUid && (
<div>
<p className="mt-8 mb-2 text-sm " data-testid="former_time_p">
{t("former_time")}
</p>
<p className="line-through ">
<Icon.FiCalendar className="mr-[10px] ml-[2px] -mt-1 inline-block h-4 w-4" />
{typeof booking.startTime === "string" && parseDate(dayjs(booking.startTime), i18n)}
</p>
</div>
)}
{!!eventType.seatsPerTimeSlot && (
)}
{!rescheduleUid && eventType.recurringEvent?.freq && recurringEventCount && (
<div className="items-start text-sm font-medium text-gray-600 dark:text-white">
<Icon.FiRefreshCw className="mr-[10px] ml-[2px] inline-block h-4 w-4" />
<p className="-ml-2 inline-block items-center px-2">
{getEveryFreqFor({
t,
recurringEvent: eventType.recurringEvent,
recurringCount: recurringEventCount,
})}
</p>
</div>
)}
<div className="text-bookinghighlight flex items-start text-sm">
<Icon.FiUser
className={`mr-[10px] ml-[2px] mt-[2px] inline-block h-4 w-4 ${
booking && booking.attendees.length / eventType.seatsPerTimeSlot >= 0.5
? "text-rose-600"
: booking && booking.attendees.length / eventType.seatsPerTimeSlot >= 0.33
? "text-yellow-500"
: "text-bookinghighlight"
}`}
/>
<p
className={`${
booking && booking.attendees.length / eventType.seatsPerTimeSlot >= 0.5
? "text-rose-600"
: booking && booking.attendees.length / eventType.seatsPerTimeSlot >= 0.33
? "text-yellow-500"
: "text-bookinghighlight"
} mb-2 font-medium`}>
{booking
? eventType.seatsPerTimeSlot - booking.attendees.length
: eventType.seatsPerTimeSlot}{" "}
/ {eventType.seatsPerTimeSlot} {t("seats_available")}
</p>
<Icon.FiCalendar className="mr-[10px] ml-[2px] mt-[2px] inline-block h-4 w-4" />
<div className="text-sm font-medium">
{(rescheduleUid || !eventType.recurringEvent?.freq) && `${parseDate(date, i18n)}`}
{!rescheduleUid &&
eventType.recurringEvent?.freq &&
recurringStrings.slice(0, 5).map((timeFormatted, key) => {
return <p key={key}>{timeFormatted}</p>;
})}
{!rescheduleUid && eventType.recurringEvent?.freq && recurringStrings.length > 5 && (
<div className="flex">
<Tooltip
content={recurringStrings.slice(5).map((timeFormatted, key) => (
<p key={key}>{timeFormatted}</p>
))}>
<p className="dark:text-darkgray-600 text-sm">
+ {t("plus_more", { count: recurringStrings.length - 5 })}
</p>
</Tooltip>
</div>
)}
</div>
</div>
)}
</BookingDescription>
</div>
<div className="p-6 sm:w-1/2">
{booking?.startTime && rescheduleUid && (
<div>
<p className="mt-8 mb-2 text-sm " data-testid="former_time_p">
{t("former_time")}
</p>
<p className="line-through ">
<Icon.FiCalendar className="mr-[10px] ml-[2px] -mt-1 inline-block h-4 w-4" />
{typeof booking.startTime === "string" && parseDate(dayjs(booking.startTime), i18n)}
</p>
</div>
)}
{!!eventType.seatsPerTimeSlot && (
<div className="text-bookinghighlight flex items-start text-sm">
<Icon.FiUser
className={`mr-[10px] ml-[2px] mt-[2px] inline-block h-4 w-4 ${
booking && booking.attendees.length / eventType.seatsPerTimeSlot >= 0.5
? "text-rose-600"
: booking && booking.attendees.length / eventType.seatsPerTimeSlot >= 0.33
? "text-yellow-500"
: "text-bookinghighlight"
}`}
/>
<p
className={`${
booking && booking.attendees.length / eventType.seatsPerTimeSlot >= 0.5
? "text-rose-600"
: booking && booking.attendees.length / eventType.seatsPerTimeSlot >= 0.33
? "text-yellow-500"
: "text-bookinghighlight"
} mb-2 font-medium`}>
{booking
? eventType.seatsPerTimeSlot - booking.attendees.length
: eventType.seatsPerTimeSlot}{" "}
/ {eventType.seatsPerTimeSlot} {t("seats_available")}
</p>
</div>
)}
</BookingDescription>
</div>
)}
<div className={classNames("p-6", showEventTypeDetails ? "sm:w-1/2" : "w-full")}>
<Form form={bookingForm} handleSubmit={bookEvent}>
<div className="mb-4">
<label htmlFor="name" className="block text-sm font-medium text-gray-700 dark:text-white">
@ -679,8 +700,11 @@ const BookingPage = ({
{input.type !== EventTypeCustomInputType.BOOL && (
<label
htmlFor={"custom_" + input.id}
className="mb-1 block text-sm font-medium text-gray-700 dark:text-white">
{input.label}
className={classNames(
"mb-1 block text-sm font-medium text-gray-700 transition-colors dark:text-white",
bookingForm.formState.errors.customInputs?.[input.id] && "!text-red-700"
)}>
{input.label} {input.required && <span className="text-red-700">*</span>}
</label>
)}
{input.type === EventTypeCustomInputType.TEXTLONG && (
@ -745,24 +769,28 @@ const BookingPage = ({
</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 className="flex">
<Group
required={input.required}
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`}
/>
))}
</>
{bookingForm.formState.errors.customInputs?.[input.id] && (
<div className="mt-px flex items-center text-xs text-red-700 ">
<p>{t("required")}</p>
</div>
)}
</Group>
</div>
)}
</div>

View File

@ -250,23 +250,24 @@ export const EventSetupTab = (
isSearchable={false}
className="h-auto !min-h-[36px] text-sm"
options={multipleDurationOptions}
value={selectedMultipleDuration}
onChange={(options) => {
const values = options
.map((opt) => opt.value)
.sort(function (a, b) {
return a - b;
});
let newOptions = [...options];
newOptions = newOptions.sort((a, b) => {
return a?.value - b?.value;
});
const values = newOptions.map((opt) => opt.value);
setMultipleDuration(values);
setSelectedMultipleDuration(options);
if (!options.find((opt) => opt.value === defaultDuration?.value)) {
if (options.length > 0) {
setDefaultDuration(options[0]);
setSelectedMultipleDuration(newOptions);
if (!newOptions.find((opt) => opt.value === defaultDuration?.value)) {
if (newOptions.length > 0) {
setDefaultDuration(newOptions[0]);
} else {
setDefaultDuration(null);
}
}
if (options.length === 1 && defaultDuration === null) {
setDefaultDuration(options[0]);
if (newOptions.length === 1 && defaultDuration === null) {
setDefaultDuration(newOptions[0]);
}
formMethods.setValue("metadata.multipleDuration", values);
}}

View File

@ -1,12 +0,0 @@
import { UserPlan } from "@prisma/client";
/**
* TODO: It should be exposed at a single place.
*/
export function useExposePlanGlobally(plan: UserPlan) {
// Don't wait for component to mount. Do it ASAP. Delaying it would delay UI Configuration.
if (typeof window !== "undefined") {
// This variable is used by embed-iframe to determine if we should allow UI configuration
window.CalComPlan = plan;
}
}

View File

@ -1,6 +1,6 @@
{
"name": "@calcom/web",
"version": "2.3.6",
"version": "2.3.8",
"private": true,
"scripts": {
"analyze": "ANALYZE=true next build",

View File

@ -28,7 +28,6 @@ import { baseEventTypeSelect } from "@calcom/prisma/selects";
import { EventTypeMetaDataSchema } from "@calcom/prisma/zod-utils";
import { BadgeCheckIcon, EventTypeDescriptionLazy as EventTypeDescription, Icon } from "@calcom/ui";
import { useExposePlanGlobally } from "@lib/hooks/useExposePlanGlobally";
import { inferSSRProps } from "@lib/types/inferSSRProps";
import { EmbedProps } from "@lib/withEmbedSsr";
@ -92,7 +91,6 @@ export default function User(props: inferSSRProps<typeof getServerSideProps> & E
const shouldAlignCentrally = !isEmbed || shouldAlignCentrallyInEmbed;
const query = { ...router.query };
delete query.user; // So it doesn't display in the Link (and make tests fail)
useExposePlanGlobally("PRO");
const nameOrUsername = user.name || user.username || "";
const telemetry = useTelemetry();
@ -272,7 +270,6 @@ export const getServerSideProps = async (context: GetServerSidePropsContext) =>
darkBrandColor: true,
avatar: true,
theme: true,
plan: true,
away: true,
verified: true,
allowDynamicBooking: true,

View File

@ -4,7 +4,7 @@ import { JSONObject } from "superjson/dist/types";
import { z } from "zod";
import { privacyFilteredLocations, LocationObject } from "@calcom/app-store/locations";
import { WEBAPP_URL } from "@calcom/lib/constants";
import { IS_TEAM_BILLING_ENABLED, WEBAPP_URL } from "@calcom/lib/constants";
import { getDefaultEvent, getGroupName, getUsernameList } from "@calcom/lib/defaultEvents";
import { useLocale } from "@calcom/lib/hooks/useLocale";
import { parseRecurringEvent } from "@calcom/lib/isRecurringEvent";
@ -72,7 +72,6 @@ async function getUserPageProps(context: GetStaticPropsContext) {
id: true,
username: true,
away: true,
plan: true,
name: true,
hideBranding: true,
timeZone: true,
@ -130,7 +129,7 @@ async function getUserPageProps(context: GetStaticPropsContext) {
if (!user || !user.eventTypes.length) return { notFound: true };
const [eventType]: (typeof user.eventTypes[number] & {
users: Pick<User, "name" | "username" | "hideBranding" | "plan" | "timeZone">[];
users: Pick<User, "name" | "username" | "hideBranding" | "timeZone">[];
})[] = [
{
...user.eventTypes[0],
@ -139,7 +138,6 @@ async function getUserPageProps(context: GetStaticPropsContext) {
name: user.name,
username: user.username,
hideBranding: user.hideBranding,
plan: user.plan,
timeZone: user.timeZone,
},
],
@ -159,9 +157,10 @@ async function getUserPageProps(context: GetStaticPropsContext) {
// Check if the user you are logging into has any active teams
const hasActiveTeam =
user.teams.filter((m) => {
if (!IS_TEAM_BILLING_ENABLED) return true;
const metadata = teamMetadataSchema.safeParse(m.team.metadata);
if (metadata.success && metadata.data?.subscriptionId) return false;
return true;
if (metadata.success && metadata.data?.subscriptionId) return true;
return false;
}).length > 0;
return {
@ -226,7 +225,6 @@ async function getDynamicGroupPageProps(context: GetStaticPropsContext) {
},
},
theme: true,
plan: true,
},
});
@ -246,7 +244,6 @@ async function getDynamicGroupPageProps(context: GetStaticPropsContext) {
name: user.name,
username: user.username,
hideBranding: user.hideBranding,
plan: user.plan,
timeZone: user.timeZone,
};
}),

View File

@ -1,5 +1,6 @@
import { DefaultSeo } from "next-seo";
import Head from "next/head";
import Script from "next/script";
import superjson from "superjson";
import "@calcom/embed-core/src/embed-iframe";
@ -36,8 +37,11 @@ function MyApp(props: AppProps) {
<AppProviders {...props}>
<DefaultSeo {...seoConfig.defaultNextSeo} />
<I18nLanguageHandler />
<Script
id="page-status"
dangerouslySetInnerHTML={{ __html: `window.CalComPageStatus = '${pageStatus}'` }}
/>
<Head>
<script dangerouslySetInnerHTML={{ __html: `window.CalComPageStatus = '${pageStatus}'` }} />
<meta name="viewport" content="width=device-width, initial-scale=1, maximum-scale=1" />
</Head>
{getLayout(

View File

@ -1,4 +1,5 @@
import Document, { DocumentContext, Head, Html, Main, NextScript, DocumentProps } from "next/document";
import Script from "next/script";
type Props = Record<string, unknown> & DocumentProps;
@ -69,7 +70,9 @@ class MyDocument extends Document<Props> {
{/* Define isEmbed here so that it can be shared with App(embed-iframe) as well as the following code to change background and hide body
Persist the embed mode in sessionStorage because query param might get lost during browsing.
*/}
<script
<Script
id="run-before-client"
strategy="beforeInteractive"
dangerouslySetInnerHTML={{
__html: `(${toRunBeforeReactOnClient.toString()})()`,
}}

View File

@ -14,7 +14,7 @@ import checkLicense from "@calcom/features/ee/common/server/checkLicense";
import ImpersonationProvider from "@calcom/features/ee/impersonation/lib/ImpersonationProvider";
import { hostedCal, isSAMLLoginEnabled } from "@calcom/features/ee/sso/lib/saml";
import { ErrorCode, isPasswordValid, verifyPassword } from "@calcom/lib/auth";
import { APP_NAME, WEBAPP_URL } from "@calcom/lib/constants";
import { APP_NAME, IS_TEAM_BILLING_ENABLED, WEBAPP_URL } from "@calcom/lib/constants";
import { symmetricDecrypt } from "@calcom/lib/crypto";
import { defaultCookies } from "@calcom/lib/default-cookies";
import rateLimit from "@calcom/lib/rateLimit";
@ -125,9 +125,10 @@ const providers: Provider[] = [
// Check if the user you are logging into has any active teams
const hasActiveTeams =
user.teams.filter((m) => {
if (!IS_TEAM_BILLING_ENABLED) return true;
const metadata = teamMetadataSchema.safeParse(m.team.metadata);
if (metadata.success && metadata.data?.subscriptionId) return false;
return true;
if (metadata.success && metadata.data?.subscriptionId) return true;
return false;
}).length > 0;
// authentication success- but does it meet the minimum password requirements?

View File

@ -46,7 +46,6 @@ async function handler(req: NextApiRequest) {
name: parsedQuery.data.full_name,
emailVerified: new Date(),
locale: "en", // TODO: We should revisit this
plan: "PRO",
identityProvider: IdentityProvider.CAL,
},
});

View File

@ -1,10 +1,5 @@
import type { NextApiRequest, NextApiResponse } from "next";
import dayjs from "@calcom/dayjs";
import prisma from "@calcom/prisma";
import { TRIAL_LIMIT_DAYS } from "@lib/config/constants";
export default async function handler(req: NextApiRequest, res: NextApiResponse) {
const apiKey = req.headers.authorization || req.query.apiKey;
if (process.env.CRON_API_KEY !== apiKey) {
@ -18,35 +13,8 @@ export default async function handler(req: NextApiRequest, res: NextApiResponse)
/**
* TODO:
* We should add and extra check for non-paying customers in Stripe so we can
* downgrade them here.
* Remove this endpoint
*/
await prisma.user.updateMany({
data: {
plan: "FREE",
},
where: {
plan: "TRIAL",
OR: [
/**
* If the user doesn't have a trial end date,
* use the default 14 day trial from creation.
*/
{
createdDate: {
lt: dayjs().subtract(TRIAL_LIMIT_DAYS, "day").toDate(),
},
trialEndsAt: null,
},
/** If it does, then honor the trial end date. */
{
trialEndsAt: {
lt: dayjs().toDate(),
},
},
],
},
});
res.json({ ok: true });
}

View File

@ -47,7 +47,6 @@ export default async function handler(req: NextApiRequest, res: NextApiResponse)
select: {
username: true,
id: true,
plan: true,
createdDate: true,
},
});
@ -81,7 +80,6 @@ export default async function handler(req: NextApiRequest, res: NextApiResponse)
? new Date(lastBooking.booking.createdAt).toLocaleDateString("en-US")
: "No info"
}</li>
<li><b>Plan:</b>&nbsp;${user.plan}</li>
<li><b>Account created:</b>&nbsp;${new Date(user.createdDate).toLocaleDateString("en-US")}</li>
</ul>
`,

View File

@ -57,26 +57,41 @@ function redirectToExternalUrl(url: string) {
function RedirectionToast({ url }: { url: string }) {
const [timeRemaining, setTimeRemaining] = useState(10);
const [isToastVisible, setIsToastVisible] = useState(true);
const parsedSuccessUrl = new URL(document.URL);
const parsedExternalUrl = new URL(url);
// eslint-disable-next-line @typescript-eslint/ban-ts-comment
/* @ts-ignore */ //https://stackoverflow.com/questions/49218765/typescript-and-iterator-type-iterableiteratort-is-not-an-array-type
for (const [name, value] of parsedExternalUrl.searchParams.entries()) {
parsedSuccessUrl.searchParams.set(name, value);
}
const urlWithSuccessParams =
parsedExternalUrl.origin +
parsedExternalUrl.pathname +
"?" +
parsedSuccessUrl.searchParams.toString() +
parsedExternalUrl.hash;
const { t } = useLocale();
const timerRef = useRef<number | null>(null);
const router = useRouter();
const { cancel: isCancellationMode } = querySchema.parse(router.query);
const urlWithSuccessParamsRef = useRef<string | null>();
if (isCancellationMode && timerRef.current) {
setIsToastVisible(false);
}
useEffect(() => {
if (!isToastVisible && timerRef.current) {
window.clearInterval(timerRef.current);
}
}, [isToastVisible]);
useEffect(() => {
const parsedExternalUrl = new URL(url);
const parsedSuccessUrl = new URL(document.URL);
// eslint-disable-next-line @typescript-eslint/ban-ts-comment
/* @ts-ignore */ //https://stackoverflow.com/questions/49218765/typescript-and-iterator-type-iterableiteratort-is-not-an-array-type
for (const [name, value] of parsedExternalUrl.searchParams.entries()) {
parsedSuccessUrl.searchParams.set(name, value);
}
const urlWithSuccessParams =
parsedExternalUrl.origin +
parsedExternalUrl.pathname +
"?" +
parsedSuccessUrl.searchParams.toString() +
parsedExternalUrl.hash;
urlWithSuccessParamsRef.current = urlWithSuccessParams;
timerRef.current = window.setInterval(() => {
if (timeRemaining > 0) {
setTimeRemaining((timeRemaining) => {
@ -90,7 +105,7 @@ function RedirectionToast({ url }: { url: string }) {
return () => {
window.clearInterval(timerRef.current as number);
};
}, [timeRemaining, urlWithSuccessParams]);
}, [timeRemaining, url]);
if (!isToastVisible) {
return null;
@ -113,7 +128,9 @@ function RedirectionToast({ url }: { url: string }) {
<div className="order-3 mt-2 w-full flex-shrink-0 sm:order-2 sm:mt-0 sm:w-auto">
<button
onClick={() => {
redirectToExternalUrl(urlWithSuccessParams);
if (urlWithSuccessParamsRef.current) {
redirectToExternalUrl(urlWithSuccessParamsRef.current);
}
}}
className="flex w-full items-center justify-center rounded-sm border border-transparent bg-white px-4 py-2 text-sm font-medium text-green-600 hover:bg-green-50">
{t("continue")}
@ -124,7 +141,6 @@ function RedirectionToast({ url }: { url: string }) {
type="button"
onClick={() => {
setIsToastVisible(false);
window.clearInterval(timerRef.current as number);
}}
className="-mr-1 flex rounded-md p-2 hover:bg-green-600 focus:outline-none focus:ring-2 focus:ring-white">
<Icon.FiX className="h-6 w-6 text-white" />
@ -349,7 +365,9 @@ export default function Success(props: SuccessProps) {
<CustomBranding lightVal={props.profile.brandColor} darkVal={props.profile.darkBrandColor} />
<main className={classNames(shouldAlignCentrally ? "mx-auto" : "", isEmbed ? "" : "max-w-3xl")}>
<div className={classNames("overflow-y-auto", isEmbed ? "" : "z-50 ")}>
{eventType.successRedirectUrl ? <RedirectionToast url={eventType.successRedirectUrl} /> : null}{" "}
{isSuccessBookingPage && !isCancellationMode && eventType.successRedirectUrl ? (
<RedirectionToast url={eventType.successRedirectUrl} />
) : null}{" "}
<div
className={classNames(
shouldAlignCentrally ? "text-center" : "",
@ -840,7 +858,6 @@ const getEventTypesFromDB = async (id: number) => {
name: true,
username: true,
hideBranding: true,
plan: true,
theme: true,
brandColor: true,
darkBrandColor: true,

View File

@ -90,7 +90,6 @@ export const getServerSideProps = async (context: GetServerSidePropsContext) =>
},
},
theme: true,
plan: true,
},
});
@ -117,7 +116,6 @@ export const getServerSideProps = async (context: GetServerSidePropsContext) =>
name: u.name,
username: u.username,
hideBranding: u.hideBranding,
plan: u.plan,
timeZone: u.timeZone,
})),
});
@ -158,7 +156,6 @@ export const getServerSideProps = async (context: GetServerSidePropsContext) =>
away: user.away,
isDynamicGroup: false,
profile,
plan: user.plan,
date,
eventType: eventTypeObject,
workingHours,

View File

@ -283,7 +283,7 @@ const EventTypePage = (props: inferSSRProps<typeof getServerSideProps>) => {
if (metadata?.multipleDuration.length < 1) {
throw new Error(t("event_setup_multiple_duration_error"));
} else {
if (!metadata?.multipleDuration?.includes(input.length)) {
if (input.length && !metadata?.multipleDuration?.includes(input.length)) {
throw new Error(t("event_setup_multiple_duration_default_error"));
}
}
@ -341,7 +341,6 @@ export const getServerSideProps = async (context: GetServerSidePropsContext) =>
id: true,
avatar: true,
email: true,
plan: true,
locale: true,
defaultScheduleId: true,
});

View File

@ -177,22 +177,17 @@ export const EventTypeList = ({ group, groupIndex, readOnly, types }: EventTypeL
}
// inject selection data into url for correct router history
const openModal = (group: EventTypeGroup, type: EventType) => {
const openDuplicateModal = (eventType: EventType) => {
const query = {
...router.query,
dialog: "new-eventtype",
eventPage: group.profile.slug,
title: type.title,
slug: type.slug,
description: type.description,
length: type.length,
type: type.schedulingType,
teamId: group.teamId,
locations: encodeURIComponent(JSON.stringify(type.locations)),
dialog: "duplicate-event-type",
title: eventType.title,
description: eventType.description,
slug: eventType.slug,
id: eventType.id,
length: eventType.length,
};
if (!group.teamId) {
delete query.teamId;
}
router.push(
{
pathname: router.pathname,
@ -361,7 +356,7 @@ export const EventTypeList = ({ group, groupIndex, readOnly, types }: EventTypeL
type="button"
data-testid={"event-type-duplicate-" + type.id}
StartIcon={Icon.FiCopy}
onClick={() => openModal(group, type)}>
onClick={() => openDuplicateModal(type)}>
{t("duplicate") as string}
</DropdownItem>
</DropdownMenuItem>
@ -469,7 +464,7 @@ export const EventTypeList = ({ group, groupIndex, readOnly, types }: EventTypeL
className="w-full rounded-none"
data-testid={"event-type-duplicate-" + type.id}
StartIcon={Icon.FiCopy}
onClick={() => openModal(group, type)}>
onClick={() => openDuplicateModal(type)}>
{t("duplicate") as string}
</Button>
</DropdownMenuItem>

View File

@ -164,7 +164,6 @@ export const getServerSideProps = async (context: GetServerSidePropsContext) =>
weekStart: true,
hideBranding: true,
theme: true,
plan: true,
brandColor: true,
darkBrandColor: true,
metadata: true,

View File

@ -36,7 +36,6 @@ const CtaRow = ({ title, description, className, children }: CtaRowProps) => {
const BillingView = () => {
const { t } = useLocale();
const { data: user } = trpc.viewer.me.useQuery();
const isPro = user?.plan === "PRO";
const [, loadChat] = useChat();
const [showChat, setShowChat] = useState(false);
const router = useRouter();
@ -53,14 +52,9 @@ const BillingView = () => {
<Meta title={t("billing")} description={t("manage_billing_description")} />
<div className="space-y-6 text-sm sm:space-y-8">
<CtaRow
className={classNames(!isPro && "pointer-events-none opacity-30")}
title={t("billing_manage_details_title")}
description={t("billing_manage_details_description")}>
<Button
color={isPro ? "primary" : "secondary"}
href={billingHref}
target="_blank"
EndIcon={Icon.FiExternalLink}>
<Button color="primary" href={billingHref} target="_blank" EndIcon={Icon.FiExternalLink}>
{t("billing_portal")}
</Button>
</CtaRow>

View File

@ -189,7 +189,7 @@ const AppearanceView = () => {
onCheckedChange={(checked) =>
formMethods.setValue("hideBranding", checked, { shouldDirty: true })
}
checked={value}
checked={!session.data?.user.belongsToActiveTeam ? false : value}
/>
</div>
</div>

View File

@ -1,4 +1,3 @@
import { UserPlan } from "@prisma/client";
import classNames from "classnames";
import { GetServerSidePropsContext } from "next";
import Link from "next/link";
@ -14,7 +13,6 @@ import { getTeamWithMembers } from "@calcom/lib/server/queries/teams";
import { collectPageParameters, telemetryEventTypes, useTelemetry } from "@calcom/lib/telemetry";
import { Avatar, Button, EventTypeDescription, Icon } from "@calcom/ui";
import { useExposePlanGlobally } from "@lib/hooks/useExposePlanGlobally";
import { useToggleQuery } from "@lib/hooks/useToggleQuery";
import { inferSSRProps } from "@lib/types/inferSSRProps";
@ -27,7 +25,6 @@ function TeamPage({ team }: TeamPageProps) {
useTheme();
const showMembers = useToggleQuery("members");
const { t } = useLocale();
useExposePlanGlobally("PRO");
const isEmbed = useIsEmbed();
const telemetry = useTelemetry();
const router = useRouter();
@ -137,10 +134,6 @@ export const getServerSideProps = async (context: GetServerSidePropsContext) =>
if (!team) return { notFound: true } as { notFound: true };
const members = team.members.filter((member) => member.plan !== UserPlan.FREE);
team.members = members ?? [];
team.eventTypes = team.eventTypes.map((type) => ({
...type,
users: type.users.map((user) => ({

View File

@ -1,4 +1,3 @@
import { UserPlan } from "@prisma/client";
import { GetServerSidePropsContext } from "next";
import { privacyFilteredLocations, LocationObject } from "@calcom/core/location";
@ -60,7 +59,6 @@ export const getServerSideProps = async (context: GetServerSidePropsContext) =>
username: true,
timeZone: true,
hideBranding: true,
plan: true,
brandColor: true,
darkBrandColor: true,
},
@ -130,7 +128,6 @@ export const getServerSideProps = async (context: GetServerSidePropsContext) =>
name: user.name,
username: user.username,
hideBranding: user.hideBranding,
plan: user.plan,
timeZone: user.timeZone,
})),
});
@ -144,8 +141,6 @@ export const getServerSideProps = async (context: GetServerSidePropsContext) =>
return {
props: {
// Team is always pro
plan: "PRO" as UserPlan,
profile: {
name: team.name || team.slug,
slug: team.slug,

View File

@ -13,7 +13,7 @@ test.describe.configure({ mode: "parallel" });
test.describe("free user", () => {
test.beforeEach(async ({ page, users }) => {
const free = await users.create({ plan: "FREE" });
const free = await users.create();
await page.goto(`/${free.username}`);
});
test.afterEach(async ({ users }) => {

View File

@ -1,7 +1,5 @@
import { expect } from "@playwright/test";
import { UserPlan } from "@prisma/client";
import { getPremiumMonthlyPlanPriceId } from "@calcom/app-store/stripepayment/lib/utils";
import stripe from "@calcom/features/ee/payments/server/stripe";
import { WEBAPP_URL } from "@calcom/lib/constants";

View File

@ -10,7 +10,7 @@ import {
test("dynamic booking", async ({ page, users }) => {
const pro = await users.create();
await pro.login();
const free = await users.create({ plan: "FREE" });
const free = await users.create({ username: "free" });
await page.goto(`/${pro.username}+${free.username}`);
await test.step("book an event first day in next month", async () => {

View File

@ -8,10 +8,10 @@ import { test } from "./lib/fixtures";
test.describe.configure({ mode: "parallel" });
test.describe("Event Types tests", () => {
test.describe("pro user", () => {
test.describe("user", () => {
test.beforeEach(async ({ page, users }) => {
const proUser = await users.create();
await proUser.login();
const user = await users.create();
await user.login();
await page.goto("/event-types");
// We wait until loading is finished
await page.waitForSelector('[data-testid="event-types"]');
@ -101,13 +101,13 @@ test.describe("Event Types tests", () => {
const params = new URLSearchParams(url);
expect(params.get("title")).toBe(firstTitle);
expect(params.get("slug")).toBe(firstSlug);
expect(params.get("slug")).toContain(firstSlug);
const formTitle = await page.inputValue("[name=title]");
const formSlug = await page.inputValue("[name=slug]");
expect(formTitle).toBe(firstTitle);
expect(formSlug).toBe(firstSlug);
expect(formSlug).toContain(firstSlug);
});
test("edit first event", async ({ page }) => {
const $eventTypes = page.locator("[data-testid=event-types] > li a");
@ -120,37 +120,7 @@ test.describe("Event Types tests", () => {
});
await page.locator("[data-testid=update-eventtype]").click();
const toast = await page.waitForSelector("div[class*='data-testid-toast-success']");
await expect(toast).toBeTruthy();
});
});
test.describe("free user", () => {
test.beforeEach(async ({ page, users }) => {
const free = await users.create({ plan: "FREE" });
await free.login();
await page.goto("/event-types");
// We wait until loading is finished
await page.waitForSelector('[data-testid="event-types"]');
});
test("has at least 2 events where first is enabled", async ({ page }) => {
const $eventTypes = page.locator("[data-testid=event-types] > li a");
const count = await $eventTypes.count();
expect(count).toBeGreaterThanOrEqual(2);
});
test("edit first event", async ({ page }) => {
const $eventTypes = page.locator("[data-testid=event-types] > li a");
const firstEventTypeElement = $eventTypes.first();
await firstEventTypeElement.click();
await page.waitForNavigation({
url: (url) => {
return !!url.pathname.match(/\/event-types\/.+/);
},
});
await page.locator("[data-testid=update-eventtype]").click();
const toast = await page.waitForSelector("div[class*='data-testid-toast-success']");
await expect(toast).toBeTruthy();
expect(toast).toBeTruthy();
});
});
});

View File

@ -1,6 +1,6 @@
import type { Page, WorkerInfo } from "@playwright/test";
import type Prisma from "@prisma/client";
import { Prisma as PrismaType, UserPlan } from "@prisma/client";
import { Prisma as PrismaType } from "@prisma/client";
import { hash } from "bcryptjs";
import dayjs from "@calcom/dayjs";
@ -238,7 +238,7 @@ const createUserFixture = (user: UserWithIncludes, page: Page) => {
};
};
type CustomUserOptsKeys = "username" | "password" | "plan" | "completedOnboarding" | "locale" | "name";
type CustomUserOptsKeys = "username" | "password" | "completedOnboarding" | "locale" | "name";
type CustomUserOpts = Partial<Pick<Prisma.User, CustomUserOptsKeys>> & { timeZone?: TimeZoneEnum };
// creates the actual user in the db.
@ -247,13 +247,10 @@ const createUser = async (
opts?: CustomUserOpts | null
): Promise<PrismaType.UserCreateInput> => {
// build a unique name for our user
const uname = `${opts?.username ?? opts?.plan?.toLocaleLowerCase() ?? UserPlan.PRO.toLowerCase()}-${
workerInfo.workerIndex
}-${Date.now()}`;
const uname = `${opts?.username || "user"}-${workerInfo.workerIndex}-${Date.now()}`;
return {
username: uname,
name: opts?.name === undefined ? (opts?.plan ?? UserPlan.PRO).toUpperCase() : opts?.name,
plan: opts?.plan ?? UserPlan.PRO,
name: opts?.name,
email: `${uname}@example.com`,
password: await hashPassword(uname),
emailVerified: new Date(),

View File

@ -1,5 +1,4 @@
import { expect } from "@playwright/test";
import { UserPlan } from "@prisma/client";
import { login } from "./fixtures/users";
import { test } from "./lib/fixtures";
@ -13,10 +12,10 @@ test.describe("user can login & logout succesfully", async () => {
test.afterAll(async ({ users }) => {
await users.deleteAll();
});
test("login flow TRAIL user & logout using dashboard", async ({ page, users }) => {
test("login flow user & logout using dashboard", async ({ page, users }) => {
// log in trail user
await test.step("Log in", async () => {
const user = await users.create({ plan: UserPlan.TRIAL });
const user = await users.create();
await user.login();
const shellLocator = page.locator(`[data-testid=dashboard-shell]`);
@ -24,11 +23,6 @@ test.describe("user can login & logout succesfully", async () => {
// expects the home page for an authorized user
await page.goto("/");
await expect(shellLocator).toBeVisible();
// Asserts to read the tested plan
const planLocator = shellLocator.locator(`[data-testid=plan-trial]`);
await expect(planLocator).toBeVisible();
await expect(planLocator).toHaveText("-TRIAL");
});
//
@ -64,27 +58,6 @@ test.describe("Login and logout tests", () => {
await expect(page.locator(`[data-testid=login-form]`)).toBeVisible();
});
// Test login with all plans
const plans = [UserPlan.PRO, UserPlan.FREE];
plans.forEach((plan) => {
test(`Should login with a ${plan} account`, async ({ page, users }) => {
// Create user and login
const user = await users.create({ plan });
await user.login();
const shellLocator = page.locator(`[data-testid=dashboard-shell]`);
// expects the home page for an authorized user
await page.goto("/");
await expect(shellLocator).toBeVisible();
// Asserts to read the tested plan
const planLocator = shellLocator.locator(`[data-testid=plan-${plan.toLowerCase()}]`);
await expect(planLocator).toBeVisible();
await expect(planLocator).toHaveText(`-${plan}`);
});
});
test.describe("Login flow validations", async () => {
test("Should warn when user does not exist", async ({ page }) => {
const alertMessage = (await localize("en"))("no_account_exists");

View File

@ -1,6 +1,5 @@
/* eslint-disable playwright/no-skipped-test */
import { expect } from "@playwright/test";
import { UserPlan } from "@prisma/client";
import { test } from "./lib/fixtures";
@ -9,7 +8,7 @@ test.describe.configure({ mode: "serial" });
test.describe("Onboarding", () => {
test.describe("Onboarding v2", () => {
test("Onboarding Flow", async ({ page, users }) => {
const user = await users.create({ plan: UserPlan.TRIAL, completedOnboarding: false, name: null });
const user = await users.create({ completedOnboarding: false, name: null });
await user.login();
// tests whether the user makes it to /getting-started

View File

@ -1 +1 @@
{"triggerEvent":"BOOKING_CREATED","createdAt":"[redacted/dynamic]","payload":{"type":"30 min","title":"30 min between PRO and Test Testson","description":"","additionalNotes":"","customInputs":{},"startTime":"[redacted/dynamic]","endTime":"[redacted/dynamic]","organizer":{"name":"PRO","email":"[redacted/dynamic]","timeZone":"[redacted/dynamic]","language":"[redacted/dynamic]"},"attendees":[{"email":"test@example.com","name":"Test Testson","timeZone":"[redacted/dynamic]","language":"[redacted/dynamic]"}],"location":"[redacted/dynamic]","destinationCalendar":null,"hideCalendarNotes":false,"requiresConfirmation":"[redacted/dynamic]","eventTypeId":"[redacted/dynamic]","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]"}}
{"triggerEvent":"BOOKING_CREATED","createdAt":"[redacted/dynamic]","payload":{"type":"30 min","title":"30 min between Nameless and Test Testson","description":"","additionalNotes":"","customInputs":{},"startTime":"[redacted/dynamic]","endTime":"[redacted/dynamic]","organizer":{"name":"Nameless","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]"}}

View File

@ -14,7 +14,7 @@
"event_request_cancelled": "Ihr geplanter Termin wurde abgesagt",
"organizer": "Organisator",
"need_to_reschedule_or_cancel": "Möchten Sie einen neuen Termin finden oder absagen?",
"cancellation_reason": "Grund für Ihre Absage",
"cancellation_reason": "Grund für Ihre Absage (optional)",
"cancellation_reason_placeholder": "Warum sagen Sie ab? (optional)",
"rejection_reason": "Grund für die Ablehnung",
"rejection_reason_title": "Die Buchungsanfrage ablehnen?",
@ -406,7 +406,7 @@
"forgotten_secret_description": "Wenn Sie dieses Passwort verloren oder vergessen haben, können Sie es ändern. Bitte beachten Sie, dass alle Integrationen, die dieses Geheimnis nutzen, aktualisiert werden müssen",
"current_incorrect_password": "Aktuelles Passwort ist falsch",
"password_hint_caplow": "Mischung aus Groß- und Kleinbuchstaben",
"password_hint_min": "Mindestens 7 Zeichen lang",
"password_hint_min": "Mindestens 8 Zeichen lang",
"password_hint_num": "Enthält mindestens 1 Zahl",
"invalid_password_hint": "Das Passwort muss mindestens 7 Zeichen lang sein und mindestens eine Nummer beinhalten, sowie eine Mischung aus Groß- und Kleinbuchstaben sein",
"incorrect_password": "Passwort ist falsch",
@ -746,6 +746,7 @@
"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",
@ -762,7 +763,7 @@
"analytics": "Analyse",
"empty_installed_apps_headline": "Keine Apps installiert",
"empty_installed_apps_description": "Apps erlauben es Ihnen, Ihren Arbeitsfluss zu verbessern und Ihre Planung viel besser zu gestalten.",
"empty_installed_apps_button": "Entdecken Sie den App Store",
"empty_installed_apps_button": "Durchsuchen Sie den App Store",
"manage_your_connected_apps": "Verwalten Sie Ihre installierten Apps oder ändern Sie die Einstellungen",
"browse_apps": "Apps durchsuchen",
"features": "Funktionen",
@ -784,6 +785,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",
"next_steps": "Nächste Schritte",
@ -894,6 +897,7 @@
"generate_api_key": "Api Key generieren",
"your_unique_api_key": "Ihr eindeutiger API-Key",
"copy_safe_api_key": "Kopieren Sie diesen API-Schlüssel und speichern Sie ihn an einem sicheren Ort. Wenn Sie diesen Schlüssel 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 Cal.com 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",
@ -1300,10 +1304,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...",
"free_to_use_apps": "Kostenlos",
"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.",
"add_calendar": "Kalender hinzufügen",
"limit_future_bookings": "Zukünftige Termine begrenzen",
"limit_future_bookings_description": "Begrenzt, wie weit in die Zukunft dieser Termin gebucht werden kann",
@ -1327,7 +1327,8 @@
"number_sms_notifications": "Telefonnummer (SMS-Benachrichtigungen)",
"attendee_email_workflow": "Teilnehmer E-Mail",
"attendee_email_info": "Die E-Mail-Adresse der buchenden Person",
"invalid_credential": "Oh nein! Es sieht so aus, als ob die Berechtigung abgelaufen sind oder entzogen wurden. Bitte erneut installieren.",
"kbar_search_placeholder": "Geben Sie einen Befehl ein oder suchen Sie...",
"invalid_credential": "Oh nein! Es sieht so aus, als ob die Berechtigung abgelaufen ist oder entzogen wurden. Bitte erneut installieren.",
"choose_common_schedule_team_event": "Gemeinsamen Zeitplan auswählen",
"choose_common_schedule_team_event_description": "Aktivieren Sie dies, wenn Sie einen gemeinsamen Zeitplan zwischen Hosts verwenden möchten. Wenn deaktiviert, ist jeder Host basierend auf seinen Standardplan buchbar.",
"reason": "Grund",
@ -1335,7 +1336,7 @@
"sender_id_error_message": "Nur Buchstaben, Zahlen und Leerzeichen erlaubt (max. 11 Zeichen)",
"test_routing_form": "Testweiterleitungsformular",
"test_preview": "Vorschau testen",
"route_to": "Route zu",
"test_preview_description": "Testen Sie Ihr Leitungsformular, ohne Daten zu senden",
"route_to": "Weiterleiten zu",
"test_preview_description": "Testen Sie Ihr Weiterleitungsformular, ohne Daten zu senden",
"test_routing": "Testweiterleitung"
}

View File

@ -750,7 +750,7 @@
"toggle_calendars_conflict": "Toggle the calendars you want to check for conflicts to prevent double bookings.",
"select_destination_calendar": "Create events on",
"connect_additional_calendar": "Connect additional calendar",
"calendar_updated_successfully":"Calendar updated successfully",
"calendar_updated_successfully": "Calendar updated successfully",
"conferencing": "Conferencing",
"calendar": "Calendar",
"payments": "Payments",
@ -940,7 +940,7 @@
"generate_api_key_description": "Generate an API key to use with {{appName}} at",
"your_unique_api_key": "Your unique API key",
"copy_safe_api_key": "Copy this API key and save it somewhere safe. If you lose this key you have to generate a new one.",
"zapier_setup_instructions": "<0>Log into your Zapier account and create a new Zap.</0><1>Select {{appName}} as your Trigger app. Also choose a Trigger event.</1><2>Choose your account and then enter your Unique API Key.</2><3>Test your Trigger.</3><4>You're set!</4>",
"zapier_setup_instructions": "<0>Log into your Zapier account and create a new Zap.</0><1>Select Cal.com as your Trigger app. Also choose a Trigger event.</1><2>Choose your account and then enter your Unique API Key.</2><3>Test your Trigger.</3><4>You're set!</4>",
"install_zapier_app": "Please first install the Zapier App in the app store.",
"connect_apple_server": "Connect to Apple Server",
"connect_caldav_server": "Connect to CalDav (Beta)",
@ -1388,6 +1388,7 @@
"number_sms_notifications": "Phone number (SMS\u00a0notifications)",
"attendee_email_workflow": "Attendee email",
"attendee_email_info": "The person booking's email",
"kbar_search_placeholder": "Type a command or search...",
"invalid_credential": "Oh no! Looks like permission expired or was revoked. Please reinstall again.",
"choose_common_schedule_team_event": "Choose a common schedule",
"choose_common_schedule_team_event_description": "Enable this if you want to use a common schedule between hosts. When disabled, each host will be booked based on their default schedule.",
@ -1408,7 +1409,7 @@
"add_one_fixed_attendee": "Add one fixed attendee and round robin through a number of attendees.",
"calcom_is_better_with_team": "Cal.com is better with teams",
"add_your_team_members": "Add your team members to your event types. Use collective scheduling to include everyone or find the most suitable person with round robin scheduling.",
"booking_limit_reached":"Booking Limit for this event type has been reached",
"booking_limit_reached": "Booking Limit for this event type has been reached",
"admin_has_disabled": "An admin has disabled {{appName}}",
"disabled_app_affects_event_type": "An admin has disabled {{appName}} which affects your event type {{eventType}}",
"disable_payment_app": "The admin has disabled {{appName}} which affects your event type {{title}}. Attendees are still able to book this type of event but will not be prompted to pay. You may hide hide the event type to prevent this until your admin renables your payment method.",
@ -1437,6 +1438,7 @@
"add_an_option": "Add an option",
"radio": "Radio",
"individual":"Individual",
"kbar_search_placeholder" : "Start typing to search",
"set_as_default": "Set as default"
"event_type_duplicate_copy_text": "{{slug}}-copy",
"set_as_default": "Set as default",
"hide_eventtype_details": "Hide EventType Details"
}

View File

@ -16,8 +16,8 @@
"event_request_cancelled": "Tu evento programado fue cancelado",
"organizer": "Organizador",
"need_to_reschedule_or_cancel": "¿Necesita reprogramar o cancelar?",
"cancellation_reason": "Motivo de la cancelación",
"cancellation_reason_placeholder": "¿Por qué razón estás cancelando? (opcional)",
"cancellation_reason": "Motivo de la cancelación (opcional)",
"cancellation_reason_placeholder": "¿Por qué razón está cancelando?",
"rejection_reason": "Motivo del rechazo",
"rejection_reason_title": "¿Rechazar la solicitud de reserva?",
"rejection_reason_description": "¿Estás seguro de que deseas rechazar la reserva? Le haremos saber a la persona que ha intentado reservar. Puedes indicar una razón a continuación.",
@ -1228,8 +1228,8 @@
"exchange_authentication_ntlm": "Autenticación NTLM",
"exchange_compression": "Compresión GZip",
"routing_forms_description": "Aquí puedes ver todos los formularios y rutas que has creado.",
"routing_forms_send_email_owner": "Enviar email al propietario",
"routing_forms_send_email_owner_description": "Envía un correo electrónico al propietario cuando se envía el formulario",
"routing_forms_send_email_owner": "Enviar correo electrónico al propietario",
"routing_forms_send_email_owner_description": "Mande un correo electrónico al propietario cuando se envía el formulario",
"add_new_form": "Añadir formulario nuevo",
"form_description": "Crea tu formulario para dirigirlo a un agente de reservas",
"copy_link_to_form": "Copiar enlace al formulario",
@ -1361,13 +1361,13 @@
"attendee_email_workflow": "Correo electrónico del asistente",
"attendee_email_info": "El correo electrónico de la persona que reserva",
"invalid_credential": "¡Oh no! Parece que el permiso ha caducado o se ha revocado. Vuelva a instalarlo.",
"choose_common_schedule_team_event": "Elija un horario común",
"choose_common_schedule_team_event_description": "Habilite esto si desea utilizar un cronograma común entre anfitriones. Cuando esté desactivado, cada anfitrión se reservará con base en su horario predeterminado.",
"choose_common_schedule_team_event": "Elige un horario común para todos",
"choose_common_schedule_team_event_description": "Habilite esto si desea utilizar un cronograma común entre anfitriones. Cuando esté desactivado, cada anfitrión se reservará en relación a su horario predeterminado.",
"reason": "Motivo",
"sender_id": "ID del remitente",
"sender_id_error_message": "Sólo se permiten letras, números y espacios (máx. 11 caracteres)",
"test_routing_form": "Prueba de formulario de ruta",
"test_preview": "Vista previa de prueba",
"test_preview": "Vista previa de la prueba",
"route_to": "Ruta hacia",
"test_preview_description": "Pruebe su formulario de ruta sin enviar ningún dato",
"test_routing": "Prueba de ruta"

View File

@ -16,8 +16,8 @@
"event_request_cancelled": "Votre événement programmé a été annulé",
"organizer": "Organisateur",
"need_to_reschedule_or_cancel": "Besoin de reprogrammer ou d'annuler ?",
"cancellation_reason": "Motif de l'annulation",
"cancellation_reason_placeholder": "Pourquoi annulez-vous ? (facultatif)",
"cancellation_reason": "Motif de l'annulation (facultatif)",
"cancellation_reason_placeholder": "Pourquoi annulez-vous ?",
"rejection_reason": "Motif de rejet",
"rejection_reason_title": "Rejeter la demande de réservation ?",
"rejection_reason_description": "Êtes-vous sûr de vouloir rejeter la réservation ? Nous informerons la personne qui a essayé de réserver. Vous pouvez fournir une raison ci-dessous.",
@ -52,6 +52,7 @@
"still_waiting_for_approval": "Un événement est toujours en attente d'approbation",
"event_is_still_waiting": "La demande d'événement est toujours en attente : {{attendeeName}} - {{date}} - {{eventType}}",
"no_more_results": "Plus de résultats",
"no_results": "Aucun résultat",
"load_more_results": "Charger plus de résultats",
"integration_meeting_id": "Identifiant de la réunion {{integrationName}} : {{meetingId}}",
"confirmed_event_type_subject": "Confirmé : {{eventType}} avec {{name}} le {{date}}",
@ -248,6 +249,7 @@
"add_to_calendar": "Ajouter au calendrier",
"add_another_calendar": "Ajouter un autre calendrier",
"other": "Autre",
"email_sign_in_subject": "Votre lien de connexion pour {{appName}}",
"emailed_you_and_attendees": "Nous vous avons envoyé, à vous et aux autres participants, une invitation par e-mail avec tous les détails.",
"emailed_you_and_attendees_recurring": "Nous vous avons envoyé, à vous et aux autres participants, une invitation au calendrier pour le premier de ces événements récurrents.",
"emailed_you_and_any_other_attendees": "Ces informations vous ont été envoyées par e-mail, ainsi qu'à tous les autres participants.",
@ -419,7 +421,8 @@
"forgotten_secret_description": "Si vous avez perdu ou oublié ce code secret, vous pouvez le modifer, mais soyez conscient que toutes les intégrations l'utilisant devront être actualisées",
"current_incorrect_password": "Le mot de passe actuel est incorrect",
"password_hint_caplow": "Mélange de lettres majuscules et minuscules",
"password_hint_min": "Minimum 7 caractères",
"password_hint_min": "Minimum 8 caractères",
"password_hint_admin_min": "Minimum 15 caractères",
"password_hint_num": "Contient au moins 1 chiffre",
"invalid_password_hint": "Le mot de passe doit comporter un minimum de 7 caractères, dont au moins un chiffre, et avoir un mélange de lettres majuscules et minuscules",
"incorrect_password": "Le mot de passe est incorrect.",
@ -463,11 +466,14 @@
"booking_confirmation": "Confirmez votre {{eventTypeTitle}} avec {{profileName}}",
"booking_reschedule_confirmation": "Reporté votre {{eventTypeTitle}} avec {{profileName}}",
"in_person_meeting": "Lien ou réunion en présentiel",
"attendeeInPerson": "En personne (adresse du participant)",
"inPerson": "En personne (adresse de l'organisateur)",
"link_meeting": "Lier une réunion",
"phone_call": "Appel téléphonique",
"your_number": "Votre numéro de téléphone",
"phone_number": "Numéro de téléphone",
"attendee_phone_number": "Numéro de téléphone du participant",
"organizer_phone_number": "Numéro de téléphone de l'organisateur",
"host_phone_number": "Votre numéro de téléphone",
"enter_phone_number": "Saisissez le numéro de téléphone",
"reschedule": "Reporter",
@ -554,6 +560,8 @@
"collective": "Collectif",
"collective_description": "Planifiez des réunions lorsque tous les membres de l'équipe sélectionnés sont disponibles.",
"duration": "Durée",
"available_durations": "Durées disponibles",
"default_duration": "Durée par défaut",
"minutes": "Minutes",
"round_robin": "Round Robin",
"round_robin_description": "Faites tourner les réunions entre plusieurs membres de l'équipe.",
@ -1228,8 +1236,8 @@
"exchange_authentication_ntlm": "Authentification NTLM",
"exchange_compression": "Compression GZip",
"routing_forms_description": "Vous pouvez voir tous les formulaires et tous les parcours que vous avez créés ici.",
"routing_forms_send_email_owner": "Envoyer un e-mail au responsable",
"routing_forms_send_email_owner_description": "Envoie un e-mail au responsable lorsque le formulaire est soumis",
"routing_forms_send_email_owner": "Envoyer un e-mail au propriétaire",
"routing_forms_send_email_owner_description": "Envoie un e-mail au propriétaire lorsque le formulaire est envoyé",
"add_new_form": "Ajouter un nouveau formulaire",
"form_description": "Créez votre formulaire pour acheminer un organisateur",
"copy_link_to_form": "Copier le lien vers le formulaire",
@ -1296,7 +1304,7 @@
"fetching_calendars_error": "Un problème est survenu lors de la récupération de vos calendriers. Veuillez <1>réessayer</1> ou contacter le service client.",
"calendar_connection_fail": "Échec de la connexion au calendrier",
"booking_confirmation_success": "Confirmation de réservation réussie",
"booking_rejection_success": "Rejet de réservation réussi",
"booking_rejection_success": "Refus de réservation réussi",
"booking_confirmation_fail": "Échec de la confirmation de la réservation",
"we_wont_show_again": "Nous n'afficherons plus ceci",
"couldnt_update_timezone": "Nous n'avons pas pu mettre à jour le fuseau horaire",
@ -1346,21 +1354,21 @@
"monthly": "Mensuellement",
"yearly": "Annuellement",
"checkout": "Paiement",
"your_team_disbanded_successfully": "Votre équipe a été dissoute avec succès",
"your_team_disbanded_successfully": "Votre équipe a bien été dissoute",
"error_creating_team": "Erreur lors de la création de l'équipe",
"you": "Vous",
"send_email": "Envoyer un e-mail",
"member_already_invited": "Le membre a déjà été invité",
"enter_email_or_username": "Saisissez une adresse e-mail ou un nom d'utilisateur",
"team_name_taken": "Ce nom est déjà pris",
"must_enter_team_name": "Vous devez entrer un nom d'équipe",
"team_url_required": "Vous devez entrer une URL d'équipe",
"must_enter_team_name": "Vous devez saisir un nom d'équipe",
"team_url_required": "Vous devez saisir une URL d'équipe",
"team_url_taken": "Cette URL est déjà prise",
"team_publish": "Publier l'équipe",
"number_sms_notifications": "Numéro de téléphone (notifications SMS)",
"attendee_email_workflow": "E-mail du participant",
"attendee_email_info": "E-mail de la personne ayant réservé",
"invalid_credential": "Oh non ! Il semble que lautorisation ait expiré ou a été révoquée. Veuillez la réinstaller.",
"invalid_credential": "Oh non ! L'autorisation semble avoir expiré ou avoir été révoquée. Veuillez la réinstaller.",
"choose_common_schedule_team_event": "Choisissez un horaire commun",
"choose_common_schedule_team_event_description": "Activez cette option si vous souhaitez utiliser un horaire commun entre les hôtes. Si désactivée, chaque hôte sera réservé en fonction de son planning par défaut.",
"reason": "Raison",

View File

@ -16,8 +16,8 @@
"event_request_cancelled": "Il tuo evento programmato è stato cancellato",
"organizer": "Organizzatore",
"need_to_reschedule_or_cancel": "È necessario riprogrammare o annullare?",
"cancellation_reason": "Motivo dell'annullamento",
"cancellation_reason_placeholder": "Perché stai annullando? (facoltativo)",
"cancellation_reason": "Motivo dell'annullamento (facoltativo)",
"cancellation_reason_placeholder": "Perché stai annullando?",
"rejection_reason": "Motivo del rifiuto",
"rejection_reason_title": "Rifiutare la richiesta di prenotazione?",
"rejection_reason_description": "Sei sicuro di voler rifiutare la prenotazione? Informeremo la persona che ha cercato di prenotare. Puoi indicare il motivo qui sotto.",
@ -52,6 +52,7 @@
"still_waiting_for_approval": "Un evento è ancora in attesa di approvazione",
"event_is_still_waiting": "La richiesta d'evento è ancora in attesa: {{attendeeName}} - {{date}} - {{eventType}}",
"no_more_results": "Nessun altro risultato",
"no_results": "Nessun risultato",
"load_more_results": "Carica più risultati",
"integration_meeting_id": "{{integrationName}} meeting ID: {{meetingId}}",
"confirmed_event_type_subject": "Confermato: {{eventType}} con {{name}} il {{date}}",
@ -248,6 +249,7 @@
"add_to_calendar": "Aggiungi al calendario",
"add_another_calendar": "Aggiungi un altro calendario",
"other": "Altro",
"email_sign_in_subject": "Link di accesso a {{appName}}",
"emailed_you_and_attendees": "Abbiamo inviato via email a te e agli altri partecipanti un invito al calendario con tutti i dettagli.",
"emailed_you_and_attendees_recurring": "Abbiamo inviato via email a te e agli altri partecipanti un invito al primo di questi eventi ricorrenti.",
"emailed_you_and_any_other_attendees": "Tu e tutti gli altri partecipanti siete stati inviati via email con queste informazioni.",
@ -419,7 +421,8 @@
"forgotten_secret_description": "Nel caso la parola segreta venga persa o dimenticata, è possibile cambiarla, ma tieni presente che tutte le integrazioni che la utilizzano dovranno essere aggiornate",
"current_incorrect_password": "La password attuale non è corretta",
"password_hint_caplow": "Combinazione di caratteri maiuscoli e minuscoli",
"password_hint_min": "La lunghezza minima è di 7 caratteri",
"password_hint_min": "La lunghezza minima è di 8 caratteri",
"password_hint_admin_min": "La lunghezza minima è di 15 caratteri",
"password_hint_num": "Deve contenere almeno 1 numero",
"invalid_password_hint": "La password deve avere una lunghezza minima di 7 caratteri e deve contenere almeno 1 numero e una combinazione di caratteri maiuscoli e minuscoli",
"incorrect_password": "La password non è corretta.",
@ -463,11 +466,14 @@
"booking_confirmation": "Conferma il tuo {{eventTypeTitle}} con {{profileName}}",
"booking_reschedule_confirmation": "Riprogramma il tuo {{eventTypeTitle}} con {{profileName}}",
"in_person_meeting": "Link o riunione di persona",
"attendeeInPerson": "Di persona (indirizzo del partecipante)",
"inPerson": "Di persona (indirizzo dell'organizzatore)",
"link_meeting": "Collegamento riunione",
"phone_call": "Numero di telefono del partecipante",
"your_number": "Il tuo numero di telefono",
"phone_number": "Numero Di Telefono",
"attendee_phone_number": "Numero di telefono del partecipante",
"organizer_phone_number": "Numero di telefono organizzatore",
"host_phone_number": "Il tuo numero di telefono",
"enter_phone_number": "Inserisci numero di telefono",
"reschedule": "Riprogramma",
@ -504,7 +510,7 @@
"add_team_members_description": "Invita altri utenti a unirsi al tuo team",
"add_team_member": "Aggiungi un membro del team",
"invite_new_member": "Invita un nuovo membro",
"invite_new_member_description": "Nota: il <1>costo di un posto aggiuntivo (15$/m)</1> verrà addebitato sul tuo abbonamento.",
"invite_new_member_description": "Nota: Sul tuo abbonamento verrà addebitato <1>un posto aggiuntivo (15$/m)</1>.",
"invite_new_team_member": "Invita qualcuno nel tuo team.",
"change_member_role": "Cambia il ruolo del membro del team",
"disable_cal_branding": "Disabilita il branding {{appName}}",
@ -554,6 +560,10 @@
"collective": "Collettivo",
"collective_description": "Pianifica le riunioni quando tutti i membri del team selezionati sono disponibili.",
"duration": "Durata",
"available_durations": "Durate disponibili",
"default_duration": "Durata predefinita",
"default_duration_no_options": "Scegliere prima delle durate disponibili",
"multiple_duration_mins": "{{count}} $t(minute_timeUnit)",
"minutes": "Minuti",
"round_robin": "Round Robin",
"round_robin_description": "Ciclo di riunioni tra più membri del team.",
@ -601,7 +611,7 @@
"monthly_other": "mesi",
"yearly_one": "anno",
"yearly_other": "anni",
"plus_more": "altri {{count}}",
"plus_more": "Altri {{count}}",
"max": "Max",
"single_theme": "Tema Singolo",
"brand_color": "Colore del Marchio",
@ -619,6 +629,7 @@
"teams": "Team",
"team": "Team",
"team_billing": "Fatturazione team",
"team_billing_description": "Gestisci la fatturazione per il tuo team",
"upgrade_to_flexible_pro_title": "Abbiamo cambiato la fatturazione per i team",
"upgrade_to_flexible_pro_message": "Alcuni membri del tuo team non hanno un posto. Aggiorna il tuo piano Pro per coprire i posti mancanti.",
"changed_team_billing_info": "A partire da gennaio 2022, applichiamo una tariffazione in base ai posti per i membri dei team. I membri del tuo team che utilizzavano Pro gratuitamente dispongono ora di un periodo di prova di 14 giorni. Terminata la prova, questi membri saranno nascosti dal tuo team, se non aggiornerai il piano.",
@ -694,6 +705,7 @@
"hide_event_type": "Nascondi tipo evento",
"edit_location": "Aggiungi un luogo",
"into_the_future": "nel futuro",
"when_booked_with_less_than_notice": "Quando si prenota con un avviso inferiore a <time></time>",
"within_date_range": "All'interno di un intervallo di date",
"indefinitely_into_future": "Indefinitamente nel futuro",
"add_new_custom_input_field": "Aggiungi un nuovo campo di input personalizzato",
@ -713,6 +725,8 @@
"delete_account_confirmation_message": "Sei sicuro di voler eliminare il tuo account {{appName}}? Chiunque abbia il link del tuo account non potrà più utilizzarlo per prenotare e le preferenze che hai salvato andranno perse.",
"integrations": "Integrazioni",
"apps": "App",
"apps_description": "Abilita le app per la tua istanza di Cal",
"apps_listing": "Elenco delle app",
"category_apps": "App {{category}}",
"app_store": "App Store",
"app_store_description": "Collegare persone, tecnologia e luogo di lavoro.",
@ -736,6 +750,7 @@
"toggle_calendars_conflict": "Attiva/disattiva i calendari in cui desideri controllare i conflitti per evitare doppie prenotazioni.",
"select_destination_calendar": "Crea eventi su",
"connect_additional_calendar": "Collega calendario aggiuntivo",
"calendar_updated_successfully": "Calendario aggiornato correttamente",
"conferencing": "Conferenze",
"calendar": "Calendario",
"payments": "Pagamenti",
@ -768,6 +783,7 @@
"trending_apps": "App di tendenza",
"explore_apps": "App {{category}}",
"installed_apps": "App installate",
"free_to_use_apps": "Gratuito",
"no_category_apps": "Nessuna app {{category}}",
"no_category_apps_description_calendar": "Aggiungi un'app di calendario per controllare i conflitti ed evitare doppie prenotazioni",
"no_category_apps_description_conferencing": "Prova ad aggiungere un'app di conferenza per integrare le videochiamate con i clienti",
@ -784,7 +800,7 @@
"analytics": "Analisi",
"empty_installed_apps_headline": "Nessuna app installata",
"empty_installed_apps_description": "Le app consentono di ottimizzare il flusso di lavoro e di migliorare significativamente la pianificazione.",
"empty_installed_apps_button": "Esplora l'App Store o installa dalle app sottostanti",
"empty_installed_apps_button": "Esplora l'App Store",
"manage_your_connected_apps": "Gestisci le app installate o modifica le impostazioni",
"browse_apps": "Sfoglia app",
"features": "Funzionalità",
@ -806,6 +822,8 @@
"verify_wallet": "Verifica Wallet",
"connect_metamask": "Connetti Metamask",
"create_events_on": "Crea eventi su:",
"enterprise_license": "Questa è una funzione Enterprise",
"enterprise_license_description": "Per abilitare questa funzione, ottenere una chiave di distribuzione presso la console {{consoleUrl}} e aggiungerla al proprio .env come CALCOM_LICENSE_KEY. Nel caso il team possieda già una licenza, contattare {{supportMail}} per assistenza.",
"missing_license": "Licenza mancante",
"signup_requires": "È necessaria una licenza commerciale",
"signup_requires_description": "{{companyName}} attualmente non offre una versione open source gratuita della pagina di registrazione. Per avere accesso completo ai componenti di registrazione è necessario acquisire una licenza commerciale. Per un uso personale consigliamo Prisma Data Platform o qualsiasi altra interfaccia Postgres per creare account.",
@ -897,6 +915,7 @@
"user_impersonation_heading": "Accesso con altra identità",
"user_impersonation_description": "Consente al nostro team di supporto di effettuare l'accesso temporaneo a tuo nome per risolvere rapidamente gli eventuali problemi da te segnalati.",
"team_impersonation_description": "Consente ai membri del tuo team di accedere temporaneamente con il tuo nome.",
"allow_booker_to_select_duration": "Consenti alla persona che prenota di selezionare la durata",
"impersonate_user_tip": "Ogni utilizzo di questa funzionalità viene controllato.",
"impersonating_user_warning": "Accesso con l'identità dell'utente \"{{user}}\".",
"impersonating_stop_instructions": "<0>Fai clic qui per interrompere</0>.",
@ -1014,6 +1033,9 @@
"error_removing_app": "Errore durante la rimozione dell'app",
"web_conference": "Conferenza Web",
"requires_confirmation": "Richiede conferma",
"always_requires_confirmation": "Sempre",
"requires_confirmation_threshold": "Richiede conferma se si prenota con un avviso di < {{time}} $t({{unit}}_timeUnit)",
"may_require_confirmation": "Può richiedere conferma",
"nr_event_type_one": "{{count}} tipo di evento",
"nr_event_type_other": "{{count}} tipi di evento",
"add_action": "Aggiungi azione",
@ -1101,6 +1123,9 @@
"event_limit_tab_description": "Con quale frequenza è possibile fare una prenotazione",
"event_advanced_tab_description": "Impostazioni calendario e altro...",
"event_advanced_tab_title": "Avanzate",
"event_setup_multiple_duration_error": "Impostazione evento: In caso di più durate è richiesta almeno 1 opzione.",
"event_setup_multiple_duration_default_error": "Impostazione evento: Selezionare una durata predefinita valida.",
"event_setup_booking_limits_error": "I limiti per le prenotazioni devono essere specificati in ordine crescente [giorno,settimana,mese,anno]",
"select_which_cal": "Seleziona il calendario al quale aggiungere prenotazioni",
"custom_event_name": "Nome evento personalizzato",
"custom_event_name_description": "Crea nomi di eventi personalizzati da visualizzare nell'evento del calendario",
@ -1154,8 +1179,11 @@
"invoices": "Fatture",
"embeds": "Integrazioni",
"impersonation": "Accesso con altra identità",
"impersonation_description": "Impostazioni per gestire gli accessi con altra identità",
"users": "Utenti",
"profile_description": "Gestisci le impostazioni del tuo profilo {{appName}}",
"users_description": "Qui è possibile trovare l'elenco di tutti gli utenti",
"users_listing": "Elenco degli utenti",
"general_description": "Gestisci le impostazioni di lingua e fuso orario",
"calendars_description": "Configura come i tipi di eventi devono interagire con i tuoi calendari",
"appearance_description": "Gestisci le impostazioni di aspetto delle tue prenotazioni",
@ -1345,21 +1373,22 @@
"billing_frequency": "Frequenza di fatturazione",
"monthly": "Mensile",
"yearly": "Annuale",
"checkout": "Checkout",
"checkout": "Pagamento",
"your_team_disbanded_successfully": "Il tuo team è stato sciolto correttamente",
"error_creating_team": "Errore durante la creazione del team",
"you": "Tu",
"send_email": "Invia e-mail",
"member_already_invited": "Membro già invitato",
"enter_email_or_username": "Immettere un indirizzo e-mail o un nome utente",
"team_name_taken": "Nome già occupato",
"must_enter_team_name": "Immettere un nome del team",
"team_url_required": "Immettere un URL del team",
"team_name_taken": "Nome già utilizzato",
"must_enter_team_name": "Necessario immettere un nome per il team",
"team_url_required": "Immettere un URL per il team",
"team_url_taken": "URL già occupato",
"team_publish": "Pubblica team",
"number_sms_notifications": "Numero di telefono (notifiche SMS)",
"attendee_email_workflow": "E-mail partecipante",
"attendee_email_info": "E-mail della persona che prenota",
"kbar_search_placeholder": "Digitare un comando o eseguire una ricerca...",
"invalid_credential": "Errore! Sembra che l'autorizzazione sia scaduta o che sia stata revocata. Prova a reinstallare.",
"choose_common_schedule_team_event": "Scegli un programma comune",
"choose_common_schedule_team_event_description": "Abilita questa opzione se desideri utilizzare un programma comune per tutti gli organizzatori. Quando l'opzione è disabilitata, ciascun organizzatore verrà prenotato in base al proprio programma predefinito.",
@ -1370,5 +1399,10 @@
"test_preview": "Test anteprima",
"route_to": "Instrada a",
"test_preview_description": "Esegui un test del modulo di instradamento senza inviare alcun dato",
"test_routing": "Test instradamento"
"test_routing": "Test instradamento",
"payment_app_disabled": "Un amministratore ha disabilitato un'app di pagamento",
"edit_event_type": "Modifica tipo di evento",
"collective_scheduling": "Pianificazione collettiva",
"make_it_easy_to_book": "Facilita la prenotazione del tuo team quando tutti sono disponibili.",
"find_the_best_person": "Trova la persona più adatta disponibile e passa tra i membri del team."
}

View File

@ -16,8 +16,8 @@
"event_request_cancelled": "スケジュールされていた予定がキャンセルされました",
"organizer": "主催者",
"need_to_reschedule_or_cancel": "再スケジュールまたはキャンセルが必要ですか?",
"cancellation_reason": "キャンセルの理由",
"cancellation_reason_placeholder": "キャンセルが必要な理由を教えください (任意)",
"cancellation_reason": "キャンセルの理由 (オプション)",
"cancellation_reason_placeholder": "キャンセルが必要な理由を教えください",
"rejection_reason": "拒否の理由",
"rejection_reason_title": "予約のリクエストを拒否しますか?",
"rejection_reason_description": "この予約を拒否してよろしいですか?予約を試みたユーザーに通知を送ります。拒否の理由は、下にご記入いただけます。",
@ -108,7 +108,7 @@
"link_expires": "追記: 有効期限は {{expiresIn}} 時間後に切れます。",
"upgrade_to_per_seat": "座席ごとにアップグレードする",
"team_upgrade_seats_details": "あなたのチームに所属する {{memberCount}} 人のメンバーのうち、{{unpaidCount}} 名分のライセンスが未払いとなっています。ライセンスごとの料金は、${{seatPrice}} / 月ですので、あなたのメンバーシップの合計金額は、${{totalCost}} / 月になると予想されます。",
"team_upgrade_banner_description": "新しいチームプランをお試しいただきありがとうございます。あなたのチーム「{{teamName}}」はアップグレードが必要なようです。",
"team_upgrade_banner_description": "新しいチームプランをお試しいただきありがとうございます。あなたのチーム「{{teamName}}」アップグレードが必要なようです。",
"team_upgrade_banner_action": "アップグレードはこちら",
"team_upgraded_successfully": "チームのアップグレードが正常に完了しました!",
"use_link_to_reset_password": "以下のリンクを使用してパスワードをリセットしてください",
@ -419,7 +419,7 @@
"forgotten_secret_description": "このシークレットを紛失または忘れた場合は変更が可能です。しかしこのシークレットを使っている連携はすべて最新のものにする必要があります",
"current_incorrect_password": "現在のパスワードが正しくありません",
"password_hint_caplow": "アルファベットの大文字と小文字を両方使用してください",
"password_hint_min": "7 文字以上入力してください",
"password_hint_min": "8 文字以上入力してください",
"password_hint_num": "少なくとも 1 文字は数字を含めてください",
"invalid_password_hint": "パスワードには少なくとも 1 文字以上数字を含め、アルファベットの大文字と小文字を両方使用した上で 7 文字以上の長さに設定してください",
"incorrect_password": "パスワードが正しくありません。",
@ -504,7 +504,7 @@
"add_team_members_description": "他のユーザーをチームに招待する",
"add_team_member": "チームメンバーを追加",
"invite_new_member": "新しいチームメンバーを招待",
"invite_new_member_description": "注意:これによりサブスクリプションに<1>追加座席分月15ドルの費用が加算されます。</1>",
"invite_new_member_description": "注: これにより、サブスクリプションに<1>追加の座席料金 (15 米ドル/メンバー) が加算されます。</1>",
"invite_new_team_member": "チームに誰かを招待する。",
"change_member_role": "チームメンバーの役割を変更する",
"disable_cal_branding": "{{appName}} のブランディングを無効化",
@ -601,7 +601,7 @@
"monthly_other": "月",
"yearly_one": "年",
"yearly_other": "年",
"plus_more": "{{count}} 回以上",
"plus_more": "あと {{count}} 回",
"max": "最大",
"single_theme": "シングルテーマ",
"brand_color": "ブランドカラー",
@ -784,7 +784,7 @@
"analytics": "分析",
"empty_installed_apps_headline": "アプリがインストールされていません",
"empty_installed_apps_description": "アプリを使えば、ワークフローの質を高め、スケジュールライフを大幅に改善することができます。",
"empty_installed_apps_button": "App Store で探す",
"empty_installed_apps_button": "App Store を見る",
"manage_your_connected_apps": "インストール済みアプリの管理や設定の変更を行う",
"browse_apps": "アプリを閲覧",
"features": "特徴",
@ -1229,7 +1229,7 @@
"exchange_compression": "GZip 圧縮",
"routing_forms_description": "作成したすべてのフォームとルートをここで確認できます。",
"routing_forms_send_email_owner": "所有者にメールを送信",
"routing_forms_send_email_owner_description": "フォーム送信時に所有者にメールを送信します",
"routing_forms_send_email_owner_description": "フォーム送信時に所有者にメールを送信します",
"add_new_form": "新しいフォームを追加",
"form_description": "予約者をルーティングするフォームを作成します",
"copy_link_to_form": "フォームへのリンクをコピー",
@ -1296,7 +1296,7 @@
"fetching_calendars_error": "カレンダーの取得中にエラーが発生しました。<1>もう一度お試しいただく</1>か、カスタマーサポートまでお問い合わせください。",
"calendar_connection_fail": "カレンダーの接続に失敗しました",
"booking_confirmation_success": "正常に予約を確認しました",
"booking_rejection_success": "正常に予約を拒否しました",
"booking_rejection_success": "予約を正常に拒否しました",
"booking_confirmation_fail": "予約の確認に失敗しました",
"we_wont_show_again": "次回から表示しない",
"couldnt_update_timezone": "タイムゾーンを更新できませんでした",
@ -1342,33 +1342,33 @@
"limit_future_bookings_description": "今後いつまでこのイベントを予約できるかを設定します",
"no_event_types": "イベントタイプは設定されていません",
"no_event_types_description": "{{name}}はあなたが予約するイベントのタイプを設定していません。",
"billing_frequency": "請求頻度",
"monthly": "ごと",
"yearly": "ごと",
"checkout": "精算",
"your_team_disbanded_successfully": "チームは正常に解散ました",
"billing_frequency": "請求頻度",
"monthly": "月",
"yearly": "年",
"checkout": "チェックアウト",
"your_team_disbanded_successfully": "チームは正常に解散されました",
"error_creating_team": "チームの作成中にエラーが発生しました",
"you": "あなた",
"send_email": "メールを送信",
"member_already_invited": "メンバーは既に招待されています",
"enter_email_or_username": "メールアドレスまたはユーザー名を入力してください",
"team_name_taken": "この名前は既に使用されています",
"must_enter_team_name": "チーム名を入力する必要があります",
"team_url_required": "チームの URL を入力する必要があります",
"must_enter_team_name": "チーム名の入力は必須です",
"team_url_required": "チームの URL の入力は必須です",
"team_url_taken": "この URL は既に使用されています",
"team_publish": "チームを公開",
"number_sms_notifications": "電話番号SMS 通知)",
"attendee_email_workflow": "出席者のメールアドレス",
"attendee_email_info": "予約者のメールアドレス",
"invalid_credential": "おっと!許可が期限切れになったか、取り消されたようです。もう一度インストールし直してください。",
"invalid_credential": "アクセス許可の有効期限が切れたか、取り消されたようです。もう一度インストールしてください。",
"choose_common_schedule_team_event": "共通のスケジュールを選択",
"choose_common_schedule_team_event_description": "ホスト間で共通のスケジュールを使用する場合は、これを有効にします。無効にすると、各ホストはデフォルトのスケジュールに基づいて予約されます。",
"choose_common_schedule_team_event_description": "ホスト間で共通のスケジュールを使用する場合には、これを有効にしてください。無効にすると、各ホストに対してはデフォルトのスケジュールに基づいて予約が行われます。",
"reason": "理由",
"sender_id": "送信者 ID",
"sender_id_error_message": "文字、数字、スペースのみ使用できます(最大 11 文字)",
"sender_id_error_message": "文字、数字、スペースのみが使用可能です (最大 11 文字)",
"test_routing_form": "ルーティングフォームをテスト",
"test_preview": "テストプレビュー",
"route_to": "ルー先",
"test_preview": "プレビューをテスト",
"route_to": "ルーティング先",
"test_preview_description": "データを送信せずにルーティングフォームをテストします",
"test_routing": "ルーティングをテスト"
}

View File

@ -16,8 +16,8 @@
"event_request_cancelled": "회의 일정이 취소되었습니다.",
"organizer": "주최자",
"need_to_reschedule_or_cancel": "다시 일정을 잡거나 취소하시겠습니까?",
"cancellation_reason": "취소 사유",
"cancellation_reason_placeholder": "왜 취소를 선택하셨나요? (선택사항)",
"cancellation_reason": "취소 사유(선택사항)",
"cancellation_reason_placeholder": "취소하는 이유가 무엇인가요?",
"rejection_reason": "거부 사유",
"rejection_reason_title": "예약 요청을 거부하시겠습니까?",
"rejection_reason_description": "예약을 거부하시겠습니까? 예약하려고 시도하셨던 다른 분께 알려드릴 것입니다. 해당 사유는 아래에서 제공하실 수 있습니다.",
@ -52,6 +52,7 @@
"still_waiting_for_approval": "이벤트가 아직 승인을 기다리고 있습니다.",
"event_is_still_waiting": "이벤트 요청이 아직 대기중입니다: {{attendeeName}} - {{date}} - {{eventType}}",
"no_more_results": "더 이상 결과 없음",
"no_results": "결과 없음",
"load_more_results": "더 많은 결과 보기",
"integration_meeting_id": "{{integrationName}} 회의 ID: {{meetingId}}",
"confirmed_event_type_subject": "확인됨: {{date}}, {{name}} 의 {{eventType}}",
@ -108,8 +109,8 @@
"link_expires": "{{expiresIn}} 시간 안에 만료됩니다.",
"upgrade_to_per_seat": "시트 단위로 업그레이드",
"team_upgrade_seats_details": "귀하의 팀원 {{memberCount}}명 중 {{unpaidCount}}명의 시트 비용이 미지급되었습니다. 시트당 ${{seatPrice}}/m로 계산시, 예상 총 멤버십 비용은 ${{totalCost}}/m입니다.",
"team_upgrade_banner_description": "당사의 새로운 팀 플랜을 사용해 주셔서 감사합니다. \"{{teamName}}\" 팀을 업그레이드해야 합니다.",
"team_upgrade_banner_action": "여기서 업그레이드",
"team_upgrade_banner_description": "당사의 새로운 팀 플랜을 사용해 주셔서 감사합니다. 귀하의 \"{{teamName}}\" 팀에 업그레이드가 필요한 것으로 확인되었습니다.",
"team_upgrade_banner_action": "여기서 업그레이드",
"team_upgraded_successfully": "팀이 성공적으로 업그레이드되었습니다!",
"use_link_to_reset_password": "비밀번호를 재설정하시려면 아래 링크를 사용하세요.",
"hey_there": "안녕하세요.",
@ -248,6 +249,7 @@
"add_to_calendar": "캘린더에 추가하기",
"add_another_calendar": "다른 캘린더 추가",
"other": "더 보기",
"email_sign_in_subject": "{{appName}}의 로그인 링크",
"emailed_you_and_attendees": "당신과 다른 참여자들에게 상세 사항이 담긴 캘린더 초대 이메일을 보냈습니다.",
"emailed_you_and_attendees_recurring": "귀하와 다른 참석자들에게 이 되풀이 이벤트의 첫 이벤트에 대한 예약 초대장을 이메일로 발송했습니다.",
"emailed_you_and_any_other_attendees": "당신과 다른 참여자들이 이 정보가 담긴 이메일을 주고 받았습니다.",
@ -420,6 +422,7 @@
"current_incorrect_password": "현재 비밀번호가 잘못되었습니다.",
"password_hint_caplow": "대문자와 소문자의 혼합",
"password_hint_min": "최소 7자 길이",
"password_hint_admin_min": "최소 15자 길이",
"password_hint_num": "1개 이상 숫자 포함",
"invalid_password_hint": "비밀번호는 7자 이상이어야 하고 숫자가 하나 이상 포함되어야 하며 대문자와 소문자가 혼합되어야 합니다",
"incorrect_password": "비밀번호가 잘못되었습니다",
@ -463,11 +466,14 @@
"booking_confirmation": "{{profileName}}로 {{eventTypeTitle}} 확인",
"booking_reschedule_confirmation": "{{profileName}}을 사용하여 {{eventTypeTitle}} 일정을 변경하세요.",
"in_person_meeting": "온라인 또는 오프라인 회의",
"attendeeInPerson": "직접 방문(참석자 주소)",
"inPerson": "방문(주최자 주소)",
"link_meeting": "회의 링크",
"phone_call": "참석자 전화 번호",
"your_number": "본인 전화 번호",
"phone_number": "전화 번호",
"attendee_phone_number": "참석자 전화 번호",
"organizer_phone_number": "주최자 전화번호",
"host_phone_number": "본인 전화 번호",
"enter_phone_number": "전화 번호 입력",
"reschedule": "일정 변경",
@ -504,7 +510,7 @@
"add_team_members_description": "다른 사람을 팀에 초대",
"add_team_member": "팀원 추가",
"invite_new_member": "새 팀원 초대",
"invite_new_member_description": "참고: 이렇게 하면 해당 구독에 <1>추가 좌석 비용($15/m)</1>이 발생합니다.",
"invite_new_member_description": "참고: 이로 인해 해당 구독에 <1>추가 시트 비용($15/m)</1>이 발생합니다.",
"invite_new_team_member": "누군가를 팀에 초대하기",
"change_member_role": "팀원 역할 변경",
"disable_cal_branding": "{{appName}} 브랜딩 비활성화",
@ -554,6 +560,10 @@
"collective": "집단",
"collective_description": "선택한 모든 팀원이 참석할 수 있을 때 회의를 예약합니다.",
"duration": "기간",
"available_durations": "사용 가능한 기간",
"default_duration": "기본 기간",
"default_duration_no_options": "사용 가능한 기간을 먼저 선택하세요",
"multiple_duration_mins": "{{count}} $t(minute_timeUnit)",
"minutes": "분",
"round_robin": "라운드 로빈",
"round_robin_description": "여러 팀 구성원 간의 주기적인 회의",
@ -619,6 +629,7 @@
"teams": "팀",
"team": "팀",
"team_billing": "팀 비용 청구",
"team_billing_description": "팀의 청구 관리",
"upgrade_to_flexible_pro_title": "팀에 대한 청구를 변경했습니다",
"upgrade_to_flexible_pro_message": "팀에 시트가 없는 구성원이 있습니다. 누락된 시트를 보완하려면 Pro 플랜을 업그레이드하세요.",
"changed_team_billing_info": "2022년 1월부터 팀원에 대해 시트별로 요금을 부과합니다. 무료로 Pro를 사용하던 팀원은 이제 14일 평가판을 사용할 수 있습니다. 평가판 기한이 만료되고 업그레이드하지 않으면 이 팀원은 팀에서 숨겨집니다.",
@ -694,6 +705,7 @@
"hide_event_type": "이벤트 타입 숨기기",
"edit_location": "위치 수정",
"into_the_future": "미래로",
"when_booked_with_less_than_notice": "<time></time>회 미만 통지로 예약한 경우",
"within_date_range": "기간 내",
"indefinitely_into_future": "무한히 미래로",
"add_new_custom_input_field": "새 사용자 정의 입력 필드 추가",
@ -713,6 +725,8 @@
"delete_account_confirmation_message": "{{appName}} 계정을 삭제하시겠습니까? 계정 링크를 공유한 사람은 더 이상 이 링크를 사용하여 예약할 수 없으며 저장한 모든 기본 설정이 손실됩니다.",
"integrations": "통합",
"apps": "앱",
"apps_description": "Cal 인스턴스용 앱 활성화",
"apps_listing": "앱 목록화",
"category_apps": "{{category}} 앱",
"app_store": "앱 스토어",
"app_store_description": "사람, 기술, 직장을 연결합니다.",
@ -736,6 +750,7 @@
"toggle_calendars_conflict": "중복 예약을 방지하기 위해 충돌을 확인하려는 캘린더를 전환합니다.",
"select_destination_calendar": "이벤트 작성일",
"connect_additional_calendar": "추가 캘린더 연결",
"calendar_updated_successfully": "캘린더가 업데이트되었습니다",
"conferencing": "회의",
"calendar": "달력",
"payments": "결제",
@ -768,6 +783,7 @@
"trending_apps": "트랜드 앱",
"explore_apps": "{{category}} 앱",
"installed_apps": "설치된 앱",
"free_to_use_apps": "무료",
"no_category_apps": "{{category}} 앱 없음",
"no_category_apps_description_calendar": "중복 예약을 방지하기 위해 충돌을 확인하려는 캘린더 앱을 추가합니다",
"no_category_apps_description_conferencing": "고객과 화상 통화를 통합하려면 회의 앱을 추가해 보세요",
@ -784,7 +800,7 @@
"analytics": "분석",
"empty_installed_apps_headline": "설치된 앱 없음",
"empty_installed_apps_description": "앱을 통해 워크플로를 강화하고 일정 관리 수명을 대폭 향상할 수 있습니다.",
"empty_installed_apps_button": "앱 스토어를 둘러보거나 아래 앱 중에서 설치",
"empty_installed_apps_button": "앱 스토어 둘러보기",
"manage_your_connected_apps": "설치된 앱 관리 또는 설정 변경",
"browse_apps": "앱 찾아보기",
"features": "기능",
@ -806,6 +822,8 @@
"verify_wallet": "지갑 인증",
"connect_metamask": "메타마스크 연결",
"create_events_on": "이벤트 생성일:",
"enterprise_license": "엔터프라이즈 기능입니다",
"enterprise_license_description": "이 기능을 활성화하려면 {{consoleUrl}} 콘솔에서 배포 키를 가져와 .env에 CALCOM_LICENSE_KEY로 추가하세요. 팀에 이미 라이선스가 있는 경우 {{supportMail}}에 문의하여 도움을 받으세요.",
"missing_license": "라이선스 없음",
"signup_requires": "상용 라이선스 필요",
"signup_requires_description": "지금은 {{companyName}}에서 가입 페이지의 무료 오픈 소스 버전을 제공하지 않습니다. 가입 구성 요소에 대한 정식 액세스 권한을 받으려면 상용 라이선스를 취득해야 합니다. 개인 용도의 경우 Prisma Data Platform 또는 기타 Postgres 인터페이스를 사용하여 계정을 생성하는 것이 좋습니다.",
@ -897,6 +915,7 @@
"user_impersonation_heading": "사용자 가장",
"user_impersonation_description": "신고하신 문제를 신속하게 해결할 수 있도록 지원 팀이 귀하의 계정에 일시적으로 로그인할 수 있게 허용합니다.",
"team_impersonation_description": "팀 구성원이 일시적으로 귀하의 자격으로 로그인할 수 있게 합니다.",
"allow_booker_to_select_duration": "예약자가 기간을 선택하도록 허용",
"impersonate_user_tip": "이 기능의 사용은 모두 감사됩니다.",
"impersonating_user_warning": "\"{{user}}\"의 사용자 이름을 가장하는 중입니다.",
"impersonating_stop_instructions": "<0>중지하려면 여기를 클릭하십시오</0>.",
@ -1014,6 +1033,9 @@
"error_removing_app": "앱 제거 중 오류 발생",
"web_conference": "웹 화상 회의",
"requires_confirmation": "확인 필수",
"always_requires_confirmation": "항상",
"requires_confirmation_threshold": "< {{time}} $t({{unit}}_timeUnit) 알림으로 예약한 경우 확인이 필요합니다",
"may_require_confirmation": "확인이 필요할 수 있음",
"nr_event_type_one": "{{count}} 이벤트 타입",
"nr_event_type_other": "{{count}} 이벤트 타입",
"add_action": "작업 추가",
@ -1101,6 +1123,9 @@
"event_limit_tab_description": "예약 가능 횟수",
"event_advanced_tab_description": "캘린더 설정 및 기타...",
"event_advanced_tab_title": "고급",
"event_setup_multiple_duration_error": "이벤트 설정: 복수 기간에는 최소 1개의 옵션이 필요합니다.",
"event_setup_multiple_duration_default_error": "이벤트 설정: 유효한 기본 기간을 선택하십시오.",
"event_setup_booking_limits_error": "예약 한도는 오름차순이어야 합니다. [일,주,월,년]",
"select_which_cal": "예약을 추가할 캘린더 선택",
"custom_event_name": "사용자 정의 이벤트 이름",
"custom_event_name_description": "캘린더 이벤트에 표시할 사용자 정의 이벤트 이름 만들기",
@ -1154,8 +1179,11 @@
"invoices": "청구서",
"embeds": "내장",
"impersonation": "가장",
"impersonation_description": "사용자 가장을 관리하기 위한 설정",
"users": "사용자",
"profile_description": "사용자 {{appName}} 프로필 설정 관리",
"users_description": "여기에서 모든 사용자 목록을 찾을 수 있습니다",
"users_listing": "사용자 목록화",
"general_description": "언어 및 시간대 설정 관리",
"calendars_description": "이벤트 타입이 캘린더와 상호 작용하는 방식 구성",
"appearance_description": "예약 모양 설정 관리",
@ -1296,7 +1324,7 @@
"fetching_calendars_error": "캘린더를 가져오는 중 문제가 발생했습니다. <1>다시 시도</1>하거나 고객 지원에 문의하십시오.",
"calendar_connection_fail": "캘린더 연결 실패",
"booking_confirmation_success": "예약 확인 성공",
"booking_rejection_success": "예약 거부 성공",
"booking_rejection_success": "예약이 거부됨",
"booking_confirmation_fail": "예약 확인 실패",
"we_wont_show_again": "다시 표시 안 함",
"couldnt_update_timezone": "시간대를 업데이트할 수 없습니다",
@ -1347,28 +1375,67 @@
"yearly": "연간",
"checkout": "체크아웃",
"your_team_disbanded_successfully": "귀하의 팀이 성공적으로 해산되었습니다",
"error_creating_team": "팀을 만드는 중 오류 발생했습니다",
"you": "",
"error_creating_team": "팀을 만드는 중 오류 발생",
"you": "귀하",
"send_email": "이메일 보내기",
"member_already_invited": "구성원이 이미 초대되었습니다",
"enter_email_or_username": "이메일 또는 사용자 이름을 입력하세요",
"team_name_taken": "이미 사용 중인 이름입니다",
"must_enter_team_name": "팀 이름을 입력하세요",
"team_url_required": "팀 URL을 입력해야 합니다",
"team_url_required": "팀 URL을 입력하세요",
"team_url_taken": "이미 사용 중인 URL입니다",
"team_publish": "팀 게시",
"number_sms_notifications": "전화번호 (SMS notifications)",
"attendee_email_workflow": "참석자 이메일",
"attendee_email_info": "예약자 이메일",
"kbar_search_placeholder": "명령어를 입력하거나 검색하세요...",
"invalid_credential": "죄송합니다! 권한이 만료되었거나 취소된 것 같습니다. 다시 설치하십시오.",
"choose_common_schedule_team_event": "공통 일정 선택하세요",
"choose_common_schedule_team_event_description": "호스트 간에 공통 일정을 사용하려면 이 옵션을 활성화하십시오. 비활성화되면 각 호스트는 기본 일정에 따라 예약됩니다.",
"reason": "유",
"choose_common_schedule_team_event": "공통 일정 선택",
"choose_common_schedule_team_event_description": "호스트 간에 공통 일정을 사용하려면 이 옵션을 활성화하세요. 비활성화되면 각 호스트는 기본 일정에 따라 예약됩니다.",
"reason": "유",
"sender_id": "발신자 ID",
"sender_id_error_message": "영문, 숫자, 공백만 허용(최대 11자)",
"test_routing_form": "라우팅 양식 테스트",
"test_preview": "테스트 미리보기",
"test_preview": "미리보기 테스트",
"route_to": "목표 경로",
"test_preview_description": "데이터를 제출하지 않고 라우팅 양식 테스트",
"test_routing": "라우팅 테스트"
"test_routing": "라우팅 테스트",
"payment_app_disabled": "관리자가 결제 앱을 비활성화했습니다",
"edit_event_type": "이벤트 타입 편집",
"collective_scheduling": "집단 일정 관리",
"make_it_easy_to_book": "모든 사람이 참석할 수 있을 때 팀을 쉽게 예약할 수 있게 합니다.",
"find_the_best_person": "가능한 최상의 인원을 찾고 팀원을 순환 배정하십시오.",
"fixed_round_robin": "고정 라운드 로빈",
"add_one_fixed_attendee": "한 명의 고정 참석자를 추가하고 여러 참석자를 통해 라운드 로빈을 추가합니다.",
"calcom_is_better_with_team": "Cal.com은 팀과 함께라면 더욱 좋습니다",
"add_your_team_members": "이벤트 타입에 팀원을 추가하세요. 집단 일정 관리를 사용해 모든 사람을 포함하거나 라운드 로빈 일정 관리로 가장 적합한 인원을 찾으십시오.",
"booking_limit_reached": "이 이벤트 타입에 대한 예약 한도에 도달했습니다",
"admin_has_disabled": "관리자가 {{appName}} 앱을 비활성화했습니다",
"disabled_app_affects_event_type": "관리자가 이벤트 타입 {{eventType}}에 영향을 미치는 {{appName}} 앱을 비활성화했습니다",
"disable_payment_app": "관리자가 이벤트 타입 {{title}}에 영향을 미치는 {{appName}} 앱을 비활성화했습니다. 참석자는 여전히 이러한 타입의 이벤트를 예약할 수 있지만 결제하라는 메시지는 표시되지 않습니다. 관리자가 결제 수단을 다시 활성화할 때까지 이를 방지하기 위해 이벤트 타입을 숨길 수 있습니다.",
"payment_disabled_still_able_to_book": "참석자는 여전히 이러한 타입의 이벤트를 예약할 수 있지만 결제하라는 메시지는 표시되지 않습니다. 관리자가 결제 수단을 다시 활성화할 때까지 이를 방지하기 위해 이벤트 타입을 숨길 수 있습니다.",
"app_disabled_with_event_type": "관리자가 이벤트 타입 {{title}}에 영향을 미치는 {{appName}} 앱을 비활성화했습니다.",
"app_disabled_video": "관리자가 이벤트 타입에 영향을 미칠 수 있는 {{appName}} 앱을 비활성화했습니다. 위치가 {{appName}} 앱에 대한 이벤트 타입이 있는 경우 기본적으로 Cal Video로 설정됩니다.",
"app_disabled_subject": "{{appName}} 앱이 비활성화되었습니다",
"navigate_installed_apps": "설치된 앱으로 이동",
"disabled_calendar": "다른 캘린더를 설치한 경우 새 예약이 추가됩니다. 그렇지 않은 경우 새로운 예약을 놓치지 않도록 새 캘린더를 연결하십시오.",
"enable_apps": "앱 활성화",
"enable_apps_description": "사용자가 Cal.com과 통합할 수 있는 앱 활성화",
"app_is_enabled": "{{appName}} 앱이 활성화되었습니다",
"app_is_disabled": "{{appName}} 앱이 비활성화되었습니다",
"keys_have_been_saved": "키가 저장되었습니다",
"disable_app": "앱 비활성화",
"disable_app_description": "이 앱을 비활성화하면 사용자가 Cal과 상호 작용하는 방식에 문제가 발생할 수 있습니다",
"edit_keys": "키 편집",
"no_available_apps": "사용 가능한 앱이 없습니다",
"no_available_apps_description": "'packages/app-store' 아래 배포에 앱이 있는지 확인하세요",
"no_apps": "Cal의 이 인스턴스에서 활성화된 앱이 없습니다",
"apps_settings": "앱 설정",
"fill_this_field": "이 필드에 입력하세요",
"options": "선택사항",
"enter_option": "선택사항 {{index}} 입력",
"add_an_option": "선택사항 추가",
"radio": "라디오",
"event_type_duplicate_copy_text": "{{slug}}-카피",
"set_as_default": "기본값으로 설정"
}

View File

@ -16,8 +16,8 @@
"event_request_cancelled": "Uw geplande gebeurtenis is geannuleerd",
"organizer": "Organisator",
"need_to_reschedule_or_cancel": "Moet u verplaatsen of annuleren?",
"cancellation_reason": "Reden voor annulering",
"cancellation_reason_placeholder": "Waarom annuleert u? (optioneel)",
"cancellation_reason": "Reden voor annulering (optioneel)",
"cancellation_reason_placeholder": "Waarom annuleert u?",
"rejection_reason": "Reden voor weigering",
"rejection_reason_title": "Boekingsverzoek weigeren?",
"rejection_reason_description": "Weet u zeker dat u de boeking wilt weigeren? We informeren de persoon die probeerde te boeken. U kunt hieronder een reden opgeven.",
@ -108,7 +108,7 @@
"link_expires": "p.s. De uitnodiging verloopt over {{expiresIn}} uur.",
"upgrade_to_per_seat": "Upgraden naar per plaats",
"team_upgrade_seats_details": "Van de {{memberCount}} leden in uw team zijn {{unpaidCount}} plaats(en) onbetaald. Voor $ {{seatPrice}}/maand per plaats bedragen de geschatte totale kosten van uw abonnement $ {{totalCost}}/maand.",
"team_upgrade_banner_description": "Bedankt voor het testen van ons nieuwe teamabonnement. We hebben gemerkt dat uw team \"{{teamName}}\" moet worden geüpgraded.",
"team_upgrade_banner_description": "Bedankt voor het testen van ons nieuwe teamabonnement. We hebben geconstateerd dat uw team \"{{teamName}}\" moet worden geüpgraded.",
"team_upgrade_banner_action": "Hier upgraden",
"team_upgraded_successfully": "Uw team is geüpgraded!",
"use_link_to_reset_password": "Gebruik de onderstaande link om uw wachtwoord te resetten",
@ -419,7 +419,7 @@
"forgotten_secret_description": "Als u dit geheim vergeten of verloren bent, kunt u het wijzigen. Maar wees u ervan bewust dat alle integraties die dit geheim gebruiken bijgewerkt moeten worden",
"current_incorrect_password": "Uw wachtwoord is verkeerd",
"password_hint_caplow": "Mix van hoofd- en kleine letters",
"password_hint_min": "Minimaal 7 tekens lang",
"password_hint_min": "Minimaal 8 tekens lang",
"password_hint_num": "Minimaal 1 cijfer bevatten",
"invalid_password_hint": "Het wachtwoord moet minimaal 7 tekens lang zijn, minimaal 1 cijfer bevatten en bestaan uit een mix van hoofd- en kleine letters",
"incorrect_password": "Uw wachtwoord is incorrect.",
@ -784,7 +784,7 @@
"analytics": "Analyse",
"empty_installed_apps_headline": "Geen apps geïnstalleerd",
"empty_installed_apps_description": "Met apps kunt u uw workflow en uw planning aanzienlijk verbeteren.",
"empty_installed_apps_button": "Ontdek de App Store",
"empty_installed_apps_button": "Bladeren in App Store",
"manage_your_connected_apps": "Beheer uw geïnstalleerde apps of wijzig instellingen",
"browse_apps": "Door apps bladeren",
"features": "Functies",

View File

@ -17,7 +17,7 @@
"organizer": "Organizador",
"need_to_reschedule_or_cancel": "Precisa reagendar ou cancelar?",
"cancellation_reason": "Motivo do cancelamento",
"cancellation_reason_placeholder": "Por que você está cancelando? (opcional)",
"cancellation_reason_placeholder": "Por que você está cancelando?",
"rejection_reason": "Motivo da recusa",
"rejection_reason_title": "Recusar a solicitação de reserva?",
"rejection_reason_description": "Tem certeza de que deseja recusar a reserva? Avisaremos a pessoa que tentou reservar. Se quiser, informe um motivo abaixo.",
@ -419,7 +419,7 @@
"forgotten_secret_description": "Caso tenha perdido ou esquecido o segredo, você poderá alterá-lo. Mas fique ciente de que todas as integrações com esse segredo precisarão ser atualizadas",
"current_incorrect_password": "Senha atual está incorreta",
"password_hint_caplow": "Mistura de letras maiúsculas e minúsculas",
"password_hint_min": "Mínimo de sete caracteres",
"password_hint_min": "Mínimo de oito caracteres",
"password_hint_admin_min": "No mínimo 15 caracteres",
"password_hint_num": "Contém pelo menos um número",
"invalid_password_hint": "A senha precisa ter pelo menos sete caracteres com pelo menos um número e ser uma combinação de letras maiúsculas e minúsculas",
@ -1356,7 +1356,7 @@
"team_name_taken": "Este nome já está em uso",
"must_enter_team_name": "É preciso inserir um nome de equipe",
"team_url_required": "É preciso inserir uma URL de equipe",
"team_url_taken": "Esta URL já está em uso",
"team_url_taken": "Este URL já está em uso",
"team_publish": "Publicar equipe",
"number_sms_notifications": "Número de telefone (notificações de SMS)",
"attendee_email_workflow": "E-mail do participante",
@ -1368,7 +1368,7 @@
"sender_id": "ID do remetente",
"sender_id_error_message": "Apenas letras, números e espaços permitidos (no máx. 11 caracteres)",
"test_routing_form": "Testar formulário de roteamento",
"test_preview": "Testar prévia",
"test_preview": "Visualizar teste",
"route_to": "Direcionar para",
"test_preview_description": "Teste seu formulário de roteamento sem enviar dados",
"test_routing": "Testar roteamento"

View File

@ -52,6 +52,7 @@
"still_waiting_for_approval": "Um evento ainda aguarda aprovação",
"event_is_still_waiting": "Pedido de evento pendente: {{attendeeName}} - {{date}} - {{eventType}}",
"no_more_results": "Não existem mais resultados",
"no_results": "Sem resultados",
"load_more_results": "Carregar mais resultados",
"integration_meeting_id": "ID da reunião de {{integrationName}}: {{meetingId}}",
"confirmed_event_type_subject": "Confirmado: {{eventType}} com {{name}} em {{date}}",
@ -248,6 +249,7 @@
"add_to_calendar": "Adicionar ao calendário",
"add_another_calendar": "Adicionar outro calendário",
"other": "Outras",
"email_sign_in_subject": "A sua ligação de início de sessão para {{appName}}",
"emailed_you_and_attendees": "Enviámos um email para si e para os outros participantes com um convite de calendário com todos os detalhes.",
"emailed_you_and_attendees_recurring": "Enviámos um email para si e para os outros participantes com um convite de calendário para o primeiro destes eventos recorrentes.",
"emailed_you_and_any_other_attendees": "Esta informação foi enviada para si e para todos os outros participantes.",
@ -420,6 +422,7 @@
"current_incorrect_password": "A senha atual está incorreta",
"password_hint_caplow": "Mistura de letras maiúsculas e minúsculas",
"password_hint_min": "Tamanho mínimo de 7 caracteres",
"password_hint_admin_min": "Tamanho mínimo de 15 caracteres",
"password_hint_num": "Contém pelo menos 1 número",
"invalid_password_hint": "A palavra-passe deve ter no mínimo 7 caracteres, com pelo menos um número e uma mistura de letras maiúsculas e minúsculas",
"incorrect_password": "Palavra-Passe incorreta",
@ -463,11 +466,14 @@
"booking_confirmation": "Confirme o seu {{eventTypeTitle}} com {{profileName}}",
"booking_reschedule_confirmation": "Reagende o seu {{eventTypeTitle}} com {{profileName}}",
"in_person_meeting": "Ligação ou reunião presencial",
"attendeeInPerson": "Pessoalmente (endereço do participante)",
"inPerson": "Pessoalmente (endereço do organizador)",
"link_meeting": "Ligar reunião",
"phone_call": "Número de telefone do participante",
"your_number": "O seu número de telefone",
"phone_number": "Número de Telefone",
"attendee_phone_number": "Número de telefone do participante",
"organizer_phone_number": "Número de telefone do organizador",
"host_phone_number": "O seu número de telefone",
"enter_phone_number": "Inserir Número do Telefone",
"reschedule": "Reagendar",
@ -554,6 +560,10 @@
"collective": "Coletivo",
"collective_description": "Agende reuniões quando todos os membros selecionados da equipa estiverem disponíveis.",
"duration": "Duração",
"available_durations": "Durações disponíveis",
"default_duration": "Duração predefinida",
"default_duration_no_options": "Por favor, selecione primeiro as durações disponíveis",
"multiple_duration_mins": "{{count}} $t(minute_timeUnit)",
"minutes": "Minutos",
"round_robin": "Round Robin",
"round_robin_description": "Reuniões de ciclo entre vários membros da equipa.",
@ -619,6 +629,7 @@
"teams": "Equipas",
"team": "Equipa",
"team_billing": "Facturação da equipa",
"team_billing_description": "Gerir a faturação para a sua equipa",
"upgrade_to_flexible_pro_title": "Alterámos os termos de facturação",
"upgrade_to_flexible_pro_message": "Há membros sem lugar na sua equipa. Actualize para o plano Pro para cobrir os lugares em falta.",
"changed_team_billing_info": "Desde Janeiro de 2022 a cobrança é efectuada por cada lugar de membro da equipa. Os membros da sua equipa que tivessem a versão Pro gratuitamente, estão agora num período experimental de 14 dias. Assim que este período expirar, estes membros ficarão ocultos da sua equipa, a menos que actualize agora.",
@ -694,6 +705,7 @@
"hide_event_type": "Ocultar Tipo de Evento",
"edit_location": "Editar Localização",
"into_the_future": "no futuro",
"when_booked_with_less_than_notice": "Quando reservado com aviso prévio inferior a <time></time>",
"within_date_range": "Dentro de um período",
"indefinitely_into_future": "Indefinidamente para o futuro",
"add_new_custom_input_field": "Adicionar novo campo personalizado",
@ -713,6 +725,8 @@
"delete_account_confirmation_message": "De certeza que quer eliminar a sua conta {{appName}}? Qualquer pessoa com quem tenha partilhado esta ligação de equipa deixará de poder fazer reservas através da respetiva ligação, e quaisquer preferências que tenha guardado serão perdidas.",
"integrations": "Integrações",
"apps": "Aplicações",
"apps_description": "Ativar aplicações para a sua instância do Cal",
"apps_listing": "Lista de aplicações",
"category_apps": "Aplicações de {{category}}",
"app_store": "App Store",
"app_store_description": "Ligar pessoas, tecnologia e o local de trabalho.",
@ -736,6 +750,7 @@
"toggle_calendars_conflict": "Escolha os calendários que deseja verificar para evitar conflitos e duplicação de reservas.",
"select_destination_calendar": "Criar eventos em",
"connect_additional_calendar": "Ligar um calendário adicional",
"calendar_updated_successfully": "Calendário atualizado com sucesso",
"conferencing": "Conferência",
"calendar": "Calendário",
"payments": "Pagamentos",
@ -768,6 +783,7 @@
"trending_apps": "Aplicações em destaque",
"explore_apps": "Aplicações de {{category}}",
"installed_apps": "Aplicações instaladas",
"free_to_use_apps": "Grátis",
"no_category_apps": "Sem aplicações de {{category}}",
"no_category_apps_description_calendar": "Adicione uma aplicação de calendário para verificar se existem conflitos e assim evitar sobreposição de marcações",
"no_category_apps_description_conferencing": "Experimente adicionar uma aplicação de conferência para integrar chamadas de vídeo com os seus clientes",
@ -806,6 +822,8 @@
"verify_wallet": "Verificar carteira",
"connect_metamask": "Ligar Metamask",
"create_events_on": "Criar eventos em:",
"enterprise_license": "Esta é uma funcionalidade empresarial",
"enterprise_license_description": "Para ativar esta funcionalidade, obtenha uma chave de instalação na consola {{consoleUrl}} e adicione-a ao seu .env como CALCOM_LICENSE_KEY. Se a sua equipa já tem uma licença, entre em contacto com {{supportMail}} para obter ajuda.",
"missing_license": "Licença em falta",
"signup_requires": "Necessita de licença comercial",
"signup_requires_description": "De momento a {{companyName}} não oferece uma versão gratuita de código aberto da página de registo. Para aceder completamente aos componentes de registo tem de adquirir uma licença comercial. Para uso pessoal, recomendamos a Prisma Data Platform ou qualquer outra interface Postgres para criar contas.",
@ -897,6 +915,7 @@
"user_impersonation_heading": "Representação de utilizador",
"user_impersonation_description": "Permite à nossa equipa de suporte iniciar sessão temporariamente em seu nome para rapidamente resolver quaisquer erros que nos reporte.",
"team_impersonation_description": "Permite que os administradores da sua equipa iniciem a sessão temporariamente em seu nome.",
"allow_booker_to_select_duration": "Permitir que o responsável pela reserva selecione a duração",
"impersonate_user_tip": "Todas as utilizações desta funcionalidade são auditadas.",
"impersonating_user_warning": "Representar o nome de utilizador \"{{user}}\".",
"impersonating_stop_instructions": "<0>Clique aqui para parar</0>.",
@ -1014,6 +1033,9 @@
"error_removing_app": "Erro ao remover a aplicação",
"web_conference": "Conferência web",
"requires_confirmation": "Requer confirmação",
"always_requires_confirmation": "Sempre",
"requires_confirmation_threshold": "Requer confirmação se reservado com um aviso prévio inferior a {{time}} $t({{unit}}_timeUnit)",
"may_require_confirmation": "Pode necessitar de confirmação",
"nr_event_type_one": "{{count}} tipo de evento",
"nr_event_type_other": "{{count}} tipos de evento",
"add_action": "Adicionar acção",
@ -1101,6 +1123,9 @@
"event_limit_tab_description": "Com que frequência pode ser reservado",
"event_advanced_tab_description": "Definições do calendário e muito mais...",
"event_advanced_tab_title": "Avançado",
"event_setup_multiple_duration_error": "Configuração do evento: durações múltiplas requerem pelo menos 1 opção.",
"event_setup_multiple_duration_default_error": "Configuração do evento: por favor, selecione uma duração predefinida válida.",
"event_setup_booking_limits_error": "Os limites de reserva devem estar ordenados de forma ascendente. [dia,semana,mês,ano]",
"select_which_cal": "Selecione para que calendário devem ser adicionadas as marcações",
"custom_event_name": "Nome de evento personalizado",
"custom_event_name_description": "Crie nomes de eventos personalizados a mostrar no calendário de eventos",
@ -1154,8 +1179,11 @@
"invoices": "Faturas",
"embeds": "Incorporações",
"impersonation": "Representação",
"impersonation_description": "Definições para gerir a personificação de utilizadores",
"users": "Utilizadores",
"profile_description": "Gerir as definições para o seu perfil {{appName}}",
"users_description": "Aqui pode encontrar uma lista de todos os utilizadores",
"users_listing": "Lista de utilizadores",
"general_description": "Faça a gestão das definições do seu idioma e fuso horário",
"calendars_description": "Configure como os tipos de evento devem interagir com os seus calendários",
"appearance_description": "Faça a gestão das definições de aparência da sua reserva",
@ -1360,6 +1388,7 @@
"number_sms_notifications": "Número de telefone (notificações SMS)",
"attendee_email_workflow": "E-mail do participante",
"attendee_email_info": "O e-mail do responsável pela reserva",
"kbar_search_placeholder": "Digite um comando ou pesquise...",
"invalid_credential": "Oh não! Parece que a permissão expirou ou foi revogada. Por favor, reinstale novamente.",
"choose_common_schedule_team_event": "Escolha uma agenda comum",
"choose_common_schedule_team_event_description": "Ative esta opção se quiser utilizar uma agenda comum entre anfitriões. Quando desativada, cada anfitrião será reservado com base na respetiva agenda pré-definida.",
@ -1370,5 +1399,43 @@
"test_preview": "Pré-visualização do teste",
"route_to": "Encaminhar para",
"test_preview_description": "Teste o encaminhamento do seu formulário sem submeter qualquer dado",
"test_routing": "Testar encaminhamento"
"test_routing": "Testar encaminhamento",
"payment_app_disabled": "Um administrador desativou uma aplicação de pagamentos",
"edit_event_type": "Editar o tipo de evento",
"collective_scheduling": "Agendamento coletivo",
"make_it_easy_to_book": "Faça com que seja simples reservar a sua equipa quando todos estiverem disponíveis.",
"find_the_best_person": "Encontre a melhor pessoa disponível e navegue na sua equipa.",
"fixed_round_robin": "Distribuição equilibrada fixa",
"add_one_fixed_attendee": "Adicione um participante fixo e distribua de forma equilibrada a partir de um número de participantes.",
"calcom_is_better_with_team": "O Cal.com é melhor com equipas",
"add_your_team_members": "Adicione os membros da sua equipa aos seus tipos de eventos. Utilize o agendamento coletivo para incluir todos os membros ou encontre a pessoa mais adequada com o agendamento baseado numa distribuição equilibrada.",
"booking_limit_reached": "O limite de reservas para este tipo de evento foi atingido",
"admin_has_disabled": "{{appName}} foi desativada por um administrador",
"disabled_app_affects_event_type": "{{appName}} foi desativada por um administrador o que afeta o seu tipo de evento {{eventType}}",
"disable_payment_app": "O administrador desativou {{appName}} o que afeta o seu tipo de evento {{title}}. Os participantes ainda poderão reservar este tipo de evento, mas não será solicitado que façam qualquer pagamento. Pode ocultar o tipo de evento para evitar esta situação, até que o seu administrador reative o seu método de pagamento.",
"payment_disabled_still_able_to_book": "Os participantes ainda poderão reservar este tipo de evento, mas não será solicitado que façam qualquer pagamento. Pode ocultar o tipo de evento para evitar esta situação, até que o seu administrador reative o seu método de pagamento.",
"app_disabled_with_event_type": "O administrador desativou {{appName}} o que afeta o seu tipo de evento {{title}}.",
"app_disabled_video": "O administrador desativou {{appName}} o que afeta os seus tipos de eventos. Se tiver tipos de eventos com {{appName}} como o sítio, então o mesmo será predefinido para o Cal Video.",
"app_disabled_subject": "{{appName}} foi desativada",
"navigate_installed_apps": "Ir para as aplicações instaladas",
"disabled_calendar": "Se tiver outro calendário instalado, os novos pedidos serão adicionados ao mesmo. Se não é este o caso, deve associar um novo calendário para garantir que não perde novas reservas.",
"enable_apps": "Ativar aplicações",
"enable_apps_description": "Ativar aplicações que os utilizadores podem integrar com o Cal.com",
"app_is_enabled": "{{appName}} foi ativada",
"app_is_disabled": "{{appName}} foi desativada",
"keys_have_been_saved": "As chaves foram guardadas",
"disable_app": "Desativar a aplicação",
"disable_app_description": "A desativação desta aplicação pode causar problemas na forma como os seus utilizadores interagem com o Cal",
"edit_keys": "Editar chaves",
"no_available_apps": "Não existem aplicações disponíveis",
"no_available_apps_description": "Por favor, certifique-se que existem aplicações na sua instalação em 'packages/app-store'",
"no_apps": "Não existem aplicações ativas nesta instância do Cal",
"apps_settings": "Definições de aplicações",
"fill_this_field": "Por favor, preencha este campo",
"options": "Opções",
"enter_option": "Insira a opção {{index}}",
"add_an_option": "Adicione uma opção",
"radio": "Radio",
"event_type_duplicate_copy_text": "{{slug}}-copia",
"set_as_default": "Predefinir"
}

View File

@ -16,8 +16,8 @@
"event_request_cancelled": "Ваша запланированная встреча была отменена",
"organizer": "Организатор",
"need_to_reschedule_or_cancel": "Хотите перенести или отменить?",
"cancellation_reason": "Причина отмены",
"cancellation_reason_placeholder": "Укажите причину отмены (необязательное поле)",
"cancellation_reason": "Причина отмены (необязательно)",
"cancellation_reason_placeholder": "Укажите причину отмены подписки",
"rejection_reason": "Причина отказа",
"rejection_reason_title": "Отказаться от бронирования?",
"rejection_reason_description": "Отказаться от брони? Мы сообщим пользователю, который пытался оформить бронь. Вы можете указать причину отказа ниже.",
@ -108,7 +108,7 @@
"link_expires": "p.s. Это истекает через {{expiresIn}} часов.",
"upgrade_to_per_seat": "Перейти на оплату из расчета за количество мест",
"team_upgrade_seats_details": "В вашей команде {{memberCount}} пользователей; из них не оплачено еще {{unpaidCount}} мест. При оплате ${{seatPrice}} в месяц за место общая сумма к оплате составляет ${{totalCost}} в месяц.",
"team_upgrade_banner_description": "Спасибо, что перешли на наш новый тариф team. Мы заметили, что вам нужно обновить \"{{teamName}}\" команды.",
"team_upgrade_banner_description": "Спасибо, что решили попробовать наш новый тариф Team. Необходимо обновить команду «{{teamName}}».",
"team_upgrade_banner_action": "Обновить",
"team_upgraded_successfully": "Вы успешно перешли на новый тариф для своей команды!",
"use_link_to_reset_password": "Используйте ссылку ниже, чтобы сбросить пароль",
@ -420,7 +420,7 @@
"current_incorrect_password": "Неверный текущий пароль",
"password_hint_caplow": "Используйте смесь заглавных и строчных букв",
"password_hint_min": "Не короче 7 символов",
"password_hint_admin_min": "Минимум 15 символов",
"password_hint_admin_min": "Не короче 15 символов",
"password_hint_num": "Не менее 1 цифры",
"invalid_password_hint": "Пароль должен быть не короче 7 символов и содержать заглавные и строчные буквы, а также не менее одной цифры",
"incorrect_password": "Неверный пароль",
@ -505,7 +505,7 @@
"add_team_members_description": "Пригласите других пользователей в свою команду",
"add_team_member": "Добавить участника команды",
"invite_new_member": "Пригласить нового участника команды",
"invite_new_member_description": "Примечание. Стоимость подписки увеличится на <1>$15/месяц за дополнительное место</1>.",
"invite_new_member_description": "Примечание: стоимость подписки увеличится на <1>15 $/месяц за дополнительное место</1>.",
"invite_new_team_member": "Пригласите кого-нибудь в вашу команду.",
"change_member_role": "Изменить роль члена команды",
"disable_cal_branding": "Отключить брендинг {{appName}}",
@ -602,7 +602,7 @@
"monthly_other": "мес.",
"yearly_one": "год",
"yearly_other": "г.",
"plus_more": "еще {{count}}",
"plus_more": "Еще {{count}}",
"max": "Макс.",
"single_theme": "Тема оформления",
"brand_color": "Цвет бренда",
@ -785,7 +785,7 @@
"analytics": "Аналитика",
"empty_installed_apps_headline": "Нет установленных приложений",
"empty_installed_apps_description": "Приложения позволяют значительно улучшить рабочий процесс и жить по расписанию.",
"empty_installed_apps_button": "Перейти в App Store или установить одно из приложений ниже",
"empty_installed_apps_button": "Перейти в App Store",
"manage_your_connected_apps": "Управление установленными приложениями и изменение настроек",
"browse_apps": "Обзор приложений",
"features": "Возможности",
@ -1230,7 +1230,7 @@
"exchange_compression": "Сжатие GZip",
"routing_forms_description": "Здесь показываются все созданные вами формы и маршруты.",
"routing_forms_send_email_owner": "Отправить письмо владельцу",
"routing_forms_send_email_owner_description": "Отправляет сообщение владельцу при отправке формы",
"routing_forms_send_email_owner_description": "Отправляет владельцу электронное письмо при отправке формы",
"add_new_form": "Добавить новую форму",
"form_description": "Создайте форму для перенаправления пользователей при бронировании",
"copy_link_to_form": "Скопировать ссылку на форму",
@ -1344,8 +1344,8 @@
"no_event_types": "Типы событий не установлены",
"no_event_types_description": "{{name}} не установил(а) для вас типы событий, которые вы можете бронировать.",
"billing_frequency": "Периодичность расчетов",
"monthly": "Ежемесячно",
"yearly": "Ежегодно",
"monthly": "Ежемесячная",
"yearly": "Ежегодная",
"checkout": "Оплатить заказ",
"your_team_disbanded_successfully": "Ваша команда расформирована",
"error_creating_team": "Ошибка при создании команды",
@ -1353,24 +1353,24 @@
"send_email": "Отправить письмо",
"member_already_invited": "Участник уже приглашен",
"enter_email_or_username": "Введите адрес электронной почты или имя пользователя",
"team_name_taken": "Это имя уже занято",
"team_name_taken": "Это название уже занято",
"must_enter_team_name": "Необходимо ввести название команды",
"team_url_required": "Необходимо ввести URL команды",
"team_url_taken": "Этот URL уже занят",
"team_url_required": "Необходимо ввести URL-адрес команды",
"team_url_taken": "Этот URL-адрес уже занят",
"team_publish": "Опубликовать команду",
"number_sms_notifications": "Номер телефона (SMS-уведомления)",
"attendee_email_workflow": "Электронная почта участника",
"attendee_email_info": "Электронная почта участника, на которого оформляется бронирование",
"invalid_credential": "Похоже, разрешение истекло или отозвано. Возобновите его.",
"invalid_credential": "Похоже, разрешение истекло или было отозвано. Необходима переустановка.",
"choose_common_schedule_team_event": "Выберите общее расписание",
"choose_common_schedule_team_event_description": "Позволяет организаторам использовать общее расписание. Если общее расписание отключено, бронирование выполняется для каждого организатора в расписании по умолчанию.",
"choose_common_schedule_team_event_description": "Позволяет организаторам использовать общее расписание. Если общее расписание отключено, то для каждого организатора бронирование выполняется в соответствии с его индивидуальным расписанием по умолчанию.",
"reason": "Причина",
"sender_id": "ID отправителя",
"sender_id_error_message": "Только буквы, цифры и пробелы (макс. 11 символов)",
"test_routing_form": "Тестировать форму машрутизации",
"test_preview": "Тестировать предварительный просмотр",
"route_to": "Путь к",
"test_routing_form": "Проверить форму машрутизации",
"test_preview": "Проверить предварительный просмотр",
"route_to": "Цель маршрута",
"test_preview_description": "Проверить форму маршрутизации без отправки данных",
"test_routing": "Тестировать машрутизацию",
"test_routing": "Проверить машрутизацию",
"booking_limit_reached": "Достигнут лимит бронирования для этого типа события"
}

View File

@ -16,8 +16,8 @@
"event_request_cancelled": "Vaš zakazan dogadjaj je odbijen",
"organizer": "Organizator",
"need_to_reschedule_or_cancel": "Morate da odložite ili otkažete?",
"cancellation_reason": "Razlog otkazivanja",
"cancellation_reason_placeholder": "Zašto otkazujete? (opciono)",
"cancellation_reason": "Razlog otkazivanja (opciono)",
"cancellation_reason_placeholder": "Zašto otkazujete?",
"rejection_reason": "Razlog odbijanja",
"rejection_reason_title": "Odbij zahtev za rezervaciju?",
"rejection_reason_description": "Da li ste sigurni da želite da odbijete rezervaciju? Obavestićemo osobu koja je pokušala da rezerviše. Možete navesti razlog ispod.",
@ -52,6 +52,7 @@
"still_waiting_for_approval": "Dogadjaj još čeka na odobrenje",
"event_is_still_waiting": "Zahtev na dogadjaj još čeka: {{attendeeName}} - {{date}} - {{eventType}}",
"no_more_results": "Nema više rezultata",
"no_results": "Nema rezultata",
"load_more_results": "Učitaj više rezultata",
"integration_meeting_id": "{{integrationName}} ID sastanka: {{meetingId}}",
"confirmed_event_type_subject": "Potvrdjeno: {{eventType}} sa {{name}} datuma {{date}}",
@ -108,7 +109,7 @@
"link_expires": "p.s. Ističe za {{expiresIn}} sati.",
"upgrade_to_per_seat": "Pretplati se na Po-Mestu",
"team_upgrade_seats_details": "Od {{memberCount}} članova u vašem timu, {{unpaidCount}} mesta/o nisu plaćena. Za ${{seatPrice}}/mesečno po mestu procenjena celokupna cena vašeg članstva je ${{totalCost}}/mesečno.",
"team_upgrade_banner_description": "Hvala što probate naš novi plan tima. Primetili smo da vaš tim „{{teamName}}“ treba da se nadogradi.",
"team_upgrade_banner_description": "Hvala vam što isprobavate naš novi plan za timove. Primetili smo da vaš tim „{{teamName}}“ treba da se nadogradi.",
"team_upgrade_banner_action": "Nadogradite ovde",
"team_upgraded_successfully": "Vaš tim je uspešno pretplaćen!",
"use_link_to_reset_password": "Resetujte lozinku koristeći link ispod",
@ -248,6 +249,7 @@
"add_to_calendar": "Dodaj u kalendar",
"add_another_calendar": "Dodaj još jedan kalendar",
"other": "Drugo",
"email_sign_in_subject": "Vaša veza za prijavu na {{appName}}",
"emailed_you_and_attendees": "Poslali smo vama i ostalim učesnicima kalendarski poziv sa svim detaljima.",
"emailed_you_and_attendees_recurring": "Poslali smo vama i ostalim učesnicima kalendarski poziv za prvi od ovih ponavljajućih događaja.",
"emailed_you_and_any_other_attendees": "Vi i ostali učesnici ste dobili e-poštu sa ovim informacijama.",
@ -420,6 +422,7 @@
"current_incorrect_password": "Trenutna lozinka je netačna",
"password_hint_caplow": "Mešavina velikih i malih slova",
"password_hint_min": "Dugačka minimalno 7 znakova",
"password_hint_admin_min": "Dugačka minimalno 15 znakova",
"password_hint_num": "Sadrži barem 1 broj",
"invalid_password_hint": "Lozinka mora da bude dugačka najmanje 7 znakova, da sadrži najmanje jedan broj i da ima mešavinu velikih i malih slova",
"incorrect_password": "Lozinka je netačna.",
@ -463,11 +466,14 @@
"booking_confirmation": "Potvrdite vaš {{eventTypeTitle}} sa {{profileName}}",
"booking_reschedule_confirmation": "Odložite vaš {{eventTypeTitle}} sa {{profileName}}",
"in_person_meeting": "Sastanak uživo ili preko online linka",
"attendeeInPerson": "Lično (adresa polaznika)",
"inPerson": "Lično (adresa organizatora)",
"link_meeting": "Link za sastanak",
"phone_call": "Broj telefona učesnika",
"your_number": "Vaš broj telefona",
"phone_number": "Broj Telefona",
"attendee_phone_number": "Broj telefona učesnika",
"organizer_phone_number": "Broj telefona organizatora",
"host_phone_number": "Vaš broj telefona",
"enter_phone_number": "Unesite broj telefona",
"reschedule": "Odloži",
@ -504,7 +510,7 @@
"add_team_members_description": "Pozovite druge da se pridruže vašem timu",
"add_team_member": "Dodaj člana tima",
"invite_new_member": "Pozovite novog člana",
"invite_new_member_description": "Napomena: Ovim će <1>biti naplaćeno dodatno mesto (15 $/m)</1> putem vaše pretplate.",
"invite_new_member_description": "</1>Napomena: Ovo će <1>povećati vašu pretplatu za dodatno mesto (15 $/mesečno)</1>.",
"invite_new_team_member": "Pozovite nekog u vaš tim.",
"change_member_role": "Promenite ulogu članu tima",
"disable_cal_branding": "Onemogući brendiranje {{appName}}",
@ -554,6 +560,10 @@
"collective": "Kolektiv",
"collective_description": "Rezerviši sastanke kad su izabrani članovi tima dostupni.",
"duration": "Trajanje",
"available_durations": "Dostupna trajanja",
"default_duration": "Podrazumevano trajanje",
"default_duration_no_options": "Najpre izaberite dostupna trajanja",
"multiple_duration_mins": "{{count}} $t(minute_timeUnit)",
"minutes": "Minuti",
"round_robin": "Round Robin",
"round_robin_description": "Ciklusirajte sastanke između više članova tima.",
@ -619,6 +629,7 @@
"teams": "Timovi",
"team": "Tim",
"team_billing": "Naplata za timove",
"team_billing_description": "Upravljajte naplatom za vaš tim",
"upgrade_to_flexible_pro_title": "Promenili smo proces naplate za timove",
"upgrade_to_flexible_pro_message": "Imate članove u timu koji nemaju mesto. Nadogradite svoj pro plan da biste pokrili mesta koja nedostaju.",
"changed_team_billing_info": "Od Januara 2022 vršimo naplatu po mestu za članove tima. Članovi vašeg tima koji su imali Pro besplatno su sada na Pro probnom periodu od 14 dana. Kad probni period istekne ovi članovi biće sakriveni od vašeg tima ukoliko se ne pretplatite na ovu uslugu.",
@ -694,6 +705,7 @@
"hide_event_type": "Sakrij tip događaja",
"edit_location": "Uredi lokaciju",
"into_the_future": "u budućnost",
"when_booked_with_less_than_notice": "Kada je rezervisano sa manje od <time></time> napomenom",
"within_date_range": "U okviru vremenskog perioda",
"indefinitely_into_future": "Na neodređeno u budućnost",
"add_new_custom_input_field": "Dodajte prilagodljivo polje unosa",
@ -713,6 +725,8 @@
"delete_account_confirmation_message": "Da li ste sigurni da želite izbrisati svoj {{appName}} nalog? S kim god ste podelili link ovog naloga više neće moći da ga koristi za rezervacije i sva podešavanja koja imate neće biti sačuvana.",
"integrations": "Integracije",
"apps": "Aplikacije",
"apps_description": "Omogućite aplikacije za vašu instancu Cal-a",
"apps_listing": "Spisak aplikacija",
"category_apps": "{{category}} aplikacije",
"app_store": "App Store",
"app_store_description": "Povezujemo ljude, tehnologiju i radno mesto.",
@ -736,6 +750,7 @@
"toggle_calendars_conflict": "Uključite kalendare koje želite da proverite da li imaju konflikta da biste sprečili dvostruka zakazivanja.",
"select_destination_calendar": "Napravi događaje na",
"connect_additional_calendar": "Poveži se sa dodatnim kalendarom",
"calendar_updated_successfully": "Kalendar je uspešno ažuriran",
"conferencing": "Konferencija",
"calendar": "Kalendar",
"payments": "Plaćanja",
@ -768,6 +783,7 @@
"trending_apps": "Aplikacije u trendu",
"explore_apps": "{{category}} aplikacije",
"installed_apps": "Instalirane aplikacije",
"free_to_use_apps": "Besplatno",
"no_category_apps": "Nema {{category}} aplikacija",
"no_category_apps_description_calendar": "Dodajte kalendar aplikaciju da biste proverili da li postoje konflikti i time sprečili dvostruka zakazivanja",
"no_category_apps_description_conferencing": "Pokušajte da dodate aplikaciju za konferenciju da biste integrisali video poziv sa svojim klijentima",
@ -784,7 +800,7 @@
"analytics": "Analitika",
"empty_installed_apps_headline": "Aplikacije nisu instalirane",
"empty_installed_apps_description": "Aplikacije omogućavaju da unapredite svoj rad i značajno poboljšate svoja zakazivanja.",
"empty_installed_apps_button": "Istražite App Store ili instalirajte neku od aplikacija u nastavku",
"empty_installed_apps_button": "Istražite App Store",
"manage_your_connected_apps": "Upravljajte instaliranim aplikacijama ili promenite postavke",
"browse_apps": "Pregledajte aplikacije",
"features": "Funkcije",
@ -806,6 +822,8 @@
"verify_wallet": "Verifikuj Wallet",
"connect_metamask": "Poveži Metamask",
"create_events_on": "Kreiraj događaje na",
"enterprise_license": "Ovo je enterprise funkcija",
"enterprise_license_description": "Da biste omogućili ovu funkciju, nabavite ključ za primenu na {{consoleUrl}} konzoli i dodajte ga u svoj .env kao CALCOM_LICENCE_KEY. Ako vaš tim već ima licencu, obratite se na {{supportMail}} za pomoć.",
"missing_license": "Nedostaje Licenca",
"signup_requires": "Komercijalna dozvola je neophodna",
"signup_requires_description": "{{companyName}} trenutno ne nudi besplatnu verziju otvorenog koda od stranice za prijavu. Da bi dobili potpuni pristup kompenentama za prijavu, neophodno je da ste vlasnik komercijalne licence. Za ličnu upotrebu preporučujemo Prisma Data Platformu ili bilo koji drugi Postgres interfejs za pravljenje naloga.",
@ -897,6 +915,7 @@
"user_impersonation_heading": "Predstavljanje kao korisnik",
"user_impersonation_description": "Dozvoljava našem timu za podršku da se privremeno prijavi umesto vas kako bismo vam pomogli da brzo rešite sve probleme koje ste nam prijavili.",
"team_impersonation_description": "Dozvolite administratorima svog tima da se privremeno prijave kao vi.",
"allow_booker_to_select_duration": "Dozvoli polazniku da izabere trajanje",
"impersonate_user_tip": "Sva korišćenja ove funkcije se nadgledaju.",
"impersonating_user_warning": "Predstavljanje kao korisnik „{{user}}“.",
"impersonating_stop_instructions": "<0>Kliknite ovde da zaustavite</0>.",
@ -1014,6 +1033,9 @@
"error_removing_app": "Greška prilikom uklanjanja aplikacije",
"web_conference": "Veb konferencija",
"requires_confirmation": "Potrebna je potvrda",
"always_requires_confirmation": "Uvek",
"requires_confirmation_threshold": "Zahteva potvrdu ako je rezervisano sa < {{time}}$t({{unit}}_timeUnit) napomenom",
"may_require_confirmation": "Može da zahteva potvrdu",
"nr_event_type_one": "{{count}} tip događaja",
"nr_event_type_other": "{{count}} tipova događaja",
"add_action": "Dodaj radnju",
@ -1101,6 +1123,9 @@
"event_limit_tab_description": "Koliko često možete da budete rezervisani",
"event_advanced_tab_description": "Podešavanja kalendara i više...",
"event_advanced_tab_title": "Napredno",
"event_setup_multiple_duration_error": "Podešavanje događaja: Višestruka trajanja zahteva najmanje 1 opciju.",
"event_setup_multiple_duration_default_error": "Podešavanje događaja: Izaberite važeće podrazumevano trajanje.",
"event_setup_booking_limits_error": "Ograničenja zakazivanja moraju da budu u rastućem nizu. [dan,nedelja,mesec,godina]",
"select_which_cal": "Izaberite u koji kalendar želite da dodate rezervacije",
"custom_event_name": "Prilagođeno ime događaja",
"custom_event_name_description": "Kreirajte prilagođena imena događaja za prikaz na kalendaru događaja",
@ -1154,8 +1179,11 @@
"invoices": "Fakture",
"embeds": "Ugrađivanja",
"impersonation": "Predstavljanje kao",
"impersonation_description": "Podešavanja za upravljanje lažnim predstavljanjem korisnika",
"users": "Korisnici",
"profile_description": "Upravljajte podešavanjima vašeg {{appName}} profila",
"users_description": "Ovde možete da nađete spisak svih korisnika",
"users_listing": "Spisak korisnika",
"general_description": "Upravljajte podešavanjima za vaš jezik i vremensku zonu",
"calendars_description": "Konfigurišite način na koji tipovi događaja vrše interakciju sa vašim kalendarima",
"appearance_description": "Upravljajte podešavanjima za izgled vaše rezervacije",
@ -1345,7 +1373,7 @@
"billing_frequency": "Učestalost naplate",
"monthly": "Mesečno",
"yearly": "Godišnje",
"checkout": "Odjava",
"checkout": "Kasa",
"your_team_disbanded_successfully": "Vaš tim je uspešno rasformiran",
"error_creating_team": "Greška prilikom kreiranja tima",
"you": "Vi",
@ -1353,22 +1381,61 @@
"member_already_invited": "Član je već pozvan",
"enter_email_or_username": "Unesite imejl ili korisničko ime",
"team_name_taken": "Ime je zauzeto",
"must_enter_team_name": "Unesite ime tima",
"must_enter_team_name": "Morate uneti naziv tima",
"team_url_required": "Morate da unesete URL tima",
"team_url_taken": "Ovaj URL je zauzet",
"team_publish": "Objavite tim",
"number_sms_notifications": "Broj telefona (obaveštenja putem SMS-a)",
"attendee_email_workflow": "Imejl polaznika",
"attendee_email_info": "Imejl osobe koja rezerviše",
"invalid_credential": "Izgleda da je dozvola istekla ili je povučena. Instalirajte ponovo.",
"kbar_search_placeholder": "Unesite komandu ili pretražite...",
"invalid_credential": "O, ne! Izgleda da je dozvola istekla ili je povučena. Instalirajte ponovo.",
"choose_common_schedule_team_event": "Izaberite zajednički raspored",
"choose_common_schedule_team_event_description": "Omogućite to ako želite da koristite zajednički raspored između domaćina. Kada je onemogućen, svaki domaćin može rezervisati na osnovu svog podrazumevanog rasporeda.",
"reason": "Razlog",
"sender_id": "ID pošiljaoca",
"sender_id_error_message": "Dozvoljena su samo slova, brojevi i razmaci (maks. 11 karaktera)",
"test_routing_form": "Obrazac testiranja usmeravanja",
"sender_id_error_message": "Dozvoljena su samo slova, brojevi i razmaci (maks. 11 znakova)",
"test_routing_form": "Testiranje obrasca za usmeravanje",
"test_preview": "Pregled testa",
"route_to": "Usmeri na",
"test_preview_description": "Proverite obrazac za usmeravanje bez slanja bilo kog podatka",
"test_routing": "Testiranje usmeravanja"
"test_routing": "Testiranje usmeravanja",
"payment_app_disabled": "Administrator je onemogućio aplikaciju za plaćanje",
"edit_event_type": "Izmenite tip događaja",
"collective_scheduling": "Kolektivno zakazivanje",
"make_it_easy_to_book": "Učinite lakim da zakažete svoj tim kada je su svi dostupni.",
"find_the_best_person": "Nađite najbolju dostupnu osobu i pregledajte svoj tim.",
"fixed_round_robin": "Fiksno kružno dodeljivanje",
"add_one_fixed_attendee": "Dodajte jednog fiksnog polaznika i kružno dodelite velikom broju polaznika.",
"calcom_is_better_with_team": "Cal.com je bolji uz timove",
"add_your_team_members": "Dodajte članove svog tima vašem tipovima događaja. Koristite kolektivno zakazivanje da uključite svakoga ili pronađete osobu koja najviše odgovara pomoću zakazivanja kružnim dodeljivanjem.",
"booking_limit_reached": "Dostignuta je granica zakazivanja za ovaj tip događaja",
"admin_has_disabled": "Administrator je onemogućio {{appName}}",
"disabled_app_affects_event_type": "Administrator je onemogućio {{appName}} što utiče na vaš tip događaja {{eventType}}",
"disable_payment_app": "Administrator je onemogućio {{appName}} što utiče na vaš tip događaja {{title}}. Polaznici još uvek mogu da zakažu ovaj tip događaja, ali im neće biti zatraženo da plate. Možete da sakrijete ovaj tip događaja da biste to sprečili dok vaš administrator ponovo ne omogući vaš način plaćanja.",
"payment_disabled_still_able_to_book": "Polaznici još uvek mogu da zakažu ovaj tip događaja, ali im neće biti zatraženo da plate. Možete da sakrijete ovaj tip događaja da biste to sprečili dok vaš administrator ponovo ne omogući vaš način plaćanja.",
"app_disabled_with_event_type": "Administrator je onemogućio {{appName}} što utiče na vaš tip događaja {{title}}.",
"app_disabled_video": "Administrator je onemogućio {{appName}} što može da utiče na vaš tip događaja. Ako imate tipove događaja sa {{appName}} kao lokacijom, onda će biti podrazumevano usmereni na Cal Video.",
"app_disabled_subject": "Aplikacija {{appName}} je onemogućena",
"navigate_installed_apps": "Idi na instalirane aplikacije",
"disabled_calendar": "Ako imate instaliran drugi kalendar, nova zakazivanja će biti dodata na njega. Ako nemate, onda povežite novi kalendar da ne biste propustili nova zakazivanja.",
"enable_apps": "Omogući aplikacije",
"enable_apps_description": "Omogući aplikacije koje korisnik može da integriše sa Cal.com",
"app_is_enabled": "Aplikacija {{appName}} je omogućena",
"app_is_disabled": "Aplikacija {{appName}} je onemogućena",
"keys_have_been_saved": "Ključevi su sačuvani",
"disable_app": "Onemogući aplikaciju",
"disable_app_description": "Onemogućavanje ove aplikacije može da stvori probleme u komunikaciji vaših korisnika sa Cal-om",
"edit_keys": "Izmeni ključeve",
"no_available_apps": "Nema dostupnih aplikacija",
"no_available_apps_description": "Uverite se da ima aplikacija u vašoj fascikli za instalaciju pod „packages/app-store“",
"no_apps": "Nema omogućenih aplikacija u ovoj instanci Cal-a",
"apps_settings": "Podešavanja aplikacija",
"fill_this_field": "Popunite ovo polje",
"options": "Opcije",
"enter_option": "Unesite opciju {{index}}",
"add_an_option": "Dodajte opciju",
"radio": "Radio",
"event_type_duplicate_copy_text": "{{slug}}-kopija",
"set_as_default": "Postavi kao podrazumevano"
}

View File

@ -52,6 +52,7 @@
"still_waiting_for_approval": "En bokning väntar fortfarande på godkännande",
"event_is_still_waiting": "Bokningsförfrågan väntar fortfarande: {{attendeeName}} - {{date}} - {{eventType}}",
"no_more_results": "Inga fler resultat",
"no_results": "Inga resultat",
"load_more_results": "Hämta fler resultat",
"integration_meeting_id": "{{integrationName}} mötes-ID: {{meetingId}}",
"confirmed_event_type_subject": "Bekräftad: {{eventType}} med {{name}} vid {{date}}",
@ -248,6 +249,7 @@
"add_to_calendar": "Lägg till i kalender",
"add_another_calendar": "Lägg till en annan kalender",
"other": "Annat",
"email_sign_in_subject": "Din inloggningslänk för {{appName}}",
"emailed_you_and_attendees": "Vi skickade dig och de andra deltagarna en kalenderinbjudan med alla detaljer.",
"emailed_you_and_attendees_recurring": "Vi skickade dig och de andra deltagarna en kalenderinbjudan via e-post för den första av dessa återkommande händelser.",
"emailed_you_and_any_other_attendees": "Du och alla andra deltagare har fått e-post med denna information.",
@ -420,6 +422,7 @@
"current_incorrect_password": "Nuvarande lösenord är felaktigt",
"password_hint_caplow": "Blandning av versaler och gemener",
"password_hint_min": "Minst 8 tecken",
"password_hint_admin_min": "Minst 15 tecken",
"password_hint_num": "Innehåller minst 1 siffra",
"invalid_password_hint": "Lösenordet måste innehålla minst 7 tecken och har minst en siffra samt en blandning av versaler och gemener",
"incorrect_password": "Lösenordet är felaktigt.",
@ -463,11 +466,14 @@
"booking_confirmation": "Bekräfta {{eventTypeTitle}} med {{profileName}}",
"booking_reschedule_confirmation": "Omboka {{eventTypeTitle}} med {{profileName}}",
"in_person_meeting": "Länk eller fysiskt möte",
"attendeeInPerson": "Personligen (deltagandeadress)",
"inPerson": "Personligen (orgnanisatörsadress)",
"link_meeting": "Länka möte",
"phone_call": "Deltagarens telefonnummer",
"your_number": "Ditt telefonnummer",
"phone_number": "Telefonnummer",
"attendee_phone_number": "Deltagarens telefonnummer",
"organizer_phone_number": "Organisatörens telefonnummer",
"host_phone_number": "Ditt telefonnummer",
"enter_phone_number": "Fyll i telefonnummer",
"reschedule": "Omplanera",
@ -554,6 +560,10 @@
"collective": "Kollektiv",
"collective_description": "Schemalägg möten när alla valda teammedlemmar är tillgängliga.",
"duration": "Längd",
"available_durations": "Tillgänglig varaktighet",
"default_duration": "Standardvaraktighet",
"default_duration_no_options": "Välj först tillgänglig varaktighet",
"multiple_duration_mins": "{{count}} $t(minute_timeUnit)",
"minutes": "Minuter",
"round_robin": "Tur och ordning",
"round_robin_description": "Variera möten mellan olika teammedlemmar.",
@ -619,6 +629,7 @@
"teams": "Teams",
"team": "Team",
"team_billing": "Team-fakturering",
"team_billing_description": "Hantera fakturering för ditt team",
"upgrade_to_flexible_pro_title": "Vi har ändrat fakturering för team",
"upgrade_to_flexible_pro_message": "Det finns medlemmar i ditt team utan en plats. Uppgradera din pro plan för att täcka saknade platser.",
"changed_team_billing_info": "Från och med januari 2022 tar vi betalt på plats-basis för teammedlemmar. Medlemmar i ditt team som hade Pro gratis är nu på en 14 dagars provperiod. När deras provperiod löper ut kommer dessa medlemmar att döljas från ditt team om du inte uppgraderar nu.",
@ -694,6 +705,7 @@
"hide_event_type": "Dölj händelsetyp",
"edit_location": "Redigera plats",
"into_the_future": "in i framtiden",
"when_booked_with_less_than_notice": "Vid bokning med mindre än <time></time> varsel",
"within_date_range": "Inom ett datumintervall",
"indefinitely_into_future": "På obestämd tid in i framtiden",
"add_new_custom_input_field": "Lägg till nytt anpassat inmatningsfält",
@ -713,6 +725,8 @@
"delete_account_confirmation_message": "Är du säker på att du vill radera ditt {{appName}} konto? Alla som du har delat din kontolänk med kommer inte längre att kunna boka med hjälp av den och eventuella inställningar du har sparat kommer att gå förlorade.",
"integrations": "Integrationer",
"apps": "Appar",
"apps_description": "Aktivera appar för din Cal-version",
"apps_listing": "App-listning",
"category_apps": "{{category}}-appar",
"app_store": "App Store",
"app_store_description": "Kopplar samman människor, teknik och arbetsplats.",
@ -736,6 +750,7 @@
"toggle_calendars_conflict": "Ändra de kalendrar du vill kontrollera för konflikter för att förhindra dubbelbokningar.",
"select_destination_calendar": "Skapa händelser i",
"connect_additional_calendar": "Anslut ytterligare kalender",
"calendar_updated_successfully": "Kalendern har uppdaterats",
"conferencing": "Konferenser",
"calendar": "Kalender",
"payments": "Betalningar",
@ -768,6 +783,7 @@
"trending_apps": "Trendande appar",
"explore_apps": "{{category}}-appar",
"installed_apps": "Installerade appar",
"free_to_use_apps": "Gratis",
"no_category_apps": "Inga {{category}}-appar",
"no_category_apps_description_calendar": "Lägg till en kalenderapp för att kontrollera konflikter och förhindra dubbelbokningar",
"no_category_apps_description_conferencing": "Prova att lägga till en konferensapp för att integrera videosamtal med dina kunder",
@ -806,6 +822,8 @@
"verify_wallet": "Verifiera plånbok",
"connect_metamask": "Anslut Metamask",
"create_events_on": "Skapa händelser i",
"enterprise_license": "Det här är en företagsfunktion",
"enterprise_license_description": "För att aktivera den här funktionen hämtar du en distributionsnyckel i konsolen {{consoleUrl}} och lägger till den i din .env som CALCOM_LICENSE_KEY. Om ditt team redan har en licens kan du kontakta {{supportMail}} för att få hjälp.",
"missing_license": "Licens saknas",
"signup_requires": "Kommersiell licens krävs",
"signup_requires_description": "{{companyName}} erbjuder för närvarande inte en gratis open source-version av registreringssidan. För att få full tillgång till de registreringskomponenter du behöver för att skaffa en kommersiell licens. För personligt bruk rekommenderar vi Prisma Data Platform eller något annat Postgres-gränssnitt för att skapa konton.",
@ -897,6 +915,7 @@
"user_impersonation_heading": "Användarpersonifiering",
"user_impersonation_description": "Gör det möjligt för vårt supportteam att tillfälligt logga in som du så att vi snabbt kan lösa problem som du rapporterar till oss.",
"team_impersonation_description": "Tillåter dina teamadministratörer att tillfälligt logga in som dig.",
"allow_booker_to_select_duration": "Låt bokaren välja varaktighet",
"impersonate_user_tip": "All användning av denna funktion granskas.",
"impersonating_user_warning": "Imiterar användarnamn \"{{user}}\".",
"impersonating_stop_instructions": "<0>Klicka här för att sluta</0>.",
@ -1014,6 +1033,9 @@
"error_removing_app": "Det gick inte att ta bort app",
"web_conference": "Webbkonferens",
"requires_confirmation": "Kräver bekräftelse",
"always_requires_confirmation": "Alltid",
"requires_confirmation_threshold": "Kräver bekräftelse om bokat med < {{time}} $t({{unit}}_timeUnit) varsel",
"may_require_confirmation": "Kan kräva bekräftelse",
"nr_event_type_one": "{{count}} händelsetyp",
"nr_event_type_other": "{{count}} händelsetyper",
"add_action": "Lägg till åtgärd",
@ -1101,6 +1123,9 @@
"event_limit_tab_description": "Hur ofta du kan bokas",
"event_advanced_tab_description": "Kalenderinställningar och mer...",
"event_advanced_tab_title": "Avancerat",
"event_setup_multiple_duration_error": "Konfiguration av händelse: Flera varaktigheter kräver minst 1 alternativ.",
"event_setup_multiple_duration_default_error": "Konfiguration av händelse: Välj en giltig standardvaraktighet.",
"event_setup_booking_limits_error": "Bokningsgränserna ska vara i stigande ordning. [dag,vecka,månad,år]",
"select_which_cal": "Välj vilken kalender du vill lägga till bokningar till",
"custom_event_name": "Anpassat händelsenamn",
"custom_event_name_description": "Skapa anpassade händelsenamn som ska visas på kalenderhändelse",
@ -1154,8 +1179,11 @@
"invoices": "Fakturor",
"embeds": "Inbäddningar",
"impersonation": "Imitering",
"impersonation_description": "Inställningar för att hantera användarens personifiering",
"users": "Användare",
"profile_description": "Hantera inställningar för din {{appName}}-profil",
"users_description": "Här hittar du en lista med alla användare",
"users_listing": "Användarlistning",
"general_description": "Hantera inställningar för språk och tidszon",
"calendars_description": "Ställ in hur dina händelsetyper interagerar med dina kalendrar",
"appearance_description": "Hantera inställningar för ditt bokningsutseende",
@ -1360,6 +1388,7 @@
"number_sms_notifications": "Telefonnummer (SMS-notiser)",
"attendee_email_workflow": "Deltagarens e-postadress",
"attendee_email_info": "Personens e-postadress för bokning",
"kbar_search_placeholder": "Skriv ett kommando eller sök...",
"invalid_credential": "Åh nej! Det verkar som om behörigheten löpt ut eller återkallats. Installera om igen.",
"choose_common_schedule_team_event": "Välj ett gemensamt schema",
"choose_common_schedule_team_event_description": "Aktivera den här funktionen om du vill använda ett gemensamt schema mellan värdarna. När den är inaktiverad bokas varje värd enligt sitt standardschema.",
@ -1370,5 +1399,43 @@
"test_preview": "Testa förhandsgranskning",
"route_to": "Dirigera till",
"test_preview_description": "Testa ditt routingformulär utan att skicka data",
"test_routing": "Testa routing"
"test_routing": "Testa routing",
"payment_app_disabled": "En administratör har inaktiverat en betalningsapp",
"edit_event_type": "Redigera händelsetyp",
"collective_scheduling": "Kollektiv schemaläggning",
"make_it_easy_to_book": "Gör det enkelt att boka ditt team när alla är tillgängliga.",
"find_the_best_person": "Hitta den bästa personen som finns och cykla genom ditt team.",
"fixed_round_robin": "Fast runda robin",
"add_one_fixed_attendee": "Lägg till en fast deltagare och rundgång för ett antal deltagare.",
"calcom_is_better_with_team": "Cal.com är bättre med teams",
"add_your_team_members": "Lägg till dina teammedlemmar i dina händelsetyper. Använd kollektiv schemaläggning för att inkludera alla eller hitta den lämpligaste personen med rundgångsschemaläggning.",
"booking_limit_reached": "Bokningsgränsen för denna händelsetyp har uppnåtts",
"admin_has_disabled": "En administratör har inaktiverat {{appName}}",
"disabled_app_affects_event_type": "En administratör har inaktiverat {{appName}} vilket påverkar din händelsetyp {{eventType}}",
"disable_payment_app": "Administratören har inaktiverat {{appName}} vilket påverkar din evenemangstyp {{title}}. Deltagarna kan fortfarande boka den här typen av händelse men kommer inte att uppmanas att betala. Du kan dölja händelsetypen för att förhindra detta tills din administratör återaktiverar din betalningsmetod.",
"payment_disabled_still_able_to_book": "Deltagarna kan fortfarande boka den här typen av händelse, men kommer inte att uppmanas att betala. Du kan dölja händelsetypen för att förhindra detta tills din administratör återaktiverar betalningsmetoden.",
"app_disabled_with_event_type": "Administratören har inaktiverat {{appName}} vilket påverkar din händelsetyp {{title}}.",
"app_disabled_video": "Administratören har inaktiverat {{appName}} vilket kan påverka dina händelsetyper. Om du har händelsetyper med {{appName}} som plats kommer den att vara standard för Cal Video.",
"app_disabled_subject": "{{appName}} har inaktiverats",
"navigate_installed_apps": "Gå till installerade appar",
"disabled_calendar": "Om du har en annan kalender installerad kommer nya bokningar att läggas till i den. Om du inte sedan ansluter en ny kalender så missar du inga nya bokningar.",
"enable_apps": "Aktivera appar",
"enable_apps_description": "Aktivera appar som användare kan integrera med Cal.com",
"app_is_enabled": "{{appName}} har aktiverats",
"app_is_disabled": "{{appName}} har inaktiverats",
"keys_have_been_saved": "Nycklar har sparats",
"disable_app": "Inaktivera app",
"disable_app_description": "Inaktivering av den här appen kan orsaka problem med hur dina användare interagerar med Cal",
"edit_keys": "Redigera nycklar",
"no_available_apps": "Det finns inga tillgängliga appar",
"no_available_apps_description": "Se till att det finns appar i din distribution under \"paket/app-store\"",
"no_apps": "Det finns inga appar aktiverade i denna instans av Cal",
"apps_settings": "App-inställningar",
"fill_this_field": "Fyll i det här fältet",
"options": "Alternativ",
"enter_option": "Ange alternativ {{index}}",
"add_an_option": "Lägg till ett alternativ",
"radio": "Radio",
"event_type_duplicate_copy_text": "{{slug}}-kopia",
"set_as_default": "Ange som standard"
}

View File

@ -52,6 +52,7 @@
"still_waiting_for_approval": "Bir etkinlik hâlâ onay bekliyor",
"event_is_still_waiting": "Etkinlik isteği hâlâ beklemede: {{attendeeName}} - {{date}} - {{eventType}}",
"no_more_results": "Başka sonuç yok",
"no_results": "Sonuç yok",
"load_more_results": "Daha fazla sonuç yükle",
"integration_meeting_id": "{{integrationName}} toplantı kimliği: {{meetingId}}",
"confirmed_event_type_subject": "Onaylandı: {{date}} tarihinde {{name}} ile {{eventType}}",
@ -248,6 +249,7 @@
"add_to_calendar": "Takvime ekle",
"add_another_calendar": "Başka bir takvim ekle",
"other": "Diğer",
"email_sign_in_subject": "{{appName}} için giriş bağlantınız",
"emailed_you_and_attendees": "Size ve diğer katılımcılara tüm ayrıntıları içeren bir takvim daveti e-postası gönderdik.",
"emailed_you_and_attendees_recurring": "Size ve diğer katılımcılara bu yinelenen etkinliklerin ilki için bir takvim daveti e-postası gönderdik.",
"emailed_you_and_any_other_attendees": "Bu bilgiler size ve diğer katılımcılara e-posta ile gönderildi.",
@ -420,6 +422,7 @@
"current_incorrect_password": "Mevcut şifre hatalı",
"password_hint_caplow": "Büyük ve küçük harf karışımı",
"password_hint_min": "En az 8 karakter uzunluğunda",
"password_hint_admin_min": "Minimum uzunluk 15 karakter",
"password_hint_num": "En az 1 rakam içermelidir",
"invalid_password_hint": "Şifre en az 7 karakter uzunluğunda olmalı, en az bir rakam içermeli ve büyük ve küçük harflerin karışımından oluşmalıdır",
"incorrect_password": "Şifre hatalı.",
@ -463,11 +466,14 @@
"booking_confirmation": "{{profileName}} ile {{eventTypeTitle}} etkinliğinizi onaylayın",
"booking_reschedule_confirmation": "{{profileName}} ile {{eventTypeTitle}} etkinliğinizi yeniden planlayın",
"in_person_meeting": "Yüz yüze görüşme",
"attendeeInPerson": "Şahsen (Katılımcı Adresi)",
"inPerson": "Şahsen (Organizatör Adresi)",
"link_meeting": "Bağlantılı toplantı",
"phone_call": "Katılımcı Telefon Numarası",
"your_number": "Telefon numaranız",
"phone_number": "Telefon Numarası",
"attendee_phone_number": "Katılımcı Telefon Numarası",
"organizer_phone_number": "Organizatör Telefon Numarası",
"host_phone_number": "Telefon Numaranız",
"enter_phone_number": "Telefon numarası girin",
"reschedule": "Yeniden planla",
@ -554,6 +560,10 @@
"collective": "Toplu",
"collective_description": "Tüm seçili ekip üyeleri müsait olduğunda toplantıları planlayın.",
"duration": "Süre",
"available_durations": "Uygun süreler",
"default_duration": "Varsayılan süre",
"default_duration_no_options": "Lütfen önce uygun süreleri seçin",
"multiple_duration_mins": "{{count}} $t(minute_timeUnit)",
"minutes": "Dakika",
"round_robin": "Round Robin",
"round_robin_description": "Toplantıları birden fazla ekip üyesi arasında döndürün.",
@ -619,6 +629,7 @@
"teams": "Ekipler",
"team": "Ekip",
"team_billing": "Ekip Faturası",
"team_billing_description": "Ekibiniz için faturalandırmayı yönetin",
"upgrade_to_flexible_pro_title": "Ekipler için faturayı değiştirdik",
"upgrade_to_flexible_pro_message": "Ekibinizde alanı olmayan üyeler var. Eksik alanları doldurmak için profesyonel plana yükseltin.",
"changed_team_billing_info": "Ocak 2022'den itibaren her ekip üyesi alanı için ücret alıyoruz. Ücretsiz PRO'ya sahip olan ekip üyeleriniz şu anda 14 günlük deneme süresinde. Deneme süreleri sona erdiğinde ve yükseltme yapmadığınızda bu üyeler ekibinizden gizlenecektir.",
@ -713,6 +724,8 @@
"delete_account_confirmation_message": "{{appName}} hesabınızı silmek istediğinizden emin misiniz? Hesap bağlantısı paylaştığınız herkes artık bu bağlantıyı kullanarak rezervasyon yapamayacak ve kaydettiğiniz tüm tercihler kaybolacak.",
"integrations": "Entegrasyonlar",
"apps": "Uygulamalar",
"apps_description": "Cal örneğiniz için uygulamayı etkinleştirin",
"apps_listing": "Uygulama listesi",
"category_apps": "{{category}} uygulamaları",
"app_store": "App Store",
"app_store_description": "İnsanları, teknolojiyi ve çalışma ortamını birbirine bağlıyor.",
@ -736,6 +749,7 @@
"toggle_calendars_conflict": "Çifte rezervasyonu önlemek için çakışmaları kontrol etmek istediğiniz takvimleri değiştirin.",
"select_destination_calendar": "Şurada etkinlik oluştur:",
"connect_additional_calendar": "Ek takvim bağlayın",
"calendar_updated_successfully": "Takvim başarıyla güncellendi",
"conferencing": "Konferans",
"calendar": "Takvim",
"payments": "Ödemeler",
@ -768,6 +782,7 @@
"trending_apps": "Popüler Uygulamalar",
"explore_apps": "{{category}} uygulamaları",
"installed_apps": "Yüklü Uygulamalar",
"free_to_use_apps": "Ücretsiz",
"no_category_apps": "{{category}} uygulaması yok",
"no_category_apps_description_calendar": "Çifte rezervasyonları önlemek amacıyla çakışmaları kontrol etmek için bir takvim uygulaması ekleyin",
"no_category_apps_description_conferencing": "Müşterilerinizle görüntülü arama yapmak için bir konferans uygulaması eklemeyi deneyin",
@ -806,6 +821,8 @@
"verify_wallet": "Cüzdanı Doğrula",
"connect_metamask": "MetaMask'i Bağla",
"create_events_on": "Şurada etkinlik oluşturun:",
"enterprise_license": "Bu bir kurumsal özelliktir",
"enterprise_license_description": "Bu özelliği etkinleştirmek için {{consoleUrl}} konsolundan bir dağıtım anahtarı alın ve bunu .env dosyanıza CALCOM_LICENSE_KEY olarak ekleyin. Ekibinizin zaten bir lisansı varsa yardım için lütfen {{supportMail}} adresinden iletişime geçin.",
"missing_license": "Eksik Lisans",
"signup_requires": "Ticari lisans gerekli",
"signup_requires_description": "{{companyName}} şu anda kayıt sayfasının ücretsiz, açık kaynaklı bir sürümünü sunmamaktadır. Kayıt bileşenlerine tam erişim elde etmek için ticari bir lisans almanız gerekiyor. Kişisel kullanım için hesap oluşturmak üzere Prisma Data veya başka bir Postgres arayüzü öneriyoruz.",
@ -897,6 +914,7 @@
"user_impersonation_heading": "Kullanıcı Kimliğine Bürünme",
"user_impersonation_description": "Bize bildirdiğiniz hataları hızlı bir şekilde çözmek için destek ekibimizin sizin adınıza geçici olarak oturum açmasına izin verir.",
"team_impersonation_description": "Ekip üyelerinizin sizin adınıza geçici olarak oturum açmasına izin verir.",
"allow_booker_to_select_duration": "Rezervasyon yapan kişinin süreyi seçmesine izin ver",
"impersonate_user_tip": "Bu özelliğin tüm kullanımları denetlenir.",
"impersonating_user_warning": "\"{{user}}\" kullanıcı adı kimliğine bürünme.",
"impersonating_stop_instructions": "<0>Durdurmak için Buraya tıklayın</0>.",
@ -1014,6 +1032,8 @@
"error_removing_app": "Uygulama kaldırılırken bir hata oluştu",
"web_conference": "Web konferansı",
"requires_confirmation": "Onay gerekli",
"always_requires_confirmation": "Her zaman",
"may_require_confirmation": "Onay gerekebilir",
"nr_event_type_one": "{{count}} etkinlik türü",
"nr_event_type_other": "{{count}} etkinlik türü",
"add_action": "Eylem ekle",
@ -1101,6 +1121,9 @@
"event_limit_tab_description": "Ne sıklıkla rezervasyon yapabilirsiniz",
"event_advanced_tab_description": "Takvim ayarları ve daha fazlası...",
"event_advanced_tab_title": "Gelişmiş",
"event_setup_multiple_duration_error": "Etkinlik Kurulumu: Birden çok süre için en az 1 seçenek gereklidir.",
"event_setup_multiple_duration_default_error": "Etkinlik Kurulumu: Lütfen geçerli bir varsayılan süre seçin.",
"event_setup_booking_limits_error": "Rezervasyon limitleri artan sırada olmalıdır. [gün,hafta,ay,yıl]",
"select_which_cal": "Rezervasyonların hangi takvime ekleneceğini seçin",
"custom_event_name": "Özel etkinlik adı",
"custom_event_name_description": "Takvim etkinliğinde görüntülenecek özelleştirilmiş etkinlik adları oluşturun",
@ -1154,8 +1177,11 @@
"invoices": "Faturalar",
"embeds": "Gömülüler",
"impersonation": "Kimliğe bürünme",
"impersonation_description": "Kullanıcı kimliğine bürünmeyi yönetmek için ayarlar",
"users": "Kullanıcılar",
"profile_description": "{{appName}} profiliniz için ayarları yönetin",
"users_description": "Burada tüm kullanıcıların bir listesini bulabilirsiniz",
"users_listing": "Kullanıcı listesi",
"general_description": "Diliniz ve saat diliminiz için ayarları yönetin",
"calendars_description": "Etkinlik türlerinizin takvimlerinizle nasıl etkileşimde bulunacağını yapılandırın",
"appearance_description": "Rezervasyon görünümünüz için ayarları yönetin",
@ -1360,6 +1386,7 @@
"number_sms_notifications": "Telefon numarası (SMS bildirimleri)",
"attendee_email_workflow": "Katılımcı e-postası",
"attendee_email_info": "Rezervasyon yaptıran kişinin e-postası",
"kbar_search_placeholder": "Bir komut girin veya arayın...",
"invalid_credential": "Hata! İznin süresi dolmuş veya iptal edilmiş gibi görünüyor. Lütfen yeniden yükleyin.",
"choose_common_schedule_team_event": "Ortak bir program seçin",
"choose_common_schedule_team_event_description": "Toplantı sahipleri arasında ortak bir plan kullanmak istiyorsanız bunu etkinleştirin. Devre dışı bırakıldığında, her toplantı sahibi varsayılan programına göre rezerve edilir.",
@ -1370,5 +1397,31 @@
"test_preview": "Ön İzlemeyi Test Et",
"route_to": "Şuraya yönlendir:",
"test_preview_description": "Herhangi bir veri göndermeden yönlendirme formunuzu test edin",
"test_routing": "Yönlendirmeyi Test Et"
"test_routing": "Yönlendirmeyi Test Et",
"payment_app_disabled": "Bir yönetici, ödeme uygulamasını devre dışı bıraktı",
"edit_event_type": "Etkinlik türünü düzenle",
"collective_scheduling": "Toplu Planlama",
"calcom_is_better_with_team": "Cal.com ekiplerle daha iyidir",
"add_your_team_members": "Ekip üyelerinizi etkinlik türlerinize ekleyin. Herkesi eklemek için toplu planlamayı kullanın veya döngüsel planlama ile en uygun kişiyi bulun.",
"booking_limit_reached": "Bu etkinlik türü için Rezervasyon Sınırına ulaşıldı",
"admin_has_disabled": "Bir yönetici {{appName}} uygulamasını devre dışı bıraktı",
"app_disabled_with_event_type": "Yönetici, etkinlik {{title}} türünüzü etkileyen {{appName}} uygulamasını devre dışı bıraktı.",
"app_disabled_subject": "{{appName}} devre dışı bırakıldı",
"navigate_installed_apps": "Yüklü uygulamalara git",
"disabled_calendar": "Yüklü başka bir takviminiz varsa yeni randevular bu takvime eklenecektir. Aksi takdirde yeni bir takvim bağlamazsanız yeni rezervasyonları kaçırabilirsiniz.",
"enable_apps": "Uygulamaları Etkinleştir",
"enable_apps_description": "Kullanıcıların Cal.com ile entegre edebileceği uygulamaları etkinleştirin",
"app_is_enabled": "{{appName}} etkinleştirildi",
"app_is_disabled": "{{appName}} devre dışı bırakıldı",
"keys_have_been_saved": "Anahtarlar kaydedildi",
"disable_app": "Uygulamaları Devre Dışı Bırak",
"edit_keys": "Anahtarları Düzenle",
"no_available_apps": "Kullanılabilir uygulama yok",
"apps_settings": "Uygulama ayarları",
"fill_this_field": "Lütfen bu alanı doldurun",
"options": "Seçenekler",
"enter_option": "{{index}} Seçeneğini Girin",
"add_an_option": "Bir seçenek ekle",
"radio": "Radio",
"set_as_default": "Varsayılan olarak ayarla"
}

View File

@ -109,7 +109,7 @@
"upgrade_to_per_seat": "Перейдіть на версію з оплатою за місце",
"team_upgrade_seats_details": "У вас стільки неоплачених місць для учасників команди: {{unpaidCount}} із {{memberCount}}. Одне місце коштує {{seatPrice}} $ за місяць, тому загальна вартість вашого членства складатиме приблизно {{totalCost}} $ за місяць.",
"team_upgrade_banner_description": "Дякуємо, що спробували наш новий план для команд! Ми помітили, що вашу команду «{{teamName}}» потрібно перевести на план вищого рівня.",
"team_upgrade_banner_action": "Перейдіть на інший план тут",
"team_upgrade_banner_action": "Перейдіть на інший план",
"team_upgraded_successfully": "Версію для вашої команди оновлено!",
"use_link_to_reset_password": "Скористайтеся посиланням нижче, щоб скинути пароль",
"hey_there": "Привіт!",
@ -419,7 +419,7 @@
"forgotten_secret_description": "Якщо ви загубили або забули цей таємний код, його можна змінити, але в такому випадку потрібно буде оновити всі інтеграції, що використовують його.",
"current_incorrect_password": "Поточний пароль неправильний",
"password_hint_caplow": "Комбінація великих і малих літер",
"password_hint_min": "Не менше ніж 7 символів завдовжки",
"password_hint_min": "Щонайменше 8 символів завдовжки",
"password_hint_num": "Містить принаймні 1 цифру",
"invalid_password_hint": "Пароль має містити не менше ніж 7 символів: принаймні одну цифру та комбінацію великих і малих літер",
"incorrect_password": "Пароль неправильний.",
@ -784,7 +784,7 @@
"analytics": "Аналітика",
"empty_installed_apps_headline": "Не встановлено жодних додатків",
"empty_installed_apps_description": "Додатки дають змогу оптимізувати робочий процес і спростити роботу з графіком.",
"empty_installed_apps_button": "Переглянути магазин додатків або встановити наведені нижче додатки",
"empty_installed_apps_button": "Переглянути магазин додатків",
"manage_your_connected_apps": "Керуйте встановленими додатками й налаштуваннями",
"browse_apps": "Огляд додатків",
"features": "Функції",
@ -1228,7 +1228,7 @@
"exchange_authentication_ntlm": "Автентифікація NTLM",
"exchange_compression": "Стиснення GZip",
"routing_forms_description": "Тут можна переглянути всі створені вами форми й переспрямування.",
"routing_forms_send_email_owner": "Надсилати власнику електронний лист",
"routing_forms_send_email_owner": "Надіслати власнику електронний лист",
"routing_forms_send_email_owner_description": "Коли надсилається форма, власник отримує електронний лист",
"add_new_form": "Додати нову форму",
"form_description": "Створіть власну форму для переспрямування автора бронювання",
@ -1345,30 +1345,30 @@
"billing_frequency": "Періодичність виставлення рахунків",
"monthly": "Щомісяця",
"yearly": "Щороку",
"checkout": "Оформлення замовлення",
"checkout": "Оплата",
"your_team_disbanded_successfully": "Вашу команду розформовано",
"error_creating_team": "Помилка створення команди",
"you": "Ви",
"send_email": "Надіслати ел. лист",
"send_email": "Надіслати лист",
"member_already_invited": "Учасника вже запрошено",
"enter_email_or_username": "Введіть адресу електронної пошти або ім’я користувача",
"enter_email_or_username": "Введіть електронну адресу або ім’я користувача",
"team_name_taken": "Це ім’я вже зайнято",
"must_enter_team_name": "Необхідно ввести назву команди",
"team_url_required": "Необхідно ввести URL-адресу команди",
"must_enter_team_name": "Потрібно ввести назву команди",
"team_url_required": "Потрібно ввести URL-адресу команди",
"team_url_taken": "Цю URL-адресу вже зайнято",
"team_publish": "Опублікувати команду",
"number_sms_notifications": "Номер телефону (SMS-сповіщення)",
"attendee_email_workflow": "Адреса ел. пошти учасника",
"attendee_email_info": "Адреса ел. пошти особи, яка бронює",
"invalid_credential": "Отакої! Схоже, дозвіл більше не дійсний або його відкликано. Повторіть інсталяцію.",
"invalid_credential": "Отакої! Схоже, дозвіл більше не дійсний або його відкликано. Перевстановіть додаток знову.",
"choose_common_schedule_team_event": "Виберіть спільний розклад",
"choose_common_schedule_team_event_description": "Увімкніть цей параметр, щоб використовувати спільний для двох ведучих розклад. Якщо цей параметр вимкнено, бронювання для кожного з ведучих відбуватиметься за їхніми власними графіками.",
"reason": "Причина",
"sender_id": "Ідентифікатор відправника",
"sender_id": "ID відправника",
"sender_id_error_message": "Дозволяються тільки літери, цифри та пробіли (макс. 11 символів)",
"test_routing_form": "Перевірка форми переспрямування",
"test_preview": "Перевірити попередній перегляд",
"route_to": "Куди переспрямувати",
"test_preview_description": "Перевірка вашої форми переспрямування без надсилання даних",
"route_to": "Кінцева точка",
"test_preview_description": "Перевірте свою форму переспрямування без надсилання даних",
"test_routing": "Перевірка переспрямування"
}

View File

@ -16,8 +16,8 @@
"event_request_cancelled": "Sự kiện của bạn đã bị huỷ",
"organizer": "Tổ chức",
"need_to_reschedule_or_cancel": "Cần phải đổi lịch hẹn hoặc huỷ?",
"cancellation_reason": "Lý do huỷ",
"cancellation_reason_placeholder": "Tại sao bạn huỷ? (nếu có)",
"cancellation_reason": "Lý do hủy",
"cancellation_reason_placeholder": "Lý do bạn muốn hủy?",
"rejection_reason": "Lý do từ chối",
"rejection_reason_title": "Từ chối lịch hẹn?",
"rejection_reason_description": "Bạn có chắc bạn muốn từ chối đặt lịch hẹn? Chúng tôi sẽ cho người đặt biết. Bạn có thể cho người dùng biết lý do từ chối dưới đây.",
@ -52,6 +52,7 @@
"still_waiting_for_approval": "Một sự kiện vẫn đang chờ được chấp thuận",
"event_is_still_waiting": "Lời mời sự kiện vẫn đang chờ: {{attendeeName}} - {{date}} - {{eventType}}",
"no_more_results": "Không còn kết quả",
"no_results": "Không có kết quả",
"load_more_results": "Tải thêm kết quả",
"integration_meeting_id": "{{integrationName}} ID buổi hẹn: {{meetingId}}",
"confirmed_event_type_subject": "Đã chấp nhận: {{eventType}} với {{name}} tại {{date}}",
@ -108,7 +109,7 @@
"link_expires": "p.s. Link hết hạn trong vòng {{expiresIn}} tiếng.",
"upgrade_to_per_seat": "Nâng cấp từng tài khoản",
"team_upgrade_seats_details": "Trong tổng {{memberCount}} thành viên của nhóm bạn, {{unpaidCount}} thành viên chưa được thanh toán. Với {{seatPrice}}$/tháng mỗi thành viên, tổng chi phí cho nhóm bạn là {{totalCost}}$/tháng.",
"team_upgrade_banner_description": "Cám ơn bạn đã dùng thử gói mới dành cho nhóm. Chúng tôi để ý thấy nhóm \"{{teamName}}\" của bạn cần được nâng cấp.",
"team_upgrade_banner_description": "Cám ơn bạn đã dùng thử gói kế hoạch mới dành cho nhóm. Chúng tôi để ý thấy nhóm \"{{teamName}}\" của bạn cần được nâng cấp.",
"team_upgrade_banner_action": "Nâng cấp tại đây",
"team_upgraded_successfully": "Nhóm bạn đã được nâng cấp thành công!",
"use_link_to_reset_password": "Sử dụng đường link dưới đây để thay đổi mật khẩu của bạn",
@ -248,6 +249,7 @@
"add_to_calendar": "Thêm vào lịch",
"add_another_calendar": "Thêm lịch khác",
"other": "Khác",
"email_sign_in_subject": "Liên kết đăng nhập cho {{appName}}",
"emailed_you_and_attendees": "Chúng tôi đã gửi email cho bạn và những người tham dự khác một lời mời theo lịch hẹn với tất cả các chi tiết đi kèm.",
"emailed_you_and_attendees_recurring": "Chúng tôi đã gửi email cho bạn và những người tham dự khác một lời mời theo lịch hẹn cho sự kiện định kỳ đầu tiên trong số những sự kiện định kỳ này.",
"emailed_you_and_any_other_attendees": "Bạn và mọi người tham dự khác đã được gửi email với thông tin này.",
@ -419,7 +421,7 @@
"forgotten_secret_description": "Nếu bạn mất hoặc quên từ bí mật này, bạn có thể đổi nó, nhưng hãy biết cho tất cả mọi tích hợp dùng từ bí mật này sẽ cần được cập nhật",
"current_incorrect_password": "Mật khẩu hiện tại không đúng",
"password_hint_caplow": "Kết hợp các chữ cái in hoa & in thường",
"password_hint_min": "Tối thiểu có 7 ký tự",
"password_hint_min": "Dài tối thiểu 8 ký tự",
"password_hint_num": "Chứa ít nhất 1 con số",
"invalid_password_hint": "Mật khẩu phải có tối thiểu 7 ký tự chứa ít nhất một con số và phải kết hợp chữ cái in hoa lẫn in thường",
"incorrect_password": "Mật khẩu không đúng.",
@ -504,7 +506,7 @@
"add_team_members_description": "Mời người khác tham gia nhóm của bạn",
"add_team_member": "Thêm thành viên nhóm",
"invite_new_member": "Mời một thành viên mới",
"invite_new_member_description": "Lưu ý: Cái này sẽ <1>tốn phí thêm thành viên (15$/tháng)</1> cho gói đăng ký của bạn.",
"invite_new_member_description": "Lưu ý: Việc này sẽ <1>mất phí bổ sung chỗ ngồi (15$/tháng)</1> cho gói đăng ký của bạn.",
"invite_new_team_member": "Mời ai đó vào nhóm của bạn.",
"change_member_role": "Thay đổi vai trò thành viên trong nhóm",
"disable_cal_branding": "Tắt thương hiệu {{appName}}",
@ -784,7 +786,7 @@
"analytics": "Phân tích",
"empty_installed_apps_headline": "Chưa cài ứng dụng nào",
"empty_installed_apps_description": "Ứng dụng cho phép bạn cải thiện tiến độ công việc và cải thiện đáng kể việc sắp xếp tổ chức.",
"empty_installed_apps_button": "Khám phá App Store hoặc Cài đặt các ứng dụng dưới đây",
"empty_installed_apps_button": "Duyệt xem App Store",
"manage_your_connected_apps": "Quản lý các ứng dụng đã cài đặt của bạn hoặc thay đổi cài đặt",
"browse_apps": "Xem ứng dụng",
"features": "Tính năng",
@ -1356,16 +1358,16 @@
"must_enter_team_name": "Cần phải nhập một tên nhóm",
"team_url_required": "Cần phải nhập một URL của nhóm",
"team_url_taken": "URL này đã có người lấy rồi",
"team_publish": "Công bố nhóm",
"team_publish": "Đăng nhóm",
"number_sms_notifications": "Số điện thoại (thông báo SMS)",
"attendee_email_workflow": "Email người tham dự",
"attendee_email_info": "Email người tham gia lịch hẹn",
"invalid_credential": "Ôi không! Có vẻ như quyền đã hết hạn hoặc đã bị thu hồi. Vui lòng cài đặt lại.",
"choose_common_schedule_team_event": "Chọn một lịch chung",
"choose_common_schedule_team_event_description": "Bật cái này nếu bạn muốn dùng lịch thông thường giữa các chủ sự kiện. Khi tắt đi, mỗi chủ sự kiện sẽ được đặt lịch theo lịch mặc định của họ.",
"choose_common_schedule_team_event_description": "Bật mục này nếu bạn muốn dùng lịch thông thường giữa các chủ sự kiện. Khi tắt đi, mỗi chủ sự kiện sẽ được đặt lịch theo lịch mặc định của họ.",
"reason": "Lý do",
"sender_id": "ID người gửi",
"sender_id_error_message": "Chỉ chữ cái, số và khoảng trắng là được phép (tối đa 11 ký tự)",
"sender_id_error_message": "Chỉ được phép sử dụng chữ cái, số và khoảng trắng (tối đa 11 ký tự)",
"test_routing_form": "Kiểm tra Biểu mẫu định hướng",
"test_preview": "Kiểm tra Xem trước",
"route_to": "Định hướng đến",

View File

@ -52,6 +52,7 @@
"still_waiting_for_approval": "有活动正在等待您的批准",
"event_is_still_waiting": "活动请求仍在等待中: {{attendeeName}} - {{date}} - {{eventType}}",
"no_more_results": "无更多结果",
"no_results": "无结果",
"load_more_results": "加载更多结果",
"integration_meeting_id": "{{integrationName}} 会议 ID: {{meetingId}}",
"confirmed_event_type_subject": "已确认: 和 {{name}} 在 {{date}} 的 {{eventType}}",

View File

@ -16,8 +16,8 @@
"event_request_cancelled": "預定的活動取消了",
"organizer": "主辦",
"need_to_reschedule_or_cancel": "要重新規劃還是取消嗎?",
"cancellation_reason": "取消原因",
"cancellation_reason_placeholder": "為什麼打算取消?(選填)",
"cancellation_reason": "取消原因(選填)",
"cancellation_reason_placeholder": "您為什麼要取消?",
"rejection_reason": "拒絕的原因",
"rejection_reason_title": "拒絕預約嗎?",
"rejection_reason_description": "確定要拒絕預約嗎?我們會讓想要進行預約的對方知道。可以在下方提供原因。",
@ -109,7 +109,7 @@
"upgrade_to_per_seat": "升級為算座位數",
"team_upgrade_seats_details": "團隊裡一共有 {{memberCount}} 位成員,{{unpaidCount}} 個座位不是付費的。每個月每個座位的費用是 ${{seatPrice}},因此會員資格總共是每個月 ${{totalCost}}。",
"team_upgrade_banner_description": "謝謝您試用我們新的團隊方案。我們注意到您的團隊「{{teamName}}」需要升級。",
"team_upgrade_banner_action": "到這裡升級",
"team_upgrade_banner_action": "在此處升級",
"team_upgraded_successfully": "團隊成功升級!",
"use_link_to_reset_password": "使用以下連結重置密碼",
"hey_there": "哈囉,",
@ -419,7 +419,7 @@
"forgotten_secret_description": "如果您已遺失或忘記此密碼,可以變更密碼,但請注意,所有使用此密碼的整合都需更新",
"current_incorrect_password": "現有密碼不正確",
"password_hint_caplow": "大小寫字母組合",
"password_hint_min": "至少 7 個字元長",
"password_hint_min": "至少 8 個字元長",
"password_hint_num": "至少包含 1 個數字",
"invalid_password_hint": "密碼長度至少要有 7 個字元,其中包含至少一個數字和大小寫字母組合",
"incorrect_password": "密碼不正確。",
@ -601,7 +601,7 @@
"monthly_other": "月",
"yearly_one": "年",
"yearly_other": "年",
"plus_more": "{{count}} 個以上",
"plus_more": "{{count}} 個",
"max": "最大",
"single_theme": "主題",
"brand_color": "品牌顏色",
@ -784,7 +784,7 @@
"analytics": "分析",
"empty_installed_apps_headline": "沒有已安裝的應用程式",
"empty_installed_apps_description": "應用程式使您能夠增強您的工作流程並顯著改善您的日程安排。",
"empty_installed_apps_button": "探索 App Store 或從下列應用程式中選擇安裝",
"empty_installed_apps_button": "瀏覽 App Store",
"manage_your_connected_apps": "管理已安裝的應用程式或是變更設定",
"browse_apps": "瀏覽應用程式",
"features": "功能",
@ -1229,7 +1229,7 @@
"exchange_compression": "GZip 壓縮",
"routing_forms_description": "您可以在這裡查看您已建立的所有表單和引導路徑。",
"routing_forms_send_email_owner": "傳送電子郵件給擁有者",
"routing_forms_send_email_owner_description": "表單提交傳送電子郵件給擁有者",
"routing_forms_send_email_owner_description": "表單提交傳送電子郵件給擁有者",
"add_new_form": "新增新表單",
"form_description": "建立您的表單來引導預約者",
"copy_link_to_form": "複製連結至表單",
@ -1343,10 +1343,10 @@
"no_event_types": "未設定活動類型",
"no_event_types_description": "{{name}} 尚未設定任何活動類型供您預約。",
"billing_frequency": "付費頻率",
"monthly": "月",
"yearly": "年",
"monthly": "月",
"yearly": "年",
"checkout": "結帳",
"your_team_disbanded_successfully": "解散團隊成功",
"your_team_disbanded_successfully": "已成功解散您的團隊",
"error_creating_team": "建立團隊時發生錯誤",
"you": "您",
"send_email": "傳送電子郵件",
@ -1360,15 +1360,15 @@
"number_sms_notifications": "電話號碼 (簡訊通知)",
"attendee_email_workflow": "與會者電子郵件",
"attendee_email_info": "預約人電子郵件",
"invalid_credential": "糟糕!權限似乎過期或遭到撤銷。請再次重新安裝。",
"choose_common_schedule_team_event": "選擇一般行程表",
"choose_common_schedule_team_event_description": "若要在多個主辦者之間使用一般行程表,請啟用此選項。停用時,會根據每個主辦者預設的行程表預約對方的時間。",
"invalid_credential": "糟糕!權限似乎過期或遭到撤銷。請重新安裝。",
"choose_common_schedule_team_event": "選擇公用行程表",
"choose_common_schedule_team_event_description": "若要在多個主辦者之間使用公用行程表,請啟用此選項。停用時,會根據每個主辦者預設的行程表預約對方的時間。",
"reason": "原因",
"sender_id": "傳送者 ID",
"sender_id_error_message": "只能使用字母、數字和空格 (最多 11 個字元)",
"test_routing_form": "測試引導表單",
"test_preview": "測試預覽",
"route_to": "引導至",
"test_preview_description": "不提交任何資料並測試引導表單",
"test_preview_description": "測試引導表單而不提交任何資料",
"test_routing": "測試引導"
}

@ -1 +1 @@
Subproject commit c358039b1c1d0c5c0b7901c6489261c4e8e8c6e0
Subproject commit 30cbf3990bb5baae7fd4fd46c93f6e5bc402f84c

View File

@ -14,8 +14,6 @@ import { AppGetServerSidePropsContext, AppPrisma } from "@calcom/types/AppGetSer
import { inferSSRProps } from "@calcom/types/inferSSRProps";
import { Button, showToast } from "@calcom/ui";
import { useExposePlanGlobally } from "@lib/hooks/useExposePlanGlobally";
import FormInputFields from "../../components/FormInputFields";
import { getSerializableForm } from "../../lib/getSerializableForm";
import { processRoute } from "../../lib/processRoute";
@ -26,7 +24,6 @@ function RoutingForm({ form, profile, ...restProps }: inferSSRProps<typeof getSe
const formFillerIdRef = useRef(uuidv4());
const isEmbed = useIsEmbed(restProps.isEmbed);
useTheme(profile.theme);
useExposePlanGlobally(profile.plan);
// TODO: We might want to prevent spam from a single user by having same formFillerId across pageviews
// But technically, a user can fill form multiple times due to any number of reasons and we currently can't differentiate b/w that.
// - like a network error
@ -183,7 +180,6 @@ export const getServerSideProps = async function getServerSideProps(
theme: true,
brandColor: true,
darkBrandColor: true,
plan: true,
},
},
},
@ -202,7 +198,6 @@ export const getServerSideProps = async function getServerSideProps(
theme: form.user.theme,
brandColor: form.user.brandColor,
darkBrandColor: form.user.darkBrandColor,
plan: form.user.plan,
},
form: getSerializableForm(form),
},

View File

@ -20,7 +20,7 @@ export const metadata = {
trending: false,
url: "https://cal.com/",
verified: true,
isGlobal: false,
isGlobal: true,
email: "help@cal.com",
appData: {
location: {

View File

@ -29,7 +29,7 @@ export default async function handler(req: NextApiRequest, res: NextApiResponse)
const userData = await prisma.user.findFirst({
where: { id: userId },
select: { id: true, plan: true, metadata: true },
select: { id: true, metadata: true },
});
if (!userData) {
res.status(404).json({ message: "Missing user data" });

View File

@ -1,88 +0,0 @@
#!/usr/bin/env ts-node
// To run this script: `yarn downgrade 2>&1 | tee result.log`
import { Prisma, UserPlan } from "@prisma/client";
import dayjs from "@calcom/dayjs";
import { TRIAL_LIMIT_DAYS } from "@calcom/lib/constants";
import prisma from "@calcom/prisma";
import { getStripeCustomerIdFromUserId } from "./customer";
import stripe from "./server";
/**
* Deprecated or should be updated
*/
export async function downgradeIllegalProUsers() {
const illegalProUsers = await prisma.user.findMany({
where: {
plan: UserPlan.PRO,
},
select: {
id: true,
name: true,
email: true,
username: true,
plan: true,
metadata: true,
},
});
const usersDowngraded: Partial<typeof illegalProUsers[number]>[] = [];
const downgrade = async (user: typeof illegalProUsers[number]) => {
await prisma.user.update({
where: { id: user.id },
data: {
plan: UserPlan.TRIAL,
trialEndsAt: dayjs().add(TRIAL_LIMIT_DAYS, "day").toDate(),
},
});
console.log(`Downgraded: ${user.email}`);
usersDowngraded.push({
id: user.id,
username: user.username,
name: user.name,
email: user.email,
plan: user.plan,
metadata: user.metadata,
});
};
for (const suspectUser of illegalProUsers) {
console.log(`Checking: ${suspectUser.email}`);
const metadata = (suspectUser.metadata as Prisma.JsonObject) ?? {};
// if their pro is already sponsored by a team, do not downgrade
if (metadata.proPaidForByTeamId !== undefined) continue;
const stripeCustomerId = await getStripeCustomerIdFromUserId(suspectUser.id);
const customer = await stripe.customers.retrieve(stripeCustomerId, {
expand: ["subscriptions.data.plan"],
});
if (!customer || customer.deleted) {
await downgrade(suspectUser);
continue;
}
const subscription = customer.subscriptions?.data[0];
if (!subscription) {
await downgrade(suspectUser);
continue;
}
// If they already have a premium username, do not downgrade
// if (suspectUser.username && isPremiumUserName(suspectUser.username)) continue;
await downgrade(suspectUser);
}
return {
usersDowngraded,
usersDowngradedAmount: usersDowngraded.length,
};
}
downgradeIllegalProUsers()
.then(({ usersDowngraded, usersDowngradedAmount }) => {
console.log(`Downgraded ${usersDowngradedAmount} illegal pro users`);
console.table(usersDowngraded);
})
.catch((e) => {
console.error(e);
process.exit(1);
});

View File

@ -28,16 +28,18 @@ export function ManageLink(props: { calEvent: CalendarEvent; attendee: Person })
width: "100%",
gap: "8px",
}}>
<>{t("need_to_make_a_change")}</>{" "}
<>{t("need_to_make_a_change")}</>
{!props.calEvent.recurringEvent && (
<>
<a href={getRescheduleLink(props.calEvent)} style={{ color: "#3e3e3e" }}>
<>{t("reschedule")}</>{" "}
<a
href={getRescheduleLink(props.calEvent)}
style={{ color: "#3e3e3e", marginLeft: "5px", marginRight: "5px" }}>
<>{t("reschedule")}</>
</a>
<>{t("or_lowercase")}</>{" "}
<>{t("or_lowercase")}</>
</>
)}
<a href={getCancelLink(props.calEvent)} style={{ color: "#3e3e3e" }}>
<a href={getCancelLink(props.calEvent)} style={{ color: "#3e3e3e", marginLeft: "5px" }}>
<>{t("cancel")}</>
</a>
</p>

View File

@ -1,5 +1,6 @@
<html>
<head>
<title>Embed Playground</title>
<!-- <link rel="prerender" href="http://localhost:3000/free"> -->
<!-- <script src="./src/embed.ts" type="module"></script> -->
<script>
@ -9,8 +10,8 @@
(()=> {
const url = new URL(document.URL);
// Only run the example specified by only=, avoids distraction and faster to test.
const only = url.searchParams.get("only");
const elementIdentifier = only ? only.replace("ns:",""): null
const only = window.only = url.searchParams.get("only");
const elementIdentifier = only !== "all" ? only.replace("ns:",""): null
if (elementIdentifier) {
location.hash="#cal-booking-place-" + elementIdentifier + "-iframe"
}
@ -56,9 +57,10 @@
rgba(148, 232, 249, 1) 100%
);
}
.debug {
.inline-embed-container {
/* border: 1px solid black; */
margin-bottom: 5px;
border-bottom: 1px solid
}
.loader {
@ -70,19 +72,26 @@
</style>
</head>
<body>
<h3>This page has a non responsive version accessible <a href="?nonResponsive">here</a></h3>
<h3>Pre-render test page available at <a href="?only=prerender-test">here</a></h3>
<span style="display: block;" ><a href="?nonResponsive">Non responsive version of this page here</a></span>
<span style="display: block;" ><a href="?only=prerender-test">Go to Pre-render test page only</a><small></small></span>
<div>
<button data-cal-namespace="prerendertestLightTheme" data-cal-config='{"theme":"light"}' data-cal-link="free?light&popup">Book with Free User[Light Theme]</button>
<div>
<i
>Corresponding Cal Link is being preloaded. Assuming that it would take you some time to click this
as you are reading this text, it would open up super fast[If you are running a production build on
local]. Try switching to slow 3G or create a custom Network configuration which is impossibly
slow</i
>
</div>
<h2>Other Popup Examples</h2>
<script>
if (only === "all" || only === "prerender-test") {
document.write(`
<button data-cal-namespace="prerendertestLightTheme" data-cal-config='{"theme":"light"}' data-cal-link="free?light&popup">Book with Free User[Light Theme]</button>
<i
>Corresponding Cal Link is being preloaded. Assuming that it would take you some time to click this
as you are reading this text, it would open up super fast[If you are running a production build on
local]. Try switching to slow 3G or create a custom Network configuration which is impossibly
slow</i
>`)
}
</script>
</div>
<span style="display: block;" ><a href="?only=all">Render All embeds together</a><small> - It would be slow to load</small></span>
<div>
<h2>Popup Examples</h2>
<button data-cal-namespace="popupAutoTheme" data-cal-link="free">Book with Free User[Auto Theme]</button>
<button data-cal-namespace="popupDarkTheme" data-cal-config='{"theme":"dark"}' data-cal-link="free">Book with Free User[Dark Theme]</button>
<button data-cal-namespace="popupTeamLinkLightTheme" data-cal-config='{"theme":"light"}' data-cal-link="team/seeded-team/collective-seeded-team-event">Book with Test Team[Light Theme]</button>
@ -90,35 +99,28 @@
<button data-cal-namespace="popupTeamLinksList" data-cal-link="team/seeded-team/">See Team Links [Auto Theme]</button>
<button data-cal-namespace="popupReschedule" data-cal-link="reschedule/qm3kwt3aTnVD7vmP9tiT2f">Reschedule Event[Auto Theme]</button>
<button data-cal-namespace="popupPaidEvent" data-cal-link="pro/paid">Book Paid Event [Auto Theme]</button>
<button data-cal-namespace="popupHideEventTypeDetails" data-cal-link="free/30min">Book Free Event [Auto Theme][uiConfig.hideEventTypeDetails=true]</button>
<button data-cal-namespace="routingFormAuto" data-cal-link="forms/948ae412-d995-4865-875a-48302588de03">Book through Routing Form [Auto Theme]</button>
<button data-cal-namespace="routingFormDark" data-cal-config='{"theme":"dark"}' data-cal-link="forms/948ae412-d995-4865-875a-48302588de03">Book through Routing Form [Dark Theme]</button>
<div>
<!-- <div>
<h2>Embed for Pages behind authentication</h2>
<button data-cal-namespace="upcomingBookings" data-cal-config='{"theme":"dark"}' data-cal-link="bookings/upcoming">Show Upcoming Bookings</button>
</div>
</div> -->
</div>
<h2>Inline Embed Examples</h2>
<div id="namespaces-test">
<div class="debug" id="cal-booking-place-default">
<h2>
Default Namespace(Cal)<i>[Dark Theme][inline][Guests(janedoe@example.com and test@example.com)]</i>
</h2>
<div>
<i><a href="?only=ns:default">Test in Zen Mode</a></i>
</div>
<div class="inline-embed-container" id="cal-booking-place-default">
<h3>
<a href="?only=ns:default">[Dark Theme][Guests(janedoe@example.com and test@example.com)]</a>
</h3>
<i class="last-action"> You would see last Booking page action in my place </i>
<div >
<div class="place" style="width:100%;"></div>
<div class="loader" id="cal-booking-loader-">Loading .....</div>
<div class="loader" id="cal-booking-loader-"></div>
</div>
</div>
<div class="debug" id="cal-booking-place-second">
<h2>Namespace "second"(Cal.ns.second)[Custom Styling][inline]</h2>
<div>
<i><a href="?only=ns:second">Test in Zen Mode</a></i>
</div>
<i class="last-action">
<i>You would see last Booking page action in my place</i>
</i>
<div class="inline-embed-container" id="cal-booking-place-second">
<h3><a href="?only=ns:second">[Custom Styling]</a></h3>
<div class="place">
<div>If you render booking embed in me, I won't restrict you. The entire page is yours. Content is by default aligned center</div>
<button
@ -143,71 +145,48 @@
Change Date Button Color
</button>
<div class="loader" id="cal-booking-loader-second">Loading .....</div>
</div>
</div>
<div class="debug" id="cal-booking-place-third">
<h2>Namespace "third"(Cal.ns.third)[inline][Custom Styling - Transparent Background]</h2>
<div>
<i><a href="?only=ns:third">Test in Zen Mode</a></i>
</div>
<i class="last-action">
<i>You would see last Booking page action in my place</i>
</i>
<div class="inline-embed-container" id="cal-booking-place-third">
<h3><a href="?only=ns:third">[Custom Styling - Transparent Background]</a></h3>
<div style="width: 30%" class="place">
<div>If you render booking embed in me, I would not let you be more than 30% wide</div>
<div class="loader" id="cal-booking-loader-third">Loading .....</div>
</div>
</div>
<div class="debug" id="cal-booking-place-fourth">
<h2>Namespace "fourth"(Cal.ns.fourth)[Team Event Test][inline taking entire width]</h2>
<div>
<i><a href="?only=ns:fourth">Test in Zen Mode</a></i>
</div>
<i class="last-action">
<i>You would see last Booking page action in my place</i>
</i>
<div class="inline-embed-container" id="cal-booking-place-fourth">
<h3><a href="?only=ns:fourth">[Team Event Test][inline taking entire width]</a></h3>
<div style="width: 30%" class="place">
<div>If you render booking embed in me, I would not let you be more than 30% wide</div>
<div class="loader" id="cal-booking-loader-third">Loading .....</div>
</div>
</div>
</div>
<div class="debug" id="cal-booking-place-fifth">
<h2>Namespace "fifth"(Cal.ns.fifth)[Team Event Test][inline along with some content]</h2>
<div>
<i><a href="?only=ns:fifth">Test in Zen Mode</a></i>
</div>
<i class="last-action">
<i>You would see last Booking page action in my place</i>
</i>
<div class="inline-embed-container" id="cal-booking-place-fifth">
<h3><a href="?only=ns:fifth">[Team Event Test][inline along with some content]</a></h3>
<div style="display:flex;align-items: center;">
<h2 style="width: 30%">
<h4 style="width: 30%">
On the right side you can book a team meeting =>
</h2>
</h4>
<div style="width: 70%" class="place">
</div>
</div>
</div>
<div class="debug" id="cal-booking-place-inline-routing-form">
<h2>Inline Routing Form</h2>
<div>
<i><a href="?only=inline-routing-form">Test in Zen Mode</a></i>
</div>
<i class="last-action">
<i>You would see last Booking page action in my place</i>
</i>
<div class="inline-embed-container" id="cal-booking-place-inline-routing-form">
<h3><a href="?only=inline-routing-form">Inline Routing Form</a></h3>
<div style="display:flex;align-items: center;">
<h2 style="width: 30%">
<h4 style="width: 30%">
On the right side you can book a team meeting =>
</h2>
</h4>
<div style="width: 70%" class="place">
</div>
</div>
</div>
<div class="inline-embed-container" id="cal-booking-place-hideEventTypeDetails">
<h3><a href="?only=hideEventTypeDetails">Hide EventType Details Test</a></h3>
<div class="place">
</div>
</div>
<script>
const callback = function (e) {
@ -224,8 +203,8 @@
);
};
const searchParams = new URL(document.URL).searchParams;
const only = searchParams.get("only");
if (!only || only === "ns:default") {
let only = window.only
if (only === "all" || only === "ns:default") {
Cal("init", {
debug: 1,
origin: "http://localhost:3000",
@ -251,7 +230,7 @@
callback,
});
}
if (!only || only === "ns:second") {
if (only === "all" || only === "ns:second") {
// Create a namespace "second". It can be accessed as Cal.ns.second with the exact same API as Cal
Cal("init", "second", {
debug: 1,
@ -278,8 +257,7 @@
callback,
});
}
if (!only || only === "ns:third") {
if (only === "all" || only === "ns:third") {
// Create a namespace "third". It can be accessed as Cal.ns.second with the exact same API as Cal
Cal("init", "third", {
debug: 1,
@ -329,8 +307,7 @@
callback,
});
}
if (!only || only === "ns:fourth") {
if (only === "all" || only === "ns:fourth") {
Cal("init", "fourth", {
debug: 1,
origin: "http://localhost:3000",
@ -372,7 +349,7 @@
callback,
});
}
if (!only || only === "ns:fifth") {
if (only === "all" || only === "ns:fifth") {
Cal("init", "fifth", {
debug: 1,
origin: "http://localhost:3000",
@ -396,7 +373,7 @@
callback,
});
}
if (!only || only === "prerender-test") {
if (only === "all" || only === "prerender-test") {
Cal('init', 'prerendertestLightTheme', {
debug: 1,
origin: "http://localhost:3000",
@ -406,7 +383,7 @@
});
}
if (!only || only === "inline-routing-form") {
if (only === "all" || only === "inline-routing-form") {
Cal('init', 'inline-routing-form', {
debug: 1,
origin: "http://localhost:3000",
@ -427,11 +404,45 @@
);
}
if (only === "all" || only === "hideEventTypeDetails") {
let identifier = "hideEventTypeDetails"
Cal('init', identifier, {
debug: 1,
origin: "http://localhost:3000",
})
Cal.ns.hideEventTypeDetails([
['inline', {
elementOrSelector: `#cal-booking-place-${identifier} .place`,
calLink: "free/30min",
config: {
iframeAttrs: {
id: `cal-booking-place-${identifier}-iframe`
},
}
}],
[
'ui', {
hideEventTypeDetails:true
}
]
]);
}
Cal('init', 'popupDarkTheme', {
debug: 1,
origin: "http://localhost:3000",
})
Cal('init', 'popupHideEventTypeDetails', {
debug: 1,
origin: "http://localhost:3000",
});
Cal.ns.popupHideEventTypeDetails('ui', {
hideEventTypeDetails:true
});
Cal('init', 'popupReschedule', {
debug: 1,
origin: "http://localhost:3000",
@ -481,7 +492,7 @@
})
if (!only || only == "ns:floatingButton") {
if (only === "all" || only == "ns:floatingButton") {
Cal.ns.floatingButton("floatingButton", {
calLink: "pro"
})

View File

@ -4,6 +4,7 @@ import { useState, useEffect, CSSProperties } from "react";
import { sdkActionManager } from "./sdk-event";
export interface UiConfig {
hideEventTypeDetails?: boolean;
theme?: "dark" | "light" | "auto";
styles?: EmbedStyles;
}
@ -14,7 +15,6 @@ declare global {
embedStore: any;
};
CalComPageStatus: string;
CalComPlan: string;
isEmbed: () => boolean;
resetEmbedStatus: () => void;
getEmbedNamespace: () => string | null;
@ -40,7 +40,9 @@ const embedStore = {
parentInformedAboutContentHeight: boolean;
windowLoadEventFired: boolean;
theme?: UiConfig["theme"];
setTheme: (arg0: string) => void;
uiConfig?: Omit<UiConfig, "styles" | "theme">;
setTheme?: (arg0: string) => void;
setUiConfig?: (arg0: UiConfig) => void;
};
let isSafariBrowser = false;
@ -147,6 +149,12 @@ export const useEmbedTheme = () => {
return theme === "auto" ? null : theme;
};
export const useEmbedUiConfig = () => {
const [uiConfig, setUiConfig] = useState(embedStore.uiConfig || {});
embedStore.setUiConfig = setUiConfig;
return uiConfig;
};
// TODO: Make it usable as an attribute directly instead of styles value. It would allow us to go beyond styles e.g. for debugging we can add a special attribute indentifying the element on which UI config has been applied
export const useEmbedStyles = (elementName: keyof EmbedStyles) => {
const [styles, setStyles] = useState({} as EmbedStyles);
@ -252,14 +260,6 @@ export const methods = {
log("Method: ui called", uiConfig);
const stylesConfig = uiConfig.styles;
// In case where parent gives instructions before CalComPlan is set.
// This is easily possible as React takes time to initialize and render components where this variable is set.
if (!window?.CalComPlan) {
return requestAnimationFrame(() => {
style(uiConfig);
});
}
// body can't be styled using React state hook as it is generated by _document.tsx which doesn't support hooks.
if (stylesConfig?.body?.background) {
document.body.style.background = stylesConfig.body.background as string;
@ -267,7 +267,15 @@ export const methods = {
if (uiConfig.theme) {
embedStore.theme = uiConfig.theme as UiConfig["theme"];
embedStore.setTheme(uiConfig.theme);
if (embedStore.setTheme) {
embedStore.setTheme(uiConfig.theme);
}
}
// Set the value here so that if setUiConfig state isn't available and later it's defined,it uses this value
embedStore.uiConfig = uiConfig;
if (embedStore.setUiConfig) {
embedStore.setUiConfig(uiConfig);
}
setEmbedStyles(stylesConfig || {});

View File

@ -780,7 +780,7 @@ async function handler(req: NextApiRequest & { userId?: number | undefined }) {
await syncServicesUpdateWebUser(
await prisma.user.findFirst({
where: { id: userId },
select: { id: true, email: true, name: true, plan: true, username: true, createdDate: true },
select: { id: true, email: true, name: true, username: true, createdDate: true },
})
);
evt.uid = booking?.uid ?? null;

View File

@ -1,96 +0,0 @@
import { Trans } from "react-i18next";
import { useLocale } from "@calcom/lib/hooks/useLocale";
import { BadgeCheckIcon, ConfirmationDialogContent, Dialog, DialogTrigger, Icon } from "@calcom/ui";
export default function LicenseBanner() {
const { t } = useLocale();
/*
Set this value to 'agree' to accept our license:
LICENSE: https://github.com/calcom/cal.com/blob/main/LICENSE
Summary of terms:
- The codebase has to stay open source, whether it was modified or not
- You can not repackage or sell the codebase
- Acquire a commercial license to remove these terms by visiting: cal.com/sales
NEXT_PUBLIC_LICENSE_CONSENT=''
*/
if (process.env.NEXT_PUBLIC_LICENSE_CONSENT === "agree" || process.env.NEXT_PUBLIC_IS_E2E) {
return null;
}
return (
<div className="fixed inset-x-0 bottom-0 left-0 pb-2 sm:pb-5 md:left-56">
<div className="mx-auto max-w-7xl px-2 sm:px-8">
<div className="rounded-sm bg-green-600 p-2 shadow-lg sm:p-3">
<div className="flex flex-wrap items-center justify-between">
<div className="flex w-0 flex-1 items-center">
<span className="flex rounded-sm bg-green-800 p-2">
<BadgeCheckIcon className="h-6 w-6 text-white" aria-hidden="true" />
</span>
<p className="ml-3 truncate font-medium text-white">
<span className="inline">
<Trans i18nKey="accept_our_license" values={{ agree: "agree" }}>
Accept our license by changing the .env variable
<span className="bg-gray-50 bg-opacity-20 px-1">NEXT_PUBLIC_LICENSE_CONSENT</span> to
&apos;agree&apos;.
</Trans>
</span>
</p>
</div>
<div className="order-3 mt-2 w-full flex-shrink-0 sm:order-2 sm:mt-0 sm:w-auto">
<Dialog>
<DialogTrigger asChild>
<button className="flex w-full items-center justify-center rounded-sm border border-transparent bg-white px-4 py-2 text-sm font-medium text-green-600 hover:bg-green-50">
{t("accept_license")}
</button>
</DialogTrigger>
<DialogContent />
</Dialog>
</div>
<div className="order-2 flex-shrink-0 sm:order-3 sm:ml-2">
<Dialog>
<DialogTrigger asChild>
<button className="-mr-1 flex rounded-sm p-2 hover:bg-green-500 focus:outline-none focus:ring-2 focus:ring-white">
<span className="sr-only">{t("dismiss")}</span>
<Icon.FiX className="h-6 w-6 text-white" aria-hidden="true" />
</button>
</DialogTrigger>
<DialogContent />
</Dialog>
</div>
</div>
</div>
</div>
</div>
);
function DialogContent() {
return (
<ConfirmationDialogContent
variety="success"
title={t("open_env")}
confirmBtnText={t("env_changed")}
cancelBtnText={t("cancel")}>
<Trans i18nKey="remove_banner_instructions" values={{ agree: "agree" }}>
To remove this banner, please open your .env file and change the
<span className="bg-green-400 bg-opacity-20 p-[2px] text-green-500">
NEXT_PUBLIC_LICENSE_CONSENT
</span>
variable to &apos;agreeapos;.
</Trans>
<h2 className="font-cal mt-8 mb-2 text-black">{t("terms_summary")}:</h2>
<ul className="ml-5 list-disc">
<li>{t("codebase_has_to_stay_opensource")}</li>
<li>{t("cannot_repackage_codebase")}</li>
<li>
{t("acquire_license")}:{" "}
<a className="text-blue-500 underline" href="https://cal.com/sales">
cal.com/sales
</a>
</li>
</ul>
</ConfirmationDialogContent>
);
}
}

View File

@ -58,7 +58,6 @@ export const getServerSideProps = async (context: GetServerSidePropsContext) =>
name: true,
username: true,
hideBranding: true,
plan: true,
theme: true,
},
},

View File

@ -1,4 +1,4 @@
import { PrismaClient, UserPlan } from "@prisma/client";
import { PrismaClient } from "@prisma/client";
import { HOSTED_CAL_FEATURES } from "@calcom/lib/constants";
import { isTeamAdmin } from "@calcom/lib/server/queries/teams";
@ -59,11 +59,8 @@ export const samlTenantProduct = async (prisma: PrismaClient, email: string) =>
};
};
export const canAccess = async (
user: { id: number; plan: UserPlan; email: string },
teamId: number | null
) => {
const { id: userId, plan, email } = user;
export const canAccess = async (user: { id: number; email: string }, teamId: number | null) => {
const { id: userId, email } = user;
if (!isSAMLLoginEnabled) {
return {
@ -80,13 +77,6 @@ export const canAccess = async (
access: false,
};
}
if (plan != UserPlan.PRO) {
return {
message: "app_upgrade_description",
access: false,
};
}
}
// Self-hosted

View File

@ -108,7 +108,6 @@ export default function MemberListItem(props: Props) {
<div className="mb-1 flex">
<span className="mr-1 text-sm font-bold leading-4">{name}</span>
{props.member.isMissingSeat && <TeamPill color="red" text={t("hidden")} />}
{!props.member.accepted && <TeamPill color="orange" text={t("pending")} />}
{props.member.role && <TeamRole role={props.member.role} />}
</div>

View File

@ -2,23 +2,17 @@ import { useRouter } from "next/router";
import { useMemo, useState } from "react";
import { getPlaceholderAvatar } from "@calcom/lib/getPlaceholderAvatar";
import { useLocale } from "@calcom/lib/hooks/useLocale";
import { trpc } from "@calcom/trpc/react";
import useMeQuery from "@calcom/trpc/react/hooks/useMeQuery";
import { Alert, Avatar, Loader, Shell } from "@calcom/ui";
import LicenseRequired from "../../common/components/LicenseRequired";
import TeamAvailabilityScreen from "../components/TeamAvailabilityScreen";
export function TeamAvailabilityPage() {
const { t } = useLocale();
const router = useRouter();
const [errorMessage, setErrorMessage] = useState("");
const me = useMeQuery();
const isFreeUser = me.data?.plan === "FREE";
const { data: team, isLoading } = trpc.viewer.teams.get.useQuery(
{ teamId: Number(router.query.id) },
{
@ -37,12 +31,11 @@ export function TeamAvailabilityPage() {
return (
<Shell
backPath={!errorMessage ? `/settings/teams/${team?.id}` : undefined}
heading={!isFreeUser && team?.name}
heading={team?.name}
flexChildrenContainer
subtitle={team && !isFreeUser && "Your team's availability at a glance"}
subtitle={team && "Your team's availability at a glance"}
HeadingLeftIcon={
team &&
!isFreeUser && (
team && (
<Avatar
size="sm"
imageSrc={getPlaceholderAvatar(team?.logo, team?.name as string)}
@ -54,11 +47,7 @@ export function TeamAvailabilityPage() {
<LicenseRequired>
{!!errorMessage && <Alert className="-mt-24 border" severity="error" title={errorMessage} />}
{isLoading && <Loader />}
{isFreeUser ? (
<Alert className="-mt-24 border" severity="warning" title={t("pro_feature_teams")} />
) : (
TeamAvailability
)}
{TeamAvailability}
</LicenseRequired>
</Shell>
);

View File

@ -34,11 +34,6 @@ const ProfileView = () => {
onError: () => {
router.push("/settings");
},
onSuccess: (team) => {
if (team) {
form.setValue("hideBranding", team.hideBranding);
}
},
}
);
@ -55,10 +50,7 @@ const ProfileView = () => {
form={form}
handleSubmit={(values) => {
if (team) {
const hideBranding = form.getValues("hideBranding");
if (team.hideBranding !== hideBranding) {
mutation.mutate({ id: team.id, hideBranding });
}
mutation.mutate({ id: team.id, hideBranding: values.hideBranding });
}
}}>
<div className="relative flex items-start">
@ -73,6 +65,7 @@ const ProfileView = () => {
<div className="flex-none">
<Controller
control={form.control}
defaultValue={team?.hideBranding ?? false}
name="hideBranding"
render={({ field }) => (
<Switch

View File

@ -6,6 +6,7 @@ import dayjs from "@calcom/dayjs";
import { defaultHandler } from "@calcom/lib/server";
import prisma from "@calcom/prisma";
import { getSenderId } from "../lib/alphanumericSenderIdSupport";
import * as twilio from "../lib/reminders/smsProviders/twilioProvider";
import customTemplate, { VariablesType } from "../lib/reminders/templates/customTemplate";
import smsReminderTemplate from "../lib/reminders/templates/smsReminderTemplate";
@ -72,6 +73,8 @@ async function handler(req: NextApiRequest, res: NextApiResponse) {
? reminder.booking?.attendees[0].timeZone
: reminder.booking?.user?.timeZone;
const senderID = getSenderId(sendTo, reminder.workflowStep.sender);
let message: string | null = reminder.workflowStep.reminderBody;
switch (reminder.workflowStep.template) {
case WorkflowTemplates.REMINDER:
@ -105,12 +108,7 @@ async function handler(req: NextApiRequest, res: NextApiResponse) {
break;
}
if (message?.length && message?.length > 0 && sendTo) {
const scheduledSMS = await twilio.scheduleSMS(
sendTo,
message,
reminder.scheduledDate,
reminder.workflowStep.sender || "Cal"
);
const scheduledSMS = await twilio.scheduleSMS(sendTo, message, reminder.scheduledDate, senderID);
await prisma.workflowReminder.update({
where: {

View File

@ -5,6 +5,7 @@ import { Dispatch, SetStateAction, useState } from "react";
import { Controller, useForm } from "react-hook-form";
import { z } from "zod";
import { SENDER_ID } from "@calcom/lib/constants";
import { useLocale } from "@calcom/lib/hooks/useLocale";
import {
Button,
@ -68,7 +69,7 @@ export const AddActionDialog = (props: IAddActionDialog) => {
mode: "onSubmit",
defaultValues: {
action: WorkflowActions.EMAIL_HOST,
sender: "Cal",
sender: SENDER_ID,
},
resolver: zodResolver(formSchema),
});
@ -166,7 +167,7 @@ export const AddActionDialog = (props: IAddActionDialog) => {
<TextField
label={t("sender_id")}
type="text"
placeholder="Cal"
placeholder={SENDER_ID}
maxLength={11}
{...form.register(`sender`)}
/>

View File

@ -3,6 +3,7 @@ import { useRouter } from "next/router";
import { Dispatch, SetStateAction, useMemo, useState } from "react";
import { Controller, UseFormReturn } from "react-hook-form";
import { SENDER_ID } from "@calcom/lib/constants";
import { useLocale } from "@calcom/lib/hooks/useLocale";
import { trpc } from "@calcom/trpc/react";
import useMeQuery from "@calcom/trpc/react/hooks/useMeQuery";
@ -73,7 +74,7 @@ export default function WorkflowDetailsPage(props: Props) {
emailSubject: null,
template: WorkflowTemplates.CUSTOM,
numberRequired: numberRequired || false,
sender: sender || "Cal",
sender: sender || SENDER_ID,
};
steps?.push(step);
form.setValue("steps", steps);

View File

@ -9,6 +9,7 @@ import { Dispatch, SetStateAction, useRef, useState } from "react";
import { Controller, UseFormReturn } from "react-hook-form";
import "react-phone-number-input/style.css";
import { SENDER_ID } from "@calcom/lib/constants";
import { useLocale } from "@calcom/lib/hooks/useLocale";
import { HttpError } from "@calcom/lib/http-error";
import { trpc } from "@calcom/trpc/react";
@ -366,7 +367,7 @@ export default function WorkflowStepContainer(props: WorkflowStepProps) {
<TextField
label={t("sender_id")}
type="text"
placeholder="Cal"
placeholder={SENDER_ID}
maxLength={11}
{...form.register(`steps.${step.stepNumber - 1}.sender`)}
/>
@ -557,7 +558,7 @@ export default function WorkflowStepContainer(props: WorkflowStepProps) {
emailSubject,
reminderBody,
template: step.template,
sender: step.sender || "Cal",
sender: step.sender || SENDER_ID,
});
} else {
const isNumberValid =
@ -596,6 +597,7 @@ export default function WorkflowStepContainer(props: WorkflowStepProps) {
reminderBody: reminderBody || "",
template: step.template,
sendTo: step.sendTo || "",
sender: step.sender || SENDER_ID,
});
setConfirmationDialogOpen(false);
}}>

View File

@ -0,0 +1,87 @@
import { SENDER_ID } from "@calcom/lib/constants";
export function getSenderId(phoneNumber?: string | null, sender?: string | null) {
const isAlphanumericSenderIdSupported = !noAlphanumericSenderIdSupport.find(
(code) => code === phoneNumber?.substring(0, code.length)
);
const senderID = isAlphanumericSenderIdSupported ? sender || SENDER_ID : "";
return senderID;
}
const noAlphanumericSenderIdSupport = [
"+93",
"+54",
"+374",
"+1",
"+375",
"+32",
"+229",
"+55",
"+237",
"+56",
"+86",
"+57",
"+243",
"+506",
"+53",
"+42",
"+593",
"+20",
"+503",
"+251",
"+594",
"+233",
"+224",
"+245",
"+852",
"+36",
"+91",
"+62",
"+98",
"+972",
"+225",
"+962",
"+7",
"+254",
"+965",
"+996",
"+231",
"+60",
"+52",
"+212",
"+95",
"+674",
"+977",
"+64",
"+505",
"+234",
"+968",
"+507",
"+595",
"+51",
"+63",
"+974",
"+7",
"+250",
"+966",
"+27",
"+82",
"+211",
"+94",
"+249",
"+268",
"+963",
"+886",
"+255",
"+66",
"+216",
"+90",
"+256",
"+971",
"+598",
"+58",
"+84",
"+260",
];

View File

@ -6,6 +6,7 @@ import {
WorkflowTriggerEvents,
} from "@prisma/client";
import { SENDER_ID } from "@calcom/lib/constants";
import type { CalendarEvent } from "@calcom/types/Calendar";
import { scheduleEmailReminder } from "./emailReminderManager";
@ -51,7 +52,7 @@ export const scheduleWorkflowReminders = async (
step.reminderBody || "",
step.id,
step.template,
step.sender || "Cal"
step.sender || SENDER_ID
);
} else if (
step.action === WorkflowActions.EMAIL_ATTENDEE ||
@ -120,7 +121,7 @@ export const sendCancelledReminders = async (
step.reminderBody || "",
step.id,
step.template,
step.sender || "Cal"
step.sender || SENDER_ID
);
} else if (
step.action === WorkflowActions.EMAIL_ATTENDEE ||

View File

@ -25,7 +25,7 @@ export const sendSMS = async (phoneNumber: string, body: string, sender: string)
body: body,
messagingServiceSid: process.env.TWILIO_MESSAGING_SID,
to: phoneNumber,
from: sender,
from: sender ? sender : process.env.TWILIO_PHONE_NUMBER,
});
return response;
@ -39,7 +39,7 @@ export const scheduleSMS = async (phoneNumber: string, body: string, scheduledDa
to: phoneNumber,
scheduleType: "fixed",
sendAt: scheduledDate,
from: sender,
from: sender ? sender : process.env.TWILIO_PHONE_NUMBER,
});
return response;

View File

@ -10,6 +10,7 @@ import dayjs from "@calcom/dayjs";
import prisma from "@calcom/prisma";
import { Prisma } from "@calcom/prisma/client";
import { getSenderId } from "../alphanumericSenderIdSupport";
import * as twilio from "./smsProviders/twilioProvider";
import customTemplate, { VariablesType } from "./templates/customTemplate";
import smsReminderTemplate from "./templates/smsReminderTemplate";
@ -57,6 +58,8 @@ export const scheduleSMSReminder = async (
const timeUnit: timeUnitLowerCase | undefined = timeSpan.timeUnit?.toLocaleLowerCase() as timeUnitLowerCase;
let scheduledDate = null;
const senderID = getSenderId(reminderPhone, sender);
if (triggerEvent === WorkflowTriggerEvents.BEFORE_EVENT) {
scheduledDate = timeSpan.time && timeUnit ? dayjs(startTime).subtract(timeSpan.time, timeUnit) : null;
} else if (triggerEvent === WorkflowTriggerEvents.AFTER_EVENT) {
@ -98,7 +101,7 @@ export const scheduleSMSReminder = async (
triggerEvent === WorkflowTriggerEvents.RESCHEDULE_EVENT
) {
try {
await twilio.sendSMS(reminderPhone, message, sender);
await twilio.sendSMS(reminderPhone, message, senderID);
} catch (error) {
console.log(`Error sending SMS with error ${error}`);
}
@ -117,7 +120,7 @@ export const scheduleSMSReminder = async (
reminderPhone,
message,
scheduledDate.toDate(),
sender
senderID
);
await prisma.workflowReminder.create({

View File

@ -34,6 +34,8 @@ import {
TextField,
} from "@calcom/ui";
import { DuplicateDialog } from "./DuplicateDialog";
// this describes the uniform data needed to create a new event type on Profile or Team
export interface EventTypeParent {
teamId: number | null | undefined; // if undefined, then it's a profile
@ -149,18 +151,7 @@ export default function CreateEventTypeButton(props: CreateEventTypeBtnProps) {
};
return (
<Dialog
name="new-eventtype"
clearQueryParamsOnClose={[
"eventPage",
"teamId",
"type",
"description",
"title",
"length",
"slug",
"locations",
]}>
<>
{!hasTeams || props.isIndividualTeam ? (
<Button
onClick={() => openModal(props.options[0])}
@ -200,124 +191,140 @@ export default function CreateEventTypeButton(props: CreateEventTypeBtnProps) {
</DropdownMenuContent>
</Dropdown>
)}
<DialogContent
type="creation"
className="overflow-y-auto"
title={teamId ? t("add_new_team_event_type") : t("add_new_event_type")}
description={t("new_event_type_to_book_description")}>
<Form
form={form}
handleSubmit={(values) => {
createMutation.mutate(values);
}}>
<div className="mt-3 space-y-6">
{teamId && (
<TextField
type="hidden"
labelProps={{ style: { display: "none" } }}
{...register("teamId", { valueAsNumber: true })}
value={teamId}
/>
)}
<TextField
label={t("title")}
placeholder={t("quick_chat")}
{...register("title")}
onChange={(e) => {
form.setValue("title", e?.target.value);
if (form.formState.touchedFields["slug"] === undefined) {
form.setValue("slug", slugify(e?.target.value));
}
}}
/>
{process.env.NEXT_PUBLIC_WEBSITE_URL !== undefined &&
process.env.NEXT_PUBLIC_WEBSITE_URL?.length >= 21 ? (
<TextField
label={`${t("url")}: ${process.env.NEXT_PUBLIC_WEBSITE_URL}`}
required
addOnLeading={<>/{pageSlug}/</>}
{...register("slug")}
onChange={(e) => {
form.setValue("slug", slugify(e?.target.value), { shouldTouch: true });
}}
/>
) : (
<TextField
label={t("url")}
required
addOnLeading={
<>
{process.env.NEXT_PUBLIC_WEBSITE_URL}/{pageSlug}/
</>
}
{...register("slug")}
/>
)}
<TextAreaField
label={t("description")}
placeholder={t("quick_video_meeting")}
{...register("description")}
/>
<div className="relative">
<TextField
type="number"
required
min="10"
placeholder="15"
label={t("length")}
className="pr-20"
{...register("length", { valueAsNumber: true })}
addOnSuffix={t("minutes")}
/>
</div>
{teamId && (
<div className="mb-4">
<label htmlFor="schedulingType" className="block text-sm font-bold text-gray-700">
{t("scheduling_type")}
</label>
{form.formState.errors.schedulingType && (
<Alert
className="mt-1"
severity="error"
message={form.formState.errors.schedulingType.message}
{/* Dialog for duplicate event type */}
{router.query.dialog === "duplicate-event-type" && <DuplicateDialog />}
{router.query.dialog === "new-eventtype" && (
<Dialog
name="new-eventtype"
clearQueryParamsOnClose={[
"eventPage",
"teamId",
"type",
"description",
"title",
"length",
"slug",
"locations",
]}>
<DialogContent
type="creation"
className="overflow-y-auto"
title={teamId ? t("add_new_team_event_type") : t("add_new_event_type")}
description={t("new_event_type_to_book_description")}>
<Form
form={form}
handleSubmit={(values) => {
createMutation.mutate(values);
}}>
<div className="mt-3 space-y-6">
{teamId && (
<TextField
type="hidden"
labelProps={{ style: { display: "none" } }}
{...register("teamId", { valueAsNumber: true })}
value={teamId}
/>
)}
<RadioArea.Group
{...register("schedulingType")}
onChange={(val) => form.setValue("schedulingType", val as SchedulingType)}
className="relative mt-1 flex space-x-6 rounded-sm rtl:space-x-reverse">
<RadioArea.Item
value={SchedulingType.COLLECTIVE}
defaultChecked={type === SchedulingType.COLLECTIVE}
className="w-1/2 text-sm">
<strong className="mb-1 block">{t("collective")}</strong>
<p>{t("collective_description")}</p>
</RadioArea.Item>
<RadioArea.Item
value={SchedulingType.ROUND_ROBIN}
defaultChecked={type === SchedulingType.ROUND_ROBIN}
className="w-1/2 text-sm">
<strong className="mb-1 block">{t("round_robin")}</strong>
<p>{t("round_robin_description")}</p>
</RadioArea.Item>
</RadioArea.Group>
<TextField
label={t("title")}
placeholder={t("quick_chat")}
{...register("title")}
onChange={(e) => {
form.setValue("title", e?.target.value);
if (form.formState.touchedFields["slug"] === undefined) {
form.setValue("slug", slugify(e?.target.value));
}
}}
/>
{process.env.NEXT_PUBLIC_WEBSITE_URL !== undefined &&
process.env.NEXT_PUBLIC_WEBSITE_URL?.length >= 21 ? (
<TextField
label={`${t("url")}: ${process.env.NEXT_PUBLIC_WEBSITE_URL}`}
required
addOnLeading={<>/{pageSlug}/</>}
{...register("slug")}
onChange={(e) => {
form.setValue("slug", slugify(e?.target.value), { shouldTouch: true });
}}
/>
) : (
<TextField
label={t("url")}
required
addOnLeading={
<>
{process.env.NEXT_PUBLIC_WEBSITE_URL}/{pageSlug}/
</>
}
{...register("slug")}
/>
)}
<TextAreaField
label={t("description")}
placeholder={t("quick_video_meeting")}
{...register("description")}
/>
<div className="relative">
<TextField
type="number"
required
min="10"
placeholder="15"
label={t("length")}
className="pr-20"
{...register("length", { valueAsNumber: true })}
addOnSuffix={t("minutes")}
/>
</div>
{teamId && (
<div className="mb-4">
<label htmlFor="schedulingType" className="block text-sm font-bold text-gray-700">
{t("scheduling_type")}
</label>
{form.formState.errors.schedulingType && (
<Alert
className="mt-1"
severity="error"
message={form.formState.errors.schedulingType.message}
/>
)}
<RadioArea.Group
{...register("schedulingType")}
onChange={(val) => form.setValue("schedulingType", val as SchedulingType)}
className="relative mt-1 flex space-x-6 rounded-sm rtl:space-x-reverse">
<RadioArea.Item
value={SchedulingType.COLLECTIVE}
defaultChecked={type === SchedulingType.COLLECTIVE}
className="w-1/2 text-sm">
<strong className="mb-1 block">{t("collective")}</strong>
<p>{t("collective_description")}</p>
</RadioArea.Item>
<RadioArea.Item
value={SchedulingType.ROUND_ROBIN}
defaultChecked={type === SchedulingType.ROUND_ROBIN}
className="w-1/2 text-sm">
<strong className="mb-1 block">{t("round_robin")}</strong>
<p>{t("round_robin_description")}</p>
</RadioArea.Item>
</RadioArea.Group>
</div>
)}
</div>
)}
</div>
<div className="mt-8 flex flex-row-reverse gap-x-2">
<Button type="submit" loading={createMutation.isLoading}>
{t("continue")}
</Button>
<DialogClose />
</div>
</Form>
</DialogContent>
</Dialog>
<div className="mt-8 flex flex-row-reverse gap-x-2">
<Button type="submit" loading={createMutation.isLoading}>
{t("continue")}
</Button>
<DialogClose />
</div>
</Form>
</DialogContent>
</Dialog>
)}
</>
);
}

View File

@ -0,0 +1,137 @@
import { useRouter } from "next/router";
import { useForm } from "react-hook-form";
import { useLocale } from "@calcom/lib/hooks/useLocale";
import { HttpError } from "@calcom/lib/http-error";
import slugify from "@calcom/lib/slugify";
import { trpc } from "@calcom/trpc/react";
import {
Button,
Dialog,
DialogClose,
DialogContent,
Form,
showToast,
TextAreaField,
TextField,
} from "@calcom/ui";
const DuplicateDialog = () => {
const { t } = useLocale();
const router = useRouter();
// react hook form
const form = useForm({
defaultValues: {
id: Number(router.query.id as string) || -1,
title: (router.query.title as string) || "",
slug: t("event_type_duplicate_copy_text", { slug: router.query.slug as string }),
description: (router.query.description as string) || "",
length: Number(router.query.length) || 30,
},
});
const { register } = form;
const duplicateMutation = trpc.viewer.eventTypes.duplicate.useMutation({
onSuccess: async ({ eventType }) => {
await router.replace("/event-types/" + eventType.id);
showToast(t("event_type_created_successfully", { eventTypeTitle: eventType.title }), "success");
},
onError: (err) => {
if (err instanceof HttpError) {
const message = `${err.statusCode}: ${err.message}`;
showToast(message, "error");
}
if (err.data?.code === "BAD_REQUEST") {
const message = `${err.data.code}: URL already exists.`;
showToast(message, "error");
}
if (err.data?.code === "UNAUTHORIZED") {
const message = `${err.data.code}: You are not able to create this event`;
showToast(message, "error");
}
},
});
const pageSlug = router.query.eventPage;
return (
<Dialog
name="duplicate-event-type"
clearQueryParamsOnClose={["description", "title", "length", "slug", "name", "id"]}>
<DialogContent type="creation" className="overflow-y-auto" title="Duplicate Event Type">
<Form
form={form}
handleSubmit={(values) => {
duplicateMutation.mutate(values);
}}>
<div className="mt-3 space-y-6">
<TextField
label={t("title")}
placeholder={t("quick_chat")}
{...register("title")}
onChange={(e) => {
form.setValue("title", e?.target.value);
if (form.formState.touchedFields["slug"] === undefined) {
form.setValue("slug", slugify(e?.target.value));
}
}}
/>
{process.env.NEXT_PUBLIC_WEBSITE_URL !== undefined &&
process.env.NEXT_PUBLIC_WEBSITE_URL?.length >= 21 ? (
<TextField
label={`${t("url")}: ${process.env.NEXT_PUBLIC_WEBSITE_URL}`}
required
addOnLeading={<>/{pageSlug}/</>}
{...register("slug")}
onChange={(e) => {
form.setValue("slug", slugify(e?.target.value), { shouldTouch: true });
}}
/>
) : (
<TextField
label={t("url")}
required
addOnLeading={
<>
{process.env.NEXT_PUBLIC_WEBSITE_URL}/{pageSlug}/
</>
}
{...register("slug")}
/>
)}
<TextAreaField
label={t("description")}
placeholder={t("quick_video_meeting")}
{...register("description")}
/>
<div className="relative">
<TextField
type="number"
required
min="10"
placeholder="15"
label={t("length")}
className="pr-20"
{...register("length", { valueAsNumber: true })}
addOnSuffix={t("minutes")}
/>
</div>
</div>
<div className="mt-8 flex flex-row-reverse gap-x-2">
<Button type="submit" loading={duplicateMutation.isLoading}>
{t("continue")}
</Button>
<DialogClose />
</div>
</Form>
</DialogContent>
</Dialog>
);
};
export { DuplicateDialog };

View File

@ -7,6 +7,7 @@
"main": "index.ts",
"dependencies": {
"@lexical/react": "^0.5.0",
"dompurify": "^2.4.1",
"lexical": "^0.5.0"
}
}

View File

@ -9,6 +9,7 @@ export const WEBSITE_URL = process.env.NEXT_PUBLIC_WEBSITE_URL || "https://cal.c
export const APP_NAME = process.env.NEXT_PUBLIC_APP_NAME || "Cal.com";
export const SUPPORT_MAIL_ADDRESS = process.env.NEXT_PUBLIC_SUPPORT_MAIL_ADDRESS || "help@cal.com";
export const COMPANY_NAME = process.env.NEXT_PUBLIC_COMPANY_NAME || "Cal.com, Inc.";
export const SENDER_ID = process.env.NEXT_PUBLIC_SENDER_ID || "Cal";
// This is the URL from which all Cal Links and their assets are served.
// Use website URL to make links shorter(cal.com and not app.cal.com)

View File

@ -1,4 +1,4 @@
import { PeriodType, Prisma, SchedulingType, UserPlan } from "@prisma/client";
import { PeriodType, Prisma, SchedulingType } from "@prisma/client";
import { DailyLocationType } from "@calcom/app-store/locations";
import { userSelect } from "@calcom/prisma/selects";
@ -15,7 +15,6 @@ type UsernameSlugLinkProps = {
bio?: string | null;
avatar?: string | null;
theme?: string | null;
plan?: UserPlan;
away?: boolean;
verified?: boolean | null;
allowDynamicBooking?: boolean | null;
@ -41,7 +40,6 @@ const user: User = {
name: "John doe",
avatar: "",
destinationCalendar: null,
plan: UserPlan.PRO,
hideBranding: true,
brandColor: "#797979",
darkBrandColor: "#efefef",

View File

@ -1,4 +1,4 @@
import { Prisma, UserPlan } from "@prisma/client";
import { Prisma } from "@prisma/client";
import prisma, { baseEventTypeSelect } from "@calcom/prisma";
import { EventTypeMetaDataSchema } from "@calcom/prisma/zod-utils";
@ -12,7 +12,6 @@ export async function getTeamWithMembers(id?: number, slug?: string, userId?: nu
email: true,
name: true,
id: true,
plan: true,
bio: true,
});
const teamSelect = Prisma.validator<Prisma.TeamSelect>()({
@ -62,7 +61,6 @@ export async function getTeamWithMembers(id?: number, slug?: string, userId?: nu
const members = team.members.map((obj) => {
return {
...obj.user,
isMissingSeat: obj.user.plan === UserPlan.FREE,
role: obj.role,
accepted: obj.accepted,
disableImpersonation: obj.disableImpersonation,

View File

@ -1,6 +1,3 @@
// import { DeploymentType } from "@prisma/admin-client";
import { User } from "@prisma/client";
import logger from "@calcom/lib/logger";
import { default as webPrisma } from "@calcom/prisma";
@ -17,7 +14,8 @@ export type TeamInfoType = {
};
export type WebUserInfoType = UserInfo & {
plan: User["plan"];
/** All users are PRO now */
plan?: "PRO";
};
export type ConsoleUserInfoType = UserInfo & {

View File

@ -96,7 +96,6 @@ export const extendEventData = (
ipAddress: "",
queryString: "",
page_url: pageUrl,
licenseConsent: !!process.env.NEXT_PUBLIC_LICENSE_CONSENT,
licensekey: process.env.CALCOM_LICENSE_KEY,
isTeamBooking:
original?.isTeamBooking === undefined

View File

@ -1,5 +1,5 @@
import { faker } from "@faker-js/faker";
import { Booking, BookingStatus, EventType, Prisma, UserPlan, Webhook } from "@prisma/client";
import { Booking, BookingStatus, EventType, Prisma, Webhook } from "@prisma/client";
import { CalendarEvent, Person, VideoCallData } from "@calcom/types/Calendar";
@ -197,7 +197,6 @@ export const buildUser = <T extends Partial<UserPayload>>(user?: T): UserPayload
locale: "en",
metadata: null,
password: null,
plan: UserPlan.PRO,
role: "USER",
schedules: [],
selectedCalendars: [],

View File

@ -0,0 +1,11 @@
/*
Warnings:
- You are about to drop the column `plan` on the `users` table. All the data in the column will be lost.
*/
-- AlterTable
ALTER TABLE "users" DROP COLUMN "plan";
-- DropEnum
DROP TYPE "UserPlan";

View File

@ -103,12 +103,6 @@ model Credential {
invalid Boolean? @default(false)
}
enum UserPlan {
FREE
TRIAL
PRO
}
enum IdentityProvider {
CAL
GOOGLE
@ -170,7 +164,6 @@ model User {
identityProviderId String?
availability Availability[]
invitedTo Int?
plan UserPlan @default(PRO)
webhooks Webhook[]
brandColor String @default("#292929")
darkBrandColor String @default("#fafafa")

View File

@ -1,4 +1,4 @@
import { BookingStatus, MembershipRole, Prisma, UserPermissionRole, UserPlan } from "@prisma/client";
import { BookingStatus, MembershipRole, Prisma, UserPermissionRole } from "@prisma/client";
import { uuid } from "short-uuid";
import dailyMeta from "@calcom/app-store/dailyvideo/_metadata";
@ -16,7 +16,6 @@ async function createUserAndEventType(opts: {
email: string;
password: string;
username: string;
plan: UserPlan;
name: string;
completedOnboarding?: boolean;
timeZone?: string;
@ -176,7 +175,6 @@ async function main() {
password: "delete-me",
username: "delete-me",
name: "delete-me",
plan: "FREE",
},
eventTypes: [],
});
@ -187,7 +185,6 @@ async function main() {
password: "onboarding",
username: "onboarding",
name: "onboarding",
plan: "TRIAL",
completedOnboarding: false,
},
eventTypes: [],
@ -199,7 +196,6 @@ async function main() {
password: "free-first-hidden",
username: "free-first-hidden",
name: "Free First Hidden Example",
plan: "FREE",
},
eventTypes: [
{
@ -221,7 +217,6 @@ async function main() {
name: "Pro Example",
password: "pro",
username: "pro",
plan: "PRO",
},
eventTypes: [
{
@ -445,7 +440,6 @@ async function main() {
password: "trial",
username: "trial",
name: "Trial Example",
plan: "TRIAL",
},
eventTypes: [
{
@ -467,7 +461,6 @@ async function main() {
password: "free",
username: "free",
name: "Free Example",
plan: "FREE",
},
eventTypes: [
{
@ -489,7 +482,6 @@ async function main() {
password: "usa",
username: "usa",
name: "USA Timezone Example",
plan: "FREE",
timeZone: "America/Phoenix",
},
eventTypes: [
@ -507,7 +499,6 @@ async function main() {
password: "teamfree",
username: "teamfree",
name: "Team Free Example",
plan: "FREE",
},
eventTypes: [],
});
@ -518,7 +509,6 @@ async function main() {
password: "teampro",
username: "teampro",
name: "Team Pro Example",
plan: "PRO",
},
eventTypes: [],
});
@ -530,7 +520,6 @@ async function main() {
password: "ADMINadmin2022!",
username: "admin",
name: "Admin Example",
plan: "PRO",
role: "ADMIN",
},
eventTypes: [],
@ -542,7 +531,6 @@ async function main() {
password: "teampro2",
username: "teampro2",
name: "Team Pro Example 2",
plan: "PRO",
},
eventTypes: [],
});
@ -553,7 +541,6 @@ async function main() {
password: "teampro3",
username: "teampro3",
name: "Team Pro Example 3",
plan: "PRO",
},
eventTypes: [],
});
@ -564,7 +551,6 @@ async function main() {
password: "teampro4",
username: "teampro4",
name: "Team Pro Example 4",
plan: "PRO",
},
eventTypes: [],
});

View File

@ -97,7 +97,6 @@ export const availiblityPageEventTypeSelect = Prisma.validator<Prisma.EventTypeS
name: true,
username: true,
hideBranding: true,
plan: true,
timeZone: true,
},
},

View File

@ -25,7 +25,6 @@ export const baseUserSelect = Prisma.validator<Prisma.UserSelect>()({
name: true,
destinationCalendar: true,
locale: true,
plan: true,
hideBranding: true,
theme: true,
brandColor: true,
@ -40,7 +39,6 @@ export const userSelect = Prisma.validator<Prisma.UserArgs>()({
allowDynamicBooking: true,
destinationCalendar: true,
locale: true,
plan: true,
avatar: true,
hideBranding: true,
theme: true,

View File

@ -49,7 +49,6 @@ async function getUserFromSession({
identityProvider: true,
brandColor: true,
darkBrandColor: true,
plan: true,
away: true,
credentials: {
select: {

View File

@ -167,7 +167,6 @@ const loggedInViewerRouter = router({
identityProvider: user.identityProvider,
brandColor: user.brandColor,
darkBrandColor: user.darkBrandColor,
plan: user.plan,
away: user.away,
bio: user.bio,
weekStart: user.weekStart,
@ -306,6 +305,7 @@ const loggedInViewerRouter = router({
const userCredentials = await prisma.credential.findMany({
where: {
userId: ctx.user.id,
app: {
categories: { has: AppCategories.calendar },
enabled: true,
@ -647,7 +647,6 @@ const loggedInViewerRouter = router({
email: true,
metadata: true,
name: true,
plan: true,
createdDate: true,
},
});

View File

@ -1,4 +1,4 @@
import { EventTypeCustomInput, MembershipRole, PeriodType, Prisma } from "@prisma/client";
import { MembershipRole, PeriodType, Prisma } from "@prisma/client";
import { PrismaClientKnownRequestError } from "@prisma/client/runtime";
// REVIEW: From lint error
import _ from "lodash";
@ -10,12 +10,12 @@ import { stripeDataSchema } from "@calcom/app-store/stripepayment/lib/server";
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 { _DestinationCalendarModel, _EventTypeModel } from "@calcom/prisma/zod";
import {
customInputSchema,
CustomInputSchema,
EventTypeMetaDataSchema,
stringOrNumber,
CustomInputSchema,
} from "@calcom/prisma/zod-utils";
import { createEventTypeInput } from "@calcom/prisma/zod/custom/eventtype";
@ -99,6 +99,14 @@ const EventTypeUpdateInput = _EventTypeModel
})
);
const EventTypeDuplicateInput = z.object({
id: z.number(),
slug: z.string(),
title: z.string(),
description: z.string(),
length: z.number(),
});
const eventOwnerProcedure = authedProcedure.use(async ({ ctx, rawInput, next }) => {
// Prevent non-owners to update/delete a team event
const event = await ctx.prisma.eventType.findUnique({
@ -193,7 +201,6 @@ export const eventTypesRouter = router({
startTime: true,
endTime: true,
bufferTime: true,
plan: true,
teams: {
where: {
accepted: true,
@ -321,9 +328,6 @@ export const eventTypesRouter = router({
}))
);
return {
viewer: {
plan: user.plan,
},
// don't display event teams without event types,
eventTypeGroups: eventTypeGroups.filter((groupBy) => !!groupBy.eventTypes?.length),
// so we can show a dropdown when the user has teams
@ -454,7 +458,6 @@ export const eventTypesRouter = router({
endTime: true,
bufferTime: true,
avatar: true,
plan: true,
},
});
if (!user) {
@ -632,4 +635,112 @@ export const eventTypesRouter = router({
id,
};
}),
duplicate: eventOwnerProcedure.input(EventTypeDuplicateInput.strict()).mutation(async ({ ctx, input }) => {
const { id: originalEventTypeId, title: newEventTitle, slug: newSlug } = input;
const eventType = await ctx.prisma.eventType.findUnique({
where: {
id: originalEventTypeId,
},
include: {
customInputs: true,
schedule: true,
users: true,
team: true,
workflows: true,
webhooks: true,
},
});
if (!eventType) {
throw new TRPCError({ code: "NOT_FOUND" });
}
// Validate user is owner of event type or in the team
if (eventType.userId !== ctx.user.id) {
if (eventType.teamId) {
const isMember = await ctx.prisma.membership.findFirst({
where: {
userId: ctx.user.id,
teamId: eventType.teamId,
},
});
if (!isMember) {
throw new TRPCError({ code: "FORBIDDEN" });
}
}
throw new TRPCError({ code: "FORBIDDEN" });
}
const {
customInputs,
users,
locations,
team,
recurringEvent,
bookingLimits,
metadata,
workflows,
// eslint-disable-next-line @typescript-eslint/no-unused-vars
id: _id,
// eslint-disable-next-line @typescript-eslint/no-unused-vars
webhooks: _webhooks,
// eslint-disable-next-line @typescript-eslint/no-unused-vars
schedule: _schedule,
// eslint-disable-next-line @typescript-eslint/ban-ts-comment
// @ts-ignore - not typed correctly as its set on SSR
descriptionAsSafeHTML: _descriptionAsSafeHTML,
...rest
} = eventType;
const data: Prisma.EventTypeCreateInput = {
...rest,
title: newEventTitle,
slug: newSlug,
locations: locations ?? undefined,
team: team ? { connect: { id: team.id } } : undefined,
users: users ? { connect: users.map((user) => ({ id: user.id })) } : undefined,
recurringEvent: recurringEvent || undefined,
bookingLimits: bookingLimits ?? undefined,
metadata: metadata === null ? Prisma.DbNull : metadata,
};
const newEventType = await ctx.prisma.eventType.create({ data });
// Create custom inputs
if (customInputs) {
const customInputsData = customInputs.map((customInput) => {
const { id: _, options, ...rest } = customInput;
return {
options: options ?? undefined,
...rest,
eventTypeId: newEventType.id,
};
});
await ctx.prisma.eventTypeCustomInput.createMany({
data: customInputsData,
});
}
if (workflows.length > 0) {
const workflowIds = workflows.map((workflow) => {
return { id: workflow.workflowId };
});
const eventUpdateData: Prisma.EventTypeUpdateInput = {
workflows: {
connect: workflowIds,
},
};
await ctx.prisma.eventType.update({
where: {
id: newEventType.id,
},
data: eventUpdateData,
});
}
return {
eventType: newEventType,
};
}),
});

View File

@ -1,4 +1,4 @@
import { MembershipRole, Prisma, UserPlan } from "@prisma/client";
import { MembershipRole, Prisma } from "@prisma/client";
import { randomBytes } from "crypto";
import { z } from "zod";
@ -45,10 +45,8 @@ export const viewerTeamsRouter = router({
...team,
membership: {
role: membership?.role as MembershipRole,
isMissingSeat: membership?.plan === UserPlan.FREE,
accepted: membership?.accepted,
},
requiresUpgrade: HOSTED_CAL_FEATURES ? !!team.members.find((m) => m.plan !== UserPlan.PRO) : false,
};
}),
// Returns teams I a member of

View File

@ -26,6 +26,7 @@ import {
deleteScheduledSMSReminder,
scheduleSMSReminder,
} from "@calcom/features/ee/workflows/lib/reminders/smsReminderManager";
import { SENDER_ID } from "@calcom/lib/constants";
import { getErrorFromUnknown } from "@calcom/lib/errors";
import { TRPCError } from "@trpc/server";
@ -154,7 +155,7 @@ export const workflowsRouter = router({
action: WorkflowActions.EMAIL_HOST,
template: WorkflowTemplates.REMINDER,
workflowId: workflow.id,
sender: "Cal",
sender: SENDER_ID,
},
});
return { workflow };
@ -469,7 +470,7 @@ export const workflowsRouter = router({
step.reminderBody || "",
step.id,
step.template,
step.sender || "Cal"
step.sender || SENDER_ID
);
}
});
@ -535,7 +536,7 @@ export const workflowsRouter = router({
emailSubject: newStep.template === WorkflowTemplates.CUSTOM ? newStep.emailSubject : null,
template: newStep.template,
numberRequired: newStep.numberRequired,
sender: newStep.sender || "Cal",
sender: newStep.sender || SENDER_ID,
},
});
//cancel all reminders of step and create new ones (not for newEventTypes)
@ -647,7 +648,7 @@ export const workflowsRouter = router({
newStep.reminderBody || "",
newStep.id || 0,
newStep.template,
newStep.sender || "Cal"
newStep.sender || SENDER_ID
);
}
});
@ -671,7 +672,7 @@ export const workflowsRouter = router({
addedSteps.forEach(async (step) => {
if (step) {
const newStep = step;
newStep.sender = step.sender || "Cal";
newStep.sender = step.sender || SENDER_ID;
const createdStep = await ctx.prisma.workflowStep.create({
data: step,
});
@ -760,7 +761,7 @@ export const workflowsRouter = router({
step.reminderBody || "",
createdStep.id,
step.template,
step.sender || "Cal"
step.sender || SENDER_ID
);
}
});
@ -897,7 +898,7 @@ export const workflowsRouter = router({
reminderBody,
0,
template,
sender || "Cal"
sender || SENDER_ID
);
return { message: "Notification sent" };
}

View File

@ -1,15 +1,5 @@
declare namespace NodeJS {
interface ProcessEnv {
/**
* Set this value to 'agree' to accept our license:
* LICENSE: https://github.com/calendso/calendso/blob/main/LICENSE
*
* Summary of terms:
* - The codebase has to stay open source, whether it was modified or not
* - You can not repackage or sell the codebase
* - Acquire a commercial license to remove these terms by visiting: cal.com/sales
**/
readonly NEXT_PUBLIC_LICENSE_CONSENT: "agree" | undefined;
/** Needed to enable enterprise-only features */
readonly CALCOM_LICENSE_KEY: string | undefined;
readonly CALCOM_TELEMETRY_DISABLED: string | undefined;

View File

@ -127,14 +127,6 @@ export const InputField = forwardRef<HTMLInputElement, InputFieldProps>(function
id={id}
type={type}
placeholder={placeholder}
{...passThrough}
{...(type == "search" && {
onChange: (e) => {
setInputValue(e.target.value);
props.onChange && props.onChange(e);
},
value: inputValue,
})}
isFullWidth={inputIsFullWidth}
className={classNames(
className,
@ -144,6 +136,14 @@ export const InputField = forwardRef<HTMLInputElement, InputFieldProps>(function
"!my-0 !ring-0"
)}
{...passThrough}
{...(type == "search" && {
onChange: (e) => {
console.log(e.target.value);
setInputValue(e.target.value);
props.onChange && props.onChange(e);
},
value: inputValue,
})}
readOnly={readOnly}
ref={ref}
/>

View File

@ -23,11 +23,11 @@ export function TopBanner(props: TopBannerProps) {
<div
data-testid="banner"
className={classNames(
" z-30 flex min-h-[40px] w-full items-start justify-between gap-8 bg-gray-50 px-4 text-center sm:items-center",
"z-30 flex min-h-[40px] w-full items-start justify-between gap-8 bg-gray-50 py-2 px-4 text-center lg:items-center",
variantClassName[variant]
)}>
<div className="flex flex-1 items-center justify-center gap-2 p-1">
<p className="flex items-center justify-center gap-2 font-sans text-sm font-medium leading-4 text-gray-900">
<div className="flex flex-1 flex-col items-start justify-center gap-2 p-1 lg:flex-row lg:items-center">
<p className="flex flex-col items-start justify-center gap-2 text-left font-sans text-sm font-medium leading-4 text-gray-900 lg:flex-row lg:items-center">
{["warning", "error"].includes(variant) && (
<ExclamationIcon className="h-5 w-5 text-black" aria-hidden="true" />
)}

View File

@ -1,6 +1,6 @@
import * as DialogPrimitive from "@radix-ui/react-dialog";
import { useRouter } from "next/router";
import React, { forwardRef, HTMLProps, ReactNode, useState } from "react";
import React, { ReactNode, useState } from "react";
import { Icon } from "react-feather";
import classNames from "@calcom/lib/classNames";

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