Compare commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
0c9b9bc296 | ||
|
|
552bfcc7db | ||
|
|
986f9dca8d | ||
|
|
3af5a7404e | ||
|
|
10d3c6ecaa | ||
|
|
a0c37ea4b7 | ||
|
|
798999ad1a | ||
|
|
d40e306baa | ||
|
|
e558e61f6b | ||
|
|
210eafcd6e | ||
|
|
c05e7d75a5 | ||
|
|
d0ebf3319a | ||
|
|
9eb4187644 | ||
|
|
958cebc82c | ||
|
|
0c36dcac4c | ||
|
|
fe996b4e41 | ||
|
|
c1c95f711b | ||
|
|
7cb7006c54 | ||
|
|
e1403bfbeb | ||
|
|
c38dd2d072 | ||
|
|
f9d9e74d6b | ||
|
|
2ab597addc | ||
|
|
33e394d5ed | ||
|
|
4577de3b17 | ||
|
|
658b529840 | ||
|
|
4aed769da4 | ||
|
|
6c753290aa | ||
|
|
59c91607db | ||
|
|
b0530d59af | ||
|
|
b1ff829745 | ||
|
|
d202b536b5 | ||
|
|
2ac5ef10ba | ||
|
|
8a2ab68e9a | ||
|
|
877220caa0 | ||
|
|
0686c08de3 | ||
|
|
d3f1f8b906 | ||
|
|
33a69d3ef6 | ||
|
|
d91b451d7b | ||
|
|
9e17149e5e | ||
|
|
de61b032b7 | ||
|
|
bdbf9bd04d | ||
|
|
ccd589ef38 | ||
|
|
b4c3753566 | ||
|
|
2fdadb98ac | ||
|
|
a33847a878 | ||
|
|
88cf535c8e | ||
|
|
ea0b59d7c0 | ||
|
|
39b93cdb5b | ||
|
|
e27fc05d5f | ||
|
|
20c010fef1 | ||
|
|
1a7911e9e2 | ||
|
|
cb09b5bc5f | ||
|
|
a99031bdd2 | ||
|
|
ada8a98362 | ||
|
|
e176e81bae | ||
|
|
b64dd66c2c | ||
|
|
7fd162601b | ||
|
|
c1f346c935 |
@@ -79,6 +79,9 @@ CALENDSO_ENCRYPTION_KEY=
|
||||
# Intercom Config
|
||||
NEXT_PUBLIC_INTERCOM_APP_ID=
|
||||
|
||||
# Secret to enable Intercom Identity Verification
|
||||
INTERCOM_SECRET=
|
||||
|
||||
# Zendesk Config
|
||||
NEXT_PUBLIC_ZENDESK_KEY=
|
||||
|
||||
|
||||
@@ -2,6 +2,7 @@ name: Cron - mark stale for inactive issues
|
||||
|
||||
permissions:
|
||||
issues: write
|
||||
pull-requests: write
|
||||
|
||||
on:
|
||||
# "Scheduled workflows run on the latest commit on the default or base branch."
|
||||
|
||||
@@ -24,7 +24,7 @@ jobs:
|
||||
- name: Analyze bundle
|
||||
run: |
|
||||
cd apps/web
|
||||
npx -p nextjs-bundle-analysis report
|
||||
npx -p nextjs-bundle-analysis@0.5.0 report
|
||||
|
||||
- name: Upload bundle
|
||||
uses: actions/upload-artifact@v2
|
||||
@@ -76,7 +76,7 @@ jobs:
|
||||
id: fc
|
||||
with:
|
||||
issue-number: ${{ github.event.number }}
|
||||
body-includes: "<!-- __NEXTJS_BUNDLE -->"
|
||||
body-includes: "<!-- __NEXTJS_BUNDLE_@calcom/web -->"
|
||||
|
||||
- name: Create Comment
|
||||
uses: peter-evans/create-or-update-comment@v1.4.4
|
||||
|
||||
@@ -1,167 +0,0 @@
|
||||
import type { FormEvent } from "react";
|
||||
import { useCallback, useEffect, useState } from "react";
|
||||
import Cropper from "react-easy-crop";
|
||||
|
||||
import { useLocale } from "@calcom/lib/hooks/useLocale";
|
||||
import { Button, Dialog, DialogClose, DialogContent, DialogTrigger } from "@calcom/ui";
|
||||
|
||||
import type { Area } from "@lib/cropImage";
|
||||
import { getCroppedImg } from "@lib/cropImage";
|
||||
import { useFileReader } from "@lib/hooks/useFileReader";
|
||||
|
||||
import Slider from "@components/Slider";
|
||||
|
||||
type ImageUploaderProps = {
|
||||
id: string;
|
||||
buttonMsg: string;
|
||||
handleAvatarChange: (imageSrc: string) => void;
|
||||
imageSrc?: string;
|
||||
target: string;
|
||||
};
|
||||
|
||||
interface FileEvent<T = Element> extends FormEvent<T> {
|
||||
target: EventTarget & T;
|
||||
}
|
||||
|
||||
// This is separate to prevent loading the component until file upload
|
||||
function CropContainer({
|
||||
onCropComplete,
|
||||
imageSrc,
|
||||
}: {
|
||||
imageSrc: string;
|
||||
onCropComplete: (croppedAreaPixels: Area) => void;
|
||||
}) {
|
||||
const { t } = useLocale();
|
||||
const [crop, setCrop] = useState({ x: 0, y: 0 });
|
||||
const [zoom, setZoom] = useState(1);
|
||||
|
||||
const handleZoomSliderChange = (value: number) => {
|
||||
value < 1 ? setZoom(1) : setZoom(value);
|
||||
};
|
||||
|
||||
return (
|
||||
<div className="crop-container h-40 max-h-40 w-40 rounded-full">
|
||||
<div className="relative h-40 w-40 rounded-full">
|
||||
<Cropper
|
||||
image={imageSrc}
|
||||
crop={crop}
|
||||
zoom={zoom}
|
||||
aspect={1}
|
||||
onCropChange={setCrop}
|
||||
onCropComplete={(croppedArea, croppedAreaPixels) => onCropComplete(croppedAreaPixels)}
|
||||
onZoomChange={setZoom}
|
||||
/>
|
||||
</div>
|
||||
<Slider
|
||||
value={zoom}
|
||||
min={1}
|
||||
max={3}
|
||||
step={0.1}
|
||||
label={t("slide_zoom_drag_instructions")}
|
||||
changeHandler={handleZoomSliderChange}
|
||||
/>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
/** @deprecated Use `packages/ui/v2/core/ImageUploader.tsx` */
|
||||
export default function ImageUploader({
|
||||
target,
|
||||
id,
|
||||
buttonMsg,
|
||||
handleAvatarChange,
|
||||
...props
|
||||
}: ImageUploaderProps) {
|
||||
const { t } = useLocale();
|
||||
const [imageSrc, setImageSrc] = useState<string | null>(null);
|
||||
const [croppedAreaPixels, setCroppedAreaPixels] = useState<Area | null>(null);
|
||||
|
||||
const [{ result }, setFile] = useFileReader({
|
||||
method: "readAsDataURL",
|
||||
});
|
||||
|
||||
useEffect(() => {
|
||||
if (props.imageSrc) setImageSrc(props.imageSrc);
|
||||
}, [props.imageSrc]);
|
||||
|
||||
const onInputFile = (e: FileEvent<HTMLInputElement>) => {
|
||||
if (!e.target.files?.length) {
|
||||
return;
|
||||
}
|
||||
setFile(e.target.files[0]);
|
||||
};
|
||||
|
||||
const showCroppedImage = useCallback(
|
||||
async (croppedAreaPixels: Area | null) => {
|
||||
try {
|
||||
if (!croppedAreaPixels) return;
|
||||
const croppedImage = await getCroppedImg(
|
||||
result as string /* result is always string when using readAsDataUrl */,
|
||||
croppedAreaPixels
|
||||
);
|
||||
setImageSrc(croppedImage);
|
||||
handleAvatarChange(croppedImage);
|
||||
} catch (e) {
|
||||
console.error(e);
|
||||
}
|
||||
},
|
||||
[result, handleAvatarChange]
|
||||
);
|
||||
|
||||
return (
|
||||
<Dialog
|
||||
onOpenChange={
|
||||
(opened) => !opened && setFile(null) // unset file on close
|
||||
}>
|
||||
<DialogTrigger asChild>
|
||||
<div className="flex items-center">
|
||||
<Button color="secondary" type="button" className="py-1 text-xs">
|
||||
{buttonMsg}
|
||||
</Button>
|
||||
</div>
|
||||
</DialogTrigger>
|
||||
<DialogContent>
|
||||
<div className="mb-4 sm:flex sm:items-start">
|
||||
<div className="mt-3 text-center sm:mt-0 sm:text-left">
|
||||
<h3 className="font-cal text-emphasis text-lg leading-6" id="modal-title">
|
||||
{t("upload_target", { target })}
|
||||
</h3>
|
||||
</div>
|
||||
</div>
|
||||
<div className="mb-4">
|
||||
<div className="cropper mt-6 flex flex-col items-center justify-center p-8">
|
||||
{!result && (
|
||||
<div className="bg-muted flex h-20 max-h-20 w-20 items-center justify-start rounded-full">
|
||||
{!imageSrc && (
|
||||
<p className="text-inverted w-full text-center text-sm sm:text-xs">
|
||||
{t("no_target", { target })}
|
||||
</p>
|
||||
)}
|
||||
{imageSrc && (
|
||||
// eslint-disable-next-line @next/next/no-img-element
|
||||
<img className="h-20 w-20 rounded-full" src={imageSrc} alt={target} />
|
||||
)}
|
||||
</div>
|
||||
)}
|
||||
{result && <CropContainer imageSrc={result as string} onCropComplete={setCroppedAreaPixels} />}
|
||||
<label className="border-default bg-default hover:bg-mutedover:text-emphasis dark:hover:bg-inverted text-default dark:text-inverted focus:ring-empthasis mt-8 rounded-sm border px-3 py-1 text-xs font-medium leading-4 focus:outline-none focus:ring-2 focus:ring-offset-1 dark:border-gray-800 dark:bg-transparent">
|
||||
<input
|
||||
onInput={onInputFile}
|
||||
type="file"
|
||||
name={id}
|
||||
placeholder={t("upload_image")}
|
||||
className="pointer-events-none absolute mt-4 opacity-0"
|
||||
accept="image/*"
|
||||
/>
|
||||
{t("choose_a_file")}
|
||||
</label>
|
||||
</div>
|
||||
</div>
|
||||
<div className="mt-5 gap-x-2 sm:mt-4 sm:flex sm:flex-row-reverse">
|
||||
<DialogClose onClick={() => showCroppedImage(croppedAreaPixels)}>{t("save")}</DialogClose>
|
||||
<DialogClose color="secondary">{t("cancel")}</DialogClose>
|
||||
</div>
|
||||
</DialogContent>
|
||||
</Dialog>
|
||||
);
|
||||
}
|
||||
@@ -1,4 +1,3 @@
|
||||
import { AdminRequired } from "components/ui/AdminRequired";
|
||||
import { noop } from "lodash";
|
||||
import type { LinkProps } from "next/link";
|
||||
import Link from "next/link";
|
||||
@@ -6,6 +5,7 @@ import { useRouter } from "next/router";
|
||||
import type { FC, MouseEventHandler } from "react";
|
||||
import { Fragment } from "react";
|
||||
|
||||
import { PermissionContainer } from "@calcom/features/auth/PermissionContainer";
|
||||
import { useLocale } from "@calcom/lib/hooks/useLocale";
|
||||
|
||||
import classNames from "@lib/classNames";
|
||||
@@ -60,7 +60,7 @@ const NavTabs: FC<NavTabProps> = ({ tabs, linkProps, ...props }) => {
|
||||
}
|
||||
: noop;
|
||||
|
||||
const Component = tab.adminRequired ? AdminRequired : Fragment;
|
||||
const Component = tab.adminRequired ? PermissionContainer : Fragment;
|
||||
const className = tab.className || "";
|
||||
return (
|
||||
<Component key={tab.name}>
|
||||
|
||||
@@ -4,7 +4,6 @@ import localFont from "next/font/local";
|
||||
import Script from "next/script";
|
||||
|
||||
import "@calcom/embed-core/src/embed-iframe";
|
||||
import { useEmbedUiConfig } from "@calcom/embed-core/src/embed-iframe";
|
||||
import LicenseRequired from "@calcom/features/ee/common/components/LicenseRequired";
|
||||
|
||||
import type { AppProps } from "@lib/app-providers";
|
||||
@@ -29,17 +28,6 @@ const calFont = localFont({
|
||||
function PageWrapper(props: AppProps) {
|
||||
const { Component, pageProps, err, router } = props;
|
||||
let pageStatus = "200";
|
||||
const { cssVarsPerTheme } = useEmbedUiConfig();
|
||||
const cssVarsStyle = [];
|
||||
if (cssVarsPerTheme) {
|
||||
for (const [themeName, cssVars] of Object.entries(cssVarsPerTheme)) {
|
||||
cssVarsStyle.push(`.${themeName} {`);
|
||||
for (const [cssVarName, value] of Object.entries(cssVars)) {
|
||||
cssVarsStyle.push(`--${cssVarName}: ${value};`);
|
||||
}
|
||||
cssVarsStyle.push(`}`);
|
||||
}
|
||||
}
|
||||
|
||||
if (router.pathname === "/404") {
|
||||
pageStatus = "404";
|
||||
@@ -77,11 +65,6 @@ function PageWrapper(props: AppProps) {
|
||||
}
|
||||
`}</style>
|
||||
|
||||
<style jsx global>
|
||||
{`
|
||||
${cssVarsStyle.join("")}
|
||||
`}
|
||||
</style>
|
||||
{getLayout(
|
||||
Component.requiresLicense ? (
|
||||
<LicenseRequired>
|
||||
|
||||
@@ -46,7 +46,7 @@ export default function SettingsShell({
|
||||
...rest
|
||||
}: { children: React.ReactNode } & ComponentProps<typeof Shell>) {
|
||||
return (
|
||||
<Shell {...rest}>
|
||||
<Shell {...rest} hideHeadingOnMobile>
|
||||
<div className="sm:mx-auto">
|
||||
<NavTabs tabs={tabs} />
|
||||
</div>
|
||||
|
||||
@@ -1,27 +0,0 @@
|
||||
import * as SliderPrimitive from "@radix-ui/react-slider";
|
||||
import React from "react";
|
||||
|
||||
const Slider = ({
|
||||
value,
|
||||
label,
|
||||
changeHandler,
|
||||
...props
|
||||
}: Omit<SliderPrimitive.SliderProps, "value"> & {
|
||||
value: number;
|
||||
label: string;
|
||||
changeHandler: (value: number) => void;
|
||||
}) => (
|
||||
<SliderPrimitive.Root
|
||||
className="slider mt-2"
|
||||
value={[value]}
|
||||
aria-label={label}
|
||||
onValueChange={(value: number[]) => changeHandler(value[0] ?? value)}
|
||||
{...props}>
|
||||
<SliderPrimitive.Track className="slider-track">
|
||||
<SliderPrimitive.Range className="slider-range" />
|
||||
</SliderPrimitive.Track>
|
||||
<SliderPrimitive.Thumb className="slider-thumb" />
|
||||
</SliderPrimitive.Root>
|
||||
);
|
||||
|
||||
export default Slider;
|
||||
@@ -6,7 +6,7 @@ import React, { useState } from "react";
|
||||
import useAddAppMutation from "@calcom/app-store/_utils/useAddAppMutation";
|
||||
import { InstallAppButton, AppDependencyComponent } from "@calcom/app-store/components";
|
||||
import DisconnectIntegration from "@calcom/features/apps/components/DisconnectIntegration";
|
||||
import LicenseRequired from "@calcom/features/ee/common/components/v2/LicenseRequired";
|
||||
import LicenseRequired from "@calcom/features/ee/common/components/LicenseRequired";
|
||||
import Shell from "@calcom/features/shell/Shell";
|
||||
import classNames from "@calcom/lib/classNames";
|
||||
import { APP_NAME, COMPANY_NAME, SUPPORT_MAIL_ADDRESS } from "@calcom/lib/constants";
|
||||
@@ -41,7 +41,7 @@ const Component = ({
|
||||
isTemplate,
|
||||
dependencies,
|
||||
}: Parameters<typeof App>[0]) => {
|
||||
const { t } = useLocale();
|
||||
const { t, i18n } = useLocale();
|
||||
const hasDescriptionItems = descriptionItems && descriptionItems.length > 0;
|
||||
const router = useRouter();
|
||||
|
||||
@@ -247,7 +247,7 @@ const Component = ({
|
||||
t("free_to_use_apps")
|
||||
) : (
|
||||
<>
|
||||
{Intl.NumberFormat("en-US", {
|
||||
{Intl.NumberFormat(i18n.language, {
|
||||
style: "currency",
|
||||
currency: "USD",
|
||||
useGrouping: false,
|
||||
@@ -366,7 +366,7 @@ export default function App(props: {
|
||||
dependencies?: string[];
|
||||
}) {
|
||||
return (
|
||||
<Shell smallHeading isPublic heading={<ShellHeading />} backPath="/apps" withoutSeo>
|
||||
<Shell smallHeading isPublic hideHeadingOnMobile heading={<ShellHeading />} backPath="/apps" withoutSeo>
|
||||
<HeadSeo
|
||||
title={props.name}
|
||||
description={props.description}
|
||||
|
||||
@@ -22,7 +22,7 @@ export default function AppsLayout({ children, actions, emptyStore, ...rest }: A
|
||||
if (session.status === "loading") return <></>;
|
||||
|
||||
return (
|
||||
<Shell {...rest} actions={actions?.("block")}>
|
||||
<Shell {...rest} actions={actions?.("block")} hideHeadingOnMobile>
|
||||
<div className="flex flex-col xl:flex-row">
|
||||
<main className="w-full">
|
||||
{emptyStore ? (
|
||||
|
||||
@@ -57,7 +57,7 @@ export default function InstalledAppsLayout({
|
||||
}
|
||||
|
||||
return (
|
||||
<Shell {...rest}>
|
||||
<Shell {...rest} hideHeadingOnMobile>
|
||||
<AppCategoryNavigation baseURL="/apps/installed" containerClassname="min-w-0 w-full">
|
||||
{children}
|
||||
</AppCategoryNavigation>
|
||||
|
||||
@@ -41,7 +41,7 @@ export function AvailableEventLocations({ locations }: { locations: Props["event
|
||||
return (
|
||||
<div key={`${location.type}-${index}`} className="flex flex-row items-center text-sm font-medium">
|
||||
{eventLocationType.iconUrl === "/link.svg" ? (
|
||||
<Link className="text-default min-h-4 min-w-4 ml-[2px] opacity-70 ltr:mr-[10px] rtl:ml-[10px] " />
|
||||
<Link className="text-default ml-[2px] h-4 w-4 ltr:mr-[10px] rtl:ml-[10px] " />
|
||||
) : (
|
||||
<img
|
||||
src={eventLocationType.iconUrl}
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
import { i18n } from "next-i18next";
|
||||
import type { TFunction } from "next-i18next";
|
||||
import { FormattedNumber, IntlProvider } from "react-intl";
|
||||
|
||||
import getPaymentAppData from "@calcom/lib/getPaymentAppData";
|
||||
import { CreditCard } from "@calcom/ui/components/icon";
|
||||
@@ -7,28 +7,29 @@ import { CreditCard } from "@calcom/ui/components/icon";
|
||||
const BookingDescriptionPayment = (props: {
|
||||
eventType: Parameters<typeof getPaymentAppData>[0];
|
||||
t: TFunction;
|
||||
i18n: typeof i18n;
|
||||
}) => {
|
||||
const paymentAppData = getPaymentAppData(props.eventType);
|
||||
if (!paymentAppData || paymentAppData.price <= 0) return null;
|
||||
|
||||
const params = {
|
||||
amount: paymentAppData.price / 100.0,
|
||||
formatParams: { amount: { currency: paymentAppData.currency } },
|
||||
};
|
||||
|
||||
return (
|
||||
<p className="text-bookinglight -ml-2 px-2 text-sm ">
|
||||
<CreditCard className="ml-[2px] -mt-1 inline-block h-4 w-4 ltr:mr-[10px] rtl:ml-[10px]" />
|
||||
{paymentAppData.paymentOption === "HOLD" ? (
|
||||
<>
|
||||
{props.t("no_show_fee_amount", {
|
||||
amount: paymentAppData.price / 100.0,
|
||||
formatParams: { amount: { currency: paymentAppData.currency } },
|
||||
})}
|
||||
</>
|
||||
<>{props.t("no_show_fee_amount", params)}</>
|
||||
) : (
|
||||
<IntlProvider locale="en">
|
||||
<FormattedNumber
|
||||
value={paymentAppData.price / 100.0}
|
||||
style="currency"
|
||||
currency={paymentAppData.currency?.toUpperCase()}
|
||||
/>
|
||||
</IntlProvider>
|
||||
<>
|
||||
{/* If undefined this will default to the browser locale */}
|
||||
{new Intl.NumberFormat(i18n?.language, {
|
||||
style: "currency",
|
||||
currency: paymentAppData.currency,
|
||||
}).format(paymentAppData.price / 100)}
|
||||
</>
|
||||
)}
|
||||
</p>
|
||||
);
|
||||
|
||||
@@ -1,7 +1,6 @@
|
||||
import dynamic from "next/dynamic";
|
||||
import { useRouter } from "next/router";
|
||||
import { useEffect, useMemo, useReducer, useState } from "react";
|
||||
import { FormattedNumber, IntlProvider } from "react-intl";
|
||||
import { z } from "zod";
|
||||
|
||||
import BookingPageTagManager from "@calcom/app-store/BookingPageTagManager";
|
||||
@@ -75,7 +74,7 @@ const AvailabilityPage = ({ profile, eventType, ...restProps }: Props) => {
|
||||
brandColor: profile.brandColor,
|
||||
darkBrandColor: profile.darkBrandColor,
|
||||
});
|
||||
const { t } = useLocale();
|
||||
const { t, i18n } = useLocale();
|
||||
const availabilityDatePickerEmbedStyles = useEmbedStyles("availabilityDatePicker");
|
||||
//TODO: Plan to remove shouldAlignCentrallyInEmbed config
|
||||
const shouldAlignCentrallyInEmbed = useEmbedNonStylesConfig("align") !== "left";
|
||||
@@ -124,16 +123,7 @@ const AvailabilityPage = ({ profile, eventType, ...restProps }: Props) => {
|
||||
[timeZone]
|
||||
);
|
||||
const paymentAppData = getPaymentAppData(eventType);
|
||||
const paymentAmount = () => {
|
||||
return;
|
||||
<IntlProvider locale="en">
|
||||
<FormattedNumber
|
||||
value={paymentAppData.price / 100.0}
|
||||
style="currency"
|
||||
currency={paymentAppData.currency?.toUpperCase()}
|
||||
/>
|
||||
</IntlProvider>;
|
||||
};
|
||||
|
||||
const rainbowAppData = getEventTypeAppData(eventType, "rainbow") || {};
|
||||
const rawSlug = profile.slug ? profile.slug.split("/") : [];
|
||||
if (rawSlug.length > 1) rawSlug.pop(); //team events have team name as slug, but user events have [user]/[type] as slug.
|
||||
@@ -257,13 +247,12 @@ const AvailabilityPage = ({ profile, eventType, ...restProps }: Props) => {
|
||||
})}
|
||||
</>
|
||||
) : (
|
||||
<IntlProvider locale="en">
|
||||
<FormattedNumber
|
||||
value={paymentAppData.price / 100.0}
|
||||
style="currency"
|
||||
currency={paymentAppData.currency?.toUpperCase()}
|
||||
/>
|
||||
</IntlProvider>
|
||||
<>
|
||||
{new Intl.NumberFormat(i18n.language, {
|
||||
style: "currency",
|
||||
currency: paymentAppData.currency,
|
||||
}).format(paymentAppData.price / 100)}
|
||||
</>
|
||||
)}
|
||||
</p>
|
||||
)}
|
||||
|
||||
@@ -554,7 +554,7 @@ const BookingPage = ({
|
||||
{showEventTypeDetails && (
|
||||
<div className="sm:border-subtle text-default flex flex-col px-6 pt-6 pb-0 sm:w-1/2 sm:border-r sm:pb-6">
|
||||
<BookingDescription isBookingPage profile={profile} eventType={eventType}>
|
||||
<BookingDescriptionPayment eventType={eventType} t={t} />
|
||||
<BookingDescriptionPayment eventType={eventType} t={t} i18n={i18n} />
|
||||
{!rescheduleUid && eventType.recurringEvent?.freq && recurringEventCount && (
|
||||
<div className="dark:text-inverted text-default items-start text-sm font-medium">
|
||||
<RefreshCw className="ml-[2px] inline-block h-4 w-4 ltr:mr-[10px] rtl:ml-[10px]" />
|
||||
|
||||
@@ -1,7 +1,5 @@
|
||||
import { Trans } from "next-i18next";
|
||||
import { useState } from "react";
|
||||
import type { Dispatch, SetStateAction } from "react";
|
||||
import { IntlProvider, FormattedNumber } from "react-intl";
|
||||
|
||||
import { useLocale } from "@calcom/lib/hooks/useLocale";
|
||||
import { trpc } from "@calcom/trpc/react";
|
||||
@@ -40,6 +38,11 @@ export const ChargeCardDialog = (props: IRescheduleDialog) => {
|
||||
},
|
||||
});
|
||||
|
||||
const currencyStringParams = {
|
||||
amount: props.paymentAmount / 100.0,
|
||||
formatParams: { amount: { currency: props.paymentCurrency } },
|
||||
};
|
||||
|
||||
return (
|
||||
<Dialog open={isOpenDialog} onOpenChange={setIsOpenDialog}>
|
||||
<DialogContent>
|
||||
@@ -49,19 +52,7 @@ export const ChargeCardDialog = (props: IRescheduleDialog) => {
|
||||
</div>
|
||||
<div className="pt-1">
|
||||
<DialogHeader title={t("charge_card")} />
|
||||
<Trans i18nKey="charge_card_dialog_body">
|
||||
<p className="text-sm text-gray-500">
|
||||
You are about to charge the attendee{" "}
|
||||
<IntlProvider locale="en">
|
||||
<FormattedNumber
|
||||
value={props.paymentAmount / 100.0}
|
||||
style="currency"
|
||||
currency={props.paymentCurrency?.toUpperCase()}
|
||||
/>
|
||||
</IntlProvider>
|
||||
. Are you sure you want to continue?
|
||||
</p>
|
||||
</Trans>
|
||||
<p>{t("charge_card_dialog_body", currencyStringParams)}</p>
|
||||
|
||||
{chargeError && (
|
||||
<div className="mt-4 flex text-red-500">
|
||||
@@ -80,16 +71,7 @@ export const ChargeCardDialog = (props: IRescheduleDialog) => {
|
||||
bookingId,
|
||||
})
|
||||
}>
|
||||
<Trans i18nKey="charge_card_confirm">
|
||||
Charge attendee{" "}
|
||||
<IntlProvider locale="en">
|
||||
<FormattedNumber
|
||||
value={props.paymentAmount / 100.0}
|
||||
style="currency"
|
||||
currency={props.paymentCurrency?.toUpperCase()}
|
||||
/>
|
||||
</IntlProvider>
|
||||
</Trans>
|
||||
{t("charge_attendee", currencyStringParams)}
|
||||
</Button>
|
||||
</DialogFooter>
|
||||
</div>
|
||||
|
||||
@@ -174,7 +174,6 @@ export const EditLocationDialog = (props: ISetLocationDialog) => {
|
||||
id="locationInput"
|
||||
placeholder={t(eventLocationType.organizerInputPlaceholder || "")}
|
||||
required
|
||||
className="border-default block w-full rounded-sm text-sm"
|
||||
// eslint-disable-next-line @typescript-eslint/no-non-null-assertion
|
||||
defaultValue={
|
||||
defaultLocation ? defaultLocation[eventLocationType.defaultValueVariable] : undefined
|
||||
|
||||
@@ -1,21 +1,23 @@
|
||||
import { SchedulingType } from "@prisma/client";
|
||||
import type { FormValues, EventTypeSetup } from "pages/event-types/[type]";
|
||||
import type { EventTypeSetup, FormValues } from "pages/event-types/[type]";
|
||||
import { useState } from "react";
|
||||
import { Controller, useFormContext } from "react-hook-form";
|
||||
import type { OptionProps, SingleValueProps } from "react-select";
|
||||
import { components } from "react-select";
|
||||
|
||||
import dayjs from "@calcom/dayjs";
|
||||
import useLockedFieldsManager from "@calcom/features/ee/managed-event-types/hooks/useLockedFieldsManager";
|
||||
import { NewScheduleButton } from "@calcom/features/schedules";
|
||||
import classNames from "@calcom/lib/classNames";
|
||||
import { useLocale } from "@calcom/lib/hooks/useLocale";
|
||||
import { weekdayNames } from "@calcom/lib/weekday";
|
||||
import { trpc } from "@calcom/trpc/react";
|
||||
import useMeQuery from "@calcom/trpc/react/hooks/useMeQuery";
|
||||
import { Badge, Button, Select, SettingsToggle, SkeletonText, EmptyScreen } from "@calcom/ui";
|
||||
import { ExternalLink, Globe, Clock } from "@calcom/ui/components/icon";
|
||||
import { Badge, Button, Select, SettingsToggle, SkeletonText } from "@calcom/ui";
|
||||
import { ExternalLink, Globe } from "@calcom/ui/components/icon";
|
||||
|
||||
type AvailabilityOption = {
|
||||
import { SelectSkeletonLoader } from "@components/availability/SkeletonLoader";
|
||||
|
||||
export type AvailabilityOption = {
|
||||
label: string;
|
||||
value: number;
|
||||
isDefault: boolean;
|
||||
@@ -62,40 +64,12 @@ const SingleValue = ({ ...props }: SingleValueProps<AvailabilityOption>) => {
|
||||
);
|
||||
};
|
||||
|
||||
const AvailabilitySelect = ({
|
||||
className = "",
|
||||
options,
|
||||
value,
|
||||
...props
|
||||
}: {
|
||||
className?: string;
|
||||
name: string;
|
||||
value: AvailabilityOption | undefined;
|
||||
options: AvailabilityOption[];
|
||||
isDisabled?: boolean;
|
||||
onBlur: () => void;
|
||||
onChange: (value: AvailabilityOption | null) => void;
|
||||
}) => {
|
||||
const { t } = useLocale();
|
||||
return (
|
||||
<Select
|
||||
placeholder={t("select")}
|
||||
options={options}
|
||||
isDisabled={props.isDisabled}
|
||||
isSearchable={false}
|
||||
onChange={props.onChange}
|
||||
className={classNames("block w-full min-w-0 flex-1 rounded-sm text-sm", className)}
|
||||
defaultValue={value}
|
||||
components={{ Option, SingleValue }}
|
||||
isMulti={false}
|
||||
/>
|
||||
);
|
||||
};
|
||||
|
||||
const format = (date: Date, hour12: boolean) =>
|
||||
Intl.DateTimeFormat(undefined, { hour: "numeric", minute: "numeric", hour12 }).format(
|
||||
new Date(dayjs.utc(date).format("YYYY-MM-DDTHH:mm:ss"))
|
||||
);
|
||||
Intl.DateTimeFormat(undefined, {
|
||||
hour: "numeric",
|
||||
minute: "numeric",
|
||||
hourCycle: hour12 ? "h12" : "h24",
|
||||
}).format(new Date(dayjs.utc(date).format("YYYY-MM-DDTHH:mm:ss")));
|
||||
|
||||
const EventTypeScheduleDetails = ({
|
||||
isManagedEventType,
|
||||
@@ -188,61 +162,57 @@ const EventTypeSchedule = ({ eventType }: { eventType: EventTypeSetup }) => {
|
||||
);
|
||||
const { watch } = useFormContext<FormValues>();
|
||||
const watchSchedule = watch("schedule");
|
||||
const formMethods = useFormContext<FormValues>();
|
||||
const [options, setOptions] = useState<AvailabilityOption[]>([]);
|
||||
|
||||
const { data, isLoading } = trpc.viewer.availability.list.useQuery();
|
||||
const { isLoading } = trpc.viewer.availability.list.useQuery(undefined, {
|
||||
onSuccess: ({ schedules }) => {
|
||||
const options = schedules.map((schedule) => ({
|
||||
value: schedule.id,
|
||||
label: schedule.name,
|
||||
isDefault: schedule.isDefault,
|
||||
isManaged: false,
|
||||
}));
|
||||
|
||||
if (!data?.schedules.length && !isLoading)
|
||||
return (
|
||||
<EmptyScreen
|
||||
Icon={Clock}
|
||||
headline={t("new_schedule_heading")}
|
||||
description={t("new_schedule_description")}
|
||||
buttonRaw={<NewScheduleButton fromEventType />}
|
||||
border={false}
|
||||
/>
|
||||
);
|
||||
// We are showing a managed event for a team admin, so adding the option to let members choose their schedule
|
||||
if (isManagedEventType) {
|
||||
options.push({
|
||||
value: 0,
|
||||
label: t("members_default_schedule"),
|
||||
isDefault: false,
|
||||
isManaged: false,
|
||||
});
|
||||
}
|
||||
|
||||
const schedules = data?.schedules || [];
|
||||
// We are showing a managed event for a member and team owner selected their own schedule, so adding
|
||||
// the managed schedule option
|
||||
if (
|
||||
isChildrenManagedEventType &&
|
||||
watchSchedule &&
|
||||
!schedules.find((schedule) => schedule.id === watchSchedule)
|
||||
) {
|
||||
options.push({
|
||||
value: watchSchedule,
|
||||
label: eventType.scheduleName ?? t("default_schedule_name"),
|
||||
isDefault: false,
|
||||
isManaged: false,
|
||||
});
|
||||
}
|
||||
|
||||
const options = schedules.map((schedule) => ({
|
||||
value: schedule.id,
|
||||
label: schedule.name,
|
||||
isDefault: schedule.isDefault,
|
||||
isManaged: false,
|
||||
}));
|
||||
setOptions(options);
|
||||
|
||||
// We are showing a managed event for a team admin, so adding the option to let members choose their schedule
|
||||
if (isManagedEventType) {
|
||||
options.push({
|
||||
value: 0,
|
||||
label: t("members_default_schedule"),
|
||||
isDefault: false,
|
||||
isManaged: false,
|
||||
});
|
||||
}
|
||||
const scheduleId = formMethods.getValues("schedule");
|
||||
const value = options.find((option) =>
|
||||
scheduleId
|
||||
? option.value === scheduleId
|
||||
: option.value === schedules.find((schedule) => schedule.isDefault)?.id
|
||||
);
|
||||
|
||||
// We are showing a managed event for a member and team owner selected their own schedule, so adding
|
||||
// the managed schedule option
|
||||
if (
|
||||
isChildrenManagedEventType &&
|
||||
watchSchedule &&
|
||||
!schedules.find((schedule) => schedule.id === watchSchedule)
|
||||
) {
|
||||
options.push({
|
||||
value: watchSchedule,
|
||||
label: eventType.scheduleName ?? t("default_schedule_name"),
|
||||
isDefault: false,
|
||||
isManaged: false,
|
||||
});
|
||||
}
|
||||
formMethods.setValue("availability", value);
|
||||
},
|
||||
});
|
||||
|
||||
const value = options.find((option) =>
|
||||
watchSchedule
|
||||
? option.value === watchSchedule
|
||||
: isManagedEventType
|
||||
? option.value === 0
|
||||
: option.value === schedules.find((schedule) => schedule.isDefault)?.id
|
||||
);
|
||||
const availabilityValue = formMethods.watch("availability");
|
||||
|
||||
return (
|
||||
<div className="space-y-4">
|
||||
@@ -251,27 +221,33 @@ const EventTypeSchedule = ({ eventType }: { eventType: EventTypeSetup }) => {
|
||||
{t("availability")}
|
||||
{shouldLockIndicator("availability")}
|
||||
</label>
|
||||
<Controller
|
||||
name="schedule"
|
||||
render={({ field }) => (
|
||||
<>
|
||||
<AvailabilitySelect
|
||||
value={value}
|
||||
options={options}
|
||||
onBlur={field.onBlur}
|
||||
isDisabled={shouldLockDisableProps("schedule").disabled}
|
||||
name={field.name}
|
||||
onChange={(selected) => {
|
||||
field.onChange(selected?.value || null);
|
||||
}}
|
||||
/>
|
||||
</>
|
||||
)}
|
||||
/>
|
||||
{isLoading && <SelectSkeletonLoader />}
|
||||
{!isLoading && (
|
||||
<Controller
|
||||
name="schedule"
|
||||
render={({ field }) => {
|
||||
return (
|
||||
<Select
|
||||
placeholder={t("select")}
|
||||
options={options}
|
||||
isSearchable={false}
|
||||
onChange={(selected) => {
|
||||
field.onChange(selected?.value || null);
|
||||
if (selected?.value) formMethods.setValue("availability", selected);
|
||||
}}
|
||||
className="block w-full min-w-0 flex-1 rounded-sm text-sm"
|
||||
value={availabilityValue}
|
||||
components={{ Option, SingleValue }}
|
||||
isMulti={false}
|
||||
/>
|
||||
);
|
||||
}}
|
||||
/>
|
||||
)}
|
||||
</div>
|
||||
{value?.value !== 0 ? (
|
||||
{availabilityValue?.value !== 0 ? (
|
||||
<EventTypeScheduleDetails
|
||||
selectedScheduleValue={value}
|
||||
selectedScheduleValue={availabilityValue}
|
||||
isManagedEventType={isManagedEventType || isChildrenManagedEventType}
|
||||
/>
|
||||
) : (
|
||||
|
||||
@@ -12,6 +12,7 @@ import { z } from "zod";
|
||||
import type { EventLocationType } from "@calcom/app-store/locations";
|
||||
import { getEventLocationType, MeetLocationType, LocationType } from "@calcom/app-store/locations";
|
||||
import useLockedFieldsManager from "@calcom/features/ee/managed-event-types/hooks/useLockedFieldsManager";
|
||||
import cx from "@calcom/lib/classNames";
|
||||
import { CAL_URL } from "@calcom/lib/constants";
|
||||
import { useLocale } from "@calcom/lib/hooks/useLocale";
|
||||
import { md } from "@calcom/lib/markdownIt";
|
||||
@@ -80,6 +81,7 @@ const DescriptionEditor = (props: DescriptionEditorProps) => {
|
||||
const [mounted, setIsMounted] = useState(false);
|
||||
const { t } = useLocale();
|
||||
const { description } = props;
|
||||
const [firstRender, setFirstRender] = useState(true);
|
||||
useEffect(() => {
|
||||
setIsMounted(true);
|
||||
}, []);
|
||||
@@ -91,6 +93,8 @@ const DescriptionEditor = (props: DescriptionEditorProps) => {
|
||||
excludedToolbarItems={["blockType"]}
|
||||
placeholder={t("quick_video_meeting")}
|
||||
editable={props.editable}
|
||||
firstRender={firstRender}
|
||||
setFirstRender={setFirstRender}
|
||||
/>
|
||||
) : (
|
||||
<SkeletonContainer>
|
||||
@@ -277,7 +281,13 @@ export const EventSetupTab = (
|
||||
<div className="flex items-center">
|
||||
<img
|
||||
src={eventLocationType.iconUrl}
|
||||
className="h-4 w-4"
|
||||
className={cx(
|
||||
"h-4 w-4",
|
||||
// invert all the icons except app icons
|
||||
eventLocationType.iconUrl &&
|
||||
!eventLocationType.iconUrl.startsWith("/api") &&
|
||||
"dark:invert"
|
||||
)}
|
||||
alt={`${eventLocationType.label} logo`}
|
||||
/>
|
||||
<span className="line-clamp-1 ms-1 text-sm">{eventLabel}</span>
|
||||
|
||||
@@ -51,6 +51,7 @@ import {
|
||||
} from "@calcom/ui/components/icon";
|
||||
|
||||
import { EmbedButton, EmbedDialog } from "@components/Embed";
|
||||
import type { AvailabilityOption } from "@components/eventtype/EventAvailabilityTab";
|
||||
|
||||
type Props = {
|
||||
children: React.ReactNode;
|
||||
@@ -63,6 +64,7 @@ type Props = {
|
||||
enabledWorkflowsNumber: number;
|
||||
formMethods: UseFormReturn<FormValues>;
|
||||
isUpdateMutationLoading?: boolean;
|
||||
availability?: AvailabilityOption;
|
||||
};
|
||||
|
||||
function getNavigation(props: {
|
||||
@@ -71,8 +73,10 @@ function getNavigation(props: {
|
||||
enabledAppsNumber: number;
|
||||
enabledWorkflowsNumber: number;
|
||||
installedAppsNumber: number;
|
||||
availability: AvailabilityOption | undefined;
|
||||
}) {
|
||||
const { eventType, t, enabledAppsNumber, installedAppsNumber, enabledWorkflowsNumber } = props;
|
||||
const { eventType, t, enabledAppsNumber, installedAppsNumber, enabledWorkflowsNumber, availability } =
|
||||
props;
|
||||
const duration =
|
||||
eventType.metadata?.multipleDuration?.map((duration) => ` ${duration}`) || eventType.length;
|
||||
|
||||
@@ -128,6 +132,7 @@ function EventTypeSingleLayout({
|
||||
enabledWorkflowsNumber,
|
||||
isUpdateMutationLoading,
|
||||
formMethods,
|
||||
availability,
|
||||
}: Props) {
|
||||
const utils = trpc.useContext();
|
||||
const { t } = useLocale();
|
||||
@@ -171,6 +176,7 @@ function EventTypeSingleLayout({
|
||||
enabledAppsNumber,
|
||||
installedAppsNumber,
|
||||
enabledWorkflowsNumber,
|
||||
availability,
|
||||
});
|
||||
navigation.splice(1, 0, {
|
||||
name: "availability",
|
||||
@@ -214,7 +220,7 @@ function EventTypeSingleLayout({
|
||||
});
|
||||
}
|
||||
return navigation;
|
||||
}, [t, eventType, installedAppsNumber, enabledAppsNumber, enabledWorkflowsNumber, team]);
|
||||
}, [t, eventType, installedAppsNumber, enabledAppsNumber, enabledWorkflowsNumber, team, availability]);
|
||||
|
||||
const permalink = `${CAL_URL}/${team ? `team/${team.slug}` : eventType.users[0].username}/${
|
||||
eventType.slug
|
||||
@@ -374,6 +380,8 @@ function EventTypeSingleLayout({
|
||||
tabs={EventTypeTabs}
|
||||
sticky
|
||||
linkProps={{ shallow: true }}
|
||||
itemClassname="items-start"
|
||||
iconClassName="md:mt-px"
|
||||
/>
|
||||
</div>
|
||||
<div className="p-2 md:mx-0 md:p-0 xl:hidden">
|
||||
|
||||
@@ -35,6 +35,7 @@ const UserProfile = (props: IUserProfileProps) => {
|
||||
const router = useRouter();
|
||||
const createEventType = trpc.viewer.eventTypes.create.useMutation();
|
||||
const telemetry = useTelemetry();
|
||||
const [firstRender, setFirstRender] = useState(true);
|
||||
|
||||
const mutation = trpc.viewer.updateProfile.useMutation({
|
||||
onSuccess: async (_data, context) => {
|
||||
@@ -147,6 +148,8 @@ const UserProfile = (props: IUserProfileProps) => {
|
||||
getText={() => md.render(getValues("bio") || user?.bio || "")}
|
||||
setText={(value: string) => setValue("bio", turndown(value))}
|
||||
excludedToolbarItems={["blockType"]}
|
||||
firstRender={firstRender}
|
||||
setFirstRender={setFirstRender}
|
||||
/>
|
||||
<p className="dark:text-inverted text-default mt-2 font-sans text-sm font-normal">
|
||||
{t("few_sentences_about_yourself")}
|
||||
|
||||
@@ -1,17 +0,0 @@
|
||||
import { useSession } from "next-auth/react";
|
||||
import type { FC } from "react";
|
||||
import { Fragment } from "react";
|
||||
|
||||
type AdminRequiredProps = {
|
||||
as?: keyof JSX.IntrinsicElements;
|
||||
children?: React.ReactNode;
|
||||
};
|
||||
|
||||
/** @deprecated use PermssionContainer instead. Will delete once V2 goes live */
|
||||
export const AdminRequired: FC<AdminRequiredProps> = ({ children, as, ...rest }) => {
|
||||
const session = useSession();
|
||||
|
||||
if (session.data?.user.role !== "ADMIN") return null;
|
||||
const Component = as ?? Fragment;
|
||||
return <Component {...rest}>{children}</Component>;
|
||||
};
|
||||
@@ -1,7 +1,6 @@
|
||||
import classNames from "classnames";
|
||||
|
||||
import { APP_NAME, LOGO } from "@calcom/lib/constants";
|
||||
import { HeadSeo } from "@calcom/ui";
|
||||
import { HeadSeo, Logo } from "@calcom/ui";
|
||||
|
||||
import Loader from "@components/Loader";
|
||||
|
||||
@@ -18,10 +17,7 @@ export default function AuthContainer(props: React.PropsWithChildren<Props>) {
|
||||
return (
|
||||
<div className="flex min-h-screen flex-col justify-center bg-[#f3f4f6] py-12 sm:px-6 lg:px-8">
|
||||
<HeadSeo title={props.title} description={props.description} />
|
||||
{props.showLogo && (
|
||||
// eslint-disable-next-line @next/next/no-img-element
|
||||
<img className="mb-auto h-4" src={LOGO} alt={`${APP_NAME} Logo`} />
|
||||
)}
|
||||
{props.showLogo && <Logo small inline={false} className="mx-auto mb-auto" />}
|
||||
|
||||
<div className={classNames(props.showLogo ? "text-center" : "", "sm:mx-auto sm:w-full sm:max-w-md")}>
|
||||
{props.heading && <h2 className="font-cal text-emphasis text-center text-3xl">{props.heading}</h2>}
|
||||
|
||||
@@ -16,8 +16,10 @@ const EditableHeading = function EditableHeading({
|
||||
const [isEditing, setIsEditing] = useState(false);
|
||||
const enableEditing = () => setIsEditing(true);
|
||||
return (
|
||||
<div className="group relative cursor-pointer" onClick={enableEditing}>
|
||||
<div className="flex items-center">
|
||||
<div
|
||||
className="group pointer-events-none relative truncate sm:pointer-events-auto"
|
||||
onClick={enableEditing}>
|
||||
<div className="flex cursor-pointer items-center">
|
||||
<label className="min-w-8 relative inline-block">
|
||||
<span className="whitespace-pre text-xl tracking-normal text-transparent">{value} </span>
|
||||
{!isEditing && isReady && (
|
||||
@@ -29,7 +31,7 @@ const EditableHeading = function EditableHeading({
|
||||
value={value}
|
||||
required
|
||||
className={classNames(
|
||||
"text-emphasis hover:text-default focus:text-emphasis absolute top-0 left-0 w-full cursor-pointer border-none bg-transparent p-0 align-top text-xl focus:outline-none focus:ring-0"
|
||||
"text-emphasis hover:text-default focus:text-emphasis absolute top-0 left-0 w-full cursor-pointer truncate border-none bg-transparent p-0 align-top text-xl focus:outline-none focus:ring-0"
|
||||
)}
|
||||
onFocus={(e) => {
|
||||
setIsEditing(true);
|
||||
|
||||
@@ -49,7 +49,7 @@ const CheckboxField = forwardRef<HTMLInputElement, Props>(
|
||||
{...rest}
|
||||
ref={ref}
|
||||
type="checkbox"
|
||||
className="text-primary-600 focus:ring-primary-500 border-default h-4 w-4 rounded"
|
||||
className="text-primary-600 focus:ring-primary-500 border-default bg-default h-4 w-4 rounded"
|
||||
/>
|
||||
</div>
|
||||
<span className="ms-3 text-sm">{description}</span>
|
||||
|
||||
@@ -3,6 +3,7 @@ import { components } from "react-select";
|
||||
|
||||
import type { EventLocationType } from "@calcom/app-store/locations";
|
||||
import { classNames } from "@calcom/lib";
|
||||
import cx from "@calcom/lib/classNames";
|
||||
import { Select } from "@calcom/ui";
|
||||
|
||||
export type LocationOption = {
|
||||
@@ -19,10 +20,14 @@ export type GroupOptionType = GroupBase<LocationOption>;
|
||||
const OptionWithIcon = ({ icon, label }: { icon?: string; label: string }) => {
|
||||
return (
|
||||
<div className="flex items-center gap-3">
|
||||
{/* TODO: figure out a way to invert icons when in dark mode. We can't just
|
||||
dark:invert due to google meet cal etc all breaking when we do this
|
||||
*/}
|
||||
{icon && <img src={icon} alt="cover" className="h-3.5 w-3.5 dark:hidden" />}
|
||||
{icon && (
|
||||
<img
|
||||
src={icon}
|
||||
alt="cover"
|
||||
// invert all the icons except app icons
|
||||
className={cx("h-3.5 w-3.5", icon && !icon.startsWith("/api") && "dark:invert")}
|
||||
/>
|
||||
)}
|
||||
<span className={classNames("text-sm font-medium")}>{label}</span>
|
||||
</div>
|
||||
);
|
||||
|
||||
@@ -155,6 +155,7 @@ const AppProviders = (props: AppPropsWithChildren) => {
|
||||
<SessionProvider session={session || undefined}>
|
||||
<CustomI18nextProvider {...props}>
|
||||
<TooltipProvider>
|
||||
{/* color-scheme makes background:transparent not work which is required by embed. We need to ensure next-theme adds color-scheme to `body` instead of `html`(https://github.com/pacocoursey/next-themes/blob/main/src/index.tsx#L74). Once that's done we can enable color-scheme support */}
|
||||
<CalcomThemeProvider
|
||||
nonce={props.pageProps.nonce}
|
||||
isThemeSupported={props.Component.isThemeSupported}
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "@calcom/web",
|
||||
"version": "2.8.3",
|
||||
"version": "2.8.7",
|
||||
"private": true,
|
||||
"scripts": {
|
||||
"analyze": "ANALYZE=true next build",
|
||||
@@ -115,8 +115,6 @@
|
||||
"react-select": "^5.7.0",
|
||||
"react-timezone-select": "^1.4.0",
|
||||
"react-use-intercom": "1.5.1",
|
||||
"react-virtualized-auto-sizer": "^1.0.6",
|
||||
"react-window": "^1.8.7",
|
||||
"remark": "^14.0.2",
|
||||
"rrule": "^2.7.1",
|
||||
"sanitize-html": "^2.10.0",
|
||||
@@ -155,8 +153,6 @@
|
||||
"@types/qrcode": "^1.4.3",
|
||||
"@types/react": "18.0.26",
|
||||
"@types/react-phone-number-input": "^3.0.14",
|
||||
"@types/react-virtualized-auto-sizer": "^1.0.1",
|
||||
"@types/react-window": "^1.8.5",
|
||||
"@types/sanitize-html": "^2.9.0",
|
||||
"@types/stripe": "^8.0.417",
|
||||
"@types/uuid": "8.3.1",
|
||||
|
||||
@@ -18,7 +18,7 @@ export const getStaticProps: GetStaticProps<
|
||||
{ user: string }
|
||||
> = async (context) => {
|
||||
const { user: username, month } = paramsSchema.parse(context.params);
|
||||
const user = await prisma.user.findUnique({
|
||||
const userWithCredentials = await prisma.user.findUnique({
|
||||
where: {
|
||||
username,
|
||||
},
|
||||
@@ -34,14 +34,15 @@ export const getStaticProps: GetStaticProps<
|
||||
).startOf("day");
|
||||
const endDate = startDate.endOf("month");
|
||||
try {
|
||||
const results = user?.credentials
|
||||
const results = userWithCredentials?.credentials
|
||||
? await getCachedResults(
|
||||
user?.credentials,
|
||||
userWithCredentials?.credentials,
|
||||
startDate.format(),
|
||||
endDate.format(),
|
||||
user?.selectedCalendars
|
||||
userWithCredentials?.selectedCalendars
|
||||
)
|
||||
: [];
|
||||
|
||||
return {
|
||||
props: { results, date: new Date().toISOString() },
|
||||
revalidate: 1,
|
||||
|
||||
@@ -1,3 +1,4 @@
|
||||
import type { NextPageContext } from "next";
|
||||
import type { DocumentContext, DocumentProps } from "next/document";
|
||||
import Document, { Head, Html, Main, NextScript } from "next/document";
|
||||
import { z } from "zod";
|
||||
@@ -7,15 +8,22 @@ import { getDirFromLang } from "@calcom/lib/i18n";
|
||||
import { csp } from "@lib/csp";
|
||||
|
||||
type Props = Record<string, unknown> & DocumentProps;
|
||||
|
||||
function setHeader(ctx: NextPageContext, name: string, value: string) {
|
||||
try {
|
||||
ctx.res?.setHeader(name, value);
|
||||
} catch (e) {
|
||||
// Getting "Error [ERR_HTTP_HEADERS_SENT]: Cannot set headers after they are sent to the client" when revalidate calendar chache
|
||||
console.log(`Error setting header ${name}=${value} for ${ctx.asPath || "unknown asPath"}`, e);
|
||||
}
|
||||
}
|
||||
class MyDocument extends Document<Props> {
|
||||
static async getInitialProps(ctx: DocumentContext) {
|
||||
const { nonce } = csp(ctx.req || null, ctx.res || null);
|
||||
if (!process.env.CSP_POLICY) {
|
||||
ctx.res?.setHeader("x-csp", "not-opted-in");
|
||||
setHeader(ctx, "x-csp", "not-opted-in");
|
||||
} else if (!ctx.res?.getHeader("x-csp")) {
|
||||
// If x-csp not set by gSSP, then it's initialPropsOnly
|
||||
ctx.res?.setHeader("x-csp", "initialPropsOnly");
|
||||
setHeader(ctx, "x-csp", "initialPropsOnly");
|
||||
}
|
||||
const asPath = ctx.asPath || "";
|
||||
// Use a dummy URL as default so that URL parsing works for relative URLs as well. We care about searchParams and pathname only
|
||||
|
||||
@@ -14,7 +14,7 @@ export default async function handler(req: NextApiRequest, res: NextApiResponse)
|
||||
return;
|
||||
}
|
||||
|
||||
const user = await prisma.user.findUnique({
|
||||
const userWithCredentials = await prisma.user.findUnique({
|
||||
where: {
|
||||
id: session.user.id,
|
||||
},
|
||||
@@ -25,11 +25,11 @@ export default async function handler(req: NextApiRequest, res: NextApiResponse)
|
||||
selectedCalendars: true,
|
||||
},
|
||||
});
|
||||
|
||||
if (!user) {
|
||||
if (!userWithCredentials) {
|
||||
res.status(401).json({ message: "Not authenticated" });
|
||||
return;
|
||||
}
|
||||
const { credentials, ...user } = userWithCredentials;
|
||||
|
||||
if (req.method === "POST") {
|
||||
await prisma.selectedCalendar.upsert({
|
||||
@@ -80,7 +80,7 @@ export default async function handler(req: NextApiRequest, res: NextApiResponse)
|
||||
});
|
||||
|
||||
// get user's credentials + their connected integrations
|
||||
const calendarCredentials = getCalendarCredentials(user.credentials);
|
||||
const calendarCredentials = getCalendarCredentials(credentials);
|
||||
// get all the connected integrations' calendars (from third party)
|
||||
const { connectedCalendars } = await getConnectedCalendars(calendarCredentials, user.selectedCalendars);
|
||||
const calendars = connectedCalendars.flatMap((c) => c.calendars).filter(notEmpty);
|
||||
|
||||
@@ -0,0 +1,28 @@
|
||||
import type { NextApiRequest, NextApiResponse } from "next";
|
||||
import crypto from "node:crypto";
|
||||
|
||||
import { getServerSession } from "@calcom/features/auth/lib/getServerSession";
|
||||
import { defaultHandler } from "@calcom/lib/server";
|
||||
|
||||
async function handler(req: NextApiRequest, res: NextApiResponse) {
|
||||
const session = await getServerSession({ req, res });
|
||||
const secret = process.env.INTERCOM_SECRET;
|
||||
|
||||
if (!session) {
|
||||
return res.status(401).json({ message: "user not authenticated" });
|
||||
}
|
||||
|
||||
if (!secret) {
|
||||
return res.status(400).json({ message: "Intercom Identity Verification secret not set" });
|
||||
}
|
||||
|
||||
const hmac = crypto.createHmac("sha256", secret);
|
||||
hmac.update(String(session.user.id));
|
||||
const hash = hmac.digest("hex");
|
||||
|
||||
return res.status(200).json({ hash });
|
||||
}
|
||||
|
||||
export default defaultHandler({
|
||||
GET: Promise.resolve({ default: handler }),
|
||||
});
|
||||
@@ -0,0 +1,80 @@
|
||||
import type { NextApiRequest, NextApiResponse } from "next";
|
||||
import { z } from "zod";
|
||||
|
||||
import { IS_SELF_HOSTED, LOGO, LOGO_ICON, WEBAPP_URL } from "@calcom/lib/constants";
|
||||
|
||||
function removePort(url: string) {
|
||||
return url.replace(/:\d+$/, "");
|
||||
}
|
||||
|
||||
function extractSubdomainAndDomain(hostname: string) {
|
||||
const hostParts = removePort(hostname).split(".");
|
||||
|
||||
const subdomainParts = hostParts.slice(0, hostParts.length - 2);
|
||||
const domain = hostParts.slice(hostParts.length - 2).join(".");
|
||||
|
||||
return [subdomainParts[0], domain];
|
||||
}
|
||||
|
||||
const logoApiSchema = z.object({
|
||||
icon: z.coerce.boolean().optional(),
|
||||
});
|
||||
|
||||
const SYSTEM_SUBDOMAINS = ["console", "app", "www"];
|
||||
|
||||
async function getTeamLogos(subdomain: string) {
|
||||
if (
|
||||
// if not cal.com
|
||||
IS_SELF_HOSTED ||
|
||||
// missing subdomain (empty string)
|
||||
!subdomain ||
|
||||
// in SYSTEM_SUBDOMAINS list
|
||||
SYSTEM_SUBDOMAINS.includes(subdomain)
|
||||
) {
|
||||
return {
|
||||
appLogo: `${WEBAPP_URL}${LOGO}`,
|
||||
appIconLogo: `${WEBAPP_URL}${LOGO_ICON}`,
|
||||
};
|
||||
}
|
||||
// load from DB
|
||||
const { default: prisma } = await import("@calcom/prisma");
|
||||
const team = await prisma.team.findUniqueOrThrow({
|
||||
where: {
|
||||
slug: subdomain,
|
||||
},
|
||||
select: {
|
||||
appLogo: true,
|
||||
appIconLogo: true,
|
||||
},
|
||||
});
|
||||
// try to use team logos, otherwise default to LOGO/LOGO_ICON regardless
|
||||
return {
|
||||
appLogo: team.appLogo || `${WEBAPP_URL}${LOGO}`,
|
||||
appIconLogo: team.appIconLogo || `${WEBAPP_URL}${LOGO_ICON}`,
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* This API endpoint is used to serve the logo associated with a team if no logo is found we serve our default logo
|
||||
*/
|
||||
export default async function handler(req: NextApiRequest, res: NextApiResponse) {
|
||||
const { query } = req;
|
||||
const parsedQuery = logoApiSchema.parse(query);
|
||||
|
||||
const hostname = req?.headers["host"];
|
||||
if (!hostname) throw new Error("No hostname");
|
||||
const domains = extractSubdomainAndDomain(hostname);
|
||||
if (!domains) throw new Error("No domains");
|
||||
|
||||
const [subdomain] = domains;
|
||||
const { appLogo, appIconLogo } = await getTeamLogos(subdomain);
|
||||
|
||||
const filteredLogo = parsedQuery?.icon ? appIconLogo : appLogo;
|
||||
|
||||
const response = await fetch(filteredLogo);
|
||||
const arrayBuffer = await response.arrayBuffer();
|
||||
const buffer = Buffer.from(arrayBuffer);
|
||||
res.setHeader("Content-Type", response.headers.get("content-type") as string);
|
||||
res.setHeader("Cache-Control", "s-maxage=86400");
|
||||
res.send(buffer);
|
||||
}
|
||||
@@ -54,7 +54,6 @@ async function handler(req: NextApiRequest, res: NextApiResponse) {
|
||||
user: {
|
||||
select: {
|
||||
id: true,
|
||||
credentials: true,
|
||||
timeZone: true,
|
||||
email: true,
|
||||
name: true,
|
||||
|
||||
@@ -13,7 +13,7 @@ export default function Apps({ categories }: InferGetStaticPropsType<typeof getS
|
||||
const { t, isLocaleReady } = useLocale();
|
||||
|
||||
return (
|
||||
<Shell isPublic large>
|
||||
<Shell isPublic large hideHeadingOnMobile>
|
||||
<div className="text-md flex items-center gap-1 px-4 pb-3 pt-3 font-normal md:px-8 lg:px-0 lg:pt-0">
|
||||
<Link
|
||||
href="/apps"
|
||||
|
||||
@@ -165,14 +165,6 @@ export default function Login({
|
||||
{...register("email")}
|
||||
/>
|
||||
<div className="relative">
|
||||
<div className="absolute -top-[6px] z-10 ltr:right-0 rtl:left-0">
|
||||
<Link
|
||||
href="/auth/forgot-password"
|
||||
tabIndex={-1}
|
||||
className="text-default text-sm font-medium">
|
||||
{t("forgot")}
|
||||
</Link>
|
||||
</div>
|
||||
<PasswordField
|
||||
id="password"
|
||||
autoComplete="off"
|
||||
@@ -180,6 +172,14 @@ export default function Login({
|
||||
className="mb-0"
|
||||
{...register("password")}
|
||||
/>
|
||||
<div className="absolute -top-1.5 ltr:right-0 rtl:left-0">
|
||||
<Link
|
||||
href="/auth/forgot-password"
|
||||
tabIndex={-1}
|
||||
className="text-default text-sm font-medium">
|
||||
{t("forgot")}
|
||||
</Link>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
import { signIn } from "next-auth/react";
|
||||
import { useRouter } from "next/router";
|
||||
import { useEffect } from "react";
|
||||
|
||||
import { samlProductID, samlTenantID } from "@calcom/features/ee/sso/lib/saml";
|
||||
import { HOSTED_CAL_FEATURES } from "@calcom/lib/constants";
|
||||
@@ -13,19 +14,22 @@ import PageWrapper from "@components/PageWrapper";
|
||||
export default function Page({ samlTenantID, samlProductID }: inferSSRProps<typeof getServerSideProps>) {
|
||||
const router = useRouter();
|
||||
|
||||
if (HOSTED_CAL_FEATURES) {
|
||||
router.push("/auth/login");
|
||||
return;
|
||||
}
|
||||
useEffect(() => {
|
||||
if (HOSTED_CAL_FEATURES) {
|
||||
router.push("/auth/login");
|
||||
}
|
||||
}, []);
|
||||
|
||||
// Initiate SAML authentication flow
|
||||
signIn(
|
||||
"saml",
|
||||
{
|
||||
callbackUrl: "/",
|
||||
},
|
||||
{ tenant: samlTenantID, product: samlProductID }
|
||||
);
|
||||
useEffect(() => {
|
||||
// Initiate SAML authentication flow
|
||||
signIn(
|
||||
"saml",
|
||||
{
|
||||
callbackUrl: "/",
|
||||
},
|
||||
{ tenant: samlTenantID, product: samlProductID }
|
||||
);
|
||||
}, []);
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
import { useRouter } from "next/router";
|
||||
import { useState } from "react";
|
||||
import { Controller, useFieldArray, useForm } from "react-hook-form";
|
||||
import { z } from "zod";
|
||||
|
||||
@@ -24,10 +25,15 @@ import {
|
||||
Tooltip,
|
||||
Dialog,
|
||||
DialogTrigger,
|
||||
DropdownMenuSeparator,
|
||||
Dropdown,
|
||||
DropdownMenuContent,
|
||||
DropdownItem,
|
||||
DropdownMenuTrigger,
|
||||
ConfirmationDialogContent,
|
||||
VerticalDivider,
|
||||
} from "@calcom/ui";
|
||||
import { Info, Plus, Trash } from "@calcom/ui/components/icon";
|
||||
import { Info, Plus, Trash, MoreHorizontal } from "@calcom/ui/components/icon";
|
||||
|
||||
import { HttpError } from "@lib/core/http/error";
|
||||
|
||||
@@ -97,6 +103,7 @@ export default function Availability() {
|
||||
|
||||
const { fromEventType } = router.query;
|
||||
const { timeFormat } = me.data || { timeFormat: null };
|
||||
const [deleteDialogOpen, setDeleteDialogOpen] = useState(false);
|
||||
const { data: schedule, isLoading } = trpc.viewer.availability.schedule.get.useQuery(
|
||||
{ scheduleId },
|
||||
{
|
||||
@@ -182,11 +189,8 @@ export default function Availability() {
|
||||
}
|
||||
CTA={
|
||||
<div className="flex items-center justify-end">
|
||||
<div className="sm:hover:bg-subtle flex items-center rounded-md px-2">
|
||||
<Skeleton
|
||||
as={Label}
|
||||
htmlFor="hiddenSwitch"
|
||||
className="mt-2 hidden cursor-pointer self-center pr-2 sm:inline">
|
||||
<div className="hidden items-center rounded-md px-2 sm:flex sm:hover:bg-gray-100">
|
||||
<Skeleton as={Label} htmlFor="hiddenSwitch" className="mt-2 cursor-pointer self-center pr-2 ">
|
||||
{t("set_to_default")}
|
||||
</Skeleton>
|
||||
<Switch
|
||||
@@ -199,7 +203,7 @@ export default function Availability() {
|
||||
/>
|
||||
</div>
|
||||
|
||||
<VerticalDivider />
|
||||
<VerticalDivider className="hidden sm:inline" />
|
||||
<Dialog>
|
||||
<DialogTrigger asChild>
|
||||
<Button
|
||||
@@ -207,6 +211,7 @@ export default function Availability() {
|
||||
variant="icon"
|
||||
color="destructive"
|
||||
aria-label={t("delete")}
|
||||
className="hidden sm:inline"
|
||||
disabled={schedule?.isLastSchedule}
|
||||
tooltip={t("requires_at_least_one_schedule")}
|
||||
/>
|
||||
@@ -223,8 +228,51 @@ export default function Availability() {
|
||||
{t("delete_schedule_description")}
|
||||
</ConfirmationDialogContent>
|
||||
</Dialog>
|
||||
|
||||
<VerticalDivider />
|
||||
<VerticalDivider className="hidden sm:inline" />
|
||||
<Dropdown>
|
||||
<DropdownMenuTrigger asChild>
|
||||
<Button className="sm:hidden" StartIcon={MoreHorizontal} variant="icon" color="secondary" />
|
||||
</DropdownMenuTrigger>
|
||||
<DropdownMenuContent style={{ minWidth: "200px" }}>
|
||||
<DropdownItem
|
||||
type="button"
|
||||
color="destructive"
|
||||
StartIcon={Trash}
|
||||
onClick={() => setDeleteDialogOpen(true)}>
|
||||
{t("delete")}
|
||||
</DropdownItem>
|
||||
<Dialog open={deleteDialogOpen} onOpenChange={setDeleteDialogOpen}>
|
||||
<ConfirmationDialogContent
|
||||
isLoading={deleteMutation.isLoading}
|
||||
variety="danger"
|
||||
title={t("delete_schedule")}
|
||||
confirmBtnText={t("delete")}
|
||||
loadingText={t("delete")}
|
||||
onConfirm={() => {
|
||||
schedule !== undefined && deleteMutation.mutate({ scheduleId: schedule.id });
|
||||
}}>
|
||||
{t("delete_schedule_description")}
|
||||
</ConfirmationDialogContent>
|
||||
</Dialog>
|
||||
<DropdownMenuSeparator />
|
||||
<div className="flex h-9 flex-row items-center justify-between py-2 px-4 hover:bg-gray-100">
|
||||
<Skeleton
|
||||
as={Label}
|
||||
htmlFor="hiddenSwitch"
|
||||
className="mt-2 cursor-pointer self-center pr-2 sm:inline">
|
||||
{t("set_to_default")}
|
||||
</Skeleton>
|
||||
<Switch
|
||||
id="hiddenSwitch"
|
||||
disabled={isLoading || schedule?.isDefault}
|
||||
checked={form.watch("isDefault")}
|
||||
onCheckedChange={(e) => {
|
||||
form.setValue("isDefault", e);
|
||||
}}
|
||||
/>
|
||||
</div>
|
||||
</DropdownMenuContent>
|
||||
</Dropdown>
|
||||
|
||||
<div className="border-default border-l-2" />
|
||||
<Button className="ml-4 lg:ml-0" type="submit" form="availability-form">
|
||||
|
||||
@@ -111,7 +111,11 @@ export default function AvailabilityPage() {
|
||||
const { t } = useLocale();
|
||||
return (
|
||||
<div>
|
||||
<Shell heading={t("availability")} subtitle={t("configure_availability")} CTA={<NewScheduleButton />}>
|
||||
<Shell
|
||||
heading={t("availability")}
|
||||
hideHeadingOnMobile
|
||||
subtitle={t("configure_availability")}
|
||||
CTA={<NewScheduleButton />}>
|
||||
<WithQuery success={({ data }) => <AvailabilityList {...data} />} customLoader={<SkeletonLoader />} />
|
||||
</Shell>
|
||||
</div>
|
||||
|
||||
@@ -122,7 +122,7 @@ export default function Troubleshoot() {
|
||||
const { t } = useLocale();
|
||||
return (
|
||||
<div>
|
||||
<Shell heading={t("troubleshoot")} subtitle={t("troubleshoot_description")}>
|
||||
<Shell heading={t("troubleshoot")} hideHeadingOnMobile subtitle={t("troubleshoot_description")}>
|
||||
{!isLoading && data && <AvailabilityView user={data} />}
|
||||
</Shell>
|
||||
</div>
|
||||
|
||||
@@ -1,8 +1,7 @@
|
||||
/* eslint-disable @typescript-eslint/no-empty-function */
|
||||
import { useAutoAnimate } from "@formkit/auto-animate/react";
|
||||
import { zodResolver } from "@hookform/resolvers/zod";
|
||||
import type { PeriodType } from "@prisma/client";
|
||||
import type { SchedulingType } from "@prisma/client";
|
||||
import type { PeriodType, SchedulingType } from "@prisma/client";
|
||||
import type { GetServerSidePropsContext } from "next";
|
||||
import { Trans } from "next-i18next";
|
||||
import { useEffect, useState } from "react";
|
||||
@@ -18,10 +17,10 @@ import { CAL_URL } from "@calcom/lib/constants";
|
||||
import { useLocale } from "@calcom/lib/hooks/useLocale";
|
||||
import { useTypedQuery } from "@calcom/lib/hooks/useTypedQuery";
|
||||
import { HttpError } from "@calcom/lib/http-error";
|
||||
import { useTelemetry, telemetryEventTypes } from "@calcom/lib/telemetry";
|
||||
import { telemetryEventTypes, useTelemetry } from "@calcom/lib/telemetry";
|
||||
import type { Prisma } from "@calcom/prisma/client";
|
||||
import { eventTypeBookingFields } from "@calcom/prisma/zod-utils";
|
||||
import type { customInputSchema, EventTypeMetaDataSchema } from "@calcom/prisma/zod-utils";
|
||||
import { eventTypeBookingFields } from "@calcom/prisma/zod-utils";
|
||||
import type { RouterOutputs } from "@calcom/trpc/react";
|
||||
import { trpc } from "@calcom/trpc/react";
|
||||
import type { IntervalLimit, RecurringEvent } from "@calcom/types/Calendar";
|
||||
@@ -34,6 +33,7 @@ import PageWrapper from "@components/PageWrapper";
|
||||
// These can't really be moved into calcom/ui due to the fact they use infered getserverside props typings
|
||||
import { EventAdvancedTab } from "@components/eventtype/EventAdvancedTab";
|
||||
import { EventAppsTab } from "@components/eventtype/EventAppsTab";
|
||||
import type { AvailabilityOption } from "@components/eventtype/EventAvailabilityTab";
|
||||
import { EventAvailabilityTab } from "@components/eventtype/EventAvailabilityTab";
|
||||
import { EventLimitsTab } from "@components/eventtype/EventLimitsTab";
|
||||
import { EventRecurringTab } from "@components/eventtype/EventRecurringTab";
|
||||
@@ -94,6 +94,7 @@ export type FormValues = {
|
||||
children: ChildrenEventType[];
|
||||
hosts: { userId: number; isFixed: boolean }[];
|
||||
bookingFields: z.infer<typeof eventTypeBookingFields>;
|
||||
availability?: AvailabilityOption;
|
||||
};
|
||||
|
||||
export type CustomInputParsed = typeof customInputSchema._output;
|
||||
@@ -262,6 +263,7 @@ const EventTypePage = (props: EventTypeSetupProps) => {
|
||||
}, [defaultValues]);
|
||||
|
||||
const appsMetadata = formMethods.getValues("metadata")?.apps;
|
||||
const availability = formMethods.watch("availability");
|
||||
const numberOfInstalledApps = eventTypeApps?.filter((app) => app.isInstalled).length || 0;
|
||||
let numberOfActiveApps = 0;
|
||||
|
||||
@@ -378,6 +380,7 @@ const EventTypePage = (props: EventTypeSetupProps) => {
|
||||
enabledWorkflowsNumber={eventType.workflows.length}
|
||||
eventType={eventType}
|
||||
team={team}
|
||||
availability={availability}
|
||||
isUpdateMutationLoading={updateMutation.isLoading}
|
||||
formMethods={formMethods}
|
||||
disableBorder={tabName === "apps" || tabName === "workflows" || tabName === "webhooks"}
|
||||
@@ -385,13 +388,66 @@ const EventTypePage = (props: EventTypeSetupProps) => {
|
||||
<Form
|
||||
form={formMethods}
|
||||
id="event-type-form"
|
||||
handleSubmit={async (values: FormValues) => {
|
||||
if (!values.children.length) return handleSubmit(values);
|
||||
const existingSlugEventTypes = values.children.filter((ch) =>
|
||||
ch.owner.eventTypeSlugs.includes(slug)
|
||||
);
|
||||
if (!existingSlugEventTypes.length) return handleSubmit(values);
|
||||
setSlugExistsChildrenDialogOpen(existingSlugEventTypes);
|
||||
handleSubmit={async (values) => {
|
||||
const {
|
||||
periodDates,
|
||||
periodCountCalendarDays,
|
||||
beforeBufferTime,
|
||||
afterBufferTime,
|
||||
seatsPerTimeSlot,
|
||||
seatsShowAttendees,
|
||||
bookingLimits,
|
||||
durationLimits,
|
||||
recurringEvent,
|
||||
locations,
|
||||
metadata,
|
||||
customInputs,
|
||||
// We don't need to send send these values to the backend
|
||||
// eslint-disable-next-line @typescript-eslint/no-unused-vars
|
||||
seatsPerTimeSlotEnabled,
|
||||
// eslint-disable-next-line @typescript-eslint/no-unused-vars
|
||||
minimumBookingNoticeInDurationType,
|
||||
availability,
|
||||
...input
|
||||
} = values;
|
||||
|
||||
if (bookingLimits) {
|
||||
const isValid = validateIntervalLimitOrder(bookingLimits);
|
||||
if (!isValid) throw new Error(t("event_setup_booking_limits_error"));
|
||||
}
|
||||
|
||||
if (durationLimits) {
|
||||
const isValid = validateIntervalLimitOrder(durationLimits);
|
||||
if (!isValid) throw new Error(t("event_setup_duration_limits_error"));
|
||||
}
|
||||
|
||||
if (metadata?.multipleDuration !== undefined) {
|
||||
if (metadata?.multipleDuration.length < 1) {
|
||||
throw new Error(t("event_setup_multiple_duration_error"));
|
||||
} else {
|
||||
if (!input.length && !metadata?.multipleDuration?.includes(input.length)) {
|
||||
throw new Error(t("event_setup_multiple_duration_default_error"));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
updateMutation.mutate({
|
||||
...input,
|
||||
locations,
|
||||
recurringEvent,
|
||||
periodStartDate: periodDates.startDate,
|
||||
periodEndDate: periodDates.endDate,
|
||||
periodCountCalendarDays: periodCountCalendarDays === "1",
|
||||
id: eventType.id,
|
||||
beforeEventBuffer: beforeBufferTime,
|
||||
afterEventBuffer: afterBufferTime,
|
||||
bookingLimits,
|
||||
durationLimits,
|
||||
seatsPerTimeSlot,
|
||||
seatsShowAttendees,
|
||||
metadata,
|
||||
customInputs,
|
||||
});
|
||||
}}>
|
||||
<div ref={animationParentRef}>{tabMap[tabName]}</div>
|
||||
</Form>
|
||||
|
||||
@@ -768,6 +768,7 @@ const EventTypesPage = () => {
|
||||
<Shell
|
||||
withoutSeo
|
||||
heading={t("event_types_page_title")}
|
||||
hideHeadingOnMobile
|
||||
subtitle={t("event_types_page_subtitle")}
|
||||
CTA={<CTA />}>
|
||||
<WithQuery
|
||||
|
||||
@@ -55,7 +55,7 @@ export default function InsightsPage() {
|
||||
|
||||
return (
|
||||
<div>
|
||||
<Shell>
|
||||
<Shell hideHeadingOnMobile>
|
||||
<UpgradeTip
|
||||
title={t("make_informed_decisions")}
|
||||
description={t("make_informed_decisions_description")}
|
||||
|
||||
@@ -6,7 +6,7 @@ import PageWrapper from "@components/PageWrapper";
|
||||
export default function MorePage() {
|
||||
const { t } = useLocale();
|
||||
return (
|
||||
<Shell>
|
||||
<Shell hideHeadingOnMobile>
|
||||
<div className="max-w-screen-lg">
|
||||
<MobileNavigationMoreItems />
|
||||
<p className="text-subtle mt-6 text-xs leading-tight md:hidden">{t("more_page_footer")}</p>
|
||||
|
||||
@@ -39,8 +39,8 @@ const BillingView = () => {
|
||||
const returnTo = router.asPath;
|
||||
const billingHref = `/api/integrations/stripepayment/portal?returnTo=${WEBAPP_URL}${returnTo}`;
|
||||
|
||||
const onContactSupportClick = () => {
|
||||
open();
|
||||
const onContactSupportClick = async () => {
|
||||
await open();
|
||||
};
|
||||
|
||||
return (
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
import { useState } from "react";
|
||||
|
||||
import type { TApiKeys } from "@calcom/ee/api-keys/components/ApiKeyListItem";
|
||||
import LicenseRequired from "@calcom/ee/common/components/v2/LicenseRequired";
|
||||
import LicenseRequired from "@calcom/ee/common/components/LicenseRequired";
|
||||
import ApiKeyDialogForm from "@calcom/features/ee/api-keys/components/ApiKeyDialogForm";
|
||||
import ApiKeyListItem from "@calcom/features/ee/api-keys/components/ApiKeyListItem";
|
||||
import { getLayout } from "@calcom/features/settings/layouts/SettingsLayout";
|
||||
|
||||
@@ -52,6 +52,7 @@ const AppearanceView = () => {
|
||||
const utils = trpc.useContext();
|
||||
const { data: user, isLoading } = trpc.viewer.me.useQuery();
|
||||
const [darkModeError, setDarkModeError] = useState(false);
|
||||
const [lightModeError, setLightModeError] = useState(false);
|
||||
|
||||
const { isLoading: isTeamPlanStatusLoading, hasPaidPlan } = useHasPaidPlan();
|
||||
|
||||
@@ -145,7 +146,14 @@ const AppearanceView = () => {
|
||||
<p className="text-default mb-2 block text-sm font-medium">{t("light_brand_color")}</p>
|
||||
<ColorPicker
|
||||
defaultValue={user.brandColor}
|
||||
onChange={(value) => formMethods.setValue("brandColor", value, { shouldDirty: true })}
|
||||
onChange={(value) => {
|
||||
if (!checkWCAGContrastColor("#ffffff", value)) {
|
||||
setLightModeError(true);
|
||||
} else {
|
||||
setLightModeError(false);
|
||||
}
|
||||
formMethods.setValue("brandColor", value, { shouldDirty: true });
|
||||
}}
|
||||
/>
|
||||
</div>
|
||||
)}
|
||||
@@ -180,6 +188,14 @@ const AppearanceView = () => {
|
||||
/>
|
||||
</div>
|
||||
) : null}
|
||||
{lightModeError ? (
|
||||
<div className="mt-4">
|
||||
<Alert
|
||||
severity="warning"
|
||||
message="Light Theme color doesn't pass contrast check. We recommend you change this colour so your buttons will be more visible."
|
||||
/>
|
||||
</div>
|
||||
) : null}
|
||||
{/* TODO future PR to preview brandColors */}
|
||||
{/* <Button
|
||||
color="secondary"
|
||||
|
||||
@@ -320,6 +320,7 @@ const ProfileForm = ({
|
||||
extraField?: React.ReactNode;
|
||||
}) => {
|
||||
const { t } = useLocale();
|
||||
const [firstRender, setFirstRender] = useState(true);
|
||||
|
||||
const profileFormSchema = z.object({
|
||||
username: z.string(),
|
||||
@@ -385,6 +386,8 @@ const ProfileForm = ({
|
||||
}}
|
||||
excludedToolbarItems={["blockType"]}
|
||||
disableLists
|
||||
firstRender={firstRender}
|
||||
setFirstRender={setFirstRender}
|
||||
/>
|
||||
</div>
|
||||
<Button disabled={isDisabled} color="primary" className="mt-8" type="submit">
|
||||
|
||||
@@ -5,7 +5,7 @@ import type { CSSProperties } from "react";
|
||||
import type { SubmitHandler } from "react-hook-form";
|
||||
import { FormProvider, useForm } from "react-hook-form";
|
||||
|
||||
import LicenseRequired from "@calcom/features/ee/common/components/v2/LicenseRequired";
|
||||
import LicenseRequired from "@calcom/features/ee/common/components/LicenseRequired";
|
||||
import { isSAMLLoginEnabled } from "@calcom/features/ee/sso/lib/saml";
|
||||
import { WEBAPP_URL } from "@calcom/lib/constants";
|
||||
import { useLocale } from "@calcom/lib/hooks/useLocale";
|
||||
|
||||
@@ -14,6 +14,7 @@ function Teams() {
|
||||
return (
|
||||
<Shell
|
||||
heading={t("teams")}
|
||||
hideHeadingOnMobile
|
||||
subtitle={t("create_manage_teams_collaborative")}
|
||||
CTA={
|
||||
<Button
|
||||
|
||||
@@ -282,7 +282,6 @@ export async function getServerSideProps(context: GetServerSidePropsContext) {
|
||||
user: {
|
||||
select: {
|
||||
id: true,
|
||||
credentials: true,
|
||||
timeZone: true,
|
||||
name: true,
|
||||
email: true,
|
||||
|
||||
@@ -860,7 +860,7 @@
|
||||
"add_new_calendar": "Přidat nový kalendář",
|
||||
"set_calendar": "Nastavte, kam se mají při rezervaci přidávat nové události.",
|
||||
"delete_schedule": "Odstranit plán",
|
||||
"delete_schedule_description": "Odstranění plánu má za následek, že bude odstraněn ze všech typů událostí. Tuto akci nelze vrátit zpět.",
|
||||
"delete_schedule_description": "Odstraněný plán bude odstraněn ze všech typů událostí. Tuto akci nelze vrátit zpět.",
|
||||
"schedule_created_successfully": "Plán {{scheduleName}} byl úspěšně vytvořen",
|
||||
"availability_updated_successfully": "Plán {{scheduleName}} byl úspěšně aktualizován",
|
||||
"schedule_deleted_successfully": "Plán byl úspěšně odstraněn",
|
||||
@@ -1154,7 +1154,7 @@
|
||||
"event_setup_multiple_duration_error": "Nastavení události: více dob trvání vyžaduje alespoň 1 možnost.",
|
||||
"event_setup_multiple_duration_default_error": "Nastavení události: vyberte prosím platnou výchozí dobu trvání.",
|
||||
"event_setup_booking_limits_error": "Limity rezervací musí být seřazeny vzestupně. [den,týden,měsíc,rok]",
|
||||
"event_setup_duration_limits_error": "Limity doby trvání musí být seřazeny vzestupně. [den,týden,měsíc,rok]",
|
||||
"event_setup_duration_limits_error": "Limity doby trvání musí být seřazeny vzestupně. [den, týden, měsíc, rok]",
|
||||
"select_which_cal": "Vyberte kalendář, do kterého se mají přidávat rezervace",
|
||||
"custom_event_name": "Vlastní název události",
|
||||
"custom_event_name_description": "Vytvářejte vlastní názvy událostí, které se budou zobrazovat v kalendáři událostí",
|
||||
@@ -1614,7 +1614,7 @@
|
||||
"form_builder_field_add_subtitle": "Přizpůsobte otázky položené na stránce rezervace",
|
||||
"get_started_zapier_templates": "Začněte používat šablony Zapier",
|
||||
"team_is_unpublished": "Tým {{team}} není zveřejněn",
|
||||
"team_is_unpublished_description": "Tento týmový odkaz není v současné době k dispozici. Kontaktujte prosím vlastníka týmu nebo ho požádejte o jeho zveřejnění.",
|
||||
"team_is_unpublished_description": "Tento týmový odkaz není v současné době k dispozici. Kontaktujte vlastníka týmu nebo ho požádejte o jeho zveřejnění.",
|
||||
"team_member": "Člen týmu",
|
||||
"a_routing_form": "Směrovací formulář",
|
||||
"form_description_placeholder": "Popis formuláře",
|
||||
|
||||
@@ -1525,7 +1525,7 @@
|
||||
"sender_name": "Absendername",
|
||||
"already_invited": "Teilnehmer bereits eingeladen",
|
||||
"no_recordings_found": "Keine Aufnahmen gefunden",
|
||||
"new_workflow_subtitle": "Neuer Workflow für...",
|
||||
"new_workflow_subtitle": "Neuer Workflow für ...",
|
||||
"reporting": "Meldungen",
|
||||
"reporting_feature": "Alle eingehenden Daten anzeigen und als CSV herunterladen",
|
||||
"teams_plan_required": "Teams-Plan benötigt",
|
||||
@@ -1586,15 +1586,15 @@
|
||||
"recommended_next_steps": "Empfohlene nächste Schritte",
|
||||
"create_a_managed_event": "Einen verwalteten Ereignistyp erstellen",
|
||||
"meetings_are_better_with_the_right": "Meetings sind mit den richtigen Teammitgliedern besser. Laden Sie sie jetzt ein.",
|
||||
"create_a_one_one_template": "Erstellen Sie eine One-on-One Vorlage für einen Termintyp und verteilen Sie diese an mehrere Mitglieder.",
|
||||
"create_a_one_one_template": "Erstellen Sie eine One-on-One-Vorlage für einen Termintyp und verteilen Sie diese an mehrere Mitglieder.",
|
||||
"collective_or_roundrobin": "Kollektiv oder Round-Robin",
|
||||
"book_your_team_members": "Buchen Sie Ihre Teammitglieder zusammen mit Kollektivveranstaltungen oder wählen Sie mit Round-Robin die richtige Person.",
|
||||
"event_no_longer_attending_subject": "Nimmt nicht mehr an {{title}} am {{date}} teil",
|
||||
"no_longer_attending": "Sie nehmen an diesem Termin nicht mehr teil",
|
||||
"attendee_no_longer_attending_subject": "Ein Teilnehmer nimmt nicht mehr an {{title}} am {{date}} teil",
|
||||
"attendee_no_longer_attending": "Ein Teilnehmer nimmt nicht mehr an Ihrem Termin teil",
|
||||
"attendee_no_longer_attending_subtitle": "{{name}} wurde abgebrochen. Das bedeutet, dass ein Platz für dieses Zeitfenster geöffnet wurde",
|
||||
"create_event_on": "Erstelle Termin in",
|
||||
"attendee_no_longer_attending_subtitle": "{{name}} hat abgesagt. Das bedeutet, dass ein Platz für dieses Zeitfenster geöffnet wurde",
|
||||
"create_event_on": "Erstelle Termin am",
|
||||
"default_app_link_title": "Standard-App-Link festlegen",
|
||||
"default_app_link_description": "Mit dem Festlegen eines Standard-App-Links können alle neu erstellten Ereignistypen den App-Link verwenden, den Sie gesetzt haben.",
|
||||
"change_default_conferencing_app": "Als Standard festlegen",
|
||||
@@ -1621,9 +1621,9 @@
|
||||
"keep_me_connected_with_form": "Verbindung mit dem Formular beibehalten",
|
||||
"fields_in_form_duplicated": "Alle Änderungen an Weiterleitungsformularen und in den Feldern des Formulars, die dupliziert werden, werden auch im Duplikat wiedergegeben.",
|
||||
"form_deleted": "Formular gelöscht",
|
||||
"delete_form": "Formular löschen",
|
||||
"delete_form": "Möchten Sie dieses Formular wirklich löschen?",
|
||||
"delete_form_action": "Ja, Formular löschen",
|
||||
"delete_form_confirmation": "Sind Sie sicher, dass Sie dieses Formular löschen möchten? Jede Person, mit der Sie den Link geteilt haben, kann nicht mehr über das Formular buchen. Außerdem würden alle zugehörigen Antworten gelöscht werden.",
|
||||
"delete_form_confirmation": "Jede Person, mit der Sie den Link geteilt haben, kann nicht mehr über das Formular buchen.",
|
||||
"delete_form_confirmation_2": "Alle zugehörigen Antworten werden gelöscht.",
|
||||
"typeform_redirect_url_copied": "Typeform Umleitungs-URL kopiert! Sie können die URL in Typeform einstellen.",
|
||||
"modifications_in_fields_warning": "Änderungen in den Feldern und Weiterleitungen der folgenden Formulare werden in diesem Formular reflektiert.",
|
||||
@@ -1644,7 +1644,7 @@
|
||||
"requires_app": "Benötigt {{dependencyName}}",
|
||||
"verification_code": "Bestätigungscode",
|
||||
"can_you_try_again": "Können Sie es zu einem anderen Zeitpunkt erneut versuchen?",
|
||||
"verify": "Verifizieren",
|
||||
"verify": "Bestätigen",
|
||||
"timezone_variable": "Zeitzone",
|
||||
"invalid_event_name_variables": "Es gibt eine ungültige Variable in Ihrem Terminnamen",
|
||||
"select_all": "Alle auswählen",
|
||||
|
||||
@@ -1784,5 +1784,10 @@
|
||||
"seats_and_no_show_fee_error": "Currently cannot enable seats and charge a no-show fee",
|
||||
"complete_your_booking": "Complete your booking",
|
||||
"complete_your_booking_subject": "Complete your booking: {{title}} on {{date}}",
|
||||
"email_invite_team": "{{email}} has been invited"
|
||||
"currency_string": "{{amount, currency}}",
|
||||
"charge_card_dialog_body": "You are about to charge the attendee {{amount, currency}}. Are you sure you want to continue?",
|
||||
"charge_attendee": "Charge attendee {{amount, currency}}",
|
||||
"payment_app_commission": "Require payment ({{paymentFeePercentage}}% + {{fee, currency}} commission per transaction)",
|
||||
"email_invite_team": "{{email}} has been invited",
|
||||
"image_size_limit_exceed": "Uploaded image shouldn't exceed 5mb size limit"
|
||||
}
|
||||
|
||||
@@ -1586,15 +1586,15 @@
|
||||
"recommended_next_steps": "Siguientes pasos recomendados",
|
||||
"create_a_managed_event": "Crear un tipo de evento administrado",
|
||||
"meetings_are_better_with_the_right": "Las reuniones son mejores si están los miembros adecuados del equipo. Invítelos ahora.",
|
||||
"create_a_one_one_template": "Cree una plantilla uno a uno para un tipo de evento y distribúyala a varios miembros.",
|
||||
"create_a_one_one_template": "Cree una plantilla de reunión individual para un tipo de evento y distribúyala a varios miembros.",
|
||||
"collective_or_roundrobin": "Colectivo o turnos rotativos",
|
||||
"book_your_team_members": "Reserve a los miembros de su equipo juntos con eventos colectivos o por ciclos para conseguir a la persona adecuada con turnos rotativos.",
|
||||
"event_no_longer_attending_subject": "Ya no asistirá a {{title}} el {{date}}",
|
||||
"event_no_longer_attending_subject": "Ya no asistirá a {{title}} a las {{date}}",
|
||||
"no_longer_attending": "Ya no asistirá a este evento",
|
||||
"attendee_no_longer_attending_subject": "Un asistente ya no asistirá a {{title}} el {{date}}",
|
||||
"attendee_no_longer_attending_subject": "Un asistente ya no asistirá a {{title}} a las {{date}}",
|
||||
"attendee_no_longer_attending": "Un asistente ya no asistirá a su evento",
|
||||
"attendee_no_longer_attending_subtitle": "{{name}} ha cancelado. Esto significa que está disponible un cupo para esta franja horaria",
|
||||
"create_event_on": "Crear evento el",
|
||||
"create_event_on": "Crear evento en",
|
||||
"default_app_link_title": "Establecer un enlace de aplicación predeterminado",
|
||||
"default_app_link_description": "Establecer un enlace de aplicación predeterminado permite que todos los tipos de eventos recién creados utilicen el enlace de aplicación que establezca.",
|
||||
"change_default_conferencing_app": "Establecer como predeterminado",
|
||||
@@ -1640,7 +1640,7 @@
|
||||
"this_meeting_has_not_started_yet": "Esta reunión aún no ha comenzado",
|
||||
"this_app_requires_connected_account": "{{appName}} requiere una cuenta {{dependencyName}} conectada",
|
||||
"connect_app": "Conectar {{dependencyName}}",
|
||||
"app_is_connected": "{{dependencyName}} está conectado",
|
||||
"app_is_connected": "{{dependencyName}} está conectada",
|
||||
"requires_app": "Requiere {{dependencyName}}",
|
||||
"verification_code": "Código de verificación",
|
||||
"can_you_try_again": "¿Puede intentarlo de nuevo con una hora diferente?",
|
||||
|
||||
@@ -1150,11 +1150,13 @@
|
||||
"current_username": "Nom d'utilisateur actuel",
|
||||
"example_1": "Exemple 1",
|
||||
"example_2": "Exemple 2",
|
||||
"booking_question_identifier": "Identifiant de la question de réservation",
|
||||
"company_size": "Taille de l'entreprise",
|
||||
"what_help_needed": "Avez-vous besoin d'aide ?",
|
||||
"variable_format": "Format des variables",
|
||||
"webhook_subscriber_url_reserved": "Le lien de l'abonné webhook est déjà défini",
|
||||
"custom_input_as_variable_info": "Ignorer tous les caractères spéciaux du libellé de champ supplémentaire (utiliser uniquement des lettres et des chiffres), utiliser des majuscules pour toutes les lettres et remplacer les espaces par des tirets bas.",
|
||||
"using_booking_questions_as_variables": "Comment puis-je utiliser les questions de réservation comme variables ?",
|
||||
"download_desktop_app": "Télécharger l'application de bureau",
|
||||
"set_ping_link": "Définir le lien Ping",
|
||||
"rate_limit_exceeded": "Limite dépassée",
|
||||
@@ -1380,6 +1382,7 @@
|
||||
"add_limit": "Ajouter une limite",
|
||||
"team_name_required": "Nom d'équipe requis",
|
||||
"show_attendees": "Partagez les informations des participants entre les invités",
|
||||
"how_booking_questions_as_variables": "Comment utiliser les questions de réservation comme variables ?",
|
||||
"format": "Format",
|
||||
"uppercase_for_letters": "Utilisez des majuscules pour toutes les lettres.",
|
||||
"replace_whitespaces_underscores": "Remplacez les espaces par des tirets bas.",
|
||||
@@ -1394,6 +1397,7 @@
|
||||
"billing_help_title": "Besoin d'autre chose ?",
|
||||
"billing_help_description": "Si vous avez besoin d'aide pour la facturation, notre équipe d'assistance est là pour vous aider.",
|
||||
"billing_help_cta": "Contacter l'assistance",
|
||||
"ignore_special_characters_booking_questions": "Ignorez les caractères spéciaux dans l'identifiant de votre question de réservation, utilisez uniquement des lettres et des chiffres.",
|
||||
"retry": "Réessayer",
|
||||
"fetching_calendars_error": "Un problème est survenu lors de la récupération de vos calendriers. Veuillez <1>réessayer</1> ou contacter le service client.",
|
||||
"calendar_connection_fail": "Échec de la connexion au calendrier",
|
||||
@@ -1702,6 +1706,13 @@
|
||||
"can_you_try_again": "Pouvez-vous réessayer avec un autre créneau ?",
|
||||
"verify": "Vérifier",
|
||||
"timezone_variable": "Fuseau horaire",
|
||||
"timezone_info": "Le fuseau horaire du destinataire",
|
||||
"event_end_time_variable": "Heure de fin de l'événement",
|
||||
"event_end_time_info": "L'heure de fin de l'événement",
|
||||
"cancel_url_variable": "Lien d'annulation",
|
||||
"cancel_url_info": "Le lien d'annulation de la réservation",
|
||||
"reschedule_url_variable": "Lien de replanification",
|
||||
"reschedule_url_info": "Le lien de replanification de la réservation",
|
||||
"invalid_event_name_variables": "Il y a une variable invalide dans le nom de votre événement",
|
||||
"select_all": "Tout sélectionner",
|
||||
"default_conferencing_bulk_title": "Mise à jour multiple des types d'événements existants",
|
||||
@@ -1766,11 +1777,17 @@
|
||||
"collect_no_show_fee": "Percevoir les frais d'absence",
|
||||
"no_show_fee_charged": "Frais d'absence facturés",
|
||||
"insights": "Statistiques",
|
||||
"testing_workflow_info_message": "Lors du test de ce workflow, sachez que les e-mails et les SMS ne peuvent être programmés qu'au moins 1 heure à l'avance",
|
||||
"insights_no_data_found_for_filter": "Aucune donnée trouvée pour le filtre ou les dates sélectionnés.",
|
||||
"acknowledge_booking_no_show_fee": "Je reconnais que si je ne participe pas à cet événement, des frais d'absence de {{amount, currency}} seront appliqués à ma carte.",
|
||||
"card_details": "Détails de la carte",
|
||||
"seats_and_no_show_fee_error": "Il n'est pas possible d'activer les places et de facturer des frais d'absence pour le moment",
|
||||
"complete_your_booking": "Terminer votre réservation",
|
||||
"complete_your_booking_subject": "Terminer votre réservation : {{title}} le {{date}}",
|
||||
"email_invite_team": "{{email}} a été invité"
|
||||
"currency_string": "{{amount, currency}}",
|
||||
"charge_card_dialog_body": "Vous êtes sur le point de facturer {{amount, currency}} au participant. Voulez-vous vraiment continuer ?",
|
||||
"charge_attendee": "Facturer {{amount, currency}} au participant",
|
||||
"payment_app_commission": "Exiger un paiement ({{paymentFeePercentage}} % + {{fee, currency}} de commission par transaction)",
|
||||
"email_invite_team": "{{email}} a été invité",
|
||||
"image_size_limit_exceed": "L'image téléchargée ne doit pas dépasser 5 Mo"
|
||||
}
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
{
|
||||
"identity_provider": "Provider di identità",
|
||||
"trial_days_left": "Ti rimangono $t(day, {\"count\": {{days}} }) di prova con il piano PRO",
|
||||
"day_one": "{{count}} giorno",
|
||||
"day_other": "{{count}} giorni",
|
||||
@@ -69,6 +70,8 @@
|
||||
"event_awaiting_approval_subject": "In attesa di approvazione: {{title}} il {{date}}",
|
||||
"event_still_awaiting_approval": "Un evento è ancora in attesa della tua approvazione",
|
||||
"booking_submitted_subject": "Prenotazione inviata: {{title}} il {{date}}",
|
||||
"download_recording_subject": "Scarica registrazione: {{title}} alle {{date}}",
|
||||
"download_your_recording": "Scarica la tua registrazione",
|
||||
"your_meeting_has_been_booked": "Il tuo meeting è stata prenotato",
|
||||
"event_type_has_been_rescheduled_on_time_date": "Il tuo {{title}} è stato riprogrammato per il {{date}}.",
|
||||
"event_has_been_rescheduled": "Aggiornato: il tuo evento è stato riprogrammato",
|
||||
@@ -224,6 +227,7 @@
|
||||
"done": "Fatto",
|
||||
"all_done": "Operazione completata!",
|
||||
"all_apps": "Tutte le app",
|
||||
"available_apps": "Applicazioni disponibili",
|
||||
"check_email_reset_password": "Controlla la tua email. Ti abbiamo inviato un link per reimpostare la password.",
|
||||
"finish": "Fine",
|
||||
"few_sentences_about_yourself": "Alcune frasi su di te. Questo apparirà sulla tua pagina url personale.",
|
||||
@@ -404,6 +408,7 @@
|
||||
"add_time_availability": "Aggiungi nuova fascia oraria",
|
||||
"add_an_extra_layer_of_security": "Aggiungi un ulteriore livello di sicurezza al tuo account nel caso in cui la password venga rubata.",
|
||||
"2fa": "Autenticazione A Due Fattori",
|
||||
"2fa_disabled": "L'autenticazione a due fattori può essere abilitata solo per l'autenticazione tramite e-mail e password",
|
||||
"enable_2fa": "Abilita autenticazione a due fattori",
|
||||
"disable_2fa": "Disabilita autenticazione a due fattori",
|
||||
"disable_2fa_recommendation": "Se è necessario disattivare 2FA, si consiglia di riattivarlo il prima possibile.",
|
||||
@@ -585,6 +590,20 @@
|
||||
"minutes": "Minuti",
|
||||
"round_robin": "Round Robin",
|
||||
"round_robin_description": "Ciclo di riunioni tra più membri del team.",
|
||||
"managed_event": "Evento gestito",
|
||||
"username_placeholder": "username",
|
||||
"managed_event_description": "Crea e distribuisci tipi di evento in blocco ai membri del team",
|
||||
"managed": "Gestito",
|
||||
"managed_event_url_clarification": "il campo \"username\" può essere compilato solo con il nome utente dei membri assegnati",
|
||||
"assign_to": "Assegna a",
|
||||
"add_members": "Aggiungi membri...",
|
||||
"count_members_one": "{{count}} membro",
|
||||
"count_members_other": "{{count}} membri",
|
||||
"no_assigned_members": "Nessun membro assegnato",
|
||||
"assigned_to": "Assegnato a",
|
||||
"start_assigning_members_above": "Inizia ad assegnare membri in alto",
|
||||
"locked_fields_admin_description": "I membri non saranno in grado di apportare modifiche qui",
|
||||
"locked_fields_member_description": "Questa opzione è stata bloccata dall'amministratore del team",
|
||||
"url": "URL",
|
||||
"hidden": "Nascosto",
|
||||
"readonly": "Sola lettura",
|
||||
@@ -736,6 +755,11 @@
|
||||
"minimum_booking_notice": "Preavviso minimo",
|
||||
"slot_interval": "Intervalli di fascia oraria",
|
||||
"slot_interval_default": "Usa durata evento (impostazione predefinita)",
|
||||
"delete_event_type": "Eliminare il tipo di evento?",
|
||||
"delete_managed_event_type": "Eliminare il tipo di evento gestito?",
|
||||
"delete_event_type_description": "Gli utenti con cui hai condiviso il link non saranno più in grado di usarlo per prenotare.",
|
||||
"delete_managed_event_type_description": "<ul><li>Questo tipo di evento sarà eliminato anche ai membri a esso assegnati.</li><li>Gli utenti con cui hai condiviso il link non saranno più in grado di usarlo per prenotare.</li></ul>",
|
||||
"confirm_delete_event_type": "Sì, elimina",
|
||||
"delete_account": "Elimina account",
|
||||
"confirm_delete_account": "Sì, elimina account",
|
||||
"delete_account_confirmation_message": "Sei sicuro di voler eliminare il tuo account {{appName}}? Chiunque abbia il link del tuo account non potrà più utilizzarlo per prenotare e le preferenze che hai salvato andranno perse.",
|
||||
@@ -1126,11 +1150,13 @@
|
||||
"current_username": "Nome utente corrente",
|
||||
"example_1": "Esempio 1",
|
||||
"example_2": "Esempio 2",
|
||||
"booking_question_identifier": "Identificatore domanda alla prenotazione",
|
||||
"company_size": "Dimensione dell'azienda",
|
||||
"what_help_needed": "Per cosa ti serve assistenza?",
|
||||
"variable_format": "Formato variabile",
|
||||
"webhook_subscriber_url_reserved": "URL dell'abbonato Webhook già definito",
|
||||
"custom_input_as_variable_info": "Ignora tutti i caratteri speciali dell'etichetta del campo di input aggiuntivo (usa solo lettere e numeri), usa solo lettere maiuscole e sostituisci gli spazi con il trattino basso.",
|
||||
"using_booking_questions_as_variables": "Come posso utilizzare le domande poste nella pagina di prenotazione come variabili?",
|
||||
"download_desktop_app": "Scarica app desktop",
|
||||
"set_ping_link": "Imposta link di Ping",
|
||||
"rate_limit_exceeded": "Limite superato",
|
||||
@@ -1176,6 +1202,7 @@
|
||||
"start_of_week": "Inizio della settimana",
|
||||
"recordings_title": "Registrazioni",
|
||||
"recording": "Registrazione",
|
||||
"happy_scheduling": "Ti auguriamo una piacevole esperienza di pianificazione",
|
||||
"select_calendars": "Seleziona su quali calendari desideri controllare i conflitti per evitare doppie prenotazioni.",
|
||||
"check_for_conflicts": "Controlla conflitti",
|
||||
"view_recordings": "Visualizza registrazioni",
|
||||
@@ -1213,6 +1240,7 @@
|
||||
"impersonation": "Accesso con altra identità",
|
||||
"impersonation_description": "Impostazioni per gestire gli accessi con altra identità",
|
||||
"users": "Utenti",
|
||||
"user": "Utente",
|
||||
"profile_description": "Gestisci le impostazioni del tuo profilo {{appName}}",
|
||||
"users_description": "Qui è possibile trovare l'elenco di tutti gli utenti",
|
||||
"users_listing": "Elenco degli utenti",
|
||||
@@ -1253,6 +1281,8 @@
|
||||
"download_responses": "Scarica risposte",
|
||||
"download_responses_description": "Scarica tutte le risposte al tuo modulo in formato CSV.",
|
||||
"download": "Scarica",
|
||||
"download_recording": "Scarica registrazione",
|
||||
"recording_from_your_recent_call": "Una registrazione della tua recente chiamata su Cal.com è pronta per il download",
|
||||
"create_your_first_form": "Crea il tuo primo modulo",
|
||||
"create_your_first_form_description": "I Moduli di instradamento consentono di porre domande qualificanti agli utenti per indirizzarli a persone o eventi pertinenti.",
|
||||
"create_your_first_webhook": "Crea il tuo primo Webhook",
|
||||
@@ -1289,6 +1319,15 @@
|
||||
"exchange_authentication_standard": "Autenticazione di base",
|
||||
"exchange_authentication_ntlm": "Autenticazione NTLM",
|
||||
"exchange_compression": "Compressione GZip",
|
||||
"exchange_version": "Versione di Exchange",
|
||||
"exchange_version_2007_SP1": "2007 SP1",
|
||||
"exchange_version_2010": "2010",
|
||||
"exchange_version_2010_SP1": "2010 SP1",
|
||||
"exchange_version_2010_SP2": "2010 SP2",
|
||||
"exchange_version_2013": "2013",
|
||||
"exchange_version_2013_SP1": "2013 SP1",
|
||||
"exchange_version_2015": "2015",
|
||||
"exchange_version_2016": "2016",
|
||||
"routing_forms_description": "Qui puoi vedere tutti i moduli e tutti i percorsi che hai creato.",
|
||||
"routing_forms_send_email_owner": "Invia e-mail al proprietario",
|
||||
"routing_forms_send_email_owner_description": "Invia un'e-mail al proprietario quando il modulo viene inviato",
|
||||
@@ -1343,6 +1382,7 @@
|
||||
"add_limit": "Aggiungi limite",
|
||||
"team_name_required": "Nome del team richiesto",
|
||||
"show_attendees": "Condividi le informazioni dei partecipanti tra gli ospiti",
|
||||
"how_booking_questions_as_variables": "Come utilizzare le domande poste nella pagina di prenotazione come variabili?",
|
||||
"format": "Formato",
|
||||
"uppercase_for_letters": "Usa tutte lettere maiuscole",
|
||||
"replace_whitespaces_underscores": "Sostituisci spazi con sottolineature",
|
||||
@@ -1357,6 +1397,7 @@
|
||||
"billing_help_title": "Hai bisogno di altro?",
|
||||
"billing_help_description": "Se hai bisogno di ulteriore aiuto per la fatturazione, il nostro team di supporto è qui per aiutarti.",
|
||||
"billing_help_cta": "Contatta l'assistenza",
|
||||
"ignore_special_characters_booking_questions": "Ignora i caratteri speciali nell'identificatore della domanda da porre nella pagina di prenotazione. Usa solo lettere e numeri",
|
||||
"retry": "Riprova",
|
||||
"fetching_calendars_error": "Si è verificato un problema durante il recupero dei calendari. <1>Riprova</1> o contatta l'assistenza clienti.",
|
||||
"calendar_connection_fail": "Impossibile connettersi al calendario",
|
||||
@@ -1439,11 +1480,17 @@
|
||||
"fixed_round_robin": "Round robin fisso",
|
||||
"add_one_fixed_attendee": "Aggiungi un partecipante fisso e fai intervenire a turno altri partecipanti.",
|
||||
"calcom_is_better_with_team": "Cal.com funziona meglio in team",
|
||||
"the_calcom_team": "Il team Cal.com",
|
||||
"add_your_team_members": "Aggiungi i membri del tuo team ai tuoi tipi di eventi. Usa la pianificazione collettiva per includere tutti o trova la persona più adatta con la pianificazione round robin.",
|
||||
"booking_limit_reached": "È stato raggiunto il limite di prenotazione per questo tipo di evento",
|
||||
"duration_limit_reached": "È stato raggiunto il limite di durata per questo tipo di evento",
|
||||
"admin_has_disabled": "Un amministratore ha disabilitato {{appName}}",
|
||||
"disabled_app_affects_event_type": "Un amministratore ha disabilitato {{appName}}, il che ha effetto sul tuo tipo di evento {{eventType}}",
|
||||
"event_replaced_notice": "Un amministratore ha sostituito uno dei tuoi tipi di evento",
|
||||
"email_subject_slug_replacement": "Un amministratore di team ha sostituito il tuo evento /{{slug}}",
|
||||
"email_body_slug_replacement_notice": "Un amministratore del team <strong>{{teamName}}</strong> ha sostituito il tuo tipo di evento <strong>/{{slug}}</strong> con un tipo di evento gestito da lui controllato.",
|
||||
"email_body_slug_replacement_info": "Il tuo link continuerà a funzionare, ma alcune sue impostazioni potrebbero essere state cambiate. Puoi verificarlo nella sezione Tipi di evento.",
|
||||
"email_body_slug_replacement_suggestion": "Per qualsiasi domanda sui tipi di evento, contatta il nostro amministratore.<br /><br />Ti auguriamo una piacevole esperienza di pianificazione, <br />Il team Cal.com",
|
||||
"disable_payment_app": "L'amministratore ha disabilitato {{appName}}, il che ha effetto sul tuo tipo di evento {{title}}. I partecipanti sono ancora in grado di prenotare questo tipo di evento ma non gli verrà richiesto di pagare. Per evitarlo, è possibile nascondere questo tipo di evento fino a che l'amministratore non riattivi il tuo metodo di pagamento.",
|
||||
"payment_disabled_still_able_to_book": "I partecipanti sono ancora in grado di prenotare questo tipo di evento ma non gli verrà richiesto di pagare. Per evitarlo, è possibile nascondere questo tipo di evento fino a che l'amministratore non riattivi il tuo metodo di pagamento.",
|
||||
"app_disabled_with_event_type": "L'amministratore ha disabilitato {{appName}}, il che ha effetto sul tuo tipo di evento {{title}}.",
|
||||
@@ -1492,6 +1539,7 @@
|
||||
"date_overrides_update_btn": "Aggiorna configurazione data specifica",
|
||||
"event_type_duplicate_copy_text": "{{slug}}-copia",
|
||||
"set_as_default": "Imposta come predefinito",
|
||||
"hide_eventtype_details": "Nascondi dettagli del tipo di evento",
|
||||
"show_navigation": "Mostra navigazione",
|
||||
"hide_navigation": "Nascondi navigazione",
|
||||
"verification_code_sent": "Codice di verifica inviato",
|
||||
@@ -1505,6 +1553,7 @@
|
||||
"create_your_first_team_webhook_description": "Crea il primo webhook per questo tipo di evento del team",
|
||||
"create_webhook_team_event_type": "Crea un webhook per questo tipo di evento del team",
|
||||
"disable_success_page": "Disabilita pagina di conferma dell'esito (funziona solo se si dispone di un URL di reindirizzamento)",
|
||||
"invalid_admin_password": "Sei un amministratore, ma la tua password non ha la lunghezza minima di 15 caratteri, né utilizzi l'autenticazione a due fattori",
|
||||
"change_password_admin": "Cambia password per ottenere i diritti di accesso come amministratore",
|
||||
"username_already_taken": "Nome utente già in uso",
|
||||
"assignment": "Assegnazione",
|
||||
@@ -1537,6 +1586,7 @@
|
||||
"ee_enterprise_license": "Licenza Enterprise “/ee”",
|
||||
"enterprise_booking_fee": "A partire da {{enterprise_booking_fee}}/mese",
|
||||
"enterprise_license_includes": "Tutto il necessario per l'utilizzo commerciale",
|
||||
"no_need_to_keep_your_code_open_source": "Non è necessario che il codice rimanga open source",
|
||||
"repackage_rebrand_resell": "Rivendi facilmente con un nuovo marchio",
|
||||
"a_vast_suite_of_enterprise_features": "Un'estesa suite di funzionalità enterprise",
|
||||
"free_license_fee": "0,00 $/mese",
|
||||
@@ -1598,6 +1648,7 @@
|
||||
"default_app_link_title": "Imposta il link dell'app predefinita",
|
||||
"default_app_link_description": "L'impostazione di un link dell'app predefinita permette a tutti i nuovi tipi di eventi di usare il link dell'app impostato.",
|
||||
"change_default_conferencing_app": "Imposta come predefinito",
|
||||
"organizer_default_conferencing_app": "Applicazione predefinita dell'organizzatore",
|
||||
"under_maintenance": "Inattivo per manutenzione",
|
||||
"under_maintenance_description": "Il team di {{appName}} sta eseguendo la manutenzione programmata. Per qualsiasi domanda, contattare il supporto.",
|
||||
"event_type_seats": "{{numberOfSeats}} posti",
|
||||
@@ -1612,6 +1663,7 @@
|
||||
"not_enough_seats": "Posti insufficienti",
|
||||
"form_builder_field_already_exists": "Esiste già un campo con questo nome",
|
||||
"form_builder_field_add_subtitle": "Personalizza le domande poste nella pagina di prenotazione",
|
||||
"show_on_booking_page": "Mostra nella pagina di prenotazione",
|
||||
"get_started_zapier_templates": "Inizia con i modelli Zapier",
|
||||
"team_is_unpublished": "{{team}} non è pubblicato",
|
||||
"team_is_unpublished_description": "Questo link del team non è attualmente disponibile. Contattare il proprietario del team o chiedergli di pubblicarlo.",
|
||||
@@ -1635,6 +1687,14 @@
|
||||
"add_1_option_per_line": "Aggiungi 1 opzione per riga",
|
||||
"select_a_router": "Seleziona un instradatore",
|
||||
"add_a_new_route": "Aggiungi un nuovo percorso",
|
||||
"make_informed_decisions": "Prendi decisioni informate con Approfondimenti",
|
||||
"make_informed_decisions_description": "Il pannello di controllo Approfondimenti offre una panoramica di tutte le attività del team e mostra le tendenze, il che consente di migliorare la pianificazione di gruppo e il processo decisionale.",
|
||||
"view_bookings_across": "Visualizza le prenotazioni di tutti i membri",
|
||||
"view_bookings_across_description": "Individua la persona che riceve il maggior numero di prenotazioni e assicura la migliore distribuzione delle prenotazioni all'interno del team",
|
||||
"identify_booking_trends": "Identifica le tendenze di prenotazione",
|
||||
"identify_booking_trends_description": "Scopri quali giorni della settimana e quali fasce orarie sono popolari tra le persone che prenotano",
|
||||
"spot_popular_event_types": "Individua i tipi di evento popolari",
|
||||
"spot_popular_event_types_description": "Scopri quali tipi di evento ricevono più clic e prenotazioni",
|
||||
"no_responses_yet": "Per ora non ci sono risposte",
|
||||
"this_will_be_the_placeholder": "Questo sarà il segnaposto",
|
||||
"this_meeting_has_not_started_yet": "Questa riunione non è ancora iniziata",
|
||||
@@ -1646,8 +1706,83 @@
|
||||
"can_you_try_again": "Riprovare specificando un orario differente.",
|
||||
"verify": "Verifica",
|
||||
"timezone_variable": "Timezone",
|
||||
"timezone_info": "Il fuso orario della persona che riceve",
|
||||
"event_end_time_variable": "Ora di fine evento",
|
||||
"event_end_time_info": "Ora di fine dell'evento",
|
||||
"cancel_url_variable": "URL di annullamento",
|
||||
"cancel_url_info": "URL per annullare la prenotazione",
|
||||
"reschedule_url_variable": "URL di riprogrammazione",
|
||||
"reschedule_url_info": "URL per riprogrammare la prenotazione",
|
||||
"invalid_event_name_variables": "Nel nome dell'evento è presente una variabile non valida",
|
||||
"select_all": "Seleziona tutto",
|
||||
"default_conferencing_bulk_title": "Aggiorna in blocco i tipi di eventi esistenti",
|
||||
"default_conferencing_bulk_description": "Aggiorna le posizioni per i tipi di eventi selezionati"
|
||||
"members_default_schedule": "Pianificazione predefinita membro",
|
||||
"set_by_admin": "Impostato dall'amministratore del team",
|
||||
"members_default_location": "Posizione predefinita del membro",
|
||||
"members_default_schedule_description": "Useremo il programma di disponibilità predefinito di ciascun membro. I membri saranno in grado di modificarlo.",
|
||||
"requires_at_least_one_schedule": "È necessario avere almeno un programma",
|
||||
"default_conferencing_bulk_description": "Aggiorna le posizioni per i tipi di eventi selezionati",
|
||||
"locked_for_members": "Bloccato per i membri",
|
||||
"locked_apps_description": "I membri saranno in grado di vedere le app attive, ma non saranno in grado di modificare le impostazioni delle app",
|
||||
"locked_webhooks_description": "I membri saranno in grado di vedere i webhook attivi, ma non saranno in grado di modificare le impostazioni dei webhook",
|
||||
"locked_workflows_description": "I membri saranno in grado di vedere i flussi di lavoro attivi, ma non saranno in grado di modificare le impostazioni dei flussi di lavoro",
|
||||
"locked_by_admin": "Bloccato dall'amministratore",
|
||||
"app_not_connected": "Non è stato connesso un account di {{appName}}.",
|
||||
"connect_now": "Connetti ora",
|
||||
"managed_event_dialog_confirm_button_one": "Sostituisci e invia notifica a {{count}} membro",
|
||||
"managed_event_dialog_confirm_button_other": "Sostituisci e invia notifica a {{count}} membri",
|
||||
"managed_event_dialog_title_one": "L'URL /{{slug}} esiste già per {{count}} membro. Sostituirlo?",
|
||||
"managed_event_dialog_title_other": "L'URL /{{slug}} esiste già per {{count}} membri. Sostituirlo?",
|
||||
"managed_event_dialog_information_one": "<strong>{{names}}</strong> utilizza già l'URL <strong>/{{slug}}</strong>.",
|
||||
"managed_event_dialog_information_other": "<strong>{{names}}</strong> utilizzano già l'URL <strong>/{{slug}}</strong>.",
|
||||
"managed_event_dialog_clarification": "Se si decide di sostituirlo, ai membri ne verrà inviata una notifica. Tornare indietro e rimuoverli se non si desidera sovrascriverlo.",
|
||||
"review_event_type": "Verifica tipo di evento",
|
||||
"looking_for_more_analytics": "Hai bisogno di più dati analitici?",
|
||||
"looking_for_more_insights": "Hai bisogno di maggiori approfondimenti?",
|
||||
"add_filter": "Aggiungi filtro",
|
||||
"select_user": "Seleziona utente",
|
||||
"select_event_type": "Seleziona tipo di evento",
|
||||
"select_date_range": "Seleziona intervallo di date",
|
||||
"popular_events": "Eventi popolari",
|
||||
"no_event_types_found": "Nessun tipo di evento trovato",
|
||||
"average_event_duration": "Durata media evento",
|
||||
"most_booked_members": "Membri più prenotati",
|
||||
"least_booked_members": "Membri meno prenotati",
|
||||
"events_created": "Eventi creati",
|
||||
"events_completed": "Eventi completati",
|
||||
"events_cancelled": "Eventi annullati",
|
||||
"events_rescheduled": "Eventi riprogrammati",
|
||||
"from_last_period": "dall'ultimo periodo",
|
||||
"from_to_date_period": "Da: {{startDate}} A: {{endDate}}",
|
||||
"analytics_for_organisation": "Approfondimenti",
|
||||
"subtitle_analytics": "Scopri di più sull'attività del tuo team",
|
||||
"redirect_url_warning": "Aggiungendo un reindirizzamento, viene disabilitata la pagina di conferma dell'esito. Assicurati che nella pagina di conferma dell'esito personalizzata compaia la dicitura \"Prenotazione confermata\".",
|
||||
"event_trends": "Tendenze eventi",
|
||||
"clear_filters": "Cancella filtri",
|
||||
"hold": "Preautorizzazione",
|
||||
"on_booking_option": "Raccogli pagamento alla prenotazione",
|
||||
"hold_option": "Addebita penale per mancata presentazione",
|
||||
"card_held": "Preautorizzazione carta",
|
||||
"charge_card": "Addebita carta",
|
||||
"card_charged": "Carta addebitata",
|
||||
"no_show_fee_amount": "Penale per mancata presentazione: {{amount, currency}}",
|
||||
"no_show_fee": "Penale per mancata presentazione",
|
||||
"submit_card": "Invia carta",
|
||||
"submit_payment_information": "Invia informazioni di pagamento",
|
||||
"meeting_awaiting_payment_method": "La tua riunione è in attesa di un metodo di pagamento",
|
||||
"no_show_fee_charged_email_subject": "Una penale per mancata presentazione di {{amount, currency}} è stata addebitata per {{title}} alle {{date}}",
|
||||
"no_show_fee_charged_text_body": "È stata addebitata una penale per mancata presentazione",
|
||||
"no_show_fee_charged_subtitle": "Una penale per mancata presentazione di {{amount, currency}} è stata addebitata per il seguente evento",
|
||||
"error_charging_card": "L'addebito della penale per mancata presentazione non è andato a buon fine. Riprovare più tardi.",
|
||||
"collect_no_show_fee": "Riscuoti penale per mancata presentazione",
|
||||
"no_show_fee_charged": "Penale per mancata presentazione addebitata",
|
||||
"insights": "Approfondimenti",
|
||||
"testing_workflow_info_message": "Durante il test di questo flusso di lavoro, tieni presente che le e-mail e i messaggi SMS possono essere programmati con almeno 1 ora di anticipo",
|
||||
"insights_no_data_found_for_filter": "Nessun dato trovato per il filtro selezionato o le date selezionate.",
|
||||
"acknowledge_booking_no_show_fee": "Confermo che in caso di mia mancata partecipazione a questo evento, una penale per mancata presentazione di {{amount, currency}} verrà addebitata sulla mia carta.",
|
||||
"card_details": "Dati della carta",
|
||||
"seats_and_no_show_fee_error": "Attualmente non è possibile abilitare i posti e addebitare la penale per mancata presentazione",
|
||||
"complete_your_booking": "Completa la prenotazione",
|
||||
"complete_your_booking_subject": "Completa la prenotazione: {{title}} del {{date}}",
|
||||
"email_invite_team": "{{email}} è stato invitato"
|
||||
}
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
{
|
||||
"identity_provider": "ID 제공자",
|
||||
"trial_days_left": "PRO 평가판의 남은 기한 $t(day, {\"count\": {{days}} })",
|
||||
"day_one": "{{count}}일",
|
||||
"day_other": "{{count}}일",
|
||||
@@ -69,6 +70,8 @@
|
||||
"event_awaiting_approval_subject": "승인을 기다리고 있습니다: {{date}} {{title}}",
|
||||
"event_still_awaiting_approval": "이벤트가 아직 승인을 기다리고 있습니다.",
|
||||
"booking_submitted_subject": "예약 제출됨: {{date}}에 {{title}}",
|
||||
"download_recording_subject": "기록 다운로드: {{title}} 날짜 {{date}}",
|
||||
"download_your_recording": "사용자 기록 다운로드",
|
||||
"your_meeting_has_been_booked": "회의가 예약되었습니다.",
|
||||
"event_type_has_been_rescheduled_on_time_date": "귀하의 {{title}} 일정이 {{date}}로 변경되었습니다.",
|
||||
"event_has_been_rescheduled": "업데이트 - 이벤트 일정이 변경되었습니다.",
|
||||
@@ -587,6 +590,20 @@
|
||||
"minutes": "분",
|
||||
"round_robin": "라운드 로빈",
|
||||
"round_robin_description": "여러 팀 구성원 간의 주기적인 회의",
|
||||
"managed_event": "관리형 이벤트",
|
||||
"username_placeholder": "사용자 이름",
|
||||
"managed_event_description": "팀원들에게 대량으로 이벤트 유형 생성 및 배포",
|
||||
"managed": "관리됨",
|
||||
"managed_event_url_clarification": "\"사용자 이름\"은 할당된 회원의 사용자 이름으로 채워집니다",
|
||||
"assign_to": "할당:",
|
||||
"add_members": "회원 추가:",
|
||||
"count_members_one": "회원 {{count}}명",
|
||||
"count_members_other": "회원 {{count}}명",
|
||||
"no_assigned_members": "할당된 회원 없음",
|
||||
"assigned_to": "할당됨:",
|
||||
"start_assigning_members_above": "위에서 회원 할당 시작",
|
||||
"locked_fields_admin_description": "회원은 이 내용을 수정할 수 없게 됩니다",
|
||||
"locked_fields_member_description": "이 옵션은 팀 관리자에 의해 잠겼습니다",
|
||||
"url": "링크",
|
||||
"hidden": "숨기기",
|
||||
"readonly": "읽기 전용",
|
||||
@@ -739,6 +756,9 @@
|
||||
"slot_interval": "시간 슬롯 간격",
|
||||
"slot_interval_default": "이벤트 길이 사용(기본값)",
|
||||
"delete_event_type": "이벤트 타입을 삭제하시겠습니까?",
|
||||
"delete_managed_event_type": "관리 이벤트 유형을 삭제하시겠습니까?",
|
||||
"delete_event_type_description": "링크를 공유한 회원은 더 이상 링크를 사용하여 예약할 수 없게 됩니다.",
|
||||
"delete_managed_event_type_description": "<ul><li>이 이벤트 유형에 할당된 회원은 이벤트 유형도 삭제됩니다.</li><li>링크를 공유한 회원은 더 이상 링크를 사용하여 예약할 수 없게 됩니다.</li> </ul>",
|
||||
"confirm_delete_event_type": "네, 삭제합니다",
|
||||
"delete_account": "계정 삭제",
|
||||
"confirm_delete_account": "예, 계정을 삭제합니다",
|
||||
@@ -1130,11 +1150,13 @@
|
||||
"current_username": "현재 사용자 이름",
|
||||
"example_1": "예 1",
|
||||
"example_2": "예 2",
|
||||
"booking_question_identifier": "예약 질문 ID",
|
||||
"company_size": "회사 규모",
|
||||
"what_help_needed": "어떻게 도와 드릴까요?",
|
||||
"variable_format": "변수 형식",
|
||||
"webhook_subscriber_url_reserved": "웹훅 구독자 URL이 이미 정의되었습니다",
|
||||
"custom_input_as_variable_info": "추가 입력 레이블의 모든 특수 문자는 무시하고(문자와 숫자만 사용) 모든 문자에 대문자를 사용하며 공백은 밑줄로 대체합니다.",
|
||||
"using_booking_questions_as_variables": "예약 질문을 변수로 어떻게 사용합니까?",
|
||||
"download_desktop_app": "데스크탑 앱 다운로드",
|
||||
"set_ping_link": "Ping 링크 설정",
|
||||
"rate_limit_exceeded": "요금 한도 초과됨",
|
||||
@@ -1180,6 +1202,7 @@
|
||||
"start_of_week": "주의 시작",
|
||||
"recordings_title": "녹음",
|
||||
"recording": "녹음",
|
||||
"happy_scheduling": "즐거운 일정 관리 되세요",
|
||||
"select_calendars": "중복 예약을 방지하기 위해 충돌을 확인하려는 캘린더를 선택합니다.",
|
||||
"check_for_conflicts": "충돌 확인",
|
||||
"view_recordings": "녹음 보기",
|
||||
@@ -1258,6 +1281,7 @@
|
||||
"download_responses": "다운로드 응답",
|
||||
"download_responses_description": "양식에 대한 모든 응답을 CSV 형식으로 다운로드합니다.",
|
||||
"download": "다운로드",
|
||||
"download_recording": "기록 다운로드",
|
||||
"recording_from_your_recent_call": "Cal.com에서 최근 통화 내용 녹음 파일을 다운로드 할 준비가 되었습니다",
|
||||
"create_your_first_form": "첫 양식 작성",
|
||||
"create_your_first_form_description": "라우팅 양식을 사용하면 적격성 질문을 올리고 올바른 사람 또는 이벤트 유형으로 라우팅할 수 있습니다.",
|
||||
@@ -1295,6 +1319,15 @@
|
||||
"exchange_authentication_standard": "기본 인증",
|
||||
"exchange_authentication_ntlm": "NTLM 인증",
|
||||
"exchange_compression": "GZip 압축",
|
||||
"exchange_version": "전환 버전",
|
||||
"exchange_version_2007_SP1": "2007 SP1",
|
||||
"exchange_version_2010": "2010",
|
||||
"exchange_version_2010_SP1": "2010 SP1",
|
||||
"exchange_version_2010_SP2": "2010 SP2",
|
||||
"exchange_version_2013": "2013",
|
||||
"exchange_version_2013_SP1": "2013 SP1",
|
||||
"exchange_version_2015": "2015",
|
||||
"exchange_version_2016": "2016",
|
||||
"routing_forms_description": "여기에서 생성했던 모든 양식과 경로를 볼 수 있습니다.",
|
||||
"routing_forms_send_email_owner": "소유자에게 이메일 보내기",
|
||||
"routing_forms_send_email_owner_description": "양식이 제출되면 소유자에게 이메일을 발송합니다",
|
||||
@@ -1349,6 +1382,7 @@
|
||||
"add_limit": "제한 추가",
|
||||
"team_name_required": "팀 이름 필수",
|
||||
"show_attendees": "게스트 간에 참석자 정보 공유",
|
||||
"how_booking_questions_as_variables": "예약 질문을 변수로 어떻게 사용합니까?",
|
||||
"format": "형식",
|
||||
"uppercase_for_letters": "모든 문자에 대문자 사용",
|
||||
"replace_whitespaces_underscores": "공백을 밑줄로 바꾸기",
|
||||
@@ -1363,6 +1397,7 @@
|
||||
"billing_help_title": "또 필요한 것이 있나요?",
|
||||
"billing_help_description": "청구와 관련하여 추가적인 도움이 필요한 경우 지원 팀이 도와드리겠습니다.",
|
||||
"billing_help_cta": "지원 문의",
|
||||
"ignore_special_characters_booking_questions": "예약 질문 ID의 특수 문자는 무시하십시오. 문자와 숫자만 사용하십시오",
|
||||
"retry": "재시도",
|
||||
"fetching_calendars_error": "캘린더를 가져오는 중 문제가 발생했습니다. <1>다시 시도</1>하거나 고객 지원에 문의하십시오.",
|
||||
"calendar_connection_fail": "캘린더 연결 실패",
|
||||
@@ -1451,8 +1486,11 @@
|
||||
"duration_limit_reached": "이 이벤트 타입에 대한 기간 한도에 도달했습니다",
|
||||
"admin_has_disabled": "관리자가 {{appName}} 앱을 비활성화했습니다",
|
||||
"disabled_app_affects_event_type": "관리자가 이벤트 타입 {{eventType}}에 영향을 미치는 {{appName}} 앱을 비활성화했습니다",
|
||||
"event_replaced_notice": "관리자가 이벤트 유형 중 하나를 바꿨습니다",
|
||||
"email_subject_slug_replacement": "팀 관리자가 당신의 이벤트 /{{slug}}를 변경하였습니다",
|
||||
"email_body_slug_replacement_notice": "<strong>{{teamName}}</strong> 팀의 관리자가 귀하의 이벤트 유형 <strong>/{{slug}}</strong>을(를) 관리 이벤트 유형으로 바꿨습니다.",
|
||||
"email_body_slug_replacement_info": "링크는 계속 유효하지만 일부 설정이 변경될 수 있습니다. 이벤트 타입에서 이를 검토할 수 있습니다.",
|
||||
"email_body_slug_replacement_suggestion": "이벤트 유형에 대해 질문이 있는 경우 관리자에게 문의하십시오.<br /><br />즐거운 일정 되시기 바랍니다. <br />Cal.com 팀",
|
||||
"disable_payment_app": "관리자가 이벤트 타입 {{title}}에 영향을 미치는 {{appName}} 앱을 비활성화했습니다. 참석자는 여전히 이러한 타입의 이벤트를 예약할 수 있지만 결제하라는 메시지는 표시되지 않습니다. 관리자가 결제 수단을 다시 활성화할 때까지 이를 방지하기 위해 이벤트 타입을 숨길 수 있습니다.",
|
||||
"payment_disabled_still_able_to_book": "참석자는 여전히 이러한 타입의 이벤트를 예약할 수 있지만 결제하라는 메시지는 표시되지 않습니다. 관리자가 결제 수단을 다시 활성화할 때까지 이를 방지하기 위해 이벤트 타입을 숨길 수 있습니다.",
|
||||
"app_disabled_with_event_type": "관리자가 이벤트 타입 {{title}}에 영향을 미치는 {{appName}} 앱을 비활성화했습니다.",
|
||||
@@ -1548,6 +1586,7 @@
|
||||
"ee_enterprise_license": "“/ee” Enterprise 라이선스",
|
||||
"enterprise_booking_fee": "시작 가격 {{enterprise_booking_fee}}/월",
|
||||
"enterprise_license_includes": "모든 상업적 기능 구비",
|
||||
"no_need_to_keep_your_code_open_source": "코드를 오픈 소스로 유지할 필요가 없습니다",
|
||||
"repackage_rebrand_resell": "쉽게 패키지를 재구성하여 새 브랜드로 재판매 가능",
|
||||
"a_vast_suite_of_enterprise_features": "방대한 엔터프라이즈 기능 모음",
|
||||
"free_license_fee": "$0.00/월",
|
||||
@@ -1609,6 +1648,7 @@
|
||||
"default_app_link_title": "기본 앱 링크 설정",
|
||||
"default_app_link_description": "기본 앱 링크를 설정하면 새로 생성된 모든 이벤트 유형에서 설정된 앱 링크를 사용할 수 있습니다.",
|
||||
"change_default_conferencing_app": "기본값으로 설정",
|
||||
"organizer_default_conferencing_app": "주최자의 기본 앱",
|
||||
"under_maintenance": "아래로 밀어 유지관리",
|
||||
"under_maintenance_description": "{{appName}} 팀에서 예정된 유지관리를 수행하고 있습니다. 궁금한 점이 있으면 지원팀에 문의하세요.",
|
||||
"event_type_seats": "{{numberOfSeats}}개 시트",
|
||||
@@ -1623,6 +1663,7 @@
|
||||
"not_enough_seats": "좌석이 부족합니다",
|
||||
"form_builder_field_already_exists": "이 이름을 가진 필드가 이미 존재합니다",
|
||||
"form_builder_field_add_subtitle": "예약 페이지에서 묻는 질문을 사용자 정의합니다",
|
||||
"show_on_booking_page": "예약 페이지에 표시",
|
||||
"get_started_zapier_templates": "Zapier 템플릿 시작하기",
|
||||
"team_is_unpublished": "{{team}} 팀은 게시되지 않았습니다",
|
||||
"team_is_unpublished_description": "이 팀 링크는 현재 사용할 수 없습니다. 팀 소유자에게 연락하거나 게시하라고 요청하십시오.",
|
||||
@@ -1665,12 +1706,36 @@
|
||||
"can_you_try_again": "다른 시간으로 다시 해보시겠어요?",
|
||||
"verify": "인증",
|
||||
"timezone_variable": "시간대",
|
||||
"timezone_info": "받는 사람의 시간대",
|
||||
"event_end_time_variable": "이벤트 종료 시간",
|
||||
"event_end_time_info": "이벤트 종료 시간",
|
||||
"cancel_url_variable": "취소 URL",
|
||||
"cancel_url_info": "예약 취소 URL",
|
||||
"reschedule_url_variable": "일정 변경 URL",
|
||||
"reschedule_url_info": "예약 일정 변경 URL",
|
||||
"invalid_event_name_variables": "이벤트 이름에 유효하지 않은 변수가 있습니다",
|
||||
"select_all": "모두 선택",
|
||||
"default_conferencing_bulk_title": "기존 이벤트 유형 일괄 업데이트",
|
||||
"members_default_schedule": "회원의 기본 일정",
|
||||
"set_by_admin": "팀 관리자에 의해 설정됨",
|
||||
"members_default_location": "회원의 기본 위치",
|
||||
"members_default_schedule_description": "각 회원의 기본 사용 가능 일정을 사용합니다. 편집 또는 변경할 수 있습니다.",
|
||||
"requires_at_least_one_schedule": "적어도 한 개 이상의 스케줄이 필요합니다",
|
||||
"default_conferencing_bulk_description": "선택한 이벤트 유형의 위치 업데이트",
|
||||
"locked_for_members": "회원에 대해 잠김",
|
||||
"locked_apps_description": "회원은 활성 앱을 볼 수 있지만 앱 설정을 편집할 수는 없습니다",
|
||||
"locked_webhooks_description": "회원은 활성 웹훅을 볼 수 있지만 웹훅 설정을 편집할 수는 없습니다",
|
||||
"locked_workflows_description": "회원은 활성 워크플로를 볼 수 있지만 워크플로 설정을 편집할 수는 없습니다",
|
||||
"locked_by_admin": "관리자에 의해 잠금 됨",
|
||||
"app_not_connected": "{{appName}} 계정을 연결하지 않으셨습니다.",
|
||||
"connect_now": "지금 연결",
|
||||
"managed_event_dialog_confirm_button_one": "회원 {{count}}명 바꾸기 및 알림",
|
||||
"managed_event_dialog_confirm_button_other": "회원 {{count}}명 바꾸기 및 알림",
|
||||
"managed_event_dialog_title_one": "URL /{{slug}}은(는) {{count}}명의 회원에 대해 이미 존재합니다. 바꾸시겠습니까?",
|
||||
"managed_event_dialog_title_other": "URL /{{slug}}은(는) {{count}}명의 회원에 대해 이미 존재합니다. 바꾸시겠습니까?",
|
||||
"managed_event_dialog_information_one": "<strong>{{names}}</strong> 님이 이미 <strong>/{{slug}}</strong> url을 사용하는 중입니다.",
|
||||
"managed_event_dialog_information_other": "<strong>{{names}}</strong> 님이 이미 <string>/{{slug}}</strong> url을 사용하는 중입니다.",
|
||||
"managed_event_dialog_clarification": "바꾸기를 선택하면 알려드립니다. 덮어쓰지 않으려면 돌아가서 제거하십시오.",
|
||||
"review_event_type": "이벤트 타입 검토",
|
||||
"looking_for_more_analytics": "추가적인 분석을 원하시나요?",
|
||||
"looking_for_more_insights": "더 많은 인사이트를 찾고계신가요?",
|
||||
@@ -1678,21 +1743,46 @@
|
||||
"select_user": "사용자 선택",
|
||||
"select_event_type": "이벤트 타입 선택",
|
||||
"select_date_range": "날짜 범위 선택",
|
||||
"popular_events": "인기 이벤트",
|
||||
"no_event_types_found": "이벤트 타입을 찾을 수 없습니다",
|
||||
"average_event_duration": "평균 이벤트 기간",
|
||||
"most_booked_members": "가장 많이 예약한 회원",
|
||||
"least_booked_members": "가장 적게 예약한 회원",
|
||||
"events_created": "생성된 이벤트",
|
||||
"events_completed": "완료된 이벤트",
|
||||
"events_cancelled": "취소된 이벤트",
|
||||
"events_rescheduled": "일정이 변경된 이벤트",
|
||||
"from_last_period": "마지막 기간부터",
|
||||
"from_to_date_period": "시작일: {{startDate}} 종료일: {{endDate}}",
|
||||
"analytics_for_organisation": "인사이트",
|
||||
"subtitle_analytics": "팀 활동에 대해 자세히 알아보기",
|
||||
"redirect_url_warning": "리디렉션을 추가하면 성공 페이지가 비활성화됩니다. 사용자 지정 성공 페이지에 \"예약 확인됨\"을 언급해야 합니다.",
|
||||
"event_trends": "이벤트 트렌드",
|
||||
"clear_filters": "필터 삭제",
|
||||
"hold": "보류",
|
||||
"on_booking_option": "예약 시 결제 징수",
|
||||
"hold_option": "노쇼 수수료 부과",
|
||||
"card_held": "카드 보류됨",
|
||||
"charge_card": "카드 부과",
|
||||
"card_charged": "카드 부과됨",
|
||||
"no_show_fee_amount": "{{amount, currency}} 노쇼 수수료",
|
||||
"no_show_fee": "No-show 수수료",
|
||||
"submit_card": "카드 제출",
|
||||
"submit_payment_information": "결제 정보 제출",
|
||||
"meeting_awaiting_payment_method": "회의에 대한 결제 수단을 기다리는 중입니다",
|
||||
"no_show_fee_charged_email_subject": "{{amount, currency}}에 대한 노쇼 수수료 부과됨: {{title}} 날짜 {{date}}",
|
||||
"no_show_fee_charged_text_body": "No-show 수수료가 부과되었습니다",
|
||||
"no_show_fee_charged_subtitle": "다음 이벤트에 대해 {{amount, currency}}에 대한 노쇼 수수료가 부과되었습니다",
|
||||
"error_charging_card": "No-show 수수료 청구 중 문제가 발생했습니다. 나중에 다시 시도해주세요.",
|
||||
"collect_no_show_fee": "No-show 수수료를 청구하세요",
|
||||
"no_show_fee_charged": "No-show 수수료가 부과됩니다",
|
||||
"insights": "인사이트",
|
||||
"testing_workflow_info_message": "이 워크플로를 테스트할 때 이메일 및 SMS는 최소 1시간 전에만 예약할 수 있다는 점에 유의하세요",
|
||||
"insights_no_data_found_for_filter": "선택한 필터 또는 날짜에 대한 데이터를 찾을 수 없습니다.",
|
||||
"acknowledge_booking_no_show_fee": "본인은 이 이벤트에 참석하지 않을 경우 {{amount, currency}}에 대한 노쇼 수수료가 내 카드에 적용됨을 인정합니다.",
|
||||
"card_details": "카드 세부정보",
|
||||
"seats_and_no_show_fee_error": "현재 좌석을 활성화할 수 없으므로 노쇼 수수료를 부과할 수 없습니다",
|
||||
"complete_your_booking": "예약을 완료하세요",
|
||||
"complete_your_booking_subject": "예약 완료: {{title}} 날짜 {{date}}",
|
||||
"email_invite_team": "{{email}} 이 초대되었습니다."
|
||||
}
|
||||
|
||||
@@ -1614,7 +1614,7 @@
|
||||
"form_builder_field_add_subtitle": "Personalize as questões colocadas na página da reserva",
|
||||
"get_started_zapier_templates": "Comece a utilizar os modelos Zapier",
|
||||
"team_is_unpublished": "A equipa {{team}} ainda não está publicada",
|
||||
"team_is_unpublished_description": "Esta ligação de equipa não está disponível neste momento. Por favor, entre em contacto com o responsável pela equipa ou solicite-lhes a respetiva publicação.",
|
||||
"team_is_unpublished_description": "Esta ligação de equipa não está disponível neste momento. Por favor, entre em contacto com o responsável pela equipa ou solicite-lhe a respetiva publicação.",
|
||||
"team_member": "Membro da equipa",
|
||||
"a_routing_form": "Um formulário de encaminhamento",
|
||||
"form_description_placeholder": "Descrição do formulário",
|
||||
|
||||
@@ -1589,11 +1589,11 @@
|
||||
"create_a_one_one_template": "Создайте единый шаблон для данного типа события и распространите его среди нескольких участников.",
|
||||
"collective_or_roundrobin": "Коллективное событие или событие с назначением участников по очереди",
|
||||
"book_your_team_members": "Бронируйте встречи с несколькими участниками команды или назначайте участников по очереди.",
|
||||
"event_no_longer_attending_subject": "Не планирует посетить {{title}} {{date}}",
|
||||
"event_no_longer_attending_subject": "Посещение {{title}} в {{date}} не планируется",
|
||||
"no_longer_attending": "Вы не идете на это мероприятие",
|
||||
"attendee_no_longer_attending_subject": "Пользователь не планирует посетить {{title}} {{date}}",
|
||||
"attendee_no_longer_attending_subject": "Пользователь не планирует посетить {{title}} в {{date}}",
|
||||
"attendee_no_longer_attending": "Пользователь не участвует в вашем мероприятии",
|
||||
"attendee_no_longer_attending_subtitle": "{{name}} отменено. Этот промежуток времени снова свободен",
|
||||
"attendee_no_longer_attending_subtitle": "{{name}} отменил посещение мероприятия. Этот промежуток времени снова свободен",
|
||||
"create_event_on": "Создать события",
|
||||
"default_app_link_title": "Установить ссылку на приложение по умолчанию",
|
||||
"default_app_link_description": "Если установить ссылку на приложение по умолчанию, она будет присваиваться всем вновь создаваемым событиям.",
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
{
|
||||
"identity_provider": "Dobavljač identiteta",
|
||||
"trial_days_left": "Preostalo vam je $t(day, {\"count\": {{days}} }) na vašem probnom PRO nalogu",
|
||||
"day_one": "{{count}} dan",
|
||||
"day_other": "{{count}} dana",
|
||||
@@ -69,6 +70,8 @@
|
||||
"event_awaiting_approval_subject": "Čeka na odobrenje: {{title}} datuma {{date}}",
|
||||
"event_still_awaiting_approval": "Događaj još čeka na vaše odobrenje",
|
||||
"booking_submitted_subject": "Rezervacija podneta:{{title}} datuma {{date}}",
|
||||
"download_recording_subject": "Preuzimanje snimka: {{title}} dana {{date}}",
|
||||
"download_your_recording": "Preuzmite svoj snimak",
|
||||
"your_meeting_has_been_booked": "Sastanak je zakazan",
|
||||
"event_type_has_been_rescheduled_on_time_date": "Ваш {{титле}} је померен за {{дате}}.",
|
||||
"event_has_been_rescheduled": "Ažurirano - Vaš dogadjaj je odložen",
|
||||
@@ -224,6 +227,7 @@
|
||||
"done": "Urađeno",
|
||||
"all_done": "Završeno!",
|
||||
"all_apps": "Sve aplikacije",
|
||||
"available_apps": "Dostupne aplikacije",
|
||||
"check_email_reset_password": "Proverite vašu e-poštu. Poslali smo vam link za resetovanje lozinke.",
|
||||
"finish": "Završi",
|
||||
"few_sentences_about_yourself": "Par rečenica o vama. Ovo će se pojaviti na vašoj ličnoj url stranici.",
|
||||
@@ -404,6 +408,7 @@
|
||||
"add_time_availability": "Dodaj novi vremenski interval",
|
||||
"add_an_extra_layer_of_security": "Dodajte dodatni nivo sigurnosti svom nalogu u slučaju da vam je lozinka ukradena.",
|
||||
"2fa": "Dvofaktorska Autentikacija",
|
||||
"2fa_disabled": "Dvostruka potvrda identiteta pože biti omogućena samo za potvrdu identiteta putem imejla i lozinke",
|
||||
"enable_2fa": "Omogući dvofaktorsku autentikaciju",
|
||||
"disable_2fa": "Onemogući dvofaktorsku autentikaciju",
|
||||
"disable_2fa_recommendation": "Ako morate da onemogućite 2FA, predlažemo da je omogućite što pre.",
|
||||
@@ -585,6 +590,20 @@
|
||||
"minutes": "Minuti",
|
||||
"round_robin": "Round Robin",
|
||||
"round_robin_description": "Ciklusirajte sastanke između više članova tima.",
|
||||
"managed_event": "Upravljani događaj",
|
||||
"username_placeholder": "korisničko ime",
|
||||
"managed_event_description": "Kreirajte i distribuirajte veliku količinu tipova događaja članovima tima",
|
||||
"managed": "Upravljano",
|
||||
"managed_event_url_clarification": "„korisničko ime“ će biti ispunjeno korisničkim imenom dodeljenog člana",
|
||||
"assign_to": "Dodelite",
|
||||
"add_members": "Dodajte članove...",
|
||||
"count_members_one": "{{count}} član",
|
||||
"count_members_other": "{{count}} članova",
|
||||
"no_assigned_members": "Nema dodeljenih članova",
|
||||
"assigned_to": "Dodeljeno",
|
||||
"start_assigning_members_above": "Počnite da dodeljujete korisnicima iznad",
|
||||
"locked_fields_admin_description": "Članovi neće moći da uređuju ovo",
|
||||
"locked_fields_member_description": "Ova opcija je zaključana od strane administratora tima",
|
||||
"url": "URL",
|
||||
"hidden": "Sakriven",
|
||||
"readonly": "Samo za čitanje",
|
||||
@@ -736,6 +755,11 @@
|
||||
"minimum_booking_notice": "Minimalni rok za najavu sastanka",
|
||||
"slot_interval": "Intervali vremenskih slotova",
|
||||
"slot_interval_default": "Koristi dužinu događaja (uobičajeno podešavanje)",
|
||||
"delete_event_type": "Izbriši tip događaja?",
|
||||
"delete_managed_event_type": "Izbriši upravljani tip događaja?",
|
||||
"delete_event_type_description": "Niko sa kime ste podelili link neće više moći da rezerviše pomoću njega.",
|
||||
"delete_managed_event_type_description": "<ul><li>Članovima dodeljenim ovom tipu događaja će takođe biti obrisani njihovi tipovi događaja.</li><li>Niko sa kim su podelili svoj link neće više moći da zakaže pomoću njega.</li></ul>",
|
||||
"confirm_delete_event_type": "Da, izbriši",
|
||||
"delete_account": "Izbriši nalog",
|
||||
"confirm_delete_account": "Da, izbriši nalog",
|
||||
"delete_account_confirmation_message": "Da li ste sigurni da želite izbrisati svoj {{appName}} nalog? S kim god ste podelili link ovog naloga više neće moći da ga koristi za rezervacije i sva podešavanja koja imate neće biti sačuvana.",
|
||||
@@ -1126,11 +1150,13 @@
|
||||
"current_username": "Trenutno korisničko ime",
|
||||
"example_1": "Primer 1",
|
||||
"example_2": "Primer 2",
|
||||
"booking_question_identifier": "Identifikator pitanja o zakazivanju",
|
||||
"company_size": "Veličina kompanije",
|
||||
"what_help_needed": "U vezi sa čim vam je potrebna pomoć?",
|
||||
"variable_format": "Format promenljive",
|
||||
"webhook_subscriber_url_reserved": "Url adresa Webhook pretplatnika je već definisana",
|
||||
"custom_input_as_variable_info": "Zanemarite sve specijalne znakove za dodatne oznake unosa (koristite samo slova i brojeve), koristite samo velika slova i zamenite razmake donjim crtama.",
|
||||
"using_booking_questions_as_variables": "Kako da koristim pitanja o zakazivanju kao promenljivu?",
|
||||
"download_desktop_app": "Preuzmite aplikaciju za stoni računar",
|
||||
"set_ping_link": "Podesi Ping vezu",
|
||||
"rate_limit_exceeded": "Premašeno ograničenje stope",
|
||||
@@ -1176,6 +1202,7 @@
|
||||
"start_of_week": "Početak nedelje",
|
||||
"recordings_title": "Snimci",
|
||||
"recording": "Snimanje",
|
||||
"happy_scheduling": "Srećno planiranje",
|
||||
"select_calendars": "Izaberite kalendare koje želite da proverite da li imaju konflikta da biste sprečili dvostruka zakazivanja.",
|
||||
"check_for_conflicts": "Proverite konflikte",
|
||||
"view_recordings": "Pregledaj snimke",
|
||||
@@ -1213,6 +1240,7 @@
|
||||
"impersonation": "Predstavljanje kao",
|
||||
"impersonation_description": "Podešavanja za upravljanje prijavljivanjem na nalog u ime korisnika",
|
||||
"users": "Korisnici",
|
||||
"user": "Korisnik",
|
||||
"profile_description": "Upravljajte podešavanjima vašeg {{appName}} profila",
|
||||
"users_description": "Ovde možete pronaći spisak svih korisnika",
|
||||
"users_listing": "Spisak korisnika",
|
||||
@@ -1253,6 +1281,8 @@
|
||||
"download_responses": "Preuzmi odgovore",
|
||||
"download_responses_description": "Preuzmite sve odgovore iz obrasca u CSV formatu.",
|
||||
"download": "Preuzmi",
|
||||
"download_recording": "Preuzmi snimak",
|
||||
"recording_from_your_recent_call": "Snimak vašeg nedavnog poziva na Cal.com je spreman za preuzimanje",
|
||||
"create_your_first_form": "Kreirajte vaš prvi formular",
|
||||
"create_your_first_form_description": "Sa formularima za rutiranje možete postavljati kvalifikaciona pitanja i usmeravati ih do prave osobe ili tipa događaja.",
|
||||
"create_your_first_webhook": "Kreirajte vaš prvi Webhook",
|
||||
@@ -1289,6 +1319,15 @@
|
||||
"exchange_authentication_standard": "Osnovna potvrda identiteta",
|
||||
"exchange_authentication_ntlm": "NTLM potvrda identiteta",
|
||||
"exchange_compression": "GZip kompresija",
|
||||
"exchange_version": "Verzija za razmenu",
|
||||
"exchange_version_2007_SP1": "2007. SP1",
|
||||
"exchange_version_2010": "2010.",
|
||||
"exchange_version_2010_SP1": "2010. SP1",
|
||||
"exchange_version_2010_SP2": "2010. SP2",
|
||||
"exchange_version_2013": "2013.",
|
||||
"exchange_version_2013_SP1": "2013. SP1",
|
||||
"exchange_version_2015": "2015.",
|
||||
"exchange_version_2016": "2016.",
|
||||
"routing_forms_description": "Možete da vidite sve formulare i rute koje ste kreirali ovde.",
|
||||
"routing_forms_send_email_owner": "Pošaljite imejl vlasniku",
|
||||
"routing_forms_send_email_owner_description": "Pošaljite imejl vlasniku kada pošaljete obrazac",
|
||||
@@ -1343,6 +1382,7 @@
|
||||
"add_limit": "Dodajte ograničenje",
|
||||
"team_name_required": "Ime tima je potrebno",
|
||||
"show_attendees": "Podelite informacije o učesnicima između gostiju",
|
||||
"how_booking_questions_as_variables": "Kako da koristite pitanja o zakazivanju kao promenljivu?",
|
||||
"format": "Format",
|
||||
"uppercase_for_letters": "Koristite velika slova za sva slova",
|
||||
"replace_whitespaces_underscores": "Zamenite prazna mesta donjom crtom",
|
||||
@@ -1357,6 +1397,7 @@
|
||||
"billing_help_title": "Treba li vam još nešto?",
|
||||
"billing_help_description": "Ako vam treba pomoć sa naplatom, naš tim podrške je tu da pomogne.",
|
||||
"billing_help_cta": "Obratite se podršci",
|
||||
"ignore_special_characters_booking_questions": "Ignorišite specijalne znakove u vašim identifikatorima pitanja o zakazivanju. Koristite samo slova i brojeve",
|
||||
"retry": "Pokušajte ponovo",
|
||||
"fetching_calendars_error": "Došlo je do problema prilikom dobavljanja vaših kalendara. <1>Pokušajte ponovo</1> ili se obratite korisničkoj podršci.",
|
||||
"calendar_connection_fail": "Povezivanje sa kalendarom nije uspelo",
|
||||
@@ -1439,11 +1480,17 @@
|
||||
"fixed_round_robin": "Fiksno kružno dodeljivanje",
|
||||
"add_one_fixed_attendee": "Dodajte jednog fiksnog učesnika i onda rotirajte ostale učesnike.",
|
||||
"calcom_is_better_with_team": "Cal.com je bolji uz timove",
|
||||
"the_calcom_team": "Cal.com tim",
|
||||
"add_your_team_members": "Dodajte članove vašeg tima vašim tipovima događaja. Koristite kolektivno zakazivanje da uključite svakoga ili pronađete osobu koja najviše odgovara pomoću zakazivanja kružnim dodeljivanjem.",
|
||||
"booking_limit_reached": "Dostignuta je granica zakazivanja za ovaj tip događaja",
|
||||
"duration_limit_reached": "Dostignuta je granica trajanja za ovaj tip događaja",
|
||||
"admin_has_disabled": "Administrator je onemogućio {{appName}}",
|
||||
"disabled_app_affects_event_type": "Administrator je onemogućio {{appName}}, što utiče na vaš tip događaja {{eventType}}",
|
||||
"event_replaced_notice": "Administrator je zamenio jedan od vaših tipova događaja",
|
||||
"email_subject_slug_replacement": "Administrator tima je zamenio vaš događaj /{{slug}}",
|
||||
"email_body_slug_replacement_notice": "Administrator iz <strong>{{teamName}}</strong> tima je zamenio vaš tip događaja <strong>{{slug}}</strong> upravljanim tipom događaja koji oni kontrolišu.",
|
||||
"email_body_slug_replacement_info": "Vaš link će nastaviti da funkcioniše, ali neka od njegovih podešavanja su se možda promenila. Možete da ih pregledate u tipovima događaja.",
|
||||
"email_body_slug_replacement_suggestion": "Ako imate bilo kakvih pitanja u vezi sa tipom događaja, obratite se svom administratoru.<br /><br />Srećno planiranje, <br />Cal.com tim",
|
||||
"disable_payment_app": "Administrator je onemogućio {{appName}}, što utiče na vaš tip događaja {{title}}. Učesnici još uvek mogu da zakažu ovaj tip događaja, ali od njih neće biti zatraženo da plate. Možete da sakrijete ovaj tip događaja da biste to sprečili dok vaš administrator ponovo ne omogući vaš način plaćanja.",
|
||||
"payment_disabled_still_able_to_book": "Učesnici još uvek mogu da zakažu ovaj tip događaja, ali od njih neće biti zatraženo da plate. Možete da sakrijete ovaj tip događaja da biste to sprečili dok vaš administrator ponovo ne omogući vaš način plaćanja.",
|
||||
"app_disabled_with_event_type": "Administrator je onemogućio {{appName}}, što utiče na vaš tip događaja {{title}}.",
|
||||
@@ -1492,6 +1539,7 @@
|
||||
"date_overrides_update_btn": "Ažuriraj zamenu",
|
||||
"event_type_duplicate_copy_text": "{{slug}}-kopija",
|
||||
"set_as_default": "Postavi kao podrazumevano",
|
||||
"hide_eventtype_details": "Sakrij detalje tipa događaja",
|
||||
"show_navigation": "Prikaži navigaciju",
|
||||
"hide_navigation": "Sakrij navigaciju",
|
||||
"verification_code_sent": "Verifikacioni kod je poslat",
|
||||
@@ -1505,6 +1553,7 @@
|
||||
"create_your_first_team_webhook_description": "Napravite svoj prvi webhook za ovaj tip timskog događaja",
|
||||
"create_webhook_team_event_type": "Napravite webhook za ovaj tip timskog događaja",
|
||||
"disable_success_page": "Onemogući stranicu Uspeh (radi samo ako imate URL za preusmeravanje)",
|
||||
"invalid_admin_password": "Vi ste administrator ali nemate lozinku koja je duga najmanje 15 znakova ili još uvek nemate dvostruku potvrdu identiteta",
|
||||
"change_password_admin": "Promenite lozinku da biste dobili pristup administratora",
|
||||
"username_already_taken": "Korisničko ime je zauzeto",
|
||||
"assignment": "Dodeljivanje",
|
||||
@@ -1537,6 +1586,7 @@
|
||||
"ee_enterprise_license": "“/ee” Licenca za preduzeća",
|
||||
"enterprise_booking_fee": "Već od {{enterprise_booking_fee}}/mesečno",
|
||||
"enterprise_license_includes": "Sve za komercijalnu upotrebu",
|
||||
"no_need_to_keep_your_code_open_source": "Nije potrebno da vaš kôd bude otvoren",
|
||||
"repackage_rebrand_resell": "Prepakujte, rebrendirajte i lako ponovo prodajte",
|
||||
"a_vast_suite_of_enterprise_features": "Ogroman izbor funkcija za preduzeća",
|
||||
"free_license_fee": "0,00 $/mesečno",
|
||||
@@ -1598,6 +1648,7 @@
|
||||
"default_app_link_title": "Podesite podrazumevani link aplikacije",
|
||||
"default_app_link_description": "Podešavanje podrazumevanog linka aplikacije omogućava novokreiranim tipovima događaja da koriste link aplikacije koji ste postavili.",
|
||||
"change_default_conferencing_app": "Postavi kao podrazumevano",
|
||||
"organizer_default_conferencing_app": "Podrazumevana aplikacija organizatora",
|
||||
"under_maintenance": "Ne radi zbog održavanja",
|
||||
"under_maintenance_description": "Tim {{appName}} izvodi zakazano održavanje. Ako imate pitanja, obratite se korisničkoj podršci.",
|
||||
"event_type_seats": "{{numberOfSeats}} mesta",
|
||||
@@ -1612,6 +1663,7 @@
|
||||
"not_enough_seats": "Nema dovoljno mesta",
|
||||
"form_builder_field_already_exists": "Polje sa ovim imenom već postoji",
|
||||
"form_builder_field_add_subtitle": "Prilagodite pitanja postavljana na stranici za zakazivanje",
|
||||
"show_on_booking_page": "Prikaži na stranici zakazivanja",
|
||||
"get_started_zapier_templates": "Započnite sa Zapier predlošcima",
|
||||
"team_is_unpublished": "{{team}} više nije objavljen",
|
||||
"team_is_unpublished_description": "Ova veza tima više nije dostupna. Obratite se vlasniku tima ili ih zamolite da ga objave.",
|
||||
@@ -1654,12 +1706,39 @@
|
||||
"can_you_try_again": "Možete li da pokušate sa drugim terminom?",
|
||||
"verify": "Verifikuj",
|
||||
"timezone_variable": "Vremenska zona",
|
||||
"timezone_info": "Vremenska zona primaoca",
|
||||
"event_end_time_variable": "Vreme završetka događaja",
|
||||
"event_end_time_info": "Vreme završetka događaja",
|
||||
"cancel_url_variable": "URL za otkazivanje",
|
||||
"cancel_url_info": "URL za otkazivanje zakazivanja",
|
||||
"reschedule_url_variable": "URL za promenu termina",
|
||||
"reschedule_url_info": "URL za promenu termina",
|
||||
"invalid_event_name_variables": "U nazivu vašeg događaja se nalazi nevažeća promenljiva",
|
||||
"select_all": "Izaberite sve",
|
||||
"default_conferencing_bulk_title": "Masovno ažuriranje postojećih vrsti događaja",
|
||||
"members_default_schedule": "Podrazumevani raspored članova",
|
||||
"set_by_admin": "Postavio administrator tima",
|
||||
"members_default_location": "Podrazumevana lokacija članova",
|
||||
"members_default_schedule_description": "Koristićemo podrazumevani raspored dostupnosti za svakog člana. Oni će moći da ga uređuju ili promene.",
|
||||
"requires_at_least_one_schedule": "Potrebno je da imate barem jedan raspored",
|
||||
"default_conferencing_bulk_description": "Ažuriranje lokacija za izabrane tipove događaja",
|
||||
"locked_for_members": "Zaključano za članove",
|
||||
"locked_apps_description": "Članovi će moći da vide aktivne aplikacije, ali neće moći da uređuju bilo kakva podešavanja aplikacije",
|
||||
"locked_webhooks_description": "Članovi će moći da vide aktivne webhook-ove, ali neće moći da uređuju bilo kakva podešavanja webhook-ova",
|
||||
"locked_workflows_description": "Članovi će moći da vide aktivne radne tokove, ali neće moći da uređuju bilo kakva podešavanja radnih tokova",
|
||||
"locked_by_admin": "Zaključao administrator",
|
||||
"app_not_connected": "Niste povezali {{appName}} nalog.",
|
||||
"connect_now": "Povežite odmah",
|
||||
"managed_event_dialog_confirm_button_one": "Zameni i obavesti {{count}} člana",
|
||||
"managed_event_dialog_confirm_button_other": "Zameni i obavesti {{count}} članova",
|
||||
"managed_event_dialog_title_one": "URL /{{slug}} već postoji za {{count}} člana. Da li želite da ga zamenite?",
|
||||
"managed_event_dialog_title_other": "URL /{{slug}} već postoji za {{count}} članova. Da li želite da ga zamenite?",
|
||||
"managed_event_dialog_information_one": "<strong>{{names}}</strong> već koristi <strong>/{{slug}}</strong> URL.",
|
||||
"managed_event_dialog_information_other": "<strong>{{names}}</strong> već koriste <string>/{{slug}}</strong> URL.",
|
||||
"managed_event_dialog_clarification": "Ako izaberete da ga zamenite, obavestićemo ih. Vratite se i uklonite ih ako ne želite da zamenite postojeće.",
|
||||
"review_event_type": "Pregledajte tip događaja",
|
||||
"looking_for_more_analytics": "Tražite još analitike?",
|
||||
"looking_for_more_insights": "Tražite još uvida?",
|
||||
"add_filter": "Dodajte filter",
|
||||
"select_user": "Izaberite korisnika",
|
||||
"select_event_type": "Izaberite tip događaja",
|
||||
@@ -1675,5 +1754,40 @@
|
||||
"events_rescheduled": "Pomereni događaji",
|
||||
"from_last_period": "iz prošlog perioda",
|
||||
"from_to_date_period": "Od: {{startDate}} Do: {{endDate}}",
|
||||
"event_trends": "Trendovi događaja"
|
||||
"analytics_for_organisation": "Uvidi",
|
||||
"subtitle_analytics": "Saznajte više o aktivnosti vašeg tima",
|
||||
"redirect_url_warning": "Dodavanje preusmeravanja će onemogućiti stranicu uspeha. Uverite se da spomenete „Zakazivanje potvrđeno“ na vašoj prilagođenoj stranici uspeha.",
|
||||
"event_trends": "Trendovi događaja",
|
||||
"clear_filters": "Izbriši filtere",
|
||||
"hold": "Zadrži",
|
||||
"on_booking_option": "Sakupite plaćanje za zakazivanje",
|
||||
"hold_option": "Naplatite nadoknadu za izostanak",
|
||||
"card_held": "Karta zadržana",
|
||||
"charge_card": "Naplati karticu",
|
||||
"card_charged": "Kartica naplaćena",
|
||||
"no_show_fee_amount": "{{amount, currency}} nadoknada za izostanak",
|
||||
"no_show_fee": "Nadoknada za izostanak",
|
||||
"submit_card": "Pošaljite karticu",
|
||||
"submit_payment_information": "Pošaljite informacije o plaćanju",
|
||||
"meeting_awaiting_payment_method": "Vaš sastanak čeka na način plaćanja",
|
||||
"no_show_fee_charged_email_subject": "Nadoknada za izostanak u iznosu od {{amount, currency}} je naplaćena za {{title}} dana {{date}}",
|
||||
"no_show_fee_charged_text_body": "Naplaćena je naknada za izostanak",
|
||||
"no_show_fee_charged_subtitle": "Naknada za izostanak u iznosu od {{amount, currency}} je naplaćena za sledeći događaj",
|
||||
"error_charging_card": "Došlo je do greške prilikom naplate naknade za izostanak. Pokušajte ponovo kasnije.",
|
||||
"collect_no_show_fee": "Sakupite naknadu za izostanak",
|
||||
"no_show_fee_charged": "Naplaćena je naknada za izostanak",
|
||||
"insights": "Uvidi",
|
||||
"testing_workflow_info_message": "Kada testirate ovaj radni tok, imajte na umu da imejlovi i SMS-ovi mogu da budu zakazani najmanje 1 sat unapred",
|
||||
"insights_no_data_found_for_filter": "Nisu pronađeni podaci za izabrani filter ili izabrane datume.",
|
||||
"acknowledge_booking_no_show_fee": "Jasno mi je da će mi biti naplaćena naknada za izostanak putem kartice u iznosu od {{amount, currency}} ukoliko ne dođem na ovaj događaj.",
|
||||
"card_details": "Informacije o kartici",
|
||||
"seats_and_no_show_fee_error": "Omogućavanje mesta i naplata naknade za izostanak trenutno nije moguće",
|
||||
"complete_your_booking": "Završite svoje zakazivanje",
|
||||
"complete_your_booking_subject": "Završite svoje zakazivanje: {{title}} dana {{date}}",
|
||||
"currency_string": "{{amount, currency}}",
|
||||
"charge_card_dialog_body": "Upravo ćete naplatiti polazniku {{amount, currency}}. Jeste li sigurni da želite da nastavite?",
|
||||
"charge_attendee": "Naplatite polazniku {{amount, currency}}",
|
||||
"payment_app_commission": "Zahtevajte plaćanje ({{paymentFeePercentage}}% + {{fee, currency}} provizija po transakciji)",
|
||||
"email_invite_team": "{{email}} je pozvan",
|
||||
"image_size_limit_exceed": "Otpremljena slika ne sme da prevazilazi ograničenje od 5mb"
|
||||
}
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
{
|
||||
"identity_provider": "Identitetsleverantör",
|
||||
"trial_days_left": "Du har $t(day, {\"count\": {{days}} }) kvar på din PRO-provperiod",
|
||||
"day_one": "{{count}} dag",
|
||||
"day_other": "{{count}} dagar",
|
||||
@@ -69,6 +70,8 @@
|
||||
"event_awaiting_approval_subject": "Inväntar godkännande: {{title}} den {{date}}",
|
||||
"event_still_awaiting_approval": "En bokning väntar fortfarande på ditt godkännande",
|
||||
"booking_submitted_subject": "Bokning skickad: {{title}} {{date}}",
|
||||
"download_recording_subject": "Ladda ner inspelning: {{title}} {{date}}",
|
||||
"download_your_recording": "Ladda ner din inspelning",
|
||||
"your_meeting_has_been_booked": "Ditt möte har bokats",
|
||||
"event_type_has_been_rescheduled_on_time_date": "Din {{title}} har ändrats till {{date}}.",
|
||||
"event_has_been_rescheduled": "Uppdatering - Din bokning har blivit omplanerad",
|
||||
@@ -224,6 +227,7 @@
|
||||
"done": "Klar",
|
||||
"all_done": "Allt klart!",
|
||||
"all_apps": "Alla",
|
||||
"available_apps": "Tillgängliga appar",
|
||||
"check_email_reset_password": "Kolla din e-post. Vi har skickat en länk till dig för att återställa ditt lösenord.",
|
||||
"finish": "Slutför",
|
||||
"few_sentences_about_yourself": "Några meningar om dig själv. Detta kommer att visas på din personliga webbsida.",
|
||||
@@ -404,6 +408,7 @@
|
||||
"add_time_availability": "Lägg till ny tidslucka",
|
||||
"add_an_extra_layer_of_security": "Lägg till ett extra säkerhetslager till ditt konto om ditt lösenord blir stulet.",
|
||||
"2fa": "Tvåfaktorsautentisering",
|
||||
"2fa_disabled": "Tvåfaktorsautentisering kan endast aktiveras för e-post- och lösenordsautentisering",
|
||||
"enable_2fa": "Aktivera tvåfaktorsautentisering",
|
||||
"disable_2fa": "Inaktivera tvåfaktorsautentisering",
|
||||
"disable_2fa_recommendation": "Om du behöver inaktivera 2FA rekommenderar vi att du återaktiverar den så snart som möjligt.",
|
||||
@@ -585,6 +590,20 @@
|
||||
"minutes": "Minuter",
|
||||
"round_robin": "Tur och ordning",
|
||||
"round_robin_description": "Variera möten mellan olika teammedlemmar.",
|
||||
"managed_event": "Hanterad händelse",
|
||||
"username_placeholder": "användarnamn",
|
||||
"managed_event_description": "Skapa och distribuera evenemangstyper i bulk till teammedlemmar",
|
||||
"managed": "Hanterad",
|
||||
"managed_event_url_clarification": "\"användarnamn\" kommer att fyllas i med användarnamnet för de medlemmar som tilldelats",
|
||||
"assign_to": "Tilldela till",
|
||||
"add_members": "Lägg till medlemmar...",
|
||||
"count_members_one": "{{count}} medlem",
|
||||
"count_members_other": "{{count}} medlemmar",
|
||||
"no_assigned_members": "Inga tilldelade medlemmar",
|
||||
"assigned_to": "Tilldelad till",
|
||||
"start_assigning_members_above": "Börja tilldela medlemmar ovan",
|
||||
"locked_fields_admin_description": "Medlemmar kommer inte att kunna redigera detta",
|
||||
"locked_fields_member_description": "Det här alternativet låstes av teamadministratören",
|
||||
"url": "Webbadress",
|
||||
"hidden": "Dold",
|
||||
"readonly": "Skrivskyddad",
|
||||
@@ -736,6 +755,11 @@
|
||||
"minimum_booking_notice": "Senaste tid för att boka",
|
||||
"slot_interval": "Tid mellan bokningar",
|
||||
"slot_interval_default": "Använd händelsens längd (standard)",
|
||||
"delete_event_type": "Radera händelsetyp?",
|
||||
"delete_managed_event_type": "Radera hanterad händelsetyp?",
|
||||
"delete_event_type_description": "Alla som du har delat den här länken med kommer inte längre att kunna boka med hjälp av den.",
|
||||
"delete_managed_event_type_description": "<ul><li>Medlemmar som tilldelats denna händelsetyp kommer också att få sina händelsetyper raderade.</li><li>Alla som de har delat sin länk med kommer inte längre att kunna boka med den.</li></ul>",
|
||||
"confirm_delete_event_type": "Ja, radera",
|
||||
"delete_account": "Radera konto",
|
||||
"confirm_delete_account": "Ja, radera konto",
|
||||
"delete_account_confirmation_message": "Är du säker på att du vill radera ditt {{appName}} konto? Alla som du har delat din kontolänk med kommer inte längre att kunna boka med hjälp av den och eventuella inställningar du har sparat kommer att gå förlorade.",
|
||||
@@ -1126,11 +1150,13 @@
|
||||
"current_username": "Nuvarande användarnamn",
|
||||
"example_1": "Exempel 1",
|
||||
"example_2": "Exempel 2",
|
||||
"booking_question_identifier": "Bokningsfrågeidentifierare",
|
||||
"company_size": "Företagets storlek",
|
||||
"what_help_needed": "Vad behöver du hjälp med?",
|
||||
"variable_format": "Variabelformat",
|
||||
"webhook_subscriber_url_reserved": "Webhook-prenumerantens url är redan definierad",
|
||||
"custom_input_as_variable_info": "Ignorera alla specialtecken i etiketten för ytterligare indata (använd endast bokstäver och siffror), använd versaler för alla bokstäver och ersätt blanktecken med understreck.",
|
||||
"using_booking_questions_as_variables": "Hur använder jag bokningsfrågor som variabler?",
|
||||
"download_desktop_app": "Ladda ned skrivbordsappen",
|
||||
"set_ping_link": "Ange Ping-länk",
|
||||
"rate_limit_exceeded": "Betygsgränsen överskriden",
|
||||
@@ -1176,6 +1202,7 @@
|
||||
"start_of_week": "Början av veckan",
|
||||
"recordings_title": "Inspelningar",
|
||||
"recording": "Inspelning",
|
||||
"happy_scheduling": "Glad schemaläggning",
|
||||
"select_calendars": "Välj vilka kalendrar du vill kontrollera för konflikter för att förhindra dubbelbokningar.",
|
||||
"check_for_conflicts": "Kontrollera om konflikter",
|
||||
"view_recordings": "Visa inspelningar",
|
||||
@@ -1213,6 +1240,7 @@
|
||||
"impersonation": "Imitering",
|
||||
"impersonation_description": "Inställningar för att hantera användarpersonifiering",
|
||||
"users": "Användare",
|
||||
"user": "Användare",
|
||||
"profile_description": "Hantera inställningar för din {{appName}}-profil",
|
||||
"users_description": "Här hittar du en lista med alla användare",
|
||||
"users_listing": "Användarlistning",
|
||||
@@ -1253,6 +1281,8 @@
|
||||
"download_responses": "Ladda ned svar",
|
||||
"download_responses_description": "Ladda ned alla svar till ditt formulär i CSV-format.",
|
||||
"download": "Ladda ned",
|
||||
"download_recording": "Ladda ner inspelning",
|
||||
"recording_from_your_recent_call": "En inspelning från ditt senaste samtal på Cal.com är redo för nedladdning",
|
||||
"create_your_first_form": "Skapa ditt första formulär",
|
||||
"create_your_first_form_description": "Med Dirigeringsformulär kan du ställa kvalificerade frågor och dirigera till rätt person eller händelsetyp.",
|
||||
"create_your_first_webhook": "Skapa din första webhook",
|
||||
@@ -1289,6 +1319,15 @@
|
||||
"exchange_authentication_standard": "Grundläggande autentisering",
|
||||
"exchange_authentication_ntlm": "NTLM-autentisering",
|
||||
"exchange_compression": "GZip-komprimering",
|
||||
"exchange_version": "Exchange-version",
|
||||
"exchange_version_2007_SP1": "2007 SP1",
|
||||
"exchange_version_2010": "2010",
|
||||
"exchange_version_2010_SP1": "2010 SP1",
|
||||
"exchange_version_2010_SP2": "2010 SP2",
|
||||
"exchange_version_2013": "2013",
|
||||
"exchange_version_2013_SP1": "2013 SP1",
|
||||
"exchange_version_2015": "2015",
|
||||
"exchange_version_2016": "2016",
|
||||
"routing_forms_description": "Du kan se alla former och rutter som du har skapat här.",
|
||||
"routing_forms_send_email_owner": "Skicka e-post till ägare",
|
||||
"routing_forms_send_email_owner_description": "Skickar ett e-postmeddelande till ägaren när formuläret skickas",
|
||||
@@ -1343,6 +1382,7 @@
|
||||
"add_limit": "Lägg till gräns",
|
||||
"team_name_required": "Teamnamn krävs",
|
||||
"show_attendees": "Dela deltagarinformation mellan gäster",
|
||||
"how_booking_questions_as_variables": "Hur används bokningsfrågor som variabler?",
|
||||
"format": "Formatera",
|
||||
"uppercase_for_letters": "Använd versaler för alla bokstäver",
|
||||
"replace_whitespaces_underscores": "Ersätt blanktecken med understreck",
|
||||
@@ -1357,6 +1397,7 @@
|
||||
"billing_help_title": "Behöver du något annat?",
|
||||
"billing_help_description": "Om du behöver mer hjälp med fakturering finns vårt supportteam här för att hjälpa till.",
|
||||
"billing_help_cta": "Kontakta support",
|
||||
"ignore_special_characters_booking_questions": "Ignorera specialtecken i din bokningsfrågeidentifierare. Använd endast bokstäver och siffror",
|
||||
"retry": "Försök igen",
|
||||
"fetching_calendars_error": "Det gick inte att hämta dina kalendrar. <1>Försök igen</1> eller kontakta kundtjänst.",
|
||||
"calendar_connection_fail": "Det gick inte att ansluta kalender",
|
||||
@@ -1439,11 +1480,17 @@
|
||||
"fixed_round_robin": "Fast Round Robin",
|
||||
"add_one_fixed_attendee": "Lägg till en fast deltagare och Round Robin för ett antal deltagare.",
|
||||
"calcom_is_better_with_team": "Cal.com är bättre med team",
|
||||
"the_calcom_team": "Cal.com-teamet",
|
||||
"add_your_team_members": "Lägg till teammedlemmar i dina händelsetyper. Använd kollektiv schemaläggning för att inkludera alla eller hitta den lämpligaste personen med Round Robin-schemaläggning.",
|
||||
"booking_limit_reached": "Bokningsgränsen för denna händelsetyp har uppnåtts",
|
||||
"duration_limit_reached": "Varaktighetsgränsen för denna händelsetyp har uppnåtts",
|
||||
"admin_has_disabled": "En administratör har inaktiverat {{appName}}",
|
||||
"disabled_app_affects_event_type": "En administratör har inaktiverat {{appName}} vilket påverkar din händelsetyp {{eventType}}",
|
||||
"event_replaced_notice": "En administratör har ersatt en av dina händelsetyper",
|
||||
"email_subject_slug_replacement": "En teamadministratör har ersatt din händelse /{{slug}}",
|
||||
"email_body_slug_replacement_notice": "En administratör i <strong>{{teamName}}</strong>-teamet har ersatt din händelsetyp <strong>/{{slug}}</strong> med en hanterad händelsetyp som de kontrollerar.",
|
||||
"email_body_slug_replacement_info": "Din länk kommer att fortsätta fungera, men vissa inställningar för den kan ha ändrats. Du kan granska den i händelsetyper.",
|
||||
"email_body_slug_replacement_suggestion": "Om du har några frågor om händelsetypen, kontakta din administratör.<br /><br />Glad schemaläggning, <br />Cal.com-teamet",
|
||||
"disable_payment_app": "Administratören har inaktiverat {{appName}} vilket påverkar din händelsetyp {{title}}. Deltagarna kan fortfarande boka den här händelsetypen, men de uppmanas inte att betala. Du kan dölja händelsetypen för att förhindra detta tills din administratör återaktiverar betalningsmetoden.",
|
||||
"payment_disabled_still_able_to_book": "Deltagarna kan fortfarande boka den här händelsetypen, men de uppmanas inte att betala. Du kan dölja händelsetypen för att förhindra detta tills din administratör återaktiverar betalningsmetoden.",
|
||||
"app_disabled_with_event_type": "Administratören har inaktiverat {{appName}} vilket påverkar din händelsetyp {{title}}.",
|
||||
@@ -1492,6 +1539,7 @@
|
||||
"date_overrides_update_btn": "Uppdatera åsidosättning",
|
||||
"event_type_duplicate_copy_text": "{{slug}}-kopia",
|
||||
"set_as_default": "Ange som standard",
|
||||
"hide_eventtype_details": "Dölj information om händelse",
|
||||
"show_navigation": "Visa navigering",
|
||||
"hide_navigation": "Dölj navigering",
|
||||
"verification_code_sent": "Verifieringskod har skickats",
|
||||
@@ -1505,6 +1553,7 @@
|
||||
"create_your_first_team_webhook_description": "Skapa din första webhook för denna teamhändelsetyp",
|
||||
"create_webhook_team_event_type": "Skapa en webhook för denna teamhändelsetyp",
|
||||
"disable_success_page": "Inaktivera framgångssida (fungerar bara om du har en omdirigerings-URL)",
|
||||
"invalid_admin_password": "Du är administratör men du har inte ett lösenord med minst 15 tecken eller ingen 2FA än",
|
||||
"change_password_admin": "Ändra lösenord för att få administratörsåtkomst",
|
||||
"username_already_taken": "Användarnamnet används redan",
|
||||
"assignment": "Tilldelning",
|
||||
@@ -1537,6 +1586,7 @@
|
||||
"ee_enterprise_license": "\"/ee\" Enterprise-licens",
|
||||
"enterprise_booking_fee": "Från {{enterprise_booking_fee}}/månad",
|
||||
"enterprise_license_includes": "Allt för ett kommersiellt användningsfall",
|
||||
"no_need_to_keep_your_code_open_source": "Inget behov av att behålla din kod som öppen källkod",
|
||||
"repackage_rebrand_resell": "Lätt att ompaketera, omprofilera och sälja vidare",
|
||||
"a_vast_suite_of_enterprise_features": "En omfattande uppsättning Enterprise-funktioner",
|
||||
"free_license_fee": "0,00 USD/månad",
|
||||
@@ -1598,6 +1648,7 @@
|
||||
"default_app_link_title": "Skapa en standardapplänk",
|
||||
"default_app_link_description": "Om du skapar en standardapplänk kan alla händelsetyper som nyligen skapats använda den applänk som du har angett.",
|
||||
"change_default_conferencing_app": "Ange som standard",
|
||||
"organizer_default_conferencing_app": "Arrangörens standardapp",
|
||||
"under_maintenance": "Nere för underhåll",
|
||||
"under_maintenance_description": "{{appName}}-teamet genomför planerat underhåll. Om du har några frågor kan du kontakta supporten.",
|
||||
"event_type_seats": "{{numberOfSeats}} platser",
|
||||
@@ -1612,6 +1663,7 @@
|
||||
"not_enough_seats": "Inte tillräckligt med platser",
|
||||
"form_builder_field_already_exists": "Ett fält med detta namn finns redan",
|
||||
"form_builder_field_add_subtitle": "Anpassa de frågor som ställs på bokningssidan",
|
||||
"show_on_booking_page": "Visa på bokningssidan",
|
||||
"get_started_zapier_templates": "Kom igång med Zapier-mallar",
|
||||
"team_is_unpublished": "{{team}} har avpublicerats",
|
||||
"team_is_unpublished_description": "Den här teamlänken är för närvarande inte tillgänglig. Kontakta teamets ägare eller be dem publicera den.",
|
||||
@@ -1635,6 +1687,14 @@
|
||||
"add_1_option_per_line": "Lägg till ett alternativ per rad",
|
||||
"select_a_router": "Välj en omdirigering",
|
||||
"add_a_new_route": "Lägg till ny omdirigering",
|
||||
"make_informed_decisions": "Ta välgrundade beslut med Insights",
|
||||
"make_informed_decisions_description": "Vår instrumentpanel i Insights visar all aktivitet i ditt team och visar trender som gör det möjligt att schemalägga teamet och fatta beslut på ett bättre sätt.",
|
||||
"view_bookings_across": "Visa bokningar för alla medlemmar",
|
||||
"view_bookings_across_description": "Se vem som får flest bokningar och se till att du får den bästa fördelningen i ditt team",
|
||||
"identify_booking_trends": "Identifiera bokningstrender",
|
||||
"identify_booking_trends_description": "Se vilka tider i veckan och vilka tider under dagen som är populära för dina bokare",
|
||||
"spot_popular_event_types": "Hitta populära händelsetyper",
|
||||
"spot_popular_event_types_description": "Se vilka av dina händelsetyper som får flest klick och bokningar",
|
||||
"no_responses_yet": "Inga svar ännu",
|
||||
"this_will_be_the_placeholder": "Det här blir platshållaren",
|
||||
"this_meeting_has_not_started_yet": "Det här mötet har inte startat ännu",
|
||||
@@ -1646,8 +1706,83 @@
|
||||
"can_you_try_again": "Kan du försöka igen med en annan tid?",
|
||||
"verify": "Verifiera",
|
||||
"timezone_variable": "Tidszon",
|
||||
"timezone_info": "Tidszonen för den person som tar emot",
|
||||
"event_end_time_variable": "Händelsens sluttid",
|
||||
"event_end_time_info": "Händelsens sluttid",
|
||||
"cancel_url_variable": "URL för att avbryta",
|
||||
"cancel_url_info": "Webbadressen fö ratt avbryta bokningen",
|
||||
"reschedule_url_variable": "URL för ombokning",
|
||||
"reschedule_url_info": "Webbadressen för att ändra bokningen",
|
||||
"invalid_event_name_variables": "Det finns en ogiltig variabel i ditt händelsenamn",
|
||||
"select_all": "Välj alla",
|
||||
"default_conferencing_bulk_title": "Uppdatera befintliga händelsetyper i bulk",
|
||||
"default_conferencing_bulk_description": "Uppdatera platserna för de valda händelsetyperna"
|
||||
"members_default_schedule": "Medlemmens standardschema",
|
||||
"set_by_admin": "Ställ in av teamadministratör",
|
||||
"members_default_location": "Medlemmens förvalda plats",
|
||||
"members_default_schedule_description": "Vi kommer att använda varje medlems standardschema för tillgänglighet. De kommer att kunna redigera eller ändra det.",
|
||||
"requires_at_least_one_schedule": "Du måste ha minst ett schema",
|
||||
"default_conferencing_bulk_description": "Uppdatera platserna för de valda händelsetyperna",
|
||||
"locked_for_members": "Låst för medlemmar",
|
||||
"locked_apps_description": "Medlemmarna kommer att kunna se de aktiva apparna, men de kommer inte att kunna ändra appinställningarna",
|
||||
"locked_webhooks_description": "Medlemmarna kommer att kunna se aktiva webhooks men inte kunna redigera inställningar för webhooks",
|
||||
"locked_workflows_description": "Medlemmarna kommer att kunna se de aktiva arbetsflödena, men de kommer inte att kunna ändra några arbetsflödesinställningar",
|
||||
"locked_by_admin": "Låst av administratör",
|
||||
"app_not_connected": "Du har inte anslutit ett {{appName}}-konto.",
|
||||
"connect_now": "Anslut nu",
|
||||
"managed_event_dialog_confirm_button_one": "Ersätt och meddela {{count}} medlem",
|
||||
"managed_event_dialog_confirm_button_other": "Ersätt och meddela {{count}} medlemmar",
|
||||
"managed_event_dialog_title_one": "Webbadressen /{{slug}} finns redan för {{count}} medlem. Vill du ersätta den?",
|
||||
"managed_event_dialog_title_other": "Webbadressen /{{slug}} finns redan för {{count}} medlemmar. Vill du ersätta den?",
|
||||
"managed_event_dialog_information_one": "<strong>{{names}}</strong> använder redan webbadressen <strong>/{{slug}}</strong>.",
|
||||
"managed_event_dialog_information_other": "<strong>{{names}}</strong> använder redan webbadressen <string>/{{slug}}</strong>.",
|
||||
"managed_event_dialog_clarification": "Om du väljer att byta ut den kommer vi att meddela dem. Gå tillbaka och ta bort dem om du inte vill skriva över den.",
|
||||
"review_event_type": "Granska händelsetyp",
|
||||
"looking_for_more_analytics": "Vill du ha fler analyser?",
|
||||
"looking_for_more_insights": "Vill du ha fler insikter?",
|
||||
"add_filter": "Lägg till filter",
|
||||
"select_user": "Välj användare",
|
||||
"select_event_type": "Välj händelsetyp",
|
||||
"select_date_range": "Välj datumintervall",
|
||||
"popular_events": "Populära händelser",
|
||||
"no_event_types_found": "Inga händelsetyper hittade",
|
||||
"average_event_duration": "Genomsnittlig händelselängd",
|
||||
"most_booked_members": "Mest bokade medlemmar",
|
||||
"least_booked_members": "Minst bokade medlemmar",
|
||||
"events_created": "Skapade händelser",
|
||||
"events_completed": "Slutförda händelser",
|
||||
"events_cancelled": "Händelser avbrutna",
|
||||
"events_rescheduled": "Händelser ombokade",
|
||||
"from_last_period": "från senaste perioden",
|
||||
"from_to_date_period": "Från: {{startDate}} till: {{endDate}}",
|
||||
"analytics_for_organisation": "Insikter",
|
||||
"subtitle_analytics": "Läs mer om ditt teams aktivitet",
|
||||
"redirect_url_warning": "Att lägga till en omdirigering kommer att inaktivera framgångssidan. Se till att nämna \"Bokning bekräftad\" på din anpassade framgångssida.",
|
||||
"event_trends": "Händelsetrender",
|
||||
"clear_filters": "Rensa filter",
|
||||
"hold": "Vänta",
|
||||
"on_booking_option": "Samla in betalning vid bokning",
|
||||
"hold_option": "Ta ut en avgift för utebliven ankomst",
|
||||
"card_held": "Kort som innehas",
|
||||
"charge_card": "Debitera kort",
|
||||
"card_charged": "Kort debiterat",
|
||||
"no_show_fee_amount": "{{amount, currency}} i avgift för utebliven ankomst",
|
||||
"no_show_fee": "Avgift för utebliven ankomst",
|
||||
"submit_card": "Använd kort",
|
||||
"submit_payment_information": "Ange betalningsinformation",
|
||||
"meeting_awaiting_payment_method": "Ditt möte väntar på en betalningsmetod",
|
||||
"no_show_fee_charged_email_subject": "Avgift för utebliven ankomst på {{amount, currency}} debiteras för {{title}} {{date}}",
|
||||
"no_show_fee_charged_text_body": "Avgift för utebliven ankomst debiterades",
|
||||
"no_show_fee_charged_subtitle": "Avgift för utebliven ankomst på {{amount, currency}} debiterades för följande händelse",
|
||||
"error_charging_card": "Något gick fel när avgiften för utebliven ankomst debiterades. Försök igen senare.",
|
||||
"collect_no_show_fee": "Samla in avgift för utebliven ankomst",
|
||||
"no_show_fee_charged": "Avgift för utebliven ankomst debiterades",
|
||||
"insights": "Insikter",
|
||||
"testing_workflow_info_message": "När det här arbetsflödet testas, var medveten om att e-postmeddelanden och SMS endast kan schemaläggas minst en timme i förväg",
|
||||
"insights_no_data_found_for_filter": "Inga data hittades för valda filter eller valda datum.",
|
||||
"acknowledge_booking_no_show_fee": "Jag bekräftar att om jag inte deltar i händelsen kommer en avgift på {{amount, currency}} för utebliven ankomst att debiteras mitt kort.",
|
||||
"card_details": "Kortuppgifter",
|
||||
"seats_and_no_show_fee_error": "För närvarande går det inte att aktivera platser och ta ut en avgift för utebliven ankomst",
|
||||
"complete_your_booking": "Slutför din bokning",
|
||||
"complete_your_booking_subject": "Slutför din bokning: {{title}} {{date}}",
|
||||
"email_invite_team": "{{email}} har bjudits in"
|
||||
}
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
{
|
||||
"identity_provider": "Постачальник посвідчень",
|
||||
"trial_days_left": "У вас ще $t(day, {\"count\": {{days}} }) користування пробною PRO-версією",
|
||||
"day_one": "{{count}} день",
|
||||
"day_other": "{{count}} д.",
|
||||
@@ -69,6 +70,8 @@
|
||||
"event_awaiting_approval_subject": "Очікування на схвалення: {{title}}, {{date}}",
|
||||
"event_still_awaiting_approval": "Захід досі чекає на ваше схвалення",
|
||||
"booking_submitted_subject": "Бронювання надіслано: {{title}}, {{date}}",
|
||||
"download_recording_subject": "Завантажити запис: {{title}} о {{date}}",
|
||||
"download_your_recording": "Завантажте запис",
|
||||
"your_meeting_has_been_booked": "Вашу нараду заброньовано",
|
||||
"event_type_has_been_rescheduled_on_time_date": "Ваш {{title}} перенесено на {{date}}.",
|
||||
"event_has_been_rescheduled": "Оновлено – ваш захід перенесено",
|
||||
@@ -224,6 +227,7 @@
|
||||
"done": "Готово",
|
||||
"all_done": "Усе готово!",
|
||||
"all_apps": "Усі",
|
||||
"available_apps": "Доступні додатки",
|
||||
"check_email_reset_password": "Перевірте електронну пошту. Ми надіслали вам посилання для скидання пароля.",
|
||||
"finish": "Готово",
|
||||
"few_sentences_about_yourself": "Кілька речень про вас. Ця інформація з’явиться на сторінці за вашою URL-адресою.",
|
||||
@@ -404,6 +408,7 @@
|
||||
"add_time_availability": "Додати нове часове вікно",
|
||||
"add_an_extra_layer_of_security": "Підвищте рівень захисту свого облікового запису на випадок, якщо у вас викрадуть пароль.",
|
||||
"2fa": "Двоетапна автентифікація",
|
||||
"2fa_disabled": "Двоетапну автентифікацію можна ввімкнути лише для автентифікації за допомогою ел. пошти й пароля",
|
||||
"enable_2fa": "Увімкнути двоетапну автентифікацію",
|
||||
"disable_2fa": "Вимкнути двоетапну автентифікацію",
|
||||
"disable_2fa_recommendation": "Якщо вам потрібно вимкнути двоетапну перевірку, рекомендуємо потім увімкнути її якомога швидше.",
|
||||
@@ -585,6 +590,20 @@
|
||||
"minutes": "Хвилини",
|
||||
"round_robin": "Ротація",
|
||||
"round_robin_description": "Кілька учасників команди призначаються для нарад циклічно й по черзі.",
|
||||
"managed_event": "Керований захід",
|
||||
"username_placeholder": "ім’я користувача",
|
||||
"managed_event_description": "Створюйте й надсилайте типи заходів усім учасникам команди одночасно",
|
||||
"managed": "Здійснюється керування",
|
||||
"managed_event_url_clarification": "в полі «ім’я користувача» буде вказано імена користувачів призначених учасників",
|
||||
"assign_to": "Призначити",
|
||||
"add_members": "Додати учасників…",
|
||||
"count_members_one": "{{count}} учасник",
|
||||
"count_members_other": "{{count}} учасники(-ів)",
|
||||
"no_assigned_members": "Немає призначених учасників",
|
||||
"assigned_to": "Призначити",
|
||||
"start_assigning_members_above": "Почніть призначати учасників вище",
|
||||
"locked_fields_admin_description": "Учасники не зможуть редагувати це",
|
||||
"locked_fields_member_description": "Адміністратор команди заблокував цю опцію",
|
||||
"url": "URL",
|
||||
"hidden": "Приховано",
|
||||
"readonly": "Тільки читання",
|
||||
@@ -736,6 +755,11 @@
|
||||
"minimum_booking_notice": "Мінімальний час до наступного заходу",
|
||||
"slot_interval": "Інтервали між бронюваннями",
|
||||
"slot_interval_default": "Використовувати тривалість заходу (за замовчуванням)",
|
||||
"delete_event_type": "Видалити тип заходу?",
|
||||
"delete_managed_event_type": "Видалити захід керованого типу?",
|
||||
"delete_event_type_description": "Користувачі, з якими ви поділилися цим посиланням, більше не зможуть здійснити бронювання з його допомогою.",
|
||||
"delete_managed_event_type_description": "<ul><li>В учасників, призначених для цього типу заходу, також буде видалено їхні типи заходів.</li><li> Користувачі, з якими вони поділилися посиланням, більше не зможуть здійснити бронювання з його допомогою.</li></ul>",
|
||||
"confirm_delete_event_type": "Так, видалити",
|
||||
"delete_account": "Видалити обліковий запис",
|
||||
"confirm_delete_account": "Так, видалити обліковий запис",
|
||||
"delete_account_confirmation_message": "Справді видалити обліковий запис {{appName}}? Усі, кому ви надавали посилання на свій обліковий запис, більше не зможуть бронювати ваш час за його допомогою. Усі збережені налаштування буде втрачено.",
|
||||
@@ -860,7 +884,7 @@
|
||||
"add_new_calendar": "Додати новий календар",
|
||||
"set_calendar": "Укажіть, куди додавати нові заходи, коли ваш час бронюють.",
|
||||
"delete_schedule": "Видалити розклад",
|
||||
"delete_schedule_description": "Видалення розкладу вилучить його з усіх типів заходів. Цю дію неможливо скасувати.",
|
||||
"delete_schedule_description": "Видалений розклад буде вилучено з усіх типів заходів. Цю дію неможливо скасувати.",
|
||||
"schedule_created_successfully": "Розклад «{{scheduleName}}» створено",
|
||||
"availability_updated_successfully": "Розклад «{{scheduleName}}» оновлено",
|
||||
"schedule_deleted_successfully": "Розклад видалено",
|
||||
@@ -1126,11 +1150,13 @@
|
||||
"current_username": "Поточне ім’я користувача",
|
||||
"example_1": "Приклад 1",
|
||||
"example_2": "Приклад 2",
|
||||
"booking_question_identifier": "Ідентифікатор запитання щодо бронювання",
|
||||
"company_size": "Розмір компанії",
|
||||
"what_help_needed": "З чим саме потрібна допомога?",
|
||||
"variable_format": "Формат змінної",
|
||||
"webhook_subscriber_url_reserved": "URL-адресу для абонента у вебгуку вже визначено",
|
||||
"custom_input_as_variable_info": "Усі спеціальні символи в додатковому введеному підписі пропускаються (використовуються тільки літери та цифри), використовуються лише великі літери, пробіли замінюються на підкреслення.",
|
||||
"using_booking_questions_as_variables": "Як використовувати запитання щодо бронювання як змінні?",
|
||||
"download_desktop_app": "Завантажити додаток для ПК",
|
||||
"set_ping_link": "Установити Ping-посилання",
|
||||
"rate_limit_exceeded": "Перевищено ліміт кількості запитів",
|
||||
@@ -1176,6 +1202,7 @@
|
||||
"start_of_week": "Початок тижня",
|
||||
"recordings_title": "Записи",
|
||||
"recording": "Запис",
|
||||
"happy_scheduling": "Щасливого планування",
|
||||
"select_calendars": "Виберіть, які календарі потрібно перевірити на наявність конфліктів, щоб уникнути подвійних бронювань.",
|
||||
"check_for_conflicts": "Перевірка конфліктів",
|
||||
"view_recordings": "Переглянути записи",
|
||||
@@ -1213,6 +1240,7 @@
|
||||
"impersonation": "Вхід під іншим іменем",
|
||||
"impersonation_description": "Налаштування входу під іншим іменем",
|
||||
"users": "Користувачі",
|
||||
"user": "Користувач",
|
||||
"profile_description": "Керуйте налаштуваннями свого профілю {{appName}}",
|
||||
"users_description": "Тут наведено список усіх користувачів",
|
||||
"users_listing": "Список користувачів",
|
||||
@@ -1253,6 +1281,8 @@
|
||||
"download_responses": "Завантажити відповіді",
|
||||
"download_responses_description": "Завантажте всі відповіді у свою форму у форматі CSV.",
|
||||
"download": "Завантажити",
|
||||
"download_recording": "Завантажити запис",
|
||||
"recording_from_your_recent_call": "Запис вашого попереднього дзвінка в Cal.com готовий до завантаження",
|
||||
"create_your_first_form": "Створіть свою першу форму",
|
||||
"create_your_first_form_description": "Форми переспрямування дають змогу надавати уточнювальні запитання та переходити до відповідної особи або типу заходу.",
|
||||
"create_your_first_webhook": "Створіть свій перший вебгук",
|
||||
@@ -1289,6 +1319,15 @@
|
||||
"exchange_authentication_standard": "Проста автентифікація",
|
||||
"exchange_authentication_ntlm": "Автентифікація NTLM",
|
||||
"exchange_compression": "Стиснення GZip",
|
||||
"exchange_version": "Версія обміну",
|
||||
"exchange_version_2007_SP1": "2007 SP1",
|
||||
"exchange_version_2010": "2010",
|
||||
"exchange_version_2010_SP1": "2010 SP1",
|
||||
"exchange_version_2010_SP2": "2010 SP2",
|
||||
"exchange_version_2013": "2013",
|
||||
"exchange_version_2013_SP1": "2013 SP1",
|
||||
"exchange_version_2015": "2015",
|
||||
"exchange_version_2016": "2016",
|
||||
"routing_forms_description": "Тут можна переглянути всі створені вами форми й переспрямування.",
|
||||
"routing_forms_send_email_owner": "Надіслати власнику електронний лист",
|
||||
"routing_forms_send_email_owner_description": "Коли надсилається форма, власник отримує електронний лист",
|
||||
@@ -1343,6 +1382,7 @@
|
||||
"add_limit": "Додати ліміт",
|
||||
"team_name_required": "Назва команди обов’язкова",
|
||||
"show_attendees": "Поширювати інформацію про учасників серед гостей",
|
||||
"how_booking_questions_as_variables": "Як використовувати запитання щодо бронювання як змінні?",
|
||||
"format": "Формат",
|
||||
"uppercase_for_letters": "Використовувати всі великі літери",
|
||||
"replace_whitespaces_underscores": "Заміняти пробіли підкресленнями",
|
||||
@@ -1357,6 +1397,7 @@
|
||||
"billing_help_title": "Потрібно щось інше?",
|
||||
"billing_help_description": "Якщо вам потрібна додаткова допомога з виставленням рахунків, наша служба підтримки готова її надати.",
|
||||
"billing_help_cta": "Звернутися в службу підтримки",
|
||||
"ignore_special_characters_booking_questions": "Пропускайте спеціальні символи в ідентифікаторі запитання щодо бронювання. Використовуйте лише літери й цифри.",
|
||||
"retry": "Повторити",
|
||||
"fetching_calendars_error": "Під час отримання календарів виникла проблема. <1>Повторіть спробу</1> або зверніться в службу підтримки.",
|
||||
"calendar_connection_fail": "Не вдалося підключитися до календаря",
|
||||
@@ -1439,11 +1480,17 @@
|
||||
"fixed_round_robin": "Фіксоване циклічне планування",
|
||||
"add_one_fixed_attendee": "Додайте одного постійного учасника та циклічно замінюйте кількох інших учасників.",
|
||||
"calcom_is_better_with_team": "З Cal.com краще працювати в командах",
|
||||
"the_calcom_team": "Команда Cal.com",
|
||||
"add_your_team_members": "Додавайте членів своєї команди в типи заходів. Колективне планування дає змогу включати всіх або знаходити найвідповіднішого члена завдяки циклічній ротації.",
|
||||
"booking_limit_reached": "Для цього типу заходу досягнуто ліміт бронювання",
|
||||
"duration_limit_reached": "Досягнуто ліміту тривалості для цього типу заходу",
|
||||
"admin_has_disabled": "Адміністратор вимкнув додаток {{appName}}",
|
||||
"disabled_app_affects_event_type": "Адміністратор вимкнув додаток {{appName}}, що впливає на ваш тип заходу «{{eventType}}»",
|
||||
"event_replaced_notice": "Адміністратор замінив один із типів ваших заходів",
|
||||
"email_subject_slug_replacement": "Адміністратор команди замінив вашу подію /{{slug}}",
|
||||
"email_body_slug_replacement_notice": "Адміністратор команди <strong>{{teamName}}</strong> замінив ваш тип події <strong>/{{slug}}</strong> на керований тип події, який він контролює.",
|
||||
"email_body_slug_replacement_info": "Ваше посилання продовжуватиме працювати, але деякі параметри можуть бути змінені. Дізнайтеся більше в типах подій.",
|
||||
"email_body_slug_replacement_suggestion": "Якщо у вас виникли запитання щодо типу заходу, зверніться до свого адміністратора.<br /><br />Щасливого планування!<br />Команда Cal.com",
|
||||
"disable_payment_app": "Адміністратор вимкнув додаток {{appName}}, що впливає на ваш тип заходу «{{title}}». Учасники все одно можуть бронювати події такого типу, але від них не вимагатиметься оплата. Ви можете приховати цей тип заходу, щоб уникнути цього, і дочекатись активації способу оплати адміністратором.",
|
||||
"payment_disabled_still_able_to_book": "Учасники все одно можуть бронювати події такого типу, але від них не вимагатиметься оплата. Ви можете приховати цей тип заходу, щоб уникнути цього, і дочекатись активації способу оплати адміністратором.",
|
||||
"app_disabled_with_event_type": "Адміністратор вимкнув додаток {{appName}}, що впливає на ваш тип заходу «{{title}}».",
|
||||
@@ -1492,6 +1539,7 @@
|
||||
"date_overrides_update_btn": "Оновити перевизначення",
|
||||
"event_type_duplicate_copy_text": "{{slug}} (копія)",
|
||||
"set_as_default": "Установити за замовчуванням",
|
||||
"hide_eventtype_details": "Приховати інформацію про тип заходу",
|
||||
"show_navigation": "Показати навігацію",
|
||||
"hide_navigation": "Приховати навігацію",
|
||||
"verification_code_sent": "Код перевірки надіслано",
|
||||
@@ -1505,6 +1553,7 @@
|
||||
"create_your_first_team_webhook_description": "Створіть свій перший вебгук для цього типу командного заходу",
|
||||
"create_webhook_team_event_type": "Створити вебгук для цього типу командного заходу",
|
||||
"disable_success_page": "Вимкнути сторінку, де показано результат бронювання (працює тільки з URL-адресою перенаправлення)",
|
||||
"invalid_admin_password": "Ви адміністратор, але у вас немає пароля, що містить принаймні 15 символів, та не налаштовано двоетапну автентифікацію",
|
||||
"change_password_admin": "Змініть пароль, щоб отримати доступ адміністратора",
|
||||
"username_already_taken": "Таке ім’я користувача вже існує",
|
||||
"assignment": "Призначення",
|
||||
@@ -1537,6 +1586,7 @@
|
||||
"ee_enterprise_license": "“/ee” Ліцензія для плану Enterprise",
|
||||
"enterprise_booking_fee": "Від {{enterprise_booking_fee}}/міс.",
|
||||
"enterprise_license_includes": "Усе для комерційного використання",
|
||||
"no_need_to_keep_your_code_open_source": "Немає потреби залишати код відкритим",
|
||||
"repackage_rebrand_resell": "Легко здійснювати перепакування, ребрендинг і перепродаж",
|
||||
"a_vast_suite_of_enterprise_features": "Великий набір корпоративних функцій",
|
||||
"free_license_fee": "$0,00/міс.",
|
||||
@@ -1582,48 +1632,50 @@
|
||||
"available_variables": "Доступні змінні",
|
||||
"scheduler": "{Scheduler}",
|
||||
"no_workflows": "Немає робочих процесів",
|
||||
"change_filter": "Змініть фільтр, щоб побачити особисті та командні робочі процеси.",
|
||||
"change_filter": "Змініть фільтр, щоб побачити особисті й командні робочі процеси.",
|
||||
"recommended_next_steps": "Рекомендовані наступні кроки",
|
||||
"create_a_managed_event": "Створіть подію керованого типу",
|
||||
"meetings_are_better_with_the_right": "Краще проводити наради з потрібними учасниками команди. Запросіть їх зараз.",
|
||||
"create_a_one_one_template": "Створіть шаблон «один на один» для типу заходу та розповсюдьте його між учасниками.",
|
||||
"create_a_one_one_template": "Створіть один шаблон індивідуальної наради для цього типу заходу й розповсюдьте його між учасниками.",
|
||||
"collective_or_roundrobin": "Колективна або циклічна",
|
||||
"book_your_team_members": "Забронюйте час усіх учасників команди одночасно за допомогою колективних подій або виберіть потрібну людину за циклічним принципом.",
|
||||
"event_no_longer_attending_subject": "Більше не відвідує захід «{{title}}» о {{date}}",
|
||||
"event_no_longer_attending_subject": "Більше не відвідує захід «{{title}}», запланований на {{date}}",
|
||||
"no_longer_attending": "Ви більше не відвідуєте цей захід",
|
||||
"attendee_no_longer_attending_subject": "Учасник більше не відвідує захід «{{title}}» о {{date}}",
|
||||
"attendee_no_longer_attending_subject": "Учасник більше не відвідує захід «{{title}}», запланований на {{date}}",
|
||||
"attendee_no_longer_attending": "Учасник більше не відвідує ваш захід",
|
||||
"attendee_no_longer_attending_subtitle": "{{name}} скасував(-ла) участь. Це означає, що на цей час з’явилося вільне місце.",
|
||||
"create_event_on": "Створити захід в календарі",
|
||||
"create_event_on": "Створити захід у календарі",
|
||||
"default_app_link_title": "Установити посилання на додаток за замовчуванням",
|
||||
"default_app_link_description": "Посилання на додаток за замовчуванням використовуватиметься для всіх новостворених типів подій.",
|
||||
"change_default_conferencing_app": "Установити за замовчуванням",
|
||||
"organizer_default_conferencing_app": "Додаток організації за замовчуванням",
|
||||
"under_maintenance": "На обслуговуванні",
|
||||
"under_maintenance_description": "Команда {{appName}} здійснює планове обслуговування. Якщо маєте запитання, зверніться в службу підтримки.",
|
||||
"event_type_seats": "Місць: {{numberOfSeats}}",
|
||||
"booking_questions_title": "Питання про бронювання",
|
||||
"booking_questions_description": "Налаштуйте запитання, які ставитимуться на сторінці бронювання",
|
||||
"booking_questions_title": "Запитання про бронювання",
|
||||
"booking_questions_description": "Налаштуйте запитання, відповіді на які потрібно буде надати на сторінці бронювання",
|
||||
"add_a_booking_question": "Додати запитання",
|
||||
"duplicate_email": "Ця адреса ел. пошти дублюється",
|
||||
"duplicate_email": "Ця електронна адреса дублюється",
|
||||
"booking_with_payment_cancelled": "Більше не можна оплатити цю подію",
|
||||
"booking_with_payment_cancelled_already_paid": "Повернення коштів за це бронювання вже виконано.",
|
||||
"booking_with_payment_cancelled_refunded": "Кошти за бронювання повернено.",
|
||||
"booking_confirmation_failed": "Не вдалося підтвердити бронювання",
|
||||
"not_enough_seats": "Недостатньо місць",
|
||||
"form_builder_field_already_exists": "Поле з такою назвою вже існує",
|
||||
"form_builder_field_add_subtitle": "Налаштуйте запитання, які ставитимуться на сторінці бронювання",
|
||||
"form_builder_field_add_subtitle": "Налаштуйте запитання, відповіді на які потрібно буде надати на сторінці бронювання",
|
||||
"show_on_booking_page": "Показати на сторінці бронювання",
|
||||
"get_started_zapier_templates": "Розпочніть роботу із шаблонами Zapier",
|
||||
"team_is_unpublished": "Публікацію такої команди скасовано: {{team}}",
|
||||
"team_is_unpublished_description": "Посилання на команду зараз недоступне. Зверніться до власника команди або попросіть його опублікувати це посилання.",
|
||||
"team_is_unpublished": "Публікацію команди «{{team}}» скасовано",
|
||||
"team_is_unpublished_description": "Посилання на команду зараз недоступне. Зверніться до власника команди або попросіть його опублікувати команду.",
|
||||
"team_member": "Учасник команди",
|
||||
"a_routing_form": "Форма переспрямування",
|
||||
"form_description_placeholder": "Опис форми",
|
||||
"keep_me_connected_with_form": "Підтримувати зі мною зв’язок за допомогою форми",
|
||||
"fields_in_form_duplicated": "Будь-які зміни, внесені у форму переспрямування і її поля, буде відображено в дублікаті цієї форми.",
|
||||
"form_deleted": "Форму видалено",
|
||||
"delete_form": "Видалити форму",
|
||||
"delete_form": "Видалити цю форму?",
|
||||
"delete_form_action": "Так, видалити форму",
|
||||
"delete_form_confirmation": "Справді видалити цю форму? Користувачі, з якими ви поділилися посиланням, не зможуть забронювати час з її допомогою. Крім того, усі пов’язані відповіді буде видалено.",
|
||||
"delete_form_confirmation": "Користувачі, з якими ви поділилися посиланням, більше не зможуть перейти за ним.",
|
||||
"delete_form_confirmation_2": "Усі пов’язані відповіді буде видалено.",
|
||||
"typeform_redirect_url_copied": "URL-адресу переспрямування Typeform скопійовано! Ви можете перейти й налаштувати URL-адресу у формі Typeform.",
|
||||
"modifications_in_fields_warning": "Зміни в полях і переспрямуваннях нижчезазначених форм будуть відображені в цій формі.",
|
||||
@@ -1635,6 +1687,14 @@
|
||||
"add_1_option_per_line": "Додати 1 варіант на рядок",
|
||||
"select_a_router": "Вибрати форму переспрямування",
|
||||
"add_a_new_route": "Додати нове переспрямування",
|
||||
"make_informed_decisions": "Приймайте обґрунтовані рішення за допомогою аналітики",
|
||||
"make_informed_decisions_description": "У розділі «Аналітика» показано всю діяльність вашої команди та тенденції, які дають змогу краще планувати час команди й приймати рішення.",
|
||||
"view_bookings_across": "Переглянути бронювання всіх учасників",
|
||||
"view_bookings_across_description": "Дізнайтеся, хто отримує найбільше бронювань і забезпечте найкращий розподіл у команді",
|
||||
"identify_booking_trends": "Визначте тенденції бронювання",
|
||||
"identify_booking_trends_description": "Дізнайтесь, які дні тижня і який час популярні в користувачів, які здійснюють бронювання",
|
||||
"spot_popular_event_types": "Визначте популярні типи заходів",
|
||||
"spot_popular_event_types_description": "Дізнайтеся, які типи заходів отримують найбільшу кількість кліків і бронювань",
|
||||
"no_responses_yet": "Поки немає відповідей",
|
||||
"this_will_be_the_placeholder": "Це заповнювач",
|
||||
"this_meeting_has_not_started_yet": "Ця нарада ще не почалася",
|
||||
@@ -1643,11 +1703,86 @@
|
||||
"app_is_connected": "{{dependencyName}} підключено",
|
||||
"requires_app": "Потрібно підключити {{dependencyName}}",
|
||||
"verification_code": "Код перевірки",
|
||||
"can_you_try_again": "Можете спробувати, указавши інший час?",
|
||||
"can_you_try_again": "Виберіть інший час і повторіть спробу.",
|
||||
"verify": "Перевірити",
|
||||
"timezone_variable": "Часовий пояс",
|
||||
"timezone_info": "Часовий пояс одержувача",
|
||||
"event_end_time_variable": "Час завершення заходу",
|
||||
"event_end_time_info": "Час завершення заходу",
|
||||
"cancel_url_variable": "URL-адреса для скасування",
|
||||
"cancel_url_info": "URL-адреса для скасування бронювання",
|
||||
"reschedule_url_variable": "URL-адреса для перенесення",
|
||||
"reschedule_url_info": "URL-адреса для перенесення бронювання",
|
||||
"invalid_event_name_variables": "Назва вашого заходу містить неприпустиму змінну",
|
||||
"select_all": "Вибрати всі",
|
||||
"default_conferencing_bulk_title": "Масове оновлення існуючих типів заходів",
|
||||
"default_conferencing_bulk_description": "Оновіть розташування для вибраних типів заходів"
|
||||
"select_all": "Вибрати все",
|
||||
"default_conferencing_bulk_title": "Масове оновлення наявних типів заходів",
|
||||
"members_default_schedule": "Розклад учасника за замовчуванням",
|
||||
"set_by_admin": "Встановлено адміністратором команди",
|
||||
"members_default_location": "Розташування учасника за замовчуванням",
|
||||
"members_default_schedule_description": "Ми використовуватимемо розклад кожного учасника за замовчуванням. Вони зможуть редагувати або змінювати його.",
|
||||
"requires_at_least_one_schedule": "У вас має бути принаймні один розклад",
|
||||
"default_conferencing_bulk_description": "Оновіть розташування для вибраних типів заходів",
|
||||
"locked_for_members": "Заблоковано для учасників",
|
||||
"locked_apps_description": "Учасники зможуть бачити активні додатки, але не зможуть редагувати будь-які налаштування додатка",
|
||||
"locked_webhooks_description": "Учасники бачитимуть активні вебгуки, але не зможуть змінювати їх налаштування",
|
||||
"locked_workflows_description": "Учасники бачитимуть активні робочі процеси, але не зможуть змінювати їх налаштування",
|
||||
"locked_by_admin": "Заблоковано адміністратором",
|
||||
"app_not_connected": "Ви не під’єднали обліковий запис {{appName}}.",
|
||||
"connect_now": "Під’єднати",
|
||||
"managed_event_dialog_confirm_button_one": "Замінити й повідомити {{count}} учасника",
|
||||
"managed_event_dialog_confirm_button_other": "Замінити й повідомити учасників ({{count}})",
|
||||
"managed_event_dialog_title_one": "URL-адреса /{{slug}} вже існує для {{count}} учасника. Хочете замінити її?",
|
||||
"managed_event_dialog_title_other": "URL-адреса /{{slug}} вже існує для кількох учасників ({{count}}). Хочете замінити її?",
|
||||
"managed_event_dialog_information_one": "<strong>{{names}}</strong> вже використовує URL-адресу <strong>/{{slug}}</strong>.",
|
||||
"managed_event_dialog_information_other": "<strong>{{names}}</strong> вже використовують URL-адресу <string>/{{slug}}</strong>.",
|
||||
"managed_event_dialog_clarification": "Якщо ви захочете замінити її, ми повідомимо їм. Поверніться й вилучіть їх, якщо ви не хочете перезаписувати її.",
|
||||
"review_event_type": "Переглянути тип заходу",
|
||||
"looking_for_more_analytics": "Вам потрібно більше аналітичних даних?",
|
||||
"looking_for_more_insights": "Вам потрібно більше аналітики?",
|
||||
"add_filter": "Додати фільтр",
|
||||
"select_user": "Вибрати користувача",
|
||||
"select_event_type": "Вибрати тип заходу",
|
||||
"select_date_range": "Виберіть діапазон дат",
|
||||
"popular_events": "Популярні заходи",
|
||||
"no_event_types_found": "Типи заходів не знайдено",
|
||||
"average_event_duration": "Середня тривалість заходу",
|
||||
"most_booked_members": "Учасники з найбільшою кількістю бронювань",
|
||||
"least_booked_members": "Учасники з найменшою кількістю бронювань",
|
||||
"events_created": "Заходи створено",
|
||||
"events_completed": "Заходи завершено",
|
||||
"events_cancelled": "Заходи скасовано",
|
||||
"events_rescheduled": "Заходи перенесено",
|
||||
"from_last_period": "після останнього періоду",
|
||||
"from_to_date_period": "З: {{startDate}} До: {{endDate}}",
|
||||
"analytics_for_organisation": "Аналітика",
|
||||
"subtitle_analytics": "Дізнайтеся більше про активність вашої команди",
|
||||
"redirect_url_warning": "Додавання переспрямування вимкне сторінку з результатом бронювання. Переконайтеся, що ви додали текст «Бронювання підтверджено» на свою сторінку з результатом бронювання.",
|
||||
"event_trends": "Тенденції заходів",
|
||||
"clear_filters": "Очистити фільтри",
|
||||
"hold": "Утримати",
|
||||
"on_booking_option": "Отримуйте оплату за бронювання",
|
||||
"hold_option": "Стягнути плату за відсутність",
|
||||
"card_held": "Карту утримано",
|
||||
"charge_card": "Списати з картки",
|
||||
"card_charged": "Списано кошти з картки",
|
||||
"no_show_fee_amount": "Плата за відсутність у розмірі {{amount, currency}}",
|
||||
"no_show_fee": "Плата за відсутність",
|
||||
"submit_card": "Надіслати картку",
|
||||
"submit_payment_information": "Надіслати платіжну інформацію",
|
||||
"meeting_awaiting_payment_method": "Потрібно вказати спосіб оплати для вашої наради",
|
||||
"no_show_fee_charged_email_subject": "Стягнуто плату за відсутність на заході «{{title}}» у розмірі {{amount, currency}} о {{date}}",
|
||||
"no_show_fee_charged_text_body": "Стягнуто плату за відсутність",
|
||||
"no_show_fee_charged_subtitle": "За нижчезазначений захід було стягнуто плату за відсутність у розмірі {{amount, currency}}",
|
||||
"error_charging_card": "Сталася помилка під час стягнення плати за відсутність. Спробуйте ще раз пізніше.",
|
||||
"collect_no_show_fee": "Стягнути плату за відсутність",
|
||||
"no_show_fee_charged": "Стягнуто плату за відсутність",
|
||||
"insights": "Аналітика",
|
||||
"testing_workflow_info_message": "Тестуючи цей робочий процес, зважайте на те, що надсилання електронних листів і SMS-повідомлень можна запланувати принаймні за 1 годину",
|
||||
"insights_no_data_found_for_filter": "Не знайдено жодних даних для вибраного фільтру або вибраних дат.",
|
||||
"acknowledge_booking_no_show_fee": "Я підтверджую, що в разі відсутності на цьому заході з моєї картки буде стягнуто плату за відсутність у розмірі {{amount, currency}}.",
|
||||
"card_details": "Інформація про картку",
|
||||
"seats_and_no_show_fee_error": "Наразі неможливо ввімкнути місця і стягнути плату за відсутність",
|
||||
"complete_your_booking": "Завершіть бронювання",
|
||||
"complete_your_booking_subject": "Завершіть бронювання: {{title}} {{date}}",
|
||||
"email_invite_team": "{{email}} запрошено"
|
||||
}
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
{
|
||||
"identity_provider": "Nhà cung cấp danh tính",
|
||||
"trial_days_left": "Bạn có $t(day, {\"count\": {{days}} }) còn lại trên bản thử nghiệm PRO",
|
||||
"day_one": "{{count}} ngày",
|
||||
"day_other": "{{count}} ngày",
|
||||
@@ -69,6 +70,8 @@
|
||||
"event_awaiting_approval_subject": "Đang chờ phê duyệt: {{title}} lúc {{date}}",
|
||||
"event_still_awaiting_approval": "Một sự kiện vẫn đang chờ bạn phê duyệt",
|
||||
"booking_submitted_subject": "Lịch hẹn đã được gửi: {{title}} lúc {{date}}",
|
||||
"download_recording_subject": "Tải xuống bản ghi âm: {{title}} vào ngày {{date}}",
|
||||
"download_your_recording": "Tải xuống bản ghi âm của bạn",
|
||||
"your_meeting_has_been_booked": "Cuộc họp của bạn đã được đặt",
|
||||
"event_type_has_been_rescheduled_on_time_date": "{{title}} của bạn đã được chuyển sang ngày {{date}}.",
|
||||
"event_has_been_rescheduled": "Đã cập nhật - Sự kiện của bạn đã được đổi",
|
||||
@@ -111,7 +114,7 @@
|
||||
"hidden_team_owner_message": "Bạn cần tài khoản Pro để sử dụng nhóm, bạn sẽ bị ẩn cho đến khi bạn thanh toán.",
|
||||
"link_expires": "p.s. Link hết hạn trong vòng {{expiresIn}} tiếng.",
|
||||
"upgrade_to_per_seat": "Nâng cấp từng tài khoản",
|
||||
"seat_options_doesnt_support_confirmation": "Tuỳ chọn chỗ ngồi không hỗ trợ yêu cầu xác nhận",
|
||||
"seat_options_doesnt_support_confirmation": "Tuỳ chọn chỗ ngồi không thể chọn cho yêu cầu xác nhận đặt lịch",
|
||||
"team_upgrade_seats_details": "Trong tổng {{memberCount}} thành viên của nhóm bạn, {{unpaidCount}} thành viên chưa được thanh toán. Với {{seatPrice}}$/tháng mỗi thành viên, tổng chi phí cho nhóm bạn là {{totalCost}}$/tháng.",
|
||||
"team_upgrade_banner_description": "Cám ơn bạn đã dùng thử gói kế hoạch mới dành cho nhóm. Chúng tôi để ý thấy nhóm \"{{teamName}}\" của bạn cần được nâng cấp.",
|
||||
"team_upgrade_banner_action": "Nâng cấp tại đây",
|
||||
@@ -224,6 +227,7 @@
|
||||
"done": "Xong",
|
||||
"all_done": "Đã xong hết!",
|
||||
"all_apps": "Tất cả",
|
||||
"available_apps": "Ứng dụng hiện có",
|
||||
"check_email_reset_password": "Kiểm tra hộp thư điện tử của bạn. Chúng tôi đã gửi cho bạn một liên kết để đặt lại mật khẩu của bạn.",
|
||||
"finish": "Hoàn thành",
|
||||
"few_sentences_about_yourself": "Vài câu về bản thân bạn - nó sẽ xuất hiện trên trang cá nhân của bạn.",
|
||||
@@ -404,6 +408,7 @@
|
||||
"add_time_availability": "Thêm khoảng thời gian mới",
|
||||
"add_an_extra_layer_of_security": "Thêm một lớp bảo mật bổ sung cho tài khoản của bạn trong trường hợp mật khẩu của bạn bị đánh cắp.",
|
||||
"2fa": "Xác thực hai yếu tố",
|
||||
"2fa_disabled": "Xác thực đa yếu tố chỉ được bật cho chức năng xác thực qua email và mật khẩu",
|
||||
"enable_2fa": "Bật xác thực hai yếu tố",
|
||||
"disable_2fa": "Tắt xác thực hai yếu tố",
|
||||
"disable_2fa_recommendation": "Nếu bạn cần tắt xác thực hai yếu tố, chúng tôi khuyên bạn nên bật lại nó càng sớm càng tốt.",
|
||||
@@ -585,6 +590,8 @@
|
||||
"minutes": "Phút",
|
||||
"round_robin": "Round Robin",
|
||||
"round_robin_description": "Luân phiên những cuộc họp giữa các thành viên trong nhóm.",
|
||||
"managed_event": "Sự kiện được quản lý",
|
||||
"username_placeholder": "tên người dùng",
|
||||
"url": "URL",
|
||||
"hidden": "Ẩn giấu",
|
||||
"readonly": "Chỉ đọc",
|
||||
|
||||
@@ -70,6 +70,8 @@
|
||||
"event_awaiting_approval_subject": "等待批准:{{date}} 的 {{title}}",
|
||||
"event_still_awaiting_approval": "有活动仍在等待您的批准",
|
||||
"booking_submitted_subject": "已提交预订:{{title}},于 {{date}}",
|
||||
"download_recording_subject": "下载录制内容: {{date}} 的 {{title}}",
|
||||
"download_your_recording": "下载您的录制内容",
|
||||
"your_meeting_has_been_booked": "您的会议已预定",
|
||||
"event_type_has_been_rescheduled_on_time_date": "您的 {{title}} 已改期至 {{date}}。",
|
||||
"event_has_been_rescheduled": "已更新 - 您的活动已被重新安排",
|
||||
@@ -225,6 +227,7 @@
|
||||
"done": "完成",
|
||||
"all_done": "全部完成!",
|
||||
"all_apps": "所有应用",
|
||||
"available_apps": "可用应用",
|
||||
"check_email_reset_password": "请检查您的邮箱,我们向您发送了一个重置密码的链接。",
|
||||
"finish": "完成",
|
||||
"few_sentences_about_yourself": "请写一段简单的自我介绍,该介绍将会展示在您的个人链接页面上。",
|
||||
@@ -405,6 +408,7 @@
|
||||
"add_time_availability": "添加新的时间段",
|
||||
"add_an_extra_layer_of_security": "在您的密码被盗时,能给您的账户添加一层额外的安全保护",
|
||||
"2fa": "两步验证",
|
||||
"2fa_disabled": "只能为电子邮件和密码身份验证启用双因素身份验证",
|
||||
"enable_2fa": "启用两步验证",
|
||||
"disable_2fa": "禁用两步验证",
|
||||
"disable_2fa_recommendation": "如果您需要临时禁用两步验证,我们建议尽快重新启用它。",
|
||||
@@ -586,6 +590,20 @@
|
||||
"minutes": "分钟",
|
||||
"round_robin": "轮流模式",
|
||||
"round_robin_description": "和多个团队成员之间轮流举行会议。",
|
||||
"managed_event": "托管活动",
|
||||
"username_placeholder": "用户名",
|
||||
"managed_event_description": "批量创建事件类型并分发给团队成员",
|
||||
"managed": "托管",
|
||||
"managed_event_url_clarification": "“用户名”将填写所分配的成员的用户名",
|
||||
"assign_to": "分配给",
|
||||
"add_members": "添加成员...",
|
||||
"count_members_one": "{{count}} 个成员",
|
||||
"count_members_other": "{{count}} 个成员",
|
||||
"no_assigned_members": "没有已分配的成员",
|
||||
"assigned_to": "已分配给",
|
||||
"start_assigning_members_above": "开始分配上面的成员",
|
||||
"locked_fields_admin_description": "成员将无法编辑此内容",
|
||||
"locked_fields_member_description": "此选项已被团队管理员锁定",
|
||||
"url": "链接",
|
||||
"hidden": "隐藏",
|
||||
"readonly": "只读",
|
||||
@@ -737,6 +755,11 @@
|
||||
"minimum_booking_notice": "最小预约通知提前时间",
|
||||
"slot_interval": "时间段间隔",
|
||||
"slot_interval_default": "使用活动时长 (默认)",
|
||||
"delete_event_type": "删除活动类型?",
|
||||
"delete_managed_event_type": "创建托管活动类型?",
|
||||
"delete_event_type_description": "您将此链接共享给的任何人都将无法再使用它进行预约。",
|
||||
"delete_managed_event_type_description": "<ul><li>分配给此活动类型的成员也将删除其活动类型。</li><li>他们将链接共享给的任何人都将无法再使用它进行预约。</li></ul>",
|
||||
"confirm_delete_event_type": "是,删除",
|
||||
"delete_account": "删除账户",
|
||||
"confirm_delete_account": "是,删除账户",
|
||||
"delete_account_confirmation_message": "您确定要删除您的 {{appName}} 账户? 任何曾通过您的账户链接与您预约过的人都将无法再使用它和您预约,您保存的任何偏好设置都将丢失。",
|
||||
@@ -1127,11 +1150,13 @@
|
||||
"current_username": "当前用户名",
|
||||
"example_1": "示例 1",
|
||||
"example_2": "示例 2",
|
||||
"booking_question_identifier": "预约问题标识符",
|
||||
"company_size": "公司规模",
|
||||
"what_help_needed": "您需要什么帮助?",
|
||||
"variable_format": "变量格式",
|
||||
"webhook_subscriber_url_reserved": "Webhook 订阅者 url 已定义",
|
||||
"custom_input_as_variable_info": "忽略附加输入标签的所有特殊字符(仅使用字母和数字),所有字母均使用大写,并将空格替换为下划线。",
|
||||
"using_booking_questions_as_variables": "如何将预约问题用作变量?",
|
||||
"download_desktop_app": "下载桌面应用",
|
||||
"set_ping_link": "设置 Ping 链接",
|
||||
"rate_limit_exceeded": "已超过速率限制",
|
||||
@@ -1177,6 +1202,7 @@
|
||||
"start_of_week": "一周开始",
|
||||
"recordings_title": "录制",
|
||||
"recording": "录制",
|
||||
"happy_scheduling": "祝日程安排愉快",
|
||||
"select_calendars": "选择要检查冲突的日历,以防止重复预约。",
|
||||
"check_for_conflicts": "检查冲突",
|
||||
"view_recordings": "查看录制",
|
||||
@@ -1214,6 +1240,7 @@
|
||||
"impersonation": "模拟",
|
||||
"impersonation_description": "用于管理用户模拟的设置",
|
||||
"users": "用户",
|
||||
"user": "用户",
|
||||
"profile_description": "管理您的 {{appName}} 个人资料的设置",
|
||||
"users_description": "在此处可以找到所有用户的列表",
|
||||
"users_listing": "用户列表",
|
||||
@@ -1254,6 +1281,8 @@
|
||||
"download_responses": "下载回应",
|
||||
"download_responses_description": "以 CSV 格式下载对表格的所有回复。",
|
||||
"download": "下载",
|
||||
"download_recording": "下载录制内容",
|
||||
"recording_from_your_recent_call": "您最近在 Cal.com 上的通话录制可供下载",
|
||||
"create_your_first_form": "创建您的第一个表格",
|
||||
"create_your_first_form_description": "利用途径表格,您可以提出符合条件的问题,并可根据途径找到正确的人或活动类型。",
|
||||
"create_your_first_webhook": "创建您的第一个 Webhook",
|
||||
@@ -1290,6 +1319,15 @@
|
||||
"exchange_authentication_standard": "基本身份验证",
|
||||
"exchange_authentication_ntlm": "NTLM 身份验证",
|
||||
"exchange_compression": "GZip 压缩",
|
||||
"exchange_version": "Exchange 版本",
|
||||
"exchange_version_2007_SP1": "2007 SP1",
|
||||
"exchange_version_2010": "2010",
|
||||
"exchange_version_2010_SP1": "2010 SP1",
|
||||
"exchange_version_2010_SP2": "2010 SP2",
|
||||
"exchange_version_2013": "2013",
|
||||
"exchange_version_2013_SP1": "2013 SP1",
|
||||
"exchange_version_2015": "2015",
|
||||
"exchange_version_2016": "2016",
|
||||
"routing_forms_description": "您可以在此处看到您创建的所有表格和途径。",
|
||||
"routing_forms_send_email_owner": "向所有者发送电子邮件",
|
||||
"routing_forms_send_email_owner_description": "提交表单后向所有者发送电子邮件",
|
||||
@@ -1344,6 +1382,7 @@
|
||||
"add_limit": "添加限制",
|
||||
"team_name_required": "需要团队名称",
|
||||
"show_attendees": "在访客之间共享参与者信息",
|
||||
"how_booking_questions_as_variables": "如何将预约问题用作变量?",
|
||||
"format": "格式",
|
||||
"uppercase_for_letters": "所有字母均使用大写",
|
||||
"replace_whitespaces_underscores": "将空格替换为下划线",
|
||||
@@ -1358,6 +1397,7 @@
|
||||
"billing_help_title": "还有其他需要?",
|
||||
"billing_help_description": "如果您在支付帐单时需要任何进一步的帮助,我们的支持团队随时为您提供。",
|
||||
"billing_help_cta": "联系支持",
|
||||
"ignore_special_characters_booking_questions": "忽略预约问题标识符中的特殊字符。仅使用字母和数字",
|
||||
"retry": "重试",
|
||||
"fetching_calendars_error": "获取您的日历时出现问题。请<1>重试</1>或联系客户支持。",
|
||||
"calendar_connection_fail": "日历连接失败",
|
||||
@@ -1440,11 +1480,17 @@
|
||||
"fixed_round_robin": "固定轮流模式",
|
||||
"add_one_fixed_attendee": "添加一名固定参与者,并由多名参与者轮流担任。",
|
||||
"calcom_is_better_with_team": "Cal.com 更适合团队",
|
||||
"the_calcom_team": "Cal.com 团队",
|
||||
"add_your_team_members": "将您的团队成员添加到您的活动类型。使用集体日程安排来包括所有人或以轮流安排的方式找到最适合的人员。",
|
||||
"booking_limit_reached": "已达到此活动类型的预约限制",
|
||||
"duration_limit_reached": "已达到此活动类型的时长限制",
|
||||
"admin_has_disabled": "管理员已禁用 {{appName}}",
|
||||
"disabled_app_affects_event_type": "管理员已禁用会影响活动类型 {{eventType}} 的 {{appName}}",
|
||||
"event_replaced_notice": "管理员已替换您的一个活动类型",
|
||||
"email_subject_slug_replacement": "团队管理员已替换您的活动/{{slug}}",
|
||||
"email_body_slug_replacement_notice": "<strong>{{teamName}}</strong> 团队的管理员已将您的活动类型 <strong>{{slug}}</strong> 替换为他们控制的托管活动类型。",
|
||||
"email_body_slug_replacement_info": "您的链接将继续有效,但其某些设置可能已更改。您可以在活动类型中查看它。",
|
||||
"email_body_slug_replacement_suggestion": "如果您对活动类型有任何疑问,请联系您的管理员。<br /><br />祝日程安排愉快,<br />Cal.com 团队",
|
||||
"disable_payment_app": "管理员已禁用会影响活动类型 {{title}} 的 {{appName}}。参与者仍然能预约此类型的活动,但不会被提示付款。您可以隐藏该活动类型来防止出现这种情况,直到管理员重新启用您的支付方式。",
|
||||
"payment_disabled_still_able_to_book": "参与者仍然能预约此类型的活动,但不会被提示付款。您可以隐藏该活动类型来防止出现这种情况,直到管理员重新启用您的支付方式。",
|
||||
"app_disabled_with_event_type": "管理员已禁用会影响活动类型 {{title}} 的 {{appName}}。",
|
||||
@@ -1493,6 +1539,7 @@
|
||||
"date_overrides_update_btn": "更新替代",
|
||||
"event_type_duplicate_copy_text": "{{slug}}-复制",
|
||||
"set_as_default": "设置为默认",
|
||||
"hide_eventtype_details": "隐藏活动类型详细信息",
|
||||
"show_navigation": "显示导航",
|
||||
"hide_navigation": "隐藏导航",
|
||||
"verification_code_sent": "验证码已发送",
|
||||
@@ -1506,6 +1553,7 @@
|
||||
"create_your_first_team_webhook_description": "为此团队活动类型创建第一个 Webhook",
|
||||
"create_webhook_team_event_type": "为此团队活动类型创建 Webhook",
|
||||
"disable_success_page": "禁用“成功”页面 (仅当有重定向链接时才有效)",
|
||||
"invalid_admin_password": "您是管理员,但您的密码长度不足 15 个字符或尚未启用 2FA",
|
||||
"change_password_admin": "更改密码以获取管理员访问权限",
|
||||
"username_already_taken": "用户名已被占用",
|
||||
"assignment": "分配",
|
||||
@@ -1600,6 +1648,7 @@
|
||||
"default_app_link_title": "设置默认应用链接",
|
||||
"default_app_link_description": "设置默认应用链接可以让所有新创建的活动类型使用您设置的应用链接。",
|
||||
"change_default_conferencing_app": "设置为默认",
|
||||
"organizer_default_conferencing_app": "组织者的默认应用",
|
||||
"under_maintenance": "停机维护",
|
||||
"under_maintenance_description": "{{appName}} 团队正在进行计划维护。如果您有任何疑问,请联系支持。",
|
||||
"event_type_seats": "{{numberOfSeats}} 个位置",
|
||||
@@ -1614,6 +1663,7 @@
|
||||
"not_enough_seats": "位置不足",
|
||||
"form_builder_field_already_exists": "具有此名称的字段已存在",
|
||||
"form_builder_field_add_subtitle": "自定义预约页面上提出的问题",
|
||||
"show_on_booking_page": "在预约页面上显示",
|
||||
"get_started_zapier_templates": "开始使用 Zapier 模板",
|
||||
"team_is_unpublished": "{{team}} 已被取消发布",
|
||||
"team_is_unpublished_description": "该团队链接当前不可用。请联系团队所有者或请他们发布。",
|
||||
@@ -1637,6 +1687,14 @@
|
||||
"add_1_option_per_line": "每行添加 1 个选项",
|
||||
"select_a_router": "选择途径",
|
||||
"add_a_new_route": "添加新途径",
|
||||
"make_informed_decisions": "使用洞察做出明智的决策",
|
||||
"make_informed_decisions_description": "我们的“洞察”仪表板会显示您团队中的所有活动,并向您展示趋势,以便更好地进行团队日程安排和制定决策。",
|
||||
"view_bookings_across": "查看所有成员的预约",
|
||||
"view_bookings_across_description": "查看谁收到的预约最多,并确保在团队中实现最佳分配",
|
||||
"identify_booking_trends": "确定预约趋势",
|
||||
"identify_booking_trends_description": "查看一周中的哪些时间和一天中的哪些时间受预约者欢迎",
|
||||
"spot_popular_event_types": "发现热门的活动类型",
|
||||
"spot_popular_event_types_description": "查看哪些活动类型获得最多点击和预约",
|
||||
"no_responses_yet": "尚无回复",
|
||||
"this_will_be_the_placeholder": "这将是占位符",
|
||||
"this_meeting_has_not_started_yet": "此会议尚未开始",
|
||||
@@ -1648,8 +1706,83 @@
|
||||
"can_you_try_again": "可否用不同的时间再次尝试?",
|
||||
"verify": "验证",
|
||||
"timezone_variable": "时区",
|
||||
"timezone_info": "接收人的时区",
|
||||
"event_end_time_variable": "活动结束时间",
|
||||
"event_end_time_info": "活动结束时间",
|
||||
"cancel_url_variable": "取消链接",
|
||||
"cancel_url_info": "取消预约的链接",
|
||||
"reschedule_url_variable": "重新安排链接",
|
||||
"reschedule_url_info": "重新安排预约的链接",
|
||||
"invalid_event_name_variables": "活动名称中存在无效变量",
|
||||
"select_all": "全选",
|
||||
"default_conferencing_bulk_title": "批量更新现有事件类型",
|
||||
"default_conferencing_bulk_description": "更新选定活动类型的位置"
|
||||
"members_default_schedule": "成员的默认时间表",
|
||||
"set_by_admin": "由团队管理员设置",
|
||||
"members_default_location": "成员的默认位置",
|
||||
"members_default_schedule_description": "我们将使用每个成员的默认可预约时间表。他们将能够编辑或更改它。",
|
||||
"requires_at_least_one_schedule": "您需要至少有一个时间表",
|
||||
"default_conferencing_bulk_description": "更新选定活动类型的位置",
|
||||
"locked_for_members": "为成员锁定",
|
||||
"locked_apps_description": "成员将能够查看活动的应用,但不能编辑任何应用设置",
|
||||
"locked_webhooks_description": "成员将能够查看活动的 Webhook,但不能编辑任何 Webhook 设置",
|
||||
"locked_workflows_description": "成员将能够查看活动的工作流程,但不能编辑任何工作流程设置",
|
||||
"locked_by_admin": "被管理员锁定",
|
||||
"app_not_connected": "您尚未连接 {{appName}} 账户。",
|
||||
"connect_now": "立即连接",
|
||||
"managed_event_dialog_confirm_button_one": "替换并通知 {{count}} 个成员",
|
||||
"managed_event_dialog_confirm_button_other": "替换并通知 {{count}} 个成员",
|
||||
"managed_event_dialog_title_one": "{{count}} 个成员的链接/{{slug}} 已存在。是否要将其替换?",
|
||||
"managed_event_dialog_title_other": "{{count}} 个成员的链接/{{slug}} 已存在。是否要将其替换?",
|
||||
"managed_event_dialog_information_one": "<strong>{{names}}</strong> 已经在使用 <strong>/{{slug}}</strong> 链接。",
|
||||
"managed_event_dialog_information_other": "<strong>{{names}}</strong> 已经在使用 <string>/{{slug}}</strong> 链接。",
|
||||
"managed_event_dialog_clarification": "如果您选择将其替换,我们会通知他们。如果您不想将其覆盖,请返回并移除它们。",
|
||||
"review_event_type": "查看活动类型",
|
||||
"looking_for_more_analytics": "寻找更多分析?",
|
||||
"looking_for_more_insights": "寻找更多洞察?",
|
||||
"add_filter": "添加筛选器",
|
||||
"select_user": "选择用户",
|
||||
"select_event_type": "选择活动类型",
|
||||
"select_date_range": "选择日期范围",
|
||||
"popular_events": "热门活动",
|
||||
"no_event_types_found": "未找到任何活动类型",
|
||||
"average_event_duration": "平均活动持续时间",
|
||||
"most_booked_members": "预约最多的成员",
|
||||
"least_booked_members": "预约最少的成员",
|
||||
"events_created": "创建的活动",
|
||||
"events_completed": "完成的活动",
|
||||
"events_cancelled": "取消的活动",
|
||||
"events_rescheduled": "重新安排的活动",
|
||||
"from_last_period": "从上一期间",
|
||||
"from_to_date_period": "开始日期: {{startDate}} 结束日期: {{endDate}}",
|
||||
"analytics_for_organisation": "洞察",
|
||||
"subtitle_analytics": "详细了解团队的活动",
|
||||
"redirect_url_warning": "添加重定向将禁用成功页面。请确保在您的自定义成功页面上提及“预约已确认”。",
|
||||
"event_trends": "活动趋势",
|
||||
"clear_filters": "清除筛选器",
|
||||
"hold": "保留",
|
||||
"on_booking_option": "预约时收款",
|
||||
"hold_option": "收取失约费",
|
||||
"card_held": "卡已保留",
|
||||
"charge_card": "对卡收款",
|
||||
"card_charged": "卡已扣款",
|
||||
"no_show_fee_amount": "{{amount, currency}} 失约费",
|
||||
"no_show_fee": "失约费",
|
||||
"submit_card": "提交卡",
|
||||
"submit_payment_information": "提交付款信息",
|
||||
"meeting_awaiting_payment_method": "您的会议正在等待付款方式",
|
||||
"no_show_fee_charged_email_subject": "已为 {{date}} 的 {{title}} 收取 {{amount, currency}} 的失约费",
|
||||
"no_show_fee_charged_text_body": "已收取失约费",
|
||||
"no_show_fee_charged_subtitle": "已为以下活动收取 {{amount, currency}} 的失约费",
|
||||
"error_charging_card": "收取失约费时出错。请稍后再试。",
|
||||
"collect_no_show_fee": "收取失约费",
|
||||
"no_show_fee_charged": "已收取失约费",
|
||||
"insights": "洞察",
|
||||
"testing_workflow_info_message": "测试此工作流程时请注意,电子邮件和短信只能提前至少 1 小时安排",
|
||||
"insights_no_data_found_for_filter": "未找到所选筛选器或所选日期的数据。",
|
||||
"acknowledge_booking_no_show_fee": "我确认,如果我不参加此活动,我的卡将被收取 {{amount, currency}} 的失约费。",
|
||||
"card_details": "卡详细信息",
|
||||
"seats_and_no_show_fee_error": "当前无法启用位置和收取失约费",
|
||||
"complete_your_booking": "完成您的预约",
|
||||
"complete_your_booking_subject": "完成您的预约: {{date}} 的 {{title}}",
|
||||
"email_invite_team": "{{email}} 已被邀请"
|
||||
}
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
{
|
||||
"identity_provider": "身分提供者",
|
||||
"trial_days_left": "試用 PRO 方案還有 $t(day, {\"count\": {{days}} })",
|
||||
"day_one": "{{count}} 天",
|
||||
"day_other": "{{count}} 天",
|
||||
@@ -69,6 +70,8 @@
|
||||
"event_awaiting_approval_subject": "等待核准:{{date}} 與 {{title}}",
|
||||
"event_still_awaiting_approval": "活動還在等待核准",
|
||||
"booking_submitted_subject": "已提出預約:{{date}} 與 {{title}}",
|
||||
"download_recording_subject": "下載錄製內容:{{date}} 的 {{title}}",
|
||||
"download_your_recording": "下載您的錄製內容",
|
||||
"your_meeting_has_been_booked": "會議已經預定",
|
||||
"event_type_has_been_rescheduled_on_time_date": "您的 {{title}} 已改期至 {{date}}。",
|
||||
"event_has_been_rescheduled": "已更新 - 已經重新預定活動時間",
|
||||
@@ -224,6 +227,7 @@
|
||||
"done": "完成",
|
||||
"all_done": "大功告成!",
|
||||
"all_apps": "所有應用程式",
|
||||
"available_apps": "可用的應用程式",
|
||||
"check_email_reset_password": "請檢查信箱。已經把重設密碼的連結寄出。",
|
||||
"finish": "結束",
|
||||
"few_sentences_about_yourself": "一些關於自己的句子,這會顯示在個人網址頁。",
|
||||
@@ -404,6 +408,7 @@
|
||||
"add_time_availability": "新增時段",
|
||||
"add_an_extra_layer_of_security": "萬一密碼遭竊的時候,為帳號增加額外安全層級。",
|
||||
"2fa": "兩階段認證",
|
||||
"2fa_disabled": "兩階段認證只能針對電子郵件和密碼驗證啟用",
|
||||
"enable_2fa": "開啟兩階段認證",
|
||||
"disable_2fa": "關閉兩階段認證",
|
||||
"disable_2fa_recommendation": "如果有必要關閉兩階段認證,我們建議還是要盡快重新開啟。",
|
||||
@@ -585,6 +590,20 @@
|
||||
"minutes": "分鐘",
|
||||
"round_robin": "循環制",
|
||||
"round_robin_description": "與多位團隊成員進行週期會議。",
|
||||
"managed_event": "受控活動",
|
||||
"username_placeholder": "使用者名稱",
|
||||
"managed_event_description": "大量建立並分配活動類型給團隊成員",
|
||||
"managed": "受控",
|
||||
"managed_event_url_clarification": "「使用者名稱」會填入指派成員的使用者名稱",
|
||||
"assign_to": "指派給",
|
||||
"add_members": "新增成員……",
|
||||
"count_members_one": "{{count}} 名成員",
|
||||
"count_members_other": "{{count}} 名成員",
|
||||
"no_assigned_members": "沒有已指派的成員",
|
||||
"assigned_to": "已指派給",
|
||||
"start_assigning_members_above": "開始指派以上成員",
|
||||
"locked_fields_admin_description": "成員將無法編輯此資訊",
|
||||
"locked_fields_member_description": "此選項已被團隊管理員鎖定",
|
||||
"url": "網址",
|
||||
"hidden": "隱藏",
|
||||
"readonly": "唯讀",
|
||||
@@ -736,6 +755,11 @@
|
||||
"minimum_booking_notice": "最晚預約通知",
|
||||
"slot_interval": "時間單位",
|
||||
"slot_interval_default": "使用活動長度(預設)",
|
||||
"delete_event_type": "要刪除活動類型嗎?",
|
||||
"delete_managed_event_type": "要建立受管活動類型嗎?",
|
||||
"delete_event_type_description": "與您共用此連結的任何使用者將無法再使用此表單預約。",
|
||||
"delete_managed_event_type_description": "<ul><li>獲指派此活動類型的成員的活動類型也將被刪除。</li><li>與他們共用此連結的任何使用者將無法再使用此連結預約。</li></ul>",
|
||||
"confirm_delete_event_type": "是,刪除",
|
||||
"delete_account": "刪除帳號",
|
||||
"confirm_delete_account": "是,刪除帳號",
|
||||
"delete_account_confirmation_message": "確定要刪除 {{appName}} 的帳號?任何分享過帳號連結的人再也無法以此進行預約,而且任何儲存過的偏好設定都會消失。",
|
||||
@@ -1126,11 +1150,13 @@
|
||||
"current_username": "目前使用者名稱",
|
||||
"example_1": "範例 1",
|
||||
"example_2": "範例 2",
|
||||
"booking_question_identifier": "預約問題識別元",
|
||||
"company_size": "公司規模",
|
||||
"what_help_needed": "您需要什麼協助?",
|
||||
"variable_format": "變數格式",
|
||||
"webhook_subscriber_url_reserved": "已定義 Webhook 訂閱者網址",
|
||||
"custom_input_as_variable_info": "忽略額外輸入標籤的所有特殊字元 (只使用字母和數字),所有字母一律使用大寫,並以底線取代空格。",
|
||||
"using_booking_questions_as_variables": "要如何將預約問題當作變數?",
|
||||
"download_desktop_app": "下載桌面版應用程式",
|
||||
"set_ping_link": "設定 Ping 連結",
|
||||
"rate_limit_exceeded": "已超過限速",
|
||||
@@ -1176,6 +1202,7 @@
|
||||
"start_of_week": "一週開始",
|
||||
"recordings_title": "錄製",
|
||||
"recording": "錄製",
|
||||
"happy_scheduling": "排程愉快",
|
||||
"select_calendars": "選擇您想要檢查衝突的行事曆來避免重複預約。",
|
||||
"check_for_conflicts": "檢查衝突",
|
||||
"view_recordings": "檢視錄製內容",
|
||||
@@ -1213,6 +1240,7 @@
|
||||
"impersonation": "模擬",
|
||||
"impersonation_description": "管理使用者模擬的設定",
|
||||
"users": "使用者",
|
||||
"user": "使用者",
|
||||
"profile_description": "管理您的 {{appName}} 個人資料設定",
|
||||
"users_description": "您可以在這裡找到列出所有使用者的名單",
|
||||
"users_listing": "使用者名單",
|
||||
@@ -1253,6 +1281,8 @@
|
||||
"download_responses": "下載回應",
|
||||
"download_responses_description": "以 CSV 格式下載您表單的所有回覆。",
|
||||
"download": "下載",
|
||||
"download_recording": "下載錄製內容",
|
||||
"recording_from_your_recent_call": "來自您最近在 Cal.com 的通話錄製內容已可供下載",
|
||||
"create_your_first_form": "建立您的第一份表單",
|
||||
"create_your_first_form_description": "您可使用引導表單提出符合條件的問題,並引導至適當的人員或活動類型。",
|
||||
"create_your_first_webhook": "建立您的第一個 Webhook",
|
||||
@@ -1289,6 +1319,15 @@
|
||||
"exchange_authentication_standard": "基本驗證",
|
||||
"exchange_authentication_ntlm": "NTLM 驗證",
|
||||
"exchange_compression": "GZip 壓縮",
|
||||
"exchange_version": "Exchange 版本",
|
||||
"exchange_version_2007_SP1": "2007 SP1",
|
||||
"exchange_version_2010": "2010",
|
||||
"exchange_version_2010_SP1": "2010 SP1",
|
||||
"exchange_version_2010_SP2": "2010 SP2",
|
||||
"exchange_version_2013": "2013",
|
||||
"exchange_version_2013_SP1": "2013 SP1",
|
||||
"exchange_version_2015": "2015",
|
||||
"exchange_version_2016": "2016",
|
||||
"routing_forms_description": "您可以在這裡查看您已建立的所有表單和引導路徑。",
|
||||
"routing_forms_send_email_owner": "傳送電子郵件給擁有者",
|
||||
"routing_forms_send_email_owner_description": "表單提交後傳送電子郵件給擁有者",
|
||||
@@ -1343,6 +1382,7 @@
|
||||
"add_limit": "新增限制",
|
||||
"team_name_required": "需提供團隊名稱",
|
||||
"show_attendees": "和賓客分享與會者資訊",
|
||||
"how_booking_questions_as_variables": "如何將預約問題當作變數?",
|
||||
"format": "格式",
|
||||
"uppercase_for_letters": "一律使用大寫字母",
|
||||
"replace_whitespaces_underscores": "以底線取代空格",
|
||||
|
||||
@@ -784,24 +784,6 @@ describe("getSchedule", () => {
|
||||
dateString: plus2DateString,
|
||||
}
|
||||
);
|
||||
|
||||
const scheduleForEventOnADayWithDateOverrideDifferentTimezone = await getSchedule(
|
||||
{
|
||||
eventTypeId: 1,
|
||||
eventTypeSlug: "",
|
||||
startTime: `${plus1DateString}T18:30:00.000Z`,
|
||||
endTime: `${plus2DateString}T18:29:59.999Z`,
|
||||
timeZone: Timezones["+6:00"],
|
||||
},
|
||||
ctx
|
||||
);
|
||||
// it should return the same as this is the utc time
|
||||
expect(scheduleForEventOnADayWithDateOverrideDifferentTimezone).toHaveTimeSlots(
|
||||
["08:30:00.000Z", "09:30:00.000Z", "10:30:00.000Z", "11:30:00.000Z"],
|
||||
{
|
||||
dateString: plus2DateString,
|
||||
}
|
||||
);
|
||||
});
|
||||
|
||||
test("that a user is considered busy when there's a booking they host", async () => {
|
||||
|
||||
@@ -12,6 +12,7 @@ export const appStoreMetadata = {} as AppStoreMetaData;
|
||||
for (const [key, value] of Object.entries(rawAppStoreMetadata)) {
|
||||
appStoreMetadata[key as keyof typeof appStoreMetadata] = {
|
||||
appData: null,
|
||||
dirName: "dirName" in value ? value.dirName : value.slug,
|
||||
__template: "",
|
||||
...value,
|
||||
} as AppStoreMetaData[keyof AppStoreMetaData];
|
||||
|
||||
@@ -24,6 +24,7 @@ import { appKeysSchema as event_type_app_card_zod_ts } from "./templates/event-t
|
||||
import { appKeysSchema as vital_zod_ts } from "./vital/zod";
|
||||
import { appKeysSchema as wordpress_zod_ts } from "./wordpress/zod";
|
||||
import { appKeysSchema as zapier_zod_ts } from "./zapier/zod";
|
||||
import { appKeysSchema as zohocrm_zod_ts } from "./zohocrm/zod";
|
||||
import { appKeysSchema as zoomvideo_zod_ts } from "./zoomvideo/zod";
|
||||
|
||||
export const appKeysSchemas = {
|
||||
@@ -49,5 +50,6 @@ export const appKeysSchemas = {
|
||||
vital: vital_zod_ts,
|
||||
wordpress: wordpress_zod_ts,
|
||||
zapier: zapier_zod_ts,
|
||||
zohocrm: zohocrm_zod_ts,
|
||||
zoomvideo: zoomvideo_zod_ts,
|
||||
};
|
||||
|
||||
@@ -24,6 +24,7 @@ import { appDataSchema as event_type_app_card_zod_ts } from "./templates/event-t
|
||||
import { appDataSchema as vital_zod_ts } from "./vital/zod";
|
||||
import { appDataSchema as wordpress_zod_ts } from "./wordpress/zod";
|
||||
import { appDataSchema as zapier_zod_ts } from "./zapier/zod";
|
||||
import { appDataSchema as zohocrm_zod_ts } from "./zohocrm/zod";
|
||||
import { appDataSchema as zoomvideo_zod_ts } from "./zoomvideo/zod";
|
||||
|
||||
export const appDataSchemas = {
|
||||
@@ -49,5 +50,6 @@ export const appDataSchemas = {
|
||||
vital: vital_zod_ts,
|
||||
wordpress: wordpress_zod_ts,
|
||||
zapier: zapier_zod_ts,
|
||||
zohocrm: zohocrm_zod_ts,
|
||||
zoomvideo: zoomvideo_zod_ts,
|
||||
};
|
||||
|
||||
@@ -352,13 +352,21 @@ type FormActionProps<T> = {
|
||||
action: FormActionType;
|
||||
children?: React.ReactNode;
|
||||
render?: (props: { routingForm: RoutingForm | null; className?: string; label?: string }) => JSX.Element;
|
||||
extraClassNames?: string;
|
||||
} & ButtonProps;
|
||||
|
||||
export const FormAction = forwardRef(function FormAction<T extends typeof Button>(
|
||||
props: FormActionProps<T>,
|
||||
forwardedRef: React.ForwardedRef<HTMLAnchorElement | HTMLButtonElement>
|
||||
) {
|
||||
const { action: actionName, routingForm, children, as: asFromElement, ...additionalProps } = props;
|
||||
const {
|
||||
action: actionName,
|
||||
routingForm,
|
||||
children,
|
||||
as: asFromElement,
|
||||
extraClassNames,
|
||||
...additionalProps
|
||||
} = props;
|
||||
const { appUrl, _delete, toggle } = useContext(actionsCtx);
|
||||
const dropdownCtxValue = useContext(dropdownCtx);
|
||||
const dropdown = dropdownCtxValue?.dropdown;
|
||||
@@ -419,11 +427,17 @@ export const FormAction = forwardRef(function FormAction<T extends typeof Button
|
||||
return <></>;
|
||||
}
|
||||
return (
|
||||
<div {...restProps} className="hover:bg-emphasis self-center rounded-md p-2">
|
||||
<div
|
||||
{...restProps}
|
||||
className={classNames(
|
||||
"sm:hover:bg-subtle self-center rounded-md p-2 hover:bg-gray-200",
|
||||
extraClassNames
|
||||
)}>
|
||||
<Switch
|
||||
checked={!routingForm.disabled}
|
||||
label={label}
|
||||
onCheckedChange={(checked) => toggle.onAction({ routingForm, checked })}
|
||||
labelOnLeading
|
||||
/>
|
||||
</div>
|
||||
);
|
||||
|
||||
@@ -66,8 +66,10 @@ const Actions = ({
|
||||
|
||||
return (
|
||||
<div className="flex items-center">
|
||||
<FormAction className="self-center" data-testid="toggle-form" action="toggle" routingForm={form} />
|
||||
<VerticalDivider />
|
||||
<div className="hidden items-center sm:inline-flex">
|
||||
<FormAction className="self-center" data-testid="toggle-form" action="toggle" routingForm={form} />
|
||||
<VerticalDivider />
|
||||
</div>
|
||||
<ButtonGroup combined containerProps={{ className: "hidden md:inline-flex items-center" }}>
|
||||
<Tooltip content={t("preview")}>
|
||||
<FormAction
|
||||
@@ -186,7 +188,7 @@ const Actions = ({
|
||||
{t("Copy Typeform Redirect Url")}
|
||||
</FormAction>
|
||||
) : null}
|
||||
<DropdownMenuSeparator />
|
||||
<DropdownMenuSeparator className="hidden sm:block" />
|
||||
<FormAction
|
||||
action="_delete"
|
||||
routingForm={form}
|
||||
@@ -196,6 +198,16 @@ const Actions = ({
|
||||
StartIcon={Trash}>
|
||||
{t("delete")}
|
||||
</FormAction>
|
||||
<div className="block sm:hidden">
|
||||
<DropdownMenuSeparator />
|
||||
<FormAction
|
||||
data-testid="toggle-form"
|
||||
action="toggle"
|
||||
routingForm={form}
|
||||
label="Disable Form"
|
||||
extraClassNames="hover:bg-subtle cursor-pointer rounded-[5px] pr-4"
|
||||
/>
|
||||
</div>
|
||||
</FormActionsDropdown>
|
||||
</div>
|
||||
<VerticalDivider />
|
||||
@@ -272,8 +284,8 @@ function SingleForm({ form, appUrl, Page }: SingleFormComponentProps) {
|
||||
subtitle={form.description || ""}
|
||||
backPath={`/${appUrl}/forms`}
|
||||
CTA={<Actions form={form} mutation={mutation} />}>
|
||||
<div className="-mx-4 px-4 sm:px-6 md:-mx-8 md:px-8">
|
||||
<div className="flex flex-col items-center md:flex-row md:items-start">
|
||||
<div className="-mx-4 mt-4 px-4 sm:px-6 md:-mx-8 md:mt-0 md:px-8">
|
||||
<div className="flex flex-col items-center items-baseline md:flex-row md:items-start">
|
||||
<div className="lg:min-w-72 lg:max-w-72 mb-6 md:mr-6">
|
||||
<TextField
|
||||
type="text"
|
||||
|
||||
@@ -253,7 +253,11 @@ export default function RoutingForms({
|
||||
}
|
||||
|
||||
RoutingForms.getLayout = (page: React.ReactElement) => {
|
||||
return <Shell withoutMain={true}>{page}</Shell>;
|
||||
return (
|
||||
<Shell withoutMain={true} hideHeadingOnMobile>
|
||||
{page}
|
||||
</Shell>
|
||||
);
|
||||
};
|
||||
|
||||
export const getServerSideProps = async function getServerSideProps(
|
||||
|
||||
@@ -1,6 +1,5 @@
|
||||
import { useRouter } from "next/router";
|
||||
import { useState } from "react";
|
||||
import { FormattedNumber, IntlProvider } from "react-intl";
|
||||
|
||||
import { useAppContextWithSchema } from "@calcom/app-store/EventTypeAppContext";
|
||||
import AppCard from "@calcom/app-store/_components/AppCard";
|
||||
@@ -47,11 +46,11 @@ const EventTypeAppCard: EventTypeAppCardComponent = function EventTypeAppCard({
|
||||
description={
|
||||
<>
|
||||
<div className="">
|
||||
{t("require_payment")} (0.5% +{" "}
|
||||
<IntlProvider locale="en">
|
||||
<FormattedNumber value={0.1} style="currency" currency={currency} />
|
||||
</IntlProvider>{" "}
|
||||
{t("commission_per_transaction")})
|
||||
{t("payment_app_commission", {
|
||||
paymentFeePercentage: 0.5,
|
||||
fee: 0.1,
|
||||
formatParams: { fee: { currency } },
|
||||
})}
|
||||
</div>
|
||||
</>
|
||||
}>
|
||||
|
||||
@@ -0,0 +1,8 @@
|
||||
import { z } from "zod";
|
||||
|
||||
export const appKeysSchema = z.object({
|
||||
client_id: z.string().min(1),
|
||||
client_secret: z.string().min(1),
|
||||
});
|
||||
|
||||
export const appDataSchema = z.object({});
|
||||
@@ -1,7 +1,13 @@
|
||||
/** @type {import("eslint").Linter.Config} */
|
||||
module.exports = {
|
||||
root: true,
|
||||
extends: ["plugin:playwright/playwright-test", "next", "plugin:prettier/recommended", "turbo"],
|
||||
extends: [
|
||||
"plugin:playwright/playwright-test",
|
||||
"next",
|
||||
"plugin:prettier/recommended",
|
||||
"turbo",
|
||||
"plugin:you-dont-need-lodash-underscore/compatible-warn",
|
||||
],
|
||||
plugins: ["unused-imports"],
|
||||
parserOptions: {
|
||||
tsconfigRootDir: __dirname,
|
||||
|
||||
@@ -25,6 +25,7 @@
|
||||
"@typescript-eslint/parser": "^5.52.0",
|
||||
"eslint": "^8.34.0",
|
||||
"eslint-plugin-unused-imports": "^2.0.0",
|
||||
"eslint-plugin-you-dont-need-lodash-underscore": "^6.12.0",
|
||||
"prettier": "^2.8.6",
|
||||
"prettier-plugin-tailwindcss": "^0.2.5",
|
||||
"tailwind-scrollbar": "^2.0.1",
|
||||
|
||||
@@ -26,9 +26,10 @@ module.exports = {
|
||||
error: "var(--cal-bg-error)",
|
||||
black: "#111111",
|
||||
brand: {
|
||||
default: "var(--cal-brand,'#111827')",
|
||||
emphasis: "var(--cal-brand-emphasis,'#101010')",
|
||||
subtle: "var(--cal-brand-subtle,'#9CA3AF')",
|
||||
default: "var(--cal-brand,#111827)",
|
||||
emphasis: "var(--cal-brand-emphasis,#101010)",
|
||||
subtle: "var(--cal-brand-subtle,#9CA3AF)",
|
||||
accent: "var(--cal-brand-accent,white)",
|
||||
},
|
||||
gray: {
|
||||
50: "#F9FAFB",
|
||||
|
||||
@@ -70,7 +70,10 @@ type EventType = Awaited<ReturnType<typeof getEventType>>;
|
||||
const getUser = (where: Prisma.UserWhereUniqueInput) =>
|
||||
prisma.user.findUnique({
|
||||
where,
|
||||
select: availabilityUserSelect,
|
||||
select: {
|
||||
...availabilityUserSelect,
|
||||
credentials: true,
|
||||
},
|
||||
});
|
||||
|
||||
type User = Awaited<ReturnType<typeof getUser>>;
|
||||
|
||||
@@ -161,6 +161,8 @@
|
||||
<h3>
|
||||
<a href="?only=ns:default">[Dark Theme][Guests(janedoe@example.com and test@example.com)]</a>
|
||||
</h3>
|
||||
<button onclick="Cal('ui',{theme:'light'})" >Toggle to Light</button>
|
||||
|
||||
<i class="last-action"> You would see last Booking page action in my place </i>
|
||||
<div>
|
||||
<div class="place" style="width: 100%"></div>
|
||||
|
||||
@@ -19,7 +19,8 @@ export const PermissionContainer: FC<AdminRequiredProps> = ({
|
||||
const session = useSession();
|
||||
|
||||
// Admin can do everything
|
||||
if (session.data?.user.role !== roleRequired || !UserPermissionRole.ADMIN) return null;
|
||||
if (session.data?.user.role !== roleRequired && session.data?.user.role != UserPermissionRole.ADMIN)
|
||||
return null;
|
||||
const Component = as ?? Fragment;
|
||||
return <Component {...rest}>{children}</Component>;
|
||||
};
|
||||
|
||||
@@ -34,7 +34,7 @@ export default function BookingLayout({
|
||||
...rest
|
||||
}: { children: React.ReactNode } & ComponentProps<typeof Shell>) {
|
||||
return (
|
||||
<Shell {...rest}>
|
||||
<Shell {...rest} hideHeadingOnMobile>
|
||||
<div className="flex max-w-6xl flex-col">
|
||||
<div className="flex flex-col lg:flex-row">
|
||||
<HorizontalTabs tabs={tabs} />
|
||||
|
||||
@@ -38,7 +38,7 @@ async function getBookingToDelete(id: number | undefined, uid: string | undefine
|
||||
user: {
|
||||
select: {
|
||||
id: true,
|
||||
credentials: true,
|
||||
credentials: true, // Not leaking at the moment, be careful with
|
||||
email: true,
|
||||
timeZone: true,
|
||||
name: true,
|
||||
|
||||
@@ -197,7 +197,12 @@ const getEventTypesFromDB = async (eventTypeId: number) => {
|
||||
id: true,
|
||||
customInputs: true,
|
||||
disableGuests: true,
|
||||
users: userSelect,
|
||||
users: {
|
||||
select: {
|
||||
credentials: true,
|
||||
...userSelect.select,
|
||||
},
|
||||
},
|
||||
slug: true,
|
||||
team: {
|
||||
select: {
|
||||
@@ -253,7 +258,12 @@ const getEventTypesFromDB = async (eventTypeId: number) => {
|
||||
hosts: {
|
||||
select: {
|
||||
isFixed: true,
|
||||
user: userSelect,
|
||||
user: {
|
||||
select: {
|
||||
credentials: true,
|
||||
...userSelect.select,
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
availability: {
|
||||
@@ -277,7 +287,7 @@ const getEventTypesFromDB = async (eventTypeId: number) => {
|
||||
};
|
||||
};
|
||||
|
||||
type IsFixedAwareUser = User & { isFixed: boolean };
|
||||
type IsFixedAwareUser = User & { isFixed: boolean; credentials: Credential[] };
|
||||
|
||||
async function ensureAvailableUsers(
|
||||
eventType: Awaited<ReturnType<typeof getEventTypesFromDB>> & {
|
||||
@@ -623,6 +633,7 @@ async function handler(
|
||||
},
|
||||
select: {
|
||||
...userSelect.select,
|
||||
credentials: true, // Don't leak to client
|
||||
metadata: true,
|
||||
},
|
||||
})
|
||||
@@ -654,7 +665,10 @@ async function handler(
|
||||
where: {
|
||||
id: eventType.userId,
|
||||
},
|
||||
...userSelect,
|
||||
select: {
|
||||
credentials: true, // Don't leak to client
|
||||
...userSelect.select,
|
||||
},
|
||||
});
|
||||
if (!eventTypeUser) {
|
||||
log.warn({ message: "NewBooking: eventTypeUser.notFound" });
|
||||
@@ -683,8 +697,7 @@ async function handler(
|
||||
return aIndex - bIndex;
|
||||
});
|
||||
const firstUsersMetadata = userMetadataSchema.parse(users[0].metadata);
|
||||
const app = getAppFromSlug(firstUsersMetadata?.defaultConferencingApp?.appSlug);
|
||||
locationBodyString = app?.appData?.location?.type || locationBodyString;
|
||||
locationBodyString = firstUsersMetadata?.defaultConferencingApp?.appLink || locationBodyString;
|
||||
defaultLocationUrl = firstUsersMetadata?.defaultConferencingApp?.appLink;
|
||||
}
|
||||
|
||||
@@ -840,6 +853,7 @@ async function handler(
|
||||
|
||||
const calEventUserFieldsResponses =
|
||||
"calEventUserFieldsResponses" in reqBody ? reqBody.calEventUserFieldsResponses : null;
|
||||
|
||||
let evt: CalendarEvent = {
|
||||
type: eventType.title,
|
||||
title: getEventName(eventNameObject), //this needs to be either forced in english, or fetched for each attendee and organizer separately
|
||||
|
||||
@@ -3,7 +3,7 @@ import { Controller, useForm } from "react-hook-form";
|
||||
|
||||
import dayjs from "@calcom/dayjs";
|
||||
import type { TApiKeys } from "@calcom/ee/api-keys/components/ApiKeyListItem";
|
||||
import LicenseRequired from "@calcom/ee/common/components/v2/LicenseRequired";
|
||||
import LicenseRequired from "@calcom/ee/common/components/LicenseRequired";
|
||||
import { useLocale } from "@calcom/lib/hooks/useLocale";
|
||||
import { trpc } from "@calcom/trpc/react";
|
||||
import { Button, DatePicker, DialogFooter, Form, showToast, Switch, TextField, Tooltip } from "@calcom/ui";
|
||||
|
||||
@@ -1,14 +1,9 @@
|
||||
/**
|
||||
* @deprecated file
|
||||
* All new changes should be made to the V2 file in
|
||||
* `/packages/features/ee/common/components/v2/LicenseRequired.tsx`
|
||||
*/
|
||||
import DOMPurify from "dompurify";
|
||||
import { useSession } from "next-auth/react";
|
||||
import type { AriaRole, ComponentType } from "react";
|
||||
import React, { Fragment } from "react";
|
||||
|
||||
import { APP_NAME, WEBAPP_URL } from "@calcom/lib/constants";
|
||||
import { APP_NAME, CONSOLE_URL, SUPPORT_MAIL_ADDRESS, WEBAPP_URL } from "@calcom/lib/constants";
|
||||
import { useLocale } from "@calcom/lib/hooks/useLocale";
|
||||
import { EmptyScreen } from "@calcom/ui";
|
||||
import { AlertTriangle } from "@calcom/ui/components/icon";
|
||||
@@ -20,20 +15,15 @@ type LicenseRequiredProps = {
|
||||
children: React.ReactNode;
|
||||
};
|
||||
|
||||
/**
|
||||
* @deprecated file
|
||||
* All new changes should be made to the V2 file in
|
||||
* `/packages/features/ee/common/components/v2/LicenseRequired.tsx`
|
||||
* This component will only render it's children if the installation has a valid
|
||||
* license.
|
||||
*/
|
||||
const LicenseRequired = ({ children, as = "", ...rest }: LicenseRequiredProps) => {
|
||||
const { t } = useLocale();
|
||||
const session = useSession();
|
||||
const { t } = useLocale();
|
||||
const Component = as || Fragment;
|
||||
const hasValidLicense = session.data ? session.data.hasValidLicense : null;
|
||||
|
||||
return (
|
||||
<Component {...rest}>
|
||||
{session.data?.hasValidLicense ? (
|
||||
{hasValidLicense === null || hasValidLicense ? (
|
||||
children
|
||||
) : (
|
||||
<EmptyScreen
|
||||
@@ -44,13 +34,12 @@ const LicenseRequired = ({ children, as = "", ...rest }: LicenseRequiredProps) =
|
||||
dangerouslySetInnerHTML={{
|
||||
__html: DOMPurify.sanitize(
|
||||
t("enterprise_license_description", {
|
||||
consoleUrl: `<a href="https://go.cal.com/console" target="_blank" class="underline">
|
||||
consoleUrl: `<a href="${CONSOLE_URL}" target="_blank" rel="noopener noreferrer" class="underline">
|
||||
${APP_NAME}
|
||||
</a>`,
|
||||
setupUrl: `<a href="${WEBAPP_URL}/auth/setup">/auth/setup</a>`,
|
||||
supportMail: `<a href="mailto:sales@cal.com" class="underline">
|
||||
sales@cal.com
|
||||
</a>`,
|
||||
setupUrl: `<a href="${WEBAPP_URL}/auth/setup" class="underline">/auth/setup</a>`,
|
||||
supportMail: `<a href="mailto:${SUPPORT_MAIL_ADDRESS}" class="underline">
|
||||
${SUPPORT_MAIL_ADDRESS}</a>`,
|
||||
})
|
||||
),
|
||||
}}
|
||||
|
||||
@@ -1,64 +0,0 @@
|
||||
import DOMPurify from "dompurify";
|
||||
import { useSession } from "next-auth/react";
|
||||
import type { AriaRole, ComponentType } from "react";
|
||||
import React, { Fragment } from "react";
|
||||
|
||||
import { APP_NAME, CONSOLE_URL, SUPPORT_MAIL_ADDRESS, WEBAPP_URL } from "@calcom/lib/constants";
|
||||
import { useLocale } from "@calcom/lib/hooks/useLocale";
|
||||
import { EmptyScreen } from "@calcom/ui";
|
||||
import { AlertTriangle } from "@calcom/ui/components/icon";
|
||||
|
||||
type LicenseRequiredProps = {
|
||||
as?: keyof JSX.IntrinsicElements | "";
|
||||
className?: string;
|
||||
role?: AriaRole | undefined;
|
||||
children: React.ReactNode;
|
||||
};
|
||||
|
||||
const LicenseRequired = ({ children, as = "", ...rest }: LicenseRequiredProps) => {
|
||||
const session = useSession();
|
||||
const { t } = useLocale();
|
||||
const Component = as || Fragment;
|
||||
const hasValidLicense = session.data ? session.data.hasValidLicense : null;
|
||||
|
||||
return (
|
||||
<Component {...rest}>
|
||||
{hasValidLicense === null || hasValidLicense ? (
|
||||
children
|
||||
) : (
|
||||
<EmptyScreen
|
||||
Icon={AlertTriangle}
|
||||
headline={t("enterprise_license")}
|
||||
description={
|
||||
<div
|
||||
dangerouslySetInnerHTML={{
|
||||
__html: DOMPurify.sanitize(
|
||||
t("enterprise_license_description", {
|
||||
consoleUrl: `<a href="${CONSOLE_URL}" target="_blank" rel="noopener noreferrer" class="underline">
|
||||
${APP_NAME}
|
||||
</a>`,
|
||||
setupUrl: `<a href="${WEBAPP_URL}/auth/setup" class="underline">/auth/setup</a>`,
|
||||
supportMail: `<a href="mailto:${SUPPORT_MAIL_ADDRESS}" class="underline">
|
||||
${SUPPORT_MAIL_ADDRESS}</a>`,
|
||||
})
|
||||
),
|
||||
}}
|
||||
/>
|
||||
}
|
||||
/>
|
||||
)}
|
||||
</Component>
|
||||
);
|
||||
};
|
||||
|
||||
export const withLicenseRequired =
|
||||
<T extends JSX.IntrinsicAttributes>(Component: ComponentType<T>) =>
|
||||
// eslint-disable-next-line react/display-name
|
||||
(hocProps: T) =>
|
||||
(
|
||||
<LicenseRequired>
|
||||
<Component {...hocProps} />
|
||||
</LicenseRequired>
|
||||
);
|
||||
|
||||
export default LicenseRequired;
|
||||
@@ -55,7 +55,6 @@ async function getBooking(bookingId: number) {
|
||||
select: {
|
||||
id: true,
|
||||
username: true,
|
||||
credentials: true,
|
||||
timeZone: true,
|
||||
email: true,
|
||||
name: true,
|
||||
@@ -176,9 +175,11 @@ async function handlePaymentSuccess(event: Stripe.Event) {
|
||||
eventTypeRaw = await getEventType(booking.eventTypeId);
|
||||
}
|
||||
|
||||
const { user } = booking;
|
||||
const { user: userWithCredentials } = booking;
|
||||
|
||||
if (!user) throw new HttpCode({ statusCode: 204, message: "No user found" });
|
||||
if (!userWithCredentials) throw new HttpCode({ statusCode: 204, message: "No user found" });
|
||||
|
||||
const { credentials, ...user } = userWithCredentials;
|
||||
|
||||
const t = await getTranslation(user.locale ?? "en", "common");
|
||||
const attendeesListPromises = booking.attendees.map(async (attendee) => {
|
||||
@@ -227,7 +228,7 @@ async function handlePaymentSuccess(event: Stripe.Event) {
|
||||
|
||||
const isConfirmed = booking.status === BookingStatus.ACCEPTED;
|
||||
if (isConfirmed) {
|
||||
const eventManager = new EventManager(user);
|
||||
const eventManager = new EventManager(userWithCredentials);
|
||||
const scheduleResult = await eventManager.create(evt);
|
||||
bookingData.references = { create: scheduleResult.referencesToCreate };
|
||||
}
|
||||
@@ -255,7 +256,14 @@ async function handlePaymentSuccess(event: Stripe.Event) {
|
||||
await prisma.$transaction([paymentUpdate, bookingUpdate]);
|
||||
|
||||
if (!isConfirmed && !eventTypeRaw?.requiresConfirmation) {
|
||||
await handleConfirmation({ user, evt, prisma, bookingId: booking.id, booking, paid: true });
|
||||
await handleConfirmation({
|
||||
user: userWithCredentials,
|
||||
evt,
|
||||
prisma,
|
||||
bookingId: booking.id,
|
||||
booking,
|
||||
paid: true,
|
||||
});
|
||||
} else {
|
||||
await sendScheduledEmails({ ...evt });
|
||||
}
|
||||
@@ -276,14 +284,32 @@ const handleSetupSuccess = async (event: Stripe.Event) => {
|
||||
|
||||
if (!payment?.data || !payment?.id) throw new HttpCode({ statusCode: 204, message: "Payment not found" });
|
||||
|
||||
const { booking, user, evt, eventTypeRaw } = await getBooking(payment.bookingId);
|
||||
const { user, evt, eventTypeRaw } = await getBooking(payment.bookingId);
|
||||
|
||||
const bookingData: Prisma.BookingUpdateInput = {
|
||||
paid: true,
|
||||
};
|
||||
|
||||
const userWithCredentials = await prisma.user.findUnique({
|
||||
where: {
|
||||
id: user.id,
|
||||
},
|
||||
select: {
|
||||
id: true,
|
||||
username: true,
|
||||
timeZone: true,
|
||||
email: true,
|
||||
name: true,
|
||||
locale: true,
|
||||
destinationCalendar: true,
|
||||
credentials: true,
|
||||
},
|
||||
});
|
||||
|
||||
if (!userWithCredentials) throw new HttpCode({ statusCode: 204, message: "No user found" });
|
||||
|
||||
if (!eventTypeRaw?.requiresConfirmation) {
|
||||
const eventManager = new EventManager(user);
|
||||
const eventManager = new EventManager(userWithCredentials);
|
||||
const scheduleResult = await eventManager.create(evt);
|
||||
bookingData.references = { create: scheduleResult.referencesToCreate };
|
||||
bookingData.status = BookingStatus.ACCEPTED;
|
||||
|
||||
@@ -57,19 +57,17 @@ const PaymentForm = (props: Props) => {
|
||||
email: router.query.email,
|
||||
};
|
||||
if (paymentOption === "HOLD" && "setupIntent" in props.payment.data) {
|
||||
const setupIntentData = props.payment.data as unknown as StripeSetupIntentData;
|
||||
payload = await stripe.confirmSetup({
|
||||
elements,
|
||||
confirmParams: {
|
||||
return_url: `${CAL_URL}/booking/${props.bookingUid}`,
|
||||
return_url: props.eventType.successRedirectUrl || `${CAL_URL}/booking/${props.bookingUid}`,
|
||||
},
|
||||
});
|
||||
} else if (paymentOption === "ON_BOOKING") {
|
||||
const paymentData = props.payment.data as unknown as StripePaymentData;
|
||||
payload = await stripe.confirmPayment({
|
||||
elements,
|
||||
confirmParams: {
|
||||
return_url: `${CAL_URL}/booking/${props.bookingUid}`,
|
||||
return_url: props.eventType.successRedirectUrl || `${CAL_URL}/booking/${props.bookingUid}`,
|
||||
},
|
||||
});
|
||||
}
|
||||
|
||||
@@ -2,7 +2,6 @@ import classNames from "classnames";
|
||||
import Head from "next/head";
|
||||
import type { FC } from "react";
|
||||
import { useEffect, useState } from "react";
|
||||
import { FormattedNumber, IntlProvider } from "react-intl";
|
||||
|
||||
import { getSuccessPageLocationMessage } from "@calcom/app-store/locations";
|
||||
import dayjs from "@calcom/dayjs";
|
||||
@@ -19,7 +18,7 @@ import type { PaymentPageProps } from "../pages/payment";
|
||||
import PaymentComponent from "./Payment";
|
||||
|
||||
const PaymentPage: FC<PaymentPageProps> = (props) => {
|
||||
const { t } = useLocale();
|
||||
const { t, i18n } = useLocale();
|
||||
const [is24h, setIs24h] = useState(isBrowserLocale24h());
|
||||
const [date, setDate] = useState(dayjs.utc(props.booking.startTime));
|
||||
const [timezone, setTimezone] = useState<string | null>(null);
|
||||
@@ -107,13 +106,10 @@ const PaymentPage: FC<PaymentPageProps> = (props) => {
|
||||
{props.payment.paymentOption === "HOLD" ? t("no_show_fee") : t("price")}
|
||||
</div>
|
||||
<div className="col-span-2 mb-6 font-semibold">
|
||||
<IntlProvider locale="en">
|
||||
<FormattedNumber
|
||||
value={paymentAppData.price / 100.0}
|
||||
style="currency"
|
||||
currency={paymentAppData?.currency?.toUpperCase()}
|
||||
/>
|
||||
</IntlProvider>
|
||||
{new Intl.NumberFormat(i18n.language, {
|
||||
style: "currency",
|
||||
currency: paymentAppData.currency,
|
||||
}).format(paymentAppData.price / 100.0)}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
import { useState } from "react";
|
||||
|
||||
import ConnectionInfo from "@calcom/ee/sso/components/ConnectionInfo";
|
||||
import LicenseRequired from "@calcom/features/ee/common/components/v2/LicenseRequired";
|
||||
import LicenseRequired from "@calcom/features/ee/common/components/LicenseRequired";
|
||||
import OIDCConnection from "@calcom/features/ee/sso/components/OIDCConnection";
|
||||
import SAMLConnection from "@calcom/features/ee/sso/components/SAMLConnection";
|
||||
import { useLocale } from "@calcom/lib/hooks/useLocale";
|
||||
|
||||
@@ -187,12 +187,12 @@ export default function HelpMenuItem({ onHelpItemSelect }: HelpMenuItemProps) {
|
||||
<p className="">{t("specific_issue")}</p>
|
||||
<button
|
||||
className="hover:text-emphasis text-defualt font-medium underline"
|
||||
onClick={() => {
|
||||
onClick={async () => {
|
||||
setActive(true);
|
||||
if (isFreshChatEnabled) {
|
||||
setFreshChat(true);
|
||||
} else if (isInterComEnabled) {
|
||||
open();
|
||||
await open();
|
||||
} else {
|
||||
loadChat({ open: true });
|
||||
}
|
||||
|
||||
@@ -15,8 +15,8 @@ export default function IntercomMenuItem(props: IntercomMenuItemProps) {
|
||||
|
||||
return (
|
||||
<button
|
||||
onClick={() => {
|
||||
open();
|
||||
onClick={async () => {
|
||||
await open();
|
||||
onHelpItemSelect();
|
||||
}}
|
||||
className="hover:bg-subtle hover:text-emphasis text-default flex w-full px-5 py-2 pr-4 text-sm font-medium">
|
||||
|
||||
@@ -25,12 +25,21 @@ export const useIntercom = () => {
|
||||
const { hasPaidPlan } = useHasPaidPlan();
|
||||
const { hasTeamPlan } = useHasTeamPlan();
|
||||
|
||||
const open = () => {
|
||||
const open = async () => {
|
||||
let userHash;
|
||||
|
||||
const req = await fetch(`/api/intercom-hash`);
|
||||
const res = await req.json();
|
||||
if (res?.hash) {
|
||||
userHash = res.hash;
|
||||
}
|
||||
|
||||
hookData.boot({
|
||||
name: data?.name ?? "",
|
||||
email: data?.email,
|
||||
userId: String(data?.id),
|
||||
createdAt: String(dayjs(data?.createdDate).unix()),
|
||||
...(userHash && { userHash }),
|
||||
customAttributes: {
|
||||
//keys should be snake cased
|
||||
user_name: data?.username,
|
||||
|
||||
@@ -29,8 +29,8 @@ import {
|
||||
import { ExternalLink, MoreHorizontal, Edit2, Lock, Trash } from "@calcom/ui/components/icon";
|
||||
|
||||
import MemberChangeRoleModal from "./MemberChangeRoleModal";
|
||||
import TeamAvailabilityModal from "./TeamAvailabilityModal";
|
||||
import TeamPill, { TeamRole } from "./TeamPill";
|
||||
import TeamAvailabilityModal from "./v2/TeamAvailabilityModal";
|
||||
|
||||
interface Props {
|
||||
team: RouterOutputs["viewer"]["teams"]["get"];
|
||||
|
||||
@@ -1,13 +1,14 @@
|
||||
import { useEffect, useState } from "react";
|
||||
|
||||
import dayjs from "@calcom/dayjs";
|
||||
import LicenseRequired from "@calcom/features/ee/common/components/LicenseRequired";
|
||||
import { WEBAPP_URL } from "@calcom/lib/constants";
|
||||
import { useLocale } from "@calcom/lib/hooks/useLocale";
|
||||
import type { RouterOutputs } from "@calcom/trpc/react";
|
||||
import { trpc } from "@calcom/trpc/react";
|
||||
import type { ITimezone } from "@calcom/ui";
|
||||
import { Avatar, DatePickerField as DatePicker, Select, TimezoneSelect } from "@calcom/ui";
|
||||
import { Avatar, DatePicker, Label, Select, TimezoneSelect } from "@calcom/ui";
|
||||
|
||||
import LicenseRequired from "../../common/components/LicenseRequired";
|
||||
import TeamAvailabilityTimes from "./TeamAvailabilityTimes";
|
||||
|
||||
interface Props {
|
||||
@@ -21,6 +22,9 @@ export default function TeamAvailabilityModal(props: Props) {
|
||||
const [selectedTimeZone, setSelectedTimeZone] = useState<ITimezone>(
|
||||
localStorage.getItem("timeOption.preferredTimeZone") || dayjs.tz.guess()
|
||||
);
|
||||
|
||||
const { t } = useLocale();
|
||||
|
||||
const [frequency, setFrequency] = useState<15 | 30 | 60>(30);
|
||||
|
||||
useEffect(() => {
|
||||
@@ -29,66 +33,71 @@ export default function TeamAvailabilityModal(props: Props) {
|
||||
|
||||
return (
|
||||
<LicenseRequired>
|
||||
<div className="flex max-h-[500px] min-h-[500px] flex-row space-x-8 rtl:space-x-reverse">
|
||||
<div className="min-w-64 w-64 space-y-5 p-5 pr-0">
|
||||
<div className="flex">
|
||||
<Avatar
|
||||
size="sm"
|
||||
imageSrc={WEBAPP_URL + "/" + props.member?.username + "/avatar.png"}
|
||||
alt={props.member?.name || ""}
|
||||
className="h-14 w-14 rounded-full"
|
||||
/>
|
||||
<div className="ms-3 inline-block pt-1">
|
||||
<span className="text-default text-lg font-bold">{props.member?.name}</span>
|
||||
<span className="text-muted -mt-1 block text-sm">{props.member?.email}</span>
|
||||
<>
|
||||
<div className="grid h-[400px] grid-cols-2 space-x-11 rtl:space-x-reverse">
|
||||
<div className="col-span-1">
|
||||
<div className="flex">
|
||||
<Avatar
|
||||
size="md"
|
||||
imageSrc={WEBAPP_URL + "/" + props.member?.username + "/avatar.png"}
|
||||
alt={props.member?.name || ""}
|
||||
/>
|
||||
<div className="flex items-center justify-center ">
|
||||
<span className="text-subtle ml-2 text-base font-semibold leading-4">
|
||||
{props.member?.name}
|
||||
</span>
|
||||
</div>
|
||||
</div>
|
||||
<div>
|
||||
<div className="text-brand-900 mt-4 mb-5 text-2xl font-semibold">{t("availability")}</div>
|
||||
<DatePicker
|
||||
minDate={new Date()}
|
||||
date={selectedDate.toDate() || dayjs().toDate()}
|
||||
onDatesChange={(newDate) => {
|
||||
setSelectedDate(dayjs(newDate));
|
||||
}}
|
||||
/>
|
||||
|
||||
<Label className="mt-4">{t("timezone")}</Label>
|
||||
<TimezoneSelect
|
||||
id="timeZone"
|
||||
autoFocus
|
||||
value={selectedTimeZone}
|
||||
className="w-64 rounded-md"
|
||||
onChange={(timezone) => setSelectedTimeZone(timezone.value)}
|
||||
classNamePrefix="react-select"
|
||||
/>
|
||||
</div>
|
||||
<div className="mt-3">
|
||||
<Label>{t("slot_length")}</Label>
|
||||
<Select
|
||||
options={[
|
||||
{ value: 15, label: "15 minutes" },
|
||||
{ value: 30, label: "30 minutes" },
|
||||
{ value: 60, label: "60 minutes" },
|
||||
]}
|
||||
isSearchable={false}
|
||||
classNamePrefix="react-select"
|
||||
className="w-64"
|
||||
value={{ value: frequency, label: `${frequency} minutes` }}
|
||||
onChange={(newFrequency) => setFrequency(newFrequency?.value ?? 30)}
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
<div className="flex flex-col">
|
||||
<span className="text-default font-bold">Date</span>
|
||||
<DatePicker
|
||||
date={selectedDate.toDate()}
|
||||
onDatesChange={(newDate) => {
|
||||
setSelectedDate(dayjs(newDate));
|
||||
}}
|
||||
/>
|
||||
</div>
|
||||
<div>
|
||||
<span className="text-default font-bold">Timezone</span>
|
||||
<TimezoneSelect
|
||||
id="timeZone"
|
||||
value={selectedTimeZone}
|
||||
onChange={(timezone) => setSelectedTimeZone(timezone.value)}
|
||||
classNamePrefix="react-select"
|
||||
className="react-select-container border-default mt-1 block w-full rounded-sm border text-sm focus:border-neutral-800 focus:ring-neutral-800"
|
||||
/>
|
||||
</div>
|
||||
<div>
|
||||
<span className="text-default font-bold">Slot Length</span>
|
||||
<Select
|
||||
options={[
|
||||
{ value: 15, label: "15 minutes" },
|
||||
{ value: 30, label: "30 minutes" },
|
||||
{ value: 60, label: "60 minutes" },
|
||||
]}
|
||||
isSearchable={false}
|
||||
classNamePrefix="react-select"
|
||||
className="react-select-container focus:ring-primary-500 focus:border-primary-500 border-default block w-full min-w-0 flex-1 rounded-sm border text-sm"
|
||||
value={{ value: frequency, label: `${frequency} minutes` }}
|
||||
onChange={(newFrequency) => setFrequency(newFrequency?.value ?? 30)}
|
||||
/>
|
||||
|
||||
<div className="col-span-1 max-h-[500px]">
|
||||
{props.team && props.member && (
|
||||
<TeamAvailabilityTimes
|
||||
teamId={props.team.id}
|
||||
memberId={props.member.id}
|
||||
frequency={frequency}
|
||||
selectedDate={selectedDate}
|
||||
selectedTimeZone={selectedTimeZone}
|
||||
/>
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
{props.team && props.member && (
|
||||
<TeamAvailabilityTimes
|
||||
className="overflow-scroll"
|
||||
teamId={props.team.id}
|
||||
memberId={props.member.id}
|
||||
frequency={frequency}
|
||||
selectedDate={selectedDate}
|
||||
selectedTimeZone={selectedTimeZone}
|
||||
/>
|
||||
)}
|
||||
</div>
|
||||
</>
|
||||
</LicenseRequired>
|
||||
);
|
||||
}
|
||||
|
||||
@@ -1,118 +0,0 @@
|
||||
import type { CSSProperties } from "react";
|
||||
import { useEffect, useState } from "react";
|
||||
import AutoSizer from "react-virtualized-auto-sizer";
|
||||
import { FixedSizeList as List } from "react-window";
|
||||
|
||||
import dayjs from "@calcom/dayjs";
|
||||
import { CAL_URL } from "@calcom/lib/constants";
|
||||
import type { RouterOutputs } from "@calcom/trpc/react";
|
||||
import { trpc } from "@calcom/trpc/react";
|
||||
import type { ITimezone } from "@calcom/ui";
|
||||
import { Avatar, DatePickerField as DatePicker, Select, TimezoneSelect } from "@calcom/ui";
|
||||
|
||||
import TeamAvailabilityTimes from "./TeamAvailabilityTimes";
|
||||
|
||||
interface Props {
|
||||
team?: RouterOutputs["viewer"]["teams"]["get"];
|
||||
}
|
||||
|
||||
export default function TeamAvailabilityScreen(props: Props) {
|
||||
const utils = trpc.useContext();
|
||||
const [selectedDate, setSelectedDate] = useState(dayjs());
|
||||
const [selectedTimeZone, setSelectedTimeZone] = useState<ITimezone>(
|
||||
localStorage.getItem("timeOption.preferredTimeZone") || dayjs.tz.guess()
|
||||
);
|
||||
const [frequency, setFrequency] = useState<15 | 30 | 60>(30);
|
||||
|
||||
useEffect(() => {
|
||||
utils.viewer.teams.getMemberAvailability.invalidate();
|
||||
// eslint-disable-next-line react-hooks/exhaustive-deps
|
||||
}, [selectedTimeZone, selectedDate]);
|
||||
|
||||
const Item = ({ index, style }: { index: number; style: CSSProperties }) => {
|
||||
const member = props.team?.members?.[index];
|
||||
if (!member) return <></>;
|
||||
|
||||
return (
|
||||
<div key={member.id} style={style} className="border-subtle flex border-r pl-4 ">
|
||||
<TeamAvailabilityTimes
|
||||
teamId={props.team?.id as number}
|
||||
memberId={member.id}
|
||||
frequency={frequency}
|
||||
selectedDate={selectedDate}
|
||||
selectedTimeZone={selectedTimeZone}
|
||||
HeaderComponent={
|
||||
<div className="mb-6 flex items-center">
|
||||
<Avatar
|
||||
size="sm"
|
||||
imageSrc={CAL_URL + "/" + member.username + "/avatar.png"}
|
||||
alt={member?.name || ""}
|
||||
className="min-w-10 min-h-10 mt-1 h-10 w-10 rounded-full"
|
||||
/>
|
||||
<div className="ms-3 inline-block overflow-hidden pt-1">
|
||||
<span className="text-default truncate text-lg font-bold">{member?.name}</span>
|
||||
<span className="text-muted -mt-1 block truncate text-sm">{member?.email}</span>
|
||||
</div>
|
||||
</div>
|
||||
}
|
||||
/>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
return (
|
||||
<div className="bg-default flex flex-1 flex-col rounded-sm border border-neutral-200">
|
||||
<div className="border-subtle flex w-full space-x-5 border-b p-4 rtl:space-x-reverse">
|
||||
<div className="flex flex-col">
|
||||
<span className="text-default text-sm font-medium">Date</span>
|
||||
<DatePicker
|
||||
date={selectedDate.toDate()}
|
||||
className="p-1.5"
|
||||
onDatesChange={(newDate) => {
|
||||
setSelectedDate(dayjs(newDate));
|
||||
}}
|
||||
/>
|
||||
</div>
|
||||
<div className="flex flex-col">
|
||||
<span className="text-default text-sm font-medium">Timezone</span>
|
||||
<TimezoneSelect
|
||||
id="timeZone"
|
||||
value={selectedTimeZone}
|
||||
onChange={(timezone) => setSelectedTimeZone(timezone.value)}
|
||||
classNamePrefix="react-select"
|
||||
className="react-select-container border-default w-full rounded-sm border text-sm"
|
||||
/>
|
||||
</div>
|
||||
<div className="hidden sm:block">
|
||||
<span className="text-default text-sm font-medium">Slot Length</span>
|
||||
<Select
|
||||
options={[
|
||||
{ value: 15, label: "15 minutes" },
|
||||
{ value: 30, label: "30 minutes" },
|
||||
{ value: 60, label: "60 minutes" },
|
||||
]}
|
||||
isSearchable={false}
|
||||
className="border-default block w-full min-w-0 flex-1 rounded-sm border text-sm"
|
||||
value={{ value: frequency, label: `${frequency} minutes` }}
|
||||
onChange={(newFrequency) => setFrequency(newFrequency?.value ?? 30)}
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
<div className="flex h-full flex-1">
|
||||
<AutoSizer>
|
||||
{({ height, width }) => (
|
||||
<List
|
||||
itemSize={240}
|
||||
itemCount={props.team?.members?.length ?? 0}
|
||||
className="List"
|
||||
height={height}
|
||||
layout="horizontal"
|
||||
width={width}>
|
||||
{Item}
|
||||
</List>
|
||||
)}
|
||||
</AutoSizer>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
@@ -3,9 +3,11 @@ import React from "react";
|
||||
import type { ITimezone } from "react-timezone-select";
|
||||
|
||||
import type { Dayjs } from "@calcom/dayjs";
|
||||
import { useLocale } from "@calcom/lib/hooks/useLocale";
|
||||
import getSlots from "@calcom/lib/slots";
|
||||
import { trpc } from "@calcom/trpc/react";
|
||||
import { Loader } from "@calcom/ui";
|
||||
|
||||
import SkeletonLoader from "./SkeletonLoaderAvailabilityTimes";
|
||||
|
||||
interface Props {
|
||||
teamId: number;
|
||||
@@ -18,6 +20,8 @@ interface Props {
|
||||
}
|
||||
|
||||
export default function TeamAvailabilityTimes(props: Props) {
|
||||
const { t } = useLocale();
|
||||
|
||||
const { data, isLoading } = trpc.viewer.teams.getMemberAvailability.useQuery(
|
||||
{
|
||||
teamId: props.teamId,
|
||||
@@ -38,27 +42,32 @@ export default function TeamAvailabilityTimes(props: Props) {
|
||||
workingHours: data?.workingHours || [],
|
||||
minimumBookingNotice: 0,
|
||||
eventLength: props.frequency,
|
||||
organizerTimeZone: `${data?.timeZone}`,
|
||||
})
|
||||
: [];
|
||||
|
||||
return (
|
||||
<div className={classNames("min-w-60 flex-grow p-5 pl-0", props.className)}>
|
||||
<div className={classNames("min-w-60 flex-grow pl-0", props.className)}>
|
||||
{props.HeaderComponent}
|
||||
{isLoading && slots.length === 0 && <Loader />}
|
||||
{!isLoading && slots.length === 0 && (
|
||||
{isLoading && slots.length === 0 && <SkeletonLoader />}
|
||||
{!isLoading && slots.length === 0 ? (
|
||||
<div className="flex flex-col items-center justify-center pt-4">
|
||||
<span className="text-subtle text-sm">No Available Slots</span>
|
||||
<span className="text-subtle text-sm">{t("no_available_slots")}</span>
|
||||
</div>
|
||||
) : (
|
||||
<>{!isLoading && <p className="text-default mb-3 text-sm">{t("time_available")}</p>}</>
|
||||
)}
|
||||
{slots.map((slot) => (
|
||||
<div key={slot.time.format()} className="flex flex-row items-center">
|
||||
<a
|
||||
className="min-w-48 border-brand-default text-bookingdarker hover:bg-brand hover:text-brandcontrast dark:hover:bg-darkmodebrand dark:hover:text-darkmodebrandcontrast bg-default dark:hover:text-inverted mb-2 mr-3 block flex-grow rounded-sm border py-2 text-center font-medium dark:border-transparent dark:bg-gray-600 dark:hover:border-black dark:hover:bg-black"
|
||||
data-testid="time">
|
||||
{slot.time.format("HH:mm")}
|
||||
</a>
|
||||
</div>
|
||||
))}
|
||||
<div className="max-h-[390px] overflow-scroll">
|
||||
{slots.map((slot) => (
|
||||
<div key={slot.time.format()} className="flex flex-row items-center ">
|
||||
<a
|
||||
className="min-w-48 border-brand-default text-bookingdarker bg-default mb-2 mr-3 block flex-grow rounded-md border py-2 text-center font-medium dark:border-transparent dark:bg-gray-600 "
|
||||
data-testid="time">
|
||||
{slot.time.tz(props.selectedTimeZone.toString()).format("HH:mm")}
|
||||
</a>
|
||||
</div>
|
||||
))}
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
@@ -105,7 +105,7 @@ export default function TeamListItem(props: Props) {
|
||||
alt="Team Logo"
|
||||
className="inline-flex justify-center"
|
||||
/>
|
||||
<div className="ms-3 inline-block">
|
||||
<div className="ms-3 inline-block truncate">
|
||||
<span className="text-default text-sm font-bold">{team.name}</span>
|
||||
<span className="text-muted block text-xs">
|
||||
{team.slug ? `${process.env.NEXT_PUBLIC_WEBSITE_URL}/team/${team.slug}` : "Unpublished team"}
|
||||
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user