Merge branch 'main' into v2/teams-billing

This commit is contained in:
Joe Au-Yeung 2022-10-20 10:40:37 -04:00
commit 8c598d2562
65 changed files with 543 additions and 287 deletions

View File

@ -65,7 +65,7 @@ const AvailableTimes: FC<AvailableTimesProps> = ({
query: Record<string, string | number | string[] | undefined>;
};
const bookingUrl: BookingURL = {
pathname: "book",
pathname: router.pathname.endsWith("/embed") ? "../book" : "book",
query: {
...router.query,
date: dayjs(slot.time).format(),

View File

@ -63,7 +63,7 @@ export default function CancelBooking(props: Props) {
<Button
color="secondary"
className="border-0 sm:border"
onClick={() => router.push("/reschedule/" + booking?.uid)}>
onClick={() => router.push(`/reschedule/${booking?.uid}`)}>
{t("reschedule_this")}
</Button>
</div>

View File

@ -1,10 +1,9 @@
// Get router variables
import autoAnimate from "@formkit/auto-animate";
import { useAutoAnimate } from "@formkit/auto-animate/react";
import { EventType } from "@prisma/client";
import * as Popover from "@radix-ui/react-popover";
import { TFunction } from "next-i18next";
import { useRouter } from "next/router";
import { useReducer, useEffect, useMemo, useState, useRef } from "react";
import { useReducer, useEffect, useMemo, useState } from "react";
import { Toaster } from "react-hot-toast";
import { FormattedNumber, IntlProvider } from "react-intl";
import { z } from "zod";
@ -48,6 +47,8 @@ import type { AvailabilityPageProps } from "../../../pages/[user]/[type]";
import type { DynamicAvailabilityPageProps } from "../../../pages/d/[link]/[slug]";
import type { AvailabilityTeamPageProps } from "../../../pages/team/[slug]/[type]";
// Get router variables
const GoBackToPreviousPage = ({ t }: { t: TFunction }) => {
const router = useRouter();
const path = router.asPath.split("/");
@ -129,11 +130,8 @@ const SlotPicker = ({
const { date, setQuery: setDate } = useRouterQuery("date");
const { month, setQuery: setMonth } = useRouterQuery("month");
const router = useRouter();
const slotPickerRef = useRef<HTMLDivElement>(null);
useEffect(() => {
slotPickerRef.current && autoAnimate(slotPickerRef.current);
}, [slotPickerRef]);
const [slotPickerRef] = useAutoAnimate<HTMLDivElement>();
useEffect(() => {
if (!router.isReady) return;
@ -304,11 +302,11 @@ const timeFormatTotimeFormatString = (timeFormat?: number | null) => {
return timeFormat === 24 ? "HH:mm" : "h:mma";
};
const AvailabilityPage = ({ profile, eventType }: Props) => {
const AvailabilityPage = ({ profile, eventType, ...restProps }: Props) => {
const { data: user } = trpc.useQuery(["viewer.me"]);
const timeFormatFromProfile = timeFormatTotimeFormatString(user?.timeFormat);
const router = useRouter();
const isEmbed = useIsEmbed();
const isEmbed = useIsEmbed(restProps.isEmbed);
const query = dateQuerySchema.parse(router.query);
const { rescheduleUid } = query;
useTheme(profile.theme);

View File

@ -1,6 +1,5 @@
import { zodResolver } from "@hookform/resolvers/zod";
import { EventTypeCustomInputType, WorkflowActions } from "@prisma/client";
import { SchedulingType } from "@prisma/client";
import { useMutation } from "@tanstack/react-query";
import { isValidPhoneNumber } from "libphonenumber-js";
import { useSession } from "next-auth/react";
@ -84,9 +83,10 @@ const BookingPage = ({
recurringEventCount,
hasHashedBookingLink,
hashedLink,
...restProps
}: BookingPageProps) => {
const { t, i18n } = useLocale();
const isEmbed = useIsEmbed();
const isEmbed = useIsEmbed(restProps.isEmbed);
const shouldAlignCentrallyInEmbed = useEmbedNonStylesConfig("align") !== "left";
const shouldAlignCentrally = !isEmbed || shouldAlignCentrallyInEmbed;
const router = useRouter();

View File

@ -153,7 +153,7 @@ export default function TeamListItem(props: Props) {
/>
</Tooltip>
<Dropdown>
<DropdownMenuTrigger asChild>
<DropdownMenuTrigger asChild className="radix-state-open:rounded-r-md">
<Button type="button" color="secondary" size="icon" StartIcon={Icon.FiMoreHorizontal} />
</DropdownMenuTrigger>
<DropdownMenuContent hidden={hideDropdown}>

View File

@ -1,8 +1,8 @@
import autoAnimate from "@formkit/auto-animate";
import { useAutoAnimate } from "@formkit/auto-animate/react";
import { EventTypeCustomInput } from "@prisma/client/";
import Link from "next/link";
import { EventTypeSetupInfered, FormValues } from "pages/event-types/[type]";
import { useEffect, useRef, useState } from "react";
import { useEffect, useState } from "react";
import { Controller, useFormContext } from "react-hook-form";
import short from "short-uuid";
import { v5 as uuidv5 } from "uuid";
@ -51,12 +51,9 @@ export const EventAdvancedTab = ({ eventType, team }: Pick<EventTypeSetupInfered
const [selectedCustomInputModalOpen, setSelectedCustomInputModalOpen] = useState(false);
const placeholderHashedLink = `${CAL_URL}/d/${hashedUrl}/${eventType.slug}`;
const animationRef = useRef(null);
const seatsEnabled = formMethods.getValues("seatsPerTimeSlotEnabled");
useEffect(() => {
animationRef.current && autoAnimate(animationRef.current);
}, [animationRef]);
const [animationRef] = useAutoAnimate<HTMLUListElement>();
const removeCustom = (index: number) => {
formMethods.getValues("customInputs").splice(index, 1);

View File

@ -1,7 +1,7 @@
import autoAnimate from "@formkit/auto-animate";
import { useAutoAnimate } from "@formkit/auto-animate/react";
import * as RadioGroup from "@radix-ui/react-radio-group";
import { EventTypeSetupInfered, FormValues } from "pages/event-types/[type]";
import { useEffect, useRef, useState } from "react";
import { useState } from "react";
import { useFormContext, Controller, useWatch } from "react-hook-form";
import { classNames } from "@calcom/lib";
@ -311,12 +311,9 @@ type BookingLimitsKey = keyof BookingLimit;
const BookingLimits = () => {
const { watch, setValue, control } = useFormContext<FormValues>();
const watchBookingLimits = watch("bookingLimits");
const animateRef = useRef(null);
const { t } = useLocale();
useEffect(() => {
animateRef.current && autoAnimate(animateRef.current);
}, [animateRef]);
const [animateRef] = useAutoAnimate<HTMLUListElement>();
const BOOKING_LIMIT_OPTIONS: {
value: keyof BookingLimit;

View File

@ -1,8 +1,8 @@
import autoAnimate from "@formkit/auto-animate";
import { useAutoAnimate } from "@formkit/auto-animate/react";
import { zodResolver } from "@hookform/resolvers/zod";
import { isValidPhoneNumber } from "libphonenumber-js";
import { EventTypeSetupInfered, FormValues } from "pages/event-types/[type]";
import { useEffect, useRef, useState } from "react";
import { useState } from "react";
import { Controller, useForm, useFormContext } from "react-hook-form";
import { z } from "zod";
@ -85,11 +85,8 @@ export const EventSetupTab = (
const Locations = () => {
const { t } = useLocale();
const animationRef = useRef(null);
useEffect(() => {
animationRef.current && autoAnimate(animationRef.current);
}, [animationRef]);
const [animationRef] = useAutoAnimate<HTMLUListElement>();
const validLocations = formMethods.getValues("locations").filter((location) => {
const eventLocation = getEventLocationType(location.type);

View File

@ -234,7 +234,7 @@ function EventTypeSingleLayout({
<VerticalDivider />
<Dropdown>
<DropdownMenuTrigger className="focus:ring-brand-900 block h-9 w-9 justify-center rounded-md border border-gray-200 bg-transparent text-gray-700 focus:bg-gray-100 focus:outline-none focus:ring-2 focus:ring-offset-1 lg:hidden">
<DropdownMenuTrigger className="block h-9 w-9 justify-center rounded-md border border-gray-200 bg-transparent text-gray-700 lg:hidden">
<Icon.FiMoreHorizontal className="group-hover:text-gray-800" />
</DropdownMenuTrigger>
<DropdownMenuContent>

View File

@ -0,0 +1,21 @@
import { GetServerSideProps, GetServerSidePropsContext, GetServerSidePropsResult } from "next";
export type EmbedProps = {
isEmbed?: boolean;
};
export default function withEmbedSsr(getServerSideProps: GetServerSideProps) {
return async (context: GetServerSidePropsContext): Promise<GetServerSidePropsResult<EmbedProps>> => {
const ssrResponse = await getServerSideProps(context);
if (!("props" in ssrResponse)) {
return ssrResponse;
}
return {
...ssrResponse,
props: {
...ssrResponse.props,
isEmbed: true,
},
};
};
}

View File

@ -23,6 +23,13 @@ const middleware: NextMiddleware = async (req) => {
}
}
// Ensure that embed query param is there in when /embed is added.
// query param is the way in which client side code knows that it is in embed mode.
if (url.pathname.endsWith("/embed") && typeof url.searchParams.get("embed") !== "string") {
url.searchParams.set("embed", "");
return NextResponse.redirect(url);
}
// Don't 404 old routing_forms links
if (url.pathname.startsWith("/apps/routing_forms")) {
url.pathname = url.pathname.replace("/apps/routing_forms", "/apps/routing-forms");

View File

@ -134,8 +134,8 @@ const nextConfig = {
destination: "/api/user/avatar?teamname=:teamname",
},
{
source: "/forms/:formId",
destination: "/apps/routing-forms/routing-link/:formId",
source: "/forms/:formQuery*",
destination: "/apps/routing-forms/routing-link/:formQuery*",
},
{
source: "/router",

View File

@ -120,7 +120,7 @@
"tailwindcss-radix": "^2.6.0",
"uuid": "^8.3.2",
"web3": "^1.7.5",
"zod": "^3.18.0"
"zod": "^3.19.1"
},
"devDependencies": {
"@babel/core": "^7.18.10",

View File

@ -30,6 +30,7 @@ import { BadgeCheckIcon, Icon } from "@calcom/ui/Icon";
import { useExposePlanGlobally } from "@lib/hooks/useExposePlanGlobally";
import { inferSSRProps } from "@lib/types/inferSSRProps";
import { EmbedProps } from "@lib/withEmbedSsr";
import AvatarGroup from "@components/ui/AvatarGroup";
import { AvatarSSR } from "@components/ui/AvatarSSR";
@ -38,7 +39,7 @@ import { ssrInit } from "@server/lib/ssr";
const EventTypeDescription = dynamic(() => import("@calcom/ui/v2/modules/event-types/EventTypeDescription"));
const HeadSeo = dynamic(() => import("@components/seo/head-seo"));
export default function User(props: inferSSRProps<typeof getServerSideProps>) {
export default function User(props: inferSSRProps<typeof getServerSideProps> & EmbedProps) {
const { users, profile, eventTypes, isDynamicGroup, dynamicNames, dynamicUsernames, isSingleUser } = props;
const [user] = users; //To be used when we only have a single user, not dynamic group
useTheme(user.theme);
@ -86,7 +87,7 @@ export default function User(props: inferSSRProps<typeof getServerSideProps>) {
</ul>
);
const isEmbed = useIsEmbed();
const isEmbed = useIsEmbed(props.isEmbed);
const eventTypeListItemEmbedStyles = useEmbedStyles("eventTypeListItem");
const shouldAlignCentrallyInEmbed = useEmbedNonStylesConfig("align") !== "left";
const shouldAlignCentrally = !isEmbed || shouldAlignCentrallyInEmbed;
@ -287,6 +288,8 @@ export const getServerSideProps = async (context: GetServerSidePropsContext) =>
if (!users.length) {
return {
notFound: true,
} as {
notFound: true;
};
}
const isDynamicGroup = users.length > 1;

View File

@ -11,10 +11,11 @@ import prisma from "@calcom/prisma";
import { EventTypeMetaDataSchema } from "@calcom/prisma/zod-utils";
import { inferSSRProps } from "@lib/types/inferSSRProps";
import { EmbedProps } from "@lib/withEmbedSsr";
import AvailabilityPage from "@components/booking/pages/AvailabilityPage";
export type AvailabilityPageProps = inferSSRProps<typeof getStaticProps>;
export type AvailabilityPageProps = inferSSRProps<typeof getStaticProps> & EmbedProps;
export default function Type(props: AvailabilityPageProps) {
const { t } = useLocale();

View File

@ -0,0 +1,21 @@
import { GetStaticPropsContext } from "next";
import { getStaticProps as _getStaticProps } from "../[type]";
export { getStaticPaths } from "../[type]";
export { default } from "../[type]";
export const getStaticProps = async (context: GetStaticPropsContext) => {
const staticResponse = await _getStaticProps(context);
if (staticResponse.notFound) {
return staticResponse;
}
return {
...staticResponse,
props: {
...staticResponse.props,
isEmbed: true,
},
};
};

View File

@ -197,6 +197,7 @@ export async function getServerSideProps(context: GetServerSidePropsContext) {
isDynamicGroupBooking,
hasHashedBookingLink: false,
hashedLink: null,
isEmbed: typeof context.query.embed === "string",
},
};
}

View File

@ -0,0 +1,7 @@
import withEmbedSsr from "@lib/withEmbedSsr";
import { getServerSideProps as _getServerSideProps } from "../[user]";
export { default } from "../[user]";
export const getServerSideProps = withEmbedSsr(_getServerSideProps);

View File

@ -3,18 +3,13 @@ import Document, { DocumentContext, Head, Html, Main, NextScript, DocumentProps
type Props = Record<string, unknown> & DocumentProps;
function toRunBeforeReactOnClient() {
const calEmbedMode = location.search.includes("embed=");
try {
// eslint-disable-next-line @calcom/eslint/avoid-web-storage
window.sessionStorage.setItem("calEmbedMode", String(calEmbedMode));
} catch (e) {}
const calEmbedMode =
location.search.includes("embed=") ||
/* Iframe Name */
window.name.includes("cal-embed");
window.isEmbed = () => {
try {
// eslint-disable-next-line @calcom/eslint/avoid-web-storage
return window.sessionStorage.getItem("calEmbedMode") === "true";
} catch (e) {}
// If we can't use sessionStorage to retrieve embed mode, just use the variable. It would fail to detect embed if page in iframe reloads without embed query param in it.
// Once an embed mode always an embed mode
return calEmbedMode;
};
@ -45,14 +40,14 @@ function toRunBeforeReactOnClient() {
class MyDocument extends Document<Props> {
static async getInitialProps(ctx: DocumentContext) {
const isEmbed = ctx.asPath?.includes("/embed") || ctx.asPath?.includes("embedType=");
const initialProps = await Document.getInitialProps(ctx);
return { ...initialProps };
return { isEmbed, ...initialProps };
}
render() {
const { locale } = this.props.__NEXT_DATA__;
const dir = locale === "ar" || locale === "he" ? "rtl" : "ltr";
return (
<Html lang={locale} dir={dir}>
<Head>
@ -82,22 +77,22 @@ class MyDocument extends Document<Props> {
/>
</Head>
<body className="dark:bg-darkgray-50 desktop-transparent bg-gray-100">
<body
className="dark:bg-darkgray-50 desktop-transparent bg-gray-100"
style={
this.props.isEmbed
? {
background: "transparent",
// Keep the embed hidden till parent initializes and
// - gives it the appropriate styles if UI instruction is there.
// - gives iframe the appropriate height(equal to document height) which can only be known after loading the page once in browser.
// - Tells iframe which mode it should be in (dark/light) - if there is a a UI instruction for that
visibility: "hidden",
}
: {}
}>
<Main />
<NextScript />
{/* In case of Embed we want background to be transparent so that it merges into the website seamlessly. Also, we keep the body hidden here and embed logic would take care of showing the body when it's ready */}
{/* We are doing it on browser and not on server because there are some pages which are not SSRd */}
<script
dangerouslySetInnerHTML={{
__html: `
if (isEmbed()) {
if(!isPageOptimizedForEmbed()) {
document.body.style.display="none";
}
document.body.style.background="transparent";
}`,
}}
/>
</body>
</Html>
);

View File

@ -1,5 +1,4 @@
import autoAnimate from "@formkit/auto-animate";
import { useRef, useEffect } from "react";
import { useAutoAnimate } from "@formkit/auto-animate/react";
import { NewScheduleButton, ScheduleListItem } from "@calcom/features/schedules";
import { useLocale } from "@calcom/lib/hooks/useLocale";
@ -16,7 +15,6 @@ import SkeletonLoader from "@components/v2/availability/SkeletonLoader";
export function AvailabilityList({ schedules }: inferQueryOutput<"viewer.availability.list">) {
const { t } = useLocale();
const utils = trpc.useContext();
const animationParentRef = useRef(null);
const meQuery = trpc.useQuery(["viewer.me"]);
@ -50,9 +48,8 @@ export function AvailabilityList({ schedules }: inferQueryOutput<"viewer.availab
});
// Adds smooth delete button - item fades and old item slides into place
useEffect(() => {
animationParentRef.current && autoAnimate(animationParentRef.current);
}, [animationParentRef]);
const [animationParentRef] = useAutoAnimate<HTMLUListElement>();
return (
<>

View File

@ -1,7 +1,7 @@
import autoAnimate from "@formkit/auto-animate";
import { useAutoAnimate } from "@formkit/auto-animate/react";
import { GetStaticPaths, GetStaticProps } from "next";
import { useRouter } from "next/router";
import { Fragment, useEffect, useRef } from "react";
import { Fragment } from "react";
import { z } from "zod";
import { WipeMyCalActionButton } from "@calcom/app-store/wipemycalother/components";
@ -46,7 +46,7 @@ export default function Bookings() {
});
// Animate page (tab) tranistions to look smoothing
const animationParentRef = useRef(null);
const buttonInView = useInViewObserver(() => {
if (!query.isFetching && query.hasNextPage && query.status === "success") {
query.fetchNextPage();
@ -73,9 +73,7 @@ export default function Bookings() {
return true;
};
useEffect(() => {
animationParentRef.current && autoAnimate(animationParentRef.current);
}, [animationParentRef]);
const [animationParentRef] = useAutoAnimate<HTMLDivElement>();
return (
<BookingLayout heading={t("bookings")} subtitle={t("bookings_description")}>

View File

@ -10,12 +10,13 @@ import { EventTypeMetaDataSchema } from "@calcom/prisma/zod-utils";
import { getWorkingHours } from "@lib/availability";
import { GetBookingType } from "@lib/getBooking";
import { inferSSRProps } from "@lib/types/inferSSRProps";
import { EmbedProps } from "@lib/withEmbedSsr";
import AvailabilityPage from "@components/booking/pages/AvailabilityPage";
import { ssrInit } from "@server/lib/ssr";
export type DynamicAvailabilityPageProps = inferSSRProps<typeof getServerSideProps>;
export type DynamicAvailabilityPageProps = inferSSRProps<typeof getServerSideProps> & EmbedProps;
export default function Type(props: DynamicAvailabilityPageProps) {
return <AvailabilityPage {...props} />;
@ -48,11 +49,15 @@ export const getServerSideProps = async (context: GetServerSidePropsContext) =>
if (!userId)
return {
notFound: true,
} as {
notFound: true;
};
if (hashedLink?.eventType.slug !== slug)
return {
notFound: true,
} as {
notFound: true;
};
const users = await prisma.user.findMany({
@ -92,6 +97,8 @@ export const getServerSideProps = async (context: GetServerSidePropsContext) =>
if (!users || !users.length) {
return {
notFound: true,
} as {
notFound: true;
};
}

View File

@ -0,0 +1,7 @@
import withEmbedSsr from "@lib/withEmbedSsr";
import { getServerSideProps as _getServerSideProps } from "../[slug]";
export { default } from "../[slug]";
export const getServerSideProps = withEmbedSsr(_getServerSideProps);

View File

@ -2,7 +2,6 @@ import { GetServerSidePropsContext } from "next";
import { JSONObject } from "superjson/dist/types";
import { parseRecurringEvent } from "@calcom/lib";
import { useLocale } from "@calcom/lib/hooks/useLocale";
import prisma from "@calcom/prisma";
import { bookEventTypeSelect } from "@calcom/prisma/selects";
@ -124,6 +123,7 @@ export async function getServerSideProps(context: GetServerSidePropsContext) {
isDynamicGroupBooking: false,
hasHashedBookingLink: true,
hashedLink: link,
isEmbed: typeof context.query.embed === "string",
},
};
}

View File

@ -1,9 +1,9 @@
/* eslint-disable @typescript-eslint/no-empty-function */
import autoAnimate from "@formkit/auto-animate";
import { useAutoAnimate } from "@formkit/auto-animate/react";
import { EventTypeCustomInput, PeriodType, Prisma, SchedulingType } from "@prisma/client";
import { GetServerSidePropsContext } from "next";
import { useRouter } from "next/router";
import { useEffect, useRef, useState } from "react";
import { useState } from "react";
import { useForm } from "react-hook-form";
import { z } from "zod";
@ -106,13 +106,11 @@ const EventTypePage = (props: inferSSRProps<typeof getServerSideProps>) => {
// TODO: It isn't a good idea to maintain state using setEventType. If we want to connect the SSR'd data to tRPC, we should useQuery(["viewer.eventTypes.get"]) with initialData
// Due to this change, when Form is saved, there is no way to propagate that info to eventType (e.g. disabling stripe app doesn't allow recurring tab to be enabled without refresh).
const [eventType, setEventType] = useState(dbEventType);
const animationParentRef = useRef(null);
const router = useRouter();
const { tabName } = querySchema.parse(router.query);
useEffect(() => {
animationParentRef.current && autoAnimate(animationParentRef.current);
}, [animationParentRef]);
const [animationParentRef] = useAutoAnimate<HTMLDivElement>();
const updateMutation = trpc.useMutation("viewer.eventTypes.update", {
onSuccess: async ({ eventType: newEventType }) => {

View File

@ -322,7 +322,10 @@ export const EventTypeList = ({ group, groupIndex, readOnly, types }: EventTypeL
/>
</Tooltip>
<Dropdown modal={false}>
<DropdownMenuTrigger asChild data-testid={"event-type-options-" + type.id}>
<DropdownMenuTrigger
asChild
data-testid={"event-type-options-" + type.id}
className="radix-state-open:rounded-r-md">
<Button
type="button"
size="icon"

View File

@ -4,11 +4,10 @@ import { useRouter } from "next/router";
import { Fragment } from "react";
import DestinationCalendarSelector from "@calcom/features/calendars/DestinationCalendarSelector";
import { WEBAPP_URL } from "@calcom/lib/constants";
import { useLocale } from "@calcom/lib/hooks/useLocale";
import { trpc } from "@calcom/trpc/react";
import { Icon } from "@calcom/ui";
import { Alert } from "@calcom/ui/v2";
import { Alert, Button } from "@calcom/ui/v2";
import Badge from "@calcom/ui/v2/core/Badge";
import EmptyScreen from "@calcom/ui/v2/core/EmptyScreen";
import Meta from "@calcom/ui/v2/core/Meta";
@ -36,6 +35,18 @@ const SkeletonLoader = () => {
);
};
const AddCalendarButton = () => {
const { t } = useLocale();
return (
<>
<Button color="secondary" StartIcon={Icon.FiPlus} href="/apps/categories/calendar">
{t("add_calendar")}
</Button>
</>
);
};
const CalendarsView = () => {
const { t } = useLocale();
const router = useRouter();
@ -51,7 +62,11 @@ const CalendarsView = () => {
return (
<>
<Meta title="Calendars" description="Configure how your event types interact with your calendars" />
<Meta
title="Calendars"
description="Configure how your event types interact with your calendars"
CTA={<AddCalendarButton />}
/>
<QueryCell
query={query}
customLoader={<SkeletonLoader />}
@ -174,7 +189,7 @@ const CalendarsView = () => {
headline={t("no_calendar_installed")}
description={t("no_calendar_installed_description")}
buttonText={t("add_a_calendar")}
buttonOnClick={() => router.push(`${WEBAPP_URL}/apps/categories/calendar`)}
buttonOnClick={() => router.push("/apps/categories/calendar")}
/>
);
}}

View File

@ -442,7 +442,7 @@ export default function Success(props: SuccessProps) {
{!props.recurringBookings && (
<span className="text-bookinglight inline text-gray-700">
<span className="underline">
<Link href={"/reschedule/" + bookingInfo?.uid}>{t("reschedule")}</Link>
<Link href={`/reschedule/${bookingInfo?.uid}`}>{t("reschedule")}</Link>
</span>
<span className="mx-2">{t("or_lowercase")}</span>
</span>

View File

@ -83,7 +83,6 @@ function TeamPage({ team }: TeamPageProps) {
return (
<div>
<HeadSeo title={teamName} description={teamName} />
<HeadSeo
title={teamName}
description={teamName}
@ -92,7 +91,7 @@ function TeamPage({ team }: TeamPageProps) {
profile: { name: `${team.name}`, image: getPlaceholderAvatar(team.logo, team.name) },
}}
/>
<div className="dark:bg-darkgray-50 h-screen rounded-md bg-gray-100 px-4 pt-12 pb-12">
<main className="dark:bg-darkgray-50 mx-auto max-w-3xl rounded-md bg-gray-100 px-4 pt-12 pb-12">
<div className="max-w-96 mx-auto mb-8 text-center">
<Avatar alt={teamName} imageSrc={getPlaceholderAvatar(team.logo, team.name)} size="lg" />
<p className="font-cal dark:text-darkgray-900 mb-2 text-2xl tracking-wider text-gray-900">
@ -117,7 +116,7 @@ function TeamPage({ team }: TeamPageProps) {
</div>
</div>
<aside className="mt-8 mb-16 flex justify-center text-center dark:text-white">
<aside className="mt-8 flex justify-center text-center dark:text-white">
<Button
color="minimal"
EndIcon={Icon.FiArrowRight}
@ -129,7 +128,7 @@ function TeamPage({ team }: TeamPageProps) {
</aside>
</div>
)}
</div>
</main>
</div>
);
}
@ -139,7 +138,7 @@ export const getServerSideProps = async (context: GetServerSidePropsContext) =>
const team = await getTeamWithMembers(undefined, slug);
if (!team) return { notFound: true };
if (!team) return { notFound: true } as { notFound: true };
const members = team.members.filter((member) => member.plan !== UserPlan.FREE);

View File

@ -10,12 +10,13 @@ import { asStringOrNull } from "@lib/asStringOrNull";
import { getWorkingHours } from "@lib/availability";
import getBooking, { GetBookingType } from "@lib/getBooking";
import { inferSSRProps } from "@lib/types/inferSSRProps";
import { EmbedProps } from "@lib/withEmbedSsr";
import AvailabilityPage from "@components/booking/pages/AvailabilityPage";
import { ssgInit } from "@server/lib/ssg";
export type AvailabilityTeamPageProps = inferSSRProps<typeof getServerSideProps>;
export type AvailabilityTeamPageProps = inferSSRProps<typeof getServerSideProps> & EmbedProps;
export default function TeamType(props: AvailabilityTeamPageProps) {
return <AvailabilityPage {...props} />;
@ -99,6 +100,8 @@ export const getServerSideProps = async (context: GetServerSidePropsContext) =>
if (!team || team.eventTypes.length != 1) {
return {
notFound: true,
} as {
notFound: true;
};
}

View File

@ -0,0 +1,7 @@
import withEmbedSsr from "@lib/withEmbedSsr";
import { getServerSideProps as _getServerSideProps } from "../[type]";
export { default } from "../[type]";
export const getServerSideProps = withEmbedSsr(_getServerSideProps);

View File

@ -3,7 +3,6 @@ import { JSONObject } from "superjson/dist/types";
import { LocationObject, privacyFilteredLocations } from "@calcom/app-store/locations";
import { parseRecurringEvent } from "@calcom/lib";
import { useLocale } from "@calcom/lib/hooks/useLocale";
import prisma from "@calcom/prisma";
import { asStringOrNull, asStringOrThrow } from "@lib/asStringOrNull";
@ -15,8 +14,6 @@ import BookingPage from "@components/booking/pages/BookingPage";
export type TeamBookingPageProps = inferSSRProps<typeof getServerSideProps>;
export default function TeamBookingPage(props: TeamBookingPageProps) {
const { t } = useLocale();
return <BookingPage {...props} />;
}
@ -141,6 +138,7 @@ export async function getServerSideProps(context: GetServerSidePropsContext) {
isDynamicGroupBooking: false,
hasHashedBookingLink: false,
hashedLink: null,
isEmbed: typeof context.query.embed === "string",
},
};
}

View File

@ -0,0 +1,7 @@
import withEmbedSsr from "@lib/withEmbedSsr";
import { getServerSideProps as _getServerSideProps } from "../[slug]";
export { default } from "../[slug]";
export const getServerSideProps = withEmbedSsr(_getServerSideProps);

View File

@ -13,7 +13,7 @@ export const createEmbedsFixture = (page: Page) => {
// eslint-disable-next-line @typescript-eslint/ban-ts-comment
//@ts-ignore
window.initialBodyDisplay = document.body.style.display;
window.initialBodyVisibility = document.body.style.visibility;
// eslint-disable-next-line @typescript-eslint/ban-ts-comment
//@ts-ignore

View File

@ -313,6 +313,7 @@
"past_bookings": "Vaše proběhlé rezervace se zobrazí zde.",
"cancelled_bookings": "Vaše zrušené rezervace se zobrazí zde.",
"unconfirmed_bookings": "Vaše nepotvrzené rezervace se zobrazí zde.",
"unconfirmed_bookings_tooltip": "Nepotvrzené rezervace",
"on": "dne",
"and": "a",
"calendar_shows_busy_between": "Kalendář vás zobrazuje jako nedostupného mezi",
@ -766,6 +767,7 @@
"no_category_apps_description_calendar": "Přidejte aplikaci kalendáře, ať máte přehled v zájmu prevence dvojích rezervací",
"no_category_apps_description_conferencing": "Zkuste přidat aplikaci pro konference, která umožní propojit videohovory s vašimi klienty",
"no_category_apps_description_payment": "Přidejte platební aplikaci, která usnadní provádění transakcí mezi vámi a vašimi klienty",
"no_category_apps_description_analytics": "Přidejte analytickou aplikaci pro vaše rezervační stránky",
"no_category_apps_description_other": "Přidejte jakýkoli jiný typ aplikace pro nejrůznější činnosti",
"installed_app_calendar_description": "Nastavte si kalendáře, ať můžete kontrolovat konflikty a zabránit tak dvojím rezervacím.",
"installed_app_conferencing_description": "Přidejte své oblíbené aplikace pro videokonference pro vaše schůzky",

View File

@ -1324,5 +1324,6 @@
"saml_sp_entity_id": "SP Entity ID",
"saml_sp_acs_url_copied": "ACS URL copied!",
"saml_sp_entity_id_copied": "SP Entity ID copied!",
"saml_btn_configure": "Configure"
"saml_btn_configure": "Configure",
"add_calendar": "Add Calendar"
}

View File

@ -313,6 +313,7 @@
"past_bookings": "Le tue prenotazioni passate appariranno qui.",
"cancelled_bookings": "Le tue prenotazioni passate appariranno qui.",
"unconfirmed_bookings": "Le tue prenotazioni non confermate appariranno qui.",
"unconfirmed_bookings_tooltip": "Prenotazioni non confermate",
"on": "su",
"and": "e",
"calendar_shows_busy_between": "Il tuo calendario ti mostra come occupato tra",
@ -766,11 +767,16 @@
"no_category_apps_description_calendar": "Aggiungi un'app di calendario per controllare i conflitti ed evitare doppie prenotazioni",
"no_category_apps_description_conferencing": "Prova ad aggiungere un'app di conferenza per integrare le videochiamate con i clienti",
"no_category_apps_description_payment": "Aggiungi un'app di pagamento per facilitare le transazioni con i clienti",
"no_category_apps_description_analytics": "Aggiungi un'app di analisi per le tue pagine di prenotazione",
"no_category_apps_description_automation": "Aggiungi un'app di automazione da usare",
"no_category_apps_description_other": "Aggiungi qualsiasi altra app per fare altre attività",
"installed_app_calendar_description": "Imposta uno o più calendari per controllare i conflitti ed evitare doppie prenotazioni.",
"installed_app_conferencing_description": "Aggiungi le tue app di videoconferenza preferite per le tue riunioni",
"installed_app_payment_description": "Configura quale servizio di elaborazione dei pagamenti usare per addebitare i clienti.",
"installed_app_analytics_description": "Imposta quali app di analisi usare per le tue pagine di prenotazione",
"installed_app_other_description": "Tutte le app installate appartenenti ad altre categorie.",
"installed_app_automation_description": "Imposta quali app di automazione usare",
"analytics": "Analisi",
"empty_installed_apps_headline": "Nessuna app installata",
"empty_installed_apps_description": "Le app consentono di ottimizzare il flusso di lavoro e di migliorare significativamente la pianificazione.",
"empty_installed_apps_button": "Esplora l'App Store",
@ -826,6 +832,7 @@
"redirect_success_booking": "Reindirizza su prenotazione ",
"you_are_being_redirected": "Verrai reindirizzato a {{ url }} tra $t(second, {\"count\": {{seconds}} }).",
"external_redirect_url": "https://example.com/redirect-to-my-success-page",
"redirect_url_description": "Reindirizza a un URL personalizzato dopo una prenotazione correttamente effettuata",
"duplicate": "Duplica",
"offer_seats": "Imposta posti",
"offer_seats_description": "Indica i posti disponibili (ciò disabiliterà le prenotazioni ospite e le prenotazioni opt-in).",
@ -1156,6 +1163,8 @@
"connect_conference_apps": "Connetti app di conferenza",
"connect_calendar_apps": "Connetti app di calendario",
"connect_payment_apps": "Connetti app di pagamento",
"connect_automation_apps": "Connetti app di automazione",
"connect_analytics_apps": "Connetti app di analisi",
"connect_other_apps": "Connetti altre app",
"current_step_of_total": "Passo {{currentStep}} di {{maxSteps}}",
"add_variable": "Aggiungi variabile",
@ -1240,6 +1249,7 @@
"back_to_signin": "Torna alla pagina di accesso",
"reset_link_sent": "Link per la reimpostazione inviato",
"password_reset_email": "Un'e-mail con le istruzioni per reimpostare la password è stata inviata all'indirizzo {{email}}.",
"password_reset_leading": "Se un'e-mail non ti dovesse arrivare a breve, assicurati che l'indirizzo specificato sia corretto e controlla la cartella della posta indesiderata. Se il problema persiste, contatta l'assistenza clienti.",
"password_updated": "Password aggiornata!",
"pending_payment": "Pagamento in sospeso",
"confirmation_page_rainbow": "Proteggi i tuoi eventi con token o NFT su Ethereum, Polygon e altri.",
@ -1252,12 +1262,53 @@
"seats": "posti",
"every_app_published": "Tutte le applicazioni pubblicate nell'App store di Cal.com sono open source e sono state testate in modo approfondito attraverso una valutazione tra pari. Tuttavia, Cal.com, Inc. non promuove né certifica tali applicazioni, a meno che siano pubblicate da Cal.com. Se noti contenuti o comportamenti inappropriati, ti invitiamo a segnalarceli.",
"report_app": "Segnala app",
"limit_booking_frequency": "Limita frequenza di prenotazione",
"limit_booking_frequency_description": "Limita quante volte è possibile prenotare questo evento",
"add_limit": "Aggiungi limite",
"team_name_required": "Nome del team richiesto",
"show_attendees": "Condividi le informazioni dei partecipanti tra gli ospiti",
"how_additional_inputs_as_variables": "Come usare input aggiuntivi come variabili",
"format": "Formato",
"uppercase_for_letters": "Usa tutte lettere maiuscole",
"replace_whitespaces_underscores": "Sostituisci spazi con sottolineature",
"manage_billing": "Gestisci fatturazione",
"manage_billing_description": "Gestisci la fatturazione di tutti gli elementi",
"billing_freeplan_title": "Stai attualmente usando il piano GRATUITO",
"billing_freeplan_description": "Lavoriamo meglio in squadra. Estendi i tuoi flussi di lavoro con tavole rotonde ed eventi di gruppo e crea moduli di instradamento avanzati",
"billing_freeplan_cta": "Prova ora",
"billing_manage_details_title": "Visualizza e gestisci i tuoi dati di fatturazione",
"billing_manage_details_description": "Visualizza e modifica i tuoi dati di fatturazione e annulla il tuo abbonamento.",
"billing_portal": "Portale di fatturazione",
"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 il supporto",
"ignore_special_characters": "Ignora caratteri speciali nelle etichette degli input aggiuntivi. Usa solo lettere e numeri",
"retry": "Riprova",
"fetching_calendars_error": "Si è verificato un problema durante il recupero dei calendari. Si prega di <1>riprovare</1> o di contattare l'assistenza clienti.",
"calendar_connection_fail": "Impossibile connettersi al calendario",
"booking_confirmation_success": "Prenotazione confermata correttamente",
"booking_confirmation_fail": "Conferma prenotazione non riuscita",
"we_wont_show_again": "Questo messaggio non verrà più visualizzato",
"couldnt_update_timezone": "Impossibile aggiornare il fuso orario",
"updated_timezone_to": "Fuso orario aggiornato a {{formattedCurrentTz}}",
"update_timezone": "Aggiorna fuso orario",
"update_timezone_question": "Aggiornare il fuso orario?",
"update_timezone_description": "Sembra che il fuso orario locale sia stato cambiato in {{formattedCurrentTz}}. È molto importante che il fuso orario sia impostato correttamente per evitare prenotazioni in orari indesiderati. Aggiornare il fuso orario?",
"dont_update": "Non aggiornare",
"require_additional_notes": "Richiedi note aggiuntive",
"require_additional_notes_description": "Chiedi di compilare le note aggiuntive al momento della prenotazione",
"email_address_action": "invia e-mail a un indirizzo e-mail specifico",
"after_event_trigger": "dopo la fine dell'evento",
"how_long_after": "Quanto tempo dopo la fine dell'evento?",
"no_available_slots": "Nessuna fascia oraria disponibile",
"time_available": "Tempo disponibile",
"install_new_calendar_app": "Installa nuova app di calendario",
"make_phone_number_required": "Rendi tassativo il numero di telefono per prenotare un evento",
"dont_have_permission": "Non hai l'autorizzazione per accedere a questa risorsa.",
"saml_config": "Configurazione SAML",
"saml_description": "Consenti ai membri del team di effettuare l'accesso utilizzando un provider di identità",
"saml_config_deleted_successfully": "Configurazione SAML eliminata",
"saml_config_updated_successfully": "Configurazione SAML aggiornata correttamente",
"saml_configuration": "Configurazione SAML",
"delete_saml_configuration": "Elimina configurazione SAML",
"delete_saml_configuration_confirmation_message": "Sei sicuro di voler eliminare la configurazione SAML? I membri del tuo team che utilizzano l'accesso SAML non saranno più in grado di accedere a Cal.com.",
@ -1265,5 +1316,12 @@
"saml_not_configured_yet": "SAML non ancora configurato",
"saml_configuration_description": "Per aggiornare la configurazione SAML, incolla nella casella sottostante i metadati SAML forniti dal tuo provider di identità.",
"saml_configuration_placeholder": "Incolla qui i metadati SAML forniti dal tuo provider di identità",
"saml_email_required": "Inserisci un indirizzo email che consenta di individuare il tuo provider di identità SAML"
"saml_email_required": "Inserisci un indirizzo email che consenta di individuare il tuo provider di identità SAML",
"saml_sp_title": "Dettagli del fornitore di servizi",
"saml_sp_description": "Il tuo provider di identità (IdP) ti chiederà le seguenti informazioni per completare la configurazione dell'applicazione SAML.",
"saml_sp_acs_url": "URL dell'ACS",
"saml_sp_entity_id": "ID Entità SP",
"saml_sp_acs_url_copied": "URL dell'ACS copiato!",
"saml_sp_entity_id_copied": "ID entità SP copiato!",
"saml_btn_configure": "Configura"
}

View File

@ -230,7 +230,7 @@
"current_time": "Ora curentă",
"details": "Detalii",
"welcome": "Bun venit",
"welcome_back": "Bine ai revenit",
"welcome_back": "Bine ați revenit",
"welcome_to_calcom": "Bine ați venit la Cal.com",
"welcome_instructions": "Spune-ne ce să te sune și spune-ne în ce fus orar. Vei putea edita mai târziu.",
"connect_caldav": "Conectează-te la serverul CalDav",
@ -243,7 +243,7 @@
"when": "Când",
"where": "Unde",
"add_to_calendar": "Adaugă în calendar",
"add_another_calendar": "Adaugă alt calendar",
"add_another_calendar": "Adăugați alt calendar",
"other": "Altele",
"emailed_you_and_attendees": "V-am trimis un e-mail dumneavoastră şi celuilalt participant la o invitaţie în calendar cu toate detaliile.",
"emailed_you_and_attendees_recurring": "V-am trimis un e-mail dvs. și celorlalți participanți cu o invitație în calendar pentru primul dintre aceste evenimente recurente.",
@ -312,7 +312,7 @@
"recurring_bookings": "De îndată ce o persoană rezervă o întâlnire recurentă cu dvs., întâlnirea va apărea aici.",
"past_bookings": "Rezervările anterioare vor apărea aici.",
"cancelled_bookings": "Rezervările tale anulate vor apărea aici.",
"unconfirmed_bookings": "Rezervările tale neconfirmate vor apărea aici.",
"unconfirmed_bookings": "Rezervările dvs. neconfirmate vor apărea aici.",
"on": "pe",
"and": "şi",
"calendar_shows_busy_between": "Calendarul vă arată ca ocupat între",
@ -496,9 +496,9 @@
"lets_create_first_administrator_user": "Haideți să creăm primul utilizator administrator.",
"new_member": "Membru Nou",
"invite": "Invită",
"add_team_members": "Adaugă membri în echipă",
"add_team_members": "Adăugați membri în echipă",
"add_team_members_description": "Invită alte persoane să se alăture echipei tale",
"add_team_member": "Adaugă membru al echipei",
"add_team_member": "Adăugați membru al echipei",
"invite_new_member": "Invită un membru nou",
"invite_new_team_member": "Invită pe cineva în echipa ta.",
"change_member_role": "Schimbați rolul de membru al echipei",
@ -665,7 +665,7 @@
"disable_guests_description": "Dezactivează adăugarea de vizitatori suplimentari în timpul rezervării.",
"private_link": "Generare URL privat",
"private_link_label": "Link privat",
"private_link_hint": "Linkul tău privat se va regenera după fiecare utilizare",
"private_link_hint": "Linkul dvs. privat se va regenera după fiecare utilizare",
"copy_private_link": "Copiere link privat",
"private_link_description": "Generați un URL privat pentru distribuire fără expunerea numelui de utilizator Cal",
"invitees_can_schedule": "Invitații pot programa",
@ -715,7 +715,7 @@
"next_step_text": "Următorul pas",
"next_step": "Sari peste",
"prev_step": "Pas anterior",
"install": "Instalează",
"install": "Instalați",
"installed": "Instalat",
"active_install_one": "{{count}} instalare activă",
"active_install_other": "{{count}} (de) instalări active",
@ -763,13 +763,13 @@
"explore_apps": "Aplicații {{category}}",
"installed_apps": "Aplicații instalate",
"no_category_apps": "Nicio aplicație {{category}}",
"no_category_apps_description_calendar": "Adaugă o aplicație calendar pentru a verifica dacă există conflicte și a preveni rezervări suprapuse",
"no_category_apps_description_calendar": "Adăugați o aplicație calendar pentru a verifica dacă există conflicte și a preveni rezervări suprapuse",
"no_category_apps_description_conferencing": "Încercați să adăugați o aplicație de conferință pentru a integra apelul video cu clienții dvs.",
"no_category_apps_description_payment": "Adaugă o aplicație de plată pentru a facilita tranzacția între tine și clienții tăi",
"no_category_apps_description_other": "Adaugă orice alt tip de aplicație pentru a face tot felul de lucruri",
"installed_app_calendar_description": "Setează calendarele să verifice dacă există conflicte, pentru a preveni rezervările suprapuse.",
"installed_app_conferencing_description": "Adaugă aplicațiile tale preferate de conferințe video pentru ședințele tale",
"installed_app_payment_description": "Configurează serviciile de procesare a plăților care vor fi utilizate la taxarea clienților.",
"no_category_apps_description_payment": "Adăugați o aplicație de plată pentru a facilita tranzacția între dvs. și clienții dvs.",
"no_category_apps_description_other": "Adăugați orice alt tip de aplicație pentru a face tot felul de lucruri",
"installed_app_calendar_description": "Setați calendarele să verifice dacă există conflicte, pentru a preveni rezervările suprapuse.",
"installed_app_conferencing_description": "Adăugați aplicațiile dvs. preferate de conferințe video pentru ședințele dvs.",
"installed_app_payment_description": "Configurați serviciile de procesare a plăților care vor fi utilizate la taxarea clienților.",
"installed_app_other_description": "Toate aplicațiile instalate din alte categorii.",
"empty_installed_apps_headline": "Nicio aplicație instalată",
"empty_installed_apps_description": "Aplicațiile vă permit să vă îmbunătățiți în mod semnificativ fluxul de lucru și activitățile legate de programări.",
@ -809,7 +809,7 @@
"set_to_default": "Setare ca implicit",
"new_schedule_btn": "Program nou",
"add_new_schedule": "Adăugați un program nou",
"add_new_calendar": "Adaugă un calendar nou",
"add_new_calendar": "Adăugați un calendar nou",
"set_calendar": "Setați unde să adăugați evenimente noi când sunteți rezervat.",
"delete_schedule": "Ștergeți programul",
"schedule_created_successfully": "Programul {{scheduleName}} a fost creat cu succes",
@ -1077,61 +1077,61 @@
"notification_sent": "Notificare trimisă",
"no_input": "Nu s-a introdus nimic",
"test_workflow_action": "Acțiune flux de lucru de testare",
"send_sms": "Trimite SMS",
"send_sms_to_number": "Sigur vrei să trimiți un SMS către {{number}}?",
"send_sms": "Trimiteți SMS",
"send_sms_to_number": "Sigur vreți să trimiteți un SMS către {{number}}?",
"missing_connected_calendar": "Nu este conectat niciun calendar implicit",
"connect_your_calendar_and_link": "Îți poți conecta calendarul de <1>aici</1>.",
"connect_your_calendar_and_link": "Vă puteți conecta calendarul de <1>aici</1>.",
"default_calendar_selected": "Calendar implicit",
"hide_from_profile": "Ascunde din profil",
"hide_from_profile": "Ascundeți din profil",
"event_setup_tab_title": "Configurare eveniment",
"event_limit_tab_title": "Limite",
"event_limit_tab_description": "Cât de des poți fi rezervat",
"event_limit_tab_description": "Cât de des puteți fi rezervat",
"event_advanced_tab_description": "Setări calendar și mai multe...",
"event_advanced_tab_title": "Avansat",
"select_which_cal": "Alege la ce calendar se adaugă rezervările",
"select_which_cal": "Alegeți la ce calendar se adaugă rezervările",
"custom_event_name": "Nume eveniment personalizat",
"custom_event_name_description": "Creează nume de evenimente personalizate pentru a fi afișate pe evenimentul calendarului",
"custom_event_name_description": "Creați nume de evenimente personalizate pentru a fi afișate pe evenimentul calendarului",
"2fa_required": "Autentificare cu doi factori necesară",
"incorrect_2fa": "Cod incorect de autentificare cu doi factori",
"which_event_type_apply": "La ce tip de eveniment se va aplica aceasta?",
"no_workflows_description": "Fluxurile de lucru permit automatizarea simplă pentru a trimite notificări și mementouri, care îți permit să construiești procese în jurul evenimentelor tale.",
"create_workflow": "Creează un flux de lucru",
"do_this": "Fă acest lucru",
"turn_off": "Dezactivează",
"no_workflows_description": "Fluxurile de lucru permit automatizarea simplă pentru a trimite notificări și mementouri, care vă permit să construiți procese în jurul evenimentelor dvs.",
"create_workflow": "Creați un flux de lucru",
"do_this": "Faceți acest lucru",
"turn_off": "Dezactivați",
"settings_updated_successfully": "Setări actualizate cu succes",
"error_updating_settings": "Eroare la actualizarea setărilor",
"personal_cal_url": "URL-ul meu personal Cal",
"bio_hint": "Câteva propoziții despre dvs. Acestea vor apărea pe pagina URL-ului dvs. personal.",
"delete_account_modal_title": "Șterge contul",
"confirm_delete_account_modal": "Sigur dorești să îți ștergi contul Cal.com?",
"delete_my_account": "Șterge contul meu",
"delete_account_modal_title": "Ștergeți contul",
"confirm_delete_account_modal": "Sigur doriți să vă ștergeți contul Cal.com?",
"delete_my_account": "Ștergeți contul meu",
"start_of_week": "Începutul săptămânii",
"select_calendars": "Selectează pe ce calendare vrei să verifici dacă există conflicte pentru a preveni rezervările suprapuse.",
"select_calendars": "Selectați pe ce calendare vreți să verificați dacă există conflicte pentru a preveni rezervările suprapuse.",
"check_for_conflicts": "Verifică dacă există conflicte",
"adding_events_to": "Adăugarea de evenimente la",
"follow_system_preferences": "Urmărește preferințele sistemului",
"follow_system_preferences": "Urmăriți preferințele sistemului",
"custom_brand_colors": "Culori personalizate ale mărcii",
"customize_your_brand_colors": "Personalizați-vă pagina de rezervare folosind culoarea mărcii dvs.",
"pro": "Pro",
"removes_cal_branding": "Elimină orice mărci de tip Cal, și anume „Powered by Cal”.",
"removes_cal_branding": "Eliminați orice mărci de tip Cal, și anume „Powered by Cal”.",
"profile_picture": "Poză de profil",
"upload": "Încarcă",
"upload": "Încărcați",
"web3": "Web3",
"rainbow_token_gated": "Accesul la acest tip de eveniment se face pe baza tokenului.",
"rainbow_connect_wallet_gate": "Conectați-vă portofelul dacă dețineți <1>{{name}}</1> (<3>{{symbol}}</3>).",
"rainbow_insufficient_balance": "Portofelul tău conectat nu conține suficient <1>{{symbol}}</1>.",
"rainbow_insufficient_balance": "Portofelul dvs. conectat nu conține suficient <1>{{symbol}}</1>.",
"rainbow_sign_message_request": "Semnați solicitarea de mesaj în portofel.",
"rainbow_signature_error": "Eroare la solicitarea semnăturii din portofel.",
"token_address": "Adresă token",
"blockchain": "Blockchain",
"old_password": "Parola veche",
"secure_password": "Noua ta parolă super sigură",
"secure_password": "Noua dvs. parolă super sigură",
"error_updating_password": "Eroare la actualizarea parolei",
"two_factor_auth": "Autentificare cu doi factori",
"recurring_event_tab_description": "Configurează un program de repetare",
"recurring_event_tab_description": "Configurați un program de repetare",
"today": "astăzi",
"appearance": "Aspect",
"appearance_subtitle": "Gestionează setările pentru aspectul rezervărilor",
"appearance_subtitle": "Gestionați setările pentru aspectul rezervărilor",
"my_account": "Contul meu",
"general": "Generalități",
"calendars": "Calendare",
@ -1141,28 +1141,28 @@
"impersonation": "Reprezentare",
"users": "Utilizatori",
"profile_description": "Gestionați setările profilului dvs. Cal",
"general_description": "Gestionează setările pentru limba și fusul orar",
"calendars_description": "Configurează cum interacționează tipurile de evenimente cu calendarele tale",
"appearance_description": "Gestionează setările pentru aspectul rezervărilor tale",
"general_description": "Gestionați setările pentru limba și fusul orar",
"calendars_description": "Configurați cum interacționează tipurile de evenimente cu calendarele dvs.",
"appearance_description": "Gestionați setările pentru aspectul rezervărilor dvs.",
"conferencing_description": "Gestionează aplicațiile de conferințe video pentru ședințele tale",
"password_description": "Gestionează setările pentru parolele contului tău",
"2fa_description": "Gestionează setările pentru parolele contului tău",
"we_just_need_basic_info": "Avem nevoie doar de câteva informații de bază pentru configurarea profilului tău.",
"skip": "Omite",
"password_description": "Gestionați setările pentru parolele contului dvs.",
"2fa_description": "Gestionați setările pentru parolele contului dvs.",
"we_just_need_basic_info": "Avem nevoie doar de câteva informații de bază pentru configurarea profilului dvs.",
"skip": "Omiteți",
"do_this_later": "Mai târziu",
"set_availability_getting_started_subtitle_1": "Definiți intervalele de timp pentru disponibilitatea dvs.",
"set_availability_getting_started_subtitle_2": "Puteți personaliza toate acestea mai târziu în pagina de disponibilitate.",
"connect_calendars_from_app_store": "Puteți adăuga mai multe calendare din magazinul de aplicații",
"connect_conference_apps": "Conectează aplicațiile de conferință",
"connect_calendar_apps": "Conectează aplicații calendar",
"connect_payment_apps": "Conectează aplicații de plată",
"connect_other_apps": "Conectează alte aplicații",
"connect_conference_apps": "Conectați aplicațiile de conferință",
"connect_calendar_apps": "Conectați aplicații calendar",
"connect_payment_apps": "Conectați aplicații de plată",
"connect_other_apps": "Conectați alte aplicații",
"current_step_of_total": "Pasul {{currentStep}} din {{maxSteps}}",
"add_variable": "Adaugă variabilă",
"add_variable": "Adăugați variabilă",
"custom_phone_number": "Număr de telefon personalizat",
"message_template": "Șablon mesaj",
"email_subject": "Subiect e-mail",
"add_dynamic_variables": "Adaugă variabile text dinamice",
"add_dynamic_variables": "Adăugați variabile text dinamice",
"event_name_info": "Numele tipului de eveniment",
"event_date_info": "Data evenimentului",
"event_time_info": "Ora începerii evenimentului",
@ -1175,83 +1175,83 @@
"download_responses": "Răspunsuri la descărcare",
"create_your_first_form": "Creați primul dvs. formular",
"create_your_first_form_description": "Cu ajutorul formularelor de direcționare puteți adresa întrebări de calificare şi direcționa către persoana sau tipul de eveniment corect.",
"create_your_first_webhook": "Creează primul tău Webhook",
"create_your_first_webhook_description": "Cu Webhook-urile, poți primi date despre ședințe în timp real atunci când se întâmplă ceva pe Cal.com.",
"create_your_first_webhook": "Creați primul dvs. Webhook",
"create_your_first_webhook_description": "Cu Webhook-urile, puteți primi date despre ședințe în timp real atunci când se întâmplă ceva pe Cal.com.",
"for_a_maximum_of": "Pentru maximum",
"event_one": "eveniment",
"event_other": "evenimente",
"profile_team_description": "Gestionează setările profilului echipei tale",
"profile_team_description": "Gestionați setările profilului echipei dvs.",
"members_team_description": "Utilizatori care sunt în grup",
"team_url": "URL-ul echipei",
"delete_team": "Șterge echipa",
"delete_team": "Ștergeți echipa",
"team_members": "Membrii echipei",
"more": "Mai mult",
"more_page_footer": "Considerăm că aplicația mobilă este o extensie a aplicației web. Dacă efectuezi orice acțiuni complicate, te rugăm să consulți aplicația web.",
"workflow_example_1": "Trimite participantului un memento prin SMS cu 24 de ore înainte de începerea evenimentului",
"workflow_example_2": "Trimite participantului un SMS personalizat atunci când evenimentul este reprogramat",
"more_page_footer": "Considerăm că aplicația mobilă este o extensie a aplicației web. Dacă efectuați orice acțiuni complicate, vă rugăm să consultați aplicația web.",
"workflow_example_1": "Trimiteți participantului un memento prin SMS cu 24 de ore înainte de începerea evenimentului",
"workflow_example_2": "Trimiteți participantului un SMS personalizat atunci când evenimentul este reprogramat",
"workflow_example_3": "Trimiteți gazdei un e-mail personalizat atunci când evenimentul nou este rezervat",
"workflow_example_4": "Trimite participantului un memento prin e-mail cu 1 oră înainte de începerea evenimentului",
"workflow_example_4": "Trimiteți participantului un memento prin e-mail cu 1 oră înainte de începerea evenimentului",
"workflow_example_5": "Trimiteți gazdei un e-mail personalizat atunci când evenimentul nou este reprogramat",
"workflow_example_6": "Trimiteți gazdei un SMS personalizat atunci când evenimentul nou este rezervat",
"welcome_to_cal_header": "Bun venit la Cal.com!",
"edit_form_later_subtitle": "Vei putea să editezi mai târziu.",
"edit_form_later_subtitle": "Veți putea să editați mai târziu.",
"connect_calendar_later": "Îmi voi conecta calendarul mai târziu",
"set_my_availability_later": "Îmi voi seta disponibilitatea mai târziu",
"problem_saving_user_profile": "A apărut o problemă la salvarea datelor tale. Încearcă din nou sau contactează serviciul de asistență pentru clienți.",
"problem_saving_user_profile": "A apărut o problemă la salvarea datelor dvs. Încercați din nou sau contactați serviciul de asistență pentru clienți.",
"purchase_missing_seats": "Cumpărați locuri lipsă",
"slot_length": "Lungime interval",
"booking_appearance": "Aspectul rezervării",
"appearance_team_description": "Gestionează setările pentru aspectul rezervărilor echipei tale",
"appearance_team_description": "Gestionați setările pentru aspectul rezervărilor echipei dvs.",
"only_owner_change": "Doar proprietarul acestei echipe poate schimba rezervarea echipei ",
"team_disable_cal_branding_description": "Eliminați orice mărci de tip Cal, și anume „Powered by Cal”",
"invited_by_team": "{{teamName}} te-a invitat să te alături echipei sale ca {{role}}",
"invited_by_team": "{{teamName}} v-a invitat să vă alăturați echipei sale ca {{role}}",
"token_invalid_expired": "Tokenul este nevalid sau expirat.",
"exchange_add": "Conectează-te la Microsoft Exchange",
"exchange_add": "Conectați-vă la Microsoft Exchange",
"exchange_authentication": "Metodă de autentificare",
"exchange_authentication_standard": "Autentificare de bază",
"exchange_authentication_ntlm": "Autentificare NTLM",
"exchange_compression": "Compresie GZip",
"routing_forms_description": "Aici poți vedea toate formularele și direcțioările pe care le-ai creat.",
"add_new_form": "Adaugă formular nou",
"form_description": "Creează formularul pentru direcționarea unei persoane care face rezervare",
"routing_forms_description": "Aici puteți vedea toate formularele și direcțioările pe care le-ati creat.",
"add_new_form": "Adăugați formular nou",
"form_description": "Creați formularul pentru direcționarea unei persoane care face rezervare",
"copy_link_to_form": "Copiați linkul la formular",
"theme": "Temă",
"theme_applies_note": "Acest lucru se aplică numai paginilor tale de rezervare publice",
"theme_applies_note": "Acest lucru se aplică numai paginilor dvs. de rezervare publice",
"theme_light": "Lumină",
"theme_dark": "Întunecat",
"theme_system": "Implicit sistem",
"add_a_team": "Adaugă o echipă",
"add_webhook_description": "Primește datele ședințelor în timp real atunci când se întâmplă ceva pe Cal.com",
"add_a_team": "Adăugați o echipă",
"add_webhook_description": "Primiți datele ședințelor în timp real atunci când se întâmplă ceva pe Cal.com",
"triggers_when": "Se declanșează când",
"test_webhook": "Testați ping-ul înainte de creare.",
"enable_webhook": "Activează Webhook",
"add_webhook": "Adaugă Webhook",
"enable_webhook": "Activați Webhook",
"add_webhook": "Adăugați Webhook",
"webhook_edited_successfully": "Webhook salvat",
"webhooks_description": "Primește datele ședințelor în timp real atunci când se întâmplă ceva pe Cal.com",
"api_keys_description": "Generează chei API pentru accesarea propriului tău cont",
"webhooks_description": "Primiți datele ședințelor în timp real atunci când se întâmplă ceva pe Cal.com",
"api_keys_description": "Generați chei API pentru accesarea propriului dvs. cont",
"new_api_key": "Cheie API nouă",
"active": "Activ",
"api_key_updated": "Nume cheie API actualizat",
"api_key_update_failed": "Eroare la actualizarea numelui cheii API",
"embeds_title": "Încorporare HTML iframe",
"embeds_description": "Încorporați toate tipurile de evenimente pe site-ul dvs.",
"create_first_api_key": "Creează prima ta cheie API",
"create_first_api_key": "Creați prima dvs. cheie API",
"create_first_api_key_description": "Cheile API permit altor aplicații să comunice cu Cal.com",
"back_to_signin": "Înapoi la autentificare",
"reset_link_sent": "Link de resetare trimis",
"password_reset_email": "Vei primi în curând un e-mail la {{email}} cu instrucțiuni de resetare a parolei.",
"password_reset_email": "Veți primi în curând un e-mail la {{email}} cu instrucțiuni de resetare a parolei.",
"password_updated": "Parolă actualizată!",
"pending_payment": "Plată în așteptare",
"confirmation_page_rainbow": "Restricționați accesul la evenimentul dvs. cu tokeni sau NFT-uri pe Ethereum, Polygon și altele.",
"not_on_cal": "Nu pe Cal.com",
"no_calendar_installed": "Niciun calendar instalat",
"no_calendar_installed_description": "Încă nu ai conectat niciunul dintre calendarele tale",
"add_a_calendar": "Adaugă un calendar",
"change_email_hint": "S-ar putea să fie nevoie să te deconectezi și să te reconectezi pentru aplicarea oricăror modificări",
"confirm_password_change_email": "Te rugăm să confirmi parola înainte de a-ți schimba adresa de e-mail",
"no_calendar_installed_description": "Încă nu ați conectat niciunul dintre calendarele dvs.",
"add_a_calendar": "Adăugați un calendar",
"change_email_hint": "S-ar putea să fie nevoie să vă deconectați și să vă reconectați pentru aplicarea oricăror modificări",
"confirm_password_change_email": "Vă rugăm să confirmați parola înainte de a vă schimba adresa de e-mail",
"seats": "locuri",
"every_app_published": "Fiecare aplicație publicată pe Cal.com App Store este open source și testată temeinic prin evaluări inter pares. Cu toate acestea, Cal.com, Inc. nu susține sau certifică aceste aplicații decât dacă sunt publicate de Cal.com. Dacă întâmpini un conținut sau un comportament necorespunzător, te rugăm să îl raportezi.",
"report_app": "Raportează aplicația",
"every_app_published": "Fiecare aplicație publicată pe Cal.com App Store este open source și testată temeinic prin evaluări inter pares. Cu toate acestea, Cal.com, Inc. nu susține sau certifică aceste aplicații decât dacă sunt publicate de Cal.com. Dacă întâmpini un conținut sau un comportament necorespunzător, vă rugăm să îl raportați.",
"report_app": "Raportați aplicația",
"billing_manage_details_title": "Vizualizați și gestionați detaliile de facturare",
"billing_manage_details_description": "Vizualizați și editați detaliile de facturare, precum și anulați abonarea.",
"billing_help_title": "Aveți nevoie de altceva?",

View File

@ -313,6 +313,7 @@
"past_bookings": "Vaše prethodne rezervacije će se pojaviti ovde.",
"cancelled_bookings": "Vaše otkazane rezervacije će se pokazati ovde.",
"unconfirmed_bookings": "Vaše nepotvrđene rezervacije će se pokazati ovde.",
"unconfirmed_bookings_tooltip": "Nepotvrđene rezervacije",
"on": "u",
"and": "i",
"calendar_shows_busy_between": "Vaš kalendar pokazuje da ste zauzeti između",
@ -542,6 +543,7 @@
"link_shared": "Link je podeljen!",
"title": "Naziv",
"description": "Opis",
"apps_status": "Status aplikacija",
"quick_video_meeting": "Kratak video poziv.",
"scheduling_type": "Tip Rezervacije",
"preview_team": "Pregled tima",
@ -766,11 +768,16 @@
"no_category_apps_description_calendar": "Dodajte kalendar aplikaciju da biste proverili da li postoje konflikti i time sprečili dvostruka zakazivanja",
"no_category_apps_description_conferencing": "Pokušajte da dodate aplikaciju za konferenciju da biste integrisali video poziv sa svojim klijentima",
"no_category_apps_description_payment": "Dodajte aplikaciju za plaćanje da biste olakšali transakcije između vas i klijenata",
"no_category_apps_description_analytics": "Dodajte analitičku aplikaciju za vaše stranice za rezervacije",
"no_category_apps_description_automation": "Dodajte aplikaciju za automatizaciju za korišćenje",
"no_category_apps_description_other": "Dodajte bilo koju vrstu aplikacije da biste uradili svakakve stvari",
"installed_app_calendar_description": "Podesite kalendar(e) da biste proverili da li postoje konflikti i time sprečili dvostruka zakazivanja.",
"installed_app_conferencing_description": "Dodajte vaše omiljene aplikacije za video konferenciju za vaše sastanke",
"installed_app_payment_description": "Konfigurišite koju vrstu servisa za obradu plaćanja želite da koristite prilikom naplate od vaših klijenata.",
"installed_app_analytics_description": "Konfigurišite koje ćete analitičke aplikacije da koristite za vaše stranice za rezervacije",
"installed_app_other_description": "Sve vaše instalirane aplikacije iz drugih kategorija.",
"installed_app_automation_description": "Konfigurišite koje ćete aplikacije za automatizaciju da koristite",
"analytics": "Analitika",
"empty_installed_apps_headline": "Aplikacije nisu instalirane",
"empty_installed_apps_description": "Aplikacije omogućavaju da unapredite svoj rad i značajno poboljšate svoja zakazivanja.",
"empty_installed_apps_button": "Istražite App Store",
@ -826,6 +833,7 @@
"redirect_success_booking": "Preusmeri na rezervaciju ",
"you_are_being_redirected": "Preusmeravamo vas na {{ url }} za $t(second, {\"count\": {{seconds}} }).",
"external_redirect_url": "https://example.com/redirect-to-my-success-page",
"redirect_url_description": "Preusmeri na prilagođenu URL stranicu nakon uspešne rezervacije",
"duplicate": "Dupliraj",
"offer_seats": "Ponudi mesta",
"offer_seats_description": "Ponudite mesta za zakazivanje. Ovo automatski onemogućava gostujuća i opciona zakazivanja.",
@ -1156,6 +1164,8 @@
"connect_conference_apps": "Povežite aplikacije za konferenciju",
"connect_calendar_apps": "Povežite aplikacije za kalendar",
"connect_payment_apps": "Povežite aplikacije za plaćanje",
"connect_automation_apps": "Povežite aplikacije za automatizaciju",
"connect_analytics_apps": "Povežite analitičke aplikacije",
"connect_other_apps": "Povežite druge aplikacije",
"current_step_of_total": "Korak {{currentStep}} od {{maxSteps}}",
"add_variable": "Dodaj promenljivu",
@ -1240,6 +1250,7 @@
"back_to_signin": "Nazad na prijavljivanje",
"reset_link_sent": "Veza za resetovanje je poslata",
"password_reset_email": "Imejl je na putu do {{email}} sa instrukcijama za resetovanje vaše lozinke.",
"password_reset_leading": "Ako uskoro ne primite imejl, proverite da li ste uneli ispravnu imejl adresu, proverite vašu fasciklu za neželjenu poštu ili se obratite podršci ako se problem nastavi.",
"password_updated": "Lozinka je ažurirana!",
"pending_payment": "Čeka se na plaćanje",
"confirmation_page_rainbow": "Zaštitite vaš događaj tokenom ili NFT-om na Ethereum-u, Polygon-u, itd.",
@ -1252,12 +1263,53 @@
"seats": "sedišta",
"every_app_published": "Svaka aplikacija objavljena na Cal.com prodavnici je otvorenog tipa i detaljno je testirana kroz recenzije saradnika. Bez obzira na to, Cal.com, Inc. ne podržava niti sertifikuje ove aplikacije osim ako ih nije objavio Cal.com. Ako naiđete na neprikladan sardžaj ili ponašanje, molimo vas da to prijavite.",
"report_app": "Prijavite aplikaciju",
"limit_booking_frequency": "Ograničite učestalost rezervacija",
"limit_booking_frequency_description": "Ograničite koliko puta ovaj događaj može da se rezerviše",
"add_limit": "Dodajte ograničenje",
"team_name_required": "Ime tima je potrebno",
"show_attendees": "Podelite informacije o učesnicima između gostiju",
"how_additional_inputs_as_variables": "Kako da koristite dodatne unose kao promenljive",
"format": "Format",
"uppercase_for_letters": "Koristite velika slova za sva slova",
"replace_whitespaces_underscores": "Zamenite prazna mesta donjom crtom",
"manage_billing": "Upravljajte naplatom",
"manage_billing_description": "Upravljajte svim u vezi sa naplatom",
"billing_freeplan_title": "Trenutno ste na BESPLATNOM planu",
"billing_freeplan_description": "Bolje radimo u timovima. Proširite svoje radne tokove sa kružnim i kolektivnim događajima i kreirajte napredne obrasce za rutiranje",
"billing_freeplan_cta": "Isprobajte sada",
"billing_manage_details_title": "Pregledajte detalje naplate i upravljajte njima",
"billing_manage_details_description": "Pogledajte i uredite vaše detalje naplate ili otkažite vašu pretplatu.",
"billing_portal": "Portal za naplatu",
"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": "Ignorišite specijalne karaktere u vašim oznakama dodatnih unosa. 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",
"booking_confirmation_success": "Potvrda rezervacije je uspešna",
"booking_confirmation_fail": "Potvrda rezervacije nije uspela",
"we_wont_show_again": "Ovo nećemo više prikazati",
"couldnt_update_timezone": "Nismo uspeli da ažuriramo vremensku zonu",
"updated_timezone_to": "Vremenska zona je ažurirana na {{formattedCurrentTz}}",
"update_timezone": "Ažurirajte vremensku zonu",
"update_timezone_question": "Ažurirajte vremensku zonu?",
"update_timezone_description": "Izgleda da je vaša lokalna vremenska zona promenjena na {{formattedCurrentTz}}. Jako je važno da imate ispravnu vremensku zonu da biste sprečili rezervacije u neželjeno vreme. Da li želite da je ažurirate?",
"dont_update": "Nemoj da ažuriraš",
"require_additional_notes": "Zahtevanje dodatnih napomena",
"require_additional_notes_description": "Zahtevajte da se popune dodatne napomene pre rezervisanja",
"email_address_action": "pošalji imejl na određene imejl adrese",
"after_event_trigger": "nakon što se događaj završi",
"how_long_after": "Koliko vremena nakon što se događaj završi?",
"no_available_slots": "Nema dostupnih mesta",
"time_available": "Dostupno vreme",
"install_new_calendar_app": "Instalirajte novu aplikaciju kalendara",
"make_phone_number_required": "Postavite da je neophodan telefonski broj za rezervaciju događaja",
"dont_have_permission": "Nemate dozvolu da pristupite ovom resursu.",
"saml_config": "SAML konfiguracija",
"saml_description": "Dozvolite članu tima da se prijavi kao Identity Provider",
"saml_config_deleted_successfully": "SAML konfiguracija uspešno izbrisana",
"saml_config_updated_successfully": "SAML konfiguracija je uspešno ažurirana",
"saml_configuration": "SAML konfiguracija",
"delete_saml_configuration": "Izbriši SAML konfiguraciju",
"delete_saml_configuration_confirmation_message": "Da li ste sigurni da želite izbrisati SAML konfiguraciju? Vaši članovi tima koji koriste SAML login neće moći više da pristupe Cal.com.",
@ -1265,5 +1317,12 @@
"saml_not_configured_yet": "SAML još nije konfigurisan",
"saml_configuration_description": "Molimo vas, nalepite SAML metadatu iz vašeg Identity Provider-a iz tekst forme ispod da bi ažurirali svoju SAML konfiguraciju.",
"saml_configuration_placeholder": "Molimo vas, nalepite SAML metadatu iz vašeg Identity Provider-a ovde",
"saml_email_required": "Molimo vas, unesite vašu e-poštu da bismo našli vaš SAML Identity Provider"
"saml_email_required": "Molimo vas, unesite vašu e-poštu da bismo našli vaš SAML Identity Provider",
"saml_sp_title": "Informacije o pružaocu usluga",
"saml_sp_description": "Vaš Identity Provider (IdP) će vam tražiti dodatne informacije da biste završili konfigurisanje SAML prijave.",
"saml_sp_acs_url": "ACS URL",
"saml_sp_entity_id": "SP entitet ID",
"saml_sp_acs_url_copied": "ACS URL je kopiran!",
"saml_sp_entity_id_copied": "SP entitet ID je kopiran!",
"saml_btn_configure": "Konfiguracija"
}

View File

@ -313,6 +313,7 @@
"past_bookings": "Geçmiş rezervasyonlarınız burada görünür.",
"cancelled_bookings": "İptal ettiğiniz rezervasyonlarınız burada görünecektir.",
"unconfirmed_bookings": "Onaylanmamış rezervasyonlarınız burada görünecektir.",
"unconfirmed_bookings_tooltip": "Onaylanmamış rezervasyonlar",
"on": "/",
"and": "ve",
"calendar_shows_busy_between": "Takviminiz sizi şu saatler arasında meşgul olarak gösteriyor:",
@ -542,6 +543,7 @@
"link_shared": "Bağlantı paylaşıldı!",
"title": "Başlık",
"description": "Açıklama",
"apps_status": "Uygulama Durumu",
"quick_video_meeting": "Hızlı bir görüntülü toplantı.",
"scheduling_type": "Planlama Türü",
"preview_team": "Ekibi ön izle",
@ -766,11 +768,16 @@
"no_category_apps_description_calendar": "Çifte rezervasyonları önlemek amacıyla çakışmaları kontrol etmek için bir takvim uygulaması ekleyin",
"no_category_apps_description_conferencing": "Müşterilerinizle görüntülü arama yapmak için bir konferans uygulaması eklemeyi deneyin",
"no_category_apps_description_payment": "Siz ve müşterileriniz arasındaki işlemleri kolaylaştırmak için bir ödeme uygulaması ekleyin",
"no_category_apps_description_analytics": "Rezervasyon sayfalarınız için bir analiz uygulaması ekleyin",
"no_category_apps_description_automation": "Kullanılacak otomasyon uygulamasını ekleyin",
"no_category_apps_description_other": "Her türlü işlemi yapmak için farklı bir uygulama ekleyin",
"installed_app_calendar_description": "Çifte rezervasyonları önlemek amacıyla çakışmaları kontrol etmek için takvimleri ayarlayın.",
"installed_app_conferencing_description": "Toplantılarınız için favori video konferans uygulamalarınızı ekleyin",
"installed_app_payment_description": "Müşterilerinizden ücret alırken hangi ödeme işleme hizmetlerinin kullanılacağını yapılandırın.",
"installed_app_analytics_description": "Rezervasyon sayfalarınız için hangi analiz uygulamalarını kullanacağınızı yapılandırın",
"installed_app_other_description": "Diğer kategorilerdeki tüm yüklü uygulamalarınız.",
"installed_app_automation_description": "Hangi otomasyon uygulamalarının kullanılacağını ayarlayın",
"analytics": "Analizler",
"empty_installed_apps_headline": "Yüklü uygulama yok",
"empty_installed_apps_description": "Uygulamalar, iş akışınızı iyileştirmenize ve planlama sürecinizi önemli ölçüde geliştirmenize olanak tanır.",
"empty_installed_apps_button": "Uygulama Mağazasını Keşfedin",
@ -826,6 +833,7 @@
"redirect_success_booking": "Rezervasyonu yönlendir ",
"you_are_being_redirected": "$t(second, {\"count\": {{seconds}} }) içinde {{ url }} adresine yönlendirileceksiniz.",
"external_redirect_url": "https://example.com/redirect-to-my-success-page",
"redirect_url_description": "Başarılı bir rezervasyondan sonra özel bir URL'ye yönlendirin",
"duplicate": "Çoğalt",
"offer_seats": "Yer teklif et",
"offer_seats_description": "Rezervasyonlar için koltuk teklif edin (bu işlem, misafirleri ve rezervasyon onaylarını devre dışı bırakır)",
@ -1156,6 +1164,8 @@
"connect_conference_apps": "Konferans uygulamalarını bağlayın",
"connect_calendar_apps": "Takvim uygulamalarını bağlayın",
"connect_payment_apps": "Ödeme uygulamalarını bağlayın",
"connect_automation_apps": "Otomasyon uygulamalarını bağlayın",
"connect_analytics_apps": "Analiz uygulamalarını bağlayın",
"connect_other_apps": "Diğer uygulamaları bağlayın",
"current_step_of_total": "Adım {{currentStep}} / {{maxSteps}}",
"add_variable": "Değişken ekle",
@ -1240,6 +1250,7 @@
"back_to_signin": "Giriş yapmak için geri dön",
"reset_link_sent": "Sıfırlama bağlantısı gönderildi",
"password_reset_email": "{{email}} adresine şifrenizi sıfırlama talimatlarını içeren bir e-posta gönderildi.",
"password_reset_leading": "Kısa süre içinde bir e-posta almazsanız, lütfen girdiğiniz e-posta adresinin doğru olup olmadığını ve spam klasörünüzü kontrol edin veya sorunun devam etmesi halinde destek ekibi ile iletişime geçin.",
"password_updated": "Şifre güncellendi!",
"pending_payment": "Bekleyen ödeme",
"confirmation_page_rainbow": "Ethereum, Polygon ve diğer token'lar veya NFT ile etkinliğinizi kontrol edin.",
@ -1252,12 +1263,53 @@
"seats": "yer",
"every_app_published": "Cal.com Uygulama Mağazası'nda yayınlanan her uygulama açık kaynak kodludur ve uzman incelemeleriyle kapsamlı bir şekilde test edilmiştir. Cal.com, Cal.com tarafından yayınlanmadıkça bu uygulamaları desteklemez veya onaylamaz. Uygunsuz içerik veya davranışla karşılaşırsanız lütfen bize bildirin.",
"report_app": "Uygulamayı bildir",
"limit_booking_frequency": "Rezervasyon sıklığını kısıtla",
"limit_booking_frequency_description": "Bu etkinlik için kaç kez rezervasyon yapılabileceğini sınırlayın",
"add_limit": "Kısıtlama Ekle",
"team_name_required": "Ekip adı gereklidir",
"show_attendees": "Katılımcı bilgilerini misafirler arasında paylaşın",
"how_additional_inputs_as_variables": "Ek Girdiler Değişken Olarak Nasıl Kullanılır",
"format": "Biçim",
"uppercase_for_letters": "Tüm harfleri büyük harf olarak kullanın",
"replace_whitespaces_underscores": "Boşlukları alt çizgi ile değiştir",
"manage_billing": "Faturalandırmayı yönet",
"manage_billing_description": "Faturalandırmayla ilgili her şeyi yönetin",
"billing_freeplan_title": "Şu anda ÜCRETSİZ plandasınız",
"billing_freeplan_description": "Ekip olarak daha iyi çalışıyoruz. Sıralı ve toplu etkinliklerle iş akışlarınızı genişletin ve gelişmiş yönlendirme formları oluşturun",
"billing_freeplan_cta": "Şimdi deneyin",
"billing_manage_details_title": "Fatura bilgilerinizi görüntüleyin ve yönetin",
"billing_manage_details_description": "Fatura bilgilerinizi görüntüleyin ve düzenleyin, ayrıca aboneliğinizi iptal edin.",
"billing_portal": "Faturalandırma portalı",
"billing_help_title": "Başka bir şeye ihtiyacınız var mı?",
"billing_help_description": "Faturalandırma konusunda daha fazla yardıma ihtiyacınız olursa destek ekibimiz size yardımcı olmaya hazır.",
"billing_help_cta": "Destek ekibimize ulaşın",
"ignore_special_characters": "Ek Giriş Etiketinizdeki özel karakterleri yok sayın. Yalnızca harf ve sayı kullanın",
"retry": "Yeniden dene",
"fetching_calendars_error": "Takvimleriniz alınırken bir sorun oluştu. Lütfen <1>tekrar deneyin</1> veya müşteri desteği ile iletişme geçin.",
"calendar_connection_fail": "Takvim bağlantısı yapılamadı",
"booking_confirmation_success": "Rezervasyon onayı başarılı",
"booking_confirmation_fail": "Rezervasyon onayı yapılamadı",
"we_wont_show_again": "Bunu tekrar göstermeyeceğiz",
"couldnt_update_timezone": "Saat dilimini güncelleyemiyoruz",
"updated_timezone_to": "Saat dilimi {{formattedCurrentTz}} olarak güncellendi",
"update_timezone": "Saat dilimini güncelle",
"update_timezone_question": "Saat Dilimi güncellensin mi?",
"update_timezone_description": "Görünüşe göre yerel saat diliminiz {{formattedCurrentTz}} olarak değiştirildi. İstenmeyen zamanlarda rezervasyon yapılmasını önlemek için doğru saat dilimini ayarlamak çok önemlidir. Saat dilimini güncellemek istiyor musunuz?",
"dont_update": "Güncelleme",
"require_additional_notes": "Ek notlar isteyin",
"require_additional_notes_description": "Rezervasyon sırasında doldurulacak ek notlar isteyin",
"email_address_action": "belirli bir e-posta adresine e-posta gönder",
"after_event_trigger": "etkinlik sona erdikten sonra",
"how_long_after": "Etkinlik sona erdikten ne kadar sonra?",
"no_available_slots": "Uygun yer yok",
"time_available": "Uygun zaman",
"install_new_calendar_app": "Yeni takvim uygulamasını yükleyin",
"make_phone_number_required": "Rezervasyon etkinliği için telefon numarasını zorunlu hale getirin",
"dont_have_permission": "Bu kaynağa erişim izniniz yok.",
"saml_config": "SAML Yapılandırması",
"saml_description": "Ekip üyelerinin bir Kimlik Sağlayıcıyı kullanarak oturum açmalarına izin verin",
"saml_config_deleted_successfully": "SAML yapılandırması başarıyla silindi",
"saml_config_updated_successfully": "SAML yapılandırması başarıyla güncellendi",
"saml_configuration": "SAML yapılandırması",
"delete_saml_configuration": "SAML yapılandırmasını sil",
"delete_saml_configuration_confirmation_message": "SAML yapılandırmasını silmek istediğinizden emin misiniz? SAML girişini kullanan ekip üyeleriniz artık Cal.com'a erişemeyecek.",
@ -1265,5 +1317,12 @@
"saml_not_configured_yet": "SAML henüz yapılandırılmadı",
"saml_configuration_description": "SAML yapılandırmanızı güncellemek için lütfen Kimlik Sağlayıcınızın gönderdiği SAML meta verilerini aşağıdaki metin kutusuna yapıştırın.",
"saml_configuration_placeholder": "Lütfen Kimlik Sağlayıcınızdan gelen SAML meta verilerini buraya yapıştırın",
"saml_email_required": "SAML Kimlik Sağlayıcınızı bulabilmemiz için lütfen bir e-posta girin"
"saml_email_required": "SAML Kimlik Sağlayıcınızı bulabilmemiz için lütfen bir e-posta girin",
"saml_sp_title": "Hizmet Sağlayıcı Ayrıntıları",
"saml_sp_description": "Kimlik Sağlayıcınız (IdP), SAML uygulama yapılandırmasını tamamlamak için sizden aşağıdaki bilgileri isteyecektir.",
"saml_sp_acs_url": "ACS URL'si",
"saml_sp_entity_id": "SP Kayıt Kimliği",
"saml_sp_acs_url_copied": "ACS URL'si kopyalandı!",
"saml_sp_entity_id_copied": "SP Kayıt Kimliği kopyalandı!",
"saml_btn_configure": "Yapılandır"
}

View File

@ -767,11 +767,16 @@
"no_category_apps_description_calendar": "Thêm một ứng dụng lịch để kiểm tra xung đột nhằm tránh đặt lịch hẹn trùng",
"no_category_apps_description_conferencing": "Thử thêm vào một ứng dụng hội nghị để hợp nhất cuộc gọi video với khách hàng của bạn",
"no_category_apps_description_payment": "Thêm một ứng dụng thanh toán để đơn giản hoá giao dịch giữa bạn và khách hàng",
"no_category_apps_description_analytics": "Thêm một ứng dụng phân tích cho những trang lịch hẹn của bạn",
"no_category_apps_description_automation": "Thêm một ứng dụng tự động hoá để sử dụng",
"no_category_apps_description_other": "Thêm loại ứng dụng khác để làm mọi loại công việc",
"installed_app_calendar_description": "Đặt (các) lịch làm nhiệm vụ kiểm tra xung đột nhằm ngăn tình trạng đặt lịch trùng.",
"installed_app_conferencing_description": "Thêm ứng dụng hội nghị video mà bạn yêu thích để phục vụ cho các cuộc họp",
"installed_app_payment_description": "Cấu hình loại dịch vụ xử lí thanh toán nào sẽ được dùng đến khi thu phí khách hàng.",
"installed_app_analytics_description": "Cấu hình ứng dụng phân tích nào cần dùng cho những trang lịch hẹn",
"installed_app_other_description": "Tất cả những ứng dụng bạn đã cài từ những danh mục khác.",
"installed_app_automation_description": "Cấu hình ứng dụng tự động hoá nên dùng",
"analytics": "Công cụ phân tích",
"empty_installed_apps_headline": "Chưa cài ứng dụng nào",
"empty_installed_apps_description": "Ứng dụng cho phép bạn cải thiện tiến độ công việc và cải thiện đáng kể việc sắp xếp tổ chức.",
"empty_installed_apps_button": "Khám phá App Store",
@ -827,6 +832,7 @@
"redirect_success_booking": "Chuyển hướng khi đặt lịch hẹn ",
"you_are_being_redirected": "Bạn sẽ được chuyển hướng đến {{ url }} sau $t(second, {\"count\": {{seconds}} }).",
"external_redirect_url": "https://example.com/redirect-to-my-success-page",
"redirect_url_description": "Chuyển hướng đến một URL tuỳ chỉnh sau khi đặt lịch hẹn thành công",
"duplicate": "Sao chép",
"offer_seats": "Cung cấp ghế ngồi",
"offer_seats_description": "Cung cấp ghế để đặt lịch. Việc này sẽ tự động vô hiệu hoá đặt lịch của khách & đặt lịch tham gia.",
@ -1157,6 +1163,8 @@
"connect_conference_apps": "Kết nối các ứng dụng hội nghị",
"connect_calendar_apps": "Kết nối các ứng dụng lịch",
"connect_payment_apps": "Kết nối các ứng dụng thanh toán",
"connect_automation_apps": "Kết nối các ứng dụng tự động hoá",
"connect_analytics_apps": "Kết nối các ứng dụng phân tích",
"connect_other_apps": "Kết nối các ứng dụng khác",
"current_step_of_total": "Bước {{currentStep}}/{{maxSteps}}",
"add_variable": "Thêm biến số",
@ -1241,6 +1249,7 @@
"back_to_signin": "Quay lại phần đăng nhập",
"reset_link_sent": "Đã gửi liên kết đặt lại",
"password_reset_email": "Một email đang được gửi tới {{email}} kèm các chỉ dẫn đặt lại mật khẩu.",
"password_reset_leading": "Nếu sau một lúc mà bạn không nhận được email, hãy kiểm tra liệu bạn có nhập địa chỉ email chính xác không, kiểm tra thư mục spam hoặc liên hệ nhờ hỗ trợ nếu vẫn còn gặp sự cố.",
"password_updated": "Mật khẩu đã được cập nhật!",
"pending_payment": "Thanh toán đang chờ xử lí",
"confirmation_page_rainbow": "Yêu cầu token để truy cập sự kiện của bạn, với các token hoặc NFT trên Ethereum, Polygon, v.v.",
@ -1253,10 +1262,26 @@
"seats": "ghế",
"every_app_published": "Mọi ứng dụng được công bố trên Cal.com App Store là dạng mã nguồn mở và được kiểm nghiệm kĩ càng thông qua khâu bình duyệt ngang hàng. Dẫu vậy, Cal.com, Inc. không công nhận hay chứng nhận những ứng dụng này nếu chúng không do Cal.com công bố. Nếu bạn gặp nội dung hay hành vi không đúng mực nào, vui lòng báo cáo vụ việc.",
"report_app": "Báo cáo ứng dụng",
"limit_booking_frequency": "Giới hạn tần suất đặt lịch hẹn",
"limit_booking_frequency_description": "Giới hạn số lần có thể đặt lịch hẹn cho sự kiện này",
"add_limit": "Thêm giới hạn",
"team_name_required": "Cần có tên nhóm",
"show_attendees": "Chia sẻ thông tin người tham gia cho khách biết",
"how_additional_inputs_as_variables": "Cách sử dụng Đầu vào bổ sung làm Tham số",
"format": "Định dạng",
"uppercase_for_letters": "In hoa hết tất cả các chữ cái",
"replace_whitespaces_underscores": "Thay thế khoảng trắng bằng dấu gạch dưới",
"manage_billing": "Quản lí thanh toán",
"manage_billing_description": "Quản lí tất cả những thanh toán",
"billing_freeplan_title": "Bạn hiện đang sử dụng gói MIỄN PHÍ",
"billing_freeplan_description": "Chúng tôi hoạt động tốt hơn theo nhóm. Mở rộng tiến độ công việc của bạn bằng round-robin và các sự kiện tập thể và tạo những biểu mẫu định hướng nâng cao",
"billing_freeplan_cta": "Thử ngay",
"billing_manage_details_title": "Xem và quản lý chi tiết thanh toán của bạn",
"billing_manage_details_description": "Xem và chỉnh sửa chi tiết thanh toán của bạn, cũng như hủy đăng ký gói của bạn.",
"billing_portal": "Cổng thanh toán",
"billing_help_title": "Cần gì nữa không?",
"billing_help_description": "Nếu bạn cần thêm bất kỳ trợ giúp nào về thanh toán, nhóm hỗ trợ của chúng tôi luôn sẵn sàng trợ giúp.",
"billing_help_cta": "Liên hệ với bộ phận hỗ trợ",
"saml_config": "Cấu hình SAML",
"saml_config_deleted_successfully": "Đã xóa cấu hình SAML thành công",
"saml_configuration": "Cấu hình SAML",

View File

@ -1,6 +1,5 @@
import autoAnimate from "@formkit/auto-animate";
import { useAutoAnimate } from "@formkit/auto-animate/react";
import Link from "next/link";
import { useEffect, useRef } from "react";
import { inferQueryOutput } from "@calcom/trpc/react";
import { Switch } from "@calcom/ui/v2";
@ -24,11 +23,7 @@ export default function AppCard({
children?: React.ReactNode;
setAppData: SetAppDataGeneric<typeof eventTypeAppCardZod>;
}) {
const animationRef = useRef(null);
useEffect(() => {
animationRef.current && autoAnimate(animationRef.current);
}, [animationRef]);
const [animationRef] = useAutoAnimate<HTMLDivElement>();
return (
<div ref={animationRef} className="mb-4 mt-2 rounded-md border border-gray-200 p-8 text-sm">

View File

@ -141,7 +141,7 @@ export const FormActionsDropdown = ({ form, children }: { form: RoutingForm; chi
size="icon"
combined
color="secondary"
className={classNames(disabled && " opacity-30")}
className={classNames("radix-state-open:rounded-r-md", disabled && "opacity-30")}
StartIcon={Icon.FiMoreHorizontal}
/>
</DropdownMenuTrigger>

View File

@ -1,6 +1,6 @@
import autoAnimate from "@formkit/auto-animate";
import { useAutoAnimate } from "@formkit/auto-animate/react";
import { App_RoutingForms_Form } from "@prisma/client";
import { useEffect, useRef, useState } from "react";
import { useEffect, useState } from "react";
import { Controller, useFieldArray } from "react-hook-form";
import { UseFormReturn } from "react-hook-form";
import { v4 as uuidv4 } from "uuid";
@ -197,11 +197,7 @@ const FormEdit = ({
name: fieldsNamespace,
});
const animationRef = useRef(null);
useEffect(() => {
animationRef.current && autoAnimate(animationRef.current);
}, [animationRef]);
const [animationRef] = useAutoAnimate<HTMLDivElement>();
const addField = () => {
appendHookFormField({

View File

@ -1,6 +1,6 @@
import autoAnimate from "@formkit/auto-animate";
import { useAutoAnimate } from "@formkit/auto-animate/react";
import { App_RoutingForms_Form } from "@prisma/client";
import React, { useState, useCallback, useRef, useEffect } from "react";
import React, { useState, useCallback } from "react";
import { Query, Config, Builder, Utils as QbUtils } from "react-awesome-query-builder";
// types
import { JsonTree, ImmutableTree, BuilderProps } from "react-awesome-query-builder";
@ -326,11 +326,8 @@ const Routes = ({
return transformRoutes().map((route) => deserializeRoute(route, config));
});
const animationRef = useRef(null);
useEffect(() => {
animationRef.current && autoAnimate(animationRef.current);
}, [animationRef]);
const [animationRef] = useAutoAnimate<HTMLDivElement>();
const mainRoutes = routes.filter((route) => !route.isFallback);
let fallbackRoute = routes.find((route) => route.isFallback);

View File

@ -1,10 +1,11 @@
import Head from "next/head";
import { useRouter } from "next/router";
import { useState, useRef, FormEvent } from "react";
import { useEffect } from "react";
import { Toaster } from "react-hot-toast";
import { v4 as uuidv4 } from "uuid";
import { useIsEmbed } from "@calcom/embed-core/embed-iframe";
import { sdkActionManager, useIsEmbed } from "@calcom/embed-core/embed-iframe";
import CustomBranding from "@calcom/lib/CustomBranding";
import classNames from "@calcom/lib/classNames";
import { useLocale } from "@calcom/lib/hooks/useLocale";
@ -22,10 +23,10 @@ import { processRoute } from "../../lib/processRoute";
import { Response, Route } from "../../types/types";
import { getQueryBuilderConfig } from "../route-builder/[...appPages]";
function RoutingForm({ form, profile }: inferSSRProps<typeof getServerSideProps>) {
function RoutingForm({ form, profile, ...restProps }: inferSSRProps<typeof getServerSideProps>) {
const [customPageMessage, setCustomPageMessage] = useState<Route["action"]["value"]>("");
const formFillerIdRef = useRef(uuidv4());
const isEmbed = useIsEmbed();
const isEmbed = useIsEmbed(restProps.isEmbed);
useTheme(profile.theme);
useExposePlanGlobally(profile.plan);
// TODO: We might want to prevent spam from a single user by having same formFillerId across pageviews
@ -53,6 +54,11 @@ function RoutingForm({ form, profile }: inferSSRProps<typeof getServerSideProps>
decidedActionRef.current = decidedAction;
};
useEffect(() => {
// Custom Page doesn't actually change Route, so fake it so that embed can adjust the scroll to make the content visible
sdkActionManager?.fire("__routeChanged", {});
}, [customPageMessage]);
const responseMutation = trpc.useMutation("viewer.app_routing_forms.public.response", {
onSuccess: () => {
const decidedAction = decidedActionRef.current;
@ -186,8 +192,8 @@ function RoutingForm({ form, profile }: inferSSRProps<typeof getServerSideProps>
) : (
<div className="mx-auto my-0 max-w-3xl md:my-24">
<div className="w-full max-w-4xl ltr:mr-2 rtl:ml-2">
<div className="-mx-4 rounded-sm border border-neutral-200 bg-white p-4 py-6 sm:mx-0 sm:px-8">
<div>{customPageMessage}</div>
<div className="main dark:bg-darkgray-100 sm:dark:border-darkgray-300 -mx-4 rounded-md border border-neutral-200 bg-white p-4 py-6 sm:mx-0 sm:px-8">
<div className="dark:text-white">{customPageMessage}</div>
</div>
</div>
</div>
@ -197,8 +203,8 @@ function RoutingForm({ form, profile }: inferSSRProps<typeof getServerSideProps>
);
}
export default function RoutingLink({ form, profile }: inferSSRProps<typeof getServerSideProps>) {
return <RoutingForm form={form} profile={profile} />;
export default function RoutingLink(props: inferSSRProps<typeof getServerSideProps>) {
return <RoutingForm {...props} />;
}
RoutingLink.isThemeSupported = true;
@ -214,12 +220,13 @@ export const getServerSideProps = async function getServerSideProps(
};
}
const formId = params.appPages[0];
if (!formId || params.appPages.length > 1) {
console.log(params.appPages);
if (!formId || params.appPages.length > 2) {
return {
notFound: true,
};
}
const isEmbed = params.appPages[1] === "embed";
const form = await prisma.app_RoutingForms_Form.findUnique({
where: {
id: formId,
@ -244,6 +251,7 @@ export const getServerSideProps = async function getServerSideProps(
return {
props: {
isEmbed,
profile: {
theme: form.user.theme,
brandColor: form.user.brandColor,

View File

@ -9,7 +9,7 @@
"@calcom/prisma": "*",
"@slack/web-api": "^6.7.2",
"slack-block-builder": "^2.6.0",
"zod": "^3.18.0"
"zod": "^3.19.1"
},
"devDependencies": {
"@calcom/types": "*"

View File

@ -21,7 +21,7 @@
"@stripe/stripe-js": "^1.35.0",
"stripe": "^9.16.0",
"uuid": "^8.3.2",
"zod": "^3.18.0"
"zod": "^3.19.1"
},
"devDependencies": {
"@calcom/types": "*",

View File

@ -106,7 +106,7 @@
</div>
<i class="last-action"> You would see last Booking page action in my place </i>
<div >
<div class="place" style="width:50%;"></div>
<div class="place" style="width:100%;"></div>
<div class="loader" id="cal-booking-loader-">Loading .....</div>
</div>
</div>
@ -415,7 +415,7 @@
"inline",
{
elementOrSelector: "#cal-booking-place-inline-routing-form .place",
calLink: "forms/9a7e8801-2f34-45ae-aa79-51859766a860",
calLink: "forms/948ae412-d995-4865-875a-48302588de03",
config: {
iframeAttrs: {
id: "cal-booking-place-inline-routing-form-iframe"

View File

@ -109,7 +109,7 @@ expect.extend({
};
}
const pathname = u.pathname;
const expectedPathname = expectedUrlDetails.pathname;
const expectedPathname = expectedUrlDetails.pathname + "/embed";
if (expectedPathname && expectedPathname !== pathname) {
return {
pass: false,
@ -150,16 +150,16 @@ expect.extend({
}, 500);
});
//At this point we know that window.initialBodyDisplay would be set as DOM would already have been ready(because linkReady event can only fire after that)
//At this point we know that window.initialBodyVisibility would be set as DOM would already have been ready(because linkReady event can only fire after that)
const {
display: displayBefore,
visibility: visibilityBefore,
background: backgroundBefore,
initialValuesSet,
} = await iframe.evaluate(() => {
return {
// eslint-disable-next-line @typescript-eslint/ban-ts-comment
//@ts-ignore
display: window.initialBodyDisplay,
visibility: window.initialBodyVisibility,
// eslint-disable-next-line @typescript-eslint/ban-ts-comment
//@ts-ignore
background: window.initialBodyBackground,
@ -168,21 +168,18 @@ expect.extend({
initialValuesSet: window.initialValuesSet,
};
});
const isOptimizedPage = u.pathname.includes("forms/");
expect(initialValuesSet).toBe(true);
if (!isOptimizedPage) {
expect(displayBefore).toBe("none");
}
expect(visibilityBefore).toBe("hidden");
expect(backgroundBefore).toBe("transparent");
const { display: displayAfter, background: backgroundAfter } = await iframe.evaluate(() => {
const { visibility: visibilityAfter, background: backgroundAfter } = await iframe.evaluate(() => {
return {
display: document.body.style.display,
visibility: document.body.style.visibility,
background: document.body.style.background,
};
});
expect(displayAfter).not.toBe("none");
expect(visibilityAfter).toBe("visible");
expect(backgroundAfter).toBe("transparent");
if (!iframeReadyEventDetail) {
return {

View File

@ -18,7 +18,7 @@ export const test = base.extend<Fixtures>({
// eslint-disable-next-line @typescript-eslint/ban-ts-comment
//@ts-ignore
window.initialBodyDisplay = document.body.style.display;
window.initialBodyVisibility = document.body.style.visibility;
// eslint-disable-next-line @typescript-eslint/ban-ts-comment
//@ts-ignore

View File

@ -55,7 +55,7 @@ export const getEmbedIframe = async ({ page, pathname }: { page: Page; pathname:
return null;
}
const u = new URL(embedIframe.url());
if (u.pathname === pathname) {
if (u.pathname === pathname + "/embed") {
return embedIframe;
}
return null;

View File

@ -1,8 +1,8 @@
const html = `<div id="wrapper" style="top:calc(50% - 30px); left:calc(50% - 30px)" class="absolute z-highest">
<div class="loader border-brand dark:border-darkmodebrand">
const html = `<div id="wrapper" style="top:50%; left:50%" class="absolute z-highest">
<div style="transform:translate(-50%,-50%)" class="loader border-brand dark:border-darkmodebrand">
<span class="loader-inner bg-brand dark:bg-darkmodebrand"></span>
</div>
<div id="error" class="hidden">
<div id="error" style="transform:translate(-50%,-50%)" class="hidden">
Something went wrong.
</div>
</div>

View File

@ -218,12 +218,8 @@ function getEmbedType() {
}
}
export const useIsEmbed = () => {
// We can't simply return isEmbed() from this method.
// isEmbed() returns different values on server and browser, which messes up the hydration.
// TODO: We can avoid using document.URL and instead use Router.
const router = useRouter();
const [isEmbed, setIsEmbed] = useState<boolean | null>(typeof router.query.embed === "string");
export const useIsEmbed = (embedSsr?: boolean) => {
const [isEmbed, setIsEmbed] = useState(embedSsr);
useEffect(() => {
const namespace = getNamespace();
const _isValidNamespace = isValidNamespace(namespace);
@ -246,7 +242,7 @@ export const useEmbedType = () => {
};
function unhideBody() {
document.body.style.display = "block";
document.body.style.visibility = "visible";
}
// If you add a method here, give type safety to parent manually by adding it to embed.ts. Look for "parentKnowsIframeReady" in it
@ -285,11 +281,7 @@ export const methods = {
return;
}
// No UI change should happen in sight. Let the parent height adjust and in next cycle show it.
// HACK: React components take time to commit their updates of isEmbed=true to DOM, so add this random 500ms delay which seems to work
// TODO: Optimize page by page and then remove this unnecessary delay
setTimeout(() => {
unhideBody();
}, 500);
unhideBody();
sdkActionManager?.fire("linkReady", {});
});
},
@ -372,6 +364,7 @@ function keepParentInformedAboutDimensionChanges() {
}
if (isBrowser) {
log("Embed SDK loaded", { isEmbed: window.isEmbed() });
const url = new URL(document.URL);
embedStore.theme = (window.getEmbedTheme() || "auto") as UiConfig["theme"];
if (url.searchParams.get("prerender") !== "true" && window.isEmbed()) {

View File

@ -181,6 +181,10 @@ export class Cal {
}
const urlInstance = new URL(`${config.origin}/${calLink}`);
if (!urlInstance.pathname.endsWith("embed")) {
// TODO: Make a list of patterns that are embeddable. All except that should be allowed with a warning that "The page isn't optimized for embedding"
urlInstance.pathname = `${urlInstance.pathname}/embed`;
}
urlInstance.searchParams.set("embed", this.namespace);
if (config.debug) {
urlInstance.searchParams.set("debug", "" + config.debug);

View File

@ -13,7 +13,7 @@
"@sendgrid/client": "^7.7.0",
"@sendgrid/mail": "^7.6.2",
"twilio": "^3.80.1",
"zod": "^3.18.0"
"zod": "^3.19.1"
},
"devDependencies": {
"@calcom/tsconfig": "*"

View File

@ -27,7 +27,7 @@
"@prisma/client": "^4.2.1",
"prisma": "^4.2.1",
"ts-node": "^10.9.1",
"zod": "^3.18.0",
"zod": "^3.19.1",
"zod-prisma": "^0.5.4"
},
"main": "index.ts",

View File

@ -12,6 +12,6 @@
"@trpc/react": "^10.0.0-proxy-beta.5",
"@trpc/server": "^10.0.0-proxy-beta.5",
"superjson": "1.9.1",
"zod": "^3.18.0"
"zod": "^3.19.1"
}
}

View File

@ -15,11 +15,8 @@ export const DropdownMenuTrigger = forwardRef<HTMLButtonElement, DropdownMenuTri
{...props}
className={
props.asChild
? classNames(
className,
"radix-state-open:bg-gray-100 radix-state-open:ring-brand-800 radix-state-open:ring-1 focus:ring-brand-800 focus:ring-1 focus-visible:outline-none"
)
: `inline-flex items-center rounded-md bg-transparent px-3 py-2 text-sm font-medium text-gray-700 hover:bg-gray-50 focus:bg-gray-100 focus:outline-none focus:ring-2 focus:ring-neutral-500 focus:ring-offset-1 group-hover:text-black ${className}`
? classNames(className, "radix-state-open:bg-gray-100 rounded-md ring-0")
: `inline-flex items-center rounded-md bg-transparent px-3 py-2 text-sm font-medium text-gray-700 ring-0 hover:bg-gray-50 focus:bg-gray-100 group-hover:text-black ${className}`
}
ref={forwardedRef}
/>

View File

@ -1,18 +1,18 @@
import Head from "next/head";
import React, { createContext, useContext, useState, useEffect } from "react";
import { useLocale } from "@calcom/lib/hooks/useLocale";
type MetaType = {
title: string;
description: string;
backButton?: boolean;
CTA?: React.ReactNode;
};
const initialMeta = {
const initialMeta: MetaType = {
title: "",
description: "",
backButton: false,
CTA: null,
};
const MetaContext = createContext({
@ -41,16 +41,16 @@ export function MetaProvider({ children }: { children: React.ReactNode }) {
* elsewhere (ie. on a Heading, Title, Subtitle, etc.)
* @example <Meta title="Password" description="Manage settings for your account passwords" />
*/
export default function Meta({ title, description, backButton }: MetaType) {
export default function Meta({ title, description, backButton, CTA }: MetaType) {
const { setMeta, meta } = useMeta();
/* @TODO: maybe find a way to have this data on first render to prevent flicker */
useEffect(() => {
if (meta.title !== title || meta.description !== description) {
setMeta({ title, description, backButton });
setMeta({ title, description, backButton, CTA });
}
// eslint-disable-next-line react-hooks/exhaustive-deps
}, [title, description, backButton]);
}, [title, description, backButton, CTA]);
return (
<Head>

View File

@ -383,6 +383,7 @@ function ShellHeader() {
<div className="mb-1 h-6 w-32 animate-pulse rounded-md bg-gray-200" />
)}
</div>
<div className="ml-auto">{meta.CTA}</div>
</div>
</header>
);

View File

@ -1,5 +1,4 @@
import autoAnimate from "@formkit/auto-animate";
import React, { useEffect, useRef } from "react";
import { useAutoAnimate } from "@formkit/auto-animate/react";
import { Props } from "react-select";
import { classNames } from "@calcom/lib";
@ -24,11 +23,8 @@ export const CheckedTeamSelect = ({
onChange: (value: readonly CheckedSelectOption[]) => void;
}) => {
const { t } = useLocale();
const animationRef = useRef(null);
useEffect(() => {
animationRef.current && autoAnimate(animationRef.current);
}, [animationRef]);
const [animationRef] = useAutoAnimate<HTMLUListElement>();
return (
<>

View File

@ -1,5 +1,3 @@
import autoAnimate from "@formkit/auto-animate";
import React, { useEffect, useRef } from "react";
import { components, GroupBase, Props, ValueContainerProps } from "react-select";
import { Icon } from "@calcom/ui/Icon";
@ -37,11 +35,6 @@ const LimitedChipsContainer = <Option, IsMulti extends boolean, Group extends Gr
export const MultiDropdownSelect = ({ options = [], value = [], ...props }: Props) => {
// const { t } = useLocale();
const animationRef = useRef(null);
useEffect(() => {
animationRef.current && autoAnimate(animationRef.current);
}, [animationRef]);
return (
<Select

View File

@ -1,5 +1,5 @@
import autoAnimate from "@formkit/auto-animate";
import { useEffect, useState, useRef } from "react";
import { useAutoAnimate } from "@formkit/auto-animate/react";
import { useEffect, useState } from "react";
import { useLocale } from "@calcom/lib/hooks/useLocale";
import { localStorage } from "@calcom/lib/webstorage";
@ -34,11 +34,7 @@ const tips = [
];
export default function Tips() {
const animationRef = useRef(null);
useEffect(() => {
animationRef.current && autoAnimate(animationRef.current, { duration: 250, easing: "ease-out" });
}, [animationRef]);
const [animationRef] = useAutoAnimate<HTMLDivElement>();
const { t } = useLocale();

View File

@ -25845,16 +25845,11 @@ zod-prisma@^0.5.4:
parenthesis "^3.1.8"
ts-morph "^13.0.2"
zod@^3.17.3:
zod@^3.17.3, zod@^3.19.1:
version "3.19.1"
resolved "https://registry.yarnpkg.com/zod/-/zod-3.19.1.tgz#112f074a97b50bfc4772d4ad1576814bd8ac4473"
integrity sha512-LYjZsEDhCdYET9ikFu6dVPGp2YH9DegXjdJToSzD9rO6fy4qiRYFoyEYwps88OseJlPyl2NOe2iJuhEhL7IpEA==
zod@^3.18.0:
version "3.18.0"
resolved "https://registry.yarnpkg.com/zod/-/zod-3.18.0.tgz#2eed58b3cafb8d9a67aa2fee69279702f584f3bc"
integrity sha512-gwTm8RfUCe8l9rDwN5r2A17DkAa8Ez4Yl4yXqc5VqeGaXaJahzYYXbTwvhroZi0SNBqTwh/bKm2N0mpCzuw4bA==
zustand@^4.0.0:
version "4.1.1"
resolved "https://registry.yarnpkg.com/zustand/-/zustand-4.1.1.tgz#5a61cc755a002df5f041840a414ae6e9a99ee22b"