Compare commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
5d1976295c | ||
|
|
e9b1fa1f50 | ||
|
|
80080dda7a | ||
|
|
27c25e1f95 | ||
|
|
858be0f5d8 | ||
|
|
68d0090f50 | ||
|
|
a43fa13b11 | ||
|
|
2bf6da30b3 | ||
|
|
d85e0b51e4 | ||
|
|
55a317d677 | ||
|
|
5fff8fc58c | ||
|
|
0d68767d0a | ||
|
|
2051e87436 | ||
|
|
77a157a12b | ||
|
|
c4f25f2c44 | ||
|
|
d967facb63 | ||
|
|
c6dad39fb0 | ||
|
|
eec7f4d7d4 | ||
|
|
95d5824ba9 | ||
|
|
5f95a4d041 | ||
|
|
8e97444129 | ||
|
|
8d8264b5f5 | ||
|
|
2ad81221d0 | ||
|
|
3d1ac4523f | ||
|
|
b38807cb0f | ||
|
|
11fb387370 | ||
|
|
1546f9115d | ||
|
|
40ecd5dd9d | ||
|
|
ac6ac14a23 | ||
|
|
c6534160e4 | ||
|
|
aa2db562b8 | ||
|
|
b7ed314be2 | ||
|
|
d102268b33 | ||
|
|
f4397d6593 | ||
|
|
192603a1fa | ||
|
|
a3d969680f | ||
|
|
adc3b3aec6 | ||
|
|
92286e2453 | ||
|
|
51b2a15cb0 | ||
|
|
fa9a099dd6 | ||
|
|
83ceb46cca | ||
|
|
75d5576e17 | ||
|
|
0a6dde4fcc | ||
|
|
73b5839284 | ||
|
|
1a42b94b13 | ||
|
|
acb910f680 | ||
|
|
38e857f9a9 | ||
|
|
b8149dee36 | ||
|
|
f583ef40f2 | ||
|
|
71574488c5 | ||
|
|
008e0e43a2 | ||
|
|
da22eda662 | ||
|
|
f94d696885 | ||
|
|
9e84eb1b2c | ||
|
|
4c549811a1 | ||
|
|
05502f0cfb | ||
|
|
e436b8e06b | ||
|
|
8146cf8c93 | ||
|
|
c05f595ef0 | ||
|
|
0a795f5363 | ||
|
|
5b50a469b2 | ||
|
|
c08365f09c | ||
|
|
477e102388 | ||
|
|
e1b10b4ec5 | ||
|
|
ff6db3c572 | ||
|
|
4fa3fdef97 | ||
|
|
f29f6632e8 | ||
|
|
36747988e6 | ||
|
|
3dd312b88b | ||
|
|
3c9e9329b0 | ||
|
|
8617f43a8e | ||
|
|
5f0eb652b0 | ||
|
|
26d7301885 | ||
|
|
ffc4d22ff1 | ||
|
|
ee286a600f | ||
|
|
72c493ee21 | ||
|
|
5bd0428d18 | ||
|
|
c7089b9db6 | ||
|
|
81ad00a816 | ||
|
|
b5730da56d | ||
|
|
9455d14ad5 | ||
|
|
a056160404 | ||
|
|
6d53cd84f8 | ||
|
|
30cb8f745d | ||
|
|
bafa3c97ac |
@@ -74,6 +74,7 @@ export const schemaUserBaseBodyParams = User.pick({
|
||||
timeZone: true,
|
||||
weekStart: true,
|
||||
theme: true,
|
||||
appTheme: true,
|
||||
defaultScheduleId: true,
|
||||
locale: true,
|
||||
hideBranding: true,
|
||||
@@ -100,6 +101,7 @@ const schemaUserEditParams = z.object({
|
||||
hideBranding: z.boolean().optional(),
|
||||
timeZone: timeZone.optional(),
|
||||
theme: z.nativeEnum(theme).optional().nullable(),
|
||||
appTheme: z.nativeEnum(theme).optional().nullable(),
|
||||
timeFormat: z.nativeEnum(timeFormat).optional(),
|
||||
defaultScheduleId: z
|
||||
.number()
|
||||
@@ -122,6 +124,7 @@ const schemaUserCreateParams = z.object({
|
||||
hideBranding: z.boolean().optional(),
|
||||
timeZone: timeZone.optional(),
|
||||
theme: z.nativeEnum(theme).optional().nullable(),
|
||||
appTheme: z.nativeEnum(theme).optional().nullable(),
|
||||
timeFormat: z.nativeEnum(timeFormat).optional(),
|
||||
defaultScheduleId: z
|
||||
.number()
|
||||
@@ -159,6 +162,7 @@ export const schemaUserReadPublic = User.pick({
|
||||
weekStart: true,
|
||||
endTime: true,
|
||||
bufferTime: true,
|
||||
appTheme: true,
|
||||
theme: true,
|
||||
defaultScheduleId: true,
|
||||
locale: true,
|
||||
|
||||
@@ -24,6 +24,7 @@
|
||||
"@types/node": "16.9.1",
|
||||
"@types/react": "18.0.26",
|
||||
"@types/react-dom": "^18.0.9",
|
||||
"@types/swagger-ui-react": "^4.18.3",
|
||||
"typescript": "^4.9.4"
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,9 +1,10 @@
|
||||
import "highlight.js/styles/default.css";
|
||||
import { type AppProps } from "next/app";
|
||||
import "swagger-ui-react/swagger-ui.css";
|
||||
|
||||
import "../styles/globals.css";
|
||||
|
||||
function MyApp({ Component, pageProps }) {
|
||||
function MyApp({ Component, pageProps }: AppProps) {
|
||||
return <Component {...pageProps} />;
|
||||
}
|
||||
|
||||
|
||||
@@ -1,9 +1,8 @@
|
||||
import dynamic from "next/dynamic";
|
||||
import type { SwaggerUI } from "swagger-ui-react";
|
||||
|
||||
import { SnippedGenerator, requestSnippets } from "@lib/snippets";
|
||||
|
||||
const SwaggerUIDynamic: SwaggerUI & { url: string } = dynamic(() => import("swagger-ui-react"), {
|
||||
const SwaggerUIDynamic = dynamic(() => import("swagger-ui-react"), {
|
||||
ssr: false,
|
||||
});
|
||||
|
||||
@@ -17,8 +16,6 @@ export default function APIDocs() {
|
||||
requestSnippets={requestSnippets}
|
||||
plugins={[SnippedGenerator]}
|
||||
tryItOutEnabled={true}
|
||||
syntaxHighlight={true}
|
||||
enableCORS={false} // Doesn't seem to work either
|
||||
docExpansion="list"
|
||||
filter={true}
|
||||
/>
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
import { useRouter } from "next/navigation";
|
||||
import { useCallback, useState } from "react";
|
||||
|
||||
import { sdkActionManager } from "@calcom/embed-core/embed-iframe";
|
||||
import { useLocale } from "@calcom/lib/hooks/useLocale";
|
||||
import { collectPageParameters, telemetryEventTypes, useTelemetry } from "@calcom/lib/telemetry";
|
||||
import type { RecurringEvent } from "@calcom/types/Calendar";
|
||||
@@ -23,13 +24,22 @@ type Props = {
|
||||
theme: string | null;
|
||||
allRemainingBookings: boolean;
|
||||
seatReferenceUid?: string;
|
||||
bookingCancelledEventProps: {
|
||||
booking: unknown;
|
||||
organizer: {
|
||||
name: string;
|
||||
email: string;
|
||||
timeZone?: string;
|
||||
};
|
||||
eventType: unknown;
|
||||
};
|
||||
};
|
||||
|
||||
export default function CancelBooking(props: Props) {
|
||||
const [cancellationReason, setCancellationReason] = useState<string>("");
|
||||
const { t } = useLocale();
|
||||
const router = useRouter();
|
||||
const { booking, allRemainingBookings, seatReferenceUid } = props;
|
||||
const { booking, allRemainingBookings, seatReferenceUid, bookingCancelledEventProps } = props;
|
||||
const [loading, setLoading] = useState(false);
|
||||
const telemetry = useTelemetry();
|
||||
const [error, setError] = useState<string | null>(booking ? null : t("booking_already_cancelled"));
|
||||
@@ -97,8 +107,17 @@ export default function CancelBooking(props: Props) {
|
||||
method: "POST",
|
||||
});
|
||||
|
||||
const bookingWithCancellationReason = {
|
||||
...(bookingCancelledEventProps.booking as object),
|
||||
cancellationReason,
|
||||
} as unknown;
|
||||
|
||||
if (res.status >= 200 && res.status < 300) {
|
||||
// tested by apps/web/playwright/booking-pages.e2e.ts
|
||||
sdkActionManager?.fire("bookingCancelled", {
|
||||
...bookingCancelledEventProps,
|
||||
booking: bookingWithCancellationReason,
|
||||
});
|
||||
router.refresh();
|
||||
} else {
|
||||
setLoading(false);
|
||||
|
||||
@@ -1,3 +1,4 @@
|
||||
import { InfoIcon } from "lucide-react";
|
||||
import dynamic from "next/dynamic";
|
||||
import Link from "next/link";
|
||||
import type { EventTypeSetupProps, FormValues } from "pages/event-types/[type]";
|
||||
@@ -59,7 +60,6 @@ export const EventAdvancedTab = ({ eventType, team }: Pick<EventTypeSetupProps,
|
||||
const selectedThemeIsDark =
|
||||
user?.theme === "dark" ||
|
||||
(!user?.theme && typeof document !== "undefined" && document.documentElement.classList.contains("dark"));
|
||||
|
||||
formMethods.getValues().bookingFields.forEach(({ name }) => {
|
||||
bookingFields[name] = `${name} input`;
|
||||
});
|
||||
@@ -101,7 +101,8 @@ export const EventAdvancedTab = ({ eventType, team }: Pick<EventTypeSetupProps,
|
||||
};
|
||||
}
|
||||
return field;
|
||||
})
|
||||
}),
|
||||
{ shouldDirty: true }
|
||||
);
|
||||
};
|
||||
|
||||
@@ -179,11 +180,20 @@ export const EventAdvancedTab = ({ eventType, team }: Pick<EventTypeSetupProps,
|
||||
{displayDestinationCalendarSelector && (
|
||||
<div className="w-full">
|
||||
<Switch
|
||||
label={t("display_add_to_calendar_organizer")}
|
||||
tooltip={t("reconnect_calendar_to_use")}
|
||||
label={
|
||||
<>
|
||||
{t("display_add_to_calendar_organizer")}
|
||||
<InfoIcon className="text-default hover:text-attention hover:bg-attention ms-1 inline h-4 w-4 rounded-md" />
|
||||
</>
|
||||
}
|
||||
checked={useEventTypeDestinationCalendarEmail}
|
||||
onCheckedChange={(val) => {
|
||||
setUseEventTypeDestinationCalendarEmail(val);
|
||||
formMethods.setValue("useEventTypeDestinationCalendarEmail", val);
|
||||
formMethods.setValue("useEventTypeDestinationCalendarEmail", val, { shouldDirty: true });
|
||||
if (val) {
|
||||
showToast(t("reconnect_calendar_to_use"), "warning");
|
||||
}
|
||||
}}
|
||||
/>
|
||||
</div>
|
||||
@@ -319,7 +329,7 @@ export const EventAdvancedTab = ({ eventType, team }: Pick<EventTypeSetupProps,
|
||||
description={t("private_link_description", { appName: APP_NAME })}
|
||||
checked={hashedLinkVisible}
|
||||
onCheckedChange={(e) => {
|
||||
formMethods.setValue("hashedLink", e ? hashedUrl : undefined);
|
||||
formMethods.setValue("hashedLink", e ? hashedUrl : undefined, { shouldDirty: true });
|
||||
setHashedLinkVisible(e);
|
||||
}}>
|
||||
<div className="border-subtle rounded-b-lg border border-t-0 p-6">
|
||||
@@ -383,10 +393,12 @@ export const EventAdvancedTab = ({ eventType, team }: Pick<EventTypeSetupProps,
|
||||
// Enabling seats will disable guests and requiring confirmation until fully supported
|
||||
if (e) {
|
||||
toggleGuests(false);
|
||||
formMethods.setValue("requiresConfirmation", false);
|
||||
formMethods.setValue("requiresConfirmation", false, { shouldDirty: true });
|
||||
setRequiresConfirmation(false);
|
||||
formMethods.setValue("metadata.multipleDuration", undefined);
|
||||
formMethods.setValue("seatsPerTimeSlot", eventType.seatsPerTimeSlot ?? 2);
|
||||
formMethods.setValue("metadata.multipleDuration", undefined, { shouldDirty: true });
|
||||
formMethods.setValue("seatsPerTimeSlot", eventType.seatsPerTimeSlot ?? 2, {
|
||||
shouldDirty: true,
|
||||
});
|
||||
} else {
|
||||
formMethods.setValue("seatsPerTimeSlot", null);
|
||||
toggleGuests(true);
|
||||
@@ -506,7 +518,7 @@ export const EventAdvancedTab = ({ eventType, team }: Pick<EventTypeSetupProps,
|
||||
{showEventNameTip && (
|
||||
<CustomEventTypeModal
|
||||
close={closeEventNameTip}
|
||||
setValue={(val: string) => formMethods.setValue("eventName", val)}
|
||||
setValue={(val: string) => formMethods.setValue("eventName", val, { shouldDirty: true })}
|
||||
defaultValue={formMethods.getValues("eventName")}
|
||||
placeHolder={eventNamePlaceholder}
|
||||
event={eventNameObject}
|
||||
|
||||
@@ -31,10 +31,14 @@ export const EventAppsTab = ({ eventType }: { eventType: EventType }) => {
|
||||
const allAppsData = formMethods.watch("metadata")?.apps || {};
|
||||
|
||||
const setAllAppsData = (_allAppsData: typeof allAppsData) => {
|
||||
formMethods.setValue("metadata", {
|
||||
...formMethods.getValues("metadata"),
|
||||
apps: _allAppsData,
|
||||
});
|
||||
formMethods.setValue(
|
||||
"metadata",
|
||||
{
|
||||
...formMethods.getValues("metadata"),
|
||||
apps: _allAppsData,
|
||||
},
|
||||
{ shouldDirty: true }
|
||||
);
|
||||
};
|
||||
|
||||
const getAppDataGetter = (appId: EventTypeAppsList): GetAppData => {
|
||||
|
||||
@@ -230,7 +230,7 @@ const EventTypeSchedule = ({ eventType }: { eventType: EventTypeSetup }) => {
|
||||
: option.value === schedules.find((schedule) => schedule.isDefault)?.id
|
||||
);
|
||||
|
||||
setValue("availability", value);
|
||||
setValue("availability", value, { shouldDirty: true });
|
||||
},
|
||||
[data]
|
||||
);
|
||||
@@ -238,7 +238,7 @@ const EventTypeSchedule = ({ eventType }: { eventType: EventTypeSetup }) => {
|
||||
|
||||
useEffect(() => {
|
||||
if (!availabilityValue?.value) return;
|
||||
setValue("schedule", availabilityValue.value);
|
||||
setValue("schedule", availabilityValue.value, { shouldDirty: true });
|
||||
}, [availabilityValue, setValue]);
|
||||
|
||||
return (
|
||||
@@ -260,7 +260,7 @@ const EventTypeSchedule = ({ eventType }: { eventType: EventTypeSetup }) => {
|
||||
isSearchable={false}
|
||||
onChange={(selected) => {
|
||||
field.onChange(selected?.value || null);
|
||||
if (selected?.value) setValue("availability", selected);
|
||||
if (selected?.value) setValue("availability", selected, { shouldDirty: true });
|
||||
}}
|
||||
className="block w-full min-w-0 flex-1 rounded-sm text-sm"
|
||||
value={availabilityValue}
|
||||
@@ -298,7 +298,7 @@ const UseCommonScheduleSettingsToggle = ({ eventType }: { eventType: EventTypeSe
|
||||
onCheckedChange={(checked) => {
|
||||
onChange(!checked);
|
||||
if (!checked) {
|
||||
setValue("schedule", null);
|
||||
setValue("schedule", null, { shouldDirty: true });
|
||||
}
|
||||
}}
|
||||
title={t("choose_common_schedule_team_event")}
|
||||
|
||||
@@ -61,7 +61,8 @@ const MinimumBookingNoticeInput = React.forwardRef<
|
||||
minimumBookingNoticeDisplayValues.type,
|
||||
"minutes",
|
||||
minimumBookingNoticeDisplayValues.value
|
||||
)
|
||||
),
|
||||
{ shouldDirty: true }
|
||||
);
|
||||
}, [minimumBookingNoticeDisplayValues, setValue, passThroughProps.name]);
|
||||
|
||||
@@ -230,7 +231,9 @@ export const EventLimitsTab = () => {
|
||||
<Select
|
||||
isSearchable={false}
|
||||
onChange={(val) => {
|
||||
formMethods.setValue("slotInterval", val && (val.value || 0) > 0 ? val.value : null);
|
||||
formMethods.setValue("slotInterval", val && (val.value || 0) > 0 ? val.value : null, {
|
||||
shouldDirty: true,
|
||||
});
|
||||
}}
|
||||
defaultValue={
|
||||
slotIntervalOptions.find(
|
||||
@@ -258,11 +261,15 @@ export const EventLimitsTab = () => {
|
||||
checked={isChecked}
|
||||
onCheckedChange={(active) => {
|
||||
if (active) {
|
||||
formMethods.setValue("bookingLimits", {
|
||||
PER_DAY: 1,
|
||||
});
|
||||
formMethods.setValue(
|
||||
"bookingLimits",
|
||||
{
|
||||
PER_DAY: 1,
|
||||
},
|
||||
{ shouldDirty: true }
|
||||
);
|
||||
} else {
|
||||
formMethods.setValue("bookingLimits", {});
|
||||
formMethods.setValue("bookingLimits", {}, { shouldDirty: true });
|
||||
}
|
||||
}}
|
||||
switchContainerClassName={classNames(
|
||||
@@ -279,7 +286,7 @@ export const EventLimitsTab = () => {
|
||||
/>
|
||||
<Controller
|
||||
name="onlyShowFirstAvailableSlot"
|
||||
render={({ field: { value } }) => {
|
||||
render={({ field: { onChange, value } }) => {
|
||||
const isChecked = value;
|
||||
return (
|
||||
<SettingsToggle
|
||||
@@ -289,7 +296,7 @@ export const EventLimitsTab = () => {
|
||||
description={t("limit_booking_only_first_slot_description")}
|
||||
checked={isChecked}
|
||||
onCheckedChange={(active) => {
|
||||
formMethods.setValue("onlyShowFirstAvailableSlot", active ?? false);
|
||||
onChange(active ?? false);
|
||||
}}
|
||||
switchContainerClassName={classNames(
|
||||
"border-subtle mt-6 rounded-lg border py-6 px-4 sm:px-6",
|
||||
@@ -301,7 +308,7 @@ export const EventLimitsTab = () => {
|
||||
/>
|
||||
<Controller
|
||||
name="durationLimits"
|
||||
render={({ field: { value } }) => {
|
||||
render={({ field: { onChange, value } }) => {
|
||||
const isChecked = Object.keys(value ?? {}).length > 0;
|
||||
return (
|
||||
<SettingsToggle
|
||||
@@ -317,11 +324,11 @@ export const EventLimitsTab = () => {
|
||||
checked={isChecked}
|
||||
onCheckedChange={(active) => {
|
||||
if (active) {
|
||||
formMethods.setValue("durationLimits", {
|
||||
onChange({
|
||||
PER_DAY: 60,
|
||||
});
|
||||
} else {
|
||||
formMethods.setValue("durationLimits", {});
|
||||
onChange({});
|
||||
}
|
||||
}}>
|
||||
<div className="border-subtle rounded-b-lg border border-t-0 p-6">
|
||||
@@ -338,7 +345,7 @@ export const EventLimitsTab = () => {
|
||||
/>
|
||||
<Controller
|
||||
name="periodType"
|
||||
render={({ field: { value } }) => {
|
||||
render={({ field: { onChange, value } }) => {
|
||||
const isChecked = value && value !== "UNLIMITED";
|
||||
|
||||
return (
|
||||
@@ -353,11 +360,18 @@ export const EventLimitsTab = () => {
|
||||
title={t("limit_future_bookings")}
|
||||
description={t("limit_future_bookings_description")}
|
||||
checked={isChecked}
|
||||
onCheckedChange={(bool) => formMethods.setValue("periodType", bool ? "ROLLING" : "UNLIMITED")}>
|
||||
onCheckedChange={(bool) => {
|
||||
if (bool && !formMethods.getValues("periodDays")) {
|
||||
formMethods.setValue("periodDays", 30, { shouldDirty: true });
|
||||
}
|
||||
return onChange(bool ? "ROLLING" : "UNLIMITED");
|
||||
}}>
|
||||
<div className="border-subtle rounded-b-lg border border-t-0 p-6">
|
||||
<RadioGroup.Root
|
||||
value={watchPeriodType}
|
||||
onValueChange={(val) => formMethods.setValue("periodType", val as PeriodType)}>
|
||||
onValueChange={(val) =>
|
||||
formMethods.setValue("periodType", val as PeriodType, { shouldDirty: true })
|
||||
}>
|
||||
{PERIOD_TYPES.map((period) => {
|
||||
if (period.type === "UNLIMITED") return null;
|
||||
return (
|
||||
@@ -388,17 +402,21 @@ export const EventLimitsTab = () => {
|
||||
options={optionsPeriod}
|
||||
isSearchable={false}
|
||||
onChange={(opt) =>
|
||||
formMethods.setValue("periodCountCalendarDays", opt?.value === 1 ? "1" : "0")
|
||||
formMethods.setValue(
|
||||
"periodCountCalendarDays",
|
||||
opt?.value === 1 ? true : false,
|
||||
{ shouldDirty: true }
|
||||
)
|
||||
}
|
||||
name="periodCoundCalendarDays"
|
||||
value={optionsPeriod.find((opt) => {
|
||||
opt.value ===
|
||||
(formMethods.getValues("periodCountCalendarDays") === "1" ? 1 : 0);
|
||||
(formMethods.getValues("periodCountCalendarDays") === true ? 1 : 0);
|
||||
})}
|
||||
defaultValue={optionsPeriod.find(
|
||||
(opt) =>
|
||||
opt.value ===
|
||||
(formMethods.getValues("periodCountCalendarDays") === "1" ? 1 : 0)
|
||||
(formMethods.getValues("periodCountCalendarDays") === true ? 1 : 0)
|
||||
)}
|
||||
/>
|
||||
</div>
|
||||
@@ -407,12 +425,12 @@ export const EventLimitsTab = () => {
|
||||
<div className="me-2 ms-2 inline-flex space-x-2 rtl:space-x-reverse">
|
||||
<Controller
|
||||
name="periodDates"
|
||||
render={() => (
|
||||
render={({ field: { onChange } }) => (
|
||||
<DateRangePicker
|
||||
startDate={formMethods.getValues("periodDates").startDate}
|
||||
endDate={formMethods.getValues("periodDates").endDate}
|
||||
onDatesChange={({ startDate, endDate }) => {
|
||||
formMethods.setValue("periodDates", {
|
||||
onChange({
|
||||
startDate,
|
||||
endDate,
|
||||
});
|
||||
@@ -446,7 +464,7 @@ export const EventLimitsTab = () => {
|
||||
onCheckedChange={(active) => {
|
||||
setOffsetToggle(active);
|
||||
if (!active) {
|
||||
formMethods.setValue("offsetStart", 0);
|
||||
formMethods.setValue("offsetStart", 0, { shouldDirty: true });
|
||||
}
|
||||
}}>
|
||||
<div className="border-subtle rounded-b-lg border border-t-0 p-6">
|
||||
@@ -577,11 +595,16 @@ const IntervalLimitsManager = <K extends "durationLimits" | "bookingLimits">({
|
||||
);
|
||||
if (!rest || !currentKeys.length) return;
|
||||
//currentDurationLimits is always defined so can be casted
|
||||
// @ts-expect-error FIXME Fix these typings
|
||||
setValue(propertyName, {
|
||||
...watchIntervalLimits,
|
||||
[rest.value]: defaultLimit,
|
||||
});
|
||||
|
||||
setValue(
|
||||
propertyName,
|
||||
// @ts-expect-error FIXME Fix these typings
|
||||
{
|
||||
...watchIntervalLimits,
|
||||
[rest.value]: defaultLimit,
|
||||
},
|
||||
{ shouldDirty: true }
|
||||
);
|
||||
};
|
||||
|
||||
return (
|
||||
@@ -611,7 +634,7 @@ const IntervalLimitsManager = <K extends "durationLimits" | "bookingLimits">({
|
||||
)}
|
||||
onLimitChange={(intervalLimitKey, val) =>
|
||||
// @ts-expect-error FIXME Fix these typings
|
||||
setValue(`${propertyName}.${intervalLimitKey}`, val)
|
||||
setValue(`${propertyName}.${intervalLimitKey}`, val, { shouldDirty: true })
|
||||
}
|
||||
onDelete={(intervalLimitKey) => {
|
||||
const current = currentIntervalLimits;
|
||||
|
||||
@@ -83,7 +83,7 @@ const DescriptionEditor = ({ isEditable }: { isEditable: boolean }) => {
|
||||
return mounted ? (
|
||||
<Editor
|
||||
getText={() => md.render(formMethods.getValues("description") || "")}
|
||||
setText={(value: string) => formMethods.setValue("description", turndown(value))}
|
||||
setText={(value: string) => formMethods.setValue("description", turndown(value), { shouldDirty: true })}
|
||||
excludedToolbarItems={["blockType"]}
|
||||
placeholder={t("quick_video_meeting")}
|
||||
editable={isEditable}
|
||||
@@ -318,7 +318,7 @@ export const EventSetupTab = (
|
||||
/>
|
||||
</div>
|
||||
<ErrorMessage
|
||||
errors={formMethods.formState.errors.locations?.[index]}
|
||||
errors={formMethods.formState.errors?.locations?.[index]}
|
||||
name={eventLocationType.defaultValueVariable}
|
||||
className="text-error my-1 ml-6 text-sm"
|
||||
as="div"
|
||||
@@ -512,16 +512,16 @@ export const EventSetupTab = (
|
||||
if (!newOptions.find((opt) => opt.value === defaultDuration?.value)) {
|
||||
if (newOptions.length > 0) {
|
||||
setDefaultDuration(newOptions[0]);
|
||||
formMethods.setValue("length", newOptions[0].value);
|
||||
formMethods.setValue("length", newOptions[0].value, { shouldDirty: true });
|
||||
} else {
|
||||
setDefaultDuration(null);
|
||||
}
|
||||
}
|
||||
if (newOptions.length === 1 && defaultDuration === null) {
|
||||
setDefaultDuration(newOptions[0]);
|
||||
formMethods.setValue("length", newOptions[0].value);
|
||||
formMethods.setValue("length", newOptions[0].value, { shouldDirty: true });
|
||||
}
|
||||
formMethods.setValue("metadata.multipleDuration", values);
|
||||
formMethods.setValue("metadata.multipleDuration", values, { shouldDirty: true });
|
||||
}}
|
||||
/>
|
||||
</div>
|
||||
@@ -542,7 +542,7 @@ export const EventSetupTab = (
|
||||
setDefaultDuration(
|
||||
selectedMultipleDuration.find((opt) => opt.value === option?.value) ?? null
|
||||
);
|
||||
if (option) formMethods.setValue("length", option.value);
|
||||
if (option) formMethods.setValue("length", option.value, { shouldDirty: true });
|
||||
}}
|
||||
/>
|
||||
</div>
|
||||
@@ -571,12 +571,12 @@ export const EventSetupTab = (
|
||||
setMultipleDuration(undefined);
|
||||
setSelectedMultipleDuration([]);
|
||||
setDefaultDuration(null);
|
||||
formMethods.setValue("metadata.multipleDuration", undefined);
|
||||
formMethods.setValue("length", eventType.length);
|
||||
formMethods.setValue("metadata.multipleDuration", undefined, { shouldDirty: true });
|
||||
formMethods.setValue("length", eventType.length, { shouldDirty: true });
|
||||
} else {
|
||||
setMultipleDuration([]);
|
||||
formMethods.setValue("metadata.multipleDuration", []);
|
||||
formMethods.setValue("length", 0);
|
||||
formMethods.setValue("metadata.multipleDuration", [], { shouldDirty: true });
|
||||
formMethods.setValue("length", 0, { shouldDirty: true });
|
||||
}
|
||||
}}
|
||||
/>
|
||||
|
||||
@@ -127,7 +127,7 @@ const AssignAllTeamMembers = ({
|
||||
labelClassName="mt-0.5 font-normal"
|
||||
checked={assignAllTeamMembers}
|
||||
onCheckedChange={(active) => {
|
||||
setValue("assignAllTeamMembers", active);
|
||||
setValue("assignAllTeamMembers", active, { shouldDirty: true });
|
||||
setAssignAllTeamMembers(active);
|
||||
if (active) {
|
||||
onActive();
|
||||
@@ -250,7 +250,8 @@ const FixedHosts = ({
|
||||
isFixed: true,
|
||||
userId: parseInt(teamMember.value, 10),
|
||||
priority: 2,
|
||||
}))
|
||||
})),
|
||||
{ shouldDirty: true }
|
||||
)
|
||||
}
|
||||
/>
|
||||
@@ -269,7 +270,7 @@ const FixedHosts = ({
|
||||
const rrHosts = getValues("hosts")
|
||||
.filter((host) => !host.isFixed)
|
||||
.sort((a, b) => (b.priority ?? 2) - (a.priority ?? 2));
|
||||
setValue("hosts", rrHosts);
|
||||
setValue("hosts", rrHosts, { shouldDirty: true });
|
||||
}
|
||||
setIsDisabled(checked);
|
||||
}}
|
||||
@@ -290,7 +291,8 @@ const FixedHosts = ({
|
||||
isFixed: true,
|
||||
userId: parseInt(teamMember.value, 10),
|
||||
priority: 2,
|
||||
}))
|
||||
})),
|
||||
{ shouldDirty: true }
|
||||
)
|
||||
}
|
||||
/>
|
||||
@@ -332,7 +334,7 @@ const AddMembersWithSwitch = ({
|
||||
assignAllTeamMembers={assignAllTeamMembers}
|
||||
setAssignAllTeamMembers={setAssignAllTeamMembers}
|
||||
onActive={onActive}
|
||||
onInactive={() => setValue("hosts", [])}
|
||||
onInactive={() => setValue("hosts", [], { shouldDirty: true })}
|
||||
/>
|
||||
</div>
|
||||
) : (
|
||||
@@ -395,7 +397,8 @@ const RoundRobinHosts = ({
|
||||
userId: parseInt(teamMember.value, 10),
|
||||
priority: 2,
|
||||
}))
|
||||
.sort((a, b) => b.priority - a.priority)
|
||||
.sort((a, b) => b.priority - a.priority),
|
||||
{ shouldDirty: true }
|
||||
)
|
||||
}
|
||||
/>
|
||||
@@ -420,7 +423,7 @@ const ChildrenEventTypes = ({
|
||||
<AssignAllTeamMembers
|
||||
assignAllTeamMembers={assignAllTeamMembers}
|
||||
setAssignAllTeamMembers={setAssignAllTeamMembers}
|
||||
onActive={() => setValue("children", childrenEventTypeOptions)}
|
||||
onActive={() => setValue("children", childrenEventTypeOptions, { shouldDirty: true })}
|
||||
/>
|
||||
{!assignAllTeamMembers ? (
|
||||
<Controller<FormValues>
|
||||
@@ -449,7 +452,7 @@ const Hosts = ({
|
||||
const { t } = useLocale();
|
||||
const {
|
||||
control,
|
||||
resetField,
|
||||
setValue,
|
||||
getValues,
|
||||
formState: { submitCount },
|
||||
} = useFormContext<FormValues>();
|
||||
@@ -469,10 +472,12 @@ const Hosts = ({
|
||||
initialValue.current = { hosts: getValues("hosts"), schedulingType, submitCount };
|
||||
return;
|
||||
}
|
||||
resetField("hosts", {
|
||||
defaultValue: initialValue.current.schedulingType === schedulingType ? initialValue.current.hosts : [],
|
||||
});
|
||||
}, [schedulingType, resetField, getValues, submitCount]);
|
||||
setValue(
|
||||
"hosts",
|
||||
initialValue.current.schedulingType === schedulingType ? initialValue.current.hosts : [],
|
||||
{ shouldDirty: true }
|
||||
);
|
||||
}, [schedulingType, setValue, getValues, submitCount]);
|
||||
|
||||
return (
|
||||
<Controller<FormValues>
|
||||
@@ -586,7 +591,7 @@ export const EventTeamTab = ({
|
||||
className="w-full"
|
||||
onChange={(val) => {
|
||||
onChange(val?.value);
|
||||
setValue("assignAllTeamMembers", false);
|
||||
setValue("assignAllTeamMembers", false, { shouldDirty: true });
|
||||
setAssignAllTeamMembers(false);
|
||||
}}
|
||||
/>
|
||||
|
||||
@@ -14,6 +14,7 @@ import { useLocale } from "@calcom/lib/hooks/useLocale";
|
||||
import { HttpError } from "@calcom/lib/http-error";
|
||||
import { SchedulingType } from "@calcom/prisma/enums";
|
||||
import { trpc, TRPCClientError } from "@calcom/trpc/react";
|
||||
import type { DialogProps } from "@calcom/ui";
|
||||
import {
|
||||
Button,
|
||||
ButtonGroup,
|
||||
@@ -133,6 +134,60 @@ function getNavigation({
|
||||
];
|
||||
}
|
||||
|
||||
function DeleteDialog({
|
||||
isManagedEvent,
|
||||
eventTypeId,
|
||||
open,
|
||||
onOpenChange,
|
||||
}: { isManagedEvent: string; eventTypeId: number } & Pick<DialogProps, "open" | "onOpenChange">) {
|
||||
const utils = trpc.useContext();
|
||||
const { t } = useLocale();
|
||||
const router = useRouter();
|
||||
const deleteMutation = trpc.viewer.eventTypes.delete.useMutation({
|
||||
onSuccess: async () => {
|
||||
await utils.viewer.eventTypes.invalidate();
|
||||
showToast(t("event_type_deleted_successfully"), "success");
|
||||
router.push("/event-types");
|
||||
onOpenChange?.(false);
|
||||
},
|
||||
onError: (err) => {
|
||||
if (err instanceof HttpError) {
|
||||
const message = `${err.statusCode}: ${err.message}`;
|
||||
showToast(message, "error");
|
||||
onOpenChange?.(false);
|
||||
} else if (err instanceof TRPCClientError) {
|
||||
showToast(err.message, "error");
|
||||
}
|
||||
},
|
||||
});
|
||||
|
||||
return (
|
||||
<Dialog open={open} onOpenChange={onOpenChange}>
|
||||
<ConfirmationDialogContent
|
||||
isPending={deleteMutation.isPending}
|
||||
variety="danger"
|
||||
title={t(`delete${isManagedEvent}_event_type`)}
|
||||
confirmBtnText={t(`confirm_delete_event_type`)}
|
||||
loadingText={t(`confirm_delete_event_type`)}
|
||||
onConfirm={(e) => {
|
||||
e.preventDefault();
|
||||
deleteMutation.mutate({ id: eventTypeId });
|
||||
}}>
|
||||
<p className="mt-5">
|
||||
<Trans
|
||||
i18nKey={`delete${isManagedEvent}_event_type_description`}
|
||||
components={{ li: <li />, ul: <ul className="ml-4 list-disc" /> }}>
|
||||
<ul>
|
||||
<li>Members assigned to this event type will also have their event types deleted.</li>
|
||||
<li>Anyone who they've shared their link with will no longer be able to book using it.</li>
|
||||
</ul>
|
||||
</Trans>
|
||||
</p>
|
||||
</ConfirmationDialogContent>
|
||||
</Dialog>
|
||||
);
|
||||
}
|
||||
|
||||
function EventTypeSingleLayout({
|
||||
children,
|
||||
eventType,
|
||||
@@ -149,9 +204,7 @@ function EventTypeSingleLayout({
|
||||
bookerUrl,
|
||||
activeWebhooksNumber,
|
||||
}: Props) {
|
||||
const utils = trpc.useContext();
|
||||
const { t } = useLocale();
|
||||
const router = useRouter();
|
||||
const [deleteDialogOpen, setDeleteDialogOpen] = useState(false);
|
||||
|
||||
const hasPermsToDelete =
|
||||
@@ -160,24 +213,6 @@ function EventTypeSingleLayout({
|
||||
formMethods.getValues("schedulingType") === SchedulingType.MANAGED ||
|
||||
isUserOrganizationAdmin;
|
||||
|
||||
const deleteMutation = trpc.viewer.eventTypes.delete.useMutation({
|
||||
onSuccess: async () => {
|
||||
await utils.viewer.eventTypes.invalidate();
|
||||
showToast(t("event_type_deleted_successfully"), "success");
|
||||
router.push("/event-types");
|
||||
setDeleteDialogOpen(false);
|
||||
},
|
||||
onError: (err) => {
|
||||
if (err instanceof HttpError) {
|
||||
const message = `${err.statusCode}: ${err.message}`;
|
||||
showToast(message, "error");
|
||||
setDeleteDialogOpen(false);
|
||||
} else if (err instanceof TRPCClientError) {
|
||||
showToast(err.message, "error");
|
||||
}
|
||||
},
|
||||
});
|
||||
|
||||
const { isManagedEventType, isChildrenManagedEventType } = useLockedFieldsManager(
|
||||
formMethods.getValues(),
|
||||
t("locked_fields_admin_description"),
|
||||
@@ -308,7 +343,7 @@ function EventTypeSingleLayout({
|
||||
id="hiddenSwitch"
|
||||
checked={!formMethods.watch("hidden")}
|
||||
onCheckedChange={(e) => {
|
||||
formMethods.setValue("hidden", !e);
|
||||
formMethods.setValue("hidden", !e, { shouldDirty: true });
|
||||
}}
|
||||
/>
|
||||
</div>
|
||||
@@ -424,7 +459,7 @@ function EventTypeSingleLayout({
|
||||
id="hiddenSwitch"
|
||||
checked={!formMethods.watch("hidden")}
|
||||
onCheckedChange={(e) => {
|
||||
formMethods.setValue("hidden", !e);
|
||||
formMethods.setValue("hidden", !e, { shouldDirty: true });
|
||||
}}
|
||||
/>
|
||||
</div>
|
||||
@@ -435,6 +470,7 @@ function EventTypeSingleLayout({
|
||||
className="ml-4 lg:ml-0"
|
||||
type="submit"
|
||||
loading={isUpdateMutationLoading}
|
||||
disabled={!formMethods.formState.isDirty}
|
||||
data-testid="update-eventtype"
|
||||
form="event-type-form">
|
||||
{t("save")}
|
||||
@@ -467,31 +503,13 @@ function EventTypeSingleLayout({
|
||||
</div>
|
||||
</div>
|
||||
</Suspense>
|
||||
<Dialog open={deleteDialogOpen} onOpenChange={setDeleteDialogOpen}>
|
||||
<ConfirmationDialogContent
|
||||
isPending={deleteMutation.isPending}
|
||||
variety="danger"
|
||||
title={t(`delete${isManagedEvent}_event_type`)}
|
||||
confirmBtnText={t(`confirm_delete_event_type`)}
|
||||
loadingText={t(`confirm_delete_event_type`)}
|
||||
onConfirm={(e) => {
|
||||
e.preventDefault();
|
||||
deleteMutation.mutate({ id: formMethods.getValues("id") });
|
||||
}}>
|
||||
<p className="mt-5">
|
||||
<Trans
|
||||
i18nKey={`delete${isManagedEvent}_event_type_description`}
|
||||
components={{ li: <li />, ul: <ul className="ml-4 list-disc" /> }}>
|
||||
<ul>
|
||||
<li>Members assigned to this event type will also have their event types deleted.</li>
|
||||
<li>
|
||||
Anyone who they've shared their link with will no longer be able to book using it.
|
||||
</li>
|
||||
</ul>
|
||||
</Trans>
|
||||
</p>
|
||||
</ConfirmationDialogContent>
|
||||
</Dialog>
|
||||
<DeleteDialog
|
||||
eventTypeId={eventType.id}
|
||||
isManagedEvent={isManagedEvent}
|
||||
open={deleteDialogOpen}
|
||||
onOpenChange={setDeleteDialogOpen}
|
||||
/>
|
||||
|
||||
<EventTypeEmbedDialog />
|
||||
</Shell>
|
||||
);
|
||||
|
||||
@@ -81,10 +81,10 @@ export default function InstantEventController({
|
||||
data-testid="instant-event-check"
|
||||
onCheckedChange={(e) => {
|
||||
if (!e) {
|
||||
formMethods.setValue("isInstantEvent", false);
|
||||
formMethods.setValue("isInstantEvent", false, { shouldDirty: true });
|
||||
setInstantEventState(false);
|
||||
} else {
|
||||
formMethods.setValue("isInstantEvent", true);
|
||||
formMethods.setValue("isInstantEvent", true, { shouldDirty: true });
|
||||
setInstantEventState(true);
|
||||
}
|
||||
}}>
|
||||
|
||||
@@ -66,7 +66,7 @@ export default function RecurringEventController({
|
||||
data-testid="recurring-event-check"
|
||||
onCheckedChange={(e) => {
|
||||
if (!e) {
|
||||
formMethods.setValue("recurringEvent", null);
|
||||
formMethods.setValue("recurringEvent", null, { shouldDirty: true });
|
||||
setRecurringEventState(null);
|
||||
} else {
|
||||
const newVal = eventType.recurringEvent || {
|
||||
@@ -74,7 +74,7 @@ export default function RecurringEventController({
|
||||
count: 12,
|
||||
freq: Frequency.WEEKLY,
|
||||
};
|
||||
formMethods.setValue("recurringEvent", newVal);
|
||||
formMethods.setValue("recurringEvent", newVal, { shouldDirty: true });
|
||||
setRecurringEventState(newVal);
|
||||
}
|
||||
}}>
|
||||
@@ -95,7 +95,7 @@ export default function RecurringEventController({
|
||||
...recurringEventState,
|
||||
interval: parseInt(event?.target.value),
|
||||
};
|
||||
formMethods.setValue("recurringEvent", newVal);
|
||||
formMethods.setValue("recurringEvent", newVal, { shouldDirty: true });
|
||||
setRecurringEventState(newVal);
|
||||
}}
|
||||
/>
|
||||
@@ -110,7 +110,7 @@ export default function RecurringEventController({
|
||||
...recurringEventState,
|
||||
freq: parseInt(event?.value || `${Frequency.WEEKLY}`),
|
||||
};
|
||||
formMethods.setValue("recurringEvent", newVal);
|
||||
formMethods.setValue("recurringEvent", newVal, { shouldDirty: true });
|
||||
setRecurringEventState(newVal);
|
||||
}}
|
||||
/>
|
||||
@@ -129,7 +129,7 @@ export default function RecurringEventController({
|
||||
...recurringEventState,
|
||||
count: parseInt(event?.target.value),
|
||||
};
|
||||
formMethods.setValue("recurringEvent", newVal);
|
||||
formMethods.setValue("recurringEvent", newVal, { shouldDirty: true });
|
||||
setRecurringEventState(newVal);
|
||||
}}
|
||||
/>
|
||||
|
||||
@@ -37,7 +37,7 @@ export default function RequiresConfirmationController({
|
||||
|
||||
useEffect(() => {
|
||||
if (!requiresConfirmation) {
|
||||
formMethods.setValue("metadata.requiresConfirmationThreshold", undefined);
|
||||
formMethods.setValue("metadata.requiresConfirmationThreshold", undefined, { shouldDirty: true });
|
||||
}
|
||||
// eslint-disable-next-line react-hooks/exhaustive-deps
|
||||
}, [requiresConfirmation]);
|
||||
@@ -82,7 +82,7 @@ export default function RequiresConfirmationController({
|
||||
checked={requiresConfirmation}
|
||||
LockedIcon={requiresConfirmationLockedProps.LockedIcon}
|
||||
onCheckedChange={(val) => {
|
||||
formMethods.setValue("requiresConfirmation", val);
|
||||
formMethods.setValue("requiresConfirmation", val, { shouldDirty: true });
|
||||
onRequiresConfirmation(val);
|
||||
}}>
|
||||
<div className="border-subtle rounded-b-lg border border-t-0 p-6">
|
||||
@@ -96,16 +96,19 @@ export default function RequiresConfirmationController({
|
||||
}
|
||||
onValueChange={(val) => {
|
||||
if (val === "always") {
|
||||
formMethods.setValue("requiresConfirmation", true);
|
||||
formMethods.setValue("requiresConfirmation", true, { shouldDirty: true });
|
||||
onRequiresConfirmation(true);
|
||||
formMethods.setValue("metadata.requiresConfirmationThreshold", undefined);
|
||||
formMethods.setValue("metadata.requiresConfirmationThreshold", undefined, {
|
||||
shouldDirty: true,
|
||||
});
|
||||
setRequiresConfirmationSetup(undefined);
|
||||
} else if (val === "notice") {
|
||||
formMethods.setValue("requiresConfirmation", true);
|
||||
formMethods.setValue("requiresConfirmation", true, { shouldDirty: true });
|
||||
onRequiresConfirmation(true);
|
||||
formMethods.setValue(
|
||||
"metadata.requiresConfirmationThreshold",
|
||||
requiresConfirmationSetup || defaultRequiresConfirmationSetup
|
||||
requiresConfirmationSetup || defaultRequiresConfirmationSetup,
|
||||
{ shouldDirty: true }
|
||||
);
|
||||
}
|
||||
}}>
|
||||
@@ -146,7 +149,8 @@ export default function RequiresConfirmationController({
|
||||
});
|
||||
formMethods.setValue(
|
||||
"metadata.requiresConfirmationThreshold.time",
|
||||
val
|
||||
val,
|
||||
{ shouldDirty: true }
|
||||
);
|
||||
}}
|
||||
className="border-default !m-0 block w-16 rounded-r-none border-r-0 text-sm [appearance:textfield] focus:z-10 focus:border-r"
|
||||
@@ -171,7 +175,8 @@ export default function RequiresConfirmationController({
|
||||
});
|
||||
formMethods.setValue(
|
||||
"metadata.requiresConfirmationThreshold.unit",
|
||||
opt?.value as UnitTypeLongPlural
|
||||
opt?.value as UnitTypeLongPlural,
|
||||
{ shouldDirty: true }
|
||||
);
|
||||
}}
|
||||
defaultValue={defaultValue}
|
||||
|
||||
@@ -37,7 +37,7 @@ const UserProfile = () => {
|
||||
if (context.avatar) {
|
||||
showToast(t("your_user_profile_updated_successfully"), "success");
|
||||
await utils.viewer.me.refetch();
|
||||
} else {
|
||||
} else
|
||||
try {
|
||||
if (eventTypes?.length === 0) {
|
||||
await Promise.all(
|
||||
@@ -50,9 +50,11 @@ const UserProfile = () => {
|
||||
console.error(error);
|
||||
}
|
||||
|
||||
await utils.viewer.me.refetch();
|
||||
router.push("/");
|
||||
}
|
||||
await utils.viewer.me.refetch();
|
||||
const redirectUrl = localStorage.getItem("onBoardingRedirect");
|
||||
localStorage.removeItem("onBoardingRedirect");
|
||||
|
||||
redirectUrl ? router.push(redirectUrl) : router.push("/");
|
||||
},
|
||||
onError: () => {
|
||||
showToast(t("problem_saving_user_profile"), "error");
|
||||
|
||||
@@ -113,7 +113,7 @@ const enum ThemeSupport {
|
||||
// e.g. Login Page
|
||||
None = "none",
|
||||
// Entire App except Booking Pages
|
||||
App = "systemOnly",
|
||||
App = "userConfigured",
|
||||
// Booking Pages(including Routing Forms)
|
||||
Booking = "userConfigured",
|
||||
}
|
||||
|
||||
@@ -127,9 +127,9 @@ const enum ThemeSupport {
|
||||
// e.g. Login Page
|
||||
None = "none",
|
||||
// Entire App except Booking Pages
|
||||
App = "systemOnly",
|
||||
App = "appConfigured",
|
||||
// Booking Pages(including Routing Forms)
|
||||
Booking = "userConfigured",
|
||||
Booking = "bookingConfigured",
|
||||
}
|
||||
|
||||
type CalcomThemeProps = PropsWithChildren<
|
||||
@@ -144,8 +144,10 @@ const CalcomThemeProvider = (props: CalcomThemeProps) => {
|
||||
const embedNamespace = getEmbedNamespace(props.router.query);
|
||||
const isEmbedMode = typeof embedNamespace === "string";
|
||||
|
||||
const themeProviderProps = getThemeProviderProps({ props, isEmbedMode, embedNamespace });
|
||||
|
||||
return (
|
||||
<ThemeProvider {...getThemeProviderProps({ props, isEmbedMode, embedNamespace })}>
|
||||
<ThemeProvider {...themeProviderProps}>
|
||||
{/* Embed Mode can be detected reliably only on client side here as there can be static generated pages as well which can't determine if it's embed mode at backend */}
|
||||
{/* color-scheme makes background:transparent not work in iframe which is required by embed. */}
|
||||
{typeof window !== "undefined" && !isEmbedMode && (
|
||||
|
||||
@@ -1,11 +1,11 @@
|
||||
import { getFeatureFlagMap } from "@calcom/features/flags/server/utils";
|
||||
import { getFeatureFlag } from "@calcom/features/flags/server/utils";
|
||||
|
||||
// If feature flag is disabled, return not found on getServerSideProps
|
||||
export const getServerSideProps = async () => {
|
||||
const prisma = await import("@calcom/prisma").then((mod) => mod.default);
|
||||
const flags = await getFeatureFlagMap(prisma);
|
||||
const insightsEnabled = await getFeatureFlag(prisma, "insights");
|
||||
|
||||
if (flags.insights === false) {
|
||||
if (!insightsEnabled) {
|
||||
return {
|
||||
notFound: true,
|
||||
} as const;
|
||||
|
||||
@@ -1,12 +1,12 @@
|
||||
import type { GetServerSidePropsContext } from "next";
|
||||
|
||||
import { getFeatureFlagMap } from "@calcom/features/flags/server/utils";
|
||||
import { getFeatureFlag } from "@calcom/features/flags/server/utils";
|
||||
|
||||
export const getServerSideProps = async (context: GetServerSidePropsContext) => {
|
||||
const prisma = await import("@calcom/prisma").then((mod) => mod.default);
|
||||
const flags = await getFeatureFlagMap(prisma);
|
||||
const organizations = await getFeatureFlag(prisma, "organizations");
|
||||
// Check if organizations are enabled
|
||||
if (flags["organizations"] !== true) {
|
||||
if (!organizations) {
|
||||
return {
|
||||
notFound: true,
|
||||
} as const;
|
||||
|
||||
@@ -4,7 +4,7 @@ import { z } from "zod";
|
||||
import { getOrgUsernameFromEmail } from "@calcom/features/auth/signup/utils/getOrgUsernameFromEmail";
|
||||
import { checkPremiumUsername } from "@calcom/features/ee/common/lib/checkPremiumUsername";
|
||||
import { isSAMLLoginEnabled } from "@calcom/features/ee/sso/lib/saml";
|
||||
import { getFeatureFlagMap } from "@calcom/features/flags/server/utils";
|
||||
import { getFeatureFlag } from "@calcom/features/flags/server/utils";
|
||||
import { IS_SELF_HOSTED, WEBAPP_URL } from "@calcom/lib/constants";
|
||||
import slugify from "@calcom/lib/slugify";
|
||||
import { teamMetadataSchema } from "@calcom/prisma/zod-utils";
|
||||
@@ -24,11 +24,22 @@ const querySchema = z.object({
|
||||
|
||||
export const getServerSideProps = async (ctx: GetServerSidePropsContext) => {
|
||||
const prisma = await import("@calcom/prisma").then((mod) => mod.default);
|
||||
const flags = await getFeatureFlagMap(prisma);
|
||||
const signupDisabled = await getFeatureFlag(prisma, "disable-signup");
|
||||
const ssr = await ssrInit(ctx);
|
||||
const token = z.string().optional().parse(ctx.query.token);
|
||||
const redirectUrlData = z
|
||||
.string()
|
||||
.refine((value) => value.startsWith(WEBAPP_URL), {
|
||||
params: (value: string) => ({ value }),
|
||||
message: "Redirect URL must start with 'cal.com'",
|
||||
})
|
||||
.optional()
|
||||
.safeParse(ctx.query.redirect);
|
||||
|
||||
const redirectUrl = redirectUrlData.success && redirectUrlData.data ? redirectUrlData.data : null;
|
||||
|
||||
const props = {
|
||||
redirectUrl,
|
||||
isGoogleLoginEnabled: IS_GOOGLE_LOGIN_ENABLED,
|
||||
isSAMLLoginEnabled,
|
||||
trpcState: ssr.dehydrate(),
|
||||
@@ -38,7 +49,7 @@ export const getServerSideProps = async (ctx: GetServerSidePropsContext) => {
|
||||
// username + email prepopulated from query params
|
||||
const { username: preFillusername, email: prefilEmail } = querySchema.parse(ctx.query);
|
||||
|
||||
if ((process.env.NEXT_PUBLIC_DISABLE_SIGNUP === "true" && !token) || flags["disable-signup"]) {
|
||||
if ((process.env.NEXT_PUBLIC_DISABLE_SIGNUP === "true" && !token) || signupDisabled) {
|
||||
return {
|
||||
notFound: true,
|
||||
} as const;
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
import type { GetServerSidePropsContext } from "next";
|
||||
|
||||
import { orgDomainConfig } from "@calcom/features/ee/organizations/lib/orgDomains";
|
||||
import { getFeatureFlagMap } from "@calcom/features/flags/server/utils";
|
||||
import { getFeatureFlag } from "@calcom/features/flags/server/utils";
|
||||
import { getBookerBaseUrlSync } from "@calcom/lib/getBookerUrl/client";
|
||||
import logger from "@calcom/lib/logger";
|
||||
import { markdownToSafeHTML } from "@calcom/lib/markdownToSafeHTML";
|
||||
@@ -37,12 +37,11 @@ export const getServerSideProps = async (context: GetServerSidePropsContext) =>
|
||||
|
||||
// Provided by Rewrite from next.config.js
|
||||
const isOrgProfile = context.query?.isOrgProfile === "1";
|
||||
const flags = await getFeatureFlagMap(prisma);
|
||||
const isOrganizationFeatureEnabled = flags["organizations"];
|
||||
const organizationsEnabled = await getFeatureFlag(prisma, "organizations");
|
||||
|
||||
log.debug("getServerSideProps", {
|
||||
isOrgProfile,
|
||||
isOrganizationFeatureEnabled,
|
||||
isOrganizationFeatureEnabled: organizationsEnabled,
|
||||
isValidOrgDomain,
|
||||
currentOrgDomain,
|
||||
});
|
||||
@@ -74,7 +73,7 @@ export const getServerSideProps = async (context: GetServerSidePropsContext) =>
|
||||
if (
|
||||
(!isValidOrgDomain && team?.parent) ||
|
||||
(!isValidOrgDomain && !!metadata?.isOrganization) ||
|
||||
!isOrganizationFeatureEnabled
|
||||
!organizationsEnabled
|
||||
) {
|
||||
return { notFound: true } as const;
|
||||
}
|
||||
|
||||
@@ -19,7 +19,6 @@ import { getEventName } from "@calcom/core/event";
|
||||
import type { ConfigType } from "@calcom/dayjs";
|
||||
import dayjs from "@calcom/dayjs";
|
||||
import {
|
||||
sdkActionManager,
|
||||
useEmbedNonStylesConfig,
|
||||
useIsBackgroundTransparent,
|
||||
useIsEmbed,
|
||||
@@ -193,22 +192,6 @@ export default function Success(props: PageProps) {
|
||||
}, [telemetry]); */
|
||||
|
||||
useEffect(() => {
|
||||
const users = eventType.users;
|
||||
if (!sdkActionManager) return;
|
||||
// TODO: We should probably make it consistent with Webhook payload. Some data is not available here, as and when requirement comes we can add
|
||||
sdkActionManager.fire("bookingSuccessful", {
|
||||
booking: bookingInfo,
|
||||
eventType,
|
||||
date: date.toString(),
|
||||
duration: calculatedDuration,
|
||||
organizer: {
|
||||
name: users[0].name || "Nameless",
|
||||
email: bookingInfo?.userPrimaryEmail || users[0].email || "Email-less",
|
||||
timeZone: users[0].timeZone,
|
||||
},
|
||||
confirmed: !needsConfirmation,
|
||||
// TODO: Add payment details
|
||||
});
|
||||
setDate(
|
||||
date.tz(localStorage.getItem("timeOption.preferredTimeZone") || dayjs.tz.guess() || "Europe/London")
|
||||
);
|
||||
@@ -294,6 +277,16 @@ export default function Success(props: PageProps) {
|
||||
const providerName = guessEventLocationType(location)?.label;
|
||||
const rescheduleProviderName = guessEventLocationType(rescheduleLocation)?.label;
|
||||
|
||||
const bookingCancelledEventProps = {
|
||||
booking: bookingInfo,
|
||||
organizer: {
|
||||
name: bookingInfo?.user?.name || "Nameless",
|
||||
email: bookingInfo?.userPrimaryEmail || bookingInfo?.user?.email || "Email-less",
|
||||
timeZone: bookingInfo?.user?.timeZone,
|
||||
},
|
||||
eventType,
|
||||
};
|
||||
|
||||
return (
|
||||
<div className={isEmbed ? "" : "h-screen"} data-testid="success-page">
|
||||
{!isEmbed && (
|
||||
@@ -610,6 +603,7 @@ export default function Success(props: PageProps) {
|
||||
theme={isSuccessBookingPage ? props.profile.theme : "light"}
|
||||
allRemainingBookings={allRemainingBookings}
|
||||
seatReferenceUid={seatReferenceUid}
|
||||
bookingCancelledEventProps={bookingCancelledEventProps}
|
||||
/>
|
||||
</>
|
||||
))}
|
||||
|
||||
@@ -425,7 +425,7 @@ export const EventTypeList = ({
|
||||
<MemoizedItem type={type} group={group} readOnly={readOnly} />
|
||||
<div className="mt-4 hidden sm:mt-0 sm:flex">
|
||||
<div className="flex justify-between space-x-2 rtl:space-x-reverse">
|
||||
{type.team && !isManagedEventType && (
|
||||
{!!type.teamId && !isManagedEventType && (
|
||||
<UserAvatarGroup
|
||||
className="relative right-3"
|
||||
size="sm"
|
||||
|
||||
@@ -119,7 +119,7 @@ export type FormValues = {
|
||||
schedule: number | null;
|
||||
periodType: PeriodType;
|
||||
periodDays: number;
|
||||
periodCountCalendarDays: "1" | "0";
|
||||
periodCountCalendarDays: boolean;
|
||||
periodDates: { startDate: Date; endDate: Date };
|
||||
seatsPerTimeSlot: number | null;
|
||||
seatsShowAttendees: boolean | null;
|
||||
@@ -192,14 +192,17 @@ const EventTypePage = (props: EventTypeSetupProps) => {
|
||||
const [animationParentRef] = useAutoAnimate<HTMLDivElement>();
|
||||
const updateMutation = trpc.viewer.eventTypes.update.useMutation({
|
||||
onSuccess: async () => {
|
||||
formMethods.setValue(
|
||||
"children",
|
||||
formMethods.getValues().children.map((child) => ({
|
||||
...child,
|
||||
created: true,
|
||||
}))
|
||||
);
|
||||
formMethods.setValue("assignAllTeamMembers", formMethods.getValues("assignAllTeamMembers") || false);
|
||||
const currentValues = formMethods.getValues();
|
||||
|
||||
currentValues.children = currentValues.children.map((child) => ({
|
||||
...child,
|
||||
created: true,
|
||||
}));
|
||||
currentValues.assignAllTeamMembers = currentValues.assignAllTeamMembers || false;
|
||||
|
||||
// Reset the form with these values as new default values to ensure the correct comparison for dirtyFields eval
|
||||
formMethods.reset(currentValues);
|
||||
|
||||
showToast(t("event_type_updated_successfully", { eventTypeTitle: eventType.title }), "success");
|
||||
},
|
||||
async onSettled() {
|
||||
@@ -253,7 +256,6 @@ const EventTypePage = (props: EventTypeSetupProps) => {
|
||||
eventType.bookingFields.forEach(({ name }) => {
|
||||
bookingFields[name] = name;
|
||||
});
|
||||
|
||||
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
||||
const defaultValues: any = useMemo(() => {
|
||||
return {
|
||||
@@ -264,7 +266,7 @@ const EventTypePage = (props: EventTypeSetupProps) => {
|
||||
beforeEventBuffer: eventType.beforeEventBuffer,
|
||||
eventName: eventType.eventName || "",
|
||||
scheduleName: eventType.scheduleName,
|
||||
periodDays: eventType.periodDays || 30,
|
||||
periodDays: eventType.periodDays,
|
||||
requiresBookerEmailVerification: eventType.requiresBookerEmailVerification,
|
||||
seatsPerTimeSlot: eventType.seatsPerTimeSlot,
|
||||
seatsShowAttendees: eventType.seatsShowAttendees,
|
||||
@@ -290,7 +292,7 @@ const EventTypePage = (props: EventTypeSetupProps) => {
|
||||
offsetStart: eventType.offsetStart,
|
||||
bookingFields: eventType.bookingFields,
|
||||
periodType: eventType.periodType,
|
||||
periodCountCalendarDays: eventType.periodCountCalendarDays ? "1" : "0",
|
||||
periodCountCalendarDays: eventType.periodCountCalendarDays ? true : false,
|
||||
schedulingType: eventType.schedulingType,
|
||||
requiresConfirmation: eventType.requiresConfirmation,
|
||||
slotInterval: eventType.slotInterval,
|
||||
@@ -316,7 +318,6 @@ const EventTypePage = (props: EventTypeSetupProps) => {
|
||||
assignAllTeamMembers: eventType.assignAllTeamMembers,
|
||||
};
|
||||
}, [eventType, periodDates, metadata]);
|
||||
|
||||
const formMethods = useForm<FormValues>({
|
||||
defaultValues,
|
||||
resolver: zodResolver(
|
||||
@@ -405,14 +406,9 @@ const EventTypePage = (props: EventTypeSetupProps) => {
|
||||
.passthrough()
|
||||
),
|
||||
});
|
||||
|
||||
useEffect(() => {
|
||||
if (!formMethods.formState.isDirty) {
|
||||
//TODO: What's the best way to sync the form with backend
|
||||
formMethods.setValue("bookingFields", defaultValues.bookingFields);
|
||||
}
|
||||
// eslint-disable-next-line react-hooks/exhaustive-deps
|
||||
}, [defaultValues]);
|
||||
const {
|
||||
formState: { isDirty: isFormDirty, dirtyFields },
|
||||
} = formMethods;
|
||||
|
||||
const appsMetadata = formMethods.getValues("metadata")?.apps;
|
||||
const availability = formMethods.watch("availability");
|
||||
@@ -453,8 +449,100 @@ const EventTypePage = (props: EventTypeSetupProps) => {
|
||||
),
|
||||
webhooks: <EventWebhooksTab eventType={eventType} />,
|
||||
} as const;
|
||||
const isObject = <T,>(value: T): boolean => {
|
||||
return value !== null && typeof value === "object" && !Array.isArray(value);
|
||||
};
|
||||
|
||||
const isArray = <T,>(value: T): boolean => {
|
||||
return Array.isArray(value);
|
||||
};
|
||||
|
||||
const isFieldDirty = (fieldName: keyof FormValues) => {
|
||||
// If the field itself is directly marked as dirty
|
||||
if (dirtyFields[fieldName] === true) {
|
||||
return true;
|
||||
}
|
||||
|
||||
// Check if the field is an object or an array
|
||||
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
||||
const fieldValue: any = getNestedField(dirtyFields, fieldName);
|
||||
if (isObject(fieldValue)) {
|
||||
for (const key in fieldValue) {
|
||||
if (fieldValue[key] === true) {
|
||||
return true;
|
||||
}
|
||||
|
||||
if (isObject(fieldValue[key]) || isArray(fieldValue[key])) {
|
||||
const nestedFieldName = `${fieldName}.${key}` as keyof FormValues;
|
||||
// Recursive call for nested objects or arrays
|
||||
if (isFieldDirty(nestedFieldName)) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
if (isArray(fieldValue)) {
|
||||
for (const element of fieldValue) {
|
||||
// If element is an object, check each property of the object
|
||||
if (isObject(element)) {
|
||||
for (const key in element) {
|
||||
if (element[key] === true) {
|
||||
return true;
|
||||
}
|
||||
|
||||
if (isObject(element[key]) || isArray(element[key])) {
|
||||
const nestedFieldName = `${fieldName}.${key}` as keyof FormValues;
|
||||
// Recursive call for nested objects or arrays within each element
|
||||
if (isFieldDirty(nestedFieldName)) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
}
|
||||
} else if (element === true) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return false;
|
||||
};
|
||||
|
||||
const getNestedField = (obj: typeof dirtyFields, path: string) => {
|
||||
const keys = path.split(".");
|
||||
let current = obj;
|
||||
|
||||
for (let i = 0; i < keys.length; i++) {
|
||||
// @ts-expect-error /—— currentKey could be any deeply nested fields thanks to recursion
|
||||
const currentKey = current[keys[i]];
|
||||
if (currentKey === undefined) return undefined;
|
||||
current = currentKey;
|
||||
}
|
||||
|
||||
return current;
|
||||
};
|
||||
|
||||
const getDirtyFields = (values: FormValues): Partial<FormValues> => {
|
||||
if (!isFormDirty) {
|
||||
return {};
|
||||
}
|
||||
const updatedFields: Partial<FormValues> = {};
|
||||
Object.keys(dirtyFields).forEach((key) => {
|
||||
const typedKey = key as keyof typeof dirtyFields;
|
||||
updatedFields[typedKey] = undefined;
|
||||
const isDirty = isFieldDirty(typedKey);
|
||||
if (isDirty) {
|
||||
// eslint-disable-next-line @typescript-eslint/ban-ts-comment
|
||||
// @ts-ignore
|
||||
updatedFields[typedKey] = values[typedKey];
|
||||
}
|
||||
});
|
||||
return updatedFields;
|
||||
};
|
||||
|
||||
const handleSubmit = async (values: FormValues) => {
|
||||
const { children } = values;
|
||||
const dirtyValues = getDirtyFields(values);
|
||||
const dirtyFieldExists = Object.keys(dirtyValues).length !== 0;
|
||||
const {
|
||||
periodDates,
|
||||
periodCountCalendarDays,
|
||||
@@ -470,7 +558,6 @@ const EventTypePage = (props: EventTypeSetupProps) => {
|
||||
locations,
|
||||
metadata,
|
||||
customInputs,
|
||||
children,
|
||||
assignAllTeamMembers,
|
||||
// We don't need to send send these values to the backend
|
||||
// eslint-disable-next-line @typescript-eslint/no-unused-vars
|
||||
@@ -483,8 +570,7 @@ const EventTypePage = (props: EventTypeSetupProps) => {
|
||||
multipleDurationEnabled,
|
||||
length,
|
||||
...input
|
||||
} = values;
|
||||
|
||||
} = dirtyValues;
|
||||
if (!Number(length)) throw new Error(t("event_setup_length_error"));
|
||||
|
||||
if (bookingLimits) {
|
||||
@@ -504,8 +590,12 @@ const EventTypePage = (props: EventTypeSetupProps) => {
|
||||
if (metadata?.multipleDuration.length < 1) {
|
||||
throw new Error(t("event_setup_multiple_duration_error"));
|
||||
} else {
|
||||
if (!length && !metadata?.multipleDuration?.includes(length)) {
|
||||
throw new Error(t("event_setup_multiple_duration_default_error"));
|
||||
// if length is unchanged, we skip this check
|
||||
if (length !== undefined) {
|
||||
if (!length && !metadata?.multipleDuration?.includes(length)) {
|
||||
//This would work but it leaves the potential of this check being useless. Need to check against length and not eventType.length, but length can be undefined
|
||||
throw new Error(t("event_setup_multiple_duration_default_error"));
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -521,14 +611,14 @@ const EventTypePage = (props: EventTypeSetupProps) => {
|
||||
|
||||
// eslint-disable-next-line @typescript-eslint/no-unused-vars
|
||||
const { availability, users, scheduleName, ...rest } = input;
|
||||
updateMutation.mutate({
|
||||
const payload = {
|
||||
...rest,
|
||||
length,
|
||||
locations,
|
||||
recurringEvent,
|
||||
periodStartDate: periodDates.startDate,
|
||||
periodEndDate: periodDates.endDate,
|
||||
periodCountCalendarDays: periodCountCalendarDays === "1",
|
||||
periodStartDate: periodDates?.startDate,
|
||||
periodEndDate: periodDates?.endDate,
|
||||
periodCountCalendarDays,
|
||||
id: eventType.id,
|
||||
beforeEventBuffer,
|
||||
afterEventBuffer,
|
||||
@@ -542,7 +632,19 @@ const EventTypePage = (props: EventTypeSetupProps) => {
|
||||
customInputs,
|
||||
children,
|
||||
assignAllTeamMembers,
|
||||
});
|
||||
};
|
||||
// Filter out undefined values
|
||||
const filteredPayload = Object.entries(payload).reduce((acc, [key, value]) => {
|
||||
if (value !== undefined) {
|
||||
// @ts-expect-error Element implicitly has any type
|
||||
acc[key] = value;
|
||||
}
|
||||
return acc;
|
||||
}, {});
|
||||
|
||||
if (dirtyFieldExists) {
|
||||
updateMutation.mutate({ ...filteredPayload, id: eventType.id });
|
||||
}
|
||||
};
|
||||
|
||||
const [slugExistsChildrenDialogOpen, setSlugExistsChildrenDialogOpen] = useState<ChildrenEventType[]>([]);
|
||||
@@ -595,6 +697,9 @@ const EventTypePage = (props: EventTypeSetupProps) => {
|
||||
form={formMethods}
|
||||
id="event-type-form"
|
||||
handleSubmit={async (values) => {
|
||||
const { children } = values;
|
||||
const dirtyValues = getDirtyFields(values);
|
||||
const dirtyFieldExists = Object.keys(dirtyValues).length !== 0;
|
||||
const {
|
||||
periodDates,
|
||||
periodCountCalendarDays,
|
||||
@@ -617,9 +722,9 @@ const EventTypePage = (props: EventTypeSetupProps) => {
|
||||
multipleDurationEnabled,
|
||||
length,
|
||||
...input
|
||||
} = values;
|
||||
} = dirtyValues;
|
||||
|
||||
if (!Number(length)) throw new Error(t("event_setup_length_error"));
|
||||
if (length && !Number(length)) throw new Error(t("event_setup_length_error"));
|
||||
|
||||
if (bookingLimits) {
|
||||
const isValid = validateIntervalLimitOrder(bookingLimits);
|
||||
@@ -638,8 +743,11 @@ const EventTypePage = (props: EventTypeSetupProps) => {
|
||||
if (metadata?.multipleDuration.length < 1) {
|
||||
throw new Error(t("event_setup_multiple_duration_error"));
|
||||
} else {
|
||||
if (!length && !metadata?.multipleDuration?.includes(length)) {
|
||||
throw new Error(t("event_setup_multiple_duration_default_error"));
|
||||
if (length !== undefined) {
|
||||
if (!length && !metadata?.multipleDuration?.includes(length)) {
|
||||
//This would work but it leaves the potential of this check being useless. Need to check against length and not eventType.length, but length can be undefined
|
||||
throw new Error(t("event_setup_multiple_duration_default_error"));
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -651,14 +759,15 @@ const EventTypePage = (props: EventTypeSetupProps) => {
|
||||
|
||||
// eslint-disable-next-line @typescript-eslint/no-unused-vars
|
||||
const { availability, users, scheduleName, ...rest } = input;
|
||||
updateMutation.mutate({
|
||||
const payload = {
|
||||
...rest,
|
||||
children,
|
||||
length,
|
||||
locations,
|
||||
recurringEvent,
|
||||
periodStartDate: periodDates.startDate,
|
||||
periodEndDate: periodDates.endDate,
|
||||
periodCountCalendarDays: periodCountCalendarDays === "1",
|
||||
periodStartDate: periodDates?.startDate,
|
||||
periodEndDate: periodDates?.endDate,
|
||||
periodCountCalendarDays,
|
||||
id: eventType.id,
|
||||
beforeEventBuffer,
|
||||
afterEventBuffer,
|
||||
@@ -670,7 +779,19 @@ const EventTypePage = (props: EventTypeSetupProps) => {
|
||||
seatsShowAvailabilityCount,
|
||||
metadata,
|
||||
customInputs,
|
||||
});
|
||||
};
|
||||
// Filter out undefined values
|
||||
const filteredPayload = Object.entries(payload).reduce((acc, [key, value]) => {
|
||||
if (value !== undefined) {
|
||||
// @ts-expect-error Element implicitly has any type
|
||||
acc[key] = value;
|
||||
}
|
||||
return acc;
|
||||
}, {});
|
||||
|
||||
if (dirtyFieldExists) {
|
||||
updateMutation.mutate({ ...filteredPayload, id: eventType.id });
|
||||
}
|
||||
}}>
|
||||
<div ref={animationParentRef}>{tabMap[tabName]}</div>
|
||||
</Form>
|
||||
|
||||
@@ -7,6 +7,7 @@ import { handleUserRedirection } from "@calcom/features/booking-redirect/handle-
|
||||
import { orgDomainConfig } from "@calcom/features/ee/organizations/lib/orgDomains";
|
||||
import { DEFAULT_DARK_BRAND_COLOR, DEFAULT_LIGHT_BRAND_COLOR } from "@calcom/lib/constants";
|
||||
import { getUsernameList } from "@calcom/lib/defaultEvents";
|
||||
import { getUserAvatarUrl } from "@calcom/lib/getAvatarUrl";
|
||||
import logger from "@calcom/lib/logger";
|
||||
import { markdownToSafeHTML } from "@calcom/lib/markdownToSafeHTML";
|
||||
import { safeStringify } from "@calcom/lib/safeStringify";
|
||||
@@ -206,7 +207,10 @@ export const getServerSideProps: GetServerSideProps<UserPageProps> = async (cont
|
||||
|
||||
const profile = {
|
||||
name: user.name || user.username || "",
|
||||
image: user.avatar,
|
||||
image: getUserAvatarUrl({
|
||||
...user,
|
||||
profile: user.profile,
|
||||
}),
|
||||
theme: user.theme,
|
||||
brandColor: user.brandColor ?? DEFAULT_LIGHT_BRAND_COLOR,
|
||||
avatarUrl: user.avatarUrl,
|
||||
|
||||
@@ -155,7 +155,7 @@ export function UserPage(props: InferGetServerSidePropsType<typeof getServerSide
|
||||
<Link
|
||||
prefetch={false}
|
||||
href={{
|
||||
pathname: `/${user.username}/${type.slug}`,
|
||||
pathname: `/${user.profile.username}/${type.slug}`,
|
||||
query,
|
||||
}}
|
||||
passHref
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "@calcom/web",
|
||||
"version": "3.7.18",
|
||||
"version": "3.8.4",
|
||||
"private": true,
|
||||
"scripts": {
|
||||
"analyze": "ANALYZE=true next build",
|
||||
|
||||
@@ -38,7 +38,7 @@ export default async function handler(req: NextApiRequest, res: NextApiResponse)
|
||||
const oldPassword = req.body.oldPassword;
|
||||
const newPassword = req.body.newPassword;
|
||||
|
||||
const currentPassword = user.password;
|
||||
const currentPassword = user.password?.hash;
|
||||
if (!currentPassword) {
|
||||
return res.status(400).json({ error: ErrorCode.UserMissingPassword });
|
||||
}
|
||||
@@ -53,12 +53,16 @@ export default async function handler(req: NextApiRequest, res: NextApiResponse)
|
||||
}
|
||||
|
||||
const hashedPassword = await hashPassword(newPassword);
|
||||
await prisma.user.update({
|
||||
await prisma.userPassword.upsert({
|
||||
where: {
|
||||
id: user.id,
|
||||
userId: user.id,
|
||||
},
|
||||
data: {
|
||||
password: hashedPassword,
|
||||
create: {
|
||||
hash: hashedPassword,
|
||||
userId: user.id,
|
||||
},
|
||||
update: {
|
||||
hash: hashedPassword,
|
||||
},
|
||||
});
|
||||
|
||||
|
||||
@@ -41,7 +41,12 @@ export default async function handler(req: NextApiRequest, res: NextApiResponse)
|
||||
email: maybeRequest.email,
|
||||
},
|
||||
data: {
|
||||
password: hashedPassword,
|
||||
password: {
|
||||
upsert: {
|
||||
create: { hash: hashedPassword },
|
||||
update: { hash: hashedPassword },
|
||||
},
|
||||
},
|
||||
emailVerified: new Date(),
|
||||
},
|
||||
});
|
||||
|
||||
@@ -41,7 +41,7 @@ async function handler(req: NextApiRequest) {
|
||||
data: {
|
||||
username,
|
||||
email: userEmail,
|
||||
password: hashedPassword,
|
||||
password: { create: { hash: hashedPassword } },
|
||||
role: "ADMIN",
|
||||
name: parsedQuery.data.full_name,
|
||||
emailVerified: new Date(),
|
||||
|
||||
@@ -23,13 +23,13 @@ export default async function handler(req: NextApiRequest, res: NextApiResponse)
|
||||
return res.status(500).json({ error: ErrorCode.InternalServerError });
|
||||
}
|
||||
|
||||
const user = await prisma.user.findUnique({ where: { id: session.user.id } });
|
||||
const user = await prisma.user.findUnique({ where: { id: session.user.id }, include: { password: true } });
|
||||
if (!user) {
|
||||
console.error(`Session references user that no longer exists.`);
|
||||
return res.status(401).json({ message: "Not authenticated" });
|
||||
}
|
||||
|
||||
if (!user.password && user.identityProvider === IdentityProvider.CAL) {
|
||||
if (!user.password?.hash && user.identityProvider === IdentityProvider.CAL) {
|
||||
return res.status(400).json({ error: ErrorCode.UserMissingPassword });
|
||||
}
|
||||
|
||||
@@ -37,8 +37,8 @@ export default async function handler(req: NextApiRequest, res: NextApiResponse)
|
||||
return res.json({ message: "Two factor disabled" });
|
||||
}
|
||||
|
||||
if (user.password && user.identityProvider === IdentityProvider.CAL) {
|
||||
const isCorrectPassword = await verifyPassword(req.body.password, user.password);
|
||||
if (user.password?.hash && user.identityProvider === IdentityProvider.CAL) {
|
||||
const isCorrectPassword = await verifyPassword(req.body.password, user.password.hash);
|
||||
if (!isCorrectPassword) {
|
||||
return res.status(400).json({ error: ErrorCode.IncorrectPassword });
|
||||
}
|
||||
|
||||
@@ -25,7 +25,7 @@ export default async function handler(req: NextApiRequest, res: NextApiResponse)
|
||||
return res.status(500).json({ error: ErrorCode.InternalServerError });
|
||||
}
|
||||
|
||||
const user = await prisma.user.findUnique({ where: { id: session.user.id } });
|
||||
const user = await prisma.user.findUnique({ where: { id: session.user.id }, include: { password: true } });
|
||||
if (!user) {
|
||||
console.error(`Session references user that no longer exists.`);
|
||||
return res.status(401).json({ message: "Not authenticated" });
|
||||
@@ -35,7 +35,7 @@ export default async function handler(req: NextApiRequest, res: NextApiResponse)
|
||||
return res.status(400).json({ error: ErrorCode.ThirdPartyIdentityProviderEnabled });
|
||||
}
|
||||
|
||||
if (!user.password) {
|
||||
if (!user.password?.hash) {
|
||||
return res.status(400).json({ error: ErrorCode.UserMissingPassword });
|
||||
}
|
||||
|
||||
@@ -48,7 +48,7 @@ export default async function handler(req: NextApiRequest, res: NextApiResponse)
|
||||
return res.status(500).json({ error: ErrorCode.InternalServerError });
|
||||
}
|
||||
|
||||
const isCorrectPassword = await verifyPassword(req.body.password, user.password);
|
||||
const isCorrectPassword = await verifyPassword(req.body.password, user.password.hash);
|
||||
if (!isCorrectPassword) {
|
||||
return res.status(400).json({ error: ErrorCode.IncorrectPassword });
|
||||
}
|
||||
|
||||
@@ -47,7 +47,7 @@ export default function Apps({ apps }: PageProps) {
|
||||
<div className="mb-16">
|
||||
<div className="grid-col-1 grid grid-cols-1 gap-3 md:grid-cols-3">
|
||||
{apps
|
||||
.sort((a, b) => (b.installCount || 0) - (a.installCount || 0))
|
||||
?.sort((a, b) => (b.installCount || 0) - (a.installCount || 0))
|
||||
.map((app) => {
|
||||
return <AppCard key={app.slug} app={app} />;
|
||||
})}
|
||||
|
||||
@@ -95,6 +95,7 @@ const IntegrationsContainer = ({
|
||||
className="mb-6"
|
||||
actions={
|
||||
<Button
|
||||
data-testid="add-apps"
|
||||
href={variant ? `/apps/categories/${variant}` : "/apps"}
|
||||
color="secondary"
|
||||
StartIcon={Plus}>
|
||||
|
||||
@@ -11,8 +11,10 @@ import { getLayout } from "@calcom/features/settings/layouts/SettingsLayout";
|
||||
import { APP_NAME } from "@calcom/lib/constants";
|
||||
import { DEFAULT_LIGHT_BRAND_COLOR, DEFAULT_DARK_BRAND_COLOR } from "@calcom/lib/constants";
|
||||
import { checkWCAGContrastColor } from "@calcom/lib/getBrandColours";
|
||||
import useGetBrandingColours from "@calcom/lib/getBrandColours";
|
||||
import { useHasPaidPlan } from "@calcom/lib/hooks/useHasPaidPlan";
|
||||
import { useLocale } from "@calcom/lib/hooks/useLocale";
|
||||
import useTheme from "@calcom/lib/hooks/useTheme";
|
||||
import { validateBookerLayouts } from "@calcom/lib/validateBookerLayouts";
|
||||
import type { userMetadata } from "@calcom/prisma/zod-utils";
|
||||
import { trpc } from "@calcom/trpc/react";
|
||||
@@ -29,6 +31,7 @@ import {
|
||||
SkeletonText,
|
||||
SettingsToggle,
|
||||
UpgradeTeamsBadge,
|
||||
useCalcomTheme,
|
||||
} from "@calcom/ui";
|
||||
|
||||
import PageWrapper from "@components/PageWrapper";
|
||||
@@ -41,10 +44,10 @@ const SkeletonLoader = ({ title, description }: { title: string; description: st
|
||||
<SkeletonText className="h-8 w-1/3" />
|
||||
</div>
|
||||
<div className="border-subtle space-y-6 border-x px-4 py-6 sm:px-6">
|
||||
<div className="flex items-center justify-center">
|
||||
<SkeletonButton className="mr-6 h-32 w-48 rounded-md p-5" />
|
||||
<SkeletonButton className="mr-6 h-32 w-48 rounded-md p-5" />
|
||||
<SkeletonButton className="mr-6 h-32 w-48 rounded-md p-5" />
|
||||
<div className="[&>*]:bg-emphasis flex w-full items-center justify-center gap-x-2 [&>*]:animate-pulse">
|
||||
<div className="h-32 flex-1 rounded-md p-5" />
|
||||
<div className="h-32 flex-1 rounded-md p-5" />
|
||||
<div className="h-32 flex-1 rounded-md p-5" />
|
||||
</div>
|
||||
<div className="flex justify-between">
|
||||
<SkeletonText className="h-8 w-1/3" />
|
||||
@@ -62,6 +65,26 @@ const SkeletonLoader = ({ title, description }: { title: string; description: st
|
||||
);
|
||||
};
|
||||
|
||||
const useBrandColors = (
|
||||
currentTheme: string | null,
|
||||
{
|
||||
brandColor,
|
||||
darkBrandColor,
|
||||
}: {
|
||||
brandColor?: string | null;
|
||||
darkBrandColor?: string | null;
|
||||
}
|
||||
): void => {
|
||||
const brandTheme = useGetBrandingColours({
|
||||
lightVal: brandColor,
|
||||
darkVal: darkBrandColor,
|
||||
});
|
||||
const selectedTheme = currentTheme ? brandTheme[currentTheme as "light" | "dark"] : {};
|
||||
useCalcomTheme({
|
||||
root: selectedTheme,
|
||||
});
|
||||
};
|
||||
|
||||
const AppearanceView = ({
|
||||
user,
|
||||
hasPaidPlan,
|
||||
@@ -77,6 +100,22 @@ const AppearanceView = ({
|
||||
user?.brandColor !== DEFAULT_LIGHT_BRAND_COLOR || user?.darkBrandColor !== DEFAULT_DARK_BRAND_COLOR
|
||||
);
|
||||
const [hideBrandingValue, setHideBrandingValue] = useState(user?.hideBranding ?? false);
|
||||
useTheme(user?.appTheme);
|
||||
useBrandColors(user?.appTheme ?? null, {
|
||||
brandColor: user?.brandColor,
|
||||
darkBrandColor: user?.darkBrandColor,
|
||||
});
|
||||
|
||||
const userAppThemeFormMethods = useForm({
|
||||
defaultValues: {
|
||||
appTheme: user.appTheme,
|
||||
},
|
||||
});
|
||||
|
||||
const {
|
||||
formState: { isSubmitting: isUserAppThemeSubmitting, isDirty: isUserAppThemeDirty },
|
||||
reset: resetUserAppThemeReset,
|
||||
} = userAppThemeFormMethods;
|
||||
|
||||
const userThemeFormMethods = useForm({
|
||||
defaultValues: {
|
||||
@@ -131,6 +170,7 @@ const AppearanceView = ({
|
||||
resetBrandColorsThemeReset({ brandColor: data.brandColor, darkBrandColor: data.darkBrandColor });
|
||||
resetBookerLayoutThemeReset({ metadata: data.metadata });
|
||||
resetUserThemeReset({ theme: data.theme });
|
||||
resetUserAppThemeReset({ appTheme: data.appTheme });
|
||||
},
|
||||
onError: (error) => {
|
||||
if (error.message) {
|
||||
@@ -147,6 +187,56 @@ const AppearanceView = ({
|
||||
return (
|
||||
<div>
|
||||
<Meta title={t("appearance")} description={t("appearance_description")} borderInShellHeader={false} />
|
||||
<div className="border-subtle mt-6 flex items-center rounded-t-lg border p-6 text-sm">
|
||||
<div>
|
||||
<p className="text-default text-base font-semibold">{t("app_theme")}</p>
|
||||
<p className="text-default">{t("app_theme_applies_note")}</p>
|
||||
</div>
|
||||
</div>
|
||||
<Form
|
||||
form={userAppThemeFormMethods}
|
||||
handleSubmit={(values) => {
|
||||
mutation.mutate({
|
||||
appTheme: values.appTheme ?? null,
|
||||
});
|
||||
}}>
|
||||
<div className="border-subtle flex flex-col justify-between border-x px-6 py-8 sm:flex-row">
|
||||
<ThemeLabel
|
||||
variant="system"
|
||||
value={undefined}
|
||||
label={t("theme_system")}
|
||||
defaultChecked={user.appTheme === null}
|
||||
register={userAppThemeFormMethods.register}
|
||||
fieldName="appTheme"
|
||||
/>
|
||||
<ThemeLabel
|
||||
variant="light"
|
||||
value="light"
|
||||
label={t("light")}
|
||||
defaultChecked={user.appTheme === "light"}
|
||||
register={userAppThemeFormMethods.register}
|
||||
fieldName="appTheme"
|
||||
/>
|
||||
<ThemeLabel
|
||||
variant="dark"
|
||||
value="dark"
|
||||
label={t("dark")}
|
||||
defaultChecked={user.appTheme === "dark"}
|
||||
register={userAppThemeFormMethods.register}
|
||||
fieldName="appTheme"
|
||||
/>
|
||||
</div>
|
||||
<SectionBottomActions className="mb-6" align="end">
|
||||
<Button
|
||||
disabled={isUserAppThemeSubmitting || !isUserAppThemeDirty}
|
||||
type="submit"
|
||||
data-testid="update-app-theme-btn"
|
||||
color="primary">
|
||||
{t("update")}
|
||||
</Button>
|
||||
</SectionBottomActions>
|
||||
</Form>
|
||||
|
||||
<div className="border-subtle mt-6 flex items-center rounded-t-lg border p-6 text-sm">
|
||||
<div>
|
||||
<p className="text-default text-base font-semibold">{t("theme")}</p>
|
||||
|
||||
@@ -159,6 +159,7 @@ export default function Signup({
|
||||
isGoogleLoginEnabled,
|
||||
isSAMLLoginEnabled,
|
||||
orgAutoAcceptEmail,
|
||||
redirectUrl,
|
||||
}: SignupProps) {
|
||||
const [premiumUsername, setPremiumUsername] = useState(false);
|
||||
const [usernameTaken, setUsernameTaken] = useState(false);
|
||||
@@ -180,6 +181,12 @@ export default function Signup({
|
||||
formState: { isSubmitting, errors, isSubmitSuccessful },
|
||||
} = formMethods;
|
||||
|
||||
useEffect(() => {
|
||||
if (redirectUrl) {
|
||||
localStorage.setItem("onBoardingRedirect", redirectUrl);
|
||||
}
|
||||
}, [redirectUrl]);
|
||||
|
||||
const loadingSubmitState = isSubmitSuccessful || isSubmitting;
|
||||
|
||||
const handleErrorsAndStripe = async (resp: Response) => {
|
||||
|
||||
@@ -13,7 +13,7 @@ import { useEffect } from "react";
|
||||
|
||||
import { sdkActionManager, useIsEmbed } from "@calcom/embed-core/embed-iframe";
|
||||
import EventTypeDescription from "@calcom/features/eventtypes/components/EventTypeDescription";
|
||||
import { WEBAPP_URL } from "@calcom/lib/constants";
|
||||
import { getOrgAvatarUrl, getTeamAvatarUrl } from "@calcom/lib/getAvatarUrl";
|
||||
import { useLocale } from "@calcom/lib/hooks/useLocale";
|
||||
import { useRouterQuery } from "@calcom/lib/hooks/useRouterQuery";
|
||||
import useTheme from "@calcom/lib/hooks/useTheme";
|
||||
@@ -123,7 +123,7 @@ function TeamPage({
|
||||
(mem) => mem.subteams?.includes(ch.slug) && mem.accepted
|
||||
).length;
|
||||
return (
|
||||
<li key={i} className="hover:bg-muted w-full">
|
||||
<li key={i} className="hover:bg-muted w-full rounded-md">
|
||||
<Link href={`/${ch.slug}`} className="flex items-center justify-between">
|
||||
<div className="flex items-center px-5 py-5">
|
||||
<div className="ms-3 inline-block truncate">
|
||||
@@ -157,6 +157,11 @@ function TeamPage({
|
||||
</div>
|
||||
);
|
||||
|
||||
const profileImageSrc =
|
||||
isValidOrgDomain || team.metadata?.isOrganization
|
||||
? getOrgAvatarUrl({ slug: currentOrgDomain, logoUrl: team.logoUrl })
|
||||
: getTeamAvatarUrl({ slug: team.slug, logoUrl: team.logoUrl, organizationId: team.parent?.id });
|
||||
|
||||
return (
|
||||
<>
|
||||
<HeadSeo
|
||||
@@ -166,22 +171,14 @@ function TeamPage({
|
||||
title: markdownStrippedBio,
|
||||
profile: {
|
||||
name: `${team.name}`,
|
||||
image: `${WEBAPP_URL}/${team.metadata?.isOrganization ? "org" : "team"}/${team.slug}/avatar.png`,
|
||||
image: profileImageSrc,
|
||||
},
|
||||
}}
|
||||
/>
|
||||
<main className="dark:bg-darkgray-50 bg-subtle mx-auto max-w-3xl rounded-md px-4 pb-12 pt-12">
|
||||
<div className="mx-auto mb-8 max-w-3xl text-center">
|
||||
<div className="relative">
|
||||
<Avatar
|
||||
alt={teamName}
|
||||
imageSrc={
|
||||
isValidOrgDomain
|
||||
? `/org/${currentOrgDomain}/avatar.png`
|
||||
: `${WEBAPP_URL}/${team.metadata?.isOrganization ? "org" : "team"}/${team.slug}/avatar.png`
|
||||
}
|
||||
size="lg"
|
||||
/>
|
||||
<Avatar alt={teamName} imageSrc={profileImageSrc} size="lg" />
|
||||
</div>
|
||||
<p className="font-cal text-emphasis mb-2 text-2xl tracking-wider" data-testid="team-name">
|
||||
{team.parent && `${team.parent.name} `}
|
||||
|
||||
@@ -0,0 +1,35 @@
|
||||
import { loginUser } from "../../fixtures/regularBookings";
|
||||
import { test } from "../../lib/fixtures";
|
||||
|
||||
const ALL_APPS = ["fathom", "matomo", "plausible", "ga4", "gtm", "metapixel"];
|
||||
|
||||
test.describe("Check analytics Apps", () => {
|
||||
test.beforeEach(async ({ page, users }) => {
|
||||
await loginUser(users);
|
||||
await page.goto("/apps/");
|
||||
});
|
||||
|
||||
test("Check analytics Apps", async ({ appsPage, page }) => {
|
||||
await appsPage.goToAppsCategory("analytics");
|
||||
await appsPage.installApp("fathom");
|
||||
await appsPage.goBackToAppsPage();
|
||||
await appsPage.installApp("matomo");
|
||||
await appsPage.goBackToAppsPage();
|
||||
await appsPage.installApp("plausible");
|
||||
await appsPage.goBackToAppsPage();
|
||||
await appsPage.installApp("ga4");
|
||||
await appsPage.goBackToAppsPage();
|
||||
await appsPage.installApp("gtm");
|
||||
await appsPage.goBackToAppsPage();
|
||||
await appsPage.installApp("metapixel");
|
||||
await appsPage.goBackToAppsPage();
|
||||
await page.goto("/event-types");
|
||||
await appsPage.goToEventType("30 min");
|
||||
await appsPage.goToAppsTab();
|
||||
await appsPage.verifyAppsInfo(0);
|
||||
for (const app of ALL_APPS) {
|
||||
await appsPage.activeApp(app);
|
||||
}
|
||||
await appsPage.verifyAppsInfo(6);
|
||||
});
|
||||
});
|
||||
@@ -86,7 +86,8 @@ testBothFutureAndLegacyRoutes.describe("Forgot password", async () => {
|
||||
});
|
||||
|
||||
// eslint-disable-next-line @typescript-eslint/no-non-null-assertion
|
||||
expect(await verifyPassword(newPassword, updatedUser.password!)).toBeTruthy();
|
||||
const updatedPassword = updatedUser.password!.hash;
|
||||
expect(await verifyPassword(newPassword, updatedPassword)).toBeTruthy();
|
||||
|
||||
// finally, make sure the same URL cannot be used to reset the password again, as it should be expired.
|
||||
await page.goto(`/auth/forgot-password/${id}`);
|
||||
|
||||
@@ -175,6 +175,35 @@ testBothFutureAndLegacyRoutes.describe("pro user", () => {
|
||||
await expect(page.locator('[data-testid="empty-screen"]')).toBeVisible();
|
||||
});
|
||||
|
||||
test("can book an unconfirmed event multiple times", async ({ page, users }) => {
|
||||
await page.locator('[data-testid="event-type-link"]:has-text("Opt in")').click();
|
||||
await selectFirstAvailableTimeSlotNextMonth(page);
|
||||
|
||||
const pageUrl = page.url();
|
||||
|
||||
await bookTimeSlot(page);
|
||||
await expect(page.locator("[data-testid=success-page]")).toBeVisible();
|
||||
|
||||
// go back to the booking page to re-book.
|
||||
await page.goto(pageUrl);
|
||||
await bookTimeSlot(page, { email: "test2@example.com" });
|
||||
await expect(page.locator("[data-testid=success-page]")).toBeVisible();
|
||||
});
|
||||
|
||||
test("cannot book an unconfirmed event multiple times with the same email", async ({ page, users }) => {
|
||||
await page.locator('[data-testid="event-type-link"]:has-text("Opt in")').click();
|
||||
await selectFirstAvailableTimeSlotNextMonth(page);
|
||||
|
||||
const pageUrl = page.url();
|
||||
|
||||
await bookTimeSlot(page);
|
||||
// go back to the booking page to re-book.
|
||||
await page.goto(pageUrl);
|
||||
|
||||
await bookTimeSlot(page);
|
||||
await expect(page.getByText("Could not book the meeting.")).toBeVisible();
|
||||
});
|
||||
|
||||
test("can book with multiple guests", async ({ page, users }) => {
|
||||
const additionalGuests = ["test@gmail.com", "test2@gmail.com"];
|
||||
|
||||
|
||||
@@ -0,0 +1,29 @@
|
||||
import { expect, type Page } from "@playwright/test";
|
||||
|
||||
export function createAppsFixture(page: Page) {
|
||||
return {
|
||||
goToAppsCategory: async (category: string) => {
|
||||
await page.getByTestId(`app-store-category-${category}`).nth(1).click();
|
||||
await page.goto("apps/categories/analytics");
|
||||
},
|
||||
installApp: async (app: string) => {
|
||||
await page.getByTestId(`app-store-app-card-${app}`).click();
|
||||
await page.getByTestId("install-app-button").click();
|
||||
},
|
||||
goBackToAppsPage: async () => {
|
||||
await page.getByTestId("add-apps").click();
|
||||
},
|
||||
goToEventType: async (eventType: string) => {
|
||||
await page.getByRole("link", { name: eventType }).click();
|
||||
},
|
||||
goToAppsTab: async () => {
|
||||
await page.getByTestId("vertical-tab-apps").click();
|
||||
},
|
||||
activeApp: async (app: string) => {
|
||||
await page.getByTestId(`${app}-app-switch`).click();
|
||||
},
|
||||
verifyAppsInfo: async (activeApps: number) => {
|
||||
await expect(page.locator(`text=6 apps, ${activeApps} active`)).toBeVisible();
|
||||
},
|
||||
};
|
||||
}
|
||||
@@ -652,7 +652,6 @@ type SupportedTestWorkflows = PrismaType.WorkflowCreateInput;
|
||||
|
||||
type CustomUserOptsKeys =
|
||||
| "username"
|
||||
| "password"
|
||||
| "completedOnboarding"
|
||||
| "locale"
|
||||
| "name"
|
||||
@@ -669,6 +668,7 @@ type CustomUserOpts = Partial<Pick<Prisma.User, CustomUserOptsKeys>> & {
|
||||
useExactUsername?: boolean;
|
||||
roleInOrganization?: MembershipRole;
|
||||
schedule?: Schedule;
|
||||
password?: string | null;
|
||||
};
|
||||
|
||||
// creates the actual user in the db.
|
||||
@@ -690,7 +690,11 @@ const createUser = (
|
||||
username: uname,
|
||||
name: opts?.name,
|
||||
email: opts?.email ?? `${uname}@example.com`,
|
||||
password: hashPassword(uname),
|
||||
password: {
|
||||
create: {
|
||||
hash: hashPassword(uname),
|
||||
},
|
||||
},
|
||||
emailVerified: new Date(),
|
||||
completedOnboarding: opts?.completedOnboarding ?? true,
|
||||
timeZone: opts?.timeZone ?? TimeZoneEnum.UK,
|
||||
@@ -791,7 +795,7 @@ async function confirmPendingPayment(page: Page) {
|
||||
|
||||
// login using a replay of an E2E routine.
|
||||
export async function login(
|
||||
user: Pick<Prisma.User, "username"> & Partial<Pick<Prisma.User, "password" | "email">>,
|
||||
user: Pick<Prisma.User, "username"> & Partial<Pick<Prisma.User, "email">> & { password?: string | null },
|
||||
page: Page
|
||||
) {
|
||||
// get locators
|
||||
@@ -812,7 +816,7 @@ export async function login(
|
||||
}
|
||||
|
||||
export async function apiLogin(
|
||||
user: Pick<Prisma.User, "username"> & Partial<Pick<Prisma.User, "password" | "email">>,
|
||||
user: Pick<Prisma.User, "username"> & Partial<Pick<Prisma.User, "email">> & { password: string | null },
|
||||
page: Page
|
||||
) {
|
||||
const csrfToken = await page
|
||||
|
||||
@@ -4,6 +4,7 @@ import { test as base } from "@playwright/test";
|
||||
import prisma from "@calcom/prisma";
|
||||
|
||||
import type { ExpectedUrlDetails } from "../../../../playwright.config";
|
||||
import { createAppsFixture } from "../fixtures/apps";
|
||||
import { createBookingsFixture } from "../fixtures/bookings";
|
||||
import { createEmailsFixture } from "../fixtures/emails";
|
||||
import { createEmbedsFixture } from "../fixtures/embeds";
|
||||
@@ -32,6 +33,7 @@ export interface Fixtures {
|
||||
workflowPage: ReturnType<typeof createWorkflowPageFixture>;
|
||||
features: ReturnType<typeof createFeatureFixture>;
|
||||
eventTypePage: ReturnType<typeof createEventTypeFixture>;
|
||||
appsPage: ReturnType<typeof createAppsFixture>;
|
||||
}
|
||||
|
||||
declare global {
|
||||
@@ -104,4 +106,8 @@ export const test = base.extend<Fixtures>({
|
||||
const eventTypePage = createEventTypeFixture(page);
|
||||
await use(eventTypePage);
|
||||
},
|
||||
appsPage: async ({ page }, use) => {
|
||||
const appsPage = createAppsFixture(page);
|
||||
await use(appsPage);
|
||||
},
|
||||
});
|
||||
|
||||
@@ -143,8 +143,12 @@ test.describe("Update Profile", () => {
|
||||
const successLocator = await page.waitForSelector('[data-testId="toast-success"]');
|
||||
|
||||
expect(await successLocator.textContent()).toContain(email);
|
||||
|
||||
// After email verification is successfull. user is sent to /event-types
|
||||
await page.waitForURL("/event-types");
|
||||
|
||||
await page.goto("/settings/my-account/profile");
|
||||
const emailInputUpdated = page.getByTestId("profile-form-email");
|
||||
const emailInputUpdated = await page.getByTestId("profile-form-email");
|
||||
expect(await emailInputUpdated.inputValue()).toEqual(email);
|
||||
});
|
||||
test("Can update a users email (verification disabled)", async ({ page, users, prisma, features }) => {
|
||||
|
||||
@@ -13,10 +13,17 @@
|
||||
"reset_password_subject": "{{appName}}: Anleitung zum Zurücksetzen des Passworts",
|
||||
"verify_email_subject": "{{appName}}: Bestätigen Sie Ihr Konto",
|
||||
"check_your_email": "Schauen Sie in Ihrem E-Mail-Postfach nach",
|
||||
"old_email_address": "Alte E-Mail-Adresse",
|
||||
"new_email_address": "Neue E-Mail-Adresse",
|
||||
"verify_email_page_body": "Wir haben eine E-Mail an {{email}} gesendet. Es ist wichtig, Ihre E-Mail-Adresse zu bestätigen, um die Zustellbarkeit für E-Mails und Kalenderbenachrichtigungen von {{appName}} zu gewährleisten.",
|
||||
"verify_email_banner_body": "Bestätigen Sie Ihre E-Mail-Adresse, um die Zustellbarkeit von E-Mails und Kalenderbenachrichtigungen zu gewährleisten",
|
||||
"verify_email_email_header": "Bestätigen Sie Ihre E-Mail-Adresse",
|
||||
"verify_email_email_button": "E-Mail bestätigen",
|
||||
"verify_email_change_description": "Sie haben vor kurzem angefordert, die E-Mail-Adresse zu ändern, die Sie verwenden, um sich bei Ihrem {{appName}} Konto anzumelden. Bitte klicken Sie auf die Schaltfläche unten, um Ihre neue E-Mail-Adresse zu bestätigen.",
|
||||
"verify_email_change_success_toast": "Ihre E-Mail wurde auf {{email}} aktualisiert",
|
||||
"verify_email_change_failure_toast": "Fehler beim Aktualisieren der E-Mail.",
|
||||
"change_of_email": "Bestätigen Sie Ihre neue {{appName}} E-Mail-Adresse",
|
||||
"change_of_email_toast": "Wir haben einen Bestätigungslink an {{email}} gesendet. Wir werden Ihre E-Mail-Adresse aktualisieren, sobald Sie auf diesen Link klicken.",
|
||||
"copy_somewhere_safe": "Speichern Sie diesen API-Schlüssel an einem sicheren Ort. Sie können ihn nicht erneut ansehen.",
|
||||
"verify_email_email_body": "Bitte bestätigen Sie Ihre E-Mail-Adresse, indem Sie unten auf die Schaltfläche klicken.",
|
||||
"verify_email_by_code_email_body": "Bitte bestätigen Sie Ihre E-Mail-Adresse mit dem unten stehenden Code.",
|
||||
@@ -56,6 +63,17 @@
|
||||
"a_refund_failed": "Erstattung fehlgeschlagen",
|
||||
"awaiting_payment_subject": "Warte auf Zahlung: {{title}} am {{date}}",
|
||||
"meeting_awaiting_payment": "Zahlung für den Termin noch offen",
|
||||
"dark_theme_contrast_error": "Die Farbe des dunklen Designs hat die Kontrastprüfung nicht bestanden. Wir empfehlen Ihnen, diese Farbe zu ändern, damit Ihre Schaltflächen sichtbarer sind.",
|
||||
"light_theme_contrast_error": "Die Farbe des hellen Designs hat die Kontrastprüfung nicht bestanden. Wir empfehlen Ihnen, diese Farbe zu ändern, damit Ihre Schaltflächen sichtbarer sind.",
|
||||
"payment_not_created_error": "Zahlung konnte nicht erstellt werden",
|
||||
"couldnt_charge_card_error": "Karte konnte nicht für die Zahlung belastet werden",
|
||||
"no_available_users_found_error": "Keine verfügbaren Benutzer gefunden. Können Sie es mit einem anderen Zeitfenster versuchen?",
|
||||
"request_body_end_time_internal_error": "Interner Fehler. Request-Body enthält keinen Endzeitpunkt",
|
||||
"create_calendar_event_error": "Kalenderereignis konnte nicht im Kalender des Organisators erstellt werden",
|
||||
"update_calendar_event_error": "Kalenderereignis konnte nicht aktualisiert werden.",
|
||||
"delete_calendar_event_error": "Kalenderereignis konnte nicht gelöscht werden.",
|
||||
"already_signed_up_for_this_booking_error": "Sie sind bereits für diese Buchung registriert.",
|
||||
"hosts_unavailable_for_booking": "Einige der Veranstalter sind nicht für die Buchung verfügbar.",
|
||||
"help": "Hilfe",
|
||||
"price": "Preis",
|
||||
"paid": "Bezahlt",
|
||||
@@ -101,6 +119,7 @@
|
||||
"requested_to_reschedule_subject_attendee": "Aktion erforderlich - Neuplanung: Bitte buchen Sie eine neue Zeit für „{{eventType}}“ mit {{name}}",
|
||||
"hi_user_name": "Hallo, {{name}}",
|
||||
"ics_event_title": "{{eventType}} mit {{name}}",
|
||||
"please_book_a_time_sometime_later": "Ups, wir konnten Sie dieses Mal nicht verbinden. Bitte planen Sie stattdessen einen zukünftigen Anruf",
|
||||
"new_event_subject": "Neuer Termin: {{attendeeName}} - {{date}} - {{eventType}}",
|
||||
"join_by_entrypoint": "{{entryPoint}} beitreten",
|
||||
"notes": "Notizen",
|
||||
@@ -117,15 +136,20 @@
|
||||
"meeting_id": "Termin-ID",
|
||||
"meeting_password": "Termin-Passwort",
|
||||
"meeting_url": "Termin-URL",
|
||||
"meeting_url_not_found": "Meeting-URL nicht gefunden",
|
||||
"token_not_found": "Token nicht gefunden",
|
||||
"some_other_host_already_accepted_the_meeting": "Ein anderer Veranstalter hat das Meeting bereits akzeptiert. Möchten Sie trotzdem beitreten? <1>Mit dem Meeting fortfahren</1>",
|
||||
"meeting_request_rejected": "Ihre Terminanfrage wurde abgelehnt",
|
||||
"rejected_event_type_with_organizer": "Abgelehnt: {{eventType}} mit {{organizer}} auf {{date}}",
|
||||
"hi": "Hi",
|
||||
"join_team": "Team beitreten",
|
||||
"manage_this_team": "Dieses Team verwalten",
|
||||
"team_info": "Informationen zum Team",
|
||||
"join_meeting": "Meeting beitreten",
|
||||
"request_another_invitation_email": "Falls Sie {{toEmail}} lieber nicht als Ihre {{appName}}-E-Mail verwenden möchten oder bereits über ein {{appName}}-Konto verfügen, fordern Sie bitte eine weitere Einladung zu dieser E-Mail an.",
|
||||
"you_have_been_invited": "Sie wurden eingeladen, dem Team {{teamName}} beizutreten",
|
||||
"user_invited_you": "{{user}} hat Sie eingeladen, dem {{entity}} {{team}} auf {{appName}} beizutreten",
|
||||
"user_invited_you_to_subteam": "{{user}} hat Sie eingeladen, dem Team {{team}} der Organisation {{parentTeamName}} auf {{appName}} beizutreten",
|
||||
"hidden_team_member_title": "Sie sind in diesem Team ausgeblendet",
|
||||
"hidden_team_member_message": "Ihre Teammitgliedschaft ist nicht bezahlt, entweder upgraden Sie Ihren Account auf Pro oder lassen den Team-Organisator wissen, dass er für Ihren Platz im Team bezahlen kann.",
|
||||
"hidden_team_owner_message": "Sie benötigen einen Pro-Account um die Teamfunktionen zu verweden. Sie werden dem Team nicht angezeigt, bis der Account upgegradet wurde.",
|
||||
@@ -229,6 +253,7 @@
|
||||
"reset_your_password": "Legen Sie Ihr neues Passwort mit den Anweisungen fest, die an Ihre E-Mail-Adresse gesendet wurden.",
|
||||
"email_change": "Melden Sie sich mit Ihrer neuen E-Mail-Adresse und Ihrem Passwort wieder an.",
|
||||
"create_your_account": "Erstellen Sie Ihr Konto",
|
||||
"create_your_calcom_account": "Erstellen Sie Ihr Cal.com-Konto",
|
||||
"sign_up": "Registrieren",
|
||||
"youve_been_logged_out": "Sie wurden abgemeldet",
|
||||
"hope_to_see_you_soon": "Wir hoffen, Sie bald wieder zu sehen!",
|
||||
@@ -266,6 +291,9 @@
|
||||
"nearly_there_instructions": "Zu guter Letzt: Ein Foto und eine Kurzbeschreibung zu Ihnen helfen, öfter gebucht zu werden, da Kunden dann genauer wissen, bei wem sie einen Termin buchen.",
|
||||
"set_availability_instructions": "Definieren Sie Zeiträume, wenn Sie wiederkehrend verfügbar sind. Sie können später weitere Zeiträume erstellen und verschiedenen Kalendern zuordnen.",
|
||||
"set_availability": "Verfügbarkeit festlegen",
|
||||
"set_availbility_description": "Legen Sie Zeitpläne für die Zeiten fest, in denen Sie gebucht werden möchten.",
|
||||
"share_a_link_or_embed": "Link oder Einbettung teilen",
|
||||
"share_a_link_or_embed_description": "Teilen Sie Ihren {{appName}} Link oder betten Sie ihn auf Ihrer Website ein.",
|
||||
"availability_settings": "Verfügbarkeitseinstellungen",
|
||||
"continue_without_calendar": "Ohne Kalender fortfahren",
|
||||
"continue_with": "Mit {{appName}} fortfahren",
|
||||
@@ -281,6 +309,7 @@
|
||||
"welcome_to_calcom": "Willkommen bei {{appName}}",
|
||||
"welcome_instructions": "Wie ist Ihr Name und in welcher Zeitzone sind Sie? Dies kann später bearbeitet werden.",
|
||||
"connect_caldav": "Mit CalDav-Server verbinden",
|
||||
"connect_ics_feed": "Mit einem ICS-Feed verbinden",
|
||||
"connect": "Verbinden",
|
||||
"try_for_free": "Kostenlos testen",
|
||||
"create_booking_link_with_calcom": "Erstellen Sie Ihren eigenen Buchungslink mit {{appName}}",
|
||||
@@ -419,6 +448,7 @@
|
||||
"browse_api_documentation": "Durchsuchen Sie unsere API Dokumentation",
|
||||
"leverage_our_api": "Nutzen Sie unsere API für die volle Kontrolle und Anpassbarkeit.",
|
||||
"create_webhook": "Webhook erstellen",
|
||||
"instant_meeting": "Sofortiges Meeting erstellt",
|
||||
"booking_cancelled": "Termin abgesagt",
|
||||
"booking_rescheduled": "Termin verlegt",
|
||||
"recording_ready": "Download-Link der Aufnahme ist nun verfügbar",
|
||||
@@ -533,6 +563,7 @@
|
||||
"enter_number_between_range": "Bitte geben Sie eine Zahl zwischen 1 und {{maxOccurences}} ein",
|
||||
"email_address": "E-Mail Adresse",
|
||||
"enter_valid_email": "Bitte geben Sie eine gültige Emaildresse ein",
|
||||
"please_schedule_future_call": " Bitte planen Sie einen Anruf in der Zukunft ein, falls wir nicht innerhalb von {{seconds}} Sekunden verfügbar sind",
|
||||
"location": "Ort",
|
||||
"address": "Adresse",
|
||||
"enter_address": "Geben Sie eine Adresse ein",
|
||||
@@ -592,6 +623,7 @@
|
||||
"new_member": "Neues Mitglied",
|
||||
"invite": "Einladen",
|
||||
"add_team_members": "Teammitglieder hinzufügen",
|
||||
"add_org_members": "Mitglieder hinzufügen",
|
||||
"add_team_members_description": "Lade andere ein, deinem Team beizutreten",
|
||||
"add_team_member": "Teammitglied hinzufügen",
|
||||
"invite_new_member": "Ein neues Mitglied einladen",
|
||||
@@ -606,8 +638,9 @@
|
||||
"hide_book_a_team_member_description": "Blendet die „Ein Teammitglied buchen“-Schaltflächen auf Ihren öffentlichen Seiten aus.",
|
||||
"danger_zone": "Achtung",
|
||||
"account_deletion_cannot_be_undone": "Vorsicht. Löschen eines Kontos kann nicht rückgängig gemacht werden.",
|
||||
"team_deletion_cannot_be_undone": "Seien Sie vorsichtig. Das Löschen eines Teams kann nicht rückgängig gemacht werden",
|
||||
"back": "Zurück",
|
||||
"cancel": "Absagen",
|
||||
"cancel": "Abbrechen",
|
||||
"cancel_all_remaining": "Alle verbleibenden absagen",
|
||||
"apply": "Anwenden",
|
||||
"cancel_event": "Termin absagen",
|
||||
@@ -656,6 +689,7 @@
|
||||
"default_duration": "Standardlaufzeit",
|
||||
"default_duration_no_options": "Bitte wählen Sie zuerst die verfügbaren Laufzeiten aus",
|
||||
"multiple_duration_mins": "{{count}} $t(minute_timeUnit)",
|
||||
"multiple_duration_timeUnit": "{{count}} $t({{unit}}_timeUnit)",
|
||||
"minutes": "Minuten",
|
||||
"round_robin": "Round Robin",
|
||||
"round_robin_description": "Treffen zwischen mehreren Teammitgliedern durchwechseln.",
|
||||
@@ -668,6 +702,7 @@
|
||||
"add_members": "Mitglieder hinzufügen...",
|
||||
"no_assigned_members": "Keine zugeordneten Mitglieder",
|
||||
"assigned_to": "Zugeordnet zu",
|
||||
"you_must_be_logged_in_to": "Sie müssen bei {{url}} angemeldet sein",
|
||||
"start_assigning_members_above": "Mit der Zuordnung von Mitgliedern oben beginnen",
|
||||
"locked_fields_admin_description": "Mitglieder werden dies nicht bearbeiten können",
|
||||
"locked_fields_member_description": "Diese Option wurde vom Team-Admin gesperrt",
|
||||
@@ -772,6 +807,7 @@
|
||||
"additional_input_description": "Erfordert die Eingabe zusätzlicher Eingaben durch den Planer vor der Terminbestätigung",
|
||||
"label": "Bezeichnung",
|
||||
"placeholder": "Platzhalter",
|
||||
"display_add_to_calendar_organizer": "„Zum Kalender hinzufügen“-E-Mail als Organisator anzeigen",
|
||||
"type": "Typ",
|
||||
"edit": "Bearbeiten",
|
||||
"add_input": "Eingabe hinzufügen",
|
||||
@@ -780,6 +816,9 @@
|
||||
"requires_confirmation_description": "Die Buchung muss manuell bestätigt werden, bevor die Buchungsinformationen an die Integrationen weitergeleitet werden und eine Bestätigungsmail gesendet wird.",
|
||||
"recurring_event": "Wiederkehrender Termin",
|
||||
"recurring_event_description": "Personen können sich für wiederkehrende Termine anmelden",
|
||||
"cannot_be_used_with_paid_event_types": "Es kann nicht mit bezahlten Ereignistypen verwendet werden",
|
||||
"warning_payment_instant_meeting_event": "Softige Meetings werden von wiederkehrenden Ereignissen und Zahlungs-Apps noch nicht unterstützt",
|
||||
"warning_instant_meeting_experimental": "Experimentell: „Sofortiges Meeting“-Ereignisse sind derzeit eine experimentelle Funktion.",
|
||||
"starting": "Beginnen",
|
||||
"disable_guests": "Gäste deaktivieren",
|
||||
"disable_guests_description": "Das Hinzufügen zusätzlicher Gäste deaktivieren.",
|
||||
@@ -788,6 +827,7 @@
|
||||
"private_link_label": "Privater Link",
|
||||
"private_link_hint": "Ihr privater Link wird nach jeder Nutzung neu generiert",
|
||||
"copy_private_link": "Privaten Link kopieren",
|
||||
"copy_private_link_to_event": "Privaten Link zum Ereignis kopieren",
|
||||
"private_link_description": "Erzeugen Sie einen privaten Link, den Sie teilen können, ohne Ihren {{appName}}-Benutzername preiszugeben",
|
||||
"invitees_can_schedule": "Teilnehmende können folgendes buchen",
|
||||
"date_range": "Datumszeitraum",
|
||||
@@ -847,6 +887,7 @@
|
||||
"next_step": "Schritt überspringen",
|
||||
"prev_step": "Vorheriger Schritt",
|
||||
"install": "Installieren",
|
||||
"start_paid_trial": "Kostenlose Testphase starten",
|
||||
"installed": "Installiert",
|
||||
"active_install_one": "{{count}} aktive Installation",
|
||||
"active_install_other": "{{count}} aktive Installationen",
|
||||
@@ -861,6 +902,7 @@
|
||||
"toggle_calendars_conflict": "Wechseln Sie die Kalender in denen nach Konflikten gesucht werden sollen, um Doppelbuchungen zu vermeiden.",
|
||||
"connect_additional_calendar": "Zusätzlichen Kalender verbinden",
|
||||
"calendar_updated_successfully": "Kalender erfolgreich aktualisiert",
|
||||
"check_here": "Hier auswählen",
|
||||
"conferencing": "Videokonferenzen",
|
||||
"calendar": "Kalender",
|
||||
"payments": "Zahlungen",
|
||||
@@ -1032,6 +1074,9 @@
|
||||
"user_impersonation_heading": "Benutzeridentitätswechsel",
|
||||
"user_impersonation_description": "Erlaubt es unserem Support-Team, sich vorübergehend mit Ihrem Konto anzumelden, um jegliche Probleme, die Sie gemeldet haben, schneller zu lösen.",
|
||||
"team_impersonation_description": "Ermöglicht den Team-Administratoren, sich vorübergehend als Sie einzuloggen.",
|
||||
"cal_signup_description": "Für Einzelpersonen kostenlos. Teampläne für kollaborative Funktionen.",
|
||||
"make_org_private": "Organisation privat stellen",
|
||||
"make_org_private_description": "Wenn diese Einstellung aktiv ist, können Ihre Organisationsmitglieder keine anderen Organisationsmitglieder sehen.",
|
||||
"make_team_private": "Team privat stellen",
|
||||
"make_team_private_description": "Wenn diese Einstellung aktiv ist, können deine Teammitglieder keine anderen Teammitglieder sehen.",
|
||||
"you_cannot_see_team_members": "Sie können nicht alle Teammitglieder eines privaten Teams sehen.",
|
||||
@@ -1091,6 +1136,7 @@
|
||||
"developer_documentation": "Entwickler-Dokumentation",
|
||||
"get_in_touch": "Kontakt aufnehmen",
|
||||
"contact_support": "Support kontaktieren",
|
||||
"premium_support": "Premium-Support",
|
||||
"community_support": "Community Support",
|
||||
"feedback": "Feedback",
|
||||
"submitted_feedback": "Vielen Dank für Ihr Feedback!",
|
||||
@@ -1254,6 +1300,7 @@
|
||||
"default_calendar_selected": "Standardkalender",
|
||||
"hide_from_profile": "Im Profil ausblenden",
|
||||
"event_setup_tab_title": "Termin-Einrichtung",
|
||||
"availability_not_found_in_schedule_error": "Keine Verfügbarkeit im Zeitplan gefunden",
|
||||
"event_limit_tab_title": "Limits",
|
||||
"event_limit_tab_description": "Wie oft können Sie gebucht werden",
|
||||
"event_advanced_tab_description": "Kalendereinstellungen & mehr...",
|
||||
@@ -1274,6 +1321,7 @@
|
||||
"do_this": "Mache dies",
|
||||
"turn_off": "Abschalten",
|
||||
"turn_on": "Einschalten",
|
||||
"cancelled_bookings_cannot_be_rescheduled": "Stornierte Buchungen können nicht verschoben werden",
|
||||
"settings_updated_successfully": "Einstellungen erfolgreich aktualisiert",
|
||||
"error_updating_settings": "Fehler beim Aktualisieren der Einstellungen",
|
||||
"personal_cal_url": "Meine persönliche {{appName}}-URL",
|
||||
@@ -1297,6 +1345,7 @@
|
||||
"customize_your_brand_colors": "Passen Sie Ihre eigene Markenfarbe auf Ihrer Buchungsseite an.",
|
||||
"pro": "Pro",
|
||||
"removes_cal_branding": "Entfernt alle {{appName}}-bezogenen Markenzeichen, z. B. „Powered by {{appName}}.“",
|
||||
"instant_meeting_with_title": "Sofortiges Meeting mit {{name}}",
|
||||
"profile_picture": "Profilbild",
|
||||
"upload": "Upload",
|
||||
"add_profile_photo": "Profilbild hinzufügen",
|
||||
@@ -1352,6 +1401,7 @@
|
||||
"event_name_info": "Name des Ereignistyps",
|
||||
"event_date_info": "Das Datum der Veranstaltung",
|
||||
"event_time_info": "Die Startzeit des Termins",
|
||||
"event_type_not_found": "Ereignistyp nicht gefunden",
|
||||
"location_variable": "Ort",
|
||||
"location_info": "Der Ort des Events",
|
||||
"additional_notes_variable": "Zusätzliche Notizen",
|
||||
@@ -1394,6 +1444,7 @@
|
||||
"slot_length": "Zeitfensterlänge",
|
||||
"booking_appearance": "Darstellung der Buchungsseite",
|
||||
"appearance_team_description": "Einstellungen für die Buchungsdarstellung Ihres Teams verwalten",
|
||||
"appearance_org_description": "Einstellungen für die Buchungsdarstellung Ihrer Organisation verwalten",
|
||||
"only_owner_change": "Nur der Besitzer dieses Teams kann Änderungen an den Buchungen des Teams vornehmen ",
|
||||
"team_disable_cal_branding_description": "Entfernt alle {{appName}}-bezogenen Markenzeichen, z. B. „Powered by {{appName}}“",
|
||||
"invited_by_team": "{{teamName}} hat Sie eingeladen, dem Team als {{role}} beizutreten",
|
||||
@@ -1447,6 +1498,7 @@
|
||||
"password_updated": "Passwort aktualisiert!",
|
||||
"pending_payment": "Zahlung ausstehend",
|
||||
"pending_invites": "Offene Einladungen",
|
||||
"pending_organization_invites": "Ausstehende Organisationseinladungen",
|
||||
"not_on_cal": "Nicht auf {{appName}}",
|
||||
"no_calendar_installed": "Kein Kalender installiert",
|
||||
"no_calendar_installed_description": "Sie haben noch keinen Ihrer Kalender verbunden",
|
||||
@@ -1458,6 +1510,8 @@
|
||||
"report_app": "App melden",
|
||||
"limit_booking_frequency": "Buchungsfrequenz begrenzen",
|
||||
"limit_booking_frequency_description": "Begrenzen, wie oft dieses Ereignis gebucht werden kann",
|
||||
"limit_booking_only_first_slot": "Buchun auf den ersten Platz beschränken",
|
||||
"limit_booking_only_first_slot_description": "Nur die Buchung des ersten Platzes am Tag erlauben",
|
||||
"limit_total_booking_duration": "Gesamtbuchungsdauer beschränken",
|
||||
"limit_total_booking_duration_description": "Begrenzt die Gesamtanzahl, die dieser Termin gebucht werden kann",
|
||||
"add_limit": "Begrenzung hinzufügen",
|
||||
@@ -1525,10 +1579,14 @@
|
||||
"your_org_disbanded_successfully": "Ihre Organization wurde erfolgreich aufgelöst",
|
||||
"error_creating_team": "Fehler beim Erstellen des Teams",
|
||||
"you": "Sie",
|
||||
"or_continue_with": "Oder fortfahren mit",
|
||||
"resend_email": "E-Mail erneut senden",
|
||||
"member_already_invited": "Mitglied wurde bereits eingeladen",
|
||||
"already_in_use_error": "Benutzername wird bereits verwendet",
|
||||
"enter_email_or_username": "E-Mail oder Benutzername eingeben",
|
||||
"enter_email": "E-Mail-Adresse eingeben",
|
||||
"enter_emails": "E-Mail-Adressen eingeben",
|
||||
"too_many_invites": "Sie sind darauf beschränkt, maximal {{nbUsers}} Benutzer gleichzeitig einzuladen.",
|
||||
"team_name_taken": "Dieser Name ist bereits vergeben",
|
||||
"must_enter_team_name": "Team-Name muss eingegeben werden",
|
||||
"team_url_required": "Team-URL muss eingegeben werden",
|
||||
@@ -1608,6 +1666,7 @@
|
||||
"individual": "Person",
|
||||
"all_bookings_filter_label": "Alle Buchungen",
|
||||
"all_users_filter_label": "Alle Benutzer",
|
||||
"all_event_types_filter_label": "Alle Ereignistypen",
|
||||
"your_bookings_filter_label": "Ihre Buchungen",
|
||||
"meeting_url_variable": "Meeting-URL",
|
||||
"meeting_url_info": "Die URL der Meeting-Konferenz",
|
||||
@@ -1704,9 +1763,11 @@
|
||||
"organizer_timezone": "Zeitzone des Veranstalters",
|
||||
"email_user_cta": "Einladung anzeigen",
|
||||
"email_no_user_invite_heading_team": "Sie wurden eingeladen, einem {{appName}}-Team beizutreten",
|
||||
"email_no_user_invite_heading_subteam": "Sie wurden eingeladen, einem Team der Organisation {{parentTeamName}} beizutreten",
|
||||
"email_no_user_invite_heading_org": "Sie wurden eingeladen, einer {{appName}}-Organisation beizutreten",
|
||||
"email_no_user_invite_subheading": "{{invitedBy}} hat Sie eingeladen, dem Team in {{appName}} beizutreten. {{appName}} ist der Event-Planer, der es Ihnen und Ihrem Team ermöglicht, Meetings ohne ständiges hin und her zu planen.",
|
||||
"email_user_invite_subheading_team": "{{invitedBy}} hat Sie eingeladen, dem Team „{{teamName}}“ beizutreten. {{appName}} ist der Event-Planer, der es Ihnen und Ihrem Team ermöglicht, Meetings ohne Hin und Her zu planen.",
|
||||
"email_user_invite_subheading_subteam": "{{invitedBy}} hat Sie eingeladen, dem Team „{{teamName}}“ in seiner bzw. ihrer Organisation {{parentTeamName}} bei {{appName}} beizutreten. {{appName}} ist der Terminplaner, der es Ihnen und Ihrem Team ermöglicht, Meetings ohne Hin und Her zu planen.",
|
||||
"email_user_invite_subheading_org": "{{invitedBy}} hat Sie eingeladen, ihrer Organisation „{{teamName}}“ in {{appName}} beizutreten. {{appName}} ist der Terminplaner, der es Ihnen und Ihrer Organisation ermöglicht, Meetings ohne ständiges hin und her zu planen.",
|
||||
"email_no_user_invite_steps_intro": "Wir begleiten Sie durch ein paar Schritte, danach können Sie in kürzester Zeit stressfreie Planung mit Ihrer {{entity}} genießen.",
|
||||
"email_no_user_step_one": "Wählen Sie Ihren Benutzernamen",
|
||||
@@ -1839,7 +1900,9 @@
|
||||
"review_event_type": "Ereignistyp überprüfen",
|
||||
"looking_for_more_analytics": "Suchen Sie nach weiteren Analysedaten?",
|
||||
"looking_for_more_insights": "Möchten Sie mehr Insights?",
|
||||
"filters": "Filter",
|
||||
"add_filter": "Filter hinzufügen",
|
||||
"remove_filters": "Alle Filter löschen",
|
||||
"select_user": "Benutzer auswählen",
|
||||
"select_event_type": "Ereignistyp auswählen",
|
||||
"select_date_range": "Datumsbereich auswählen",
|
||||
@@ -1930,6 +1993,7 @@
|
||||
"connect_google_workspace": "Google Workspace verbinden",
|
||||
"google_workspace_admin_tooltip": "Sie müssen ein Workspace-Admin sein, um diese Funktion nutzen zu können",
|
||||
"first_event_type_webhook_description": "Erstellen Sie Ihren ersten Webhook für diesen Termintypen",
|
||||
"create_instant_meeting_webhook_description": "Erstellen Sie Ihren ersten Webhook mit „Sofortiges Meeting erstellt“ als Auslöser für diesen Ereignistyp",
|
||||
"install_app_on": "App installieren auf",
|
||||
"create_for": "Erstellen für",
|
||||
"currency": "Währung",
|
||||
@@ -1994,6 +2058,8 @@
|
||||
"add_times_to_your_email": "Zeiten zur E-Mail hinzufügen",
|
||||
"select_time": "Zeit auswählen",
|
||||
"select_date": "Datum auswählen",
|
||||
"connecting_you_to_someone": "Wir verbinden Sie mit jemandem.",
|
||||
"please_do_not_close_this_tab": "Diesen Tab bitte nicht schließen",
|
||||
"see_all_available_times": "Alle verfügbaren Zeiten ansehen",
|
||||
"org_team_names_example_1": "z.B. Marketing-Team",
|
||||
"org_team_names_example_2": "z.B. Vertriebsteam",
|
||||
@@ -2011,8 +2077,12 @@
|
||||
"description_requires_booker_email_verification": "Um sicherzustellen, dass die E-Mail des Buchenden vor der Terminplanung verifiziert ist",
|
||||
"requires_confirmation_mandatory": "Textnachrichten können nur an Teilnehmer gesendet werden, wenn der Ereignistyp bestätigt werden muss.",
|
||||
"organizations": "Organisationen",
|
||||
"upload_cal_video_logo": "Cal Video Logo hochladen",
|
||||
"update_cal_video_logo": "Cal Video Logo aktualisieren",
|
||||
"cal_video_logo_upload_instruction": "Um sicherzustellen, dass Ihr Logo vor dem dunklen Hintergrund von Cal Video gut sichtbar ist, laden Sie bitte ein helles Bild im PNG- oder SVG-Format hoch, um die Transparenz zu erhalten.",
|
||||
"org_admin_other_teams": "Weitere Teams",
|
||||
"org_admin_other_teams_description": "Hier können Sie Teams innerhalb Ihrer Organisation sehen, zu denen Sie nicht gehören. Sie können sich bei Bedarf selbst hinzufügen.",
|
||||
"not_part_of_org": "Sie gehören keiner Organisation an",
|
||||
"no_other_teams_found": "Keine weiteren Teams gefunden",
|
||||
"no_other_teams_found_description": "Es gibt keine weiteren Teams in dieser Organisation.",
|
||||
"attendee_first_name_variable": "Vorname des Teilnehmers",
|
||||
@@ -2048,7 +2118,9 @@
|
||||
"org_error_processing": "Beim Verarbeiten dieser Organisation ist ein Fehler aufgetreten",
|
||||
"orgs_page_description": "Eine Liste aller Organisationen. Wenn Sie eine Organisation akzeptieren, können sich alle Benutzer mit dieser E-Mail-Domain OHNE E-Mail-Überprüfung registrieren.",
|
||||
"unverified": "Nicht verifiziert",
|
||||
"verified": "Verifiziert",
|
||||
"dns_missing": "DNS fehlt",
|
||||
"dns_configured": "DNS konfiguriert",
|
||||
"mark_dns_configured": "Als DNS konfiguriert kennzeichnen",
|
||||
"value": "Wert",
|
||||
"your_organization_updated_sucessfully": "Ihre Organisation wurde erfolgreich aktualisiert",
|
||||
@@ -2058,8 +2130,11 @@
|
||||
"oAuth": "OAuth",
|
||||
"recently_added": "Kürzlich hinzugefügt",
|
||||
"connect_all_calendars": "Verbinden Sie alle Ihre Kalender",
|
||||
"connect_all_calendars_description": "{{appName}} liest die Verfügbarkeit aus allen Ihren vorhandenen Kalendern.",
|
||||
"workflow_automation": "Workflow-Automatisierung",
|
||||
"workflow_automation_description": "Personalisieren Sie Ihre Planungserfahrung mit Workflows",
|
||||
"scheduling_for_your_team": "Workflow-Automatisierung",
|
||||
"scheduling_for_your_team_description": "Planen Sie Ihr Team mit kollektiver Terminplanung und Round-Robin-Terminplanung",
|
||||
"no_members_found": "Keine Mitglieder gefunden",
|
||||
"event_setup_length_error": "Ereignis-Einrichtung: Die Dauer muss mindestens 1 Minute betragen.",
|
||||
"availability_schedules": "Verfügbarkeitspläne",
|
||||
@@ -2092,10 +2167,100 @@
|
||||
"overlay_my_calendar": "Meinen Kalender überlagern",
|
||||
"overlay_my_calendar_toc": "Durch das Verbinden mit Ihrem Kalender akzeptieren Sie unsere Datenschutzerklärung und Nutzungsbedingungen. Sie können den Zugriff jederzeit widerrufen.",
|
||||
"view_overlay_calendar_events": "Sehen Sie sich Ihre Kalendertermine an, um Buchungskonflikte zu vermeiden.",
|
||||
"join_event_location": "{{eventLocationType}} beitreten",
|
||||
"troubleshooting": "Problembehandlung",
|
||||
"calendars_were_checking_for_conflicts": "Kalender, die wir auf Konflikten überprüfen",
|
||||
"availabilty_schedules": "Verfügbarkeitspläne",
|
||||
"manage_calendars": "Kalender verwalten",
|
||||
"manage_availability_schedules": "Verfügbarkeitspläne verwalten",
|
||||
"lock_timezone_toggle_on_booking_page": "Zeitzone auf der Buchungsseite sperren",
|
||||
"description_lock_timezone_toggle_on_booking_page": "Um die Zeitzone auf der Buchungsseite zu sperren, nützlich für Termine in Person.",
|
||||
"event_setup_multiple_payment_apps_error": "Sie können nur eine Zahlungs-App pro Ereignistyp aktiv haben.",
|
||||
"number_in_international_format": "Bitte geben Sie die Nummer im internationalen Format ein.",
|
||||
"install_calendar": "Kalender installieren",
|
||||
"branded_subdomain": "Marken-Subdomain",
|
||||
"branded_subdomain_description": "Holen Sie sich Ihre eigene Marken-Subdomain, wie z.B. acme.cal.com",
|
||||
"org_insights": "Organisationsweite Insights",
|
||||
"org_insights_description": "Verstehen Sie, wie Ihre gesamte Organisation Zeit verbringt",
|
||||
"extensive_whitelabeling": "Dedizierte Onboarding- und Engineeringsupport",
|
||||
"extensive_whitelabeling_description": "Passen Sie Ihre Planungserfahrung mit Ihrem eigenen Logo, eigenen Farben und mehr an",
|
||||
"unlimited_teams": "Unbegrenzte Teams",
|
||||
"unlimited_teams_description": "Fügen Sie Ihrer Organisation so viele Unterteams hinzu, wie notwendig",
|
||||
"unified_billing": "Einheitliche Abrechnung",
|
||||
"unified_billing_description": "Fügen Sie eine einzige Kreditkarte hinzu, um alle Abos Ihres Teams zu bezahlen",
|
||||
"advanced_managed_events": "Erweiterte verwaltete Ereignistypen",
|
||||
"advanced_managed_events_description": "Fügen Sie eine einzige Kreditkarte hinzu, um alle Abonnements Ihres Teams zu bezahlen",
|
||||
"enterprise_description": "Rüsten Sie auf Enterprise auf, um Ihre Organisation zu erstellen",
|
||||
"create_your_org": "Erstellen Sie Ihre Organisation",
|
||||
"create_your_org_description": "Rüsten Sie auf Enterprise auf und erhalten Sie eine Subdomain, einheitliche Abrechnung, Insights, umfangreiches Whitelabeling und mehr",
|
||||
"other_payment_app_enabled": "Sie können nur eine Zahlungs-App pro Ereignistyp aktivieren",
|
||||
"admin_delete_organization_description": "<ul><li>Teams, die Mitglieder dieser Organisation sind, werden ebenfalls zusammen mit ihren Ereignistypen gelöscht</li><li>Benutzer, die Teil der Organisation waren, werden nicht gelöscht und ihre Ereignistypen bleiben ebenfalls intakt.</li><li>Benutzernamen würden geändert werden, um zu ermöglichen, dass sie außerhalb der Organisation existieren können</li></ul>",
|
||||
"admin_delete_organization_title": "{{organizationName}} löschen?",
|
||||
"published": "Veröffentlicht",
|
||||
"unpublished": "Unveröffentlicht",
|
||||
"publish": "Veröffentlichen",
|
||||
"org_publish_error": "Organisation konnte nicht veröffentlicht werden",
|
||||
"troubleshooter_tooltip": "Öffnen Sie die Problembehandlung und finden Sie heraus, was mit Ihrem Zeitplan nicht stimmt",
|
||||
"need_help": "Brauchst du Hilfe?",
|
||||
"troubleshooter": "Problembehandlung",
|
||||
"please_install_a_calendar": "Bitte installieren Sie einen Kalender",
|
||||
"instant_tab_title": "Sofortige Buchung",
|
||||
"instant_event_tab_description": "Personen sofort buchen lassen",
|
||||
"uprade_to_create_instant_bookings": "Rüsten Sie auf Enterprise auf und lassen Sie Ihre Gäste an einem Sofortanruf teilnehmen, an dem die Teilnehmer direkt teilnehmen können. Dies ist nur für Team-Ereignistypen verfügbar",
|
||||
"dont_want_to_wait": "Sie möchten nicht warten?",
|
||||
"meeting_started": "Meeting gestartet",
|
||||
"pay_and_book": "Zum Buchen bezahlen",
|
||||
"booking_not_found_error": "Buchung konnte nicht gefunden werden",
|
||||
"booking_seats_full_error": "Die Buchungsplätze sind voll",
|
||||
"missing_payment_credential_error": "Fehlende Zahlungsinformationen",
|
||||
"missing_payment_app_id_error": "Fehlende Zahlungsapp-ID",
|
||||
"not_enough_available_seats_error": "Die Buchung hat nicht genügend Plätze verfügbar",
|
||||
"user_redirect_title": "{{username}} ist derzeit kurzzeitig abwesend.",
|
||||
"user_redirect_description": "In der Zwischenzeit wird {{profile.username}} für alle neuen geplanten Meetings im Namen von {{username}} verantwortlich sein.",
|
||||
"out_of_office": "Abwesend",
|
||||
"out_of_office_description": "Konfigurieren Sie Aktionen in Ihrem Profil während Sie abwesend sind.",
|
||||
"send_request": "Anfrage senden",
|
||||
"start_date_and_end_date_required": "Startdatum und Enddatum sind erforderlich",
|
||||
"start_date_must_be_before_end_date": "Das Startdatum muss vor dem Enddatum liegen",
|
||||
"start_date_must_be_in_the_future": "Das Startdatum muss in der Zukunft liegen",
|
||||
"user_not_found": "Benutzer nicht gefunden",
|
||||
"out_of_office_entry_already_exists": "Abwesenheitseintrag existiert bereits",
|
||||
"out_of_office_id_required": "Abwesenheitseintrags-ID erforderlich",
|
||||
"booking_redirect_infinite_not_allowed": "Es besteht bereits eine Buchungsumleitung von diesem Benutzer zu Ihnen.",
|
||||
"success_entry_created": "Neuer Eintrag wurde erfolgreich erstellt",
|
||||
"booking_redirect_email_subject": "Buchungsumleitungsbenachrichtigung",
|
||||
"booking_redirect_email_title": "Buchungsumleitungsbenachrichtigung",
|
||||
"booking_redirect_email_description": "Sie haben eine Umleitung der Buchung von {{toName}} erhalten, so dass deren Profillinks für das Zeitfenster an Sie umgeleitet werden: ",
|
||||
"success_accept_booking_redirect": "Sie haben diese Buchungsumleitungsanfrage akzeptiert.",
|
||||
"success_reject_booking_redirect": "Sie haben diese Buchungsumleitungsanfrage abgelehnt.",
|
||||
"copy_link_booking_redirect_request": "Link zum Teilen der Anfrage kopieren",
|
||||
"booking_redirect_request_title": "Buchungsumleitungsanfrage",
|
||||
"select_team_member": "Teammitglied auswählen",
|
||||
"going_away_title": "Verreisen Sie? Markieren Sie einfach Ihren Profillink für eine gewisse Zeit als nicht verfügbar.",
|
||||
"redirect_team_enabled": "Leiten Sie Ihr Profil an ein anderes Teammitglied um",
|
||||
"redirect_team_disabled": "Leiten Sie Ihr Profil an ein anderes Teammitglied um (Teamplan erforderlich)",
|
||||
"out_of_office_unavailable_list": "Abwesenheits-Unverfügbarkeitsliste",
|
||||
"success_deleted_entry_out_of_office": "Eintrag erfolgreich gelöscht",
|
||||
"temporarily_out_of_office": "Temporär abwesend?",
|
||||
"add_a_redirect": "Umleitung hinzufügen",
|
||||
"create_entry": "Eintrag erstellen",
|
||||
"time_range": "Zeitspanne",
|
||||
"automatically_add_all_team_members": "Alle Teammitglieder hinzufügen, einschließlich zukünftiger Mitglieder",
|
||||
"redirect_to": "Umleiten zu",
|
||||
"having_trouble_finding_time": "Haben Sie Schwierigkeiten, ein Zeitfenster zu finden?",
|
||||
"show_more": "Mehr anzeigen",
|
||||
"assignment_description": "Planen Sie Meetings ein, wenn jeder verfügbar ist, oder rotieren Sie durch die Mitglieder Ihres Team",
|
||||
"lowest": "Niedrigste",
|
||||
"low": "Niedrig",
|
||||
"medium": "Mittel",
|
||||
"high": "Hoch",
|
||||
"Highest": "Höchste",
|
||||
"send_booker_to": "Bucher senden an",
|
||||
"set_priority": "Priorität festlegen",
|
||||
"priority_for_user": "Priorität von {{userName}}",
|
||||
"change_priority": "Priorität ändern",
|
||||
"field_identifiers_as_variables": "Verwenden Sie Feldbezeichner als Variablen für Ihre benutzerdefinierte Ereignisumleitung",
|
||||
"field_identifiers_as_variables_with_example": "Verwenden Sie Feldbezeichner als Variablen für Ihre benutzerdefinierte Ereignisumleitung (z. B. {{variable}})",
|
||||
"account_already_linked": "Konto ist bereits verknüpft",
|
||||
"ADD_NEW_STRINGS_ABOVE_THIS_LINE_TO_PREVENT_MERGE_CONFLICTS": "↑↑↑↑↑↑↑↑↑↑↑↑↑ Fügen Sie Ihre neuen Code-Zeilen über dieser hinzu ↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑"
|
||||
}
|
||||
|
||||
@@ -638,7 +638,7 @@
|
||||
"hide_book_a_team_member_description": "Hide Book a Team Member Button from your public pages.",
|
||||
"danger_zone": "Danger zone",
|
||||
"account_deletion_cannot_be_undone":"Be Careful. Account deletion cannot be undone.",
|
||||
"team_deletion_cannot_be_undone":"Be Careful. Team deletion cannot be undone",
|
||||
"team_deletion_cannot_be_undone":"Be Careful. Team deletion cannot be undone.",
|
||||
"back": "Back",
|
||||
"cancel": "Cancel",
|
||||
"cancel_all_remaining": "Cancel all remaining",
|
||||
@@ -808,6 +808,7 @@
|
||||
"label": "Label",
|
||||
"placeholder": "Placeholder",
|
||||
"display_add_to_calendar_organizer":"Display 'Add to calendar' email as the organizer",
|
||||
"reconnect_calendar_to_use": "Please note that you may need to disconnect and then reconnect your 'Add to Calendar' account to use this feature.",
|
||||
"type": "Type",
|
||||
"edit": "Edit",
|
||||
"add_input": "Add an Input",
|
||||
@@ -1472,8 +1473,10 @@
|
||||
"route_to_the_right_person": "Route to the right person based on the answers to your form",
|
||||
"form_description": "Create your form to route a booker",
|
||||
"copy_link_to_form": "Copy link to form",
|
||||
"theme": "Theme",
|
||||
"theme": "Booking Page Theme",
|
||||
"theme_applies_note": "This only applies to your public booking pages",
|
||||
"app_theme": "Dashboard Theme",
|
||||
"app_theme_applies_note": "This only applies to your logged in dashboard",
|
||||
"theme_system": "System default",
|
||||
"add_a_team": "Add a team",
|
||||
"add_webhook_description": "Receive meeting data in real-time when something happens in {{appName}}",
|
||||
@@ -2195,7 +2198,7 @@
|
||||
"create_your_org_description": "Upgrade to Enterprise and receive a subdomain, unified billing, Insights, extensive whitelabeling and more",
|
||||
"other_payment_app_enabled": "You can only enable one payment app per event type",
|
||||
"admin_delete_organization_description": "<ul><li>Teams that are member of this organization will also be deleted along with their event-types</li><li>Users that were part of the organization will not be deleted and their event-types will also remain intact.</li><li>Usernames would be changed to allow them to exist outside the organization</li></ul>",
|
||||
"admin_delete_organization_title": "Delete organization?",
|
||||
"admin_delete_organization_title": "Delete {{organizationName}}?",
|
||||
"published": "Published",
|
||||
"unpublished": "Unpublished",
|
||||
"publish": "Publish",
|
||||
|
||||
@@ -893,7 +893,7 @@
|
||||
"number_apps_other": "{{count}} aplicaciones",
|
||||
"trending_apps": "Aplicaciones populares",
|
||||
"most_popular": "Más popular",
|
||||
"installed_apps": "Aplicaciones instaladas",
|
||||
"installed_apps": "Apps instaladas",
|
||||
"free_to_use_apps": "Gratis",
|
||||
"no_category_apps": "No hay aplicaciones de {{category}}",
|
||||
"all_apps": "Todas las aplicaciones",
|
||||
|
||||
@@ -13,10 +13,17 @@
|
||||
"reset_password_subject": "{{appName}} : Instructions de réinitialisation de mot de passe",
|
||||
"verify_email_subject": "{{appName}} : Vérifiez votre compte",
|
||||
"check_your_email": "Vérifiez votre e-mail",
|
||||
"old_email_address": "Ancienne adresse e-mail",
|
||||
"new_email_address": "Nouvelle adresse e-mail",
|
||||
"verify_email_page_body": "Nous avons envoyé un e-mail à {{email}}. Il est important de vérifier votre adresse e-mail pour garantir la meilleure délivrabilité des e-mails et des calendriers de {{appName}}.",
|
||||
"verify_email_banner_body": "Vérifiez votre adresse e-mail pour garantir la meilleure délivrabilité des e-mails et des calendriers",
|
||||
"verify_email_email_header": "Vérifiez votre adresse e-mail",
|
||||
"verify_email_email_button": "Vérifier l'adresse e-mail",
|
||||
"verify_email_change_description": "Vous avez récemment demandé à changer l'adresse e-mail que vous utilisez pour vous connecter à votre compte {{appName}}. Veuillez cliquer sur le bouton ci-dessous pour confirmer votre nouvelle adresse e-mail.",
|
||||
"verify_email_change_success_toast": "Mise à jour de votre adresse e-mail. Nouvelle : {{email}}",
|
||||
"verify_email_change_failure_toast": "Échec de la mise à jour de l'adresse e-mail.",
|
||||
"change_of_email": "Faites vérifier votre nouvelle adresse e-mail {{appName}}",
|
||||
"change_of_email_toast": "Nous avons envoyé un lien de vérification à {{email}}. Nous mettrons à jour votre adresse e-mail une fois que vous aurez cliqué sur ce lien.",
|
||||
"copy_somewhere_safe": "Sauvegardez cette clé API dans un endroit sûr. Vous ne pourrez plus la revoir.",
|
||||
"verify_email_email_body": "Veuillez vérifier votre adresse e-mail en cliquant sur le bouton ci-dessous.",
|
||||
"verify_email_by_code_email_body": "Veuillez vérifier votre adresse e-mail en utilisant le code ci-dessous.",
|
||||
@@ -56,6 +63,17 @@
|
||||
"a_refund_failed": "Un remboursement a échoué",
|
||||
"awaiting_payment_subject": "En attente de paiement : {{title}} le {{date}}",
|
||||
"meeting_awaiting_payment": "Votre rendez-vous est en attente de paiement",
|
||||
"dark_theme_contrast_error": "La couleur du thème sombre ne passe pas la vérification du contraste. Nous vous recommandons de modifier cette couleur afin que vos boutons soient plus visibles.",
|
||||
"light_theme_contrast_error": "La couleur du thème clair ne passe pas la vérification du contraste. Nous vous recommandons de modifier cette couleur afin que vos boutons soient plus visibles.",
|
||||
"payment_not_created_error": "Le paiement n'a pas pu être créé",
|
||||
"couldnt_charge_card_error": "Impossible de débiter la carte pour le Paiement",
|
||||
"no_available_users_found_error": "Aucun utilisateur disponible n'a été trouvé. Pourriez-vous essayer un autre créneau horaire ?",
|
||||
"request_body_end_time_internal_error": "Erreur interne. Le corps de la demande ne contient pas l'heure de fin",
|
||||
"create_calendar_event_error": "Impossible de créer un événement de Calendrier dans le calendrier de l'Organisateur",
|
||||
"update_calendar_event_error": "Impossible de mettre à jour l'événement de Calendrier.",
|
||||
"delete_calendar_event_error": "Impossible de supprimer l'événement de Calendrier.",
|
||||
"already_signed_up_for_this_booking_error": "Vous êtes déjà inscrit(e) à cette réservation.",
|
||||
"hosts_unavailable_for_booking": "Certains hôtes sont indisponibles pour la réservation.",
|
||||
"help": "Aide",
|
||||
"price": "Prix",
|
||||
"paid": "Payé",
|
||||
@@ -101,6 +119,7 @@
|
||||
"requested_to_reschedule_subject_attendee": "Replanification requise : veuillez réserver un nouveau créneau pour {{eventType}} avec {{name}}",
|
||||
"hi_user_name": "Bonjour {{name}}",
|
||||
"ics_event_title": "{{eventType}} avec {{name}}",
|
||||
"please_book_a_time_sometime_later": "Oups, nous n'avons pas pu vous connecter cette fois-ci. Veuillez plutôt planifier un appel ultérieur",
|
||||
"new_event_subject": "Nouvel événement : {{attendeeName}} - {{date}} - {{eventType}}",
|
||||
"join_by_entrypoint": "Rejoindre par {{entryPoint}}",
|
||||
"notes": "Notes",
|
||||
@@ -117,15 +136,20 @@
|
||||
"meeting_id": "ID du rendez-vous",
|
||||
"meeting_password": "Mot de passe du rendez-vous",
|
||||
"meeting_url": "Lien du rendez-vous",
|
||||
"meeting_url_not_found": "L'URL du rendez-vous est introuvable",
|
||||
"token_not_found": "Jeton introuvable",
|
||||
"some_other_host_already_accepted_the_meeting": "D'autres hôtes ont déjà accepté le rendez-vous. Voulez-vous quand même participer ? <1>Aller quand même dans Rendez-vous</1>",
|
||||
"meeting_request_rejected": "Votre demande de rendez-vous a été refusée",
|
||||
"rejected_event_type_with_organizer": "Refusé : {{eventType}} avec {{organizer}} le {{date}}",
|
||||
"hi": "Bonjour",
|
||||
"join_team": "Rejoindre l'équipe",
|
||||
"manage_this_team": "Gérer cette équipe",
|
||||
"team_info": "Informations sur l'équipe",
|
||||
"join_meeting": "Rejoindre le rendez-vous",
|
||||
"request_another_invitation_email": "Si vous préférez ne pas utiliser {{toEmail}} comme votre adresse e-mail {{appName}} ou si vous avez déjà un compte {{appName}}, veuillez demander une autre invitation à cette adresse.",
|
||||
"you_have_been_invited": "Vous avez été invité à rejoindre l'équipe {{teamName}}",
|
||||
"user_invited_you": "{{user}} vous a invité à rejoindre l'{{entity}} {{team}} sur {{appName}}",
|
||||
"user_invited_you_to_subteam": "{{user}} vous a invité(e) à rejoindre l'équipe {{team}} de l'organisation {{parentTeamName}} sur {{appName}}",
|
||||
"hidden_team_member_title": "Vous êtes masqué dans cette équipe",
|
||||
"hidden_team_member_message": "Votre place n'est pas payée, passez au plan Pro ou faites savoir au propriétaire de l'équipe qu'il peut payer votre place.",
|
||||
"hidden_team_owner_message": "Vous avez besoin d'un compte Pro pour utiliser les équipes, vous êtes masqué jusqu'à ce que vous mettiez à niveau.",
|
||||
@@ -285,6 +309,7 @@
|
||||
"welcome_to_calcom": "Bienvenue sur {{appName}}",
|
||||
"welcome_instructions": "Dites-nous comment vous appeler et indiquez-nous dans quel fuseau horaire vous vous trouvez. Vous pourrez modifier cela plus tard. ",
|
||||
"connect_caldav": "Se connecter au serveur CalDAV (Bêta)",
|
||||
"connect_ics_feed": "Connexion à un flux ICS",
|
||||
"connect": "Connecter",
|
||||
"try_for_free": "Essayez gratuitement",
|
||||
"create_booking_link_with_calcom": "Créez votre propre lien de réservation avec {{appName}}",
|
||||
@@ -423,12 +448,14 @@
|
||||
"browse_api_documentation": "Parcourez la documentation de l'API",
|
||||
"leverage_our_api": "Profitez de notre API pour tout personnaliser et contrôler.",
|
||||
"create_webhook": "Créer un webhook",
|
||||
"instant_meeting": "Rendez-vous instantané créé",
|
||||
"booking_cancelled": "Réservation annulée",
|
||||
"booking_rescheduled": "Réservation replanifiée",
|
||||
"recording_ready": "Lien de téléchargement de l'enregistrement prêt",
|
||||
"booking_created": "Réservation créée",
|
||||
"booking_rejected": "Réservation rejetée",
|
||||
"booking_requested": "Réservation demandée",
|
||||
"booking_payment_initiated": "Paiement de la réservation initiée",
|
||||
"meeting_ended": "Rendez-vous terminé",
|
||||
"form_submitted": "Formulaire envoyé",
|
||||
"booking_paid": "Réservation payée",
|
||||
@@ -536,6 +563,7 @@
|
||||
"enter_number_between_range": "Veuillez entrer un nombre entre 1 et {{maxOccurences}}",
|
||||
"email_address": "Adresse e-mail",
|
||||
"enter_valid_email": "Veuillez saisir une adresse e-mail valide",
|
||||
"please_schedule_future_call": " Veuillez programmer un appel ultérieur si nous ne sommes pas disponibles dans {{seconds}} secondes",
|
||||
"location": "Lieu",
|
||||
"address": "Adresse",
|
||||
"enter_address": "Entrer une adresse",
|
||||
@@ -595,6 +623,7 @@
|
||||
"new_member": "Nouveau membre",
|
||||
"invite": "Inviter",
|
||||
"add_team_members": "Ajouter des membres d'équipe",
|
||||
"add_org_members": "Ajouter des membres",
|
||||
"add_team_members_description": "Invitez d'autres personnes à rejoindre votre équipe.",
|
||||
"add_team_member": "Ajouter un membre d'équipe",
|
||||
"invite_new_member": "Inviter un nouveau membre d'équipe",
|
||||
@@ -609,6 +638,7 @@
|
||||
"hide_book_a_team_member_description": "Masquez le bouton Réserver un membre d'équipe de vos pages publiques.",
|
||||
"danger_zone": "Zone de danger",
|
||||
"account_deletion_cannot_be_undone": "Attention, la suppression de compte est irréversible.",
|
||||
"team_deletion_cannot_be_undone": "Attention. La suppression de l'équipe est irréversible",
|
||||
"back": "Retour",
|
||||
"cancel": "Annuler",
|
||||
"cancel_all_remaining": "Annuler tous les événements restants",
|
||||
@@ -672,6 +702,7 @@
|
||||
"add_members": "Ajouter des membres...",
|
||||
"no_assigned_members": "Aucun membre assigné",
|
||||
"assigned_to": "Assigné à",
|
||||
"you_must_be_logged_in_to": "Vous devez être connecté(e) à {{url}}",
|
||||
"start_assigning_members_above": "Commencez à assigner des membres ci-dessus",
|
||||
"locked_fields_admin_description": "Les membres ne pourront pas modifier ceci.",
|
||||
"locked_fields_member_description": "Cette option a été verrouillée par l'administrateur de l'équipe.",
|
||||
@@ -776,6 +807,7 @@
|
||||
"additional_input_description": "Exiger que le planificateur saisisse des champs supplémentaires avant la confirmation de la réservation.",
|
||||
"label": "Libellé",
|
||||
"placeholder": "Texte de substitution",
|
||||
"display_add_to_calendar_organizer": "Afficher l'e-mail « Ajouter au calendrier » en tant qu'organisateur",
|
||||
"type": "Type",
|
||||
"edit": "Modifier",
|
||||
"add_input": "Ajouter un champ",
|
||||
@@ -784,6 +816,9 @@
|
||||
"requires_confirmation_description": "La réservation doit être confirmée manuellement avant d'être poussée vers les intégrations et qu'un e-mail de confirmation soit envoyé.",
|
||||
"recurring_event": "Événement récurrent",
|
||||
"recurring_event_description": "Les personnes peuvent s'inscrire à des événements récurrents.",
|
||||
"cannot_be_used_with_paid_event_types": "À ne pas utiliser avec les types d'événement payants",
|
||||
"warning_payment_instant_meeting_event": "Les Rendez-vous instantanés ne sont pas encore pris en charge par les événements récurrents et les applications de paiement",
|
||||
"warning_instant_meeting_experimental": "Expérimental : Les événements de Rendez-vous instantané sont actuellement expérimentaux.",
|
||||
"starting": "Début",
|
||||
"disable_guests": "Désactiver les invités",
|
||||
"disable_guests_description": "Désactivez l'ajout d'invités supplémentaires lors de la réservation.",
|
||||
@@ -792,6 +827,7 @@
|
||||
"private_link_label": "Lien privé",
|
||||
"private_link_hint": "Votre lien privé sera régénéré après chaque utilisation",
|
||||
"copy_private_link": "Copier le lien privé",
|
||||
"copy_private_link_to_event": "Copier le lien privé de l'événement",
|
||||
"private_link_description": "Générez un lien privé à partager sans révéler votre nom d'utilisateur {{appName}}.",
|
||||
"invitees_can_schedule": "Les invités peuvent réserver",
|
||||
"date_range": "Plage de dates",
|
||||
@@ -866,6 +902,7 @@
|
||||
"toggle_calendars_conflict": "Activez/désactivez les calendriers pour lesquels vous souhaitez vérifier les conflits afin d'éviter les doubles réservations.",
|
||||
"connect_additional_calendar": "Connecter un calendrier supplémentaire",
|
||||
"calendar_updated_successfully": "Calendrier mis à jour avec succès",
|
||||
"check_here": "Vérifier ici",
|
||||
"conferencing": "Visioconférence",
|
||||
"calendar": "Calendrier",
|
||||
"payments": "Paiements",
|
||||
@@ -1038,6 +1075,8 @@
|
||||
"user_impersonation_description": "Autorisez notre équipe d'assistance à se connecter temporairement à votre place pour nous aider à résoudre les problèmes que vous pourriez nous signaler.",
|
||||
"team_impersonation_description": "Autorisez les propriétaires/administrateurs de votre équipe à se connecter temporairement à votre place.",
|
||||
"cal_signup_description": "Gratuit pour les particuliers. Plans Équipes pour les fonctionnalités collaboratives.",
|
||||
"make_org_private": "Rendre l'organisation privée",
|
||||
"make_org_private_description": "Les membres de votre organisation ne pourront pas voir les autres membres de l'organisation lorsque cette option est activée.",
|
||||
"make_team_private": "Rendre l'équipe privée",
|
||||
"make_team_private_description": "Les membres de votre équipe ne pourront pas voir les autres membres de l'équipe si cette option est activée.",
|
||||
"you_cannot_see_team_members": "Vous ne pouvez pas voir tous les membres d'une équipe privée.",
|
||||
@@ -1222,6 +1261,7 @@
|
||||
"organizer_name_variable": "Nom de l'organisateur",
|
||||
"app_upgrade_description": "Pour pouvoir utiliser cette fonctionnalité, vous devez passer à un compte Pro.",
|
||||
"invalid_number": "Numéro de téléphone invalide",
|
||||
"invalid_url_error_message": "URL non valide pour {{label}}. Exemple d'URL : {{sampleUrl}}",
|
||||
"navigate": "Naviguer",
|
||||
"open": "Ouvrir",
|
||||
"close": "Fermer",
|
||||
@@ -1260,6 +1300,7 @@
|
||||
"default_calendar_selected": "Calendrier par défaut",
|
||||
"hide_from_profile": "Masquer du profil",
|
||||
"event_setup_tab_title": "Configuration",
|
||||
"availability_not_found_in_schedule_error": "Aucune disponibilité trouvée dans le planning",
|
||||
"event_limit_tab_title": "Limites",
|
||||
"event_limit_tab_description": "Fréquence de réservation",
|
||||
"event_advanced_tab_description": "Paramètres du calendrier, etc.",
|
||||
@@ -1280,6 +1321,7 @@
|
||||
"do_this": "Effectuer ceci",
|
||||
"turn_off": "Désactiver",
|
||||
"turn_on": "Activer",
|
||||
"cancelled_bookings_cannot_be_rescheduled": "Les réservations annulées ne peuvent pas être reprogrammées",
|
||||
"settings_updated_successfully": "Paramètres mis à jour avec succès",
|
||||
"error_updating_settings": "Erreur lors de la mise à jour des paramètres",
|
||||
"personal_cal_url": "Mon lien {{appName}} personnel",
|
||||
@@ -1296,12 +1338,14 @@
|
||||
"select_calendars": "Sélectionnez les calendriers dont vous souhaitez vérifier les conflits afin d'éviter les doubles réservations.",
|
||||
"check_for_conflicts": "Vérifier les conflits",
|
||||
"view_recordings": "Voir les enregistrements",
|
||||
"check_for_recordings": "Chercher des enregistrements",
|
||||
"adding_events_to": "Ajout d'événements",
|
||||
"follow_system_preferences": "Suivre les préférences système",
|
||||
"custom_brand_colors": "Couleurs de marque personnalisées",
|
||||
"customize_your_brand_colors": "Personnalisez la couleur de votre page de réservation à l'image de votre marque.",
|
||||
"pro": "Pro",
|
||||
"removes_cal_branding": "Supprimez toutes les références à la marque {{appName}}, par exemple « Propulsé par {{appName}} ».",
|
||||
"instant_meeting_with_title": "Rendez-vous instantané avec {{name}}",
|
||||
"profile_picture": "Photo de profil",
|
||||
"upload": "Télécharger",
|
||||
"add_profile_photo": "Ajouter une photo de profil",
|
||||
@@ -1357,6 +1401,7 @@
|
||||
"event_name_info": "Nom du type d'événement",
|
||||
"event_date_info": "Date de l'événement",
|
||||
"event_time_info": "Heure de début de l'événement",
|
||||
"event_type_not_found": "Type d'événement introuvable",
|
||||
"location_variable": "Lieu",
|
||||
"location_info": "Lieu de l'événement",
|
||||
"additional_notes_variable": "Notes supplémentaires",
|
||||
@@ -1399,6 +1444,7 @@
|
||||
"slot_length": "Durée du créneau",
|
||||
"booking_appearance": "Apparence",
|
||||
"appearance_team_description": "Gérez les paramètres d'apparence de vos réservations d'équipe.",
|
||||
"appearance_org_description": "Gérez les paramètres d'apparence de vos réservations d'organisation",
|
||||
"only_owner_change": "Seul le propriétaire de cette équipe peut apporter des modifications à la réservation de l'équipe ",
|
||||
"team_disable_cal_branding_description": "Supprimez toutes les références à la marque {{appName}}, par exemple « Propulsé par {{appName}} ».",
|
||||
"invited_by_team": "{{teamName}} vous a invité à rejoindre son équipe en tant que {{role}}",
|
||||
@@ -1452,6 +1498,7 @@
|
||||
"password_updated": "Mot de passe mis à jour !",
|
||||
"pending_payment": "Paiement en attente",
|
||||
"pending_invites": "Invitations en attente",
|
||||
"pending_organization_invites": "Invitations de l'organisation en attente",
|
||||
"not_on_cal": "Pas sur {{appName}}",
|
||||
"no_calendar_installed": "Aucun calendrier installé",
|
||||
"no_calendar_installed_description": "Vous n'avez pas encore connecté vos calendriers.",
|
||||
@@ -1463,6 +1510,8 @@
|
||||
"report_app": "Signaler l'application",
|
||||
"limit_booking_frequency": "Limiter la fréquence de réservation",
|
||||
"limit_booking_frequency_description": "Limitez le nombre de réservations possibles pour cet événement.",
|
||||
"limit_booking_only_first_slot": "Limiter la réservation à un seul créneau",
|
||||
"limit_booking_only_first_slot_description": "N'autoriser la réservation que du premier créneau de chaque jour",
|
||||
"limit_total_booking_duration": "Limiter la durée totale de réservation",
|
||||
"limit_total_booking_duration_description": "Limitez la durée totale pendant laquelle cet événement peut être réservé.",
|
||||
"add_limit": "Ajouter une limite",
|
||||
@@ -1535,6 +1584,9 @@
|
||||
"member_already_invited": "Le membre a déjà été invité",
|
||||
"already_in_use_error": "Nom d'utilisateur déjà utilisé",
|
||||
"enter_email_or_username": "Saisissez une adresse e-mail ou un nom d'utilisateur",
|
||||
"enter_email": "Saisissez une adresse e-mail",
|
||||
"enter_emails": "Saisissez des adresses e-mail",
|
||||
"too_many_invites": "Vous ne pouvez inviter qu'un maximum de {{nbUsers}} utilisateurs à la fois.",
|
||||
"team_name_taken": "Ce nom est déjà pris",
|
||||
"must_enter_team_name": "Vous devez saisir un nom d'équipe",
|
||||
"team_url_required": "Vous devez saisir un lien d'équipe",
|
||||
@@ -1614,6 +1666,7 @@
|
||||
"individual": "Particulier",
|
||||
"all_bookings_filter_label": "Toutes les réservations",
|
||||
"all_users_filter_label": "Tous les utilisateurs",
|
||||
"all_event_types_filter_label": "Tous les types d'événement",
|
||||
"your_bookings_filter_label": "Vos réservations",
|
||||
"meeting_url_variable": "Lien du rendez-vous",
|
||||
"meeting_url_info": "Lien de la visioconférence du rendez-vous",
|
||||
@@ -1628,6 +1681,7 @@
|
||||
"date_overrides_mark_all_day_unavailable_other": "Marquer comme indisponible aux dates sélectionnées",
|
||||
"date_overrides_add_btn": "Ajouter la superposition",
|
||||
"date_overrides_update_btn": "Mettre à jour la superposition",
|
||||
"date_successfully_added": "Ajout du remplacement de date réussi",
|
||||
"event_type_duplicate_copy_text": "{{slug}}-copie",
|
||||
"set_as_default": "Définir par défaut",
|
||||
"hide_eventtype_details": "Masquer les détails du type d'événement",
|
||||
@@ -1654,6 +1708,7 @@
|
||||
"minimum_round_robin_hosts_count": "Nombre d'hôtes requis pour participer",
|
||||
"hosts": "Hôtes",
|
||||
"upgrade_to_enable_feature": "Vous devez créer une équipe pour activer cette fonctionnalité. Cliquez pour créer une équipe.",
|
||||
"orgs_upgrade_to_enable_feature": "Vous devez passer sur notre formule entreprise pour activer cette fonctionnalité.",
|
||||
"new_attendee": "Nouveau participant",
|
||||
"awaiting_approval": "En attente de validation",
|
||||
"requires_google_calendar": "Cette application nécessite une connexion à Google Agenda",
|
||||
@@ -1708,9 +1763,11 @@
|
||||
"organizer_timezone": "Fuseau horaire de l'organisateur",
|
||||
"email_user_cta": "Voir l'invitation",
|
||||
"email_no_user_invite_heading_team": "Vous avez été invité à rejoindre une équipe {{appName}}",
|
||||
"email_no_user_invite_heading_subteam": "Vous avez été invité(e) à rejoindre une équipe de l'organisation {{parentTeamName}}",
|
||||
"email_no_user_invite_heading_org": "Vous avez été invité à rejoindre une organisation {{appName}}",
|
||||
"email_no_user_invite_subheading": "{{invitedBy}} vous a invité à rejoindre son équipe sur {{appName}}. {{appName}} est le planificateur d'événements qui vous permet à vous et à votre équipe d'organiser des rendez-vous sans échanges d'e-mails.",
|
||||
"email_user_invite_subheading_team": "{{invitedBy}} vous a invité à rejoindre son équipe « {{teamName}} » sur {{appName}}. {{appName}} est le planificateur d'événements qui vous permet à vous et à votre équipe d'organiser des rendez-vous sans échanges d'e-mails.",
|
||||
"email_user_invite_subheading_subteam": "{{invitedBy}} vous a invité(e) à rejoindre son équipe {{teamName}} dans son organisation {{parentTeamName}} sur {{appName}}. {{appName}} est le planificateur d'événements qui vous permet à vous et à votre équipe d'organiser des rendez-vous sans échanges d'e-mails.",
|
||||
"email_user_invite_subheading_org": "{{invitedBy}} vous a invité à rejoindre son organisation « {{teamName}} » sur {{appName}}. {{appName}} est le planificateur d'événements qui vous permet à vous et à votre organisation d'organiser des rendez-vous sans échanges d'e-mails.",
|
||||
"email_no_user_invite_steps_intro": "Nous vous guiderons à travers quelques étapes courtes et vous profiterez d'une planification sans stress avec votre {{entity}} en un rien de temps.",
|
||||
"email_no_user_step_one": "Choisissez votre nom d'utilisateur",
|
||||
@@ -1843,6 +1900,7 @@
|
||||
"review_event_type": "Examiner le type d'événement",
|
||||
"looking_for_more_analytics": "Vous cherchez plus d'analyses ?",
|
||||
"looking_for_more_insights": "Vous cherchez plus de statistiques ?",
|
||||
"filters": "Filtres",
|
||||
"add_filter": "Ajouter un filtre",
|
||||
"remove_filters": "Effacer les filtres",
|
||||
"select_user": "Sélectionner un utilisateur",
|
||||
@@ -1935,6 +1993,7 @@
|
||||
"connect_google_workspace": "Connecter Google Workspace",
|
||||
"google_workspace_admin_tooltip": "Vous devez être un administrateur d'espace de travail pour utiliser cette fonctionnalité",
|
||||
"first_event_type_webhook_description": "Créez votre premier webhook pour ce type d'événement",
|
||||
"create_instant_meeting_webhook_description": "Créez votre premier webhook avec « Rendez-vous instantané créé » comme déclencheur pour ce type d'événement",
|
||||
"install_app_on": "Installer l’application sur",
|
||||
"create_for": "Créer pour",
|
||||
"currency": "Devise",
|
||||
@@ -1999,6 +2058,8 @@
|
||||
"add_times_to_your_email": "Sélectionnez quelques créneaux disponibles et intégrez-les dans votre e-mail.",
|
||||
"select_time": "Sélectionner un créneau",
|
||||
"select_date": "Sélectionner une date",
|
||||
"connecting_you_to_someone": "Nous vous mettons en relation avec quelqu'un.",
|
||||
"please_do_not_close_this_tab": "Veuillez ne pas fermer cet onglet",
|
||||
"see_all_available_times": "Voir tous les créneaux disponibles",
|
||||
"org_team_names_example_1": "p. ex. Équipe marketing",
|
||||
"org_team_names_example_2": "p. ex. Équipe de vente",
|
||||
@@ -2016,8 +2077,12 @@
|
||||
"description_requires_booker_email_verification": "Pour assurer la vérification par e-mail du participant avant la planification des événements.",
|
||||
"requires_confirmation_mandatory": "Les messages ne peuvent être envoyés aux participants que lorsque le type d'événement nécessite une confirmation.",
|
||||
"organizations": "Organisations",
|
||||
"upload_cal_video_logo": "Téléverser un logo vidéo Cal",
|
||||
"update_cal_video_logo": "Mettre à jour un logo vidéo Cal",
|
||||
"cal_video_logo_upload_instruction": "Pour assurer que votre logo reste visible sur le fond sombre de la vidéo Cal, veuillez téléverser une image de couleur claire au format PNG ou SVG afin de maintenir la transparence.",
|
||||
"org_admin_other_teams": "Autres équipes",
|
||||
"org_admin_other_teams_description": "Ici, vous pouvez voir les équipes au sein de votre organisation dont vous ne faites pas partie. Vous pouvez vous y ajouter si nécessaire.",
|
||||
"not_part_of_org": "Vous ne faites partie d'aucune organisation",
|
||||
"no_other_teams_found": "Aucune autre équipe trouvée",
|
||||
"no_other_teams_found_description": "Il n'y a pas d'autres équipes dans cette organisation.",
|
||||
"attendee_first_name_variable": "Prénom du participant",
|
||||
@@ -2053,52 +2118,149 @@
|
||||
"org_error_processing": "Une erreur s'est produite lors du traitement de cette organisation",
|
||||
"orgs_page_description": "Une liste de toutes les organisations. Accepter une organisation permettra à tous les utilisateurs avec ce domaine de messagerie de s'inscrire SANS vérification d'e-mail.",
|
||||
"unverified": "Non vérifié",
|
||||
"verified": "Vérifié",
|
||||
"dns_missing": "DNS manquant",
|
||||
"dns_configured": "DNS configuré",
|
||||
"mark_dns_configured": "Marquer comme DNS configuré",
|
||||
"value": "Valeur",
|
||||
"your_organization_updated_sucessfully": "Votre organisation a été mise à jour avec succès",
|
||||
"team_no_event_types": "Cette équipe n'a aucun type d'événement",
|
||||
"seat_options_doesnt_multiple_durations": "L'option par place ne prend pas en charge les durées multiples",
|
||||
"include_calendar_event": "Inclure l'événement du calendrier",
|
||||
"oAuth": "OAuth",
|
||||
"recently_added": "Ajoutées récemment",
|
||||
"connect_all_calendars": "Connectez vos calendriers",
|
||||
"connect_all_calendars_description": "{{appName}} relève la disponibilité de tous vos calendriers existants.",
|
||||
"workflow_automation": "Automatisation de workflows",
|
||||
"workflow_automation_description": "Personnalisez votre expérience de planification avec les workflows",
|
||||
"scheduling_for_your_team": "Automatisation de workflows",
|
||||
"scheduling_for_your_team_description": "Planifiez pour votre équipe grâce à la planification collective et par permutation circulaire",
|
||||
"no_members_found": "Aucun membre trouvé",
|
||||
"event_setup_length_error": "Configuration de l'événement : la durée doit être d'au moins 1 minute.",
|
||||
"availability_schedules": "Horaires de disponibilité",
|
||||
"unauthorized": "Non autorisé",
|
||||
"access_cal_account": "{{clientName}} aimerait accéder à votre compte {{appName}}",
|
||||
"select_account_team": "Sélectionner un compte ou une équipe",
|
||||
"allow_client_to": "Cela permettra au {{clientName}} de",
|
||||
"associate_with_cal_account": "Vous associer à vos informations personnelles de {{clientName}}",
|
||||
"see_personal_info": "Voir vos informations personnelles, y compris celles que vous avez rendues publiques",
|
||||
"see_primary_email_address": "Voir votre adresse e-mail principale",
|
||||
"connect_installed_apps": "Vous connecter à vos applications installées",
|
||||
"access_event_type": "Lire, modifier, supprimer vos types d'événements",
|
||||
"access_availability": "Lire, modifier, supprimer vos disponibilités",
|
||||
"access_bookings": "Lire, modifier, supprimer vos réservations",
|
||||
"allow_client_to_do": "Autoriser {{clientName}} à faire cela ?",
|
||||
"oauth_access_information": "En cliquant sur autoriser, vous autorisez cette application à utiliser vos informations conformément à ses conditions de service et à sa politique de confidentialité. Vous pouvez supprimer l'accès dans l'App Store de {{appName}}.",
|
||||
"allow": "Autoriser",
|
||||
"view_only_edit_availability_not_onboarded": "Cet utilisateur n'a pas terminé son intégration. Vous ne pourrez pas définir sa disponibilité tant qu'il n'aura pas terminé son intégration.",
|
||||
"view_only_edit_availability": "Vous consultez les disponibilités de cet utilisateur. Vous ne pouvez modifier que vos propres disponibilités.",
|
||||
"you_can_override_calendar_in_advanced_tab": "Vous pouvez modifier ceci pour chaque événement dans les paramètres avancés de chaque type d'événement.",
|
||||
"edit_users_availability": "Modifier la disponibilité de l'utilisateur : {{username}}",
|
||||
"resend_invitation": "Renvoyer l'invitation",
|
||||
"invitation_resent": "L'invitation a été renvoyée.",
|
||||
"saml_sso": "SAML",
|
||||
"add_client": "Ajouter un client",
|
||||
"copy_client_secret_info": "Après avoir copié le secret, vous ne pourrez plus le voir",
|
||||
"add_new_client": "Ajouter un nouveau client",
|
||||
"this_app_is_not_setup_already": "Cette application n'a pas encore été configurée",
|
||||
"as_csv": "au format CSV",
|
||||
"overlay_my_calendar": "Superposer mon calendrier",
|
||||
"overlay_my_calendar_toc": "En vous connectant à votre calendrier, vous acceptez notre politique de confidentialité et nos conditions d'utilisation. Vous pouvez révoquer cet accès à tout moment.",
|
||||
"view_overlay_calendar_events": "Consultez les événements de votre calendrier afin d'éviter les réservations incompatibles.",
|
||||
"join_event_location": "Rejoignez {{eventLocationType}}",
|
||||
"troubleshooting": "Dépannage",
|
||||
"calendars_were_checking_for_conflicts": "Calendriers dont nous vérifions les conflits",
|
||||
"availabilty_schedules": "Horaires de disponibilité",
|
||||
"manage_calendars": "Gérer les calendriers",
|
||||
"manage_availability_schedules": "Gérer les horaires de disponibilité",
|
||||
"lock_timezone_toggle_on_booking_page": "Verrouiller le fuseau horaire sur la page de réservation",
|
||||
"description_lock_timezone_toggle_on_booking_page": "Pour verrouiller le fuseau horaire sur la page de réservation, utile pour les événements en personne.",
|
||||
"event_setup_multiple_payment_apps_error": "Vous ne pouvez activer qu'une seule application de paiement par type d'événement.",
|
||||
"number_in_international_format": "Veuillez entrer le numéro au format international.",
|
||||
"install_calendar": "Installer Calendrier",
|
||||
"branded_subdomain": "Sous-domaine de marque",
|
||||
"branded_subdomain_description": "Obtenez votre propre sous-domaine de marque, tel que acme.cal.com",
|
||||
"org_insights": "Statistiques à l'échelle de l'organisation",
|
||||
"org_insights_description": "Comprenez comment l'ensemble de votre organisation passe son temps",
|
||||
"extensive_whitelabeling": "Marque blanche étendue",
|
||||
"extensive_whitelabeling_description": "Personnalisez votre expérience de planification avec votre propre logo, vos couleurs, etc.",
|
||||
"unlimited_teams": "Équipes illimitées",
|
||||
"unlimited_teams_description": "Ajoutez autant de sous-équipes que nécessaire à votre organisation",
|
||||
"unified_billing": "Facturation unifiée",
|
||||
"unified_billing_description": "Ajoutez une seule carte de crédit pour payer tous les abonnements de votre équipe",
|
||||
"advanced_managed_events": "Types d'événements gérés avancés",
|
||||
"advanced_managed_events_description": "Ajoutez une seule carte de crédit pour payer tous les abonnements de votre équipe",
|
||||
"enterprise_description": "Passez en Entreprise pour créer votre organisation",
|
||||
"create_your_org": "Créer votre organisation",
|
||||
"create_your_org_description": "Passez en Entreprise et recevez un sous-domaine, une facturation unifiée, des statistiques, un marquage blanc extensif et plus encore",
|
||||
"other_payment_app_enabled": "Vous ne pouvez activer qu'une seule application de paiement par type d'événement",
|
||||
"admin_delete_organization_description": "<ul><li>Les équipes qui sont membres de cette organisation seront également supprimées, ainsi que leurs types d'événements</li><li>Les utilisateurs qui faisaient partie de l'organisation ne seront pas supprimés et leurs types d'événements resteront intacts.</li><li>Les noms d'utilisateur seront modifiés pour leur permettre d'exister en dehors de l'organisation</li></ul>",
|
||||
"admin_delete_organization_title": "Supprimer {{organizationName}} ?",
|
||||
"published": "Publiée",
|
||||
"unpublished": "Non publiée",
|
||||
"publish": "Publier",
|
||||
"org_publish_error": "L'organisation n'a pas pu être publiée",
|
||||
"troubleshooter_tooltip": "Ouvrez l'outil de dépannage et déterminez ce qui ne va pas avec votre planning",
|
||||
"need_help": "Besoin d'aide ?",
|
||||
"troubleshooter": "Dépannage",
|
||||
"please_install_a_calendar": "Veuillez installer un publier",
|
||||
"instant_tab_title": "Réservation instantanée",
|
||||
"instant_event_tab_description": "Autoriser les gens à réserver immédiatement",
|
||||
"uprade_to_create_instant_bookings": "Passez en Entreprise et autorisez les invités à rejoindre un appel instantané auquel les participants peuvent accéder directement. Ceci n'est valable que pour les types d'événements d'équipe",
|
||||
"dont_want_to_wait": "Vous ne voulez pas attendre ?",
|
||||
"meeting_started": "Le rendez-vous a commencé",
|
||||
"pay_and_book": "Payer pour réserver",
|
||||
"booking_not_found_error": "Impossible de trouver la réservation",
|
||||
"booking_seats_full_error": "Les places de réservation sont complètes",
|
||||
"missing_payment_credential_error": "Identifiants de paiement manquants",
|
||||
"missing_payment_app_id_error": "Id de l'application de paiement manquant",
|
||||
"not_enough_available_seats_error": "La réservation n'a pas assez de places disponibles",
|
||||
"user_redirect_title": "{{username}} est actuellement en absence de courte durée.",
|
||||
"user_redirect_description": "Pendant ce temps, {{profile.username}} sera responsable de tous les rendez-vous programmés de la part de {{username}}.",
|
||||
"out_of_office": "Pas au bureau",
|
||||
"out_of_office_description": "Configurez des actions dans votre profil pendant votre absence.",
|
||||
"send_request": "Envoyer une demande",
|
||||
"start_date_and_end_date_required": "Les dates de début et de fin sont requises",
|
||||
"start_date_must_be_before_end_date": "La date de début doit être antérieure à la date de fin",
|
||||
"start_date_must_be_in_the_future": "La date de début doit être dans le futur",
|
||||
"user_not_found": "Utilisateur introuvable",
|
||||
"out_of_office_entry_already_exists": "L'entrée Pas au bureau existe déjà",
|
||||
"out_of_office_id_required": "L'id de l'entrée Pas au bureau est requis",
|
||||
"booking_redirect_infinite_not_allowed": "Une redirection de réservation de cet utilisateur vers vous existe déjà.",
|
||||
"success_entry_created": "Création d'une nouvelle entrée réussie",
|
||||
"booking_redirect_email_subject": "Notification de redirection de réservation",
|
||||
"booking_redirect_email_title": "Notification de redirection de réservation",
|
||||
"booking_redirect_email_description": "Vous avez reçu une redirection de réservation de {{toName}} pour que les liens vers son profil soient redirigés vers vous pendant l'intervalle de temps : ",
|
||||
"success_accept_booking_redirect": "Vous avez accepté cette demande de redirection de réservation.",
|
||||
"success_reject_booking_redirect": "Vous avez refusé cette demande de redirection de réservation.",
|
||||
"copy_link_booking_redirect_request": "Copier le lien pour partager la demande",
|
||||
"booking_redirect_request_title": "Demande de redirection de réservation",
|
||||
"select_team_member": "Sélectionner un membre de l'équipe",
|
||||
"going_away_title": "Vous partez ? Marquez simplement le lien de votre profil comme étant indisponible pendant un certain temps.",
|
||||
"redirect_team_enabled": "Redirigez votre profil vers un autre membre de l'équipe",
|
||||
"redirect_team_disabled": "Redirigez votre profil vers un autre membre de l'équipe (formule Équipe requise)",
|
||||
"out_of_office_unavailable_list": "Liste des indisponibilités Pas au bureau",
|
||||
"success_deleted_entry_out_of_office": "Suppression de l'entrée réussie",
|
||||
"temporarily_out_of_office": "Temporairement Pas au bureau ?",
|
||||
"add_a_redirect": "Ajouter une redirection",
|
||||
"create_entry": "Créer une entrée",
|
||||
"time_range": "Plage temporelle",
|
||||
"automatically_add_all_team_members": "Ajouter tous les membres de l'équipe, y compris les futurs membres",
|
||||
"redirect_to": "Redirection vers",
|
||||
"having_trouble_finding_time": "Vous avez du mal à trouver une heure ?",
|
||||
"show_more": "En voir plus",
|
||||
"assignment_description": "Planifiez des rendez-vous lorsque tout le monde est disponible ou faites tourner les membres de votre équipe",
|
||||
"lowest": "le plus bas",
|
||||
"low": "bas",
|
||||
"medium": "moyen",
|
||||
"high": "élevé",
|
||||
"Highest": "le plus élevé",
|
||||
"send_booker_to": "Envoyer un utilisateur vers",
|
||||
"set_priority": "Définir la priorité",
|
||||
"priority_for_user": "Priorité pour {{userName}}",
|
||||
"change_priority": "changer la priorité",
|
||||
"field_identifiers_as_variables": "Utiliser les identifiants de champs comme variables pour la redirection de votre événement personnalisé",
|
||||
"field_identifiers_as_variables_with_example": "Utiliser les identifiants de champs comme variables pour la redirection de votre événement personnalisé (par ex. : {{variable}})",
|
||||
"account_already_linked": "Le compte est déjà associé",
|
||||
"ADD_NEW_STRINGS_ABOVE_THIS_LINE_TO_PREVENT_MERGE_CONFLICTS": "↑↑↑↑↑↑↑↑↑↑↑↑↑ Ajoutez vos nouvelles chaînes ci-dessus ↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑"
|
||||
}
|
||||
|
||||
@@ -807,6 +807,8 @@
|
||||
"additional_input_description": "דרוש מהמתזמן להזין נתונים נוספים לפני אישור התזמון",
|
||||
"label": "תווית",
|
||||
"placeholder": "מציין מיקום",
|
||||
"display_add_to_calendar_organizer": "הצגת דוא״ל ‚הוספה ללוח השנה’ כגוף מארגן",
|
||||
"reconnect_calendar_to_use": "נא לשים לב שכנראה צריך להתנתק ואז להתחבר ללוח השנה שלך מחדש כדי להשתמש ביכולת הזאת.",
|
||||
"type": "סוג",
|
||||
"edit": "עריכה",
|
||||
"add_input": "הוספת קלט",
|
||||
@@ -1473,6 +1475,8 @@
|
||||
"copy_link_to_form": "להעתיק את הקישור לטופס",
|
||||
"theme": "ערכת נושא",
|
||||
"theme_applies_note": "חלה רק על דפים של הזמנות ציבוריות",
|
||||
"app_theme": "ערכת עיצוב ללוח בקרה",
|
||||
"app_theme_applies_note": "חל רק על לוח הבקרה שנכנסת אליו",
|
||||
"theme_system": "ברירת המחדל של המערכת",
|
||||
"add_a_team": "הוספת צוות",
|
||||
"add_webhook_description": "לקבל נתוני פגישה בזמן אמת כשמשהו קורה ב-{{appName}}",
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
{
|
||||
"identity_provider": "Azonosítás szolgáltató",
|
||||
"trial_days_left": "Még $t(day, {\"count\": {{days}} }) maradt a PRO próbaidőszakodból",
|
||||
"day_one": "{{count}} nap",
|
||||
"day_other": "{{count}} nap",
|
||||
@@ -7,6 +8,7 @@
|
||||
"upgrade_now": "Frissíts most",
|
||||
"accept_invitation": "Meghívó elfogadása",
|
||||
"have_any_questions": "Van kérdésed? Itt vagyunk hogy segítsünk.",
|
||||
"verify_email_email_button": "Email igazolása",
|
||||
"organizer": "Szervező",
|
||||
"rejection_reason": "Lemondás oka",
|
||||
"help": "Segítség",
|
||||
@@ -160,7 +162,7 @@
|
||||
"yearly_one": "év",
|
||||
"yearly_other": "év",
|
||||
"name": "Név",
|
||||
"logo": "Logo",
|
||||
"logo": "Logó",
|
||||
"error": "Hiba",
|
||||
"type": "Típus",
|
||||
"edit": "Szerkesztés",
|
||||
|
||||
@@ -13,10 +13,17 @@
|
||||
"reset_password_subject": "{{appName}}: istruzioni per reimpostare la password",
|
||||
"verify_email_subject": "{{appName}}: verifica il tuo account",
|
||||
"check_your_email": "Controlla la tua e-mail",
|
||||
"old_email_address": "Vecchio indirizzo email",
|
||||
"new_email_address": "Nuovo indirizzo email",
|
||||
"verify_email_page_body": "Abbiamo inviato un'e-mail a {{email}}. È importante verificare il tuo indirizzo e-mail per garantire la consegna delle e-mail e delle notifiche del calendario {{appName}}.",
|
||||
"verify_email_banner_body": "Verifica il tuo indirizzo e-mail per garantire la consegna delle e-mail e delle notifiche del calendario",
|
||||
"verify_email_email_header": "Verifica il tuo indirizzo e-mail",
|
||||
"verify_email_email_button": "Verifica l'e-mail",
|
||||
"verify_email_change_description": "Hai recentemente chiesto di cambiare l'indirizzo email che utilizzi per accedere al tuo account {{appName}}. Fai clic sul pulsante qui sotto pe confermare il tuo nuovo indirizzo email.",
|
||||
"verify_email_change_success_toast": "Indirizzo email aggiornato: {{email}}",
|
||||
"verify_email_change_failure_toast": "Aggiornamento email non riuscito.",
|
||||
"change_of_email": "Verifica il tuo nuovo indirizzo email per {{appName}}",
|
||||
"change_of_email_toast": "Abbiamo inviato un link di verifica a {{email}}. Aggiorneremo il tuo indirizzo email quando farai clic sul link.",
|
||||
"copy_somewhere_safe": "Salva questa chiave API in un posto sicuro. Non potrai visualizzarla in seguito.",
|
||||
"verify_email_email_body": "Verifica il tuo indirizzo e-mail facendo clic sul pulsante qui sotto.",
|
||||
"verify_email_by_code_email_body": "Verifica il tuo indirizzo e-mail utilizzando il codice sottostante.",
|
||||
@@ -56,6 +63,17 @@
|
||||
"a_refund_failed": "Un rimborso non è riuscito",
|
||||
"awaiting_payment_subject": "In attesa del pagamento: {{title}} il {{date}}",
|
||||
"meeting_awaiting_payment": "La tua riunione è in attesa di pagamento",
|
||||
"dark_theme_contrast_error": "Il colore del tema scuro non supera il controllo del contrasto. Ti consigliamo di cambiare questo colore in modo che i pulsanti siano più visibili.",
|
||||
"light_theme_contrast_error": "Il colore del tema chiaro non supera il controllo del contrasto. Ti consigliamo di cambiare questo colore in modo che i pulsanti siano più visibili.",
|
||||
"payment_not_created_error": "Non è stato possibile creare il pagamento",
|
||||
"couldnt_charge_card_error": "Non è stato possibile addebitare la carta per il pagamento",
|
||||
"no_available_users_found_error": "Nessun utente disponibile. Puoi provare un altro intervallo temporale?",
|
||||
"request_body_end_time_internal_error": "Errore interno. La richiesta non contiene l'ora finale",
|
||||
"create_calendar_event_error": "Impossibile creare l'evento nel calendario dell'organizzatore",
|
||||
"update_calendar_event_error": "Impossibile aggiornare l'evento nel calendario.",
|
||||
"delete_calendar_event_error": "Impossibile eliminare l'evento dal calendario.",
|
||||
"already_signed_up_for_this_booking_error": "Ti sei già registrato per questa prenotazione.",
|
||||
"hosts_unavailable_for_booking": "Alcuni degli organizzatori non sono disponibili per la prenotazione.",
|
||||
"help": "Guida",
|
||||
"price": "Prezzo",
|
||||
"paid": "Pagato",
|
||||
@@ -101,6 +119,7 @@
|
||||
"requested_to_reschedule_subject_attendee": "Azione richiesta per la riprogrammazione: prenota un nuovo orario per {{eventType}} con {{name}}",
|
||||
"hi_user_name": "Ciao {{name}}",
|
||||
"ics_event_title": "{{eventType}} con {{name}}",
|
||||
"please_book_a_time_sometime_later": "Oops, non siamo riusciti a collegarti. Pianifica una chiamata futura",
|
||||
"new_event_subject": "Richiesta di nuovo evento: {{attendeeName}} - {{date}} - {{eventType}}",
|
||||
"join_by_entrypoint": "Entra da {{entryPoint}}",
|
||||
"notes": "Osservazioni",
|
||||
@@ -117,15 +136,20 @@
|
||||
"meeting_id": "Meeting ID",
|
||||
"meeting_password": "Password del meeting",
|
||||
"meeting_url": "URL del Meeting",
|
||||
"meeting_url_not_found": "URL della riunione non trovato",
|
||||
"token_not_found": "Token non trovato",
|
||||
"some_other_host_already_accepted_the_meeting": "Qualche altro host ha già accettato la riunione. Vuoi comunque partecipare? <1>Prosegui con la riunione</1>",
|
||||
"meeting_request_rejected": "La tua richiesta di riunione è stata rifiutata",
|
||||
"rejected_event_type_with_organizer": "Rifiutato: {{eventType}} con {{organizer}} il {{date}}",
|
||||
"hi": "Ciao",
|
||||
"join_team": "Unisciti al team",
|
||||
"manage_this_team": "Gestisci questo team",
|
||||
"team_info": "Informazioni sul team",
|
||||
"join_meeting": "Partecipa alla riunione",
|
||||
"request_another_invitation_email": "Se preferisci non usare {{toEmail}} come email {{appName}} o già hai un account {{appName}}, per favore richiedi un altro invito a quella email.",
|
||||
"you_have_been_invited": "Sei stato invitato ad unirti al team {{teamName}}",
|
||||
"user_invited_you": "{{user}} ti ha invitato a unirti a {{entity}} {{team}} su {{appName}}",
|
||||
"user_invited_you_to_subteam": "{{user}} ti ha invitato a entrare nel team {{team}} dell'organizzazione {{parentTeamName}} su {{appName}}",
|
||||
"hidden_team_member_title": "Sei nascosto in questo team",
|
||||
"hidden_team_member_message": "Il tuo posto non è pagato. Passa a Pro oppure informa il proprietario del team che può pagare il tuo posto.",
|
||||
"hidden_team_owner_message": "Per utilizzare i team ti occorre un account Pro. Sarai nascosto finché non cambierai piano.",
|
||||
@@ -229,6 +253,7 @@
|
||||
"reset_your_password": "Imposta la nuova password seguendo le istruzioni che ti sono state inviate via e-mail.",
|
||||
"email_change": "Accedi di nuovo con il nuovo indirizzo e-mail e la nuova password.",
|
||||
"create_your_account": "Crea il tuo account",
|
||||
"create_your_calcom_account": "Crea il tuo accout Cal.com",
|
||||
"sign_up": "Registrati",
|
||||
"youve_been_logged_out": "Hai fatto il log out",
|
||||
"hope_to_see_you_soon": "Speriamo di rivederti presto!",
|
||||
@@ -266,6 +291,9 @@
|
||||
"nearly_there_instructions": "Infine: qualche informazione su di te e una foto sono utili per ottenere prenotazioni e per far capire alle persone con chi stanno prenotando.",
|
||||
"set_availability_instructions": "Definisci intervalli di tempo di quando sei disponibile su una base ricorrente. Puoi crearne di più in seguito e assegnarli a calendari diversi.",
|
||||
"set_availability": "Imposta la tua disponibilità",
|
||||
"set_availbility_description": "Programma gli orari in cui vuoi essere prenotato.",
|
||||
"share_a_link_or_embed": "Condividi o pubblica un link",
|
||||
"share_a_link_or_embed_description": "Condividi il tuo link {{appName}} o inseriscilo nel tuo sito.",
|
||||
"availability_settings": "Impostazioni di disponibilità",
|
||||
"continue_without_calendar": "Continua senza calendario",
|
||||
"continue_with": "Continua con {{appName}}",
|
||||
@@ -281,6 +309,7 @@
|
||||
"welcome_to_calcom": "Benvenuto in {{appName}}",
|
||||
"welcome_instructions": "Dimmi come chiamarti e facci sapere in che timezone sei. Potrai cambiarlo dopo.",
|
||||
"connect_caldav": "Connetti al server CalDav",
|
||||
"connect_ics_feed": "Collega a un feed ICS",
|
||||
"connect": "Connetti",
|
||||
"try_for_free": "Provalo gratuitamente",
|
||||
"create_booking_link_with_calcom": "Crea il tuo link di prenotazione con {{appName}}",
|
||||
@@ -419,6 +448,7 @@
|
||||
"browse_api_documentation": "Sfoglia la nostra documentazione API",
|
||||
"leverage_our_api": "Sfrutta la nostra API per il pieno controllo e la personalizzabilità.",
|
||||
"create_webhook": "Crea Webhook",
|
||||
"instant_meeting": "Riunione immediata creata",
|
||||
"booking_cancelled": "Prenotazione cancellata",
|
||||
"booking_rescheduled": "Prenotazione Riprogrammata",
|
||||
"recording_ready": "Il link per scaricare la registrazione è pronto",
|
||||
@@ -533,11 +563,13 @@
|
||||
"enter_number_between_range": "Insrisci un numero compreso tra 1 e {{maxOccurences}}",
|
||||
"email_address": "Indirizzo email",
|
||||
"enter_valid_email": "Immettere un indirizzo e-mail valido",
|
||||
"please_schedule_future_call": " Pianifica una chiamata futura se non siamo disponibili entro {{seconds}} secondi",
|
||||
"location": "Luogo",
|
||||
"address": "Indirizzo",
|
||||
"enter_address": "Immettere l'indirizzo",
|
||||
"in_person_attendee_address": "Di persona (indirizzo del partecipante)",
|
||||
"yes": "Sì",
|
||||
"no": "No",
|
||||
"additional_notes": "Note aggiuntive",
|
||||
"booking_fail": "Impossibile prenotare la riunione.",
|
||||
"reschedule_fail": "Impossibile riprogrammare la riunione.",
|
||||
@@ -591,6 +623,7 @@
|
||||
"new_member": "Nuovo Membro",
|
||||
"invite": "Invita",
|
||||
"add_team_members": "Aggiungi membri del team",
|
||||
"add_org_members": "Aggiungi membri",
|
||||
"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",
|
||||
@@ -605,6 +638,7 @@
|
||||
"hide_book_a_team_member_description": "Nasconde il pulsante Prenota un membro del team nelle pagine pubbliche.",
|
||||
"danger_zone": "Zona pericolosa",
|
||||
"account_deletion_cannot_be_undone": "Attenzione. Non è possibile annullare l'eliminazione dell'account.",
|
||||
"team_deletion_cannot_be_undone": "Attenzione. Non è possibile annullare l'eliminazione del team",
|
||||
"back": "Indietro",
|
||||
"cancel": "Annulla",
|
||||
"cancel_all_remaining": "Annulla tutti i rimanenti",
|
||||
@@ -655,6 +689,7 @@
|
||||
"default_duration": "Durata predefinita",
|
||||
"default_duration_no_options": "Scegliere prima delle durate disponibili",
|
||||
"multiple_duration_mins": "{{count}} $t(minute_timeUnit)",
|
||||
"multiple_duration_timeUnit": "{{count}} $t({{unit}}_timeUnit)",
|
||||
"minutes": "Minuti",
|
||||
"round_robin": "Round Robin",
|
||||
"round_robin_description": "Ciclo di riunioni tra più membri del team.",
|
||||
@@ -667,6 +702,7 @@
|
||||
"add_members": "Aggiungi membri...",
|
||||
"no_assigned_members": "Nessun membro assegnato",
|
||||
"assigned_to": "Assegnato a",
|
||||
"you_must_be_logged_in_to": "Devi effettuare l'accesso a {{url}}",
|
||||
"start_assigning_members_above": "Inizia ad assegnare membri in alto",
|
||||
"locked_fields_admin_description": "I membri non potranno fare modifiche",
|
||||
"locked_fields_member_description": "Questa opzione è stata bloccata dall'amministratore del team",
|
||||
@@ -771,6 +807,7 @@
|
||||
"additional_input_description": "Chiedi al creatore del programma di inserire dei campi di input aggiuntivi prima che la prenotazione sia confermata",
|
||||
"label": "Etichetta",
|
||||
"placeholder": "Segnaposto",
|
||||
"display_add_to_calendar_organizer": "Visualizza l'email \"Aggiungi al calendario\" come organizzatore",
|
||||
"type": "Tipo",
|
||||
"edit": "Modifica",
|
||||
"add_input": "Aggiungi un input",
|
||||
@@ -779,6 +816,9 @@
|
||||
"requires_confirmation_description": "La prenotazione deve essere confermata manualmente prima di essere inviata alle integrazioni e che venga inviata una e-mail di conferma.",
|
||||
"recurring_event": "Evento ricorrente",
|
||||
"recurring_event_description": "Le persone possono iscriversi a eventi ricorrenti",
|
||||
"cannot_be_used_with_paid_event_types": "Non può essere utilizzato con i tipi di evento a pagamento",
|
||||
"warning_payment_instant_meeting_event": "Le riunioni immediate non sono ancora disponibili con gli eventi ricorrenti e le app di pagamento",
|
||||
"warning_instant_meeting_experimental": "Sperimentale: gli eventi Riunione immediata al momento sono in fase di sperimentazione.",
|
||||
"starting": "Inizio",
|
||||
"disable_guests": "Disabilita Ospiti",
|
||||
"disable_guests_description": "Disabilita l'aggiunta di ulteriori ospiti durante la prenotazione.",
|
||||
@@ -787,6 +827,7 @@
|
||||
"private_link_label": "Link privato",
|
||||
"private_link_hint": "Il tuo link privato verrà rigenerato dopo ogni utilizzo",
|
||||
"copy_private_link": "Copia link privato",
|
||||
"copy_private_link_to_event": "Copia link privato nell'evento",
|
||||
"private_link_description": "Genera un URL privato da condividere senza mostrare il tuo nome utente {{appName}}",
|
||||
"invitees_can_schedule": "Gli inviti possono programmare",
|
||||
"date_range": "Range di date",
|
||||
@@ -846,6 +887,7 @@
|
||||
"next_step": "Salta passo",
|
||||
"prev_step": "Passo precedente",
|
||||
"install": "Installa",
|
||||
"start_paid_trial": "Inizia la prova gratuita",
|
||||
"installed": "Installato",
|
||||
"active_install_one": "{{count}} installazione attiva",
|
||||
"active_install_other": "{{count}} installazioni attive",
|
||||
@@ -860,6 +902,7 @@
|
||||
"toggle_calendars_conflict": "Attiva/disattiva i calendari in cui desideri controllare i conflitti per evitare doppie prenotazioni.",
|
||||
"connect_additional_calendar": "Collega calendario aggiuntivo",
|
||||
"calendar_updated_successfully": "Calendario aggiornato correttamente",
|
||||
"check_here": "Controlla qui",
|
||||
"conferencing": "Conferenze",
|
||||
"calendar": "Calendario",
|
||||
"payments": "Pagamenti",
|
||||
@@ -1031,6 +1074,9 @@
|
||||
"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 proprietari/amministratori del tuo team di accedere temporaneamente con il tuo nome.",
|
||||
"cal_signup_description": "Gratuito per le singole pesone. Piani collettivi per le funzionalità di collaborazione.",
|
||||
"make_org_private": "Rendi l'organizzazione privata",
|
||||
"make_org_private_description": "Quando questa opzione è attivata, i membri della tua organizzazione non possono vedere gli altri membri.",
|
||||
"make_team_private": "Rendi il team privato",
|
||||
"make_team_private_description": "Quando questa opzione è attivata, i membri del tuo team non potranno vedere gli altri membri del team.",
|
||||
"you_cannot_see_team_members": "Non è possibile vedere tutti i membri di un team privato.",
|
||||
@@ -1090,6 +1136,7 @@
|
||||
"developer_documentation": "Documentazione sviluppatore",
|
||||
"get_in_touch": "Contattaci",
|
||||
"contact_support": "Contatta il supporto",
|
||||
"premium_support": "Assistenza Premium",
|
||||
"community_support": "Supporto della community",
|
||||
"feedback": "Feedback",
|
||||
"submitted_feedback": "Grazie per il tuo feedback!",
|
||||
@@ -1253,6 +1300,7 @@
|
||||
"default_calendar_selected": "Calendario predefinito",
|
||||
"hide_from_profile": "Nascondi nel profilo",
|
||||
"event_setup_tab_title": "Impostazione evento",
|
||||
"availability_not_found_in_schedule_error": "Nessuna disponibilità trovata nel programma",
|
||||
"event_limit_tab_title": "Limiti",
|
||||
"event_limit_tab_description": "Con quale frequenza è possibile fare una prenotazione",
|
||||
"event_advanced_tab_description": "Impostazioni calendario e altro...",
|
||||
@@ -1273,6 +1321,7 @@
|
||||
"do_this": "Esegui questa operazione",
|
||||
"turn_off": "Disattiva",
|
||||
"turn_on": "Attiva",
|
||||
"cancelled_bookings_cannot_be_rescheduled": "Le prenotazioni annullate non possono essere riprogrammate",
|
||||
"settings_updated_successfully": "Impostazioni aggiornate correttamente",
|
||||
"error_updating_settings": "Errore durante l'aggiornamento delle impostazioni",
|
||||
"personal_cal_url": "Il mio URL personale di {{appName}}",
|
||||
@@ -1296,6 +1345,7 @@
|
||||
"customize_your_brand_colors": "Personalizza la tua pagina di prenotazione con i colori del tuo marchio.",
|
||||
"pro": "Pro",
|
||||
"removes_cal_branding": "Rimuove tutte le scritte con il marchio {{appName}}, ad es., 'Powered by {{appName}}.'",
|
||||
"instant_meeting_with_title": "Riunione immediata con {{name}}",
|
||||
"profile_picture": "Immagine del profilo",
|
||||
"upload": "Carica",
|
||||
"add_profile_photo": "Aggiungi foto del profilo",
|
||||
@@ -1351,6 +1401,7 @@
|
||||
"event_name_info": "Nome del tipo di evento",
|
||||
"event_date_info": "Data dell'evento",
|
||||
"event_time_info": "Ora di inizio dell'evento",
|
||||
"event_type_not_found": "EventType non trovato",
|
||||
"location_variable": "Luogo",
|
||||
"location_info": "Luogo dell'evento",
|
||||
"additional_notes_variable": "Note aggiuntive",
|
||||
@@ -1393,6 +1444,7 @@
|
||||
"slot_length": "Durata della fascia oraria",
|
||||
"booking_appearance": "Aspetto prenotazione",
|
||||
"appearance_team_description": "Gestisci le impostazioni di aspetto delle prenotazioni del tuo team",
|
||||
"appearance_org_description": "Imposta l'aspetto delle prenotazioni per la tua organizzazione",
|
||||
"only_owner_change": "Solo il proprietario del team può apportare modifiche alla prenotazione del team ",
|
||||
"team_disable_cal_branding_description": "Rimuove tutte le scritte con il marchio {{appName}}, ad es., 'Powered by {{appName}}'",
|
||||
"invited_by_team": "{{teamName}} ti ha invitato a unirti al team come {{role}}",
|
||||
@@ -1446,6 +1498,7 @@
|
||||
"password_updated": "Password aggiornata!",
|
||||
"pending_payment": "Pagamento in sospeso",
|
||||
"pending_invites": "Inviti in sospeso",
|
||||
"pending_organization_invites": "Inviti in sospeso dell'organizzazione",
|
||||
"not_on_cal": "Non presente su {{appName}}",
|
||||
"no_calendar_installed": "Nessun calendario installato",
|
||||
"no_calendar_installed_description": "Non hai ancora collegato alcun calendario",
|
||||
@@ -1457,6 +1510,8 @@
|
||||
"report_app": "Segnala app",
|
||||
"limit_booking_frequency": "Limita frequenza di prenotazione",
|
||||
"limit_booking_frequency_description": "Indica quante volte è possibile prenotare questo evento",
|
||||
"limit_booking_only_first_slot": "Limita prenotazione solo prima fascia",
|
||||
"limit_booking_only_first_slot_description": "Consenti la prenotazione solo nella prima fascia oraria di ogni giorno",
|
||||
"limit_total_booking_duration": "Limita durata totale prenotazione",
|
||||
"limit_total_booking_duration_description": "Indicare la quantità di tempo totale disponibile per prenotare questo evento",
|
||||
"add_limit": "Aggiungi limite",
|
||||
@@ -1524,10 +1579,14 @@
|
||||
"your_org_disbanded_successfully": "La tua organizzazione è stata sciolta",
|
||||
"error_creating_team": "Errore durante la creazione del team",
|
||||
"you": "Tu",
|
||||
"or_continue_with": "Oppure continua con",
|
||||
"resend_email": "Invia di nuovo l'e-mail",
|
||||
"member_already_invited": "Membro già invitato",
|
||||
"already_in_use_error": "Nome utente già in uso",
|
||||
"enter_email_or_username": "Immettere un indirizzo e-mail o un nome utente",
|
||||
"enter_email": "Inserisci un'email",
|
||||
"enter_emails": "Inserisci email",
|
||||
"too_many_invites": "Puoi invitare un massimo di {{nbUsers}} utenti alla volta.",
|
||||
"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",
|
||||
@@ -1607,6 +1666,7 @@
|
||||
"individual": "Persona",
|
||||
"all_bookings_filter_label": "Tutte le prenotazioni",
|
||||
"all_users_filter_label": "Tutti gli utenti",
|
||||
"all_event_types_filter_label": "Tutti i tipi di evento",
|
||||
"your_bookings_filter_label": "Le tue prenotazioni",
|
||||
"meeting_url_variable": "URL della riunione",
|
||||
"meeting_url_info": "URL dell'evento, dell'incontro o della conferenza",
|
||||
@@ -1703,9 +1763,11 @@
|
||||
"organizer_timezone": "Fuso orario organizzatore",
|
||||
"email_user_cta": "Visualizza invito",
|
||||
"email_no_user_invite_heading_team": "Hai ricevuto un invito a entrare a far parte del team di {{appName}}",
|
||||
"email_no_user_invite_heading_subteam": "Sei stato invitato a partecipare a un team dell'organizzazione {{parentTeamName}}",
|
||||
"email_no_user_invite_heading_org": "Hai ricevuto un invito a entrare a far parte dell'organizzazione {{appName}}",
|
||||
"email_no_user_invite_subheading": "{{invitedBy}} ti ha invitato a unirti al suo team su {{appName}}. {{appName}} è uno strumento di pianificazione di eventi che permette al tuo team di pianificare le riunioni senza scambiare decine di e-mail.",
|
||||
"email_user_invite_subheading_team": "{{invitedBy}} ti ha invitato a unirti al suo team `{{teamName}}` su {{appName}}. {{appName}} è uno strumento di pianificazione di eventi che permette a te e al tuo team di pianificare le riunioni senza scambiare decine di e-mail.",
|
||||
"email_user_invite_subheading_subteam": "{{invitedBy}} ti ha invitato a entrare nel team {{teamName}} della sua organizzazione {{parentTeamName}} su {{appName}}. {{appName}} è lo strumento di programmazione degli eventi che consente a te e al tuo team di programmare riunioni senza lo scambio di infinite email.",
|
||||
"email_user_invite_subheading_org": "{{invitedBy}} ti ha invitato a unirti alla sua organizzazione `{{teamName}}` su {{appName}}. {{appName}} è uno strumento di pianificazione di eventi che permette a te e alla tua organizzazione di pianificare le riunioni senza scambiare decine di e-mail.",
|
||||
"email_no_user_invite_steps_intro": "Ti assisteremo nei passaggi iniziali e potrai rapidamente pianificare eventi per il/la {{entity}} senza fatica.",
|
||||
"email_no_user_step_one": "Scegli il tuo nome utente",
|
||||
@@ -1838,7 +1900,9 @@
|
||||
"review_event_type": "Rivedi tipo di evento",
|
||||
"looking_for_more_analytics": "Hai bisogno di più dati analitici?",
|
||||
"looking_for_more_insights": "Ti servono più Insights?",
|
||||
"filters": "Filtri",
|
||||
"add_filter": "Aggiungi filtro",
|
||||
"remove_filters": "Cancella tutti i filtri",
|
||||
"select_user": "Seleziona utente",
|
||||
"select_event_type": "Seleziona tipo di evento",
|
||||
"select_date_range": "Seleziona intervallo di date",
|
||||
@@ -1929,6 +1993,7 @@
|
||||
"connect_google_workspace": "Collega Google Workspace",
|
||||
"google_workspace_admin_tooltip": "Per usare questa funzione è necessario essere amministratori di Workspace",
|
||||
"first_event_type_webhook_description": "Crea il primo webhook per questo tipo di evento",
|
||||
"create_instant_meeting_webhook_description": "Crea il tuo primo webhook con \"Riunione immediata creata\" come attivatore per questo tipo di evento",
|
||||
"install_app_on": "Installa l'app su",
|
||||
"create_for": "Crea per",
|
||||
"currency": "Valuta",
|
||||
@@ -1993,6 +2058,8 @@
|
||||
"add_times_to_your_email": "Seleziona alcuni orari disponibili e incorporali nella tua e-mail",
|
||||
"select_time": "Seleziona l'ora",
|
||||
"select_date": "Seleziona la data",
|
||||
"connecting_you_to_someone": "Ti stiamo collegando con qualcuno.",
|
||||
"please_do_not_close_this_tab": "Non chiudere questa scheda",
|
||||
"see_all_available_times": "Vedi tutti gli orari disponibili",
|
||||
"org_team_names_example_1": "ad es., team di marketing",
|
||||
"org_team_names_example_2": "ad es., team vendite",
|
||||
@@ -2010,8 +2077,12 @@
|
||||
"description_requires_booker_email_verification": "Per garantire la verifica dell'indirizzo e-mail della persona che prenota prima di panificare eventi",
|
||||
"requires_confirmation_mandatory": "I messaggi di testo possono solo essere inviati ai partecipanti quando il tipo di evento richiede una conferma.",
|
||||
"organizations": "Organizzazioni",
|
||||
"upload_cal_video_logo": "Carica il logo Cal Video",
|
||||
"update_cal_video_logo": "Aggiorna il logo Cal Video",
|
||||
"cal_video_logo_upload_instruction": "Per fare in modo che il tuo logo sia visibile sullo sfondo scuro del video Cal, carica un'immagine di colore chiaro in formato PNG o SVG per mantenere la trasparenza.",
|
||||
"org_admin_other_teams": "Atri team",
|
||||
"org_admin_other_teams_description": "Qui puoi vedere i team di cui non fai parte all'interno della tua organizzazione. Puoi entrare a far parte di tali team se necessario.",
|
||||
"not_part_of_org": "Non fai parte di alcuna organizzazione",
|
||||
"no_other_teams_found": "Nessun altro team trovato",
|
||||
"no_other_teams_found_description": "Non ci sono altri team in questa organizzazione.",
|
||||
"attendee_first_name_variable": "Nome del partecipante",
|
||||
@@ -2047,7 +2118,9 @@
|
||||
"org_error_processing": "Si è verificato un errore durante l'elaborazione di questa organizzazione",
|
||||
"orgs_page_description": "Un elenco di tutte le organizzazioni. Accettando un'organizzazione, tutti gli utenti con il rispettivo dominio e-mail potranno registrarsi SENZA verificare l'indirizzo e-mail.",
|
||||
"unverified": "Non verificato",
|
||||
"verified": "Verificato",
|
||||
"dns_missing": "DNS mancante",
|
||||
"dns_configured": "DNS Configurato",
|
||||
"mark_dns_configured": "Contrassegna DNS come configurato",
|
||||
"value": "Valore",
|
||||
"your_organization_updated_sucessfully": "L'organizzazione è stata aggiornata correttamente",
|
||||
@@ -2057,8 +2130,11 @@
|
||||
"oAuth": "OAuth",
|
||||
"recently_added": "Aggiunti di recente",
|
||||
"connect_all_calendars": "Collega tutti i tuoi calendari",
|
||||
"connect_all_calendars_description": "{{appName}} legge la disponibilità da tutti i tuoi calendari esistenti.",
|
||||
"workflow_automation": "Automazione dei flussi di lavoro",
|
||||
"workflow_automation_description": "Personalizza la tua esperienza di programmazione con i flussi di lavoro",
|
||||
"scheduling_for_your_team": "Automazione dei flussi di lavoro",
|
||||
"scheduling_for_your_team_description": "Usa la programmazione collettiva e round robin per il tuo team",
|
||||
"no_members_found": "Nessun membro trovato",
|
||||
"event_setup_length_error": "Impostazione evento: la durata deve essere di almeno 1 minuto.",
|
||||
"availability_schedules": "Calendario disponibilità",
|
||||
@@ -2091,10 +2167,100 @@
|
||||
"overlay_my_calendar": "Sovrapponi il mio calendario",
|
||||
"overlay_my_calendar_toc": "Collegando il tuo calendario, accetti la nostra informativa sulla privacy e i termini di servizio. Puoi revocare l'accesso in qualsiasi momento.",
|
||||
"view_overlay_calendar_events": "Visualizza gli eventi del tuo calendario per prevenire prenotazioni in conflitto.",
|
||||
"join_event_location": "Partecipa a {{eventLocationType}}",
|
||||
"troubleshooting": "Risoluzione problemi",
|
||||
"calendars_were_checking_for_conflicts": "Calendari in cui stiamo cercando possibili conflitti",
|
||||
"availabilty_schedules": "Programmi di disponibilità",
|
||||
"manage_calendars": "Gestisci calendari",
|
||||
"manage_availability_schedules": "Gestisci programmi di disponibilità",
|
||||
"lock_timezone_toggle_on_booking_page": "Blocca fuso orario nella pagina di prenotazione",
|
||||
"description_lock_timezone_toggle_on_booking_page": "Per bloccare il fuso orario nella pagina di prenotazione, utile per gli eventi di persona.",
|
||||
"event_setup_multiple_payment_apps_error": "Puoi avere solo un'app di pagamento abilitata per ciascun tipo di evento.",
|
||||
"number_in_international_format": "Inserisci il numero in formato internazionale.",
|
||||
"install_calendar": "Installa Calendario",
|
||||
"branded_subdomain": "Sottodominio del marchio",
|
||||
"branded_subdomain_description": "Crea un sottodominio per il tuo marchio, come acme.cal.com",
|
||||
"org_insights": "Approfondimenti a livello di organizzazione",
|
||||
"org_insights_description": "Comprendi come la tua organizzazione utilizza il tempo",
|
||||
"extensive_whitelabeling": "Assistenza per l'onboarding e supporto tecnico dedicati",
|
||||
"extensive_whitelabeling_description": "Personalizza la tua esperienza di programmazione con il tuo logo, colori e altro ancora",
|
||||
"unlimited_teams": "Numero illimitato di team",
|
||||
"unlimited_teams_description": "Aggiungi alla tua organizzazione tutti i sotto-team necessari",
|
||||
"unified_billing": "Fatturazione unificata",
|
||||
"unified_billing_description": "Indica una singola carta di credito per pagare tutti gli abbonamenti del tuo team",
|
||||
"advanced_managed_events": "Tipi di evento gestiti avanzati",
|
||||
"advanced_managed_events_description": "Indica una singola carta di credito per pagare tutti gli abbonamenti del tuo team",
|
||||
"enterprise_description": "Passa a Enterprise per creare la tua organizzazione",
|
||||
"create_your_org": "Crea la tua organizzazione",
|
||||
"create_your_org_description": "Passa a Enterprise per avere sottodominio, fatturazione unificata, approfondimenti, personalizzazione completa e altro ancora",
|
||||
"other_payment_app_enabled": "Puoi attivare solo un'app di pagamento per ciascun tipo di evento",
|
||||
"admin_delete_organization_description": "<ul><li>Verranno eliminati anche i team che sono membri di questa organizzazione, insieme ai rispettivi tipi di evento</li><li>Gli utenti che facevano parte dell'organizzazione non verranno eliminati e anche i loro tipi di evento rimarranno inalterati.</li><li>I nomi utente saranno modificati in modo che possano esistere al di fuori dell'organizzazione</li></ul>",
|
||||
"admin_delete_organization_title": "Eliminare {{organizationName}}?",
|
||||
"published": "Pubblicato",
|
||||
"unpublished": "Pubblicazione annullata",
|
||||
"publish": "Pubblica",
|
||||
"org_publish_error": "Non è stato possibile pubblicare l'organizzazione",
|
||||
"troubleshooter_tooltip": "Apri lo strumento di soluzione dei problemi per individuare il problema del tuo programma",
|
||||
"need_help": "Hai bisogno di aiuto?",
|
||||
"troubleshooter": "Risoluzione problemi",
|
||||
"please_install_a_calendar": "Installa un calendario",
|
||||
"instant_tab_title": "Prenotazione immediata",
|
||||
"instant_event_tab_description": "Consenti alle persone di prenotare immediatamente",
|
||||
"uprade_to_create_instant_bookings": "Passa a Enterprise e permetti agli ospiti di partecipare a una chiamata immediata a cui i partecipanti possono accedere direttamente. Ciò è riservato agli eventi per team",
|
||||
"dont_want_to_wait": "Non vuoi aspettare?",
|
||||
"meeting_started": "Riunione iniziata",
|
||||
"pay_and_book": "Pagare per prenotare",
|
||||
"booking_not_found_error": "Prenotazione non trovata",
|
||||
"booking_seats_full_error": "I posti sono esauriti",
|
||||
"missing_payment_credential_error": "Credenziali di pagamento mancanti",
|
||||
"missing_payment_app_id_error": "ID dell'app di pagamento mancante",
|
||||
"not_enough_available_seats_error": "La prenotazione non ha abbastanza posti disponibili",
|
||||
"user_redirect_title": "{{username}} è attualmente assente per un breve periodo.",
|
||||
"user_redirect_description": "Nel frattempo, {{profile.username}} sarà responsabile di tutte le nuove riunioni programmate per conto di {{username}}.",
|
||||
"out_of_office": "Assente",
|
||||
"out_of_office_description": "Configura le azioni nel tuo profilo mentre sei assente.",
|
||||
"send_request": "Invia richiesta",
|
||||
"start_date_and_end_date_required": "Data iniziale e data finale sono obbligatorie",
|
||||
"start_date_must_be_before_end_date": "La data iniziale deve precedere quella finale",
|
||||
"start_date_must_be_in_the_future": "La data iniziale deve essere nel futuro",
|
||||
"user_not_found": "Utente non trovato",
|
||||
"out_of_office_entry_already_exists": "La voce per l'assenza esiste già",
|
||||
"out_of_office_id_required": "L'ID della voce per l'assenza è obbligatorio",
|
||||
"booking_redirect_infinite_not_allowed": "Esiste già un reindirizzamento della prenotazione dall'utente a te.",
|
||||
"success_entry_created": "Nuova voce creata correttamente",
|
||||
"booking_redirect_email_subject": "Notifica di reindirizzamento prenotazione",
|
||||
"booking_redirect_email_title": "Notifica di reindirizzamento prenotazione",
|
||||
"booking_redirect_email_description": "Hai ricevuto un reindirizzamento delle prenotazioni da {{toName}}. I link del suo profilo rimanderanno ai tuoi, nel periodo: ",
|
||||
"success_accept_booking_redirect": "Hai accettato questa richiesta di reindirizzamento delle prenotazioni.",
|
||||
"success_reject_booking_redirect": "Hai rifiutato questa richiesta di reindirizzamento delle prenotazioni.",
|
||||
"copy_link_booking_redirect_request": "Copia link per condividere la richiesta",
|
||||
"booking_redirect_request_title": "Richiesta di reindirizzamento delle prenotazioni",
|
||||
"select_team_member": "Seleziona un membro del team",
|
||||
"going_away_title": "Devi assentarti? Segnala che il link al tuo profilo non è disponibile per un periodo.",
|
||||
"redirect_team_enabled": "Rendirizza il tuo profilo a un altro membro del team",
|
||||
"redirect_team_disabled": "Reindirizza il tuo profilo a un altro membro del team (è necessario il piano Team)",
|
||||
"out_of_office_unavailable_list": "Elenco di indisponibilità per assenza",
|
||||
"success_deleted_entry_out_of_office": "Voce eliminata",
|
||||
"temporarily_out_of_office": "Temporaneamente assente?",
|
||||
"add_a_redirect": "Aggiungi un reindirizzamento",
|
||||
"create_entry": "Crea una voce",
|
||||
"time_range": "Intervallo temporale",
|
||||
"automatically_add_all_team_members": "Aggiungi tutti i membri del team, compresi quelli futuri",
|
||||
"redirect_to": "Reindirizza a",
|
||||
"having_trouble_finding_time": "Non riesci a trovare un orario?",
|
||||
"show_more": "Mostra altro",
|
||||
"assignment_description": "Pianifica le riunioni quando tutti sono disponibili, oppure ruota tra i membri del tuo team",
|
||||
"lowest": "più basso",
|
||||
"low": "basso",
|
||||
"medium": "medio",
|
||||
"high": "alto",
|
||||
"Highest": "più alto",
|
||||
"send_booker_to": "Rimanda la persona che prenota a",
|
||||
"set_priority": "Imposta priorità",
|
||||
"priority_for_user": "Priorità per {{userName}}",
|
||||
"change_priority": "cambia priorità",
|
||||
"field_identifiers_as_variables": "Usa gli identificatori di campo come variabili per il reindirizzamento dell'evento personalizzato",
|
||||
"field_identifiers_as_variables_with_example": "Usa gli identificatori di campo come variabili per il reindirizzamento dell'evento personalizzato (es: {{variable}})",
|
||||
"account_already_linked": "L'account è già collegato",
|
||||
"ADD_NEW_STRINGS_ABOVE_THIS_LINE_TO_PREVENT_MERGE_CONFLICTS": "↑↑↑↑↑↑↑↑↑↑↑↑↑ Aggiungi le tue nuove stringhe qui sopra ↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑"
|
||||
}
|
||||
|
||||
@@ -13,10 +13,17 @@
|
||||
"reset_password_subject": "{{appName}}: Instruções para redefinir sua senha",
|
||||
"verify_email_subject": "{{appName}}: Verificar sua conta",
|
||||
"check_your_email": "Verifique seu e-mail",
|
||||
"old_email_address": "E-mail antigo",
|
||||
"new_email_address": "Novo e-mail",
|
||||
"verify_email_page_body": "Enviamos um e-mail para {{email}}. É importante verificar seu endereço de e-mail para garantir o melhor envio de e-mail e calendário do {{appName}}.",
|
||||
"verify_email_banner_body": "Verifique seu endereço de e-mail para garantir o fornecimento do melhor e-mail e calendário",
|
||||
"verify_email_email_header": "Verifique seu endereço de e-mail",
|
||||
"verify_email_email_button": "Verificar e-mail",
|
||||
"verify_email_change_description": "Você solicitou recentemente a alteração do endereço de e-mail que você usa para entrar em sua conta {{appName}}. Por favor, clique no botão abaixo para confirmar o seu novo endereço de e-mail.",
|
||||
"verify_email_change_success_toast": "Seu e-mail foi atualizado para {{email}}",
|
||||
"verify_email_change_failure_toast": "Não foi possível atualizar o e-mail.",
|
||||
"change_of_email": "Verifique seu novo endereço de e-mail da sua conta {{appName}}",
|
||||
"change_of_email_toast": "Enviamos um link de verificação para {{email}}. Atualizaremos seu endereço de e-mail quando você clicar neste link.",
|
||||
"copy_somewhere_safe": "Salve esta chave de API em um lugar seguro. Você não será capaz de vê-la novamente.",
|
||||
"verify_email_email_body": "Por favor, verifique seu endereço de e-mail clicando no botão abaixo.",
|
||||
"verify_email_by_code_email_body": "Por favor, verifique seu endereço de e-mail usando o código abaixo.",
|
||||
@@ -56,6 +63,17 @@
|
||||
"a_refund_failed": "Um reembolso falhou",
|
||||
"awaiting_payment_subject": "Aguardando pagamento: {{title}} em {{date}}",
|
||||
"meeting_awaiting_payment": "Sua reunião está aguardando o pagamento",
|
||||
"dark_theme_contrast_error": "A cor do tema escuro não foi aprovada pela verificação de contraste. Recomendamos que você altere esta cor para que seus botões fiquem mais visíveis.",
|
||||
"light_theme_contrast_error": "A cor do tema claro não foi aprovada pela verificação de contraste. Recomendamos que você altere esta cor para que seus botões fiquem mais visíveis.",
|
||||
"payment_not_created_error": "O pagamento não pôde ser criado",
|
||||
"couldnt_charge_card_error": "Não foi possível cobrar o cartão para o pagamento",
|
||||
"no_available_users_found_error": "Nenhum usuário disponível foi encontrado. Você poderia tentar outra faixa horária?",
|
||||
"request_body_end_time_internal_error": "Erro interno. O corpo da solicitação não contém a hora final",
|
||||
"create_calendar_event_error": "Não foi possível criar um evento no calendário do organizador",
|
||||
"update_calendar_event_error": "Não foi possível atualizar o evento.",
|
||||
"delete_calendar_event_error": "Não foi possível deletar o evento.",
|
||||
"already_signed_up_for_this_booking_error": "Você já está inscrito para esta reserva.",
|
||||
"hosts_unavailable_for_booking": "Alguns dos anfitriões não estão disponíveis para reserva.",
|
||||
"help": "Ajuda",
|
||||
"price": "Preço",
|
||||
"paid": "Pago",
|
||||
@@ -101,6 +119,7 @@
|
||||
"requested_to_reschedule_subject_attendee": "Ação de reagendamento necessária: reserve outro horário para {{eventType}} com {{name}}",
|
||||
"hi_user_name": "Olá {{name}}",
|
||||
"ics_event_title": "{{eventType}} com {{name}}",
|
||||
"please_book_a_time_sometime_later": "Ops, não foi possível conectá-lo desta vez. Por favor, agende uma chamada mais tarde",
|
||||
"new_event_subject": "Novo evento: {{attendeeName}} - {{date}} - {{eventType}}",
|
||||
"join_by_entrypoint": "Junte-se através de {{entryPoint}}",
|
||||
"notes": "Notas",
|
||||
@@ -117,15 +136,20 @@
|
||||
"meeting_id": "ID da reunião",
|
||||
"meeting_password": "Senha da reunião",
|
||||
"meeting_url": "URL da reunião",
|
||||
"meeting_url_not_found": "URL da reunião não encontrada",
|
||||
"token_not_found": "Token não encontrado",
|
||||
"some_other_host_already_accepted_the_meeting": "Algum outro anfitrião já aceitou a reunião. Ainda deseja entrar? <1>Entrar na reunião</1>",
|
||||
"meeting_request_rejected": "A sua solicitação de reunião foi rejeitada",
|
||||
"rejected_event_type_with_organizer": "Rejeitado: {{eventType}} com {{organizer}} em {{date}}",
|
||||
"hi": "Olá",
|
||||
"join_team": "Entrar no time",
|
||||
"manage_this_team": "Gerenciar esta equipe",
|
||||
"team_info": "Informações da equipe",
|
||||
"join_meeting": "Entrar na reunião",
|
||||
"request_another_invitation_email": "Se não quiser utilizar {{toEmail}} como email do {{appName}}, ou já tem uma conta em {{appName}}, por favor peça outro convite para esse email.",
|
||||
"you_have_been_invited": "Você recebeu um convite para se juntar ao time {{teamName}}",
|
||||
"user_invited_you": "{{user}} convidou para ingressar na equipe {{team}} de {{entity}} em {{appName}}",
|
||||
"user_invited_you_to_subteam": "{{user}} convidou você para se juntar à equipe {{team}} da organização {{parentTeamName}} no {{appName}}",
|
||||
"hidden_team_member_title": "Você está oculto nesta equipe",
|
||||
"hidden_team_member_message": "Seu assento não é pago, atualize para a versão Pro ou avise o proprietário da equipe de que é possível pagar pelo seu assento.",
|
||||
"hidden_team_owner_message": "É preciso ter uma conta Pro para usar equipes, você permanecerá oculto até a atualização.",
|
||||
@@ -229,6 +253,7 @@
|
||||
"reset_your_password": "Defina sua nova senha com as instruções enviadas para seu endereço de e-mail.",
|
||||
"email_change": "Volte a entrar com seu novo endereço de e-mail e senha.",
|
||||
"create_your_account": "Criar sua conta",
|
||||
"create_your_calcom_account": "Crie sua conta Cal.com",
|
||||
"sign_up": "Cadastro",
|
||||
"youve_been_logged_out": "A sessão foi encerrada",
|
||||
"hope_to_see_you_soon": "Esperamos te ver novamente em breve!",
|
||||
@@ -266,6 +291,9 @@
|
||||
"nearly_there_instructions": "Por último, uma breve descrição de você mesmo e uma foto ajudam a conseguir agendamentos e a mostrar às pessoas com quem elas estão agendando.",
|
||||
"set_availability_instructions": "Definir períodos em que você está disponível de forma recorrente. Mais tarde você poderá criar outros períodos e atribuí-los a diferentes calendários.",
|
||||
"set_availability": "Definir disponibilidade",
|
||||
"set_availbility_description": "Defina os horários que você quer que estejam disponíveis para reservas.",
|
||||
"share_a_link_or_embed": "Compartilhe um link ou incorpore",
|
||||
"share_a_link_or_embed_description": "Compartilhe o link da sua conta {{appName}} ou incorpore no seu site.",
|
||||
"availability_settings": "Configurações de disponibilidade",
|
||||
"continue_without_calendar": "Continuar sem calendário",
|
||||
"continue_with": "Continuar com {{appName}}",
|
||||
@@ -281,6 +309,7 @@
|
||||
"welcome_to_calcom": "Bem vindo ao {{appName}}",
|
||||
"welcome_instructions": "Diga como devemos te chamar e qual o seu fuso horário. Você poderá editar isto mais tarde.",
|
||||
"connect_caldav": "Conectar a um servidor CalDav",
|
||||
"connect_ics_feed": "Conectar a um feed ICS",
|
||||
"connect": "Conectar",
|
||||
"try_for_free": "Experimente gratuitamente",
|
||||
"create_booking_link_with_calcom": "Crie o seu próprio link para reservas com {{appName}}",
|
||||
@@ -419,6 +448,7 @@
|
||||
"browse_api_documentation": "Consulte a nossa documentação da API",
|
||||
"leverage_our_api": "Utilize nossa API para controle e personalização completa.",
|
||||
"create_webhook": "Criar Webhook",
|
||||
"instant_meeting": "Reunião instantânea criada",
|
||||
"booking_cancelled": "Reserva cancelada",
|
||||
"booking_rescheduled": "Reserva Reagendada",
|
||||
"recording_ready": "Link para download de gravação pronto",
|
||||
@@ -533,6 +563,7 @@
|
||||
"enter_number_between_range": "Por favor, insira um número entre 1 e {{maxOccurences}}",
|
||||
"email_address": "Endereço de e-mail",
|
||||
"enter_valid_email": "Insira um e-mail válido",
|
||||
"please_schedule_future_call": " Por favor, agende uma chamada futura se não estivermos disponíveis em {{seconds}} segundos",
|
||||
"location": "Local",
|
||||
"address": "Local",
|
||||
"enter_address": "Insira o endereço",
|
||||
@@ -592,6 +623,7 @@
|
||||
"new_member": "Novo Membro",
|
||||
"invite": "Convidar",
|
||||
"add_team_members": "Adicionar membros da equipe",
|
||||
"add_org_members": "Adicionar membros",
|
||||
"add_team_members_description": "Convidar outros para se juntar à sua equipe",
|
||||
"add_team_member": "Adicionar membro da equipe",
|
||||
"invite_new_member": "Convidar um novo membro",
|
||||
@@ -606,6 +638,7 @@
|
||||
"hide_book_a_team_member_description": "Oculte o botão de agendar um membro da equipe em suas páginas públicas.",
|
||||
"danger_zone": "Zona de perigo",
|
||||
"account_deletion_cannot_be_undone": "Cuidado. A exclusão da conta não pode ser desfeita.",
|
||||
"team_deletion_cannot_be_undone": "Cuidado. A exclusão de uma equipe não pode ser desfeita",
|
||||
"back": "Voltar",
|
||||
"cancel": "Cancelar",
|
||||
"cancel_all_remaining": "Cancelar todos os restantes",
|
||||
@@ -668,6 +701,7 @@
|
||||
"add_members": "Adicionar membros...",
|
||||
"no_assigned_members": "Nenhum membro atribuído",
|
||||
"assigned_to": "Atribuído a",
|
||||
"you_must_be_logged_in_to": "Você deve estar logado em {{url}}",
|
||||
"start_assigning_members_above": "Começar a atribuir os membros acima",
|
||||
"locked_fields_admin_description": "Os membros não poderão editar isto",
|
||||
"locked_fields_member_description": "Esta opção foi bloqueada pelo administrador da equipe",
|
||||
@@ -772,6 +806,7 @@
|
||||
"additional_input_description": "Solicitar que o agendador insira entradas adicionais antes que o pedido seja confirmado",
|
||||
"label": "Etiqueta",
|
||||
"placeholder": "Preenchimento",
|
||||
"display_add_to_calendar_organizer": "Exibir o e-mail 'Adicionar ao calendário' como organizador",
|
||||
"type": "Tipo",
|
||||
"edit": "Editar",
|
||||
"add_input": "Adicionar um campo",
|
||||
|
||||
@@ -13,10 +13,17 @@
|
||||
"reset_password_subject": "{{appName}}: Instruções de redefinição da senha",
|
||||
"verify_email_subject": "{{appName}}: Verifique a sua conta",
|
||||
"check_your_email": "Verifique o seu e-mail",
|
||||
"old_email_address": "E-mail antigo",
|
||||
"new_email_address": "Novo e-mail",
|
||||
"verify_email_page_body": "Enviámos um e-mail para {{email}}. É importante verificar o seu endereço de e-mail para garantir que receberá as comunicações de {{appName}}.",
|
||||
"verify_email_banner_body": "Confirme o seu endereço de e-mail para garantir a melhor entrega possível de e-mail e de agenda",
|
||||
"verify_email_email_header": "Confirme o seu endereço de e-mail",
|
||||
"verify_email_email_button": "Confirmar o seu e-mail",
|
||||
"verify_email_change_description": "Solicitou recentemente a alteração do endereço de e-mail que utiliza para entrar na sua conta {{appName}}. Clique no botão abaixo para confirmar o seu novo endereço de e-mail.",
|
||||
"verify_email_change_success_toast": "Atualizou o seu e-mail para {{email}}",
|
||||
"verify_email_change_failure_toast": "Falha ao atualizar o e-mail.",
|
||||
"change_of_email": "Confirme o seu novo endereço de e-mail para {{appName}}",
|
||||
"change_of_email_toast": "Enviámos uma ligação de confirmação para {{email}}. Atualizaremos o seu endereço de e-mail após clicar nessa ligação.",
|
||||
"copy_somewhere_safe": "Guarde esta chave da API num local seguro. Não poderá consultar a mesma novamente.",
|
||||
"verify_email_email_body": "Confirme o seu endereço de e-mail clicando no botão abaixo.",
|
||||
"verify_email_by_code_email_body": "Confirme o seu endereço de e-mail utilizando o código abaixo.",
|
||||
@@ -142,6 +149,7 @@
|
||||
"request_another_invitation_email": "Se não quiser utilizar {{toEmail}} como email do {{appName}}, ou já tem uma conta em {{appName}}, por favor peça outro convite para esse email.",
|
||||
"you_have_been_invited": "Recebeu um convite para juntar-se à equipa {{teamName}}",
|
||||
"user_invited_you": "{{user}} enviou um convite para se juntar a {{team}} de {{entity}} em {{appName}}",
|
||||
"user_invited_you_to_subteam": "{{user}} enviou um convite para se juntar à equipa {{team}} da organização {{parentTeamName}} em {{appName}}",
|
||||
"hidden_team_member_title": "Está oculto nesta equipa",
|
||||
"hidden_team_member_message": "O seu lugar não está pago, faça upgrade para PRO ou informe ao proprietário da equipa que pode pagar pelo seu lugar.",
|
||||
"hidden_team_owner_message": "Precisa de uma conta Pro para usar equipas, ficará oculto até que actualize.",
|
||||
@@ -245,6 +253,7 @@
|
||||
"reset_your_password": "Defina a sua nova palavra-passe seguindo as instruções enviadas para o seu endereço de e-mail.",
|
||||
"email_change": "Inicie sessão novamente com o seu novo endereço de e-mail e palavra-passe.",
|
||||
"create_your_account": "Crie a sua conta",
|
||||
"create_your_calcom_account": "Crie a sua conta Cal.com",
|
||||
"sign_up": "Registar",
|
||||
"youve_been_logged_out": "A sessão foi encerrada",
|
||||
"hope_to_see_you_soon": "Esperamos ver-nos em breve de novo!",
|
||||
@@ -282,6 +291,9 @@
|
||||
"nearly_there_instructions": "Por último, uma breve descrição sobre si e uma fotografia ajudam a obter reservas e a mostrar às pessoas com quem estão a reservar.",
|
||||
"set_availability_instructions": "Definir períodos em que está disponível numa base recorrente. Mais tarde poderá criar mais destes, e atribuí-los a diferentes calendários.",
|
||||
"set_availability": "Definir disponibilidade",
|
||||
"set_availbility_description": "Defina agendamentos para os horários que pretende que sejam reservados.",
|
||||
"share_a_link_or_embed": "Partilhe uma ligação ou incorpore",
|
||||
"share_a_link_or_embed_description": "Partilhe a sua ligação {{appName}} ou incorpore no seu site.",
|
||||
"availability_settings": "Configurações de disponibilidade",
|
||||
"continue_without_calendar": "Continuar sem calendário",
|
||||
"continue_with": "Continuar com {{appName}}",
|
||||
@@ -297,6 +309,7 @@
|
||||
"welcome_to_calcom": "Boas-vindas ao {{appName}}",
|
||||
"welcome_instructions": "Diga-nos como lhe devemos chamar e qual o seu fuso horário. Poderá editar isto mais tarde.",
|
||||
"connect_caldav": "Ligar a servidor CalDav",
|
||||
"connect_ics_feed": "Ligue-se a uma Feed ICS",
|
||||
"connect": "Ligar",
|
||||
"try_for_free": "Experimente gratuitamente",
|
||||
"create_booking_link_with_calcom": "Crie a sua própria ligação de reservas com {{appName}}",
|
||||
@@ -435,6 +448,7 @@
|
||||
"browse_api_documentation": "Consulte a nossa documentação da API",
|
||||
"leverage_our_api": "Tire partido da nossa API para controlo e personalização total.",
|
||||
"create_webhook": "Criar Webhook",
|
||||
"instant_meeting": "Reunião instantânea criada",
|
||||
"booking_cancelled": "Reserva cancelada",
|
||||
"booking_rescheduled": "Reserva Reagendada",
|
||||
"recording_ready": "Ligação de transferência da gravação pronta",
|
||||
@@ -549,6 +563,7 @@
|
||||
"enter_number_between_range": "Introduza um número entre 1 e {{maxOccurences}}",
|
||||
"email_address": "Endereço de email",
|
||||
"enter_valid_email": "Insira um e-mail válido",
|
||||
"please_schedule_future_call": " Se não estivermos disponíveis em {{seconds}} segundos, por favor, agende uma chamada para mais tarde",
|
||||
"location": "Localização",
|
||||
"address": "Local",
|
||||
"enter_address": "Especifique o endereço",
|
||||
@@ -608,6 +623,7 @@
|
||||
"new_member": "Novo Membro",
|
||||
"invite": "Convidar",
|
||||
"add_team_members": "Adicionar membros da equipa",
|
||||
"add_org_members": "Adicionar membros",
|
||||
"add_team_members_description": "Convidar outros a juntarem-se à sua equipa",
|
||||
"add_team_member": "Adicionar membro da equipa",
|
||||
"invite_new_member": "Convidar um Novo Membro",
|
||||
@@ -622,6 +638,7 @@
|
||||
"hide_book_a_team_member_description": "Ocultar o botão Reservar um Membro da equipa das suas páginas públicas.",
|
||||
"danger_zone": "Zona de perigo",
|
||||
"account_deletion_cannot_be_undone": "Cuidado. A eliminação da conta não pode ser anulada.",
|
||||
"team_deletion_cannot_be_undone": "Cuidado. A eliminação de uma equipa não pode ser anulada",
|
||||
"back": "Anterior",
|
||||
"cancel": "Cancelar",
|
||||
"cancel_all_remaining": "Cancelar todos os restantes",
|
||||
@@ -672,6 +689,7 @@
|
||||
"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)",
|
||||
"multiple_duration_timeUnit": "{{count}} $t({{unit}}_timeUnit)",
|
||||
"minutes": "Minutos",
|
||||
"round_robin": "Round Robin",
|
||||
"round_robin_description": "Reuniões de ciclo entre vários membros da equipa.",
|
||||
@@ -684,6 +702,7 @@
|
||||
"add_members": "Adicionar membros...",
|
||||
"no_assigned_members": "Sem membros atribuídos",
|
||||
"assigned_to": "Atribuído a",
|
||||
"you_must_be_logged_in_to": "Deve ter sessão iniciada para {{url}}",
|
||||
"start_assigning_members_above": "Comece a atribuir membros acima",
|
||||
"locked_fields_admin_description": "Os membros não poderão editar isto",
|
||||
"locked_fields_member_description": "Esta opção foi bloqueada pelo administrador da equipa",
|
||||
@@ -788,6 +807,7 @@
|
||||
"additional_input_description": "Requerer que o responsável pelo agendamento especifique entradas complementares antes do pedido ser confirmado",
|
||||
"label": "Etiqueta",
|
||||
"placeholder": "Preenchimento",
|
||||
"display_add_to_calendar_organizer": "Mostrar o e-mail 'Adicionar ao calendário' como organizador",
|
||||
"type": "Tipo",
|
||||
"edit": "Editar",
|
||||
"add_input": "Adicionar um Campo",
|
||||
@@ -796,6 +816,9 @@
|
||||
"requires_confirmation_description": "A reserva precisa ser confirmada manualmente antes de ser enviada para as integrações e ser enviada uma mensagem de confirmação.",
|
||||
"recurring_event": "Evento recorrente",
|
||||
"recurring_event_description": "As pessoas podem subscrever para eventos recorrentes",
|
||||
"cannot_be_used_with_paid_event_types": "Não pode ser utilizado com tipos de eventos pagos",
|
||||
"warning_payment_instant_meeting_event": "As Reuniões Instantâneas ainda não são suportadas com eventos recorrentes e aplicações de pagamento",
|
||||
"warning_instant_meeting_experimental": "Experimental: atualmente os Eventos de Reuniões Instantâneas são experimentais.",
|
||||
"starting": "A iniciar",
|
||||
"disable_guests": "Desativar Convidados",
|
||||
"disable_guests_description": "Desativar a adição de convidados adicionais durante a reserva.",
|
||||
@@ -804,6 +827,7 @@
|
||||
"private_link_label": "Ligação privada",
|
||||
"private_link_hint": "A sua ligação será renovada após cada utilização",
|
||||
"copy_private_link": "Copiar ligação privada",
|
||||
"copy_private_link_to_event": "Copiar ligação privada para o evento",
|
||||
"private_link_description": "Crie um URL privado para partilhar sem expor o seu nome de utilizador do {{appName}}",
|
||||
"invitees_can_schedule": "Os convidados podem agendar",
|
||||
"date_range": "Intervalo de Datas",
|
||||
@@ -863,6 +887,7 @@
|
||||
"next_step": "Ignorar passo",
|
||||
"prev_step": "Passo anterior",
|
||||
"install": "Instalar",
|
||||
"start_paid_trial": "Iniciar avaliação gratuita",
|
||||
"installed": "Instalado",
|
||||
"active_install_one": "{{count}} instalação activa",
|
||||
"active_install_other": "{{count}} instalações activas",
|
||||
@@ -877,6 +902,7 @@
|
||||
"toggle_calendars_conflict": "Escolha os calendários que deseja verificar para evitar conflitos e duplicação de reservas.",
|
||||
"connect_additional_calendar": "Ligar um calendário adicional",
|
||||
"calendar_updated_successfully": "Calendário atualizado com sucesso",
|
||||
"check_here": "Verificar aqui",
|
||||
"conferencing": "Conferência",
|
||||
"calendar": "Calendário",
|
||||
"payments": "Pagamentos",
|
||||
@@ -1048,6 +1074,9 @@
|
||||
"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.",
|
||||
"cal_signup_description": "Gratuito para pessoas singulares. Planos de equipa para funcionalidades colaborativas.",
|
||||
"make_org_private": "Tornar a organização privada",
|
||||
"make_org_private_description": "Os membros da sua organização não poderão ver outros membros da organização quando esta opção estiver ativada.",
|
||||
"make_team_private": "Tornar a equipa privada",
|
||||
"make_team_private_description": "Os membros da sua equipa não poderão ver outros membros da equipa quando esta opção estiver ativada.",
|
||||
"you_cannot_see_team_members": "Não pode ver todos os membros da equipa de uma equipa privada.",
|
||||
@@ -1107,6 +1136,7 @@
|
||||
"developer_documentation": "Documentação para programadores",
|
||||
"get_in_touch": "Entre em contacto",
|
||||
"contact_support": "Contacte o suporte",
|
||||
"premium_support": "Suporte Premium",
|
||||
"community_support": "Apoio da Comunidade",
|
||||
"feedback": "Feedback",
|
||||
"submitted_feedback": "Obrigado pelo seu feedback!",
|
||||
@@ -1270,6 +1300,7 @@
|
||||
"default_calendar_selected": "Calendário predefinido",
|
||||
"hide_from_profile": "Ocultar do perfil",
|
||||
"event_setup_tab_title": "Configuração do evento",
|
||||
"availability_not_found_in_schedule_error": "Não foi encontrada nenhuma disponibilidade na agenda",
|
||||
"event_limit_tab_title": "Limites",
|
||||
"event_limit_tab_description": "Com que frequência pode ser reservado",
|
||||
"event_advanced_tab_description": "Definições do calendário e muito mais...",
|
||||
@@ -1290,6 +1321,7 @@
|
||||
"do_this": "Fazer isto",
|
||||
"turn_off": "Desligar",
|
||||
"turn_on": "Ativar",
|
||||
"cancelled_bookings_cannot_be_rescheduled": "As reservas canceladas não podem ser reagendadas",
|
||||
"settings_updated_successfully": "Definições atualizadas com sucesso",
|
||||
"error_updating_settings": "Erro ao atualizar as definições",
|
||||
"personal_cal_url": "O meu endereço pessoal do {{appName}}",
|
||||
@@ -1313,6 +1345,7 @@
|
||||
"customize_your_brand_colors": "Personalize a cor da sua marca na sua página de reservas.",
|
||||
"pro": "Pro",
|
||||
"removes_cal_branding": "Remove qualquer identificação de marca relacionada o {{appName}}, por exemplo, 'Com tecnologia {{appName}}.'",
|
||||
"instant_meeting_with_title": "Reunião instantânea com {{name}}",
|
||||
"profile_picture": "Imagem do perfil",
|
||||
"upload": "Carregar",
|
||||
"add_profile_photo": "Adicionar imagem de perfil",
|
||||
@@ -1368,6 +1401,7 @@
|
||||
"event_name_info": "O nome do tipo de evento",
|
||||
"event_date_info": "A data do evento",
|
||||
"event_time_info": "A hora de início do evento",
|
||||
"event_type_not_found": "Tipo de evento não encontrado",
|
||||
"location_variable": "Localização",
|
||||
"location_info": "O local do evento",
|
||||
"additional_notes_variable": "Notas Adicionais",
|
||||
@@ -1410,6 +1444,7 @@
|
||||
"slot_length": "Comprimento do espaço",
|
||||
"booking_appearance": "Aparência da reserva",
|
||||
"appearance_team_description": "Faça a gestão das definições de aparência da reserva da sua equipa",
|
||||
"appearance_org_description": "Faça a gestão das definições de aparência da reserva da sua organização",
|
||||
"only_owner_change": "Só o proprietário desta equipa pode fazer alterações na reserva da equipa ",
|
||||
"team_disable_cal_branding_description": "Remove qualquer identificação de marca relacionada o {{appName}}, por exemplo, 'Com tecnologia {{appName}}'",
|
||||
"invited_by_team": "{{teamName}} enviou um convite para se juntar à sua equipa como {{role}}",
|
||||
@@ -1463,6 +1498,7 @@
|
||||
"password_updated": "Palavra-passe atualizada!",
|
||||
"pending_payment": "Pagamento pendente",
|
||||
"pending_invites": "Convites pendentes",
|
||||
"pending_organization_invites": "Convites de organização pendentes",
|
||||
"not_on_cal": "Não existe em {{appName}}",
|
||||
"no_calendar_installed": "Nenhum calendário instalado",
|
||||
"no_calendar_installed_description": "Ainda não associou nenhum dos seus calendários",
|
||||
@@ -1474,6 +1510,8 @@
|
||||
"report_app": "Reportar aplicação",
|
||||
"limit_booking_frequency": "Limitar a frequência das reservas",
|
||||
"limit_booking_frequency_description": "Limitar o número de vezes que este evento pode ser reservado",
|
||||
"limit_booking_only_first_slot": "Limitar a reserva apenas à primeira vaga",
|
||||
"limit_booking_only_first_slot_description": "Permitir que apenas a primeira vaga de todos os dias seja reservada",
|
||||
"limit_total_booking_duration": "Limitar a duração total da reserva",
|
||||
"limit_total_booking_duration_description": "Limitar o tempo total que pode ser reservado para este evento",
|
||||
"add_limit": "Adicionar limite",
|
||||
@@ -1541,10 +1579,14 @@
|
||||
"your_org_disbanded_successfully": "A sua organização foi dissolvida com sucesso",
|
||||
"error_creating_team": "Erro ao criar a equipa",
|
||||
"you": "Você",
|
||||
"or_continue_with": "Ou continuar com",
|
||||
"resend_email": "Reenviar e-mail",
|
||||
"member_already_invited": "O membro já foi convidado",
|
||||
"already_in_use_error": "O nome de utilizador já está a ser utilizado",
|
||||
"enter_email_or_username": "Especifique um e-mail ou nome de utilizador",
|
||||
"enter_email": "Inserir um e-mail",
|
||||
"enter_emails": "Inserir e-mails",
|
||||
"too_many_invites": "Está limitado a convidar um máximo de {{nbUsers}} utilizadores de cada vez.",
|
||||
"team_name_taken": "Este nome já está a ser utilizado",
|
||||
"must_enter_team_name": "Deve especificar um nome para a equipa",
|
||||
"team_url_required": "Deve especificar um endereço para a equipa",
|
||||
@@ -1624,6 +1666,7 @@
|
||||
"individual": "Individual",
|
||||
"all_bookings_filter_label": "Todas as reservas",
|
||||
"all_users_filter_label": "Todos os utilizadores",
|
||||
"all_event_types_filter_label": "Todos os tipos de evento",
|
||||
"your_bookings_filter_label": "As suas reservas",
|
||||
"meeting_url_variable": "URL da reunião",
|
||||
"meeting_url_info": "URL da reunião de conferência",
|
||||
@@ -1720,9 +1763,11 @@
|
||||
"organizer_timezone": "Fuso horário do organizador",
|
||||
"email_user_cta": "Ver convite",
|
||||
"email_no_user_invite_heading_team": "Recebeu um convite para fazer parte de uma equipa {{appName}}",
|
||||
"email_no_user_invite_heading_subteam": "Recebeu um convite para fazer parte de uma equipa da organização {{parentTeamName}}",
|
||||
"email_no_user_invite_heading_org": "Recebeu um convite para fazer parte de uma organização {{appName}}",
|
||||
"email_no_user_invite_subheading": "Recebeu um convite de {{invitedBy}} para fazer parte da respetiva equipa em {{appName}}. {{appName}} é um ferramenta de conciliação e agendamento de eventos que lhe permite a si e à sua equipa agendar reuniões sem o pingue-pongue de e-mails.",
|
||||
"email_user_invite_subheading_team": "Recebeu um convite de {{invitedBy}} para fazer parte da equipa `{{teamName}}` em {{appName}}. {{appName}} é uma ferramenta de conciliação e agendamento de eventos que lhe permite a si e à sua equipa agendar reuniões sem o pingue-pongue de mensagens eletrónicas.",
|
||||
"email_user_invite_subheading_subteam": "Recebeu um convite de {{invitedBy}} para fazer parte da equipa {{teamName}} da organização {{parentTeamName}} em {{appName}}. {{appName}} é uma ferramenta de conciliação e agendamento de eventos que lhe permite, a si e à sua equipa, agendar reuniões sem o pingue-pongue de mensagens eletrónicas.",
|
||||
"email_user_invite_subheading_org": "Recebeu um convite de {{invitedBy}} para fazer parte de {{teamName}} da sua organização em {{appName}}. {{appName}} é a solução de agendamento de eventos que lhe permite a si e à sua organização agendar reuniões sem o pingue-pongue de e-mails.",
|
||||
"email_no_user_invite_steps_intro": "Vamos ajudar com alguns pequenos passos para que você e a sua {{entity}} possam desfrutar rapidamente de uma gestão de agendamentos sem complicações.",
|
||||
"email_no_user_step_one": "Escolha o seu nome de utilizador",
|
||||
@@ -1855,7 +1900,9 @@
|
||||
"review_event_type": "Rever Tipo de Evento",
|
||||
"looking_for_more_analytics": "À procura de mais análises?",
|
||||
"looking_for_more_insights": "À procura de mais Insights?",
|
||||
"filters": "Filtros",
|
||||
"add_filter": "Adicionar filtro",
|
||||
"remove_filters": "Limpar todos os filtros",
|
||||
"select_user": "Selecionar utilizador",
|
||||
"select_event_type": "Selecionar tipo de evento",
|
||||
"select_date_range": "Selecionar intervalo de datas",
|
||||
@@ -1946,6 +1993,7 @@
|
||||
"connect_google_workspace": "Associar o Google Workspace",
|
||||
"google_workspace_admin_tooltip": "Tem de ser um Administrador do Workspace para utilizar esta funcionalidade",
|
||||
"first_event_type_webhook_description": "Crie o seu primeiro webhook para este tipo de evento",
|
||||
"create_instant_meeting_webhook_description": "Crie o seu primeiro webhook com 'Reunião instantânea criada' como acionador para este tipo de evento",
|
||||
"install_app_on": "Instalar aplicação em",
|
||||
"create_for": "Criar para",
|
||||
"currency": "Moeda",
|
||||
@@ -2010,6 +2058,8 @@
|
||||
"add_times_to_your_email": "Selecione alguns horários disponíveis e incorpore-os no seu E-mail",
|
||||
"select_time": "Selecionar horário",
|
||||
"select_date": "Selecionar data",
|
||||
"connecting_you_to_someone": "Estamos a conectá-lo a alguém.",
|
||||
"please_do_not_close_this_tab": "Não feche este separador",
|
||||
"see_all_available_times": "Ver todos os horários disponíveis",
|
||||
"org_team_names_example_1": "Por exemplo, Equipa de Marketing",
|
||||
"org_team_names_example_2": "Por exemplo, Equipa de Vendas",
|
||||
@@ -2027,8 +2077,12 @@
|
||||
"description_requires_booker_email_verification": "Para garantir a confirmação do e-mail do responsável pela reserva antes de agendar eventos",
|
||||
"requires_confirmation_mandatory": "As mensagens de texto só podem ser enviadas aos participantes quando o tipo de evento requerer confirmação.",
|
||||
"organizations": "Organizações",
|
||||
"upload_cal_video_logo": "Carregar logótipo de Cal Video",
|
||||
"update_cal_video_logo": "Atualizar logótipo de Cal Video",
|
||||
"cal_video_logo_upload_instruction": "Para garantir que o seu logótipo seja visível contra o fundo escuro do Cal Video, carregue uma imagem de cores claras em formato PNG ou SVG, para manter a transparência.",
|
||||
"org_admin_other_teams": "Outras equipas",
|
||||
"org_admin_other_teams_description": "Aqui pode ver equipas dentro da sua organização e das quais não faz parte. Pode adicionar-se às mesmas, se necessário.",
|
||||
"not_part_of_org": "Você não faz parte de nenhuma organização",
|
||||
"no_other_teams_found": "Não foram encontradas outras equipas",
|
||||
"no_other_teams_found_description": "Não há outras equipas nesta organização.",
|
||||
"attendee_first_name_variable": "Primeiro nome do participante",
|
||||
@@ -2064,7 +2118,9 @@
|
||||
"org_error_processing": "Ocorreu um erro ao processar esta organização",
|
||||
"orgs_page_description": "Uma lista de todas as organizações. Aceitar uma organização permitirá que todos os utilizadores com esse domínio de e-mail se registem SEM a verificação de e-mail.",
|
||||
"unverified": "Não verificada",
|
||||
"verified": "Confirmado",
|
||||
"dns_missing": "DNS em falta",
|
||||
"dns_configured": "DNS configurado",
|
||||
"mark_dns_configured": "Marcar como o DNS configurado",
|
||||
"value": "Valor",
|
||||
"your_organization_updated_sucessfully": "A sua organização foi atualizada com sucesso",
|
||||
@@ -2074,8 +2130,11 @@
|
||||
"oAuth": "OAuth",
|
||||
"recently_added": "Adicionados recentemente",
|
||||
"connect_all_calendars": "Associe todos os seus calendários",
|
||||
"connect_all_calendars_description": "{{appName}} lê a disponibilidade de todos os seus calendários existentes.",
|
||||
"workflow_automation": "Automação de fluxo de trabalho",
|
||||
"workflow_automation_description": "Personalize a sua experiência de agendamento com fluxos de trabalho",
|
||||
"scheduling_for_your_team": "Automação de fluxo de trabalho",
|
||||
"scheduling_for_your_team_description": "Faça o agendamento para a sua equipa, com agendamento coletivo e distribuição equilibrada",
|
||||
"no_members_found": "Nenhum membro encontrado",
|
||||
"event_setup_length_error": "Configuração do Evento: a duração deve ser de, pelo menos, 1 minuto.",
|
||||
"availability_schedules": "Horários de Disponibilidade",
|
||||
@@ -2108,10 +2167,100 @@
|
||||
"overlay_my_calendar": "Sobrepor o meu calendário",
|
||||
"overlay_my_calendar_toc": "Ao ligar-se ao seu calendário, você aceita a nossa política de privacidade e termos de utilização. Pode revogar o acesso a qualquer momento.",
|
||||
"view_overlay_calendar_events": "Consulte os eventos do seu calendário para evitar agendamentos conflituantes.",
|
||||
"join_event_location": "Junte-se a {{eventLocationType}}",
|
||||
"troubleshooting": "Resolução de problemas",
|
||||
"calendars_were_checking_for_conflicts": "Calendários que estamos a verificar por conflitos",
|
||||
"availabilty_schedules": "Horários de disponibilidade",
|
||||
"manage_calendars": "Gerir calendários",
|
||||
"manage_availability_schedules": "Gerir horários de disponibilidade",
|
||||
"lock_timezone_toggle_on_booking_page": "Bloquear fuso horário na página de reserva",
|
||||
"description_lock_timezone_toggle_on_booking_page": "Para bloquear o fuso horário na página de reservas. Útil para eventos presenciais.",
|
||||
"event_setup_multiple_payment_apps_error": "Só pode ter uma aplicação de pagamentos ativada por tipo de evento.",
|
||||
"number_in_international_format": "Insira o número no formato internacional.",
|
||||
"install_calendar": "Instalar Calendário",
|
||||
"branded_subdomain": "Subdomínio de marca",
|
||||
"branded_subdomain_description": "Obtenha o seu próprio subdomínio de marca. Por exemplo, acme.cal.com",
|
||||
"org_insights": "Insights de toda a organização",
|
||||
"org_insights_description": "Compreenda como toda a sua organização está a utilizar o tempo",
|
||||
"extensive_whitelabeling": "Apoio dedicado à integração e engenharia",
|
||||
"extensive_whitelabeling_description": "Personalize a sua experiência de agendamento com o seu próprio logótipo, cores e muito mais",
|
||||
"unlimited_teams": "Equipas ilimitadas",
|
||||
"unlimited_teams_description": "Adicione à sua organização tantas subequipas quanto for necessário",
|
||||
"unified_billing": "Faturação unificada",
|
||||
"unified_billing_description": "Adicione um único cartão de crédito para pagar todas as subscrições da sua equipa",
|
||||
"advanced_managed_events": "Tipos de evento geridos avançados",
|
||||
"advanced_managed_events_description": "Adicione um único cartão de crédito para pagar todas as subscrições da sua equipa",
|
||||
"enterprise_description": "Atualize para Enterprise para criar a sua Organização",
|
||||
"create_your_org": "Crie a sua Organização",
|
||||
"create_your_org_description": "Atualize para Enterprise e receba um subdomínio, faturação unificada, Insights, personalização abrangente da imagem corporativa e muito mais",
|
||||
"other_payment_app_enabled": "Só pode ativar uma aplicação de pagamento por tipo de evento",
|
||||
"admin_delete_organization_description": "<ul><li>As equipas que são membros desta organização também serão eliminadas, em conjunto com os respetivos tipos de evento</li><li>Utilizadores que faziam parte da organização não serão eliminados e os seus tipos de evento também permanecerão intactos.</li><li>Os nomes de utilizador serão alterados para permitir que possam existir fora da organização</li></ul>",
|
||||
"admin_delete_organization_title": "Eliminar {{organizationName}}?",
|
||||
"published": "Publicada",
|
||||
"unpublished": "Não publicada",
|
||||
"publish": "Publicar",
|
||||
"org_publish_error": "Não foi possível publicar a organização",
|
||||
"troubleshooter_tooltip": "Abra o solucionador de problemas e descubra o que está errado com o seu agendamento",
|
||||
"need_help": "Precisa de ajuda?",
|
||||
"troubleshooter": "Solucionador de problemas",
|
||||
"please_install_a_calendar": "Por favor, instale um calendário",
|
||||
"instant_tab_title": "Reserva imediata",
|
||||
"instant_event_tab_description": "Permita que as pessoas reservem imediatamente",
|
||||
"uprade_to_create_instant_bookings": "Atualize para Enterprise e permita que os convidados se juntem a uma chamada instantânea em que os participantes podem entrar diretamente. Isto é apenas para tipos de evento de equipa",
|
||||
"dont_want_to_wait": "Não quer esperar?",
|
||||
"meeting_started": "Reunião iniciada",
|
||||
"pay_and_book": "Pagar para reservar",
|
||||
"booking_not_found_error": "Não foi possível encontrar a reserva",
|
||||
"booking_seats_full_error": "Os lugares para reserva estão cheios",
|
||||
"missing_payment_credential_error": "Credenciais de pagamento em falta",
|
||||
"missing_payment_app_id_error": "ID da aplicação de pagamento em falta",
|
||||
"not_enough_available_seats_error": "A reserva não tem lugares disponíveis suficientes",
|
||||
"user_redirect_title": "{{username}} está ausente por um breve período de tempo.",
|
||||
"user_redirect_description": "Entretanto, {{profile.username}} ficará responsável por todas as novas reuniões agendadas em nome de {{username}}.",
|
||||
"out_of_office": "Fora do escritório",
|
||||
"out_of_office_description": "Configure as ações no seu perfil enquanto estiver ausente.",
|
||||
"send_request": "Enviar pedido",
|
||||
"start_date_and_end_date_required": "Data de início e data de fim são necessárias",
|
||||
"start_date_must_be_before_end_date": "Data de início deve ser anterior à data de fim",
|
||||
"start_date_must_be_in_the_future": "Data de início deve ser no futuro",
|
||||
"user_not_found": "Utilizador não encontrado",
|
||||
"out_of_office_entry_already_exists": "Já existe uma entrada de fora do escritório",
|
||||
"out_of_office_id_required": "É necessária a ID da entrada de fora do escritório",
|
||||
"booking_redirect_infinite_not_allowed": "Já existe um redirecionamento de reservas desse utilizador para si.",
|
||||
"success_entry_created": "Criou uma nova entrada com sucesso",
|
||||
"booking_redirect_email_subject": "Notificação de redirecionamento de reserva",
|
||||
"booking_redirect_email_title": "Notificação de redirecionamento de reserva",
|
||||
"booking_redirect_email_description": "Recebeu um redirecionamento de reservas de {{toName}}, pelo que as ligações do respetivo perfil serão redirecionados para o seu perfil durante o intervalo de tempo: ",
|
||||
"success_accept_booking_redirect": "Aceitou este pedido de redirecionamento de reservas.",
|
||||
"success_reject_booking_redirect": "Rejeitou este pedido de redirecionamento de reservas.",
|
||||
"copy_link_booking_redirect_request": "Copiar ligação para partilhar o pedido",
|
||||
"booking_redirect_request_title": "Pedido de redirecionamento de reservas",
|
||||
"select_team_member": "Selecionar o membro da equipa",
|
||||
"going_away_title": "Vai ausentar-se? Simplesmente marque a ligação do seu perfil como indisponível por um determinado período de tempo.",
|
||||
"redirect_team_enabled": "Redirecionar o seu perfil para outro membro da equipa",
|
||||
"redirect_team_disabled": "Redirecionar o seu perfil para outro membro da equipa (é necessário um plano Team)",
|
||||
"out_of_office_unavailable_list": "Lista de indisponibilidades de fora do escritório",
|
||||
"success_deleted_entry_out_of_office": "Entrada eliminada com sucesso",
|
||||
"temporarily_out_of_office": "Temporariamente fora do escritório?",
|
||||
"add_a_redirect": "Adicionar um redirecionamento",
|
||||
"create_entry": "Criar entrada",
|
||||
"time_range": "Intervalo de tempo",
|
||||
"automatically_add_all_team_members": "Adicionar todos os membros da equipa, incluindo membros futuros",
|
||||
"redirect_to": "Redirecionar para",
|
||||
"having_trouble_finding_time": "Com dificuldades em encontrar um horário?",
|
||||
"show_more": "Mostrar mais",
|
||||
"assignment_description": "Agende reuniões quando todos estiverem disponíveis ou rode entre os membros da sua equipa",
|
||||
"lowest": "a mais baixa",
|
||||
"low": "baixa",
|
||||
"medium": "média",
|
||||
"high": "alta",
|
||||
"Highest": "a mais alta",
|
||||
"send_booker_to": "Enviar responsável pela reserva para",
|
||||
"set_priority": "Definir prioridade",
|
||||
"priority_for_user": "Prioridade para {{userName}}",
|
||||
"change_priority": "alterar prioridade",
|
||||
"field_identifiers_as_variables": "Utilize identificadores de campo como variáveis para o seu redirecionamento de evento personalizado",
|
||||
"field_identifiers_as_variables_with_example": "Utilize identificadores de campo como variáveis para o seu redirecionamento de evento personalizado (ex.: {{variable}})",
|
||||
"account_already_linked": "A conta já está vinculada",
|
||||
"ADD_NEW_STRINGS_ABOVE_THIS_LINE_TO_PREVENT_MERGE_CONFLICTS": "↑↑↑↑↑↑↑↑↑↑↑↑↑ Add your new strings above here ↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑"
|
||||
}
|
||||
|
||||
@@ -1447,6 +1447,8 @@
|
||||
"copy_link_to_form": "将链接复制到表格",
|
||||
"theme": "主题",
|
||||
"theme_applies_note": "这仅适用于您的公开预约页面",
|
||||
"app_theme": "仪表板主题",
|
||||
"app_theme_applies_note": "这仅在您登录仪表板时适用",
|
||||
"theme_system": "系统默认设置",
|
||||
"add_a_team": "添加团队",
|
||||
"add_webhook_description": "当 {{appName}} 上有活动时实时接收会议数据",
|
||||
|
||||
@@ -0,0 +1,47 @@
|
||||
#!/bin/bash
|
||||
|
||||
# Only deploy to production
|
||||
if [ "$VERCEL_ENV" != "production" ]; then exit 0; fi
|
||||
|
||||
checkRoute () {
|
||||
if [ "$1" != '1' ]; then rm -rf $2; fi
|
||||
}
|
||||
|
||||
# These conditionals are used to remove directories from the build that are not needed in production
|
||||
# This is to reduce the size of the build and prevent OOM errors
|
||||
checkRoute "$APP_ROUTER_EVENT_TYPES_ENABLED" app/future/event-types
|
||||
checkRoute "$APP_ROUTER_SETTINGS_ADMIN_ENABLED" app/future/settings/admin
|
||||
checkRoute "$APP_ROUTER_APPS_INSTALLED_CATEGORY_ENABLED" app/future/apps/installed
|
||||
checkRoute "$APP_ROUTER_APPS_SLUG_ENABLED" app/future/apps/\[slug\]
|
||||
checkRoute "$APP_ROUTER_APPS_SLUG_SETUP_ENABLED" app/future/apps/\[slug\]/setup
|
||||
checkRoute "$APP_ROUTER_APPS_CATEGORIES_ENABLED" app/future/apps/categories
|
||||
checkRoute "$APP_ROUTER_APPS_CATEGORIES_CATEGORY_ENABLED" app/future/apps/categories/\[category\]
|
||||
checkRoute "$APP_ROUTER_WORKFLOWS_ENABLED" app/future/workflows
|
||||
checkRoute "$APP_ROUTER_SETTINGS_TEAMS_ENABLED" app/future/settings/teams
|
||||
checkRoute "$APP_ROUTER_GETTING_STARTED_STEP_ENABLED" app/future/getting-started
|
||||
checkRoute "$APP_ROUTER_APPS_ENABLED" app/future/apps
|
||||
checkRoute "$APP_ROUTER_BOOKINGS_STATUS_ENABLED" app/future/bookings
|
||||
checkRoute "$APP_ROUTER_VIDEO_ENABLED" app/future/video
|
||||
checkRoute "$APP_ROUTER_TEAMS_ENABLED" app/future/teams
|
||||
|
||||
# These are routes that don't have and environment variable to enable or disable them
|
||||
# Will stop removing gradually as we test and confirm that they are working
|
||||
rm -rf \
|
||||
app/future/[user]\
|
||||
app/future/auth\
|
||||
app/future/booking\
|
||||
app/future/connect-and-join\
|
||||
app/future/d\
|
||||
app/future/enterprise\
|
||||
app/future/insights\
|
||||
app/future/maintenance\
|
||||
app/future/more\
|
||||
app/future/org\
|
||||
app/future/payment\
|
||||
app/future/reschedule\
|
||||
app/future/routing-forms\
|
||||
app/future/settings\
|
||||
app/future/signup\
|
||||
app/future/team
|
||||
|
||||
exit 1
|
||||
@@ -42,6 +42,7 @@ describe("handleChildrenEventTypes", () => {
|
||||
connectedLink: null,
|
||||
prisma: prismaMock,
|
||||
profileId: null,
|
||||
updatedValues: {},
|
||||
});
|
||||
expect(result.newUserIds).toEqual(undefined);
|
||||
expect(result.oldUserIds).toEqual(undefined);
|
||||
@@ -62,6 +63,7 @@ describe("handleChildrenEventTypes", () => {
|
||||
connectedLink: null,
|
||||
prisma: prismaMock,
|
||||
profileId: null,
|
||||
updatedValues: {},
|
||||
});
|
||||
expect(result.newUserIds).toEqual(undefined);
|
||||
expect(result.oldUserIds).toEqual(undefined);
|
||||
@@ -88,6 +90,7 @@ describe("handleChildrenEventTypes", () => {
|
||||
connectedLink: null,
|
||||
prisma: prismaMock,
|
||||
profileId: null,
|
||||
updatedValues: {},
|
||||
});
|
||||
expect(result.newUserIds).toEqual(undefined);
|
||||
expect(result.oldUserIds).toEqual(undefined);
|
||||
@@ -125,6 +128,7 @@ describe("handleChildrenEventTypes", () => {
|
||||
connectedLink: null,
|
||||
prisma: prismaMock,
|
||||
profileId: null,
|
||||
updatedValues: {},
|
||||
});
|
||||
expect(prismaMock.eventType.create).toHaveBeenCalledWith({
|
||||
data: {
|
||||
@@ -173,13 +177,14 @@ describe("handleChildrenEventTypes", () => {
|
||||
connectedLink: null,
|
||||
prisma: prismaMock,
|
||||
profileId: null,
|
||||
updatedValues: {
|
||||
bookingLimits: undefined,
|
||||
},
|
||||
});
|
||||
expect(prismaMock.eventType.update).toHaveBeenCalledWith({
|
||||
data: {
|
||||
...evType,
|
||||
hidden: false,
|
||||
bookingLimits: undefined,
|
||||
durationLimits: undefined,
|
||||
recurringEvent: undefined,
|
||||
hashedLink: { create: { link: expect.any(String) } },
|
||||
},
|
||||
where: {
|
||||
@@ -207,6 +212,7 @@ describe("handleChildrenEventTypes", () => {
|
||||
connectedLink: null,
|
||||
prisma: prismaMock,
|
||||
profileId: null,
|
||||
updatedValues: {},
|
||||
});
|
||||
expect(result.newUserIds).toEqual([]);
|
||||
expect(result.oldUserIds).toEqual([]);
|
||||
@@ -232,6 +238,7 @@ describe("handleChildrenEventTypes", () => {
|
||||
connectedLink: null,
|
||||
prisma: prismaMock,
|
||||
profileId: null,
|
||||
updatedValues: {},
|
||||
});
|
||||
// Have been called
|
||||
expect(result.newUserIds).toEqual([5]);
|
||||
@@ -270,6 +277,7 @@ describe("handleChildrenEventTypes", () => {
|
||||
connectedLink: null,
|
||||
prisma: prismaMock,
|
||||
profileId: null,
|
||||
updatedValues: {},
|
||||
});
|
||||
expect(prismaMock.eventType.create).toHaveBeenCalledWith({
|
||||
data: {
|
||||
@@ -318,14 +326,15 @@ describe("handleChildrenEventTypes", () => {
|
||||
connectedLink: null,
|
||||
prisma: prismaMock,
|
||||
profileId: null,
|
||||
updatedValues: {
|
||||
length: 30,
|
||||
},
|
||||
});
|
||||
expect(prismaMock.eventType.update).toHaveBeenCalledWith({
|
||||
data: {
|
||||
...evType,
|
||||
bookingLimits: undefined,
|
||||
durationLimits: undefined,
|
||||
recurringEvent: undefined,
|
||||
scheduleId: undefined,
|
||||
hidden: false,
|
||||
length: 30,
|
||||
hashedLink: undefined,
|
||||
},
|
||||
where: {
|
||||
userId_parentId: {
|
||||
@@ -379,6 +388,7 @@ describe("handleChildrenEventTypes", () => {
|
||||
connectedLink: null,
|
||||
prisma: prismaMock,
|
||||
profileId: null,
|
||||
updatedValues: {},
|
||||
});
|
||||
expect(prismaMock.eventType.create).toHaveBeenCalledWith({
|
||||
data: {
|
||||
@@ -404,13 +414,8 @@ describe("handleChildrenEventTypes", () => {
|
||||
});
|
||||
expect(prismaMock.eventType.update).toHaveBeenCalledWith({
|
||||
data: {
|
||||
...evType,
|
||||
bookingLimits: undefined,
|
||||
durationLimits: undefined,
|
||||
recurringEvent: undefined,
|
||||
hidden: false,
|
||||
hashedLink: undefined,
|
||||
workflows: undefined,
|
||||
scheduleId: undefined,
|
||||
},
|
||||
where: {
|
||||
userId_parentId: {
|
||||
|
||||
@@ -37,6 +37,7 @@ export const getSampleUserInSession = function () {
|
||||
disableImpersonation: false,
|
||||
organizationId: null,
|
||||
theme: "",
|
||||
appTheme: "",
|
||||
createdDate: new Date(),
|
||||
trialEndsAt: new Date(),
|
||||
completedOnboarding: false,
|
||||
|
||||
@@ -14,6 +14,7 @@ import closecom_config_json from "./closecom/config.json";
|
||||
import cron_config_json from "./cron/config.json";
|
||||
import { metadata as dailyvideo__metadata_ts } from "./dailyvideo/_metadata";
|
||||
import deel_config_json from "./deel/config.json";
|
||||
import demodesk_config_json from "./demodesk/config.json";
|
||||
import discord_config_json from "./discord/config.json";
|
||||
import eightxeight_config_json from "./eightxeight/config.json";
|
||||
import element_call_config_json from "./element-call/config.json";
|
||||
@@ -34,6 +35,7 @@ import ics_feedcalendar_config_json from "./ics-feedcalendar/config.json";
|
||||
import intercom_config_json from "./intercom/config.json";
|
||||
import { metadata as jitsivideo__metadata_ts } from "./jitsivideo/_metadata";
|
||||
import { metadata as larkcalendar__metadata_ts } from "./larkcalendar/_metadata";
|
||||
import linear_config_json from "./linear/config.json";
|
||||
import make_config_json from "./make/config.json";
|
||||
import matomo_config_json from "./matomo/config.json";
|
||||
import metapixel_config_json from "./metapixel/config.json";
|
||||
@@ -96,6 +98,7 @@ export const appStoreMetadata = {
|
||||
cron: cron_config_json,
|
||||
dailyvideo: dailyvideo__metadata_ts,
|
||||
deel: deel_config_json,
|
||||
demodesk: demodesk_config_json,
|
||||
discord: discord_config_json,
|
||||
eightxeight: eightxeight_config_json,
|
||||
"element-call": element_call_config_json,
|
||||
@@ -116,6 +119,7 @@ export const appStoreMetadata = {
|
||||
intercom: intercom_config_json,
|
||||
jitsivideo: jitsivideo__metadata_ts,
|
||||
larkcalendar: larkcalendar__metadata_ts,
|
||||
linear: linear_config_json,
|
||||
make: make_config_json,
|
||||
matomo: matomo_config_json,
|
||||
metapixel: metapixel_config_json,
|
||||
|
||||
@@ -14,6 +14,7 @@ export const apiHandlers = {
|
||||
closecom: import("./closecom/api"),
|
||||
cron: import("./cron/api"),
|
||||
deel: import("./deel/api"),
|
||||
demodesk: import("./demodesk/api"),
|
||||
discord: import("./discord/api"),
|
||||
eightxeight: import("./eightxeight/api"),
|
||||
"element-call": import("./element-call/api"),
|
||||
@@ -34,6 +35,7 @@ export const apiHandlers = {
|
||||
intercom: import("./intercom/api"),
|
||||
jitsivideo: import("./jitsivideo/api"),
|
||||
larkcalendar: import("./larkcalendar/api"),
|
||||
linear: import("./linear/api"),
|
||||
make: import("./make/api"),
|
||||
matomo: import("./matomo/api"),
|
||||
metapixel: import("./metapixel/api"),
|
||||
|
||||
@@ -5,6 +5,7 @@
|
||||
import around_config_json from "./around/config.json";
|
||||
import campfire_config_json from "./campfire/config.json";
|
||||
import { metadata as dailyvideo__metadata_ts } from "./dailyvideo/_metadata";
|
||||
import demodesk_config_json from "./demodesk/config.json";
|
||||
import discord_config_json from "./discord/config.json";
|
||||
import eightxeight_config_json from "./eightxeight/config.json";
|
||||
import element_call_config_json from "./element-call/config.json";
|
||||
@@ -41,6 +42,7 @@ export const appStoreMetadata = {
|
||||
around: around_config_json,
|
||||
campfire: campfire_config_json,
|
||||
dailyvideo: dailyvideo__metadata_ts,
|
||||
demodesk: demodesk_config_json,
|
||||
discord: discord_config_json,
|
||||
eightxeight: eightxeight_config_json,
|
||||
"element-call": element_call_config_json,
|
||||
|
||||
@@ -0,0 +1,30 @@
|
||||
---
|
||||
items:
|
||||
- iframe: { src: https://www.youtube.com/embed/X6XVzJ0E8xY }
|
||||
- 1.jpeg
|
||||
- 2.webp
|
||||
- 3.webp
|
||||
- 4.webp
|
||||
---
|
||||
|
||||
{DESCRIPTION}
|
||||
|
||||
### AI superpowers for your sales meetings
|
||||
|
||||
#### Coach and onboard sellers with AI-based insights
|
||||
- Automatically record all meetings, GDPR & MiFID compliant
|
||||
- Transcribe and summarize meetings using Demodesk’s AI
|
||||
- Coach teams along conversational metrics and scorecards
|
||||
- Automatically log key information into your CRM
|
||||
|
||||
#### Schedule more customer meetings, on auto-pilot
|
||||
- Let customers schedule time without email back-and-forth
|
||||
- Qualify website leads and route them to the right person
|
||||
- Automate and control handoff processes in your team
|
||||
- Automate admin work and sync interactions to your CRM
|
||||
|
||||
#### Run secure video meetings that convert into revenue
|
||||
- Host professional, secure video meetings without downloads
|
||||
- Engage customers with interactive screen sharing
|
||||
- Coach sellers in real-time along standardized playbooks
|
||||
- Automate preparation, follow-up and CRM documentation
|
||||
@@ -0,0 +1,16 @@
|
||||
import { createDefaultInstallation } from "@calcom/app-store/_utils/installation";
|
||||
import type { AppDeclarativeHandler } from "@calcom/types/AppHandler";
|
||||
|
||||
import appConfig from "../config.json";
|
||||
|
||||
const handler: AppDeclarativeHandler = {
|
||||
appType: appConfig.type,
|
||||
variant: appConfig.variant,
|
||||
slug: appConfig.slug,
|
||||
supportsMultipleInstalls: false,
|
||||
handlerType: "add",
|
||||
createCredential: ({ appType, user, slug, teamId }) =>
|
||||
createDefaultInstallation({ appType, user: user, slug, key: {}, teamId }),
|
||||
};
|
||||
|
||||
export default handler;
|
||||
@@ -0,0 +1 @@
|
||||
export { default as add } from "./add";
|
||||
@@ -0,0 +1,25 @@
|
||||
{
|
||||
"/*": "Don't modify slug - If required, do it using cli edit command",
|
||||
"name": "Demodesk",
|
||||
"slug": "demodesk",
|
||||
"type": "demodesk_conferencing",
|
||||
"logo": "icon.svg",
|
||||
"url": "https://demodesk.com",
|
||||
"variant": "conferencing",
|
||||
"categories": ["conferencing"],
|
||||
"publisher": "Cal.com, Inc.",
|
||||
"email": "support@cal.com",
|
||||
"appData": {
|
||||
"location": {
|
||||
"type": "integrations:{SLUG}_video",
|
||||
"label": "{TITLE}",
|
||||
"linkType": "static",
|
||||
"organizerInputPlaceholder": "https://demodesk.com/meet/mylink",
|
||||
"urlRegExp": "^http(s)?:\\/\\/(www\\.)?demodesk.com\\/[a-zA-Z0-9]*"
|
||||
}
|
||||
},
|
||||
"description": "Run Professional Video Meetings, Coach Sales Teams in Real-Time with AI, And Schedule Meetings on Auto-Pilot. 100% GDPR Compliant, Enterprise Ready.",
|
||||
"isTemplate": false,
|
||||
"__createdUsingCli": true,
|
||||
"__template": "event-type-location-video-static"
|
||||
}
|
||||
@@ -0,0 +1 @@
|
||||
export * as api from "./api";
|
||||
@@ -0,0 +1,14 @@
|
||||
{
|
||||
"$schema": "https://json.schemastore.org/package.json",
|
||||
"private": true,
|
||||
"name": "@calcom/demodesk",
|
||||
"version": "0.0.1",
|
||||
"main": "./index.ts",
|
||||
"dependencies": {
|
||||
"@calcom/lib": "*"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@calcom/types": "*"
|
||||
},
|
||||
"description": "Run Professional Video Meetings, Coach Sales Teams in Real-Time with AI, And Schedule Meetings on Auto-Pilot. 100% GDPR Compliant, Enterprise Ready."
|
||||
}
|
||||
|
After Width: | Height: | Size: 146 KiB |
|
After Width: | Height: | Size: 425 KiB |
|
After Width: | Height: | Size: 130 KiB |
|
After Width: | Height: | Size: 2.2 MiB |
|
After Width: | Height: | Size: 37 KiB |
|
After Width: | Height: | Size: 32 KiB |
@@ -0,0 +1,4 @@
|
||||
<svg width="2484" height="2484" viewBox="0 0 2484 2484" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||
<rect width="2484" height="2484" rx="200" fill="#DDDDDD"/>
|
||||
<path d="M1126.24 514.004H687.083C663.823 514.004 645 532.827 645 556.088V656.4C645 679.661 663.823 698.484 687.083 698.484H1109.75C1451.81 698.484 1699.98 941.881 1699.98 1241.1C1699.98 1365.7 1657.75 1480.59 1586.85 1572.29C1599.97 1525.62 1607.02 1476.42 1607.02 1425.58C1607.02 1126.36 1418.26 882.963 1025.89 882.963H687.083C663.823 882.963 645 901.786 645 925.047V1025.36C645 1048.62 663.823 1067.44 687.083 1067.44H1019.19C1270.01 1068.59 1412.48 1228.09 1412.48 1425.58C1412.48 1623.06 1270.01 1783.75 1019.19 1783.75H850.096V1294.04C850.096 1270.79 831.274 1251.96 808.014 1251.96H687.083C663.823 1251.96 645 1270.79 645 1294.04V1926.15C645 1949.4 663.823 1968.23 687.083 1968.23H1064.45H1064.53H1126.28C1579.28 1968.23 1894.59 1642.04 1894.59 1241.13C1894.59 840.229 1607.05 512.703 1126.24 514.004Z" fill="#2B2D28"/>
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 998 B |
@@ -21,6 +21,7 @@ const EventTypeAppCard: EventTypeAppCardComponent = function EventTypeAppCard({
|
||||
switchChecked={enabled}
|
||||
teamId={eventType.team?.id || undefined}>
|
||||
<TextField
|
||||
dataTestid={app.slug}
|
||||
name="Tracking ID"
|
||||
value={trackingId}
|
||||
disabled={disabled}
|
||||
|
||||
@@ -21,6 +21,7 @@ const EventTypeAppCard: EventTypeAppCardComponent = function EventTypeAppCard({
|
||||
switchChecked={enabled}
|
||||
teamId={eventType.team?.id || undefined}>
|
||||
<TextField
|
||||
dataTestid={app.slug}
|
||||
name="Tracking ID"
|
||||
value={trackingId}
|
||||
disabled={disabled}
|
||||
|
||||
@@ -11,6 +11,7 @@ import { encodeOAuthState } from "../../_utils/oauth/encodeOAuthState";
|
||||
export const scopes = [
|
||||
"https://www.googleapis.com/auth/calendar.readonly",
|
||||
"https://www.googleapis.com/auth/calendar.events",
|
||||
"https://www.googleapis.com/auth/userinfo.profile",
|
||||
];
|
||||
|
||||
let client_id = "";
|
||||
|
||||
@@ -1,9 +1,12 @@
|
||||
import type { Auth } from "googleapis";
|
||||
import { google } from "googleapis";
|
||||
import type { NextApiRequest, NextApiResponse } from "next";
|
||||
|
||||
import { renewSelectedCalendarCredentialId } from "@calcom/lib/connectedCalendar";
|
||||
import { WEBAPP_URL_FOR_OAUTH, WEBAPP_URL } from "@calcom/lib/constants";
|
||||
import { getSafeRedirectUrl } from "@calcom/lib/getSafeRedirectUrl";
|
||||
import { HttpError } from "@calcom/lib/http-error";
|
||||
import logger from "@calcom/lib/logger";
|
||||
import { defaultHandler, defaultResponder } from "@calcom/lib/server";
|
||||
import prisma from "@calcom/prisma";
|
||||
import { Prisma } from "@calcom/prisma/client";
|
||||
@@ -91,6 +94,10 @@ async function getHandler(req: NextApiRequest, res: NextApiResponse) {
|
||||
throw new HttpError({ message: "Internal Error", statusCode: 500 });
|
||||
}
|
||||
|
||||
// Update the user's profile photo with google profile photo if it's null
|
||||
// Since we don't want to block the user from using the app if this fails, we don't await this
|
||||
updateProfilePhoto(oAuth2Client, req.session.user.id);
|
||||
|
||||
const credential = await prisma.credential.create({
|
||||
data: {
|
||||
type: "google_calendar",
|
||||
@@ -99,23 +106,38 @@ async function getHandler(req: NextApiRequest, res: NextApiResponse) {
|
||||
appId: "google-calendar",
|
||||
},
|
||||
});
|
||||
|
||||
const selectedCalendarWhereUnique = {
|
||||
userId: req.session.user.id,
|
||||
externalId: primaryCal.id,
|
||||
integration: "google_calendar",
|
||||
};
|
||||
|
||||
// Wrapping in a try/catch to reduce chance of race conditions-
|
||||
// also this improves performance for most of the happy-paths.
|
||||
try {
|
||||
await prisma.selectedCalendar.create({
|
||||
data: {
|
||||
userId: req.session.user.id,
|
||||
externalId: primaryCal.id,
|
||||
credentialId: credential.id,
|
||||
integration: "google_calendar",
|
||||
...selectedCalendarWhereUnique,
|
||||
},
|
||||
});
|
||||
} catch (error) {
|
||||
await prisma.credential.delete({ where: { id: credential.id } });
|
||||
let errorMessage = "something_went_wrong";
|
||||
if (error instanceof Prisma.PrismaClientKnownRequestError && error.code === "P2002") {
|
||||
// it is possible a selectedCalendar was orphaned, in this situation-
|
||||
// we want to recover by connecting the existing selectedCalendar to the new Credential.
|
||||
if (await renewSelectedCalendarCredentialId(selectedCalendarWhereUnique, credential.id)) {
|
||||
res.redirect(
|
||||
getSafeRedirectUrl(state?.returnTo) ??
|
||||
getInstalledAppPath({ variant: "calendar", slug: "google-calendar" })
|
||||
);
|
||||
return;
|
||||
}
|
||||
// else
|
||||
errorMessage = "account_already_linked";
|
||||
}
|
||||
await prisma.credential.delete({ where: { id: credential.id } });
|
||||
res.redirect(
|
||||
`${
|
||||
getSafeRedirectUrl(state?.onErrorReturnTo) ??
|
||||
@@ -156,6 +178,23 @@ async function getHandler(req: NextApiRequest, res: NextApiResponse) {
|
||||
);
|
||||
}
|
||||
|
||||
async function updateProfilePhoto(oAuth2Client: Auth.OAuth2Client, userId: number) {
|
||||
try {
|
||||
const oauth2 = google.oauth2({ version: "v2", auth: oAuth2Client });
|
||||
const userDetails = await oauth2.userinfo.get();
|
||||
if (userDetails.data?.picture) {
|
||||
await prisma.user.update({
|
||||
where: { id: userId, avatarUrl: null, avatar: null },
|
||||
data: {
|
||||
avatarUrl: userDetails.data.picture,
|
||||
},
|
||||
});
|
||||
}
|
||||
} catch (error) {
|
||||
logger.error("Error updating avatarUrl from google calendar connect", error);
|
||||
}
|
||||
}
|
||||
|
||||
export default defaultHandler({
|
||||
GET: Promise.resolve({ default: defaultResponder(getHandler) }),
|
||||
});
|
||||
|
||||
@@ -9,9 +9,7 @@ afterEach(() => {
|
||||
});
|
||||
|
||||
vi.mock("@calcom/features/flags/server/utils", () => ({
|
||||
getFeatureFlagMap: vi.fn().mockResolvedValue({
|
||||
"calendar-cache": true,
|
||||
}),
|
||||
getFeatureFlag: vi.fn().mockReturnValue(true),
|
||||
}));
|
||||
|
||||
vi.mock("./getGoogleAppKeys", () => ({
|
||||
|
||||
@@ -6,7 +6,7 @@ import { RRule } from "rrule";
|
||||
|
||||
import { MeetLocationType } from "@calcom/app-store/locations";
|
||||
import dayjs from "@calcom/dayjs";
|
||||
import { getFeatureFlagMap } from "@calcom/features/flags/server/utils";
|
||||
import { getFeatureFlag } from "@calcom/features/flags/server/utils";
|
||||
import { getLocation, getRichDescription } from "@calcom/lib/CalEventParser";
|
||||
import type CalendarService from "@calcom/lib/CalendarService";
|
||||
import logger from "@calcom/lib/logger";
|
||||
@@ -264,6 +264,7 @@ export default class GoogleCalendarService implements Calendar {
|
||||
calendarId: selectedCalendar,
|
||||
eventId: event.id || "",
|
||||
requestBody: {
|
||||
location: getLocation(calEventRaw),
|
||||
description: getRichDescription({
|
||||
...calEventRaw,
|
||||
}),
|
||||
@@ -461,10 +462,10 @@ export default class GoogleCalendarService implements Calendar {
|
||||
items: { id: string }[];
|
||||
}): Promise<EventBusyDate[] | null> {
|
||||
const calendar = await this.authedCalendar();
|
||||
const flags = await getFeatureFlagMap(prisma);
|
||||
const calendarCacheEnabled = await getFeatureFlag(prisma, "calendar-cache");
|
||||
|
||||
let freeBusyResult: calendar_v3.Schema$FreeBusyResponse = {};
|
||||
if (!flags["calendar-cache"]) {
|
||||
if (!calendarCacheEnabled) {
|
||||
this.log.warn("Calendar Cache is disabled - Skipping");
|
||||
const { timeMin, timeMax, items } = args;
|
||||
const apires = await calendar.freebusy.query({
|
||||
|
||||
@@ -21,6 +21,7 @@ const EventTypeAppCard: EventTypeAppCardComponent = function EventTypeAppCard({
|
||||
switchChecked={enabled}
|
||||
teamId={eventType.team?.id || undefined}>
|
||||
<TextField
|
||||
dataTestid={app.slug}
|
||||
name="Tracking ID"
|
||||
value={trackingId}
|
||||
disabled={disabled}
|
||||
|
||||
@@ -2,6 +2,7 @@
|
||||
"name": "ICS Feed",
|
||||
"title": "ICS Feed",
|
||||
"slug": "ics-feed",
|
||||
"dirName":"ics-feedcalendar",
|
||||
"type": "ics-feed_calendar",
|
||||
"logo": "icon.svg",
|
||||
"variant": "calendar",
|
||||
|
||||
@@ -0,0 +1,19 @@
|
||||
---
|
||||
items:
|
||||
- 1.jpg
|
||||
---
|
||||
|
||||
{DESCRIPTION}
|
||||
|
||||
### Overview
|
||||
The Linear integration with Google calendar allows you to connect your calendar and automate your status. If your teammates need to know whether you’re out of office, now they will.
|
||||
|
||||
### How it works
|
||||
Once connected, your Linear status will detect out of office events in Google calendar and update your status when those events are upcoming or active.
|
||||
|
||||
### To Configure
|
||||
Install Google Calendar from your Linear settings under My Account > Linked accounts.
|
||||
|
||||
Connect your account and sign into your Google account to allow access.
|
||||
|
||||
Success! Your calendar is connected and will update your Linear status with out of office notices.
|
||||
@@ -0,0 +1,20 @@
|
||||
import { createDefaultInstallation } from "@calcom/app-store/_utils/installation";
|
||||
import type { AppDeclarativeHandler } from "@calcom/types/AppHandler";
|
||||
|
||||
import appConfig from "../config.json";
|
||||
|
||||
const handler: AppDeclarativeHandler = {
|
||||
appType: appConfig.type,
|
||||
variant: appConfig.variant,
|
||||
slug: appConfig.slug,
|
||||
supportsMultipleInstalls: false,
|
||||
handlerType: "add",
|
||||
redirect: {
|
||||
newTab: true,
|
||||
url: "https://linear.app/settings/integrations/google-calendar",
|
||||
},
|
||||
createCredential: ({ appType, user, slug, teamId }) =>
|
||||
createDefaultInstallation({ appType, user: user, slug, key: {}, teamId }),
|
||||
};
|
||||
|
||||
export default handler;
|
||||
@@ -0,0 +1 @@
|
||||
export { default as add } from "./add";
|
||||
@@ -0,0 +1,17 @@
|
||||
{
|
||||
"/*": "Don't modify slug - If required, do it using cli edit command",
|
||||
"name": "Linear",
|
||||
"slug": "linear",
|
||||
"type": "linear_other",
|
||||
"logo": "icon.svg",
|
||||
"url": "https://linear.app",
|
||||
"variant": "other",
|
||||
"categories": ["other"],
|
||||
"publisher": "Linear",
|
||||
"email": "support@linear.app",
|
||||
"description": "Linear is a better way to build products. Connect your calendar to automate your status",
|
||||
"isTemplate": false,
|
||||
"__createdUsingCli": true,
|
||||
"dependencies": ["google-calendar"],
|
||||
"__template": "link-as-an-app"
|
||||
}
|
||||
@@ -0,0 +1 @@
|
||||
export * as api from "./api";
|
||||
@@ -0,0 +1,14 @@
|
||||
{
|
||||
"$schema": "https://json.schemastore.org/package.json",
|
||||
"private": true,
|
||||
"name": "@calcom/linear",
|
||||
"version": "0.0.0",
|
||||
"main": "./index.ts",
|
||||
"dependencies": {
|
||||
"@calcom/lib": "*"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@calcom/types": "*"
|
||||
},
|
||||
"description": "Connect your calendar to automate your status"
|
||||
}
|
||||
|
After Width: | Height: | Size: 42 KiB |
@@ -0,0 +1,50 @@
|
||||
<svg width="2484" height="2484" viewBox="0 0 2484 2484" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||
<g clip-path="url(#clip0_981_40)">
|
||||
<path d="M0 0H2484V2484H0V0Z" fill="url(#paint0_linear_981_40)"/>
|
||||
<g filter="url(#filter0_dd_981_40)">
|
||||
<path d="M636.219 1385.11C633.456 1373.33 647.489 1365.91 656.048 1374.47L1109.53 1827.95C1118.09 1836.51 1110.67 1850.54 1098.89 1847.78C870.04 1794.1 689.904 1613.96 636.219 1385.11Z" fill="url(#paint1_linear_981_40)"/>
|
||||
<path d="M621.024 1203.36C620.804 1206.88 622.128 1210.32 624.619 1212.81L1271.19 1859.38C1273.68 1861.87 1277.12 1863.2 1280.64 1862.98C1310.06 1861.14 1338.93 1857.26 1367.11 1851.48C1376.6 1849.53 1379.9 1837.86 1373.05 1831.01L652.994 1110.95C646.138 1104.1 634.473 1107.4 632.522 1116.89C626.737 1145.07 622.856 1173.94 621.024 1203.36Z" fill="url(#paint2_linear_981_40)"/>
|
||||
<path d="M673.3 989.942C671.233 994.585 672.286 1000.01 675.879 1003.6L1480.4 1808.12C1483.99 1811.71 1489.42 1812.77 1494.06 1810.7C1516.24 1800.82 1537.74 1789.68 1558.46 1777.36C1565.32 1773.29 1566.38 1763.87 1560.74 1758.23L725.772 923.262C720.132 917.622 710.71 918.68 706.635 925.537C694.322 946.258 683.18 967.758 673.3 989.942Z" fill="url(#paint3_linear_981_40)"/>
|
||||
<path d="M778.222 845.479C773.625 840.882 773.339 833.508 777.671 828.661C891.501 701.225 1057.08 621 1241.4 621C1584.7 621 1863 899.298 1863 1242.6C1863 1426.92 1782.77 1592.5 1655.34 1706.33C1650.49 1710.66 1643.12 1710.37 1638.52 1705.78L778.222 845.479Z" fill="url(#paint4_linear_981_40)"/>
|
||||
</g>
|
||||
</g>
|
||||
<defs>
|
||||
<filter id="filter0_dd_981_40" x="585.357" y="590.926" width="1313.29" height="1313.29" filterUnits="userSpaceOnUse" color-interpolation-filters="sRGB">
|
||||
<feFlood flood-opacity="0" result="BackgroundImageFix"/>
|
||||
<feColorMatrix in="SourceAlpha" type="matrix" values="0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 127 0" result="hardAlpha"/>
|
||||
<feOffset dy="5.5693"/>
|
||||
<feGaussianBlur stdDeviation="17.8218"/>
|
||||
<feColorMatrix type="matrix" values="0 0 0 0 0.118924 0 0 0 0 0.158031 0 0 0 0 0.570833 0 0 0 0.7 0"/>
|
||||
<feBlend mode="normal" in2="BackgroundImageFix" result="effect1_dropShadow_981_40"/>
|
||||
<feColorMatrix in="SourceAlpha" type="matrix" values="0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 127 0" result="hardAlpha"/>
|
||||
<feOffset dy="1.94926"/>
|
||||
<feGaussianBlur stdDeviation="7.5"/>
|
||||
<feColorMatrix type="matrix" values="0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0.17 0"/>
|
||||
<feBlend mode="normal" in2="effect1_dropShadow_981_40" result="effect2_dropShadow_981_40"/>
|
||||
<feBlend mode="normal" in="SourceGraphic" in2="effect2_dropShadow_981_40" result="shape"/>
|
||||
</filter>
|
||||
<linearGradient id="paint0_linear_981_40" x1="1242" y1="0" x2="1242" y2="2484" gradientUnits="userSpaceOnUse">
|
||||
<stop stop-color="#5C6BF1"/>
|
||||
<stop offset="1" stop-color="#283188"/>
|
||||
</linearGradient>
|
||||
<linearGradient id="paint1_linear_981_40" x1="665.358" y1="736.329" x2="1312.97" y2="1685.57" gradientUnits="userSpaceOnUse">
|
||||
<stop stop-color="white"/>
|
||||
<stop offset="1" stop-color="white" stop-opacity="0.65"/>
|
||||
</linearGradient>
|
||||
<linearGradient id="paint2_linear_981_40" x1="665.358" y1="736.329" x2="1312.97" y2="1685.57" gradientUnits="userSpaceOnUse">
|
||||
<stop stop-color="white"/>
|
||||
<stop offset="1" stop-color="white" stop-opacity="0.65"/>
|
||||
</linearGradient>
|
||||
<linearGradient id="paint3_linear_981_40" x1="665.358" y1="736.329" x2="1312.97" y2="1685.57" gradientUnits="userSpaceOnUse">
|
||||
<stop stop-color="white"/>
|
||||
<stop offset="1" stop-color="white" stop-opacity="0.65"/>
|
||||
</linearGradient>
|
||||
<linearGradient id="paint4_linear_981_40" x1="665.358" y1="736.329" x2="1312.97" y2="1685.57" gradientUnits="userSpaceOnUse">
|
||||
<stop stop-color="white"/>
|
||||
<stop offset="1" stop-color="white" stop-opacity="0.65"/>
|
||||
</linearGradient>
|
||||
<clipPath id="clip0_981_40">
|
||||
<rect width="2484" height="2484" rx="200" fill="white"/>
|
||||
</clipPath>
|
||||
</defs>
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 3.7 KiB |
@@ -23,6 +23,7 @@ const EventTypeAppCard: EventTypeAppCardComponent = function EventTypeAppCard({
|
||||
teamId={eventType.team?.id || undefined}>
|
||||
<div className="flex flex-col gap-2">
|
||||
<TextField
|
||||
dataTestid={`${app.slug}-url`}
|
||||
name="Matomo URL"
|
||||
placeholder="Enter your Matomo URL here"
|
||||
value={matomoUrl}
|
||||
@@ -32,6 +33,7 @@ const EventTypeAppCard: EventTypeAppCardComponent = function EventTypeAppCard({
|
||||
}}
|
||||
/>
|
||||
<TextField
|
||||
dataTestid={`${app.slug}-site-id`}
|
||||
disabled={disabled}
|
||||
name="Site ID"
|
||||
placeholder="Enter your Site ID"
|
||||
|
||||
@@ -19,6 +19,7 @@ const EventTypeAppCard: EventTypeAppCardComponent = function EventTypeAppCard({
|
||||
switchChecked={enabled}
|
||||
teamId={eventType.team?.id || undefined}>
|
||||
<TextField
|
||||
dataTestid={app.slug}
|
||||
name="Pixel ID"
|
||||
value={trackingId}
|
||||
disabled={disabled}
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
import type { Calendar as OfficeCalendar } from "@microsoft/microsoft-graph-types-beta";
|
||||
import type { NextApiRequest, NextApiResponse } from "next";
|
||||
|
||||
import { renewSelectedCalendarCredentialId } from "@calcom/lib/connectedCalendar";
|
||||
import { WEBAPP_URL } from "@calcom/lib/constants";
|
||||
import { handleErrorsJson } from "@calcom/lib/errors";
|
||||
import { getSafeRedirectUrl } from "@calcom/lib/getSafeRedirectUrl";
|
||||
@@ -119,23 +120,36 @@ export default async function handler(req: NextApiRequest, res: NextApiResponse)
|
||||
appId: "office365-calendar",
|
||||
},
|
||||
});
|
||||
const selectedCalendarWhereUnique = {
|
||||
userId: req.session?.user.id,
|
||||
integration: "office365_calendar",
|
||||
externalId: defaultCalendar.id,
|
||||
};
|
||||
// Wrapping in a try/catch to reduce chance of race conditions-
|
||||
// also this improves performance for most of the happy-paths.
|
||||
try {
|
||||
await prisma.selectedCalendar.create({
|
||||
data: {
|
||||
userId: req.session?.user.id,
|
||||
integration: "office365_calendar",
|
||||
externalId: defaultCalendar.id,
|
||||
...selectedCalendarWhereUnique,
|
||||
credentialId: credential.id,
|
||||
},
|
||||
});
|
||||
} catch (error) {
|
||||
await prisma.credential.delete({ where: { id: credential.id } });
|
||||
let errorMessage = "something_went_wrong";
|
||||
if (error instanceof Prisma.PrismaClientKnownRequestError && error.code === "P2002") {
|
||||
// it is possible a selectedCalendar was orphaned, in this situation-
|
||||
// we want to recover by connecting the existing selectedCalendar to the new Credential.
|
||||
if (await renewSelectedCalendarCredentialId(selectedCalendarWhereUnique, credential.id)) {
|
||||
res.redirect(
|
||||
getSafeRedirectUrl(state?.returnTo) ??
|
||||
getInstalledAppPath({ variant: "calendar", slug: "office365-calendar" })
|
||||
);
|
||||
return;
|
||||
}
|
||||
// else
|
||||
errorMessage = "account_already_linked";
|
||||
}
|
||||
await prisma.credential.delete({ where: { id: credential.id } });
|
||||
res.redirect(
|
||||
`${
|
||||
getSafeRedirectUrl(state?.onErrorReturnTo) ??
|
||||
@@ -146,8 +160,9 @@ export default async function handler(req: NextApiRequest, res: NextApiResponse)
|
||||
}
|
||||
}
|
||||
|
||||
return res.redirect(
|
||||
res.redirect(
|
||||
getSafeRedirectUrl(state?.returnTo) ??
|
||||
getInstalledAppPath({ variant: "calendar", slug: "office365-calendar" })
|
||||
);
|
||||
return;
|
||||
}
|
||||
|
||||
@@ -23,6 +23,7 @@ const EventTypeAppCard: EventTypeAppCardComponent = function EventTypeAppCard({
|
||||
teamId={eventType.team?.id || undefined}>
|
||||
<div className="flex flex-col gap-2">
|
||||
<TextField
|
||||
dataTestid={`${app.slug}-url`}
|
||||
name="Plausible URL"
|
||||
defaultValue="https://plausible.io/js/script.js"
|
||||
placeholder="https://plausible.io/js/script.js"
|
||||
@@ -33,6 +34,7 @@ const EventTypeAppCard: EventTypeAppCardComponent = function EventTypeAppCard({
|
||||
}}
|
||||
/>
|
||||
<TextField
|
||||
dataTestid={`${app.slug}-tracking-id`}
|
||||
disabled={disabled}
|
||||
name="Tracked Domain"
|
||||
placeholder="yourdomain.com"
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
import type { NextApiRequest, NextApiResponse } from "next";
|
||||
import { stringify } from "querystring";
|
||||
|
||||
import { renewSelectedCalendarCredentialId } from "@calcom/lib/connectedCalendar";
|
||||
import { WEBAPP_URL } from "@calcom/lib/constants";
|
||||
import { getSafeRedirectUrl } from "@calcom/lib/getSafeRedirectUrl";
|
||||
import logger from "@calcom/lib/logger";
|
||||
@@ -86,23 +87,36 @@ async function getHandler(req: NextApiRequest, res: NextApiResponse) {
|
||||
appId: config.slug,
|
||||
},
|
||||
});
|
||||
const selectedCalendarWhereUnique = {
|
||||
userId: req.session?.user.id,
|
||||
integration: config.type,
|
||||
externalId: primaryCalendar.uid,
|
||||
};
|
||||
// Wrapping in a try/catch to reduce chance of race conditions-
|
||||
// also this improves performance for most of the happy-paths.
|
||||
try {
|
||||
await prisma.selectedCalendar.create({
|
||||
data: {
|
||||
userId: req.session.user.id,
|
||||
integration: config.type,
|
||||
externalId: primaryCalendar.uid,
|
||||
...selectedCalendarWhereUnique,
|
||||
credentialId: credential.id,
|
||||
},
|
||||
});
|
||||
} catch (error) {
|
||||
await prisma.credential.delete({ where: { id: credential.id } });
|
||||
let errorMessage = "something_went_wrong";
|
||||
if (error instanceof Prisma.PrismaClientKnownRequestError && error.code === "P2002") {
|
||||
// it is possible a selectedCalendar was orphaned, in this situation-
|
||||
// we want to recover by connecting the existing selectedCalendar to the new Credential.
|
||||
if (await renewSelectedCalendarCredentialId(selectedCalendarWhereUnique, credential.id)) {
|
||||
res.redirect(
|
||||
getSafeRedirectUrl(state?.returnTo) ??
|
||||
getInstalledAppPath({ variant: "calendar", slug: config.slug })
|
||||
);
|
||||
return;
|
||||
}
|
||||
// else
|
||||
errorMessage = "account_already_linked";
|
||||
}
|
||||
await prisma.credential.delete({ where: { id: credential.id } });
|
||||
res.redirect(
|
||||
`${
|
||||
getSafeRedirectUrl(state?.onErrorReturnTo) ??
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
import { Meta, Canvas, ArgsTable } from "@storybook/blocks";
|
||||
import { Title } from "@calcom/storybook/components";
|
||||
import { Booker } from "./Booker";
|
||||
import { BookerWebWrapper as Booker } from "./wrappers/BookerWebWrapper";
|
||||
import * as BookerStories from "./Booker.stories";
|
||||
|
||||
<Meta of={BookerStories} />
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
import type { Meta, StoryObj } from "@storybook/react";
|
||||
|
||||
import { Booker } from "./Booker";
|
||||
import { BookerWebWrapper as Booker } from "./wrappers/BookerWebWrapper";
|
||||
|
||||
const meta: Meta<typeof Booker> = {
|
||||
component: Booker,
|
||||
|
||||
@@ -1,13 +0,0 @@
|
||||
import type { BookerProps } from "@calcom/features/bookings/Booker";
|
||||
import { Booker as BookerComponent } from "@calcom/features/bookings/Booker";
|
||||
|
||||
import type { AtomsGlobalConfigProps } from "../types";
|
||||
|
||||
type BookerAtomProps = BookerProps & AtomsGlobalConfigProps;
|
||||
|
||||
/**
|
||||
* @TODO Before we can turn this into a reusable atom
|
||||
* * Use the webAppUrl coming from AtomsGlobalConfigProps to make url dynamic
|
||||
* * Find a solution for translations
|
||||
*/
|
||||
export const Booker = (props: BookerAtomProps) => <BookerComponent {...props} />;
|
||||